This commit is contained in:
George Saines 2014-05-22 09:55:34 -07:00
commit a2fbe5865a
35 changed files with 562 additions and 278 deletions

View file

@ -378,6 +378,7 @@ self.onWorldLoaded = function onWorldLoaded() {
} }
var t3 = new Date(); var t3 = new Date();
console.log("And it was so: (" + (diff / self.world.totalFrames).toFixed(3) + "ms per frame,", self.world.totalFrames, "frames)\nSimulation :", diff + "ms \nSerialization:", (t2 - t1) + "ms\nDelivery :", (t3 - t2) + "ms"); console.log("And it was so: (" + (diff / self.world.totalFrames).toFixed(3) + "ms per frame,", self.world.totalFrames, "frames)\nSimulation :", diff + "ms \nSerialization:", (t2 - t1) + "ms\nDelivery :", (t3 - t2) + "ms");
self.world.goalManager.destroy();
self.world = null; self.world = null;
}; };
@ -408,6 +409,7 @@ self.onWorldLoadProgress = function onWorldLoadProgress(progress) {
self.abort = function abort() { self.abort = function abort() {
if(self.world) { if(self.world) {
self.world.abort(); self.world.abort();
self.world.goalManager.destroy();
self.world = null; self.world = null;
} }
self.postMessage({type: 'abort'}); self.postMessage({type: 'abort'});

View file

@ -1,3 +1,4 @@
Backbone.Mediator.setValidationEnabled false
app = require 'application' app = require 'application'
channelSchemas = channelSchemas =
@ -17,6 +18,10 @@ definitionSchemas =
'misc': require './schemas/definitions/misc' 'misc': require './schemas/definitions/misc'
init = -> init = ->
# Set up Backbone.Mediator schemas
setUpDefinitions()
setUpChannels()
Backbone.Mediator.setValidationEnabled document.location.href.search(/codecombat.com/) is -1
app.initialize() app.initialize()
Backbone.history.start({ pushState: true }) Backbone.history.start({ pushState: true })
handleNormalUrls() handleNormalUrls()
@ -25,10 +30,6 @@ init = ->
treemaExt.setup() treemaExt.setup()
filepicker.setKey('AvlkNoldcTOU4PvKi2Xm7z') filepicker.setKey('AvlkNoldcTOU4PvKi2Xm7z')
# Set up Backbone.Mediator schemas
setUpDefinitions()
setUpChannels()
$ -> init() $ -> init()
handleNormalUrls = -> handleNormalUrls = ->

View file

@ -9,7 +9,7 @@ module.exports = class CocoClass
@nicksUsed: {} @nicksUsed: {}
@remainingNicks: [] @remainingNicks: []
@nextNick: -> @nextNick: ->
return "CocoClass " + classCount unless @nicks.length return (@name or "CocoClass") + " " + classCount unless @nicks.length
@remainingNicks = if @remainingNicks.length then @remainingNicks else @nicks.slice() @remainingNicks = if @remainingNicks.length then @remainingNicks else @nicks.slice()
baseNick = @remainingNicks.splice(Math.floor(Math.random() * @remainingNicks.length), 1)[0] baseNick = @remainingNicks.splice(Math.floor(Math.random() * @remainingNicks.length), 1)[0]
i = 0 i = 0
@ -37,7 +37,7 @@ module.exports = class CocoClass
destroy: -> destroy: ->
# teardown subscriptions, prevent new ones # teardown subscriptions, prevent new ones
@stopListening?() @stopListening?()
@off() @off?()
@unsubscribeAll() @unsubscribeAll()
@stopListeningToShortcuts() @stopListeningToShortcuts()
@constructor.nicksUsed[@nick] = false @constructor.nicksUsed[@nick] = false
@ -65,6 +65,7 @@ module.exports = class CocoClass
Backbone.Mediator.subscribe(channel, func, @) Backbone.Mediator.subscribe(channel, func, @)
unsubscribeAll: -> unsubscribeAll: ->
return unless Backbone?.Mediator?
for channel, func of @subscriptions for channel, func of @subscriptions
func = utils.normalizeFunc(func, @) func = utils.normalizeFunc(func, @)
Backbone.Mediator.unsubscribe(channel, func, @) Backbone.Mediator.unsubscribe(channel, func, @)

View file

@ -47,7 +47,9 @@ module.exports = class God extends CocoClass
setLevel: (@level) -> setLevel: (@level) ->
setLevelSessionIDs: (@levelSessionIDs) -> setLevelSessionIDs: (@levelSessionIDs) ->
setGoalManager: (goalManager) -> @angelsShare.goalManager = goalManager setGoalManager: (goalManager) ->
@angelsShare.goalManager?.destroy() unless @angelsShare.goalManager is goalManager
@angelsShare.goalManager = goalManager
setWorldClassMap: (worldClassMap) -> @angelsShare.worldClassMap = worldClassMap setWorldClassMap: (worldClassMap) -> @angelsShare.worldClassMap = worldClassMap
onTomeCast: (e) -> onTomeCast: (e) ->

View file

@ -63,12 +63,14 @@ module.exports = class LevelLoader extends CocoClass
url += "?team=#{@team}" if @team url += "?team=#{@team}" if @team
session = new LevelSession().setURL url session = new LevelSession().setURL url
@session = @supermodel.loadModel(session, 'level_session', {cache:false}).model @sessionResource = @supermodel.loadModel(session, 'level_session', {cache:false})
@session = @sessionResource.model
@session.once 'sync', -> @url = -> '/db/level.session/' + @id @session.once 'sync', -> @url = -> '/db/level.session/' + @id
if @opponentSessionID if @opponentSessionID
opponentSession = new LevelSession().setURL "/db/level_session/#{@opponentSessionID}" opponentSession = new LevelSession().setURL "/db/level_session/#{@opponentSessionID}"
@opponentSession = @supermodel.loadModel(opponentSession, 'opponent_session').model @opponentSessionResource = @supermodel.loadModel(opponentSession, 'opponent_session')
@opponentSession = @opponentSessionResource.model
# Supermodel (Level) Loading # Supermodel (Level) Loading
@ -111,6 +113,8 @@ module.exports = class LevelLoader extends CocoClass
@thangIDs = _.uniq thangIDs @thangIDs = _.uniq thangIDs
@thangNames = new ThangNamesCollection(@thangIDs) @thangNames = new ThangNamesCollection(@thangIDs)
worldNecessities.push @supermodel.loadCollection(@thangNames, 'thang_names') worldNecessities.push @supermodel.loadCollection(@thangNames, 'thang_names')
worldNecessities.push @sessionResource if @sessionResource?.isLoading
worldNecessities.push @opponentSessionResource if @opponentSessionResource?.isLoading
for obj in objUniq componentVersions for obj in objUniq componentVersions
url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}" url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}"
@ -144,6 +148,7 @@ module.exports = class LevelLoader extends CocoClass
for thangTypeName in thangsToLoad for thangTypeName in thangsToLoad
thangType = nameModelMap[thangTypeName] thangType = nameModelMap[thangTypeName]
continue if thangType.isFullyLoaded()
thangType.fetch() thangType.fetch()
thangType = @supermodel.loadModel(thangType, 'thang').model thangType = @supermodel.loadModel(thangType, 'thang').model
res = @supermodel.addSomethingResource "sprite_sheet", 5 res = @supermodel.addSomethingResource "sprite_sheet", 5
@ -165,16 +170,27 @@ module.exports = class LevelLoader extends CocoClass
app.tracker.updatePlayState(@level, @session) unless @headless app.tracker.updatePlayState(@level, @session) unless @headless
buildLoop: => buildLoop: =>
return if @lastBuilt and new Date().getTime() - @lastBuilt < 10 someLeft = false
return clearInterval @buildLoopInterval unless @spriteSheetsToBuild.length
for spriteSheetResource, i in @spriteSheetsToBuild for spriteSheetResource, i in @spriteSheetsToBuild
if spriteSheetResource.thangType.loaded continue if spriteSheetResource.spriteSheetKeys
@buildSpriteSheetsForThangType spriteSheetResource.thangType someLeft = true
@spriteSheetsToBuild.splice i, 1 thangType = spriteSheetResource.thangType
@lastBuilt = new Date().getTime() if thangType.loaded and not thangType.loading
spriteSheetResource.markLoaded() keys = @buildSpriteSheetsForThangType spriteSheetResource.thangType
return if keys and keys.length
@listenTo spriteSheetResource.thangType, 'build-complete', @onBuildComplete
spriteSheetResource.spriteSheetKeys = keys
else
spriteSheetResource.markLoaded()
clearInterval @buildLoopInterval unless someLeft
onBuildComplete: (e) ->
resource = null
for resource in @spriteSheetsToBuild
break if e.thangType is resource.thangType
resource.spriteSheetKeys = (k for k in resource.spriteSheetKeys when k isnt e.key)
resource.markLoaded() if resource.spriteSheetKeys.length is 0
denormalizeSession: -> denormalizeSession: ->
return if @headless or @sessionDenormalized or @spectateMode return if @headless or @sessionDenormalized or @spectateMode
@ -201,13 +217,16 @@ module.exports = class LevelLoader extends CocoClass
# queue = new createjs.LoadQueue() # queue = new createjs.LoadQueue()
# queue.loadFile('/file/'+f) # queue.loadFile('/file/'+f)
@grabThangTypeTeams() unless @thangTypeTeams @grabThangTypeTeams() unless @thangTypeTeams
keys = []
for team in @thangTypeTeams[thangType.get('original')] ? [null] for team in @thangTypeTeams[thangType.get('original')] ? [null]
spriteOptions = {resolutionFactor: SPRITE_RESOLUTION_FACTOR, async: false} spriteOptions = {resolutionFactor: SPRITE_RESOLUTION_FACTOR, async: true}
if thangType.get('kind') is 'Floor' if thangType.get('kind') is 'Floor'
spriteOptions.resolutionFactor = 2 spriteOptions.resolutionFactor = 2
if team and color = @teamConfigs[team]?.color if team and color = @teamConfigs[team]?.color
spriteOptions.colorConfig = team: color spriteOptions.colorConfig = team: color
@buildSpriteSheet thangType, spriteOptions key = @buildSpriteSheet thangType, spriteOptions
if _.isString(key) then keys.push key
keys
grabThangTypeTeams: -> grabThangTypeTeams: ->
@grabTeamConfigs() @grabTeamConfigs()

View file

@ -35,6 +35,7 @@ module.exports = class DOMScriptModule extends ScriptModule
sides: dom.highlight.sides sides: dom.highlight.sides
offset: dom.highlight.offset offset: dom.highlight.offset
rotation: dom.highlight.rotation rotation: dom.highlight.rotation
note.event = _.pick note.event, (value) -> not _.isUndefined value
@maybeApplyDelayToNote note @maybeApplyDelayToNote note
note note

View file

@ -52,12 +52,14 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
@debugScripts = @view.getQueryVariable 'dev' @debugScripts = @view.getQueryVariable 'dev'
@initProperties() @initProperties()
@addScriptSubscriptions() @addScriptSubscriptions()
@beginTicking()
setScripts: (@originalScripts) -> setScripts: (@originalScripts) ->
@quiet = true @quiet = true
@initProperties() @initProperties()
@loadFromSession() @loadFromSession()
@quiet = false @quiet = false
@addScriptSubscriptions()
@run() @run()
initProperties: -> initProperties: ->
@ -74,6 +76,25 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
script.id = (idNum++).toString() unless script.id script.id = (idNum++).toString() unless script.id
callback = makeCallback(script.channel) # curry in the channel argument callback = makeCallback(script.channel) # curry in the channel argument
@addNewSubscription(script.channel, callback) @addNewSubscription(script.channel, callback)
beginTicking: ->
@tickInterval = setInterval @tick, 5000
tick: =>
scriptStates = {}
now = new Date()
for script in @scripts
scriptStates[script.id] =
timeSinceLastEnded: (if script.lastEnded then now - script.lastEnded else 0) / 1000
timeSinceLastTriggered: (if script.lastTriggered then now - script.lastTriggered else 0) / 1000
stateEvent =
scriptRunning: @currentNoteGroup?.scriptID or ''
noteGroupRunning: @currentNoteGroup?.name or ''
scriptStates: scriptStates
timeSinceLastScriptEnded: (if @lastScriptEnded then now - @lastScriptEnded else 0) / 1000
Backbone.Mediator.publish 'script-manager:tick', stateEvent
loadFromSession: -> loadFromSession: ->
# load the queue with note groups to skip through # load the queue with note groups to skip through
@ -88,6 +109,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
return unless script return unless script
@triggered.push(script.id) @triggered.push(script.id)
noteChain = @processScript(script) noteChain = @processScript(script)
return unless noteChain
if scripts.currentScriptOffset if scripts.currentScriptOffset
noteGroup.skipMe = true for noteGroup in noteChain[..scripts.currentScriptOffset-1] noteGroup.skipMe = true for noteGroup in noteChain[..scripts.currentScriptOffset-1]
@addNoteChain(noteChain, false) @addNoteChain(noteChain, false)
@ -107,6 +129,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
@triggered.push(scriptID) @triggered.push(scriptID)
@ended.push(scriptID) @ended.push(scriptID)
noteChain = @processScript(script) noteChain = @processScript(script)
return unless noteChain
noteGroup.skipMe = true for noteGroup in noteChain noteGroup.skipMe = true for noteGroup in noteChain
@addNoteChain(noteChain, false) @addNoteChain(noteChain, false)
@ -123,6 +146,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
destroy: -> destroy: ->
@onEndAll() @onEndAll()
clearInterval @tickInterval
super() super()
# TRIGGERERING NOTES # TRIGGERERING NOTES
@ -147,10 +171,11 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
continue unless @scriptPrereqsSatisfied(script) continue unless @scriptPrereqsSatisfied(script)
continue unless scriptMatchesEventPrereqs(script, event) continue unless scriptMatchesEventPrereqs(script, event)
# everything passed! # everything passed!
console.log "SCRIPT: Running script '#{script.id}'" if @debugScripts console.debug "SCRIPT: Running script '#{script.id}'" if @debugScripts
script.lastTriggered = new Date().getTime() script.lastTriggered = new Date().getTime()
@triggered.push(script.id) unless alreadyTriggered @triggered.push(script.id) unless alreadyTriggered
noteChain = @processScript(script) noteChain = @processScript(script)
if not noteChain then return @trackScriptCompletions (script.id)
@addNoteChain(noteChain) @addNoteChain(noteChain)
@run() @run()
@ -159,10 +184,10 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
processScript: (script) -> processScript: (script) ->
noteChain = script.noteChain noteChain = script.noteChain
return null unless noteChain?.length
noteGroup.scriptID = script.id for noteGroup in noteChain noteGroup.scriptID = script.id for noteGroup in noteChain
if noteChain.length lastNoteGroup = noteChain[noteChain.length - 1]
lastNoteGroup = noteChain[noteChain.length - 1] lastNoteGroup.isLast = true
lastNoteGroup.isLast = true
return noteChain return noteChain
addNoteChain: (noteChain, clearYields=true) -> addNoteChain: (noteChain, clearYields=true) ->
@ -207,7 +232,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
@notifyScriptStateChanged() @notifyScriptStateChanged()
@scriptInProgress = true @scriptInProgress = true
@currentTimeouts = [] @currentTimeouts = []
console.log "SCRIPT: Starting note group '#{nextNoteGroup.name}'" if @debugScripts console.debug "SCRIPT: Starting note group '#{nextNoteGroup.name}'" if @debugScripts
for module in nextNoteGroup.modules for module in nextNoteGroup.modules
@processNote(note, nextNoteGroup) for note in module.startNotes() @processNote(note, nextNoteGroup) for note in module.startNotes()
if nextNoteGroup.script.duration if nextNoteGroup.script.duration
@ -221,12 +246,12 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
@ignoreEvents = true @ignoreEvents = true
for noteGroup, i in @noteGroupQueue for noteGroup, i in @noteGroupQueue
break unless noteGroup.skipMe break unless noteGroup.skipMe
console.log "SCRIPT: Skipping note group '#{noteGroup.name}'" if @debugScripts console.debug "SCRIPT: Skipping note group '#{noteGroup.name}'" if @debugScripts
@processNoteGroup(noteGroup) @processNoteGroup(noteGroup)
for module in noteGroup.modules for module in noteGroup.modules
notes = module.skipNotes() notes = module.skipNotes()
@processNote(note, noteGroup) for note in notes @processNote(note, noteGroup) for note in notes
@trackScriptCompletions(noteGroup) @trackScriptCompletionsFromNoteGroup(noteGroup)
@noteGroupQueue = @noteGroupQueue[i..] @noteGroupQueue = @noteGroupQueue[i..]
@ignoreEvents = false @ignoreEvents = false
@ -268,14 +293,13 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
return if @ending # kill infinite loops right here return if @ending # kill infinite loops right here
@ending = true @ending = true
return unless @currentNoteGroup? return unless @currentNoteGroup?
console.log "SCRIPT: Ending note group '#{@currentNoteGroup.name}'" if @debugScripts console.debug "SCRIPT: Ending note group '#{@currentNoteGroup.name}'" if @debugScripts
clearTimeout(timeout) for timeout in @currentTimeouts clearTimeout(timeout) for timeout in @currentTimeouts
for module in @currentNoteGroup.modules for module in @currentNoteGroup.modules
@processNote(note, @currentNoteGroup) for note in module.endNotes() @processNote(note, @currentNoteGroup) for note in module.endNotes()
Backbone.Mediator.publish 'note-group-ended' unless @quiet Backbone.Mediator.publish 'note-group-ended' unless @quiet
@scriptInProgress = false @scriptInProgress = false
@ended.push(@currentNoteGroup.scriptID) if @currentNoteGroup.isLast @trackScriptCompletionsFromNoteGroup(@currentNoteGroup)
@trackScriptCompletions(@currentNoteGroup)
@currentNoteGroup = null @currentNoteGroup = null
unless @noteGroupQueue.length unless @noteGroupQueue.length
@notifyScriptStateChanged() @notifyScriptStateChanged()
@ -302,7 +326,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
for module in noteGroup.modules for module in noteGroup.modules
notes = module.skipNotes() notes = module.skipNotes()
@processNote(note, noteGroup) for note in notes unless @quiet @processNote(note, noteGroup) for note in notes unless @quiet
@trackScriptCompletions(noteGroup) unless @quiet @trackScriptCompletionsFromNoteGroup(noteGroup) unless @quiet
@noteGroupQueue = [] @noteGroupQueue = []
@ -317,11 +341,18 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
Backbone.Mediator.publish 'level-enable-controls', {} Backbone.Mediator.publish 'level-enable-controls', {}
Backbone.Mediator.publish 'level-set-letterbox', { on: false } Backbone.Mediator.publish 'level-set-letterbox', { on: false }
trackScriptCompletions: (noteGroup) -> trackScriptCompletionsFromNoteGroup: (noteGroup) ->
return if @quiet
return unless noteGroup.isLast return unless noteGroup.isLast
@ended.push(noteGroup.scriptID) unless noteGroup.scriptID in @ended @trackScriptCompletions(noteGroup.scriptID)
Backbone.Mediator.publish 'script:ended', {scriptID: noteGroup.scriptID}
trackScriptCompletions: (scriptID) ->
return if @quiet
@ended.push(scriptID) unless scriptID in @ended
for script in @scripts
if script.id is scriptID
script.lastEnded = new Date()
@lastScriptEnded = new Date()
Backbone.Mediator.publish 'script:ended', {scriptID: scriptID}
notifyScriptStateChanged: -> notifyScriptStateChanged: ->
return if @quiet return if @quiet

View file

@ -36,4 +36,4 @@ module.exports = class ScriptModule extends CocoClass
Math.max(0, sums...) Math.max(0, sums...)
maybeApplyDelayToNote: (note) -> maybeApplyDelayToNote: (note) ->
note.delay = @scrubbingTime + @movementTime note.delay = (@scrubbingTime + @movementTime) or 0

View file

@ -23,7 +23,7 @@ module.exports = class Simulator extends CocoClass
@cleanupSimulation() @cleanupSimulation()
@god?.destroy() @god?.destroy()
super() super()
fetchAndSimulateOneGame: (humanGameID, ogresGameID) => fetchAndSimulateOneGame: (humanGameID, ogresGameID) =>
return if @destroyed return if @destroyed
$.ajax $.ajax
@ -34,20 +34,22 @@ module.exports = class Simulator extends CocoClass
"humansGameID": humanGameID "humansGameID": humanGameID
"ogresGameID": ogresGameID "ogresGameID": ogresGameID
error: (errorData) -> error: (errorData) ->
console.log "There was an error fetching two games! #{JSON.stringify errorData}" console.warn "There was an error fetching two games! #{JSON.stringify errorData}"
success: (taskData) => success: (taskData) =>
return if @destroyed
@trigger 'statusUpdate', 'Setting up simulation...' @trigger 'statusUpdate', 'Setting up simulation...'
#refactor this #refactor this
@task = new SimulationTask(taskData) @task = new SimulationTask(taskData)
@supermodel ?= new SuperModel() @supermodel ?= new SuperModel()
@supermodel.resetProgress() @supermodel.resetProgress()
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @task.getLevelName(), sessionID: @task.getFirstSessionID(), headless: true @levelLoader = new LevelLoader supermodel: @supermodel, levelID: @task.getLevelName(), sessionID: @task.getFirstSessionID(), headless: true
if @supermodel.finished() if @supermodel.finished()
@simulateSingleGame() @simulateSingleGame()
else else
@listenToOnce @supermodel, 'loaded-all', @simulateSingleGame @listenToOnce @supermodel, 'loaded-all', @simulateSingleGame
simulateSingleGame: -> simulateSingleGame: ->
return if @destroyed return if @destroyed
@trigger 'statusUpdate', 'Simulating...' @trigger 'statusUpdate', 'Simulating...'
@ -55,32 +57,31 @@ module.exports = class Simulator extends CocoClass
@setupGod() @setupGod()
try try
@commenceSingleSimulation() @commenceSingleSimulation()
catch err catch error
console.log err @handleSingleSimulationError error
@handleSingleSimulationError()
commenceSingleSimulation: -> commenceSingleSimulation: ->
@god.createWorld @generateSpellsObject() @god.createWorld @generateSpellsObject()
Backbone.Mediator.subscribeOnce 'god:infinite-loop', @handleSingleSimulationInfiniteLoop, @ Backbone.Mediator.subscribeOnce 'god:infinite-loop', @handleSingleSimulationInfiniteLoop, @
Backbone.Mediator.subscribeOnce 'god:goals-calculated', @processSingleGameResults, @ Backbone.Mediator.subscribeOnce 'god:goals-calculated', @processSingleGameResults, @
handleSingleSimulationError: -> handleSingleSimulationError: (error) ->
console.log "There was an error simulating a single game!" console.error "There was an error simulating a single game!", error
if @options.headlessClient if @options.headlessClient
console.log "GAMERESULT:tie" console.log "GAMERESULT:tie"
process.exit(0) process.exit(0)
@cleanupSimulation() @cleanupSimulation()
handleSingleSimulationInfiniteLoop: -> handleSingleSimulationInfiniteLoop: ->
console.log "There was an infinite loop in the single game!" console.log "There was an infinite loop in the single game!"
if @options.headlessClient if @options.headlessClient
console.log "GAMERESULT:tie" console.log "GAMERESULT:tie"
process.exit(0) process.exit(0)
@cleanupSimulation() @cleanupSimulation()
processSingleGameResults: (simulationResults) -> processSingleGameResults: (simulationResults) ->
console.log "Processing results!"
taskResults = @formTaskResultsObject simulationResults taskResults = @formTaskResultsObject simulationResults
console.log "Processing results:", taskResults
humanSessionRank = taskResults.sessions[0].metrics.rank humanSessionRank = taskResults.sessions[0].metrics.rank
ogreSessionRank = taskResults.sessions[1].metrics.rank ogreSessionRank = taskResults.sessions[1].metrics.rank
if @options.headlessClient if @options.headlessClient
@ -93,12 +94,12 @@ module.exports = class Simulator extends CocoClass
process.exit(0) process.exit(0)
else else
@sendSingleGameBackToServer(taskResults) @sendSingleGameBackToServer(taskResults)
@cleanupSimulation() @cleanupSimulation()
sendSingleGameBackToServer: (results) -> sendSingleGameBackToServer: (results) ->
@trigger 'statusUpdate', 'Simulation completed, sending results back to server!' @trigger 'statusUpdate', 'Simulation completed, sending results back to server!'
$.ajax $.ajax
url: "/queue/scoring/recordTwoGames" url: "/queue/scoring/recordTwoGames"
data: results data: results
@ -107,8 +108,8 @@ module.exports = class Simulator extends CocoClass
success: @handleTaskResultsTransferSuccess success: @handleTaskResultsTransferSuccess
error: @handleTaskResultsTransferError error: @handleTaskResultsTransferError
complete: @cleanupAndSimulateAnotherTask complete: @cleanupAndSimulateAnotherTask
fetchAndSimulateTask: => fetchAndSimulateTask: =>
return if @destroyed return if @destroyed
@ -134,7 +135,7 @@ module.exports = class Simulator extends CocoClass
console.error "There was a horrible Error: #{JSON.stringify errorData}" console.error "There was a horrible Error: #{JSON.stringify errorData}"
@trigger 'statusUpdate', 'There was an error fetching games to simulate. Retrying in 10 seconds.' @trigger 'statusUpdate', 'There was an error fetching games to simulate. Retrying in 10 seconds.'
@simulateAnotherTaskAfterDelay() @simulateAnotherTaskAfterDelay()
handleNoGamesResponse: -> handleNoGamesResponse: ->
info = 'Finding game to simulate...' info = 'Finding game to simulate...'
@ -191,7 +192,7 @@ module.exports = class Simulator extends CocoClass
setupGod: -> setupGod: ->
@god.setLevel @level.serialize @supermodel @god.setLevel @level.serialize @supermodel
@god.setLevelSessionIDs (session.id for session in @task.getSessions()) @god.setLevelSessionIDs (session.sessionID for session in @task.getSessions())
@god.setWorldClassMap @world.classMap @god.setWorldClassMap @world.classMap
@god.setGoalManager new GoalManager(@world, @level.get 'goals') @god.setGoalManager new GoalManager(@world, @level.get 'goals')
@ -235,7 +236,7 @@ module.exports = class Simulator extends CocoClass
sendResultsBackToServer: (results) -> sendResultsBackToServer: (results) ->
@trigger 'statusUpdate', 'Simulation completed, sending results back to server!' @trigger 'statusUpdate', 'Simulation completed, sending results back to server!'
console.log "Sending result back to server!", results console.log "Sending result back to server:", results
if @options.headlessClient and @options.testing if @options.headlessClient and @options.testing
return @fetchAndSimulateTask() return @fetchAndSimulateTask()
@ -250,9 +251,10 @@ module.exports = class Simulator extends CocoClass
complete: @cleanupAndSimulateAnotherTask complete: @cleanupAndSimulateAnotherTask
handleTaskResultsTransferSuccess: (result) => handleTaskResultsTransferSuccess: (result) =>
return if @destroyed
console.log "Task registration result: #{JSON.stringify result}" console.log "Task registration result: #{JSON.stringify result}"
@trigger 'statusUpdate', 'Results were successfully sent back to server!' @trigger 'statusUpdate', 'Results were successfully sent back to server!'
console.log "Simulated by you: " + @simulatedByYou console.log "Simulated by you:", @simulatedByYou
@simulatedByYou++ @simulatedByYou++
unless @options.headlessClient unless @options.headlessClient
simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1 simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1
@ -260,10 +262,12 @@ module.exports = class Simulator extends CocoClass
application.tracker?.trackEvent 'Simulator Result', label: "Success" application.tracker?.trackEvent 'Simulator Result', label: "Success"
handleTaskResultsTransferError: (error) => handleTaskResultsTransferError: (error) =>
return if @destroyed
@trigger 'statusUpdate', 'There was an error sending the results back to the server.' @trigger 'statusUpdate', 'There was an error sending the results back to the server.'
console.log "Task registration error: #{JSON.stringify error}" console.log "Task registration error: #{JSON.stringify error}"
cleanupAndSimulateAnotherTask: => cleanupAndSimulateAnotherTask: =>
return if @destroyed
@cleanupSimulation() @cleanupSimulation()
@fetchAndSimulateTask() @fetchAndSimulateTask()
@ -472,6 +476,5 @@ class SimulationTask
spellKey = pathComponents.join '/' spellKey = pathComponents.join '/'
@thangSpells[thang.id].push spellKey @thangSpells[thang.id].push spellKey
if not method.cloneOf and spellKey is desiredSpellKey if not method.cloneOf and spellKey is desiredSpellKey
console.log "Setting #{desiredSpellKey} from world!" #console.log "Setting #{desiredSpellKey} from world!"
return method.source return method.source

View file

@ -33,6 +33,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
camera: null camera: null
spriteSheetCache: null spriteSheetCache: null
showInvisible: false showInvisible: false
async: true
possessed: false possessed: false
flipped: false flipped: false
@ -75,28 +76,32 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@ranges = [] @ranges = []
@handledDisplayEvents = {} @handledDisplayEvents = {}
@age = 0 @age = 0
@stillLoading = true
if @thangType.isFullyLoaded() if @thangType.isFullyLoaded()
@setupSprite() @setupSprite()
else else
@stillLoading = true
@thangType.fetch() @thangType.fetch()
@listenToOnce(@thangType, 'sync', @setupSprite) @listenToOnce(@thangType, 'sync', @setupSprite)
setupSprite: -> setupSprite: ->
for trigger, sounds of @thangType.get('soundTriggers') or {} when trigger isnt 'say' for trigger, sounds of @thangType.get('soundTriggers') or {} when trigger isnt 'say'
AudioPlayer.preloadSoundReference sound for sound in sounds AudioPlayer.preloadSoundReference sound for sound in sounds
@stillLoading = false
if @thangType.get('raster') if @thangType.get('raster')
@stillLoading = false
@actions = {} @actions = {}
@isRaster = true @isRaster = true
@setUpRasterImage() @setUpRasterImage()
else else
@actions = @thangType.getActions() result = @buildSpriteSheet()
@buildFromSpriteSheet @buildSpriteSheet() if _.isString result # async build
@createMarks() @listenToOnce @thangType, 'build-complete', @setupSprite
else
@stillLoading = false
@actions = @thangType.getActions()
@buildFromSpriteSheet result
@createMarks()
finishSetup: -> finishSetup: ->
return unless @thang
@updateBaseScale() @updateBaseScale()
@scaleFactor = @thang.scaleFactor if @thang?.scaleFactor @scaleFactor = @thang.scaleFactor if @thang?.scaleFactor
@update true # Reflect initial scale and other state @update true # Reflect initial scale and other state
@ -120,7 +125,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
buildSpriteSheet: -> buildSpriteSheet: ->
options = _.extend @options, @thang?.getSpriteOptions?() ? {} options = _.extend @options, @thang?.getSpriteOptions?() ? {}
options.colorConfig = @options.colorConfig if @options.colorConfig options.colorConfig = @options.colorConfig if @options.colorConfig
options.async = false options.async = @options.async
@thangType.getSpriteSheet options @thangType.getSpriteSheet options
setImageObject: (newImageObject) -> setImageObject: (newImageObject) ->
@ -677,6 +682,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
updateGold: -> updateGold: ->
# TODO: eventually this should be moved into some sort of team-based update # TODO: eventually this should be moved into some sort of team-based update
# rather than an each-thang-that-shows-gold-per-team thing. # rather than an each-thang-that-shows-gold-per-team thing.
return unless @thang
return if @thang.gold is @lastGold return if @thang.gold is @lastGold
gold = Math.floor @thang.gold gold = Math.floor @thang.gold
if @thang.world.age is 0 if @thang.world.age is 0
@ -752,7 +758,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
endFunc = => endFunc = =>
@lastTween = null @lastTween = null
@imageObject.gotoAndPlay(endAnimation) @imageObject.gotoAndPlay(endAnimation) unless @stillLoading
@shadow.action = 'idle' @shadow.action = 'idle'
@update true @update true
@possessed = false @possessed = false

View file

@ -181,7 +181,8 @@ module.exports = class Mark extends CocoClass
return @listenToOnce(@thangType, 'sync', @onLoadedThangType) if not @thangType.loaded return @listenToOnce(@thangType, 'sync', @onLoadedThangType) if not @thangType.loaded
CocoSprite = require './CocoSprite' CocoSprite = require './CocoSprite'
markSprite = new CocoSprite @thangType, @thangType.spriteOptions # don't bother with making these render async for now, but maybe later for fun and more complexity of code
markSprite = new CocoSprite @thangType, {async: false}
markSprite.queueAction 'idle' markSprite.queueAction 'idle'
@mark = markSprite.imageObject @mark = markSprite.imageObject
@markSprite = markSprite @markSprite = markSprite
@ -234,28 +235,27 @@ module.exports = class Mark extends CocoClass
if @name is 'debug' or (@name is 'shadow' and @sprite.thang?.shape in ["rectangle", "box"]) if @name is 'debug' or (@name is 'shadow' and @sprite.thang?.shape in ["rectangle", "box"])
@mark.rotation = @sprite.thang.rotation * 180 / Math.PI @mark.rotation = @sprite.thang.rotation * 180 / Math.PI
updateScale: -> updateScale: (log) ->
if @name is 'bounds' and (@sprite.thang.width isnt @lastWidth or @sprite.thang.height isnt @lastHeight) if @name is 'bounds' and (@sprite.thang.width isnt @lastWidth or @sprite.thang.height isnt @lastHeight)
oldMark = @mark oldMark = @mark
@buildBounds() @buildBounds()
oldMark.parent.addChild @mark oldMark.parent.addChild @mark
oldMark.parent.swapChildren oldMark, @mark oldMark.parent.swapChildren oldMark, @mark
oldMark.parent.removeChild oldMark oldMark.parent.removeChild oldMark
if @markSprite?
@markSprite.scaleFactor = 1.2
@markSprite.updateScale()
return unless @name in ["selection", "target", "repair", "highlight"] return unless @name in ["selection", "target", "repair", "highlight"]
scale = 0.5
if @sprite?.imageObject if @sprite?.imageObject
size = @sprite.getAverageDimension() size = @sprite.getAverageDimension()
size += 60 if @name is 'selection' size += 60 if @name is 'selection'
size += 60 if @name is 'repair' size += 60 if @name is 'repair'
size *= @sprite.scaleFactor size *= @sprite.scaleFactor
scale = size / {selection: 128, target: 128, repair: 320, highlight: 160}[@name] scale = size / {selection: 128, target: 128, repair: 320, highlight: 160}[@name]
scale /= 3
if @sprite?.thang.spriteName.search(/(dungeon|indoor).wall/i) isnt -1 if @sprite?.thang.spriteName.search(/(dungeon|indoor).wall/i) isnt -1
scale *= 2 scale *= 2
if @markSprite?
@markSprite.scaleFactor = scale
@markSprite.updateScale()
else
@mark.scaleX = @mark.scaleY = Math.min 1, scale @mark.scaleX = @mark.scaleY = Math.min 1, scale
if @name in ['selection', 'target', 'repair'] if @name in ['selection', 'target', 'repair']
@mark.scaleY *= @camera.y2x # code applies perspective @mark.scaleY *= @camera.y2x # code applies perspective

View file

@ -21,21 +21,33 @@ module.exports = class MusicPlayer extends CocoClass
onPlayMusic: (e) -> onPlayMusic: (e) ->
src = e.file src = e.file
if src src = "/file#{e.file}#{AudioPlayer.ext}"
src = "/file#{src}#{AudioPlayer.ext}" if (not e.file) or src is @currentMusic?.src
return @currentMusic.play('none', 0, 0, -1, 0.3) if src is @currentMusic?.src if e.play then @restartCurrentMusic() else @fadeOutCurrentMusic()
media = AudioPlayer.getStatus(src) return
if not media?.loaded
AudioPlayer.preloadSound(src) media = AudioPlayer.getStatus(src)
@standingBy = e if not media?.loaded
return AudioPlayer.preloadSound(src)
@standingBy = e
return
@standingBy = null @standingBy = null
if @currentMusic @fadeOutCurrentMusic()
f = -> @stop() @startNewMusic(src) if e.play
createjs.Tween.get(@currentMusic).to({volume:0.0}, CROSSFADE_LENGTH).call(f)
restartCurrentMusic: ->
@currentMusic = createjs.Sound.play(src, 'none', 0, 0, -1, 0.3) if src and e.play return unless @currentMusic
@currentMusic.play('none', 0, 0, -1, 0.3)
@updateMusicVolume()
fadeOutCurrentMusic: ->
return unless @currentMusic
f = -> @stop()
createjs.Tween.get(@currentMusic).to({volume:0.0}, CROSSFADE_LENGTH).call(f)
startNewMusic: (src) ->
@currentMusic = createjs.Sound.play(src, 'none', 0, 0, -1, 0.3) if src
return unless @currentMusic return unless @currentMusic
@currentMusic.volume = 0.0 @currentMusic.volume = 0.0
if me.get('music') if me.get('music')

View file

@ -103,6 +103,7 @@ module.exports = Surface = class Surface extends CocoClass
@stage.removeAllChildren() @stage.removeAllChildren()
@stage.removeEventListener 'stagemousemove', @onMouseMove @stage.removeEventListener 'stagemousemove', @onMouseMove
@stage.removeEventListener 'stagemousedown', @onMouseDown @stage.removeEventListener 'stagemousedown', @onMouseDown
@stage.removeEventListener 'stagemouseup', @onMouseUp
@stage.removeAllEventListeners() @stage.removeAllEventListeners()
@stage.enableDOMEvents false @stage.enableDOMEvents false
@stage.enableMouseOver 0 @stage.enableMouseOver 0
@ -281,6 +282,7 @@ module.exports = Surface = class Surface extends CocoClass
onSetPlaying: (e) -> onSetPlaying: (e) ->
@playing = (e ? {}).playing ? true @playing = (e ? {}).playing ? true
@setPlayingCalled = true
if @playing and @currentFrame >= (@world.totalFrames - 5) if @playing and @currentFrame >= (@world.totalFrames - 5)
@currentFrame = 0 @currentFrame = 0
if @fastForwarding and not @playing if @fastForwarding and not @playing
@ -352,6 +354,7 @@ module.exports = Surface = class Surface extends CocoClass
@casting = true @casting = true
@wasPlayingWhenCastingBegan = @playing @wasPlayingWhenCastingBegan = @playing
Backbone.Mediator.publish 'level-set-playing', { playing: false } Backbone.Mediator.publish 'level-set-playing', { playing: false }
@setPlayingCalled = false # don't overwrite playing settings if they changed by, say, scripts
if @coordinateDisplay? if @coordinateDisplay?
@surfaceTextLayer.removeChild @coordinateDisplay @surfaceTextLayer.removeChild @coordinateDisplay
@ -370,7 +373,7 @@ module.exports = Surface = class Surface extends CocoClass
# This has a tendency to break scripts that are waiting for playback to change when the level is loaded # This has a tendency to break scripts that are waiting for playback to change when the level is loaded
# so only run it after the first world is created. # so only run it after the first world is created.
Backbone.Mediator.publish 'level-set-playing', { playing: @wasPlayingWhenCastingBegan } unless event.firstWorld Backbone.Mediator.publish 'level-set-playing', { playing: @wasPlayingWhenCastingBegan } unless event.firstWorld or @setPlayingCalled
fastForwardTo = null fastForwardTo = null
if @playing if @playing
@ -414,6 +417,7 @@ module.exports = Surface = class Surface extends CocoClass
@stage.enableMouseOver(10) @stage.enableMouseOver(10)
@stage.addEventListener 'stagemousemove', @onMouseMove @stage.addEventListener 'stagemousemove', @onMouseMove
@stage.addEventListener 'stagemousedown', @onMouseDown @stage.addEventListener 'stagemousedown', @onMouseDown
@stage.addEventListener 'stagemouseup', @onMouseUp
@canvas.on 'mousewheel', @onMouseWheel @canvas.on 'mousewheel', @onMouseWheel
@hookUpChooseControls() if @options.choosing @hookUpChooseControls() if @options.choosing
createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED
@ -537,6 +541,11 @@ module.exports = Surface = class Surface extends CocoClass
onBackground = not @stage.hitTest e.stageX, e.stageY onBackground = not @stage.hitTest e.stageX, e.stageY
Backbone.Mediator.publish 'surface:stage-mouse-down', onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e Backbone.Mediator.publish 'surface:stage-mouse-down', onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e
onMouseUp: (e) =>
return if @disabled
onBackground = not @stage.hitTest e.stageX, e.stageY
Backbone.Mediator.publish 'surface:stage-mouse-up', onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e
onMouseWheel: (e) => onMouseWheel: (e) =>
# https://github.com/brandonaaron/jquery-mousewheel # https://github.com/brandonaaron/jquery-mousewheel
e.preventDefault() e.preventDefault()

View file

@ -39,8 +39,6 @@ module.exports = class WizardSprite extends IndieSprite
else if options.name else if options.name
@setNameLabel options.name @setNameLabel options.name
finishSetup: -> # No initial setup update needed.
makeIndieThang: (thangType, thangID, pos) -> makeIndieThang: (thangType, thangID, pos) ->
thang = super thangType, thangID, pos thang = super thangType, thangID, pos
thang.isSelectable = false thang.isSelectable = false
@ -49,6 +47,13 @@ module.exports = class WizardSprite extends IndieSprite
thang.pos.z += thang.bobHeight thang.pos.z += thang.bobHeight
thang thang
finishSetup: ->
@updateBaseScale()
@scaleFactor = @thang.scaleFactor if @thang?.scaleFactor
@updateScale()
@updateRotation()
# Don't call general update() because Thang isn't built yet
onPlayerStatesChanged: (e) -> onPlayerStatesChanged: (e) ->
for playerID, state of e.states for playerID, state of e.states
continue unless playerID is @thang.id continue unless playerID is @thang.id

View file

@ -14,6 +14,7 @@ module.exports = class GoalManager extends CocoClass
# If you want weird goals or hybrid goals, make a custom goal. # If you want weird goals or hybrid goals, make a custom goal.
nextGoalID: 0 nextGoalID: 0
nicks: ["GoalManager"]
constructor: (@world, @initialGoals, @team) -> constructor: (@world, @initialGoals, @team) ->
super() super()

View file

@ -185,6 +185,22 @@ module.exports.thangNames = thangNames =
"Lacos" "Lacos"
"Upfish" "Upfish"
] ]
"Ogre Peon M": [
"Durbo"
"Kurger"
"Mudwich"
"Ba Bo"
"Zugger"
"Toe Pod"
]
"Ogre Peon F": [
"Iblet"
"Lorba"
"Zzoya"
"Yamra"
"Greeke"
"Vapa"
]
"Ogre M": [ "Ogre M": [
"Krogg" "Krogg"
"Dronck" "Dronck"

View file

@ -3,10 +3,10 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
loading: "Carregando..." loading: "Carregando..."
saving: "Salvando..." saving: "Salvando..."
sending: "Enviando..." sending: "Enviando..."
# send: "Send" send: "Enviar"
cancel: "Cancelar" cancel: "Cancelar"
save: "Salvar" save: "Salvar"
# publish: "Publish" publish: "Publicar"
create: "Criar" create: "Criar"
delay_1_sec: "1 segundo" delay_1_sec: "1 segundo"
delay_3_sec: "3 segundos" delay_3_sec: "3 segundos"
@ -14,7 +14,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
manual: "Manual" manual: "Manual"
fork: "Fork" fork: "Fork"
play: "Jogar" play: "Jogar"
# retry: "Retry" retry: "Tente novamente"
# watch: "Watch" # watch: "Watch"
# unwatch: "Unwatch" # unwatch: "Unwatch"
# submit_patch: "Submit Patch" # submit_patch: "Submit Patch"
@ -36,11 +36,11 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
nav: nav:
play: "Jogar" play: "Jogar"
# community: "Community" community: "Comunidade"
editor: "Editor" editor: "Editor"
blog: "Blog" blog: "Blog"
forum: "Fórum" forum: "Fórum"
# account: "Account" account: "Conta"
admin: "Administrador" admin: "Administrador"
home: "Início" home: "Início"
contribute: "Contribuir" contribute: "Contribuir"
@ -78,7 +78,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
creating: "Criando a nova conta..." creating: "Criando a nova conta..."
sign_up: "Criar conta" sign_up: "Criar conta"
log_in: "Entre com a senha" log_in: "Entre com a senha"
# social_signup: "Or, you can sign up through Facebook or G+:" social_signup: "Ou, você pode fazer login pelo Facebook ou G+:"
home: home:
slogan: "Aprenda a programar em JavaScript enquanto se diverte com um jogo." slogan: "Aprenda a programar em JavaScript enquanto se diverte com um jogo."
@ -133,15 +133,15 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
wizard_settings: wizard_settings:
title: "Configurações do Feiticeiro" title: "Configurações do Feiticeiro"
customize_avatar: "Personalize o seu Avatar" customize_avatar: "Personalize o seu Avatar"
# active: "Active" active: "Ativo"
# color: "Color" color: "Cor"
# group: "Group" group: "Grupo"
clothes: "Roupas" clothes: "Roupas"
trim: "Aparar" trim: "Aparar"
cloud: "Nuvem" cloud: "Nuvem"
# team: "Team" team: "Time"
spell: "Feitiço" spell: "Feitiço"
boots: "Boots" boots: "Botas"
hue: "Matiz" hue: "Matiz"
saturation: "Saturação" saturation: "Saturação"
lightness: "Luminosidade" lightness: "Luminosidade"
@ -152,7 +152,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
autosave: "As alterações serão salvas automaticamente." autosave: "As alterações serão salvas automaticamente."
me_tab: "Eu" me_tab: "Eu"
picture_tab: "Foto" picture_tab: "Foto"
# upload_picture: "Upload a picture" upload_picture: "Enviar uma foto"
wizard_tab: "Feiticeiro" wizard_tab: "Feiticeiro"
password_tab: "Senha" password_tab: "Senha"
emails_tab: "Emails" emails_tab: "Emails"
@ -167,8 +167,8 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
# email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity." # email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
# email_any_notes: "Any Notifications" # email_any_notes: "Any Notifications"
# email_any_notes_description: "Disable to stop all activity notification emails." # email_any_notes_description: "Disable to stop all activity notification emails."
# email_recruit_notes: "Job Opportunities" email_recruit_notes: "Oportunidades de emprego"
# email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job." email_recruit_notes_description: "Se você jogar muito bem, nós podemos lhe contactar para lhe oferecer um emprego (melhor)"
contributor_emails: "Emails para as Classes de Contribuidores" contributor_emails: "Emails para as Classes de Contribuidores"
contribute_prefix: "Estamos procurando pessoas para se juntar à nossa turma! Confira a nossa " contribute_prefix: "Estamos procurando pessoas para se juntar à nossa turma! Confira a nossa "
contribute_page: "página de contribuição" contribute_page: "página de contribuição"
@ -180,22 +180,22 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
# job_profile: "Job Profile" # job_profile: "Job Profile"
# job_profile_approved: "Your job profile has been approved by CodeCombat. Employers will be able to see it until you either mark it inactive or it has not been changed for four weeks." # job_profile_approved: "Your job profile has been approved by CodeCombat. Employers will be able to see it until you either mark it inactive or it has not been changed for four weeks."
# job_profile_explanation: "Hi! Fill this out, and we will get in touch about finding you a software developer job." # job_profile_explanation: "Hi! Fill this out, and we will get in touch about finding you a software developer job."
# sample_profile: "See a sample profile" sample_profile: "Veja um perfil de exemplo"
# view_profile: "View Your Profile" view_profile: "Visualizar seu perfil"
account_profile: account_profile:
edit_settings: "Editar as configurações" edit_settings: "Editar as configurações"
profile_for_prefix: "Perfil de " profile_for_prefix: "Perfil de "
profile_for_suffix: "" profile_for_suffix: ""
# approved: "Approved" approved: "Aprovado"
# not_approved: "Not Approved" not_approved: "Não Aprovado"
# looking_for: "Looking for:" # looking_for: "Looking for:"
# last_updated: "Last updated:" last_updated: "Última atualização:"
# contact: "Contact" contact: "Contato"
# work_experience: "Work Experience" # work_experience: "Work Experience"
# education: "Education" education: "Formação"
# our_notes: "Our Notes" # our_notes: "Our Notes"
# projects: "Projects" projects: "Projetos"
# employers: # employers:
# want_to_hire_our_players: "Want to hire expert CodeCombat players?" # want_to_hire_our_players: "Want to hire expert CodeCombat players?"
@ -203,8 +203,8 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
# candidates_count_prefix: "We currently have " # candidates_count_prefix: "We currently have "
# candidates_count_many: "many" # candidates_count_many: "many"
# candidates_count_suffix: "highly skilled and vetted developers looking for work." # candidates_count_suffix: "highly skilled and vetted developers looking for work."
# candidate_name: "Name" candidate_name: "Nome"
# candidate_location: "Location" candidate_location: "Localização"
# candidate_looking_for: "Looking For" # candidate_looking_for: "Looking For"
# candidate_role: "Role" # candidate_role: "Role"
# candidate_top_skills: "Top Skills" # candidate_top_skills: "Top Skills"
@ -421,7 +421,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
results: "Resultados" results: "Resultados"
description: "Descrição" description: "Descrição"
or: "ou" or: "ou"
# subject: "Subject" subject: "Assunto"
email: "Email" email: "Email"
password: "Senha" password: "Senha"
message: "Mensagem" message: "Mensagem"
@ -437,7 +437,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
easy: "Fácil" easy: "Fácil"
medium: "Médio" medium: "Médio"
hard: "Difícil" hard: "Difícil"
# player: "Player" player: "Jogador"
about: about:
who_is_codecombat: "Quem é CodeCombat?" who_is_codecombat: "Quem é CodeCombat?"
@ -705,8 +705,8 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
# unknown: "Unknown error." # unknown: "Unknown error."
# resources: # resources:
# your_sessions: "Your Sessions" your_sessions: "Suas sessões"
# level: "Level" level: "vel"
# social_network_apis: "Social Network APIs" # social_network_apis: "Social Network APIs"
# facebook_status: "Facebook Status" # facebook_status: "Facebook Status"
# facebook_friends: "Facebook Friends" # facebook_friends: "Facebook Friends"
@ -719,27 +719,27 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
# patches: "Patches" # patches: "Patches"
# patched_model: "Source Document" # patched_model: "Source Document"
# model: "Model" # model: "Model"
# system: "System" system: "Sistema"
# component: "Component" component: "Componente"
# components: "Components" components: "Componentes"
# thang: "Thang" # thang: "Thang"
# thangs: "Thangs" # thangs: "Thangs"
# level_session: "Your Session" level_session: "Sua sessão"
# opponent_session: "Opponent Session" # opponent_session: "Opponent Session"
# article: "Article" article: "Artigos"
# user_names: "User Names" user_names: "Nomes de usuário"
# thang_names: "Thang Names" # thang_names: "Thang Names"
# files: "Files" files: "Arquivos"
# top_simulators: "Top Simulators" # top_simulators: "Top Simulators"
# source_document: "Source Document" # source_document: "Source Document"
# document: "Document" document: "Documento"
# sprite_sheet: "Sprite Sheet" sprite_sheet: "Planilha"
# delta: # delta:
# added: "Added" added: "Adicionado"
# modified: "Modified" modified: "Modificado"
# deleted: "Deleted" deleted: "Removido"
# moved_index: "Moved Index" # moved_index: "Moved Index"
# text_diff: "Text Diff" # text_diff: "Text Diff"
# merge_conflict_with: "MERGE CONFLICT WITH" merge_conflict_with: "CONFLITO DE MERGE COM"
# no_changes: "No Changes" no_changes: "Sem mudanças"

View file

@ -37,7 +37,7 @@ class CocoModel extends Backbone.Model
@loading = false @loading = false
@markToRevert() @markToRevert()
@loadFromBackup() @loadFromBackup()
getNormalizedURL: -> "#{@urlRoot}/#{@id}" getNormalizedURL: -> "#{@urlRoot}/#{@id}"
set: -> set: ->
@ -69,11 +69,17 @@ class CocoModel extends Backbone.Model
@set 'editPath', document.location.pathname @set 'editPath', document.location.pathname
options ?= {} options ?= {}
success = options.success success = options.success
options.success = (resp) => error = options.error
options.success = (model, res) =>
@trigger "save:success", @ @trigger "save:success", @
success(@, resp) if success success(@, res) if success
@markToRevert() @markToRevert()
@clearBackup() @clearBackup()
options.error = (model, res) =>
error(@, res) if error
errorMessage = "Error saving #{@get('name') ? @type()}"
console.error errorMessage, res.responseJSON
noty text: "#{errorMessage}: #{res.status} #{res.statusText}", layout: 'topCenter', type: 'error', killer: false, timeout: 10000
@trigger "save", @ @trigger "save", @
return super attrs, options return super attrs, options
@ -164,7 +170,7 @@ class CocoModel extends Backbone.Model
getDelta: -> getDelta: ->
differ = deltasLib.makeJSONDiffer() differ = deltasLib.makeJSONDiffer()
differ.diff @_revertAttributes, @attributes differ.diff @_revertAttributes, @attributes
getDeltaWith: (otherModel) -> getDeltaWith: (otherModel) ->
differ = deltasLib.makeJSONDiffer() differ = deltasLib.makeJSONDiffer()
differ.diff @attributes, otherModel.attributes differ.diff @attributes, otherModel.attributes

View file

@ -17,6 +17,16 @@ module.exports = class SuperModel extends Backbone.Model
# necessarily have the same model or collection that was passed in, if it was fetched from # necessarily have the same model or collection that was passed in, if it was fetched from
# the cache. # the cache.
report: ->
# Useful for debugging why a SuperModel never finishes loading.
console.info "SuperModel report ------------------------"
console.info "#{_.values(@resources).length} resources."
unfinished = []
for resource in _.values(@resources) when resource
console.info '\t', resource.name, "loaded", resource.isLoaded
unfinished.push resource unless resource.isLoaded
unfinished
loadModel: (model, name, fetchOptions, value=1) -> loadModel: (model, name, fetchOptions, value=1) ->
cachedModel = @getModelByURL(model.getURL()) cachedModel = @getModelByURL(model.getURL())
if cachedModel if cachedModel
@ -118,6 +128,9 @@ module.exports = class SuperModel extends Backbone.Model
@storeResource(res, value) @storeResource(res, value)
return res return res
removeModelResource: (modelOrCollection) ->
@removeResource _.find(@resources, (resource) -> resource?.model is modelOrCollection)
addRequestResource: (name, jqxhrOptions, value=1) -> addRequestResource: (name, jqxhrOptions, value=1) ->
@checkName(name) @checkName(name)
res = new RequestResource(name, jqxhrOptions, value) res = new RequestResource(name, jqxhrOptions, value)
@ -143,11 +156,20 @@ module.exports = class SuperModel extends Backbone.Model
@denom += value @denom += value
_.defer @updateProgress if @denom _.defer @updateProgress if @denom
removeResource: (resource) ->
return unless @resources[resource.rid]
@resources[resource.rid] = null
--@num if resource.isLoaded
--@denom
_.defer @updateProgress
onResourceLoaded: (r) -> onResourceLoaded: (r) ->
return unless @resources[r.rid]
@num += r.value @num += r.value
_.defer @updateProgress _.defer @updateProgress
onResourceFailed: (source) -> onResourceFailed: (source) ->
return unless @resources[r.rid]
@trigger('failed', source) @trigger('failed', source)
updateProgress: => updateProgress: =>
@ -160,13 +182,13 @@ module.exports = class SuperModel extends Backbone.Model
@progress = newProg @progress = newProg
@trigger('update-progress', @progress) @trigger('update-progress', @progress)
@trigger('loaded-all') if @finished() @trigger('loaded-all') if @finished()
setMaxProgress: (@maxProgress) -> setMaxProgress: (@maxProgress) ->
resetProgress: -> @progress = 0 resetProgress: -> @progress = 0
clearMaxProgress: -> clearMaxProgress: ->
@maxProgress = 1 @maxProgress = 1
_.defer @updateProgress _.defer @updateProgress
getProgress: -> return @progress getProgress: -> return @progress
getResource: (rid) -> getResource: (rid) ->

View file

@ -62,7 +62,7 @@ module.exports = class ThangType extends CocoModel
@options = @fillOptions options @options = @fillOptions options
key = @spriteSheetKey(@options) key = @spriteSheetKey(@options)
if ss = @spriteSheets[key] then return ss if ss = @spriteSheets[key] then return ss
return if @building[key] return key if @building[key]
@t0 = new Date().getTime() @t0 = new Date().getTime()
@initBuild(options) @initBuild(options)
@addGeneralFrames() unless @options.portraitOnly @addGeneralFrames() unless @options.portraitOnly
@ -151,25 +151,34 @@ module.exports = class ThangType extends CocoModel
buildQueue.push @builder buildQueue.push @builder
@builder.t0 = new Date().getTime() @builder.t0 = new Date().getTime()
@builder.buildAsync() unless buildQueue.length > 1 @builder.buildAsync() unless buildQueue.length > 1
@builder.on 'complete', @onBuildSpriteSheetComplete, @, true, key @builder.on 'complete', @onBuildSpriteSheetComplete, @, true, [@builder, key, @options]
return true @builder = null
return key
spriteSheet = @builder.build() spriteSheet = @builder.build()
console.debug "Built #{@get('name')}#{if @options.portraitOnly then ' portrait' else ''} in #{new Date().getTime() - @t0}ms." @logBuild @t0, false, @options.portraitOnly
@spriteSheets[key] = spriteSheet @spriteSheets[key] = spriteSheet
delete @building[key] delete @building[key]
@builder = null
spriteSheet spriteSheet
onBuildSpriteSheetComplete: (e, key) -> onBuildSpriteSheetComplete: (e, data) ->
console.log "Built #{@get('name')}#{if @options.portraitOnly then ' portrait' else ''} async in #{new Date().getTime() - @builder.t0}ms." if @builder [builder, key, options] = data
@logBuild builder.t0, true, options.portraitOnly
buildQueue = buildQueue.slice(1) buildQueue = buildQueue.slice(1)
buildQueue[0].t0 = new Date().getTime() if buildQueue[0] buildQueue[0].t0 = new Date().getTime() if buildQueue[0]
buildQueue[0]?.buildAsync() buildQueue[0]?.buildAsync()
@spriteSheets[key] = e.target.spriteSheet @spriteSheets[key] = e.target.spriteSheet
delete @building[key] delete @building[key]
@trigger 'build-complete' @trigger 'build-complete', {key:key, thangType:@}
@builder = null
@vectorParser = null @vectorParser = null
logBuild: (startTime, async, portrait) ->
kind = if async then 'Async' else 'Sync '
portrait = if portrait then '(Portrait)' else ''
name = _.string.rpad @get('name'), 20
time = _.string.lpad '' + new Date().getTime() - startTime, 6
console.debug "Built sheet: #{name} #{time}ms #{kind} #{portrait}"
spriteSheetKey: (options) -> spriteSheetKey: (options) ->
colorConfigs = [] colorConfigs = []
for groupName, config of options.colorConfig or {} for groupName, config of options.colorConfig or {}
@ -196,6 +205,7 @@ module.exports = class ThangType extends CocoModel
options = if _.isPlainObject spriteOptionsOrKey then spriteOptionsOrKey else {} options = if _.isPlainObject spriteOptionsOrKey then spriteOptionsOrKey else {}
options.portraitOnly = true options.portraitOnly = true
spriteSheet = @buildSpriteSheet(options) spriteSheet = @buildSpriteSheet(options)
return if _.isString spriteSheet
return unless spriteSheet return unless spriteSheet
canvas = $("<canvas width='#{size}' height='#{size}'></canvas>") canvas = $("<canvas width='#{size}' height='#{size}'></canvas>")
stage = new createjs.Stage(canvas[0]) stage = new createjs.Stage(canvas[0])

View file

@ -223,19 +223,19 @@ _.extend LevelSessionSchema.properties,
sessionID: sessionID:
title: 'Opponent Session ID' title: 'Opponent Session ID'
description: 'The session ID of an opponent.' description: 'The session ID of an opponent.'
type: ['object', 'string'] type: ['object', 'string','null']
userID: userID:
title: 'Opponent User ID' title: 'Opponent User ID'
description: 'The user ID of an opponent' description: 'The user ID of an opponent'
type: ['object','string'] type: ['object','string','null']
name: name:
title: 'Opponent name' title: 'Opponent name'
description: 'The name of the opponent' description: 'The name of the opponent'
type: 'string' type: ['string','null']
totalScore: totalScore:
title: 'Opponent total score' title: 'Opponent total score'
description: 'The totalScore of a user when the match was computed' description: 'The totalScore of a user when the match was computed'
type: ['number','string'] type: ['number','string', 'null']
metrics: metrics:
type: 'object' type: 'object'
properties: properties:

View file

@ -15,8 +15,21 @@ module.exports =
type: "object" type: "object"
properties: properties:
response: response:
type: "string" type: "object"
properties:
status: { type: "string" }
authResponse:
type: "object"
properties:
accessToken: { type: "string" }
expiresIn: { type: "number" }
signedRequest: { type: "string" }
userID: { type: "string" }
required: ["response"] required: ["response"]
"facebook-logged-out": {}
"linkedin-loaded": {}
"gapi-loaded": "gapi-loaded":
{} # TODO schema {} # TODO schema

View file

@ -53,8 +53,9 @@ module.exports =
"level:team-set": "level:team-set":
{} # TODO schema {} # TODO schema
"level:docs-hidden": "level:docs-shown": {}
{} # TODO schema
"level:docs-hidden": {}
"level:victory-hidden": "level:victory-hidden":
{} # TODO schema {} # TODO schema
@ -74,9 +75,32 @@ module.exports =
"script:ended": "script:ended":
{} # TODO schema {} # TODO schema
"end-all-scripts": {}
"script:state-changed": "script:state-changed":
{} # TODO schema {} # TODO schema
'script-manager:tick':
type: 'object'
additionalProperties: false
properties:
scriptRunning: { type: 'string' }
noteGroupRunning: { type: 'string' }
timeSinceLastScriptEnded: { type: 'number' }
scriptStates:
type: 'object'
additionalProperties:
title: 'Script State'
type: 'object'
additionalProperties: false
properties:
timeSinceLastEnded:
type: 'number'
description: 'seconds since this script ended last'
timeSinceLastTriggered:
type: 'number'
description: 'seconds since this script was triggered last'
"play-sound": "play-sound":
{} # TODO schema {} # TODO schema
@ -114,5 +138,21 @@ module.exports =
"level-scrub-back": "level-scrub-back":
{} # TODO schema {} # TODO schema
"level-show-victory":
type: 'object'
additionalProperties: false
properties:
showModal: { type: 'boolean' }
"level-highlight-dom":
type: 'object'
additionalProperties: false
properties:
selector: { type: 'string' }
delay: { type: 'number' }
sides: { type: 'array', items: { 'enum': ['left', 'right', 'top', 'bottom'] }}
offset: { type: 'object' }
rotation: { type: 'number' }
"goal-manager:new-goal-states": "goal-manager:new-goal-states":
{} # TODO schema {} # TODO schema

View file

@ -638,7 +638,7 @@ block content
.tab-pane.well#rules .tab-pane.well#rules
h1(data-i18n="ladder.tournament_rules") Tournament Rules h1(data-i18n="ladder.tournament_rules") Tournament Rules
h2 General 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. 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. Although we don't anticipate the rules changing, they are subject to change.
h2 Dates and Times 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. 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.

View file

@ -39,11 +39,10 @@ module.exports = class ThangsTabView extends View
'level-thang-edited': 'onLevelThangEdited' 'level-thang-edited': 'onLevelThangEdited'
'level-thang-done-editing': 'onLevelThangDoneEditing' 'level-thang-done-editing': 'onLevelThangDoneEditing'
'level:view-switched': 'onViewSwitched' 'level:view-switched': 'onViewSwitched'
'sprite:mouse-down': 'onSpriteMouseDown'
'sprite:dragged': 'onSpriteDragged' 'sprite:dragged': 'onSpriteDragged'
'sprite:mouse-up': 'onSpriteMouseUp' 'sprite:mouse-up': 'onSpriteMouseUp'
'sprite:double-clicked': 'onSpriteDoubleClicked' 'sprite:double-clicked': 'onSpriteDoubleClicked'
'surface:stage-mouse-down': 'onStageMouseDown' 'surface:stage-mouse-up': 'onStageMouseUp'
events: events:
'click #extant-thangs-filter button': 'onFilterExtantThangs' 'click #extant-thangs-filter button': 'onFilterExtantThangs'
@ -108,7 +107,7 @@ module.exports = class ThangsTabView extends View
afterRender: -> afterRender: ->
super() super()
return unless @supermodel.finished() return unless @supermodel.finished()
$('.tab-content').click @selectAddThang $('.tab-content').mousedown @selectAddThang
$('#thangs-list').bind 'mousewheel', @preventBodyScrollingInThangList $('#thangs-list').bind 'mousewheel', @preventBodyScrollingInThangList
@$el.find('#extant-thangs-filter button:first').button('toggle') @$el.find('#extant-thangs-filter button:first').button('toggle')
$(window).resize @onWindowResize $(window).resize @onWindowResize
@ -181,13 +180,13 @@ module.exports = class ThangsTabView extends View
onSpriteMouseDown: (e) -> onSpriteMouseDown: (e) ->
# Sprite clicks happen after stage clicks, but we need to know whether a sprite is being clicked. # Sprite clicks happen after stage clicks, but we need to know whether a sprite is being clicked.
clearTimeout @backgroundAddClickTimeout # clearTimeout @backgroundAddClickTimeout
if e.originalEvent.nativeEvent.button == 2 # if e.originalEvent.nativeEvent.button == 2
@onSpriteContextMenu e # @onSpriteContextMenu e
onStageMouseDown: (e) -> onStageMouseUp: (e) ->
if @addThangSprite if @addThangSprite
# If we click on the background, we need to add @addThangSprite, but not if onSpriteMouseDown will fire. # If we click on the background, we need to add @addThangSprite, but not if onSpriteMouseUp will fire.
@backgroundAddClickTimeout = _.defer => @onExtantThangSelected {} @backgroundAddClickTimeout = _.defer => @onExtantThangSelected {}
$('#contextmenu').hide() $('#contextmenu').hide()
@ -202,6 +201,9 @@ module.exports = class ThangsTabView extends View
@calculateMovement(stageX / w, stageY / h, w / h) @calculateMovement(stageX / w, stageY / h, w / h)
onSpriteMouseUp: (e) -> onSpriteMouseUp: (e) ->
clearTimeout @backgroundAddClickTimeout
if e.originalEvent.nativeEvent.button == 2
@onSpriteContextMenu e
clearInterval(@movementInterval) if @movementInterval? clearInterval(@movementInterval) if @movementInterval?
@movementInterval = null @movementInterval = null
@surface.camera.dragDisabled = false @surface.camera.dragDisabled = false

View file

@ -72,6 +72,9 @@ module.exports = class LadderSubmissionView extends CocoView
transpileSession: -> transpileSession: ->
submittedCode = @session.get('code') submittedCode = @session.get('code')
language = @session.get('codeLanguage') or 'javascript'
@session.set('submittedCodeLanguage', language)
@session.save() # TODO: maybe actually use a callback to make sure this works?
transpiledCode = {} transpiledCode = {}
for thang, spells of submittedCode for thang, spells of submittedCode
transpiledCode[thang] = {} transpiledCode[thang] = {}
@ -80,7 +83,7 @@ module.exports = class LadderSubmissionView extends CocoView
#DRY this #DRY this
aetherOptions = aetherOptions =
problems: {} problems: {}
language: "javascript" language: language
functionName: spellID functionName: spellID
functionParameters: [] functionParameters: []
yieldConditionally: spellID is "plan" yieldConditionally: spellID is "plan"

View file

@ -150,9 +150,12 @@ module.exports = class LadderTabView extends CocoView
# LADDER LOADING # LADDER LOADING
refreshLadder: -> refreshLadder: ->
@supermodel.resetProgress()
@ladderLimit ?= parseInt @getQueryVariable('top_players', 20) @ladderLimit ?= parseInt @getQueryVariable('top_players', 20)
for team in @teams for team in @teams
@leaderboards[team.id]?.destroy() if oldLeaderboard = @leaderboards[team.id]
@supermodel.removeModelResource oldLeaderboard
oldLeaderboard.destroy()
teamSession = _.find @sessions.models, (session) -> session.get('team') is team.id teamSession = _.find @sessions.models, (session) -> session.get('team') is team.id
@leaderboards[team.id] = new LeaderboardData(@level, team.id, teamSession, @ladderLimit) @leaderboards[team.id] = new LeaderboardData(@level, team.id, teamSession, @ladderLimit)
@leaderboardRes = @supermodel.addModelResource(@leaderboards[team.id], 'leaderboard', 3) @leaderboardRes = @supermodel.addModelResource(@leaderboards[team.id], 'leaderboard', 3)
@ -242,7 +245,7 @@ module.exports = class LadderTabView extends CocoView
if teamName.toLowerCase() is "humans" then rankClass = "rank-text humans-rank-text" if teamName.toLowerCase() is "humans" then rankClass = "rank-text humans-rank-text"
message = "#{histogramData.length} players" message = "#{histogramData.length} players"
if @leaderboards[teamName].session? if @leaderboards[teamName].session?
if @leaderboards[teamName].myRank <= histogramData.length if @leaderboards[teamName].myRank <= histogramData.length
message="##{@leaderboards[teamName].myRank} of #{histogramData.length}" message="##{@leaderboards[teamName].myRank} of #{histogramData.length}"
else else

View file

@ -65,7 +65,7 @@ module.exports = class LadderView extends RootView
@insertSubView(@simulateTab = new SimulateTabView()) @insertSubView(@simulateTab = new SimulateTabView())
@refreshInterval = setInterval(@fetchSessionsAndRefreshViews.bind(@), 20 * 1000) @refreshInterval = setInterval(@fetchSessionsAndRefreshViews.bind(@), 20 * 1000)
hash = document.location.hash[1..] if document.location.hash hash = document.location.hash[1..] if document.location.hash
if hash and not (hash in ['my-matches', 'simulate', 'ladder']) if hash and not (hash in ['my-matches', 'simulate', 'ladder', 'prizes', 'rules'])
@showPlayModal(hash) if @sessions.loaded @showPlayModal(hash) if @sessions.loaded
fetchSessionsAndRefreshViews: -> fetchSessionsAndRefreshViews: ->

View file

@ -26,6 +26,9 @@ module.exports = class MyMatchesTabView extends CocoView
for session in @sessions.models for session in @sessions.models
for match in (session.get('matches') or []) for match in (session.get('matches') or [])
id = match.opponents[0].userID id = match.opponents[0].userID
unless id
console.error "Found bad opponent ID in malformed match:", match, "from session", session
continue
ids.push id unless @nameMap[id] ids.push id unless @nameMap[id]
return @finishRendering() unless ids.length return @finishRendering() unless ids.length
@ -35,7 +38,7 @@ module.exports = class MyMatchesTabView extends CocoView
for session in @sessions.models for session in @sessions.models
for match in session.get('matches') or [] for match in session.get('matches') or []
opponent = match.opponents[0] opponent = match.opponents[0]
@nameMap[opponent.userID] ?= nameMap[opponent.userID].name @nameMap[opponent.userID] ?= nameMap[opponent.userID]?.name ? "<bad match data>"
@finishRendering() @finishRendering()
$.ajax('/db/user/-/names', { $.ajax('/db/user/-/names', {
@ -94,6 +97,7 @@ module.exports = class MyMatchesTabView extends CocoView
afterRender: -> afterRender: ->
super() super()
@removeSubView subview for key, subview of @subviews when subview instanceof LadderSubmissionView
@$el.find('.ladder-submission-view').each (i, el) => @$el.find('.ladder-submission-view').each (i, el) =>
placeholder = $(el) placeholder = $(el)
sessionID = placeholder.data('session-id') sessionID = placeholder.data('session-id')

View file

@ -13,6 +13,7 @@ module.exports = class DocsModal extends View
'enter': 'hide' 'enter': 'hide'
constructor: (options) -> constructor: (options) ->
@firstOnly = options.firstOnly
@docs = options?.docs @docs = options?.docs
general = @docs.generalArticles or [] general = @docs.generalArticles or []
specific = @docs.specificArticles or [] specific = @docs.specificArticles or []
@ -25,6 +26,7 @@ module.exports = class DocsModal extends View
@docs = specific.concat(general) @docs = specific.concat(general)
@docs = $.extend(true, [], @docs) @docs = $.extend(true, [], @docs)
@docs = [@docs[0]] if @firstOnly and @docs[0]
doc.html = marked(utils.i18n doc, 'body') for doc in @docs doc.html = marked(utils.i18n doc, 'body') for doc in @docs
doc.name = (utils.i18n doc, 'name') for doc in @docs doc.name = (utils.i18n doc, 'name') for doc in @docs
doc.slug = _.string.slugify(doc.name) for doc in @docs doc.slug = _.string.slugify(doc.name) for doc in @docs
@ -47,6 +49,10 @@ module.exports = class DocsModal extends View
clickTab: (e) => clickTab: (e) =>
@$el.find('li.active').removeClass('active') @$el.find('li.active').removeClass('active')
afterInsert: ->
super()
Backbone.Mediator.publish 'level:docs-shown'
onHidden: -> onHidden: ->
Backbone.Mediator.publish 'level:docs-hidden' Backbone.Mediator.publish 'level:docs-hidden'

View file

@ -21,8 +21,11 @@ module.exports = class ThangAvatarView extends View
unless @thangType.isFullyLoaded() or @thangType.loading unless @thangType.isFullyLoaded() or @thangType.loading
@thangType.fetch() @thangType.fetch()
@supermodel.loadModel @thangType, 'thang' # couldn't get the level view to load properly through the supermodel
# so just doing it manually this time.
@listenTo @thangType, 'sync', @render
@listenTo @thangType, 'build-complete', @render
getSpriteThangType: -> getSpriteThangType: ->
thangs = @supermodel.getModels(ThangType) thangs = @supermodel.getModels(ThangType)
@ -34,7 +37,7 @@ module.exports = class ThangAvatarView extends View
context = super context context = super context
context.thang = @thang context.thang = @thang
options = @thang?.getSpriteOptions() or {} options = @thang?.getSpriteOptions() or {}
options.async = false options.async = true
context.avatarURL = @thangType.getPortraitSource(options) unless @thangType.loading context.avatarURL = @thangType.getPortraitSource(options) unless @thangType.loading
context.includeName = @includeName context.includeName = @includeName
context context

View file

@ -153,6 +153,11 @@ module.exports = class SpellView extends View
name: 'spell-beautify' name: 'spell-beautify'
bindKey: {win: 'Ctrl-Shift-B', mac: 'Command-Shift-B|Ctrl-Shift-B'} bindKey: {win: 'Ctrl-Shift-B', mac: 'Command-Shift-B|Ctrl-Shift-B'}
exec: -> Backbone.Mediator.publish 'spell-beautify' exec: -> Backbone.Mediator.publish 'spell-beautify'
addCommand
name: 'prevent-line-jump'
bindKey: {win: 'Ctrl-L', mac: 'Command-L'}
passEvent: true
exec: -> # just prevent default ACE go-to-line alert
fillACE: -> fillACE: ->
@ace.setValue @spell.source @ace.setValue @spell.source

View file

@ -133,7 +133,10 @@ module.exports = class PlayLevelView extends View
showGuide: -> showGuide: ->
@seenDocs = true @seenDocs = true
DocsModal = require './level/modal/docs_modal' DocsModal = require './level/modal/docs_modal'
options = {docs: @levelLoader.level.get('documentation'), supermodel: @supermodel} options =
docs: @levelLoader.level.get('documentation')
supermodel: @supermodel
firstOnly: true
@openModalView(new DocsModal(options), true) @openModalView(new DocsModal(options), true)
Backbone.Mediator.subscribeOnce 'modal-closed', @onLevelStarted, @ Backbone.Mediator.subscribeOnce 'modal-closed', @onLevelStarted, @
return true return true
@ -287,6 +290,7 @@ module.exports = class PlayLevelView extends View
unless @isEditorPreview unless @isEditorPreview
@loadEndTime = new Date() @loadEndTime = new Date()
loadDuration = @loadEndTime - @loadStartTime loadDuration = @loadEndTime - @loadStartTime
console.debug "Level unveiled after #{(loadDuration / 1000).toFixed(2)}s"
application.tracker?.trackEvent 'Finished Level Load', level: @levelID, label: @levelID, loadDuration: loadDuration application.tracker?.trackEvent 'Finished Level Load', level: @levelID, label: @levelID, loadDuration: loadDuration
application.tracker?.trackTiming loadDuration, 'Level Load Time', @levelID, @levelID application.tracker?.trackTiming loadDuration, 'Level Load Time', @levelID, @levelID

View file

@ -55,6 +55,7 @@ addPairwiseTaskToQueue = (taskPair, cb) ->
if taskPairError? then return cb taskPairError if taskPairError? then return cb taskPairError
cb null cb null
# We should rip these out, probably
module.exports.resimulateAllSessions = (req, res) -> module.exports.resimulateAllSessions = (req, res) ->
unless isUserAdmin req then return errors.unauthorized res, "Unauthorized. Even if you are authorized, you shouldn't do this" unless isUserAdmin req then return errors.unauthorized res, "Unauthorized. Even if you are authorized, you shouldn't do this"
@ -99,6 +100,30 @@ resimulateSession = (originalLevelID, levelMajorVersion, session, cb) =>
if taskPairError? then return cb taskPairError, null if taskPairError? then return cb taskPairError, null
cb null cb null
selectRandomSkipIndex = (numberOfSessions) ->
numbers = [0...numberOfSessions]
numberWeights = []
lambda = 0.025
for number, index in numbers
numberWeights[index] = lambda*Math.exp(-1*lambda*number) + lambda/(numberOfSessions/15)
sum = numberWeights.reduce (a, b) -> a + b
for number,index in numberWeights
numberWeights[index] /= sum
rand = (min, max) -> Math.random() * (max - min) + min
totalWeight = 1
randomNumber = Math.random()
weightSum = 0
for number, i in numbers
weightSum += numberWeights[i]
if (randomNumber <= weightSum)
return numbers[i]
module.exports.getTwoGames = (req, res) -> module.exports.getTwoGames = (req, res) ->
#if userIsAnonymous req then return errors.unauthorized(res, "You need to be logged in to get games.") #if userIsAnonymous req then return errors.unauthorized(res, "You need to be logged in to get games.")
humansGameID = req.body.humansGameID humansGameID = req.body.humansGameID
@ -106,53 +131,64 @@ module.exports.getTwoGames = (req, res) ->
unless ogresGameID and humansGameID unless ogresGameID and humansGameID
#fetch random games here #fetch random games here
queryParams = queryParams =
"levelID":"greed" "levelID":"greed"
"submitted":true "submitted":true
"team":"humans" "team":"humans"
selection = "team totalScore transpiledCode teamSpells levelID creatorName creator" selection = "team totalScore transpiledCode teamSpells levelID creatorName creator submitDate"
LevelSession.count queryParams, (err, numberOfHumans) => LevelSession.count queryParams, (err, numberOfHumans) =>
query = LevelSession if err? then return errors.serverError(res, "Couldn't get the number of human games")
.find(queryParams) humanSkipCount = selectRandomSkipIndex(numberOfHumans)
.limit(1) ogreCountParams =
.select(selection) "levelID": "greed"
.skip(Math.floor(Math.random()*numberOfHumans)) "submitted":true
.lean() "team":"ogres"
query.exec (err, randomSession) => LevelSession.count ogreCountParams, (err, numberOfOgres) =>
if err? then return errors.serverError(res, "Couldn't select a top 15 random session!") if err? then return errors.serverError(res, "Couldnt' get the number of ogre games")
randomSession = randomSession[0] ogresSkipCount = selectRandomSkipIndex(numberOfOgres)
queryParams =
"levelID":"greed"
"submitted":true
"totalScore":
$lte: randomSession.totalScore
"team": "ogres"
query = LevelSession query = LevelSession
.find(queryParams) .aggregate()
.select(selection) .match(queryParams)
.sort(totalScore: -1) .project(selection)
.sort({"submitDate": -1})
.skip(humanSkipCount)
.limit(1) .limit(1)
.lean() query.exec (err, randomSession) =>
query.exec (err, otherSession) => if err? then return errors.serverError(res, "Couldn't select a random session! #{err}")
if err? then return errors.serverError(res, "Couldnt' select the other top 15 random session!") randomSession = randomSession[0]
otherSession = otherSession[0] queryParams =
taskObject = "levelID":"greed"
"messageGenerated": Date.now() "submitted":true
"sessions": [] "team": "ogres"
for session in [randomSession, otherSession] query = LevelSession
sessionInformation = .aggregate()
"sessionID": session._id .match(queryParams)
"team": session.team ? "No team" .project(selection)
"transpiledCode": session.transpiledCode .sort({"submitDate": -1})
"teamSpells": session.teamSpells ? {} .skip(ogresSkipCount)
"levelID": session.levelID .limit(1)
"creatorName": session.creatorName query.exec (err, otherSession) =>
"creator": session.creator if err? then return errors.serverError(res, "Couldnt' select the other random session!")
"totalScore": session.totalScore otherSession = otherSession[0]
taskObject =
taskObject.sessions.push sessionInformation "messageGenerated": Date.now()
sendResponseObject req, res, taskObject "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
console.log "Dispatching random game between", taskObject.sessions[0].creatorName, "and", taskObject.sessions[1].creatorName
sendResponseObject req, res, taskObject
else else
console.log "Directly simulating #{humansGameID} vs. #{ogresGameID}."
LevelSession.findOne(_id: humansGameID).lean().exec (err, humanSession) => LevelSession.findOne(_id: humansGameID).lean().exec (err, humanSession) =>
if err? then return errors.serverError(res, "Couldn't find the human game") if err? then return errors.serverError(res, "Couldn't find the human game")
LevelSession.findOne(_id: ogresGameID).lean().exec (err, ogreSession) => LevelSession.findOne(_id: ogresGameID).lean().exec (err, ogreSession) =>
@ -167,23 +203,25 @@ module.exports.getTwoGames = (req, res) ->
"transpiledCode": session.transpiledCode "transpiledCode": session.transpiledCode
"teamSpells": session.teamSpells ? {} "teamSpells": session.teamSpells ? {}
"levelID": session.levelID "levelID": session.levelID
taskObject.sessions.push sessionInformation taskObject.sessions.push sessionInformation
sendResponseObject req, res, taskObject sendResponseObject req, res, taskObject
module.exports.recordTwoGames = (req, res) -> module.exports.recordTwoGames = (req, res) ->
@clientResponseObject = req.body sessions = req.body.sessions
console.log "Recording non-chained result of", sessions?[0]?.name, sessions[0]?.metrics?.rank, "and", sessions?[1]?.name, sessions?[1]?.metrics?.rank
yetiGuru = clientResponseObject: req.body, isRandomMatch: true
async.waterfall [ async.waterfall [
fetchLevelSession.bind(@) fetchLevelSession.bind(yetiGuru)
updateSessions.bind(@) updateSessions.bind(yetiGuru)
indexNewScoreArray.bind(@) indexNewScoreArray.bind(yetiGuru)
addMatchToSessions.bind(@) addMatchToSessions.bind(yetiGuru)
updateUserSimulationCounts.bind(@, req.user._id) updateUserSimulationCounts.bind(yetiGuru, req.user._id)
], (err, successMessageObject) -> ], (err, successMessageObject) ->
if err? then return errors.serverError res, "There was an error recording the single game:#{err}" 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!"} sendResponseObject req, res, {"message":"The single game was submitted successfully!"}
module.exports.createNewTask = (req, res) -> module.exports.createNewTask = (req, res) ->
@ -193,12 +231,13 @@ module.exports.createNewTask = (req, res) ->
transpiledCode = req.body.transpiledCode transpiledCode = req.body.transpiledCode
requestLevelMajorVersion = parseInt(req.body.levelMajorVersion) requestLevelMajorVersion = parseInt(req.body.levelMajorVersion)
yetiGuru = {}
async.waterfall [ async.waterfall [
validatePermissions.bind(@,req,requestSessionID) validatePermissions.bind(yetiGuru,req,requestSessionID)
fetchAndVerifyLevelType.bind(@,currentLevelID) fetchAndVerifyLevelType.bind(yetiGuru,currentLevelID)
fetchSessionObjectToSubmit.bind(@, requestSessionID) fetchSessionObjectToSubmit.bind(yetiGuru, requestSessionID)
updateSessionToSubmit.bind(@, transpiledCode) updateSessionToSubmit.bind(yetiGuru, transpiledCode)
fetchInitialSessionsToRankAgainst.bind(@, requestLevelMajorVersion, originalLevelID) fetchInitialSessionsToRankAgainst.bind(yetiGuru, requestLevelMajorVersion, originalLevelID)
generateAndSendTaskPairsToTheQueue generateAndSendTaskPairsToTheQueue
], (err, successMessageObject) -> ], (err, successMessageObject) ->
if err? then return errors.serverError res, "There was an error submitting the game to the queue:#{err}" if err? then return errors.serverError res, "There was an error submitting the game to the queue:#{err}"
@ -256,9 +295,9 @@ updateSessionToSubmit = (transpiledCode, sessionToUpdate, callback) ->
submittedCode: sessionToUpdate.code submittedCode: sessionToUpdate.code
transpiledCode: transpiledCode transpiledCode: transpiledCode
submitDate: new Date() submitDate: new Date()
meanStrength: 25 #meanStrength: 25 # Let's try not resetting the score on resubmission
standardDeviation: 25/3 standardDeviation: 25/3
totalScore: 10 #totalScore: 10 # Let's try not resetting the score on resubmission
numberOfWinsAndTies: 0 numberOfWinsAndTies: 0
numberOfLosses: 0 numberOfLosses: 0
isRanking: true isRanking: true
@ -300,13 +339,14 @@ generateAndSendTaskPairsToTheQueue = (sessionToRankAgainst,submittedSession, cal
module.exports.dispatchTaskToConsumer = (req, res) -> module.exports.dispatchTaskToConsumer = (req, res) ->
yetiGuru = {}
async.waterfall [ async.waterfall [
checkSimulationPermissions.bind(@,req) checkSimulationPermissions.bind(yetiGuru,req)
receiveMessageFromSimulationQueue receiveMessageFromSimulationQueue
changeMessageVisibilityTimeout changeMessageVisibilityTimeout
parseTaskQueueMessage parseTaskQueueMessage
constructTaskObject constructTaskObject
constructTaskLogObject.bind(@, getUserIDFromRequest(req)) constructTaskLogObject.bind(yetiGuru, getUserIDFromRequest(req))
processTaskObject processTaskObject
], (err, taskObjectToSend) -> ], (err, taskObjectToSend) ->
if err? if err?
@ -357,7 +397,6 @@ constructTaskObject = (taskMessageBody, message, callback) ->
"sessionID": session._id "sessionID": session._id
"submitDate": session.submitDate "submitDate": session.submitDate
"team": session.team ? "No team" "team": session.team ? "No team"
"code": session.submittedCode
"transpiledCode": session.transpiledCode "transpiledCode": session.transpiledCode
"teamSpells": session.teamSpells ? {} "teamSpells": session.teamSpells ? {}
"levelID": session.levelID "levelID": session.levelID
@ -397,34 +436,38 @@ getSessionInformation = (sessionIDString, callback) ->
module.exports.processTaskResult = (req, res) -> module.exports.processTaskResult = (req, res) ->
originalSessionID = req.body?.originalSessionID originalSessionID = req.body?.originalSessionID
async.waterfall [ yetiGuru = {}
verifyClientResponse.bind(@,req.body) try
fetchTaskLog.bind(@) async.waterfall [
checkTaskLog.bind(@) verifyClientResponse.bind(yetiGuru,req.body)
deleteQueueMessage.bind(@) fetchTaskLog.bind(yetiGuru)
fetchLevelSession.bind(@) checkTaskLog.bind(yetiGuru)
checkSubmissionDate.bind(@) deleteQueueMessage.bind(yetiGuru)
logTaskComputation.bind(@) fetchLevelSession.bind(yetiGuru)
updateSessions.bind(@) checkSubmissionDate.bind(yetiGuru)
indexNewScoreArray.bind(@) logTaskComputation.bind(yetiGuru)
addMatchToSessions.bind(@) updateSessions.bind(yetiGuru)
updateUserSimulationCounts.bind(@, req.user._id) indexNewScoreArray.bind(yetiGuru)
determineIfSessionShouldContinueAndUpdateLog.bind(@) addMatchToSessions.bind(yetiGuru)
findNearestBetterSessionID.bind(@) updateUserSimulationCounts.bind(yetiGuru, req.user._id)
addNewSessionsToQueue.bind(@) determineIfSessionShouldContinueAndUpdateLog.bind(yetiGuru)
], (err, results) -> findNearestBetterSessionID.bind(yetiGuru)
if err is "shouldn't continue" addNewSessionsToQueue.bind(yetiGuru)
markSessionAsDoneRanking originalSessionID, (err) -> ], (err, results) ->
if err? then return sendResponseObject req, res, {"error":"There was an error marking the session as done ranking"} if err is "shouldn't continue"
sendResponseObject req, res, {"message":"The scores were updated successfully, person lost so no more games are being inserted!"} markSessionAsDoneRanking originalSessionID, (err) ->
else if err is "no session was found" if err? then return sendResponseObject req, res, {"error":"There was an error marking the session as done ranking"}
markSessionAsDoneRanking originalSessionID, (err) -> sendResponseObject req, res, {"message":"The scores were updated successfully, person lost so no more games are being inserted!"}
if err? then return sendResponseObject req, res, {"error":"There was an error marking the session as done ranking"} else if err is "no session was found"
sendResponseObject req, res, {"message":"There were no more games to rank (game is at top)!"} markSessionAsDoneRanking originalSessionID, (err) ->
else if err? if err? then return sendResponseObject req, res, {"error":"There was an error marking the session as done ranking"}
errors.serverError res, "There was an error:#{err}" sendResponseObject req, res, {"message":"There were no more games to rank (game is at top)!"}
else else if err?
sendResponseObject req, res, {"message":"The scores were updated successfully and more games were sent to the queue!"} errors.serverError res, "There was an error:#{err}"
else
sendResponseObject req, res, {"message":"The scores were updated successfully and more games were sent to the queue!"}
catch e
errors.serverError res, "There was an error processing the task result!"
verifyClientResponse = (responseObject, callback) -> verifyClientResponse = (responseObject, callback) ->
#TODO: better verification #TODO: better verification
@ -432,6 +475,7 @@ verifyClientResponse = (responseObject, callback) ->
callback "The response to that query is required to be a JSON object." callback "The response to that query is required to be a JSON object."
else else
@clientResponseObject = responseObject @clientResponseObject = responseObject
#log.info "Verified client response!" #log.info "Verified client response!"
callback null, responseObject callback null, responseObject
@ -459,6 +503,7 @@ deleteQueueMessage = (callback) ->
fetchLevelSession = (callback) -> fetchLevelSession = (callback) ->
findParameters = findParameters =
_id: @clientResponseObject.originalSessionID _id: @clientResponseObject.originalSessionID
query = LevelSession query = LevelSession
.findOne(findParameters) .findOne(findParameters)
.lean() .lean()
@ -490,11 +535,12 @@ updateSessions = (callback) ->
async.map sessionIDs, retrieveOldSessionData, (err, oldScores) => async.map sessionIDs, retrieveOldSessionData, (err, oldScores) =>
if err? then callback err, {"error": "There was an error retrieving the old scores"} if err? then callback err, {"error": "There was an error retrieving the old scores"}
try
oldScoreArray = _.toArray putRankingFromMetricsIntoScoreObject @clientResponseObject, oldScores oldScoreArray = _.toArray putRankingFromMetricsIntoScoreObject @clientResponseObject, oldScores
newScoreArray = bayes.updatePlayerSkills oldScoreArray newScoreArray = bayes.updatePlayerSkills oldScoreArray
saveNewScoresToDatabase newScoreArray, callback saveNewScoresToDatabase newScoreArray, callback
catch e
callback e
saveNewScoresToDatabase = (newScoreArray, callback) -> saveNewScoresToDatabase = (newScoreArray, callback) ->
async.eachSeries newScoreArray, updateScoreInSession, (err) -> async.eachSeries newScoreArray, updateScoreInSession, (err) ->
@ -541,7 +587,8 @@ addMatchToSessions = (newScoreObject, callback) ->
#log.info "Writing match object to database..." #log.info "Writing match object to database..."
#use bind with async to do the writes #use bind with async to do the writes
sessionIDs = _.pluck @clientResponseObject.sessions, 'sessionID' sessionIDs = _.pluck @clientResponseObject.sessions, 'sessionID'
async.each sessionIDs, updateMatchesInSession.bind(@,matchObject), (err) -> callback err async.each sessionIDs, updateMatchesInSession.bind(@,matchObject), (err) ->
callback err
updateMatchesInSession = (matchObject, sessionID, callback) -> updateMatchesInSession = (matchObject, sessionID, callback) ->
currentMatchObject = {} currentMatchObject = {}
@ -562,7 +609,11 @@ updateMatchesInSession = (matchObject, sessionID, callback) ->
updateUserSimulationCounts = (reqUserID,callback) -> updateUserSimulationCounts = (reqUserID,callback) ->
incrementUserSimulationCount reqUserID, 'simulatedBy', (err) => incrementUserSimulationCount reqUserID, 'simulatedBy', (err) =>
if err? then return callback err if err? then return callback err
incrementUserSimulationCount @levelSession.creator, 'simulatedFor', callback console.log "Incremented user simulation count!"
unless @isRandomMatch
incrementUserSimulationCount @levelSession.creator, 'simulatedFor', callback
else
callback null
incrementUserSimulationCount = (userID, type, callback) => incrementUserSimulationCount = (userID, type, callback) =>
inc = {} inc = {}
@ -605,13 +656,16 @@ determineIfSessionShouldContinueAndUpdateLog = (cb) ->
findNearestBetterSessionID = (cb) -> findNearestBetterSessionID = (cb) ->
levelOriginalID = @levelSession.level.original try
levelMajorVersion = @levelSession.level.majorVersion levelOriginalID = @levelSession.level.original
sessionID = @clientResponseObject.originalSessionID levelMajorVersion = @levelSession.level.majorVersion
sessionTotalScore = @newScoresObject[sessionID].totalScore sessionID = @clientResponseObject.originalSessionID
opponentSessionID = _.pull(_.keys(@newScoresObject), sessionID) sessionTotalScore = @newScoresObject[sessionID].totalScore
opponentSessionTotalScore = @newScoresObject[opponentSessionID].totalScore opponentSessionID = _.pull(_.keys(@newScoresObject), sessionID)
opposingTeam = calculateOpposingTeam(@clientResponseObject.originalSessionTeam) opponentSessionTotalScore = @newScoresObject[opponentSessionID].totalScore
opposingTeam = calculateOpposingTeam(@clientResponseObject.originalSessionTeam)
catch e
cb e
retrieveAllOpponentSessionIDs sessionID, (err, opponentSessionIDs) -> retrieveAllOpponentSessionIDs sessionID, (err, opponentSessionIDs) ->
if err? then return cb err, null if err? then return cb err, null
@ -708,7 +762,7 @@ hasTaskTimedOut = (taskSentTimestamp) -> taskSentTimestamp + scoringTaskTimeoutI
handleTimedOutTask = (req, res, taskBody) -> errors.clientTimeout res, "The results weren't provided within the timeout" handleTimedOutTask = (req, res, taskBody) -> errors.clientTimeout res, "The results weren't provided within the timeout"
putRankingFromMetricsIntoScoreObject = (taskObject,scoreObject) -> putRankingFromMetricsIntoScoreObject = (taskObject, scoreObject) ->
scoreObject = _.indexBy scoreObject, 'id' scoreObject = _.indexBy scoreObject, 'id'
scoreObject[session.sessionID].gameRanking = session.metrics.rank for session in taskObject.sessions scoreObject[session.sessionID].gameRanking = session.metrics.rank for session in taskObject.sessions
return scoreObject return scoreObject

View file

@ -22,7 +22,7 @@ productionLogging = (tokens, req, res) ->
else if status >= 300 then color = 36 else if status >= 300 then color = 36
elapsed = (new Date()) - req._startTime elapsed = (new Date()) - req._startTime
elapsedColor = if elapsed < 500 then 90 else 31 elapsedColor = if elapsed < 500 then 90 else 31
if (status isnt 200 and status isnt 304 and status isnt 302) or elapsed > 500 if (status isnt 200 and status isnt 204 and status isnt 304 and status isnt 302) or elapsed > 500
return "\x1b[90m#{req.method} #{req.originalUrl} \x1b[#{color}m#{res.statusCode} \x1b[#{elapsedColor}m#{elapsed}ms\x1b[0m" return "\x1b[90m#{req.method} #{req.originalUrl} \x1b[#{color}m#{res.statusCode} \x1b[#{elapsedColor}m#{elapsed}ms\x1b[0m"
null null
@ -92,7 +92,7 @@ sendMain = (req, res) ->
fs.readFile path.join(__dirname, 'public', 'main.html'), 'utf8', (err, data) -> fs.readFile path.join(__dirname, 'public', 'main.html'), 'utf8', (err, data) ->
log.error "Error modifying main.html: #{err}" if err log.error "Error modifying main.html: #{err}" if err
# insert the user object directly into the html so the application can have it immediately. Sanitize </script> # 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('/', '\\/')) data = data.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user)).replace(/\//g, '\\/'))
res.send data res.send data
setupFacebookCrossDomainCommunicationRoute = (app) -> setupFacebookCrossDomainCommunicationRoute = (app) ->