From c209918070bbee5560709842e3f54f60547d4456 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Tue, 28 Apr 2020 19:22:38 -0600 Subject: [PATCH] swiss models and handlers --- build.sbt | 7 +- modules/common/src/main/Lilaisms.scala | 1 + modules/swiss/src/main/BsonHandlers.scala | 132 ++++++++++++++++++++++ modules/swiss/src/main/Env.scala | 19 ++++ modules/swiss/src/main/Status.scala | 22 ++++ modules/swiss/src/main/Swiss.scala | 76 +++++++++++++ modules/swiss/src/main/package.scala | 6 + modules/tournament/src/main/Pairing.scala | 2 +- 8 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 modules/swiss/src/main/BsonHandlers.scala create mode 100644 modules/swiss/src/main/Env.scala create mode 100644 modules/swiss/src/main/Status.scala create mode 100644 modules/swiss/src/main/Swiss.scala create mode 100644 modules/swiss/src/main/package.scala diff --git a/build.sbt b/build.sbt index 15c337b486..1776387917 100644 --- a/build.sbt +++ b/build.sbt @@ -54,7 +54,7 @@ lazy val modules = Seq( playban, insight, perfStat, slack, quote, challenge, study, studySearch, fishnet, explorer, learn, plan, event, coach, practice, evalCache, irwin, - activity, relay, streamer, bot, clas + activity, relay, streamer, bot, clas, swiss ) lazy val moduleRefs = modules map projectToRef @@ -253,6 +253,11 @@ lazy val tournament = module("tournament", Seq(scalatags, lettuce) ++ reactivemongo.bundle ) +lazy val swiss = module("swiss", + Seq(common, hub, socket, game, round, security, chat, memo, i18n, room), + Seq(scalatags, lettuce) ++ reactivemongo.bundle +) + lazy val simul = module("simul", Seq(common, hub, socket, game, round, chat, memo, quote, room), Seq(lettuce) ++ reactivemongo.bundle diff --git a/modules/common/src/main/Lilaisms.scala b/modules/common/src/main/Lilaisms.scala index 811083c7c4..b258791e54 100644 --- a/modules/common/src/main/Lilaisms.scala +++ b/modules/common/src/main/Lilaisms.scala @@ -37,6 +37,7 @@ trait Lilaisms with scalaz.syntax.ToValidationOps { type StringValue = lila.base.LilaTypes.StringValue + type IntValue = lila.base.LilaTypes.IntValue @inline implicit def toPimpedFuture[A](f: Fu[A]) = new PimpedFuture(f) @inline implicit def toPimpedFutureBoolean(f: Fu[Boolean]) = new PimpedFutureBoolean(f) diff --git a/modules/swiss/src/main/BsonHandlers.scala b/modules/swiss/src/main/BsonHandlers.scala new file mode 100644 index 0000000000..63b7627e05 --- /dev/null +++ b/modules/swiss/src/main/BsonHandlers.scala @@ -0,0 +1,132 @@ +package lila.swiss + +import chess.Clock.{ Config => ClockConfig } +import chess.variant.Variant +import chess.StartingPosition +import lila.db.BSON +import lila.db.dsl._ +import reactivemongo.api.bson._ + +private object BsonHandlers { + + implicit private[swiss] val statusHandler = tryHandler[Status]( + { case BSONInteger(v) => Status(v) toTry s"No such status: $v" }, + x => BSONInteger(x.id) + ) + + implicit val clockHandler = tryHandler[ClockConfig]( + { + case doc: BSONDocument => + for { + limit <- doc.getAsTry[Int]("limit") + inc <- doc.getAsTry[Int]("increment") + } yield ClockConfig(limit, inc) + }, + c => + BSONDocument( + "limit" -> c.limitSeconds, + "increment" -> c.incrementSeconds + ) + ) + implicit val variantHandler = lila.db.dsl.quickHandler[Variant]( + { + case BSONString(v) => Variant orDefault v + case _ => Variant.default + }, + v => BSONString(v.key) + ) + private lazy val fenIndex: Map[String, StartingPosition] = StartingPosition.all.view.map { p => + p.fen -> p + }.toMap + implicit val startingPositionHandler = lila.db.dsl.quickHandler[StartingPosition]( + { + case BSONString(v) => fenIndex.getOrElse(v, StartingPosition.initial) + case _ => StartingPosition.initial + }, + v => BSONString(v.fen) + ) + + 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 playerHandler = new BSON[SwissPlayer] { + def reads(r: BSON.Reader) = SwissPlayer( + id = r.get[SwissPlayer.Id]("_id"), + userId = r str "uid", + rating = r int "r", + provisional = r boolD "pr" + ) + def writes(w: BSON.Writer, o: SwissPlayer) = $doc( + "_id" -> o.id, + "uid" -> o.userId, + "r" -> o.rating, + "pr" -> w.boolO(o.provisional) + ) + } + + 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( + gameId = r str "g", + white = white, + black = black, + winner = r boolO "w" map { + case true => white + case _ => black + } + ) + } + def writes(w: BSON.Writer, o: SwissPairing) = $doc( + "g" -> o.gameId, + "w" -> o.white, + "b" -> o.black, + "w" -> o.winner.map(o.white ==) + ) + } + + implicit val roundNumberHandler = intAnyValHandler[SwissRound.Number](_.value, SwissRound.Number.apply) + implicit val roundIdHandler = tryHandler[SwissRound.Id]( + { + case BSONString(v) => + (v split ':' match { + case Array(swissId, number) => + number.toIntOption map { n => + SwissRound.Id(Swiss.Id(swissId), SwissRound.Number(n)) + } + case _ => None + }) toTry s"Invalid round ID $v" + }, + id => BSONString(id.toString) + ) + + implicit val roundHandler = new BSON[SwissRound] { + def reads(r: BSON.Reader) = + SwissRound( + id = r.get[SwissRound.Id]("_id"), + pairings = r.get[List[SwissPairing]]("p"), + byes = r.get[List[SwissPlayer.Number]]("b") + ) + def writes(w: BSON.Writer, o: SwissRound) = $doc( + "id" -> o.id, + "p" -> o.pairings, + "b" -> o.byes + ) + } + + implicit val swissIdHandler = stringAnyValHandler[Swiss.Id](_.value, Swiss.Id.apply) + implicit val swissHandler = Macros.handler[Swiss] +} diff --git a/modules/swiss/src/main/Env.scala b/modules/swiss/src/main/Env.scala new file mode 100644 index 0000000000..de16dfc136 --- /dev/null +++ b/modules/swiss/src/main/Env.scala @@ -0,0 +1,19 @@ +package lila.clas + +import com.softwaremill.macwire._ + +import lila.common.config._ + +@Module +final class Env( + db: lila.db.Db, + userRepo: lila.user.UserRepo +)(implicit ec: scala.concurrent.ExecutionContext) { + + private val colls = wire[SwissColls] + +} + +private class SwissColls(db: lila.db.Db) { + val swiss = db(CollName("swiss")) +} diff --git a/modules/swiss/src/main/Status.scala b/modules/swiss/src/main/Status.scala new file mode 100644 index 0000000000..34049af013 --- /dev/null +++ b/modules/swiss/src/main/Status.scala @@ -0,0 +1,22 @@ +package lila.swiss + +sealed abstract private[swiss] class Status(val id: Int) extends Ordered[Status] { + def compare(other: Status) = Integer.compare(id, other.id) + def name = toString + def is(s: Status): Boolean = this == s +} + +private[swiss] object Status { + + case object Created extends Status(10) + case object Started extends Status(20) + case object Finished extends Status(30) + + val all = List(Created, Started, Finished) + + val byId = all map { v => + (v.id, v) + } toMap + + def apply(id: Int): Option[Status] = byId get id +} diff --git a/modules/swiss/src/main/Swiss.scala b/modules/swiss/src/main/Swiss.scala new file mode 100644 index 0000000000..c4b1ea9ef2 --- /dev/null +++ b/modules/swiss/src/main/Swiss.scala @@ -0,0 +1,76 @@ +package lila.swiss + +import org.joda.time.{ DateTime, Duration, Interval } +import ornicar.scalalib.Random +import chess.Clock.{ Config => ClockConfig } +import chess.StartingPosition + +import lila.user.User +import lila.game.Game + +case class Swiss( + _id: Swiss.Id, + name: String, + status: Status, + clock: ClockConfig, + variant: chess.variant.Variant, + position: StartingPosition, + rated: Boolean, + nbRounds: Int, + nbPlayers: Int, + createdAt: DateTime, + createdBy: User.ID, + startsAt: DateTime, + winnerId: Option[User.ID] = None, + description: Option[String] = None, + hasChat: Boolean = true +) {} + +object Swiss { + + case class Id(value: String) extends AnyVal with StringValue + case class Round(value: Int) extends AnyVal with IntValue + + def makeId = Id(scala.util.Random.alphanumeric take 8 mkString) +} + +case class SwissPlayer( + id: SwissPlayer.Id, + userId: User.ID, + rating: Int, + provisional: Boolean +) + +object SwissPlayer { + + case class Id(swissId: Swiss.Id, number: Number) + + case class Number(value: Int) extends AnyVal with IntValue +} + +case class SwissRound( + id: SwissRound.Id, + pairings: List[SwissPairing], + byes: List[SwissPlayer.Number] +) {} + +object SwissRound { + + case class Id(swissId: Swiss.Id, number: Number) { + override def toString = s"$swissId:$number" + } + + case class Number(value: Int) extends AnyVal with IntValue +} + +case class SwissPairing( + gameId: Game.ID, + white: SwissPlayer.Number, + black: SwissPlayer.Number, + winner: Option[SwissPlayer.Number] +) + +object SwissPairing { + + case class Id(value: String) extends AnyVal with StringValue +} diff --git a/modules/swiss/src/main/package.scala b/modules/swiss/src/main/package.scala new file mode 100644 index 0000000000..60ea78b876 --- /dev/null +++ b/modules/swiss/src/main/package.scala @@ -0,0 +1,6 @@ +package lila + +package object swiss extends PackageObject { + + private[swiss] val logger = lila.log("swiss") +} diff --git a/modules/tournament/src/main/Pairing.scala b/modules/tournament/src/main/Pairing.scala index f4da92ad7a..b9e3d0fc1c 100644 --- a/modules/tournament/src/main/Pairing.scala +++ b/modules/tournament/src/main/Pairing.scala @@ -5,7 +5,7 @@ import lila.game.{ Game, IdGenerator } import lila.user.User case class Pairing( - id: Game.ID, // game Id + id: Game.ID, tourId: Tournament.ID, status: chess.Status, user1: User.ID,