Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
544 changes: 544 additions & 0 deletions chess/binary_fen.py

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions data/test_binary_fen_cases.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
binary_fen,canonical_binary_fen,fen,variant
0000000000000000000003,0000000000000000000003,8/8/8/8/8/8/8/8 w - - 0 1,standard
0000000000000000000103,0000000000000000000103,8/8/8/8/8/8/8/8 b - - 0 1,standard
00000000000000800f000103,00000000000000800f000103,8/8/8/8/8/8/8/7k b - - 0 1,standard
0000000000000000000203,0000000000000000000203,8/8/8/8/8/8/8/8 w - - 0 2,standard
0000000000000000000303,0000000000000000000303,8/8/8/8/8/8/8/8 b - - 0 2,standard
000000000000000064df0603,000000000000000064df0603,8/8/8/8/8/8/8/8 b - - 100 432,standard
ffff00001000efff2d844ad200000000111111113e955fe3000103,ffff00001000efff2d844ad200000000111111113e955fe3000103,rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1,standard
70c621102c00a3446a00004403111111730f013103,70c621102c00a3446a00004403111111730f013103,4nrk1/1pp3pp/p4p2/4P3/2BB1n2/8/PP3P1P/2K3R1 b - - 1 25,standard
20400006400000080ac0b1000003,20400006400000080ac0b1000003,5k2/6p1/8/1Pp5/6P1/8/8/3K4 w - c6 0 1,standard
10000000180040802ac10f000103,10000000180040802ac10f000103,4k3/8/8/8/3pP3/8/6N1/7K b - e3 0 1,standard
917d731812a4ff91ad0d004400201801203531111119e5eb000003,917d731812a4ff91ad0d004400201801203531111119e5eb000003,r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1,standard
95dd00000000dd95ad8d000000111111be9e000003,95dd00000000dd95ad8d000000111111be9e000003,r1k1r2q/p1ppp1pp/8/8/8/8/P1PPP1PP/R1K1R2Q w KQkq - 0 1,chess960
a5dd00000000dda5ad8d000000111111be9e000003,a5dd00000000dda5ad8d000000111111be9e000003,r1k2r1q/p1ppp1pp/8/8/8/8/P1PPP1PP/R1K2R1Q w KQkq - 0 1,chess960
00000090c0002094ad0b2354010003,00000090c0002094ad0b2354010003,8/8/8/4B2b/6nN/8/5P2/2R1K2k w Q - 1 1,chess960
040000000000c019ab0d70000203,040000000000c019ab0d70000203,2r5/8/8/8/8/8/6PP/k2KR3 w K - 0 2,chess960
100800000000c02bd9da007b020003,100800000000c02bd9da007b020003,4r3/3k4/8/8/8/8/6PP/qR1K1R2 w KQ - 2 1,chess960
704f1ee8e81e4f70d60a44000002020813191113511571be000403,704f1ee8e81e4f70d60a44000002020813191113511571be000403,4rrk1/pbbp2p1/1ppnp3/3n1pqp/3N1PQP/1PPNP3/PBBP2P1/4RRK1 w Ff - 0 3,chess960
00000002180000308a1c0f030103,00000002180000308a1c0f030103,8/8/8/1k6/3Pp3/8/8/4KQ2 b - d3 3 1,standard
8901080000810091ad0d10e1f7000703,8901080000810091ad0d10e1f7000703,r2r3k/p7/3p4/8/8/P6P/8/R3K2R b KQq - 0 4,chess960
53b7486001243edc24da060000425013111111e1b30e041403,53b7486001243edc24da060000425013111111e1b30e041403,rn2k1r1/ppp1pp1p/3p2p1/5bn1/P7/2N2B2/1PPPPP2/2BNK1RR w Gkq - 4 11,chess960
0000000000000c026a0b000003,0000000000000c026a0b000003,8/8/8/8/8/8/2Rk4/1K6 w - - 0 1,standard
bb5844275dedffff00000000000000000000000000c0001050133eb9e5001608,bb5844275dedffff00000000000000000000000000c0001050133eb9e5001608,rn1qkb1r/3bn1p1/2p3P1/pPP2P2/P1PPP1P1/P1PP1PPP/PPPPPPPP/PPPPPPPP w kq a6 0 12,horde
009494000101d7a1460600002081421101001906,009494000101d7a1460600002081421101001906,8/2p1p2p/2Q1N2B/8/p7/N7/PPP1P1PP/R4B1R b - - 0 13,antichess
0081104243088000001c011011000106,0081104243088000001c011011000106,8/p6p/4p3/1P4P1/Pp4p1/3P4/7P/8 b - a3 0 1,antichess
00811042c208010000110c1011000106,00811042c208010000110c1011000106,8/p6p/4p3/1P4P1/1p4pP/3P4/P7/8 b - h3 0 1,antichess
008010434a0081000001c10011000206,008010434a0081000001c10011000206,8/7p/4p3/pP4P1/1p1P2p1/8/P6P/8 w - a6 0 2,antichess
000110c24a008100000101c011000206,000110c24a008100000101c011000206,8/p7/4p3/1P4Pp/1p1P2p1/8/P6P/8 w - h6 0 2,antichess
8fd720080000ffbf2d844a0d00000010111111715379000707,8fd720080000ffbf2d844a0d00000010111111715379000707,rnbq3r/ppp1p1pp/5p2/3p4/8/8/PPPPPPPP/RNBQKB1R b KQ - 0 4,atomic
00c0a40810816892a70d000410111301002107,00c0a40810816892a70d000410111301002107,8/6pp/2p2p1n/3p4/4P3/B6P/3P1PP1/1r2K2R b K - 0 17,atomic
000000000000ffff793542867b3542a6000009,000000000000ffff793542867b3542a6000009,8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1,racing_kings
0000000000407fff793542867f3542a6010109,0000000000407fff793542867f3542a6010109,8/8/8/8/8/6K1/krbnNBR1/qrbnNBRQ b - - 1 1,racing_kings
efe708901800e7e7264462000000a0811f111171535973000d04,efe708901800e7e7264462000000a0811f111171535973000d04,rnbq1bnr/ppp2ppp/3k4/4p2Q/3PK3/8/PPP2PPP/RNB2BNR b - - 0 7,koth
62d72832805ca341a600684004101591512111770f03290521,62d72832805ca341a600684004101591512111770f03290521,1r3rk1/pbp1N1pp/3p1q2/1p2bp2/7P/2PBB1P1/PP3Q1R/R5K1 b - - 1+2 3 21,three_check
a28d72240048f761660a00000400511191b11187030236011021200100,a28d72240048f761660a00000400511191b11187030236011021200100,1r3Q1n/p1kp3p/1p2ppq1/2p2b2/8/3P2P1/PPP1PPBP/R4RK1[RNbbnnp] w - - 2 28,crazyhouse
f9bd4b0100c1adee4248a200006060179011111151b3350801020100000001008000000000000000,f9bd4b0100c1adee4248a200006060179011111151b3350801020100000001008000000000000000,b2nkbnQ~/p1pppp1p/pP1q2p1/r7/8/R5PR/P1PP1P1P/1NBQ1BNK[R] w - - 1 2,crazyhouse
00000000000000000000010000000000,00000000000000000000010000000000,8/8/8/8/8/8/8/8[] w - - 0 1,crazyhouse
ffff00000000ffff2d844ad200000000111111113e955be30000010000000000ef0000000000002a,ffff00000000ffff2d844ad200000000111111113e955be30000010000000000ef0000000000002a,r~n~b~q~kb~n~r~/pppppppp/8/8/8/8/PPPPPPPP/RN~BQ~KB~NR[] w KQkq - 0 1,crazyhouse
0000000000000000,0000000000000000,8/8/8/8/8/8/8/8 w - - 0 1,chess960
00000000000000000001,00000000000000000001,8/8/8/8/8/8/8/8 b - - 0 1,chess960
000000000000000064df06,000000000000000064df06,8/8/8/8/8/8/8/8 b - - 100 432,chess960
ffff00001000efff2d844ad200000000111111113e955fe3,ffff00001000efff2d844ad200000000111111113e955fe3,rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1,chess960
20400006400000080ac0b1,20400006400000080ac0b1,5k2/6p1/8/1Pp5/6P1/8/8/3K4 w - c6 0 1,chess960
10000000180040802ac10f,10000000180040802ac10f,4k3/8/8/8/3pP3/8/6N1/7K b - e3 0 1,chess960
8901080000810091ad0d10e1f70007,8901080000810091ad0d10e1f70007,r2r3k/p7/3p4/8/8/P6P/8/R3K2R b KQq - 0 4,chess960
95dd00000000dd95ad8d000000111111be9e,95dd00000000dd95ad8d000000111111be9e,r1k1r2q/p1ppp1pp/8/8/8/8/P1PPP1PP/R1K1R2Q w KQkq - 0 1,chess960
00000002180000308a1c0f030103,00000002180000308a1c0f030103,8/8/8/1k6/3Pp3/8/8/4KQ2 b - d3 3 1,chess960
704f1ee8e81e4f70d60a44000002020813191113511571be000402,704f1ee8e81e4f70d60a44000002020813191113511571be000402,4rrk1/pbbp2p1/1ppnp3/3n1pqp/3N1PQP/1PPNP3/PBBP2P1/4RRK1 w Ff - 0 3,chess960
ffff00000000ffff2d844ad200000000111111113e955be3000004,ffff00000000ffff2d844ad200000000111111113e955be3000004,rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1,koth
ffff00000000ffff2d844ad200000000111111113e955be363000501,ffff00000000ffff2d844ad200000000111111113e955be363000501,rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 99 1 +0+1,three_check
00800000000008001a000106,00800000000008001a000106,8/7p/8/8/8/8/3K4/8 b - - 0 1,antichess
ffff00000000ffff2d844ad200000000111111113e955be3020407,ffff00000000ffff2d844ad200000000111111113e955be3020407,rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 2 3,atomic
ffff0066ffffffff000000000000000000000000000000000000111111113e955be3000008,ffff0066ffffffff000000000000000000000000000000000000111111113e955be3000008,rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1,horde
000000000000ffff793542867b3542a6000009,000000000000ffff793542867b3542a6000009,8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1,racing_kings
ffff00000000ffff2d844ad200000000111111113e955be300e407010000000000ef0000000000002a,ffff00000000ffff2d844ad200000000111111113e955be300e407010000000000ef0000000000002a,r~n~b~q~kb~n~r~/pppppppp/8/8/8/8/PPPPPPPP/RN~BQ~KB~NR/ w KQkq - 0 499,crazyhouse
ffff00000000ffff2d844ad200000000111111113e955be30000010000000000,ffff00000000ffff2d844ad200000000111111113e955be30000010000000000,rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/ w KQkq - 0 1,crazyhouse
27 changes: 27 additions & 0 deletions fuzz/binary_fen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import chess.binary_fen
from chess.binary_fen import BinaryFen

from pythonfuzz.main import PythonFuzz


@PythonFuzz
def fuzz(buf):
binary_fen = BinaryFen.parse_from_bytes(buf)
try:
board, std_mode = binary_fen.to_board()
except ValueError:
pass
else:
board.status()
list(board.legal_moves)
binary_fen2 = BinaryFen.parse_from_board(board,std_mode=std_mode)
encoded = binary_fen2.to_bytes()
board2, std_mode2 = BinaryFen.decode(encoded)
assert board == board2
assert binary_fen2 == binary_fen2.to_canonical(), "from_board should be canonical"
assert binary_fen.to_canonical() == binary_fen2.to_canonical()
assert std_mode == std_mode2


if __name__ == "__main__":
fuzz()
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/000-std
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0000000000000000
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/001-std
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
00000000000000000001
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/002-std
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
000000000000000064df06
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/003-std
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ffff00001000efff2d844ad200000000111111113e955fe3
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/004-std
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20400006400000080ac0b1
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/005-std
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
10000000180040802ac10f
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/006-std
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
00000002180000308a1c0f030103
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/007-koth
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ffff00000000ffff2d844ad200000000111111113e955be3000004
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/008-3c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ffff00000000ffff2d844ad200000000111111113e955be363000501
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/009-anti
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
00800000000008001a000106
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/010-atom
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ffff00000000ffff2d844ad200000000111111113e955be3020407
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/011-hord
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ffff0066ffffffff000000000000000000000000000000000000111111113e955be3000008
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/012-rk
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
000000000000ffff793542867b3542a6000009
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/013-zh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ffff00000000ffff2d844ad200000000111111113e955be300e407010000000000ef0000000000002a
1 change: 1 addition & 0 deletions fuzz/corpus/binary_fen/014-z
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ffff00000000ffff2d844ad200000000111111113e955be30000010000000000
265 changes: 265 additions & 0 deletions test_binary_fen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
#!/usr/bin/env python3

# Almost all tests adapted from https://github.com/lichess-org/scalachess/blob/8c94e2087f83affb9718fd2be19c34866c9a1a22/test-kit/src/test/scala/format/BinaryFenTest.scala#L1

import asyncio
import copy
import csv
import logging
import os
import os.path
import platform
import sys
import tempfile
import textwrap
import unittest
import io

import chess
import chess.variant
import chess.binary_fen

from dataclasses import asdict

from chess import Board
from chess.binary_fen import BinaryFen, ChessHeader, VariantHeader


class BinaryFenTestCase(unittest.TestCase):
def test_nibble_roundtrip(self):
for lo in range(16):
for hi in range(16):
data = bytearray()
chess.binary_fen._write_nibbles(data, lo, hi)
read_lo, read_hi = chess.binary_fen._read_nibbles(iter(data))
self.assertEqual(lo, read_lo)
self.assertEqual(hi, read_hi)

def test_std_mode_eq(self):
self.assertEqual(ChessHeader.STANDARD,ChessHeader.from_int_opt(0))

def test_bitboard_roundtrip(self):
test_bitboards = [
0x0000000000000000,
0xFFFFFFFFFFFFFFFF,
0x1234567890ABCDEF,
0x0F0F0F0F0F0F0F0F,
0xF0F0F0F0F0F0F0F0,
0x8000000000000001,
0x7FFFFFFFFFFFFFFE,
]
for bb in test_bitboards:
data = bytearray()
chess.binary_fen._write_bitboard(data, bb)
read_bb = chess.binary_fen._read_bitboard(iter(data))
self.assertEqual(bb, read_bb)

def test_leb128_roundtrip(self):
test_values = [
0,
1,
3,
127,
128,
255,
16384,
2097151,
268435455,
2147483647,
]
for value in test_values:
data = bytearray()
chess.binary_fen._write_leb128(data, value)
read_value = chess.binary_fen._read_leb128(iter(data))
self.assertEqual(value, read_value)

def test_to_canonical_1(self):
# illegal position, but it should not matter
canon = BinaryFen(
occupied=chess.BB_A1 | chess.BB_B1 | chess.BB_C1,
nibbles = [15, 15, 15],
halfmove_clock=3,
plies=5,
variant_header=ChessHeader.STANDARD.value,
variant_data=None,
)
cases = [BinaryFen(
occupied=chess.BB_A1 | chess.BB_B1 | chess.BB_C1,
nibbles = [11, 15, 11],
halfmove_clock=3,
plies=4,
variant_header=ChessHeader.STANDARD.value,
variant_data=None
),
BinaryFen(
occupied=chess.BB_A1 | chess.BB_B1 | chess.BB_C1,
nibbles = [15, 15, 11],
halfmove_clock=3,
plies=4,
variant_header=ChessHeader.STANDARD.value,
variant_data=None
),
BinaryFen(
occupied=chess.BB_A1 | chess.BB_B1 | chess.BB_C1,
nibbles = [11, 15, 15],
halfmove_clock=3,
plies=4,
variant_header=ChessHeader.STANDARD.value,
variant_data=None
),
]
for case in cases:
with self.subTest(case=case):
self.assertNotEqual(canon, case)
canon_case = case.to_canonical()
self.assertEqual(canon, canon_case)

def test_to_canonical_2(self):
# illegal position, but it should not matter
canon = BinaryFen(
occupied=chess.BB_A1 | chess.BB_B1 | chess.BB_C1,
nibbles = [15, 15, 15],
halfmove_clock=3,
plies=5,
variant_header=ChessHeader.STANDARD.value,
variant_data=None,
)
cases = [BinaryFen(
occupied=chess.BB_A1 | chess.BB_B1 | chess.BB_C1,
nibbles = [11, 15, 11],
halfmove_clock=3,
plies=5,
variant_header=ChessHeader.STANDARD.value,
variant_data=None
),
BinaryFen(
occupied=chess.BB_A1 | chess.BB_B1 | chess.BB_C1,
nibbles = [15, 15, 11],
halfmove_clock=3,
plies=5,
variant_header=ChessHeader.STANDARD.value,
variant_data=None
),
BinaryFen(
occupied=chess.BB_A1 | chess.BB_B1 | chess.BB_C1,
nibbles = [11, 15, 15],
halfmove_clock=3,
plies=5,
variant_header=ChessHeader.STANDARD.value,
variant_data=None
),
BinaryFen(
occupied=chess.BB_A1 | chess.BB_B1 | chess.BB_C1,
nibbles = [11, 11, 11],
halfmove_clock=3,
plies=5,
variant_header=ChessHeader.STANDARD.value,
variant_data=None
),
]
for case in cases:
with self.subTest(case=case):
self.assertNotEqual(canon, case)
canon_case = case.to_canonical()
self.assertEqual(canon, canon_case)

_VARIANT_CLASSES = {
"standard": lambda fen: chess.Board(fen=fen, chess960=False),
"chess960": lambda fen: chess.Board(fen=fen, chess960=True),
"koth": lambda fen: chess.variant.KingOfTheHillBoard(fen=fen),
"three_check": lambda fen: chess.variant.ThreeCheckBoard(fen=fen),
"antichess": lambda fen: chess.variant.AntichessBoard(fen=fen),
"atomic": lambda fen: chess.variant.AtomicBoard(fen=fen),
"horde": lambda fen: chess.variant.HordeBoard(fen=fen),
"racing_kings": lambda fen: chess.variant.RacingKingsBoard(fen=fen),
"crazyhouse": lambda fen: chess.variant.CrazyhouseBoard(fen=fen),
}

def _run_case(self, binary_fen_hex, canonical_hex, fen, variant_name):
expected_board = self._VARIANT_CLASSES[variant_name](fen)

decoded_board, std_mode = BinaryFen.decode(bytes.fromhex(binary_fen_hex))

encoded_hex = BinaryFen.encode(expected_board, std_mode=std_mode).hex()
self.assertEqual(
encoded_hex, canonical_hex, "encode(board) must equal canonical_binary_fen"
)

self.assertEqual(
decoded_board,
expected_board,
"decode(canonical_binary_fen) must equal board from FEN",
)

parsed = BinaryFen.parse_from_bytes(bytes.fromhex(binary_fen_hex))
self.assertEqual(
parsed.to_canonical().to_bytes().hex(),
canonical_hex,
"parse_from_bytes(binary_fen).to_canonical() must equal canonical_binary_fen",
)

def test_data_driven(self):
csv_path = os.path.join(os.path.dirname(__file__), "data/test_binary_fen_cases.csv")
with open(csv_path, newline="") as f:
reader = csv.DictReader(f)
for row in reader:
with self.subTest(
binary_fen=row["binary_fen"], fen=row["fen"], variant=row["variant"]
):
self._run_case(
binary_fen_hex=row["binary_fen"],
canonical_hex=row["canonical_binary_fen"],
fen=row["fen"],
variant_name=row["variant"] or "standard",
)

def test_fuzz_fails(self):
fuzz_fails = [
"23d7",
"e17f11efd84522d34878ffffffa600000000ce1b23ffff000943",
"20f7076f1718f99824a5020724b3cfc1020146ae00004f85ae28aebc",
"edf9b3c5cb7fa5008000004081c83e4092a7e63dd95a",
"f7cef6e64ed47a4ede172a100000009b004c909b",
"bb7cb00cc3f31dc3f325b8",
"4584aced8100da50a20bd7251705a15b108000251705",
"77ff05111f77111f4214e803647fff6429f0a2f65933310185016400000045bf1e8be6b013ed02",
"55d648e9a20fd600400000e9a29c0010043b26fb41d50a50",
"d8805347e76003102228687fffff41b19e2bff00000100020220c6",
]
for fuzz_fail in fuzz_fails:
with self.subTest(fuzz_fail=fuzz_fail):
data = bytes.fromhex(fuzz_fail)
binary_fen = BinaryFen.parse_from_bytes(data)
try:
board, std_mode = binary_fen.to_board()
except ValueError:
continue
# print("binary_fen", binary_fen)
# print("ep square", board.ep_square)
# print("fullmove", board.fullmove_number)
# print("halfmove_clock", board.halfmove_clock)
# print("fen", board.fen())
# print()
# should not error
board.status()
list(board.legal_moves)
binary_fen2 = BinaryFen.parse_from_board(board,std_mode=std_mode)
# print("encoded", binary_fen2.to_bytes().hex())
# print("binary_fen2", binary_fen2)
# dbg(binary_fen, binary_fen2)
# print("CANONICAL")
# dbg(binary_fen.to_canonical(), binary_fen)
self.assertEqual(binary_fen2, binary_fen2.to_canonical(), "from board should produce canonical value")
self.assertEqual(binary_fen.to_canonical(), binary_fen2.to_canonical())
board2, std_mode2 = binary_fen2.to_board()
self.assertEqual(board, board2)
self.assertEqual(std_mode, std_mode2)

def dbg(a, b):
from pprint import pprint
from deepdiff import DeepDiff
pprint(DeepDiff(a, b),indent=2)

if __name__ == "__main__":
print("#"*80)
unittest.main()