Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-05-19 22:45:20 -07:00
commit 704e44bc66
47 changed files with 1187 additions and 263 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -113,6 +113,7 @@ module.exports = class Angel extends CocoClass
@doWork()
finalizePreload: ->
@say "Finalize preload."
@worker.postMessage func: 'finalizePreload'
infinitelyLooped: =>

View file

@ -54,7 +54,7 @@ module.exports = class God extends CocoClass
@createWorld e.spells, e.preload
createWorld: (spells, preload=false) ->
console.log "#{@nick}: Let there be light upon #{@level.name}!"
console.log "#{@nick}: Let there be light upon #{@level.name}! (preload: #{preload})"
userCodeMap = @getUserCodeMap spells
# We only want one world being simulated, so we abort other angels, unless we had one preloading this very code.
@ -66,6 +66,9 @@ module.exports = class God extends CocoClass
if not hadPreloader and isPreloading
angel.finalizePreload()
hadPreloader = true
else if preload and angel.running and not angel.work.preload
# It's still running for real, so let's not preload.
return
else
angel.abort()
return if hadPreloader

View file

@ -135,6 +135,7 @@ module.exports = class LevelLoader extends CocoClass
onWorldNecessitiesLoaded: =>
@initWorld()
@supermodel.clearMaxProgress()
@trigger 'world-necessities-loaded'
return if @headless and not @editorMode
thangsToLoad = _.uniq( (t.spriteName for t in @world.thangs when t.exists) )
nameModelTuples = ([thangType.get('name'), thangType] for thangType in @thangNames.models)

View file

@ -23,6 +23,7 @@ module.exports = class Simulator extends CocoClass
@cleanupSimulation()
@god?.destroy()
super()
fetchAndSimulateOneGame: (humanGameID, ogresGameID) =>
return if @destroyed
$.ajax
@ -35,7 +36,7 @@ module.exports = class Simulator extends CocoClass
error: (errorData) ->
console.log "There was an error fetching two games! #{JSON.stringify errorData}"
success: (taskData) =>
@trigger 'statusUpdate', 'Setting up simulation...'
#refactor this
@task = new SimulationTask(taskData)
@ -49,7 +50,7 @@ module.exports = class Simulator extends CocoClass
@listenToOnce @supermodel, 'loaded-all', @simulateSingleGame
simulateSingleGame: ->
return if @destroyed
console.log "Commencing simulation!"
@trigger 'statusUpdate', 'Simulating...'
@assignWorldAndLevelFromLevelLoaderAndDestroyIt()
@setupGod()
try
@ -57,6 +58,7 @@ module.exports = class Simulator extends CocoClass
catch err
console.log err
@handleSingleSimulationError()
commenceSingleSimulation: ->
@god.createWorld @generateSpellsObject()
Backbone.Mediator.subscribeOnce 'god:infinite-loop', @handleSingleSimulationInfiniteLoop, @
@ -89,10 +91,22 @@ module.exports = class Simulator extends CocoClass
else if ogreSessionRank < humanSessionRank
console.log "GAMERESULT:ogres"
process.exit(0)
else
@sendSingleGameBackToServer(taskResults)
@cleanupSimulation()
sendSingleGameBackToServer: (results) ->
@trigger 'statusUpdate', 'Simulation completed, sending results back to server!'
$.ajax
url: "/queue/scoring/recordTwoGames"
data: results
type: "PUT"
parse: true
success: @handleTaskResultsTransferSuccess
error: @handleTaskResultsTransferError
complete: @cleanupAndSimulateAnotherTask
fetchAndSimulateTask: =>
@ -120,12 +134,13 @@ module.exports = class Simulator extends CocoClass
console.error "There was a horrible Error: #{JSON.stringify errorData}"
@trigger 'statusUpdate', 'There was an error fetching games to simulate. Retrying in 10 seconds.'
@simulateAnotherTaskAfterDelay()
handleNoGamesResponse: ->
info = 'There were no games to simulate--all simulations are done or in process. Retrying in 10 seconds.'
info = 'Finding game to simulate...'
console.log info
@trigger 'statusUpdate', info
@simulateAnotherTaskAfterDelay()
@fetchAndSimulateOneGame()
application.tracker?.trackEvent 'Simulator Result', label: "No Games"
simulateAnotherTaskAfterDelay: =>

View file

@ -37,12 +37,16 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
possessed: false
flipped: false
flippedCount: 0
originalScaleX: null
originalScaleY: null
actionQueue: null
actions: null
rotation: 0
# Scale numbers
baseScaleX: 1 # scale + flip (for current action) / resolutionFactor.
baseScaleY: 1 # These numbers rarely change, so keep them around.
scaleFactor: 1 # Current scale adjustment. This can change rapidly.
targetScaleFactor: 1 # What the scaleFactor is going toward during a tween.
# ACTION STATE
# Actions have relations. If you say 'move', 'move_side' may play because of a direction
# relationship, and if you say 'cast', 'cast_begin' may happen first, or 'cast_end' after.
@ -71,7 +75,6 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@ranges = []
@handledDisplayEvents = {}
@age = 0
@scaleFactor = @targetScaleFactor = 1
if @thangType.isFullyLoaded()
@setupSprite()
else
@ -94,6 +97,8 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
finishSetup: ->
return unless @thang
@updateBaseScale()
@scaleFactor = @thang.scaleFactor if @thang?.scaleFactor
@update true # Reflect initial scale and other state
setUpRasterImage: ->
@ -102,8 +107,6 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@setImageObject image
$(image.image).one 'load', => @updateScale?()
@configureMouse()
@originalScaleX = image.scaleX
@originalScaleY = image.scaleY
@imageObject.sprite = @
@imageObject.layerPriority = @thangType.get 'layerPriority'
@imageObject.name = @thang?.spriteName or @thangType.get 'name'
@ -112,14 +115,6 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@imageObject.regY = -reg.y
@finishSetup()
destroy: ->
mark.destroy() for name, mark of @marks
label.destroy() for name, label of @labels
p.removeChild @healthBar if p = @healthBar?.parent
@imageObject?.off 'animationend', @playNextAction
clearInterval @effectInterval if @effectInterval
super()
toString: -> "<CocoSprite: #{@thang?.id}>"
buildSpriteSheet: ->
@ -139,17 +134,11 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
sprite = new createjs.Sprite(spriteSheet)
else
sprite = new createjs.Shape()
sprite.scaleX = sprite.scaleY = 1 / @options.resolutionFactor
# temp, until these are re-exported with perspective
if @options.camera and @thangType.get('name') in ['Dungeon Floor', 'Indoor Floor', 'Grass', 'Goal Trigger', 'Obstacle']
sprite.scaleY *= @options.camera.y2x
@setImageObject sprite
@addHealthBar()
@configureMouse()
# TODO: generalize this later?
@originalScaleX = sprite.scaleX
@originalScaleY = sprite.scaleY
@imageObject.sprite = @
@imageObject.layerPriority = @thangType.get 'layerPriority'
@imageObject.name = @thang?.spriteName or @thangType.get 'name'
@ -183,6 +172,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@currentAction = action
return @hide() unless action.animation or action.container or action.relatedActions
@show()
@updateBaseScale()
return @updateActionDirection() unless action.animation or action.container
m = if action.container then "gotoAndStop" else "gotoAndPlay"
@imageObject.framerate = action.framerate or 20
@ -307,6 +297,18 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
[@imageObject.x, @imageObject.y] = [sup.x, sup.y]
@lastPos = p1.copy?() or _.clone(p1)
@hasMoved = true
updateBaseScale: ->
scale = 1
scale = @thangType.get('scale') or 1 if @isRaster
scale /= @options.resolutionFactor unless @isRaster
@baseScaleX = @baseScaleY = scale
@baseScaleX *= -1 if @getActionProp 'flipX'
@baseScaleY *= -1 if @getActionProp 'flipY'
# temp, until these are re-exported with perspective
floors = ['Dungeon Floor', 'Indoor Floor', 'Grass', 'Goal Trigger', 'Obstacle']
if @options.camera and @thangType.get('name') in floors
@baseScaleY *= @options.camera.y2x
updateScale: ->
return unless @imageObject
@ -316,18 +318,17 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
return unless bounds
@imageObject.scaleX = @thang.width * Camera.PPM / bounds.width
@imageObject.scaleY = @thang.height * Camera.PPM * @options.camera.y2x / bounds.height
@imageObject.regX ?= 0
@imageObject.regX += bounds.width / 2
@imageObject.regY ?= 0
@imageObject.regY += bounds.height / 2
@imageObject.regX = bounds.width / 2
@imageObject.regY = bounds.height / 2
unless @thang.spriteName is 'Beam'
@imageObject.scaleX *= @thangType.get('scale') ? 1
@imageObject.scaleY *= @thangType.get('scale') ? 1
[@lastThangWidth, @lastThangHeight] = [@thang.width, @thang.height]
return
scaleX = if @getActionProp 'flipX' then -1 else 1
scaleY = if @getActionProp 'flipY' then -1 else 1
scaleX = scaleY = 1
if @thangType.get('name') in ['Arrow', 'Spear']
# Scales the arrow so it appears longer when flying parallel to horizon.
# To do that, we convert angle to [0, 90] (mirroring half-planes twice), then make linear function out of it:
@ -342,18 +343,12 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
angle = 180 - angle if angle > 90
scaleX = 0.5 + 0.5 * (90 - angle) / 90
if @isRaster # scale is worked into building the sprite sheet for animations
scale = @thangType.get('scale') or 1
scaleX *= scale
scaleY *= scale
# console.error "No thang for", @ unless @thang
# TODO: support using scaleFactorX/Y from the thang object
@imageObject.scaleX = @baseScaleX * @scaleFactor * scaleX
@imageObject.scaleY = @baseScaleY * @scaleFactor * scaleY
console.error "No thang for", @ unless @thang
scaleFactorX = @thang.scaleFactorX ? @scaleFactor
scaleFactorY = @thang.scaleFactorY ? @scaleFactor
@imageObject.scaleX = @originalScaleX * scaleX * scaleFactorX
@imageObject.scaleY = @originalScaleY * scaleY * scaleFactorY
if (@thang.scaleFactor or 1) isnt @targetScaleFactor
if @thang and (@thang.scaleFactor or 1) isnt @targetScaleFactor
createjs.Tween.removeTweens(@)
createjs.Tween.get(@).to({scaleFactor:@thang.scaleFactor or 1}, 2000, createjs.Ease.elasticOut)
@targetScaleFactor = @thang.scaleFactor or 1
@ -783,3 +778,11 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
return unless @healthBar
@healthBar.x = @imageObject.x
@healthBar.y = @imageObject.y
destroy: ->
mark.destroy() for name, mark of @marks
label.destroy() for name, label of @labels
p.removeChild @healthBar if p = @healthBar?.parent
@imageObject?.off 'animationend', @playNextAction
clearInterval @effectInterval if @effectInterval
super()

View file

@ -201,7 +201,8 @@ module.exports = class Mark extends CocoClass
Backbone.Mediator.publish 'sprite:loaded'
update: (pos=null) ->
return false unless @on and @mark and @sprite?.thangType.isFullyLoaded()
return false unless @on and @mark
return false if @sprite? and not @sprite.thangType.isFullyLoaded()
@mark.visible = not @hidden
@updatePosition pos
@updateRotation()
@ -240,20 +241,20 @@ module.exports = class Mark extends CocoClass
oldMark.parent.addChild @mark
oldMark.parent.swapChildren oldMark, @mark
oldMark.parent.removeChild oldMark
if @markSprite?
@markSprite.updateScale()
return unless @name in ["selection", "target", "repair", "highlight"]
scale = 0.5
if @sprite?.imageObject
size = @sprite.getAverageDimension()
size += 60 if @name is 'selection'
size += 60 if @name is 'repair'
size *= @sprite.scaleFactor
scale = size / {selection: 128, target: 128, repair: 320, highlight: 160}[@name]
scale /= 3
if @sprite?.thang.spriteName.search(/(dungeon|indoor).wall/i) isnt -1
scale *= 2
@mark.scaleX = @mark.scaleY = Math.min 1, scale
if @markSprite?
@mark.scaleX *= @markSprite.originalScaleX
@mark.scaleY *= @markSprite.originalScaleY
@mark.scaleX = @mark.scaleY = Math.min 1, scale
if @name in ['selection', 'target', 'repair']
@mark.scaleY *= @camera.y2x # code applies perspective

View file

@ -15,7 +15,7 @@ module.exports = class GoalManager extends CocoClass
nextGoalID: 0
constructor: (@world, @initialGoals) ->
constructor: (@world, @initialGoals, @team) ->
super()
@init()
@ -78,8 +78,8 @@ module.exports = class GoalManager extends CocoClass
# main instance gets them and updates their existing goal states,
# passes the word along
onNewWorldCreated: (e) ->
@updateGoalStates(e.goalStates) if e.goalStates?
@world = e.world
@updateGoalStates(e.goalStates) if e.goalStates?
updateGoalStates: (newGoalStates) ->
for goalID, goalState of newGoalStates
@ -104,12 +104,19 @@ module.exports = class GoalManager extends CocoClass
notifyGoalChanges: ->
overallStatus = @checkOverallStatus()
event = {goalStates: @goalStates, goals: @goals, overallStatus: overallStatus}
event =
goalStates: @goalStates
goals: @goals
overallStatus: overallStatus
timedOut: @world.totalFrames is @world.maxTotalFrames
console.log 'timed out', @world.totalFrames is @world.maxTotalFrames, @world.totalFrames, @world.maxTotalFrames, @world.frames.length
Backbone.Mediator.publish('goal-manager:new-goal-states', event)
checkOverallStatus: (ignoreIncomplete=false) ->
overallStatus = null
statuses = if @goalStates then (val.status for key, val of @goalStates) else []
goals = if @goalStates then _.values @goalStates else []
goals = (g for g in goals when g.team in [undefined, @team]) if @team
statuses = if @goalStates then (goal.status for goal in goals) else []
overallStatus = 'success' if statuses.length > 0 and _.every(statuses, (s) -> s is 'success' or (ignoreIncomplete and s is null))
overallStatus = 'failure' if statuses.length > 0 and 'failure' in statuses
overallStatus

View file

@ -222,6 +222,10 @@
multiplayer: "Multiplayer"
restart: "Restart"
goals: "Goals"
success: "Success!"
incomplete: "Incomplete"
timed_out: "Ran out of time"
failing: "Failing"
action_timeline: "Action Timeline"
click_to_select: "Click on a unit to select it."
reload_title: "Reload All Code?"
@ -656,6 +660,8 @@
rank_submitted: "Submitted for Ranking"
rank_failed: "Failed to Rank"
rank_being_ranked: "Game Being Ranked"
rank_last_submitted: "submitted "
help_simulate: "Help simulate games?"
code_being_simulated: "Your new code is being simulated by other players for ranking. This will refresh as new matches come in."
no_ranked_matches_pre: "No ranked matches for the "
no_ranked_matches_post: " team! Play against some competitors and then come back here to get your game ranked."
@ -675,6 +681,31 @@
fight: "Fight!"
watch_victory: "Watch your victory"
defeat_the: "Defeat the"
tournament_ends: "Tournament ends"
tournament_rules: "Tournament Rules"
tournament_blurb: "Write code, collect gold, build armies, crush foes, win prizes, and upgrade your career in our $40,000 Greed tournament! Check out the details"
tournament_blurb_blog: "on our blog"
ladder_prizes:
title: "Tournament Prizes"
blurb_1: "These prizes will be awarded according to"
blurb_2: "the tournament rules"
blurb_3: "to the top human and ogre players."
blurb_4: "Two teams means double the prizes!"
blurb_5: "(There will be two first place winners, two second-place winners, etc.)"
rank: "Rank"
prizes: "Prizes"
total_value: "Total Value"
in_cash: "in cash"
custom_wizard: "Custom CodeCombat Wizard"
custom_avatar: "Custom CodeCombat avatar"
heap: "for six months of \"Startup\" access"
credits: "credits"
one_month_coupon: "coupon: choose either Rails or HTML"
one_month_discount: "discount, 30% off: choose either Rails or HTML"
license: "license"
oreilly: "ebook of your choice"
multiplayer_launch:
introducing_dungeon_arena: "Introducing Dungeon Arena"

View file

@ -16,7 +16,7 @@ class CocoModel extends Backbone.Model
console.error("#{@} needs a className set.")
@markToRevert()
@addSchemaDefaults()
@once 'sync', @onLoaded, @
@on 'sync', @onLoaded, @
@on 'error', @onError, @
@saveBackup = _.debounce(@saveBackup, 500)

View file

@ -30,7 +30,7 @@ module.exports = class ThangType extends CocoModel
isFullyLoaded: ->
# TODO: Come up with a better way to identify when the model doesn't have everything needed to build the sprite. ie when it's a projection without all the required data.
return @get('actions') or @get('raster') # needs one of these two things
getActions: ->
return {} unless @isFullyLoaded()
return @actions or @buildActions()

View file

@ -1,3 +1,14 @@
.ladder-submission-view
h3
text-decoration: underline
button
text-shadow: 0px -1px 0px black
.last-submitted, .help-simulate
font-size: 14px
font-weight: normal
.last-submitted
float: left
.help-simulate
float: right

View file

@ -1,6 +1,47 @@
#ladder-view
.main-content-area
background-color: whitesmoke
#level-column img
margin: -14px -12px 0px -12px
width: 100%
width: -webkit-calc(100% + 24px)
width: calc(100% + 24px)
h1
text-align: center
.tournament-blurb
margin: -20px -12px 20px -12px
padding: 10px
background-color: white
h2
text-align: center
a
font-weight: bold
.sponsor-logos
padding: 10px 15px 10px 15px
-webkit-filter: grayscale(100%)
-webkit-transition: .5s ease-in-out
-moz-filter: grayscale(100%)
-moz-transition: .5s ease-in-out
-o-filter: grayscale(100%)
-o-transition: .5s ease-in-out
filter: grayscale(100%)
transition: .5s ease-in-out
&:hover
-webkit-filter: grayscale(0%)
-moz-filter: grayscale(0%)
-o-filter: grayscale(0%)
filter: grayscale(0%)
img
margin: 0px 15px
.tab-pane
margin-top: 10px
@ -63,18 +104,38 @@
td
padding: 1px 2px
tr.stale
opacity: 0.5
tr.win .state-cell
color: #172
tr.loss .state-cell
color: #712
#must-log-in button
margin-right: 10px
#prize_table
width: 960px
font-weight: bold
thead
font-size: 24px
tbody
tr:not(:first-child)
border-top: 10px solid #ddd
td
vertical-align: middle
&:nth-child(1), &:nth-child(3)
text-align: center
font-size: 24px
li
list-style: none
&:not(:last-child)
margin-bottom: 10px
border-bottom: 1px solid #ddd
img
margin-right: 10px
@media only screen and (max-width: 800px)
#ladder-view
#level-column img
width: 100%
width: 100%

View file

@ -26,4 +26,13 @@
fill: #555555
shape-rendering: crispEdges
tr.fresh
background-color: #39F
tr.stale
opacity: 0.5
tr.win .state-cell
color: #172
tr.loss .state-cell
color: #712

View file

@ -5,24 +5,26 @@
left: 10px
top: -100px
@include transition(top 0.5s ease-in-out)
background-color: rgba(200,200,200,0.8)
&.brighter
background-color: rgba(200,200,200,1.0)
background-color: rgba(200,200,200,1.0)
border: black
padding: 15px 7px 5px 5px
padding: 15px 7px 2px 5px
box-sizing: border-box
border: 1px solid #333
border-radius: 5px
h3
.goals-status
font-size: 14px
margin: 0
color: black
i
margin-right: 5px
.success
color: darkgreen
.timed-out
color: darkslategray
.failure
color: darkred
.incomplete
color: darkgoldenrod
ul
padding-left: 0

View file

@ -38,7 +38,7 @@ block content
input#email.form-control(name="email", type="text", value="#{me.get('email')}")
if !isProduction
.form-group.checkbox
label(for="email", data-i18n="account_settings.admin") Admin
label(for="admin", data-i18n="account_settings.admin") Admin
input#admin(name="admin", type="checkbox", checked=me.get('permissions').indexOf('admin') != -1)

View file

@ -1,7 +1,16 @@
button.btn.btn-success.rank-button
span(data-i18n="ladder.rank_no_code").unavailable.hidden No New Code to Rank
span(data-i18n="ladder.rank_my_game").rank.hidden Rank My Game!
span(data-i18n="ladder.rank_submitting").submitting.hidden Submitting...
span(data-i18n="ladder.rank_submitted").submitted.hidden Submitted for Ranking
span(data-i18n="ladder.rank_failed").failed.hidden Failed to Rank
span(data-i18n="ladder.rank_being_ranked").ranking.hidden Game Being Ranked
button.btn.btn-lg.btn-block.btn-success.rank-button
span(data-i18n="ladder.rank_no_code").unavailable.secret No New Code to Rank
span(data-i18n="ladder.rank_my_game").rank.secret Rank My Game!
span(data-i18n="ladder.rank_submitting").submitting.secret Submitting...
span(data-i18n="ladder.rank_submitted").submitted.secret Submitted for Ranking
span(data-i18n="ladder.rank_failed").failed.secret Failed to Rank
span(data-i18n="ladder.rank_being_ranked").ranking.secret Game Being Ranked
if lastSubmitted
.last-submitted.secret
span(data-i18n="ladder.rank_last_submitted") submitted
| #{lastSubmitted}
a(href=simulateURL, data-i18n="ladder.help_simulate").help-simulate.secret Help simulate games?
.clearfix

View file

