2015-08-13 14:17:38 -04:00
log = require ' winston '
async = require ' async '
errors = require ' ../../commons/errors '
scoringUtils = require ' ./scoringUtils '
2016-04-06 13:56:06 -04:00
LevelSession = require ' ../../models/LevelSession '
2015-12-15 19:37:53 -05:00
Mandate = require ' ../../models/Mandate '
2015-08-13 14:17:38 -04:00
module.exports = getTwoGames = (req, res) ->
2015-08-15 08:38:47 -04:00
#return errors.unauthorized(res, 'You need to be logged in to get games.') unless req.user?.get('email')
2015-08-13 14:17:38 -04:00
return if scoringUtils . simulatorIsTooOld req , res
2015-08-15 08:38:47 -04:00
humansSessionID = req . body . humansGameID
ogresSessionID = req . body . ogresGameID
return getSpecificSessions res , humansSessionID , ogresSessionID if humansSessionID and ogresSessionID
2015-12-15 19:37:53 -05:00
Mandate . findOne ( { } ) . cache ( 5 * 60 * 1000 ) . exec (err, mandate) ->
if err then return errors . serverError res , " Error fetching our Mandate: #{ err } "
if ( throughputRatio = mandate ? . get ' simulationThroughputRatio ' ) ? and Math . random ( ) > throughputRatio
return sendSessionsResponse ( res ) ( null , [ ] )
options =
background: req . body . background
levelID: req . body . levelID
leagueID: req . body . leagueID
getRandomSessions req . user , options , sendSessionsResponse ( res )
2015-08-13 14:17:38 -04:00
2015-08-15 08:38:47 -04:00
sessionSelectionString = ' team totalScore transpiledCode submittedCodeLanguage teamSpells levelID creatorName creator submitDate leagues '
2015-08-13 14:17:38 -04:00
2015-08-15 08:38:47 -04:00
sendSessionsResponse = (res) ->
(err, sessions) ->
if err then return errors . serverError res , " Couldn ' t get two games to simulate: #{ err } "
2015-09-09 11:59:24 -04:00
unless _ . filter ( sessions ) . length is 2
2015-08-15 08:38:47 -04:00
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
2015-08-13 14:17:38 -04:00
2015-08-15 08:38:47 -04:00
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
2015-11-29 15:30:19 -05:00
getRandomSessions = (user, options, callback) ->
2015-08-15 08:38:47 -04:00
# 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.
2015-12-06 12:20:30 -05:00
if not leagueID = options . leagueID
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 options , callback unless leagueIDs . length and Math . random ( ) > 1 / leagueIDs . length
leagueID = _ . sample leagueIDs
queryParameters = { ' leagues.leagueID ' : leagueID }
queryParameters.levelID = options . levelID if options . levelID
findRandomSession queryParameters , (err, session) ->
2015-08-15 08:38:47 -04:00
if err then return callback err
2015-11-29 15:30:19 -05:00
unless session then return sampleByLevel options , callback
2015-08-15 08:38:47 -04:00
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
2015-12-06 12:20:30 -05:00
findNextLeagueOpponent session , queryParameters , (err, otherSession) ->
2015-08-15 08:38:47 -04:00
if err then return callback err
2015-12-06 12:20:30 -05:00
if otherSession
session.shouldUpdateLastOpponentSubmitDateForLeague = leagueID
return callback null , [ session , otherSession ]
2015-08-15 08:38:47 -04:00
# No opposing league session found; try to play an external match
delete queryParameters [ ' leagues.leagueID ' ]
2015-12-06 12:20:30 -05:00
delete queryParameters . submitDate
2015-08-15 08:38:47 -04:00
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.
2016-03-28 18:34:19 -04:00
ladderLevelIDs = [ ' dueling-grounds ' , ' cavern-survival ' , ' multiplayer-treasure-grove ' , ' harrowland ' , ' zero-sum ' , ' ace-of-coders ' , ' wakka-maul ' ]
2015-11-29 15:30:19 -05:00
backgroundLadderLevelIDs = _ . without ladderLevelIDs , ' zero-sum ' , ' ace-of-coders '
sampleByLevel = (options, callback) ->
2015-12-06 12:20:30 -05:00
levelID = options . levelID or _ . sample ( if options . background then backgroundLadderLevelIDs else ladderLevelIDs )
2015-08-15 08:38:47 -04:00
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
2015-08-13 14:17:38 -04:00
2015-12-06 12:20:30 -05:00
findNextLeagueOpponent = (session, queryParams, callback) ->
queryParams.submitted = true
league = _ . find session . leagues , leagueID: queryParams [ ' leagues.leagueID ' ]
lastOpponentSubmitDate = league . lastOpponentSubmitDate or new Date ( )
queryParams.submitDate = $lt: lastOpponentSubmitDate
sort = submitDate: - 1
LevelSession . findOne ( queryParams ) . sort ( sort ) . select ( sessionSelectionString ) . lean ( ) . exec (err, otherSession) ->
return callback err if err
if otherSession and otherSession . creator + ' ' is session . creator + ' '
queryParams.submitDate.$lt = new Date ( new Date ( queryParams . submitDate . $lt ) - 1 )
return LevelSession . findOne ( queryParams ) . sort ( sort ) . select ( sessionSelectionString ) . lean ( ) . exec callback
callback null , otherSession
2015-08-13 14:17:38 -04:00
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
2015-08-15 08:38:47 -04:00
LevelSession . findOne ( queryParams ) . sort ( sort ) . select ( sessionSelectionString ) . lean ( ) . exec (err, session) ->
2015-08-13 14:17:38 -04:00
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.
2015-08-15 08:38:47 -04:00
LevelSession . findOne ( queryParams ) . sort ( sort ) . select ( sessionSelectionString ) . lean ( ) . exec (err, session) ->
2015-08-13 14:17:38 -04:00
return callback err if err
callback null , session
2015-08-15 08:38:47 -04:00
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.
# We bias it towards recently submitted sessions.
findEarliestSubmission queryParams , (err, startDate) ->
return callback err , null unless startDate
now = new Date ( )
interval = now - startDate
cutoff = new Date now - Math . pow ( Math . random ( ) , 4 ) * interval
queryParams.submitDate = $gte: startDate , $lt: cutoff
LevelSession . findOne ( queryParams ) . sort ( submitDate: - 1 ) . select ( sessionSelectionString ) . lean ( ) . exec (err, session) ->
return callback err if err
callback null , 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
result = earliestSubmissionCache [ cacheKey ] = earliest ? . submitDate
callback null , result