Improve engine performances
parent
2aca12c94d
commit
7ede900287
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 "")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue