Working on the ladder and team views.

This commit is contained in:
Scott Erickson 2014-02-17 17:42:41 -08:00
parent dcb6c49fd8
commit 452a43cae9
9 changed files with 346 additions and 77 deletions

View 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)}"

View file

@ -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

View file

@ -1,4 +1,6 @@
#ladder-view
color: black
.score-cell
width: 50px
width: 50px
.play-button
margin-bottom: 10px

View file

@ -0,0 +1,5 @@
#ladder-team-view
#competitors-column .well
font-size: 16px
font-weight: bold
padding: 7px

View file

@ -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

View 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

View 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]

View file

@ -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) ->

View file

@ -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()