diff --git a/app/styles/play/level/tome/tome.sass b/app/styles/play/level/tome/tome.sass index 0fd9f5ac7..c8eaee5f3 100644 --- a/app/styles/play/level/tome/tome.sass +++ b/app/styles/play/level/tome/tome.sass @@ -33,3 +33,6 @@ &.right .arrow left: -3% + pre + display: inline-block + padding: 5px diff --git a/app/templates/play/level/tome/spell_palette_entry_popover.jade b/app/templates/play/level/tome/spell_palette_entry_popover.jade index 37b097869..1002593e1 100644 --- a/app/templates/play/level/tome/spell_palette_entry_popover.jade +++ b/app/templates/play/level/tome/spell_palette_entry_popover.jade @@ -20,7 +20,8 @@ else if doc.type == 'function' if doc.type != 'function' && doc.type != 'snippet' p.value strong Current Value: - code.current-value(data-prop=doc.name)= value + pre + code.current-value(data-prop=doc.name)= value if doc.args && doc.args.length p.args diff --git a/app/views/home_view.coffee b/app/views/home_view.coffee index 359b3d935..6c81fde84 100644 --- a/app/views/home_view.coffee +++ b/app/views/home_view.coffee @@ -105,9 +105,13 @@ module.exports = class HomeView extends View @wizardSprite?.destroy() onSimulateButtonClick: (e) => + @alreadyPostedResults = false $.get "/queue/scoring", (data) => + console.log data levelName = data.sessions[0].levelID #TODO: Refactor. So much refactor. + @taskData = data + @teamSessionMap = @generateTeamSessionMap data world = {} god = new God() levelLoader = new LevelLoader(levelName, @supermodel, data.sessions[0].sessionID) @@ -138,8 +142,63 @@ module.exports = class HomeView extends View Backbone.Mediator.subscribe 'god:new-world-created', @onWorldCreated, @ onWorldCreated: (data) -> - console.log "GOAL STATES" - console.log data + return if @alreadyPostedResults + taskResults = @translateGoalStatesIntoTaskResults data.goalStates + console.log "Task Results" + console.log taskResults + $.ajax + url: "/queue/scoring" + data: taskResults + type: 'PUT' + success: (result) => + console.log "TASK REGISTRATION RESULT:#{JSON.stringify result}" + error: (error) => + console.log "TASK REGISTRATION ERROR:#{JSON.stringify error}" + complete: (result) => + @alreadyPostedResults = true + + + translateGoalStatesIntoTaskResults: (goalStates) => + taskResults = {} + taskResults = + taskID: @taskData.taskID + receiptHandle: @taskData.receiptHandle + calculationTime: 500 + sessions: [] + + for session in @taskData.sessions + sessionResult = + sessionID: session.sessionID + sessionChangedTime: session.sessionChangedTime + metrics: + rank: @calculateSessionRank session.sessionID, goalStates + taskResults.sessions.push sessionResult + taskResults + + calculateSessionRank: (sessionID, goalStates) -> + humansDestroyed = goalStates["destroy-humans"].status is "success" + ogresDestroyed = goalStates["destroy-ogres"].status is "success" + console.log "Humans destroyed:#{humansDestroyed}" + console.log "Ogres destroyed:#{ogresDestroyed}" + console.log "Team Session Map: #{JSON.stringify @teamSessionMap}" + if humansDestroyed is ogresDestroyed + return 0 + else if humansDestroyed and @teamSessionMap["ogres"] is sessionID + return 0 + else if humansDestroyed and @teamSessionMap["ogres"] isnt sessionID + return 1 + else if ogresDestroyed and @teamSessionMap["humans"] is sessionID + return 0 + else + return 1 + + + generateTeamSessionMap: (task) -> + teamSessionMap = {} + for session in @taskData.sessions + teamSessionMap[session.team] = session.sessionID + teamSessionMap + filterProgrammableComponents: (thangs, spellToSourceMap) => diff --git a/app/views/play/ladder_view.coffee b/app/views/play/ladder_view.coffee index 1489fa386..37e2ed006 100644 --- a/app/views/play/ladder_view.coffee +++ b/app/views/play/ladder_view.coffee @@ -20,7 +20,9 @@ class LeaderboardCollection extends CocoCollection constructor: (level, options) -> super() options ?= {} - @url = "/db/level/#{level.get('original')}.#{level.get('version').major}/leaderboard?#{$.param(options)}" + #@url = "/db/level/#{level.get('original')}.#{level.get('version').major}/leaderboard?#{$.param(options)}" + @url = "/db/level/#{level.get('original')}/leaderboard?#{$.param(options)}" + module.exports = class LadderView extends RootView id: 'ladder-view' @@ -38,7 +40,8 @@ module.exports = class LadderView extends RootView # @sessions.once 'sync', @onMySessionsLoaded, @ onLevelLoaded: -> @startLoadingPhaseTwoMaybe() - onMySessionsLoaded: -> @startLoadingPhaseTwoMaybe() + onMySessionsLoaded: -> + @startLoadingPhaseTwoMaybe() startLoadingPhaseTwoMaybe: -> return unless @level.loaded # and @sessions.loaded @@ -55,8 +58,9 @@ module.exports = class LadderView extends RootView @leaderboards = {} @challengers = {} for team in teams -# teamSession = _.find @sessions.models, (session) -> session.get('team') is team + teamSession = _.find @sessions.models, (session) -> session.get('team') is team teamSession = null + console.log "Team session: #{JSON.stringify teamSession}" @leaderboards[team] = new LeaderboardData(@level, team, teamSession) @leaderboards[team].once 'sync', @onLeaderboardLoaded, @ # @challengers[team] = new ChallengersData(@level, team, teamSession) @@ -102,7 +106,7 @@ class LeaderboardData @topPlayers.once 'sync', @leaderboardPartLoaded, @ # if @session -# score = @session.get('score') or 25 +# score = @session.get('totalScore') or 25 # @playersAbove = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 4, team: @team}) # @playersAbove.fetch() # @playersAbove.once 'sync', @leaderboardPartLoaded, @ @@ -122,7 +126,7 @@ class LeaderboardData class ChallengersData constructor: (@level, @team, @session) -> _.extend @, Backbone.Events - score = @session?.get('score') or 25 + score = @session?.get('totalScore') or 25 @easyPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score - 5, limit: 1, team: @team}) @easyPlayer.fetch() @easyPlayer.once 'sync', @challengerLoaded, @ diff --git a/app/views/play/level/tome/spell_palette_entry_view.coffee b/app/views/play/level/tome/spell_palette_entry_view.coffee index 4f26afc17..0ef882854 100644 --- a/app/views/play/level/tome/spell_palette_entry_view.coffee +++ b/app/views/play/level/tome/spell_palette_entry_view.coffee @@ -8,6 +8,39 @@ filters = require 'lib/image_filter' # If we use marked somewhere else, we'll have to make sure to preserve options marked.setOptions {gfm: true, sanitize: false, smartLists: true, breaks: true} +safeJSONStringify = (input, maxDepth) -> + recursion = (input, path, depth) -> + output = {} + pPath = undefined + refIdx = undefined + path = path or "" + depth = depth or 0 + depth++ + return "{depth over " + maxDepth + "}" if maxDepth and depth > maxDepth + for p of input + pPath = ((if path then (path + ".") else "")) + p + if typeof input[p] is "function" + output[p] = "{function}" + else if typeof input[p] is "object" + refIdx = refs.indexOf(input[p]) + if -1 isnt refIdx + output[p] = "{reference to " + refsPaths[refIdx] + "}" + else + refs.push input[p] + refsPaths.push pPath + output[p] = recursion(input[p], pPath, depth) + else + output[p] = input[p] + output + refs = [] + refsPaths = [] + maxDepth = maxDepth or 5 + if typeof input is "object" + output = recursion(input) + else + output = input + JSON.stringify output, null, 1 + module.exports = class SpellPaletteEntryView extends View tagName: 'div' # Could also try <code> instead of <div>, but would need to adjust colors className: 'spell-palette-entry-view' @@ -61,6 +94,7 @@ module.exports = class SpellPaletteEntryView extends View formatPopover: -> content = popoverTemplate doc: @doc, value: @formatValue(), marked: marked, argumentExamples: (arg.example or arg.default or arg.name for arg in @doc.args ? []) owner = if @doc.owner is 'this' then @thang else window[@doc.owner] + content = content.replace /#{spriteName}/g, @thang.spriteName # No quotes like we'd get with @formatValue content.replace /\#\{(.*?)\}/g, (s, properties) => @formatValue downTheChain(owner, properties.split('.')) formatValue: (v) -> @@ -70,7 +104,7 @@ module.exports = class SpellPaletteEntryView extends View v = @thang[@doc.name] else v = window[@doc.owner][@doc.name] - if @type is 'number' and not isNaN v + if @doc.type is 'number' and not isNaN v if v == Math.round v return v return v.toFixed 2 @@ -82,6 +116,8 @@ module.exports = class SpellPaletteEntryView extends View return v.name if _.isArray v return '[' + (@formatValue v2 for v2 in v).join(', ') + ']' + if _.isPlainObject v + return safeJSONStringify v, 2 v onMouseOver: (e) -> @@ -95,7 +131,6 @@ module.exports = class SpellPaletteEntryView extends View onFrameChanged: (e) -> return unless e.selectedThang?.id is @thang.id @options.thang = @thang = e.selectedThang # Update our thang to the current version - @$el.find("code.current-value").text(@formatValue()) destroy: -> @$el.off() diff --git a/server/commons/queue.coffee b/server/commons/queue.coffee index 6cc1b06ae..1e86f99cd 100644 --- a/server/commons/queue.coffee +++ b/server/commons/queue.coffee @@ -10,7 +10,7 @@ crypto = require 'crypto' module.exports.queueClient = undefined -defaultMessageVisibilityTimeoutInSeconds = 20 +defaultMessageVisibilityTimeoutInSeconds = 500 defaultMessageReceiptTimeout = 10 @@ -190,7 +190,7 @@ class MongoQueue extends events.EventEmitter conditions = queue: @queueName scheduledVisibilityTime: - $lt: new Date() + $lte: new Date() options = sort: 'scheduledVisibilityTime' @@ -214,7 +214,7 @@ class MongoQueue extends events.EventEmitter queue: @queueName receiptHandle: receiptHandle scheduledVisibilityTime: - $lt: new Date() + $gte: new Date() @Message.findOneAndRemove conditions, {}, (err, data) => if err? then @emit 'error',err,data else @emit 'delete',err,data @@ -237,14 +237,19 @@ class MongoQueue extends events.EventEmitter queue: @queueName receiptHandle: receiptHandle scheduledVisibilityTime: - $lt: new Date() + $gte: new Date() update = $set: scheduledVisibilityTime: @_constructDefaultVisibilityTimeoutDate secondsFromNow @Message.findOneAndUpdate conditions, update, (err, data) => - if err? then @emit 'error',err,data else @emit 'update',err,data + if err? + log.error "There was a problem updating the message visibility timeout:#{err}" + @emit 'error',err,data + else + @emit 'update',err,data + log.info "The message visibility time was updated" callback? err, data diff --git a/server/levels/level_handler.coffee b/server/levels/level_handler.coffee index 944fda598..de769f818 100644 --- a/server/levels/level_handler.coffee +++ b/server/levels/level_handler.coffee @@ -70,10 +70,10 @@ LevelHandler = class LevelHandler extends Handler sessionQuery = { level: {original: level.original.toString(), majorVersion: level.version.major} - creator: req.user.id + #creator: req.user.id + submitted: true } - - Session.find(sessionQuery).exec (err, results) => + Session.find sessionQuery, '_id totalScore submitted team creatorName', (err, results) => return @sendDatabaseError(res, err) if err res.send(results) res.end() @@ -83,7 +83,28 @@ LevelHandler = class LevelHandler extends Handler # [original, version] = id.split('.') # version = parseInt version # console.log 'get leaderboard for', original, version, req.query - return res.send([]) + @getDocumentForIdOrSlug id, (err, level) => + return @sendDatabaseError(res, err) if err + return @sendNotFoundError(res) unless level? + return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, level) + + if parseInt(req.query.order) is 1 + scoreQuery = {"$gte":parseFloat req.query.scoreOffset} + else + scoreQuery = {"$lte": parseFloat req.query.scoreOffset} + + sessionsQuery = + level: {original: level.original.toString(), majorVersion: level.version.major} + team: req.query.team + totalScore: scoreQuery + submitted: true + console.log sessionsQuery + query = Session.find(sessionsQuery).sort({"totalScore":parseInt(req.query.order)}).limit(parseInt req.query.limit) + Session.find sessionsQuery, '_id totalScore submitted team creatorName', (err, resultSessions) => + return @sendDatabaseError(res, err) if err + if resultSessions + return @sendSuccess res, resultSessions + res.send([]) getFeedback: (req, res, id) -> @getDocumentForIdOrSlug id, (err, level) => diff --git a/server/queues/scoring.coffee b/server/queues/scoring.coffee index f8fd841d1..56438bbb4 100644 --- a/server/queues/scoring.coffee +++ b/server/queues/scoring.coffee @@ -39,7 +39,7 @@ module.exports.createNewTask = (req, res) -> LevelSession.find { "levelID": "project-dota", "submitted": true}, (err, submittedSessions) -> taskPairs = [] for session in submittedSessions - if String(session._id) isnt req.body.session + if String(session._id) isnt req.body.session and session.team isnt sessionToScore.team taskPairs.push [req.body.session,String session._id] async.each taskPairs, sendTaskPairToQueue, (taskPairError) -> return errors.serverError res, "There was an error sending the task pairs to the queue" if taskPairError? @@ -61,6 +61,7 @@ module.exports.dispatchTaskToConsumer = (req, res) -> if (not message?) or message.isEmpty() or taskQueueReceiveError? return errors.gatewayTimeoutError res, "No messages were receieved from the queue. Msg:#{taskQueueReceiveError}" + messageBody = parseTaskQueueMessage req, res, message return errors.serverError res, "There was an error parsing the queue message" unless messageBody? @@ -74,6 +75,8 @@ module.exports.dispatchTaskToConsumer = (req, res) -> setTaskObjectTaskLogID taskObject, taskLogObject._id + taskObject.receiptHandle = message.getReceiptHandle() + sendResponseObject req, res, taskObject @@ -159,16 +162,18 @@ module.exports.processTaskResult = (req, res) -> return errors.badInput res, "That computational task has already been performed" if taskLogJSON.calculationTimeMS return handleTimedOutTask req, res, clientResponseObject if hasTaskTimedOut taskLogJSON.sentDate + destroyQueueMessage clientResponseObject.receiptHandle, (err) -> + return errors.badInput res, "The queue message is already back in the queue, rejecting results." if err? - logTaskComputation clientResponseObject, taskLog, (loggingError) -> - if loggingError? - return errors.serverError res, "There as a problem logging the task computation: #{loggingError}" + logTaskComputation clientResponseObject, taskLog, (loggingError) -> + if loggingError? + return errors.serverError res, "There as a problem logging the task computation: #{loggingError}" - updateScores clientResponseObject, (updatingScoresError, newScores) -> - if updatingScoresError? - return errors.serverError res, "There was an error updating the scores.#{updatingScoresError}" + updateScores clientResponseObject, (updatingScoresError, newScores) -> + if updatingScoresError? + return errors.serverError res, "There was an error updating the scores.#{updatingScoresError}" - sendResponseObject req, res, {"message":"The scores were updated successfully!"} + sendResponseObject req, res, {"message":"The scores were updated successfully!"} @@ -178,6 +183,7 @@ hasTaskTimedOut = (taskSentTimestamp) -> taskSentTimestamp + scoringTaskTimeoutI handleTimedOutTask = (req, res, taskBody) -> errors.clientTimeout res, "The results weren't provided within the timeout" +destroyQueueMessage = (receiptHandle, callback) -> scoringTaskQueue.deleteMessage receiptHandle, callback verifyClientResponse = (responseObject, res) -> unless typeof responseObject is "object" @@ -217,14 +223,12 @@ updateScoreInSession = (scoreObject,callback) -> LevelSession.findOne sessionObjectQuery, (err, session) -> return callback err, null if err? - - session.meanStrength = scoreObject.meanStrength - session.standardDeviation = scoreObject.standardDeviation - session.totalScore = scoreObject.meanStrength - 1.8 * scoreObject.standardDeviation - - log.info "Saving session #{session._id}!" - - session.save callback + updateObject = + meanStrength: scoreObject.meanStrength + standardDeviation: scoreObject.standardDeviation + totalScore: scoreObject.meanStrength - 1.8 * scoreObject.standardDeviation + log.info "New total score for session #{scoreObject.id} is #{updateObject.totalScore}" + LevelSession.update sessionObjectQuery, updateObject, callback putRankingFromMetricsIntoScoreObject = (taskObject,scoreObject) ->