complexify the codebase. Current excuse: crazyhouse.

pull/1472/merge
Thibault Duplessis 2016-01-15 20:21:16 +07:00
parent b2e80952c2
commit d9c0ff3eef
20 changed files with 146 additions and 79 deletions

View File

@ -6,7 +6,7 @@ import akka.actor._
import akka.pattern.ask
import scala.concurrent.duration._
import chess.format.UciMove
import chess.format.Uci
import lila.analyse.Info
import lila.common.Chronometer
import lila.game.{ Game, GameRepo }
@ -32,10 +32,10 @@ final class Client(
(millis - config.moveTime(level)) / 1000f
}
(for {
uciMove (UciMove(moveResult.move) toValid s"${game.id} wrong bestmove: $moveResult").future
uciMove (Uci.Move(moveResult.move) toValid s"${game.id} wrong bestmove: $moveResult").future
result game.toChess(uciMove.orig, uciMove.dest, uciMove.promotion).future
(c, move) = result
progress1 = game.update(c, move)
progress1 = game.update(c, Left(move))
progress = progress1.game.clock.filter(_.isRunning).fold(progress1) { clock =>
val newClock = clock.giveTime(move.color, aiLagSeconds)
progress1.flatMap(_ withClock newClock) + lila.game.Event.Clock(newClock)

View File

@ -1,6 +1,6 @@
package lila.analyse
import chess.format.UciMove
import chess.format.Uci
case class Evaluation(
score: Option[Score],
@ -26,7 +26,7 @@ object Evaluation {
case first :: rest if first != move => first :: rest
case _ => Nil
}
val best = variation.headOption flatMap UciMove.apply
val best = variation.headOption flatMap Uci.Move.apply
Info(
ply = index + 1 + startedAtPly,
score = after.score,

View File

@ -1,7 +1,7 @@
package lila.analyse
import chess.Color
import chess.format.UciMove
import chess.format.Uci
case class Info(
ply: Int,
@ -10,7 +10,7 @@ case class Info(
// variation is first in UCI, then converted to PGN before storage
variation: List[String] = Nil,
// best is always in UCI (used for hilight)
best: Option[UciMove] = None) {
best: Option[Uci.Move] = None) {
def turn = 1 + (ply - 1) / 2
@ -57,7 +57,7 @@ object Info {
case Array(cp) => Info(ply, Score(cp)).some
case Array(cp, ma) => Info(ply, Score(cp), parseIntOption(ma)).some
case Array(cp, ma, va) => Info(ply, Score(cp), parseIntOption(ma), va.split(' ').toList).some
case Array(cp, ma, va, be) => Info(ply, Score(cp), parseIntOption(ma), va.split(' ').toList, UciMove piotr be).some
case Array(cp, ma, va, be) => Info(ply, Score(cp), parseIntOption(ma), va.split(' ').toList, Uci.Move piotr be).some
case _ => none
}

View File

@ -1,8 +1,9 @@
package lila.analyse
import chess.format.pgn.Dumper
import chess.format.UciMove
import chess.format.Uci
import chess.{ Replay, Move, Situation }
import scalaz.Validation.FlatMap._
import lila.common.LilaException
@ -25,7 +26,7 @@ private[analyse] object UciToPgn {
def uciToPgn(ply: Int, variation: List[String]): Valid[List[PgnMove]] = for {
situation if (ply == replay.setup.startedAtTurn + 1) success(replay.setup.situation)
else replay moveAtPly ply map (_.situationBefore) toValid "No move found"
ucis variation.map(UciMove.apply).sequence toValid "Invalid UCI moves " + variation
ucis variation.map(Uci.Move.apply).sequence toValid "Invalid UCI moves " + variation
moves ucis.foldLeft[Valid[(Situation, List[Move])]](success(situation -> Nil)) {
case (scalaz.Success((sit, moves)), uci) =>
sit.move(uci.orig, uci.dest, uci.promotion) prefixFailuresWith s"ply $ply " map { move =>

@ -1 +1 @@
Subproject commit 6961025066a29b6907db4b243bad7c4a8abb68d2
Subproject commit e92da748fb3976b4351cf9c847810035e4bd3786

View File

@ -40,7 +40,7 @@ object BSONHandlers {
)
}
private[game] implicit val gameBSONHandler = new BSON[Game] {
implicit val gameBSONHandler = new BSON[Game] {
import Game.BSONFields._
import PgnImport.pgnImportBSONHandler

View File

@ -5,7 +5,7 @@ import play.api.libs.json._
import chess.Pos
import chess.Pos.{ piotr, allPiotrs }
import chess.{ PromotableRole, Pos, Color, Situation, Move => ChessMove, Clock => ChessClock, Status }
import chess.{ PromotableRole, Pos, Color, Situation, Move => ChessMove, Drop => ChessDrop, Clock => ChessClock, Status }
import lila.chat.{ Line, UserLine, PlayerLine }
import lila.common.Maths.truncateAt
@ -31,7 +31,6 @@ object Event {
case class Move(
orig: Pos,
dest: Pos,
color: Color,
san: String,
fen: String,
check: Boolean,
@ -65,7 +64,6 @@ object Event {
def apply(move: ChessMove, situation: Situation, state: State, clock: Option[Event]): Move = Move(
orig = move.orig,
dest = move.dest,
color = move.piece.color,
san = chess.format.pgn.Dumper(move),
fen = chess.format.Forsyth.exportBoard(situation.board),
check = situation.check,
@ -82,6 +80,45 @@ object Event {
possibleMoves = situation.destinations)
}
case class Drop(
role: chess.Role,
pos: Pos,
san: String,
fen: String,
check: Boolean,
threefold: Boolean,
state: State,
clock: Option[Event],
possibleMoves: Map[Pos, List[Pos]]) extends Event {
def typ = "drop"
def data = Json.obj(
"uci" -> s"${role.pgn}@${pos.key}",
"san" -> san,
"fen" -> fen,
"check" -> check.option(true),
"threefold" -> threefold.option(true),
"ply" -> state.turns,
"status" -> state.status,
"winner" -> state.winner,
"wDraw" -> state.whiteOffersDraw.option(true),
"bDraw" -> state.blackOffersDraw.option(true),
"clock" -> clock.map(_.data),
"dests" -> PossibleMoves.json(possibleMoves)
).noNull
}
object Drop {
def apply(drop: ChessDrop, situation: Situation, state: State, clock: Option[Event]): Drop = Drop(
role = drop.piece.role,
pos = drop.pos,
san = chess.format.pgn.Dumper(drop),
fen = chess.format.Forsyth.exportBoard(situation.board),
check = situation.check,
threefold = situation.threefoldRepetition,
state = state,
clock = clock,
possibleMoves = situation.destinations)
}
case class PossibleMoves(
color: Color,
moves: Map[Pos, List[Pos]]) extends Event {

View File

@ -4,7 +4,7 @@ import chess.Color.{ White, Black }
import chess.format.Uci
import chess.Pos.piotr, chess.Role.forsyth
import chess.variant.{ Variant, Crazyhouse }
import chess.{ History => ChessHistory, CheckCount, Castles, Role, Board, Move, Pos, Game => ChessGame, Clock, Status, Color, Piece, Mode, PositionHash }
import chess.{ History => ChessHistory, CheckCount, Castles, Role, Board, Move, Drop, MoveOrDrop, Pos, Game => ChessGame, Clock, Status, Color, Piece, Mode, PositionHash }
import org.joda.time.DateTime
import scala.concurrent.duration.FiniteDuration
@ -150,7 +150,7 @@ case class Game(
def update(
game: ChessGame,
move: Move,
moveOrDrop: MoveOrDrop,
blur: Boolean = false,
lag: Option[FiniteDuration] = None): Progress = {
val (history, situation) = (game.board.history, game.situation)
@ -158,7 +158,7 @@ case class Game(
def copyPlayer(player: Player) = player.copy(
blurs = math.min(
playerMoves(player.color),
player.blurs + (blur && move.color == player.color).fold(1, 0))
player.blurs + (blur && moveOrDrop.fold(_.color, _.color) == player.color).fold(1, 0))
)
val updated = copy(
@ -197,7 +197,10 @@ case class Game(
updated.playableCorrespondenceClock map Event.CorrespondenceClock.apply
}
val events = Event.Move(move, situation, state, clockEvent) ::
val events = moveOrDrop.fold(
Event.Move(_, situation, state, clockEvent),
Event.Drop(_, situation, state, clockEvent)
) ::
{
// abstraction leak, I know.
(updated.variant.threeCheck && situation.check) ?? List(Event.CheckCount(

View File

@ -2,7 +2,7 @@ package lila.importer
import akka.actor.ActorRef
import chess.Color
import chess.format.UciMove
import chess.format.Uci
import lila.game.{ Game, Player, Source, GameRepo, Pov }
import lila.hub.actorApi.map.Tell
import lila.round.actorApi.round._
@ -28,7 +28,7 @@ final class Live(
GameRepo game id flatMap {
_ filter (g => g.playable && g.imported) match {
case None => fufail("No such playing game: " + id)
case Some(game) => UciMove(move) match {
case Some(game) => Uci(move) match {
case None => move match {
case "1-0" => fuccess {
roundMap ! Tell(game.id, Resign(game.blackPlayer.id))
@ -48,12 +48,10 @@ final class Live(
}
}
private def applyMove(pov: Pov, move: UciMove) {
private def applyMove(pov: Pov, uci: Uci) {
roundMap ! Tell(pov.gameId, HumanPlay(
playerId = pov.playerId,
orig = move.orig.toString,
dest = move.dest.toString,
prom = move.promotion map (_.name),
uci = uci,
blur = false,
lag = 0.millis
))

View File

@ -4,6 +4,7 @@ import scala.util.{ Try, Success, Failure }
import org.joda.time.DateTime
import play.api.libs.json._
import scalaz.Validation.FlatMap._
private[opening] case class Generated(
fen: String,
@ -42,7 +43,7 @@ private[opening] object Generated {
implicit val generatedMoveJSONRead = Json.reads[Move]
implicit val generatedJSONRead = Json.reads[Generated]
import chess.format.UciMove
import chess.format.Uci
private[opening] def toPgn(
situation: chess.Situation,
@ -52,8 +53,8 @@ private[opening] object Generated {
player = situation.color)
(uciMoves.foldLeft(Try(game)) {
case (game, moveStr) => game flatMap { g =>
(UciMove(moveStr) toValid s"Invalid UCI move $moveStr" flatMap {
case UciMove(orig, dest, prom) => g(orig, dest, prom) map (_._1)
(Uci.Move(moveStr) toValid s"Invalid UCI move $moveStr" flatMap {
case Uci.Move(orig, dest, prom) => g(orig, dest, prom) map (_._1)
}).fold(errs => Failure(new Exception(errs.shows)), Success.apply)
}
}) map (_.pgnMoves)

View File

@ -2,7 +2,7 @@ package lila.puzzle
import scala.util.{ Try, Success, Failure }
import chess.format.{ Forsyth, UciMove }
import chess.format.{ Forsyth, Uci }
import chess.Game
import org.joda.time.DateTime
import play.api.libs.json._
@ -36,8 +36,8 @@ object Generated {
private[puzzle] def fenOf(moves: Seq[String]): Try[String] =
(moves.init.foldLeft(Try(Game(chess.variant.Standard))) {
case (game, moveStr) => game flatMap { g =>
(UciMove(moveStr) toValid s"Invalid UCI move $moveStr" flatMap {
case UciMove(orig, dest, prom) => g(orig, dest, prom) map (_._1)
(Uci.Move(moveStr) toValid s"Invalid UCI move $moveStr" flatMap {
case Uci.Move(orig, dest, prom) => g(orig, dest, prom) map (_._1)
}).fold(errs => Failure(new Exception(errs.shows)), Success.apply)
}
}) map { game =>

View File

@ -34,10 +34,10 @@ case class Puzzle(
def enabled = vote.sum > -9000
def fenAfterInitialMove: Option[String] = {
import chess.format.{ UciMove, Forsyth }
import chess.format.{ Uci, Forsyth }
for {
sit1 <- Forsyth << fen
uci <- UciMove(initialMove)
uci <- Uci.Move(initialMove)
sit2 <- sit1.move(uci.orig, uci.dest, uci.promotion).toOption map (_.situationAfter)
} yield Forsyth >> sit2
}

View File

@ -3,7 +3,7 @@ package lila.round
import org.joda.time.DateTime
import play.api.libs.json._
import chess.format.UciMove
import chess.format.Uci
import chess.{ Pos, Move }
import lila.game.Game
@ -12,7 +12,7 @@ case class Forecast(
steps: Forecast.Steps,
date: DateTime) {
def apply(g: Game, lastMove: Move): Option[(Forecast, UciMove)] =
def apply(g: Game, lastMove: Move): Option[(Forecast, Uci.Move)] =
nextMove(g, lastMove) map { move =>
copy(
steps = steps.collect {
@ -25,7 +25,7 @@ case class Forecast(
// accept up to 30 lines of 30 moves each
def truncate = copy(steps = steps.take(30).map(_ take 30))
private def nextMove(g: Game, last: Move) = steps.foldLeft(none[UciMove]) {
private def nextMove(g: Game, last: Move) = steps.foldLeft(none[Uci.Move]) {
case (None, fst :: snd :: _) if g.turns == fst.ply && fst.is(last) => snd.uciMove
case (move, _) => move
}
@ -45,10 +45,10 @@ object Forecast {
check: Option[Boolean],
dests: String) {
def is(move: Move) = move.uciString == uci
def is(move: UciMove) = move.uci == uci
def is(move: Move) = move.toUci.uci == uci
def is(move: Uci.Move) = move.uci == uci
def uciMove = UciMove(uci)
def uciMove = Uci.Move(uci)
}
implicit val forecastStepJsonFormat = Json.format[Step]

View File

@ -8,7 +8,7 @@ import org.joda.time.DateTime
import scala.concurrent.duration.Duration
import scala.concurrent.Promise
import chess.format.UciMove
import chess.format.Uci
import chess.Pos
import Forecast.Step
import lila.game.{ Pov, Game }
@ -44,13 +44,11 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) {
uciMove: String,
steps: Forecast.Steps): Funit =
if (!pov.isMyTurn) fufail("not my turn")
else UciMove(uciMove).fold[Funit](fufail(s"Invalid move $uciMove")) { uci =>
else Uci.Move(uciMove).fold[Funit](fufail(s"Invalid move $uciMove")) { uci =>
val promise = Promise[Unit]
roundMap ! Tell(pov.game.id, actorApi.round.HumanPlay(
playerId = pov.playerId,
orig = uci.orig.key,
dest = uci.dest.key,
prom = uci.promotion.map(_.name),
uci = uci,
blur = true,
lag = Duration.Zero,
promise = promise.some))
@ -73,7 +71,7 @@ final class ForecastApi(coll: Coll, roundMap: akka.actor.ActorSelection) {
else fuccess(fc.some)
}
def nextMove(g: Game, last: chess.Move): Fu[Option[UciMove]] = g.forecastable ?? {
def nextMove(g: Game, last: chess.Move): Fu[Option[Uci.Move]] = g.forecastable ?? {
loadForPlay(Pov player g) flatMap {
case None => fuccess(none)
case Some(fc) => fc(g, last) match {

View File

@ -1,8 +1,8 @@
package lila.round
import chess.format.Forsyth
import chess.format.{ Forsyth, Uci }
import chess.Pos.posAt
import chess.{ Status, Role, Color }
import chess.{ Status, Role, Color, MoveOrDrop }
import scalaz.Validation.FlatMap._
import actorApi.round.{ HumanPlay, AiPlay, ImportPlay, DrawNo, TakebackNo, PlayResult, Cheat, ForecastPlay }
@ -19,20 +19,26 @@ private[round] final class Player(
uciMemo: UciMemo) {
def human(play: HumanPlay, round: ActorRef)(pov: Pov): Fu[Events] = play match {
case HumanPlay(playerId, origS, destS, promS, blur, lag, promiseOption) => pov match {
case HumanPlay(playerId, uci, blur, lag, promiseOption) => pov match {
case Pov(game, color) if game playableBy color => {
(for {
orig posAt(origS) toValid "Wrong orig " + origS
dest posAt(destS) toValid "Wrong dest " + destS
promotion = Role promotable promS
newChessGameAndMove game.toChess(orig, dest, promotion, lag)
(newChessGame, move) = newChessGameAndMove
} yield game.update(newChessGame, move, blur, lag.some) -> move).prefixFailuresWith(s"$pov ")
(uci match {
case Uci.Move(orig, dest, prom) => game.toChess.apply(orig, dest, prom, lag) map {
case (ncg, move) => ncg -> (Left(move): MoveOrDrop)
}
case Uci.Drop(role, pos) => game.toChess.drop(role, pos, lag) map {
case (ncg, drop) => ncg -> (Right(drop): MoveOrDrop)
}
}).map {
case (newChessGame, moveOrDrop) =>
game.update(newChessGame, moveOrDrop, blur, lag.some) -> moveOrDrop
}.prefixFailuresWith(s"$pov ")
.fold(errs => ClientErrorException.future(errs.shows), fuccess).flatMap {
case (progress, move) =>
case (progress, moveOrDrop) =>
(GameRepo save progress) >>-
(pov.game.hasAi ! uciMemo.add(pov.game, move)) >>-
notifyMove(move, progress.game) >>
moveOrDrop.left.toOption.foreach { move =>
pov.game.hasAi ! uciMemo.add(pov.game, move)
} >>-
notifyMove(moveOrDrop, progress.game) >>
progress.game.finished.fold(
moveFinish(progress.game, color) map { progress.events ::: _ }, {
cheatDetector(progress.game) addEffect {
@ -41,7 +47,9 @@ private[round] final class Player(
if (progress.game.playableByAi) round ! AiPlay
if (pov.opponent.isOfferingDraw) round ! DrawNo(pov.player.id)
if (pov.player.isProposingTakeback) round ! TakebackNo(pov.player.id)
if (pov.game.forecastable) round ! ForecastPlay(move)
moveOrDrop.left.toOption.ifTrue(pov.game.forecastable).foreach { move =>
round ! ForecastPlay(move)
}
} inject progress.events
}) >>- promiseOption.foreach(_.success(()))
}
@ -60,7 +68,7 @@ private[round] final class Player(
case Pov(game, color) if game.turnOf(color) && game.playableEvenImported =>
game.toChess(orig, dest, promotion).future.flatMap {
case (newChessGame, move) =>
val progress = game.update(newChessGame, move)
val progress = game.update(newChessGame, Left(move))
(GameRepo save progress) >>-
(progress.game.finished ?? moveFinish(progress.game, color)) inject Nil
}
@ -72,20 +80,21 @@ private[round] final class Player(
(game.playable && game.player.isAi).fold(
engine.play(game, game.aiLevel | 1) flatMap {
case lila.ai.actorApi.PlayResult(progress, move, _) =>
notifyMove(move, progress.game)
notifyMove(Left(move), progress.game)
moveFinish(progress.game, game.turnColor) map { progress.++ }
},
fufail(s"Not AI turn")
) prefixFailure s"[ai play] game ${game.id} turn ${game.turns}"
private def notifyMove(move: chess.Move, game: Game) {
private def notifyMove(moveOrDrop: MoveOrDrop, game: Game) {
val color = moveOrDrop.fold(_.color, _.color)
bus.publish(MoveEvent(
gameId = game.id,
color = move.color,
color = color,
fen = Forsyth exportBoard game.toChess.board,
move = move.keyString,
move = moveOrDrop.fold(_.toUci.keys, _.toUci.uci),
mobilePushable = game.mobilePushable,
opponentUserId = game.player(!move.color).userId,
opponentUserId = game.player(!color).userId,
simulId = game.simulId
), 'moveEvent)
}

View File

@ -179,8 +179,7 @@ private[round] final class Round(
case ForecastPlay(lastMove) => handle { game =>
forecastApi.nextMove(game, lastMove) map { mOpt =>
mOpt foreach { move =>
self ! HumanPlay(
game.player.id, move.orig.key, move.dest.key, move.promotion.map(_.name), false, 0.seconds)
self ! HumanPlay(game.player.id, move, false, 0.seconds)
}
Nil
}

View File

@ -6,6 +6,7 @@ import scala.concurrent.Promise
import akka.actor._
import akka.pattern.{ ask, pipe }
import chess.Color
import chess.format.Uci
import play.api.libs.json.{ JsObject, Json }
import actorApi._, round._
@ -44,14 +45,25 @@ private[round] final class SocketHandler(
{
case ("p", o) => o int "v" foreach { v => socket ! PingVersion(uid, v) }
case ("move", o) => parseMove(o) foreach {
case (orig, dest, prom, blur, lag) =>
case (move, blur, lag) =>
member push ackEvent
val promise = Promise[Unit]
promise.future onFailure {
case _: Exception => socket ! Resync(uid)
}
round(HumanPlay(
playerId, orig, dest, prom, blur, lag.millis, promise.some
playerId, move, blur, lag.millis, promise.some
))
}
case ("drop", o) => parseDrop(o) foreach {
case (drop, blur, lag) =>
member push ackEvent
val promise = Promise[Unit]
promise.future onFailure {
case _: Exception => socket ! Resync(uid)
}
round(HumanPlay(
playerId, drop, blur, lag.millis, promise.some
))
}
case ("rematch-yes", _) => round(RematchYes(playerId))
@ -137,9 +149,19 @@ private[round] final class SocketHandler(
orig d str "from"
dest d str "to"
prom = d str "promotion"
move <- Uci.Move.fromStrings(orig, dest, prom)
blur = (d int "b") == Some(1)
lag = d int "lag"
} yield (orig, dest, prom, blur, ~lag)
} yield (move, blur, ~lag)
private def parseDrop(o: JsObject) = for {
d o obj "d"
role d str "role"
pos d str "pos"
drop <- Uci.Drop.fromStrings(role, pos)
blur = (d int "b") == Some(1)
lag = d int "lag"
} yield (drop, blur, ~lag)
private val ackEvent = Json.obj("t" -> "ack")
}

View File

@ -5,6 +5,7 @@ import scala.concurrent.duration.FiniteDuration
import scala.concurrent.Promise
import chess.Color
import chess.format.Uci
import lila.game.{ Game, Event, PlayerRef }
import lila.socket.SocketMember
@ -93,9 +94,7 @@ package round {
case class HumanPlay(
playerId: String,
orig: String,
dest: String,
prom: Option[String],
uci: Uci,
blur: Boolean,
lag: FiniteDuration,
promise: Option[Promise[Unit]] = None) {

View File

@ -1,6 +1,6 @@
package lila.socket
import chess.format.UciMove
import chess.format.Uci
import lila.common.PimpedJson._
import play.api.libs.json.JsObject
@ -17,7 +17,7 @@ case class AnaMove(
case (game, move) => Step(
ply = game.turns,
move = game.pgnMoves.lastOption.map { san =>
Step.Move(UciMove(move), san)
Step.Move(Uci(move), san)
},
fen = chess.format.Forsyth >> game,
check = game.situation.check,

View File

@ -1,6 +1,6 @@
package lila.socket
import chess.format.UciMove
import chess.format.Uci
import chess.Pos
import play.api.libs.functional.syntax._
@ -27,16 +27,16 @@ case class Step(
object Step {
case class Move(uci: UciMove, san: String) {
case class Move(uci: Uci, san: String) {
def uciString = uci.uci
}
case class Eval(
cp: Option[Int] = None,
mate: Option[Int] = None,
best: Option[UciMove])
best: Option[Uci.Move])
private implicit val uciJsonWriter: Writes[UciMove] = Writes { uci =>
private implicit val uciJsonWriter: Writes[Uci.Move] = Writes { uci =>
JsString(uci.uci)
}
private implicit val evalJsonWriter = Json.writes[Eval]