@ -1,5 +1,6 @@
extends /templates/base
block content
- var base = "/images/pages/play/ladder/prize_";
div#level-column
if levelDescription
@ -7,6 +8,31 @@ block content
else
h1= level.get('name')
if level.get('name') == 'Greed'
.tournament-blurb
h2
span(data-i18n="ladder.tournament_ends") Tournament ends
| #{tournamentTimeLeft}
p
span(data-i18n="ladder.tournament_blurb") Write code, collect gold, build armies, crush foes, win prizes, and upgrade your career in our $40,000 Greed tournament! Check out the details
|
a(href="http://blog.codecombat.com/multiplayer-programming-tournament", data-i18n="ladder.tournament_blurb_blog") on our blog
| .
.sponsor-logos
a(href="https://heapanalytics.com/")
img(src=base + "heap.png")
a(href="https://www.firebase.com/")
img(src=base + "firebase.png")
a(href="https://onemonthrails.com/")
img(src=base + "one_month.png")
a(href="http://www.jetbrains.com/webstorm/")
img(src=base + "webstorm.png")
a(href="http://shop.oreilly.com/category/ebooks.do")
img(src=base + "oreilly.png")
a(href="http://aws.amazon.com/")
img(src=base + "aws.png")
div#columns.row
div.column.col-md-2
for team in teams
@ -45,8 +71,627 @@ block content
.tab-pane.well#simulate
#simulate-tab-view
.tab-pane.well#prizes
h1(data-i18n="ladder.prizes_remember_to_add_i18n_tags") Tournament Prizes
p(data-i18n="ladder.prizes_but_this_is_just_placeholder") Fill in the Greed prizes list here.
h1(data-i18n="ladder_prizes.title") Tournament Prizes
p
span(data-i18n="ladder_prizes.blurb_1") These prizes will be awarded according to
|
a(href="#rules", data-i18n="ladder_prizes.blurb_2") the tournament rules
|
span(data-i18n="ladder_prizes.blurb_3") to the top human and ogre players.
|
strong(data-i18n="ladder_prizes.blurb_4") Two teams means double the prizes!
|
span(data-i18n="ladder_prizes.blurb_5") (There will be two first place winners, two second-place winners, etc.)
table#prize_table.table
thead
tr
td(data-i18n="ladder_prizes.rank") Rank
td(data-i18n="ladder_prizes.prizes") Prizes
td(data-i18n="ladder_prizes.total_value") Total Value
tbody
tr
td 1st
td
ul.list-unstyled
li
img(src=base + "cash1.png")
span $512
span(data-i18n="ladder_prizes.in_cash") in cash
li
img(src=base + "custom_wizard.png")
span(data-i18n="ladder_prizes.custom_wizard") Custom CodeCombat Wizard
li
img(src=base + "custom_avatar.png")
span(data-i18n="ladder_prizes.custom_avatar") Custom CodeCombat avatar
li
img(src=base + "heap.png")
span
a(href="https://heapanalytics.com/") Heap Analytics
|
span(data-i18n="ladder_prizes.heap") for six months of "Startup" access
| - $354
li
img(src=base + "firebase.png")
span
a(href="https://www.firebase.com/") Firebase
|
span(data-i18n="ladder_prizes.credits") credits
| - $300
li
img(src=base + "one_month.png")
span
a(href="https://onemonthrails.com/") One Month Rails
|
span(data-i18n="ladder_prizes.one_month_coupon") coupon: choose either Rails or HTML
| - $99
li
img(src=base + "webstorm.png")
span
a(href="http://www.jetbrains.com/webstorm/") Webstorm
|
span(data-i18n="ladder_prizes.license") license
| - $49
li
img(src=base + "oreilly.png")
span
a(href="http://shop.oreilly.com/category/ebooks.do") O'Reilly
|
span(data-i18n="ladder_prizes.oreilly") ebook of your choice
| - $40
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $2054
tr
td 2nd
td
ul.list-unstyled
li
img(src=base + "cash2.png")
span $256
span(data-i18n="ladder_prizes.in_cash") in cash
li
img(src=base + "custom_avatar.png")
span(data-i18n="ladder_prizes.custom_avatar") Custom CodeCombat avatar
li
img(src=base + "heap.png")
span
a(href="https://heapanalytics.com/") Heap Analytics
|
span(data-i18n="ladder_prizes.heap") for six months of "Startup" access
| - $354
li
img(src=base + "firebase.png")
span
a(href="https://www.firebase.com/") Firebase
|
span(data-i18n="ladder_prizes.credits") credits
| - $300
li
img(src=base + "one_month.png")
span
a(href="https://onemonthrails.com/") One Month Rails
|
span(data-i18n="ladder_prizes.one_month_discount") discount, 30% off: choose either Rails or HTML
| - $30
li
img(src=base + "webstorm.png")
span
a(href="http://www.jetbrains.com/webstorm/") Webstorm
|
span(data-i18n="ladder_prizes.license") license
| - $49
li
img(src=base + "oreilly.png")
span
a(href="http://shop.oreilly.com/category/ebooks.do") O'Reilly
|
span(data-i18n="ladder_prizes.oreilly") ebook of your choice
| - $40
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $1229
tr
td 3rd
td
ul.list-unstyled
li
img(src=base + "cash2.png")
span $128
span(data-i18n="ladder_prizes.in_cash") in cash
li
img(src=base + "custom_avatar.png")
span(data-i18n="ladder_prizes.custom_avatar") Custom CodeCombat avatar
li
img(src=base + "heap.png")
span
a(href="https://heapanalytics.com/") Heap Analytics
|
span(data-i18n="ladder_prizes.heap") for six months of "Startup" access
| - $354
li
img(src=base + "firebase.png")
span
a(href="https://www.firebase.com/") Firebase
|
span(data-i18n="ladder_prizes.credits") credits
| - $300
li
img(src=base + "one_month.png")
span
a(href="https://onemonthrails.com/") One Month Rails
|
span(data-i18n="ladder_prizes.one_month_discount") discount, 30% off: choose either Rails or HTML
| - $30
li
img(src=base + "webstorm.png")
span
a(href="http://www.jetbrains.com/webstorm/") Webstorm
|
span(data-i18n="ladder_prizes.license") license
| - $49
li
img(src=base + "oreilly.png")
span
a(href="http://shop.oreilly.com/category/ebooks.do") O'Reilly
|
span(data-i18n="ladder_prizes.oreilly") ebook of your choice
| - $40
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $1101
tr
td 4th
td
ul.list-unstyled
li
img(src=base + "cash3.png")
span $64
span(data-i18n="ladder_prizes.in_cash") in cash
li
img(src=base + "heap.png")
span
a(href="https://heapanalytics.com/") Heap Analytics
|
span(data-i18n="ladder_prizes.heap") for six months of "Startup" access
| - $354
li
img(src=base + "firebase.png")
span
a(href="https://www.firebase.com/") Firebase
|
span(data-i18n="ladder_prizes.credits") credits
| - $300
li
img(src=base + "one_month.png")
span
a(href="https://onemonthrails.com/") One Month Rails
|
span(data-i18n="ladder_prizes.one_month_discount") discount, 30% off: choose either Rails or HTML
| - $30
li
img(src=base + "webstorm.png")
span
a(href="http://www.jetbrains.com/webstorm/") Webstorm
|
span(data-i18n="ladder_prizes.license") license
| - $49
li
img(src=base + "oreilly.png")
span
a(href="http://shop.oreilly.com/category/ebooks.do") O'Reilly
|
span(data-i18n="ladder_prizes.oreilly") ebook of your choice
| - $40
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $887
tr
td 5th
td
ul.list-unstyled
li
img(src=base + "cash3.png")
span $32
span(data-i18n="ladder_prizes.in_cash") in cash
li
img(src=base + "heap.png")
span
a(href="https://heapanalytics.com/") Heap Analytics
|
span(data-i18n="ladder_prizes.heap") for six months of "Startup" access
| - $354
li
img(src=base + "firebase.png")
span
a(href="https://www.firebase.com/") Firebase
|
span(data-i18n="ladder_prizes.credits") credits
| - $300
li
img(src=base + "one_month.png")
span
a(href="https://onemonthrails.com/") One Month Rails
|
span(data-i18n="ladder_prizes.one_month_discount") discount, 30% off: choose either Rails or HTML
| - $30
li
img(src=base + "webstorm.png")
span
a(href="http://www.jetbrains.com/webstorm/") Webstorm
|
span(data-i18n="ladder_prizes.license") license
| - $49
li
img(src=base + "oreilly.png")
span
a(href="http://shop.oreilly.com/category/ebooks.do") O'Reilly
|
span(data-i18n="ladder_prizes.oreilly") ebook of your choice
| - $40
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $855
tr
td 6th
td
ul.list-unstyled
li
img(src=base + "cash3.png")
span $16
span(data-i18n="ladder_prizes.in_cash") in cash
li
img(src=base + "firebase.png")
span
a(href="https://www.firebase.com/") Firebase
|
span(data-i18n="ladder_prizes.credits") credits
| - $300
li
img(src=base + "one_month.png")
span
a(href="https://onemonthrails.com/") One Month Rails
|
span(data-i18n="ladder_prizes.one_month_discount") discount, 30% off: choose either Rails or HTML
| - $30
li
img(src=base + "webstorm.png")
span
a(href="http://www.jetbrains.com/webstorm/") Webstorm
|
span(data-i18n="ladder_prizes.license") license
| - $49
li
img(src=base + "oreilly.png")
span
a(href="http://shop.oreilly.com/category/ebooks.do") O'Reilly
|
span(data-i18n="ladder_prizes.oreilly") ebook of your choice
| - $40
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $485
tr
td 7th
td
ul.list-unstyled
li
img(src=base + "cash3.png")
span $8
span(data-i18n="ladder_prizes.in_cash") in cash
li
img(src=base + "firebase.png")
span
a(href="https://www.firebase.com/") Firebase
|
span(data-i18n="ladder_prizes.credits") credits
| - $300
li
img(src=base + "one_month.png")
span
a(href="https://onemonthrails.com/") One Month Rails
|
span(data-i18n="ladder_prizes.one_month_discount") discount, 30% off: choose either Rails or HTML
| - $30
li
img(src=base + "webstorm.png")
span
a(href="http://www.jetbrains.com/webstorm/") Webstorm
|
span(data-i18n="ladder_prizes.license") license
| - $49
li
img(src=base + "oreilly.png")
span
a(href="http://shop.oreilly.com/category/ebooks.do") O'Reilly
|
span(data-i18n="ladder_prizes.oreilly") ebook of your choice
| - $40
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $477
tr
td 8th
td
ul.list-unstyled
li
img(src=base + "cash3.png")
span $4
span(data-i18n="ladder_prizes.in_cash") in cash
li
img(src=base + "firebase.png")
span
a(href="https://www.firebase.com/") Firebase
|
span(data-i18n="ladder_prizes.credits") credits
| - $300
li
img(src=base + "one_month.png")
span
a(href="https://onemonthrails.com/") One Month Rails
|
span(data-i18n="ladder_prizes.one_month_discount") discount, 30% off: choose either Rails or HTML
| - $30
li
img(src=base + "webstorm.png")
span
a(href="http://www.jetbrains.com/webstorm/") Webstorm
|
span(data-i18n="ladder_prizes.license") license
| - $49
li
img(src=base + "oreilly.png")
span
a(href="http://shop.oreilly.com/category/ebooks.do") O'Reilly
|
span(data-i18n="ladder_prizes.oreilly") ebook of your choice
| - $40
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $473
tr
td 9th
td
ul.list-unstyled
li
img(src=base + "cash3.png")
span $2
span(data-i18n="ladder_prizes.in_cash") in cash
li
img(src=base + "firebase.png")
span
a(href="https://www.firebase.com/") Firebase
|
span(data-i18n="ladder_prizes.credits") credits
| - $300
li
img(src=base + "one_month.png")
span
a(href="https://onemonthrails.com/") One Month Rails
|
span(data-i18n="ladder_prizes.one_month_discount") discount, 30% off: choose either Rails or HTML
| - $30
li
img(src=base + "webstorm.png")
span
a(href="http://www.jetbrains.com/webstorm/") Webstorm
|
span(data-i18n="ladder_prizes.license") license
| - $49
li
img(src=base + "oreilly.png")
span
a(href="http://shop.oreilly.com/category/ebooks.do") O'Reilly
|
span(data-i18n="ladder_prizes.oreilly") ebook of your choice
| - $40
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $471
tr
td 10th
td
ul.list-unstyled
li
img(src=base + "cash3.png")
span $1
span(data-i18n="ladder_prizes.in_cash") in cash
li
img(src=base + "firebase.png")
span
a(href="https://www.firebase.com/") Firebase
|
span(data-i18n="ladder_prizes.credits") credits
| - $300
li
img(src=base + "one_month.png")
span
a(href="https://onemonthrails.com/") One Month Rails
|
span(data-i18n="ladder_prizes.one_month_discount") discount, 30% off: choose either Rails or HTML
| - $30
li
img(src=base + "webstorm.png")
span
a(href="http://www.jetbrains.com/webstorm/") Webstorm
|
span(data-i18n="ladder_prizes.license") license
| - $49
li
img(src=base + "oreilly.png")
span
a(href="http://shop.oreilly.com/category/ebooks.do") O'Reilly
|
span(data-i18n="ladder_prizes.oreilly") ebook of your choice
| - $40
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $470
tr
td 11 - 40
td
ul.list-unstyled
li
img(src=base + "webstorm.png")
span
a(href="http://www.jetbrains.com/webstorm/") Webstorm
|
span(data-i18n="ladder_prizes.license") license
| - $49
li
img(src=base + "oreilly.png")
span
a(href="http://shop.oreilly.com/category/ebooks.do") O'Reilly
|
span(data-i18n="ladder_prizes.oreilly") ebook of your choice
| - $40
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $139
tr
td 41 - 100
td
ul.list-unstyled
li
img(src=base + "oreilly.png")
span
a(href="http://shop.oreilly.com/category/ebooks.do") O'Reilly
|
span(data-i18n="ladder_prizes.oreilly") ebook of your choice
| - $40
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $90
tr
td 101+
td
ul.list-unstyled
li
img(src=base + "aws.png")
span
a(href="http://aws.amazon.com/") Amazon Web Services
|
span(data-i18n="ladder_prizes.credits") credits
| - $50
td $50
.tab-pane.well#rules
h1(data-i18n="ladder.rules_remember_to_add_i18n_tags") Tournament Rules
p(data-i18n="ladder.rules_but_this_is_just_placeholder") We probably don't have to translate legal documents from English, but we should translate any conversational summary.
h1(data-i18n="ladder.tournament_rules") Tournament Rules
h2 General
p You don't have to buy anything to participate in the tournament, and trying to pay us won't increase your odds of winning.
h2 Dates and Times
p The tournament starts on Tuesday, May 20 at 8:30AM and ends on Tuesday, June 10 at 5:00PM PDT. After the tournament finishes, we will check the games manually to prevent duplicate entries and cheating. We will email all the winners within two weeks of the end date.
h2 Eligilibity
p The tournament is open to anyone over the age of 13. Players are allowed to form teams to compete, but we will only be rewarding submissions, so if a team of 10 wins, they will need to split the prize.
p The tournament is NOT open to people who live in countries or states that prohibit participating or receiving a prize in a challenge (these include, but are not limited to Brazil, Quebec, Italy, Cuba, Sudan, Iran, North Korea, and Syria). Organizations involved in putting the tournament together (namely CodeCombat and all of our employees) are excluded from participating/winning prizes.
h2 Submission Requirements
p
| To be eligible to win prizes, players must submit their code to the Greed ladder for ranking AND defeat our default AI. Every player that submits their code to the ladder and beats our default AI will receive $50 in AWS credits as described on the
a(href="#prizes", data-i18n="ladder_prizes.tournament_prizes") Tournament Prizes
| page.
p
| There are some restrictions regarding who can use the AWS credits. Please see the additional rules of use on
a(href="https://aws.amazon.com/awscredits") Amazon's AWS credits page.
h2 Submission Rights
p We reserve the right to use your submission and site identity (including username, avatar, and any information you mark as public) to promote the tournament. This is in keeping with our overall site terms of service.
h2 Judging Criteria
p
| We will calculate final rankings by running the top games from the public leaderboard from both teams against each other and sorting solutions by wins and losses. The number of games from each side to be used in the final ranking is yet to be determined, but is probably around 150. The final ranking will be performed with a snapshot of solutions taken the end of the contest. The final ranking methedology is subject to change. We will not be evaluating code in any manual way for common traits like adequate documentation, cleanliness, etc. We reserve the right to disqualify any player for any reason. The public leaderboards are a good proxy for your final rank, but are not guaranteed to be accurate. To repeat,
strong the leaderboards are only a preliminary proxy for your final rank
| .
p
| Your rank will change as players submit more solutions and more matches are played according to
a(href="https://github.com/codecombat/bayesian-battle") our open-source ranking library, Bayesian Battle
| , but our final ranking will use an exhaustive pairwise matching round amongst the top players as described above.
h2 Prizes
p
| Prizes will be awarded to everyone that achieves a rank covered on the
a(href="#prizes", data-i18n="ladder.prizes") Tournament Prizes
| page.
p Please remember that the player ranks listed on the prize page refer to ranks WITHIN a leaderboard. So if you are the #2 Ogre player, you will win the #2 prize. Similarly, if you are the #3 Human player, you will receive the #3 prize. If you have submissions on both leaderboards, we will only count your highest submission for the purposes of distributing prizes. As a result, your final ranking may be higher than your preliminary ranking due to removing duplicate submissions above you.
h2 Verifying Potential Winners
p We may ask players to identify themselves so that we can detect duplicate entries. This may be done in the form of a Facebook, Google+, or LinkedIn profile, but we may need more information. All players eligible for prizes agree that refusing to provide us with identifying information may lead to ineligibility for prizes.
p On a related note, if we have reason to believe that a player has intentionally submitted duplicate entries for the purpose of receiving more prizes or manipulating the leaderboards in any way, we will remove that player and all submissions we believe to be associated with them. We want this to be fair for everyone.
h2 Prize Distribution
p Different sponsors require different ways of claiming their prizes, and we will work with winners to ensure they are able to redeem their prizes in a timely fashion. For cash prizes, we will deliver the money via PayPal. We will not ship checks, money orders, or cash through the mail. We will assume reasonable international money transfer costs to deliver cash prizes through Paypal.
p Winners are responsible for any taxes associated with claiming their prizes. CodeCombat is not responsible for filing paperwork on behalf of winners for tax claims.
h2 Contact
p
| If you have any questions or would like to get in touch with us for any other reason, we can be reached at team@codecombat.com. You can also post to our public
a(href="http://discourse.codecombat.com/") Discourse forum
| .

View file

@ -30,7 +30,7 @@ div#columns.row
th(data-i18n="general.when") When
th
for match in team.matches
tr(class=(match.stale ? "stale " : "") + match.state)
tr(class=(match.stale ? "stale " : "") + (match.fresh ? "fresh " : "") + match.state)
td.state-cell
if match.state === 'win'
span(data-i18n="general.win").win Win

View file

@ -1,3 +1,8 @@
ul#primary-goals-list
h3
span(data-i18n="play_level.goals") Goals
div.goals-status
strong(data-i18n="play_level.goals") Goals
span.spl.spr :
span(data-i18n="play_level.success").secret.goal-status.success Success!
span(data-i18n="play_level.incomplete").secret.goal-status.incomplete Incomplete
span(data-i18n="play_level.timed_out").secret.goal-status.timed-out Ran out of time
span(data-i18n="play_level.failing").secret.goal-status.failure Failing

View file

@ -284,7 +284,11 @@ module.exports = class ThangTypeEditView extends View
fixed = scaleValue.toFixed(1)
@scale = scaleValue
@$el.find('.scale-label').text " #{fixed}x "
@currentObject.scaleX = @currentObject.scaleY = scaleValue / resValue if @currentObject?
if @currentSprite
@currentSprite.scaleFactor = scaleValue
@currentSprite.updateScale()
else if @currentObject?
@currentObject.scaleX = @currentObject.scaleY = scaleValue / resValue
@updateGrid()
@updateDots()

View file

@ -2,11 +2,12 @@ CocoView = require 'views/kinds/CocoView'
template = require 'templates/play/common/ladder_submission'
module.exports = class LadderSubmissionView extends CocoView
class: "ladder-submission-view"
className: "ladder-submission-view"
template: template
events:
'click .rank-button': 'rankSession'
'click .help-simulate': 'onHelpSimulate'
constructor: (options) ->
super options
@ -16,9 +17,11 @@ module.exports = class LadderSubmissionView extends CocoView
getRenderData: ->
ctx = super()
ctx.readyToRank = @session?.readyToRank()
ctx.isRanking = @ession?.get('isRanking')
ctx.isRanking = @session?.get('isRanking')
ctx.simulateURL = "/play/ladder/#{@level.get('slug')}#simulate"
ctx.lastSubmitted = moment(submitDate).fromNow() if submitDate = @session?.get('submitDate')
ctx
afterRender: ->
super()
return unless @supermodel.finished()
@ -34,9 +37,13 @@ module.exports = class LadderSubmissionView extends CocoView
@setRankingButtonText rankingState
setRankingButtonText: (spanClass) ->
@rankButton.find('span').addClass('hidden')
@rankButton.find(".#{spanClass}").removeClass('hidden')
@rankButton.find('span').hide()
@rankButton.find(".#{spanClass}").show()
@rankButton.toggleClass 'disabled', spanClass isnt 'rank'
helpSimulate = spanClass in ['submitted', 'ranking']
@$el.find('.help-simulate').toggle(helpSimulate, 'slow')
showLastSubmitted = not (spanClass in ['submitting'])
@$el.find('.last-submitted').toggle(showLastSubmitted)
rankSession: (e) ->
return unless @session.readyToRank()
@ -86,3 +93,5 @@ module.exports = class LadderSubmissionView extends CocoView
transpiledCode[thang][spellID] = aether.transpile spell
transpiledCode
onHelpSimulate: ->
$('a[href="#simulate"]').tab('show')

View file

@ -53,6 +53,7 @@ module.exports = class LadderView extends RootView
ctx.levelID = @levelID
ctx.levelDescription = marked(@level.get('description')) if @level.get('description')
ctx._ = _
ctx.tournamentTimeLeft = moment(new Date(1402444800000)).fromNow()
ctx
afterRender: ->
@ -100,6 +101,10 @@ module.exports = class LadderView extends RootView
e.stopPropagation()
e.preventDefault()
@showApologeticSignupModal()
if link and /#rules$/.test link
@$el.find('a[href="#rules"]').tab('show')
if link and /#prizes/.test link
@$el.find('a[href="#prizes"]').tab('show')
destroy: ->
clearInterval @refreshInterval

View file

@ -13,6 +13,7 @@ module.exports = class MyMatchesTabView extends CocoView
constructor: (options, @level, @sessions) ->
super(options)
@nameMap = {}
@previouslyRankingTeams = {}
@refreshMatches()
refreshMatches: ->
@ -58,6 +59,9 @@ module.exports = class MyMatchesTabView extends CocoView
state = 'win'
state = 'loss' if match.metrics.rank > opponent.metrics.rank
state = 'tie' if match.metrics.rank is opponent.metrics.rank
fresh = match.date > (new Date(new Date() - 20 * 1000)).toISOString()
if fresh
Backbone.Mediator.publish 'play-sound', trigger: 'chat_received'
{
state: state
opponentName: @nameMap[opponent.userID]
@ -65,6 +69,7 @@ module.exports = class MyMatchesTabView extends CocoView
when: moment(match.date).fromNow()
sessionID: opponent.sessionID
stale: match.date < submitDate
fresh: fresh
}
for team in @teams
@ -81,6 +86,10 @@ module.exports = class MyMatchesTabView extends CocoView
if scoreHistory?.length > 1
team.scoreHistory = scoreHistory
if not team.isRanking and @previouslyRankingTeams[team.id]
Backbone.Mediator.publish 'play-sound', trigger: 'cast-end'
@previouslyRankingTeams[team.id] = team.isRanking
ctx
afterRender: ->
@ -91,13 +100,14 @@ module.exports = class MyMatchesTabView extends CocoView
session = _.find @sessions.models, {id: sessionID}
ladderSubmissionView = new LadderSubmissionView session: session, level: @level
@insertSubView ladderSubmissionView, placeholder
ladderSubmissionView.$el.find('.rank-button').addClass 'btn-block'
@$el.find('.score-chart-wrapper').each (i, el) =>
scoreWrapper = $(el)
team = _.find @teams, name: scoreWrapper.data('team-name')
@generateScoreLineChart(scoreWrapper.attr('id'), team.scoreHistory, team.name)
@$el.find('tr.fresh').removeClass('fresh', 5000)
generateScoreLineChart: (wrapperID, scoreHistory,teamName) =>
margin =
top: 20

View file

@ -19,13 +19,26 @@ module.exports = class GoalsView extends View
'surface:playback-ended': 'onSurfacePlaybackEnded'
events:
'mouseenter': -> @$el.css('top', -10)
'mouseleave': -> @updateTop()
'mouseenter': ->
@mouseEntered = true
@updatePlacement()
'mouseleave': ->
@mouseEntered = false
@updatePlacement()
toggleCollapse: (e) ->
@$el.toggleClass('expanded').toggleClass('collapsed')
onNewGoalStates: (e) ->
@$el.find('.goal-status').addClass 'secret'
classToShow = null
classToShow = 'success' if e.overallStatus is 'success'
classToShow = 'failure' if e.overallStatus is 'failure'
classToShow ?= 'timed-out' if e.timedOut
classToShow ?= 'incomplete'
@$el.find('.goal-status.'+classToShow).removeClass 'secret'
list = $('#primary-goals-list', @$el)
list.empty()
goals = []
@ -53,10 +66,14 @@ module.exports = class GoalsView extends View
@$el.removeClass('secret') if goals.length > 0
onSurfacePlaybackRestarted: ->
@playbackEnded = false
@$el.removeClass 'brighter'
@updatePlacement()
onSurfacePlaybackEnded: ->
@playbackEnded = true
@$el.addClass 'brighter'
@updatePlacement()
render: ->
super()
@ -64,11 +81,16 @@ module.exports = class GoalsView extends View
afterRender: ->
super()
@updateTop()
@updatePlacement()
updateTop: ->
@$el.css('top', 26 - @$el.outerHeight())
updatePlacement: ->
if @playbackEnded or @mouseEntered
# expand
@$el.css('top', -10)
else
# collapse
@$el.css('top', 26 - @$el.outerHeight())
onSetLetterbox: (e) ->
@$el.toggle not e.on
@updateTop()
@updatePlacement()

View file

@ -15,6 +15,8 @@ module.exports = class LevelLoadingView extends View
@$el.find('.to-remove').remove()
showReady: ->
return if @shownReady
@shownReady = true
ready = $.i18n.t('play_level.loading_ready', defaultValue: 'Ready!')
@$el.find('#tip-wrapper .tip').addClass('ready').text ready
Backbone.Mediator.publish 'play-sound', trigger: 'level_loaded', volume: 0.75 # old: loading_ready

View file

@ -13,6 +13,7 @@ module.exports = class DocsModal extends View
'enter': 'hide'
constructor: (options) ->
@firstOnly = options.firstOnly
@docs = options?.docs
general = @docs.generalArticles or []
specific = @docs.specificArticles or []
@ -25,6 +26,7 @@ module.exports = class DocsModal extends View
@docs = specific.concat(general)
@docs = $.extend(true, [], @docs)
@docs = [@docs[0]] if @firstOnly and @docs[0]
doc.html = marked(utils.i18n doc, 'body') for doc in @docs
doc.name = (utils.i18n doc, 'name') for doc in @docs
doc.slug = _.string.slugify(doc.name) for doc in @docs

View file

@ -46,7 +46,6 @@ module.exports = class CastButtonView extends View
Backbone.Mediator.publish 'tome:manual-cast', {}
onCastOptionsClick: (e) =>
console.log 'cast options click', $(e.target)
Backbone.Mediator.publish 'focus-editor'
@castButtonGroup.removeClass 'open'
@setAutocastDelay $(e.target).attr 'data-delay'
@ -58,7 +57,9 @@ module.exports = class CastButtonView extends View
onCastSpells: (e) ->
return if e.preload
@casting = true
Backbone.Mediator.publish 'play-sound', trigger: 'cast', volume: 0.5
if @hasStartedCastingOnce # Don't play this sound the first time
Backbone.Mediator.publish 'play-sound', trigger: 'cast', volume: 0.5
@hasStartedCastingOnce = true
@updateCastButton()
@onWorldLoadProgressChanged progress: 0
@ -69,7 +70,9 @@ module.exports = class CastButtonView extends View
onNewWorld: (e) ->
@casting = false
Backbone.Mediator.publish 'play-sound', trigger: 'cast-end', volume: 0.5
if @hasCastOnce # Don't play this sound the first time
Backbone.Mediator.publish 'play-sound', trigger: 'cast-end', volume: 0.5
@hasCastOnce = true
@updateCastButton()
updateCastButton: ->

View file

@ -69,14 +69,13 @@ module.exports = class PlayLevelView extends View
shortcuts:
'ctrl+s': 'onCtrlS'
# Initial Setup #############################################################
constructor: (options, @levelID) ->
console.profile?() if PROFILE_ME
super options
if not me.get('hourOfCode') and @getQueryVariable "hour_of_code"
me.set 'hourOfCode', true
me.save()
$('body').append($("<img src='http://code.org/api/hour/begin_codecombat.png' style='visibility: hidden;'>"))
application.tracker?.trackEvent 'Hour of Code Begin', {}
@setUpHourOfCode()
@isEditorPreview = @getQueryVariable 'dev'
@sessionID = @getQueryVariable 'session'
@ -92,9 +91,11 @@ module.exports = class PlayLevelView extends View
@load()
application.tracker?.trackEvent 'Started Level Load', level: @levelID, label: @levelID
onLevelLoadError: (e) ->
# TODO NOW: remove this in favor of the supermodel handling it
application.router.navigate "/play?not_found=#{@levelID}", {trigger: true}
setUpHourOfCode: ->
me.set 'hourOfCode', true
me.save()
$('body').append($("<img src='http://code.org/api/hour/begin_codecombat.png' style='visibility: hidden;'>"))
application.tracker?.trackEvent 'Hour of Code Begin', {}
setLevel: (@level, givenSupermodel) ->
@supermodel.models = givenSupermodel.models
@ -112,6 +113,33 @@ module.exports = class PlayLevelView extends View
@loadStartTime = new Date()
@god = new God debugWorker: true
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @getQueryVariable('opponent'), team: @getQueryVariable("team")
@listenToOnce @levelLoader, 'world-necessities-loaded', @onWorldNecessitiesLoaded
# CocoView overridden methods ###############################################
updateProgress: (progress) ->
super(progress)
return if @seenDocs
return unless @levelLoader.session.loaded and @levelLoader.level.loaded
return unless showFrequency = @levelLoader.level.get('showsGuide')
session = @levelLoader.session
diff = new Date().getTime() - new Date(session.get('created')).getTime()
return if showFrequency is 'first-time' and diff > (5 * 60 * 1000)
articles = @levelLoader.supermodel.getModels Article
for article in articles
return unless article.loaded
@showGuide()
showGuide: ->
@seenDocs = true
DocsModal = require './level/modal/docs_modal'
options =
docs: @levelLoader.level.get('documentation')
supermodel: @supermodel
firstOnly: true
@openModalView(new DocsModal(options), true)
Backbone.Mediator.subscribeOnce 'modal-closed', @onLevelStarted, @
return true
getRenderData: ->
c = super()
@ -129,62 +157,26 @@ module.exports = class PlayLevelView extends View
@$el.find('#level-done-button').hide()
$('body').addClass('is-playing')
updateProgress: (progress) ->
super(progress)
if not @worldInitialized and @levelLoader.session.loaded and @levelLoader.level.loaded and @levelLoader.world and (not @levelLoader.opponentSession or @levelLoader.opponentSession.loaded)
@grabLevelLoaderData()
@onWorldInitialized()
return if @seenDocs
return unless @levelLoader.session.loaded and @levelLoader.level.loaded
return unless showFrequency = @levelLoader.level.get('showsGuide')
session = @levelLoader.session
diff = new Date().getTime() - new Date(session.get('created')).getTime()
return if showFrequency is 'first-time' and diff > (5 * 60 * 1000)
articles = @levelLoader.supermodel.getModels Article
for article in articles
return unless article.loaded
@showGuide()
afterInsert: ->
super()
@showWizardSettingsModal() if not me.get('name')
onWorldInitialized: ->
@worldInitialized = true
# Partially Loaded Setup ####################################################
onWorldNecessitiesLoaded: ->
# Called when we have enough to build the world, but not everything is loaded
@grabLevelLoaderData()
team = @getQueryVariable("team") ? @world.teamForPlayer(0)
@loadOpponentTeam(team)
@god.setLevel @level.serialize @supermodel
@god.setLevelSessionIDs if @otherSession then [@session.id, @otherSession.id] else [@session.id]
@god.setWorldClassMap @world.classMap
@setupGod()
@setTeam team
@initGoalManager()
@insertSubviews ladderGame: (@level.get('type') is "ladder")
@insertSubviews()
@initVolume()
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
@originalSessionState = $.extend(true, {}, @session.get('state'))
@register()
@controlBar.setBus(@bus)
showGuide: ->
@seenDocs = true
DocsModal = require './level/modal/docs_modal'
options = {docs: @levelLoader.level.get('documentation'), supermodel: @supermodel}
@openModalView(new DocsModal(options), true)
Backbone.Mediator.subscribeOnce 'modal-closed', @onLevelLoaded, @
return true
onLoaded: ->
_.defer => @onLevelLoaded()
onLevelLoaded: ->
return unless @levelLoader.progress() is 1 # double check, since closing the guide may trigger this early
@loadingView.showReady()
if window.currentModal and not window.currentModal.destroyed
return Backbone.Mediator.subscribeOnce 'modal-closed', @onLevelLoaded, @
# Save latest level played in local storage
if not (@levelLoader.level.get('type') in ['ladder', 'ladder-tutorial'])
me.set('lastLevel', @levelID)
me.save()
@levelLoader.destroy()
@levelLoader = null
@initSurface()
@initScriptManager()
grabLevelLoaderData: ->
@ -212,7 +204,79 @@ module.exports = class PlayLevelView extends View
# For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet.
@session.set 'multiplayer', false
onLevelStarted: (e) ->
setupGod: ->
@god.setLevel @level.serialize @supermodel
@god.setLevelSessionIDs if @otherSession then [@session.id, @otherSession.id] else [@session.id]
@god.setWorldClassMap @world.classMap
setTeam: (team) ->
team = team?.team unless _.isString team
team ?= 'humans'
me.team = team
Backbone.Mediator.publish 'level:team-set', team: team
@team = team
initGoalManager: ->
@goalManager = new GoalManager(@world, @level.get('goals'), @team)
@god.setGoalManager @goalManager
insertSubviews: ->
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, thangs: @world.thangs, supermodel: @supermodel
@insertSubView new PlaybackView session: @session
@insertSubView new GoalsView {}
@insertSubView new GoldView {}
@insertSubView new HUDView {}
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
worldName = utils.i18n @level.attributes, 'name'
@controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level, supermodel: @supermodel, playableTeams: @world.playableTeams}
#Backbone.Mediator.publish('level-set-debug', debug: true) if me.displayName() is 'Nick!'
initVolume: ->
volume = me.get('volume')
volume = 1.0 unless volume?
Backbone.Mediator.publish 'level-set-volume', volume: volume
initScriptManager: ->
@scriptManager = new ScriptManager({scripts: @world.scripts or [], view:@, session: @session})
@scriptManager.loadFromSession()
register: ->
@bus = LevelBus.get(@levelID, @session.id)
@bus.setSession(@session)
@bus.setSpells @tome.spells
@bus.connect() if @session.get('multiplayer')
# Load Completed Setup ######################################################
onLoaded: ->
_.defer => @onLevelLoaded()
onLevelLoaded: ->
# Everything is now loaded
return unless @levelLoader.progress() is 1 # double check, since closing the guide may trigger this early
# Save latest level played in local storage
if not (@levelLoader.level.get('type') in ['ladder', 'ladder-tutorial'])
me.set('lastLevel', @levelID)
me.save()
@levelLoader.destroy()
@levelLoader = null
@initSurface()
initSurface: ->
surfaceCanvas = $('canvas#surface', @$el)
@surface = new Surface(@world, surfaceCanvas, thangTypes: @supermodel.getModels(ThangType), playJingle: not @isEditorPreview)
worldBounds = @world.getBounds()
bounds = [{x:worldBounds.left, y:worldBounds.top}, {x:worldBounds.right, y:worldBounds.bottom}]
@surface.camera.setBounds(bounds)
@surface.camera.zoomTo({x:0, y:0}, 0.1, 0)
# Once Surface is Loaded ####################################################
onLevelStarted: ->
@loadingView.showReady()
if window.currentModal and not window.currentModal.destroyed
return Backbone.Mediator.subscribeOnce 'modal-closed', @onLevelStarted, @
@surface.showLevel()
if @otherSession
# TODO: colorize name and cloud by team, colorize wizard by user's color config
@ -229,34 +293,17 @@ module.exports = class PlayLevelView extends View
application.tracker?.trackEvent 'Finished Level Load', level: @levelID, label: @levelID, loadDuration: loadDuration
application.tracker?.trackTiming loadDuration, 'Level Load Time', @levelID, @levelID
onSupermodelLoadedOne: =>
@modelsLoaded ?= 0
@modelsLoaded += 1
@updateInitString()
updateInitString: ->
return if @surface
@modelsLoaded ?= 0
canvas = @$el.find('#surface')[0]
ctx = canvas.getContext('2d')
ctx.font="20px Georgia"
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.fillText("Loaded #{@modelsLoaded} thingies",50,50)
insertSubviews: ->
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, thangs: @world.thangs, supermodel: @supermodel
@insertSubView new PlaybackView session: @session
@insertSubView new GoalsView {}
@insertSubView new GoldView {}
@insertSubView new HUDView {}
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
worldName = utils.i18n @level.attributes, 'name'
@controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level, supermodel: @supermodel, playableTeams: @world.playableTeams}
#Backbone.Mediator.publish('level-set-debug', debug: true) if me.displayName() is 'Nick!'
afterInsert: ->
super()
@showWizardSettingsModal() if not me.get('name')
onSurfaceSetUpNewWorld: ->
return if @alreadyLoadedState
@alreadyLoadedState = true
state = @originalSessionState
if state.frame and @level.get('type') isnt 'ladder' # https://github.com/codecombat/codecombat/issues/714
Backbone.Mediator.publish 'level-set-time', { time: 0, frameOffset: state.frame }
if state.selected
# TODO: Should also restore selected spell here by saving spellName
Backbone.Mediator.publish 'level-select-sprite', { thangID: state.selected, spellName: null }
if state.playing?
Backbone.Mediator.publish 'level-set-playing', { playing: state.playing }
# callbacks
@ -412,46 +459,11 @@ module.exports = class PlayLevelView extends View
@bus.removeFirebaseData =>
@bus.disconnect()
# initialization
addPointer: ->
p = $('#pointer')
return if p.length
@$el.append($('<img src="/images/level/pointer.png" id="pointer">'))
initSurface: ->
surfaceCanvas = $('canvas#surface', @$el)
@surface = new Surface(@world, surfaceCanvas, thangTypes: @supermodel.getModels(ThangType), playJingle: not @isEditorPreview)
worldBounds = @world.getBounds()
bounds = [{x:worldBounds.left, y:worldBounds.top}, {x:worldBounds.right, y:worldBounds.bottom}]
@surface.camera.setBounds(bounds)
@surface.camera.zoomTo({x:0, y:0}, 0.1, 0)
initGoalManager: ->
@goalManager = new GoalManager(@world, @level.get('goals'))
@god.setGoalManager @goalManager
initScriptManager: ->
@scriptManager = new ScriptManager({scripts: @world.scripts or [], view:@, session: @session})
@scriptManager.loadFromSession()
initVolume: ->
volume = me.get('volume')
volume = 1.0 unless volume?
Backbone.Mediator.publish 'level-set-volume', volume: volume
onSurfaceSetUpNewWorld: ->
return if @alreadyLoadedState
@alreadyLoadedState = true
state = @originalSessionState
if state.frame and @level.get('type') isnt 'ladder' # https://github.com/codecombat/codecombat/issues/714
Backbone.Mediator.publish 'level-set-time', { time: 0, frameOffset: state.frame }
if state.selected
# TODO: Should also restore selected spell here by saving spellName
Backbone.Mediator.publish 'level-select-sprite', { thangID: state.selected, spellName: null }
if state.playing?
Backbone.Mediator.publish 'level-set-playing', { playing: state.playing }
preloadNextLevel: =>
# TODO: Loading models in the middle of gameplay causes stuttering. Most of the improvement in loading time is simply from passing the supermodel from this level to the next, but if we can find a way to populate the level early without it being noticeable, that would be even better.
# return if @destroyed
@ -460,12 +472,6 @@ module.exports = class PlayLevelView extends View
# @supermodel.populateModel nextLevel
# @preloaded = true
register: ->
@bus = LevelBus.get(@levelID, @session.id)
@bus.setSession(@session)
@bus.setSpells @tome.spells
@bus.connect() if @session.get('multiplayer')
onSessionWillSave: (e) ->
# Something interesting has happened, so (at a lower frequency), we'll save a screenshot.
#@saveScreenshot e.session
@ -475,12 +481,6 @@ module.exports = class PlayLevelView extends View
return unless screenshot = @surface?.screenshot()
session.save {screenshot: screenshot}, {patch: true}
setTeam: (team) ->
team = team?.team unless _.isString team
team ?= 'humans'
me.team = team
Backbone.Mediator.publish 'level:team-set', team: team
# Dynamic sound loading
onNewWorld: (e) ->

View file

@ -9,7 +9,7 @@ import sys
current_directory = os.path.dirname(os.path.realpath(sys.argv[0]))
coco_path = os.getenv("COCO_DIR",os.path.join(current_directory,os.pardir))
brunch_path = coco_path + os.sep + "node_modules" + os.sep + ".bin" + os.sep + "brunch"
subprocess.Popen("ulimit -n 10000",shell=True)
subprocess.Popen("ulimit -n 100000",shell=True)
while True:
print("Starting brunch. After the first compile, it'll keep running and watch for changes.")
call(brunch_path + " w",shell=True,cwd=coco_path)

View file

@ -121,7 +121,7 @@ module.exports = class Handler
@modelClass.findOne(criteria, project).sort(sort).exec (err, document) ->
return done(err) if err
callback(null, document?.toObject() or null)
funcs = {}
for id in ids
return errors.badInput(res, "Given an invalid id: #{id}") unless Handler.isID(id)

View file

@ -103,28 +103,89 @@ module.exports.getTwoGames = (req, res) ->
#if userIsAnonymous req then return errors.unauthorized(res, "You need to be logged in to get games.")
humansGameID = req.body.humansGameID
ogresGameID = req.body.ogresGameID
unless ogresGameID and humansGameID
#fetch random games here
return errors.badInput(res, "You need to supply two games(for now)")
LevelSession.findOne(_id: humansGameID).lean().exec (err, humanSession) =>
if err? then return errors.serverError(res, "Couldn't find the human game")
LevelSession.findOne(_id: ogresGameID).lean().exec (err, ogreSession) =>
if err? then return errors.serverError(res, "Couldn't find the ogre game")
taskObject =
"messageGenerated": Date.now()
"sessions": []
for session in [humanSession, ogreSession]
sessionInformation =
"sessionID": session._id
"team": session.team ? "No team"
"transpiledCode": session.transpiledCode
"teamSpells": session.teamSpells ? {}
"levelID": session.levelID
queryParams =
"levelID":"greed"
"submitted":true
"team":"humans"
selection = "team totalScore transpiledCode teamSpells levelID creatorName creator"
LevelSession.count queryParams, (err, numberOfHumans) =>
query = LevelSession
.find(queryParams)
.limit(1)
.select(selection)
.skip(Math.floor(Math.random()*numberOfHumans))
.lean()
query.exec (err, randomSession) =>
if err? then return errors.serverError(res, "Couldn't select a top 15 random session!")
randomSession = randomSession[0]
queryParams =
"levelID":"greed"
"submitted":true
"totalScore":
$lte: randomSession.totalScore
"team": "ogres"
query = LevelSession
.find(queryParams)
.select(selection)
.sort(totalScore: -1)
.limit(1)
.lean()
query.exec (err, otherSession) =>
if err? then return errors.serverError(res, "Couldnt' select the other top 15 random session!")
otherSession = otherSession[0]
taskObject =
"messageGenerated": Date.now()
"sessions": []
for session in [randomSession, otherSession]
sessionInformation =
"sessionID": session._id
"team": session.team ? "No team"
"transpiledCode": session.transpiledCode
"teamSpells": session.teamSpells ? {}
"levelID": session.levelID
"creatorName": session.creatorName
"creator": session.creator
"totalScore": session.totalScore
taskObject.sessions.push sessionInformation
sendResponseObject req, res, taskObject
else
LevelSession.findOne(_id: humansGameID).lean().exec (err, humanSession) =>
if err? then return errors.serverError(res, "Couldn't find the human game")
LevelSession.findOne(_id: ogresGameID).lean().exec (err, ogreSession) =>
if err? then return errors.serverError(res, "Couldn't find the ogre game")
taskObject =
"messageGenerated": Date.now()
"sessions": []
for session in [humanSession, ogreSession]
sessionInformation =
"sessionID": session._id
"team": session.team ? "No team"
"transpiledCode": session.transpiledCode
"teamSpells": session.teamSpells ? {}
"levelID": session.levelID
taskObject.sessions.push sessionInformation
sendResponseObject req, res, taskObject
module.exports.recordTwoGames = (req, res) ->
@clientResponseObject = req.body
taskObject.sessions.push sessionInformation
sendResponseObject req, res, taskObject
async.waterfall [
fetchLevelSession.bind(@)
updateSessions.bind(@)
indexNewScoreArray.bind(@)
addMatchToSessions.bind(@)
updateUserSimulationCounts.bind(@, req.user._id)
], (err, successMessageObject) ->
if err? then return errors.serverError res, "There was an error recording the single game:#{err}"
sendResponseObject req, res, {"message":"The single game was submitted successfully!"}
module.exports.createNewTask = (req, res) ->
requestSessionID = req.body.session
originalLevelID = req.body.originalLevelID
@ -214,7 +275,7 @@ fetchInitialSessionsToRankAgainst = (levelMajorVersion, levelID, submittedSessio
submittedCode:
$exists: true
team: opposingTeam
sortParameters =
totalScore: 1
@ -663,9 +724,7 @@ retrieveOldSessionData = (sessionID, callback) ->
"totalScore":session.totalScore ? (25 - 1.8*(25/3))
"id": sessionID
callback err, oldScoreObject
markSessionAsDoneRanking = (sessionID, cb) ->
markSessionAsDoneRanking = (sessionID, cb) ->
#console.log "Marking session as done ranking..."
LevelSession.update {"_id":sessionID}, {"isRanking":false}, cb

View file

@ -21,6 +21,10 @@ module.exports.setup = (app) ->
app.post '/queue/scoring/getTwoGames', (req, res) ->
handler = loadQueueHandler 'scoring'
handler.getTwoGames req, res
app.put '/queue/scoring/recordTwoGames', (req, res) ->
handler = loadQueueHandler 'scoring'
handler.recordTwoGames req, res
app.all '/queue/*', (req, res) ->
setResponseHeaderToJSONContentType res

View file

@ -91,8 +91,8 @@ setupFallbackRouteToIndex = (app) ->
sendMain = (req, res) ->
fs.readFile path.join(__dirname, 'public', 'main.html'), 'utf8', (err, data) ->
log.error "Error modifying main.html: #{err}" if err
# insert the user object directly into the html so the application can have it immediately
data = data.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user)))
# insert the user object directly into the html so the application can have it immediately. Sanitize </script>
data = data.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user)).replace('/', '\\/'))
res.send data
setupFacebookCrossDomainCommunicationRoute = (app) ->