Skip to content

Commit

Permalink
Merge pull request #68 from niklasf/atomic-castling
Browse files Browse the repository at this point in the history
hash consistency when rook is (indirectly) exploded in atomic
  • Loading branch information
ornicar committed Mar 4, 2016
2 parents df77c5e + dc85807 commit 66acc38
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 55 deletions.
21 changes: 21 additions & 0 deletions src/main/scala/Board.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ case class Board(
b3 b2.place(pawn.color.queen, pos)
} yield b3

def castles: Castles = history.castles

def withHistory(h: History): Board = copy(history = h)

def withCastles(c: Castles) = withHistory(history withCastles c)
Expand All @@ -119,6 +121,25 @@ case class Board(

def ensureCrazyData = withCrazyData(crazyData | Crazyhouse.Data.init)

def fixCastles: Board = withCastles {
if (variant.allowsCastling) {
val wkPos = kingPosOf(White)
val bkPos = kingPosOf(Black)
val wkReady = wkPos.fold(false)(_.y == 1)
val bkReady = bkPos.fold(false)(_.y == 8)
def rookReady(color: Color, kPos: Option[Pos], left: Boolean) = kPos.fold(false) { kp =>
actorsOf(color) exists { a =>
a.piece.role == Rook && a.pos.y == kp.y && (left ^ (a.pos.x > kp.x))
}
}
Castles(
whiteKingSide = castles.whiteKingSide && wkReady && rookReady(White, wkPos, false),
whiteQueenSide = castles.whiteQueenSide && wkReady && rookReady(White, wkPos, true),
blackKingSide = castles.blackKingSide && bkReady && rookReady(Black, bkPos, false),
blackQueenSide = castles.blackQueenSide && bkReady && rookReady(Black, bkPos, true))
} else Castles.none
}

def updateHistory(f: History => History) = copy(history = f(history))

def count(p: Piece): Int = pieces.values count (_ == p)
Expand Down
32 changes: 9 additions & 23 deletions src/main/scala/Move.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,15 @@ case class Move(
val h2 = h1.copy(lastMove = Some(toUci))

// my broken castles
val h3 =
if ((piece is King) && h2.canCastle(color).any)
h2 withoutCastles color
else if (piece is Rook) (for {
kingPos after kingPosOf color
side Side.kingRookSide(kingPos, orig)
if h2 canCastle color on side
} yield h2.withoutCastle(color, side)) | h2
else h2

// opponent broken castles
val h4 = (for {
cPos capture
cPiece before(cPos)
if cPiece is Rook
kingPos after kingPosOf !color
side Side.kingRookSide(kingPos, cPos)
if h3 canCastle !color on side
} yield h3.withoutCastle(!color, side)) | h3

// captured king
if (after kingPosOf !color isDefined) h4 else h4 withoutCastles !color
}
if ((piece is King) && h2.canCastle(color).any)
h2 withoutCastles color
else if (piece is Rook) (for {
kingPos after kingPosOf color
side Side.kingRookSide(kingPos, orig)
if h2 canCastle color on side
} yield h2.withoutCastle(color, side)) | h2
else h2
} fixCastles

board.variant.finalizeBoard(board, toUci, capture flatMap before.apply) updateHistory { h =>
// Update position hashes last, only after updating the board,
Expand Down
4 changes: 4 additions & 0 deletions src/main/scala/Situation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ case class Situation(board: Board, color: Color) {
def drop(role: Role, pos: Pos): Valid[Drop] =
board.variant.drop(this, role, pos)

def fixCastles = copy(
board = board fixCastles
)

def withHistory(history: History) = copy(
board = board withHistory history
)
Expand Down
30 changes: 2 additions & 28 deletions src/main/scala/format/Forsyth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ object Forsyth {

def <<@(variant: Variant, rawSource: String): Option[Situation] = read(rawSource) { fen =>
makeBoard(variant, fen) map { board =>
val fixedSource = fixCastles(variant, fen) | fen
val splitted = fixedSource split ' '
val splitted = fen split ' '
val colorOption = splitted lift 1 flatMap (_ lift 0) flatMap Color.apply
val situation = colorOption match {
case Some(color) => Situation(board, color)
Expand All @@ -41,7 +40,7 @@ object Forsyth {
val history = History.make(lastMove, castles)
(splitted lift 6 flatMap makeCheckCount).fold(history)(history.withCheckCount)
}
}
} fixCastles
}
}

Expand Down Expand Up @@ -222,30 +221,5 @@ object Forsyth {
}
}

private[chess] def fixCastles(variant: Variant, fen: String): Option[String] =
fen.split(' ').toList match {
case boardStr :: color :: castlesStr :: rest if !variant.allowsCastling =>
Some(s"$boardStr $color - ${rest.mkString(" ")}")
case boardStr :: color :: castlesStr :: rest => makeBoard(variant, boardStr) map { board =>
val c1 = Castles(castlesStr)
val wkPos = board.kingPosOf(White)
val bkPos = board.kingPosOf(Black)
val wkReady = wkPos.fold(false)(_.y == 1)
val bkReady = bkPos.fold(false)(_.y == 8)
def rookReady(color: Color, kPos: Option[Pos], left: Boolean) = kPos.fold(false) { kp =>
board actorsOf color exists { a =>
a.piece.role == Rook && a.pos.y == kp.y && (left ^ (a.pos.x > kp.x))
}
}
val c2 = Castles(
whiteKingSide = c1.whiteKingSide && wkReady && rookReady(White, wkPos, false),
whiteQueenSide = c1.whiteQueenSide && wkReady && rookReady(White, wkPos, true),
blackKingSide = c1.blackKingSide && bkReady && rookReady(Black, bkPos, false),
blackQueenSide = c1.blackQueenSide && bkReady && rookReady(Black, bkPos, true))
s"$boardStr $color $c2 ${rest.mkString(" ")}"
}
case _ => None
}

private def read[A](source: String)(f: String => A): A = f(source.replace("_", " ").trim)
}
18 changes: 16 additions & 2 deletions src/test/scala/HashTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package chess

import Pos._
import format.Uci
import variant.{Standard, Crazyhouse, ThreeCheck, Antichess}
import variant.{ Standard, Crazyhouse, ThreeCheck, Antichess, Atomic }

class HashTest extends ChessTest {

Expand Down Expand Up @@ -122,7 +122,7 @@ class HashTest extends ChessTest {
hashAfterMove mustEqual hashAfter
}

"be consistent in antichess" in {
"be consistent when king is captured in antichess" in {
val fen = "rnbqkb1r/ppp1pppp/3p1n2/1B6/8/4P3/PPPP1PPP/RNBQK1NR w KQkq - 2 3"
val situation = ((format.Forsyth << fen) get) withVariant Antichess
val move = situation.move(Pos.B5, Pos.E8, None).toOption.get
Expand All @@ -135,6 +135,20 @@ class HashTest extends ChessTest {

hashAfterMove mustEqual hashAfter
}

"be consistent when rook is exploded in atomic" in {
val fen = "rnbqkb1r/ppppp1pp/5p1n/6N1/8/8/PPPPPPPP/RNBQKB1R w KQkq - 2 3"
val situation = format.Forsyth.<<@(Atomic, fen).get
val move = situation.move(Pos.G5, Pos.H7, None).toOption.get
val hashAfterMove = hash(move.situationAfter)

// 3. Nxh7
val fenAfter = "rnbqkb2/ppppp1p1/5p2/8/8/8/PPPPPPPP/RNBQKB1R b KQkq - 0 3"
val situationAfter = format.Forsyth.<<@(Atomic, fenAfter).get
val hashAfter = hash(situationAfter)

hashAfterMove mustEqual hashAfter
}
}

}
4 changes: 2 additions & 2 deletions src/test/scala/format/ForsythTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ class ForsythTest extends ChessTest {
}
}
"fix impossible castle flags" should {
def fixCastles(fen: String) = f.fixCastles(Standard, fen)
def fixCastles(fen: String) = (f << fen).map(f >> _)
"messed up" in {
val fen = "yayyyyyyyyyyy"
fixCastles(fen) must beNone
Expand Down Expand Up @@ -242,7 +242,7 @@ class ForsythTest extends ChessTest {
"castling not allowed in variant" in {
val fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
val fix = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"
f.fixCastles(Antichess, fen) must beSome(fix)
(f <<@(Antichess, fen)).map(f >> _) must beSome(fix)
}
}
"ignore impossible en passant squares" should {
Expand Down

0 comments on commit 66acc38

Please sign in to comment.