mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-14 05:55:00 -04:00
Merge pull request #463 from codecombat/master
Merge ladder view into production
This commit is contained in:
commit
ae4366bdb1
14 changed files with 721 additions and 212 deletions
app
models
styles/play
templates/play
views
server/levels
|
@ -210,3 +210,10 @@ module.exports = class ThangType extends CocoModel
|
|||
|
||||
onFileUploaded: =>
|
||||
console.log 'Image uploaded'
|
||||
|
||||
wizOriginal = "52a00d55cf1818f2be00000b"
|
||||
url = "/db/thang_type/#{wizOriginal}/version"
|
||||
wizardType = new module.exports()
|
||||
wizardType.url = -> url
|
||||
wizardType.fetch()
|
||||
module.exports.wizardType = wizardType
|
|
@ -1,4 +1,10 @@
|
|||
#ladder-view
|
||||
h1
|
||||
text-align: center
|
||||
|
||||
.tab-pane
|
||||
margin-top: 10px
|
||||
|
||||
.score-cell
|
||||
width: 50px
|
||||
|
||||
|
@ -10,10 +16,6 @@
|
|||
width: 45%
|
||||
margin: 0 2.5%
|
||||
|
||||
#simulation-status-text
|
||||
display: inline
|
||||
margin-left: 10px
|
||||
|
||||
.name-col-cell
|
||||
max-width: 300px
|
||||
text-overflow: ellipsis
|
||||
|
|
108
app/styles/play/ladder/play_modal.sass
Normal file
108
app/styles/play/ladder/play_modal.sass
Normal file
|
@ -0,0 +1,108 @@
|
|||
#ladder-play-modal
|
||||
.tutorial-suggestion
|
||||
text-align: center
|
||||
font-size: 18px
|
||||
|
||||
.play-option
|
||||
margin-bottom: 15px
|
||||
width: 100%
|
||||
height: 100px
|
||||
overflow: hidden
|
||||
background: white
|
||||
border: 1px solid #333
|
||||
position: relative
|
||||
|
||||
-webkit-transition: opacity 0.3s ease-in-out
|
||||
-moz-transition: opacity 0.3s ease-in-out
|
||||
-ms-transition: opacity 0.3s ease-in-out
|
||||
-o-transition: opacity 0.3s ease-in-out
|
||||
transition: opacity 0.3s ease-in-out
|
||||
|
||||
opacity: 0.4
|
||||
|
||||
border-radius: 5px
|
||||
|
||||
.play-option:hover
|
||||
opacity: 1
|
||||
|
||||
.my-icon
|
||||
position: relative
|
||||
left: 0
|
||||
top: -10px
|
||||
|
||||
.opponent-icon
|
||||
position: relative
|
||||
float: right
|
||||
right: 0
|
||||
top: -10px
|
||||
-moz-transform: scaleX(-1)
|
||||
-o-transform: scaleX(-1)
|
||||
-webkit-transform: scaleX(-1)
|
||||
transform: scaleX(-1)
|
||||
filter: FlipH
|
||||
-ms-filter: "FlipH"
|
||||
|
||||
.name-label
|
||||
border-bottom: 20px solid lightslategray
|
||||
height: 0
|
||||
width: 40%
|
||||
position: absolute
|
||||
bottom: 0
|
||||
color: black
|
||||
font-weight: bold
|
||||
text-align: center
|
||||
|
||||
span
|
||||
position: relative
|
||||
top: 1px
|
||||
|
||||
.my-name
|
||||
border-right: 15px solid transparent
|
||||
left: 0
|
||||
span
|
||||
left: 3px
|
||||
|
||||
.opponent-name
|
||||
border-left: 15px solid transparent
|
||||
right: 0
|
||||
//text-align: right
|
||||
span
|
||||
right: 3px
|
||||
|
||||
.difficulty
|
||||
border-top: 25px solid darkgray
|
||||
border-left: 20px solid transparent
|
||||
border-right: 20px solid transparent
|
||||
height: 0
|
||||
width: 30%
|
||||
position: absolute
|
||||
left: 35%
|
||||
top: 0
|
||||
color: black
|
||||
text-align: center
|
||||
font-size: 18px
|
||||
font-weight: bold
|
||||
|
||||
span
|
||||
position: relative
|
||||
top: -25px
|
||||
|
||||
.easy-option .difficulty
|
||||
border-top: 25px solid limegreen
|
||||
|
||||
.medium-option .difficulty
|
||||
border-top: 25px solid darkorange
|
||||
|
||||
.hard-option .difficulty
|
||||
border-top: 25px solid black
|
||||
color: white
|
||||
|
||||
.vs
|
||||
position: absolute
|
||||
left: 40%
|
||||
right: 40%
|
||||
text-align: center
|
||||
top: 35px
|
||||
font-size: 40px
|
||||
font-weight: bolder
|
||||
color: black
|
|
@ -2,17 +2,11 @@ extends /templates/base
|
|||
block content
|
||||
|
||||
div#level-column
|
||||
h3= level.get('name')
|
||||
div#level-description
|
||||
!{description}
|
||||
|
||||
if !me.get('anonymous')
|
||||
//a(href="http://www.youtube.com/watch?v=IFvfZiJGDsw&list=HL1392928835&feature=mh_lolz").intro-button.btn.btn-primary.btn-lg Watch the Video
|
||||
|
||||
a(href="/play/level/brawlwood-tutorial").intro-button.btn.btn-primary.btn-lg Play the Tutorial
|
||||
h1= level.get('name')
|
||||
|
||||
//if !me.get('anonymous')
|
||||
// a(href="/play/level/brawlwood-tutorial").intro-button.btn.btn-primary.btn-lg Play the Tutorial
|
||||
|
||||
hr
|
||||
|
||||
if me.get('anonymous')
|
||||
div#must-log-in
|
||||
p
|
||||
|
@ -23,41 +17,37 @@ block content
|
|||
|
||||
else
|
||||
div#columns.row
|
||||
div.column.col-md-2
|
||||
for team in teams
|
||||
div.column.col-md-6
|
||||
a(href="/play/ladder/#{levelID}/team/#{team.id}", style="background-color: #{team.primaryColor}").play-button.btn.btn-danger.btn-block.btn-lg
|
||||
div.column.col-md-4
|
||||
a(style="background-color: #{team.primaryColor}", data-team=team.id).play-button.btn.btn-danger.btn-block.btn-lg
|
||||
span Play As
|
||||
span= team.name
|
||||
|
||||
table.table.table-bordered.table-condensed.table-hover
|
||||
//(style="background-color: #{team.bgColor}")
|
||||
tr
|
||||
th(colspan=3, style="color: #{team.primaryColor}")
|
||||
span= team.name
|
||||
span Leaderboard
|
||||
tr
|
||||
th Score
|
||||
th.name-col-cell Name
|
||||
th
|
||||
|
||||
for session in team.leaderboard.topPlayers.models
|
||||
- var myRow = session.get('creator') == me.id
|
||||
tr(class=myRow ? "success" : "")
|
||||
td.score-cell= session.get('totalScore').toFixed(2)
|
||||
td.name-col-cell= session.get('creatorName') || "Anonymous"
|
||||
td
|
||||
if(!myRow)
|
||||
a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}") Battle as #{team.otherTeam}!
|
||||
else
|
||||
a(href="/play/ladder/#{levelID}/team/#{team.id}") View your #{team.id} matches.
|
||||
|
||||
unless me.attributes.anonymous
|
||||
hr
|
||||
button.btn.btn-warning.btn-lg.highlight#simulate-button(style="margin-bottom:10px;") Simulate Games!
|
||||
p(id="simulation-status-text", style="display:inline; margin-left:10px;")
|
||||
if simulationStatus
|
||||
| #{simulationStatus}
|
||||
else
|
||||
| By simulating games you can get your game ranked faster!
|
||||
if me.isAdmin()
|
||||
button.btn.btn-danger.btn-lg.highlight#simulate-all-button(style="margin-bottom:10px; float: right;") RESET AND SIMULATE GAMES
|
||||
div.column.col-md-2
|
||||
|
||||
hr
|
||||
|
||||
ul.nav.nav-pills
|
||||
li.active
|
||||
a(href="#ladder", data-toggle="tab") Ladder
|
||||
li
|
||||
a(href="#my-matches", data-toggle="tab") My Matches
|
||||
li
|
||||
a(href="#simulate", data-toggle="tab") Simulate
|
||||
|
||||
div.tab-content
|
||||
.tab-pane.active.well#ladder
|
||||
#ladder-tab-view
|
||||
.tab-pane.well#my-matches
|
||||
#my-matches-tab-view
|
||||
.tab-pane.well#simulate
|
||||
p(id="simulation-status-text")
|
||||
if simulationStatus
|
||||
| #{simulationStatus}
|
||||
else
|
||||
| By simulating games you can get your game ranked faster!
|
||||
p
|
||||
button.btn.btn-warning.btn-lg.highlight#simulate-button() Simulate Games!
|
||||
if me.isAdmin()
|
||||
p
|
||||
button.btn.btn-danger.btn-lg.highlight#simulate-all-button() RESET AND SIMULATE GAMES
|
23
app/templates/play/ladder/ladder_tab.jade
Normal file
23
app/templates/play/ladder/ladder_tab.jade
Normal file
|
@ -0,0 +1,23 @@
|
|||
div#columns.row
|
||||
for team in teams
|
||||
div.column.col-md-6
|
||||
table.table.table-bordered.table-condensed.table-hover
|
||||
tr
|
||||
th(colspan=3, style="color: #{team.primaryColor}")
|
||||
span= team.name
|
||||
span Leaderboard
|
||||
tr
|
||||
th Score
|
||||
th.name-col-cell Name
|
||||
th
|
||||
|
||||
for session in team.leaderboard.topPlayers.models
|
||||
- var myRow = session.get('creator') == me.id
|
||||
tr(class=myRow ? "success" : "")
|
||||
td.score-cell= session.get('totalScore').toFixed(2)
|
||||
td.name-col-cell= session.get('creatorName') || "Anonymous"
|
||||
td
|
||||
if(!myRow)
|
||||
a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}") Battle as #{team.otherTeam}!
|
||||
else
|
||||
a(href="/play/ladder/#{levelID}/team/#{team.id}") View your #{team.id} matches.
|
53
app/templates/play/ladder/my_matches_tab.jade
Normal file
53
app/templates/play/ladder/my_matches_tab.jade
Normal file
|
@ -0,0 +1,53 @@
|
|||
//if matches.length
|
||||
// p#your-score
|
||||
// span Your Current Score:
|
||||
// span
|
||||
// strong= score
|
||||
|
||||
div#columns.row
|
||||
for team in teams
|
||||
div.matches-column.col-md-6
|
||||
table.table.table-bordered.table-condensed
|
||||
|
||||
tr
|
||||
th(colspan=4, style="color: #{team.primaryColor}")
|
||||
span Your
|
||||
span
|
||||
span= team.name
|
||||
span
|
||||
span Matches
|
||||
|
||||
if team.session
|
||||
button.btn.btn-sm.btn-warning.pull-right.rank-button(data-session-id=team.session.id)
|
||||
span.unavailable.hidden No New Code to Rank
|
||||
span.rank.hidden Rank My Game!
|
||||
span.ranking.hidden Submitting...
|
||||
span.ranked.hidden Submitted for Ranking
|
||||
span.failed.hidden Failed to Rank
|
||||
|
||||
tr
|
||||
th Result
|
||||
th Opponent
|
||||
th When
|
||||
th
|
||||
for match in team.matches
|
||||
tr
|
||||
td.state-cell
|
||||
if match.state === 'win'
|
||||
span.win Win
|
||||
if match.state === 'loss'
|
||||
span.loss Loss
|
||||
if match.state === 'tie'
|
||||
span.tie Tie
|
||||
td.name-cell= match.opponentName || "Anonymous"
|
||||
td.time-cell= match.when
|
||||
td.battle-cell
|
||||
- var text = match.state === 'win' ? 'Watch your victory' : 'Defeat the ' + team.otherTeam
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{match.sessionID}")= text
|
||||
|
||||
if !team.matches.length
|
||||
tr
|
||||
td(colspan=4).alert.alert-warning
|
||||
| No ranked matches for this team!
|
||||
| Play against some competitors and then come back here to get your game ranked.
|
||||
|
64
app/templates/play/ladder/play_modal.jade
Normal file
64
app/templates/play/ladder/play_modal.jade
Normal file
|
@ -0,0 +1,64 @@
|
|||
extends /templates/modal/modal_base
|
||||
|
||||
block modal-header-content
|
||||
h3 Choose an Opponent
|
||||
|
||||
block modal-body-content
|
||||
|
||||
p.tutorial-suggestion
|
||||
span Not sure what's going on?
|
||||
|
|
||||
a(href="/play/level/brawlwood-tutorial") Play the tutorial first.
|
||||
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}")
|
||||
div.play-option
|
||||
img(src=portraitSRC).my-icon
|
||||
img(src=portraitSRC).opponent-icon
|
||||
div.my-name.name-label
|
||||
span= myName
|
||||
div.opponent-name.name-label
|
||||
span Simple AI
|
||||
div.difficulty
|
||||
span Warmup
|
||||
div.vs VS
|
||||
|
||||
if challengers.easy
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.easy.sessionID}")
|
||||
div.play-option.easy-option
|
||||
img(src=portraitSRC).my-icon
|
||||
img(src=portraitSRC).opponent-icon
|
||||
div.my-name.name-label
|
||||
span= myName
|
||||
div.opponent-name.name-label
|
||||
span= challengers.easy.opponentName
|
||||
div.difficulty
|
||||
span Easy
|
||||
div.vs VS
|
||||
|
||||
if challengers.medium
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.medium.sessionID}")
|
||||
div.play-option.medium-option
|
||||
img(src=portraitSRC).my-icon
|
||||
img(src=portraitSRC).opponent-icon
|
||||
div.my-name.name-label
|
||||
span= myName
|
||||
div.opponent-name.name-label
|
||||
span= challengers.medium.opponentName
|
||||
div.difficulty
|
||||
span Medium
|
||||
div.vs VS
|
||||
|
||||
if challengers.hard
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.hard.sessionID}")
|
||||
div.play-option.hard-option
|
||||
img(src=portraitSRC).my-icon
|
||||
img(src=portraitSRC).opponent-icon
|
||||
div.my-name.name-label
|
||||
span= myName
|
||||
div.opponent-name.name-label
|
||||
span= challengers.hard.opponentName
|
||||
div.difficulty
|
||||
span Hard
|
||||
div.vs VS
|
||||
|
||||
block modal-footer
|
|
@ -28,12 +28,8 @@ module.exports = class HomeView extends View
|
|||
@$el.find('.modal').on 'shown.bs.modal', ->
|
||||
$('input:visible:first', @).focus()
|
||||
|
||||
wizOriginal = "52a00d55cf1818f2be00000b"
|
||||
url = "/db/thang_type/#{wizOriginal}/version"
|
||||
@wizardType = new ThangType()
|
||||
@wizardType.url = -> url
|
||||
@wizardType.fetch()
|
||||
@wizardType.once 'sync', @initCanvas
|
||||
@wizardType = ThangType.wizardType
|
||||
if @wizardType.loaded then @initCanvas else @wizardType.once 'sync', @initCanvas, @
|
||||
|
||||
# Try to find latest level and set "Play" link to go to that level
|
||||
if localStorage?
|
||||
|
@ -48,7 +44,7 @@ module.exports = class HomeView extends View
|
|||
else
|
||||
console.log("TODO: Insert here code to get latest level played from the database. If this can't be found, we just let the user play the first level.")
|
||||
|
||||
initCanvas: =>
|
||||
initCanvas: ->
|
||||
@stage = new createjs.Stage($('#beginner-campaign canvas', @$el)[0])
|
||||
@createWizard()
|
||||
|
||||
|
|
97
app/views/play/ladder/ladder_tab.coffee
Normal file
97
app/views/play/ladder/ladder_tab.coffee
Normal file
|
@ -0,0 +1,97 @@
|
|||
CocoView = require 'views/kinds/CocoView'
|
||||
Level = require 'models/Level'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
CocoCollection = require 'models/CocoCollection'
|
||||
LeaderboardCollection = require 'collections/LeaderboardCollection'
|
||||
{teamDataFromLevel} = require './utils'
|
||||
|
||||
HIGHEST_SCORE = 1000000
|
||||
|
||||
class LevelSessionsCollection extends CocoCollection
|
||||
url: ''
|
||||
model: LevelSession
|
||||
|
||||
constructor: (levelID) ->
|
||||
super()
|
||||
@url = "/db/level/#{levelID}/all_sessions"
|
||||
|
||||
module.exports = class LadderView extends CocoView
|
||||
id: 'ladder-tab-view'
|
||||
template: require 'templates/play/ladder/ladder_tab'
|
||||
startsLoading: true
|
||||
|
||||
constructor: (options, @level, @sessions) ->
|
||||
super(options)
|
||||
@teams = teamDataFromLevel @level
|
||||
@leaderboards = {}
|
||||
for team in @teams
|
||||
# teamSession = _.find @sessions.models, (session) -> session.get('team') is team.id
|
||||
teamSession = null
|
||||
# console.log "Team session: #{JSON.stringify teamSession}"
|
||||
@leaderboards[team.id] = new LeaderboardData(@level, team.id, teamSession)
|
||||
@leaderboards[team.id].once 'sync', @onLeaderboardLoaded, @
|
||||
|
||||
onChallengersLoaded: -> @renderMaybe()
|
||||
onLeaderboardLoaded: -> @renderMaybe()
|
||||
|
||||
renderMaybe: ->
|
||||
leaderboardModels = _.values(@leaderboards)
|
||||
return unless _.every leaderboardModels, (loader) -> loader.loaded
|
||||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
ctx.link = "/play/level/#{@level.get('name')}"
|
||||
ctx.teams = @teams
|
||||
team.leaderboard = @leaderboards[team.id] for team in @teams
|
||||
ctx.levelID = @levelID
|
||||
ctx
|
||||
|
||||
class LeaderboardData
|
||||
constructor: (@level, @team, @session) ->
|
||||
_.extend @, Backbone.Events
|
||||
@topPlayers = new LeaderboardCollection(@level, {order:-1, scoreOffset: HIGHEST_SCORE, team: @team, limit: if @session then 10 else 20})
|
||||
@topPlayers.fetch()
|
||||
@topPlayers.comparator = (model) ->
|
||||
return -model.get('totalScore')
|
||||
@topPlayers.sort()
|
||||
|
||||
@topPlayers.once 'sync', @leaderboardPartLoaded, @
|
||||
|
||||
# if @session
|
||||
# score = @session.get('totalScore') or 25
|
||||
# @playersAbove = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 4, team: @team})
|
||||
# @playersAbove.fetch()
|
||||
# @playersAbove.once 'sync', @leaderboardPartLoaded, @
|
||||
# @playersBelow = new LeaderboardCollection(@level, {order:-1, scoreOffset: score, limit: 4, team: @team})
|
||||
# @playersBelow.fetch()
|
||||
# @playersBelow.once 'sync', @leaderboardPartLoaded, @
|
||||
|
||||
leaderboardPartLoaded: ->
|
||||
if @session
|
||||
if @topPlayers.loaded # and @playersAbove.loaded and @playersBelow.loaded
|
||||
@loaded = true
|
||||
@fetchNames()
|
||||
else
|
||||
@loaded = true
|
||||
@fetchNames()
|
||||
|
||||
fetchNames: ->
|
||||
sessionCollections = [@topPlayers, @playersAbove, @playersBelow]
|
||||
sessionCollections = (s for s in sessionCollections when s)
|
||||
ids = []
|
||||
for collection in sessionCollections
|
||||
ids.push model.get('creator') for model in collection.models
|
||||
|
||||
success = (nameMap) =>
|
||||
for collection in sessionCollections
|
||||
session.set('creatorName', nameMap[session.get('creator')]) for session in collection.models
|
||||
@trigger 'sync'
|
||||
|
||||
$.ajax('/db/user/-/names', {
|
||||
data: {ids: ids}
|
||||
type: 'POST'
|
||||
success: success
|
||||
})
|
104
app/views/play/ladder/my_matches_tab.coffee
Normal file
104
app/views/play/ladder/my_matches_tab.coffee
Normal file
|
@ -0,0 +1,104 @@
|
|||
CocoView = require 'views/kinds/CocoView'
|
||||
Level = require 'models/Level'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
LeaderboardCollection = require 'collections/LeaderboardCollection'
|
||||
{teamDataFromLevel} = require './utils'
|
||||
|
||||
module.exports = class MyMatchesTabView extends CocoView
|
||||
id: 'my-matches-tab-view'
|
||||
template: require 'templates/play/ladder/my_matches_tab'
|
||||
startsLoading: true
|
||||
|
||||
events:
|
||||
'click .rank-button': 'rankSession'
|
||||
|
||||
constructor: (options, @level, @sessions) ->
|
||||
super(options)
|
||||
@teams = teamDataFromLevel @level
|
||||
@nameMap = {}
|
||||
@loadNames()
|
||||
|
||||
loadNames: ->
|
||||
ids = []
|
||||
for session in @sessions.models
|
||||
ids.push match.opponents[0].userID for match in session.get('matches') or []
|
||||
|
||||
success = (@nameMap) =>
|
||||
for session in @sessions.models
|
||||
for match in session.get('matches') or []
|
||||
opponent = match.opponents[0]
|
||||
opponent.userName = @nameMap[opponent.userID]
|
||||
@finishRendering()
|
||||
|
||||
$.ajax('/db/user/-/names', {
|
||||
data: {ids: ids}
|
||||
type: 'POST'
|
||||
success: success
|
||||
})
|
||||
|
||||
finishRendering: ->
|
||||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
ctx.levelID = @level.get('slug') or @level.id
|
||||
ctx.teams = @teams
|
||||
|
||||
convertMatch = (match) =>
|
||||
opponent = match.opponents[0]
|
||||
state = 'win'
|
||||
state = 'loss' if match.metrics.rank > opponent.metrics.rank
|
||||
state = 'tie' if match.metrics.rank is opponent.metrics.rank
|
||||
{
|
||||
state: state
|
||||
opponentName: @nameMap[opponent.userID]
|
||||
opponentID: opponent.userID
|
||||
when: moment(match.date).fromNow()
|
||||
sessionID: opponent.sessionID
|
||||
}
|
||||
|
||||
for team in @teams
|
||||
team.session = (s for s in @sessions.models when s.get('team') is team.id)[0]
|
||||
team.readyToRank = @readyToRank(team.session)
|
||||
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)
|
||||
|
||||
ctx
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@$el.find('.rank-button').each (i, el) =>
|
||||
button = $(el)
|
||||
sessionID = button.data('session-id')
|
||||
session = _.find @sessions.models, { id: sessionID }
|
||||
@setRankingButtonText button, if @readyToRank(session) then 'rank' else 'unavailable'
|
||||
|
||||
readyToRank: (session) ->
|
||||
c1 = session?.get('code')
|
||||
c2 = session?.get('submittedCode')
|
||||
c1 and not _.isEqual(c1, c2)
|
||||
|
||||
rankSession: (e) ->
|
||||
button = $(e.target).closest('.rank-button')
|
||||
sessionID = button.data('session-id')
|
||||
session = _.find @sessions.models, { id: sessionID }
|
||||
return unless @readyToRank(session)
|
||||
|
||||
@setRankingButtonText(button, 'ranking')
|
||||
success = => @setRankingButtonText(button, 'ranked')
|
||||
failure = => @setRankingButtonText(button, 'failed')
|
||||
|
||||
$.ajax '/queue/scoring', {
|
||||
type: 'POST'
|
||||
data: { session: sessionID }
|
||||
success: success
|
||||
failure: failure
|
||||
}
|
||||
|
||||
setRankingButtonText: (rankButton, spanClass) ->
|
||||
rankButton.find('span').addClass('hidden')
|
||||
rankButton.find(".#{spanClass}").removeClass('hidden')
|
||||
rankButton.toggleClass 'disabled', spanClass isnt 'rank'
|
143
app/views/play/ladder/play_modal.coffee
Normal file
143
app/views/play/ladder/play_modal.coffee
Normal file
|
@ -0,0 +1,143 @@
|
|||
View = require 'views/kinds/ModalView'
|
||||
template = require 'templates/play/ladder/play_modal'
|
||||
ThangType = require 'models/ThangType'
|
||||
{me} = require 'lib/auth'
|
||||
LeaderboardCollection = require 'collections/LeaderboardCollection'
|
||||
|
||||
module.exports = class LadderPlayModal extends View
|
||||
id: "ladder-play-modal"
|
||||
template: template
|
||||
closeButton: true
|
||||
startsLoading = true
|
||||
|
||||
constructor: (options, @level, @session, @team) ->
|
||||
super(options)
|
||||
@nameMap = {}
|
||||
@otherTeam = if team is 'ogres' then 'humans' else 'ogres'
|
||||
@startLoadingChallengersMaybe()
|
||||
@wizardType = ThangType.wizardType
|
||||
|
||||
# PART 1: Load challengers from the db unless some are in the matches
|
||||
|
||||
startLoadingChallengersMaybe: ->
|
||||
matches = @session?.get('matches')
|
||||
if matches?.length then @loadNames() else @loadChallengers()
|
||||
|
||||
loadChallengers: ->
|
||||
@challengers = new ChallengersData(@level, @team, @otherTeam, @session)
|
||||
@challengers.on 'sync', @loadNames, @
|
||||
|
||||
# PART 2: Loading the names of the other users
|
||||
|
||||
loadNames: ->
|
||||
@challengers = @getChallengers()
|
||||
ids = (challenger.opponentID for challenger in _.values @challengers)
|
||||
|
||||
success = (@nameMap) =>
|
||||
for challenger in _.values(@challengers)
|
||||
challenger.opponentName = @nameMap[challenger.opponentID] or 'Anoner'
|
||||
@checkWizardLoaded()
|
||||
|
||||
$.ajax('/db/user/-/names', {
|
||||
data: {ids: ids}
|
||||
type: 'POST'
|
||||
success: success
|
||||
})
|
||||
|
||||
# PART 3: Make sure wizard is loaded
|
||||
|
||||
checkWizardLoaded: ->
|
||||
if @wizardType.loaded then @finishRendering() else @wizardType.once 'sync', @finishRendering, @
|
||||
|
||||
# PART 4: Render
|
||||
|
||||
finishRendering: ->
|
||||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
ctx.levelID = @level.get('slug') or @level.id
|
||||
ctx.teamName = _.string.titleize @team
|
||||
ctx.teamID = @team
|
||||
ctx.otherTeamID = @otherTeam
|
||||
ctx.challengers = if not @startsLoading then @challengers else {}
|
||||
ctx.portraitSRC = @wizardType.getPortraitSource()
|
||||
ctx.myName = me.get('name') || 'Newcomer'
|
||||
ctx
|
||||
|
||||
# Choosing challengers
|
||||
|
||||
getChallengers: ->
|
||||
# make an object of challengers to everything needed to link to them
|
||||
challengers = {}
|
||||
if @challengers
|
||||
easyInfo = @challengeInfoFromSession(@challengers.easyPlayer.models[0])
|
||||
mediumInfo = @challengeInfoFromSession(@challengers.mediumPlayer.models[0])
|
||||
hardInfo = @challengeInfoFromSession(@challengers.hardPlayer.models[0])
|
||||
else
|
||||
matches = @session.get('matches')
|
||||
won = (m for m in matches when m.metrics.rank < m.opponents[0].metrics.rank)
|
||||
lost = (m for m in matches when m.metrics.rank > m.opponents[0].metrics.rank)
|
||||
tied = (m for m in matches when m.metrics.rank is m.opponents[0].metrics.rank)
|
||||
easyInfo = @challengeInfoFromMatches(won)
|
||||
mediumInfo = @challengeInfoFromMatches(tied)
|
||||
hardInfo = @challengeInfoFromMatches(lost)
|
||||
@addChallenger easyInfo, challengers, 'easy'
|
||||
@addChallenger mediumInfo, challengers, 'medium'
|
||||
@addChallenger hardInfo, challengers, 'hard'
|
||||
challengers
|
||||
|
||||
addChallenger: (info, challengers, title) ->
|
||||
# check for duplicates first
|
||||
return unless info
|
||||
for key, value of challengers
|
||||
return if value.sessionID is info.sessionID
|
||||
challengers[title] = info
|
||||
|
||||
challengeInfoFromSession: (session) ->
|
||||
# given a model from the db, return info needed for a link to the match
|
||||
return unless session
|
||||
return {
|
||||
sessionID: session.id
|
||||
opponentID: session.get('creator')
|
||||
}
|
||||
|
||||
challengeInfoFromMatches: (matches) ->
|
||||
return unless matches?.length
|
||||
match = _.sample matches
|
||||
opponent = match.opponents[0]
|
||||
return {
|
||||
sessionID: opponent.sessionID
|
||||
opponentID: opponent.userID
|
||||
}
|
||||
|
||||
|
||||
class ChallengersData
|
||||
constructor: (@level, @team, @otherTeam, @session) ->
|
||||
_.extend @, Backbone.Events
|
||||
score = @session?.get('totalScore') or 25
|
||||
@easyPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score - 5, limit: 1, team: @otherTeam})
|
||||
@easyPlayer.fetch()
|
||||
@easyPlayer.once 'sync', @challengerLoaded, @
|
||||
@mediumPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 1, team: @otherTeam})
|
||||
@mediumPlayer.fetch()
|
||||
@mediumPlayer.once 'sync', @challengerLoaded, @
|
||||
@hardPlayer = new LeaderboardCollection(@level, {order:-1, scoreOffset: score + 5, limit: 1, team: @otherTeam})
|
||||
@hardPlayer.fetch()
|
||||
@hardPlayer.once 'sync', @challengerLoaded, @
|
||||
console.log 'fetching challengers yes'
|
||||
|
||||
challengerLoaded: ->
|
||||
console.log 'challenger loaded'
|
||||
if @allLoaded()
|
||||
@loaded = true
|
||||
@trigger 'sync'
|
||||
|
||||
playerIDs: ->
|
||||
collections = [@easyPlayer, @mediumPlayer, @hardPlayer]
|
||||
(c.models[0].get('creator') for c in collections when c?.models[0])
|
||||
|
||||
allLoaded: ->
|
||||
_.all [@easyPlayer.loaded, @mediumPlayer.loaded, @hardPlayer.loaded]
|
22
app/views/play/ladder/utils.coffee
Normal file
22
app/views/play/ladder/utils.coffee
Normal file
|
@ -0,0 +1,22 @@
|
|||
{hslToHex} = require 'lib/utils'
|
||||
|
||||
module.exports.teamDataFromLevel = (level) ->
|
||||
alliedSystem = _.find level.get('systems'), (value) -> value.config?.teams?
|
||||
teamNames = (teamName for teamName, teamConfig of alliedSystem.config.teams when teamConfig.playable)
|
||||
teamConfigs = alliedSystem.config.teams
|
||||
|
||||
teams = []
|
||||
for team in teamNames or []
|
||||
otherTeam = if team is 'ogres' then 'humans' else 'ogres'
|
||||
color = teamConfigs[team].color
|
||||
bgColor = hslToHex([color.hue, color.saturation, color.lightness + (1 - color.lightness) * 0.5])
|
||||
primaryColor = hslToHex([color.hue, 0.5, 0.5])
|
||||
teams.push({
|
||||
id: team
|
||||
name: _.string.titleize(team)
|
||||
otherTeam: otherTeam
|
||||
bgColor: bgColor
|
||||
primaryColor: primaryColor
|
||||
})
|
||||
|
||||
teams
|
|
@ -3,8 +3,11 @@ Level = require 'models/Level'
|
|||
Simulator = require 'lib/simulator/Simulator'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
CocoCollection = require 'models/CocoCollection'
|
||||
LeaderboardCollection = require 'collections/LeaderboardCollection'
|
||||
{hslToHex} = require 'lib/utils'
|
||||
{teamDataFromLevel} = require './ladder/utils'
|
||||
|
||||
LadderTabView = require './ladder/ladder_tab'
|
||||
MyMatchesTabView = require './ladder/my_matches_tab'
|
||||
LadderPlayModal = require './ladder/play_modal'
|
||||
|
||||
HIGHEST_SCORE = 1000000
|
||||
|
||||
|
@ -14,7 +17,7 @@ class LevelSessionsCollection extends CocoCollection
|
|||
|
||||
constructor: (levelID) ->
|
||||
super()
|
||||
@url = "/db/level/#{levelID}/all_sessions"
|
||||
@url = "/db/level/#{levelID}/my_sessions"
|
||||
|
||||
module.exports = class LadderView extends RootView
|
||||
id: 'ladder-view'
|
||||
|
@ -24,9 +27,48 @@ module.exports = class LadderView extends RootView
|
|||
events:
|
||||
'click #simulate-button': 'onSimulateButtonClick'
|
||||
'click #simulate-all-button': 'onSimulateAllButtonClick'
|
||||
'click .play-button': 'onClickPlayButton'
|
||||
|
||||
constructor: (options, @levelID) ->
|
||||
super(options)
|
||||
@level = new Level(_id:@levelID)
|
||||
@level.fetch()
|
||||
@level.once 'sync', @onLevelLoaded, @
|
||||
@sessions = new LevelSessionsCollection(levelID)
|
||||
@sessions.fetch({})
|
||||
@sessions.once 'sync', @onMySessionsLoaded, @
|
||||
@simulator = new Simulator()
|
||||
@simulator.on 'statusUpdate', @updateSimulationStatus, @
|
||||
@teams = []
|
||||
|
||||
onLevelLoaded: -> @renderMaybe()
|
||||
onMySessionsLoaded: -> @renderMaybe()
|
||||
|
||||
renderMaybe: ->
|
||||
return unless @level.loaded and @sessions.loaded
|
||||
@teams = teamDataFromLevel @level
|
||||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
ctx.link = "/play/level/#{@level.get('name')}"
|
||||
ctx.simulationStatus = @simulationStatus
|
||||
ctx.teams = @teams
|
||||
ctx.levelID = @levelID
|
||||
ctx
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
return if @startsLoading
|
||||
@insertSubView(@ladderTab = new LadderTabView({}, @level, @sessions))
|
||||
@insertSubView(@myMatchesTab = new MyMatchesTabView({}, @level, @sessions))
|
||||
|
||||
# Simulations
|
||||
|
||||
onSimulateAllButtonClick: (e) ->
|
||||
submitIDs = _.pluck @leaderboards[@teams[0]].topPlayers.models, "id"
|
||||
submitIDs = _.pluck @leaderboards[@teams[0].id].topPlayers.models, "id"
|
||||
for ID in submitIDs
|
||||
$.ajax
|
||||
url: '/queue/scoring'
|
||||
|
@ -58,140 +100,9 @@ module.exports = class LadderView extends RootView
|
|||
console.log "There was a problem with the named simulation status: #{e}"
|
||||
$("#simulation-status-text").text @simulationStatus
|
||||
|
||||
|
||||
constructor: (options, @levelID) ->
|
||||
super(options)
|
||||
@level = new Level(_id:@levelID)
|
||||
@level.fetch()
|
||||
@level.once 'sync', @onLevelLoaded, @
|
||||
@simulator = new Simulator()
|
||||
@simulator.on 'statusUpdate', @updateSimulationStatus, @
|
||||
|
||||
# @sessions = new LevelSessionsCollection(levelID)
|
||||
# @sessions.fetch({})
|
||||
# @sessions.once 'sync', @onMySessionsLoaded, @
|
||||
|
||||
onLevelLoaded: -> @startLoadingPhaseTwoMaybe()
|
||||
onMySessionsLoaded: ->
|
||||
@startLoadingPhaseTwoMaybe()
|
||||
|
||||
startLoadingPhaseTwoMaybe: ->
|
||||
return unless @level.loaded # and @sessions.loaded
|
||||
@loadPhaseTwo()
|
||||
|
||||
loadPhaseTwo: ->
|
||||
alliedSystem = _.find @level.get('systems'), (value) -> value.config?.teams?
|
||||
teams = []
|
||||
for teamName, teamConfig of alliedSystem.config.teams
|
||||
continue unless teamConfig.playable
|
||||
teams.push teamName
|
||||
@teams = teams
|
||||
@teamConfigs = alliedSystem.config.teams
|
||||
|
||||
@leaderboards = {}
|
||||
@challengers = {}
|
||||
for team in teams
|
||||
# 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, @
|
||||
|
||||
onChallengersLoaded: -> @renderMaybe()
|
||||
onLeaderboardLoaded: -> @renderMaybe()
|
||||
|
||||
renderMaybe: ->
|
||||
loaders = _.values(@leaderboards) # .concat(_.values(@challengers))
|
||||
return unless _.every loaders, (loader) -> loader.loaded
|
||||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
description = @level.get('description')
|
||||
ctx.description = if description then marked(description) else ''
|
||||
ctx.link = "/play/level/#{@level.get('name')}"
|
||||
ctx.simulationStatus = @simulationStatus
|
||||
ctx.teams = []
|
||||
ctx.levelID = @levelID
|
||||
for team in @teams or []
|
||||
otherTeam = if team is 'ogres' then 'humans' else 'ogres'
|
||||
color = @teamConfigs[team].color
|
||||
bgColor = hslToHex([color.hue, color.saturation, color.lightness + (1 - color.lightness) * 0.5])
|
||||
primaryColor = hslToHex([color.hue, 0.5, 0.5])
|
||||
ctx.teams.push({
|
||||
id: team
|
||||
name: _.string.titleize(team)
|
||||
leaderboard: @leaderboards[team]
|
||||
otherTeam: otherTeam
|
||||
bgColor: bgColor
|
||||
primaryColor: primaryColor
|
||||
})
|
||||
ctx
|
||||
|
||||
class LeaderboardData
|
||||
constructor: (@level, @team, @session) ->
|
||||
_.extend @, Backbone.Events
|
||||
@topPlayers = new LeaderboardCollection(@level, {order:-1, scoreOffset: HIGHEST_SCORE, team: @team, limit: if @session then 10 else 20})
|
||||
@topPlayers.fetch()
|
||||
@topPlayers.comparator = (model) ->
|
||||
return -model.get('totalScore')
|
||||
@topPlayers.sort()
|
||||
|
||||
@topPlayers.once 'sync', @leaderboardPartLoaded, @
|
||||
|
||||
# if @session
|
||||
# score = @session.get('totalScore') or 25
|
||||
# @playersAbove = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 4, team: @team})
|
||||
# @playersAbove.fetch()
|
||||
# @playersAbove.once 'sync', @leaderboardPartLoaded, @
|
||||
# @playersBelow = new LeaderboardCollection(@level, {order:-1, scoreOffset: score, limit: 4, team: @team})
|
||||
# @playersBelow.fetch()
|
||||
# @playersBelow.once 'sync', @leaderboardPartLoaded, @
|
||||
|
||||
leaderboardPartLoaded: ->
|
||||
if @session
|
||||
if @topPlayers.loaded # and @playersAbove.loaded and @playersBelow.loaded
|
||||
@loaded = true
|
||||
@fetchNames()
|
||||
else
|
||||
@loaded = true
|
||||
@fetchNames()
|
||||
|
||||
fetchNames: ->
|
||||
sessionCollections = [@topPlayers, @playersAbove, @playersBelow]
|
||||
sessionCollections = (s for s in sessionCollections when s)
|
||||
ids = []
|
||||
for collection in sessionCollections
|
||||
ids.push model.get('creator') for model in collection.models
|
||||
|
||||
success = (nameMap) =>
|
||||
for collection in sessionCollections
|
||||
session.set('creatorName', nameMap[session.get('creator')]) for session in collection.models
|
||||
@trigger 'sync'
|
||||
|
||||
$.ajax('/db/user/-/names', {
|
||||
data: {ids: ids}
|
||||
type: 'POST'
|
||||
success: success
|
||||
})
|
||||
|
||||
class ChallengersData
|
||||
constructor: (@level, @team, @session) ->
|
||||
_.extend @, Backbone.Events
|
||||
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, @
|
||||
@mediumPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 1, team: @team})
|
||||
@mediumPlayer.fetch()
|
||||
@mediumPlayer.once 'sync', @challengerLoaded, @
|
||||
@hardPlayer = new LeaderboardCollection(@level, {order:-1, scoreOffset: score + 5, limit: 1, team: @team})
|
||||
@hardPlayer.fetch()
|
||||
@hardPlayer.once 'sync', @challengerLoaded, @
|
||||
|
||||
challengerLoaded: ->
|
||||
if @easyPlayer.loaded and @mediumPlayer.loaded and @hardPlayer.loaded
|
||||
@loaded = true
|
||||
@trigger 'sync'
|
||||
onClickPlayButton: (e) ->
|
||||
button = $(e.target).closest('.play-button')
|
||||
teamID = button.data('team')
|
||||
session = (s for s in @sessions.models when s.get('team') is teamID)[0]
|
||||
modal = new LadderPlayModal({}, @level, session, teamID)
|
||||
@openModalView modal
|
|
@ -27,7 +27,7 @@ LevelHandler = class LevelHandler extends Handler
|
|||
getByRelationship: (req, res, args...) ->
|
||||
return @getSession(req, res, args[0]) if args[1] is 'session'
|
||||
return @getLeaderboard(req, res, args[0]) if args[1] is 'leaderboard'
|
||||
return @getAllSessions(req, res, args[0]) if args[1] is 'all_sessions'
|
||||
return @getMySessions(req, res, args[0]) if args[1] is 'my_sessions'
|
||||
return @getFeedback(req, res, args[0]) if args[1] is 'feedback'
|
||||
return @sendNotFoundError(res)
|
||||
|
||||
|
@ -86,26 +86,15 @@ 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.
|
||||
|
||||
getAllSessions: (req, res, id) ->
|
||||
getMySessions: (req, res, id) ->
|
||||
@fetchLevelByIDAndHandleErrors id, req, res, (err, level) =>
|
||||
sessionQuery =
|
||||
level:
|
||||
original: level.original.toString()
|
||||
majorVersion: level.version.major
|
||||
submitted: true
|
||||
|
||||
propertiesToReturn = [
|
||||
'_id'
|
||||
'totalScore'
|
||||
'submitted'
|
||||
'team'
|
||||
'creatorName'
|
||||
]
|
||||
|
||||
query = Session
|
||||
.find(sessionQuery)
|
||||
.select(propertiesToReturn.join ' ')
|
||||
creator: req.user._id+''
|
||||
|
||||
query = Session.find(sessionQuery)
|
||||
query.exec (err, results) =>
|
||||
if err then @sendDatabaseError(res, err) else @sendSuccess res, results
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue