import ctypes from test.base import ChessLibTestBase from test.base import BLACK from test.base import WHITE from test.base import sq from test.base import popcount class Board(ctypes.Structure): _fields_ = [ ("pieces", ctypes.c_uint64 * 12), ("occ", ctypes.c_uint64 * 3), ("king_square", ctypes.c_uint64 * 2), ("castling_rights", ctypes.c_uint8), ("ep_square", ctypes.c_int), ("side_to_move", ctypes.c_int), ("halfmove_clock", ctypes.c_int), ("fullmove_number", ctypes.c_int), ] class FenTests(ChessLibTestBase): @classmethod def setUpClass(cls): super().setUpClass() cls.lib.load_fen.argtypes = [ctypes.POINTER(Board), ctypes.c_char_p] cls.lib.load_fen.restype = ctypes.c_int def rank_mask(self, r): return sum(1 << (r*8 + f) for f in range(8)) def test_startpos_fields_and_occupancies(self): fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" b = Board() self.lib.load_fen(ctypes.byref(b), fen.encode("ascii")) self.assertEqual(b.side_to_move, WHITE) self.assertEqual(b.castling_rights, 0b1111) # KQkq self.assertEqual(b.ep_square, -1) self.assertEqual(b.halfmove_clock, 0) self.assertEqual(b.fullmove_number, 1) white_expected = self.rank_mask(0) | self.rank_mask(1) # ranks 1 & 2 black_expected = self.rank_mask(6) | self.rank_mask(7) # ranks 7 & 8 self.assertEqual(int(b.occ[WHITE]), white_expected) self.assertEqual(int(b.occ[BLACK]), black_expected) self.assertEqual(int(b.occ[2]), white_expected | black_expected) self.assertEqual(popcount(int(b.occ[WHITE])), 16) self.assertEqual(popcount(int(b.occ[BLACK])), 16) def test_castling_flags_parsing(self): fen = "r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 5 42" b = Board() self.lib.load_fen(ctypes.byref(b), fen.encode("ascii")) self.assertEqual(b.side_to_move, BLACK) self.assertEqual(b.castling_rights, 0b1111) self.assertEqual(b.halfmove_clock, 5) self.assertEqual(b.fullmove_number, 42) def test_en_passant_targets(self): # EP at e6, black to move fen1 = "8/8/8/3pP3/8/8/8/8 b KQkq e6 0 1" b1 = Board() self.lib.load_fen(ctypes.byref(b1), fen1.encode("ascii")) self.assertEqual(b1.side_to_move, BLACK) self.assertEqual(b1.ep_square, sq("e6")) # EP at d3, white to move fen2 = "8/8/8/8/3Pp3/8/8/8 w KQkq d3 12 7" b2 = Board() self.lib.load_fen(ctypes.byref(b2), fen2.encode("ascii")) self.assertEqual(b2.side_to_move, WHITE) self.assertEqual(b2.ep_square, sq("d3")) def test_malformed_piece_field(self): # rank sums wrong (9 on a rank), bad char, missing separators bad = [ "9/8/8/8/8/8/8/8 w - - 0 1", "8/8/8/8/8/8/8/7x w - - 0 1", "8/8/8/8/8/8/8/8w - - 0 1", ] for fen in bad: b = Board() r = self.lib.load_fen(ctypes.byref(b), fen.encode("ascii")) self.assertEqual(r, -1) def test_malformed_side_castling_ep(self): bad = [ "8/8/8/8/8/8/8/8 x - - 0 1", # bad side "8/8/8/8/8/8/8/8 w KX - 0 1", # bad castling char "8/8/8/8/8/8/8/8 w - j9 0 1", # bad EP square "8/8/8/8/8/8/8/8 w - e4 0 1", # EP not rank 3/6 (your code allows any 1..8; consider tightening) ] for fen in bad: b = Board() r = self.lib.load_fen(ctypes.byref(b), fen.encode("ascii")) self.assertEqual(r, -1)