mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-20 18:10:44 -04:00
+ Merged Simulators
This commit is contained in:
parent
ad85bf5b77
commit
6d244e8560
5 changed files with 89 additions and 405 deletions
|
@ -124,6 +124,7 @@ class Angel
|
|||
@shared.busyAngels.push @
|
||||
|
||||
console.log "Running world..."
|
||||
#console.error "worker.postMessage: " + @worker.postMessage + ", work: " + work
|
||||
@worker.postMessage func: 'runWorld', args: work
|
||||
console.log @id + ": Setting interval."
|
||||
clearTimeout @purgatoryTimer
|
||||
|
@ -238,6 +239,3 @@ module.exports = class God
|
|||
@angelsShare.goalManager?.destroy()
|
||||
@angelsShare.goalManager = null
|
||||
@angelsShare = null
|
||||
|
||||
#TODO: self.world.totalFrames??
|
||||
#TODO: Don't show arguments.
|
||||
|
|
|
@ -6,11 +6,16 @@ God = require 'lib/Buddha'
|
|||
|
||||
module.exports = class Simulator extends CocoClass
|
||||
|
||||
constructor: ->
|
||||
constructor: (workerCode) ->
|
||||
_.extend @, Backbone.Events
|
||||
@trigger 'statusUpdate', 'Starting simulation!'
|
||||
@retryDelayInSeconds = 10
|
||||
@taskURL = '/queue/scoring'
|
||||
@simulatedByYou = 0
|
||||
if workerCode
|
||||
@god = new God maxWorkerPoolSize: 1, maxAngels: 1, workerCode: workerCode # Start loading worker.
|
||||
else
|
||||
@god = new God maxWorkerPoolSize: 1, maxAngels: 1
|
||||
|
||||
destroy: ->
|
||||
@off()
|
||||
|
@ -19,6 +24,17 @@ module.exports = class Simulator extends CocoClass
|
|||
|
||||
fetchAndSimulateTask: =>
|
||||
return if @destroyed
|
||||
|
||||
if headless
|
||||
if @dumpThisTime # The first heapdump would be useless to find leaks.
|
||||
console.log "Writing snapshot."
|
||||
heapdump.writeSnapshot()
|
||||
@dumpThisTime = true if heapdump
|
||||
|
||||
if testing
|
||||
_.delay @setupSimulationAndLoadLevel, 0, testFile, "Testing...", status: 400
|
||||
return
|
||||
|
||||
@trigger 'statusUpdate', 'Fetching simulation data!'
|
||||
$.ajax
|
||||
url: @taskURL
|
||||
|
@ -32,7 +48,9 @@ module.exports = class Simulator extends CocoClass
|
|||
@simulateAnotherTaskAfterDelay()
|
||||
|
||||
handleNoGamesResponse: ->
|
||||
@trigger 'statusUpdate', 'There were no games to simulate--all simulations are done or in process. Retrying in 10 seconds.'
|
||||
info = 'There were no games to simulate--all simulations are done or in process. Retrying in 10 seconds.'
|
||||
console.log info
|
||||
@trigger 'statusUpdate', info
|
||||
@simulateAnotherTaskAfterDelay()
|
||||
|
||||
simulateAnotherTaskAfterDelay: =>
|
||||
|
@ -54,8 +72,6 @@ module.exports = class Simulator extends CocoClass
|
|||
|
||||
@supermodel ?= new SuperModel()
|
||||
|
||||
@god = new God maxAngels: 2 # Start loading worker.
|
||||
|
||||
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: levelID, sessionID: @task.getFirstSessionID(), headless: true
|
||||
if @supermodel.finished()
|
||||
@simulateGame()
|
||||
|
@ -64,7 +80,9 @@ module.exports = class Simulator extends CocoClass
|
|||
|
||||
simulateGame: ->
|
||||
return if @destroyed
|
||||
@trigger 'statusUpdate', 'All resources loaded, simulating!', @task.getSessions()
|
||||
info = 'All resources loaded, simulating!'
|
||||
console.log info
|
||||
@trigger 'statusUpdate', info, @task.getSessions()
|
||||
@assignWorldAndLevelFromLevelLoaderAndDestroyIt()
|
||||
@setupGod()
|
||||
|
||||
|
@ -75,6 +93,7 @@ module.exports = class Simulator extends CocoClass
|
|||
@simulateAnotherTaskAfterDelay()
|
||||
|
||||
assignWorldAndLevelFromLevelLoaderAndDestroyIt: ->
|
||||
console.log "Assigning world and level"
|
||||
@world = @levelLoader.world
|
||||
@level = @levelLoader.level
|
||||
@levelLoader.destroy()
|
||||
|
@ -82,21 +101,45 @@ module.exports = class Simulator extends CocoClass
|
|||
|
||||
setupGod: ->
|
||||
@god.level = @level.serialize @supermodel
|
||||
@god.setWorldClassMap = @world.classMap
|
||||
@god.setWorldClassMap @world.classMap
|
||||
@setupGoalManager()
|
||||
@setupGodSpells()
|
||||
|
||||
|
||||
setupGoalManager: ->
|
||||
goalManager = new GoalManager @world
|
||||
goalManager.goals = @god.level.goals
|
||||
goalManager.goalStates = @manuallyGenerateGoalStates()
|
||||
@god.setGoalManager goalManager
|
||||
@god.setGoalManager new GoalManager(@world, @level.get 'goals')
|
||||
|
||||
|
||||
commenceSimulationAndSetupCallback: ->
|
||||
@god.createWorld @generateSpellsObject()
|
||||
Backbone.Mediator.subscribeOnce 'god:infinite-loop', @onInfiniteLoop, @
|
||||
Backbone.Mediator.subscribeOnce 'god:new-world-created', @processResults, @
|
||||
|
||||
#Search for leaks, headless-client only.
|
||||
if headless and leaktest and not @memwatch?
|
||||
leakcount = 0
|
||||
maxleakcount = 0
|
||||
console.log "Setting leak callbacks."
|
||||
@memwatch = require 'memwatch'
|
||||
|
||||
@memwatch.on 'leak', (info) =>
|
||||
console.warn "LEAK!!\n" + JSON.stringify(info)
|
||||
|
||||
unless @hd?
|
||||
if (leakcount++ is maxleakcount)
|
||||
@hd = new @memwatch.HeapDiff()
|
||||
|
||||
@memwatch.on 'stats', (stats) =>
|
||||
console.warn "stats callback: " + stats
|
||||
diff = @hd.end()
|
||||
console.warn "HeapDiff:\n" + JSON.stringify(diff)
|
||||
|
||||
if exitOnLeak
|
||||
console.warn "Exiting because of Leak."
|
||||
process.exit()
|
||||
@hd = new @memwatch.HeapDiff()
|
||||
|
||||
|
||||
onInfiniteLoop: ->
|
||||
console.warn "Skipping infinitely looping game."
|
||||
@trigger 'statusUpdate', "Infinite loop detected; grabbing a new game in #{@retryDelayInSeconds} seconds."
|
||||
|
@ -110,6 +153,9 @@ module.exports = class Simulator extends CocoClass
|
|||
@trigger 'statusUpdate', 'Simulation completed, sending results back to server!'
|
||||
console.log "Sending result back to server!"
|
||||
|
||||
if headless and testing
|
||||
return @fetchAndSimulateTask()
|
||||
|
||||
$.ajax
|
||||
url: "/queue/scoring"
|
||||
data: results
|
||||
|
@ -121,23 +167,19 @@ module.exports = class Simulator extends CocoClass
|
|||
handleTaskResultsTransferSuccess: (result) =>
|
||||
console.log "Task registration result: #{JSON.stringify result}"
|
||||
@trigger 'statusUpdate', 'Results were successfully sent back to server!'
|
||||
simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1
|
||||
$('#simulated-by-you').text(simulatedBy)
|
||||
console.log "Simulated by you: " + @simulatedByYou
|
||||
@simulatedByYou++
|
||||
if not headless
|
||||
simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1
|
||||
$('#simulated-by-you').text(simulatedBy)
|
||||
|
||||
handleTaskResultsTransferError: (error) =>
|
||||
@trigger 'statusUpdate', 'There was an error sending the results back to the server.'
|
||||
console.log "Task registration error: #{JSON.stringify error}"
|
||||
|
||||
cleanupAndSimulateAnotherTask: =>
|
||||
@cleanupSimulation()
|
||||
@fetchAndSimulateTask()
|
||||
|
||||
cleanupSimulation: ->
|
||||
@god?.destroy()
|
||||
@god = null
|
||||
@world = null
|
||||
@level = null
|
||||
|
||||
formTaskResultsObject: (simulationResults) ->
|
||||
taskResults =
|
||||
taskID: @task.getTaskID()
|
||||
|
@ -148,7 +190,6 @@ module.exports = class Simulator extends CocoClass
|
|||
sessions: []
|
||||
|
||||
for session in @task.getSessions()
|
||||
|
||||
sessionResult =
|
||||
sessionID: session.sessionID
|
||||
submitDate: session.submitDate
|
||||
|
@ -178,6 +219,10 @@ module.exports = class Simulator extends CocoClass
|
|||
else
|
||||
return 1
|
||||
|
||||
setupGodSpells: ->
|
||||
@generateSpellsObject()
|
||||
@god.spells = @spells
|
||||
|
||||
generateSpellsObject: ->
|
||||
@currentUserCodeMap = @task.generateSpellKeyToSourceMap()
|
||||
@spells = {}
|
||||
|
|
|
@ -84,7 +84,7 @@ class CocoModel extends Backbone.Model
|
|||
if @type() is 'ThangType'
|
||||
@_revertAttributes = _.clone @attributes # No deep clones for these!
|
||||
else
|
||||
@_revertAttributes = _.cloneDeep(@attributes)
|
||||
@_revertAttributes = $.extend(true, {}, @attributes)
|
||||
|
||||
revert: ->
|
||||
@set(@_revertAttributes, {silent: true}) if @_revertAttributes
|
||||
|
@ -97,7 +97,7 @@ class CocoModel extends Backbone.Model
|
|||
not _.isEqual @attributes, @_revertAttributes
|
||||
|
||||
cloneNewMinorVersion: ->
|
||||
newData = _.clone @attributes # needs to be deep?
|
||||
newData = _.clone @attributes
|
||||
|
||||
clone = new @constructor(newData)
|
||||
clone
|
||||
|
|
|
@ -114,8 +114,6 @@ module.exports = class PlayLevelView extends View
|
|||
@loadStartTime = new Date()
|
||||
@god = new God()
|
||||
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @getQueryVariable('opponent'), team: @getQueryVariable("team")
|
||||
#@listenToOnce(@levelLoader, 'loaded-all', @onLevelLoaderLoaded)
|
||||
#@listenTo(@levelLoader, 'progress', @onLevelLoaderProgressChanged)
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
|
@ -171,12 +169,12 @@ module.exports = class PlayLevelView extends View
|
|||
team = @getQueryVariable("team") ? @world.teamForPlayer(0)
|
||||
@loadOpponentTeam(team)
|
||||
@god.level = @level.serialize @supermodel
|
||||
@god.worldClassMap = @world.classMap
|
||||
@god.setWorldClassMap @world.classMap
|
||||
@setTeam team
|
||||
@initSurface()
|
||||
@initGoalManager()
|
||||
@initScriptManager()
|
||||
@insertSubviews ladderGame: (@level.get('type') is "ladder")
|
||||
@insertSubviews()
|
||||
@initVolume()
|
||||
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
|
||||
@originalSessionState = $.extend(true, {}, @session.get('state'))
|
||||
|
@ -429,7 +427,7 @@ module.exports = class PlayLevelView extends View
|
|||
|
||||
initGoalManager: ->
|
||||
@goalManager = new GoalManager(@world, @level.get('goals'))
|
||||
@god.goalManager = @goalManager
|
||||
@god.setGoalManager @goalManager
|
||||
|
||||
initScriptManager: ->
|
||||
@scriptManager = new ScriptManager({scripts: @world.scripts or [], view:@, session: @session})
|
||||
|
|
|
@ -4,11 +4,11 @@ At some point, most of the code can be merged with Simulator.coffee
|
|||
###
|
||||
|
||||
# SETTINGS
|
||||
debug = false # Enable logging of ajax calls mainly
|
||||
testing = false # Instead of simulating 'real' games, use the same one over and over again. Good for leak hunting.
|
||||
leaktest = false # Install callback that tries to find leaks automatically
|
||||
exitOnLeak = false # Exit if leak is found. Only useful if leaktest is set to true, obviously.
|
||||
heapdump = false # Dumps the whole heap after every pass. The heap dumps can then be viewed in Chrome browser.
|
||||
GLOBAL.debug = false # Enable logging of ajax calls mainly
|
||||
GLOBAL.testing = false # Instead of simulating 'real' games, use the same one over and over again. Good for leak hunting.
|
||||
GLOBAL.leaktest = false # Install callback that tries to find leaks automatically
|
||||
GLOBAL.exitOnLeak = false # Exit if leak is found. Only useful if leaktest is set to true, obviously.
|
||||
GLOBAL.heapdump = false # Dumps the whole heap after every pass. The heap dumps can then be viewed in Chrome browser.
|
||||
|
||||
server = if testing then "http://127.0.0.1:3000" else "http://codecombat.com"
|
||||
|
||||
|
@ -19,8 +19,8 @@ disable = [
|
|||
'../locale/locale'
|
||||
]
|
||||
|
||||
bowerComponents = "./bower_components/"
|
||||
headlessClient = "./headless_client/"
|
||||
GLOBAL.bowerComponents = "./bower_components/"
|
||||
GLOBAL.headlessClient = "./headless_client/"
|
||||
|
||||
|
||||
# Start of the actual code. Setting up the enivronment to match the environment of the browser
|
||||
|
@ -45,6 +45,10 @@ JASON = require 'jason'
|
|||
|
||||
# Global emulated stuff
|
||||
GLOBAL.window = GLOBAL
|
||||
GLOBAL.headless = true
|
||||
GLOBAL.document = location: pathname: "headless_client"
|
||||
GLOBAL.console.debug = console.log
|
||||
|
||||
GLOBAL.Worker = require('webworker-threads').Worker
|
||||
Worker::removeEventListener = (what) ->
|
||||
if what is 'message'
|
||||
|
@ -71,10 +75,9 @@ GLOBAL.localStorage =
|
|||
# since it will replace that.
|
||||
# (Why is there no easier way?)
|
||||
hookedLoader = (request, parent, isMain) ->
|
||||
#if request is 'lib/god'
|
||||
# console.log 'I choose you, SimpleGod.'
|
||||
# request = './headless_client/SimpleGod'
|
||||
#else
|
||||
if request == 'lib/God'
|
||||
request = 'lib/Buddha'
|
||||
|
||||
if request in disable or ~request.indexOf('templates')
|
||||
console.log 'Ignored ' + request if debug
|
||||
return class fake
|
||||
|
@ -195,7 +198,7 @@ $.ajax
|
|||
LevelLoader = require 'lib/LevelLoader'
|
||||
GoalManager = require 'lib/world/GoalManager'
|
||||
|
||||
God = require 'lib/Buddha'
|
||||
|
||||
|
||||
workerCode = require headlessClient + 'worker_world'
|
||||
|
||||
|
@ -205,370 +208,10 @@ $.ajax
|
|||
|
||||
CocoClass = require 'lib/CocoClass'
|
||||
|
||||
class Simulator extends CocoClass
|
||||
Simulator = require 'lib/simulator/Simulator'
|
||||
|
||||
constructor: ->
|
||||
_.extend @, Backbone.Events
|
||||
@trigger 'statusUpdate', 'Starting simulation!'
|
||||
@retryDelayInSeconds = 10
|
||||
@taskURL = 'queue/scoring'
|
||||
@simulatedByYou = 0
|
||||
|
||||
@god = new God maxWorkerPoolSize: 1, maxAngels: 1, workerCode: workerCode # Start loading worker.
|
||||
|
||||
destroy: ->
|
||||
@off()
|
||||
@cleanupSimulation()
|
||||
super()
|
||||
|
||||
fetchAndSimulateTask: =>
|
||||
return if @destroyed
|
||||
|
||||
if testing
|
||||
test = require headlessClient + 'test.js'
|
||||
console.log test
|
||||
_.delay @setupSimulationAndLoadLevel, 0, test, "Testing...", status: 400
|
||||
return
|
||||
|
||||
if @ranonce and heapdump
|
||||
console.log "Writing snapshot."
|
||||
heapdump.writeSnapshot()
|
||||
@ranonce = true
|
||||
|
||||
@trigger 'statusUpdate', 'Fetching simulation data!'
|
||||
$.ajax
|
||||
url: @taskURL
|
||||
type: "GET"
|
||||
parse: true
|
||||
error: @handleFetchTaskError
|
||||
success: @setupSimulationAndLoadLevel
|
||||
|
||||
handleFetchTaskError: (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.'
|
||||
@simulateAnotherTaskAfterDelay()
|
||||
|
||||
handleNoGamesResponse: ->
|
||||
console.log "Nothing to do."
|
||||
@trigger 'statusUpdate', 'There were no games to simulate--nice. Retrying in 10 seconds.'
|
||||
@simulateAnotherTaskAfterDelay()
|
||||
|
||||
simulateAnotherTaskAfterDelay: =>
|
||||
console.log "Retrying..."
|
||||
console.log "Retrying in #{@retryDelayInSeconds}"
|
||||
retryDelayInMilliseconds = @retryDelayInSeconds * 1000
|
||||
_.delay @fetchAndSimulateTask, retryDelayInMilliseconds
|
||||
|
||||
setupSimulationAndLoadLevel: (taskData, textStatus, jqXHR) =>
|
||||
return @handleNoGamesResponse() if jqXHR.status is 204
|
||||
@trigger 'statusUpdate', 'Setting up simulation!'
|
||||
|
||||
@task = new SimulationTask(taskData)
|
||||
try
|
||||
levelID = @task.getLevelName()
|
||||
catch err
|
||||
console.error err
|
||||
@trigger 'statusUpdate', "Error simulating game: #{err}. Trying another game in #{@retryDelayInSeconds} seconds."
|
||||
@simulateAnotherTaskAfterDelay()
|
||||
return
|
||||
|
||||
@supermodel ?= new SuperModel()
|
||||
|
||||
#console.log "Creating loader with levelID: " + levelID + " and SessionID: " + @task.getFirstSessionID() + " - task: " + JSON.stringify(@task)
|
||||
|
||||
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: levelID, sessionID: @task.getFirstSessionID(), headless: true
|
||||
|
||||
console.log "Waiting for loaded game"
|
||||
|
||||
@listenToOnce(@levelLoader, 'loaded-all', @simulateGame)
|
||||
|
||||
simulateGame: ->
|
||||
console.warn "Simulate game."
|
||||
return if @destroyed
|
||||
@trigger 'statusUpdate', 'All resources loaded, simulating!', @task.getSessions()
|
||||
console.log "assignWorld"
|
||||
@assignWorldAndLevelFromLevelLoaderAndDestroyIt()
|
||||
console.log "SetupGod"
|
||||
@setupGod()
|
||||
try
|
||||
@commenceSimulationAndSetupCallback()
|
||||
catch err
|
||||
console.log "There was an error in simulation(#{err}). Trying again in #{@retryDelayInSeconds} seconds"
|
||||
|
||||
#TODO: Comment out.
|
||||
throw err
|
||||
|
||||
@simulateAnotherTaskAfterDelay()
|
||||
|
||||
assignWorldAndLevelFromLevelLoaderAndDestroyIt: ->
|
||||
console.log "Assigning world and level"
|
||||
@world = @levelLoader.world
|
||||
@level = @levelLoader.level
|
||||
@levelLoader.destroy()
|
||||
@levelLoader = null
|
||||
|
||||
setupGod: ->
|
||||
@god.level = @level.serialize @supermodel
|
||||
@god.setWorldClassMap @world.classMap
|
||||
@setupGoalManager()
|
||||
|
||||
setupGoalManager: ->
|
||||
goalManager = new GoalManager @world
|
||||
goalManager.goals = @god.level.goals
|
||||
goalManager.goalStates = @manuallyGenerateGoalStates()
|
||||
@god.setGoalManager goalManager
|
||||
|
||||
commenceSimulationAndSetupCallback: ->
|
||||
console.log "Creating World."
|
||||
@god.createWorld(@generateSpellsObject())
|
||||
Backbone.Mediator.subscribeOnce 'god:infinite-loop', @onInfiniteLoop, @
|
||||
Backbone.Mediator.subscribeOnce 'god:goals-calculated', @processResults, @
|
||||
|
||||
#Search for leaks
|
||||
if leaktest and not @memwatch?
|
||||
leakcount = 0
|
||||
maxleakcount = 0
|
||||
console.log "Setting leak callbacks."
|
||||
@memwatch = require 'memwatch'
|
||||
|
||||
@memwatch.on 'leak', (info) =>
|
||||
console.warn "LEAK!!\n" + JSON.stringify(info)
|
||||
|
||||
unless @hd?
|
||||
if (leakcount++ is maxleakcount)
|
||||
@hd = new @memwatch.HeapDiff()
|
||||
|
||||
@memwatch.on 'stats', (stats) =>
|
||||
console.warn "stats callback: " + stats
|
||||
diff = @hd.end()
|
||||
console.warn "HeapDiff:\n" + JSON.stringify(diff)
|
||||
|
||||
if exitOnLeak
|
||||
console.warn "Exiting because of Leak."
|
||||
process.exit()
|
||||
@hd = new @memwatch.HeapDiff()
|
||||
|
||||
|
||||
|
||||
onInfiniteLoop: ->
|
||||
console.warn "Skipping infinitely looping game."
|
||||
@trigger 'statusUpdate', "Infinite loop detected; grabbing a new game in #{@retryDelayInSeconds} seconds."
|
||||
_.delay @cleanupAndSimulateAnotherTask, @retryDelayInSeconds * 1000
|
||||
|
||||
processResults: (simulationResults) ->
|
||||
|
||||
console.log "Processing Results"
|
||||
|
||||
taskResults = @formTaskResultsObject simulationResults
|
||||
console.warn taskResults
|
||||
@sendResultsBackToServer taskResults
|
||||
|
||||
sendResultsBackToServer: (results) =>
|
||||
@trigger 'statusUpdate', 'Simulation completed, sending results back to server!'
|
||||
console.log "Sending result back to server"
|
||||
|
||||
if testing
|
||||
return @fetchAndSimulateTask()
|
||||
|
||||
$.ajax
|
||||
url: "queue/scoring"
|
||||
data: results
|
||||
parse: true
|
||||
type: "PUT"
|
||||
success: @handleTaskResultsTransferSuccess
|
||||
error: @handleTaskResultsTransferError
|
||||
complete: @cleanupAndSimulateAnotherTask
|
||||
|
||||
handleTaskResultsTransferSuccess: (result) =>
|
||||
console.log "Task registration result: #{JSON.stringify result}"
|
||||
@trigger 'statusUpdate', 'Results were successfully sent back to server!'
|
||||
console.log "Simulated by you: " + @simulatedByYou
|
||||
@simulatedByYou++
|
||||
|
||||
handleTaskResultsTransferError: (error) =>
|
||||
@trigger 'statusUpdate', 'There was an error sending the results back to the server.'
|
||||
console.log "Task registration error: #{JSON.stringify error}"
|
||||
|
||||
cleanupAndSimulateAnotherTask: =>
|
||||
#@cleanupSimulation() Not needed for Buddha.
|
||||
@fetchAndSimulateTask()
|
||||
|
||||
cleanupSimulation: ->
|
||||
@god?.destroy()
|
||||
@god = null
|
||||
@world = null
|
||||
@level = null
|
||||
|
||||
formTaskResultsObject: (simulationResults) ->
|
||||
taskResults =
|
||||
taskID: @task.getTaskID()
|
||||
receiptHandle: @task.getReceiptHandle()
|
||||
originalSessionID: @task.getFirstSessionID()
|
||||
originalSessionRank: -1
|
||||
calculationTime: 500
|
||||
sessions: []
|
||||
|
||||
for session in @task.getSessions()
|
||||
|
||||
sessionResult =
|
||||
sessionID: session.sessionID
|
||||
submitDate: session.submitDate
|
||||
creator: session.creator
|
||||
metrics:
|
||||
rank: @calculateSessionRank session.sessionID, simulationResults.goalStates, @task.generateTeamToSessionMap()
|
||||
if session.sessionID is taskResults.originalSessionID
|
||||
taskResults.originalSessionRank = sessionResult.metrics.rank
|
||||
taskResults.originalSessionTeam = session.team
|
||||
taskResults.sessions.push sessionResult
|
||||
|
||||
return taskResults
|
||||
|
||||
calculateSessionRank: (sessionID, goalStates, teamSessionMap) ->
|
||||
humansDestroyed = goalStates["destroy-humans"].status is "success"
|
||||
ogresDestroyed = goalStates["destroy-ogres"].status is "success"
|
||||
if humansDestroyed is ogresDestroyed
|
||||
return 0
|
||||
else if humansDestroyed and teamSessionMap["ogres"] is sessionID
|
||||
return 0
|
||||
else if humansDestroyed and teamSessionMap["ogres"] isnt sessionID
|
||||
return 1
|
||||
else if ogresDestroyed and teamSessionMap["humans"] is sessionID
|
||||
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"
|
||||
|
||||
generateSpellsObject: ->
|
||||
@currentUserCodeMap = @task.generateSpellKeyToSourceMap()
|
||||
@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
|
||||
return true if @methodBelongsToTemplateThang method
|
||||
|
||||
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) ->
|
||||
spellKeyComponents = [thang, methodName]
|
||||
spellKeyComponents[0] = _.string.slugify spellKeyComponents[0]
|
||||
spellKey = spellKeyComponents.join '/'
|
||||
spellKey
|
||||
|
||||
|
||||
createSpellAndAssignName: (spellKey, spellName) ->
|
||||
@spells[spellKey] ?= {}
|
||||
@spells[spellKey].name = spellName
|
||||
|
||||
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
|
||||
source = @currentUserCodeMap[[slugifiedThangID,methodName].join '/'] ? ""
|
||||
aether = @spells[spellKey].thangs[thang.id].aether
|
||||
try
|
||||
aether.transpile source
|
||||
catch e
|
||||
console.log "Couldn't transpile #{spellKey}:\n#{source}\n", e
|
||||
aether.transpile ''
|
||||
|
||||
createAether: (methodName, method) ->
|
||||
aetherOptions =
|
||||
functionName: methodName
|
||||
protectAPI: true
|
||||
includeFlow: false
|
||||
requiresThis: true
|
||||
yieldConditionally: false
|
||||
problems:
|
||||
jshint_W040: {level: "ignore"}
|
||||
jshint_W030: {level: "ignore"} # aether_NoEffect instead
|
||||
aether_MissingThis: {level: 'error'}
|
||||
if methodName is 'hear'
|
||||
aetherOptions.functionParameters = ['speaker', 'message', 'data']
|
||||
#console.log "creating aether with options", aetherOptions
|
||||
|
||||
return new Aether aetherOptions
|
||||
|
||||
class SimulationTask
|
||||
constructor: (@rawData) ->
|
||||
#console.log 'Simulating sessions', (session for session in @getSessions())
|
||||
|
||||
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]
|
||||
teamCode = {}
|
||||
for thangName, thangSpells of session.code
|
||||
for spellName, spell of thangSpells
|
||||
fullSpellName = [thangName,spellName].join '/'
|
||||
if _.contains(teamSpells, fullSpellName)
|
||||
teamCode[fullSpellName]=spell
|
||||
|
||||
_.merge spellKeyToSourceMap, teamCode
|
||||
commonSpells = session.teamSpells["common"]
|
||||
_.merge spellKeyToSourceMap, _.pick(session.code, commonSpells) if commonSpells?
|
||||
|
||||
|
||||
spellKeyToSourceMap
|
||||
|
||||
sim = new Simulator()
|
||||
GLOBAL.testFile = require headlessClient + 'test.js'
|
||||
|
||||
sim = new Simulator workerCode
|
||||
|
||||
sim.fetchAndSimulateTask()
|
Loading…
Reference in a new issue