mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-14 07:00:01 -04:00
Working on the ladder and team views.
This commit is contained in:
parent
dcb6c49fd8
commit
452a43cae9
9 changed files with 346 additions and 77 deletions
11
app/collections/LeaderboardCollection.coffee
Normal file
11
app/collections/LeaderboardCollection.coffee
Normal file
|
@ -0,0 +1,11 @@
|
|||
CocoCollection = require 'models/CocoCollection'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
|
||||
module.exports = class LeaderboardCollection extends CocoCollection
|
||||
url: ''
|
||||
model: LevelSession
|
||||
|
||||
constructor: (level, options) ->
|
||||
super()
|
||||
options ?= {}
|
||||
@url = "/db/level/#{level.get('original')}.#{level.get('version').major}/leaderboard?#{$.param(options)}"
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
gplusClientID = "800329290710-j9sivplv2gpcdgkrsis9rff3o417mlfa.apps.googleusercontent.com"
|
||||
|
||||
go = (path) -> -> @routeDirectly path, arguments
|
||||
|
||||
module.exports = class CocoRouter extends Backbone.Router
|
||||
subscribe: ->
|
||||
Backbone.Mediator.subscribe 'gapi-loaded', @onGPlusAPILoaded, @
|
||||
|
@ -15,28 +17,21 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
|
||||
# editor views tend to have the same general structure
|
||||
'editor/:model(/:slug_or_id)(/:subview)': 'editorModelView'
|
||||
|
||||
# Experimenting with direct links
|
||||
'play/ladder/:levelID/team/:team': go('play/ladder/team_view')
|
||||
|
||||
# db and file urls call the server directly
|
||||
'db/*path': 'routeToServer'
|
||||
'file/*path': 'routeToServer'
|
||||
|
||||
'play/level/:levelID/leaderboard/:teamID/:startRank/:endRank': 'getPaginatedLevelRank'
|
||||
'play/level/:levelID/player/:playerID': 'getPlayerLevelInfo'
|
||||
|
||||
# most go through here
|
||||
'*name': 'general'
|
||||
|
||||
home: -> @openRoute('home')
|
||||
general: (name) ->
|
||||
@openRoute(name)
|
||||
|
||||
getPaginatedLevelRank: (levelID,teamID,startRank,endRank) ->
|
||||
return
|
||||
|
||||
getPlayerLevelInfo: (levelID,playerID) ->
|
||||
return
|
||||
|
||||
|
||||
|
||||
editorModelView: (modelName, slugOrId, subview) ->
|
||||
modulePrefix = "views/editor/#{modelName}/"
|
||||
suffix = subview or (if slugOrId then 'edit' else 'home')
|
||||
|
@ -97,6 +92,14 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
view = @getView(route)
|
||||
@cache[route] = view unless view and view.cache is false
|
||||
return view
|
||||
|
||||
routeDirectly: (path, args) ->
|
||||
path = "views/#{path}"
|
||||
ViewClass = @tryToLoadModule path
|
||||
return @showNotFound() if not ViewClass
|
||||
view = new ViewClass({}, args...) # options, then any path fragment args
|
||||
view.render()
|
||||
@openView(view)
|
||||
|
||||
getView: (route, suffix='_view') ->
|
||||
# iteratively breaks down the url pieces looking for the view
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#ladder-view
|
||||
color: black
|
||||
.score-cell
|
||||
width: 50px
|
||||
width: 50px
|
||||
|
||||
.play-button
|
||||
margin-bottom: 10px
|
||||
|
|
5
app/styles/play/ladder/team.sass
Normal file
5
app/styles/play/ladder/team.sass
Normal file
|
@ -0,0 +1,5 @@
|
|||
#ladder-team-view
|
||||
#competitors-column .well
|
||||
font-size: 16px
|
||||
font-weight: bold
|
||||
padding: 7px
|
|
@ -5,34 +5,30 @@ block content
|
|||
h3= level.get('name')
|
||||
div#level-description
|
||||
!{description}
|
||||
|
||||
hr
|
||||
div#columns.row
|
||||
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
|
||||
span Play
|
||||
span= team.name
|
||||
|
||||
table.table.table-bordered.table-condensed
|
||||
tr
|
||||
th(colspan=3, style="color: #{team.primaryColor}")
|
||||
span= team.name
|
||||
span Leaderboard
|
||||
|
||||
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= session.get('creatorName')
|
||||
td
|
||||
if(!myRow)
|
||||
a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}") COMPETE
|
||||
|
||||
if me.isAdmin()
|
||||
button.btn.btn-warning.btn-lg.highlight#simulate-button(style="margin-bottom:10px") Simulate all games
|
||||
|
||||
div#leaderboard-column
|
||||
ul.nav.nav-pills
|
||||
for team in teams
|
||||
li
|
||||
a(href="#team-#{team.id}", data-toggle="tab")= team.name
|
||||
|
||||
.tab-content
|
||||
for team in teams
|
||||
.tab-pane(id="team-#{team.id}")
|
||||
.leaderboard
|
||||
h4 Leaderboard
|
||||
table.table
|
||||
for session in team.leaderboard.topPlayers.models
|
||||
tr
|
||||
td.score-cell= session.get('totalScore')
|
||||
td= session.get('creatorName')
|
||||
td
|
||||
a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}") COMPETE
|
||||
|
||||
//.challengers
|
||||
// h4 Challengers
|
||||
//
|
||||
// if team.easyChallenger
|
||||
// div.challenger-select
|
||||
// | Easy challenger:
|
||||
// button
|
||||
// a(href=link+'?team='+team.id+'&opponent='+team.easyChallenger.id)
|
||||
// TODO: finish once the scoring system is finished
|
||||
hr
|
||||
button.btn.btn-warning.btn-lg.highlight#simulate-button(style="margin-bottom:10px") Simulate all games
|
83
app/templates/play/ladder/team.jade
Normal file
83
app/templates/play/ladder/team.jade
Normal file
|
@ -0,0 +1,83 @@
|
|||
extends /templates/base
|
||||
block content
|
||||
|
||||
ol.breadcrumb
|
||||
li
|
||||
a(href="/") Home
|
||||
li
|
||||
a(href="/play/ladder/#{levelID}")= level.get('name')
|
||||
li.active= teamName
|
||||
|
||||
|
||||
|
||||
div#columns.row
|
||||
div#matches-column.col-md-6
|
||||
h3 Ranked Games
|
||||
|
||||
if matches.length
|
||||
table.table.table-bordered.table-condensed
|
||||
for match in 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
|
||||
td.time-cell= match.when
|
||||
td.battle-cell
|
||||
a.button.btn(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{match.sessionID}") Battle!
|
||||
|
||||
else
|
||||
div.alert.alert-warning
|
||||
| No ranked matches played yet!
|
||||
| Play some competitors on the right and then come back to get your game ranked.
|
||||
|
||||
// finish this once matches are available
|
||||
|
||||
div#competitors-column.col-md-6
|
||||
h3 Your Competitors
|
||||
|
||||
.well.text-muted
|
||||
div.row
|
||||
div.col-md-2
|
||||
span.warmup Warmup
|
||||
span :
|
||||
div.col-md-10
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}")
|
||||
span.warmup Play vs Default
|
||||
|
||||
if challengers.easy
|
||||
.well
|
||||
div.row.text-info.bg-info
|
||||
div.col-md-2
|
||||
span.easy Easy
|
||||
span :
|
||||
div.col-md-10
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.easy.sessionID}")
|
||||
span Play vs
|
||||
span= challengers.easy.opponentName
|
||||
|
||||
if challengers.medium
|
||||
.well
|
||||
div.row.text-warning.bg-warning
|
||||
div.col-md-2
|
||||
span.medium Medium
|
||||
span :
|
||||
div.col-md-10
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.medium.sessionID}")
|
||||
span Play vs
|
||||
span= challengers.medium.opponentName
|
||||
|
||||
if challengers.hard
|
||||
.well
|
||||
div.row.text-danger.bg-danger
|
||||
div.col-md-2
|
||||
span.hard Hard
|
||||
span :
|
||||
div.col-md-10
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.hard.sessionID}")
|
||||
span Play vs
|
||||
span= challengers.hard.opponentName
|
159
app/views/play/ladder/team_view.coffee
Normal file
159
app/views/play/ladder/team_view.coffee
Normal file
|
@ -0,0 +1,159 @@
|
|||
RootView = require 'views/kinds/RootView'
|
||||
Level = require 'models/Level'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
LeaderboardCollection = require 'collections/LeaderboardCollection'
|
||||
|
||||
module.exports = class LadderView extends RootView
|
||||
id: 'ladder-team-view'
|
||||
template: require 'templates/play/ladder/team'
|
||||
startsLoading: true
|
||||
|
||||
# PART 1: Loading Level/Session
|
||||
|
||||
constructor: (options, @levelID, @team) ->
|
||||
super(options)
|
||||
@level = new Level(_id:@levelID)
|
||||
@level.fetch()
|
||||
@level.once 'sync', @onLevelLoaded, @
|
||||
|
||||
url = "/db/level/#{@levelID}/session?team=#{@team}"
|
||||
@session = new LevelSession()
|
||||
@session.url = -> url
|
||||
@session.fetch()
|
||||
@session.once 'sync', @onSessionLoaded, @
|
||||
|
||||
onLevelLoaded: -> @startLoadingChallengersMaybe()
|
||||
onSessionLoaded: -> @startLoadingChallengersMaybe()
|
||||
|
||||
# PART 2: Loading some challengers if we don't have any matches yet
|
||||
|
||||
startLoadingChallengersMaybe: ->
|
||||
return unless @level.loaded and @session.loaded
|
||||
matches = @session.get('matches')
|
||||
if matches?.length then @loadNames() else @loadChallengers()
|
||||
|
||||
loadChallengers: ->
|
||||
@challengers = new ChallengersData(@level, @team, @session)
|
||||
@challengers.on 'sync', @loadNames, @
|
||||
|
||||
# PART 3: Loading the names of the other users
|
||||
|
||||
loadNames: ->
|
||||
ids = []
|
||||
ids.push match.opponents[0].userID for match in @session.get('matches') or []
|
||||
ids = ids.concat(@challengers.playerIDs()) if @challengers
|
||||
|
||||
success = (@nameMap) =>
|
||||
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
|
||||
})
|
||||
|
||||
# PART 4: Rendering
|
||||
|
||||
finishRendering: ->
|
||||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
ctx.levelID = @levelID
|
||||
ctx.teamName = _.string.titleize @team
|
||||
ctx.teamID = @team
|
||||
ctx.challengers = if not @startsLoading then @getChallengers() else {}
|
||||
|
||||
convertMatch = (match) ->
|
||||
opponent = match.opponent[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
|
||||
}
|
||||
|
||||
ctx.matches = (convertMatch(match) for match in @session.get('matches') or [])
|
||||
console.log 'context is', ctx
|
||||
ctx
|
||||
|
||||
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
|
||||
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
|
||||
opponentName: @nameMap[session.get('creator')] or 'Anoner'
|
||||
opponentID: session.get('creator')
|
||||
}
|
||||
|
||||
challengeInfoFromMatches: (matches) ->
|
||||
return unless matches?.length
|
||||
match = _.sample matches
|
||||
opponent = match.opponents[0]
|
||||
return {
|
||||
sessionID: opponent.sessionID
|
||||
opponentName: opponent.userName or 'Anoner'
|
||||
opponentID: opponent.userID
|
||||
}
|
||||
|
||||
|
||||
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 @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]
|
|
@ -2,6 +2,8 @@ RootView = require 'views/kinds/RootView'
|
|||
Level = require 'models/Level'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
CocoCollection = require 'models/CocoCollection'
|
||||
LeaderboardCollection = require 'collections/LeaderboardCollection'
|
||||
{hslToHex} = require 'lib/utils'
|
||||
|
||||
HIGHEST_SCORE = 1000000
|
||||
|
||||
|
@ -12,17 +14,6 @@ class LevelSessionsCollection extends CocoCollection
|
|||
constructor: (levelID) ->
|
||||
super()
|
||||
@url = "/db/level/#{levelID}/all_sessions"
|
||||
|
||||
class LeaderboardCollection extends CocoCollection
|
||||
url: ''
|
||||
model: LevelSession
|
||||
|
||||
constructor: (level, options) ->
|
||||
super()
|
||||
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'
|
||||
|
@ -44,9 +35,9 @@ module.exports = class LadderView extends RootView
|
|||
alert "(do not push more than once pls)"
|
||||
|
||||
|
||||
constructor: (options, levelID) ->
|
||||
constructor: (options, @levelID) ->
|
||||
super(options)
|
||||
@level = new Level(_id:levelID)
|
||||
@level = new Level(_id:@levelID)
|
||||
@level.fetch()
|
||||
@level.once 'sync', @onLevelLoaded, @
|
||||
|
||||
|
@ -69,6 +60,7 @@ module.exports = class LadderView extends RootView
|
|||
continue unless teamConfig.playable
|
||||
teams.push teamName
|
||||
@teams = teams
|
||||
@teamConfigs = alliedSystem.config.teams
|
||||
|
||||
@leaderboards = {}
|
||||
@challengers = {}
|
||||
|
@ -78,8 +70,6 @@ module.exports = class LadderView extends RootView
|
|||
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)
|
||||
# @challengers[team].once 'sync', @onChallengersLoaded, @
|
||||
|
||||
onChallengersLoaded: -> @renderMaybe()
|
||||
onLeaderboardLoaded: -> @renderMaybe()
|
||||
|
@ -97,26 +87,24 @@ module.exports = class LadderView extends RootView
|
|||
ctx.description = if description then marked(description) else ''
|
||||
ctx.link = "/play/level/#{@level.get('name')}"
|
||||
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
|
||||
# easyChallenger: @challengers[team].easyPlayer.models[0]
|
||||
# mediumChallenger: @challengers[team].mediumPlayer.models[0]
|
||||
# hardChallenger: @challengers[team].hardPlayer.models[0]
|
||||
bgColor: bgColor
|
||||
primaryColor: primaryColor
|
||||
})
|
||||
ctx
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@$el.find('#leaderboard-column .nav a:first').tab('show')
|
||||
|
||||
class LeaderboardData
|
||||
constructor: (@level, @team, @session) ->
|
||||
console.log 'creating leaderboard data', @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()
|
||||
|
@ -139,10 +127,28 @@ class LeaderboardData
|
|||
if @session
|
||||
if @topPlayers.loaded # and @playersAbove.loaded and @playersBelow.loaded
|
||||
@loaded = true
|
||||
@trigger 'sync'
|
||||
@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) ->
|
||||
|
|
|
@ -124,23 +124,27 @@ module.exports = class PlayLevelView extends View
|
|||
@session = @levelLoader.session
|
||||
@world = @levelLoader.world
|
||||
@level = @levelLoader.level
|
||||
team = @getQueryVariable("team") ? @world.teamForPlayer(0)
|
||||
|
||||
if s = @levelLoader.opponentSession
|
||||
spells = s.get('teamSpells')?[s.get('team')]
|
||||
opponentCode = s.get('code')
|
||||
myCode = @session.get('code') or {}
|
||||
for spell in spells
|
||||
continue unless c = opponentCode[spell]
|
||||
myCode[spell] = c
|
||||
@session.set('code', myCode)
|
||||
opponentSpells = []
|
||||
for spellTeam, spells of @session.get('teamSpells') or {}
|
||||
continue if spellTeam is team
|
||||
opponentSpells = opponentSpells.concat spells
|
||||
|
||||
otherSession = @levelLoader.opponentSession
|
||||
opponentCode = otherSession?.get('code') or {}
|
||||
myCode = @session.get('code') or {}
|
||||
for spell in opponentSpells
|
||||
c = opponentCode[spell]
|
||||
if c then myCode[spell] = c else delete myCode[spell]
|
||||
@session.set('code', myCode)
|
||||
|
||||
@levelLoader.destroy()
|
||||
@levelLoader = null
|
||||
@loadingScreen.destroy()
|
||||
@god.level = @level.serialize @supermodel
|
||||
@god.worldClassMap = @world.classMap
|
||||
#@setTeam @world.teamForPlayer _.size @session.get 'players' # TODO: players aren't initialized yet?
|
||||
@setTeam @getQueryVariable("team") ? @world.teamForPlayer(0)
|
||||
@setTeam team
|
||||
@initSurface()
|
||||
@initGoalManager()
|
||||
@initScriptManager()
|
||||
|
|
Loading…
Reference in a new issue