2013-03-22 10:36:09 -06:00
|
|
|
package lila.game
|
|
|
|
|
2014-11-17 15:40:10 -07:00
|
|
|
import chess.Color.{ White, Black }
|
2013-05-31 09:55:11 -06:00
|
|
|
import chess.Pos.piotr, chess.Role.forsyth
|
2015-01-11 07:23:25 -07:00
|
|
|
import chess.{ History => ChessHistory, CheckCount, Castles, Role, Board, Move, Pos, Game => ChessGame, Clock, Status, Color, Piece, Mode, PositionHash }
|
|
|
|
import chess.variant.Variant
|
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
|
2014-07-31 16:45:20 -06:00
|
|
|
import lila.rating.PerfType
|
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,
|
2013-12-05 16:53:18 -07:00
|
|
|
binaryPgn: ByteArray,
|
2013-03-22 10:36:09 -06:00
|
|
|
status: Status,
|
2014-03-05 13:11:55 -07:00
|
|
|
turns: Int, // = ply
|
2014-02-19 16:16:33 -07:00
|
|
|
startedAtTurn: Int,
|
2013-03-22 10:36:09 -06:00
|
|
|
clock: Option[Clock],
|
2013-12-01 17:38:03 -07:00
|
|
|
castleLastMoveTime: CastleLastMoveTime,
|
2014-11-30 03:22:23 -07:00
|
|
|
daysPerTurn: Option[Int],
|
2013-12-05 12:40:11 -07:00
|
|
|
positionHashes: PositionHash = Array(),
|
2014-07-30 13:37:50 -06:00
|
|
|
checkCount: CheckCount = CheckCount(0, 0),
|
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)
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
2014-05-20 13:36:47 -06:00
|
|
|
def playerByUserId(userId: String): Option[Player] = players find (_.userId == Some(userId))
|
|
|
|
|
2013-03-22 11:53:13 -06:00
|
|
|
def opponent(p: Player): Player = opponent(p.color)
|
|
|
|
|
|
|
|
def opponent(c: Color): Player = player(!c)
|
|
|
|
|
2014-10-31 11:04:57 -06:00
|
|
|
lazy val firstColor = (whitePlayer before blackPlayer).fold(White, Black)
|
2013-12-05 14:47:10 -07:00
|
|
|
def firstPlayer = player(firstColor)
|
|
|
|
def secondPlayer = player(!firstColor)
|
|
|
|
|
2013-03-22 11:53:13 -06:00
|
|
|
def turnColor = Color(0 == turns % 2)
|
|
|
|
|
2014-04-18 01:53:58 -06:00
|
|
|
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
|
2014-10-12 16:31:15 -06:00
|
|
|
def isMandatory = isTournament
|
2014-06-09 11:51:02 -06:00
|
|
|
def nonMandatory = !isMandatory
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2014-06-09 11:51:02 -06:00
|
|
|
def hasChat = !isTournament && nonAi
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2013-12-05 12:40:11 -07:00
|
|
|
// in tenths
|
|
|
|
private def lastMoveTime: Option[Long] = castleLastMoveTime.lastMoveTime map {
|
|
|
|
_.toLong + (createdAt.getMillis / 100)
|
2014-12-24 06:58:33 -07:00
|
|
|
} orElse updatedAt.map(_.getMillis / 100)
|
|
|
|
|
2014-11-30 03:22:23 -07:00
|
|
|
private def lastMoveTimeDate: Option[DateTime] = castleLastMoveTime.lastMoveTime map { lmt =>
|
|
|
|
createdAt plusMillis (lmt * 100)
|
2014-12-24 06:58:33 -07:00
|
|
|
} orElse updatedAt
|
2014-11-30 03:22:23 -07:00
|
|
|
|
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
|
2014-12-02 03:16:25 -07:00
|
|
|
lazy val moveTimes: Vector[Int] = BinaryFormat.moveTime read binaryMoveTimes take playedTurns
|
2014-05-08 01:35:20 -06:00
|
|
|
|
2013-12-05 12:40:11 -07:00
|
|
|
def moveTimesInSeconds: Vector[Float] = moveTimes.map(_.toFloat / 10)
|
2013-12-04 12:08:31 -07:00
|
|
|
|
2014-08-03 00:08:30 -06:00
|
|
|
/**
|
|
|
|
* Fullmove number: The number of the full move.
|
|
|
|
* It starts at 1, and is incremented after Black's move.
|
|
|
|
* NOTE: Duplicates chess.Game.fullMoveNumber (avoids loading toChess)
|
|
|
|
*/
|
|
|
|
def fullMoveNumber: Int = 1 + turns / 2
|
|
|
|
|
2013-12-05 16:53:18 -07:00
|
|
|
lazy val pgnMoves: PgnMoves = BinaryFormat.pgn read binaryPgn
|
|
|
|
|
2014-08-02 09:47:49 -06:00
|
|
|
def openingPgnMoves(nb: Int): PgnMoves = BinaryFormat.pgn.read(binaryPgn, nb)
|
|
|
|
|
2013-03-22 11:53:13 -06:00
|
|
|
lazy val toChess: ChessGame = {
|
|
|
|
|
2014-10-22 16:27:26 -06:00
|
|
|
val pieces = 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,
|
2013-12-05 16:53:18 -07:00
|
|
|
turns = turns,
|
2014-02-19 16:16:33 -07:00
|
|
|
startedAtTurn = startedAtTurn,
|
2013-12-05 16:53:18 -07:00
|
|
|
pgnMoves = pgnMoves)
|
2013-03-22 11:53:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
lazy val toChessHistory = ChessHistory(
|
2013-12-01 17:38:03 -07:00
|
|
|
lastMove = castleLastMoveTime.lastMove,
|
|
|
|
castles = castleLastMoveTime.castles,
|
2014-07-30 13:37:50 -06:00
|
|
|
positionHashes = positionHashes,
|
|
|
|
checkCount = checkCount)
|
2013-03-22 11:53:13 -06:00
|
|
|
|
|
|
|
def update(
|
|
|
|
game: ChessGame,
|
|
|
|
move: Move,
|
2013-12-05 16:53:18 -07:00
|
|
|
blur: Boolean = false): Progress = {
|
2013-03-22 11:53:13 -06:00
|
|
|
val (history, situation) = (game.board.history, game.situation)
|
2013-10-03 04:18:13 -06:00
|
|
|
|
|
|
|
val events = (players collect {
|
2014-02-17 02:12:19 -07:00
|
|
|
case p if p.isHuman => Event.possibleMoves(situation, p.color)
|
2013-10-03 04:18:13 -06:00
|
|
|
}) :::
|
|
|
|
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),
|
2014-10-22 16:27:26 -06:00
|
|
|
binaryPieces = BinaryFormat.piece write game.board.pieces,
|
2013-12-05 16:53:18 -07:00
|
|
|
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,
|
2014-07-30 13:37:50 -06:00
|
|
|
checkCount = history.checkCount,
|
2013-12-01 17:38:03 -07:00
|
|
|
castleLastMoveTime = CastleLastMoveTime(
|
|
|
|
castles = history.castles,
|
|
|
|
lastMove = history.lastMove,
|
2013-12-09 15:04:53 -07:00
|
|
|
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,
|
2013-12-09 15:04:53 -07:00
|
|
|
clock = game.clock)
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2014-11-30 03:22:23 -07:00
|
|
|
val clockEvent = updated.clock map Event.Clock.apply orElse {
|
|
|
|
updated.correspondenceClock map Event.CorrespondenceClock.apply
|
|
|
|
}
|
|
|
|
|
|
|
|
val finalEvents = events ::: clockEvent.toList ::: {
|
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)
|
|
|
|
})
|
2014-10-19 09:13:10 -06:00
|
|
|
)) ?? List(Event.Reload)
|
2014-07-31 13:06:22 -06:00
|
|
|
} ::: {
|
|
|
|
// abstraction leak, I know.
|
|
|
|
(updated.variant.threeCheck && situation.check) ?? List(Event.CheckCount(
|
|
|
|
white = updated.checkCount.white,
|
|
|
|
black = updated.checkCount.black
|
|
|
|
))
|
2013-12-05 12:40:11 -07:00
|
|
|
}
|
2013-03-22 10:36:09 -06:00
|
|
|
|
2013-12-09 15:04:53 -07:00
|
|
|
Progress(this, updated, finalEvents)
|
2013-03-22 11:53:13 -06:00
|
|
|
}
|
|
|
|
|
2013-12-09 15:04:53 -07: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
|
|
|
|
))
|
|
|
|
|
2014-02-02 08:49:48 -07:00
|
|
|
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
|
2014-02-02 08:49:48 -07:00
|
|
|
}
|
|
|
|
)
|
2013-03-22 11:53:13 -06:00
|
|
|
|
2014-11-30 04:30:39 -07:00
|
|
|
def correspondenceClock: Option[CorrespondenceClock] =
|
|
|
|
daysPerTurn ifTrue (playable && bothPlayersHaveMoved) map { days =>
|
|
|
|
val increment = days * 24 * 60 * 60
|
|
|
|
val secondsLeft = lastMoveTimeDate.fold(increment) { lmd =>
|
2014-11-30 06:03:09 -07:00
|
|
|
(lmd.getSeconds + increment - nowSeconds).toInt max 0
|
2014-11-30 04:30:39 -07:00
|
|
|
}
|
|
|
|
CorrespondenceClock(
|
|
|
|
increment = increment,
|
|
|
|
whiteTime = turnColor.fold(secondsLeft, increment),
|
|
|
|
blackTime = turnColor.fold(increment, secondsLeft))
|
2014-11-30 03:22:23 -07:00
|
|
|
}
|
|
|
|
|
2014-07-26 08:06:32 -06:00
|
|
|
def speed = chess.Speed(clock)
|
|
|
|
|
2014-12-30 08:34:50 -07:00
|
|
|
lazy val perfType = PerfType(PerfPicker.key(this))
|
2014-07-31 16:45:20 -06:00
|
|
|
|
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) =
|
2013-08-02 07:23:25 -06:00
|
|
|
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) =
|
2013-05-18 11:36:13 -06:00
|
|
|
!player(color).isOfferingRematch &&
|
|
|
|
finishedOrAborted &&
|
2014-06-09 11:51:02 -06:00
|
|
|
nonMandatory
|
2013-03-22 10:36:09 -06:00
|
|
|
|
2013-03-22 11:53:13 -06:00
|
|
|
def playerCanProposeTakeback(color: Color) =
|
2014-06-09 11:51:02 -06:00
|
|
|
started && playable && !isTournament &&
|
2013-03-22 11:53:13 -06:00
|
|
|
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
|
|
|
|
2014-06-09 11:51:02 -06:00
|
|
|
def moretimeable = playable && nonMandatory && hasClock
|
2013-03-22 10:36:09 -06:00
|
|
|
|
2014-12-02 03:16:25 -07:00
|
|
|
def abortable = status == Status.Started && playedTurns < 2 && nonMandatory
|
2013-03-22 10:36:09 -06:00
|
|
|
|
2015-01-10 16:18:39 -07:00
|
|
|
def berserkable = status == Status.Started && playedTurns < 2
|
|
|
|
|
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-12-02 03:16:25 -07:00
|
|
|
def accountable = playedTurns >= 2
|
2014-06-12 11:37:54 -06:00
|
|
|
|
2014-02-02 05:07:00 -07:00
|
|
|
def replayable = imported || finished
|
|
|
|
|
2014-12-02 03:16:25 -07:00
|
|
|
def analysable = replayable && playedTurns > 4 && Game.analysableVariants(variant)
|
2014-02-27 17:18:22 -07:00
|
|
|
|
|
|
|
def fromPosition = source ?? (Source.Position==)
|
2013-10-24 08:58:22 -06:00
|
|
|
|
|
|
|
def imported = source exists (_ == Source.Import)
|
2013-07-03 04:03:09 -06:00
|
|
|
|
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
|
|
|
|
2014-11-30 06:03:09 -07:00
|
|
|
def outoftimePlayer: Option[Player] =
|
|
|
|
outoftimePlayerClock orElse outoftimePlayerCorrespondence
|
|
|
|
|
|
|
|
private def outoftimePlayerClock: Option[Player] = for {
|
2013-03-22 11:53:13 -06:00
|
|
|
c ← clock
|
2014-06-22 09:00:04 -06:00
|
|
|
if started && playable && bothPlayersHaveMoved
|
2014-02-19 15:48:55 -07:00
|
|
|
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
|
|
|
|
2014-11-30 06:03:09 -07:00
|
|
|
private def outoftimePlayerCorrespondence: Option[Player] = for {
|
|
|
|
c ← correspondenceClock
|
|
|
|
if c outoftime player.color
|
|
|
|
} yield player
|
|
|
|
|
2014-12-30 08:52:28 -07:00
|
|
|
def isCorrespondence = speed == chess.Speed.Correspondence
|
2014-11-29 06:27:57 -07:00
|
|
|
|
2013-03-22 11:53:13 -06:00
|
|
|
def hasClock = clock.isDefined
|
2013-03-22 10:36:09 -06:00
|
|
|
|
2014-12-30 08:34:50 -07:00
|
|
|
def isUnlimited = !hasClock && daysPerTurn.isEmpty
|
|
|
|
|
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
|
|
|
|
2014-12-02 03:16:25 -07:00
|
|
|
def playerWhoDidNotMove: Option[Player] = playedTurns 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
|
|
|
|
2014-12-02 03:16:25 -07:00
|
|
|
def onePlayerHasMoved = playedTurns > 0
|
|
|
|
def bothPlayersHaveMoved = playedTurns > 1
|
2013-03-22 10:36:09 -06:00
|
|
|
|
2014-12-02 03:16:25 -07:00
|
|
|
def playerMoves(color: Color): Int = (playedTurns + 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
|
|
|
|
2014-12-02 03:16:25 -07:00
|
|
|
def playerBlurPercent(color: Color): Int = (playedTurns > 5).fold(
|
2013-03-22 11:53:13 -06:00
|
|
|
(player(color).blurs * 100) / playerMoves(color),
|
|
|
|
0
|
|
|
|
)
|
2013-03-22 10:36:09 -06:00
|
|
|
|
2014-12-02 16:41:39 -07:00
|
|
|
def isBeingPlayed = !isPgnImport && !finishedOrAborted
|
2013-08-01 08:44:38 -06:00
|
|
|
|
2014-11-15 06:53:01 -07:00
|
|
|
def olderThan(seconds: Int) = updatedAt.??(_ isBefore DateTime.now.minusSeconds(seconds))
|
2013-03-22 10:36:09 -06:00
|
|
|
|
2014-12-02 02:42:23 -07:00
|
|
|
def unplayed = !bothPlayersHaveMoved && (createdAt isBefore Game.unplayedDate)
|
2014-11-30 07:53:39 -07:00
|
|
|
|
2014-12-03 02:35:06 -07:00
|
|
|
def abandoned = (status <= Status.Started) && ((updatedAt | createdAt) isBefore hasAi.fold(Game.aiAbandonedDate, 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
|
|
|
|
2014-12-02 03:16:25 -07:00
|
|
|
def resetTurns = copy(turns = 0, startedAtTurn = 0)
|
2013-08-02 02:20:41 -06:00
|
|
|
|
2014-11-09 10:52:20 -07:00
|
|
|
lazy val opening =
|
|
|
|
if (playable || fromPosition || variant.exotic) none
|
|
|
|
else chess.OpeningExplorer openingOf pgnMoves
|
|
|
|
|
2014-12-02 10:42:33 -07:00
|
|
|
private def playerMaps[A](f: Player => Option[A]): List[A] = players flatMap { f(_) }
|
2013-03-22 10:36:09 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
object Game {
|
|
|
|
|
2015-01-11 07:23:25 -07:00
|
|
|
val analysableVariants: Set[Variant] = Set(
|
|
|
|
chess.variant.Standard,
|
|
|
|
chess.variant.Chess960,
|
|
|
|
chess.variant.KingOfTheHill)
|
2014-08-13 02:51:15 -06:00
|
|
|
val unanalysableVariants: Set[Variant] = Variant.all.toSet -- analysableVariants
|
2014-08-08 03:01:47 -06:00
|
|
|
|
2013-03-22 10:36:09 -06:00
|
|
|
val gameIdSize = 8
|
|
|
|
val playerIdSize = 4
|
|
|
|
val fullIdSize = 12
|
|
|
|
val tokenSize = 4
|
|
|
|
|
2014-12-02 02:42:23 -07:00
|
|
|
val unplayedHours = 24
|
|
|
|
def unplayedDate = DateTime.now minusHours unplayedHours
|
2014-12-03 02:35:06 -07:00
|
|
|
|
2014-12-02 17:37:08 -07:00
|
|
|
val abandonedDays = 15
|
2014-12-02 02:42:23 -07:00
|
|
|
def abandonedDate = DateTime.now minusDays abandonedDays
|
2013-03-22 10:36:09 -06:00
|
|
|
|
2014-12-03 02:35:06 -07:00
|
|
|
val aiAbandonedDays = 3
|
|
|
|
def aiAbandonedDate = DateTime.now minusDays abandonedDays
|
|
|
|
|
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,
|
2014-10-25 09:07:55 -06:00
|
|
|
pgnImport: Option[PgnImport],
|
2014-11-29 06:27:57 -07:00
|
|
|
castles: Castles = Castles.init,
|
2014-11-30 03:22:23 -07:00
|
|
|
daysPerTurn: Option[Int] = None): Game = Game(
|
2013-05-07 17:44:26 -06:00
|
|
|
id = IdGenerator.game,
|
2013-12-01 02:01:20 -07:00
|
|
|
whitePlayer = whitePlayer,
|
|
|
|
blackPlayer = blackPlayer,
|
2013-12-01 13:04:52 -07:00
|
|
|
binaryPieces = if (game.isStandardInit) BinaryFormat.piece.standard
|
2014-10-22 16:27:26 -06:00
|
|
|
else BinaryFormat.piece write game.board.pieces,
|
2013-12-05 16:53:18 -07:00
|
|
|
binaryPgn = ByteArray.empty,
|
2013-05-07 17:44:26 -06:00
|
|
|
status = Status.Created,
|
|
|
|
turns = game.turns,
|
2014-02-19 16:16:33 -07:00
|
|
|
startedAtTurn = game.startedAtTurn,
|
2013-05-07 17:44:26 -06:00
|
|
|
clock = game.clock,
|
2014-10-25 09:07:55 -06:00
|
|
|
castleLastMoveTime = CastleLastMoveTime.init.copy(castles = castles),
|
2014-11-30 03:22:23 -07:00
|
|
|
daysPerTurn = daysPerTurn,
|
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,
|
2014-08-02 09:47:49 -06:00
|
|
|
tvAt = none,
|
|
|
|
analysed = false),
|
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"
|
2013-12-09 15:04:53 -07:00
|
|
|
val playerIds = "is"
|
2013-12-11 01:56:11 -07:00
|
|
|
val playerUids = "us"
|
2014-12-02 10:42:33 -07:00
|
|
|
val playingUids = "pl"
|
2013-12-02 16:44:09 -07:00
|
|
|
val binaryPieces = "ps"
|
2013-12-05 16:53:18 -07:00
|
|
|
val binaryPgn = "pg"
|
2013-12-02 16:44:09 -07:00
|
|
|
val status = "s"
|
|
|
|
val turns = "t"
|
2014-02-19 16:16:33 -07:00
|
|
|
val startedAtTurn = "st"
|
2013-12-02 16:44:09 -07:00
|
|
|
val clock = "c"
|
|
|
|
val positionHashes = "ph"
|
2014-07-30 13:37:50 -06:00
|
|
|
val checkCount = "cc"
|
2013-12-02 16:44:09 -07:00
|
|
|
val castleLastMoveTime = "cl"
|
2014-11-30 03:22:23 -07:00
|
|
|
val daysPerTurn = "cd"
|
2013-12-05 12:40:11 -07:00
|
|
|
val moveTimes = "mt"
|
2013-12-02 16:44:09 -07:00
|
|
|
val rated = "ra"
|
2014-06-06 03:43:00 -06:00
|
|
|
val analysed = "an"
|
2013-12-02 16:44:09 -07:00
|
|
|
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"
|
2013-12-15 14:52:01 -07:00
|
|
|
val winnerColor = "w"
|
2013-12-09 12:29:44 -07:00
|
|
|
val winnerId = "wid"
|
2014-11-30 06:35:18 -07:00
|
|
|
val initialFen = "if"
|
|
|
|
val checkAt = "ck"
|
2013-12-03 13:31:31 -07:00
|
|
|
}
|
|
|
|
|
2014-07-30 13:37:50 -06:00
|
|
|
private[game] implicit val checkCountWriter = new BSONWriter[CheckCount, BSONArray] {
|
|
|
|
def write(cc: CheckCount) = BSONArray(cc.white, cc.black)
|
|
|
|
}
|
|
|
|
|
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
|
2013-12-15 14:52:01 -07:00
|
|
|
val winC = r boolO winnerColor map Color.apply
|
2013-12-09 15:04:53 -07:00
|
|
|
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))
|
2013-12-15 14:52:01 -07:00
|
|
|
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)
|
|
|
|
}
|
2014-08-15 05:38:15 -06:00
|
|
|
val createdAtValue = r date createdAt
|
2013-12-02 16:44:09 -07:00
|
|
|
Game(
|
2013-12-09 17:06:13 -07:00
|
|
|
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,
|
2014-11-15 06:34:40 -07:00
|
|
|
status = Status(r int status) err "game invalid status",
|
2013-12-04 12:08:31 -07:00
|
|
|
turns = nbTurns,
|
2014-02-19 16:16:33 -07:00
|
|
|
startedAtTurn = r intD startedAtTurn,
|
2014-08-15 05:38:15 -06:00
|
|
|
clock = r.getO[Color => Clock](clock)(clockBSONHandler(createdAtValue)) map (_(Color(0 == nbTurns % 2))),
|
2013-12-05 12:40:11 -07:00
|
|
|
positionHashes = r.bytesD(positionHashes).value,
|
2014-07-30 13:37:50 -06:00
|
|
|
checkCount = {
|
|
|
|
val counts = r.intsD(checkCount)
|
|
|
|
CheckCount(~counts.headOption, ~counts.lastOption)
|
|
|
|
},
|
2013-12-02 16:44:09 -07:00
|
|
|
castleLastMoveTime = r.get[CastleLastMoveTime](castleLastMoveTime)(castleLastMoveTimeBSONHandler),
|
2014-11-30 03:22:23 -07:00
|
|
|
daysPerTurn = r intO daysPerTurn,
|
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),
|
2015-01-11 07:23:25 -07:00
|
|
|
variant = Variant(r intD variant) | chess.variant.Standard,
|
2013-12-02 16:44:09 -07:00
|
|
|
next = r strO next,
|
|
|
|
bookmarks = r intD bookmarks,
|
2014-08-15 05:38:15 -06:00
|
|
|
createdAt = createdAtValue,
|
2013-12-02 16:44:09 -07:00
|
|
|
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,
|
2014-08-02 09:47:49 -06:00
|
|
|
tvAt = r dateO tvAt,
|
2014-11-09 10:52:20 -07:00
|
|
|
analysed = r boolD analysed)
|
2013-12-05 12:40:11 -07:00
|
|
|
)
|
2013-12-02 16:44:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
def writes(w: BSON.Writer, o: Game) = BSONDocument(
|
|
|
|
id -> o.id,
|
2013-12-09 15:04:53 -07:00
|
|
|
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,
|
2014-02-19 16:16:33 -07:00
|
|
|
startedAtTurn -> w.intO(o.startedAtTurn),
|
2014-08-15 05:38:15 -06:00
|
|
|
clock -> (o.clock map { c => clockBSONHandler(o.createdAt).write(_ => c) }),
|
2013-12-05 12:40:11 -07:00
|
|
|
positionHashes -> w.bytesO(o.positionHashes),
|
2014-07-30 13:37:50 -06:00
|
|
|
checkCount -> o.checkCount.nonEmpty.option(o.checkCount),
|
2013-12-02 16:44:09 -07:00
|
|
|
castleLastMoveTime -> castleLastMoveTimeBSONHandler.write(o.castleLastMoveTime),
|
2014-11-30 03:22:23 -07:00
|
|
|
daysPerTurn -> o.daysPerTurn,
|
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,
|
2014-08-02 09:47:49 -06:00
|
|
|
tvAt -> o.metadata.tvAt.map(w.date),
|
|
|
|
analysed -> w.boolO(o.metadata.analysed)
|
2013-12-05 12:40:11 -07:00
|
|
|
)
|
2013-12-02 16:44:09 -07:00
|
|
|
}
|
2013-12-04 12:08:31 -07:00
|
|
|
|
|
|
|
import lila.db.ByteArray.ByteArrayBSONHandler
|
|
|
|
|
2014-08-15 05:38:15 -06:00
|
|
|
def clockBSONHandler(since: DateTime) = new BSONHandler[BSONBinary, Color => Clock] {
|
|
|
|
def read(bin: BSONBinary) = BinaryFormat clock since read {
|
2013-12-04 12:08:31 -07:00
|
|
|
ByteArrayBSONHandler read bin
|
|
|
|
}
|
2014-02-17 02:12:19 -07:00
|
|
|
def write(clock: Color => Clock) = ByteArrayBSONHandler write {
|
2014-08-15 05:38:15 -06:00
|
|
|
BinaryFormat clock since write clock(chess.White)
|
2013-12-04 12:08:31 -07:00
|
|
|
}
|
|
|
|
}
|
2013-03-22 10:36:09 -06:00
|
|
|
}
|
|
|
|
|
2013-12-01 17:38:03 -07:00
|
|
|
case class CastleLastMoveTime(
|
|
|
|
castles: Castles,
|
|
|
|
lastMove: Option[(Pos, Pos)],
|
2013-12-09 15:04:53 -07:00
|
|
|
lastMoveTime: Option[Int], // tenths of seconds since game creation
|
|
|
|
check: Option[Pos]) {
|
2013-12-01 17:38:03 -07:00
|
|
|
|
2014-02-17 02:12:19 -07:00
|
|
|
def lastMoveString = lastMove map { case (a, b) => a.toString + b.toString }
|
2013-12-01 17:38:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
object CastleLastMoveTime {
|
|
|
|
|
2013-12-09 15:04:53 -07:00
|
|
|
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
|
|
|
}
|