2014-02-05 12:39:14 -05:00
config = require ' ../../server_config '
2014-02-10 16:18:39 -05:00
log = require ' winston '
2014-02-05 12:39:14 -05:00
mongoose = require ' mongoose '
async = require ' async '
errors = require ' ../commons/errors '
aws = require ' aws-sdk '
2014-02-06 16:25:11 -05:00
db = require ' ./../routes/db '
2014-02-05 12:39:14 -05:00
queues = require ' ../commons/queue '
2014-02-05 18:07:15 -05:00
LevelSession = require ' ../levels/sessions/LevelSession '
2014-03-07 21:16:48 -05:00
Level = require ' ../levels/Level '
2014-03-20 18:40:02 -04:00
User = require ' ../users/User '
2014-02-07 18:52:24 -05:00
TaskLog = require ' ./task/ScoringTask '
2014-02-08 13:11:43 -05:00
bayes = new ( require ' bayesian-battle ' ) ( )
2014-02-05 12:39:14 -05:00
2014-02-06 17:32:35 -05:00
scoringTaskQueue = undefined
2014-07-22 09:59:47 -04:00
scoringTaskTimeoutInSeconds = 600
2014-02-06 17:32:35 -05:00
2015-02-12 20:07:00 -05:00
SIMULATOR_VERSION = 3
2015-02-11 23:24:12 -05:00
2014-02-10 16:18:39 -05:00
module.exports.setup = (app) -> connectToScoringQueue ( )
2014-02-05 18:07:15 -05:00
connectToScoringQueue = ->
2014-02-06 17:32:35 -05:00
queues . initializeQueueClient ->
2014-06-30 22:16:26 -04:00
queues . queueClient . registerQueue ' scoring ' , { } , (error, data) ->
2014-07-13 11:18:55 -04:00
if error ? then throw new Error " There was an error registering the scoring queue: #{ error } "
2014-02-06 17:32:35 -05:00
scoringTaskQueue = data
2014-11-25 12:28:42 -05:00
#log.info 'Connected to scoring task queue!'
2014-03-20 18:40:02 -04:00
2014-03-15 12:20:13 -04:00
module.exports.messagesInQueueCount = (req, res) ->
scoringTaskQueue . totalMessagesInQueue (err, count) ->
if err ? then return errors . serverError res , " There was an issue finding the Mongoose count: #{ err } "
2014-03-15 12:40:58 -04:00
response = String ( count )
2014-03-15 12:20:13 -04:00
res . send ( response )
res . end ( )
2014-02-26 15:14:02 -05:00
module.exports.addPairwiseTaskToQueueFromRequest = (req, res) ->
2014-02-24 10:50:43 -05:00
taskPair = req . body . sessions
2014-04-11 20:11:55 -04:00
addPairwiseTaskToQueue req . body . sessions , (err, success) ->
2014-02-26 15:14:02 -05:00
if err ? then return errors . serverError res , " There was an error adding pairwise tasks: #{ err } "
2015-04-13 00:57:47 -04:00
sendResponseObject req , res , { message: ' All task pairs were succesfully sent to the queue ' }
2014-02-26 15:14:02 -05:00
addPairwiseTaskToQueue = (taskPair, cb) ->
2014-06-30 22:16:26 -04:00
LevelSession . findOne ( _id: taskPair [ 0 ] ) . lean ( ) . exec (err, firstSession) =>
2014-04-02 18:00:43 -04:00
if err ? then return cb err
2014-06-30 22:16:26 -04:00
LevelSession . find ( _id: taskPair [ 1 ] ) . exec (err, secondSession) =>
2014-04-02 18:00:43 -04:00
if err ? then return cb err
2014-02-24 10:50:43 -05:00
try
taskPairs = generateTaskPairs ( secondSession , firstSession )
catch e
2014-04-02 18:00:43 -04:00
if e then return cb e
2014-02-24 10:50:43 -05:00
2014-02-26 15:14:02 -05:00
sendEachTaskPairToTheQueue taskPairs , (taskPairError) ->
2014-04-02 18:00:43 -04:00
if taskPairError ? then return cb taskPairError
cb null
2014-03-23 18:34:38 -04:00
2014-05-22 00:56:11 -04:00
# We should rip these out, probably
2014-03-22 12:05:53 -04:00
module.exports.resimulateAllSessions = (req, res) ->
2014-06-30 22:16:26 -04:00
unless isUserAdmin req then return errors . unauthorized res , ' Unauthorized. Even if you are authorized, you shouldn \' t do this '
2014-03-23 18:34:38 -04:00
2014-03-22 12:05:53 -04:00
originalLevelID = req . body . originalLevelID
levelMajorVersion = parseInt ( req . body . levelMajorVersion )
2014-03-23 18:34:38 -04:00
2014-03-22 12:05:53 -04:00
findParameters =
submitted: true
2014-03-23 18:34:38 -04:00
level:
2014-03-22 12:05:53 -04:00
original: originalLevelID
2014-03-23 18:34:38 -04:00
majorVersion: levelMajorVersion
2014-03-22 12:05:53 -04:00
query = LevelSession
2014-04-02 18:00:43 -04:00
. find ( findParameters )
. lean ( )
2014-03-23 18:34:38 -04:00
2014-03-22 12:05:53 -04:00
query . exec (err, result) ->
if err ? then return errors . serverError res , err
result = _ . sample result , 10
2014-06-30 22:16:26 -04:00
async . each result , resimulateSession . bind ( @ , originalLevelID , levelMajorVersion ) , (err) ->
2014-03-22 12:05:53 -04:00
if err ? then return errors . serverError res , err
2015-04-13 00:57:47 -04:00
sendResponseObject req , res , { message: ' All task pairs were succesfully sent to the queue ' }
2014-03-23 18:34:38 -04:00
2014-03-22 12:19:21 -04:00
resimulateSession = (originalLevelID, levelMajorVersion, session, cb) =>
sessionUpdateObject =
submitted: true
submitDate: new Date ( )
meanStrength: 25
standardDeviation: 25 / 3
totalScore: 10
numberOfWinsAndTies: 0
numberOfLosses: 0
isRanking: true
LevelSession . update { _id: session . _id } , sessionUpdateObject , (err, updatedSession) ->
if err ? then return cb err , null
opposingTeam = calculateOpposingTeam ( session . team )
2014-03-24 13:58:40 -04:00
fetchInitialSessionsToRankAgainst levelMajorVersion , originalLevelID , opposingTeam , (err, sessionsToRankAgainst) ->
2014-03-22 12:19:21 -04:00
if err ? then return cb err , null
taskPairs = generateTaskPairs ( sessionsToRankAgainst , session )
sendEachTaskPairToTheQueue taskPairs , (taskPairError) ->
if taskPairError ? then return cb taskPairError , null
cb null
2014-03-02 20:51:57 -05:00
2015-02-27 00:25:17 -05:00
earliestSubmissionCache = { }
findEarliestSubmission = (queryParams, callback) ->
cacheKey = JSON . stringify queryParams
return callback null , cached if cached = earliestSubmissionCache [ cacheKey ]
LevelSession . findOne ( queryParams ) . sort ( submitDate: 1 ) . lean ( ) . exec (err, earliest) ->
return callback err if err
result = earliestSubmissionCache [ cacheKey ] = earliest ? . submitDate
callback null , result
2015-04-12 14:34:19 -04:00
findRecentRandomSession = (queryParams, callback) ->
2015-02-27 00:25:17 -05:00
# We pick a random submitDate between the first submit date for the level and now, then do a $lt fetch to find a session to simulate.
# We bias it towards recently submitted sessions.
findEarliestSubmission queryParams , (err, startDate) ->
return callback err , null unless startDate
now = new Date ( )
interval = now - startDate
2015-04-13 00:57:47 -04:00
cutoff = new Date now - Math . pow ( Math . random ( ) , 4 ) * interval
2015-02-27 00:25:17 -05:00
queryParams.submitDate = $gte: startDate , $lt: cutoff
selection = ' team totalScore transpiledCode submittedCodeLanguage teamSpells levelID creatorName creator submitDate '
LevelSession . findOne ( queryParams ) . sort ( submitDate: - 1 ) . select ( selection ) . lean ( ) . exec (err, session) ->
return callback err if err
callback null , session
2015-04-12 14:34:19 -04:00
findRandomSession = (queryParams, callback) ->
queryParams.submitted = true
favorRecent = queryParams . favorRecent
delete queryParams . favorRecent
if favorRecent
return findRecentRandomSession queryParams , callback
queryParams.randomSimulationIndex = $lte: Math . random ( )
selection = ' team totalScore transpiledCode submittedCodeLanguage teamSpells levelID creatorName creator submitDate '
sort = randomSimulationIndex: - 1
LevelSession . findOne ( queryParams ) . sort ( sort ) . select ( selection ) . lean ( ) . exec (err, session) ->
return callback err if err
return callback null , session if session
delete queryParams . randomSimulationIndex # Effectively switch to $gt, if our randomSimulationIndex was lower than the lowest one.
LevelSession . findOne ( queryParams ) . sort ( sort ) . select ( selection ) . lean ( ) . exec (err, session) ->
return callback err if err
callback null , session
2015-02-27 00:25:17 -05:00
formatSessionInformation = (session) ->
sessionID: session . _id
team: session . team ? ' No team '
transpiledCode: session . transpiledCode
submittedCodeLanguage: session . submittedCodeLanguage
teamSpells: session . teamSpells ? { }
levelID: session . levelID
creatorName: session . creatorName
creator: session . creator
totalScore: session . totalScore
2014-05-21 18:34:17 -04:00
2014-05-19 13:11:20 -04:00
module.exports.getTwoGames = (req, res) ->
2015-02-27 00:25:17 -05:00
#if isUserAnonymous req then return errors.unauthorized(res, 'You need to be logged in to get games.')
2014-05-19 13:11:20 -04:00
humansGameID = req . body . humansGameID
ogresGameID = req . body . ogresGameID
2015-02-11 23:24:12 -05:00
return if simulatorIsTooOld req , res
2015-02-11 17:01:38 -05:00
#ladderGameIDs = ['greed', 'criss-cross', 'brawlwood', 'dungeon-arena', 'gold-rush', 'sky-span'] # Let's not give any extra simulations to old ladders.
2015-03-20 13:16:07 -04:00
ladderGameIDs = [ ' dueling-grounds ' , ' cavern-survival ' , ' multiplayer-treasure-grove ' , ' harrowland ' , ' zero-sum ' ]
2014-07-10 13:42:17 -04:00
levelID = _ . sample ladderGameIDs
2014-05-19 13:11:20 -04:00
unless ogresGameID and humansGameID
2015-04-12 14:34:19 -04:00
recentHumans = 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: recentHumans } , { levelID: levelID , team: ' ogres ' , favorRecent: not recentHumans } ] , findRandomSession , (err, sessions) ->
2015-02-27 00:25:17 -05:00
if err then return errors . serverError ( res , " Couldn ' t get two games to simulate for #{ levelID } . " )
unless sessions . length is 2
2014-06-30 22:16:26 -04:00
res . send ( 204 , ' No games to score. ' )
2014-06-27 11:50:04 -04:00
return res . end ( )
2015-02-27 00:25:17 -05:00
taskObject = messageGenerated: Date . now ( ) , sessions: ( formatSessionInformation session for session in sessions )
#console.log 'Dispatching random game between', taskObject.sessions[0].creatorName, 'and', taskObject.sessions[1].creatorName
sendResponseObject req , res , taskObject
2014-05-19 21:58:45 -04:00
else
2014-11-25 12:28:42 -05:00
#console.log "Directly simulating #{humansGameID} vs. #{ogresGameID}."
2015-08-10 21:12:53 -04:00
selection = ' team totalScore transpiledCode submittedCodeLanguage teamSpells levelID creatorName creator submitDate '
2014-06-20 20:19:18 -04:00
LevelSession . findOne ( _id: humansGameID ) . select ( selection ) . lean ( ) . exec (err, humanSession) =>
2014-06-30 22:16:26 -04:00
if err ? then return errors . serverError ( res , ' Couldn \' t find the human game ' )
2014-06-20 20:19:18 -04:00
LevelSession . findOne ( _id: ogresGameID ) . select ( selection ) . lean ( ) . exec (err, ogreSession) =>
2014-06-30 22:16:26 -04:00
if err ? then return errors . serverError ( res , ' Couldn \' t find the ogre game ' )
2015-02-27 00:25:17 -05:00
taskObject = messageGenerated: Date . now ( ) , sessions: ( formatSessionInformation session for session in [ humanSession , ogreSession ] )
2014-05-19 21:58:45 -04:00
sendResponseObject req , res , taskObject
module.exports.recordTwoGames = (req, res) ->
2014-05-22 00:56:11 -04:00
sessions = req . body . sessions
2014-11-25 12:28:42 -05:00
#console.log 'Recording non-chained result of', sessions?[0]?.name, sessions[0]?.metrics?.rank, 'and', sessions?[1]?.name, sessions?[1]?.metrics?.rank
2015-02-11 23:24:12 -05:00
return if simulatorIsTooOld req , res
req . body ? . simulator ? . user = ' ' + req . user ? . _id
2014-05-22 00:56:11 -04:00
yetiGuru = clientResponseObject: req . body , isRandomMatch: true
2014-05-19 21:58:45 -04:00
async . waterfall [
2015-04-13 00:57:47 -04:00
calculateSessionScores . bind ( yetiGuru ) # Fetches a few small properties from both sessions, prepares @levelSessionUpdates with the score part
indexNewScoreArray . bind ( yetiGuru ) # Creates and returns @newScoresObject, no query
addMatchToSessionsAndUpdate . bind ( yetiGuru ) # Adds matches to the session updates and does the writes
2014-12-01 16:53:17 -05:00
updateUserSimulationCounts . bind ( yetiGuru , req . user ? . _id )
2014-05-19 21:58:45 -04:00
] , (err, successMessageObject) ->
2015-04-13 00:57:47 -04:00
if err ? then return errors . serverError res , " There was an error recording the single game: #{ err } "
sendResponseObject req , res , { message: ' The single game was submitted successfully! ' }
2014-05-19 17:59:43 -04:00
2014-02-10 16:18:39 -05:00
module.exports.createNewTask = (req, res) ->
2014-02-18 14:46:14 -05:00
requestSessionID = req . body . session
2014-03-24 13:58:40 -04:00
originalLevelID = req . body . originalLevelID
currentLevelID = req . body . levelID
2014-05-14 14:42:15 -04:00
transpiledCode = req . body . transpiledCode
2014-03-06 21:48:41 -05:00
requestLevelMajorVersion = parseInt ( req . body . levelMajorVersion )
2014-04-02 18:00:43 -04:00
2014-05-22 00:56:11 -04:00
yetiGuru = { }
2014-03-24 13:58:40 -04:00
async . waterfall [
2014-06-30 22:16:26 -04:00
validatePermissions . bind ( yetiGuru , req , requestSessionID )
fetchAndVerifyLevelType . bind ( yetiGuru , currentLevelID )
2014-05-22 00:56:11 -04:00
fetchSessionObjectToSubmit . bind ( yetiGuru , requestSessionID )
updateSessionToSubmit . bind ( yetiGuru , transpiledCode )
fetchInitialSessionsToRankAgainst . bind ( yetiGuru , requestLevelMajorVersion , originalLevelID )
2014-03-24 13:58:40 -04:00
generateAndSendTaskPairsToTheQueue
] , (err, successMessageObject) ->
if err ? then return errors . serverError res , " There was an error submitting the game to the queue: #{ err } "
sendResponseObject req , res , successMessageObject
2014-06-30 22:16:26 -04:00
validatePermissions = (req, sessionID, callback) ->
if isUserAnonymous req then return callback ' You are unauthorized to submit that game to the simulator '
2014-03-24 13:58:40 -04:00
if isUserAdmin req then return callback null
2014-03-11 19:31:39 -04:00
2014-03-24 13:58:40 -04:00
findParameters =
_id: sessionID
selectString = ' creator submittedCode code '
query = LevelSession
. findOne ( findParameters )
. select ( selectString )
. lean ( )
query . exec (err, retrievedSession) ->
if err ? then return callback err
userHasPermissionToSubmitCode = retrievedSession . creator is req . user ? . id and
2014-04-02 18:00:43 -04:00
not _ . isEqual ( retrievedSession . code , retrievedSession . submittedCode )
2014-06-30 22:16:26 -04:00
unless userHasPermissionToSubmitCode then return callback ' You are unauthorized to submit that game to the simulator '
2014-03-24 13:58:40 -04:00
callback null
fetchAndVerifyLevelType = (levelID, cb) ->
findParameters =
_id: levelID
selectString = ' type '
query = Level
. findOne ( findParameters )
. select ( selectString )
. lean ( )
query . exec (err, levelWithType) ->
if err ? then return cb err
2014-10-18 17:51:43 -04:00
if not levelWithType . type or not ( levelWithType . type in [ ' ladder ' , ' hero-ladder ' ] ) then return cb ' Level isn \' t of type " ladder " '
2014-03-24 13:58:40 -04:00
cb null
fetchSessionObjectToSubmit = (sessionID, callback) ->
findParameters =
_id: sessionID
selectString = ' team code '
query = LevelSession
. findOne ( findParameters )
. select ( selectString )
query . exec (err, session) ->
callback err , session ? . toObject ( )
2014-05-14 14:42:15 -04:00
updateSessionToSubmit = (transpiledCode, sessionToUpdate, callback) ->
2014-03-24 13:58:40 -04:00
sessionUpdateObject =
submitted: true
submittedCode: sessionToUpdate . code
2014-05-14 14:42:15 -04:00
transpiledCode: transpiledCode
2014-03-24 13:58:40 -04:00
submitDate: new Date ( )
2014-05-22 00:56:11 -04:00
#meanStrength: 25 # Let's try not resetting the score on resubmission
2014-03-24 13:58:40 -04:00
standardDeviation: 25 / 3
2014-05-22 00:56:11 -04:00
#totalScore: 10 # Let's try not resetting the score on resubmission
2014-03-24 13:58:40 -04:00
numberOfWinsAndTies: 0
numberOfLosses: 0
isRanking: true
2015-04-12 14:34:19 -04:00
randomSimulationIndex: Math . random ( )
2014-03-24 13:58:40 -04:00
LevelSession . update { _id: sessionToUpdate . _id } , sessionUpdateObject , (err, result) ->
callback err , sessionToUpdate
fetchInitialSessionsToRankAgainst = (levelMajorVersion, levelID, submittedSession, callback) ->
opposingTeam = calculateOpposingTeam ( submittedSession . team )
findParameters =
2014-06-30 22:16:26 -04:00
' level.original ' : levelID
' level.majorVersion ' : levelMajorVersion
2014-03-24 13:58:40 -04:00
submitted: true
team: opposingTeam
2014-05-19 17:59:43 -04:00
2014-03-24 13:58:40 -04:00
sortParameters =
totalScore: 1
2014-03-11 19:31:39 -04:00
2014-03-24 13:58:40 -04:00
limitNumber = 1
2014-04-11 20:11:55 -04:00
query = LevelSession . aggregate [
{ $match: findParameters }
{ $sort: sortParameters }
{ $limit: limitNumber }
]
2014-03-11 19:31:39 -04:00
2014-03-24 13:58:40 -04:00
query . exec (err, sessionToRankAgainst) ->
callback err , sessionToRankAgainst , submittedSession
2014-03-11 19:31:39 -04:00
2014-06-30 22:16:26 -04:00
generateAndSendTaskPairsToTheQueue = (sessionToRankAgainst, submittedSession, callback) ->
2014-03-24 13:58:40 -04:00
taskPairs = generateTaskPairs ( sessionToRankAgainst , submittedSession )
sendEachTaskPairToTheQueue taskPairs , (taskPairError) ->
if taskPairError ? then return callback taskPairError
2014-06-30 22:16:26 -04:00
#console.log 'Sent task pairs to the queue!'
2014-04-14 14:01:55 -04:00
#console.log taskPairs
2015-04-13 00:57:47 -04:00
callback null , { message: ' All task pairs were succesfully sent to the queue ' }
2014-02-17 17:39:21 -05:00
module.exports.dispatchTaskToConsumer = (req, res) ->
2014-05-22 00:56:11 -04:00
yetiGuru = { }
2014-03-24 13:58:40 -04:00
async . waterfall [
2014-06-30 22:16:26 -04:00
checkSimulationPermissions . bind ( yetiGuru , req )
2014-03-24 13:58:40 -04:00
receiveMessageFromSimulationQueue
changeMessageVisibilityTimeout
parseTaskQueueMessage
constructTaskObject
2014-05-22 00:56:11 -04:00
constructTaskLogObject . bind ( yetiGuru , getUserIDFromRequest ( req ) )
2014-03-24 13:58:40 -04:00
processTaskObject
] , (err, taskObjectToSend) ->
2014-04-02 18:00:43 -04:00
if err ?
2014-06-30 22:16:26 -04:00
if typeof err is ' string ' and err . indexOf ' No more games in the queue ' isnt - 1
res . send ( 204 , ' No games to score. ' )
2014-03-24 13:58:40 -04:00
return res . end ( )
else
return errors . serverError res , " There was an error dispatching the task: #{ err } "
sendResponseObject req , res , taskObjectToSend
checkSimulationPermissions = (req, cb) ->
2014-04-02 18:00:43 -04:00
if isUserAnonymous req
2014-06-30 22:16:26 -04:00
cb ' You need to be logged in to simulate games '
2014-03-24 13:58:40 -04:00
else
cb null
2014-04-02 18:00:43 -04:00
2014-03-24 13:58:40 -04:00
receiveMessageFromSimulationQueue = (cb) ->
2014-04-02 18:00:43 -04:00
scoringTaskQueue . receiveMessage (err, message) ->
2014-03-24 13:58:40 -04:00
if err ? then return cb " No more games in the queue, error: #{ err } "
2014-06-30 22:16:26 -04:00
if messageIsInvalid ( message ) then return cb ' Message received from queue is invalid '
2014-03-24 13:58:40 -04:00
cb null , message
changeMessageVisibilityTimeout = (message, cb) ->
message . changeMessageVisibilityTimeout scoringTaskTimeoutInSeconds , (err) -> cb err , message
2014-06-30 22:16:26 -04:00
parseTaskQueueMessage = (message, cb) ->
2014-03-24 13:58:40 -04:00
try
2014-06-30 22:16:26 -04:00
if typeof message . getBody ( ) is ' object '
2014-03-24 13:58:40 -04:00
messageBody = message . getBody ( )
else
messageBody = JSON . parse message . getBody ( )
cb null , messageBody , message
catch e
cb " There was an error parsing the task.Error: #{ e } "
constructTaskObject = (taskMessageBody, message, callback) ->
async . map taskMessageBody . sessions , getSessionInformation , (err, sessions) ->
if err ? then return callback err
2015-02-27 00:25:17 -05:00
taskObject = messageGenerated: Date . now ( ) , sessions: ( formatSessionInformation session for session in sessions )
2014-03-24 13:58:40 -04:00
callback null , taskObject , message
constructTaskLogObject = (calculatorUserID, taskObject, message, callback) ->
taskLogObject = new TaskLog
2014-06-30 22:16:26 -04:00
' createdAt ' : new Date ( )
' calculator ' : calculatorUserID
' sentDate ' : Date . now ( )
' messageIdentifierString ' : message . getReceiptHandle ( )
2014-03-24 13:58:40 -04:00
taskLogObject . save (err) -> callback err , taskObject , taskLogObject , message
2014-06-30 22:16:26 -04:00
processTaskObject = (taskObject, taskLogObject, message, cb) ->
2014-03-24 13:58:40 -04:00
taskObject.taskID = taskLogObject . _id
taskObject.receiptHandle = message . getReceiptHandle ( )
cb null , taskObject
getSessionInformation = (sessionIDString, callback) ->
2014-04-02 18:00:43 -04:00
findParameters =
2014-03-24 13:58:40 -04:00
_id: sessionIDString
2014-06-20 20:19:18 -04:00
selectString = ' submitDate team submittedCode teamSpells levelID creator creatorName transpiledCode submittedCodeLanguage totalScore '
2014-03-24 13:58:40 -04:00
query = LevelSession
2014-04-02 18:00:43 -04:00
. findOne ( findParameters )
. select ( selectString )
. lean ( )
2014-03-24 13:58:40 -04:00
query . exec (err, session) ->
2014-06-30 22:16:26 -04:00
if err ? then return callback err , { ' error ' : ' There was an error retrieving the session. ' }
2014-03-24 13:58:40 -04:00
callback null , session
2014-04-02 18:00:43 -04:00
module.exports.processTaskResult = (req, res) ->
2015-02-11 23:24:12 -05:00
return if simulatorIsTooOld req , res
2014-04-14 14:01:55 -04:00
originalSessionID = req . body ? . originalSessionID
2015-02-11 23:24:12 -05:00
req . body ? . simulator ? . user = ' ' + req . user ? . _id
2014-05-22 00:56:11 -04:00
yetiGuru = { }
2014-05-20 18:12:24 -04:00
try
async . waterfall [
2014-06-30 22:16:26 -04:00
verifyClientResponse . bind ( yetiGuru , req . body )
2014-05-22 00:56:11 -04:00
fetchTaskLog . bind ( yetiGuru )
checkTaskLog . bind ( yetiGuru )
deleteQueueMessage . bind ( yetiGuru )
fetchLevelSession . bind ( yetiGuru )
checkSubmissionDate . bind ( yetiGuru )
logTaskComputation . bind ( yetiGuru )
2015-04-13 00:57:47 -04:00
calculateSessionScores . bind ( yetiGuru )
2014-05-22 00:56:11 -04:00
indexNewScoreArray . bind ( yetiGuru )
2015-04-13 00:57:47 -04:00
addMatchToSessionsAndUpdate . bind ( yetiGuru )
2014-12-01 16:53:17 -05:00
updateUserSimulationCounts . bind ( yetiGuru , req . user ? . _id )
2014-05-22 00:56:11 -04:00
determineIfSessionShouldContinueAndUpdateLog . bind ( yetiGuru )
findNearestBetterSessionID . bind ( yetiGuru )
addNewSessionsToQueue . bind ( yetiGuru )
2014-05-20 18:12:24 -04:00
] , (err, results) ->
2014-06-30 22:16:26 -04:00
if err is ' shouldn \' t continue '
2014-05-20 18:12:24 -04:00
markSessionAsDoneRanking originalSessionID , (err) ->
2014-06-30 22:16:26 -04:00
if err ? then return sendResponseObject req , res , { ' error ' : ' There was an error marking the session as done ranking ' }
2015-04-13 00:57:47 -04:00
sendResponseObject req , res , { message: ' The scores were updated successfully, person lost so no more games are being inserted! ' }
2014-06-30 22:16:26 -04:00
else if err is ' no session was found '
2014-05-20 18:12:24 -04:00
markSessionAsDoneRanking originalSessionID , (err) ->
2014-06-30 22:16:26 -04:00
if err ? then return sendResponseObject req , res , { ' error ' : ' There was an error marking the session as done ranking ' }
2015-04-13 00:57:47 -04:00
sendResponseObject req , res , { message: ' There were no more games to rank (game is at top)! ' }
2014-05-20 18:12:24 -04:00
else if err ?
errors . serverError res , " There was an error: #{ err } "
else
2015-04-13 00:57:47 -04:00
sendResponseObject req , res , { message: ' The scores were updated successfully and more games were sent to the queue! ' }
2014-05-20 18:12:24 -04:00
catch e
2014-06-30 22:16:26 -04:00
errors . serverError res , ' There was an error processing the task result! '
2014-05-22 00:56:11 -04:00
2014-04-02 18:00:43 -04:00
verifyClientResponse = (responseObject, callback) ->
#TODO: better verification
2014-06-30 22:16:26 -04:00
if typeof responseObject isnt ' object ' or responseObject ? . originalSessionID ? . length isnt 24
callback ' The response to that query is required to be a JSON object. '
2014-04-02 18:00:43 -04:00
else
@clientResponseObject = responseObject
callback null , responseObject
fetchTaskLog = (responseObject, callback) ->
2015-04-13 00:57:47 -04:00
TaskLog . findOne ( _id: responseObject . taskID ) . lean ( ) . exec (err, taskLog) =>
2014-05-23 12:24:34 -04:00
return callback new Error ( " Couldn ' t find TaskLog for _id #{ responseObject . taskID } ! " ) unless taskLog
2014-04-02 18:00:43 -04:00
@taskLog = taskLog
2015-04-13 00:57:47 -04:00
callback err , taskLog
2014-04-02 18:00:43 -04:00
checkTaskLog = (taskLog, callback) ->
2014-06-30 22:16:26 -04:00
if taskLog . calculationTimeMS then return callback ' That computational task has already been performed '
if hasTaskTimedOut taskLog . sentDate then return callback ' The task has timed out '
2014-04-02 18:00:43 -04:00
callback null
deleteQueueMessage = (callback) ->
scoringTaskQueue . deleteMessage @ clientResponseObject . receiptHandle , (err) ->
callback err
fetchLevelSession = (callback) ->
2015-04-13 00:57:47 -04:00
LevelSession . findOne ( _id: @ clientResponseObject . originalSessionID ) . select ( ' submitDate creator level standardDeviation meanStrength totalScore submittedCodeLanguage ' ) . lean ( ) . exec (err, session) =>
2014-04-02 18:00:43 -04:00
@levelSession = session
callback err
checkSubmissionDate = (callback) ->
supposedSubmissionDate = new Date ( @ clientResponseObject . sessions [ 0 ] . submitDate )
if Number ( supposedSubmissionDate ) isnt Number ( @ levelSession . submitDate )
2014-06-30 22:16:26 -04:00
callback ' The game has been resubmitted. Removing from queue... '
2014-04-02 18:00:43 -04:00
else
callback null
logTaskComputation = (callback) ->
2014-06-30 22:16:26 -04:00
@ taskLog . set ( ' calculationTimeMS ' , @ clientResponseObject . calculationTimeMS )
2015-04-13 00:57:47 -04:00
@ taskLog . set ( ' sessions ' ) # Huh?
2014-04-02 18:00:43 -04:00
@taskLog.calculationTimeMS = @ clientResponseObject . calculationTimeMS
@taskLog.sessions = @ clientResponseObject . sessions
@ taskLog . save (err, saved) ->
callback err
2015-04-13 00:57:47 -04:00
calculateSessionScores = (callback) ->
2014-04-02 18:00:43 -04:00
sessionIDs = _ . pluck @ clientResponseObject . sessions , ' sessionID '
async . map sessionIDs , retrieveOldSessionData , (err, oldScores) =>
2015-04-13 00:57:47 -04:00
if err ? then callback err , { error: ' There was an error retrieving the old scores ' }
2014-05-20 18:35:42 -04:00
try
oldScoreArray = _ . toArray putRankingFromMetricsIntoScoreObject @ clientResponseObject , oldScores
newScoreArray = bayes . updatePlayerSkills oldScoreArray
2015-04-13 00:57:47 -04:00
createSessionScoreUpdate . call @ , scoreObject for scoreObject in newScoreArray
callback err , newScoreArray
2014-05-20 18:35:42 -04:00
catch e
callback e
2014-04-02 18:00:43 -04:00
2015-04-13 00:57:47 -04:00
createSessionScoreUpdate = (scoreObject) ->
newTotalScore = scoreObject . meanStrength - 1.8 * scoreObject . standardDeviation
scoreHistoryAddition = [ Date . now ( ) , newTotalScore ]
@ levelSessionUpdates ? = { }
@ levelSessionUpdates [ scoreObject . id ] =
meanStrength: scoreObject . meanStrength
standardDeviation: scoreObject . standardDeviation
totalScore: newTotalScore
$push: { scoreHistory: { $each: [ scoreHistoryAddition ] , $slice: - 1000 } }
randomSimulationIndex: Math . random ( )
2014-04-02 18:00:43 -04:00
indexNewScoreArray = (newScoreArray, callback) ->
newScoresObject = _ . indexBy newScoreArray , ' id '
@newScoresObject = newScoresObject
callback null , newScoresObject
2015-04-13 00:57:47 -04:00
addMatchToSessionsAndUpdate = (newScoreObject, callback) ->
2014-04-02 18:00:43 -04:00
matchObject = { }
matchObject.date = new Date ( )
matchObject.opponents = { }
for session in @ clientResponseObject . sessions
sessionID = session . sessionID
2015-02-11 23:24:12 -05:00
matchObject . opponents [ sessionID ] = match = { }
match.sessionID = sessionID
match.userID = session . creator
match.name = session . name
match.totalScore = session . totalScore
match.metrics = { }
match.metrics.rank = Number ( newScoreObject [ sessionID ] ? . gameRanking ? 0 )
match.codeLanguage = newScoreObject [ sessionID ] . submittedCodeLanguage
2014-04-02 18:00:43 -04:00
2015-04-13 00:57:47 -04:00
#log.info "Match object computed, result: #{JSON.stringify(matchObject, null, 2)}"
2014-06-30 22:16:26 -04:00
#log.info 'Writing match object to database...'
2014-04-02 18:00:43 -04:00
#use bind with async to do the writes
sessionIDs = _ . pluck @ clientResponseObject . sessions , ' sessionID '
2014-06-30 22:16:26 -04:00
async . each sessionIDs , updateMatchesInSession . bind ( @ , matchObject ) , (err) ->
2014-05-22 12:02:57 -04:00
callback err
2014-04-02 18:00:43 -04:00
updateMatchesInSession = (matchObject, sessionID, callback) ->
currentMatchObject = { }
currentMatchObject.date = matchObject . date
currentMatchObject.metrics = matchObject . opponents [ sessionID ] . metrics
opponentsClone = _ . cloneDeep matchObject . opponents
opponentsClone = _ . omit opponentsClone , sessionID
opponentsArray = _ . toArray opponentsClone
currentMatchObject.opponents = opponentsArray
2014-07-13 23:19:51 -04:00
currentMatchObject.codeLanguage = matchObject . opponents [ opponentsArray [ 0 ] . sessionID ] . codeLanguage
2015-04-08 15:00:12 -04:00
#currentMatchObject.simulator = @clientResponseObject.simulator # Uncomment when actively debugging simulation mismatches
#currentMatchObject.randomSeed = parseInt(@clientResponseObject.randomSeed or 0, 10) # Uncomment when actively debugging simulation mismatches
2015-04-13 00:57:47 -04:00
sessionUpdateObject = @ levelSessionUpdates [ sessionID ]
sessionUpdateObject.$push.matches = { $each: [ currentMatchObject ] , $slice: - 200 }
#log.info "Update is #{JSON.stringify(sessionUpdateObject, null, 2)}"
LevelSession . update { _id: sessionID } , sessionUpdateObject , callback
2014-04-02 18:00:43 -04:00
2014-06-30 22:16:26 -04:00
updateUserSimulationCounts = (reqUserID, callback) ->
2014-04-02 18:00:43 -04:00
incrementUserSimulationCount reqUserID , ' simulatedBy ' , (err) =>
if err ? then return callback err
2014-11-25 12:28:42 -05:00
#console.log 'Incremented user simulation count!'
2014-05-22 12:02:57 -04:00
unless @ isRandomMatch
incrementUserSimulationCount @ levelSession . creator , ' simulatedFor ' , callback
else
callback null
2014-04-02 18:00:43 -04:00
incrementUserSimulationCount = (userID, type, callback) =>
2014-12-01 16:53:17 -05:00
return callback null unless userID
2014-04-02 18:00:43 -04:00
inc = { }
inc [ type ] = 1
User . update { _id: userID } , { $inc: inc } , (err, affected) ->
log . error " Error incrementing #{ type } for #{ userID } : #{ err } " if err
callback err
determineIfSessionShouldContinueAndUpdateLog = (cb) ->
sessionID = @ clientResponseObject . originalSessionID
sessionRank = parseInt @ clientResponseObject . originalSessionRank
2014-03-02 20:51:57 -05:00
2015-04-13 00:57:47 -04:00
queryParameters = _id: sessionID
updateParameters = ' $inc ' : { }
2014-03-02 20:51:57 -05:00
if sessionRank is 0
2014-06-30 22:16:26 -04:00
updateParameters [ ' $inc ' ] = { numberOfWinsAndTies: 1 }
2014-02-26 20:30:56 -05:00
else
2014-06-30 22:16:26 -04:00
updateParameters [ ' $inc ' ] = { numberOfLosses: 1 }
2014-03-02 20:51:57 -05:00
2015-04-13 00:57:47 -04:00
LevelSession . findOneAndUpdate queryParameters , updateParameters , { select: ' numberOfWinsAndTies numberOfLosses ' , lean: true } , (err, updatedSession) ->
2014-02-26 20:30:56 -05:00
if err ? then return cb err , updatedSession
totalNumberOfGamesPlayed = updatedSession . numberOfWinsAndTies + updatedSession . numberOfLosses
2014-03-20 21:51:11 -04:00
if totalNumberOfGamesPlayed < 10
2014-06-30 22:16:26 -04:00
#console.log 'Number of games played is less than 10, continuing...'
2014-04-02 18:00:43 -04:00
cb null
2014-02-26 20:30:56 -05:00
else
2014-03-01 19:56:04 -05:00
ratio = ( updatedSession . numberOfLosses ) / ( totalNumberOfGamesPlayed )
2014-03-23 18:34:38 -04:00
if ratio > 0.33
2014-06-30 22:16:26 -04:00
cb ' shouldn \' t continue '
2014-11-25 12:28:42 -05:00
#console.log "Ratio(#{ratio}) is bad, ending simulation"
2014-02-26 20:30:56 -05:00
else
2014-04-14 14:01:55 -04:00
#console.log "Ratio(#{ratio}) is good, so continuing simulations"
2014-04-02 18:00:43 -04:00
cb null
findNearestBetterSessionID = (cb) ->
2014-05-20 18:55:23 -04:00
try
levelOriginalID = @ levelSession . level . original
levelMajorVersion = @ levelSession . level . majorVersion
sessionID = @ clientResponseObject . originalSessionID
sessionTotalScore = @ newScoresObject [ sessionID ] . totalScore
opponentSessionID = _ . pull ( _ . keys ( @ newScoresObject ) , sessionID )
opponentSessionTotalScore = @ newScoresObject [ opponentSessionID ] . totalScore
opposingTeam = calculateOpposingTeam ( @ clientResponseObject . originalSessionTeam )
catch e
cb e
2014-04-02 18:00:43 -04:00
2014-03-01 19:52:17 -05:00
retrieveAllOpponentSessionIDs sessionID , (err, opponentSessionIDs) ->
if err ? then return cb err , null
2014-03-02 20:51:57 -05:00
2014-03-01 19:52:17 -05:00
queryParameters =
2014-03-02 20:51:57 -05:00
totalScore:
2014-03-23 18:34:38 -04:00
$gt: opponentSessionTotalScore
2014-03-02 20:51:57 -05:00
_id:
2014-03-01 19:52:17 -05:00
$nin: opponentSessionIDs
2014-06-30 22:16:26 -04:00
' level.original ' : levelOriginalID
' level.majorVersion ' : levelMajorVersion
2014-03-01 19:52:17 -05:00
submitted: true
team: opposingTeam
2014-03-20 18:40:02 -04:00
2014-03-18 17:25:22 -04:00
if opponentSessionTotalScore < 30
2014-03-23 18:34:38 -04:00
# Don't play a ton of matches at low scores--skip some in proportion to how close to 30 we are.
# TODO: this could be made a lot more flexible.
2014-06-30 22:16:26 -04:00
queryParameters [ ' totalScore ' ] [ ' $gt ' ] = opponentSessionTotalScore + 2 * ( 30 - opponentSessionTotalScore ) / 20
2014-03-02 20:51:57 -05:00
2014-03-01 19:52:17 -05:00
limitNumber = 1
2014-03-02 20:51:57 -05:00
2014-03-01 19:52:17 -05:00
sortParameters =
totalScore: 1
2014-03-02 20:51:57 -05:00
2014-03-01 19:52:17 -05:00
selectString = ' _id totalScore '
2014-03-02 20:51:57 -05:00
2014-03-01 19:52:17 -05:00
query = LevelSession . findOne ( queryParameters )
2015-04-13 00:57:47 -04:00
. sort ( sortParameters )
. limit ( limitNumber )
. select ( selectString )
. lean ( )
2014-03-02 20:51:57 -05:00
2014-04-14 14:01:55 -04:00
#console.log "Finding session with score near #{opponentSessionTotalScore}"
2014-03-01 19:52:17 -05:00
query . exec (err, session) ->
if err ? then return cb err , session
2014-06-30 22:16:26 -04:00
unless session then return cb ' no session was found '
2014-04-14 14:01:55 -04:00
#console.log "Found session with score #{session.totalScore}"
2014-03-01 19:52:17 -05:00
cb err , session . _id
retrieveAllOpponentSessionIDs = (sessionID, cb) ->
2015-04-13 00:57:47 -04:00
query = LevelSession . findOne ( { _id: sessionID } )
. select ( ' matches.opponents.sessionID matches.date submitDate ' )
. lean ( )
2014-02-26 15:14:02 -05:00
query . exec (err, session) ->
2014-03-01 19:52:17 -05:00
if err ? then return cb err , null
2014-03-12 00:16:53 -04:00
opponentSessionIDs = ( match . opponents [ 0 ] . sessionID for match in session . matches when match . date > session . submitDate )
2014-03-01 19:52:17 -05:00
cb err , opponentSessionIDs
2014-03-02 20:51:57 -05:00
2014-02-26 15:14:02 -05:00
calculateOpposingTeam = (sessionTeam) ->
2014-06-30 22:16:26 -04:00
teams = [ ' ogres ' , ' humans ' ]
2014-02-26 15:14:02 -05:00
opposingTeams = _ . pull teams , sessionTeam
return opposingTeams [ 0 ]
2014-03-02 20:51:57 -05:00
2014-04-02 18:00:43 -04:00
addNewSessionsToQueue = (sessionID, callback) ->
sessions = [ @ clientResponseObject . originalSessionID , sessionID ]
addPairwiseTaskToQueue sessions , callback
2014-02-17 17:39:21 -05:00
messageIsInvalid = (message) -> ( not message ? ) or message . isEmpty ( )
sendEachTaskPairToTheQueue = (taskPairs, callback) -> async . each taskPairs , sendTaskPairToQueue , callback
generateTaskPairs = (submittedSessions, sessionToScore) ->
taskPairs = [ ]
for session in submittedSessions
2014-04-11 20:11:55 -04:00
if session . toObject ?
session = session . toObject ( )
2014-06-30 22:16:26 -04:00
teams = [ ' ogres ' , ' humans ' ]
2014-02-17 17:39:21 -05:00
opposingTeams = _ . pull teams , sessionToScore . team
if String ( session . _id ) isnt String ( sessionToScore . _id ) and session . team in opposingTeams
2014-06-30 22:16:26 -04:00
#console.log 'Adding game to taskPairs!'
taskPairs . push [ sessionToScore . _id , String session . _id ]
2014-02-18 14:46:14 -05:00
return taskPairs
2014-02-17 17:39:21 -05:00
sendTaskPairToQueue = (taskPair, callback) ->
2014-06-30 22:16:26 -04:00
scoringTaskQueue . sendMessage { sessions: taskPair } , 5 , (err, data) -> callback ? err , data
2014-02-17 17:39:21 -05:00
getUserIDFromRequest = (req) -> if req . user ? then return req . user . _id else return null
isUserAnonymous = (req) -> if req . user ? then return req . user . get ( ' anonymous ' ) else return true
2014-02-08 13:11:43 -05:00
2014-02-20 12:44:44 -05:00
isUserAdmin = (req) -> return Boolean ( req . user ? . isAdmin ( ) )
2014-06-30 22:16:26 -04:00
sendResponseObject = (req, res, object) ->
2014-02-07 15:36:56 -05:00
res . setHeader ( ' Content-Type ' , ' application/json ' )
res . send ( object )
res . end ( )
2014-02-08 13:11:43 -05:00
hasTaskTimedOut = (taskSentTimestamp) -> taskSentTimestamp + scoringTaskTimeoutInSeconds * 1000 < Date . now ( )
2014-02-07 18:52:24 -05:00
2014-06-30 22:16:26 -04:00
handleTimedOutTask = (req, res, taskBody) -> errors . clientTimeout res , ' The results weren \' t provided within the timeout '
2014-02-07 18:52:24 -05:00
2014-05-22 00:56:11 -04:00
putRankingFromMetricsIntoScoreObject = (taskObject, scoreObject) ->
2014-02-08 17:21:16 -05:00
scoreObject = _ . indexBy scoreObject , ' id '
2014-05-22 00:56:11 -04:00
scoreObject [ session . sessionID ] . gameRanking = session . metrics . rank for session in taskObject . sessions
2014-02-18 14:46:14 -05:00
return scoreObject
2014-02-05 18:07:15 -05:00
2014-02-17 17:39:21 -05:00
retrieveOldSessionData = (sessionID, callback) ->
2015-04-13 00:57:47 -04:00
formatOldScoreObject = (session) ->
standardDeviation: session . standardDeviation ? 25 / 3
meanStrength: session . meanStrength ? 25
totalScore: session . totalScore ? ( 25 - 1.8 * ( 25 / 3 ) )
id: sessionID
submittedCodeLanguage: session . submittedCodeLanguage
return formatOldScoreObject @ levelSession if sessionID is @ levelSession ? . _id # No need to fetch again
query = _id: sessionID
selection = ' standardDeviation meanStrength totalScore submittedCodeLanguage '
LevelSession . findOne ( query ) . select ( selection ) . lean ( ) . exec (err, session) ->
2014-06-30 22:16:26 -04:00
return callback err , { ' error ' : ' There was an error retrieving the session. ' } if err ?
2015-04-13 00:57:47 -04:00
callback err , formatOldScoreObject session
2014-05-19 17:59:43 -04:00
markSessionAsDoneRanking = (sessionID, cb) ->
2014-06-30 22:16:26 -04:00
#console.log 'Marking session as done ranking...'
2015-04-13 00:57:47 -04:00
LevelSession . update { _id: sessionID } , { isRanking: false } , cb
2015-02-11 23:24:12 -05:00
simulatorIsTooOld = (req, res) ->
clientSimulator = req . body . simulator
return false if clientSimulator ? . version >= SIMULATOR_VERSION
message = " Old simulator version #{ clientSimulator ? . version } , need to clear cache and get version #{ SIMULATOR_VERSION } . "
log . debug " 400: #{ message } "
res . send 400 , message
res . end ( )
true