tournament TV WIP

This commit is contained in:
Thibault Duplessis 2016-01-25 13:32:25 +07:00
parent 5136172150
commit 8eb06fccc5
10 changed files with 136 additions and 28 deletions

View file

@ -50,7 +50,8 @@ object BSONHandlers {
createdAt = r date "createdAt",
createdBy = r str "createdBy",
startsAt = startsAt,
winnerId = r strO "winner")
winnerId = r strO "winner",
featuredId = r strO "featured")
}
def writes(w: BSON.Writer, o: Tournament) = BSONDocument(
"_id" -> o.id,
@ -72,7 +73,8 @@ object BSONHandlers {
"createdAt" -> w.date(o.createdAt),
"createdBy" -> w.str(o.createdBy),
"startsAt" -> w.date(o.startsAt),
"winner" -> o.winnerId)
"winner" -> o.winnerId,
"featured" -> o.featuredId)
}
implicit val playerBSONHandler = new BSON[Player] {

View file

@ -15,7 +15,11 @@ final class JsonView(
cached: Cached,
performance: Performance) {
private case class CachableData(pairings: JsArray, games: JsArray, podium: Option[JsArray])
private case class CachableData(
pairings: JsArray,
games: JsArray,
featured: Option[JsObject],
podium: Option[JsArray])
def apply(
tour: Tournament,
@ -54,6 +58,7 @@ final class JsonView(
"lastGames" -> data.games,
"standing" -> stand,
"me" -> myInfo.map(myInfoJson),
"featured" -> data.featured,
"podium" -> data.podium,
"playerInfo" -> playerInfoJson,
"quote" -> lila.quote.Quote.one(tour.id)
@ -100,6 +105,19 @@ final class JsonView(
)
}
private def fetchFeaturedGame(tour: Tournament): Fu[Option[FeaturedGame]] =
tour.featuredId.ifTrue(tour.isStarted) ?? PairingRepo.byId flatMap {
_ ?? { pairing =>
GameRepo game pairing.gameId flatMap {
_ ?? { game =>
cached ranking tour map { ranking =>
RankedPairing(ranking)(pairing) map { FeaturedGame(game, _) }
}
}
}
}
}
private def sheetNbs(userId: String, sheet: ScoreSheet, pairings: Pairings) = sheet match {
case s: arena.ScoringSystem.Sheet => Json.obj(
"game" -> s.scores.size,
@ -129,13 +147,21 @@ final class JsonView(
for {
pairings <- PairingRepo.recentByTour(id, 40)
games <- GameRepo games pairings.take(4).map(_.gameId)
tour <- TournamentRepo byId id
featured <- tour ?? fetchFeaturedGame
podium <- podiumJson(id)
} yield CachableData(
JsArray(pairings map pairingJson),
JsArray(games map gameJson),
featured map featuredJson,
podium),
timeToLive = 1 second)
private def featuredJson(featured: FeaturedGame) = Json.obj(
"game" -> gameJson(featured.game),
"rank1" -> featured.rankedPairing.rank1,
"rank2" -> featured.rankedPairing.rank2)
private def myInfoJson(i: PlayerInfo) = Json.obj(
"rank" -> i.rank,
"withdraw" -> i.withdraw)

View file

@ -23,7 +23,8 @@ case class Tournament(
createdAt: DateTime,
createdBy: String,
startsAt: DateTime,
winnerId: Option[String] = None) {
winnerId: Option[String] = None,
featuredId: Option[String] = None) {
def isCreated = status == Status.Created
def isStarted = status == Status.Started

View file

@ -69,10 +69,11 @@ private[tournament] final class TournamentApi(
play.api.Logger("tourpairing").warn(s"Give up making http://lichess.org/tournament/${tour.id} ${pairings.size} pairings in ${nowMillis - startAt}ms")
funit
case pairings => pairings.map { pairing =>
PairingRepo.insert(pairing) >> autoPairing(tour, pairing)
}.sequenceFu.map {
_ map StartGame.apply foreach { sendTo(tour.id, _) }
} >>- {
PairingRepo.insert(pairing) >>
autoPairing(tour, pairing) addEffect { game =>
sendTo(tour.id, StartGame(game))
}
}.sequenceFu >> featureOneOf(tour, pairings, ranking) >>- {
val time = nowMillis - startAt
if (time > 100)
play.api.Logger("tourpairing").debug(s"Done making http://lichess.org/tournament/${tour.id} ${pairings.size} pairings in ${time}ms")
@ -82,6 +83,16 @@ private[tournament] final class TournamentApi(
}
}
private def featureOneOf(tour: Tournament, pairings: Pairings, ranking: Ranking): Funit =
tour.featuredId.ifTrue(pairings.nonEmpty) ?? PairingRepo.byId map2
RankedPairing(ranking) map (_.flatten) flatMap { curOption =>
val candidates = pairings flatMap RankedPairing(ranking)
if (curOption.exists(_.pairing.playing)) funit
else candidates.sortBy(-_.bestRank).headOption ?? { best =>
TournamentRepo.setFeaturedGameId(tour.id, best.pairing.gameId)
}
}
def tourAndRanks(game: Game): Fu[Option[TourAndRanks]] = ~{
for {
tourId <- game.tournamentId

View file

@ -114,6 +114,16 @@ object TournamentRepo {
BSONDocument("$set" -> BSONDocument("winner" -> userId))
).void
def setFeaturedGameId(tourId: String, gameId: String) = coll.update(
selectId(tourId),
BSONDocument("$set" -> BSONDocument("featured" -> gameId))
).void
def featuredGameId(tourId: String) = coll.find(
selectId(tourId),
BSONDocument("featured" -> true)
).one[BSONDocument].map(_.flatMap(_.getAs[String]("featured")))
private def allCreatedSelect(aheadMinutes: Int) = createdSelect ++ BSONDocument(
"$or" -> BSONArray(
BSONDocument("schedule" -> BSONDocument("$exists" -> false)),

View file

@ -25,3 +25,23 @@ case class TourAndRanks(
tour: Tournament,
whiteRank: Int,
blackRank: Int)
case class RankedPairing(pairing: Pairing, rank1: Int, rank2: Int) {
def bestRank = rank1 min rank2
def rankSum = rank1 + rank2
def bestColor = chess.Color(rank1 < rank2)
}
object RankedPairing {
def apply(ranking: Ranking)(pairing: Pairing): Option[RankedPairing] = for {
r1 <- ranking get pairing.user1
r2 <- ranking get pairing.user2
} yield RankedPairing(pairing, r1 + 1, r2 + 1)
}
case class FeaturedGame(
game: lila.game.Game,
rankedPairing: RankedPairing)

View file

@ -355,10 +355,22 @@ ol.scheduled_tournaments a {
position: relative;
float: right;
width: 252px;
border: 1px solid #ccc;
text-align: center;
transition: 0.13s;
}
#tournament_side .featured {
margin-bottom: 10px;
}
#tournament_side .box {
border: 1px solid #ccc;
}
#tournament_side .cg-board-wrap {
width: 252px;
height: 252px;
}
#tournament_side .vstext {
width: 240px;
}
#tournament_side .scroll-shadow-soft {
max-height: 510px;
overflow: auto;

View file

@ -3,8 +3,6 @@ var partial = require('chessground').util.partial;
var util = require('./util');
var status = require('game').status;
var statusClasses = ['playing', 'draw', 'win', 'loss'];
function user(p, it) {
return {
tag: p.s === 0 ? 'playing' : (
@ -16,6 +14,26 @@ function user(p, it) {
};
}
function featured(f) {
return m('div.featured', [
m('div.vstext.clearfix', [
m('div.left', [
f.game.user1.name,
m('br'),
f.game.user1.title ? f.game.user1.title + ' ' : '',
f.game.user1.rating
]),
m('div.right', [
f.game.user2.name,
m('br'),
f.game.user2.rating,
f.game.user2.title ? ' ' + f.game.user2.title : ''
])
]),
util.miniBoard(f.game)
]);
}
module.exports = function(ctrl) {
var pairing = function(p) {
return {
@ -31,9 +49,12 @@ module.exports = function(ctrl) {
]
};
};
return m('div.all_pairings.scroll-shadow-soft', {
onclick: function() {
return !ctrl.vm.disableClicks;
}
}, ctrl.data.pairings.map(pairing));
return [
ctrl.data.featured ? featured(ctrl.data.featured) : null,
m('div.box.all_pairings.scroll-shadow-soft', {
onclick: function() {
return !ctrl.vm.disableClicks;
}
}, ctrl.data.pairings.map(pairing))
];
};

View file

@ -32,7 +32,7 @@ module.exports = function(ctrl) {
var avgOp = pairingsLen ? Math.round(data.pairings.reduce(function(a, b) {
return a + b.op.rating;
}, 0) / pairingsLen) : null;
return m('div.player', {
return m('div.box.player', {
config: function(el, isUpdate) {
if (!isUpdate) $('body').trigger('lichess.content_loaded');
}

View file

@ -3,19 +3,23 @@ var partial = require('chessground').util.partial;
var boardContent = m('div.cg-board-wrap', m('div.cg-board'));
function miniBoard(game) {
return m('a', {
key: game.id,
href: '/' + game.id + (game.color === 'white' ? '' : '/black'),
class: 'mini_board live_' + game.id + ' parse_fen is2d',
'data-color': game.color,
'data-fen': game.fen,
'data-lastmove': game.lastMove,
config: function(el, isUpdate) {
if (!isUpdate) lichess.parseFen($(el));
}
}, boardContent);
}
function miniGame(game) {
return m('div', [
m('a', {
key: game.id,
href: '/' + game.id + (game.color === 'white' ? '' : '/black'),
class: 'mini_board live_' + game.id + ' parse_fen is2d',
'data-color': game.color,
'data-fen': game.fen,
'data-lastmove': game.lastMove,
config: function(el, isUpdate) {
if (!isUpdate) lichess.parseFen($(el));
}
}, boardContent),
miniBoard(game),
m('div.vstext.clearfix', [
m('div.left', [
game.user1.name,
@ -90,6 +94,7 @@ module.exports = {
games: function(games) {
return m('div.game_list.playing', games.map(miniGame));
},
miniBoard: miniBoard,
clock: function(time) {
return function(el, isUpdate) {
if (!isUpdate) $(el).clock({