Test and implement game server

pull/1/merge
Thibault Duplessis 2012-03-03 16:51:21 +01:00
parent d14b03fc42
commit 970c39dc0f
8 changed files with 143 additions and 74 deletions

View File

@ -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)

View File

@ -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

View File

@ -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
}
}

View File

@ -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)

View File

@ -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
)
}
}

View File

@ -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
}

View File

@ -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
"""
}
}
}
}
}
}

View File

@ -12,4 +12,6 @@ trait SystemTest
with Fixtures {
implicit def stringToBoard(str: String): Board = Visual << str
def addNewLines(str: String) = "\n" + str + "\n"
}