Improve engine performances

pull/1/merge
Thibault Duplessis 2012-02-29 01:16:16 +01:00
parent 2aca12c94d
commit 7ede900287
13 changed files with 95 additions and 73 deletions

View File

@ -2,24 +2,40 @@ package lila.benchmark
import annotation.tailrec
import com.google.caliper.Param
import ornicar.scalalib.OrnicarValidation
import lila.chess.Game
import lila.chess.{ Game, Pos }
import lila.chess.Pos._
// a caliper benchmark is a class that extends com.google.caliper.Benchmark
// the SimpleScalaBenchmark trait does it and also adds some convenience functionality
class Benchmark extends SimpleScalaBenchmark {
class Benchmark extends SimpleScalaBenchmark with OrnicarValidation {
@Param(Array("100"))
val length: Int = 0
def timeImmortal(reps: Int) = repeat(reps) {
val s = 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)
val s = 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)
if (s.isFailure) throw new Exception("success")
"haha"
}
def timeDeepBlue(reps: Int) = repeat(reps) {
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)
//def timeDeepBlue(reps: Int) = repeat(reps) {
//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)
//}
def playMoves(moves: (Pos, Pos)*): Valid[Game] = {
val game = moves.foldLeft(success(Game()): Valid[Game]) {
(vg, move) {
vg flatMap { g
// will be called to pass as playable squares to the client
g.situation.destinations
g.playMove(move._1, move._2)
}
}
}
if (game.isFailure) throw new RuntimeException("Failure!")
game
}
}

View File

@ -168,5 +168,5 @@ case class Actor(piece: Piece, pos: Pos, board: Board) {
private def history = board.history
private def friends = board occupation color
private def enemies = board occupation !color
private val pawnDir: Direction = if (color == White) _.up else _.down
private lazy val pawnDir: Direction = if (color == White) _.up else _.down
}

View File

@ -43,8 +43,6 @@ case class Board(pieces: Map[Pos, Piece], history: History) {
if (pieces contains at) None
else Some(copy(pieces = pieces + ((at, piece))))
def takeValid(at: Pos): Valid[Board] = take(at) toSuccess ("No piece at " + at + " to take")
def take(at: Pos): Option[Board] = pieces get at map { piece
copy(pieces = pieces - at)
}
@ -87,8 +85,6 @@ case class Board(pieces: Map[Pos, Piece], history: History) {
def updateHistory(f: History History) = copy(history = f(history))
def as(c: Color) = Situation(this, c)
def count(p: Piece) = pieces.values count (_ == p)
def visual = Visual >> this

View File

@ -7,26 +7,15 @@ case class Game(
player: Color,
reversedPgnMoves: List[String] = Nil) {
def this() = this(Board.empty, White)
def playMoves(moves: (Pos, Pos)*): Valid[Game] =
moves.foldLeft(success(this): Valid[Game]) { (sit, move)
sit flatMap { s s.playMove(move._1, move._2) }
}
def playMove(from: Pos, to: Pos, promotion: PromotableRole = Queen): Valid[Game] = for {
move situation.move(from, to, promotion)
} yield copy(
board = move.after,
player = !player,
reversedPgnMoves = (PgnDump move move) :: reversedPgnMoves
)
} yield {
val newGame = copy(board = move.after, player = !player)
val pgnMove = PgnDump.move(situation, move, newGame.situation)
newGame.copy(reversedPgnMoves = pgnMove :: reversedPgnMoves)
}
val players = List(White, Black)
def situation = board as player
def as(c: Color) = copy(player = c)
lazy val situation = Situation(board, player)
def pgnMoves = reversedPgnMoves.reverse
}

View File

@ -13,14 +13,6 @@ case class Move(
def withHistory(h: History) = copy(after = after withHistory h)
def situation = before as piece.color
// does this move check the opponent?
def checks: Boolean = (after as !color).check
// does this move checkmate the opponent?
def checkMates: Boolean = (after as !color).checkMate
// does this move capture an opponent piece?
def captures = capture.isDefined

View File

@ -41,12 +41,9 @@ sealed case class Pos private (x: Int, y: Int) {
object Pos {
private val bounds: Set[Int] = (1 to 8) toSet
def makePos(x: Int, y: Int): Option[Pos] = allCoords get (x, y)
def makePos(x: Int, y: Int): Option[Pos] =
if (bounds(x) && bounds(y)) Some(Pos(x, y)) else None
def unsafe(x: Int, y: Int): Pos = Pos(x, y)
def unsafe(x: Int, y: Int): Pos = allCoords((x, y))
def xToString(x: Int) = (96 + x).toChar.toString
@ -117,5 +114,14 @@ object Pos {
val allKeys: Map[String, Pos] = Map("a1" -> A1, "a2" -> A2, "a3" -> A3, "a4" -> A4, "a5" -> A5, "a6" -> A6, "a7" -> A7, "a8" -> A8, "b1" -> B1, "b2" -> B2, "b3" -> B3, "b4" -> B4, "b5" -> B5, "b6" -> B6, "b7" -> B7, "b8" -> B8, "c1" -> C1, "c2" -> C2, "c3" -> C3, "c4" -> C4, "c5" -> C5, "c6" -> C6, "c7" -> C7, "c8" -> C8, "d1" -> D1, "d2" -> D2, "d3" -> D3, "d4" -> D4, "d5" -> D5, "d6" -> D6, "d7" -> D7, "d8" -> D8, "e1" -> E1, "e2" -> E2, "e3" -> E3, "e4" -> E4, "e5" -> E5, "e6" -> E6, "e7" -> E7, "e8" -> E8, "f1" -> F1, "f2" -> F2, "f3" -> F3, "f4" -> F4, "f5" -> F5, "f6" -> F6, "f7" -> F7, "f8" -> F8, "g1" -> G1, "g2" -> G2, "g3" -> G3, "g4" -> G4, "g5" -> G5, "g6" -> G6, "g7" -> G7, "g8" -> G8, "h1" -> H1, "h2" -> H2, "h3" -> H3, "h4" -> H4, "h5" -> H5, "h6" -> H6, "h7" -> H7, "h8" -> H8)
val allCoords: Map[(Int, Int), Pos] = {
for {
x 1 to 8
xString = xToString(x)
y 1 to 8
key = xString + y.toString
} yield (x, y) -> allKeys(key)
} toMap
def all = allKeys.values
}

View File

@ -8,6 +8,8 @@ case class Situation(board: Board, color: Color) {
case actor if actor.moves.nonEmpty actor.pos -> actor.moves
} toMap
lazy val destinations: Map[Pos, List[Pos]] = moves mapValues { ms => ms map (_.dest) }
lazy val check: Boolean = board kingPosOf color map { king
board actorsOf !color exists (_ threatens king)
} getOrElse false
@ -33,9 +35,7 @@ case class Situation(board: Board, color: Color) {
b3 b2.place(color - promotion, to)
} yield m.copy(after = b3, promotion = Some(promotion))
} toSuccess "Invalid move %s->%s".format(from, to).wrapNel
def as(c: Color) = copy(color = c)
} toSuccess "Invalid move %s %s".format(from, to).wrapNel
}
object Situation {

View File

@ -3,27 +3,21 @@ package format
object PgnDump {
def move(m: Move): String = {
import m._
def disambiguate = {
val candidates = situation.actors filter { a
a.pos != orig && a.piece.role == piece.role && (a.destinations contains dest)
}
if (candidates.isEmpty) ""
else if (candidates exists (_.pos ?| orig)) orig.file + orig.rank else orig.file
}
def capturing = if (captures) "x" else ""
def checking = if (checkMates) "#" else if (checks) "+" else ""
def move(situation: Situation, data: Move, nextSituation: Situation): String = {
import data._
((promotion, piece.role) match {
case _ if castle if (orig ?> dest) "O-O-O" else "O-O"
case _ if enpassant orig.file + 'x' + dest.rank
case (Some(promotion), _) dest.key + promotion.pgn
case (_, Pawn) if captures orig.file + 'x' + dest.key
case (_, Pawn) dest.key
case (_, role) role.pgn + disambiguate + capturing + dest.key
case _ "?"
}) + checking
case _ if castle if (orig ?> dest) "O-O-O" else "O-O"
case _ if enpassant orig.file + 'x' + dest.rank
case (Some(promotion), _) dest.key + promotion.pgn
case (_, Pawn) (if (captures) orig.file + "x" else "") + dest.key
case (_, role) role.pgn + {
val candidates = situation.actors filter { a
a.piece.role == piece.role && a.pos != orig && (a.destinations contains dest)
}
if (candidates.isEmpty) ""
else if (candidates exists (_.pos ?| orig)) orig.file + orig.rank else orig.file
} + (if (captures) "x" else "") + dest.key
case _ "?"
}) + (if (nextSituation.check) if (nextSituation.checkMate) "#" else "+" else "")
}
}

View File

@ -23,7 +23,7 @@ class BoardTest extends LilaTest {
}
"allow a piece to be taken" in {
board takeValid A1 must beSuccess.like {
board take A1 must beSome.like {
case b b(A1) must beNone
}
}

View File

@ -12,6 +12,21 @@ trait LilaTest
implicit def stringToBoard(str: String): Board = Visual << str
implicit def stringToSituationBuilder(str: String) = new {
def as(color: Color): Situation = Situation(Visual << str, color)
}
implicit def richGame(game: Game) = new {
def as(color: Color): Game = game.copy(player = color)
def playMoves(moves: (Pos, Pos)*): Valid[Game] =
moves.foldLeft(success(game): Valid[Game]) { (vg, move)
vg flatMap { g g.playMove(move._1, move._2) }
}
}
def bePoss(poss: Pos*): Matcher[Option[Iterable[Pos]]] = beSome.like {
case p sortPoss(p.toList) must_== sortPoss(poss.toList)
}

View File

@ -0,0 +1,12 @@
package lila.chess
import Pos._
class PlayOneMoveTest extends LilaTest {
"playing a move" should {
"only process things once" in {
Game().playMoves(E2 -> E4) must beSuccess
}
}
}

View File

@ -5,17 +5,19 @@ import Pos._
class PgnDumpTest extends LilaTest {
"complete game dump" should {
"only moves" in {
val gioachineGreco = Game().playMoves(D2 -> D4, D7 -> D5, C2 -> C4, D5 -> C4, E2 -> E3, B7 -> B5, A2 -> A4, C7 -> C6, A4 -> B5, C6 -> B5, D1 -> F3)
val peruvianImmortal = 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)
"dump a game to pgn" should {
"move list" in {
"Gioachine Greco" in {
val game = Game().playMoves(D2 -> D4, D7 -> D5, C2 -> C4, D5 -> C4, E2 -> E3, B7 -> B5, A2 -> A4, C7 -> C6, A4 -> B5, C6 -> B5, D1 -> F3)
game map (_.pgnMoves) must beSuccess.like {
gioachineGreco map (_.pgnMoves) must beSuccess.like {
case ms ms must_== ("d4 d5 c4 dxc4 e3 b5 a4 c6 axb5 cxb5 Qf3" split ' ').toSeq
}
}
"Peruvian Immortal" in {
val game = 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)
game map (_.pgnMoves) must beSuccess.like {
peruvianImmortal map (_.pgnMoves) must beSuccess.like {
case ms ms must_== ("e4 d5 exd5 Qxd5 Nc3 Qa5 d4 c6 Nf3 Bg4 Bf4 e6 h3 Bxf3 Qxf3 Bb4 Be2 Nd7 a3 O-O-O axb4 Qxa1+ Kd2 Qxh1 Qxc6+ bxc6 Ba6#" split ' ').toSeq
}
}

View File

@ -42,8 +42,8 @@ object ApplicationBuild extends Build with Resolvers with Dependencies {
lazy val benchmark = Project("benchmark", file("benchmark"), settings = Project.defaultSettings).settings(
fork in run := true,
libraryDependencies := Seq(instrumenter, gson),
resolvers := Seq(codahale, sonatype),
libraryDependencies := Seq(scalalib, instrumenter, gson),
resolvers := Seq(iliaz, codahale, sonatype),
shellPrompt := ShellPrompt.buildShellPrompt,
scalacOptions := Seq("-deprecation", "-unchecked"),
// we need to add the runtime classpath as a "-cp" argument