codecombat/app/lib/world/docs.coffee

798 lines
22 KiB
CoffeeScript
Raw Normal View History

2014-01-03 13:32:13 -05:00
{me} = require('lib/auth')
# If we use marked somewhere else, we'll have to make sure to preserve options
marked.setOptions {gfm: true, sanitize: false, smartLists: true, breaks: true}
markedWithImages = (s) ->
s = s.replace /!\[(.*?)\]\((.+)? (\d+) (\d+) ?(.*?)?\)/g, '<img src="/images/docs/$2" alt="$1" title="$1" style="width: $3px; height: $4px;" class="$5"></img>' # setting width/height attrs doesn't prevent flickering, but inline css does
marked(s)
module.exports.getDocsFor = (thang, props, isSnippet=false) ->
docs = []
types = {}
for prop in props ? []
type = if isSnippet then 'snippet' else typeof thang[prop]
(types[type] ?= []).push prop
order = ["function", "object", "string", "number", "boolean", "undefined", "snippet"]
order.push type for type of types when not (type in order)
for type in order
for prop in (types[type] ? [])
docClass = if D.hasOwnProperty prop then D[prop] else Doc
docs.push new docClass(thang, prop, type)
docs
module.exports.hasLevelDocs = (levelID) ->
D[levelID]?
module.exports.getLevelDocs = (levelID, world) ->
levelDocsClass = D[levelID] ? Level
new levelDocsClass world
module.exports.Doc = class Doc
writable: false
owner: "this"
constructor: (@thang, @prop, @type) ->
if @owner isnt "this"
@type = typeof window[@owner][@prop]
@buildShortName()
buildShortName: ->
@shortName = @prop
if @type is 'function' then @shortName += "()"
if @type isnt 'snippet' then @shortName = "#{@owner}.#{@shortName}"
@shorterName = @shortName.replace 'this.', ''
@shortName += ';'
title: ->
typeStr = @type
if @type is 'function' and @owner is 'this'
typeStr = 'method'
if @type isnt 'function' and not @writable
typeStr += ' (read-only)'
nameStr = @shortName
"""<h4>#{nameStr} - <code class=\"prop-type\">#{typeStr}</code></h4>"""
html: ->
s = markedWithImages(@doc())
if @type in ['function', 'snippet']
exampleCode = @example()
if exampleCode.split('\n').length > 1
exampleCode = "```\n#{exampleCode}```"
else
exampleCode = "`#{exampleCode}`"
s += marked("**Example**:\n#{exampleCode}")
args = @args?() or []
if args.length
s += marked("**Arguments**:")
s += (arg.html() for arg in args).join('')
else
s += @value()
s
value: ->
"""
<strong>Current value</strong>:
<code class=\"current-value\" data-prop=\"#{@prop}\">#{@formatValue()}</code>
"""
doc: ->
"""
This does something. I think.
"""
example: ->
s = "#{@owner}.#{@shortName};"
if @type is 'function'
exampleArgs = (arg.example ? arg.default ? arg.name for arg in @args?() ? []).join ', '
s = "#{@owner}.#{@prop}(#{exampleArgs});"
s
formatValue: ->
if @owner is 'this'
v = @thang[@prop]
else
v = window[@owner][@prop]
if @type is 'number' and not isNaN v
if v == Math.round v
return v
return v.toFixed 2
if _.isString v
return "\"#{v}\""
v
module.exports.Arg = class Arg
constructor: (@name, @type, @example, @description, @default) ->
html: ->
s = "`#{@name}`: `#{@type}`"
if @example then s += " (ex: `#{@example}`)"
if @description then s += "\n#{@description}"
if @default? then s += "\n*Default value*: `#{@default}`"
marked s
module.exports.Level = class Level
constructor: (@world) ->
docID: (docName="doc") ->
"level-doc-#{docName}"
html: (docName="doc", title, buttons) ->
forVictory = docName is "victory"
docID = @docID docName
title ?= @world.name + (if forVictory then " Complete" else "")
docHTML = if @[docName] then markedWithImages(@[docName]()) else "No #{docName}. Wail!"
if forVictory
buttonHTML = @victoryButtons()
else
buttonHTML = @buttons(buttons ? ["I'm Ready."])
"""<div id="#{docID}" class="level-doc modal hide fade" tabindex="-1">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h3>#{title}</h3>
</div>
<div class="modal-body">
#{docHTML}
</div>
<div class="modal-footer">
#{buttonHTML}
</div>
</div>
"""
doc: ->
"""
Venusaur? Oh no, counterattack with whatever you feel like, man!
"""
victory: ->
"""
Detect it, it it no going and you tell me do things I done runnin'.
"""
buttons: (names) ->
buttonHTML = []
for name in names
buttonHTML.push(
"""
<button class="btn btn-primary" data-dismiss="modal">#{name}</button>
"""
)
buttonHTML.join ' '
victoryButtons: ->
buttons = """
<button id="victory-stay" data-dismiss="modal" class="btn">Stay A While</button>
<a href="/" class="btn">Go Home</a>
"""
if me.get 'anonymous'
buttons += """
<button class="btn btn-success sign-up-button">Sign Up for Updates</button>
"""
if @world.nextLevel
buttons += """
<a href="/play/level/#{@world.nextLevel}" class="btn btn-primary" data-dismiss="modal" >Next Level</a>
"""
buttons += """
<div class="share-buttons">
<div class="g-plusone" data-href="http://codecombat.com"></div>
<div class="fb-like" data-href="http://codecombat.com" data-send="false" data-layout="button_count" data-width="350" data-show-faces="true"></div>
</div>
"""
buttons
D = {} # save typing for all these things
# Markdown: http://daringfireball.net/projects/markdown/syntax
# GitHub Flavored Markdown: https://help.github.com/articles/github-flavored-markdown
# Our Markdown parser and compiler: https://github.com/chjj/marked
# I have extended the img syntax to take width and height (preventing flicker).
D.ifElse = class IfElse extends Doc
buildShortName: ->
@shortName = @shorterName = "if/else"
doc: ->
"""
The `if` control statement lets you choose whether to run the following code based on whether the condition evaluates truthily.
You can add an optional `else` clause to run instead when the condition evaluates falsily.
"""
example: ->
"""
if(2 + 2 === 4) {
// Code here
}
else {
// Code here
}
"""
D.forLoop = class ForLoop extends Doc
buildShortName: ->
@shortName = @shorterName = "for-loop"
doc: ->
"""
The `for` loop lets you run code many times. It has four parts:
* initial setup: `var i = 0;` (run at the beginning)
* loop condition: `i < 10;` (code runs while this is true)
* loop iteration: `i += 1` (runs after every iteration)
* main loop code: `console.log("Counted to", i);`
"""
example: ->
"""
for(var i = 0; i < 10; i += 1) {
// Code here
}
"""
D.whileLoop = class WhileLoop extends Doc
buildShortName: ->
@shortName = @shorterName = "while-loop"
doc: ->
"""
The `while` loop lets you run code many times--as long as the condition is true.
"""
example: ->
"""
var i = 10;
while(i < 10) {
// Code here
i -= 1;
}
"""
D.rotateTo = class RotateTo extends Doc
doc: ->
"""
The `rotateTo` method rotates the #{@thang.spriteName}. ![Rotation of 0° points to the right, 90° points up, etc.](rotate.png 160 160)
Use this method to change the direction that the #{@thang.spriteName} shoots.
"""
args: ->
[new Arg "degrees", "number", "180", "Desired rotation in degrees"]
D.shoot = class Shoot extends Doc
doc: ->
"""
Calling `this.shoot();` makes the #{@thang.spriteName} choose the `shoot` action.
It's equivalent to: `this.setAction(this.actions.shoot);`
"""
D.pos = class Pos extends Doc
doc: ->
"""
The `x` (horizontal) and `y` (vertical) coordinates of the #{@thang.spriteName}'s center.
"""
D.rotation = class Rotation extends Doc
doc: ->
"""
The #{@thang.spriteName}'s rotation in radians (`0` to `2 * Math.PI`).
Use the `rotateTo` method to set this value.
"""
D.degrees = class Degrees extends Doc
doc: ->
"""
The #{@thang.spriteName}'s rotation in degrees (`0` to `360`).
Use the `rotateTo` method to set this value.
"""
D.attackRange = class AttackRange extends Doc
doc: ->
"""
How far the #{@thang.spriteName}'s attack reaches, in meters.
"""
D.health = class Health extends Doc
doc: ->
"""
How many health points the #{@thang.spriteName} has left.
"""
D.team = class Team extends Doc
doc: ->
"""
What team the #{@thang.spriteName} is on.
"""
D.actions = class Actions extends Doc
doc: ->
"""
The #{@thang.spriteName}'s available actions.
To, say, move, use: `this.setAction("move");`
"""
formatValue: () ->
v = @thang[@prop]
'[' + ("\"#{actionName}\"" for actionName of v).join(', ') + ']'
D.action = class Action extends Doc
doc: ->
"""
The current action the #{@thang.spriteName} is running.
To change this, use the `setAction` method.
"""
D.setAction = class SetAction extends Doc
doc: ->
"""
Sets the action that the #{@thang.spriteName} is running. Only actions in `this.actions` are valid.
"""
#For example, if `this.action` is currently `"idle"` and you want it to move instead, you'd set a target location, then: `this.setAction("move");`
args: ->
exampleAction = "idle"
for action in ["move", "shoot", "attack"]
if @thang.actions[action]
exampleAction = action
break
unless exampleAction
exampleAction = _.some @thang.actions, "name"
exampleAction = "\"#{exampleAction}\""
[new Arg "action", "object", exampleAction, "The action to perform (must be one of `this.actions`)."]
D.target = class Target extends Doc
doc: ->
"""
The current target upon which the #{@thang.spriteName} is running its `action`.
To change this, use the `setTarget` method.
"""
formatValue: () ->
v = @thang[@prop]
if v? then v.toString() else 'null'
D.setTarget = class SetTarget extends Doc
doc: ->
"""
Sets what the #{@thang.spriteName} is targeting with its current `action`, such as an enemy to attack or a position to move to.
"""
#For example, if `this.action` is currently `this.actions.move`, the #{@thang.spriteName} will move toward its `target`; if its action is `this.actions.attack`, it will try to attack its target.
#For some actions, you can also pass `x` and `y` coordinates as a target instead of another unit: `this.setTarget(65, 40);`
args: ->
exampleTarget = "65, 40"
for [methods, code] in [[["getNearestEnemy"], "this.getNearestEnemy()"],
[["pos", "move"], "this.pos.x + 20, this.pos.y - 20"],
[["attack"], "enemy"],
[["shoot"], "enemy"]]
if (m for m in methods when m in @thang.programmableProperties).length
exampleTarget = code
break
[new Arg "target", "object", exampleTarget, "The new target upon which to act."]
D.chooseAction = class ChooseAction extends Doc
doc: ->
"""
The `chooseAction` method is run every frame, allowing the #{@thang.spriteName} a chance to look at the world and decide what action to pursue this frame.
To see available actions, hover over the `actions` property below. To choose an action, say `attack`, you can write: `this.setAction(this.actions.attack);`
"""
D.plan = class Plan extends Doc
doc: ->
"""
The `plan` method (spell) is where you write a sequence of methods (spells) to command the #{@thang.spriteName}.
Type your methods into the Spell Editor below. Hover over your available methods at the bottom to see what they do.
"""
example: ->
"""
this.moveRight();
this.moveRight();
this.attackNearbyEnemy();
"""
D.attack = class Attack extends Doc
doc: ->
"""
The `attack` method takes an enemy `target`, sets the #{@thang.spriteName}'s `target` to that `target` with the `setTarget` method, and sets the #{@thang.spriteName}'s `action` to the `this.actions.attack` action with the `setAction` method.
"""
args: ->
exampleTarget = "enemy"
for [methods, code] in [[["getNearestEnemy"], "this.getNearestEnemy()"],
[["getEnemies"], "this.getEnemies()[0]"]]
if (m for m in methods when m in @thang.programmableProperties).length
exampleTarget = code
break
[new Arg "target", "object", exampleTarget, "The target enemy to attack."]
D.moveXY = class MoveXY extends Doc
doc: ->
"""
The `moveXY` method sets the #{@thang.spriteName}'s `targetPos` to the given `(x, y)` coordinates and also sets the #{@thang.spriteName}'s `action` to `move`.
"""
args: ->
[
new Arg "x", "number", "24", "The x coordinate toward which to move."
new Arg "y", "number", "35", "The y coordinate toward which to move."
]
D.distanceTo = class DistanceTo extends Doc
doc: ->
"""
Returns the distance in meters to the `target` unit from the center of the #{@thang.spriteName}.
"""
args: ->
exampleTarget = "enemy"
for [methods, code] in [#[["getNearestEnemy"], "this.getNearestEnemy()"],
[["getEnemies"], "this.getEnemies()[0]"],
[["getFriends"], "this.getFriends()[0]"]]
if (m for m in methods when m in @thang.programmableProperties).length
exampleTarget = code
break
[new Arg "target", "object", exampleTarget, "The target unit whose distance you want to measure."]
D.getEnemies = class GetEnemies extends Doc
doc: ->
"""
Returns an array of all living enemies within eyesight.
"""
example: ->
"""
var enemies = this.getEnemies();
for(var i = 0; i < enemies.length; ++i) {
var enemy = enemies[i];
// Do something with each enemy here
this.attack(enemy); // Example
}
"""
D.getFriends = class GetFriends extends Doc
doc: ->
"""
Returns an array of all living friends within eyesight.
"""
example: ->
"""
var friends = this.getFriends();
for(var i = 0; i < friends.length; ++i) {
var friend = friends[i];
// Do something with each friend here
this.follow(friend); // Example
}
"""
D.getItems = class GetItems extends Doc
doc: ->
"""
Returns an array of all living items within eyesight.
"""
example: ->
"""
var items = this.getItems()
for(var i = 0; i < items.length; ++i) {
var item = items[i];
// Do something with each item here
this.move(item.pos); // Example
}
"""
D.attackNearbyEnemy = class GetNearbyEnemy extends Doc
doc: ->
"""
Attacks any enemy within #{@thang.attackNearbyEnemyRange ? 5} meters of the #{@thang.spriteName}.
"""
D.getNearestEnemy = class GetNearestEnemy extends Doc
doc: ->
"""
Returns the closest living enemy, or `null` if there aren\'t any.
"""
example: ->
"""
var enemy = this.getNearestEnemy();
"""
D.getNearestFriend = class GetNearestFriend extends Doc
doc: ->
"""
Returns the closest living friend, or `null` if there aren\'t any.
"""
example: ->
"""
var friend = this.getNearestFriend();
"""
D.getNearestCombtaant = class GetNearestCombatant extends Doc
doc: ->
"""
Returns the closest living friend or foe, or `null` if there aren\'t any.
"""
example: ->
"""
var enemy = this.getNearestCombatant();
"""
D.attackXY = class AttackXY extends Doc
doc: ->
"""
The `attackXY` method makes the #{@thang.spriteName} attack the ground at the given `(x, y)` coordinates.
"""
args: ->
[
new Arg "x", "number", "24", "The x coordinate to attack."
new Arg "y", "number", "35", "The y coordinate to attack."
]
D.patrol = class Patrol extends Doc
doc: ->
"""
The `patrol` method causes the #{@thang.spriteName} to move between the given waypoints in a loop. When combined with code to `attack` nearby enemies, you can use it to guard an area.
"""
args: ->
[new Arg "patrolPoints", "array", "[{x: 15, y: 45}, {x: 30, y: 40}, {x: 25, y: 35}]", "An array of positions to move between."]
D.attackNearestEnemy = class AttackNearestEnemy extends Doc
doc: ->
"""
The `attackNearestEnemy` method causes the #{@thang.spriteName} to charge at the nearest enemy and try to slay it.
"""
D.moveRight = class MoveRight extends Doc
doc: ->
"""
Moves the #{@thang.spriteName} right by #{@thang.simpleMoveDistance} meters.
"""
example: ->
"""
this.moveRight();
"""
D.moveLeft = class MoveLeft extends Doc
doc: ->
"""
Moves the #{@thang.spriteName} left by #{@thang.simpleMoveDistance} meters.
"""
example: ->
"""
this.moveLeft();
"""
D.moveUp = class MoveUp extends Doc
doc: ->
"""
Moves the #{@thang.spriteName} up by #{@thang.simpleMoveDistance} meters.
"""
example: ->
"""
this.moveUp();
"""
D.moveDown = class MoveDown extends Doc
doc: ->
"""
Moves the #{@thang.spriteName} down by #{@thang.simpleMoveDistance} meters.
"""
example: ->
"""
this.moveDown();
"""
D.say = class Say extends Doc
doc: ->
"""
Makes the #{@thang.spriteName} say the given message. Anything within #{@thang.voiceRange ? 20} meters will hear it.
"""
args: ->
[new Arg "message", "string", "\"Follow me!\"", "The message to say."]
D.chaseAndAttack = class ChaseAndAttack extends Doc
doc: ->
"""
Makes the #{@thang.spriteName} attack `target` if in range, otherwise move to `target`.
"""
args: ->
[new Arg "target", "object", "this.getNearestEnemy()", "The unit to chase and attack."]
D.wait = class Wait extends Doc
doc: ->
"""
The `wait()` method makes the #{@thang.spriteName} wait for a moment before continuing to execute the rest of the code.
It currently doesn't work inside nested functions in a `plan()` method.
"""
args: ->
[
new Arg "duration", "number", "0.1", "Number of seconds to wait."
]
D.addRect = class AddRect extends Doc
doc: ->
"""
The `addRect()` method adds a rectangle centered at the given `(x, y)` coordinate with the given `width` and `height`.
"""
args: ->
[
new Arg "x", "number", "30", "x-coordinate of center of the rectangle."
new Arg "y", "number", "12", "y-coordinate of center of the rectangle."
new Arg "width", "number", "4", "width of the rectangle."
new Arg "height", "number", "24", "height of the rectangle."
]
D.removeRectAt = class RemoveRectAt extends Doc
doc: ->
"""
The `removeRectAt()` method removes a previously added rectangle centered at the given `(x, y)` coordinate.
"""
args: ->
[
new Arg "x", "number", "30", "x-coordinate of center of the rectangle to remove."
new Arg "y", "number", "12", "y-coordinate of center of the rectangle to remove."
]
D.getNavGrid = class GetNavGrid extends Doc
doc: ->
"""
The `getNavGrid()` method returns an undocumented data structure CodeCombat uses in its pathfinding system. Sorry--will improve.
Just use its `grid` property, which is a two-dimensional array with one-meter resolution indicating where the walls are:
```
var grid = this.getNavGrid().grid;
var y = 12;
var x = 30;
var occupied = grid[y][x].length > 0;
```
"""
D.spawnedRectangles = class SpawnedRectangles extends Doc
doc: ->
"""
An array of rectangle objects which have been added with the `addRect()` method.
You can get a rectangle's dimensions, like this:
```
for(var i = 0; i < this.spawnedRectangles.length; ++i) {
var rect = this.spawnedRectangles[i];
// rect.pos.x, rect.pos.y, rect.width, rect.height
}
```
"""
D.pow = class Pow extends Doc
owner: "Math"
doc: ->
"""
Returns `base` to the `exponent` power, that is, <code>base<sup>exponent</sup></code>.
"""
example: ->
"""
Math.pow(7, 2); // returns 49
"""
args: ->
[
new Arg "base", "number", "7", "The base number."
new Arg "exponent", "number", "2", "The exponent used to raise the `base`."
]
D.sqrt = class Sqrt extends Doc
owner: "Math"
doc: ->
"""
Returns the square root of a non-negative number. Equivalent to `Math.pow(x, 0.5)`.
"""
example: ->
"""
Math.sqrt(49); // returns 7
"""
args: ->
[new Arg "x", "number", "49", ""]
D.sin = class Sin extends Doc
owner: "Math"
doc: ->
"""
Returns the sine of a number (between -1 and 1).
"""
example: ->
"""
Math.sin(Math.PI / 4); // returns 2
"""
args: ->
[new Arg "x", "number", "Math.PI / 2", "A number in radians."]
D.cos = class Cos extends Doc
owner: "Math"
doc: ->
"""
Returns the cosine of a number (between -1 and 1).
"""
example: ->
"""
Math.cos(3 * Math.PI / 4); // returns -2
"""
args: ->
[new Arg "x", "number", "Math.PI / 2", "A number in radians."]
D.tan = class Tan extends Doc
owner: "Math"
doc: ->
"""
Returns the tangent of a number (between -1 and 1).
"""
example: ->
"""
Math.tan(Math.PI / 4); // returns 0.9999999999999999
"""
args: ->
[new Arg "x", "number", "Math.PI / 2", "A number in radians."]
D.atan2 = class Atan2 extends Doc
owner: "Math"
doc: ->
"""
Returns the arctangent of the quotient of its arguments--a numeric value between -π and π representing the counterclockwise angle theta of an (x, y) point and the positive X axis.
"""
args: ->
[
new Arg "y", "number", "90", ""
new Arg "x", "number", "15", ""
]
D.PI = class PI extends Doc
owner: "Math"
doc: ->
"""
The ratio of the circumference of a circle to its diameter, approximately 3.14159.
"""
D.random = class Random extends Doc
owner: "Math"
doc: ->
"""
Returns a random number x such that 0 <= x < 1.
"""