swiss WIP
parent
3d3085d62f
commit
391046a7e7
|
@ -18,11 +18,11 @@ final class Swiss(
|
|||
env.swiss.api.byId(SwissModel.Id(id)) flatMap {
|
||||
_.fold(swissNotFound.fuccess) { swiss =>
|
||||
for {
|
||||
version <- env.swiss.version(swiss.id)
|
||||
// rounds <- env.swiss.roundsOf(swiss)
|
||||
version <- env.swiss.version(swiss.id)
|
||||
leaderboard <- env.swiss.api.leaderboard(swiss, page = 1)
|
||||
json <- env.swiss.json(
|
||||
swiss = swiss,
|
||||
// rounds = rounds,
|
||||
leaderboard = leaderboard,
|
||||
me = ctx.me,
|
||||
socketVersion = version.some
|
||||
)
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
db.swiss.ensureIndex({teamId:1,startsAt: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})
|
||||
|
|
|
@ -45,68 +45,61 @@ private object BsonHandlers {
|
|||
},
|
||||
v => BSONString(v.fen)
|
||||
)
|
||||
implicit val swissPointsHandler = intAnyValHandler[Swiss.Points](_.double, Swiss.Points.apply)
|
||||
|
||||
implicit val swissPointsHandler = intAnyValHandler[Swiss.Points](_.double, Swiss.Points.apply)
|
||||
implicit val swissScoreHandler = intAnyValHandler[Swiss.Score](_.double, Swiss.Score.apply)
|
||||
implicit val playerNumberHandler = intAnyValHandler[SwissPlayer.Number](_.value, SwissPlayer.Number.apply)
|
||||
implicit val playerIdHandler = tryHandler[SwissPlayer.Id](
|
||||
{
|
||||
case BSONString(v) =>
|
||||
(v split ':' match {
|
||||
case Array(swissId, number) =>
|
||||
number.toIntOption map { n =>
|
||||
SwissPlayer.Id(Swiss.Id(swissId), SwissPlayer.Number(n))
|
||||
}
|
||||
case _ => None
|
||||
}) toTry s"Invalid player ID $v"
|
||||
},
|
||||
id => BSONString(s"${id.swissId}:${id.number}")
|
||||
)
|
||||
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] {
|
||||
def reads(r: BSON.Reader) = SwissPlayer(
|
||||
id = r.get[SwissPlayer.Id]("_id"),
|
||||
userId = r str "uid",
|
||||
_id = r.get[SwissPlayer.Id]("_id"),
|
||||
swissId = r.get[Swiss.Id]("s"),
|
||||
number = r.get[SwissPlayer.Number]("n"),
|
||||
userId = r str "u",
|
||||
rating = r int "r",
|
||||
provisional = r boolD "pr",
|
||||
points = r.get[Swiss.Points]("p")
|
||||
points = r.get[Swiss.Points]("p"),
|
||||
score = r.get[Swiss.Score]("c")
|
||||
)
|
||||
def writes(w: BSON.Writer, o: SwissPlayer) = $doc(
|
||||
"_id" -> o.id,
|
||||
"uid" -> o.userId,
|
||||
"_id" -> o._id,
|
||||
"s" -> o.swissId,
|
||||
"n" -> o.number,
|
||||
"u" -> o.userId,
|
||||
"r" -> o.rating,
|
||||
"pr" -> w.boolO(o.provisional),
|
||||
"p" -> o.points
|
||||
"p" -> o.points,
|
||||
"c" -> o.score
|
||||
)
|
||||
}
|
||||
|
||||
implicit val swissIdHandler = stringAnyValHandler[Swiss.Id](_.value, Swiss.Id.apply)
|
||||
implicit val pairingIdHandler = stringAnyValHandler[SwissPairing.Id](_.value, SwissPairing.Id.apply)
|
||||
implicit val roundNumberHandler = intAnyValHandler[SwissRound.Number](_.value, SwissRound.Number.apply)
|
||||
|
||||
implicit val pairingHandler = new BSON[SwissPairing] {
|
||||
def reads(r: BSON.Reader) = {
|
||||
val white = r.get[SwissPlayer.Number]("w")
|
||||
val black = r.get[SwissPlayer.Number]("b")
|
||||
SwissPairing(
|
||||
_id = r.get[SwissPairing.Id]("_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 {
|
||||
case true => white
|
||||
case _ => black
|
||||
}
|
||||
)
|
||||
}
|
||||
def reads(r: BSON.Reader) =
|
||||
r.get[List[SwissPlayer.Number]]("u") match {
|
||||
case List(white, black) =>
|
||||
SwissPairing(
|
||||
_id = r.get[SwissPairing.Id]("_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 {
|
||||
case true => white
|
||||
case _ => black
|
||||
}
|
||||
)
|
||||
case _ => sys error "Invalid swiss pairing users"
|
||||
}
|
||||
def writes(w: BSON.Writer, o: SwissPairing) = $doc(
|
||||
"_id" -> o._id,
|
||||
"s" -> o.swissId,
|
||||
"r" -> o.round,
|
||||
"g" -> o.gameId,
|
||||
"w" -> o.white,
|
||||
"b" -> o.black,
|
||||
"u" -> o.players,
|
||||
"w" -> o.winner.map(o.white ==)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ final private class PairingSystem(executable: String) {
|
|||
private object writer {
|
||||
|
||||
private type Bits = List[(Int, String)]
|
||||
private type PairingMap = Map[SwissPlayer.Number, Map[Int, SwissPairing]]
|
||||
private type PairingMap = Map[SwissPlayer.Number, Map[SwissRound.Number, SwissPairing]]
|
||||
|
||||
def apply(swiss: Swiss, players: List[SwissPlayer], pairings: List[SwissPairing]): String = {
|
||||
val pairingMap: PairingMap = pairings.foldLeft[PairingMap](Map.empty) {
|
||||
|
@ -40,22 +40,22 @@ final private class PairingSystem(executable: String) {
|
|||
pairing.players.foldLeft(acc) {
|
||||
case (acc, player) =>
|
||||
acc.updatedWith(player) { acc =>
|
||||
(~acc).updated(pairing.round.value, pairing).some
|
||||
(~acc).updated(pairing.round, pairing).some
|
||||
}
|
||||
}
|
||||
}
|
||||
s"XXR ${swiss.nbRounds}" :: players.map(player(pairingMap, swiss.round)).map(format)
|
||||
s"XXR ${swiss.nbRounds}" :: players.map(player(swiss, pairingMap)).map(format)
|
||||
} mkString "\n"
|
||||
|
||||
// https://www.fide.com/FIDE/handbook/C04Annex2_TRF16.pdf
|
||||
private def player(pairingMap: PairingMap, rounds: SwissRound.Number)(p: SwissPlayer): Bits =
|
||||
private def player(swiss: Swiss, pairingMap: PairingMap)(p: SwissPlayer): Bits =
|
||||
List(
|
||||
3 -> "001",
|
||||
8 -> p.number.toString,
|
||||
84 -> f"${p.points.value}%1.1f"
|
||||
) ::: {
|
||||
val pairings = ~pairingMap.get(p.number)
|
||||
(1 to rounds.value).toList.flatMap { rn =>
|
||||
swiss.finishedRounds.flatMap { rn =>
|
||||
val pairing = pairings get rn
|
||||
List(
|
||||
95 -> pairing.map(_ opponentOf p.number).??(_.toString),
|
||||
|
@ -64,7 +64,7 @@ final private class PairingSystem(executable: String) {
|
|||
case true => "1"
|
||||
case false => "0"
|
||||
}
|
||||
).map { case (l, s) => (l + (rn - 1) * 10, s) }
|
||||
).map { case (l, s) => (l + (rn.value - 1) * 10, s) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import chess.Speed
|
|||
import org.joda.time.DateTime
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.game.{ Game, PerfPicker }
|
||||
import lila.game.PerfPicker
|
||||
import lila.hub.LightTeam.TeamID
|
||||
import lila.rating.PerfType
|
||||
import lila.user.User
|
||||
|
@ -17,7 +17,7 @@ case class Swiss(
|
|||
clock: ClockConfig,
|
||||
variant: chess.variant.Variant,
|
||||
rated: Boolean,
|
||||
round: SwissRound.Number,
|
||||
round: SwissRound.Number, // ongoing round
|
||||
nbRounds: Int,
|
||||
nbPlayers: Int,
|
||||
createdAt: DateTime,
|
||||
|
@ -37,6 +37,9 @@ case class Swiss(
|
|||
|
||||
def isNowOrSoon = startsAt.isBefore(DateTime.now plusMinutes 15) && !isFinished
|
||||
|
||||
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)
|
||||
|
||||
def speed = Speed(clock)
|
||||
|
||||
def perfType: Option[PerfType] = PerfPicker.perfType(speed, variant, none)
|
||||
|
@ -60,65 +63,9 @@ object Swiss {
|
|||
case class Points(double: Int) extends AnyVal {
|
||||
def value: Float = double / 2f
|
||||
}
|
||||
case class Score(double: Int) extends AnyVal {
|
||||
def value: Float = double / 2f
|
||||
}
|
||||
|
||||
def makeId = Id(scala.util.Random.alphanumeric take 8 mkString)
|
||||
}
|
||||
|
||||
case class SwissPlayer(
|
||||
id: SwissPlayer.Id,
|
||||
userId: User.ID,
|
||||
rating: Int,
|
||||
provisional: Boolean,
|
||||
points: Swiss.Points
|
||||
) {
|
||||
def number = id.number
|
||||
}
|
||||
|
||||
object SwissPlayer {
|
||||
|
||||
case class Id(swissId: Swiss.Id, number: Number)
|
||||
|
||||
case class Number(value: Int) extends AnyVal with IntValue
|
||||
}
|
||||
|
||||
// case class SwissRound(
|
||||
// number: SwissRound.Number,
|
||||
// pairings: List[SwissPairing]
|
||||
// )
|
||||
|
||||
object SwissRound {
|
||||
|
||||
case class Number(value: Int) extends AnyVal with IntValue
|
||||
}
|
||||
|
||||
case class SwissPairing(
|
||||
_id: SwissPairing.Id, // random
|
||||
swissId: Swiss.Id,
|
||||
round: SwissRound.Number,
|
||||
gameId: Game.ID,
|
||||
white: SwissPlayer.Number,
|
||||
black: SwissPlayer.Number,
|
||||
winner: Option[SwissPlayer.Number]
|
||||
) {
|
||||
def players = List(white, black)
|
||||
def has(number: SwissPlayer.Number) = white == number || black == number
|
||||
def colorOf(number: SwissPlayer.Number) = chess.Color(white == number)
|
||||
def opponentOf(number: SwissPlayer.Number) = if (white == number) black else white
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
case class SwissBye(
|
||||
round: SwissRound.Number,
|
||||
player: SwissPlayer.Number
|
||||
)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package lila.swiss
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import reactivemongo.api._
|
||||
|
||||
import lila.common.GreatPlayer
|
||||
import lila.db.dsl._
|
||||
import lila.hub.LightTeam.TeamID
|
||||
import lila.user.User
|
||||
import lila.db.dsl._
|
||||
import lila.common.GreatPlayer
|
||||
|
||||
final class SwissApi(
|
||||
colls: SwissColls
|
||||
|
@ -37,6 +38,48 @@ final class SwissApi(
|
|||
colls.swiss.insert.one(swiss) inject swiss
|
||||
}
|
||||
|
||||
def leaderboard(swiss: Swiss, page: Int): Fu[List[LeaderboardPlayer]] =
|
||||
colls.player
|
||||
.aggregateList(maxDocs = 10, readPreference = ReadPreference.secondaryPreferred) { implicit framework =>
|
||||
import framework._
|
||||
Match($doc("s" -> swiss.id)) -> List(
|
||||
Sort(Descending("t")),
|
||||
Skip((page - 1) * 10),
|
||||
Limit(10),
|
||||
PipelineOperator(
|
||||
$doc(
|
||||
"$lookup" -> $doc(
|
||||
"from" -> colls.pairing.name,
|
||||
"let" -> $doc("n" -> "$n"),
|
||||
"pipeline" -> $arr(
|
||||
$doc(
|
||||
"$match" -> $doc(
|
||||
"$expr" -> $doc(
|
||||
"$and" -> $arr(
|
||||
$doc("s" -> swiss.id),
|
||||
$doc("u" -> "$$n")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
"as" -> "pairings"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
.map {
|
||||
_ map { doc =>
|
||||
LeaderboardPlayer(
|
||||
playerHandler.read(doc),
|
||||
(~doc.getAsOpt[List[SwissPairing]]("pairings")).map { p =>
|
||||
p.round -> p
|
||||
}.toMap
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def pairingsOf(swiss: Swiss) =
|
||||
colls.pairing.ext.find($doc("s" -> swiss.id)).sort($sort asc "r").list[SwissPairing]()
|
||||
|
||||
|
|
|
@ -15,12 +15,12 @@ import lila.socket.Socket.SocketVersion
|
|||
import lila.user.User
|
||||
|
||||
final class SwissJson(
|
||||
// lightUserApi: lila.user.LightUserApi
|
||||
lightUserApi: lila.user.LightUserApi
|
||||
)(implicit ec: ExecutionContext) {
|
||||
|
||||
def apply(
|
||||
swiss: Swiss,
|
||||
// rounds: List[SwissRound],
|
||||
leaderboard: List[LeaderboardPlayer],
|
||||
me: Option[User],
|
||||
socketVersion: Option[SocketVersion]
|
||||
)(implicit lang: Lang): Fu[JsObject] = fuccess {
|
||||
|
@ -34,7 +34,26 @@ final class SwissJson(
|
|||
"clock" -> swiss.clock,
|
||||
"variant" -> swiss.variant.key,
|
||||
"nbRounds" -> swiss.nbRounds,
|
||||
"nbPlayers" -> swiss.nbPlayers
|
||||
"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.==)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
.add("isStarted" -> swiss.isStarted)
|
||||
.add("isFinished" -> swiss.isFinished)
|
||||
|
@ -45,6 +64,25 @@ final class SwissJson(
|
|||
|
||||
private def formatDate(date: DateTime) = ISODateTimeFormat.dateTime print date
|
||||
|
||||
implicit private val playerNumberWriter: Writes[SwissPlayer.Number] = Writes[SwissPlayer.Number] { n =>
|
||||
JsNumber(n.value)
|
||||
}
|
||||
implicit private val pointsWriter: Writes[Swiss.Points] = Writes[Swiss.Points] { p =>
|
||||
JsNumber(p.value)
|
||||
}
|
||||
implicit private val scoreWriter: Writes[Swiss.Score] = Writes[Swiss.Score] { s =>
|
||||
JsNumber(s.value)
|
||||
}
|
||||
|
||||
implicit private val pairingWrites: OWrites[SwissPairing] = OWrites { p =>
|
||||
Json.obj(
|
||||
"gameId" -> p.gameId,
|
||||
"white" -> p.white,
|
||||
"black" -> p.black,
|
||||
"winner" -> p.winner
|
||||
)
|
||||
}
|
||||
|
||||
implicit private val clockWrites: OWrites[chess.Clock.Config] = OWrites { clock =>
|
||||
Json.obj(
|
||||
"limit" -> clock.limitSeconds,
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package lila.swiss
|
||||
|
||||
import lila.user.User
|
||||
import lila.game.Game
|
||||
|
||||
case class SwissPlayer(
|
||||
_id: SwissPlayer.Id, // random
|
||||
swissId: Swiss.Id,
|
||||
number: SwissPlayer.Number,
|
||||
userId: User.ID,
|
||||
rating: Int,
|
||||
provisional: Boolean,
|
||||
points: Swiss.Points,
|
||||
score: Swiss.Score
|
||||
)
|
||||
|
||||
object SwissPlayer {
|
||||
|
||||
case class Id(value: String) extends AnyVal with StringValue
|
||||
|
||||
def makeId = Id(scala.util.Random.alphanumeric take 8 mkString)
|
||||
|
||||
case class Number(value: Int) extends AnyVal with IntValue
|
||||
}
|
||||
|
||||
object SwissRound {
|
||||
|
||||
case class Number(value: Int) extends AnyVal with IntValue
|
||||
}
|
||||
|
||||
case class SwissPairing(
|
||||
_id: SwissPairing.Id, // random
|
||||
swissId: Swiss.Id,
|
||||
round: SwissRound.Number,
|
||||
gameId: Game.ID,
|
||||
white: SwissPlayer.Number,
|
||||
black: SwissPlayer.Number,
|
||||
winner: Option[SwissPlayer.Number]
|
||||
) {
|
||||
def players = List(white, black)
|
||||
def has(number: SwissPlayer.Number) = white == number || black == number
|
||||
def colorOf(number: SwissPlayer.Number) = chess.Color(white == number)
|
||||
def opponentOf(number: SwissPlayer.Number) = if (white == number) black else white
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
case class SwissBye(
|
||||
round: SwissRound.Number,
|
||||
player: SwissPlayer.Number
|
||||
)
|
||||
|
||||
case class LeaderboardPlayer(
|
||||
player: SwissPlayer,
|
||||
pairings: Map[SwissRound.Number, SwissPairing]
|
||||
)
|
Loading…
Reference in New Issue