mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-24 06:22:21 -05:00
140 lines
8.1 KiB
CoffeeScript
140 lines
8.1 KiB
CoffeeScript
log = require 'winston'
|
|
async = require 'async'
|
|
errors = require '../../commons/errors'
|
|
scoringUtils = require './scoringUtils'
|
|
LevelSession = require '../../models/LevelSession'
|
|
Mandate = require '../../models/Mandate'
|
|
|
|
module.exports = getTwoGames = (req, res) ->
|
|
#return errors.unauthorized(res, 'You need to be logged in to get games.') unless req.user?.get('email')
|
|
return if scoringUtils.simulatorIsTooOld req, res
|
|
humansSessionID = req.body.humansGameID
|
|
ogresSessionID = req.body.ogresGameID
|
|
return getSpecificSessions res, humansSessionID, ogresSessionID if humansSessionID and ogresSessionID
|
|
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)
|
|
|
|
sessionSelectionString = 'team totalScore submittedCode submittedCodeLanguage teamSpells levelID creatorName creator submitDate leagues'
|
|
|
|
sendSessionsResponse = (res) ->
|
|
(err, sessions) ->
|
|
if err then return errors.serverError res, "Couldn't get two games to simulate: #{err}"
|
|
unless _.filter(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 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, options, 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.
|
|
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) ->
|
|
if err then return callback err
|
|
unless session then return sampleByLevel options, 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
|
|
findNextLeagueOpponent session, queryParameters, (err, otherSession) ->
|
|
if err then return callback err
|
|
if otherSession
|
|
session.shouldUpdateLastOpponentSubmitDateForLeague = leagueID
|
|
return callback null, [session, otherSession]
|
|
# No opposing league session found; try to play an external match
|
|
delete queryParameters['leagues.leagueID']
|
|
delete queryParameters.submitDate
|
|
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', 'ace-of-coders', 'wakka-maul']
|
|
backgroundLadderLevelIDs = _.without ladderLevelIDs, 'zero-sum', 'ace-of-coders'
|
|
sampleByLevel = (options, callback) ->
|
|
levelID = options.levelID or _.sample(if options.background then backgroundLadderLevelIDs else ladderLevelIDs)
|
|
favorRecentHumans = Math.random() < 0.5 # We pick one session favoring recent submissions, then find another one uniformly to play against
|
|
async.map [{levelID: levelID, team: 'humans', favorRecent: favorRecentHumans}, {levelID: levelID, team: 'ogres', favorRecent: not favorRecentHumans}], findRandomSession, callback
|
|
|
|
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
|
|
|
|
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
|
|
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.
|
|
# 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
|