Test and implement game server
parent
d14b03fc42
commit
970c39dc0f
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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) ⇒
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,4 +12,6 @@ trait SystemTest
|
|||
with Fixtures {
|
||||
|
||||
implicit def stringToBoard(str: String): Board = Visual << str
|
||||
|
||||
def addNewLines(str: String) = "\n" + str + "\n"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue