|
4 | 4 |
|
5 | 5 | import asyncio |
6 | 6 | import copy |
| 7 | +import csv |
7 | 8 | import logging |
8 | 9 | import os |
9 | 10 | import os.path |
|
23 | 24 | from chess import Board |
24 | 25 | from chess.binary_fen import BinaryFen, ChessHeader, VariantHeader |
25 | 26 |
|
26 | | -KOTH = chess.variant.KingOfTheHillBoard |
27 | | -THREE_CHECKS = chess.variant.ThreeCheckBoard |
28 | | -ANTI = chess.variant.AntichessBoard |
29 | | -ATOMIC = chess.variant.AtomicBoard |
30 | | -HORDE = chess.variant.HordeBoard |
31 | | -RK = chess.variant.RacingKingsBoard |
32 | | -ZH = chess.variant.CrazyhouseBoard |
33 | 27 |
|
34 | 28 | class BinaryFenTestCase(unittest.TestCase): |
35 | | - |
36 | 29 | def test_nibble_roundtrip(self): |
37 | 30 | for lo in range(16): |
38 | 31 | for hi in range(16): |
@@ -170,78 +163,66 @@ def test_to_canonical_2(self): |
170 | 163 | canon_case = case.to_canonical() |
171 | 164 | self.assertEqual(canon, canon_case) |
172 | 165 |
|
173 | | - def test_binary_fen_roundtrip(self): |
174 | | - cases = [ |
175 | | - Board(fen="8/8/8/8/8/8/8/8 w - - 0 1"), |
176 | | - Board(fen="8/8/8/8/8/8/8/8 b - - 0 1"), |
177 | | - Board(fen="8/8/8/8/8/8/8/7k b - - 0 1"), |
178 | | - Board(fen="8/8/8/8/8/8/8/8 w - - 0 2"), |
179 | | - Board(fen="8/8/8/8/8/8/8/8 b - - 0 2"), |
180 | | - Board(fen="8/8/8/8/8/8/8/8 b - - 100 432"), |
181 | | - Board(fen="rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"), |
182 | | - Board(fen="4nrk1/1pp3pp/p4p2/4P3/2BB1n2/8/PP3P1P/2K3R1 b - - 1 25"), |
183 | | - Board(fen="4nrk1/1pp3pp/p4p2/4P3/2BB1n2/8/PP3P1P/2K3R1 b - - 1 25"), |
184 | | - Board(fen="5k2/6p1/8/1Pp5/6P1/8/8/3K4 w - c6 0 1"), |
185 | | - Board(fen="4k3/8/8/8/3pP3/8/6N1/7K b - e3 0 1"), |
186 | | - Board(fen="r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1"), |
187 | | - Board(fen="r1k1r2q/p1ppp1pp/8/8/8/8/P1PPP1PP/R1K1R2Q w KQkq - 0 1",chess960=True), |
188 | | - Board(fen="r1k2r1q/p1ppp1pp/8/8/8/8/P1PPP1PP/R1K2R1Q w KQkq - 0 1", chess960=True), |
189 | | - Board(fen="8/8/8/4B2b/6nN/8/5P2/2R1K2k w Q - 1 1", chess960=True), |
190 | | - Board(fen="2r5/8/8/8/8/8/6PP/k2KR3 w K - 0 2", chess960=True), |
191 | | - Board(fen="4r3/3k4/8/8/8/8/6PP/qR1K1R2 w KQ - 2 1", chess960=True), |
192 | | - Board(fen="4rrk1/pbbp2p1/1ppnp3/3n1pqp/3N1PQP/1PPNP3/PBBP2P1/4RRK1 w Ff - 0 3", chess960=True), |
193 | | - Board(fen="8/8/8/1k6/3Pp3/8/8/4KQ2 b - d3 3 1"), |
194 | | - Board(fen="r2r3k/p7/3p4/8/8/P6P/8/R3K2R b KQq - 0 4",chess960=True), |
195 | | - Board(fen="rn2k1r1/ppp1pp1p/3p2p1/5bn1/P7/2N2B2/1PPPPP2/2BNK1RR w Gkq - 4 11", chess960=True), |
196 | | - Board(fen="8/8/8/8/8/8/2Rk4/1K6 w - - 0 1"), |
197 | | - |
198 | | - HORDE(fen="rn1qkb1r/3bn1p1/2p3P1/pPP2P2/P1PPP1P1/P1PP1PPP/PPPPPPPP/PPPPPPPP w kq a6 0 12"), |
199 | | - |
200 | | - ANTI("8/2p1p2p/2Q1N2B/8/p7/N7/PPP1P1PP/R4B1R b - - 0 13"), |
201 | | - ANTI("8/p6p/4p3/1P4P1/Pp4p1/3P4/7P/8 b - a3 0 1"), |
202 | | - ANTI("8/p6p/4p3/1P4P1/1p4pP/3P4/P7/8 b - h3 0 1"), |
203 | | - ANTI("8/7p/4p3/pP4P1/1p1P2p1/8/P6P/8 w - a6 0 2"), |
204 | | - ANTI("8/p7/4p3/1P4Pp/1p1P2p1/8/P6P/8 w - h6 0 2"), |
205 | | - |
206 | | - ATOMIC(fen="rnbq3r/ppp1p1pp/5p2/3p4/8/8/PPPPPPPP/RNBQKB1R b KQ - 0 4"), |
207 | | - ATOMIC(fen="8/6pp/2p2p1n/3p4/4P3/B6P/3P1PP1/1r2K2R b K - 0 17"), |
208 | | - |
209 | | - RK(fen="8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1"), |
210 | | - RK(fen="8/8/8/8/8/6K1/krbnNBR1/qrbnNBRQ b - - 1 1"), |
211 | | - |
212 | | - KOTH(fen="rnbq1bnr/ppp2ppp/3k4/4p2Q/3PK3/8/PPP2PPP/RNB2BNR b - - 0 7"), |
213 | | - |
214 | | - THREE_CHECKS(fen="1r3rk1/pbp1N1pp/3p1q2/1p2bp2/7P/2PBB1P1/PP3Q1R/R5K1 b - - 3 21 +2+1"), |
215 | | - |
216 | | - ZH(fen="1r3Q1n/p1kp3p/1p2ppq1/2p2b2/8/3P2P1/PPP1PPBP/R4RK1/NRpnnbb w - - 2 28"), |
217 | | - ZH(fen="b2nkbnQ~/p1pppp1p/pP1q2p1/r7/8/R5PR/P1PP1P1P/1NBQ1BNK/R w - - 1 2"), |
218 | | - ZH(fen="8/8/8/8/8/8/8/8/ w - - 0 1"), |
219 | | - ZH(fen="r~n~b~q~kb~n~r~/pppppppp/8/8/8/8/PPPPPPPP/RN~BQ~KB~NR/ w KQkq - 0 1"), |
220 | | - ] |
221 | | - for case in cases: |
222 | | - case_fen = case.fen() |
223 | | - with self.subTest(fen=case_fen): |
224 | | - bin_fen = BinaryFen.parse_from_board(case) |
225 | | - bin_fen2 = BinaryFen.parse_from_bytes(bin_fen.to_bytes()) |
226 | | - self.assertEqual(bin_fen2, bin_fen2.to_canonical(), "from_bytes should produce canonical value") |
227 | | - self.assertEqual(bin_fen.to_canonical(), bin_fen2.to_canonical()) |
228 | | - decoded, _ = bin_fen2.to_board() |
229 | | - self.assertEqual(case, decoded) |
230 | | - |
231 | | - |
232 | | - # tests that failed the fuzzer at some point |
233 | | - def test_fuzzer_fail(self): |
| 166 | + _VARIANT_CLASSES = { |
| 167 | + "standard": lambda fen: chess.Board(fen=fen, chess960=False), |
| 168 | + "chess960": lambda fen: chess.Board(fen=fen, chess960=True), |
| 169 | + "koth": lambda fen: chess.variant.KingOfTheHillBoard(fen=fen), |
| 170 | + "three_check": lambda fen: chess.variant.ThreeCheckBoard(fen=fen), |
| 171 | + "antichess": lambda fen: chess.variant.AntichessBoard(fen=fen), |
| 172 | + "atomic": lambda fen: chess.variant.AtomicBoard(fen=fen), |
| 173 | + "horde": lambda fen: chess.variant.HordeBoard(fen=fen), |
| 174 | + "racing_kings": lambda fen: chess.variant.RacingKingsBoard(fen=fen), |
| 175 | + "crazyhouse": lambda fen: chess.variant.CrazyhouseBoard(fen=fen), |
| 176 | + } |
| 177 | + |
| 178 | + def _run_case(self, binary_fen_hex, canonical_hex, fen, variant_name): |
| 179 | + expected_board = self._VARIANT_CLASSES[variant_name](fen) |
| 180 | + |
| 181 | + decoded_board, std_mode = BinaryFen.decode(bytes.fromhex(binary_fen_hex)) |
| 182 | + |
| 183 | + encoded_hex = BinaryFen.encode(expected_board, std_mode=std_mode).hex() |
| 184 | + self.assertEqual( |
| 185 | + encoded_hex, canonical_hex, "encode(board) must equal canonical_binary_fen" |
| 186 | + ) |
| 187 | + |
| 188 | + self.assertEqual( |
| 189 | + decoded_board, |
| 190 | + expected_board, |
| 191 | + "decode(canonical_binary_fen) must equal board from FEN", |
| 192 | + ) |
| 193 | + |
| 194 | + parsed = BinaryFen.parse_from_bytes(bytes.fromhex(binary_fen_hex)) |
| 195 | + self.assertEqual( |
| 196 | + parsed.to_canonical().to_bytes().hex(), |
| 197 | + canonical_hex, |
| 198 | + "parse_from_bytes(binary_fen).to_canonical() must equal canonical_binary_fen", |
| 199 | + ) |
| 200 | + |
| 201 | + def test_data_driven(self): |
| 202 | + csv_path = os.path.join(os.path.dirname(__file__), "data/test_binary_fen_cases.csv") |
| 203 | + with open(csv_path, newline="") as f: |
| 204 | + reader = csv.DictReader(f) |
| 205 | + for row in reader: |
| 206 | + with self.subTest( |
| 207 | + binary_fen=row["binary_fen"], fen=row["fen"], variant=row["variant"] |
| 208 | + ): |
| 209 | + self._run_case( |
| 210 | + binary_fen_hex=row["binary_fen"], |
| 211 | + canonical_hex=row["canonical_binary_fen"], |
| 212 | + fen=row["fen"], |
| 213 | + variant_name=row["variant"] or "standard", |
| 214 | + ) |
234 | 215 | fuzz_fails = [ |
235 | | - "23d7", |
236 | | - "e17f11efd84522d34878ffffffa600000000ce1b23ffff000943", |
237 | | - "20f7076f1718f99824a5020724b3cfc1020146ae00004f85ae28aebc", |
238 | | - "edf9b3c5cb7fa5008000004081c83e4092a7e63dd95a", |
239 | | - "f7cef6e64ed47a4ede172a100000009b004c909b", |
240 | | - "bb7cb00cc3f31dc3f325b8", |
241 | | - "4584aced8100da50a20bd7251705a15b108000251705", |
242 | | - "77ff05111f77111f4214e803647fff6429f0a2f65933310185016400000045bf1e8be6b013ed02", |
243 | | - "55d648e9a20fd600400000e9a29c0010043b26fb41d50a50", |
244 | | - "d8805347e76003102228687fffff41b19e2bff00000100020220c6" |
| 216 | + "23d7", |
| 217 | + "e17f11efd84522d34878ffffffa600000000ce1b23ffff000943", |
| 218 | + "20f7076f1718f99824a5020724b3cfc1020146ae00004f85ae28aebc", |
| 219 | + "edf9b3c5cb7fa5008000004081c83e4092a7e63dd95a", |
| 220 | + "f7cef6e64ed47a4ede172a100000009b004c909b", |
| 221 | + "bb7cb00cc3f31dc3f325b8", |
| 222 | + "4584aced8100da50a20bd7251705a15b108000251705", |
| 223 | + "77ff05111f77111f4214e803647fff6429f0a2f65933310185016400000045bf1e8be6b013ed02", |
| 224 | + "55d648e9a20fd600400000e9a29c0010043b26fb41d50a50", |
| 225 | + "d8805347e76003102228687fffff41b19e2bff00000100020220c6", |
245 | 226 | ] |
246 | 227 | for fuzz_fail in fuzz_fails: |
247 | 228 | with self.subTest(fuzz_fail=fuzz_fail): |
|
0 commit comments