Integrate events in game flow

This commit is contained in:
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, ps = ps,
aiLevel = None, aiLevel = None,
isWinner = 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) elo = Some(1280)
) )
val white = newDbPlayer("white", "ip ar jp bn kp cb lp dq mp ek np fb op gn pp hr") 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 dest posAt(destString) toValid "Wrong dest " + destString
promotion Role promotable promString toValid "Wrong promotion " + promString promotion Role promotable promString toValid "Wrong promotion " + promString
gameAndPlayer repo player fullId toValid "Wrong ID " + fullId gameAndPlayer repo player fullId toValid "Wrong ID " + fullId
(game, player) = gameAndPlayer (g1, _) = gameAndPlayer
chessGame = game.toChess chessGame = g1.toChess
newChessGameAndMove chessGame(orig, dest, promotion) newChessGameAndMove chessGame(orig, dest, promotion)
(newChessGame, move) = newChessGameAndMove (newChessGame, move) = newChessGameAndMove
g1 = game update newChessGame g2 = g1.update(newChessGame, move)
eventStacks = game.eventStacks mapValues (_ withMove move optimize)
g2 = g1 withEventStacks eventStacks
result unsafe { repo save g2 } result unsafe { repo save g2 }
} yield newChessGame.situation.destinations } 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 { val allPieces = (game.board.pieces map {
case (pos, piece) (pos, piece, false) case (pos, piece) (pos, piece, false)
}) ++ (game.deads map { }) ++ (game.deads map {
case (pos, piece) (pos, piece, true) case (pos, piece) (pos, piece, true)
}) })
val events = Event fromMove move
copy( copy(
pgn = game.pgnMoves, pgn = game.pgnMoves,
players = for { players = for {
@ -92,23 +93,18 @@ case class DbGame(
if (dead) piece.role.forsyth.toUpper if (dead) piece.role.forsyth.toUpper
else piece.role.forsyth 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 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 { object DbGame {

View file

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

View file

@ -5,16 +5,19 @@ import lila.chess._
import Piotr._ import Piotr._
sealed trait Event { sealed trait Event {
def encode: Option[String] 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] def decode(str: String): Option[Event]
} }
object EventDecoder { object EventDecoder {
val all: Map[Char, EventDecoder] = Map( val all: Map[Char, EventDecoder] = Map(
's' -> StartEvent, 's' -> StartEvent,
'p' -> PossibleMovesEvent, 'p' -> PossibleMovesEvent,
@ -32,25 +35,20 @@ object EventDecoder {
} }
case class StartEvent() extends Event { case class StartEvent() extends Event {
def encode = Some("s") def encode = Some("s")
} }
object StartEvent extends EventDecoder { object StartEvent extends EventDecoder {
def decode(str: String) = Some(StartEvent()) def decode(str: String) = Some(StartEvent())
} }
case class MoveEvent(orig: Pos, dest: Pos, color: Color) extends Event { case class MoveEvent(orig: Pos, dest: Pos, color: Color) extends Event {
def encode = for { def encode = for {
o encodePos get orig o encodePos get orig
d encodePos get dest d encodePos get dest
} yield "m" + o + d + color.letter } yield "m" + o + d + color.letter
} }
object MoveEvent extends EventDecoder { object MoveEvent extends EventDecoder {
def apply(move: Move): MoveEvent = MoveEvent(move.orig, move.dest, move.piece.color) def apply(move: Move): MoveEvent = MoveEvent(move.orig, move.dest, move.piece.color)
def decode(str: String) = str.toList match { def decode(str: String) = str.toList match {
case List(o, d, c) for { case List(o, d, c) for {
orig decodePos get o orig decodePos get o
@ -62,7 +60,6 @@ object MoveEvent extends EventDecoder {
} }
case class PossibleMovesEvent(moves: Map[Pos, List[Pos]]) extends Event { case class PossibleMovesEvent(moves: Map[Pos, List[Pos]]) extends Event {
def encode = Some("p" + ((moves map { def encode = Some("p" + ((moves map {
case (orig, dests) for { case (orig, dests) for {
o encodePos get orig o encodePos get orig
@ -71,7 +68,6 @@ case class PossibleMovesEvent(moves: Map[Pos, List[Pos]]) extends Event {
}).flatten mkString ",")) }).flatten mkString ","))
} }
object PossibleMovesEvent extends EventDecoder { object PossibleMovesEvent extends EventDecoder {
def decode(str: String) = Some(PossibleMovesEvent( def decode(str: String) = Some(PossibleMovesEvent(
(str.split(",") map { line (str.split(",") map { line
line.toList match { line.toList match {
@ -87,13 +83,11 @@ object PossibleMovesEvent extends EventDecoder {
} }
case class EnpassantEvent(killed: Pos) extends Event { case class EnpassantEvent(killed: Pos) extends Event {
def encode = for { def encode = for {
k encodePos get killed k encodePos get killed
} yield "E" + k } yield "E" + k
} }
object EnpassantEvent extends EventDecoder { object EnpassantEvent extends EventDecoder {
def decode(str: String) = for { def decode(str: String) = for {
k str.headOption k str.headOption
killed decodePos get k 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 { case class CastlingEvent(king: (Pos, Pos), rook: (Pos, Pos), color: Color) extends Event {
def encode = for { def encode = for {
k1 encodePos get king._1 k1 encodePos get king._1
k2 encodePos get king._2 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 } yield "c" + k1 + k2 + r1 + r2 + color.letter
} }
object CastlingEvent extends EventDecoder { object CastlingEvent extends EventDecoder {
def decode(str: String) = str.toList match { def decode(str: String) = str.toList match {
case List(k1, k2, r1, r2, c) for { case List(k1, k2, r1, r2, c) for {
king1 decodePos get k1 king1 decodePos get k1
@ -126,22 +118,18 @@ object CastlingEvent extends EventDecoder {
} }
case class RedirectEvent(url: String) extends Event { case class RedirectEvent(url: String) extends Event {
def encode = Some("r" + url) def encode = Some("r" + url)
} }
object RedirectEvent extends EventDecoder { object RedirectEvent extends EventDecoder {
def decode(str: String) = Some(RedirectEvent(str)) def decode(str: String) = Some(RedirectEvent(str))
} }
case class PromotionEvent(role: PromotableRole, pos: Pos) extends Event { case class PromotionEvent(role: PromotableRole, pos: Pos) extends Event {
def encode = for { def encode = for {
p encodePos get pos p encodePos get pos
} yield "P" + role.forsyth + p } yield "P" + role.forsyth + p
} }
object PromotionEvent extends EventDecoder { object PromotionEvent extends EventDecoder {
def decode(str: String) = str.toList match { def decode(str: String) = str.toList match {
case List(r, p) for { case List(r, p) for {
role Role promotable r role Role promotable r
@ -152,13 +140,11 @@ object PromotionEvent extends EventDecoder {
} }
case class CheckEvent(pos: Pos) extends Event { case class CheckEvent(pos: Pos) extends Event {
def encode = for { def encode = for {
p encodePos get pos p encodePos get pos
} yield "C" + p } yield "C" + p
} }
object CheckEvent extends EventDecoder { object CheckEvent extends EventDecoder {
def decode(str: String) = for { def decode(str: String) = for {
p str.headOption p str.headOption
pos decodePos get p pos decodePos get p
@ -166,11 +152,9 @@ object CheckEvent extends EventDecoder {
} }
case class MessageEvent(author: String, message: String) extends Event { case class MessageEvent(author: String, message: String) extends Event {
def encode = Some("M" + author + " " + message.replace("|", "(pipe)")) def encode = Some("M" + author + " " + message.replace("|", "(pipe)"))
} }
object MessageEvent extends EventDecoder { object MessageEvent extends EventDecoder {
def decode(str: String) = str.split(' ').toList match { def decode(str: String) = str.split(' ').toList match {
case author :: words Some(MessageEvent( case author :: words Some(MessageEvent(
author, (words mkString " ").replace("(pipe)", "|") author, (words mkString " ").replace("(pipe)", "|")
@ -180,38 +164,30 @@ object MessageEvent extends EventDecoder {
} }
case class EndEvent() extends Event { case class EndEvent() extends Event {
def encode = Some("e") def encode = Some("e")
} }
object EndEvent extends EventDecoder { object EndEvent extends EventDecoder {
def decode(str: String) = Some(EndEvent()) def decode(str: String) = Some(EndEvent())
} }
case class ThreefoldEvent() extends Event { case class ThreefoldEvent() extends Event {
def encode = Some("t") def encode = Some("t")
} }
object ThreefoldEvent extends EventDecoder { object ThreefoldEvent extends EventDecoder {
def decode(str: String) = Some(ThreefoldEvent()) def decode(str: String) = Some(ThreefoldEvent())
} }
case class ReloadTableEvent() extends Event { case class ReloadTableEvent() extends Event {
def encode = Some("R") def encode = Some("R")
} }
object ReloadTableEvent extends EventDecoder { object ReloadTableEvent extends EventDecoder {
def decode(str: String) = Some(ReloadTableEvent()) def decode(str: String) = Some(ReloadTableEvent())
} }
case class MoretimeEvent(color: Color, seconds: Int) extends Event { case class MoretimeEvent(color: Color, seconds: Int) extends Event {
def encode = Some("T" + color.letter + seconds) def encode = Some("T" + color.letter + seconds)
} }
object MoretimeEvent extends EventDecoder { object MoretimeEvent extends EventDecoder {
def decode(str: String) = for { def decode(str: String) = for {
c str.headOption c str.headOption
color Color(c) 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 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 withEvents(newEvents: List[Event]): EventStack = {
def versionEvents(v: Int, events: List[Event]): List[(Int, Event)] = events match { 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 dbGame = newDbGame
val game = dbGame.toChess val game = dbGame.toChess
"identity" in { "identity" in {
val dbg2 = dbGame update game val dbg2 = dbGame.update(game, anyMove)
"white pieces" in { "white pieces" in {
dbg2 playerByColor "white" map (_.ps) map sortPs must_== { dbg2 playerByColor "white" map (_.ps) map sortPs must_== {
dbGame playerByColor "white" map (_.ps) map sortPs dbGame playerByColor "white" map (_.ps) map sortPs
@ -49,7 +49,7 @@ R QK q
H1 -> White.rook H1 -> White.rook
)) ))
"identity" in { "identity" in {
val dbg2 = dbGame update game val dbg2 = dbGame.update(game, anyMove)
"white pieces" in { "white pieces" in {
dbg2 playerByColor "white" map (_.ps) map sortPs must_== { dbg2 playerByColor "white" map (_.ps) map sortPs must_== {
dbGame playerByColor "white" map (_.ps) map sortPs dbGame playerByColor "white" map (_.ps) map sortPs
@ -62,7 +62,7 @@ R QK q
} }
} }
"new pieces positions" in { "new pieces positions" in {
val dbg2 = newDbGame update game val dbg2 = newDbGame.update(game, anyMove)
"white pieces" in { "white pieces" in {
dbg2 playerByColor "white" map (_.ps) map sortPs must_== { dbg2 playerByColor "white" map (_.ps) map sortPs must_== {
dbGame playerByColor "white" map (_.ps) map sortPs dbGame playerByColor "white" map (_.ps) map sortPs

View file

@ -73,24 +73,55 @@ class EventStackTest extends SystemTest {
} }
} }
"apply move events" in { "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 { "start with no events" in {
EventStack().events must beEmpty EventStack().events must beEmpty
} }
"add a move event" in { "move" in {
val stack = EventStack() withMove newMove( addMoves(EventStack(), newMove(
piece = White.pawn, orig = D2, dest = D4 piece = White.pawn, orig = D2, dest = D4
) )).events must_== Seq(
stack.events must_== Seq(
1 -> MoveEvent(D2, D4, White) 1 -> MoveEvent(D2, D4, White)
) )
} }
"add two move events" in { "capture" in {
val stack = EventStack() withMove newMove( addMoves(EventStack(), newMove(
piece = White.pawn, orig = D2, dest = D4 piece = White.pawn, orig = D2, dest = E3, capture = Some(E3)
) withMove newMove( )).events must_== Seq(
piece = Black.pawn, orig = D7, dest = D5 1 -> MoveEvent(D2, E3, White)
) )
stack.events must_== Seq( }
"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), 1 -> MoveEvent(D2, D4, White),
2 -> MoveEvent(D7, D5, Black) 2 -> MoveEvent(D7, D5, Black)
) )

View file

@ -3,6 +3,7 @@ package lila.system
import scala.util.Random import scala.util.Random
import lila.chess._ import lila.chess._
import Pos._
import model._ import model._
import DbGame._ import DbGame._
@ -134,4 +135,6 @@ trait Fixtures {
castles = castles, castles = castles,
promotion = promotion, promotion = promotion,
enpassant = enpassant) 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") 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 { "report success" in {
val game = insert() val game = insert()
sequenceValid(play(game)) must beSuccess sequenceValid(play(game)) must beSuccess
} }
"be persisted" in { "be persisted" in {
val game = insert()
play(game)
val found = repo game game.id
"update turns" in { "update turns" in {
val game = insert() found must beSome.like {
play(game)
repo game game.id must beSome.like {
case g g.turns must_== 27 case g g.turns must_== 27
} }
} }
"update board" in { "update board" in {
val game = insert() found must beSome.like {
play(game)
repo game game.id must beSome.like {
case g addNewLines(g.toChess.board.visual) must_== """ case g addNewLines(g.toChess.board.visual) must_== """
kr nr kr nr
p n ppp 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 Normal file
View file

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