mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 17:45:40 -05:00
Simulate games in background while playing levels if we think there's extra compute power
This commit is contained in:
parent
9d21f87345
commit
95c703a7df
18 changed files with 148 additions and 65 deletions
|
@ -71,7 +71,7 @@ setUpBackboneMediator = ->
|
||||||
if false # Debug which events are being fired
|
if false # Debug which events are being fired
|
||||||
originalPublish = Backbone.Mediator.publish
|
originalPublish = Backbone.Mediator.publish
|
||||||
Backbone.Mediator.publish = ->
|
Backbone.Mediator.publish = ->
|
||||||
console.log 'Publishing event:', arguments...
|
console.log 'Publishing event:', arguments... unless /(tick|frame-changed)/.test(arguments[0])
|
||||||
originalPublish.apply Backbone.Mediator, arguments
|
originalPublish.apply Backbone.Mediator, arguments
|
||||||
|
|
||||||
setUpMoment = ->
|
setUpMoment = ->
|
||||||
|
|
|
@ -25,9 +25,10 @@ module.exports = class Angel extends CocoClass
|
||||||
super()
|
super()
|
||||||
@say 'Got my wings.'
|
@say 'Got my wings.'
|
||||||
isIE = window.navigator and (window.navigator.userAgent.search('MSIE') isnt -1 or window.navigator.appName is 'Microsoft Internet Explorer')
|
isIE = window.navigator and (window.navigator.userAgent.search('MSIE') isnt -1 or window.navigator.appName is 'Microsoft Internet Explorer')
|
||||||
if isIE or @shared.headless
|
slowerSimulations = isIE #or @shared.headless
|
||||||
# Since IE is so slow to serialize without transferable objects, we can't trust it.
|
# Since IE is so slow to serialize without transferable objects, we can't trust it.
|
||||||
# We also noticed the headless_client simulator needing more time. (This does both Simulators, though.)
|
# We also noticed the headless_client simulator needing more time. (This does both Simulators, though.) If we need to use lots of headless clients, enable this.
|
||||||
|
if slowerSimulations
|
||||||
@infiniteLoopIntervalDuration *= 10
|
@infiniteLoopIntervalDuration *= 10
|
||||||
@infiniteLoopTimeoutDuration *= 10
|
@infiniteLoopTimeoutDuration *= 10
|
||||||
@abortTimeoutDuration *= 10
|
@abortTimeoutDuration *= 10
|
||||||
|
@ -89,7 +90,7 @@ module.exports = class Angel extends CocoClass
|
||||||
|
|
||||||
# We have to abort like an infinite loop if we see one of these; they're not really recoverable
|
# We have to abort like an infinite loop if we see one of these; they're not really recoverable
|
||||||
when 'non-user-code-problem'
|
when 'non-user-code-problem'
|
||||||
Backbone.Mediator.publish 'god:non-user-code-problem', problem: event.data.problem
|
Backbone.Mediator.publish 'god:non-user-code-problem', problem: event.data.problem, god: @shared.god
|
||||||
if @shared.firstWorld
|
if @shared.firstWorld
|
||||||
@infinitelyLooped(false, true) # For now, this should do roughly the right thing if it happens during load.
|
@infinitelyLooped(false, true) # For now, this should do roughly the right thing if it happens during load.
|
||||||
else
|
else
|
||||||
|
@ -108,9 +109,9 @@ module.exports = class Angel extends CocoClass
|
||||||
when 'console-log'
|
when 'console-log'
|
||||||
@log event.data.args...
|
@log event.data.args...
|
||||||
when 'user-code-problem'
|
when 'user-code-problem'
|
||||||
Backbone.Mediator.publish 'god:user-code-problem', problem: event.data.problem
|
Backbone.Mediator.publish 'god:user-code-problem', problem: event.data.problem, god: @shared.god
|
||||||
when 'world-load-progress-changed'
|
when 'world-load-progress-changed'
|
||||||
Backbone.Mediator.publish 'god:world-load-progress-changed', progress: event.data.progress
|
Backbone.Mediator.publish 'god:world-load-progress-changed', progress: event.data.progress, god: @shared.god
|
||||||
unless event.data.progress is 1 or @work.preload or @work.headless or @work.synchronous or @deserializationQueue.length or (@shared.firstWorld and not @shared.spectate)
|
unless event.data.progress is 1 or @work.preload or @work.headless or @work.synchronous or @deserializationQueue.length or (@shared.firstWorld and not @shared.spectate)
|
||||||
@worker.postMessage func: 'serializeFramesSoFar' # Stream it!
|
@worker.postMessage func: 'serializeFramesSoFar' # Stream it!
|
||||||
|
|
||||||
|
@ -126,7 +127,8 @@ module.exports = class Angel extends CocoClass
|
||||||
|
|
||||||
beholdGoalStates: (goalStates, overallStatus, preload=false) ->
|
beholdGoalStates: (goalStates, overallStatus, preload=false) ->
|
||||||
return if @aborting
|
return if @aborting
|
||||||
Backbone.Mediator.publish 'god:goals-calculated', goalStates: goalStates, preload: preload, overallStatus: overallStatus
|
Backbone.Mediator.publish 'god:goals-calculated', goalStates: goalStates, preload: preload, overallStatus: overallStatus, god: @shared.god
|
||||||
|
@shared.god.trigger 'goals-calculated', goalStates: goalStates, preload: preload, overallStatus: overallStatus
|
||||||
@finishWork() if @shared.headless
|
@finishWork() if @shared.headless
|
||||||
|
|
||||||
beholdWorld: (serialized, goalStates, startFrame, endFrame, streamingWorld) ->
|
beholdWorld: (serialized, goalStates, startFrame, endFrame, streamingWorld) ->
|
||||||
|
@ -176,8 +178,9 @@ module.exports = class Angel extends CocoClass
|
||||||
return if @aborting
|
return if @aborting
|
||||||
problem = type: 'runtime', level: 'error', id: 'runtime_InfiniteLoop', message: 'Code never finished. It\'s either really slow or has an infinite loop.'
|
problem = type: 'runtime', level: 'error', id: 'runtime_InfiniteLoop', message: 'Code never finished. It\'s either really slow or has an infinite loop.'
|
||||||
problem.message = 'Escape pressed; code aborted.' if escaped
|
problem.message = 'Escape pressed; code aborted.' if escaped
|
||||||
Backbone.Mediator.publish 'god:user-code-problem', problem: problem
|
Backbone.Mediator.publish 'god:user-code-problem', problem: problem, god: @shared.god
|
||||||
Backbone.Mediator.publish 'god:infinite-loop', firstWorld: @shared.firstWorld, nonUserCodeProblem: nonUserCodeProblem
|
Backbone.Mediator.publish 'god:infinite-loop', firstWorld: @shared.firstWorld, nonUserCodeProblem: nonUserCodeProblem, god: @shared.god
|
||||||
|
@shared.god.trigger 'infinite-loop', firstWorld: @shared.firstWorld, nonUserCodeProblem: nonUserCodeProblem # For Simulator. TODO: refactor all the god:* Mediator events to be local events.
|
||||||
@reportLoadError() if nonUserCodeProblem
|
@reportLoadError() if nonUserCodeProblem
|
||||||
@fireWorker()
|
@fireWorker()
|
||||||
|
|
||||||
|
@ -308,7 +311,7 @@ module.exports = class Angel extends CocoClass
|
||||||
i = 0
|
i = 0
|
||||||
while i < work.testWorld.totalFrames
|
while i < work.testWorld.totalFrames
|
||||||
frame = work.testWorld.getFrame i++
|
frame = work.testWorld.getFrame i++
|
||||||
Backbone.Mediator.publish 'god:world-load-progress-changed', progress: 1
|
Backbone.Mediator.publish 'god:world-load-progress-changed', progress: 1, god: @shared.god
|
||||||
work.testWorld.ended = true
|
work.testWorld.ended = true
|
||||||
system.finish work.testWorld.thangs for system in work.testWorld.systems
|
system.finish work.testWorld.thangs for system in work.testWorld.systems
|
||||||
work.t2 = now()
|
work.t2 = now()
|
||||||
|
|
|
@ -25,6 +25,7 @@ module.exports = class God extends CocoClass
|
||||||
workerCode: options.workerCode or '/javascripts/workers/worker_world.js' # Either path or function
|
workerCode: options.workerCode or '/javascripts/workers/worker_world.js' # Either path or function
|
||||||
headless: options.headless # Whether to just simulate the goals, or to deserialize all simulation results
|
headless: options.headless # Whether to just simulate the goals, or to deserialize all simulation results
|
||||||
spectate: options.spectate
|
spectate: options.spectate
|
||||||
|
god: @
|
||||||
godNick: @nick
|
godNick: @nick
|
||||||
workQueue: []
|
workQueue: []
|
||||||
firstWorld: true
|
firstWorld: true
|
||||||
|
@ -61,6 +62,7 @@ module.exports = class God extends CocoClass
|
||||||
setWorldClassMap: (worldClassMap) -> @angelsShare.worldClassMap = worldClassMap
|
setWorldClassMap: (worldClassMap) -> @angelsShare.worldClassMap = worldClassMap
|
||||||
|
|
||||||
onTomeCast: (e) ->
|
onTomeCast: (e) ->
|
||||||
|
return unless e.god is @
|
||||||
@lastSubmissionCount = e.submissionCount
|
@lastSubmissionCount = e.submissionCount
|
||||||
@lastFlagHistory = (flag for flag in e.flagHistory when flag.source isnt 'code')
|
@lastFlagHistory = (flag for flag in e.flagHistory when flag.source isnt 'code')
|
||||||
@lastDifficulty = e.difficulty
|
@lastDifficulty = e.difficulty
|
||||||
|
@ -142,9 +144,9 @@ module.exports = class God extends CocoClass
|
||||||
when 'console-log'
|
when 'console-log'
|
||||||
console.log "|#{@nick}'s debugger|", event.data.args...
|
console.log "|#{@nick}'s debugger|", event.data.args...
|
||||||
when 'debug-value-return'
|
when 'debug-value-return'
|
||||||
Backbone.Mediator.publish 'god:debug-value-return', event.data.serialized
|
Backbone.Mediator.publish 'god:debug-value-return', event.data.serialized, god: @
|
||||||
when 'debug-world-load-progress-changed'
|
when 'debug-world-load-progress-changed'
|
||||||
Backbone.Mediator.publish 'god:debug-world-load-progress-changed', progress: event.data.progress
|
Backbone.Mediator.publish 'god:debug-world-load-progress-changed', progress: event.data.progress, god: @
|
||||||
|
|
||||||
onNewWorldCreated: (e) ->
|
onNewWorldCreated: (e) ->
|
||||||
@currentUserCodeMap = @filterUserCodeMapWhenFromWorld e.world.userCodeMap
|
@currentUserCodeMap = @filterUserCodeMapWhenFromWorld e.world.userCodeMap
|
||||||
|
|
|
@ -45,6 +45,7 @@ module.exports = class Simulator extends CocoClass
|
||||||
humansGameID: humanGameID
|
humansGameID: humanGameID
|
||||||
ogresGameID: ogresGameID
|
ogresGameID: ogresGameID
|
||||||
simulator: @simulator
|
simulator: @simulator
|
||||||
|
background: Boolean(@options.background)
|
||||||
error: (errorData) ->
|
error: (errorData) ->
|
||||||
console.warn "There was an error fetching two games! #{JSON.stringify errorData}"
|
console.warn "There was an error fetching two games! #{JSON.stringify errorData}"
|
||||||
if errorData?.responseText?.indexOf("Old simulator") isnt -1
|
if errorData?.responseText?.indexOf("Old simulator") isnt -1
|
||||||
|
@ -84,8 +85,8 @@ module.exports = class Simulator extends CocoClass
|
||||||
@handleSingleSimulationError error
|
@handleSingleSimulationError error
|
||||||
|
|
||||||
commenceSingleSimulation: ->
|
commenceSingleSimulation: ->
|
||||||
Backbone.Mediator.subscribeOnce 'god:infinite-loop', @handleSingleSimulationInfiniteLoop, @
|
@listenToOnce @god, 'infinite-loop', @handleSingleSimulationInfiniteLoop
|
||||||
Backbone.Mediator.subscribeOnce 'god:goals-calculated', @processSingleGameResults, @
|
@listenToOnce @god, 'goals-calculated', @processSingleGameResults
|
||||||
@god.createWorld @generateSpellsObject()
|
@god.createWorld @generateSpellsObject()
|
||||||
|
|
||||||
handleSingleSimulationError: (error) ->
|
handleSingleSimulationError: (error) ->
|
||||||
|
@ -96,7 +97,7 @@ module.exports = class Simulator extends CocoClass
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
@cleanupAndSimulateAnotherTask()
|
@cleanupAndSimulateAnotherTask()
|
||||||
|
|
||||||
handleSingleSimulationInfiniteLoop: ->
|
handleSingleSimulationInfiniteLoop: (e) ->
|
||||||
console.log 'There was an infinite loop in the single game!'
|
console.log 'There was an infinite loop in the single game!'
|
||||||
return if @destroyed
|
return if @destroyed
|
||||||
if @options.headlessClient and @options.simulateOnlyOneGame
|
if @options.headlessClient and @options.simulateOnlyOneGame
|
||||||
|
@ -105,7 +106,6 @@ module.exports = class Simulator extends CocoClass
|
||||||
@cleanupAndSimulateAnotherTask()
|
@cleanupAndSimulateAnotherTask()
|
||||||
|
|
||||||
processSingleGameResults: (simulationResults) ->
|
processSingleGameResults: (simulationResults) ->
|
||||||
return console.error "Weird, we destroyed the Simulator before it processed results?" if @destroyed
|
|
||||||
try
|
try
|
||||||
taskResults = @formTaskResultsObject simulationResults
|
taskResults = @formTaskResultsObject simulationResults
|
||||||
catch error
|
catch error
|
||||||
|
@ -165,6 +165,7 @@ module.exports = class Simulator extends CocoClass
|
||||||
@simulateAnotherTaskAfterDelay()
|
@simulateAnotherTaskAfterDelay()
|
||||||
|
|
||||||
handleNoGamesResponse: ->
|
handleNoGamesResponse: ->
|
||||||
|
@noTasks = true
|
||||||
info = 'Finding game to simulate...'
|
info = 'Finding game to simulate...'
|
||||||
console.log info
|
console.log info
|
||||||
@trigger 'statusUpdate', info
|
@trigger 'statusUpdate', info
|
||||||
|
@ -223,7 +224,7 @@ module.exports = class Simulator extends CocoClass
|
||||||
@god.setLevel @level.serialize(@supermodel, @session, @otherSession)
|
@god.setLevel @level.serialize(@supermodel, @session, @otherSession)
|
||||||
@god.setLevelSessionIDs (session.sessionID 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'), null, {headless: true}
|
||||||
humanFlagHistory = _.filter @session.get('state')?.flagHistory ? [], (event) => event.source isnt 'code' and event.team is (@session.get('team') ? 'humans')
|
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')
|
ogreFlagHistory = _.filter @otherSession.get('state')?.flagHistory ? [], (event) => event.source isnt 'code' and event.team is (@otherSession.get('team') ? 'ogres')
|
||||||
@god.lastFlagHistory = humanFlagHistory.concat ogreFlagHistory
|
@god.lastFlagHistory = humanFlagHistory.concat ogreFlagHistory
|
||||||
|
@ -232,8 +233,8 @@ module.exports = class Simulator extends CocoClass
|
||||||
@god.lastDifficulty = 0
|
@god.lastDifficulty = 0
|
||||||
|
|
||||||
commenceSimulationAndSetupCallback: ->
|
commenceSimulationAndSetupCallback: ->
|
||||||
Backbone.Mediator.subscribeOnce 'god:infinite-loop', @onInfiniteLoop, @
|
@listenToOnce @god, 'infinite-loop', @onInfiniteLoop
|
||||||
Backbone.Mediator.subscribeOnce 'god:goals-calculated', @processResults, @
|
@listenToOnce @god, 'goals-calculated', @processResults
|
||||||
@god.createWorld @generateSpellsObject()
|
@god.createWorld @generateSpellsObject()
|
||||||
|
|
||||||
# Search for leaks, headless-client only.
|
# Search for leaks, headless-client only.
|
||||||
|
@ -260,14 +261,13 @@ module.exports = class Simulator extends CocoClass
|
||||||
process.exit()
|
process.exit()
|
||||||
@hd = new @memwatch.HeapDiff()
|
@hd = new @memwatch.HeapDiff()
|
||||||
|
|
||||||
onInfiniteLoop: ->
|
onInfiniteLoop: (e) ->
|
||||||
return if @destroyed
|
return if @destroyed
|
||||||
console.warn 'Skipping infinitely looping game.'
|
console.warn 'Skipping infinitely looping game.'
|
||||||
@trigger 'statusUpdate', "Infinite loop detected; grabbing a new game in #{@retryDelayInSeconds} seconds."
|
@trigger 'statusUpdate', "Infinite loop detected; grabbing a new game in #{@retryDelayInSeconds} seconds."
|
||||||
_.delay @cleanupAndSimulateAnotherTask, @retryDelayInSeconds * 1000
|
_.delay @cleanupAndSimulateAnotherTask, @retryDelayInSeconds * 1000
|
||||||
|
|
||||||
processResults: (simulationResults) ->
|
processResults: (simulationResults) ->
|
||||||
return console.error "Weird, we destroyed the Simulator before it processed results?" if @destroyed
|
|
||||||
try
|
try
|
||||||
taskResults = @formTaskResultsObject simulationResults
|
taskResults = @formTaskResultsObject simulationResults
|
||||||
catch error
|
catch error
|
||||||
|
@ -317,9 +317,13 @@ module.exports = class Simulator extends CocoClass
|
||||||
cleanupAndSimulateAnotherTask: =>
|
cleanupAndSimulateAnotherTask: =>
|
||||||
return if @destroyed
|
return if @destroyed
|
||||||
@cleanupSimulation()
|
@cleanupSimulation()
|
||||||
@fetchAndSimulateTask()
|
if @options.background or @noTasks
|
||||||
|
@fetchAndSimulateOneGame()
|
||||||
|
else
|
||||||
|
@fetchAndSimulateTask()
|
||||||
|
|
||||||
cleanupSimulation: ->
|
cleanupSimulation: ->
|
||||||
|
@stopListening @god
|
||||||
@world = null
|
@world = null
|
||||||
@level = null
|
@level = null
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,9 @@ module.exports = class GoalManager extends CocoClass
|
||||||
nextGoalID: 0
|
nextGoalID: 0
|
||||||
nicks: ['GoalManager']
|
nicks: ['GoalManager']
|
||||||
|
|
||||||
constructor: (@world, @initialGoals, @team) ->
|
constructor: (@world, @initialGoals, @team, options) ->
|
||||||
super()
|
super()
|
||||||
|
@options = options or {}
|
||||||
@init()
|
@init()
|
||||||
|
|
||||||
init: ->
|
init: ->
|
||||||
|
@ -107,6 +108,7 @@ module.exports = class GoalManager extends CocoClass
|
||||||
@addNewSubscription(channel, f(channel))
|
@addNewSubscription(channel, f(channel))
|
||||||
|
|
||||||
notifyGoalChanges: ->
|
notifyGoalChanges: ->
|
||||||
|
return if @options.headless
|
||||||
overallStatus = @checkOverallStatus()
|
overallStatus = @checkOverallStatus()
|
||||||
event =
|
event =
|
||||||
goalStates: @goalStates
|
goalStates: @goalStates
|
||||||
|
|
|
@ -199,7 +199,6 @@ 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()
|
||||||
Backbone.Mediator.publish 'supermodel:load-progress-changed', progress: @progress
|
|
||||||
|
|
||||||
setMaxProgress: (@maxProgress) ->
|
setMaxProgress: (@maxProgress) ->
|
||||||
resetProgress: -> @progress = 0
|
resetProgress: -> @progress = 0
|
||||||
|
|
|
@ -27,13 +27,16 @@ worldUpdatedEventSchema = c.object {required: ['world', 'firstWorld', 'goalState
|
||||||
finished: {type: 'boolean'}
|
finished: {type: 'boolean'}
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
'god:user-code-problem': c.object {required: ['problem']},
|
'god:user-code-problem': c.object {required: ['problem', 'god']},
|
||||||
|
god: {type: 'object'}
|
||||||
problem: {type: 'object'}
|
problem: {type: 'object'}
|
||||||
|
|
||||||
'god:non-user-code-problem': c.object {required: ['problem']},
|
'god:non-user-code-problem': c.object {required: ['problem', 'god']},
|
||||||
|
god: {type: 'object'}
|
||||||
problem: {type: 'object'}
|
problem: {type: 'object'}
|
||||||
|
|
||||||
'god:infinite-loop': c.object {required: ['firstWorld']},
|
'god:infinite-loop': c.object {required: ['firstWorld', 'god']},
|
||||||
|
god: {type: 'object'}
|
||||||
firstWorld: {type: 'boolean'}
|
firstWorld: {type: 'boolean'}
|
||||||
nonUserCodeProblem: {type: 'boolean'}
|
nonUserCodeProblem: {type: 'boolean'}
|
||||||
|
|
||||||
|
@ -41,17 +44,21 @@ module.exports =
|
||||||
|
|
||||||
'god:streaming-world-updated': worldUpdatedEventSchema
|
'god:streaming-world-updated': worldUpdatedEventSchema
|
||||||
|
|
||||||
'god:goals-calculated': c.object {required: ['goalStates']},
|
'god:goals-calculated': c.object {required: ['goalStates', 'god']},
|
||||||
|
god: {type: 'object'}
|
||||||
goalStates: goalStatesSchema
|
goalStates: goalStatesSchema
|
||||||
preload: {type: 'boolean'}
|
preload: {type: 'boolean'}
|
||||||
overallStatus: {type: ['string', 'null'], enum: ['success', 'failure', 'incomplete', null]}
|
overallStatus: {type: ['string', 'null'], enum: ['success', 'failure', 'incomplete', null]}
|
||||||
|
|
||||||
'god:world-load-progress-changed': c.object {required: ['progress']},
|
'god:world-load-progress-changed': c.object {required: ['progress', 'god']},
|
||||||
|
god: {type: 'object'}
|
||||||
progress: {type: 'number', minimum: 0, maximum: 1}
|
progress: {type: 'number', minimum: 0, maximum: 1}
|
||||||
|
|
||||||
'god:debug-world-load-progress-changed': c.object {required: ['progress']},
|
'god:debug-world-load-progress-changed': c.object {required: ['progress', 'god']},
|
||||||
|
god: {type: 'object'}
|
||||||
progress: {type: 'number', minimum: 0, maximum: 1}
|
progress: {type: 'number', minimum: 0, maximum: 1}
|
||||||
|
|
||||||
'god:debug-value-return': c.object {required: ['key']},
|
'god:debug-value-return': c.object {required: ['key', 'god']},
|
||||||
|
god: {type: 'object'}
|
||||||
key: {type: 'string'}
|
key: {type: 'string'}
|
||||||
value: {}
|
value: {}
|
||||||
|
|
|
@ -48,9 +48,6 @@ module.exports =
|
||||||
session: {type: 'object'}
|
session: {type: 'object'}
|
||||||
level: {type: 'object'}
|
level: {type: 'object'}
|
||||||
|
|
||||||
'supermodel:load-progress-changed': c.object {required: ['progress']},
|
|
||||||
progress: {type: 'number', minimum: 0, maximum: 1}
|
|
||||||
|
|
||||||
'buy-gems-modal:update-products': { }
|
'buy-gems-modal:update-products': { }
|
||||||
|
|
||||||
'buy-gems-modal:purchase-initiated': c.object {required: ['productID']},
|
'buy-gems-modal:purchase-initiated': c.object {required: ['productID']},
|
||||||
|
|
|
@ -7,13 +7,14 @@ module.exports =
|
||||||
preload: {type: 'boolean'}
|
preload: {type: 'boolean'}
|
||||||
realTime: {type: 'boolean'}
|
realTime: {type: 'boolean'}
|
||||||
|
|
||||||
'tome:cast-spells': c.object {title: 'Cast Spells', description: 'Published when spells are cast', required: ['spells', 'preload', 'realTime', 'submissionCount', 'flagHistory', 'difficulty']},
|
'tome:cast-spells': c.object {title: 'Cast Spells', description: 'Published when spells are cast', required: ['spells', 'preload', 'realTime', 'submissionCount', 'flagHistory', 'difficulty', 'god']},
|
||||||
spells: [type: 'object']
|
spells: {type: 'object'}
|
||||||
preload: [type: 'boolean']
|
preload: {type: 'boolean'}
|
||||||
realTime: [type: 'boolean']
|
realTime: {type: 'boolean'}
|
||||||
submissionCount: [type: 'integer']
|
submissionCount: {type: 'integer'}
|
||||||
flagHistory: [type: 'array']
|
flagHistory: {type: 'array'}
|
||||||
difficulty: [type: 'integer']
|
difficulty: {type: 'integer'}
|
||||||
|
god: {type: 'object'}
|
||||||
|
|
||||||
'tome:manual-cast': c.object {title: 'Manually Cast Spells', description: 'Published when you wish to manually recast all spells', required: []},
|
'tome:manual-cast': c.object {title: 'Manually Cast Spells', description: 'Published when you wish to manually recast all spells', required: []},
|
||||||
realTime: {type: 'boolean'}
|
realTime: {type: 'boolean'}
|
||||||
|
|
|
@ -16,12 +16,7 @@ module.exports = class SimulateTabView extends CocoView
|
||||||
@simulatorsLeaderboardData = new SimulatorsLeaderboardData(me)
|
@simulatorsLeaderboardData = new SimulatorsLeaderboardData(me)
|
||||||
@simulatorsLeaderboardDataRes = @supermodel.addModelResource(@simulatorsLeaderboardData, 'top_simulators', {cache: false})
|
@simulatorsLeaderboardDataRes = @supermodel.addModelResource(@simulatorsLeaderboardData, 'top_simulators', {cache: false})
|
||||||
@simulatorsLeaderboardDataRes.load()
|
@simulatorsLeaderboardDataRes.load()
|
||||||
require 'vendor/aether-javascript'
|
require "vendor/aether-#{codeLanguage}" for codeLanguage in ['javascript', 'python', 'coffeescript', 'lua', 'clojure', 'io']
|
||||||
require 'vendor/aether-python'
|
|
||||||
require 'vendor/aether-coffeescript'
|
|
||||||
require 'vendor/aether-lua'
|
|
||||||
require 'vendor/aether-clojure'
|
|
||||||
require 'vendor/aether-io'
|
|
||||||
|
|
||||||
onLoaded: ->
|
onLoaded: ->
|
||||||
super()
|
super()
|
||||||
|
|
|
@ -187,7 +187,7 @@ module.exports = class SpectateLevelView extends RootView
|
||||||
# callbacks
|
# callbacks
|
||||||
|
|
||||||
onInfiniteLoop: (e) ->
|
onInfiniteLoop: (e) ->
|
||||||
return unless e.firstWorld
|
return unless e.firstWorld and e.god is @god
|
||||||
@openModalView new InfiniteLoopModal()
|
@openModalView new InfiniteLoopModal()
|
||||||
window.tracker?.trackEvent 'Saw Initial Infinite Loop', level: @world.name, label: @world.name
|
window.tracker?.trackEvent 'Saw Initial Infinite Loop', level: @world.name, label: @world.name
|
||||||
|
|
||||||
|
|
|
@ -44,12 +44,14 @@ module.exports = class LevelLoadingView extends CocoView
|
||||||
@$el.addClass('manually-sized').css('height', newHeight)
|
@$el.addClass('manually-sized').css('height', newHeight)
|
||||||
|
|
||||||
onLevelLoaded: (e) ->
|
onLevelLoaded: (e) ->
|
||||||
|
return if @level
|
||||||
@level = e.level
|
@level = e.level
|
||||||
@prepareGoals e
|
@prepareGoals e
|
||||||
@prepareTip()
|
@prepareTip()
|
||||||
@prepareIntro()
|
@prepareIntro()
|
||||||
|
|
||||||
onSessionLoaded: (e) ->
|
onSessionLoaded: (e) ->
|
||||||
|
return if @session
|
||||||
@session = e.session if e.session.get('creator') is me.id
|
@session = e.session if e.session.get('creator') is me.id
|
||||||
|
|
||||||
prepareGoals: (e) ->
|
prepareGoals: (e) ->
|
||||||
|
|
|
@ -19,6 +19,7 @@ LevelComponent = require 'models/LevelComponent'
|
||||||
Article = require 'models/Article'
|
Article = require 'models/Article'
|
||||||
Camera = require 'lib/surface/Camera'
|
Camera = require 'lib/surface/Camera'
|
||||||
AudioPlayer = require 'lib/AudioPlayer'
|
AudioPlayer = require 'lib/AudioPlayer'
|
||||||
|
Simulator = require 'lib/simulator/Simulator'
|
||||||
|
|
||||||
# subviews
|
# subviews
|
||||||
LevelLoadingView = require './LevelLoadingView'
|
LevelLoadingView = require './LevelLoadingView'
|
||||||
|
@ -139,11 +140,11 @@ module.exports = class PlayLevelView extends RootView
|
||||||
trackLevelLoadEnd: ->
|
trackLevelLoadEnd: ->
|
||||||
return if @isEditorPreview
|
return if @isEditorPreview
|
||||||
@loadEndTime = new Date()
|
@loadEndTime = new Date()
|
||||||
loadDuration = @loadEndTime - @loadStartTime
|
@loadDuration = @loadEndTime - @loadStartTime
|
||||||
console.debug "Level unveiled after #{(loadDuration / 1000).toFixed(2)}s"
|
console.debug "Level unveiled after #{(@loadDuration / 1000).toFixed(2)}s"
|
||||||
unless @observing
|
unless @observing
|
||||||
application.tracker?.trackEvent 'Finished Level Load', category: 'Play Level', label: @levelID, level: @levelID, loadDuration: loadDuration
|
application.tracker?.trackEvent 'Finished Level Load', category: 'Play Level', label: @levelID, level: @levelID, loadDuration: @loadDuration
|
||||||
application.tracker?.trackTiming loadDuration, 'Level Load Time', @levelID, @levelID
|
application.tracker?.trackTiming @loadDuration, 'Level Load Time', @levelID, @levelID
|
||||||
|
|
||||||
# CocoView overridden methods ###############################################
|
# CocoView overridden methods ###############################################
|
||||||
|
|
||||||
|
@ -195,6 +196,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
@worldLoadFakeResources.push @supermodel.addSomethingResource "world_simulation_#{percent}%", 1
|
@worldLoadFakeResources.push @supermodel.addSomethingResource "world_simulation_#{percent}%", 1
|
||||||
|
|
||||||
onWorldLoadProgressChanged: (e) ->
|
onWorldLoadProgressChanged: (e) ->
|
||||||
|
return unless e.god is @god
|
||||||
return unless @worldLoadFakeResources
|
return unless @worldLoadFakeResources
|
||||||
@lastWorldLoadPercent ?= 0
|
@lastWorldLoadPercent ?= 0
|
||||||
worldLoadPercent = Math.floor 100 * e.progress
|
worldLoadPercent = Math.floor 100 * e.progress
|
||||||
|
@ -240,7 +242,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
@god.setGoalManager @goalManager
|
@god.setGoalManager @goalManager
|
||||||
|
|
||||||
insertSubviews: ->
|
insertSubviews: ->
|
||||||
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, otherSession: @otherSession, thangs: @world.thangs, supermodel: @supermodel, level: @level, observing: @observing, courseID: @courseID, courseInstanceID: @courseInstanceID
|
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, otherSession: @otherSession, thangs: @world.thangs, supermodel: @supermodel, level: @level, observing: @observing, courseID: @courseID, courseInstanceID: @courseInstanceID, god: @god
|
||||||
@insertSubView new LevelPlaybackView session: @session, level: @level
|
@insertSubView new LevelPlaybackView session: @session, level: @level
|
||||||
@insertSubView new GoalsView {}
|
@insertSubView new GoalsView {}
|
||||||
@insertSubView new LevelFlagsView levelID: @levelID, world: @world if @$el.hasClass 'flags'
|
@insertSubView new LevelFlagsView levelID: @levelID, world: @world if @$el.hasClass 'flags'
|
||||||
|
@ -273,6 +275,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
# Load Completed Setup ######################################################
|
# Load Completed Setup ######################################################
|
||||||
|
|
||||||
onSessionLoaded: (e) ->
|
onSessionLoaded: (e) ->
|
||||||
|
return if @session
|
||||||
Backbone.Mediator.publish "ipad:language-chosen", language: e.session.get('codeLanguage') ? "python"
|
Backbone.Mediator.publish "ipad:language-chosen", language: e.session.get('codeLanguage') ? "python"
|
||||||
# Just the level and session have been loaded by the level loader
|
# Just the level and session have been loaded by the level loader
|
||||||
if e.level.get('slug') is 'zero-sum'
|
if e.level.get('slug') is 'zero-sum'
|
||||||
|
@ -288,7 +291,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
e.session.set 'heroConfig', {"thangType":raider,"inventory":{}}
|
e.session.set 'heroConfig', {"thangType":raider,"inventory":{}}
|
||||||
else if e.level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop'] and not _.size e.session.get('heroConfig')?.inventory ? {}
|
else if e.level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop'] and not _.size e.session.get('heroConfig')?.inventory ? {}
|
||||||
@setupManager?.destroy()
|
@setupManager?.destroy()
|
||||||
@setupManager = new LevelSetupManager({supermodel: @supermodel, level: @level, levelID: @levelID, parent: @, session: @session, courseID: @courseID, courseInstanceID: @courseInstanceID})
|
@setupManager = new LevelSetupManager({supermodel: @supermodel, level: e.level, levelID: @levelID, parent: @, session: e.session, courseID: @courseID, courseInstanceID: @courseInstanceID})
|
||||||
@setupManager.open()
|
@setupManager.open()
|
||||||
|
|
||||||
@onRealTimeMultiplayerLevelLoaded e.session if e.level.get('type') in ['hero-ladder', 'course-ladder']
|
@onRealTimeMultiplayerLevelLoaded e.session if e.level.get('type') in ['hero-ladder', 'course-ladder']
|
||||||
|
@ -365,6 +368,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
# TODO: Is it possible to create a Mongoose ObjectId for 'ls', instead of the string returned from get()?
|
# TODO: Is it possible to create a Mongoose ObjectId for 'ls', instead of the string returned from get()?
|
||||||
application.tracker?.trackEvent 'Started Level', category:'Play Level', levelID: @levelID, ls: @session?.get('_id') unless @observing
|
application.tracker?.trackEvent 'Started Level', category:'Play Level', levelID: @levelID, ls: @session?.get('_id') unless @observing
|
||||||
$(window).trigger 'resize'
|
$(window).trigger 'resize'
|
||||||
|
_.delay (=> @perhapsStartSimulating?()), 10 * 1000
|
||||||
|
|
||||||
playAmbientSound: ->
|
playAmbientSound: ->
|
||||||
return if @destroyed
|
return if @destroyed
|
||||||
|
@ -384,6 +388,65 @@ module.exports = class PlayLevelView extends RootView
|
||||||
Backbone.Mediator.publish 'level:suppress-selection-sounds', suppress: false
|
Backbone.Mediator.publish 'level:suppress-selection-sounds', suppress: false
|
||||||
@surface.focusOnHero()
|
@surface.focusOnHero()
|
||||||
|
|
||||||
|
perhapsStartSimulating: ->
|
||||||
|
return unless @shouldSimulate()
|
||||||
|
require "vendor/aether-#{codeLanguage}" for codeLanguage in ['javascript', 'python', 'coffeescript', 'lua', 'clojure', 'io']
|
||||||
|
@simulateNextGame()
|
||||||
|
|
||||||
|
simulateNextGame: ->
|
||||||
|
return @simulator.fetchAndSimulateOneGame() if @simulator
|
||||||
|
@simulator = new Simulator background: true
|
||||||
|
# Crude method of mitigating Simulator memory leak issues
|
||||||
|
fetchAndSimulateOneGameOriginal = @simulator.fetchAndSimulateOneGame
|
||||||
|
@simulator.fetchAndSimulateOneGame = =>
|
||||||
|
if @simulator.simulatedByYou >= 10
|
||||||
|
console.log '------------------- Destroying Simulator and making a new one -----------------'
|
||||||
|
@simulator.destroy()
|
||||||
|
@simulator = null
|
||||||
|
@simulateNextGame()
|
||||||
|
else
|
||||||
|
fetchAndSimulateOneGameOriginal.apply @simulator
|
||||||
|
@simulator.fetchAndSimulateOneGame()
|
||||||
|
|
||||||
|
shouldSimulate: ->
|
||||||
|
# Crude heuristics are crude.
|
||||||
|
defaultCores = 2
|
||||||
|
cores = window.navigator.hardwareConcurrency or defaultCores # Available on Chrome/Opera, soon Safari
|
||||||
|
defaultHeapLimit = 793000000
|
||||||
|
heapLimit = window.performance?.memory?.jsHeapSizeLimit or defaultHeapLimit # Only available on Chrome, basically just says 32- vs. 64-bit
|
||||||
|
levelType = @level.get 'type', true
|
||||||
|
gamesSimulated = me.get('simulatedBy')
|
||||||
|
console.debug "Should we start simulating? Cores:", window.navigator.hardwareConcurrency, "Heap limit:", window.performance?.memory?.jsHeapSizeLimit, "Load duration:", @loadDuration
|
||||||
|
return false unless $.browser?.desktop
|
||||||
|
return false if $.browser?.msie or $.browser?.msedge
|
||||||
|
return false if $.browser.linux
|
||||||
|
return false if me.level() < 8
|
||||||
|
if levelType is 'course'
|
||||||
|
return false
|
||||||
|
else if levelType is 'hero' and gamesSimulated
|
||||||
|
return false if cores < 8
|
||||||
|
return false if heapLimit < defaultHeapLimit
|
||||||
|
return false if @loadDuration > 10000
|
||||||
|
else if levelType is 'hero-ladder' and gamesSimulated
|
||||||
|
return false if cores < 4
|
||||||
|
return false if heapLimit < defaultHeapLimit
|
||||||
|
return false if @loadDuration > 15000
|
||||||
|
else if levelType is 'hero-ladder' and not gamesSimulated
|
||||||
|
return false if cores < 8
|
||||||
|
return false if heapLimit <= defaultHeapLimit
|
||||||
|
return false if @loadDuration > 20000
|
||||||
|
else if levelType is 'course-ladder'
|
||||||
|
return false if cores <= defaultCores
|
||||||
|
return false if heapLimit < defaultHeapLimit
|
||||||
|
return false if @loadDuration > 18000
|
||||||
|
else
|
||||||
|
console.warn "Unwritten level type simulation heuristics; fill these in for new level type #{levelType}?"
|
||||||
|
return false if cores < 8
|
||||||
|
return false if heapLimit < defaultHeapLimit
|
||||||
|
return false if @loadDuration > 10000
|
||||||
|
console.debug "We should have the power. Begin background ladder simulation."
|
||||||
|
true
|
||||||
|
|
||||||
# callbacks
|
# callbacks
|
||||||
|
|
||||||
onCtrlS: (e) ->
|
onCtrlS: (e) ->
|
||||||
|
@ -457,7 +520,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
application.tracker?.trackEvent 'Confirmed Restart', category: 'Play Level', level: @level.get('name'), label: @level.get('name') unless @observing
|
application.tracker?.trackEvent 'Confirmed Restart', category: 'Play Level', level: @level.get('name'), label: @level.get('name') unless @observing
|
||||||
|
|
||||||
onInfiniteLoop: (e) ->
|
onInfiniteLoop: (e) ->
|
||||||
return unless e.firstWorld
|
return unless e.firstWorld and e.god is @god
|
||||||
@openModalView new InfiniteLoopModal nonUserCodeProblem: e.nonUserCodeProblem
|
@openModalView new InfiniteLoopModal nonUserCodeProblem: e.nonUserCodeProblem
|
||||||
application.tracker?.trackEvent 'Saw Initial Infinite Loop', category: 'Play Level', level: @level.get('name'), label: @level.get('name') unless @observing
|
application.tracker?.trackEvent 'Saw Initial Infinite Loop', category: 'Play Level', level: @level.get('name'), label: @level.get('name') unless @observing
|
||||||
|
|
||||||
|
@ -557,6 +620,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
@goalManager?.destroy()
|
@goalManager?.destroy()
|
||||||
@scriptManager?.destroy()
|
@scriptManager?.destroy()
|
||||||
@setupManager?.destroy()
|
@setupManager?.destroy()
|
||||||
|
@simulator?.destroy()
|
||||||
if ambientSound = @ambientSound
|
if ambientSound = @ambientSound
|
||||||
# Doesn't seem to work; stops immediately.
|
# Doesn't seem to work; stops immediately.
|
||||||
createjs.Tween.get(ambientSound).to({volume: 0.0}, 1500).call -> ambientSound.stop()
|
createjs.Tween.get(ambientSound).to({volume: 0.0}, 1500).call -> ambientSound.stop()
|
||||||
|
|
|
@ -123,6 +123,7 @@ module.exports = class CastButtonView extends CocoView
|
||||||
|
|
||||||
onGoalsCalculated: (e) ->
|
onGoalsCalculated: (e) ->
|
||||||
# When preloading, with real-time playback enabled, we highlight the submit button when we think they'll win.
|
# When preloading, with real-time playback enabled, we highlight the submit button when we think they'll win.
|
||||||
|
return unless e.god is @god
|
||||||
return unless e.preload
|
return unless e.preload
|
||||||
return if @options.level.get 'hidesRealTimePlayback'
|
return if @options.level.get 'hidesRealTimePlayback'
|
||||||
return if @options.level.get('slug') in ['course-thornbush-farm', 'thornbush-farm'] # Don't show it until they actually win for this first one.
|
return if @options.level.get('slug') in ['course-thornbush-farm', 'thornbush-farm'] # Don't show it until they actually win for this first one.
|
||||||
|
|
|
@ -47,7 +47,7 @@ module.exports = class Spell
|
||||||
@source = @originalSource = p.aiSource
|
@source = @originalSource = p.aiSource
|
||||||
@thangs = {}
|
@thangs = {}
|
||||||
if @canRead() # We can avoid creating these views if we'll never use them.
|
if @canRead() # We can avoid creating these views if we'll never use them.
|
||||||
@view = new SpellView {spell: @, level: options.level, session: @session, otherSession: @otherSession, worker: @worker}
|
@view = new SpellView {spell: @, level: options.level, session: @session, otherSession: @otherSession, worker: @worker, god: options.god}
|
||||||
@view.render() # Get it ready and code loaded in advance
|
@view.render() # Get it ready and code loaded in advance
|
||||||
@tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel, codeLanguage: @language, level: options.level
|
@tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel, codeLanguage: @language, level: options.level
|
||||||
@tabView.render()
|
@tabView.render()
|
||||||
|
|
|
@ -883,6 +883,7 @@ module.exports = class SpellView extends CocoView
|
||||||
@spellHasChanged = false
|
@spellHasChanged = false
|
||||||
|
|
||||||
onUserCodeProblem: (e) ->
|
onUserCodeProblem: (e) ->
|
||||||
|
return unless e.god is @options.god
|
||||||
return @onInfiniteLoop e if e.problem.id is 'runtime_InfiniteLoop'
|
return @onInfiniteLoop e if e.problem.id is 'runtime_InfiniteLoop'
|
||||||
return unless e.problem.userInfo.methodName is @spell.name
|
return unless e.problem.userInfo.methodName is @spell.name
|
||||||
return unless spellThang = _.find @spell.thangs, (spellThang, thangID) -> thangID is e.problem.userInfo.thangID
|
return unless spellThang = _.find @spell.thangs, (spellThang, thangID) -> thangID is e.problem.userInfo.thangID
|
||||||
|
@ -893,6 +894,7 @@ module.exports = class SpellView extends CocoView
|
||||||
@updateAether false, false
|
@updateAether false, false
|
||||||
|
|
||||||
onNonUserCodeProblem: (e) ->
|
onNonUserCodeProblem: (e) ->
|
||||||
|
return unless e.god is @options.god
|
||||||
return unless @spellThang
|
return unless @spellThang
|
||||||
problem = @spellThang.aether.createUserCodeProblem type: 'runtime', kind: 'Unhandled', message: "Unhandled error: #{e.problem.message}"
|
problem = @spellThang.aether.createUserCodeProblem type: 'runtime', kind: 'Unhandled', message: "Unhandled error: #{e.problem.message}"
|
||||||
@spellThang.aether.addProblem problem
|
@spellThang.aether.addProblem problem
|
||||||
|
|
|
@ -62,7 +62,7 @@ module.exports = class TomeView extends CocoView
|
||||||
@createSpells programmableThangs, programmableThangs[0]?.world # Do before spellList, thangList, and castButton
|
@createSpells programmableThangs, programmableThangs[0]?.world # Do before spellList, thangList, and castButton
|
||||||
unless @options.level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder']
|
unless @options.level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder']
|
||||||
@spellList = @insertSubView new SpellListView spells: @spells, supermodel: @supermodel, level: @options.level
|
@spellList = @insertSubView new SpellListView spells: @spells, supermodel: @supermodel, level: @options.level
|
||||||
@castButton = @insertSubView new CastButtonView spells: @spells, level: @options.level, session: @options.session
|
@castButton = @insertSubView new CastButtonView spells: @spells, level: @options.level, session: @options.session, god: @options.god
|
||||||
@teamSpellMap = @generateTeamSpellMap(@spells)
|
@teamSpellMap = @generateTeamSpellMap(@spells)
|
||||||
unless programmableThangs.length
|
unless programmableThangs.length
|
||||||
@cast()
|
@cast()
|
||||||
|
@ -136,6 +136,7 @@ module.exports = class TomeView extends CocoView
|
||||||
observing: @options.observing
|
observing: @options.observing
|
||||||
levelID: @options.levelID
|
levelID: @options.levelID
|
||||||
level: @options.level
|
level: @options.level
|
||||||
|
god: @options.god
|
||||||
|
|
||||||
for thangID, spellKeys of @thangSpells
|
for thangID, spellKeys of @thangSpells
|
||||||
thang = world.getThangByID thangID
|
thang = world.getThangByID thangID
|
||||||
|
@ -168,7 +169,7 @@ module.exports = class TomeView extends CocoView
|
||||||
difficulty = sessionState.difficulty ? 0
|
difficulty = sessionState.difficulty ? 0
|
||||||
if @options.observing
|
if @options.observing
|
||||||
difficulty = Math.max 0, difficulty - 1 # Show the difficulty they won, not the next one.
|
difficulty = Math.max 0, difficulty - 1 # Show the difficulty they won, not the next one.
|
||||||
Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: preload, realTime: realTime, submissionCount: sessionState.submissionCount ? 0, flagHistory: sessionState.flagHistory ? [], difficulty: difficulty
|
Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: preload, realTime: realTime, submissionCount: sessionState.submissionCount ? 0, flagHistory: sessionState.flagHistory ? [], difficulty: difficulty, god: @options.god
|
||||||
|
|
||||||
onToggleSpellList: (e) ->
|
onToggleSpellList: (e) ->
|
||||||
@spellList?.rerenderEntries()
|
@spellList?.rerenderEntries()
|
||||||
|
|
|
@ -10,7 +10,9 @@ module.exports = getTwoGames = (req, res) ->
|
||||||
humansSessionID = req.body.humansGameID
|
humansSessionID = req.body.humansGameID
|
||||||
ogresSessionID = req.body.ogresGameID
|
ogresSessionID = req.body.ogresGameID
|
||||||
return getSpecificSessions res, humansSessionID, ogresSessionID if humansSessionID and ogresSessionID
|
return getSpecificSessions res, humansSessionID, ogresSessionID if humansSessionID and ogresSessionID
|
||||||
getRandomSessions req.user, sendSessionsResponse(res)
|
options =
|
||||||
|
background: req.body.background
|
||||||
|
getRandomSessions req.user, options, sendSessionsResponse(res)
|
||||||
|
|
||||||
sessionSelectionString = 'team totalScore transpiledCode submittedCodeLanguage teamSpells levelID creatorName creator submitDate leagues'
|
sessionSelectionString = 'team totalScore transpiledCode submittedCodeLanguage teamSpells levelID creatorName creator submitDate leagues'
|
||||||
|
|
||||||
|
@ -33,18 +35,18 @@ getSpecificSession = (sessionID, callback) ->
|
||||||
if err? then return callback "Couldn\'t find target simulation session #{sessionID}"
|
if err? then return callback "Couldn\'t find target simulation session #{sessionID}"
|
||||||
callback null, session
|
callback null, session
|
||||||
|
|
||||||
getRandomSessions = (user, callback) ->
|
getRandomSessions = (user, options, callback) ->
|
||||||
# Determine whether to play a random match, an internal league match, or an external league match.
|
# Determine whether to play a random match, an internal league match, or an external league match.
|
||||||
# Only people in a league will end up simulating internal league matches (for leagues they're in) except by dumb chance.
|
# Only people in a league will end up simulating internal league matches (for leagues they're in) except by dumb chance.
|
||||||
# If we don't like that, we can rework sampleByLevel to have an opportunity to switch to internal leagues if the first session had a league affiliation.
|
# If we don't like that, we can rework sampleByLevel to have an opportunity to switch to internal leagues if the first session had a league affiliation.
|
||||||
leagueIDs = user?.get('clans') or []
|
leagueIDs = user?.get('clans') or []
|
||||||
leagueIDs = leagueIDs.concat user?.get('courseInstances') or []
|
leagueIDs = leagueIDs.concat user?.get('courseInstances') or []
|
||||||
leagueIDs = (leagueID + '' for leagueID in leagueIDs) # Make sure to fetch them as strings.
|
leagueIDs = (leagueID + '' for leagueID in leagueIDs) # Make sure to fetch them as strings.
|
||||||
return sampleByLevel callback unless leagueIDs.length and Math.random() > 1 / leagueIDs.length
|
return sampleByLevel options, callback unless leagueIDs.length and Math.random() > 1 / leagueIDs.length
|
||||||
leagueID = _.sample leagueIDs
|
leagueID = _.sample leagueIDs
|
||||||
findRandomSession {'leagues.leagueID': leagueID}, (err, session) ->
|
findRandomSession {'leagues.leagueID': leagueID}, (err, session) ->
|
||||||
if err then return callback err
|
if err then return callback err
|
||||||
unless session then return sampleByLevel callback
|
unless session then return sampleByLevel options, callback
|
||||||
otherTeam = scoringUtils.calculateOpposingTeam session.team
|
otherTeam = scoringUtils.calculateOpposingTeam session.team
|
||||||
queryParameters = team: otherTeam, levelID: session.levelID
|
queryParameters = team: otherTeam, levelID: session.levelID
|
||||||
if Math.random() < 0.5
|
if Math.random() < 0.5
|
||||||
|
@ -67,8 +69,9 @@ getRandomSessions = (user, callback) ->
|
||||||
# Sampling by level: we pick a level, then find a human and ogre session for that level, one at random, one biased towards recent submissions.
|
# Sampling by level: we pick a level, then find a human and ogre session for that level, one at random, one biased towards recent submissions.
|
||||||
#ladderLevelIDs = ['greed', 'criss-cross', 'brawlwood', 'dungeon-arena', 'gold-rush', 'sky-span'] # Let's not give any extra simulations to old ladders.
|
#ladderLevelIDs = ['greed', 'criss-cross', 'brawlwood', 'dungeon-arena', 'gold-rush', 'sky-span'] # Let's not give any extra simulations to old ladders.
|
||||||
ladderLevelIDs = ['dueling-grounds', 'cavern-survival', 'multiplayer-treasure-grove', 'harrowland', 'zero-sum', 'ace-of-coders', 'wakka-maul']
|
ladderLevelIDs = ['dueling-grounds', 'cavern-survival', 'multiplayer-treasure-grove', 'harrowland', 'zero-sum', 'ace-of-coders', 'wakka-maul']
|
||||||
sampleByLevel = (callback) ->
|
backgroundLadderLevelIDs = _.without ladderLevelIDs, 'zero-sum', 'ace-of-coders'
|
||||||
levelID = _.sample ladderLevelIDs
|
sampleByLevel = (options, callback) ->
|
||||||
|
levelID = _.sample(if options.background then backgroundLadderLevelIDs else ladderLevelIDs)
|
||||||
favorRecentHumans = Math.random() < 0.5 # We pick one session favoring recent submissions, then find another one uniformly to play against
|
favorRecentHumans = Math.random() < 0.5 # We pick one session favoring recent submissions, then find another one uniformly to play against
|
||||||
async.map [{levelID: levelID, team: 'humans', favorRecent: favorRecentHumans}, {levelID: levelID, team: 'ogres', favorRecent: not favorRecentHumans}], findRandomSession, callback
|
async.map [{levelID: levelID, team: 'humans', favorRecent: favorRecentHumans}, {levelID: levelID, team: 'ogres', favorRecent: not favorRecentHumans}], findRandomSession, callback
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue