diff --git a/src/logic/board.py b/src/logic/board.py index 581b86b..00f98f2 100644 --- a/src/logic/board.py +++ b/src/logic/board.py @@ -18,6 +18,8 @@ class Board: self._white_castling_rights = set() self._black_castling_rights = set() self._en_passant_target = None + self._n_moves = 0 + self._n_half_moves = 0 @staticmethod def _piece_class_from_char(c: str) -> Type[Piece]: @@ -83,6 +85,8 @@ class Board: for c in position[index:]: index += 1 if c == "-" or c == " ": + if c == "-": + index += 1 break sides = "kq" @@ -98,7 +102,24 @@ class Board: # -- En passant target if position[index] != "-": - ret._en_passant_target = position[index:index+2] + pos = Position.from_algebraic(position[index:index+2]) + index += 2 + if pos.y == 2: + pos.y += 1 + assert pos in ret._white, "En passant target is not in the position" + ret._en_passant_target = ret._white[pos] + elif pos.y == 5: + pos.y -= 1 + assert pos in ret._black, "En passant target is not in the position" + ret._en_passant_target = ret._black[pos] + else: + raise ValueError("You can't have a en passant target that is not on the third or sixth rank") + else: + index += 1 + index += 1 + + ret._n_half_moves = int(position[index:position.find(" ", index + 1)]) + ret._n_moves = int(position[position.find(" ", index)+1:]) return ret @@ -180,6 +201,14 @@ class Board: if move.pos in other_pieces: del other_pieces[move.pos] + if piece.colour == Colour.BLACK: + ret._n_moves = self._n_moves + 1 + + if move.is_capturing or type(piece) == Pawn: + ret._n_half_moves = 0 + else: + ret._n_half_moves = self._n_half_moves + 1 + if move.en_passant: pos_to_remove = Position(move.pos.x, move.pos.y + (1 if self._turn == Colour.BLACK else -1)) del other_pieces[pos_to_remove] @@ -232,4 +261,67 @@ class Board: return ret -INITIAL_BOARD = Board.setup_FEN_position("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") + def to_fen_string(self): + ret = "" + for y in range(7, -1, -1): + empty_cell_counter = 0 + for x in range(8): + pos = Position(x, y) + + piece = None + if pos in self._white: + piece = self._white[pos] + elif pos in self._black: + piece = self._black[pos] + + if piece is None: + empty_cell_counter += 1 + continue + + if empty_cell_counter > 0: + ret += str(empty_cell_counter) + empty_cell_counter = 0 + letter = piece.letter() + ret += letter.lower() if piece.colour == Colour.BLACK else letter.upper() + + if empty_cell_counter > 0: + ret += str(empty_cell_counter) + + if y > 0: + ret += "/" + ret += " " + + ret += "w" if self._turn == Colour.WHITE else "b" + ret += " " + + if len(self._white_castling_rights) == 0 and len(self._black_castling_rights) == 0: + ret += "-" + else: + if CastleSide.King in self._white_castling_rights: + ret += "K" + if CastleSide.Queen in self._white_castling_rights: + ret += "Q" + + if CastleSide.King in self._black_castling_rights: + ret += "k" + if CastleSide.Queen in self._black_castling_rights: + ret += "q" + ret += " " + + if self._en_passant_target is not None: + pos = self._en_passant_target.pos + pos.y += -1 if self._en_passant_target.colour == Colour.WHITE else 1 + ret += pos.to_algebraic() + else: + ret += "-" + ret += " " + + ret += str(self._n_half_moves) + ret += " " + ret += str(self._n_moves) + + return ret + +_fen_pos = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" +_fen_pos = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" +INITIAL_BOARD = Board.setup_FEN_position(_fen_pos) diff --git a/src/logic/pieces/pawn.py b/src/logic/pieces/pawn.py index 03a6c65..2556607 100644 --- a/src/logic/pieces/pawn.py +++ b/src/logic/pieces/pawn.py @@ -75,3 +75,6 @@ class Pawn(Piece): return self.keep_only_blocking(ret, board) return ret + + def letter(self): + return "p" diff --git a/src/logic/position.py b/src/logic/position.py index 837c8b3..c8cc95f 100644 --- a/src/logic/position.py +++ b/src/logic/position.py @@ -17,6 +17,14 @@ class Position: return x >= Position._MIN_POS and x <= Position._MAX_POS \ and y >= Position._MIN_POS and y <= Position._MAX_POS + @staticmethod + def from_algebraic(square: str) -> "Position": + assert len(square) == 2, f"'{square}' is malformed" + x = Position._FILES.index(square[0]) + y = Position._RANKS.index(int(square[1])) + + return Position(x, y) + def to_algebraic(self) -> str: return f"{Position._FILES[self.x]}{Position._RANKS[self.y]}" diff --git a/tests/fen.py b/tests/fen.py new file mode 100644 index 0000000..6b7d5fe --- /dev/null +++ b/tests/fen.py @@ -0,0 +1,30 @@ +import unittest + +import sys +sys.path.append('src') # you must execute pytest from the stickfosh dir for this to work + +from logic.board import Board + +class FENTests(unittest.TestCase): + def testInitialPosition(self): + pos = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" + self.assertEqual(pos, Board.setup_FEN_position(pos).to_fen_string()) + + def testRandomPositions(self): + pos = "r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1 b Qk - 0 1" + self.assertEqual(pos, Board.setup_FEN_position(pos).to_fen_string()) + + pos = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1" + self.assertEqual(pos, Board.setup_FEN_position(pos).to_fen_string()) + + pos = "4k2r/6r1/8/8/8/8/3R4/R3K3 w Qk - 0 1" + self.assertEqual(pos, Board.setup_FEN_position(pos).to_fen_string()) + + pos = "8/8/8/4p1K1/2k1P3/8/8/8 b - - 0 1" + self.assertEqual(pos, Board.setup_FEN_position(pos).to_fen_string()) + + pos = "8/5k2/3p4/1p1Pp2p/pP2Pp1P/P4P1K/8/8 b - - 99 50" + self.assertEqual(pos, Board.setup_FEN_position(pos).to_fen_string()) + + + diff --git a/tests/position.py b/tests/position.py new file mode 100644 index 0000000..c36937c --- /dev/null +++ b/tests/position.py @@ -0,0 +1,30 @@ +import unittest + +import sys +sys.path.append('src') # you must execute pytest from the stickfosh dir for this to work + +from logic.position import Position + +class PositionTests(unittest.TestCase): + def testXY2Algebraic(self): + self.assertEqual(Position(0, 0).to_algebraic(), "a1") + self.assertEqual(Position(1, 0).to_algebraic(), "b1") + + self.assertEqual(Position(2, 1).to_algebraic(), "c2") + self.assertEqual(Position(4, 2).to_algebraic(), "e3") + + self.assertEqual(Position(7, 7).to_algebraic(), "h8") + + def testAlgebraic2XY(self): + self.assertEqual(Position.from_algebraic("a1"), Position(0, 0)) + self.assertEqual(Position.from_algebraic("b1"), Position(1, 0)) + + self.assertEqual(Position.from_algebraic("c2"), Position(2, 1)) + self.assertEqual(Position.from_algebraic("e3"), Position(4, 2)) + + self.assertEqual(Position.from_algebraic("h8"), Position(7, 7)) + + self.assertRaises(AssertionError, lambda : Position.from_algebraic("a11")) + + self.assertRaises(ValueError, lambda : Position.from_algebraic("j1")) + self.assertRaises(ValueError, lambda : Position.from_algebraic("a9"))