mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 23:58:02 -05:00
Refactored level handler and level session schema
This commit is contained in:
parent
7cec574e17
commit
643cafc89f
3 changed files with 243 additions and 154 deletions
|
@ -3,10 +3,10 @@ LevelLoader = require 'lib/LevelLoader'
|
|||
GoalManager = require 'lib/world/GoalManager'
|
||||
|
||||
module.exports = class Simulator
|
||||
|
||||
constructor: ->
|
||||
@retryDelayInSeconds = 10
|
||||
@taskURL = "/queue/scoring"
|
||||
|
||||
@taskURL = '/queue/scoring'
|
||||
|
||||
fetchAndSimulateTask: =>
|
||||
$.ajax
|
||||
|
@ -15,48 +15,45 @@ module.exports = class Simulator
|
|||
error: @handleFetchTaskError
|
||||
success: @setupSimulationAndLoadLevel
|
||||
|
||||
cleanupSimulation: ->
|
||||
|
||||
|
||||
handleFetchTaskError: (errorData) =>
|
||||
console.log "There were no games to score. Error: #{JSON.stringify errorData}"
|
||||
console.log "Retrying in #{@retryDelayInSeconds}"
|
||||
_.delay @fetchAndSimulateTask, @retryDelayInSeconds * 1000
|
||||
|
||||
@simulateAnotherTaskAfterDelay()
|
||||
|
||||
simulateAnotherTaskAfterDelay: =>
|
||||
retryDelayInMilliseconds = @retryDelayInSeconds * 1000
|
||||
_.delay @fetchAndSimulateTask, retryDelayInMilliseconds
|
||||
|
||||
setupSimulationAndLoadLevel: (taskData) =>
|
||||
@task = new SimulationTask(taskData)
|
||||
@superModel = new SuperModel()
|
||||
@god = new God()
|
||||
|
||||
@levelLoader = new LevelLoader @task.getLevelName(), @superModel, @task.getFirstSessionID()
|
||||
|
||||
@levelLoader.once 'loaded-all', @simulateGame
|
||||
|
||||
|
||||
|
||||
simulateGame: =>
|
||||
@assignWorldAndLevelFromLevelLoaderAndDestroyIt()
|
||||
@setupGod()
|
||||
|
||||
try
|
||||
@commenceSimulationAndSetupCallback()
|
||||
catch e
|
||||
console.log "There was an error in simulation. Trying again in #{retryDelayInSeconds} seconds"
|
||||
console.log "Error #{e}"
|
||||
_.delay @fetchAndSimulateTask, @retryDelayInSeconds * 1000
|
||||
catch err
|
||||
console.log "There was an error in simulation(#{err}). Trying again in #{retryDelayInSeconds} seconds"
|
||||
@simulateAnotherTaskAfterDelay()
|
||||
|
||||
assignWorldAndLevelFromLevelLoaderAndDestroyIt: ->
|
||||
@world = @levelLoader.world
|
||||
@level = @levelLoader.level
|
||||
@levelLoader.destroy()
|
||||
|
||||
|
||||
setupGod: ->
|
||||
@god.level = @level.serialize @supermodel
|
||||
@god.worldClassMap = world.classMap
|
||||
@setupGoalManager()
|
||||
@setupGodSpells()
|
||||
|
||||
|
||||
setupGoalManager: ->
|
||||
@god.goalManager = new GoalManager @world
|
||||
@god.goalManager.goals = @fetchGoalsFromWorldNoteChain()
|
||||
|
@ -66,7 +63,6 @@ module.exports = class Simulator
|
|||
@god.createWorld()
|
||||
Backbone.Mediator.subscribeOnce 'god:new-world-created', @processResults, @
|
||||
|
||||
|
||||
processResults: (simulationResults) ->
|
||||
taskResults = @formTaskResultsObject simulationResults
|
||||
sendResultsBackToServer taskResults
|
||||
|
@ -90,7 +86,7 @@ module.exports = class Simulator
|
|||
@cleanupSimulation()
|
||||
@fetchAndSimulateTask()
|
||||
|
||||
|
||||
cleanupSimulation: ->
|
||||
|
||||
formTaskResultsObject: (simulationResults) ->
|
||||
taskResults =
|
||||
|
|
|
@ -21,6 +21,8 @@ LevelHandler = class LevelHandler extends Handler
|
|||
'icon'
|
||||
]
|
||||
|
||||
postEditableProperties: ['name']
|
||||
|
||||
getByRelationship: (req, res, args...) ->
|
||||
return @getSession(req, res, args[0]) if args[1] is 'session'
|
||||
return @getLeaderboard(req, res, args[0]) if args[1] is 'leaderboard'
|
||||
|
@ -28,108 +30,134 @@ LevelHandler = class LevelHandler extends Handler
|
|||
return @getFeedback(req, res, args[0]) if args[1] is 'feedback'
|
||||
return @sendNotFoundError(res)
|
||||
|
||||
getSession: (req, res, id) ->
|
||||
@getDocumentForIdOrSlug id, (err, level) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless level?
|
||||
return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, level)
|
||||
|
||||
sessionQuery = {
|
||||
level: {original: level.original.toString(), majorVersion: level.version.major}
|
||||
creator: req.user.id
|
||||
}
|
||||
|
||||
# TODO: generalize this for levels that need teams
|
||||
team = req.query.team
|
||||
team ?= 'humans' if level.name is 'Project DotA'
|
||||
sessionQuery.team = team if team
|
||||
|
||||
Session.findOne(sessionQuery).exec (err, doc) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
if doc
|
||||
@sendSuccess(res, doc)
|
||||
return
|
||||
|
||||
initVals = sessionQuery
|
||||
initVals.state = {complete:false, scripts:{currentScript:null}} # will not save empty objects
|
||||
initVals.permissions = [{target:req.user.id, access:'owner'}, {target:'public', access:'write'}]
|
||||
initVals.team = team if team
|
||||
session = new Session(initVals)
|
||||
session.save (err) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
@sendSuccess(res, @formatEntity(req, session))
|
||||
# TODO: tying things like @formatEntity and saveChangesToDocument don't make sense
|
||||
# associated with the handler, because the handler might return a different type
|
||||
# of model, like in this case. Refactor to move that logic to the model instead.
|
||||
|
||||
getAllSessions: (req, res, id) ->
|
||||
@getDocumentForIdOrSlug id, (err, level) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless level?
|
||||
return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, level)
|
||||
|
||||
sessionQuery = {
|
||||
level: {original: level.original.toString(), majorVersion: level.version.major}
|
||||
#creator: req.user.id
|
||||
submitted: true
|
||||
}
|
||||
Session.find sessionQuery, '_id totalScore submitted team creatorName', (err, results) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
res.send(results)
|
||||
res.end()
|
||||
|
||||
getLeaderboard: (req, res, id) ->
|
||||
# stub handler
|
||||
# [original, version] = id.split('.')
|
||||
# version = parseInt version
|
||||
# console.log 'get leaderboard for', original, version, req.query
|
||||
[original, version] = id.split '.'
|
||||
version = parseInt(version) or 0
|
||||
|
||||
req.query.order = parseInt(req.query.order) or -1
|
||||
req.query.scoreOffset = parseInt(req.query.scoreOffset) ? 100000
|
||||
req.query.team ?= 'humans'
|
||||
req.query.limit ?= parseInt(req.query.limit) or 20
|
||||
|
||||
|
||||
if parseInt(req.query.order) is 1
|
||||
scoreQuery = {"$gte":parseFloat req.query.scoreOffset}
|
||||
else
|
||||
scoreQuery = {"$lte": parseFloat req.query.scoreOffset}
|
||||
|
||||
sessionsQuery =
|
||||
level: {original: original, majorVersion: version}
|
||||
team: req.query.team
|
||||
totalScore: scoreQuery
|
||||
submitted: true
|
||||
|
||||
sortObject =
|
||||
"totalScore": parseInt(req.query.order)
|
||||
query = Session.find(sessionsQuery).sort(sortObject).limit(parseInt req.query.limit)
|
||||
Session.find sessionsQuery, 'totalScore creatorName creator', (err, resultSessions) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
if resultSessions
|
||||
return @sendSuccess res, resultSessions
|
||||
res.send([])
|
||||
|
||||
getFeedback: (req, res, id) ->
|
||||
fetchLevelByIDAndHandleErrors: (id, req, res, callback) ->
|
||||
@getDocumentForIdOrSlug id, (err, level) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless level?
|
||||
return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, level, 'get')
|
||||
callback err, level
|
||||
|
||||
feedbackQuery = {
|
||||
getSession: (req, res, id) ->
|
||||
@fetchLevelByIDAndHandleErrors id, req, res, (err, level) =>
|
||||
sessionQuery =
|
||||
level:
|
||||
original: level.original.toString()
|
||||
majorVersion: level.version.major
|
||||
creator: req.user.id
|
||||
|
||||
# TODO: generalize this for levels that need teams
|
||||
if req.query.team?
|
||||
sessionQuery.team = req.query.team
|
||||
else if level.name is 'Project DotA'
|
||||
sessionQuery.team = 'humans'
|
||||
|
||||
Session.findOne(sessionQuery).exec (err, doc) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendSuccess(res, doc) if doc?
|
||||
@createAndSaveNewSession sessionQuery, req, res
|
||||
|
||||
|
||||
createAndSaveNewSession: (sessionQuery, req, res) =>
|
||||
initVals = sessionQuery
|
||||
|
||||
initVals.state =
|
||||
complete:false
|
||||
scripts:
|
||||
currentScript:null # will not save empty objects
|
||||
|
||||
initVals.permissions = [
|
||||
{
|
||||
target:req.user.id
|
||||
access:'owner'
|
||||
}
|
||||
{
|
||||
target:'public'
|
||||
access:'write'
|
||||
}
|
||||
]
|
||||
session = new Session(initVals)
|
||||
|
||||
session.save (err) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
@sendSuccess(res, @formatEntity(req, session))
|
||||
# TODO: tying things like @formatEntity and saveChangesToDocument don't make sense
|
||||
# associated with the handler, because the handler might return a different type
|
||||
# of model, like in this case. Refactor to move that logic to the model instead.
|
||||
|
||||
getAllSessions: (req, res, id) ->
|
||||
@fetchLevelByIDAndHandleErrors id, req, res, (err, level) =>
|
||||
sessionQuery =
|
||||
level:
|
||||
original: level.original.toString()
|
||||
majorVersion: level.version.major
|
||||
submitted: true
|
||||
|
||||
propertiesToReturn = [
|
||||
'_id'
|
||||
'totalScore'
|
||||
'submitted'
|
||||
'team'
|
||||
'creatorName'
|
||||
]
|
||||
|
||||
query = Session
|
||||
.find(sessionQuery)
|
||||
.select(propertiesToReturn.join ' ')
|
||||
|
||||
query.exec (err, results) =>
|
||||
if err then @sendDatabaseError(res, err) else @sendSuccess res, results
|
||||
|
||||
getLeaderboard: (req, res, id) ->
|
||||
@validateLeaderboardRequestParameters req
|
||||
[original, version] = id.split '.'
|
||||
version = parseInt(version) ? 0
|
||||
scoreQuery = {}
|
||||
scoreQuery[if req.query.order is 1 then "$gte" else "$lte"] = req.query.scoreOffset
|
||||
|
||||
sessionsQueryParameters =
|
||||
level:
|
||||
original: original
|
||||
majorVersion: version
|
||||
team: req.query.team
|
||||
totalScore: scoreQuery
|
||||
submitted: true
|
||||
|
||||
sortParameters =
|
||||
"totalScore": req.query.order
|
||||
|
||||
selectProperties = [
|
||||
'totalScore'
|
||||
'creatorName'
|
||||
'creator'
|
||||
]
|
||||
|
||||
query = Session
|
||||
.find(sessionsQueryParameters)
|
||||
.limit(req.query.limit)
|
||||
.sort(sortParameters)
|
||||
.select(selectProperties.join ' ')
|
||||
|
||||
query.exec (err, resultSessions) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
resultSessions ?= []
|
||||
@sendSuccess res, resultSessions
|
||||
|
||||
validateLeaderboardRequestParameters: (req) ->
|
||||
req.query.order = parseInt(req.query.order) ? -1
|
||||
req.query.scoreOffset = parseFloat(req.query.scoreOffset) ? 100000
|
||||
req.query.team ?= 'humans'
|
||||
req.query.limit = parseInt(req.query.limit) ? 20
|
||||
|
||||
getFeedback: (req, res, id) ->
|
||||
@fetchLevelByIDAndHandleErrors id, req, res, (err, level) =>
|
||||
feedbackQuery =
|
||||
creator: mongoose.Types.ObjectId(req.user.id.toString())
|
||||
'level.original': level.original.toString()
|
||||
'level.majorVersion': level.version.major
|
||||
}
|
||||
|
||||
Feedback.findOne(feedbackQuery).exec (err, doc) =>
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless doc?
|
||||
@sendSuccess(res, doc)
|
||||
return
|
||||
|
||||
postEditableProperties: ['name']
|
||||
|
||||
module.exports = new LevelHandler()
|
||||
|
|
|
@ -1,73 +1,138 @@
|
|||
c = require '../../commons/schemas'
|
||||
|
||||
LevelSessionPlayerSchema = c.object {
|
||||
id: c.objectId(links: [{rel: 'extra', href: "/db/user/{($)}"}])
|
||||
time: { type: 'Number' }
|
||||
changes: { type: 'Number' }
|
||||
}
|
||||
LevelSessionPlayerSchema = c.object
|
||||
id: c.objectId
|
||||
links: [
|
||||
{
|
||||
rel: 'extra'
|
||||
href: "/db/user/{($)}"
|
||||
}
|
||||
]
|
||||
time:
|
||||
type: 'Number'
|
||||
changes:
|
||||
type: 'Number'
|
||||
|
||||
LevelSessionLevelSchema = c.object {required: ['original', 'majorVersion']}, {
|
||||
|
||||
LevelSessionLevelSchema = c.object {required: ['original', 'majorVersion']},
|
||||
original: c.objectId({})
|
||||
majorVersion: {type: 'integer', minimum: 0, default: 0}}
|
||||
majorVersion:
|
||||
type: 'integer'
|
||||
minimum: 0
|
||||
default: 0
|
||||
|
||||
LevelSessionSchema = c.object {
|
||||
|
||||
LevelSessionSchema = c.object
|
||||
title: "Session"
|
||||
description: "A single session for a given level."
|
||||
}
|
||||
|
||||
|
||||
_.extend LevelSessionSchema.properties,
|
||||
# denormalization
|
||||
creatorName: { type: 'string' }
|
||||
levelName: { type: 'string' }
|
||||
levelID: { type: 'string' }
|
||||
multiplayer: { type: 'boolean' }
|
||||
creatorName:
|
||||
type: 'string'
|
||||
levelName:
|
||||
type: 'string'
|
||||
levelID:
|
||||
type: 'string'
|
||||
multiplayer:
|
||||
type: 'boolean'
|
||||
creator: c.objectId
|
||||
links:
|
||||
[
|
||||
{
|
||||
rel: 'extra'
|
||||
href: "/db/user/{($)}"
|
||||
}
|
||||
]
|
||||
created: c.date
|
||||
title: 'Created'
|
||||
readOnly: true
|
||||
|
||||
changed: c.date
|
||||
title: 'Changed'
|
||||
readOnly: true
|
||||
|
||||
creator: c.objectId(links: [{rel: 'extra', href: "/db/user/{($)}"}])
|
||||
created: c.date( { title: 'Created', readOnly: true })
|
||||
changed: c.date( { title: 'Changed', readOnly: true })
|
||||
team: c.shortString()
|
||||
level: LevelSessionLevelSchema
|
||||
screenshot: { type: 'string' }
|
||||
state: c.object {}, {
|
||||
complete: { type: 'boolean' }
|
||||
scripts: c.object {}, {
|
||||
ended: { type: 'object', additionalProperties: { type: 'number' }}
|
||||
currentScript: { type: ['null', 'string']}
|
||||
currentScriptOffset: { type: 'number' }}
|
||||
selected: { type: ['null', 'string'] }
|
||||
playing: { type: 'boolean' }
|
||||
frame: { type: 'number' }
|
||||
thangs: { type: 'object', additionalProperties: {
|
||||
title: 'Thang'
|
||||
|
||||
screenshot:
|
||||
type: 'string'
|
||||
|
||||
state: c.object {},
|
||||
complete:
|
||||
type: 'boolean'
|
||||
scripts: c.object {},
|
||||
ended:
|
||||
type: 'object'
|
||||
additionalProperties:
|
||||
type: 'number'
|
||||
currentScript:
|
||||
type: [
|
||||
'null'
|
||||
'string'
|
||||
]
|
||||
currentScriptOffset:
|
||||
type: 'number'
|
||||
|
||||
selected:
|
||||
type: [
|
||||
'null'
|
||||
'string'
|
||||
]
|
||||
playing:
|
||||
type: 'boolean'
|
||||
frame:
|
||||
type: 'number'
|
||||
thangs:
|
||||
type: 'object'
|
||||
properties: {
|
||||
methods: { type: 'object', additionalProperties: {
|
||||
title: 'Thang Method'
|
||||
type: 'object'
|
||||
properties: {
|
||||
metrics: { type: 'object' }
|
||||
source: { type: 'string' }
|
||||
}
|
||||
}}
|
||||
}
|
||||
}}
|
||||
}
|
||||
additionalProperties:
|
||||
title: 'Thang'
|
||||
type: 'object'
|
||||
properties:
|
||||
methods:
|
||||
type: 'object'
|
||||
additionalProperties:
|
||||
title: 'Thang Method'
|
||||
type: 'object'
|
||||
properties:
|
||||
metrics:
|
||||
type: 'object'
|
||||
source:
|
||||
type: 'string'
|
||||
|
||||
# TODO: specify this more
|
||||
code: { type: 'object' }
|
||||
code:
|
||||
type: 'object'
|
||||
|
||||
teamSpells:
|
||||
type: 'object'
|
||||
additionalProperties:
|
||||
type: 'array'
|
||||
|
||||
players:
|
||||
type: 'object'
|
||||
|
||||
players: { type: 'object' }
|
||||
chat: { type: 'array' }
|
||||
chat:
|
||||
type: 'array'
|
||||
|
||||
meanStrength: {type: 'number', default: 25}
|
||||
standardDeviation: {type:'number', default:25/3, minimum: 0}
|
||||
totalScore: {type: 'number', default: 10}
|
||||
submitted: {type: 'boolean', default: false, index:true}
|
||||
meanStrength:
|
||||
type: 'number'
|
||||
default: 25
|
||||
|
||||
standardDeviation:
|
||||
type:'number'
|
||||
default:25/3
|
||||
minimum: 0
|
||||
|
||||
totalScore:
|
||||
type: 'number'
|
||||
default: 10
|
||||
|
||||
submitted:
|
||||
type: 'boolean'
|
||||
default: false
|
||||
index:true
|
||||
|
||||
c.extendBasicProperties LevelSessionSchema, 'level.session'
|
||||
c.extendPermissionsProperties LevelSessionSchema, 'level.session'
|
||||
|
|
Loading…
Reference in a new issue