Skip to content

Commit

Permalink
Add impossible check validation for Horde & Crazyhouse (#463)
Browse files Browse the repository at this point in the history
* Add impossible check validation for all variants.

* refactor impossible check validation for variants to delegate to variant implementation instead of standard.

* Fix build error with racing kings.

* Fix format

* remove implementation of hasValidCheckers from Variant parent class

* Fix formatting

* Fix Variant test and test cases.

* Fix Variant test formatting

* remove hasValidChecker method from Variant.

* Fix atomic chess.

* Fix atomic chess.

* Fix formatting.

* Simplify changes to Standard.hasValidCheckers

* Simplify changes to Atomic.valid

* revert changes to king of the hill.

* Remove impossible check validation from atomic chess.

* Remove impossible check validation from atomic chess.
  • Loading branch information
Oziomajnr authored Aug 13, 2023
1 parent 68a6dfd commit 9891ea2
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 75 deletions.
2 changes: 1 addition & 1 deletion src/main/scala/variant/Chess960.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ case object Chess960
def validMoves(situation: Situation): List[Move] =
Standard.validMoves(situation)

override def valid(situation: Situation, strict: Boolean) = Standard.valid(situation, strict)
override def valid(situation: Situation, strict: Boolean): Boolean = Standard.valid(situation, strict)

def pieces = pieces(scala.util.Random.nextInt(960))

Expand Down
4 changes: 3 additions & 1 deletion src/main/scala/variant/Crazyhouse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ case object Crazyhouse

override def valid(situation: Situation, strict: Boolean) =
(Color.all forall validSide(situation.board, false)) &&
(!strict || (situation.board.board.byRole(Pawn).count <= 16 && situation.board.nbPieces <= 32))
(!strict || (situation.board.board
.byRole(Pawn)
.count <= 16 && situation.board.nbPieces <= 32 && Standard.hasValidCheckers(situation)))

private def canDropPawnOn(square: Square) = square.rank != Rank.First && square.rank != Rank.Eighth

Expand Down
5 changes: 4 additions & 1 deletion src/main/scala/variant/Horde.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ case object Horde
override def valid(situation: Situation, strict: Boolean) =
situation.board.kingOf(White).isEmpty && validSide(situation.board, strict)(
Black
) && !pawnsOnPromotionRank(situation.board, White)
) && !pawnsOnPromotionRank(situation.board, White) && (!strict || situation.color.white || Standard
.hasValidCheckers(
situation
))

/** The game has a special end condition when black manages to capture all of white's pawns */
override def specialEnd(situation: Situation) =
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/variant/KingOfTheHill.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ case object KingOfTheHill
def validMoves(situation: Situation): List[Move] =
Standard.validMoves(situation)

override def valid(situation: Situation, strict: Boolean) = Standard.valid(situation, strict)
override def valid(situation: Situation, strict: Boolean): Boolean = Standard.valid(situation, strict)

// E4, D4, E5, D5
private val center = 0x1818000000L
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/variant/Standard.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ case object Standard
override def valid(situation: Situation, strict: Boolean): Boolean =
super.valid(situation, strict) && (!strict || hasValidCheckers(situation))

private def hasValidCheckers(situation: Situation): Boolean =
def hasValidCheckers(situation: Situation): Boolean =
situation.checkers.isEmpty || {
isValidChecksForMultipleCheckers(situation, situation.checkers) &&
isValidCheckersForEnPassant(situation, situation.checkers)
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/variant/ThreeCheck.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ case object ThreeCheck
def validMoves(situation: Situation): List[Move] =
Standard.validMoves(situation)

override def valid(situation: Situation, strict: Boolean) = Standard.valid(situation, strict)
override def valid(situation: Situation, strict: Boolean): Boolean = Standard.valid(situation, strict)

override def finalizeBoard(board: Board, uci: format.Uci, capture: Option[Piece]): Board =
board updateHistory:
Expand Down
247 changes: 178 additions & 69 deletions src/test/scala/VariantTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,74 +12,74 @@ class VariantTest extends ChessTest:

"variants" should:
"validate situation correctly" in:
Fragment.foreach(List(Standard, Chess960, ThreeCheck, KingOfTheHill)) { variant =>

"two-step pawn advance with no check should be valid" in:
val position = EpdFen("2r3k1/p2Q1pp1/1p5p/3p4/P7/KP6/2r5/8 b - - 1 36")
val game = fenToGame(position, variant).flatMap(_.playMoves(A7 -> A5)).toOption.get
game.situation.playable(true) must beTrue

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

game1.variant.valid(game1, true) must beFalse
game2.variant.valid(game2, false) must beTrue
game1.variant.valid(game1, true) must beFalse
game2.variant.valid(game2, false) must beTrue

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

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

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

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

"when two checkers are not on the same rank, file or diagonal" in:
val game = Fen
.read(variant, EpdFen("rnbqk2r/1p1p1ppp/p1P5/3np1b1/4P3/4K3/PPP2PPP/RNBQ1BNR b HAkq - 0 4"))
.get
game.variant.valid(game, true) must beTrue
game.variant.valid(game, 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(variant, EpdFen("1kb2b1r/1r3K1p/p7/2pP4/4P3/8/PPP3PP/RNBQ1BNR w HAk c6 0 4"))
.get
game.variant.valid(game, true) must beTrue
game.variant.valid(game, 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(variant, EpdFen("1bb4r/kr5p/p7/2pP4/4PK2/8/PPP3PP/RNBQ1BNR w HAh c6 0 4"))
.get
game.variant.valid(game, true) must beTrue
game.variant.valid(game, false) must beTrue
Fragment.foreach(List(Standard, Chess960, ThreeCheck, KingOfTheHill, Crazyhouse)) { variant =>
s"for variant $variant" in:
"two-step pawn advance with no check should be valid" in:
val position = EpdFen("2r3k1/p2Q1pp1/1p5p/3p4/P7/KP6/2r5/8 b - - 1 36")
val game = fenToGame(position, variant).flatMap(_.playMoves(A7 -> A5)).toOption.get
game.situation.playable(true) must beTrue

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

game1.variant.valid(game1, true) must beFalse
game1.variant.valid(game1, false) must beTrue
game2.variant.valid(game2, true) must beFalse
game2.variant.valid(game2, false) must beTrue

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

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

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

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

"when two checkers are not on the same rank, file or diagonal" in:
val game = Fen
.read(variant, EpdFen("rnbqk2r/1p1p1ppp/p1P5/3np1b1/4P3/4K3/PPP2PPP/RNBQ1BNR w HAkq - 0 4"))
.get
game.variant.valid(game, true) must beTrue
game.variant.valid(game, 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(variant, EpdFen("1kb2b1r/1r3K1p/p7/2pP4/4P3/8/PPP3PP/RNBQ1BNR w HAk c6 0 4"))
.get
game.variant.valid(game, true) must beTrue
game.variant.valid(game, 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(variant, EpdFen("1bb4r/kr5p/p7/2pP4/4PK2/8/PPP3PP/RNBQ1BNR w HAh c6 0 4"))
.get
game.variant.valid(game, true) must beTrue
game.variant.valid(game, false) must beTrue
}

"standard" should:
Expand Down Expand Up @@ -347,6 +347,28 @@ K r
"initialize the board without castling rights" in:
Board.init(RacingKings).history.castles.isEmpty must beTrue

"validate situation correctly" in:
Fragment.foreach(
List(
"1bb4r/kr5p/p7/2pP4/4PK2/8/PPP3PP/RNBQ1BNR w HAh c6 0 4",
"1kb2b1r/1r3K1p/p7/2pP4/4P3/8/PPP3PP/RNBQ1BNR w HAk c6 0 4",
"rnbqk2r/1p1p1ppp/p1P5/3np1b1/4P3/4K3/PPP2PPP/RNBQ1BNR w HAkq - 0 4",
"r1bqkbnr/1p1p1ppp/p7/2pP4/3KP3/8/PPP3PP/RNBQ1BNR w HAkq c6 0 4",
"1nbqk3/1p1prppp/p1P5/8/4K3/8/PPP1rPPP/RNBQ1BNR w HA - 0 4",
"2b4r/kr5p/p7/2pP2b1/4PK2/8/PPP3PP/RNBQ1BNR w HAh c6 0 4",
"1k6/5K1r/p7/2pP4/4P3/8/PPP3PP/RNBQ1BNR w HA c6 0 4",
"r1bqkbnr/1p1p1ppp/p7/2pP4/4P3/8/PPP2pPP/RNBQKBNR w KQkq c6 0 4",
"r1bqkbnr/1p1p1ppp/p7/2pPp3/4P3/5n2/PPP2PPP/RNBQKBNR w KQkq c6 0 4"
)
) { fen =>
s"for fen $fen" in:
val game = Fen
.read(RacingKings, EpdFen(fen))
.get
game.variant.valid(game, true) must beFalse
game.variant.valid(game, false) must beTrue
}

"antichess" should:
"initialize the board without castling rights" in:
Board.init(Antichess).history.castles.isEmpty must beTrue
Expand All @@ -359,11 +381,32 @@ K r
game.situation.board.materialImbalance must_== -20
}

"validate situation correctly" in:
Fragment.foreach(
List(
"1bb4r/kr5p/p7/2pP4/4PK2/8/PPP3PP/RNBQ1BNR w HAh c6 0 4",
"1kb2b1r/1r3K1p/p7/2pP4/4P3/8/PPP3PP/RNBQ1BNR w HAk c6 0 4",
"rnbqk2r/1p1p1ppp/p1P5/3np1b1/4P3/4K3/PPP2PPP/RNBQ1BNR w HAkq - 0 4",
"r1bqkbnr/1p1p1ppp/p7/2pP4/3KP3/8/PPP3PP/RNBQ1BNR w HAkq c6 0 4",
"1nbqk3/1p1prppp/p1P5/8/4K3/8/PPP1rPPP/RNBQ1BNR w HA - 0 4",
"2b4r/kr5p/p7/2pP2b1/4PK2/8/PPP3PP/RNBQ1BNR w HAh c6 0 4",
"1k6/5K1r/p7/2pP4/4P3/8/PPP3PP/RNBQ1BNR w HA c6 0 4",
"r1bqkbnr/1p1p1ppp/p7/2pP4/4P3/8/PPP2pPP/RNBQKBNR w KQkq c6 0 4",
"r1bqkbnr/1p1p1ppp/p7/2pPp3/4P3/5n2/PPP2PPP/RNBQKBNR w KQkq c6 0 4"
)
) { fen =>
s"for fen $fen" in:
val game = Fen
.read(Antichess, EpdFen(fen))
.get
game.variant.valid(game, true) must beTrue
game.variant.valid(game, false) must beTrue
}
"horde" should:
"initialize the board with black castling rights" in:
Board.init(Horde).history.castles must_== Castles("kq")

"racing kind" should:
"racing king" should:
"validate situation correctly" in:
"with any check at all for white" in:
val position = EpdFen("8/8/8/k5R1/8/8/1rbnNB1K/qrbnNBRQ b - - 0 1")
Expand All @@ -376,3 +419,69 @@ K r
val game = fenToGame(position, RacingKings).toOption.get
game.situation.playable(true) must beFalse
game.situation.playable(false) must beTrue

"horde" should:
"validate situation correctly" in:
"two-step pawn advance with no check should be valid" in:
val position = EpdFen("2r3k1/p2P1pp1/1p5p/3p4/P7/PP6/2P3P1/8 b - - 1 36")
val game = fenToGame(position, Horde).flatMap(_.playMoves(A7 -> A5)).toOption.get
game.situation.playable(true) must beTrue

"when previous move is a double pawn push and checker is not the pushed pawn or a sliding piece" in:
val game = Fen
.read(Horde, EpdFen("1r6/6q1/8/3k4/2pPP3/8/PPP2PPP/PPPPPPPP b - d3 0 1"))
.get
game.variant.valid(game, false) must beTrue
game.variant.valid(game, true) 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(
Horde,
EpdFen("5r2/8/4k2R/8/3pP3/8/PPPP1PPP/2PPPPP1 b - e3 0 1")
)
.get
game.variant.valid(game, true) must beFalse
game.variant.valid(game, false) must beTrue

"when previous move is a double pawn push and the only checker is a bishop but not discovered check" in:
val game = Fen
.read(Horde, EpdFen("5r2/8/4k3/8/3pP1B1/8/PPPP1PPP/2PPPPP1 b - e3 0 1"))
.get
game.variant.valid(game, true) must beFalse
game.variant.valid(game, false) must beTrue

"when multiple checkers are aligned with the king" in:
val game = Fen
.read(Horde, EpdFen("1q6/8/R2k1R2/8/8/8/8/8 b - - 0 1"))
.get
game.variant.valid(game, true) must beFalse
game.variant.valid(game, false) must beTrue

"when previous move is a double pawn push and the only checker is the pushed pawn" in:
val game = Fen
.read(Horde, EpdFen("1r6/6q1/8/4k3/2pP4/2P5/PP3PPP/PPPPPPPP b - d3 0 3"))
.get
game.variant.valid(game, true) must beTrue
game.variant.valid(game, false) must beTrue

"when two checkers are not on the same rank, file or diagonal" in:
val game = Fen
.read(Horde, EpdFen("7r/3k4/8/1B2N2q/1B6/8/PPPPPPPP/PPPPPPPP w - - 0 1"))
.get
game.variant.valid(game, true) must beTrue
game.variant.valid(game, 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(Horde, EpdFen("8/8/8/8/3Pp3/8/k5R1/8 b - d3 0 2"))
.get
game.variant.valid(game, true) must beTrue
game.variant.valid(game, 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(Horde, EpdFen("8/8/8/8/1k1Pp3/8/8/4B3 b - d3 0 2"))
.get
game.variant.valid(game, true) must beTrue
game.variant.valid(game, false) must beTrue

0 comments on commit 9891ea2

Please sign in to comment.