Integrate events in game flow

pull/1/merge
Thibault Duplessis 2012-03-04 11:48:37 +01:00
parent d1f4bbdc50
commit 3ffbe38da7
11 changed files with 87 additions and 78 deletions

View File

@ -61,7 +61,7 @@ class Benchmark extends SimpleScalaBenchmark {
ps = ps,
aiLevel = None,
isWinner = None,
evts = Some("0s|1Msystem White creates the game|2Msystem Black joins the game|3r/ipkkf590ldrr"),
evts = "0s|1Msystem White creates the game|2Msystem Black joins the game",
elo = Some(1280)
)
val white = newDbPlayer("white", "ip ar jp bn kp cb lp dq mp ek np fb op gn pp hr")

View File

@ -16,13 +16,11 @@ final class Server(repo: GameRepo) {
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
(g1, _) = gameAndPlayer
chessGame = g1.toChess
newChessGameAndMove chessGame(orig, dest, promotion)
(newChessGame, move) = newChessGameAndMove
g1 = game update newChessGame
eventStacks = game.eventStacks mapValues (_ withMove move optimize)
g2 = g1 withEventStacks eventStacks
g2 = g1.update(newChessGame, move)
result unsafe { repo save g2 }
} yield newChessGame.situation.destinations

View File

@ -75,12 +75,13 @@ case class DbGame(
)
}
def update(game: Game): DbGame = {
def update(game: Game, move: Move): DbGame = {
val allPieces = (game.board.pieces map {
case (pos, piece) (pos, piece, false)
}) ++ (game.deads map {
case (pos, piece) (pos, piece, true)
})
val events = Event fromMove move
copy(
pgn = game.pgnMoves,
players = for {
@ -92,23 +93,18 @@ case class DbGame(
if (dead) piece.role.forsyth.toUpper
else piece.role.forsyth
}
} mkString " "
} mkString " ",
evts = (player.eventStack withEvents {
events ::: List(
PossibleMovesEvent(
if (color == game.player) game.situation.destinations else Map.empty
)
)
}).optimize encode
),
turns = game.turns
)
}
def eventStacks: Map[DbPlayer, EventStack] = players map { player =>
(player, EventStack decode player.evts)
} toMap
def withEventStacks(stacks: Map[DbPlayer, EventStack]): DbGame = copy(
players = players map { player
stacks get player some { stack
player.copy(evts = stack.encode)
} none player
}
)
}
object DbGame {

View File

@ -10,5 +10,7 @@ case class DbPlayer(
evts: String = "",
elo: Option[Int]) {
def eventStack = EventStack decode evts
def isAi = aiLevel.isDefined
}

View File

@ -5,16 +5,19 @@ import lila.chess._
import Piotr._
sealed trait Event {
def encode: Option[String]
}
object Event {
def fromMove(move: Move): List[Event] = MoveEvent(move) :: List(
if (move.enpassant) move.capture map EnpassantEvent.apply else None,
move.promotion map { role PromotionEvent(role, move.dest) }
).flatten
}
trait EventDecoder {
sealed trait EventDecoder {
def decode(str: String): Option[Event]
}
object EventDecoder {
val all: Map[Char, EventDecoder] = Map(
's' -> StartEvent,
'p' -> PossibleMovesEvent,
@ -32,25 +35,20 @@ object EventDecoder {
}
case class StartEvent() extends Event {
def encode = Some("s")
}
object StartEvent extends EventDecoder {
def decode(str: String) = Some(StartEvent())
}
case class MoveEvent(orig: Pos, dest: Pos, color: Color) extends Event {
def encode = for {
o encodePos get orig
d encodePos get dest
} yield "m" + o + d + color.letter
}
object MoveEvent extends EventDecoder {
def apply(move: Move): MoveEvent = MoveEvent(move.orig, move.dest, move.piece.color)
def decode(str: String) = str.toList match {
case List(o, d, c) for {
orig decodePos get o
@ -62,7 +60,6 @@ object MoveEvent extends EventDecoder {
}
case class PossibleMovesEvent(moves: Map[Pos, List[Pos]]) extends Event {
def encode = Some("p" + ((moves map {
case (orig, dests) for {
o encodePos get orig
@ -71,7 +68,6 @@ case class PossibleMovesEvent(moves: Map[Pos, List[Pos]]) extends Event {
}).flatten mkString ","))
}
object PossibleMovesEvent extends EventDecoder {
def decode(str: String) = Some(PossibleMovesEvent(
(str.split(",") map { line
line.toList match {
@ -87,13 +83,11 @@ object PossibleMovesEvent extends EventDecoder {
}
case class EnpassantEvent(killed: Pos) extends Event {
def encode = for {
k encodePos get killed
} yield "E" + k
}
object EnpassantEvent extends EventDecoder {
def decode(str: String) = for {
k str.headOption
killed decodePos get k
@ -101,7 +95,6 @@ object EnpassantEvent extends EventDecoder {
}
case class CastlingEvent(king: (Pos, Pos), rook: (Pos, Pos), color: Color) extends Event {
def encode = for {
k1 encodePos get king._1
k2 encodePos get king._2
@ -110,7 +103,6 @@ case class CastlingEvent(king: (Pos, Pos), rook: (Pos, Pos), color: Color) exten
} yield "c" + k1 + k2 + r1 + r2 + color.letter
}
object CastlingEvent extends EventDecoder {
def decode(str: String) = str.toList match {
case List(k1, k2, r1, r2, c) for {
king1 decodePos get k1
@ -126,22 +118,18 @@ object CastlingEvent extends EventDecoder {
}
case class RedirectEvent(url: String) extends Event {
def encode = Some("r" + url)
}
object RedirectEvent extends EventDecoder {
def decode(str: String) = Some(RedirectEvent(str))
}
case class PromotionEvent(role: PromotableRole, pos: Pos) extends Event {
def encode = for {
p encodePos get pos
} yield "P" + role.forsyth + p
}
object PromotionEvent extends EventDecoder {
def decode(str: String) = str.toList match {
case List(r, p) for {
role Role promotable r
@ -152,13 +140,11 @@ object PromotionEvent extends EventDecoder {
}
case class CheckEvent(pos: Pos) extends Event {
def encode = for {
p encodePos get pos
} yield "C" + p
}
object CheckEvent extends EventDecoder {
def decode(str: String) = for {
p str.headOption
pos decodePos get p
@ -166,11 +152,9 @@ object CheckEvent extends EventDecoder {
}
case class MessageEvent(author: String, message: String) extends Event {
def encode = Some("M" + author + " " + message.replace("|", "(pipe)"))
}
object MessageEvent extends EventDecoder {
def decode(str: String) = str.split(' ').toList match {
case author :: words Some(MessageEvent(
author, (words mkString " ").replace("(pipe)", "|")
@ -180,38 +164,30 @@ object MessageEvent extends EventDecoder {
}
case class EndEvent() extends Event {
def encode = Some("e")
}
object EndEvent extends EventDecoder {
def decode(str: String) = Some(EndEvent())
}
case class ThreefoldEvent() extends Event {
def encode = Some("t")
}
object ThreefoldEvent extends EventDecoder {
def decode(str: String) = Some(ThreefoldEvent())
}
case class ReloadTableEvent() extends Event {
def encode = Some("R")
}
object ReloadTableEvent extends EventDecoder {
def decode(str: String) = Some(ReloadTableEvent())
}
case class MoretimeEvent(color: Color, seconds: Int) extends Event {
def encode = Some("T" + color.letter + seconds)
}
object MoretimeEvent extends EventDecoder {
def decode(str: String) = for {
c str.headOption
color Color(c)

View File

@ -25,12 +25,6 @@ case class EventStack(events: Seq[(Int, Event)]) {
def version: Int = events.lastOption map (_._1) getOrElse 0
def withMove(move: Move): EventStack = withEvents(MoveEvent(move) :: {
move match {
case _ Nil
}
})
def withEvents(newEvents: List[Event]): EventStack = {
def versionEvents(v: Int, events: List[Event]): List[(Int, Event)] = events match {

View File

@ -13,7 +13,7 @@ class ChessToModelTest extends SystemTest {
val dbGame = newDbGame
val game = dbGame.toChess
"identity" in {
val dbg2 = dbGame update game
val dbg2 = dbGame.update(game, anyMove)
"white pieces" in {
dbg2 playerByColor "white" map (_.ps) map sortPs must_== {
dbGame playerByColor "white" map (_.ps) map sortPs
@ -49,7 +49,7 @@ R QK q
H1 -> White.rook
))
"identity" in {
val dbg2 = dbGame update game
val dbg2 = dbGame.update(game, anyMove)
"white pieces" in {
dbg2 playerByColor "white" map (_.ps) map sortPs must_== {
dbGame playerByColor "white" map (_.ps) map sortPs
@ -62,7 +62,7 @@ R QK q
}
}
"new pieces positions" in {
val dbg2 = newDbGame update game
val dbg2 = newDbGame.update(game, anyMove)
"white pieces" in {
dbg2 playerByColor "white" map (_.ps) map sortPs must_== {
dbGame playerByColor "white" map (_.ps) map sortPs

View File

@ -73,24 +73,55 @@ class EventStackTest extends SystemTest {
}
}
"apply move events" in {
def addMoves(eventStack: EventStack, moves: Move*) = moves.foldLeft(eventStack) {
case (stack, move) stack withEvents (Event fromMove move)
}
"start with no events" in {
EventStack().events must beEmpty
}
"add a move event" in {
val stack = EventStack() withMove newMove(
"move" in {
addMoves(EventStack(), newMove(
piece = White.pawn, orig = D2, dest = D4
)
stack.events must_== Seq(
)).events must_== Seq(
1 -> MoveEvent(D2, D4, White)
)
}
"add two move events" in {
val stack = EventStack() withMove newMove(
piece = White.pawn, orig = D2, dest = D4
) withMove newMove(
piece = Black.pawn, orig = D7, dest = D5
)
stack.events must_== Seq(
"capture" in {
addMoves(EventStack(), newMove(
piece = White.pawn, orig = D2, dest = E3, capture = Some(E3)
)).events must_== Seq(
1 -> MoveEvent(D2, E3, White)
)
}
"enpassant" in {
addMoves(EventStack(), newMove(
piece = White.pawn, orig = D5, dest = E6, capture = Some(E5), enpassant = true
)).events must_== Seq(
1 -> MoveEvent(D5, E6, White),
2 -> EnpassantEvent(E5)
)
}
"promotion" in {
addMoves(EventStack(), newMove(
piece = White.pawn, orig = D7, dest = D8, promotion = Some(Rook)
)).events must_== Seq(
1 -> MoveEvent(D7, D8, White),
2 -> PromotionEvent(Rook, D8)
)
}
"castling" in {
addMoves(EventStack(), newMove(
piece = White.king, orig = E1, dest = G1, castles = true
)).events must_== Seq(
1 -> MoveEvent(E1, G1, White),
2 -> CastlingEvent((E1, G1), (H1, F1), White)
)
}
"two moves" in {
addMoves(EventStack(),
newMove(piece = White.pawn, orig = D2, dest = D4),
newMove(piece = Black.pawn, orig = D7, dest = D5)
).events must_== Seq(
1 -> MoveEvent(D2, D4, White),
2 -> MoveEvent(D7, D5, Black)
)

View File

@ -3,6 +3,7 @@ package lila.system
import scala.util.Random
import lila.chess._
import Pos._
import model._
import DbGame._
@ -134,4 +135,6 @@ trait Fixtures {
castles = castles,
promotion = promotion,
enpassant = enpassant)
val anyMove = newMove(White.pawn, D2, D4)
}

View File

@ -54,24 +54,23 @@ RNBQKBNR
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
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 {
val game = insert()
play(game)
val found = repo game game.id
"update turns" in {
val game = insert()
play(game)
repo game game.id must beSome.like {
found must beSome.like {
case g g.turns must_== 27
}
}
"update board" in {
val game = insert()
play(game)
repo game game.id must beSome.like {
found must beSome.like {
case g addNewLines(g.toChess.board.visual) must_== """
kr nr
p n ppp
@ -84,6 +83,15 @@ B p p
"""
}
}
"event stacks" in {
val stack = found flatMap (_ playerByColor "white") map (_.eventStack)
"high version number" in {
stack must beSome.like { case s => s.version must be_>(20) }
}
"rotated" in {
stack must beSome.like { case s => s.events.size must_== 16 }
}
}
}
}
}

1
todo 100644
View File

@ -0,0 +1 @@
ensure I can not play my opponent move