from test.base import ChessLibTestBase from binding.python_c_ffi import Board from binding.python_c_ffi import sq from binding.python_c_ffi import BLACK from binding.python_c_ffi import WHITE class TestAttackers(ChessLibTestBase): def test_square_attacked(self): cases = [ # Pawns: bp d5 attacks c4/e4; not d4 ("8/8/8/3p4/8/8/8/8 w - - 0 1", "c4", BLACK, True, "bp d5 -> c4"), ("8/8/8/3p4/8/8/8/8 w - - 0 1", "e4", BLACK, True, "bp d5 -> e4"), ("8/8/8/3p4/8/8/8/8 w - - 0 1", "d4", BLACK, False, "bp d5 not d4"), # Knights ("8/8/8/3n4/8/8/8/8 w - - 0 1", "f4", BLACK, True, "bn d5 -> f4"), ("8/8/8/3n4/8/8/8/8 w - - 0 1", "e7", BLACK, True, "bn d5 -> e7"), ("8/8/8/3n4/8/8/8/8 w - - 0 1", "e5", BLACK, False, "bn d5 not e5"), # Bishops (wB d4) ("8/8/8/8/3B4/8/8/8 w - - 0 1", "c5", WHITE, True, "wB d4 -> c5"), ("8/8/8/8/3B4/8/8/8 w - - 0 1", "e5", WHITE, True, "wB d4 -> e5"), # Rooks (wR e4) ("8/8/8/8/4R3/8/8/8 w - - 0 1", "e8", WHITE, True, "wR e4 -> e8 clear"), ("4k3/4p3/8/8/4R3/8/8/8 w - - 0 1", "e8", WHITE, False, "wR e4 blocked by pe7"), ("4k3/4p3/8/8/4R3/8/8/8 w - - 0 1", "e7", WHITE, True, "wR e4 blocked can attack pawn on e7"), ("8/8/8/8/4R3/8/8/8 w - - 0 1", "a4", WHITE, True, "wR e4 -> a4"), # Queens (wQ d4) ("8/8/8/8/3Q4/8/8/8 w - - 0 1", "h8", WHITE, True, "wQ d4 -> h8"), ("8/8/8/8/3Q4/8/8/8 w - - 0 1", "d8", WHITE, True, "wQ d4 -> d8"), # Kings (bk e4) ("8/8/8/8/4k3/8/8/4K3 w - - 0 1", "e3", BLACK, True, "bk e4 -> e3"), ("8/8/8/8/4k3/8/8/4K3 w - - 0 1", "g4", BLACK, False, "bk e4 not g4"), # Edge knights (bn a1) ("8/8/8/8/8/8/8/n7 w - - 0 1", "b3", BLACK, True, "bn a1 -> b3"), ("8/8/8/8/8/8/8/n7 w - - 0 1", "c2", BLACK, True, "bn a1 -> c2"), ("8/8/8/8/8/8/8/n7 w - - 0 1", "a2", BLACK, False, "bn a1 not a2"), ] for fen, sq_str, by, expected, msg in cases: with self.subTest(msg=msg, fen=fen, sq=sq_str, by=by): b = Board() self.chess_ffi.load_fen(b, fen) got = self.chess_ffi.square_attacked(b, sq(sq_str), by) self.assertEqual(expected, got, msg) def test_in_check(self): cases = [ # Unblocked slider (Qe2 checks e8) ("4k3/8/8/8/8/8/4Q3/4K3 b - - 0 1", BLACK, True, "Qe2 -> e8"), # Blocked by pe7 ("4k3/4p3/8/8/8/8/4Q3/4K3 b - - 0 1", BLACK, False, "Qe2 blocked by pe7"), # Knight check (Nc7 checks e8) ("4k3/2N5/8/8/8/8/8/4K3 b - - 0 1", BLACK, True, "Nc7 -> e8"), # Pawn check: bp e5 checks f4 (WK on f4) ("4k3/8/8/4p3/5K2/8/8/8 b - - 0 1", WHITE, True, "bp e5 -> f4"), # Not checking WK e1 ("4k3/8/8/4p3/8/8/8/4K3 b - - 0 1", WHITE, False, "bp e5 not e1"), # King adjacency (illegal but detected) # Ensure that this case does not make it through a legality filter. ("8/8/8/8/8/8/4k3/4K3 w - - 0 1", WHITE, True, "BK e2 checks WK e1"), # Double check (Qe2 + Nc7 vs BK e8) ("4k3/2N5/8/8/8/8/4Q3/4K3 b - - 0 1", BLACK, True, "double check still true"), ] for fen, side, expected, msg in cases: with self.subTest(msg=msg, fen=fen, side=side): b = Board() self.chess_ffi.load_fen(b, fen) actual = self.chess_ffi.in_check(b, side) self.assertEqual(expected, actual, msg) def test_attackers_to_popcount(self): cases = [ # Pawns (original correction): only c5 & e5 attack d4 ("8/8/8/2p1p3/3B4/2p1p3/8/8 w - - 0 1", "d4", BLACK, 2, "black pawns c5,e5 attack d4; c3,e3 do not"), # 0 attackers on empty board ("8/8/8/8/8/8/8/8 w - - 0 1", "d4", WHITE, 0, "no pieces -> no attackers"), # Knights: b5 and f5 both hit d4 ("8/8/8/1n3n2/8/8/8/8 w - - 0 1", "d4", BLACK, 2, "two black knights b5,f5 attack d4"), # White pawns from below (c3,e3) attack d4 ("8/8/8/8/8/2P1P3/8/8 w - - 0 1", "d4", WHITE, 2, "white pawns c3,e3 attack d4"), # Rooks on the file: d8 and d1 both see d4 ("3R4/8/8/8/8/8/8/3R4 w - - 0 1", "d4", WHITE, 2, "two white rooks d8,d1 attack d4"), # Bishops on diagonals: b6 and f2 see d4 ("8/8/1B6/8/8/8/5B2/8 w - - 0 1", "d4", WHITE, 2, "two white bishops b6,f2 attack d4"), # Mixed sliders: Ba7 and Rd1 both hit d4 ("8/B7/8/8/8/8/8/3R4 w - - 0 1", "d4", WHITE, 2, "white bishop a7 and rook d1 attack d4"), # Rook blocked by own pawn -> no attack up the file ("8/8/8/8/8/8/3P4/3R4 w - - 0 1", "d4", WHITE, 0, "rook d1 blocked by own pawn d2"), # Two queens (different lines) both attack d4 ("3Q4/8/8/8/7Q/8/8/8 w - - 0 1", "d4", WHITE, 2, "queens d8 (file) and h4 (rank) attack d4"), # Double-check-style: Qe2 and Nc7 attack e8 ("4k3/2N5/8/8/8/8/4Q3/4K3 w - - 0 1", "e8", WHITE, 2, "white queen e2 and knight c7 attack e8"), # Blocked slider by enemy pawn: rook e4 cannot reach e8 ("4k3/4p3/8/8/4R3/8/8/4K3 w - - 0 1", "e8", WHITE, 0, "white rook e4 blocked by black pawn e7"), # Three independent attackers to e4: Nf6, Be3 (from b1 path), Re1 ("8/8/5N2/8/8/8/8/1B2R3 w - - 0 1", "e4", WHITE, 3, "knight f6, bishop b1 path, rook e1 all attack e4"), # King adjacency (illegal in play but functionally one attacker) # We need to ensure that the legal move filter catches this case. ("8/8/8/8/8/8/4k3/4K3 w - - 0 1", "e1", BLACK, 1, "black king e2 attacks e1"), ] for fen, sq_str, by, expected_cnt, msg in cases: with self.subTest(msg=msg, fen=fen, sq=sq_str, by=by): b = Board() self.chess_ffi.load_fen(b, fen) mask = self.chess_ffi.attackers_to(b, sq(sq_str), by) self.assertEqual(expected_cnt, int(mask).bit_count())