Skip to content

Commit

Permalink
swiss WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
ornicar committed Apr 30, 2020
1 parent 39b0394 commit 80c8355
Show file tree
Hide file tree
Showing 15 changed files with 324 additions and 131 deletions.
1 change: 1 addition & 0 deletions bin/mongodb/swiss.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
db.swiss.ensureIndex({teamId:1,startsAt:1})
db.swiss_player.ensureIndex({s:1,u:1})
db.swiss_player.ensureIndex({s:1,c:-1})
db.swiss_pairing.ensureIndex({s:1,r:1})
db.swiss_pairing.ensureIndex({s:1,u:1,r:1})
4 changes: 1 addition & 3 deletions modules/swiss/src/main/BsonHandlers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ private object BsonHandlers {
implicit val playerNumberHandler = intAnyValHandler[SwissPlayer.Number](_.value, SwissPlayer.Number.apply)
implicit val roundNumberHandler = intAnyValHandler[SwissRound.Number](_.value, SwissRound.Number.apply)
implicit val swissIdHandler = stringAnyValHandler[Swiss.Id](_.value, Swiss.Id.apply)
implicit val pairingIdHandler = stringAnyValHandler[SwissPairing.Id](_.value, SwissPairing.Id.apply)
implicit val playerIdHandler = stringAnyValHandler[SwissPlayer.Id](_.value, SwissPlayer.Id.apply)

implicit val playerHandler = new BSON[SwissPlayer] {
Expand Down Expand Up @@ -81,10 +80,9 @@ private object BsonHandlers {
r.get[List[SwissPlayer.Number]]("u") match {
case List(white, black) =>
SwissPairing(
_id = r.get[SwissPairing.Id]("_id"),
_id = r str "_id",
swissId = r.get[Swiss.Id]("s"),
round = r.get[SwissRound.Number]("r"),
gameId = r str "g",
white = white,
black = black,
winner = r boolO "w" map {
Expand Down
18 changes: 14 additions & 4 deletions modules/swiss/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,27 @@ import lila.common.config._

@Module
final class Env(
remoteSocketApi: lila.socket.RemoteSocket,
db: lila.db.Db,
chatApi: lila.chat.ChatApi,
lightUserApi: lila.user.LightUserApi
)(implicit ec: scala.concurrent.ExecutionContext) {
)(
implicit
ec: scala.concurrent.ExecutionContext,
system: akka.actor.ActorSystem,
// mat: akka.stream.Materializer,
idGenerator: lila.game.IdGenerator,
mode: play.api.Mode
) {

private val colls = wire[SwissColls]

val api = wire[SwissApi]

def version(tourId: Swiss.Id): Fu[SocketVersion] =
fuccess(SocketVersion(0))
// socket.rooms.ask[SocketVersion](tourId)(GetVersion)
private lazy val socket = wire[SwissSocket]

def version(swissId: Swiss.Id): Fu[SocketVersion] =
socket.rooms.ask[SocketVersion](swissId.value)(GetVersion)

lazy val json = wire[SwissJson]

Expand Down
12 changes: 6 additions & 6 deletions modules/swiss/src/main/Swiss.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ case class Swiss(
) {
def id = _id

def isCreated = status == Status.Created
def isStarted = status == Status.Started
def isFinished = status == Status.Finished
def isEnterable = !isFinished

def isNowOrSoon = startsAt.isBefore(DateTime.now plusMinutes 15) && !isFinished
def isCreated = status == Status.Created
def isStarted = status == Status.Started
def isFinished = status == Status.Finished
def isEnterable = !isFinished
def isNowOrSoon = startsAt.isBefore(DateTime.now plusMinutes 15) && !isFinished
def secondsToStart = (startsAt.getSeconds - nowSeconds).toInt atLeast 0

def allRounds: List[SwissRound.Number] = (1 to round.value).toList.map(SwissRound.Number.apply)
def finishedRounds: List[SwissRound.Number] = (1 to (round.value - 1)).toList.map(SwissRound.Number.apply)
Expand Down
5 changes: 5 additions & 0 deletions modules/swiss/src/main/SwissApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,9 @@ final class SwissApi(

def featuredInTeam(teamId: TeamID): Fu[List[Swiss]] =
colls.swiss.ext.find($doc("teamId" -> teamId)).sort($sort desc "startsAt").list[Swiss](5)

private def insertPairing(pairing: SwissPairing) =
colls.pairing.insert.one {
pairingHandler.write(pairing) ++ $doc("d" -> DateTime.now)
}.void
}
122 changes: 87 additions & 35 deletions modules/swiss/src/main/SwissJson.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,62 +8,114 @@ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext

import lila.common.{ GreatPlayer, LightUser, Uptime }
import lila.db.dsl._
import lila.game.Game
import lila.hub.LightTeam.TeamID
import lila.quote.Quote.quoteWriter
import lila.rating.PerfType
import lila.socket.Socket.SocketVersion
import lila.user.User

final class SwissJson(
colls: SwissColls,
lightUserApi: lila.user.LightUserApi
)(implicit ec: ExecutionContext) {

import BsonHandlers._

def apply(
swiss: Swiss,
leaderboard: List[LeaderboardPlayer],
me: Option[User],
socketVersion: Option[SocketVersion]
)(implicit lang: Lang): Fu[JsObject] = fuccess {
Json
.obj(
"id" -> swiss.id.value,
"createdBy" -> swiss.createdBy,
"startsAt" -> formatDate(swiss.startsAt),
"name" -> swiss.name,
"perf" -> swiss.perfType,
"clock" -> swiss.clock,
"variant" -> swiss.variant.key,
"nbRounds" -> swiss.nbRounds,
"nbPlayers" -> swiss.nbPlayers,
"leaderboard" -> leaderboard.map { l =>
Json.obj(
"player" -> Json.obj(
"user" -> lightUserApi.sync(l.player.userId),
"rating" -> l.player.rating,
"points" -> l.player.points,
"score" -> l.player.score
),
"pairings" -> swiss.allRounds.map(l.pairings.get).map {
_.fold[JsValue](JsNull) { p =>
Json.obj(
"o" -> p.opponentOf(l.player.number),
"g" -> p.gameId,
"w" -> p.winner.map(l.player.number.==)
)(implicit lang: Lang): Fu[JsObject] =
me.?? { fetchMyInfo(swiss, _) } map { myInfo =>
Json
.obj(
"id" -> swiss.id.value,
"createdBy" -> swiss.createdBy,
"startsAt" -> formatDate(swiss.startsAt),
"name" -> swiss.name,
"perf" -> swiss.perfType,
"clock" -> swiss.clock,
"variant" -> swiss.variant.key,
"round" -> swiss.round,
"nbRounds" -> swiss.nbRounds,
"nbPlayers" -> swiss.nbPlayers,
"leaderboard" -> leaderboard.map { l =>
Json.obj(
"player" -> Json
.obj(
"user" -> lightUserApi.sync(l.player.userId),
"rating" -> l.player.rating,
"points" -> l.player.points,
"score" -> l.player.score
)
.add("provisional" -> l.player.provisional),
"pairings" -> swiss.allRounds.map(l.pairings.get).map {
_.fold[JsValue](JsNull) { p =>
Json.obj(
"o" -> p.opponentOf(l.player.number),
"g" -> p.gameId,
"w" -> p.winner.map(l.player.number.==)
)
}
}
}
)
}
)
.add("isStarted" -> swiss.isStarted)
.add("isFinished" -> swiss.isFinished)
.add("socketVersion" -> socketVersion.map(_.value))
.add("quote" -> swiss.isCreated.option(lila.quote.Quote.one(swiss.id.value)))
.add("description" -> swiss.description)
.add("secondsToStart" -> swiss.isCreated.option(swiss.secondsToStart))
.add("me" -> myInfo.map(myInfoJson(me)))
}

def fetchMyInfo(swiss: Swiss, me: User): Fu[Option[MyInfo]] =
colls.swiss.one[SwissPlayer]($doc("s" -> swiss.id, "u" -> me.id)) flatMap {
_ ?? { player =>
colls.pairing
.find(
$doc("s" -> swiss.id, "u" -> me.id),
$doc("_id" -> true).some
)
}
)
.add("isStarted" -> swiss.isStarted)
.add("isFinished" -> swiss.isFinished)
.add("socketVersion" -> socketVersion.map(_.value))
.add("quote" -> swiss.isCreated.option(lila.quote.Quote.one(swiss.id.value)))
.add("description" -> swiss.description)
}
.sort($sort desc "d")
.one[Bdoc]
.dmap { _.flatMap(_.getAsOpt[Game.ID]("_id")) }
.flatMap { gameId =>
getOrGuessRank(swiss, player) dmap { rank =>
MyInfo(rank + 1, false, gameId).some
}
}
}
}

// if the user is not yet in the cached ranking,
// guess its rank based on other players scores in the DB
private def getOrGuessRank(swiss: Swiss, player: SwissPlayer): Fu[Int] = ???
// cached ranking swiss flatMap {
// _ get player.userId match {
// case Some(rank) => fuccess(rank)
// case None => playerRepo.computeRankOf(player)
// }
// }

private def formatDate(date: DateTime) = ISODateTimeFormat.dateTime print date

private def myInfoJson(u: Option[User])(i: MyInfo) =
Json
.obj(
"rank" -> i.rank,
"withdraw" -> i.withdraw,
"gameId" -> i.gameId,
"username" -> u.map(_.titleUsername)
)

implicit private val roundNumberWriter: Writes[SwissRound.Number] = Writes[SwissRound.Number] { n =>
JsNumber(n.value)
}
implicit private val playerNumberWriter: Writes[SwissPlayer.Number] = Writes[SwissPlayer.Number] { n =>
JsNumber(n.value)
}
Expand Down
14 changes: 8 additions & 6 deletions modules/swiss/src/main/model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ object SwissRound {
}

case class SwissPairing(
_id: SwissPairing.Id, // random
_id: Game.ID,
swissId: Swiss.Id,
round: SwissRound.Number,
gameId: Game.ID,
white: SwissPlayer.Number,
black: SwissPlayer.Number,
winner: Option[SwissPlayer.Number]
) {
def gameId = _id
def players = List(white, black)
def has(number: SwissPlayer.Number) = white == number || black == number
def colorOf(number: SwissPlayer.Number) = chess.Color(white == number)
Expand All @@ -45,10 +45,6 @@ case class SwissPairing(

object SwissPairing {

case class Id(value: String) extends AnyVal with StringValue

def makeId = Id(scala.util.Random.alphanumeric take 8 mkString)

case class Pending(
white: SwissPlayer.Number,
black: SwissPlayer.Number
Expand All @@ -64,3 +60,9 @@ case class LeaderboardPlayer(
player: SwissPlayer,
pairings: Map[SwissRound.Number, SwissPairing]
)

case class MyInfo(rank: Int, withdraw: Boolean, gameId: Option[Game.ID]) {
def page = {
math.floor((rank - 1) / 10) + 1
}.toInt
}
4 changes: 2 additions & 2 deletions modules/tournament/src/main/JsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ final class JsonView(
)(implicit lang: Lang): Fu[JsObject] =
for {
data <- cachableData get tour.id
myInfo <- me ?? { myInfo(tour, _) }
myInfo <- me ?? { fetchMyInfo(tour, _) }
pauseDelay = me flatMap { u =>
pause.remainingDelay(u.id, tour)
}
Expand Down Expand Up @@ -142,7 +142,7 @@ final class JsonView(
cachableData invalidate tour.id
}

def myInfo(tour: Tournament, me: User): Fu[Option[MyInfo]] =
def fetchMyInfo(tour: Tournament, me: User): Fu[Option[MyInfo]] =
playerRepo.find(tour.id, me.id) flatMap {
_ ?? { player =>
fetchCurrentGameId(tour, me) flatMap { gameId =>
Expand Down
2 changes: 1 addition & 1 deletion ui/swiss/gulpfile.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
require('@build/tsProject')('LichessTournament', 'lichess.tournament', __dirname);
require('@build/tsProject')('LichessSwiss', 'lichess.swiss', __dirname);
require('@build/cssProject')(__dirname);
7 changes: 3 additions & 4 deletions ui/swiss/src/boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ export default function(opts: SwissOpts): void {
li.socket = li.StrongSocket(
'/swiss/' + cfg.data.id, cfg.data.socketVersion, {
receive: function(t, d) {
return tournament.socketReceive(t, d);
return swiss.socketReceive(t, d);
}
});
cfg.socketSend = lichess.socket.send;
cfg.element = element;
cfg.$side = $('.tour__side').clone();
cfg.$faq = $('.tour__faq').clone();
tournament = LichessTournament.start(cfg);
cfg.$side = $('.swiss__side').clone();
LichessSwiss.start(cfg);
}
50 changes: 50 additions & 0 deletions ui/swiss/src/ctrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import makeSocket from './socket';
import xhr from './xhr';
import { myPage, players } from './pagination';
import * as sound from './sound';
import * as tour from './tournament';
import { TournamentData, TournamentOpts, Pages, PlayerInfo, TeamInfo, Standing } from './interfaces';
import { TournamentSocket } from './socket';

interface CtrlTeamInfo {
requested?: string;
loaded?: TeamInfo;
}

export default class TournamentController {

opts: TournamentOpts;
data: TournamentData;
trans: Trans;
socket: TournamentSocket;
page: number;
pages: Pages = {};
lastPageDisplayed: number | undefined;
focusOnMe: boolean;
joinSpinner: boolean = false;
playerInfo: PlayerInfo = {};
teamInfo: CtrlTeamInfo = {};
disableClicks: boolean = true;
searching: boolean = false;
joinWithTeamSelector: boolean = false;
redraw: () => void;

private watchingGameId: string;
private lastStorage = window.lichess.storage.make('last-redirect');

constructor(opts: TournamentOpts, redraw: () => void) {
this.opts = opts;
this.data = opts.data;
this.redraw = redraw;
this.trans = window.lichess.trans(opts.i18n);
this.socket = makeSocket(opts.socketSend, this);
this.page = this.data.standing.page;
this.focusOnMe = tour.isIn(this);
setTimeout(() => this.disableClicks = false, 1500);
this.loadPage(this.data.standing);
this.scrollToMe();
sound.end(this.data);
sound.countDown(this.data);
this.redirectToMyGame();
if (this.data.featured) this.startWatching(this.data.featured.id);
}
Loading

0 comments on commit 80c8355

Please sign in to comment.