mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-26 22:13:32 -04:00
Added league-based ladder game simulation.
This commit is contained in:
parent
f757f220be
commit
bda2483738
9 changed files with 190 additions and 78 deletions
app/schemas/models
server
|
@ -294,6 +294,14 @@ _.extend LevelSessionSchema.properties,
|
|||
simulator: {type: 'object', description: 'Holds info on who simulated the match, and with what tools.'}
|
||||
randomSeed: {description: 'Stores the random seed that was used during this match.'}
|
||||
|
||||
leagues:
|
||||
c.array {description: 'Multiplayer data for the league corresponding to Clans and CourseInstances the player is a part of.'},
|
||||
c.object {},
|
||||
leagueID: {type: 'string', description: 'The _id of a Clan or CourseInstance the user belongs to.'}
|
||||
stats: c.object {description: 'Multiplayer match statistics corresponding to this entry in the league.'}
|
||||
|
||||
LevelSessionSchema.properties.leagues.items.properties.stats.properties = _.pick LevelSessionSchema.properties, 'meanStrength', 'standardDeviation', 'totalScore', 'numberOfWinsAndTies', 'numberOfLosses', 'scoreHistory', 'matches'
|
||||
|
||||
c.extendBasicProperties LevelSessionSchema, 'level.session'
|
||||
c.extendPermissionsProperties LevelSessionSchema, 'level.session'
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ LevelSessionSchema.index({submitted: 1, team: 1, level:1, totalScore: -1}, {name
|
|||
LevelSessionSchema.index({levelID: 1, submitted:1, team: 1}, {name: 'get all scores index', sparse: true})
|
||||
LevelSessionSchema.index({submitted: 1, team: 1, levelID: 1, submitDate: -1}, {name: 'matchmaking index', sparse: true})
|
||||
LevelSessionSchema.index({submitted: 1, team: 1, levelID: 1, randomSimulationIndex: -1}, {name: 'matchmaking random index', sparse: true})
|
||||
LevelSessionSchema.index({'leagues.leagueID': 1, submitted: 1, levelID: 1, team: 1, randomSimulationIndex: -1}, {name: 'league-based matchmaking random index', sparse: true}) # Really need MongoDB 3.2 for partial indexes for this and several others: https://jira.mongodb.org/browse/SERVER-785
|
||||
|
||||
LevelSessionSchema.plugin(plugins.PermissionsPlugin)
|
||||
LevelSessionSchema.plugin(AchievablePlugin)
|
||||
|
|
|
@ -36,7 +36,7 @@ module.exports.addPairwiseTaskToQueueFromRequest = (req, res) ->
|
|||
taskPair = req.body.sessions
|
||||
scoringUtils.addPairwiseTaskToQueue req.body.sessions, (err, success) ->
|
||||
if err? then return errors.serverError res, "There was an error adding pairwise tasks: #{err}"
|
||||
scoringUtils.sendResponseObject req, res, {message: 'All task pairs were succesfully sent to the queue'}
|
||||
scoringUtils.sendResponseObject res, {message: 'All task pairs were succesfully sent to the queue'}
|
||||
|
||||
|
||||
module.exports.getTwoGames = getTwoGames
|
||||
|
|
|
@ -3,6 +3,7 @@ async = require 'async'
|
|||
errors = require '../../commons/errors'
|
||||
scoringUtils = require './scoringUtils'
|
||||
LevelSession = require '../../levels/sessions/LevelSession'
|
||||
Level = require '../../levels/Level'
|
||||
|
||||
module.exports = createNewTask = (req, res) ->
|
||||
requestSessionID = req.body.session
|
||||
|
@ -16,12 +17,12 @@ module.exports = createNewTask = (req, res) ->
|
|||
validatePermissions.bind(yetiGuru, req, requestSessionID)
|
||||
fetchAndVerifyLevelType.bind(yetiGuru, currentLevelID)
|
||||
fetchSessionObjectToSubmit.bind(yetiGuru, requestSessionID)
|
||||
updateSessionToSubmit.bind(yetiGuru, transpiledCode)
|
||||
updateSessionToSubmit.bind(yetiGuru, transpiledCode, req.user)
|
||||
fetchInitialSessionsToRankAgainst.bind(yetiGuru, requestLevelMajorVersion, originalLevelID)
|
||||
generateAndSendTaskPairsToTheQueue
|
||||
], (err, successMessageObject) ->
|
||||
if err? then return errors.serverError res, "There was an error submitting the game to the queue:#{err}"
|
||||
scoringUtils.sendResponseObject req, res, successMessageObject
|
||||
scoringUtils.sendResponseObject res, successMessageObject
|
||||
|
||||
|
||||
validatePermissions = (req, sessionID, callback) ->
|
||||
|
@ -45,22 +46,37 @@ fetchAndVerifyLevelType = (levelID, cb) ->
|
|||
cb null
|
||||
|
||||
fetchSessionObjectToSubmit = (sessionID, callback) ->
|
||||
LevelSession.findOne({_id: sessionID}).select('team code').exec (err, session) ->
|
||||
LevelSession.findOne({_id: sessionID}).select('team code leagues').exec (err, session) ->
|
||||
callback err, session?.toObject()
|
||||
|
||||
updateSessionToSubmit = (transpiledCode, sessionToUpdate, callback) ->
|
||||
updateSessionToSubmit = (transpiledCode, user, sessionToUpdate, callback) ->
|
||||
sessionUpdateObject =
|
||||
submitted: true
|
||||
submittedCode: sessionToUpdate.code
|
||||
transpiledCode: transpiledCode
|
||||
submitDate: new Date()
|
||||
#meanStrength: 25 # Let's try not resetting the score on resubmission
|
||||
standardDeviation: 25/3
|
||||
standardDeviation: 25 / 3
|
||||
#totalScore: 10 # Let's try not resetting the score on resubmission
|
||||
numberOfWinsAndTies: 0
|
||||
numberOfLosses: 0
|
||||
isRanking: true
|
||||
randomSimulationIndex: Math.random()
|
||||
|
||||
# Reset all league stats as well, and enter the session into any leagues the user is currently part of (not retroactive when joining new leagues)
|
||||
leagueIDs = user.get('clans') or []
|
||||
#leagueIDs = leagueIDs.concat user.get('courseInstances') or []
|
||||
leagueIDs = (leagueID + '' for leagueID in leagueIDs) # Make sure to save them as strings.
|
||||
newLeagues = []
|
||||
for leagueID in leagueIDs
|
||||
league = _.find(sessionToUpdate.leagues, leagueID: leagueID) ? leagueID: leagueID
|
||||
league.stats ?= {}
|
||||
league.stats.standardDeviation = 25 / 3
|
||||
league.stats.numberOfWinsAndTies = 0
|
||||
league.stats.numberOfLosses = 0
|
||||
newLeagues.push(league)
|
||||
unless _.isEqual newLeagues, sessionToUpdate.leagues
|
||||
sessionUpdateObject.leagues = sessionToUpdate.leagues = newLeagues
|
||||
LevelSession.update {_id: sessionToUpdate._id}, sessionUpdateObject, (err, result) ->
|
||||
callback err, sessionToUpdate
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ module.exports = dispatchTaskToConsumer = (req, res) ->
|
|||
return res.end()
|
||||
else
|
||||
return errors.serverError res, "There was an error dispatching the task: #{err}"
|
||||
scoringUtils.sendResponseObject req, res, taskObjectToSend
|
||||
scoringUtils.sendResponseObject res, taskObjectToSend
|
||||
|
||||
|
||||
checkSimulationPermissions = (req, cb) ->
|
||||
|
|
|
@ -5,42 +5,89 @@ scoringUtils = require './scoringUtils'
|
|||
LevelSession = require '../../levels/sessions/LevelSession'
|
||||
|
||||
module.exports = getTwoGames = (req, res) ->
|
||||
#if isUserAnonymous req then return errors.unauthorized(res, 'You need to be logged in to get games.')
|
||||
humansGameID = req.body.humansGameID
|
||||
ogresGameID = req.body.ogresGameID
|
||||
#return errors.unauthorized(res, 'You need to be logged in to get games.') unless req.user?.get('email')
|
||||
return if scoringUtils.simulatorIsTooOld req, res
|
||||
#ladderGameIDs = ['greed', 'criss-cross', 'brawlwood', 'dungeon-arena', 'gold-rush', 'sky-span'] # Let's not give any extra simulations to old ladders.
|
||||
ladderGameIDs = ['dueling-grounds', 'cavern-survival', 'multiplayer-treasure-grove', 'harrowland', 'zero-sum']
|
||||
levelID = _.sample ladderGameIDs
|
||||
unless ogresGameID and humansGameID
|
||||
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) ->
|
||||
if err then return errors.serverError(res, "Couldn't get two games to simulate for #{levelID}.")
|
||||
unless sessions.length is 2
|
||||
res.send(204, 'No games to score.')
|
||||
return res.end()
|
||||
taskObject = messageGenerated: Date.now(), sessions: (scoringUtils.formatSessionInformation session for session in sessions)
|
||||
#console.log 'Dispatching random game between', taskObject.sessions[0].creatorName, 'and', taskObject.sessions[1].creatorName
|
||||
scoringUtils.sendResponseObject req, res, taskObject
|
||||
else
|
||||
#console.log "Directly simulating #{humansGameID} vs. #{ogresGameID}."
|
||||
selection = 'team totalScore transpiledCode submittedCodeLanguage teamSpells levelID creatorName creator submitDate'
|
||||
LevelSession.findOne(_id: humansGameID).select(selection).lean().exec (err, humanSession) =>
|
||||
if err? then return errors.serverError(res, 'Couldn\'t find the human game')
|
||||
LevelSession.findOne(_id: ogresGameID).select(selection).lean().exec (err, ogreSession) =>
|
||||
if err? then return errors.serverError(res, 'Couldn\'t find the ogre game')
|
||||
taskObject = messageGenerated: Date.now(), sessions: (scoringUtils.formatSessionInformation session for session in [humanSession, ogreSession])
|
||||
scoringUtils.sendResponseObject req, res, taskObject
|
||||
humansSessionID = req.body.humansGameID
|
||||
ogresSessionID = req.body.ogresGameID
|
||||
return getSpecificSessions res, humansSessionID, ogresSessionID if humansSessionID and ogresSessionID
|
||||
getRandomSessions req.user, sendSessionsResponse(res)
|
||||
|
||||
sessionSelectionString = 'team totalScore transpiledCode submittedCodeLanguage teamSpells levelID creatorName creator submitDate leagues'
|
||||
|
||||
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) ->
|
||||
sendSessionsResponse = (res) ->
|
||||
(err, sessions) ->
|
||||
if err then return errors.serverError res, "Couldn't get two games to simulate: #{err}"
|
||||
unless sessions.length is 2
|
||||
console.log 'No games to score.', sessions.length
|
||||
res.send 204, 'No games to score.'
|
||||
return res.end()
|
||||
taskObject = messageGenerated: Date.now(), sessions: (scoringUtils.formatSessionInformation session for session in sessions)
|
||||
#console.log 'Dispatching ladder game simulation between', taskObject.sessions[0].creatorName, 'and', taskObject.sessions[1].creatorName
|
||||
scoringUtils.sendResponseObject res, taskObject
|
||||
|
||||
getSpecificSessions = (res, humansSessionID, ogresSessionID) ->
|
||||
async.map [humansSessionID, ogresSessionID], getSpecificSession, sendSessionsResponse(res)
|
||||
|
||||
getSpecificSession = (sessionID, callback) ->
|
||||
LevelSession.findOne(_id: sessionID).select(sessionSelectionString).lean().exec (err, session) ->
|
||||
if err? then return callback "Couldn\'t find target simulation session #{sessionID}"
|
||||
callback null, session
|
||||
|
||||
getRandomSessions = (user, callback) ->
|
||||
# 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.
|
||||
# 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 = leagueIDs.concat user.get('courseInstances') or []
|
||||
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
|
||||
leagueID = _.sample leagueIDs
|
||||
findRandomSession {'leagues.leagueID': leagueID, favorRecent: true}, (err, session) ->
|
||||
if err then return callback err
|
||||
unless session then return sampleByLevel callback
|
||||
otherTeam = scoringUtils.calculateOpposingTeam session.team
|
||||
queryParameters = team: otherTeam, levelID: session.levelID
|
||||
if Math.random() < 0.5
|
||||
# Try to play a match on the internal league ladder for this level
|
||||
queryParameters['leagues.leagueID'] = leagueID
|
||||
findRandomSession queryParameters, (err, otherSession) ->
|
||||
if err then return callback err
|
||||
if otherSession then return callback null, [session, otherSession]
|
||||
# No opposing league session found; try to play an external match
|
||||
delete queryParameters['leagues.leagueID']
|
||||
findRandomSession queryParameters, (err, otherSession) ->
|
||||
if err then return callback err
|
||||
callback null, [session, otherSession]
|
||||
else
|
||||
# Play what will probably end up being an external match
|
||||
findRandomSession queryParameters, (err, otherSession) ->
|
||||
if err then return callback err
|
||||
callback null, [session, otherSession]
|
||||
|
||||
# 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 = ['dueling-grounds', 'cavern-survival', 'multiplayer-treasure-grove', 'harrowland', 'zero-sum']
|
||||
sampleByLevel = (callback) ->
|
||||
levelID = _.sample ladderLevelIDs
|
||||
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
|
||||
|
||||
findRandomSession = (queryParams, callback) ->
|
||||
# In MongoDB 3.2, we will be able to easily get a random document with aggregate $sample: https://jira.mongodb.org/browse/SERVER-533
|
||||
queryParams.submitted = true
|
||||
favorRecent = queryParams.favorRecent
|
||||
delete queryParams.favorRecent
|
||||
if favorRecent
|
||||
return findRecentRandomSession queryParams, callback
|
||||
queryParams.randomSimulationIndex = $lte: Math.random()
|
||||
sort = randomSimulationIndex: -1
|
||||
LevelSession.findOne(queryParams).sort(sort).select(sessionSelectionString).lean().exec (err, session) ->
|
||||
return callback err if err
|
||||
result = earliestSubmissionCache[cacheKey] = earliest?.submitDate
|
||||
callback null, result
|
||||
return callback null, session if session
|
||||
delete queryParams.randomSimulationIndex # Just find the highest-indexed session, if our randomSimulationIndex was lower than the lowest one.
|
||||
LevelSession.findOne(queryParams).sort(sort).select(sessionSelectionString).lean().exec (err, session) ->
|
||||
return callback err if err
|
||||
callback null, session
|
||||
|
||||
findRecentRandomSession = (queryParams, callback) ->
|
||||
# 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.
|
||||
|
@ -51,26 +98,17 @@ findRecentRandomSession = (queryParams, callback) ->
|
|||
interval = now - startDate
|
||||
cutoff = new Date now - Math.pow(Math.random(), 4) * interval
|
||||
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) ->
|
||||
LevelSession.findOne(queryParams).sort(submitDate: -1).select(sessionSelectionString).lean().exec (err, session) ->
|
||||
return callback err if err
|
||||
callback null, session
|
||||
|
||||
findRandomSession = (queryParams, callback) ->
|
||||
# In MongoDB 3.2, we will be able to easily get a random document with aggregate $sample: https://jira.mongodb.org/browse/SERVER-533
|
||||
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) ->
|
||||
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
|
||||
return callback null, session if session
|
||||
delete queryParams.randomSimulationIndex # Just find the highest-indexed session, 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
|
||||
result = earliestSubmissionCache[cacheKey] = earliest?.submitDate
|
||||
callback null, result
|
||||
|
||||
|
||||
|
|
|
@ -29,16 +29,16 @@ module.exports = processTaskResult = (req, res) ->
|
|||
], (err, results) ->
|
||||
if err is 'shouldn\'t continue'
|
||||
markSessionAsDoneRanking originalSessionID, (err) ->
|
||||
if err? then return scoringUtils.sendResponseObject req, res, {'error': 'There was an error marking the session as done ranking'}
|
||||
scoringUtils.sendResponseObject req, res, {message: 'The scores were updated successfully, person lost so no more games are being inserted!'}
|
||||
if err? then return scoringUtils.sendResponseObject res, {'error': 'There was an error marking the session as done ranking'}
|
||||
scoringUtils.sendResponseObject res, {message: 'The scores were updated successfully, person lost so no more games are being inserted!'}
|
||||
else if err is 'no session was found'
|
||||
markSessionAsDoneRanking originalSessionID, (err) ->
|
||||
if err? then return scoringUtils.sendResponseObject req, res, {'error': 'There was an error marking the session as done ranking'}
|
||||
scoringUtils.sendResponseObject req, res, {message: 'There were no more games to rank (game is at top)!'}
|
||||
if err? then return scoringUtils.sendResponseObject res, {'error': 'There was an error marking the session as done ranking'}
|
||||
scoringUtils.sendResponseObject res, {message: 'There were no more games to rank (game is at top)!'}
|
||||
else if err?
|
||||
errors.serverError res, "There was an error:#{err}"
|
||||
else
|
||||
scoringUtils.sendResponseObject req, res, {message: 'The scores were updated successfully and more games were sent to the queue!'}
|
||||
scoringUtils.sendResponseObject res, {message: 'The scores were updated successfully and more games were sent to the queue!'}
|
||||
catch e
|
||||
errors.serverError res, 'There was an error processing the task result!'
|
||||
|
||||
|
@ -70,7 +70,7 @@ deleteQueueMessage = (callback) ->
|
|||
callback err
|
||||
|
||||
fetchLevelSession = (callback) ->
|
||||
selectString = 'submitDate creator level standardDeviation meanStrength totalScore submittedCodeLanguage'
|
||||
selectString = 'submitDate creator level standardDeviation meanStrength totalScore submittedCodeLanguage leagues'
|
||||
LevelSession.findOne(_id: @clientResponseObject.originalSessionID).select(selectString).lean().exec (err, session) =>
|
||||
@levelSession = session
|
||||
callback err
|
||||
|
|
|
@ -18,4 +18,4 @@ module.exports = recordTwoGames = (req, res) ->
|
|||
scoringUtils.updateUserSimulationCounts.bind(yetiGuru, req.user?._id)
|
||||
], (err, successMessageObject) ->
|
||||
if err? then return errors.serverError res, "There was an error recording the single game: #{err}"
|
||||
scoringUtils.sendResponseObject req, res, {message: 'The single game was submitted successfully!'}
|
||||
scoringUtils.sendResponseObject res, {message: 'The single game was submitted successfully!'}
|
||||
|
|
|
@ -20,7 +20,7 @@ module.exports.simulatorIsTooOld = (req, res) ->
|
|||
true
|
||||
|
||||
|
||||
module.exports.sendResponseObject = (req, res, object) ->
|
||||
module.exports.sendResponseObject = (res, object) ->
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.send(object)
|
||||
res.end()
|
||||
|
@ -40,37 +40,65 @@ module.exports.formatSessionInformation = (session) ->
|
|||
|
||||
module.exports.calculateSessionScores = (callback) ->
|
||||
sessionIDs = _.pluck @clientResponseObject.sessions, 'sessionID'
|
||||
async.map sessionIDs, retrieveOldSessionData, (err, oldScores) =>
|
||||
if err? then callback err, {error: 'There was an error retrieving the old scores'}
|
||||
async.map sessionIDs, retrieveOldSessionData.bind(@), (err, oldScores) =>
|
||||
if err? then return callback err, {error: 'There was an error retrieving the old scores'}
|
||||
try
|
||||
oldScoreArray = _.toArray putRankingFromMetricsIntoScoreObject @clientResponseObject, oldScores
|
||||
newScoreArray = bayes.updatePlayerSkills oldScoreArray
|
||||
newScoreArray = updatePlayerSkills oldScoreArray
|
||||
createSessionScoreUpdate.call @, scoreObject for scoreObject in newScoreArray
|
||||
callback err, newScoreArray
|
||||
catch e
|
||||
callback e
|
||||
|
||||
retrieveOldSessionData = (sessionID, callback) ->
|
||||
formatOldScoreObject = (session) ->
|
||||
standardDeviation: session.standardDeviation ? 25/3
|
||||
meanStrength: session.meanStrength ? 25
|
||||
totalScore: session.totalScore ? (25 - 1.8*(25/3))
|
||||
id: sessionID
|
||||
submittedCodeLanguage: session.submittedCodeLanguage
|
||||
formatOldScoreObject = (session) =>
|
||||
oldScoreObject =
|
||||
standardDeviation: session.standardDeviation ? 25/3
|
||||
meanStrength: session.meanStrength ? 25
|
||||
totalScore: session.totalScore ? (25 - 1.8*(25/3))
|
||||
id: sessionID
|
||||
submittedCodeLanguage: session.submittedCodeLanguage
|
||||
if session.leagues?.length
|
||||
_.find(@clientResponseObject.sessions, sessionID: sessionID).leagues = session.leagues
|
||||
oldScoreObject.leagues = []
|
||||
for league in session.leagues
|
||||
oldScoreObject.leagues.push
|
||||
leagueID: league.leagueID
|
||||
stats:
|
||||
id: sessionID
|
||||
standardDeviation: league.stats.standardDeviation ? 25/3
|
||||
meanStrength: league.stats.meanStrength ? 25
|
||||
totalScore: league.stats.totalScore ? (25 - 1.8*(25/3))
|
||||
oldScoreObject
|
||||
|
||||
return formatOldScoreObject @levelSession if sessionID is @levelSession?._id # No need to fetch again
|
||||
|
||||
query = _id: sessionID
|
||||
selection = 'standardDeviation meanStrength totalScore submittedCodeLanguage'
|
||||
selection = 'standardDeviation meanStrength totalScore submittedCodeLanguage leagues'
|
||||
LevelSession.findOne(query).select(selection).lean().exec (err, session) ->
|
||||
return callback err, {'error': 'There was an error retrieving the session.'} if err?
|
||||
callback err, formatOldScoreObject session
|
||||
|
||||
putRankingFromMetricsIntoScoreObject = (taskObject, scoreObject) ->
|
||||
scoreObject = _.indexBy scoreObject, 'id'
|
||||
scoreObject[session.sessionID].gameRanking = session.metrics.rank for session in taskObject.sessions
|
||||
sharedLeagueIDs = (league.leagueID for league in (taskObject.sessions[0].leagues ? []) when _.find(taskObject.sessions[1].leagues, leagueID: league.leagueID))
|
||||
for session in taskObject.sessions
|
||||
scoreObject[session.sessionID].gameRanking = session.metrics.rank
|
||||
for league in (session.leagues ? []) when league.leagueID in sharedLeagueIDs
|
||||
# We will also score any shared leagues, and we indicate that by assigning a non-null gameRanking to them.
|
||||
_.find(scoreObject[session.sessionID].leagues, leagueID: league.leagueID).stats.gameRanking = session.metrics.rank
|
||||
return scoreObject
|
||||
|
||||
updatePlayerSkills = (oldScoreArray) ->
|
||||
newScoreArray = bayes.updatePlayerSkills oldScoreArray
|
||||
scoreObjectA = newScoreArray[0]
|
||||
scoreObjectB = newScoreArray[1]
|
||||
for leagueA in (scoreObjectA.leagues ? []) when leagueA.stats.gameRanking?
|
||||
leagueB = _.find scoreObjectB.leagues, leagueID: leagueA.leagueID
|
||||
[leagueA.stats, leagueB.stats] = bayes.updatePlayerSkills [leagueA.stats, leagueB.stats]
|
||||
leagueA.stats.updated = leagueB.stats.updated = true
|
||||
newScoreArray
|
||||
|
||||
createSessionScoreUpdate = (scoreObject) ->
|
||||
newTotalScore = scoreObject.meanStrength - 1.8 * scoreObject.standardDeviation
|
||||
scoreHistoryAddition = [Date.now(), newTotalScore]
|
||||
|
@ -81,6 +109,17 @@ createSessionScoreUpdate = (scoreObject) ->
|
|||
totalScore: newTotalScore
|
||||
$push: {scoreHistory: {$each: [scoreHistoryAddition], $slice: -1000}}
|
||||
randomSimulationIndex: Math.random()
|
||||
for league, leagueIndex in (scoreObject.leagues ? [])
|
||||
continue unless league.stats.updated
|
||||
newTotalScore = league.stats.meanStrength - 1.8 * league.stats.standardDeviation
|
||||
scoreHistoryAddition = [scoreHistoryAddition[0], newTotalScore]
|
||||
leagueSetPrefix = "leagues.#{leagueIndex}.stats."
|
||||
@levelSessionUpdates[scoreObject.id].$set ?= {}
|
||||
@levelSessionUpdates[scoreObject.id].$push ?= {}
|
||||
@levelSessionUpdates[scoreObject.id].$set[leagueSetPrefix + 'meanStrength'] = league.stats.meanStrength
|
||||
@levelSessionUpdates[scoreObject.id].$set[leagueSetPrefix + 'standardDeviation'] = league.stats.standardDeviation
|
||||
@levelSessionUpdates[scoreObject.id].$set[leagueSetPrefix + 'totalScore'] = newTotalScore
|
||||
@levelSessionUpdates[scoreObject.id].$push[leagueSetPrefix + 'scoreHistory'] = {$each: [scoreHistoryAddition], $slice: -1000}
|
||||
|
||||
|
||||
module.exports.indexNewScoreArray = (newScoreArray, callback) ->
|
||||
|
@ -119,12 +158,22 @@ updateMatchesInSession = (matchObject, sessionID, callback) ->
|
|||
opponentsClone = _.omit opponentsClone, sessionID
|
||||
opponentsArray = _.toArray opponentsClone
|
||||
currentMatchObject.opponents = opponentsArray
|
||||
currentMatchObject.codeLanguage = matchObject.opponents[opponentsArray[0].sessionID].codeLanguage
|
||||
currentMatchObject.codeLanguage = matchObject.opponents[opponentsArray[0].sessionID].codeLanguage # TODO: we have our opponent code language in twice, do we maybe want our own code language instead?
|
||||
#currentMatchObject.simulator = @clientResponseObject.simulator # Uncomment when actively debugging simulation mismatches
|
||||
#currentMatchObject.randomSeed = parseInt(@clientResponseObject.randomSeed or 0, 10) # Uncomment when actively debugging simulation mismatches
|
||||
sessionUpdateObject = @levelSessionUpdates[sessionID]
|
||||
sessionUpdateObject.$push.matches = {$each: [currentMatchObject], $slice: -200}
|
||||
#log.info "Update is #{JSON.stringify(sessionUpdateObject, null, 2)}"
|
||||
|
||||
myScoreObject = @newScoresObject[sessionID]
|
||||
opponentSession = _.find @clientResponseObject.sessions, (session) -> session.sessionID isnt sessionID
|
||||
for league, leagueIndex in myScoreObject.leagues ? []
|
||||
continue unless league.stats.updated
|
||||
opponentLeagueTotalScore = _.find(opponentSession.leagues, leagueID: league.leagueID).stats.totalScore ? (25 - 1.8*(25/3))
|
||||
leagueMatch = _.cloneDeep currentMatchObject
|
||||
leagueMatch.opponents[0].totalScore = opponentLeagueTotalScore
|
||||
sessionUpdateObject.$push["leagues.#{leagueIndex}.stats.matches"] = {$each: [leagueMatch], $slice: -200}
|
||||
|
||||
#log.info "Update for #{sessionID} is #{JSON.stringify(sessionUpdateObject, null, 2)}"
|
||||
LevelSession.update {_id: sessionID}, sessionUpdateObject, callback
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue