Merge branch 'master' into play-button

This commit is contained in:
Tay Yang Shun 2014-02-27 14:09:37 +08:00
commit 04895a9204
20 changed files with 259 additions and 53 deletions

View file

@ -113,7 +113,6 @@ module.exports = class LevelBus extends Bus
@changedSessionProperties.teamSpells = true
@session.set({'teamSpells': @teamSpellMap})
@saveSession()
console.log spellTeam, me.team, e.spell.spellKey
if spellTeam is me.team
@onSpellChanged e # Save the new spell to the session, too.

View file

@ -110,16 +110,19 @@ module.exports = class LevelLoader extends CocoClass
@updateCompleted = true
denormalizeSession: ->
return if @session.get 'levelName'
return if @sessionDenormalized
patch =
'levelName': @level.get('name')
'levelID': @level.get('slug') or @level.id
if me.id is @session.get 'creator'
patch.creatorName = me.get('name')
@session.set key, value for key, value of patch
tempSession = new LevelSession _id: @session.id
tempSession.save(patch, {patch: true})
for key, value of patch
if @session.get(key) is value
delete patch[key]
unless _.isEmpty patch
@session.set key, value for key, value of patch
tempSession = new LevelSession _id: @session.id
tempSession.save(patch, {patch: true})
@sessionDenormalized = true
# World init

View file

@ -64,7 +64,7 @@ module.exports = class Simulator
setupGoalManager: ->
@god.goalManager = new GoalManager @world
@god.goalManager.goals = @fetchGoalsFromWorldNoteChain()
@god.goalManager.goals = @god.level.goals
@god.goalManager.goalStates = @manuallyGenerateGoalStates()
commenceSimulationAndSetupCallback: ->
@ -108,17 +108,22 @@ module.exports = class Simulator
taskResults =
taskID: @task.getTaskID()
receiptHandle: @task.getReceiptHandle()
originalSessionID: @task.getFirstSessionID()
originalSessionRank: -1
calculationTime: 500
sessions: []
for session in @task.getSessions()
sessionResult =
sessionID: session.sessionID
submitDate: session.submitDate
creator: session.creator
metrics:
rank: @calculateSessionRank session.sessionID, simulationResults.goalStates, @task.generateTeamToSessionMap()
if session.sessionID is taskResults.originalSessionID
taskResults.originalSessionRank = sessionResult.metrics.rank
taskResults.originalSessionTeam = session.team
taskResults.sessions.push sessionResult
return taskResults
@ -137,8 +142,6 @@ module.exports = class Simulator
else
return 1
fetchGoalsFromWorldNoteChain: -> return @god.goalManager.world.scripts[0].noteChain[0].goals.add
manuallyGenerateGoalStates: ->
goalStates =
"destroy-humans":
@ -190,7 +193,7 @@ module.exports = class Simulator
spellKeyComponents[0] = _.string.slugify spellKeyComponents[0]
spellKey = spellKeyComponents.join '/'
spellKey
createSpellAndAssignName: (spellKey, spellName) ->
@spells[spellKey] ?= {}
@ -262,10 +265,10 @@ class SimulationTask
fullSpellName = [thangName,spellName].join '/'
if _.contains(teamSpells, fullSpellName)
teamCode[fullSpellName]=spell
_.merge spellKeyToSourceMap, teamCode
commonSpells = session.teamSpells["common"]
_.merge spellKeyToSourceMap, _.pick(session.code, commonSpells) if commonSpells?
spellKeyToSourceMap

View file

@ -5,6 +5,7 @@ module.exports = class PointChooser extends CocoClass
super()
@buildShape()
@options.stage.addEventListener 'stagemousedown', @onMouseDown
@options.camera.dragDisabled = true
destroy: ->
@options.stage.removeEventListener 'stagemousedown', @onMouseDown

View file

@ -7,6 +7,7 @@ module.exports = class RegionChooser extends CocoClass
@options.stage.addEventListener 'stagemousedown', @onMouseDown
@options.stage.addEventListener 'stagemousemove', @onMouseMove
@options.stage.addEventListener 'stagemouseup', @onMouseUp
@options.camera.dragDisabled = true
destroy: ->
@options.stage.removeEventListener 'stagemousedown', @onMouseDown

View file

@ -25,6 +25,7 @@ module.exports = class SpriteBoss extends CocoClass
constructor: (@options) ->
super()
@dragged = 0
@options ?= {}
@camera = @options.camera
@surfaceLayer = @options.surfaceLayer
@ -238,11 +239,12 @@ module.exports = class SpriteBoss extends CocoClass
@selectThang e.thangID, e.spellName
onCameraDragged: ->
@dragged = true
@dragged += 1
onSpriteMouseUp: (e) ->
return if key.shift and @options.choosing
return @dragged = false if @dragged
return @dragged = 0 if @dragged > 3
@dragged = 0
sprite = if e.sprite?.thang?.isSelectable then e.sprite else null
@selectSprite e, sprite

View file

@ -0,0 +1,2 @@
#docs-modal .modal-dialog
width: 800px

View file

@ -2,6 +2,18 @@ extends /templates/base
block content
h3 Espionage mode
h5 Please enter the email/username of the person you want to spy on
.form
.form-group
label.control-label Email
input#user-email
.form-group
label.control-label Username
input#user-username
button.btn.btn-primary.btn-large#enter-espionage-mode 007
h3(data-i18n="admin.av_title") Admin Views
h4(data-i18n="admin.av_entities_sub_title") Entities

View file

@ -1,12 +1,12 @@
h4.home
a(href="/")
a(href=homeLink || "/")
i.icon-home.icon-white
span(data-i18n="play_level.home") Home
h4.title #{worldName}
button.btn.btn-xs.btn-inverse.banner#docs-button(title="Show level instructions", data-i18n="play_level.guide") Guide
button.btn.btn-xs.btn-success.banner#docs-button(title="Show level instructions", data-i18n="play_level.guide") Guide
if ladderGame
button.btn.btn-xs.btn-inverse.banner#multiplayer-button(title="Leaderboard", data-i18n="play_level.leaderboard") Leaderboard

View file

@ -38,12 +38,13 @@ class LiveEditingMarkup extends TreemaNode.nodeMap.ace
url: InkBlob.url
filename: InkBlob.filename
mimetype: InkBlob.mimetype
description: ''
createdFor: []
path: @settings.filePath
@uploadingPath = [@settings.filePath, InkBlob.filename].join('/')
$.ajax('/file', { type: 'POST', data: body, success: @onFileUploaded })
onFileUploaded: (e) =>
@editor.insert "![#{e.metadata.name}](/file/#{e._id})"
@editor.insert "![#{e.metadata.name}](/file/#{@uploadingPath})"
onEditorChange: =>
@saveChanges()

View file

@ -1,6 +1,35 @@
{backboneFailure, genericFailure} = require 'lib/errors'
View = require 'views/kinds/RootView'
template = require 'templates/admin'
storage = require 'lib/storage'
module.exports = class AdminView extends View
id: "admin-view"
template: template
events:
'click #enter-espionage-mode': 'enterEspionageMode'
enterEspionageMode: ->
userEmail = $("#user-email").val().toLowerCase()
username = $("#user-username").val().toLowerCase()
userIdentifier = userEmail || username
postData =
usernameLower: username
emailLower: userEmail
$.ajax
type: "POST",
url: "/auth/spy"
data: postData
success: @espionageSuccess
error: @espionageFailure
espionageSuccess: (model) ->
storage.save('whoami',model)
window.location.reload()
espionageFailure: (jqxhr, status,error)->
console.log "There was an error entering espionage mode: #{error}"

View file

@ -35,6 +35,7 @@ module.exports = class ArticleEditView extends View
data = $.extend(true, {}, @article.attributes)
options =
data: data
filePath: "db/thang.type/#{@article.get('original')}"
schema: Article.schema.attributes
callbacks:
change: @pushChangesToPreview

View file

@ -47,12 +47,17 @@ module.exports = class ControlBarView extends View
text += " (#{numPlayers})" if numPlayers > 1
$('#multiplayer-button', @$el).text(text)
getRenderData: (context={}) ->
super context
context.worldName = @worldName
context.multiplayerEnabled = @session.get('multiplayer')
context.ladderGame = @ladderGame
context
getRenderData: (c={}) ->
super c
c.worldName = @worldName
c.multiplayerEnabled = @session.get('multiplayer')
c.ladderGame = @ladderGame
c.homeLink = "/"
levelID = @level.get('slug')
if levelID in ["project-dota", "brawlwood", "ladder-tutorial"]
levelID = 'project-dota' if levelID is 'ladder-tutorial'
c.homeLink = "/play/ladder/" + levelID
c
showGuideModal: ->
options = {docs: @level.get('documentation'), supermodel: @supermodel}

View file

@ -270,7 +270,7 @@ module.exports = class HUDView extends View
if prop is "rotation"
return (val * 180 / Math.PI).toFixed(0) + "˚"
if typeof val is 'number'
if Math.round(val) == val then return val.toFixed(0) # int
if Math.round(val) == val or prop is 'gold' then return val.toFixed(0) # int
if -10 < val < 10 then return val.toFixed(2)
if -100 < val < 100 then return val.toFixed(1)
return val.toFixed(0)

View file

@ -6,6 +6,7 @@ Article = require 'models/Article'
module.exports = class DocsModal extends View
template: template
id: 'docs-modal'
shortcuts:
'enter': 'hide'

View file

@ -82,11 +82,20 @@ module.exports = class DebugView extends View
else
@$el.hide()
if @variableChain?.length is 2
Backbone.Mediator.publish 'tome:spell-debug-property-hovered', property: @variableChain[1], owner: @variableChain[0]
clearTimeout @hoveredPropertyTimeout if @hoveredPropertyTimeout
@hoveredPropertyTimeout = _.delay @notifyPropertyHovered, 500
else
Backbone.Mediator.publish 'tome:spell-debug-property-hovered', property: null
@notifyPropertyHovered()
@updateMarker()
notifyPropertyHovered: =>
clearTimeout @hoveredPropertyTimeout if @hoveredPropertyTimeout
@hoveredPropertyTimeout = null
oldHoveredProperty = @hoveredProperty
@hoveredProperty = if @variableChain?.length is 2 then owner: @variableChain[0], property: @variableChain[1] else {}
unless _.isEqual oldHoveredProperty, @hoveredProperty
Backbone.Mediator.publish 'tome:spell-debug-property-hovered', @hoveredProperty
updateMarker: ->
if @marker
@ace.getSession().removeMarker @marker

View file

@ -141,17 +141,13 @@ module.exports = class PlayLevelView extends View
otherSession = @levelLoader.opponentSession
opponentCode = otherSession?.get('submittedCode') or {}
console.log "otherSession", otherSession, "opponentSpells", opponentSpells
myCode = @session.get('code') or {}
for spell in opponentSpells
[thang, spell] = spell.split '/'
c = opponentCode[thang]?[spell]
console.log "Got opponent code", c, "for", spell, "and had my code", myCode[spell]
myCode[thang] ?= {}
if c then myCode[thang][spell] = c else delete myCode[thang][spell]
console.log "Going to set session code from", _.cloneDeep(myCode)
@session.set('code', myCode)
console.log "Just set session code to", _.cloneDeep(@session.get('code'))
if @session.get('multiplayer') and otherSession?
# For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet.
@session.set 'multiplayer', false

View file

@ -139,6 +139,13 @@ _.extend LevelSessionSchema.properties,
submittedCode:
type: 'object'
numberOfWinsAndTies:
type: 'number'
default: 0
numberOfLosses:
type: 'number'
default: 0
matches:
type: 'array'

View file

@ -12,7 +12,7 @@ TaskLog = require './task/ScoringTask'
bayes = new (require 'bayesian-battle')()
scoringTaskQueue = undefined
scoringTaskTimeoutInSeconds = 120
scoringTaskTimeoutInSeconds = 180
module.exports.setup = (app) -> connectToScoringQueue()
@ -24,24 +24,27 @@ connectToScoringQueue = ->
scoringTaskQueue = data
log.info "Connected to scoring task queue!"
module.exports.addPairwiseTaskToQueue = (req, res) ->
module.exports.addPairwiseTaskToQueueFromRequest = (req, res) ->
taskPair = req.body.sessions
#unless isUserAdmin req then return errors.forbidden res, "You do not have the permissions to submit that game to the leaderboard"
#fetch both sessions
addPairwiseTaskToQueue req.body.sessions (err, success) ->
if err? then return errors.serverError res, "There was an error adding pairwise tasks: #{err}"
sendResponseObject req, res, {"message":"All task pairs were succesfully sent to the queue"}
addPairwiseTaskToQueue = (taskPair, cb) ->
LevelSession.findOne(_id:taskPair[0]).lean().exec (err, firstSession) =>
if err? then return errors.serverError res, "There was an error fetching the first session in the pair"
if err? then return cb err, false
LevelSession.find(_id:taskPair[1]).exec (err, secondSession) =>
if err? then return errors.serverError res, "There was an error fetching the second session"
if err? then return cb err, false
try
taskPairs = generateTaskPairs(secondSession, firstSession)
catch e
if e then return errors.serverError res, "There was an error generating the task pairs"
sendEachTaskPairToTheQueue taskPairs, (taskPairError) ->
if taskPairError? then return errors.serverError res, "There was an error sending the task pairs to the queue"
if e then return cb e, false
sendResponseObject req, res, {"message":"All task pairs were succesfully sent to the queue"}
sendEachTaskPairToTheQueue taskPairs, (taskPairError) ->
if taskPairError? then return cb taskPairError,false
cb null, true
module.exports.createNewTask = (req, res) ->
requestSessionID = req.body.session
@ -56,8 +59,8 @@ module.exports.createNewTask = (req, res) ->
updateSessionToSubmit sessionToSubmit, (err, data) ->
if err? then return errors.serverError res, "There was an error updating the session"
fetchSessionsToRankAgainst (err, sessionsToRankAgainst) ->
opposingTeam = calculateOpposingTeam(sessionToSubmit.team)
fetchInitialSessionsToRankAgainst opposingTeam, (err, sessionsToRankAgainst) ->
if err? then return errors.serverError res, "There was an error fetching the sessions to rank against"
taskPairs = generateTaskPairs(sessionsToRankAgainst, sessionToSubmit)
@ -114,9 +117,102 @@ module.exports.processTaskResult = (req, res) ->
addMatchToSessions clientResponseObject, newScoresObject, (err, data) ->
if err? then return errors.serverError res, "There was an error updating the sessions with the match! #{JSON.stringify err}"
console.log "Sending response object"
sendResponseObject req, res, {"message":"The scores were updated successfully!"}
originalSessionID = clientResponseObject.originalSessionID
originalSessionTeam = clientResponseObject.originalSessionTeam
originalSessionRank = parseInt clientResponseObject.originalSessionRank
determineIfSessionShouldContinueAndUpdateLog originalSessionID, originalSessionRank, (err, sessionShouldContinue) ->
if err? then return errors.serverError res, "There was an error determining if the session should continue, #{err}"
if sessionShouldContinue
opposingTeam = calculateOpposingTeam(originalSessionTeam)
opponentID = _.pull(_.keys(newScoresObject), originalSessionID)
sessionNewScore = newScoresObject[originalSessionID].totalScore
opponentNewScore = newScoresObject[opponentID].totalScore
findNearestBetterSessionID sessionNewScore, opponentNewScore, opponentID ,opposingTeam, (err, opponentSessionID) ->
if err? then return errors.serverError res, "There was an error finding the nearest sessionID!"
unless opponentSessionID then return sendResponseObject req, res, {"message":"There were no more games to rank(game is at top!"}
addPairwiseTaskToQueue [originalSessionID, opponentSessionID], (err, success) ->
if err? then return errors.serverError res, "There was an error sending the pairwise tasks to the queue!"
sendResponseObject req, res, {"message":"The scores were updated successfully and more games were sent to the queue!"}
else
console.log "Player lost, achieved rank #{originalSessionRank}"
sendResponseObject req, res, {"message":"The scores were updated successfully, person lost so no more games are being inserted!"}
determineIfSessionShouldContinueAndUpdateLog = (sessionID, sessionRank, cb) ->
queryParameters =
_id: sessionID
updateParameters =
"$inc": {}
if sessionRank is 0
updateParameters["$inc"] = {numberOfWinsAndTies: 1}
else
updateParameters["$inc"] = {numberOfLosses: 1}
LevelSession.findOneAndUpdate queryParameters, updateParameters,{select: 'numberOfWinsAndTies numberOfLosses'}, (err, updatedSession) ->
if err? then return cb err, updatedSession
updatedSession = updatedSession.toObject()
totalNumberOfGamesPlayed = updatedSession.numberOfWinsAndTies + updatedSession.numberOfLosses
if totalNumberOfGamesPlayed < 5
console.log "Number of games played is less than 5, continuing..."
cb null, true
else if totalNumberOfGamesPlayed > 15
console.log "Too many games played, ending..."
cb null, false
else
ratio = (updatedSession.numberOfLosses - 5) / (totalNumberOfGamesPlayed)
if ratio > 0.66
cb null, false
console.log "Ratio(#{ratio}) is bad, ending simulation"
else
console.log "Ratio(#{ratio}) is good, so continuing simulations"
cb null, true
findNearestBetterSessionID = (sessionTotalScore, opponentSessionTotalScore, opponentSessionID, opposingTeam, cb) ->
queryParameters =
totalScore:
$gt:opponentSessionTotalScore + 0.5
_id:
$ne: opponentSessionID
levelID: "project-dota"
submitted: true
submittedCode:
$exists: true
team: opposingTeam
limitNumber = 1
sortParameters =
totalScore: 1
selectString = '_id totalScore'
query = LevelSession.findOne(queryParameters)
.sort(sortParameters)
.limit(limitNumber)
.select(selectString)
.lean()
console.log "Finding session with score near #{opponentSessionTotalScore}"
query.exec (err, session) ->
if err? then return cb err, session
unless session then return cb err, null
console.log "Found session with score #{session.totalScore}"
cb err, session._id
calculateOpposingTeam = (sessionTeam) ->
teams = ['ogres','humans']
opposingTeams = _.pull teams, sessionTeam
return opposingTeams[0]
validatePermissions = (req, sessionID, callback) ->
if isUserAnonymous req then return callback null, false
if isUserAdmin req then return callback null, true
@ -177,15 +273,30 @@ updateSessionToSubmit = (sessionToUpdate, callback) ->
meanStrength: 25
standardDeviation: 25/3
totalScore: 10
numberOfWinsAndTies: 0
numberOfLosses: 0
LevelSession.update {_id: sessionToUpdate._id}, sessionUpdateObject, callback
fetchSessionsToRankAgainst = (callback) ->
submittedSessionsQuery =
fetchInitialSessionsToRankAgainst = (opposingTeam, callback) ->
console.log "Fetching sessions to rank against for opposing team #{opposingTeam}"
findParameters =
levelID: "project-dota"
submitted: true
submittedCode:
$exists: true
LevelSession.find submittedSessionsQuery, callback
team: opposingTeam
sortParameters =
totalScore: 1
limitNumber = 1
query = LevelSession.find(findParameters)
.sort(sortParameters)
.limit(limitNumber)
query.exec callback
generateTaskPairs = (submittedSessions, sessionToScore) ->
taskPairs = []

View file

@ -28,7 +28,30 @@ module.exports.setup = (app) ->
return done(null, user)
)
))
app.post '/auth/spy', (req, res, next) ->
if req?.user?.isAdmin()
username = req.body.usernameLower
emailLower = req.body.emailLower
if emailLower
query = {"emailLower":emailLower}
else if username
query = {"nameLower":username}
else
return errors.badInput res, "You need to supply one of emailLower or username"
User.findOne query, (err, user) ->
if err? then return errors.serverError res, "There was an error finding the specified user"
unless user then return errors.badInput res, "The specified user couldn't be found"
req.logIn user, (err) ->
if err? then return errors.serverError res, "There was an error logging in with the specified"
res.send(UserHandler.formatEntity(req, user))
return res.end()
else
return errors.unauthorized res, "You must be an admin to enter espionage mode"
app.post('/auth/login', (req, res, next) ->
authentication.authenticate('local', (err, user, info) ->
return next(err) if err