Tracking who is simulating matches so we can see patterns in ill-reported matches. Rejecting simulations from simulators with old versions of the Simulator code.

This commit is contained in:
Nick Winter 2015-02-11 20:24:12 -08:00
parent 2efb6aafbc
commit 63fa2f86d4
6 changed files with 53 additions and 13 deletions

View file

@ -5,9 +5,23 @@ GoalManager = require 'lib/world/GoalManager'
God = require 'lib/God' God = require 'lib/God'
{createAetherOptions} = require 'lib/aether_utils' {createAetherOptions} = require 'lib/aether_utils'
SIMULATOR_VERSION = 1
simulatorInfo = {}
if $.browser
simulatorInfo['desktop'] = $.browser.desktop if $.browser.desktop
simulatorInfo['name'] = $.browser.name if $.browser.name
simulatorInfo['platform'] = $.browser.platform if $.browser.platform
simulatorInfo['version'] = $.browser.versionNumber if $.browser.versionNumber
module.exports = class Simulator extends CocoClass module.exports = class Simulator extends CocoClass
constructor: (@options) -> constructor: (@options) ->
@options ?= {} @options ?= {}
simulatorType = if @options.headlessClient then 'headless' else 'browser'
@simulator =
type: simulatorType
version: SIMULATOR_VERSION
info: simulatorInfo
_.extend @, Backbone.Events _.extend @, Backbone.Events
@trigger 'statusUpdate', 'Starting simulation!' @trigger 'statusUpdate', 'Starting simulation!'
@retryDelayInSeconds = 2 @retryDelayInSeconds = 2
@ -28,10 +42,17 @@ module.exports = class Simulator extends CocoClass
type: 'POST' type: 'POST'
parse: true parse: true
data: data:
'humansGameID': humanGameID humansGameID: humanGameID
'ogresGameID': ogresGameID ogresGameID: ogresGameID
simulator: @simulator
error: (errorData) -> error: (errorData) ->
console.warn "There was an error fetching two games! #{JSON.stringify errorData}" console.warn "There was an error fetching two games! #{JSON.stringify errorData}"
if errorData?.responseText?.indexOf("Old simulator") isnt -1
noty {
text: errorData.responseText
layout: 'center'
type: 'error'
}
success: (taskData) => success: (taskData) =>
return if @destroyed return if @destroyed
unless taskData unless taskData
@ -278,7 +299,6 @@ module.exports = class Simulator extends CocoClass
return if @destroyed return if @destroyed
console.log "Task registration result: #{JSON.stringify result}" console.log "Task registration result: #{JSON.stringify result}"
@trigger 'statusUpdate', 'Results were successfully sent back to server!' @trigger 'statusUpdate', 'Results were successfully sent back to server!'
console.log 'Simulated by you:', @simulatedByYou
@simulatedByYou++ @simulatedByYou++
unless @options.headlessClient unless @options.headlessClient
simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1 simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1
@ -307,6 +327,7 @@ module.exports = class Simulator extends CocoClass
originalSessionRank: -1 originalSessionRank: -1
calculationTime: 500 calculationTime: 500
sessions: [] sessions: []
simulator: @simulator
for session in @task.getSessions() for session in @task.getSessions()
sessionResult = sessionResult =

View file

@ -285,6 +285,7 @@ _.extend LevelSessionSchema.properties,
codeLanguage: codeLanguage:
type: ['string', 'null'] # 'null' in case an opponent session got corrupted, don't care much here type: ['string', 'null'] # 'null' in case an opponent session got corrupted, don't care much here
description: 'What submittedCodeLanguage the opponent used during the match' description: 'What submittedCodeLanguage the opponent used during the match'
simulator: {type: 'object', description: 'Holds info on who simulated the match, and with what tools.'}
c.extendBasicProperties LevelSessionSchema, 'level.session' c.extendBasicProperties LevelSessionSchema, 'level.session'
c.extendPermissionsProperties LevelSessionSchema, 'level.session' c.extendPermissionsProperties LevelSessionSchema, 'level.session'

View file

@ -31,7 +31,7 @@ div#columns.row
th(data-i18n="general.when") When th(data-i18n="general.when") When
th th
for match in team.matches for match in team.matches
tr(class=(match.stale ? "stale " : "") + (match.fresh ? "fresh " : "") + match.state) tr(class=(match.stale ? "stale " : "") + (match.fresh ? "fresh " : "") + match.state, title=match.simulator)
td.state-cell td.state-cell
if match.state === 'win' if match.state === 'win'
span(data-i18n="general.win").win Win span(data-i18n="general.win").win Win

View file

@ -72,6 +72,7 @@ module.exports = class MyMatchesTabView extends CocoView
stale: match.date < submitDate stale: match.date < submitDate
fresh: fresh fresh: fresh
codeLanguage: match.codeLanguage codeLanguage: match.codeLanguage
simulator: JSON.stringify(match.simulator)
} }
for team in @teams for team in @teams

View file

@ -15,6 +15,8 @@ bayes = new (require 'bayesian-battle')()
scoringTaskQueue = undefined scoringTaskQueue = undefined
scoringTaskTimeoutInSeconds = 600 scoringTaskTimeoutInSeconds = 600
SIMULATOR_VERSION = 1
module.exports.setup = (app) -> connectToScoringQueue() module.exports.setup = (app) -> connectToScoringQueue()
connectToScoringQueue = -> connectToScoringQueue = ->
@ -124,6 +126,7 @@ module.exports.getTwoGames = (req, res) ->
#if userIsAnonymous req then return errors.unauthorized(res, 'You need to be logged in to get games.') #if userIsAnonymous req then return errors.unauthorized(res, 'You need to be logged in to get games.')
humansGameID = req.body.humansGameID humansGameID = req.body.humansGameID
ogresGameID = req.body.ogresGameID ogresGameID = req.body.ogresGameID
return if 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 = ['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'] ladderGameIDs = ['dueling-grounds', 'cavern-survival', 'multiplayer-treasure-grove']
levelID = _.sample ladderGameIDs levelID = _.sample ladderGameIDs
@ -224,6 +227,8 @@ module.exports.getTwoGames = (req, res) ->
module.exports.recordTwoGames = (req, res) -> module.exports.recordTwoGames = (req, res) ->
sessions = req.body.sessions sessions = req.body.sessions
#console.log 'Recording non-chained result of', sessions?[0]?.name, sessions[0]?.metrics?.rank, 'and', sessions?[1]?.name, sessions?[1]?.metrics?.rank #console.log 'Recording non-chained result of', sessions?[0]?.name, sessions[0]?.metrics?.rank, 'and', sessions?[1]?.name, sessions?[1]?.metrics?.rank
return if simulatorIsTooOld req, res
req.body?.simulator?.user = '' + req.user?._id
yetiGuru = clientResponseObject: req.body, isRandomMatch: true yetiGuru = clientResponseObject: req.body, isRandomMatch: true
async.waterfall [ async.waterfall [
@ -442,7 +447,9 @@ getSessionInformation = (sessionIDString, callback) ->
callback null, session callback null, session
module.exports.processTaskResult = (req, res) -> module.exports.processTaskResult = (req, res) ->
return if simulatorIsTooOld req, res
originalSessionID = req.body?.originalSessionID originalSessionID = req.body?.originalSessionID
req.body?.simulator?.user = '' + req.user?._id
yetiGuru = {} yetiGuru = {}
try try
async.waterfall [ async.waterfall [
@ -578,14 +585,14 @@ addMatchToSessions = (newScoreObject, callback) ->
matchObject.opponents = {} matchObject.opponents = {}
for session in @clientResponseObject.sessions for session in @clientResponseObject.sessions
sessionID = session.sessionID sessionID = session.sessionID
matchObject.opponents[sessionID] = {} matchObject.opponents[sessionID] = match = {}
matchObject.opponents[sessionID].sessionID = sessionID match.sessionID = sessionID
matchObject.opponents[sessionID].userID = session.creator match.userID = session.creator
matchObject.opponents[sessionID].name = session.name match.name = session.name
matchObject.opponents[sessionID].totalScore = session.totalScore match.totalScore = session.totalScore
matchObject.opponents[sessionID].metrics = {} match.metrics = {}
matchObject.opponents[sessionID].metrics.rank = Number(newScoreObject[sessionID]?.gameRanking ? 0) match.metrics.rank = Number(newScoreObject[sessionID]?.gameRanking ? 0)
matchObject.opponents[sessionID].codeLanguage = newScoreObject[sessionID].submittedCodeLanguage match.codeLanguage = newScoreObject[sessionID].submittedCodeLanguage
#log.info "Match object computed, result: #{matchObject}" #log.info "Match object computed, result: #{matchObject}"
#log.info 'Writing match object to database...' #log.info 'Writing match object to database...'
@ -603,6 +610,7 @@ updateMatchesInSession = (matchObject, sessionID, callback) ->
opponentsArray = _.toArray opponentsClone opponentsArray = _.toArray opponentsClone
currentMatchObject.opponents = opponentsArray currentMatchObject.opponents = opponentsArray
currentMatchObject.codeLanguage = matchObject.opponents[opponentsArray[0].sessionID].codeLanguage currentMatchObject.codeLanguage = matchObject.opponents[opponentsArray[0].sessionID].codeLanguage
currentMatchObject.simulator = @clientResponseObject.simulator
LevelSession.findOne {'_id': sessionID}, (err, session) -> LevelSession.findOne {'_id': sessionID}, (err, session) ->
session = session.toObject() session = session.toObject()
currentMatchObject.playtime = session.playtime ? 0 currentMatchObject.playtime = session.playtime ? 0
@ -785,3 +793,12 @@ retrieveOldSessionData = (sessionID, callback) ->
markSessionAsDoneRanking = (sessionID, cb) -> markSessionAsDoneRanking = (sessionID, cb) ->
#console.log 'Marking session as done ranking...' #console.log 'Marking session as done ranking...'
LevelSession.update {'_id': sessionID}, {'isRanking': false}, cb LevelSession.update {'_id': sessionID}, {'isRanking': false}, cb
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

View file

@ -50,7 +50,7 @@ setupErrorMiddleware = (app) ->
res.status(err.status ? 500).send(error: "Something went wrong!") res.status(err.status ? 500).send(error: "Something went wrong!")
message = "Express error: #{req.method} #{req.path}: #{err.message}" message = "Express error: #{req.method} #{req.path}: #{err.message}"
log.error "#{message}, stack: #{err.stack}" log.error "#{message}, stack: #{err.stack}"
hipchat.sendTowerHipChatMessage(message) hipchat.sendHipChatMessage(message, ['tower'], {papertrail: true})
else else
next(err) next(err)
setupExpressMiddleware = (app) -> setupExpressMiddleware = (app) ->