codecombat/app/lib/simulator/Simulator.coffee

506 lines
19 KiB
CoffeeScript
Raw Normal View History

2014-02-14 13:49:16 -05:00
SuperModel = require 'models/SuperModel'
CocoClass = require 'core/CocoClass'
2014-02-14 13:49:16 -05:00
LevelLoader = require 'lib/LevelLoader'
GoalManager = require 'lib/world/GoalManager'
God = require 'lib/God'
2014-08-30 16:43:56 -04:00
{createAetherOptions} = require 'lib/aether_utils'
2014-05-08 14:43:00 -04:00
2016-05-05 16:22:30 -04:00
SIMULATOR_VERSION = 4
simulatorInfo = {}
if $.browser
simulatorInfo['desktop'] = $.browser.desktop if $.browser.desktop
simulatorInfo['name'] = $.browser.name if $.browser.name
simulatorInfo['platform'] = $.browser.platform if $.browser.platform
simulatorInfo['version'] = $.browser.versionNumber if $.browser.versionNumber
module.exports = class Simulator extends CocoClass
constructor: (@options) ->
@options ?= {}
simulatorType = if @options.headlessClient then 'headless' else 'browser'
@simulator =
type: simulatorType
version: SIMULATOR_VERSION
info: simulatorInfo
_.extend @, Backbone.Events
@trigger 'statusUpdate', 'Starting simulation!'
@retryDelayInSeconds = 2
@taskURL = '/queue/scoring'
2014-05-05 23:07:34 -04:00
@simulatedByYou = 0
@god = new God maxAngels: 1, workerCode: @options.workerCode, headless: true # Start loading worker.
destroy: ->
@off()
@cleanupSimulation()
@god?.destroy()
super()
2014-05-20 11:00:44 -04:00
2014-05-19 13:11:20 -04:00
fetchAndSimulateOneGame: (humanGameID, ogresGameID) =>
return if @destroyed
$.ajax
2014-06-30 22:16:26 -04:00
url: '/queue/scoring/getTwoGames'
type: 'POST'
2014-05-19 13:11:20 -04:00
parse: true
data:
humansGameID: humanGameID
ogresGameID: ogresGameID
simulator: @simulator
background: Boolean(@options.background)
levelID: @options.levelID
leagueID: @options.leagueID
2014-05-19 13:11:20 -04:00
error: (errorData) ->
console.warn "There was an error fetching two games! #{JSON.stringify errorData}"
if errorData?.responseText?.indexOf("Old simulator") isnt -1
noty {
text: errorData.responseText
layout: 'center'
type: 'error'
}
2014-05-19 13:11:20 -04:00
success: (taskData) =>
2014-05-20 11:00:44 -04:00
return if @destroyed
unless taskData
@retryDelayInSeconds = 10
@trigger 'statusUpdate', "No games to simulate. Trying another game in #{@retryDelayInSeconds} seconds."
@simulateAnotherTaskAfterDelay()
return
@trigger 'statusUpdate', 'Setting up simulation...'
2014-05-19 13:11:20 -04:00
#refactor this
@task = new SimulationTask(taskData)
2014-05-20 11:00:44 -04:00
2014-05-19 13:11:20 -04:00
@supermodel ?= new SuperModel()
@supermodel.resetProgress()
@stopListening @supermodel, 'loaded-all'
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @task.getLevelName(), sessionID: @task.getFirstSessionID(), opponentSessionID: @task.getSecondSessionID(), headless: true
2014-05-20 11:00:44 -04:00
2014-05-19 13:11:20 -04:00
if @supermodel.finished()
@simulateSingleGame()
else
@listenToOnce @supermodel, 'loaded-all', @simulateSingleGame
2014-05-20 11:00:44 -04:00
2014-05-19 13:11:20 -04:00
simulateSingleGame: ->
return if @destroyed
@assignWorldAndLevelFromLevelLoaderAndDestroyIt()
@trigger 'statusUpdate', 'Simulating...'
2014-05-19 13:11:20 -04:00
@setupGod()
try
@commenceSingleSimulation()
catch error
@handleSingleSimulationError error
2014-05-20 11:00:44 -04:00
2014-05-19 13:11:20 -04:00
commenceSingleSimulation: ->
@listenToOnce @god, 'infinite-loop', @handleSingleSimulationInfiniteLoop
@listenToOnce @god, 'goals-calculated', @processSingleGameResults
2014-05-24 00:24:50 -04:00
@god.createWorld @generateSpellsObject()
2014-05-20 11:00:44 -04:00
handleSingleSimulationError: (error) ->
2014-06-30 22:16:26 -04:00
console.error 'There was an error simulating a single game!', error
return if @destroyed
if @options.headlessClient and @options.simulateOnlyOneGame
2014-06-30 22:16:26 -04:00
console.log 'GAMERESULT:tie'
2014-05-19 13:11:20 -04:00
process.exit(0)
@cleanupAndSimulateAnotherTask()
2014-05-20 11:00:44 -04:00
handleSingleSimulationInfiniteLoop: (e) ->
2014-06-30 22:16:26 -04:00
console.log 'There was an infinite loop in the single game!'
return if @destroyed
if @options.headlessClient and @options.simulateOnlyOneGame
2014-06-30 22:16:26 -04:00
console.log 'GAMERESULT:tie'
2014-05-19 13:11:20 -04:00
process.exit(0)
@cleanupAndSimulateAnotherTask()
2014-05-20 11:00:44 -04:00
2014-05-19 13:11:20 -04:00
processSingleGameResults: (simulationResults) ->
try
taskResults = @formTaskResultsObject simulationResults
catch error
console.log "Failed to form task results:", error
return @cleanupAndSimulateAnotherTask()
2014-06-30 22:16:26 -04:00
console.log 'Processing results:', taskResults
2014-05-19 13:11:20 -04:00
humanSessionRank = taskResults.sessions[0].metrics.rank
ogreSessionRank = taskResults.sessions[1].metrics.rank
2014-06-06 15:18:00 -04:00
if @options.headlessClient and @options.simulateOnlyOneGame
2014-05-19 13:11:20 -04:00
if humanSessionRank is ogreSessionRank
2014-06-30 22:16:26 -04:00
console.log 'GAMERESULT:tie'
2014-05-19 13:11:20 -04:00
else if humanSessionRank < ogreSessionRank
2014-06-30 22:16:26 -04:00
console.log 'GAMERESULT:humans'
2014-05-19 13:11:20 -04:00
else if ogreSessionRank < humanSessionRank
2014-06-30 22:16:26 -04:00
console.log 'GAMERESULT:ogres'
2014-05-19 13:11:20 -04:00
process.exit(0)
else
@sendSingleGameBackToServer(taskResults)
2014-05-20 11:00:44 -04:00
sendSingleGameBackToServer: (results) ->
@trigger 'statusUpdate', 'Simulation completed, sending results back to server!'
2014-05-20 11:00:44 -04:00
$.ajax
2014-06-30 22:16:26 -04:00
url: '/queue/scoring/recordTwoGames'
data: results
2014-06-30 22:16:26 -04:00
type: 'PUT'
parse: true
success: @handleTaskResultsTransferSuccess
error: @handleTaskResultsTransferError
complete: @cleanupAndSimulateAnotherTask
2014-05-20 11:00:44 -04:00
2014-02-14 13:49:16 -05:00
fetchAndSimulateTask: =>
2014-03-16 23:36:02 -04:00
return if @destroyed
# Because there's some bug where the chained rankings don't work, let's just do getTwoGames until we fix it.
return @fetchAndSimulateOneGame()
2014-05-05 23:07:34 -04:00
if @options.headlessClient
2014-05-05 23:07:34 -04:00
if @dumpThisTime # The first heapdump would be useless to find leaks.
2014-06-30 22:16:26 -04:00
console.log 'Writing snapshot.'
@options.heapdump.writeSnapshot()
@dumpThisTime = true if @options.heapdump
2014-05-05 23:07:34 -04:00
if @options.testing
2014-06-30 22:16:26 -04:00
_.delay @setupSimulationAndLoadLevel, 0, @options.testFile, 'Testing...', status: 400
2014-05-05 23:07:34 -04:00
return
@trigger 'statusUpdate', 'Fetching simulation data!'
2014-02-14 13:49:16 -05:00
$.ajax
url: @taskURL
2014-06-30 22:16:26 -04:00
type: 'GET'
parse: true
2014-02-14 13:49:16 -05:00
error: @handleFetchTaskError
success: @setupSimulationAndLoadLevel
cache: false
2014-02-14 13:49:16 -05:00
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()
2014-05-20 11:00:44 -04:00
handleNoGamesResponse: ->
@noTasks = true
info = 'Finding game to simulate...'
2014-05-05 23:07:34 -04:00
console.log info
@trigger 'statusUpdate', info
@fetchAndSimulateOneGame()
simulateAnotherTaskAfterDelay: =>
console.log "Retrying in #{@retryDelayInSeconds}"
retryDelayInMilliseconds = @retryDelayInSeconds * 1000
_.delay @fetchAndSimulateTask, retryDelayInMilliseconds
2014-02-14 13:49:16 -05:00
setupSimulationAndLoadLevel: (taskData, textStatus, jqXHR) =>
return @handleNoGamesResponse() if jqXHR.status is 204
@trigger 'statusUpdate', 'Setting up simulation!'
2014-02-14 13:49:16 -05:00
@task = new SimulationTask(taskData)
2014-03-26 15:12:43 -04:00
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()
@supermodel.resetProgress()
@stopListening @supermodel, 'loaded-all'
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: levelID, sessionID: @task.getFirstSessionID(), opponentSessionID: @task.getSecondSessionID(), headless: true
if @supermodel.finished()
@simulateGame()
else
@listenToOnce @supermodel, 'loaded-all', @simulateGame
2014-02-14 13:49:16 -05:00
2014-03-24 12:58:34 -04:00
simulateGame: ->
return if @destroyed
2014-05-05 23:07:34 -04:00
info = 'All resources loaded, simulating!'
console.log info
2014-02-14 13:49:16 -05:00
@assignWorldAndLevelFromLevelLoaderAndDestroyIt()
@trigger 'statusUpdate', info, @task.getSessions()
2014-02-14 13:49:16 -05:00
@setupGod()
2014-02-14 13:49:16 -05:00
try
@commenceSimulationAndSetupCallback()
catch err
console.error 'There was an error in simulation:', err, err.stack, "-- trying again in #{@retryDelayInSeconds} seconds"
@simulateAnotherTaskAfterDelay()
2014-02-14 13:49:16 -05:00
assignWorldAndLevelFromLevelLoaderAndDestroyIt: ->
@world = @levelLoader.world
2014-05-15 19:43:16 -04:00
@task.setWorld(@world)
2014-02-14 13:49:16 -05:00
@level = @levelLoader.level
@session = @levelLoader.session
@otherSession = @levelLoader.opponentSession
2014-02-14 13:49:16 -05:00
@levelLoader.destroy()
2014-02-15 18:44:45 -05:00
@levelLoader = null
2014-02-14 13:49:16 -05:00
setupGod: ->
@god.setLevel @level.serialize(@supermodel, @session, @otherSession)
2014-05-20 11:00:44 -04:00
@god.setLevelSessionIDs (session.sessionID for session in @task.getSessions())
2014-05-05 23:07:34 -04:00
@god.setWorldClassMap @world.classMap
@god.setGoalManager new GoalManager @world, @level.get('goals'), null, {headless: true}
humanFlagHistory = _.filter @session.get('state')?.flagHistory ? [], (event) => event.source isnt 'code' and event.team is (@session.get('team') ? 'humans')
ogreFlagHistory = _.filter @otherSession.get('state')?.flagHistory ? [], (event) => event.source isnt 'code' and event.team is (@otherSession.get('team') ? 'ogres')
@god.lastFlagHistory = humanFlagHistory.concat ogreFlagHistory
#console.log 'got flag history', @god.lastFlagHistory, 'from', humanFlagHistory, ogreFlagHistory, @session.get('state'), @otherSession.get('state')
@god.lastSubmissionCount = 0 # TODO: figure out how to combine submissionCounts from both players so we can use submissionCount random seeds again.
@god.lastDifficulty = 0
2014-05-05 23:07:34 -04:00
2014-02-14 13:49:16 -05:00
commenceSimulationAndSetupCallback: ->
@listenToOnce @god, 'infinite-loop', @onInfiniteLoop
@listenToOnce @god, 'goals-calculated', @processResults
2014-05-24 00:24:50 -04:00
@god.createWorld @generateSpellsObject()
2014-02-14 13:49:16 -05:00
2014-06-30 22:16:26 -04:00
# Search for leaks, headless-client only.
if @options.headlessClient and @options.leakTest and not @memwatch?
2014-05-05 23:07:34 -04:00
leakcount = 0
maxleakcount = 0
2014-06-30 22:16:26 -04:00
console.log 'Setting leak callbacks.'
2014-05-05 23:07:34 -04:00
@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) =>
2014-06-30 22:16:26 -04:00
console.warn 'stats callback: ' + stats
2014-05-05 23:07:34 -04:00
diff = @hd.end()
console.warn "HeapDiff:\n" + JSON.stringify(diff)
if @options.exitOnLeak
2014-06-30 22:16:26 -04:00
console.warn 'Exiting because of Leak.'
2014-05-05 23:07:34 -04:00
process.exit()
@hd = new @memwatch.HeapDiff()
onInfiniteLoop: (e) ->
return if @destroyed
2014-06-30 22:16:26 -04:00
console.warn 'Skipping infinitely looping game.'
2014-03-26 15:12:43 -04:00
@trigger 'statusUpdate', "Infinite loop detected; grabbing a new game in #{@retryDelayInSeconds} seconds."
2014-03-26 15:34:45 -04:00
_.delay @cleanupAndSimulateAnotherTask, @retryDelayInSeconds * 1000
2014-02-14 13:49:16 -05:00
processResults: (simulationResults) ->
try
taskResults = @formTaskResultsObject simulationResults
catch error
console.log "Failed to form task results:", error
return @cleanupAndSimulateAnotherTask()
unless taskResults.taskID
console.error "*** Error: taskResults has no taskID ***\ntaskResults:", taskResults
@cleanupAndSimulateAnotherTask()
else
@sendResultsBackToServer taskResults
2014-02-14 13:49:16 -05:00
sendResultsBackToServer: (results) ->
status = 'Recording:'
for session in results.sessions
states = ['wins', if _.find(results.sessions, (s) -> s.metrics.rank is 0) then 'loses' else 'draws']
status += " #{session.name} #{states[session.metrics.rank]}"
@trigger 'statusUpdate', status
2014-06-30 22:16:26 -04:00
console.log 'Sending result back to server:'
2014-06-06 15:18:00 -04:00
console.log JSON.stringify results
if @options.headlessClient and @options.testing
2014-05-05 23:07:34 -04:00
return @fetchAndSimulateTask()
2014-02-14 13:49:16 -05:00
$.ajax
2014-06-30 22:16:26 -04:00
url: '/queue/scoring'
2014-02-14 13:49:16 -05:00
data: results
2014-06-30 22:16:26 -04:00
type: 'PUT'
parse: true
2014-02-14 13:49:16 -05:00
success: @handleTaskResultsTransferSuccess
error: @handleTaskResultsTransferError
2014-02-14 19:53:34 -05:00
complete: @cleanupAndSimulateAnotherTask
2014-02-14 13:49:16 -05:00
handleTaskResultsTransferSuccess: (result) =>
2014-05-20 11:00:44 -04:00
return if @destroyed
2014-02-14 13:49:16 -05:00
console.log "Task registration result: #{JSON.stringify result}"
@trigger 'statusUpdate', 'Results were successfully sent back to server!'
2014-05-05 23:07:34 -04:00
@simulatedByYou++
unless @options.headlessClient
2014-05-05 23:07:34 -04:00
simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1
$('#simulated-by-you').text(simulatedBy)
2014-02-14 13:49:16 -05:00
handleTaskResultsTransferError: (error) =>
2014-05-20 11:00:44 -04:00
return if @destroyed
@trigger 'statusUpdate', 'There was an error sending the results back to the server.'
2014-02-14 13:49:16 -05:00
console.log "Task registration error: #{JSON.stringify error}"
cleanupAndSimulateAnotherTask: =>
2014-05-20 11:00:44 -04:00
return if @destroyed
@cleanupSimulation()
if @options.background or @noTasks
@fetchAndSimulateOneGame()
else
@fetchAndSimulateTask()
2014-02-14 13:49:16 -05:00
cleanupSimulation: ->
@stopListening @god
@world = null
@level = null
2014-02-14 13:49:16 -05:00
formTaskResultsObject: (simulationResults) ->
taskResults =
taskID: @task.getTaskID()
receiptHandle: @task.getReceiptHandle()
2014-02-26 15:14:02 -05:00
originalSessionID: @task.getFirstSessionID()
originalSessionRank: -1
2014-02-14 13:49:16 -05:00
calculationTime: 500
sessions: []
simulator: @simulator
randomSeed: @task.world.randomSeed
2014-02-14 13:49:16 -05:00
for session in @task.getSessions()
2014-02-14 13:49:16 -05:00
sessionResult =
sessionID: session.sessionID
2014-02-18 14:46:14 -05:00
submitDate: session.submitDate
creator: session.creator
name: session.creatorName
totalScore: session.totalScore
2014-02-14 13:49:16 -05:00
metrics:
rank: @calculateSessionRank session.sessionID, simulationResults.goalStates, @task.generateTeamToSessionMap()
shouldUpdateLastOpponentSubmitDateForLeague: session.shouldUpdateLastOpponentSubmitDateForLeague
2014-02-26 15:14:02 -05:00
if session.sessionID is taskResults.originalSessionID
taskResults.originalSessionRank = sessionResult.metrics.rank
taskResults.originalSessionTeam = session.team
2014-02-14 13:49:16 -05:00
taskResults.sessions.push sessionResult
return taskResults
calculateSessionRank: (sessionID, goalStates, teamSessionMap) ->
2014-04-08 17:58:34 -04:00
ogreGoals = (goalState for key, goalState of goalStates when goalState.team is 'ogres')
humanGoals = (goalState for key, goalState of goalStates when goalState.team is 'humans')
ogresWon = _.all ogreGoals, {status: 'success'}
humansWon = _.all humanGoals, {status: 'success'}
if ogresWon is humansWon
2014-02-14 13:49:16 -05:00
return 0
2014-06-30 22:16:26 -04:00
else if ogresWon and teamSessionMap['ogres'] is sessionID
2014-02-14 13:49:16 -05:00
return 0
2014-06-30 22:16:26 -04:00
else if ogresWon and teamSessionMap['ogres'] isnt sessionID
2014-02-14 13:49:16 -05:00
return 1
2014-06-30 22:16:26 -04:00
else if humansWon and teamSessionMap['humans'] is sessionID
2014-02-14 13:49:16 -05:00
return 0
else
return 1
generateSpellsObject: ->
@currentUserCodeMap = @task.generateSpellKeyToSourceMap()
2014-02-14 13:49:16 -05:00
@spells = {}
for thang in @level.attributes.thangs
continue if @thangIsATemplate thang
@generateSpellKeyToSourceMapPropertiesFromThang thang
@spells
2014-02-14 13:49:16 -05:00
thangIsATemplate: (thang) ->
for component in thang.components
continue unless @componentHasProgrammableMethods component
for methodName, method of component.config.programmableMethods
return true if @methodBelongsToTemplateThang method
2014-02-14 13:49:16 -05: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) ->
spellKeyComponents = [thang, methodName]
spellKeyComponents[0] = _.string.slugify spellKeyComponents[0]
spellKey = spellKeyComponents.join '/'
spellKey
2014-02-26 20:45:08 -05:00
2014-02-14 13:49:16 -05:00
createSpellAndAssignName: (spellKey, spellName) ->
@spells[spellKey] ?= {}
@spells[spellKey].name = spellName
2014-02-14 13:49:16 -05:00
createSpellThang: (thang, method, spellKey) ->
@spells[spellKey].thangs ?= {}
@spells[spellKey].thangs[thang.id] ?= {}
spellTeam = @task.getSpellKeyToTeamMap()[spellKey]
playerTeams = @task.getPlayerTeams()
useProtectAPI = true
if spellTeam not in playerTeams
useProtectAPI = false
else
spellSession = _.filter(@task.getSessions(), {team: spellTeam})[0]
unless codeLanguage = spellSession?.submittedCodeLanguage
2014-06-30 22:16:26 -04:00
console.warn 'Session', spellSession.creatorName, spellSession.team, 'didn\'t have submittedCodeLanguage, just:', spellSession
@spells[spellKey].thangs[thang.id].aether = @createAether @spells[spellKey].name, method, useProtectAPI, codeLanguage ? 'javascript'
2014-02-14 13:49:16 -05:00
transpileSpell: (thang, spellKey, methodName) ->
2014-02-14 13:49:16 -05:00
slugifiedThangID = _.string.slugify thang.id
2014-05-15 19:43:16 -04:00
generatedSpellKey = [slugifiedThangID,methodName].join '/'
2014-06-30 22:16:26 -04:00
source = @currentUserCodeMap[generatedSpellKey] ? ''
aether = @spells[spellKey].thangs[thang.id].aether
2016-05-05 16:22:30 -04:00
#unless _.contains(@task.spellKeysToTranspile, generatedSpellKey)
try
aether.transpile source
catch e
console.log "Couldn't transpile #{spellKey}:\n#{source}\n", e
aether.transpile ''
2014-02-14 13:49:16 -05:00
createAether: (methodName, method, useProtectAPI, codeLanguage) ->
2016-05-05 16:22:30 -04:00
aetherOptions = createAetherOptions functionName: methodName, codeLanguage: codeLanguage, skipProtectAPI: not useProtectAPI, useInterpreter: true
2014-02-14 13:49:16 -05:00
return new Aether aetherOptions
class SimulationTask
constructor: (@rawData) ->
@spellKeyToTeamMap = {}
2014-02-14 13:49:16 -05:00
getLevelName: ->
2014-04-26 17:21:26 -04:00
levelName = @rawData.sessions?[0]?.levelID
2014-02-14 13:49:16 -05:00
return levelName if levelName?
2014-06-30 22:16:26 -04:00
@throwMalformedTaskError 'The level name couldn\'t be deduced from the task.'
2014-02-14 13:49:16 -05:00
generateTeamToSessionMap: ->
teamSessionMap = {}
for session in @rawData.sessions
2014-06-30 22:16:26 -04:00
@throwMalformedTaskError 'Two players share the same team' if teamSessionMap[session.team]?
2014-02-14 13:49:16 -05:00
teamSessionMap[session.team] = session.sessionID
teamSessionMap
throwMalformedTaskError: (errorString) ->
throw new Error "The task was malformed, reason: #{errorString}"
getFirstSessionID: -> @rawData.sessions[0].sessionID
getSecondSessionID: -> @rawData.sessions[1].sessionID
2014-02-14 13:49:16 -05:00
getTaskID: -> @rawData.taskID
getReceiptHandle: -> @rawData.receiptHandle
getSessions: -> @rawData.sessions
getSpellKeyToTeamMap: -> @spellKeyToTeamMap
getPlayerTeams: -> _.pluck @rawData.sessions, 'team'
2014-02-14 13:49:16 -05:00
setWorld: (@world) ->
2014-02-14 13:49:16 -05:00
generateSpellKeyToSourceMap: ->
2016-05-05 16:22:30 -04:00
# TODO: we always now only have hero-placeholder/plan vs. hero-placeholder-1/plan on humans vs. ogres, always just have to retranspile for Esper, and never need to transpile for NPCs or other methods, so we can get rid of almost all of this stuff.
playerTeams = _.pluck @rawData.sessions, 'team'
2014-02-14 13:49:16 -05:00
spellKeyToSourceMap = {}
for session in @rawData.sessions
teamSpells = session.teamSpells[session.team]
allTeams = _.keys session.teamSpells
for team in allTeams
for spell in session.teamSpells[team]
@spellKeyToTeamMap[spell] = team
teamCode = {}
2016-05-05 16:22:30 -04:00
for thangName, thangSpells of session.submittedCode
for spellName, spell of thangSpells
2014-06-30 22:16:26 -04:00
fullSpellName = [thangName, spellName].join '/'
if _.contains(teamSpells, fullSpellName)
2016-05-05 16:22:30 -04:00
teamCode[fullSpellName] = LZString.decompressFromUTF16 spell
_.merge spellKeyToSourceMap, teamCode
2014-02-26 20:45:08 -05:00
2014-02-14 13:49:16 -05:00
spellKeyToSourceMap