2014-02-14 10:49:16 -08:00
SuperModel = require ' models/SuperModel '
LevelLoader = require ' lib/LevelLoader '
GoalManager = require ' lib/world/GoalManager '
2014-02-14 15:50:42 -08:00
God = require ' lib/God '
2014-02-14 10:49:16 -08:00
module.exports = class Simulator
2014-02-14 14:55:30 -08:00
2014-02-14 10:49:16 -08:00
constructor: ->
2014-02-20 08:06:11 -08:00
_ . extend @ , Backbone . Events
@ trigger ' statusUpdate ' , ' Starting simulation! '
2014-02-14 10:49:16 -08:00
@retryDelayInSeconds = 10
2014-02-14 14:55:30 -08:00
@taskURL = ' /queue/scoring '
2014-02-14 10:49:16 -08:00
fetchAndSimulateTask: =>
2014-02-20 08:06:11 -08:00
@ trigger ' statusUpdate ' , ' Fetching simulation data! '
2014-02-14 10:49:16 -08:00
$ . ajax
url: @ taskURL
type: " GET "
error: @ handleFetchTaskError
success: @ setupSimulationAndLoadLevel
handleFetchTaskError: (errorData) =>
console . log " There were no games to score. Error: #{ JSON . stringify errorData } "
console . log " Retrying in #{ @ retryDelayInSeconds } "
2014-02-20 08:06:11 -08:00
@ trigger ' statusUpdate ' , ' There were no games to simulate! Trying again in 10 seconds. '
2014-02-14 10:49:16 -08:00
2014-02-14 14:55:30 -08:00
@ simulateAnotherTaskAfterDelay ( )
simulateAnotherTaskAfterDelay: =>
retryDelayInMilliseconds = @ retryDelayInSeconds * 1000
_ . delay @ fetchAndSimulateTask , retryDelayInMilliseconds
2014-02-14 10:49:16 -08:00
setupSimulationAndLoadLevel: (taskData) =>
2014-02-20 08:06:11 -08:00
@ trigger ' statusUpdate ' , ' Setting up simulation! '
2014-02-14 10:49:16 -08:00
@task = new SimulationTask ( taskData )
2014-02-14 15:50:42 -08:00
@supermodel = new SuperModel ( )
2014-02-15 17:29:54 -08:00
@god = new God maxWorkerPoolSize: 1 , maxAngels: 1 # Start loading worker.
2014-02-14 10:49:16 -08:00
2014-02-15 15:44:45 -08:00
@levelLoader = new LevelLoader supermodel: @ supermodel , levelID: @ task . getLevelName ( ) , sessionID: @ task . getFirstSessionID ( ) , headless: true
2014-02-14 10:49:16 -08:00
@ levelLoader . once ' loaded-all ' , @ simulateGame
simulateGame: =>
2014-02-20 08:06:11 -08:00
@ trigger ' statusUpdate ' , ' All resources loaded, simulating! ' , @ task . getSessions ( )
2014-02-14 10:49:16 -08:00
@ assignWorldAndLevelFromLevelLoaderAndDestroyIt ( )
@ setupGod ( )
2014-02-14 14:55:30 -08:00
2014-02-14 10:49:16 -08:00
try
@ commenceSimulationAndSetupCallback ( )
2014-02-14 14:55:30 -08:00
catch err
2014-02-15 15:45:53 -08:00
console . log " There was an error in simulation( #{ err } ). Trying again in #{ @ retryDelayInSeconds } seconds "
2014-02-14 14:55:30 -08:00
@ simulateAnotherTaskAfterDelay ( )
2014-02-14 10:49:16 -08:00
assignWorldAndLevelFromLevelLoaderAndDestroyIt: ->
@world = @ levelLoader . world
@level = @ levelLoader . level
@ levelLoader . destroy ( )
2014-02-15 15:44:45 -08:00
@levelLoader = null
2014-02-14 10:49:16 -08:00
setupGod: ->
@god.level = @ level . serialize @ supermodel
2014-02-14 15:50:42 -08:00
@god.worldClassMap = @ world . classMap
2014-02-14 10:49:16 -08:00
@ setupGoalManager ( )
@ setupGodSpells ( )
setupGoalManager: ->
@god.goalManager = new GoalManager @ world
2014-02-26 17:45:08 -08:00
@god.goalManager.goals = @ god . level . goals
2014-02-14 10:49:16 -08:00
@god.goalManager.goalStates = @ manuallyGenerateGoalStates ( )
commenceSimulationAndSetupCallback: ->
@ god . createWorld ( )
Backbone . Mediator . subscribeOnce ' god:new-world-created ' , @ processResults , @
processResults: (simulationResults) ->
taskResults = @ formTaskResultsObject simulationResults
2014-02-14 15:50:42 -08:00
@ sendResultsBackToServer taskResults
2014-02-14 10:49:16 -08:00
sendResultsBackToServer: (results) =>
2014-02-20 08:06:11 -08:00
@ trigger ' statusUpdate ' , ' Simulation completed, sending results back to server! '
2014-02-18 11:46:14 -08:00
console . log " Sending result back to server! "
2014-02-14 10:49:16 -08:00
$ . ajax
2014-02-18 11:46:14 -08:00
url: " /queue/scoring "
2014-02-14 10:49:16 -08:00
data: results
type: " PUT "
success: @ handleTaskResultsTransferSuccess
error: @ handleTaskResultsTransferError
2014-02-14 16:53:34 -08:00
complete: @ cleanupAndSimulateAnotherTask
2014-02-14 10:49:16 -08:00
2014-02-20 08:06:11 -08:00
handleTaskResultsTransferSuccess: (result) =>
2014-02-14 10:49:16 -08:00
console . log " Task registration result: #{ JSON . stringify result } "
2014-02-20 08:06:11 -08:00
@ trigger ' statusUpdate ' , ' Results were successfully sent back to server! '
2014-02-14 10:49:16 -08:00
2014-02-20 08:06:11 -08:00
handleTaskResultsTransferError: (error) =>
@ trigger ' statusUpdate ' , ' There was an error sending the results back to the server. '
2014-02-14 10:49:16 -08:00
console . log " Task registration error: #{ JSON . stringify error } "
cleanupAndSimulateAnotherTask: =>
@ cleanupSimulation ( )
@ fetchAndSimulateTask ( )
2014-02-14 14:55:30 -08:00
cleanupSimulation: ->
2014-02-15 15:44:45 -08:00
@ god . destroy ( )
@god = null
@world = null
@level = null
2014-02-14 10:49:16 -08:00
formTaskResultsObject: (simulationResults) ->
taskResults =
taskID: @ task . getTaskID ( )
receiptHandle: @ task . getReceiptHandle ( )
2014-02-26 12:14:02 -08:00
originalSessionID: @ task . getFirstSessionID ( )
originalSessionRank: - 1
2014-02-14 10:49:16 -08:00
calculationTime: 500
sessions: [ ]
2014-02-14 15:50:42 -08:00
for session in @ task . getSessions ( )
2014-02-26 17:45:08 -08:00
2014-02-14 10:49:16 -08:00
sessionResult =
sessionID: session . sessionID
2014-02-18 11:46:14 -08:00
submitDate: session . submitDate
creator: session . creator
2014-02-14 10:49:16 -08:00
metrics:
2014-02-14 15:50:42 -08:00
rank: @ calculateSessionRank session . sessionID , simulationResults . goalStates , @ task . generateTeamToSessionMap ( )
2014-02-26 12:14:02 -08:00
if session . sessionID is taskResults . originalSessionID
taskResults.originalSessionRank = sessionResult . metrics . rank
taskResults.originalSessionTeam = session . team
2014-02-14 10:49:16 -08:00
taskResults . sessions . push sessionResult
return taskResults
2014-02-14 15:50:42 -08:00
calculateSessionRank: (sessionID, goalStates, teamSessionMap) ->
2014-02-14 10:49:16 -08:00
humansDestroyed = goalStates [ " destroy-humans " ] . status is " success "
ogresDestroyed = goalStates [ " destroy-ogres " ] . status is " success "
if humansDestroyed is ogresDestroyed
return 0
2014-02-14 15:50:42 -08:00
else if humansDestroyed and teamSessionMap [ " ogres " ] is sessionID
2014-02-14 10:49:16 -08:00
return 0
2014-02-14 15:50:42 -08:00
else if humansDestroyed and teamSessionMap [ " ogres " ] isnt sessionID
2014-02-14 10:49:16 -08:00
return 1
2014-02-14 15:50:42 -08:00
else if ogresDestroyed and teamSessionMap [ " humans " ] is sessionID
2014-02-14 10:49:16 -08:00
return 0
else
return 1
manuallyGenerateGoalStates: ->
goalStates =
" destroy-humans " :
keyFrame: 0
killed:
" Human Base " : false
status: " incomplete "
" destroy-ogres " :
keyFrame : 0
killed:
" Ogre Base " : false
status: " incomplete "
setupGodSpells: ->
@ generateSpellsObject ( )
@god.spells = @ spells
2014-02-14 15:50:42 -08:00
generateSpellsObject: ->
@currentUserCodeMap = @ task . generateSpellKeyToSourceMap ( )
2014-02-14 10:49:16 -08:00
@spells = { }
for thang in @ level . attributes . thangs
continue if @ thangIsATemplate thang
@ generateSpellKeyToSourceMapPropertiesFromThang thang
thangIsATemplate: (thang) ->
for component in thang . components
continue unless @ componentHasProgrammableMethods component
for methodName , method of component . config . programmableMethods
2014-02-14 15:50:42 -08:00
return true if @ methodBelongsToTemplateThang method
2014-02-14 10:49:16 -08:00
return false
componentHasProgrammableMethods: (component) -> component . config ? and _ . has component . config , ' programmableMethods '
methodBelongsToTemplateThang: (method) -> typeof method is ' string '
generateSpellKeyToSourceMapPropertiesFromThang: (thang) =>
for component in thang . components
continue unless @ componentHasProgrammableMethods component
for methodName , method of component . config . programmableMethods
spellKey = @ generateSpellKeyFromThangIDAndMethodName thang . id , methodName
@ createSpellAndAssignName spellKey , methodName
@ createSpellThang thang , method , spellKey
@ transpileSpell thang , spellKey , methodName
generateSpellKeyFromThangIDAndMethodName: (thang, methodName) ->
2014-02-26 09:21:41 -08:00
spellKeyComponents = [ thang , methodName ]
2014-02-14 15:50:42 -08:00
spellKeyComponents [ 0 ] = _ . string . slugify spellKeyComponents [ 0 ]
2014-02-26 09:21:41 -08:00
spellKey = spellKeyComponents . join ' / '
spellKey
2014-02-26 17:45:08 -08:00
2014-02-14 10:49:16 -08:00
createSpellAndAssignName: (spellKey, spellName) ->
@ spells [ spellKey ] ? = { }
2014-02-14 15:50:42 -08:00
@ spells [ spellKey ] . name = spellName
2014-02-14 10:49:16 -08:00
createSpellThang: (thang, method, spellKey) ->
@ spells [ spellKey ] . thangs ? = { }
@ spells [ spellKey ] . thangs [ thang . id ] ? = { }
@ spells [ spellKey ] . thangs [ thang . id ] . aether = @ createAether @ spells [ spellKey ] . name , method
transpileSpell: (thang, spellKey, methodName) ->
slugifiedThangID = _ . string . slugify thang . id
2014-02-26 09:21:41 -08:00
source = @ currentUserCodeMap [ [ slugifiedThangID , methodName ] . join ' / ' ] ? " "
2014-02-14 10:49:16 -08:00
@ spells [ spellKey ] . thangs [ thang . id ] . aether . transpile source
createAether: (methodName, method) ->
aetherOptions =
functionName: methodName
protectAPI: false
includeFlow: false
2014-02-22 12:01:05 -08:00
#includeFlow: true
requiresThis: true
yieldConditionally: false
problems:
jshint_W040: { level: " ignore " }
jshint_W030: { level: " ignore " } # aether_NoEffect instead
aether_MissingThis: { level: ' error ' }
#functionParameters: # TODOOOOO
if methodName is ' hear '
aetherOptions.functionParameters = [ ' speaker ' , ' message ' , ' data ' ]
2014-02-25 14:46:48 -08:00
#console.log "creating aether with options", aetherOptions
2014-02-14 10:49:16 -08:00
return new Aether aetherOptions
class SimulationTask
constructor: (@rawData) ->
2014-02-20 08:06:11 -08:00
console . log ' Simulating sessions ' , ( session for session in @ getSessions ( ) )
2014-02-14 10:49:16 -08:00
getLevelName: ->
levelName = @ rawData . sessions ? [ 0 ] ? . levelID
return levelName if levelName ?
@ throwMalformedTaskError " The level name couldn ' t be deduced from the task. "
generateTeamToSessionMap: ->
teamSessionMap = { }
for session in @ rawData . sessions
@ throwMalformedTaskError " Two players share the same team " if teamSessionMap [ session . team ] ?
teamSessionMap [ session . team ] = session . sessionID
teamSessionMap
throwMalformedTaskError: (errorString) ->
throw new Error " The task was malformed, reason: #{ errorString } "
getFirstSessionID: -> @ rawData . sessions [ 0 ] . sessionID
getTaskID: -> @ rawData . taskID
getReceiptHandle: -> @ rawData . receiptHandle
getSessions: -> @ rawData . sessions
generateSpellKeyToSourceMap: ->
spellKeyToSourceMap = { }
for session in @ rawData . sessions
teamSpells = session . teamSpells [ session . team ]
2014-02-26 09:21:41 -08:00
teamCode = { }
for thangName , thangSpells of session . code
for spellName , spell of thangSpells
fullSpellName = [ thangName , spellName ] . join ' / '
if _ . contains ( teamSpells , fullSpellName )
teamCode [ fullSpellName ] = spell
2014-02-26 17:45:08 -08:00
2014-02-26 09:21:41 -08:00
_ . merge spellKeyToSourceMap , teamCode
2014-02-14 10:49:16 -08:00
commonSpells = session . teamSpells [ " common " ]
_ . merge spellKeyToSourceMap , _ . pick ( session . code , commonSpells ) if commonSpells ?
2014-02-26 17:45:08 -08:00
2014-02-14 10:49:16 -08:00
spellKeyToSourceMap