Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support impossible checks when validate position #446

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions src/main/scala/Situation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import bitboard.Bitboard.*

import chess.format.Uci
import Square.prevRank
import chess.variant.Crazyhouse
import chess.variant.Antichess
import chess.variant.{ Antichess, Crazyhouse, Standard }

case class Situation(board: Board, color: Color):
export board.{ history, isOccupied, kingOf, variant }
Expand Down Expand Up @@ -50,7 +49,35 @@ case class Situation(board: Board, color: Color):
inline def winner: Option[Color] = variant.winner(this)

def playable(strict: Boolean): Boolean =
(board valid strict) && !end && copy(color = !color).check.no
(board valid strict) && !end && copy(color = !color).check.no && hasValidCheckers
lenguyenthanh marked this conversation as resolved.
Show resolved Hide resolved

private def hasValidCheckers: Boolean =
variant != Standard ||
checkers.fold(true) { checkers_ =>
checkers_.squares.isEmpty || (isValidCheckersForEnPassant(
lenguyenthanh marked this conversation as resolved.
Show resolved Hide resolved
checkers_
) && isValidChecksForMultipleCheckers(checkers_))
}

private def isValidCheckersForEnPassant(activeCheckers: Bitboard): Boolean =
val attackingSliders = board.sliders.squares.filter(slider_ => activeCheckers.squares.head == slider_)
lenguyenthanh marked this conversation as resolved.
Show resolved Hide resolved
potentialEpSquare.fold(true) { enPassantSquare_ =>
val enPassantOriginalSquare =
lenguyenthanh marked this conversation as resolved.
Show resolved Hide resolved
(!color).fold[Square](enPassantSquare_.down.get, enPassantSquare_.up.get)
val enPassantFinalSquare =
(!color).fold[Square](enPassantSquare_.up.get, enPassantSquare_.down.get)
activeCheckers.squares.size == 1 &&
(enPassantFinalSquare == activeCheckers.squares.head
|| attackingSliders.size == 1 && between(
ourKing.get,
attackingSliders.head
).squares.contains(enPassantOriginalSquare))
}

private def isValidChecksForMultipleCheckers(activeCheckers: Bitboard): Boolean =
activeCheckers.squares.size <= 1 || (activeCheckers.squares.size == 2 && {
!Bitboard.aligned(activeCheckers.squares.head, activeCheckers.squares.last, ourKing.get)
lenguyenthanh marked this conversation as resolved.
Show resolved Hide resolved
})

lazy val status: Option[Status] =
if checkMate then Status.Mate.some
Expand Down
66 changes: 66 additions & 0 deletions src/test/scala/SituationTest.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package chess

import chess.format.{ EpdFen, Fen }
import chess.variant.Standard

class SituationTest extends ChessTest:

"a game" should:
Expand Down Expand Up @@ -86,3 +89,66 @@ K r
val game = "k Q K" as White
game.playable(true) must beFalse
game.playable(false) must beFalse

"when previous move is a double pawn push and checker is not the pushed pawn or a sliding piece" in:
val game1 = Fen
.read(Standard, EpdFen("r1bqkbnr/1p1p1ppp/p7/2pPp3/4P3/5n2/PPP2PPP/RNBQKBNR w KQkq c6 0 4"))
.get
val game2 = Fen
.read(Standard, EpdFen("r1bqkbnr/1p1p1ppp/p7/2pP4/4P3/8/PPP2pPP/RNBQKBNR w KQkq c6 0 4"))
.get

game1.playable(true) must beFalse
game1.playable(false) must beFalse
game2.playable(true) must beFalse
game2.playable(false) must beFalse

"when previous move is a double pawn push and the only checker is a rook but not discovered check" in:
val game = Fen
.read(Standard, EpdFen("1k6/5K1r/p7/2pP4/4P3/8/PPP3PP/RNBQ1BNR w HA c6 0 4"))
.get
game.playable(true) must beFalse
game.playable(false) must beFalse

"when previous move is a double pawn push and the only checker is a bishop but not discovered check" in:
val game = Fen
.read(Standard, EpdFen("2b4r/kr5p/p7/2pP2b1/4PK2/8/PPP3PP/RNBQ1BNR w HAh c6 0 4"))
.get
game.playable(true) must beFalse
game.playable(false) must beFalse

"when multiple checkers are aligned with the king" in:
val game = Fen
.read(Standard, EpdFen("1nbqk3/1p1prppp/p1P5/8/4K3/8/PPP1rPPP/RNBQ1BNR b HA - 0 4"))
.get
game.playable(true) must beFalse
game.playable(false) must beFalse

"be playable" in:
"when previous move is a double pawn push and the only checker is the pushed pawn" in:
val game = Fen
.read(Standard, EpdFen("r1bqkbnr/1p1p1ppp/p7/2pP4/3KP3/8/PPP3PP/RNBQ1BNR w HAkq c6 0 4"))
.get
game.playable(true) must beTrue
game.playable(false) must beTrue

"when two checkers are not on the same rank, file or diagonal" in:
val game = Fen
.read(Standard, EpdFen("rnbqk2r/1p1p1ppp/p1P5/3np1b1/4P3/4K3/PPP2PPP/RNBQ1BNR b HAkq - 0 4"))
.get
game.playable(true) must beTrue
game.playable(false) must beTrue

"when previous move is a double pawn push and the only checker is a discovered rook check" in:
val game = Fen
.read(Standard, EpdFen("1kb2b1r/1r3K1p/p7/2pP4/4P3/8/PPP3PP/RNBQ1BNR w HAk c6 0 4"))
.get
game.playable(true) must beTrue
game.playable(false) must beTrue

"when previous move is a double pawn push and the only checker is a discovered bishop check" in:
val game = Fen
.read(Standard, EpdFen("1bb4r/kr5p/p7/2pP4/4PK2/8/PPP3PP/RNBQ1BNR w HAh c6 0 4"))
.get
game.playable(true) must beTrue
game.playable(false) must beTrue