game db awesomeness
parent
662a6d681d
commit
653b24b354
|
@ -1,10 +1,10 @@
|
|||
package lila.app
|
||||
package round
|
||||
package lila.game
|
||||
|
||||
import play.api.libs.json._
|
||||
|
||||
import chess.{ PromotableRole, Pos, Color, Situation, Move ⇒ ChessMove, Clock ⇒ ChessClock }
|
||||
import Pos.{ piotr, allPiotrs }
|
||||
import org.apache.commons.lang3.StringEscapeUtils.escapeXml
|
||||
|
||||
sealed trait Event {
|
||||
def typ: String
|
||||
|
@ -116,16 +116,27 @@ object Event {
|
|||
|
||||
case class Message(author: String, message: String) extends Event {
|
||||
def typ = "message"
|
||||
def data = JsString(Room render (author, message))
|
||||
def data = JsString(renderRoom(author, message))
|
||||
override def owner = true
|
||||
}
|
||||
|
||||
case class WatcherMessage(message: WatcherRoom.Message) extends Event {
|
||||
private def renderRoom(author: String, text: String): String =
|
||||
"""<li class="%s%s">%s</li>""".format(
|
||||
author,
|
||||
(author == "system") ?? " trans_me",
|
||||
escapeXml(text))
|
||||
|
||||
case class WatcherMessage(author: Option[String], text: String) extends Event {
|
||||
def typ = "message"
|
||||
def data = JsString(WatcherRoom render message)
|
||||
def data = JsString(renderWatcherRoom(author, text))
|
||||
override def watcher = true
|
||||
}
|
||||
|
||||
private def renderWatcherRoom(author: Option[String], text: String): String =
|
||||
"""<li><span>%s</span>%s</li>""".format(
|
||||
author.fold("Anonymous")("@" + _),
|
||||
escapeXml(text))
|
||||
|
||||
case class End() extends Empty {
|
||||
def typ = "end"
|
||||
}
|
|
@ -47,332 +47,335 @@ case class Game(
|
|||
updatedAt: Option[DateTime] = None,
|
||||
metadata: Option[Metadata] = None) {
|
||||
|
||||
// val players = List(whitePlayer, blackPlayer)
|
||||
|
||||
// val playersByColor: Map[Color, Player] = Map(
|
||||
// White -> whitePlayer,
|
||||
// Black -> blackPlayer
|
||||
// )
|
||||
|
||||
// def player(color: Color): Player = color match {
|
||||
// case White ⇒ whitePlayer
|
||||
// case Black ⇒ blackPlayer
|
||||
// }
|
||||
|
||||
// def player(playerId: String): Option[Player] =
|
||||
// players find (_.id == playerId)
|
||||
|
||||
// def player(user: User): Option[Player] =
|
||||
// players find (_ isUser user)
|
||||
|
||||
// def player(c: Color.type ⇒ Color): Player = player(c(Color))
|
||||
|
||||
// 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)
|
||||
|
||||
// def turnColor = Color(0 == turns % 2)
|
||||
|
||||
// def turnOf(p: Player) = p == player
|
||||
|
||||
// def fullIdOf(player: Player): Option[String] =
|
||||
// (players contains player) option id + player.id
|
||||
|
||||
// def fullIdOf(color: Color): String = id + player(color).id
|
||||
|
||||
// def isTournament = tournamentId.isDefined
|
||||
// def nonTournament = tournamentId.isEmpty
|
||||
|
||||
// def hasChat = nonTournament && nonAi
|
||||
|
||||
// lazy val toChess: Game = {
|
||||
|
||||
// def posPiece(posCode: Char, roleCode: Char, color: Color): Option[(Pos, Piece)] = for {
|
||||
// pos ← piotr(posCode)
|
||||
// role ← forsyth(roleCode)
|
||||
// } yield (pos, Piece(color, role))
|
||||
|
||||
// val (pieces, deads) = {
|
||||
// for {
|
||||
// player ← players
|
||||
// color = player.color
|
||||
// piece ← player.ps grouped 2
|
||||
// } yield (color, piece(0), piece(1))
|
||||
// }.foldLeft((Map[Pos, Piece](), List[(Pos, Piece)]())) {
|
||||
// case ((ps, ds), (color, pos, role)) ⇒ {
|
||||
// if (role.isUpper) posPiece(pos, role.toLower, color) map { p ⇒ (ps, p :: ds) }
|
||||
// else posPiece(pos, role, color) map { p ⇒ (ps + p, ds) }
|
||||
// } getOrElse (ps, ds)
|
||||
// case (acc, _) ⇒ acc
|
||||
// }
|
||||
|
||||
// Game(
|
||||
// board = Board(pieces, toChessHistory, variant),
|
||||
// player = Color(0 == turns % 2),
|
||||
// clock = clock,
|
||||
// deads = deads,
|
||||
// turns = turns
|
||||
// )
|
||||
// }
|
||||
|
||||
// lazy val toChessHistory = ChessHistory(
|
||||
// lastMove = lastMove,
|
||||
// castles = castles,
|
||||
// positionHashes = positionHashes)
|
||||
|
||||
// def update(
|
||||
// game: Game,
|
||||
// move: Move,
|
||||
// blur: Boolean = false): (Progress, String) = {
|
||||
// val (history, situation) = (game.board.history, game.situation)
|
||||
// val events =
|
||||
// Event.possibleMoves(game.situation, White) ::
|
||||
// Event.possibleMoves(game.situation, Black) ::
|
||||
// Event.State(game.situation.color, game.turns) ::
|
||||
// (Event fromMove move) :::
|
||||
// (Event fromSituation game.situation)
|
||||
|
||||
// def copyPlayer(player: Player) = player.copy(
|
||||
// ps = player encodePieces game.allPieces,
|
||||
// blurs = player.blurs + (blur && move.color == player.color).fold(1, 0),
|
||||
// moveTimes = ((!isPgnImport) && (move.color == player.color)).fold(
|
||||
// lastMoveTime.fold("") { lmt ⇒
|
||||
// (nowSeconds - lmt) |> { mt ⇒
|
||||
// val encoded = MoveTime encode mt
|
||||
// player.moveTimes.isEmpty.fold(encoded.toString, player.moveTimes + encoded)
|
||||
// }
|
||||
// }, player.moveTimes
|
||||
// )
|
||||
// )
|
||||
|
||||
// val updated = copy(
|
||||
// whitePlayer = copyPlayer(whitePlayer),
|
||||
// blackPlayer = copyPlayer(blackPlayer),
|
||||
// turns = game.turns,
|
||||
// positionHashes = history.positionHashes mkString,
|
||||
// castles = history.castleNotation,
|
||||
// lastMove = history.lastMoveString,
|
||||
// status = situation.status | status,
|
||||
// clock = game.clock,
|
||||
// check = if (situation.check) situation.kingPos else None,
|
||||
// lastMoveTime = nowSeconds.some
|
||||
// )
|
||||
|
||||
// val finalEvents = events :::
|
||||
// ~updated.clock.map(c ⇒ List(Event.Clock(c))) ::: {
|
||||
// (updated.playable && (
|
||||
// abortable != updated.abortable || (Color.all exists { color ⇒
|
||||
// playerCanOfferDraw(color) != updated.playerCanOfferDraw(color)
|
||||
// })
|
||||
// )).fold(Color.all map Event.ReloadTable, Nil)
|
||||
// }
|
||||
val players = List(whitePlayer, blackPlayer)
|
||||
|
||||
val playersByColor: Map[Color, Player] = Map(
|
||||
White -> whitePlayer,
|
||||
Black -> blackPlayer
|
||||
)
|
||||
|
||||
def player(color: Color): Player = color match {
|
||||
case White ⇒ whitePlayer
|
||||
case Black ⇒ blackPlayer
|
||||
}
|
||||
|
||||
def player(playerId: String): Option[Player] =
|
||||
players find (_.id == playerId)
|
||||
|
||||
def player(user: User): Option[Player] =
|
||||
players find (_ isUser user)
|
||||
|
||||
def player(c: Color.type ⇒ Color): Player = player(c(Color))
|
||||
|
||||
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)
|
||||
|
||||
def turnColor = Color(0 == turns % 2)
|
||||
|
||||
def turnOf(p: Player) = p == player
|
||||
|
||||
def fullIdOf(player: Player): Option[String] =
|
||||
(players contains player) option id + player.id
|
||||
|
||||
def fullIdOf(color: Color): String = id + player(color).id
|
||||
|
||||
def tournamentId = metadata flatMap (_.tournamentId)
|
||||
|
||||
def isTournament = tournamentId.isDefined
|
||||
def nonTournament = tournamentId.isEmpty
|
||||
|
||||
def hasChat = nonTournament && nonAi
|
||||
|
||||
lazy val toChess: ChessGame = {
|
||||
|
||||
def posPiece(posCode: Char, roleCode: Char, color: Color): Option[(Pos, Piece)] = for {
|
||||
pos ← piotr(posCode)
|
||||
role ← forsyth(roleCode)
|
||||
} yield (pos, Piece(color, role))
|
||||
|
||||
val (pieces, deads) = {
|
||||
for {
|
||||
player ← players
|
||||
color = player.color
|
||||
piece ← player.ps grouped 2
|
||||
} yield (color, piece(0), piece(1))
|
||||
}.foldLeft((Map[Pos, Piece](), List[(Pos, Piece)]())) {
|
||||
case ((ps, ds), (color, pos, role)) ⇒ {
|
||||
if (role.isUpper) posPiece(pos, role.toLower, color) map { p ⇒ (ps, p :: ds) }
|
||||
else posPiece(pos, role, color) map { p ⇒ (ps + p, ds) }
|
||||
} | (ps, ds)
|
||||
case (acc, _) ⇒ acc
|
||||
}
|
||||
|
||||
ChessGame(
|
||||
board = Board(pieces, toChessHistory, variant),
|
||||
player = Color(0 == turns % 2),
|
||||
clock = clock,
|
||||
deads = deads,
|
||||
turns = turns
|
||||
)
|
||||
}
|
||||
|
||||
lazy val toChessHistory = ChessHistory(
|
||||
lastMove = lastMove,
|
||||
castles = castles,
|
||||
positionHashes = positionHashes)
|
||||
|
||||
def update(
|
||||
game: ChessGame,
|
||||
move: Move,
|
||||
blur: Boolean = false): (Progress, String) = {
|
||||
val (history, situation) = (game.board.history, game.situation)
|
||||
val events =
|
||||
Event.possibleMoves(game.situation, White) ::
|
||||
Event.possibleMoves(game.situation, Black) ::
|
||||
Event.State(game.situation.color, game.turns) ::
|
||||
(Event fromMove move) :::
|
||||
(Event fromSituation game.situation)
|
||||
|
||||
def copyPlayer(player: Player) = player.copy(
|
||||
ps = player encodePieces game.allPieces,
|
||||
blurs = player.blurs + (blur && move.color == player.color).fold(1, 0),
|
||||
moveTimes = ((!isPgnImport) && (move.color == player.color)).fold(
|
||||
lastMoveTime.fold("") { lmt ⇒
|
||||
(nowSeconds - lmt) |> { mt ⇒
|
||||
val encoded = MoveTime encode mt
|
||||
player.moveTimes.isEmpty.fold(encoded.toString, player.moveTimes + encoded)
|
||||
}
|
||||
}, player.moveTimes
|
||||
)
|
||||
)
|
||||
|
||||
val updated = copy(
|
||||
whitePlayer = copyPlayer(whitePlayer),
|
||||
blackPlayer = copyPlayer(blackPlayer),
|
||||
turns = game.turns,
|
||||
positionHashes = history.positionHashes mkString,
|
||||
castles = history.castleNotation,
|
||||
lastMove = history.lastMoveString,
|
||||
status = situation.status | status,
|
||||
clock = game.clock,
|
||||
check = if (situation.check) situation.kingPos else None,
|
||||
lastMoveTime = nowSeconds.some
|
||||
)
|
||||
|
||||
val finalEvents = events :::
|
||||
~updated.clock.map(c ⇒ List(Event.Clock(c))) ::: {
|
||||
(updated.playable && (
|
||||
abortable != updated.abortable || (Color.all exists { color ⇒
|
||||
playerCanOfferDraw(color) != updated.playerCanOfferDraw(color)
|
||||
})
|
||||
)).fold(Color.all map Event.ReloadTable, Nil)
|
||||
}
|
||||
|
||||
// Progress(this, updated, finalEvents) -> game.pgnMoves
|
||||
// }
|
||||
|
||||
// def updatePlayer(color: Color, f: Player ⇒ Player) = color match {
|
||||
// case White ⇒ copy(whitePlayer = f(whitePlayer))
|
||||
// case Black ⇒ copy(blackPlayer = f(blackPlayer))
|
||||
// }
|
||||
|
||||
// def updatePlayers(f: Player ⇒ Player) = copy(
|
||||
// whitePlayer = f(whitePlayer),
|
||||
// blackPlayer = f(blackPlayer)
|
||||
// )
|
||||
|
||||
// def start = started.fold(this, copy(
|
||||
// status = Status.Started,
|
||||
// mode = Mode(mode.rated && (players forall (_.hasUser))),
|
||||
// updatedAt = DateTime.now.some
|
||||
// ))
|
||||
|
||||
// def startClock(compensation: Float) = clock.filterNot(_.isRunning).fold(this) { c ⇒
|
||||
// copy(clock = c.run.giveTime(creatorColor, compensation).some)
|
||||
// }
|
||||
|
||||
// def hasMoveTimes = players forall (_.hasMoveTimes)
|
||||
Progress(this, updated, finalEvents) -> game.pgnMoves
|
||||
}
|
||||
|
||||
def updatePlayer(color: Color, f: Player ⇒ Player) = color match {
|
||||
case White ⇒ copy(whitePlayer = f(whitePlayer))
|
||||
case Black ⇒ copy(blackPlayer = f(blackPlayer))
|
||||
}
|
||||
|
||||
def updatePlayers(f: Player ⇒ Player) = copy(
|
||||
whitePlayer = f(whitePlayer),
|
||||
blackPlayer = f(blackPlayer)
|
||||
)
|
||||
|
||||
def start = started.fold(this, copy(
|
||||
status = Status.Started,
|
||||
mode = Mode(mode.rated && (players forall (_.hasUser))),
|
||||
updatedAt = DateTime.now.some
|
||||
))
|
||||
|
||||
def startClock(compensation: Float) = clock.filterNot(_.isRunning).fold(this) { c ⇒
|
||||
copy(clock = c.run.giveTime(creatorColor, compensation).some)
|
||||
}
|
||||
|
||||
def hasMoveTimes = players forall (_.hasMoveTimes)
|
||||
|
||||
// def started = status >= Status.Started
|
||||
|
||||
// def notStarted = !started
|
||||
|
||||
// def aborted = status == Status.Aborted
|
||||
def started = status >= Status.Started
|
||||
|
||||
def notStarted = !started
|
||||
|
||||
def aborted = status == Status.Aborted
|
||||
|
||||
// def playable = status < Status.Aborted
|
||||
def playable = status < Status.Aborted
|
||||
|
||||
// def playableBy(p: Player): Boolean = playable && turnOf(p)
|
||||
def playableBy(p: Player): Boolean = playable && turnOf(p)
|
||||
|
||||
// def playableBy(c: Color): Boolean = playableBy(player(c))
|
||||
def playableBy(c: Color): Boolean = playableBy(player(c))
|
||||
|
||||
// def continuable = status != Status.Mate && status != Status.Stalemate
|
||||
def continuable = status != Status.Mate && status != Status.Stalemate
|
||||
|
||||
// def fenString = Forsyth >> toChess
|
||||
def fenString = Forsyth >> toChess
|
||||
|
||||
// def aiLevel: Option[Int] = players find (_.isAi) flatMap (_.aiLevel)
|
||||
def aiLevel: Option[Int] = players find (_.isAi) flatMap (_.aiLevel)
|
||||
|
||||
// def hasAi: Boolean = players exists (_.isAi)
|
||||
// def nonAi = !hasAi
|
||||
def hasAi: Boolean = players exists (_.isAi)
|
||||
def nonAi = !hasAi
|
||||
|
||||
// def mapPlayers(f: Player ⇒ Player) = copy(
|
||||
// whitePlayer = f(whitePlayer),
|
||||
// blackPlayer = f(blackPlayer)
|
||||
// )
|
||||
def mapPlayers(f: Player ⇒ Player) = copy(
|
||||
whitePlayer = f(whitePlayer),
|
||||
blackPlayer = f(blackPlayer)
|
||||
)
|
||||
|
||||
// def playerCanOfferDraw(color: Color) =
|
||||
// started && playable &&
|
||||
// turns >= 2 &&
|
||||
// !player(color).isOfferingDraw &&
|
||||
// !(opponent(color).isAi) &&
|
||||
// !(playerHasOfferedDraw(color))
|
||||
def playerCanOfferDraw(color: Color) =
|
||||
started && playable &&
|
||||
turns >= 2 &&
|
||||
!player(color).isOfferingDraw &&
|
||||
!(opponent(color).isAi) &&
|
||||
!(playerHasOfferedDraw(color))
|
||||
|
||||
// def playerHasOfferedDraw(color: Color) =
|
||||
// player(color).lastDrawOffer.fold(false)(_ >= turns - 1)
|
||||
def playerHasOfferedDraw(color: Color) =
|
||||
player(color).lastDrawOffer.fold(false)(_ >= turns - 1)
|
||||
|
||||
// def playerCanRematch(color: Color) =
|
||||
// finishedOrAborted && opponent(color).isHuman && nonTournament
|
||||
def playerCanRematch(color: Color) =
|
||||
finishedOrAborted && opponent(color).isHuman && nonTournament
|
||||
|
||||
// def playerCanProposeTakeback(color: Color) =
|
||||
// started && playable && nonTournament &&
|
||||
// bothPlayersHaveMoved &&
|
||||
// !opponent(color).isProposingTakeback
|
||||
def playerCanProposeTakeback(color: Color) =
|
||||
started && playable && nonTournament &&
|
||||
bothPlayersHaveMoved &&
|
||||
!opponent(color).isProposingTakeback
|
||||
|
||||
// def moretimeable = playable && nonTournament && hasClock
|
||||
def moretimeable = playable && nonTournament && hasClock
|
||||
|
||||
// def abortable = status == Status.Started && turns < 2 && nonTournament
|
||||
def abortable = status == Status.Started && turns < 2 && nonTournament
|
||||
|
||||
// def resignable = playable && !abortable
|
||||
def resignable = playable && !abortable
|
||||
|
||||
// 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)
|
||||
// ),
|
||||
// List(Event.End())
|
||||
// )
|
||||
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)
|
||||
),
|
||||
List(Event.End())
|
||||
)
|
||||
|
||||
// def rated = mode.rated
|
||||
def rated = mode.rated
|
||||
|
||||
// def finished = status >= Status.Mate
|
||||
def finished = status >= Status.Mate
|
||||
|
||||
// def finishedOrAborted = finished || aborted
|
||||
def finishedOrAborted = finished || aborted
|
||||
|
||||
// def winner = players find (_.wins)
|
||||
def winner = players find (_.wins)
|
||||
|
||||
// def loser = winner map opponent
|
||||
def loser = winner map opponent
|
||||
|
||||
// def winnerColor: Option[Color] = winner map (_.color)
|
||||
def winnerColor: Option[Color] = winner map (_.color)
|
||||
|
||||
// def winnerUserId: Option[String] = winner flatMap (_.userId)
|
||||
def winnerUserId: Option[String] = winner flatMap (_.userId)
|
||||
|
||||
// def loserUserId: Option[String] = loser flatMap (_.userId)
|
||||
def loserUserId: Option[String] = loser flatMap (_.userId)
|
||||
|
||||
// def wonBy(c: Color): Option[Boolean] = winnerColor map (_ == c)
|
||||
def wonBy(c: Color): Option[Boolean] = winnerColor map (_ == c)
|
||||
|
||||
// def outoftimePlayer: Option[Player] = for {
|
||||
// c ← clock
|
||||
// if playable
|
||||
// if !c.isRunning || (c outoftime player.color)
|
||||
// } yield player
|
||||
def outoftimePlayer: Option[Player] = for {
|
||||
c ← clock
|
||||
if playable
|
||||
if !c.isRunning || (c outoftime player.color)
|
||||
} yield player
|
||||
|
||||
// def hasClock = clock.isDefined
|
||||
def hasClock = clock.isDefined
|
||||
|
||||
// def isClockRunning = clock.fold(false)(_.isRunning)
|
||||
def isClockRunning = clock.fold(false)(_.isRunning)
|
||||
|
||||
// def withClock(c: Clock) = Progress(this, copy(clock = Some(c)))
|
||||
def withClock(c: Clock) = Progress(this, copy(clock = Some(c)))
|
||||
|
||||
// def estimateTotalTime = clock.fold(1200)(_.estimateTotalTime)
|
||||
def estimateTotalTime = clock.fold(1200)(_.estimateTotalTime)
|
||||
|
||||
// def creator = player(creatorColor)
|
||||
def creator = player(creatorColor)
|
||||
|
||||
// def invitedColor = !creatorColor
|
||||
def invitedColor = !creatorColor
|
||||
|
||||
// def invited = player(invitedColor)
|
||||
def invited = player(invitedColor)
|
||||
|
||||
// def playerWhoDidNotMove: Option[Player] = turns match {
|
||||
// case 0 ⇒ player(White).some
|
||||
// case 1 ⇒ player(Black).some
|
||||
// case _ ⇒ none
|
||||
// }
|
||||
def playerWhoDidNotMove: Option[Player] = turns match {
|
||||
case 0 ⇒ player(White).some
|
||||
case 1 ⇒ player(Black).some
|
||||
case _ ⇒ none
|
||||
}
|
||||
|
||||
// def bothPlayersHaveMoved = turns > 1
|
||||
def bothPlayersHaveMoved = turns > 1
|
||||
|
||||
// def playerMoves(color: Color): Int = (turns + color.fold(1, 0)) / 2
|
||||
def playerMoves(color: Color): Int = (turns + color.fold(1, 0)) / 2
|
||||
|
||||
// def playerHasMoved(color: Color) = playerMoves(color) > 0
|
||||
def playerHasMoved(color: Color) = playerMoves(color) > 0
|
||||
|
||||
// def playerBlurPercent(color: Color): Int = (turns > 5).fold(
|
||||
// (player(color).blurs * 100) / playerMoves(color),
|
||||
// 0
|
||||
// )
|
||||
def playerBlurPercent(color: Color): Int = (turns > 5).fold(
|
||||
(player(color).blurs * 100) / playerMoves(color),
|
||||
0
|
||||
)
|
||||
|
||||
// def deadPiecesOf(color: Color): List[Role] = toChess.deads collect {
|
||||
// case (_, piece) if piece is color ⇒ piece.role
|
||||
// }
|
||||
def deadPiecesOf(color: Color): List[Role] = toChess.deads collect {
|
||||
case (_, piece) if piece is color ⇒ piece.role
|
||||
}
|
||||
|
||||
// def isBeingPlayed =
|
||||
// !finishedOrAborted && updatedAt.fold(false)(_ > DateTime.now - 20.seconds)
|
||||
def isBeingPlayed =
|
||||
!finishedOrAborted && updatedAt.fold(false)(_ > DateTime.now - 20.seconds)
|
||||
|
||||
// def abandoned = updatedAt.fold(false) { u ⇒
|
||||
// (status <= Status.Started) && (u <= Game.abandonedDate)
|
||||
// }
|
||||
def abandoned = updatedAt.fold(false) { u ⇒
|
||||
(status <= Status.Started) && (u <= Game.abandonedDate)
|
||||
}
|
||||
|
||||
// def hasBookmarks = bookmarks > 0
|
||||
def hasBookmarks = bookmarks > 0
|
||||
|
||||
// def showBookmarks = if (hasBookmarks) bookmarks else ""
|
||||
def showBookmarks = hasBookmarks ?? bookmarks
|
||||
|
||||
// def encode = RawGame(
|
||||
// id = id,
|
||||
// tk = token.some filter (Game.defaultToken !=),
|
||||
// p = players map (_.encode),
|
||||
// s = status.id,
|
||||
// t = turns,
|
||||
// c = clock map RawClock.encode,
|
||||
// lm = lastMove,
|
||||
// ck = check map (_.key),
|
||||
// cc = creatorColor.white.fold(None, Some(false)),
|
||||
// ph = positionHashes.some filter (_.nonEmpty),
|
||||
// cs = castles.some filter ("-" !=),
|
||||
// ra = mode.rated option true,
|
||||
// v = variant.exotic option variant.id,
|
||||
// next = next,
|
||||
// lmt = lastMoveTime,
|
||||
// bm = bookmarks.some filter (0 <),
|
||||
// r960 = is960Rematch option true,
|
||||
// ca = createdAt,
|
||||
// ua = updatedAt,
|
||||
// me = metadata map (_.encode))
|
||||
def encode = RawGame(
|
||||
id = id,
|
||||
tk = token.some filter (Game.defaultToken !=),
|
||||
p = players map (_.encode),
|
||||
s = status.id,
|
||||
t = turns,
|
||||
c = clock map RawClocks.encode,
|
||||
lm = lastMove,
|
||||
ck = check map (_.key),
|
||||
cc = creatorColor.white.fold(None, Some(false)),
|
||||
ph = positionHashes.some filter (_.nonEmpty),
|
||||
cs = castles.some filter ("-" !=),
|
||||
ra = mode.rated option true,
|
||||
v = variant.exotic option variant.id,
|
||||
next = next,
|
||||
lmt = lastMoveTime,
|
||||
bm = bookmarks.some filter (0 <),
|
||||
r960 = is960Rematch option true,
|
||||
ca = createdAt,
|
||||
ua = updatedAt,
|
||||
me = metadata map (_.encode))
|
||||
|
||||
// def userIds = playerMaps(_.userId)
|
||||
def userIds = playerMaps(_.userId)
|
||||
|
||||
// def userElos = playerMaps(_.elo)
|
||||
def userElos = playerMaps(_.elo)
|
||||
|
||||
// def averageUsersElo = userElos match {
|
||||
// case a :: b :: Nil ⇒ Some((a + b) / 2)
|
||||
// case a :: Nil ⇒ Some((a + 1200) / 2)
|
||||
// case _ ⇒ None
|
||||
// }
|
||||
def averageUsersElo = userElos match {
|
||||
case a :: b :: Nil ⇒ Some((a + b) / 2)
|
||||
case a :: Nil ⇒ Some((a + 1200) / 2)
|
||||
case _ ⇒ None
|
||||
}
|
||||
|
||||
// def with960Rematch(v: Boolean) = this.copy(is960Rematch = v)
|
||||
def with960Rematch(v: Boolean) = this.copy(is960Rematch = v)
|
||||
|
||||
// def withTournamentId(id: String) = this.copy(tournamentId = id.some)
|
||||
def withTournamentId(id: String) = this.copy(
|
||||
metadata = metadata map (_.copy(tournamentId = id.some)))
|
||||
|
||||
// def withId(newId: String) = this.copy(id = newId)
|
||||
def withId(newId: String) = this.copy(id = newId)
|
||||
|
||||
// def source = metadata map (_.source)
|
||||
def source = metadata map (_.source)
|
||||
|
||||
// def pgnImport = metadata flatMap (_.pgnImport)
|
||||
def pgnImport = metadata flatMap (_.pgnImport)
|
||||
|
||||
// def isPgnImport = pgnImport.isDefined
|
||||
def isPgnImport = pgnImport.isDefined
|
||||
|
||||
// private def playerMaps[A](f: Player ⇒ Option[A]): List[A] = players.map(f).flatten
|
||||
private def playerMaps[A](f: Player ⇒ Option[A]): List[A] = players.map(f).flatten
|
||||
}
|
||||
|
||||
object Game {
|
||||
|
@ -387,35 +390,51 @@ object Game {
|
|||
|
||||
def takeGameId(fullId: String) = fullId take gameIdSize
|
||||
|
||||
// def apply(
|
||||
// game: Game,
|
||||
// whitePlayer: Player,
|
||||
// blackPlayer: Player,
|
||||
// ai: Option[(Color, Int)],
|
||||
// creatorColor: Color,
|
||||
// mode: Mode,
|
||||
// variant: Variant,
|
||||
// source: Source,
|
||||
// pgnImport: Option[PgnImport]): Game = Game(
|
||||
// id = IdGenerator.game,
|
||||
// token = IdGenerator.token,
|
||||
// whitePlayer = whitePlayer withEncodedPieces game.allPieces,
|
||||
// blackPlayer = blackPlayer withEncodedPieces game.allPieces,
|
||||
// status = Status.Created,
|
||||
// turns = game.turns,
|
||||
// clock = game.clock,
|
||||
// lastMove = None,
|
||||
// check = None,
|
||||
// creatorColor = creatorColor,
|
||||
// positionHashes = "",
|
||||
// castles = "KQkq",
|
||||
// mode = mode,
|
||||
// variant = variant,
|
||||
// lastMoveTime = None,
|
||||
// metadata = Metadata(
|
||||
// source = source,
|
||||
// pgnImport = pgnImport).some,
|
||||
// createdAt = DateTime.now)
|
||||
def apply(
|
||||
game: ChessGame,
|
||||
whitePlayer: Player,
|
||||
blackPlayer: Player,
|
||||
ai: Option[(Color, Int)],
|
||||
creatorColor: Color,
|
||||
mode: Mode,
|
||||
variant: Variant,
|
||||
source: Source,
|
||||
pgnImport: Option[PgnImport]): Game = Game(
|
||||
id = IdGenerator.game,
|
||||
token = IdGenerator.token,
|
||||
whitePlayer = whitePlayer withEncodedPieces game.allPieces,
|
||||
blackPlayer = blackPlayer withEncodedPieces game.allPieces,
|
||||
status = Status.Created,
|
||||
turns = game.turns,
|
||||
clock = game.clock,
|
||||
lastMove = None,
|
||||
check = None,
|
||||
creatorColor = creatorColor,
|
||||
positionHashes = "",
|
||||
castles = "KQkq",
|
||||
mode = mode,
|
||||
variant = variant,
|
||||
lastMoveTime = None,
|
||||
metadata = Metadata(
|
||||
source = source,
|
||||
pgnImport = pgnImport).some,
|
||||
createdAt = DateTime.now)
|
||||
|
||||
import lila.db.JsonTube
|
||||
import play.api.libs.json._
|
||||
|
||||
val json = JsonTube(
|
||||
reads = Reads[Game](js ⇒
|
||||
(for {
|
||||
obj ← js.asOpt[JsObject]
|
||||
rawGame ← RawGames.json.read(obj).asOpt
|
||||
game ← rawGame.decode
|
||||
} yield JsSuccess(game): JsResult[Game]) | JsError(Seq.empty)
|
||||
),
|
||||
writes = Writes[Game](game ⇒
|
||||
RawGames.json.write(game.encode) getOrElse JsUndefined(s"[db] Can't write game ${game.id}")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case class RawGame(
|
||||
|
@ -440,35 +459,34 @@ case class RawGame(
|
|||
ua: Option[DateTime],
|
||||
me: Option[RawMetadata]) {
|
||||
|
||||
// def decode: Option[Game] = for {
|
||||
// whitePlayer ← p.headOption map (_ decode Color.White)
|
||||
// blackPlayer ← p lift 1 map (_ decode Color.Black)
|
||||
// trueStatus ← Status(s)
|
||||
// metadata = me map (_.decode)
|
||||
// } yield Game(
|
||||
// id = id,
|
||||
// token = tk | Game.defaultToken,
|
||||
// whitePlayer = whitePlayer,
|
||||
// blackPlayer = blackPlayer,
|
||||
// status = trueStatus,
|
||||
// turns = t,
|
||||
// clock = c map (_.decode),
|
||||
// lastMove = lm,
|
||||
// check = ck flatMap Pos.posAt,
|
||||
// creatorColor = cc.fold(Color.white)(Color.apply),
|
||||
// positionHashes = ph | "",
|
||||
// castles = cs | "-",
|
||||
// mode = (ra map Mode.apply) | Mode.Casual,
|
||||
// variant = (v flatMap Variant.apply) | Variant.Standard,
|
||||
// next = next,
|
||||
// lastMoveTime = lmt,
|
||||
// bookmarks = bm | 0,
|
||||
// is960Rematch = r960 | false,
|
||||
// createdAt = ca,
|
||||
// updatedAt = ua,
|
||||
// tournamentId = tid,
|
||||
// metadata = me flatMap (_.decode)
|
||||
// )
|
||||
def decode: Option[Game] = for {
|
||||
whitePlayer ← p.headOption map (_ decode Color.White)
|
||||
blackPlayer ← p lift 1 map (_ decode Color.Black)
|
||||
trueStatus ← Status(s)
|
||||
metadata = me map (_.decode)
|
||||
} yield Game(
|
||||
id = id,
|
||||
token = tk | Game.defaultToken,
|
||||
whitePlayer = whitePlayer,
|
||||
blackPlayer = blackPlayer,
|
||||
status = trueStatus,
|
||||
turns = t,
|
||||
clock = c map (_.decode),
|
||||
lastMove = lm,
|
||||
check = ck flatMap Pos.posAt,
|
||||
creatorColor = cc.fold(Color.white)(Color.apply),
|
||||
positionHashes = ph | "",
|
||||
castles = cs | "-",
|
||||
mode = (ra map Mode.apply) | Mode.Casual,
|
||||
variant = (v flatMap Variant.apply) | Variant.Standard,
|
||||
next = next,
|
||||
lastMoveTime = lmt,
|
||||
bookmarks = bm | 0,
|
||||
is960Rematch = r960 | false,
|
||||
createdAt = ca,
|
||||
updatedAt = ua,
|
||||
metadata = me flatMap (_.decode)
|
||||
)
|
||||
}
|
||||
|
||||
object RawGames {
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
package lila.game
|
||||
|
||||
import chess.{ Color, Variant, Status }
|
||||
import chess.format.Forsyth
|
||||
|
||||
import lila.user.User
|
||||
import lila.db.{ Repo, DbApi }
|
||||
import lila.db.Implicits._
|
||||
|
||||
import play.api.libs.json.Json
|
||||
import play.api.libs.concurrent.Execution.Implicits._
|
||||
|
||||
import reactivemongo.api._
|
||||
import reactivemongo.bson._
|
||||
import reactivemongo.core.commands._
|
||||
|
||||
import play.modules.reactivemongo.Implicits._
|
||||
import play.modules.reactivemongo.MongoJSONHelpers._
|
||||
|
||||
import com.roundeights.hasher.Implicits._
|
||||
import org.joda.time.DateTime
|
||||
import org.scala_tools.time.Imports._
|
||||
import scala.util.Random
|
||||
|
||||
final class GameRepo(coll: ReactiveColl) extends Repo[String, Game](coll, Game.json) {
|
||||
|
||||
type ID = String
|
||||
|
||||
import Game._
|
||||
|
||||
def player(gameId: ID, color: Color): Fu[Option[Player]] =
|
||||
find byId gameId map2 { game: Game ⇒ game player color }
|
||||
|
||||
def pov(gameId: ID, color: Color): Fu[Option[Pov]] =
|
||||
find byId gameId map2 { game: Game ⇒ Pov(game, game player color) }
|
||||
|
||||
def pov(gameId: ID, color: String): Fu[Option[Pov]] =
|
||||
Color(color) zmap (pov(gameId, _))
|
||||
|
||||
def pov(fullId: ID): Fu[Option[Pov]] =
|
||||
find byId (fullId take gameIdSize) map { gameOption ⇒
|
||||
gameOption flatMap { g ⇒
|
||||
g player (fullId drop gameIdSize) map { Pov(g, _) }
|
||||
}
|
||||
}
|
||||
|
||||
// def pov(ref: PovRef): Fu[Option[Pov]] = pov(ref.gameId, ref.color)
|
||||
|
||||
// def token(id: ID): Fu[String] = io {
|
||||
// primitiveProjection[String](idSelector(id), "tk") | Game.defaultToken
|
||||
// }
|
||||
|
||||
// def save(game: Game): Fu[Unit] = io {
|
||||
// update(idSelector(game), _grater asDBObject game.encode)
|
||||
// }
|
||||
|
||||
// def save(progress: Progress): Fu[Unit] =
|
||||
// GameDiff(progress.origin.encode, progress.game.encode) |> {
|
||||
// case (Nil, Nil) ⇒ io()
|
||||
// case (sets, unsets) ⇒ {
|
||||
// val fullSets = ("ua" -> new Date) :: sets
|
||||
// val ops = unsets.isEmpty.fold(
|
||||
// $set(fullSets),
|
||||
// $set(fullSets) ++ $unset(unsets)
|
||||
// )
|
||||
// val wc = WriteConcern.None
|
||||
// io { collection.update(idSelector(progress.origin), ops, concern = wc) }
|
||||
// }
|
||||
// }
|
||||
|
||||
// def insert(game: Game): Fu[Option[String]] = io {
|
||||
// insert(game.encode)
|
||||
// }
|
||||
|
||||
// // makes the asumption that player 0 is white!
|
||||
// // proved to be true on prod DB at March 31 2012
|
||||
// def setEloDiffs(id: ID, white: Int, black: Int) = io {
|
||||
// update(idSelector(id), $set(Seq("p.0.ed" -> white, "p.1.ed" -> black)))
|
||||
// }
|
||||
|
||||
// def setUser(id: ID, color: Color, user: User) = io {
|
||||
// val pn = "p.%d".format(color.fold(0, 1))
|
||||
// update(idSelector(id), $set(Seq(pn + ".uid" -> user.id, pn + ".elo" -> user.elo)))
|
||||
// }
|
||||
|
||||
// def incBookmarks(id: ID, value: Int) = io {
|
||||
// update(idSelector(id), $inc("bm" -> value))
|
||||
// }
|
||||
|
||||
// def finish(id: ID, winnerId: Option[String]) = io {
|
||||
// update(
|
||||
// idSelector(id),
|
||||
// (winnerId.fold($set(Seq.empty)) { userId ⇒ $set(Seq("wid" -> userId)) }) ++ $unset(Seq(
|
||||
// "c.t",
|
||||
// "ph",
|
||||
// "lmt",
|
||||
// "p.0.previousMoveTs",
|
||||
// "p.1.previousMoveTs",
|
||||
// "p.0.lastDrawOffer",
|
||||
// "p.1.lastDrawOffer",
|
||||
// "p.0.isOfferingDraw",
|
||||
// "p.1.isOfferingDraw",
|
||||
// "p.0.isProposingTakeback",
|
||||
// "p.1.isProposingTakeback"
|
||||
// ))
|
||||
// )
|
||||
// }
|
||||
|
||||
// def findRandomStandardCheckmate(distribution: Int): Fu[Option[Game]] = io {
|
||||
// find(Query.mate ++ ("v" $exists false))
|
||||
// .sort(Query.sortCreated)
|
||||
// .limit(1)
|
||||
// .skip(Random nextInt distribution)
|
||||
// .toList.map(_.decode).flatten.headOption
|
||||
// }
|
||||
|
||||
// def denormalize(game: Game): Fu[Unit] = io {
|
||||
// val userIds = game.players.map(_.userId).flatten
|
||||
// if (userIds.nonEmpty) update(idSelector(game), $set(Seq("uids" -> userIds)))
|
||||
// if (game.mode.rated) update(idSelector(game), $set(Seq("ra" -> true)))
|
||||
// if (game.variant.exotic) update(idSelector(game), $set(Seq("if" -> (Forsyth >> game.toChess))))
|
||||
// }
|
||||
|
||||
// def saveNext(game: Game, nextId: ID): Fu[Unit] = io {
|
||||
// update(
|
||||
// idSelector(game),
|
||||
// $set(Seq("next" -> nextId)) ++
|
||||
// $unset(Seq("p.0.isOfferingRematch", "p.1.isOfferingRematch"))
|
||||
// )
|
||||
// }
|
||||
|
||||
// def initialFen(gameId: ID): Fu[Option[String]] = io {
|
||||
// primitiveProjection[String](idSelector(gameId), "if")
|
||||
// }
|
||||
|
||||
// val unplayedIds: Fu[List[ID]] = io {
|
||||
// primitiveProjections[ID](
|
||||
// ("t" $lt 2) ++ ("ca" $lt (DateTime.now - 3.day) $gt (DateTime.now - 1.week)),
|
||||
// "_id"
|
||||
// )
|
||||
// }
|
||||
|
||||
// // bookmarks and pgns should also be removed
|
||||
// def remove(id: ID): Fu[Unit] = io {
|
||||
// remove(idSelector(id))
|
||||
// }
|
||||
|
||||
// // bookmarks and pgns should also be removed
|
||||
// def removeIds(ids: List[ID]): Fu[Unit] = io {
|
||||
// remove("_id" $in ids)
|
||||
// }
|
||||
|
||||
// val candidatesToAutofinish: Fu[List[Game]] = io {
|
||||
// find(Query.playable ++
|
||||
// Query.clock(true) ++
|
||||
// ("ca" $gt (DateTime.now - 1.day)) ++ // index
|
||||
// ("ua" $lt (DateTime.now - 2.hour))
|
||||
// ).toList.map(_.decode).flatten
|
||||
// }
|
||||
|
||||
// def abandoned(max: Int): Fu[List[Game]] = io {
|
||||
// find(
|
||||
// Query.notFinished ++ ("ua" $lt Game.abandonedDate)
|
||||
// ).limit(max).toList.map(_.decode).flatten
|
||||
// }
|
||||
|
||||
// val featuredCandidates: Fu[List[Game]] = io {
|
||||
// find(Query.playable ++
|
||||
// Query.clock(true) ++
|
||||
// ("t" $gt 1) ++
|
||||
// ("ca" $gt (DateTime.now - 4.minutes)) ++
|
||||
// ("ua" $gt (DateTime.now - 15.seconds))
|
||||
// ).toList.map(_.decode).flatten
|
||||
// }
|
||||
|
||||
// def count(query: DBObject): Fu[Int] = io {
|
||||
// super.count(query).toInt
|
||||
// }
|
||||
|
||||
// def count(query: Query.type ⇒ DBObject): Fu[Int] = count(query(Query))
|
||||
|
||||
// def exists(id: ID) = count(idSelector(id)) map (_ > 0)
|
||||
|
||||
// def recentGames(limit: Int): Fu[List[Game]] = io {
|
||||
// find(Query.started ++ Query.turnsGt(1))
|
||||
// .sort(Query.sortCreated)
|
||||
// .limit(limit)
|
||||
// .toList.map(_.decode).flatten
|
||||
// }
|
||||
|
||||
// def games(ids: List[ID]): Fu[List[Game]] = io {
|
||||
// find("_id" $in ids).toList.map(_.decode).flatten
|
||||
// } map { gs ⇒
|
||||
// val gsMap = gs.map(g ⇒ g.id -> g).toMap
|
||||
// ids.map(gsMap.get).flatten
|
||||
// }
|
||||
|
||||
// def nbPerDay(days: Int): Fu[List[Int]] = ((days to 1 by -1).toList map { day ⇒
|
||||
// val from = DateTime.now.withTimeAtStartOfDay - day.days
|
||||
// val to = from + 1.day
|
||||
// count(("ca" $gte from $lt to))
|
||||
// }).sequence
|
||||
|
||||
// def recentAverageElo(minutes: Int): Fu[(Int, Int)] = io {
|
||||
// val result = collection.mapReduce(
|
||||
// mapFunction = """function() {
|
||||
// emit(!!this.ra, this.p);
|
||||
// }""",
|
||||
// reduceFunction = """function(rated, values) {
|
||||
// var sum = 0, nb = 0;
|
||||
// values.forEach(function(game) {
|
||||
// if(typeof game[0] != "undefined") {
|
||||
// game.forEach(function(player) {
|
||||
// if(player.elo) {
|
||||
// sum += player.elo;
|
||||
// ++nb;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// return nb == 0 ? nb : Math.round(sum / nb);
|
||||
// }""",
|
||||
// output = MapReduceInlineOutput,
|
||||
// query = Some {
|
||||
// ("ca" $gte (DateTime.now - minutes.minutes)) ++ ("p.elo" $exists true)
|
||||
// }
|
||||
// )
|
||||
// (for {
|
||||
// ratedRow ← result.hasNext option result.next
|
||||
// rated ← ratedRow.getAs[Double]("value")
|
||||
// casualRow ← result.hasNext option result.next
|
||||
// casual ← casualRow.getAs[Double]("value")
|
||||
// } yield rated.toInt -> casual.toInt) | (0, 0)
|
||||
// }
|
||||
|
||||
// private def idSelector(game: Game): DBObject = idSelector(game.id)
|
||||
// private def idSelector(id: ID): DBObject = DBObject("_id" -> id)
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
package lila.app
|
||||
package game
|
||||
package lila.game
|
||||
|
||||
import chess.Color
|
||||
|
||||
case class Pov(game: DbGame, color: Color) {
|
||||
case class Pov(game: Game, color: Color) {
|
||||
|
||||
def player = game player color
|
||||
|
||||
|
@ -22,16 +21,16 @@ case class Pov(game: DbGame, color: Color) {
|
|||
|
||||
def ref = PovRef(game.id, color)
|
||||
|
||||
def withGame(g: DbGame) = Pov(g, color)
|
||||
def withGame(g: Game) = Pov(g, color)
|
||||
}
|
||||
|
||||
object Pov {
|
||||
|
||||
def apply(game: DbGame): List[Pov] = game.players.map { apply(game, _) }
|
||||
def apply(game: Game): List[Pov] = game.players.map { apply(game, _) }
|
||||
|
||||
def apply(game: DbGame, player: DbPlayer) = new Pov(game, player.color)
|
||||
def apply(game: Game, player: Player) = new Pov(game, player.color)
|
||||
|
||||
def apply(game: DbGame, playerId: String): Option[Pov] =
|
||||
def apply(game: Game, playerId: String): Option[Pov] =
|
||||
game player playerId map { p ⇒ new Pov(game, p.color) }
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package lila.game
|
||||
|
||||
// events are kept in insertion/addition order
|
||||
case class Progress(origin: Game, game: Game, events: List[Event] = Nil) {
|
||||
|
||||
def map(f: Game ⇒ Game) = copy(game = f(game))
|
||||
|
||||
def flatMap(f: Game ⇒ Progress) = f(game) match {
|
||||
case Progress(_, g2, e2) ⇒ copy(game = g2, events = events ::: e2)
|
||||
}
|
||||
|
||||
def +(event: Event) = copy(events = events :+ event)
|
||||
|
||||
def ++(es: List[Event]) = copy(events = events ::: es)
|
||||
|
||||
def withGame(g: Game) = copy(game = g)
|
||||
}
|
||||
|
||||
object Progress {
|
||||
|
||||
def apply(game: Game): Progress =
|
||||
new Progress(game, game)
|
||||
|
||||
def apply(game: Game, events: List[Event]): Progress =
|
||||
new Progress(game, game, events)
|
||||
|
||||
def apply(game: Game, events: Event): Progress =
|
||||
new Progress(game, game, events :: Nil)
|
||||
}
|
|
@ -1,245 +0,0 @@
|
|||
package lila.app
|
||||
package game
|
||||
|
||||
import DbGame._
|
||||
|
||||
import chess.{ Color, Variant, Status }
|
||||
import chess.format.Forsyth
|
||||
import round.Progress
|
||||
import user.User
|
||||
|
||||
import com.novus.salat._
|
||||
import com.novus.salat.dao._
|
||||
import com.mongodb.casbah.{ WriteConcern, MongoCollection }
|
||||
import com.mongodb.casbah.query.Imports._
|
||||
import com.mongodb.casbah.map_reduce.MapReduceInlineOutput
|
||||
import org.joda.time.DateTime
|
||||
import org.scala_tools.time.Imports._
|
||||
import java.util.Date
|
||||
import scala.util.Random
|
||||
import scalaz.effects._
|
||||
|
||||
final class GameRepo(collection: MongoCollection)
|
||||
extends SalatDAO[RawDbGame, String](collection) {
|
||||
|
||||
def game(gameId: String): IO[Option[DbGame]] = io {
|
||||
if (gameId.size != gameIdSize) None
|
||||
else findOneById(gameId) flatMap (_.decode)
|
||||
}
|
||||
|
||||
def game(query: DBObject): IO[Option[DbGame]] = io {
|
||||
super.findOne(query) flatMap (_.decode)
|
||||
}
|
||||
|
||||
def player(gameId: String, color: Color): IO[Option[DbPlayer]] =
|
||||
game(gameId) map { gameOption ⇒
|
||||
gameOption map { _ player color }
|
||||
}
|
||||
|
||||
def pov(gameId: String, color: Color): IO[Option[Pov]] =
|
||||
game(gameId) map { gameOption ⇒
|
||||
gameOption map { g ⇒ Pov(g, g player color) }
|
||||
}
|
||||
|
||||
def pov(gameId: String, color: String): IO[Option[Pov]] =
|
||||
Color(color).fold(io(none[Pov]))(pov(gameId, _))
|
||||
|
||||
def pov(fullId: String): IO[Option[Pov]] =
|
||||
game(fullId take gameIdSize) map { gameOption ⇒
|
||||
gameOption flatMap { g ⇒
|
||||
g player (fullId drop gameIdSize) map { Pov(g, _) }
|
||||
}
|
||||
}
|
||||
|
||||
def pov(ref: PovRef): IO[Option[Pov]] = pov(ref.gameId, ref.color)
|
||||
|
||||
def token(id: String): IO[String] = io {
|
||||
primitiveProjection[String](idSelector(id), "tk") | DbGame.defaultToken
|
||||
}
|
||||
|
||||
def save(game: DbGame): IO[Unit] = io {
|
||||
update(idSelector(game), _grater asDBObject game.encode)
|
||||
}
|
||||
|
||||
def save(progress: Progress): IO[Unit] =
|
||||
GameDiff(progress.origin.encode, progress.game.encode) |> {
|
||||
case (Nil, Nil) ⇒ io()
|
||||
case (sets, unsets) ⇒ {
|
||||
val fullSets = ("ua" -> new Date) :: sets
|
||||
val ops = unsets.isEmpty.fold(
|
||||
$set(fullSets),
|
||||
$set(fullSets) ++ $unset(unsets)
|
||||
)
|
||||
val wc = WriteConcern.None
|
||||
io { collection.update(idSelector(progress.origin), ops, concern = wc) }
|
||||
}
|
||||
}
|
||||
|
||||
def insert(game: DbGame): IO[Option[String]] = io {
|
||||
insert(game.encode)
|
||||
}
|
||||
|
||||
// makes the asumption that player 0 is white!
|
||||
// proved to be true on prod DB at March 31 2012
|
||||
def setEloDiffs(id: String, white: Int, black: Int) = io {
|
||||
update(idSelector(id), $set(Seq("p.0.ed" -> white, "p.1.ed" -> black)))
|
||||
}
|
||||
|
||||
def setUser(id: String, color: Color, user: User) = io {
|
||||
val pn = "p.%d".format(color.fold(0, 1))
|
||||
update(idSelector(id), $set(Seq(pn + ".uid" -> user.id, pn + ".elo" -> user.elo)))
|
||||
}
|
||||
|
||||
def incBookmarks(id: String, value: Int) = io {
|
||||
update(idSelector(id), $inc("bm" -> value))
|
||||
}
|
||||
|
||||
def finish(id: String, winnerId: Option[String]) = io {
|
||||
update(
|
||||
idSelector(id),
|
||||
(winnerId.fold($set(Seq.empty)) { userId ⇒ $set(Seq("wid" -> userId)) }) ++ $unset(Seq(
|
||||
"c.t",
|
||||
"ph",
|
||||
"lmt",
|
||||
"p.0.previousMoveTs",
|
||||
"p.1.previousMoveTs",
|
||||
"p.0.lastDrawOffer",
|
||||
"p.1.lastDrawOffer",
|
||||
"p.0.isOfferingDraw",
|
||||
"p.1.isOfferingDraw",
|
||||
"p.0.isProposingTakeback",
|
||||
"p.1.isProposingTakeback"
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
def findRandomStandardCheckmate(distribution: Int): IO[Option[DbGame]] = io {
|
||||
find(Query.mate ++ ("v" $exists false))
|
||||
.sort(Query.sortCreated)
|
||||
.limit(1)
|
||||
.skip(Random nextInt distribution)
|
||||
.toList.map(_.decode).flatten.headOption
|
||||
}
|
||||
|
||||
def denormalize(game: DbGame): IO[Unit] = io {
|
||||
val userIds = game.players.map(_.userId).flatten
|
||||
if (userIds.nonEmpty) update(idSelector(game), $set(Seq("uids" -> userIds)))
|
||||
if (game.mode.rated) update(idSelector(game), $set(Seq("ra" -> true)))
|
||||
if (game.variant.exotic) update(idSelector(game), $set(Seq("if" -> (Forsyth >> game.toChess))))
|
||||
}
|
||||
|
||||
def saveNext(game: DbGame, nextId: String): IO[Unit] = io {
|
||||
update(
|
||||
idSelector(game),
|
||||
$set(Seq("next" -> nextId)) ++
|
||||
$unset(Seq("p.0.isOfferingRematch", "p.1.isOfferingRematch"))
|
||||
)
|
||||
}
|
||||
|
||||
def initialFen(gameId: String): IO[Option[String]] = io {
|
||||
primitiveProjection[String](idSelector(gameId), "if")
|
||||
}
|
||||
|
||||
val unplayedIds: IO[List[String]] = io {
|
||||
primitiveProjections[String](
|
||||
("t" $lt 2) ++ ("ca" $lt (DateTime.now - 3.day) $gt (DateTime.now - 1.week)),
|
||||
"_id"
|
||||
)
|
||||
}
|
||||
|
||||
// bookmarks and pgns should also be removed
|
||||
def remove(id: String): IO[Unit] = io {
|
||||
remove(idSelector(id))
|
||||
}
|
||||
|
||||
// bookmarks and pgns should also be removed
|
||||
def removeIds(ids: List[String]): IO[Unit] = io {
|
||||
remove("_id" $in ids)
|
||||
}
|
||||
|
||||
val candidatesToAutofinish: IO[List[DbGame]] = io {
|
||||
find(Query.playable ++
|
||||
Query.clock(true) ++
|
||||
("ca" $gt (DateTime.now - 1.day)) ++ // index
|
||||
("ua" $lt (DateTime.now - 2.hour))
|
||||
).toList.map(_.decode).flatten
|
||||
}
|
||||
|
||||
def abandoned(max: Int): IO[List[DbGame]] = io {
|
||||
find(
|
||||
Query.notFinished ++ ("ua" $lt DbGame.abandonedDate)
|
||||
).limit(max).toList.map(_.decode).flatten
|
||||
}
|
||||
|
||||
val featuredCandidates: IO[List[DbGame]] = io {
|
||||
find(Query.playable ++
|
||||
Query.clock(true) ++
|
||||
("t" $gt 1) ++
|
||||
("ca" $gt (DateTime.now - 4.minutes)) ++
|
||||
("ua" $gt (DateTime.now - 15.seconds))
|
||||
).toList.map(_.decode).flatten
|
||||
}
|
||||
|
||||
def count(query: DBObject): IO[Int] = io {
|
||||
super.count(query).toInt
|
||||
}
|
||||
|
||||
def count(query: Query.type ⇒ DBObject): IO[Int] = count(query(Query))
|
||||
|
||||
def exists(id: String) = count(idSelector(id)) map (_ > 0)
|
||||
|
||||
def recentGames(limit: Int): IO[List[DbGame]] = io {
|
||||
find(Query.started ++ Query.turnsGt(1))
|
||||
.sort(Query.sortCreated)
|
||||
.limit(limit)
|
||||
.toList.map(_.decode).flatten
|
||||
}
|
||||
|
||||
def games(ids: List[String]): IO[List[DbGame]] = io {
|
||||
find("_id" $in ids).toList.map(_.decode).flatten
|
||||
} map { gs ⇒
|
||||
val gsMap = gs.map(g ⇒ g.id -> g).toMap
|
||||
ids.map(gsMap.get).flatten
|
||||
}
|
||||
|
||||
def nbPerDay(days: Int): IO[List[Int]] = ((days to 1 by -1).toList map { day ⇒
|
||||
val from = DateTime.now.withTimeAtStartOfDay - day.days
|
||||
val to = from + 1.day
|
||||
count(("ca" $gte from $lt to))
|
||||
}).sequence
|
||||
|
||||
def recentAverageElo(minutes: Int): IO[(Int, Int)] = io {
|
||||
val result = collection.mapReduce(
|
||||
mapFunction = """function() {
|
||||
emit(!!this.ra, this.p);
|
||||
}""",
|
||||
reduceFunction = """function(rated, values) {
|
||||
var sum = 0, nb = 0;
|
||||
values.forEach(function(game) {
|
||||
if(typeof game[0] != "undefined") {
|
||||
game.forEach(function(player) {
|
||||
if(player.elo) {
|
||||
sum += player.elo;
|
||||
++nb;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return nb == 0 ? nb : Math.round(sum / nb);
|
||||
}""",
|
||||
output = MapReduceInlineOutput,
|
||||
query = Some {
|
||||
("ca" $gte (DateTime.now - minutes.minutes)) ++ ("p.elo" $exists true)
|
||||
}
|
||||
)
|
||||
(for {
|
||||
ratedRow ← result.hasNext option result.next
|
||||
rated ← ratedRow.getAs[Double]("value")
|
||||
casualRow ← result.hasNext option result.next
|
||||
casual ← casualRow.getAs[Double]("value")
|
||||
} yield rated.toInt -> casual.toInt) | (0, 0)
|
||||
}
|
||||
|
||||
private def idSelector(game: DbGame): DBObject = idSelector(game.id)
|
||||
private def idSelector(id: String): DBObject = DBObject("_id" -> id)
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package lila.app
|
||||
package round
|
||||
|
||||
import game.DbGame
|
||||
|
||||
// events are kept in insertion/addition order
|
||||
case class Progress(origin: DbGame, game: DbGame, events: List[Event] = Nil) {
|
||||
|
||||
def map(f: DbGame ⇒ DbGame) = copy(game = f(game))
|
||||
|
||||
def flatMap(f: DbGame ⇒ Progress) = f(game) match {
|
||||
case Progress(_, g2, e2) ⇒ copy(game = g2, events = events ::: e2)
|
||||
}
|
||||
|
||||
def +(event: Event) = copy(events = events :+ event)
|
||||
|
||||
def ++(es: List[Event]) = copy(events = events ::: es)
|
||||
|
||||
def withGame(g: DbGame) = copy(game = g)
|
||||
}
|
||||
|
||||
object Progress {
|
||||
|
||||
def apply(game: DbGame): Progress =
|
||||
new Progress(game, game)
|
||||
|
||||
def apply(game: DbGame, events: List[Event]): Progress =
|
||||
new Progress(game, game, events)
|
||||
|
||||
def apply(game: DbGame, events: Event): Progress =
|
||||
new Progress(game, game, events :: Nil)
|
||||
}
|
|
@ -31,12 +31,4 @@ object Room {
|
|||
case "b" ⇒ "black"
|
||||
case _ ⇒ "system"
|
||||
}, encoded drop 1)
|
||||
|
||||
def render(msg: (String, String)): String = msg match {
|
||||
case (author, text) ⇒ """<li class="%s%s">%s</li>""".format(
|
||||
author,
|
||||
if (author == "system") " trans_me" else "",
|
||||
escapeXml(text)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,12 +34,4 @@ object WatcherRoom {
|
|||
case username :: rest ⇒ Message(Some(username), rest mkString "|")
|
||||
case Nil ⇒ Message(None, "")
|
||||
}
|
||||
|
||||
def render(msg: Message): String =
|
||||
"""<li><span>%s</span>%s</li>""".format(
|
||||
msg.username.fold("Anonymous") { u ⇒
|
||||
"""<a class="user_link" href="%s">%s</a>""".format(userRoute(u), u take 12)
|
||||
},
|
||||
escapeXml(msg.text)
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue