lila/modules/game/src/main/Game.scala

542 lines
16 KiB
Scala
Raw Normal View History

2013-03-22 10:36:09 -06:00
package lila.game
2013-05-24 11:04:49 -06:00
import chess.Color._
2013-05-31 09:55:11 -06:00
import chess.Pos.piotr, chess.Role.forsyth
2014-02-17 02:12:19 -07:00
import chess.{ History => ChessHistory, Castles, Role, Board, Move, Pos, Game => ChessGame, Clock, Status, Color, Piece, Variant, Mode, PositionHash }
2013-12-04 12:32:29 -07:00
import com.github.nscala_time.time.Imports._
2013-12-05 12:40:11 -07:00
import org.joda.time.DateTime
2013-09-30 15:10:42 -06:00
2013-12-01 05:01:17 -07:00
import lila.db.ByteArray
2013-05-24 11:04:49 -06:00
import lila.user.User
2013-03-22 10:36:09 -06:00
case class Game(
id: String,
whitePlayer: Player,
blackPlayer: Player,
2013-12-01 05:01:17 -07:00
binaryPieces: ByteArray,
binaryPgn: ByteArray,
2013-03-22 10:36:09 -06:00
status: Status,
2014-03-05 13:11:55 -07:00
turns: Int, // = ply
startedAtTurn: Int,
2013-03-22 10:36:09 -06:00
clock: Option[Clock],
castleLastMoveTime: CastleLastMoveTime,
2013-12-05 12:40:11 -07:00
positionHashes: PositionHash = Array(),
2014-05-08 01:35:20 -06:00
binaryMoveTimes: ByteArray = ByteArray.empty, // tenths of seconds
2013-03-22 10:36:09 -06:00
mode: Mode = Mode.default,
variant: Variant = Variant.default,
next: Option[String] = None,
bookmarks: Int = 0,
createdAt: DateTime = DateTime.now,
updatedAt: Option[DateTime] = None,
2013-12-05 12:40:11 -07:00
metadata: Metadata) {
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
val players = List(whitePlayer, blackPlayer)
val playersByColor: Map[Color, Player] = Map(
White -> whitePlayer,
Black -> blackPlayer
)
def player(color: Color): Player = color match {
2014-02-17 02:12:19 -07:00
case White => whitePlayer
case Black => blackPlayer
2013-03-22 11:53:13 -06:00
}
def player(playerId: String): Option[Player] =
players find (_.id == playerId)
def player(user: User): Option[Player] =
players find (_ isUser user)
2014-02-17 02:12:19 -07:00
def player(c: Color.type => Color): Player = player(c(Color))
2013-03-22 11:53:13 -06:00
def isPlayerFullId(player: Player, fullId: String): Boolean =
(fullId.size == Game.fullIdSize) && player.id == (fullId drop 8)
def player: Player = player(turnColor)
def opponent(p: Player): Player = opponent(p.color)
def opponent(c: Color): Player = player(!c)
2013-12-05 14:47:10 -07:00
private lazy val firstColor = (whitePlayer before blackPlayer).fold(White, Black)
def firstPlayer = player(firstColor)
def secondPlayer = player(!firstColor)
2013-03-22 11:53:13 -06:00
def turnColor = Color(0 == turns % 2)
def turnOf(p: Player): Boolean = p == player
def turnOf(c: Color): Boolean = c == turnColor
def turnOf(u: User): Boolean = player(u) ?? turnOf
2013-03-22 11:53:13 -06:00
2014-03-05 13:11:55 -07:00
def playedTurns = turns - startedAtTurn
2013-03-22 11:53:13 -06:00
def fullIdOf(player: Player): Option[String] =
(players contains player) option id + player.id
def fullIdOf(color: Color): String = id + player(color).id
2013-12-05 12:40:11 -07:00
def tournamentId = metadata.tournamentId
2013-03-22 11:53:13 -06:00
def isTournament = tournamentId.isDefined
def nonTournament = tournamentId.isEmpty
def hasChat = nonTournament && nonAi
2013-12-05 12:40:11 -07:00
// in tenths
private def lastMoveTime: Option[Long] = castleLastMoveTime.lastMoveTime map {
_.toLong + (createdAt.getMillis / 100)
}
2014-02-17 02:12:19 -07:00
def lastMoveTimeInSeconds: Option[Int] = lastMoveTime.map(x => (x / 10).toInt)
2013-12-05 12:40:11 -07:00
2014-05-08 01:35:20 -06:00
// in tenths of seconds
lazy val moveTimes: Vector[Int] = BinaryFormat.moveTime read binaryMoveTimes take turns
2013-12-05 12:40:11 -07:00
def moveTimesInSeconds: Vector[Float] = moveTimes.map(_.toFloat / 10)
2013-12-04 12:08:31 -07:00
lazy val pgnMoves: PgnMoves = BinaryFormat.pgn read binaryPgn
2013-03-22 11:53:13 -06:00
lazy val toChess: ChessGame = {
val (pieces, deads) = BinaryFormat.piece read binaryPieces
2013-03-22 11:53:13 -06:00
ChessGame(
board = Board(pieces, toChessHistory, variant),
player = Color(0 == turns % 2),
clock = clock,
deads = deads,
turns = turns,
startedAtTurn = startedAtTurn,
pgnMoves = pgnMoves)
2013-03-22 11:53:13 -06:00
}
lazy val toChessHistory = ChessHistory(
lastMove = castleLastMoveTime.lastMove,
castles = castleLastMoveTime.castles,
2013-03-22 11:53:13 -06:00
positionHashes = positionHashes)
def update(
game: ChessGame,
move: Move,
blur: Boolean = false): Progress = {
2013-03-22 11:53:13 -06:00
val (history, situation) = (game.board.history, game.situation)
val events = (players collect {
2014-02-17 02:12:19 -07:00
case p if p.isHuman => Event.possibleMoves(situation, p.color)
}) :::
Event.State(situation.color, game.turns) ::
(Event fromMove move) :::
(Event fromSituation situation)
2013-03-22 11:53:13 -06:00
def copyPlayer(player: Player) = player.copy(
2013-12-05 12:40:11 -07:00
blurs = player.blurs + (blur && move.color == player.color).fold(1, 0)
2013-03-22 11:53:13 -06:00
)
val updated = copy(
whitePlayer = copyPlayer(whitePlayer),
blackPlayer = copyPlayer(blackPlayer),
binaryPieces = BinaryFormat.piece write game.allPieces,
binaryPgn = BinaryFormat.pgn write game.pgnMoves,
2013-03-22 11:53:13 -06:00
turns = game.turns,
2013-12-05 12:40:11 -07:00
positionHashes = history.positionHashes,
castleLastMoveTime = CastleLastMoveTime(
castles = history.castles,
lastMove = history.lastMove,
lastMoveTime = Some(((nowMillis - createdAt.getMillis) / 100).toInt),
check = situation.kingPos ifTrue situation.check),
2014-05-08 01:35:20 -06:00
binaryMoveTimes = isPgnImport.fold(
ByteArray.empty,
BinaryFormat.moveTime write lastMoveTime.fold(Vector(0)) { lmt => moveTimes :+ (nowTenths - lmt).toInt }
2013-12-05 12:40:11 -07:00
),
2013-03-22 11:53:13 -06:00
status = situation.status | status,
clock = game.clock)
2013-03-22 11:53:13 -06:00
2014-02-17 02:12:19 -07:00
val finalEvents = events ::: updated.clock.??(c => List(Event.Clock(c))) ::: {
2013-12-05 12:40:11 -07:00
(updated.playable && (
2014-02-17 02:12:19 -07:00
abortable != updated.abortable || (Color.all exists { color =>
2013-12-05 12:40:11 -07:00
playerCanOfferDraw(color) != updated.playerCanOfferDraw(color)
})
)).??(Color.all map Event.ReloadTable)
}
2013-03-22 10:36:09 -06:00
Progress(this, updated, finalEvents)
2013-03-22 11:53:13 -06:00
}
def check = castleLastMoveTime.check
2014-02-17 02:12:19 -07:00
def updatePlayer(color: Color, f: Player => Player) = color.fold(
2013-10-03 04:18:40 -06:00
copy(whitePlayer = f(whitePlayer)),
copy(blackPlayer = f(blackPlayer)))
2013-03-22 11:53:13 -06:00
2014-02-17 02:12:19 -07:00
def updatePlayers(f: Player => Player) = copy(
2013-03-22 11:53:13 -06:00
whitePlayer = f(whitePlayer),
2013-10-03 04:18:40 -06:00
blackPlayer = f(blackPlayer))
2013-03-22 11:53:13 -06:00
def start = started.fold(this, copy(
status = Status.Started,
2013-12-20 06:13:38 -07:00
mode = Mode(mode.rated && userIds.distinct.size == 2),
2013-03-22 11:53:13 -06:00
updatedAt = DateTime.now.some
))
def startClock(compensation: Float) = copy(
clock = clock map {
2014-02-17 02:12:19 -07:00
case c: chess.PausedClock => c.start.giveTime(White, compensation)
case c => c
}
)
2013-03-22 11:53:13 -06:00
def started = status >= Status.Started
def notStarted = !started
2013-12-24 08:12:53 -07:00
def joinable = notStarted && !imported
2013-03-22 11:53:13 -06:00
def aborted = status == Status.Aborted
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def playable = status < Status.Aborted
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def playableBy(p: Player): Boolean = playable && turnOf(p)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def playableBy(c: Color): Boolean = playableBy(player(c))
2013-03-22 10:36:09 -06:00
2013-05-18 13:14:57 -06:00
def playableByAi: Boolean = playable && player.isAi
2013-03-22 11:53:13 -06:00
def continuable = status != Status.Mate && status != Status.Stalemate
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def aiLevel: Option[Int] = players find (_.isAi) flatMap (_.aiLevel)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def hasAi: Boolean = players exists (_.isAi)
def nonAi = !hasAi
2013-03-22 10:36:09 -06:00
2014-02-17 02:12:19 -07:00
def mapPlayers(f: Player => Player) = copy(
2013-03-22 11:53:13 -06:00
whitePlayer = f(whitePlayer),
blackPlayer = f(blackPlayer)
)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def playerCanOfferDraw(color: Color) =
started && playable &&
turns >= 2 &&
!player(color).isOfferingDraw &&
!(opponent(color).isAi) &&
!(playerHasOfferedDraw(color))
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def playerHasOfferedDraw(color: Color) =
player(color).lastDrawOffer ?? (_ >= turns - 1)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def playerCanRematch(color: Color) =
!player(color).isOfferingRematch &&
finishedOrAborted &&
nonTournament
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def playerCanProposeTakeback(color: Color) =
started && playable && nonTournament &&
bothPlayersHaveMoved &&
2013-05-24 11:04:49 -06:00
!player(color).isProposingTakeback &&
2013-03-22 11:53:13 -06:00
!opponent(color).isProposingTakeback
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def moretimeable = playable && nonTournament && hasClock
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def abortable = status == Status.Started && turns < 2 && nonTournament
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def resignable = playable && !abortable
2013-10-09 08:46:00 -06:00
def drawable = playable && !abortable
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def finish(status: Status, winner: Option[Color]) = Progress(
this,
copy(
status = status,
whitePlayer = whitePlayer finish (winner == Some(White)),
blackPlayer = blackPlayer finish (winner == Some(Black)),
clock = clock map (_.stop)
),
2014-02-17 02:12:19 -07:00
List(Event.End) ::: clock.??(c => List(Event.Clock(c)))
2013-03-22 11:53:13 -06:00
)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def rated = mode.rated
2014-05-01 06:08:54 -06:00
def casual = !rated
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def finished = status >= Status.Mate
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def finishedOrAborted = finished || aborted
2013-03-22 10:36:09 -06:00
2014-02-02 05:07:00 -07:00
def replayable = imported || finished
def analysable = replayable && !fromPosition && turns > 4
2014-02-27 17:18:22 -07:00
def fromPosition = source ?? (Source.Position==)
def imported = source exists (_ == Source.Import)
2013-03-22 11:53:13 -06:00
def winner = players find (_.wins)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def loser = winner map opponent
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def winnerColor: Option[Color] = winner map (_.color)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def winnerUserId: Option[String] = winner flatMap (_.userId)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def loserUserId: Option[String] = loser flatMap (_.userId)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def wonBy(c: Color): Option[Boolean] = winnerColor map (_ == c)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def outoftimePlayer: Option[Player] = for {
c clock
if started && playable && (bothPlayersHaveMoved || isTournament)
if (!c.isRunning && !c.isInit) || (c outoftime player.color)
2013-03-22 11:53:13 -06:00
} yield player
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def hasClock = clock.isDefined
2013-03-22 10:36:09 -06:00
2013-10-09 07:26:17 -06:00
def isClockRunning = clock ?? (_.isRunning)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def withClock(c: Clock) = Progress(this, copy(clock = Some(c)))
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def estimateTotalTime = clock.fold(1200)(_.estimateTotalTime)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def playerWhoDidNotMove: Option[Player] = turns match {
2014-02-17 02:12:19 -07:00
case 0 => player(White).some
case 1 => player(Black).some
case _ => none
2013-03-22 11:53:13 -06:00
}
2013-03-22 10:36:09 -06:00
2013-10-09 07:26:17 -06:00
def onePlayerHasMoved = turns > 0
2013-03-22 11:53:13 -06:00
def bothPlayersHaveMoved = turns > 1
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def playerMoves(color: Color): Int = (turns + color.fold(1, 0)) / 2
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def playerHasMoved(color: Color) = playerMoves(color) > 0
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def playerBlurPercent(color: Color): Int = (turns > 5).fold(
(player(color).blurs * 100) / playerMoves(color),
0
)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def deadPiecesOf(color: Color): List[Role] = toChess.deads collect {
2014-02-17 02:12:19 -07:00
case piece if piece is color => piece.role
2013-03-22 11:53:13 -06:00
}
2013-03-22 10:36:09 -06:00
2013-12-01 05:01:17 -07:00
def isBeingPlayed = !finishedOrAborted && !olderThan(60)
2013-08-01 08:44:38 -06:00
def olderThan(seconds: Int) = updatedAt.??(_ < DateTime.now - seconds.seconds)
2013-03-22 10:36:09 -06:00
2013-09-26 03:05:30 -06:00
def abandoned = (status <= Status.Started) && (updatedAt | createdAt) < Game.abandonedDate
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def hasBookmarks = bookmarks > 0
2013-03-22 10:36:09 -06:00
2014-02-12 12:24:44 -07:00
def showBookmarks = hasBookmarks ?? bookmarks.toString
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def userIds = playerMaps(_.userId)
2013-03-22 10:36:09 -06:00
2013-12-17 15:20:18 -07:00
def userRatings = playerMaps(_.rating)
2013-03-22 10:36:09 -06:00
2013-12-17 15:20:18 -07:00
def averageUsersRating = userRatings match {
2014-02-17 02:12:19 -07:00
case a :: b :: Nil => Some((a + b) / 2)
case a :: Nil => Some((a + 1200) / 2)
case _ => None
2013-03-22 11:53:13 -06:00
}
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def withTournamentId(id: String) = this.copy(
2013-12-05 12:40:11 -07:00
metadata = metadata.copy(tournamentId = id.some)
)
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def withId(newId: String) = this.copy(id = newId)
2013-03-22 10:36:09 -06:00
2013-12-05 12:40:11 -07:00
def source = metadata.source
2013-03-22 10:36:09 -06:00
2013-12-05 12:40:11 -07:00
def pgnImport = metadata.pgnImport
2013-03-22 10:36:09 -06:00
2013-03-22 11:53:13 -06:00
def isPgnImport = pgnImport.isDefined
2013-03-22 10:36:09 -06:00
def resetTurns = copy(turns = 0)
2014-02-17 02:12:19 -07:00
private def playerMaps[A](f: Player => Option[A]): List[A] = players.map(f).flatten
2013-03-22 10:36:09 -06:00
}
object Game {
val gameIdSize = 8
val playerIdSize = 4
val fullIdSize = 12
val tokenSize = 4
2013-09-26 03:05:30 -06:00
def abandonedDate = DateTime.now - 7.days
2013-03-22 10:36:09 -06:00
def takeGameId(fullId: String) = fullId take gameIdSize
2013-05-17 18:38:39 -06:00
def takePlayerId(fullId: String) = fullId drop gameIdSize
2013-03-22 10:36:09 -06:00
2013-05-07 17:44:26 -06:00
def make(
game: ChessGame,
whitePlayer: Player,
blackPlayer: Player,
mode: Mode,
variant: Variant,
source: Source,
pgnImport: Option[PgnImport]): Game = Game(
id = IdGenerator.game,
2013-12-01 02:01:20 -07:00
whitePlayer = whitePlayer,
blackPlayer = blackPlayer,
binaryPieces = if (game.isStandardInit) BinaryFormat.piece.standard
else BinaryFormat.piece write game.allPieces,
binaryPgn = ByteArray.empty,
2013-05-07 17:44:26 -06:00
status = Status.Created,
turns = game.turns,
startedAtTurn = game.startedAtTurn,
2013-05-07 17:44:26 -06:00
clock = game.clock,
castleLastMoveTime = CastleLastMoveTime.init,
2013-05-07 17:44:26 -06:00
mode = mode,
variant = variant,
metadata = Metadata(
2013-08-02 03:09:13 -06:00
source = source.some,
pgnImport = pgnImport,
tournamentId = none,
2013-12-05 12:40:11 -07:00
tvAt = none),
2013-05-07 17:44:26 -06:00
createdAt = DateTime.now)
2013-03-22 11:53:13 -06:00
2013-12-03 13:31:31 -07:00
private[game] lazy val tube = lila.db.BsTube(gameBSONHandler)
2013-12-02 16:44:09 -07:00
import reactivemongo.bson._
import lila.db.BSON
import Player.playerBSONHandler
2013-12-05 12:40:11 -07:00
import PgnImport.pgnImportBSONHandler
2013-12-02 16:44:09 -07:00
import CastleLastMoveTime.castleLastMoveTimeBSONHandler
2013-12-03 13:31:31 -07:00
object BSONFields {
2013-12-02 16:44:09 -07:00
val id = "_id"
2013-12-04 17:32:37 -07:00
val whitePlayer = "p0"
val blackPlayer = "p1"
val playerIds = "is"
2013-12-11 01:56:11 -07:00
val playerUids = "us"
2013-12-02 16:44:09 -07:00
val binaryPieces = "ps"
val binaryPgn = "pg"
2013-12-02 16:44:09 -07:00
val status = "s"
val turns = "t"
val startedAtTurn = "st"
2013-12-02 16:44:09 -07:00
val clock = "c"
val positionHashes = "ph"
val castleLastMoveTime = "cl"
2013-12-05 12:40:11 -07:00
val moveTimes = "mt"
2013-12-02 16:44:09 -07:00
val rated = "ra"
val variant = "v"
2013-12-11 13:35:29 -07:00
val next = "ne"
2013-12-02 16:44:09 -07:00
val bookmarks = "bm"
val createdAt = "ca"
val updatedAt = "ua"
2013-12-05 12:40:11 -07:00
val source = "so"
val pgnImport = "pgni"
val tournamentId = "tid"
val tvAt = "tv"
val winnerColor = "w"
2013-12-09 12:29:44 -07:00
val winnerId = "wid"
2013-12-03 13:31:31 -07:00
}
implicit val gameBSONHandler = new BSON[Game] {
import BSONFields._
2013-12-02 16:44:09 -07:00
2013-12-11 01:56:11 -07:00
private val emptyPlayerBuilder = playerBSONHandler.read(BSONDocument())
2013-12-02 16:44:09 -07:00
def reads(r: BSON.Reader): Game = {
2013-12-04 12:08:31 -07:00
val nbTurns = r int turns
val winC = r boolO winnerColor map Color.apply
val (whiteId, blackId) = r str playerIds splitAt 4
2013-12-11 01:56:11 -07:00
val uids = ~r.getO[List[String]](playerUids)
val (whiteUid, blackUid) = (uids.headOption.filter(_.nonEmpty), uids.lift(1).filter(_.nonEmpty))
def player(field: String, color: Color, id: Player.Id, uid: Player.UserId): Player = {
val builder = r.getO[Player.Builder](field)(playerBSONHandler) | emptyPlayerBuilder
val win = winC map (_ == color)
builder(color)(id)(uid)(win)
}
2013-12-02 16:44:09 -07:00
Game(
id = r str id,
2013-12-11 01:56:11 -07:00
whitePlayer = player(whitePlayer, White, whiteId, whiteUid),
blackPlayer = player(blackPlayer, Black, blackId, blackUid),
2013-12-02 16:44:09 -07:00
binaryPieces = r bytes binaryPieces,
2013-12-09 12:29:44 -07:00
binaryPgn = r bytesD binaryPgn,
2013-12-02 16:44:09 -07:00
status = Status(r int status) err "Invalid status",
2013-12-04 12:08:31 -07:00
turns = nbTurns,
startedAtTurn = r intD startedAtTurn,
2014-02-17 02:12:19 -07:00
clock = r.getO[Color => Clock](clock) map (_(Color(0 == nbTurns % 2))),
2013-12-05 12:40:11 -07:00
positionHashes = r.bytesD(positionHashes).value,
2013-12-02 16:44:09 -07:00
castleLastMoveTime = r.get[CastleLastMoveTime](castleLastMoveTime)(castleLastMoveTimeBSONHandler),
2014-05-08 01:35:20 -06:00
binaryMoveTimes = (r bytesO moveTimes) | ByteArray.empty,
2013-12-02 16:44:09 -07:00
mode = Mode(r boolD rated),
2013-12-05 12:40:11 -07:00
variant = Variant(r intD variant) | Variant.Standard,
2013-12-02 16:44:09 -07:00
next = r strO next,
bookmarks = r intD bookmarks,
createdAt = r date createdAt,
updatedAt = r dateO updatedAt,
2013-12-05 12:40:11 -07:00
metadata = Metadata(
source = r intO source flatMap Source.apply,
pgnImport = r.getO[PgnImport](pgnImport)(PgnImport.pgnImportBSONHandler),
tournamentId = r strO tournamentId,
tvAt = r dateO tvAt)
)
2013-12-02 16:44:09 -07:00
}
def writes(w: BSON.Writer, o: Game) = BSONDocument(
id -> o.id,
playerIds -> (o.whitePlayer.id + o.blackPlayer.id),
2013-12-11 13:14:21 -07:00
playerUids -> w.listO(List(~o.whitePlayer.userId, ~o.blackPlayer.userId)),
2014-02-17 02:12:19 -07:00
whitePlayer -> w.docO(playerBSONHandler write ((_: Color) => (_: Player.Id) => (_: Player.UserId) => (_: Player.Win) => o.whitePlayer)),
blackPlayer -> w.docO(playerBSONHandler write ((_: Color) => (_: Player.Id) => (_: Player.UserId) => (_: Player.Win) => o.blackPlayer)),
2013-12-02 16:44:09 -07:00
binaryPieces -> o.binaryPieces,
2013-12-09 12:29:44 -07:00
binaryPgn -> w.byteArrayO(o.binaryPgn),
2013-12-02 16:44:09 -07:00
status -> o.status.id,
turns -> o.turns,
startedAtTurn -> w.intO(o.startedAtTurn),
2014-02-17 02:12:19 -07:00
clock -> (o.clock map { c => clockBSONHandler.write(_ => c) }),
2013-12-05 12:40:11 -07:00
positionHashes -> w.bytesO(o.positionHashes),
2013-12-02 16:44:09 -07:00
castleLastMoveTime -> castleLastMoveTimeBSONHandler.write(o.castleLastMoveTime),
2013-12-05 12:40:11 -07:00
moveTimes -> (BinaryFormat.moveTime write o.moveTimes),
2013-12-02 16:44:09 -07:00
rated -> w.boolO(o.mode.rated),
2013-12-05 14:47:10 -07:00
variant -> o.variant.exotic.option(o.variant.id).map(w.int),
2013-12-02 16:44:09 -07:00
next -> o.next,
bookmarks -> w.intO(o.bookmarks),
createdAt -> w.date(o.createdAt),
updatedAt -> o.updatedAt.map(w.date),
2013-12-05 12:40:11 -07:00
source -> o.metadata.source.map(_.id),
pgnImport -> o.metadata.pgnImport,
tournamentId -> o.metadata.tournamentId,
tvAt -> o.metadata.tvAt.map(w.date)
)
2013-12-02 16:44:09 -07:00
}
2013-12-04 12:08:31 -07:00
import lila.db.ByteArray.ByteArrayBSONHandler
2014-02-17 02:12:19 -07:00
implicit val clockBSONHandler = new BSONHandler[BSONBinary, Color => Clock] {
2013-12-04 12:08:31 -07:00
def read(bin: BSONBinary) = BinaryFormat.clock read {
ByteArrayBSONHandler read bin
}
2014-02-17 02:12:19 -07:00
def write(clock: Color => Clock) = ByteArrayBSONHandler write {
2013-12-04 12:08:31 -07:00
BinaryFormat.clock write clock(chess.White)
}
}
2013-03-22 10:36:09 -06:00
}
case class CastleLastMoveTime(
castles: Castles,
lastMove: Option[(Pos, Pos)],
lastMoveTime: Option[Int], // tenths of seconds since game creation
check: Option[Pos]) {
2014-02-17 02:12:19 -07:00
def lastMoveString = lastMove map { case (a, b) => a.toString + b.toString }
}
object CastleLastMoveTime {
def init = CastleLastMoveTime(Castles.all, None, None, None)
2013-03-22 10:36:09 -06:00
2013-12-02 16:44:09 -07:00
import reactivemongo.bson._
import lila.db.ByteArray.ByteArrayBSONHandler
2013-03-22 10:36:09 -06:00
2013-12-02 16:44:09 -07:00
implicit val castleLastMoveTimeBSONHandler = new BSONHandler[BSONBinary, CastleLastMoveTime] {
def read(bin: BSONBinary) = BinaryFormat.castleLastMoveTime read {
ByteArrayBSONHandler read bin
}
def write(clmt: CastleLastMoveTime) = ByteArrayBSONHandler write {
BinaryFormat.castleLastMoveTime write clmt
}
}
2013-03-22 10:36:09 -06:00
}