diff --git a/chess/src/main/scala/Game.scala b/chess/src/main/scala/Game.scala index 15c4db2c8f..5d284e7a16 100644 --- a/chess/src/main/scala/Game.scala +++ b/chess/src/main/scala/Game.scala @@ -7,7 +7,8 @@ case class Game( player: Color = White, pgnMoves: String = "", clock: Option[Clock] = None, - deads: List[(Pos, Piece)] = Nil) { + deads: List[(Pos, Piece)] = Nil, + turns: Int = 0) { def playMove( orig: Pos, @@ -18,6 +19,7 @@ case class Game( val newGame = copy( board = move.afterWithPositionHashesUpdated, player = !player, + turns = turns + 1, deads = (for { cpos ← move.capture cpiece ← board(cpos) diff --git a/chess/src/test/scala/PlayTest.scala b/chess/src/test/scala/PlayTest.scala index 5a714eeb51..7284e93992 100644 --- a/chess/src/test/scala/PlayTest.scala +++ b/chess/src/test/scala/PlayTest.scala @@ -7,15 +7,7 @@ class PlayTest extends ChessTest { "playing a game" should { "opening one" in { - val game = Game().playMoves( - E2 -> E4, - E7 -> E5, - F1 -> C4, - G8 -> F6, - D2 -> D3, - C7 -> C6, - C1 -> G5, - H7 -> H6) + val game = Game().playMoves(E2 -> E4, E7 -> E5, F1 -> C4, G8 -> F6, D2 -> D3, C7 -> C6, C1 -> G5, H7 -> H6) "current game" in { game must beSuccess.like { case g ⇒ addNewLines(g.board.visual) must_== """ @@ -46,29 +38,8 @@ RN QK NR } } "Deep Blue vs Kasparov 1" in { - Game().playMoves( - E2 -> E4, - C7 -> C5, - C2 -> C3, - D7 -> D5, - E4 -> D5, - D8 -> D5, - D2 -> D4, - G8 -> F6, - G1 -> F3, - C8 -> G4, - F1 -> E2, - E7 -> E6, - H2 -> H3, - G4 -> H5, - E1 -> G1, - B8 -> C6, - C1 -> E3, - C5 -> D4, - C3 -> D4, - F8 -> B4 - ) must beSuccess.like { - case g ⇒ addNewLines(g.board.visual) must_== """ + Game().playMoves(E2 -> E4, C7 -> C5, C2 -> C3, D7 -> D5, E4 -> D5, D8 -> D5, D2 -> D4, G8 -> F6, G1 -> F3, C8 -> G4, F1 -> E2, E7 -> E6, H2 -> H3, G4 -> H5, E1 -> G1, B8 -> C6, C1 -> E3, C5 -> D4, C3 -> D4, F8 -> B4) must beSuccess.like { + case g ⇒ addNewLines(g.board.visual) must_== """ r k r pp ppp n pn @@ -81,36 +52,8 @@ RN Q RK } } "Peruvian Immortal" in { - Game().playMoves( - E2 -> E4, - D7 -> D5, - E4 -> D5, - D8 -> D5, - B1 -> C3, - D5 -> A5, - D2 -> D4, - C7 -> C6, - G1 -> F3, - C8 -> G4, - C1 -> F4, - E7 -> E6, - H2 -> H3, - G4 -> F3, - D1 -> F3, - F8 -> B4, - F1 -> E2, - B8 -> D7, - A2 -> A3, - E8 -> C8, - A3 -> B4, - A5 -> A1, - E1 -> D2, - A1 -> H1, - F3 -> C6, - B7 -> C6, - E2 -> A6 - ) must beSuccess.like { - case g ⇒ addNewLines(g.board.visual) must_== """ + Game().playMoves(E2 -> E4, D7 -> D5, E4 -> D5, D8 -> D5, B1 -> C3, D5 -> A5, D2 -> D4, C7 -> C6, G1 -> F3, C8 -> G4, C1 -> F4, E7 -> E6, H2 -> H3, G4 -> F3, D1 -> F3, F8 -> B4, F1 -> E2, B8 -> D7, A2 -> A3, E8 -> C8, A3 -> B4, A5 -> A1, E1 -> D2, A1 -> H1, F3 -> C6, B7 -> C6, E2 -> A6) must beSuccess.like { + case g ⇒ addNewLines(g.board.visual) must_== """ kr nr p n ppp B p p diff --git a/system/src/main/scala/Server.scala b/system/src/main/scala/Server.scala new file mode 100644 index 0000000000..a2f7211476 --- /dev/null +++ b/system/src/main/scala/Server.scala @@ -0,0 +1,30 @@ +package lila.system + +import model._ +import lila.chess._ +import Pos.posAt + +final class Server(repo: GameRepo) { + + def playMove( + fullId: String, + moveString: String, + promString: Option[String] = None): Valid[Map[Pos, List[Pos]]] = for { + moveParts ← decodeMoveString(moveString) toValid "Wrong move" + (origString, destString) = moveParts + orig ← posAt(origString) toValid "Wrong orig " + origString + dest ← posAt(destString) toValid "Wrong dest " + destString + promotion ← Role promotable promString toValid "Wrong promotion " + promString + gameAndPlayer ← repo player fullId toValid "Wrong ID " + fullId + (game, player) = gameAndPlayer + chessGame = game.toChess + newChessGame ← chessGame.playMove(orig, dest, promotion) + newGame = game update newChessGame + result ← unsafe { repo save newGame } + } yield newChessGame.situation.destinations + + def decodeMoveString(moveString: String): Option[(String, String)] = moveString match { + case MoveString(orig, dest) ⇒ (orig, dest).some + case _ ⇒ none + } +} diff --git a/system/src/main/scala/SystemEnv.scala b/system/src/main/scala/SystemEnv.scala index eb9df46b6b..52419f6970 100644 --- a/system/src/main/scala/SystemEnv.scala +++ b/system/src/main/scala/SystemEnv.scala @@ -5,12 +5,13 @@ import com.mongodb.casbah.commons.conversions.scala._ import com.redis.RedisClient import com.typesafe.config._ -import repo._ - trait SystemEnv { val config: Config + def server = new Server( + repo = gameRepo) + def gameRepo = new GameRepo( mongodb(config getString "mongo.collection.game")) @@ -27,7 +28,7 @@ trait SystemEnv { object SystemEnv extends EnvBuilder { def apply(overrides: String = "") = new SystemEnv { - val config = makeConfig(overrides, "lila.conf") + val config = makeConfig(overrides, "/home/thib/lila/lila.conf") } } @@ -36,7 +37,6 @@ trait EnvBuilder { import java.io.File def makeConfig(sources: String*) = sources.foldLeft(ConfigFactory.defaultOverrides) { - case (config, source) if source isEmpty ⇒ config case (config, source) if source contains '=' ⇒ config.withFallback(ConfigFactory parseString source) case (config, source) ⇒ diff --git a/system/src/main/scala/model/DbGame.scala b/system/src/main/scala/model/DbGame.scala index 1ac8a6cfea..a6b0e049c8 100644 --- a/system/src/main/scala/model/DbGame.scala +++ b/system/src/main/scala/model/DbGame.scala @@ -16,9 +16,9 @@ case class DbGame( clock: Option[DbClock], lastMove: Option[String]) { - def playerById(id: String) = playersById get id + def playerById(id: String): Option[DbPlayer] = playersById get id - def playerByColor(color: String) = playersByColor get color + def playerByColor(color: String): Option[DbPlayer] = playersByColor get color lazy val playersByColor: Map[String, DbPlayer] = players map { p ⇒ (p.color, p) } toMap lazy val playersById: Map[String, DbPlayer] = players map { p ⇒ (p.id, p) } toMap @@ -33,8 +33,6 @@ case class DbGame( role ← Piotr.decodeRole get roleCode } yield (pos, Piece(color, role)) - val LastMove = """^([a-h][1-8]) ([a-h][1-8])$""".r - val (pieces, deads) = { for { player ← players @@ -54,7 +52,7 @@ case class DbGame( pieces, History( lastMove = lastMove flatMap { - case LastMove(a, b) ⇒ for (from ← posAt(a); to ← posAt(b)) yield (from, to) + case MoveString(a, b) ⇒ for (from ← posAt(a); to ← posAt(b)) yield (from, to) case _ ⇒ None } ) @@ -72,7 +70,8 @@ case class DbGame( limit = c.limit, times = Map(White -> whiteTime, Black -> blackTime) ), - deads = deads + deads = deads, + turns = turns ) } @@ -94,7 +93,8 @@ case class DbGame( else piece.role.forsyth } } mkString " " - ) + ), + turns = game.turns ) } } diff --git a/system/src/main/scala/package.scala b/system/src/main/scala/package.scala index 97fa94aa56..fb24839fd2 100644 --- a/system/src/main/scala/package.scala +++ b/system/src/main/scala/package.scala @@ -10,4 +10,6 @@ with scalaz.Booleans { implicit def addPP[A](a: A) = new { def pp[A] = a~println } + + val MoveString = """^([a-h][1-8]) ([a-h][1-8])$""".r } diff --git a/system/src/test/scala/ServerTest.scala b/system/src/test/scala/ServerTest.scala new file mode 100644 index 0000000000..f132ba9fe4 --- /dev/null +++ b/system/src/test/scala/ServerTest.scala @@ -0,0 +1,90 @@ +package lila.system + +import model._ + +class ServerTest extends SystemTest { + + val env = SystemEnv() + val repo = env.gameRepo + val server = env.server + + def insert() = { + val game = newDbGameWithRandomIds + repo insert game + game + } + def move(game: DbGame, m: String = "d2 d4") = for { + player ← game playerByColor "white" + fullId ← game fullIdOf player + } yield server.playMove(fullId, m) + + "the server" should { + "play a single move" in { + "report success" in { + val game = insert() + move(game) must beSome.like { case r ⇒ r must beSuccess } + } + "be persisted" in { + "update turns" in { + val game = insert() + move(game) + repo game game.id must beSome.like { + case g ⇒ g.turns must_== 1 + } + } + "update board" in { + val game = insert() + move(game) + repo game game.id must beSome.like { + case g ⇒ addNewLines(g.toChess.board.visual) must_== """ +rnbqkbnr +pppppppp + + + P + +PPP PPPP +RNBQKBNR +""" + } + } + } + } + "play the Peruvian Immortal" in { + + val moves = List("e2 e4", "d7 d5", "e4 d5", "d8 d5", "b1 c3", "d5 a5", "d2 d4", "c7 c6", "g1 f3", "c8 g4", "c1 f4", "e7 e6", "h2 h3", "g4 f3", "d1 f3", "f8 b4", "f1 e2", "b8 d7", "a2 a3", "e8 c8", "a3 b4", "a5 a1", "e1 d2", "a1 h1", "f3 c6", "b7 c6", "e2 a6") + + def play(game: DbGame) = for(m <- moves) yield move(game, m).get + + "report success" in { + val game = insert() + sequenceValid(play(game)) must beSuccess + } + "be persisted" in { + "update turns" in { + val game = insert() + play(game) + repo game game.id must beSome.like { + case g ⇒ g.turns must_== 27 + } + } + "update board" in { + val game = insert() + play(game) + repo game game.id must beSome.like { + case g ⇒ addNewLines(g.toChess.board.visual) must_== """ + kr nr +p n ppp +B p p + + P P B + N P + PPK PP + q +""" + } + } + } + } + } +} diff --git a/system/src/test/scala/SystemTest.scala b/system/src/test/scala/SystemTest.scala index c22e5b789c..f9ab1d924b 100644 --- a/system/src/test/scala/SystemTest.scala +++ b/system/src/test/scala/SystemTest.scala @@ -12,4 +12,6 @@ trait SystemTest with Fixtures { implicit def stringToBoard(str: String): Board = Visual << str + + def addNewLines(str: String) = "\n" + str + "\n" }