diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee index feb87fc93..fd278b8cc 100644 --- a/app/lib/surface/Surface.coffee +++ b/app/lib/surface/Surface.coffee @@ -497,13 +497,15 @@ module.exports = Surface = class Surface extends CocoClass # seems to be a bug where only one object can register with the Ticker... oldFrame = @currentFrame oldWorldFrame = Math.floor oldFrame + lastFrame = @world.totalFrames - 1 while true Dropper.tick() @trailmaster.tick() if @trailmaster # Skip some frame updates unless we're playing and not at end (or we haven't drawn much yet) - frameAdvanced = (@playing and @currentFrame < @world.totalFrames) or @totalFramesDrawn < 2 - @currentFrame += @world.frameRate / @options.frameRate if frameAdvanced and @playing - @currentFrame = Math.min(@currentFrame, @world.totalFrames - 1) + frameAdvanced = (@playing and @currentFrame < lastFrame) or @totalFramesDrawn < 2 + if frameAdvanced and @playing + @currentFrame += @world.frameRate / @options.frameRate + @currentFrame = Math.min @currentFrame, lastFrame newWorldFrame = Math.floor @currentFrame worldFrameAdvanced = newWorldFrame isnt oldWorldFrame if worldFrameAdvanced diff --git a/app/locale/en.coffee b/app/locale/en.coffee index e3c694f82..2766dda9c 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -179,6 +179,9 @@ module.exports = nativeDescription: "English", englishDescription: "English", tr victory_sign_up: "Sign Up to Save Progress" victory_sign_up_poke: "Want to save your code? Create a free account!" victory_rate_the_level: "Rate the level: " + victory_rank_my_game: "Rank My Game" + victory_ranking_game: "Submitting..." + victory_return_to_ladder: "Return to Ladder" victory_play_next_level: "Play Next Level" victory_go_home: "Go Home" victory_review: "Tell us more!" diff --git a/app/styles/play/level/hud.sass b/app/styles/play/level/hud.sass index 0379fc151..c83b20933 100644 --- a/app/styles/play/level/hud.sass +++ b/app/styles/play/level/hud.sass @@ -208,11 +208,11 @@ height: 19px div - @include box-sizing(border-box) + border-radius: 1px background-color: #6BA1C8 height: 100% - border-bottom: 2px groove #201B15 - border-right: 1px solid #201B15 + border-bottom: 2px groove darken(#6BA1C8, 30%) + border-right: 1px solid darken(#6BA1C8, 10%) position: absolute top: 0 diff --git a/app/templates/play/ladder/my_matches_tab.jade b/app/templates/play/ladder/my_matches_tab.jade index d2b543478..c70192221 100644 --- a/app/templates/play/ladder/my_matches_tab.jade +++ b/app/templates/play/ladder/my_matches_tab.jade @@ -11,11 +11,7 @@ div#columns.row tr th(colspan=4, style="color: #{team.primaryColor}") - span Your - span - span= team.name - span - span Matches + span Your #{team.name} Matches - #{team.wins} Wins, #{team.losses} Losses if team.session button.btn.btn-sm.btn-warning.pull-right.rank-button(data-session-id=team.session.id) diff --git a/app/templates/play/level/modal/victory.jade b/app/templates/play/level/modal/victory.jade index 45c7f567f..6329f0bbe 100644 --- a/app/templates/play/level/modal/victory.jade +++ b/app/templates/play/level/modal/victory.jade @@ -14,8 +14,10 @@ div!= body .modal-footer - if level.get('type') === 'ladder' - a.btn.btn-primary(href="/play/ladder/#{level.get('slug')}#my-matches", data-dismiss="modal", data-i18n="play_level.victory_go_home") Go Home + if readyToRank + button.btn.btn-success.rank-game-button(data-i18n="play_level.victory_rank_my_game") Rank My Game + else if level.get('type') === 'ladder' + a.btn.btn-primary(href="/play/ladder/#{level.get('slug')}#my-matches", data-dismiss="modal", data-i18n="play_level.victory_go_ladder") Return to Ladder else if hasNextLevel button.btn.btn-primary.next-level-button(data-dismiss="modal", data-i18n="play_level.victory_play_next_level") Play Next Level else diff --git a/app/views/play/ladder/my_matches_tab.coffee b/app/views/play/ladder/my_matches_tab.coffee index 998b63bfa..a094aef82 100644 --- a/app/views/play/ladder/my_matches_tab.coffee +++ b/app/views/play/ladder/my_matches_tab.coffee @@ -73,6 +73,9 @@ module.exports = class MyMatchesTabView extends CocoView team.matches = (convertMatch(match) for match in team.session?.get('matches') or []) team.matches.reverse() team.score = (team.session?.get('totalScore') or 10).toFixed(2) + team.wins = _.filter(team.matches, {state: 'win'}).length + team.ties = _.filter(team.matches, {state: 'tie'}).length + team.losses = _.filter(team.matches, {state: 'loss'}).length ctx @@ -91,7 +94,6 @@ module.exports = class MyMatchesTabView extends CocoView c1 and not _.isEqual(c1, c2) rankSession: (e) -> - console.log "Clicked" button = $(e.target).closest('.rank-button') sessionID = button.data('session-id') session = _.find @sessions.models, { id: sessionID } diff --git a/app/views/play/level/hud_view.coffee b/app/views/play/level/hud_view.coffee index 662c9fc8f..324c5e68b 100644 --- a/app/views/play/level/hud_view.coffee +++ b/app/views/play/level/hud_view.coffee @@ -135,7 +135,7 @@ module.exports = class HUDView extends View props = @$el.find('.thang-props') props.find(":not(.thang-name)").remove() props.find('.thang-name').text(if @thang.type then "#{@thang.id} - #{@thang.type}" else @thang.id) - propNames = @thang.hudProperties ? [] + propNames = _.without @thang.hudProperties ? [], 'action' nColumns = Math.ceil propNames.length / 5 columns = ($('
').appendTo(props) for i in [0 ... nColumns]) for prop, i in propNames @@ -316,11 +316,11 @@ module.exports = class HUDView extends View @timespans = {} dt = @thang.world.dt actionHistory = @thang.world.actionsForThang @thang.id, true - [lastFrame, lastAction] = [0, 'idle'] + [lastFrame, lastAction] = [0, null] for hist in actionHistory.concat {frame: @thang.world.totalFrames, name: 'END'} [newFrame, newAction] = [hist.frame, hist.name] continue if newAction is lastAction - if newFrame > lastFrame + if newFrame > lastFrame and lastAction # TODO: don't push it if it didn't exist until then (@timespans[lastAction] ?= []).push [lastFrame * dt, newFrame * dt] [lastFrame, lastAction] = [newFrame, newAction] diff --git a/app/views/play/level/modal/victory_modal.coffee b/app/views/play/level/modal/victory_modal.coffee index 6773982ad..c71b2bcac 100644 --- a/app/views/play/level/modal/victory_modal.coffee +++ b/app/views/play/level/modal/victory_modal.coffee @@ -11,6 +11,7 @@ module.exports = class VictoryModal extends View events: 'click .next-level-button': 'onPlayNextLevel' + 'click .rank-game-button': 'onRankGame' # review events 'mouseover .rating i': (e) -> @showStars(@starNum($(e.target))) @@ -58,6 +59,21 @@ module.exports = class VictoryModal extends View @saveReview() if @$el.find('.review textarea').val() Backbone.Mediator.publish('play-next-level') + onRankGame: (e) -> + button = @$el.find('.rank-game-button') + button.text($.i18n.t('play_level.victory_ranking_game', defaultValue: 'Submitting...')) + button.prop 'disabled', true + ajaxData = session: @session.id, levelID: @level.id, originalLevelID: @level.get('original'), levelMajorVersion: @level.get('version').major + ladderURL = "/play/ladder/#{@level.get('slug')}#my-matches" + goToLadder = -> Backbone.Mediator.publish 'router:navigate', route: ladderURL + $.ajax '/queue/scoring', + type: 'POST' + data: ajaxData + success: goToLadder + failure: (response) -> + console.error "Couldn't submit game for ranking:", response + goToLadder() + getRenderData: -> c = super() c.body = @body @@ -65,6 +81,10 @@ module.exports = class VictoryModal extends View c.hasNextLevel = _.isObject(@level.get('nextLevel')) and (@level.get('name') isnt "Mobile Artillery") c.levelName = @level.get('i18n')?[me.lang()]?.name ? @level.get('name') c.level = @level + if c.level.get('type') is 'ladder' + c1 = @session?.get('code') + c2 = @session?.get('submittedCode') + c.readyToRank = @session.get('levelID') and c1 and not _.isEqual(c1, c2) if me.get 'hourOfCode' # Show the Hour of Code "I'm Done" tracking pixel after they played for 30 minutes elapsed = (new Date() - new Date(me.get('dateCreated'))) diff --git a/server/levels/level_handler.coffee b/server/levels/level_handler.coffee index 94bf33dd8..01b7eec40 100644 --- a/server/levels/level_handler.coffee +++ b/server/levels/level_handler.coffee @@ -89,15 +89,27 @@ LevelHandler = class LevelHandler extends Handler # associated with the handler, because the handler might return a different type # of model, like in this case. Refactor to move that logic to the model instead. - getMySessions: (req, res, id) -> - @fetchLevelByIDAndHandleErrors id, req, res, (err, level) => + getMySessions: (req, res, slugOrID) -> + findParameters = {} + if Handler.isID slugOrID + findParameters["_id"] = slugOrID + else + findParameters["slug"] = slugOrID + selectString = 'original version.major permissions' + query = Level.findOne(findParameters) + .select(selectString) + .lean() + + query.exec (err, level) => + return @sendDatabaseError(res, err) if err + return @sendNotFoundError(res) unless level? sessionQuery = level: original: level.original.toString() majorVersion: level.version.major creator: req.user._id+'' - - query = Session.find(sessionQuery) + + query = Session.find(sessionQuery).select('-screenshot') query.exec (err, results) => if err then @sendDatabaseError(res, err) else @sendSuccess res, results diff --git a/server/levels/sessions/level_session_schema.coffee b/server/levels/sessions/level_session_schema.coffee index 290422c10..ca8df4c05 100644 --- a/server/levels/sessions/level_session_schema.coffee +++ b/server/levels/sessions/level_session_schema.coffee @@ -146,6 +146,18 @@ _.extend LevelSessionSchema.properties, numberOfLosses: type: 'number' default: 0 + + scoreHistory: + type: 'array' + title: 'Score History' + description: 'A list of objects representing the score history of a session' + items: + title: 'Score History Point' + description: 'An array with the format [unix timestamp, totalScore]' + type: 'array' + items: + type: 'number' + matches: type: 'array' diff --git a/server/queues/scoring.coffee b/server/queues/scoring.coffee index 1e2cd3f29..0462cd503 100644 --- a/server/queues/scoring.coffee +++ b/server/queues/scoring.coffee @@ -443,10 +443,15 @@ updateScoreInSession = (scoreObject,callback) -> if err? then return callback err, null session = session.toObject() + newTotalScore = scoreObject.meanStrength - 1.8 * scoreObject.standardDeviation + scoreHistoryAddition = [Date.now(), newTotalScore] updateObject = meanStrength: scoreObject.meanStrength standardDeviation: scoreObject.standardDeviation - totalScore: scoreObject.meanStrength - 1.8 * scoreObject.standardDeviation + totalScore: newTotalScore + $push: + scoreHistory: scoreHistoryAddition + LevelSession.update {"_id": scoreObject.id}, updateObject, callback log.info "New total score for session #{scoreObject.id} is #{updateObject.totalScore}" diff --git a/server/routes/mail.coffee b/server/routes/mail.coffee index ed6b58795..d629883d7 100644 --- a/server/routes/mail.coffee +++ b/server/routes/mail.coffee @@ -5,6 +5,7 @@ errors = require '../commons/errors' #request = require 'request' config = require '../../server_config' LevelSession = require '../levels/sessions/LevelSession.coffee' +Level = require '../levels/Level.coffee' log = require 'winston' sendwithus = require '../sendwithus' @@ -16,9 +17,23 @@ module.exports.setup = (app) -> app.all config.mail.mailchimpWebhook, handleMailchimpWebHook app.get '/mail/cron/ladder-update', handleLadderUpdate +getAllLadderScores = (next) -> + query = Level.find({type: 'ladder'}) + .select('levelID') + .lean() + query.exec (err, levels) -> + if err + log.error "Couldn't fetch ladder levels. Error: ", err + return next [] + for level in levels + for team in ['humans', 'ogres'] + 'I ... am not doing this.' + handleLadderUpdate = (req, res) -> + log.info("Going to see about sending ladder update emails.") res.send('Great work, Captain Cron! I can take it from here.') res.end() + # TODO: somehow fetch the histograms emailDays = [1, 2, 4, 7, 30] now = new Date() getTimeFromDaysAgo = (daysAgo) -> @@ -35,7 +50,6 @@ handleLadderUpdate = (req, res) -> query = LevelSession.find(findParameters) .select(selectString) .lean() - mongoose = require 'mongoose' do (daysAgo) -> query.exec (err, results) -> if err