Skip to content

Commit a875173

Browse files
committed
test: add comprehensive check detection and castling tests
1 parent f09c27f commit a875173

File tree

2 files changed

+201
-0
lines changed

2 files changed

+201
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package dev.tabansi.chess
2+
3+
import dev.tabansi.chess.Board.EMPTY
4+
import dev.tabansi.chess.Board.KING
5+
import dev.tabansi.chess.Board.PLAYER_1
6+
import dev.tabansi.chess.Board.PLAYER_2
7+
import dev.tabansi.chess.Board.ROOK
8+
import dev.tabansi.chess.Board.board
9+
import dev.tabansi.chess.pieces.King
10+
import kotlin.test.Test
11+
import kotlin.test.assertFalse
12+
import kotlin.test.assertTrue
13+
14+
class CastlingTest {
15+
16+
private fun clearBoard() {
17+
for (i in 0 until 8) for (j in 0 until 8) board[i][j] = EMPTY
18+
}
19+
20+
@Test
21+
fun white_kingside_castle_allowed_when_path_clear_and_safe() {
22+
clearBoard()
23+
val p1 = Player(PLAYER_1)
24+
// Place only king and rook in starting squares
25+
board[7][4] = KING + PLAYER_1
26+
board[7][7] = ROOK + PLAYER_1
27+
val moves = p1.king.getMoves()
28+
assertTrue(moves.contains(BoardSpace(7, 6)), "White should be able to castle king side when path is clear and safe")
29+
}
30+
31+
@Test
32+
fun white_kingside_castle_disallowed_if_pass_through_square_attacked() {
33+
clearBoard()
34+
val p1 = Player(PLAYER_1)
35+
// King & rook on starting squares
36+
board[7][4] = KING + PLAYER_1
37+
board[7][7] = ROOK + PLAYER_1
38+
// Attack pass-through square (7,5) with an enemy rook on same file
39+
// Place enemy rook at (3,5) so it attacks (7,5) but not (7,4)
40+
board[3][5] = ROOK + PLAYER_2
41+
val moves = p1.king.getMoves()
42+
assertFalse(moves.contains(BoardSpace(7, 6)), "White castling should be disallowed if f1 (7,5) is attacked")
43+
}
44+
45+
@Test
46+
fun white_kingside_castle_disallowed_if_final_square_attacked() {
47+
clearBoard()
48+
val p1 = Player(PLAYER_1)
49+
// King & rook on starting squares
50+
board[7][4] = KING + PLAYER_1
51+
board[7][7] = ROOK + PLAYER_1
52+
// Attack final square (7,6) with a bishop-like diagonal from (5,4)
53+
// Using a rook along diagonal is not possible; we simulate with an enemy queen that moves diagonally.
54+
board[5][4] = "QN" + PLAYER_2
55+
val moves = p1.king.getMoves()
56+
assertFalse(moves.contains(BoardSpace(7, 6)), "White castling should be disallowed if g1 (7,6) is attacked")
57+
}
58+
59+
@Test
60+
fun black_queenside_castle_allowed_when_path_clear_and_safe() {
61+
clearBoard()
62+
val p2 = Player(PLAYER_2)
63+
// Place only king and rook in starting squares for black
64+
board[0][4] = KING + PLAYER_2
65+
board[0][0] = ROOK + PLAYER_2
66+
val moves = p2.king.getMoves()
67+
assertTrue(moves.contains(BoardSpace(0, 2)), "Black should be able to castle queen side when path is clear and safe")
68+
}
69+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package dev.tabansi.chess
2+
3+
import dev.tabansi.chess.Board.BISHOP
4+
import dev.tabansi.chess.Board.EMPTY
5+
import dev.tabansi.chess.Board.KING
6+
import dev.tabansi.chess.Board.KNIGHT
7+
import dev.tabansi.chess.Board.PAWN
8+
import dev.tabansi.chess.Board.PLAYER_1
9+
import dev.tabansi.chess.Board.PLAYER_2
10+
import dev.tabansi.chess.Board.QUEEN
11+
import dev.tabansi.chess.Board.ROOK
12+
import dev.tabansi.chess.Board.board
13+
import dev.tabansi.chess.pieces.King
14+
import dev.tabansi.chess.pieces.Piece
15+
import kotlin.test.*
16+
17+
class CheckDetectionTest {
18+
19+
private fun clearBoard() {
20+
for (i in 0 until 8) for (j in 0 until 8) board[i][j] = EMPTY
21+
}
22+
23+
private fun place(x: Int, y: Int, piece: String) {
24+
board[x][y] = piece
25+
}
26+
27+
@Test
28+
fun rook_vertical_check_true_and_blocked_false() {
29+
clearBoard()
30+
val p1 = Player(PLAYER_1)
31+
val k = King(BoardSpace(4, 4), p1)
32+
place(4, 4, KING + PLAYER_1)
33+
34+
// Enemy rook on same file with clear path -> check
35+
place(0, 4, ROOK + PLAYER_2)
36+
assertTrue(k.isInCheck(), "Rook on same file with no blockers should give check")
37+
38+
// Block the line -> no check
39+
place(2, 4, PAWN + PLAYER_1)
40+
assertFalse(k.isInCheck(), "Blocked rook line should not give check")
41+
}
42+
43+
@Test
44+
fun rook_horizontal_check_true_and_blocked_false() {
45+
clearBoard()
46+
val p2 = Player(PLAYER_2)
47+
val k = King(BoardSpace(3, 3), p2)
48+
place(3, 3, KING + PLAYER_2)
49+
50+
// Enemy rook on same rank with clear path -> check
51+
place(3, 7, ROOK + PLAYER_1)
52+
assertTrue(k.isInCheck(), "Rook on same rank with no blockers should give check")
53+
54+
// Block the line -> no check
55+
place(3, 5, PAWN + PLAYER_2)
56+
assertFalse(k.isInCheck(), "Blocked rook line should not give check")
57+
}
58+
59+
@Test
60+
fun bishop_diagonal_check_true_and_blocked_false() {
61+
clearBoard()
62+
val p1 = Player(PLAYER_1)
63+
val k = King(BoardSpace(4, 4), p1)
64+
place(4, 4, KING + PLAYER_1)
65+
66+
// Enemy bishop on clear diagonal -> check
67+
place(1, 1, BISHOP + PLAYER_2)
68+
assertTrue(k.isInCheck(), "Bishop on clear diagonal should give check")
69+
70+
// Block the diagonal -> no check
71+
place(2, 2, PAWN + PLAYER_1)
72+
assertFalse(k.isInCheck(), "Blocked bishop diagonal should not give check")
73+
}
74+
75+
@Test
76+
fun queen_sliding_check_true() {
77+
clearBoard()
78+
val p2 = Player(PLAYER_2)
79+
val k = King(BoardSpace(6, 6), p2)
80+
place(6, 6, KING + PLAYER_2)
81+
// Enemy queen on diagonal -> check
82+
place(4, 4, QUEEN + PLAYER_1)
83+
assertTrue(k.isInCheck(), "Queen on diagonal should give check")
84+
}
85+
86+
@Test
87+
fun knight_check_true_and_false() {
88+
clearBoard()
89+
val p1 = Player(PLAYER_1)
90+
val k = King(BoardSpace(4, 4), p1)
91+
place(4, 4, KING + PLAYER_1)
92+
93+
// Knight attacks from (5,6)
94+
place(5, 6, KNIGHT + PLAYER_2)
95+
assertTrue(k.isInCheck(), "Knight in L position should give check")
96+
97+
// Move knight to a non-attacking square
98+
place(5, 6, EMPTY)
99+
place(5, 5, KNIGHT + PLAYER_2)
100+
assertFalse(k.isInCheck(), "Knight not in L position should not give check")
101+
}
102+
103+
@Test
104+
fun pawn_threats_for_both_colors() {
105+
// P1 king attacked by P2 pawn from one row above (x-1, y±1)
106+
clearBoard()
107+
val p1 = Player(PLAYER_1)
108+
val k1 = King(BoardSpace(4, 4), p1)
109+
place(4, 4, KING + PLAYER_1)
110+
place(3, 3, PAWN + PLAYER_2) // attacks (4,4)
111+
assertTrue(k1.isInCheck(), "P2 pawn should threaten P1 king from (x-1, y-1)")
112+
113+
// P2 king attacked by P1 pawn from one row below (x+1, y±1)
114+
clearBoard()
115+
val p2 = Player(PLAYER_2)
116+
val k2 = King(BoardSpace(3, 3), p2)
117+
place(3, 3, KING + PLAYER_2)
118+
place(4, 4, PAWN + PLAYER_1) // attacks (3,3)
119+
assertTrue(k2.isInCheck(), "P1 pawn should threaten P2 king from (x+1, y+1)")
120+
}
121+
122+
@Test
123+
fun adjacent_king_threat_detected() {
124+
clearBoard()
125+
val p1 = Player(PLAYER_1)
126+
val k = King(BoardSpace(4, 4), p1)
127+
place(4, 4, KING + PLAYER_1)
128+
// Enemy king adjacent
129+
place(5, 5, KING + PLAYER_2)
130+
assertTrue(k.isInCheck(), "Adjacent enemy king should count as a check")
131+
}
132+
}

0 commit comments

Comments
 (0)