From 9784699fbdf0f62c6cec04eb9e1d785237a9f680 Mon Sep 17 00:00:00 2001 From: George Munyoro Date: Mon, 26 Oct 2020 15:46:26 -0400 Subject: [PATCH 01/11] Created method for checking if square is attacked by opponent --- skaak/board.py | 122 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 113 insertions(+), 9 deletions(-) diff --git a/skaak/board.py b/skaak/board.py index adf58a1..f78e7a4 100644 --- a/skaak/board.py +++ b/skaak/board.py @@ -15,9 +15,16 @@ def __init__(self, fen: str = chess.STARTING_FEN, **kwargs) -> None: self.full: int = 0 self.half: int = 0 - self.set_fen(fen) + @property + def white_king_position(self) -> int: + return self.board.index("K") + + @property + def black_king_position(self) -> int: + return self.board.index("k") + def __repr__(self) -> str: ascii_repr: str = "" for i, char in enumerate(self.board): @@ -36,14 +43,16 @@ def _128_index_to_san(index: int) -> str: return f"{rank}{file}" @staticmethod - def _draw_indexed_board(highlighted_squares: List[int]=[]) -> None: + def _draw_indexed_board(highlighted_squares: List[int] = []) -> None: for i in range(128): - if i & 0x88 != 0: continue + if i & 0x88 != 0: + continue if i % 8 == 0: - print('\n') - if i in highlighted_squares: i = '*' - print('{:>4}'.format(i), end=' ') - print('\n') + print("\n") + if i in highlighted_squares: + i = "*" + print("{:>4}".format(i), end=" ") + print("\n") def move(self, move: chess.Move) -> None: if (0x88 & move.initial_square) != 0: @@ -71,11 +80,80 @@ def undo_move(self) -> None: self.half -= 1 + def is_square_attacked(self, square: int) -> bool: + if square & 0x88 != 0: + return None + + for direction in chess.MOVES["r"]: + for i in count(square + direction, direction): + if (i & 0x88) != 0: + break + if (self.board[i].isupper() and self.turn == chess.WHITE) or ( + self.board[i].islower() and self.turn == chess.BLACK + ): + break + + if self.board[i].lower() in "rq": + return True + else: + break + + for direction in chess.MOVES["b"]: + for i in count(square + direction, direction): + if (i & 0x88) != 0: + break + if ( + self.board[i].isupper() + and self.turn == chess.WHITE + or self.board[i].islower() + and self.turn == chess.BLACK + ): + break + + if self.board[i].lower() in "bq": + return True + else: + break + + if self.turn == chess.WHITE: + if ( + self.board[square + chess.NORTH + chess.EAST] == "p" + or self.board[square + chess.NORTH + chess.WEST] == "p" + ): + return True + elif self.turn == chess.BLACK: + if ( + self.board[square + chess.SOUTH + chess.EAST] == "P" + or self.board[square + chess.SOUTH + chess.WEST] == "P" + ): + return True + + non_sliding_pieces = ["k", "n"] + for piece_type in non_sliding_pieces: + for i in chess.MOVES[piece_type]: + if (square + i) & 0x88 == 0: + if ( + self.board[square + i] == piece_type.lower() + and self.turn == chess.WHITE + ) or ( + self.board[square + i] == piece_type.upper() + and self.turn == chess.BLACK + ): + return True + + return False + + def generate_legal_moves(self) -> List[chess.Move]: + for move in self.generate_pseudo_moves(): + if move.pseudo: + yield(move) + def generate_pseudo_moves(self) -> List[chess.Move]: for square, piece in enumerate(self.board): if (square & 0x88) != 0: continue + if self.turn == chess.WHITE and not piece.isupper(): continue elif self.turn == chess.BLACK and not piece.islower(): @@ -95,6 +173,16 @@ def generate_pseudo_moves(self) -> List[chess.Move]: break if piece.lower() == "p": + if ( + self.turn == chess.WHITE + and direction == (chess.NORTH * 2) + and square // 16 != 6 + ) or ( + self.turn == chess.BLACK + and direction == (chess.SOUTH * 2) + and square // 16 != 1 + ): + break if (j % 16 != square % 16) and self.board[j] in "-.": break if (j % 16 == square % 16) and self.board[j] not in "-.": @@ -104,25 +192,41 @@ def generate_pseudo_moves(self) -> List[chess.Move]: ] != "": break - yield chess.Move( + move = chess.Move( initial_square=square, target_square=j, moving_piece=self.board[square], attacked_piece=self.board[j], capture=(self.board[j] not in "-."), score=0, + pseudo=True, ) + self.move(move=move) + self.undo_move() + + if not self.in_check(): + move.pseudo = False + + yield (move) + if self.board[j] not in "-." or piece.lower() in "knp": break + def in_check(self) -> bool: + return ( + self.is_square_attacked(self.white_king_position) + if self.turn == chess.WHITE + else self.is_square_attacked(self.black_king_position) + ) + def perft(self, depth: int) -> int: nodes = 0 if depth == 0: return 1 - for move in self.generate_pseudo_moves(): + for move in self.generate_legal_moves(): self.move(move) nodes += self.perft(depth - 1) self.undo_move() From 5f467e0be71a356a089534f0cbd327d6b96d1f22 Mon Sep 17 00:00:00 2001 From: George Munyoro Date: Mon, 26 Oct 2020 15:46:43 -0400 Subject: [PATCH 02/11] Converted move namedtuple to class --- skaak/chess.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/skaak/chess.py b/skaak/chess.py index 959713c..b4295d8 100644 --- a/skaak/chess.py +++ b/skaak/chess.py @@ -1,5 +1,3 @@ -from collections import namedtuple - STARTING_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" WHITE = 0 @@ -9,9 +7,15 @@ NORTH, EAST, SOUTH, WEST = -16, 1, 16, -1 -Move = namedtuple( - "Move", "initial_square target_square moving_piece attacked_piece capture score" -) +class Move: + def __init__(self, **kwargs): + self.initial_square: int = kwargs.pop('initial_square', None) + self.target_square: int = kwargs.pop('target_square', None) + self.moving_piece: int = kwargs.pop('moving_piece', None) + self.attacked_piece: int = kwargs.pop('attacked_piece', None) + self.capture: bool = kwargs.pop('capture', None) + self.score: int = kwargs.pop('score', None) + self.pseudo: bool = kwargs.pop('pseudo', None) RANKS = "abcdefgh" From 89036579a10fa89ef6a7084d3b4b1b1b1eea54b0 Mon Sep 17 00:00:00 2001 From: George Munyoro Date: Sat, 31 Oct 2020 21:52:32 -0700 Subject: [PATCH 03/11] - Moved king safety checking for move generation into legal moves method - Fixed bug in move directions dict for knights --- skaak/board.py | 18 ++++++------------ skaak/chess.py | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/skaak/board.py b/skaak/board.py index f78e7a4..4a3efc8 100644 --- a/skaak/board.py +++ b/skaak/board.py @@ -145,8 +145,10 @@ def is_square_attacked(self, square: int) -> bool: def generate_legal_moves(self) -> List[chess.Move]: for move in self.generate_pseudo_moves(): - if move.pseudo: + self.move(move) + if not self.in_check(): yield(move) + self.undo_move() def generate_pseudo_moves(self) -> List[chess.Move]: for square, piece in enumerate(self.board): @@ -192,27 +194,19 @@ def generate_pseudo_moves(self) -> List[chess.Move]: ] != "": break - move = chess.Move( + yield(chess.Move( initial_square=square, target_square=j, moving_piece=self.board[square], attacked_piece=self.board[j], capture=(self.board[j] not in "-."), score=0, - pseudo=True, - ) - - self.move(move=move) - self.undo_move() - - if not self.in_check(): - move.pseudo = False - - yield (move) + )) if self.board[j] not in "-." or piece.lower() in "knp": break + def in_check(self) -> bool: return ( self.is_square_attacked(self.white_king_position) diff --git a/skaak/chess.py b/skaak/chess.py index b4295d8..f6db2dc 100644 --- a/skaak/chess.py +++ b/skaak/chess.py @@ -39,7 +39,7 @@ def __init__(self, **kwargs): SOUTH + SOUTH + EAST, NORTH + NORTH + WEST, SOUTH + EAST + EAST, - EAST + EAST + SOUTH, + WEST + SOUTH + SOUTH, WEST + WEST + SOUTH, ), "k": ( From bd66b7a1ca6e21818ca1865cdb4304b41da22013 Mon Sep 17 00:00:00 2001 From: George Munyoro Date: Sat, 31 Oct 2020 22:01:43 -0700 Subject: [PATCH 04/11] Fixed bug in move generation: double moves were not checked to ensure both spaces in front of the pawn are empty --- skaak/board.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skaak/board.py b/skaak/board.py index 4a3efc8..090ad36 100644 --- a/skaak/board.py +++ b/skaak/board.py @@ -178,11 +178,11 @@ def generate_pseudo_moves(self) -> List[chess.Move]: if ( self.turn == chess.WHITE and direction == (chess.NORTH * 2) - and square // 16 != 6 + and (square // 16 != 6 or self.board[square+chess.NORTH] != chess.EMPTY) ) or ( self.turn == chess.BLACK and direction == (chess.SOUTH * 2) - and square // 16 != 1 + and (square // 16 != 1 or self.board[square+chess.SOUTH] != chess.EMPTY) ): break if (j % 16 != square % 16) and self.board[j] in "-.": From d336e94869b10240473ba7d9e9aea7df09f1e686 Mon Sep 17 00:00:00 2001 From: George Munyoro Date: Sun, 1 Nov 2020 16:08:56 -0800 Subject: [PATCH 05/11] - Replaced hard coded default FEN strings with constants - Wrote test for board is_square_attacked method - write test for generate_fen board method --- tests/test_board.py | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/tests/test_board.py b/tests/test_board.py index 08604df..4e87c7e 100644 --- a/tests/test_board.py +++ b/tests/test_board.py @@ -1,16 +1,17 @@ from skaak import Chessboard from skaak import chess +import os def test_board_set_starting_fen_by_default(): board = Chessboard() - assert board.fen == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" + assert board.fen == chess.STARTING_FEN def test_board_repr(): board = Chessboard() - board.set_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") + board.set_fen(chess.STARTING_FEN) assert str(board) == ( "\nr n b q k b n r " "\np p p p p p p p " @@ -73,7 +74,7 @@ def test_board_set_fen(): def test_board_move(): - board = Chessboard("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") + board = Chessboard(chess.STARTING_FEN) moves = ( chess.Move( @@ -111,8 +112,8 @@ def test_board_move(): def test_board_move_gen(): - board = Chessboard("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") - + board = Chessboard(chess.STARTING_FEN) + assert board.perft(0) == 1 assert board.perft(1) == 20 assert board.perft(2) == 400 @@ -121,8 +122,39 @@ def test_board_move_gen(): assert board.perft(5) == 4865609 +def test_board_generate_fen(): + with open('tests/FEN/endgames.txt', 'r') as endgames_fen_text_file: + for fen in endgames_fen_text_file.readlines(): + board = Chessboard(fen) + assert board.generate_fen().strip() == fen.strip() + + +def test_board_is_square_attacked(): + fens = { + '8/5K2/8/7k/8/8/8/8 w - - 0 1': ( + 38, 39, 54, 70, 71 + ), + '3Q4/3p2p1/4pp2/K7/bPp1BP1B/2P2p2/8/2k3r1 w - - 0 1': ( + 34, 38, 39, 49, 51, 52, 53, 54, 70, 81, 83, 86, 97, + 98, 99, 100, 102, 113, 115, 116, 117, 119 + ), + '3B4/PP2r3/1rp5/P3k1Pb/2p4p/3P2qN/8/K7 w - - 0 1': ( + 4, 17, 18, 19, 21, 22, 23, 32, 35, 36, 37, 38, 49, + 51, 53, 54, 65, 67, 68, 69, 70, 81, 83, 84, 85, 87, + 97, 100, 101, 102, 103, 113, 115, 116, 118 + ), + } + + for fen in fens: + attacked_squares = fens[fen] + board = Chessboard(fen) + for square in [i for i in range(128) if (i & 0x88) == 0]: + if board.is_square_attacked(square): + assert square in attacked_squares + + def test_board_undo_move(): - board = Chessboard("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") + board = Chessboard(chess.STARTING_FEN) og_board = board.board moves = ( From e1f9631d27e97b10b2429e9fbb43d6a701b60428 Mon Sep 17 00:00:00 2001 From: George Munyoro Date: Sun, 1 Nov 2020 16:15:44 -0800 Subject: [PATCH 06/11] - wrote method for checking what color piece is occupying a particular square - Fixed bug in is_square_attackd method where it would stop searching once it reached an empty square - Created fen generator method for generating a fen from the current board state - Added pieces constant for checking if a value is a valid piece --- skaak/board.py | 40 ++++++++++++++++++++++++++++++++++++++++ skaak/chess.py | 2 ++ 2 files changed, 42 insertions(+) diff --git a/skaak/board.py b/skaak/board.py index 090ad36..d69916c 100644 --- a/skaak/board.py +++ b/skaak/board.py @@ -25,6 +25,10 @@ def white_king_position(self) -> int: def black_king_position(self) -> int: return self.board.index("k") + @property + def legal_moves(self) -> List[chess.Move]: + return self.generate_legal_moves() + def __repr__(self) -> str: ascii_repr: str = "" for i, char in enumerate(self.board): @@ -80,10 +84,21 @@ def undo_move(self) -> None: self.half -= 1 + def get_square_color(self, square: int) -> int: + if self.board[square] == chess.EMPTY: + return None + + if (self.board[square].isupper()): + return chess.WHITE + return chess.BLACK + def is_square_attacked(self, square: int) -> bool: if square & 0x88 != 0: return None + if (self.board[square] != chess.EMPTY) and self.get_square_color(square) != self.turn: + return None + for direction in chess.MOVES["r"]: for i in count(square + direction, direction): if (i & 0x88) != 0: @@ -95,6 +110,8 @@ def is_square_attacked(self, square: int) -> bool: if self.board[i].lower() in "rq": return True + elif self.board[i] == chess.EMPTY: + continue else: break @@ -112,6 +129,8 @@ def is_square_attacked(self, square: int) -> bool: if self.board[i].lower() in "bq": return True + elif self.board[i] == chess.EMPTY: + continue else: break @@ -143,6 +162,26 @@ def is_square_attacked(self, square: int) -> bool: return False + def generate_fen(self) -> str: + fen = '' + empty_square_count = 0 + for i, j in enumerate(self.board): + if (i & 0x88) != 0: + if empty_square_count > 0: + fen += str(empty_square_count) + empty_square_count = 0 + continue + if i % 8 == 0 and i != 0: fen += '/' + if j in chess.PIECES: + if empty_square_count > 0: + fen += str(empty_square_count) + empty_square_count = 0 + fen += j + elif j == chess.EMPTY: + empty_square_count += 1 + + return fen + f' {"wb"[self.turn]} {self.castling} {self.en_pas} {self.half} {self.full}' + def generate_legal_moves(self) -> List[chess.Move]: for move in self.generate_pseudo_moves(): self.move(move) @@ -244,6 +283,7 @@ def set_fen(self, fen: str) -> None: self.turn = chess.WHITE if turn == "w" else chess.BLACK self.castling = castling + self.en_pas = en_pas self.half = int(half) self.full = int(full) diff --git a/skaak/chess.py b/skaak/chess.py index f6db2dc..28f47a2 100644 --- a/skaak/chess.py +++ b/skaak/chess.py @@ -7,6 +7,8 @@ NORTH, EAST, SOUTH, WEST = -16, 1, 16, -1 +PIECES = "rnbqkpRNBQKP" + class Move: def __init__(self, **kwargs): self.initial_square: int = kwargs.pop('initial_square', None) From e3fe3b6f63532ce4932ecfe5735ac3249598b317 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Sun, 1 Nov 2020 19:19:24 +0000 Subject: [PATCH 07/11] Restyled by autopep8 --- skaak/board.py | 4 ++-- skaak/chess.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/skaak/board.py b/skaak/board.py index d69916c..94484b7 100644 --- a/skaak/board.py +++ b/skaak/board.py @@ -171,7 +171,8 @@ def generate_fen(self) -> str: fen += str(empty_square_count) empty_square_count = 0 continue - if i % 8 == 0 and i != 0: fen += '/' + if i % 8 == 0 and i != 0: + fen += '/' if j in chess.PIECES: if empty_square_count > 0: fen += str(empty_square_count) @@ -245,7 +246,6 @@ def generate_pseudo_moves(self) -> List[chess.Move]: if self.board[j] not in "-." or piece.lower() in "knp": break - def in_check(self) -> bool: return ( self.is_square_attacked(self.white_king_position) diff --git a/skaak/chess.py b/skaak/chess.py index 28f47a2..dc47418 100644 --- a/skaak/chess.py +++ b/skaak/chess.py @@ -9,6 +9,7 @@ PIECES = "rnbqkpRNBQKP" + class Move: def __init__(self, **kwargs): self.initial_square: int = kwargs.pop('initial_square', None) @@ -19,6 +20,7 @@ def __init__(self, **kwargs): self.score: int = kwargs.pop('score', None) self.pseudo: bool = kwargs.pop('pseudo', None) + RANKS = "abcdefgh" MOVES = { From 67ee080f9fc46939ec74f5dac602541a049f66ef Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Sun, 1 Nov 2020 19:19:26 +0000 Subject: [PATCH 08/11] Restyled by black --- skaak/board.py | 45 ++++++++++++++++---------- skaak/chess.py | 14 ++++----- tests/test_board.py | 77 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 98 insertions(+), 38 deletions(-) diff --git a/skaak/board.py b/skaak/board.py index 94484b7..e22b83c 100644 --- a/skaak/board.py +++ b/skaak/board.py @@ -88,7 +88,7 @@ def get_square_color(self, square: int) -> int: if self.board[square] == chess.EMPTY: return None - if (self.board[square].isupper()): + if self.board[square].isupper(): return chess.WHITE return chess.BLACK @@ -96,7 +96,9 @@ def is_square_attacked(self, square: int) -> bool: if square & 0x88 != 0: return None - if (self.board[square] != chess.EMPTY) and self.get_square_color(square) != self.turn: + if (self.board[square] != chess.EMPTY) and self.get_square_color( + square + ) != self.turn: return None for direction in chess.MOVES["r"]: @@ -163,7 +165,7 @@ def is_square_attacked(self, square: int) -> bool: return False def generate_fen(self) -> str: - fen = '' + fen = "" empty_square_count = 0 for i, j in enumerate(self.board): if (i & 0x88) != 0: @@ -172,7 +174,7 @@ def generate_fen(self) -> str: empty_square_count = 0 continue if i % 8 == 0 and i != 0: - fen += '/' + fen += "/" if j in chess.PIECES: if empty_square_count > 0: fen += str(empty_square_count) @@ -181,13 +183,16 @@ def generate_fen(self) -> str: elif j == chess.EMPTY: empty_square_count += 1 - return fen + f' {"wb"[self.turn]} {self.castling} {self.en_pas} {self.half} {self.full}' + return ( + fen + + f' {"wb"[self.turn]} {self.castling} {self.en_pas} {self.half} {self.full}' + ) def generate_legal_moves(self) -> List[chess.Move]: for move in self.generate_pseudo_moves(): self.move(move) if not self.in_check(): - yield(move) + yield (move) self.undo_move() def generate_pseudo_moves(self) -> List[chess.Move]: @@ -218,11 +223,17 @@ def generate_pseudo_moves(self) -> List[chess.Move]: if ( self.turn == chess.WHITE and direction == (chess.NORTH * 2) - and (square // 16 != 6 or self.board[square+chess.NORTH] != chess.EMPTY) + and ( + square // 16 != 6 + or self.board[square + chess.NORTH] != chess.EMPTY + ) ) or ( self.turn == chess.BLACK and direction == (chess.SOUTH * 2) - and (square // 16 != 1 or self.board[square+chess.SOUTH] != chess.EMPTY) + and ( + square // 16 != 1 + or self.board[square + chess.SOUTH] != chess.EMPTY + ) ): break if (j % 16 != square % 16) and self.board[j] in "-.": @@ -234,14 +245,16 @@ def generate_pseudo_moves(self) -> List[chess.Move]: ] != "": break - yield(chess.Move( - initial_square=square, - target_square=j, - moving_piece=self.board[square], - attacked_piece=self.board[j], - capture=(self.board[j] not in "-."), - score=0, - )) + yield ( + chess.Move( + initial_square=square, + target_square=j, + moving_piece=self.board[square], + attacked_piece=self.board[j], + capture=(self.board[j] not in "-."), + score=0, + ) + ) if self.board[j] not in "-." or piece.lower() in "knp": break diff --git a/skaak/chess.py b/skaak/chess.py index dc47418..878c697 100644 --- a/skaak/chess.py +++ b/skaak/chess.py @@ -12,13 +12,13 @@ class Move: def __init__(self, **kwargs): - self.initial_square: int = kwargs.pop('initial_square', None) - self.target_square: int = kwargs.pop('target_square', None) - self.moving_piece: int = kwargs.pop('moving_piece', None) - self.attacked_piece: int = kwargs.pop('attacked_piece', None) - self.capture: bool = kwargs.pop('capture', None) - self.score: int = kwargs.pop('score', None) - self.pseudo: bool = kwargs.pop('pseudo', None) + self.initial_square: int = kwargs.pop("initial_square", None) + self.target_square: int = kwargs.pop("target_square", None) + self.moving_piece: int = kwargs.pop("moving_piece", None) + self.attacked_piece: int = kwargs.pop("attacked_piece", None) + self.capture: bool = kwargs.pop("capture", None) + self.score: int = kwargs.pop("score", None) + self.pseudo: bool = kwargs.pop("pseudo", None) RANKS = "abcdefgh" diff --git a/tests/test_board.py b/tests/test_board.py index 4e87c7e..9510799 100644 --- a/tests/test_board.py +++ b/tests/test_board.py @@ -60,10 +60,7 @@ def test_board_set_fen(): "fen": "r5R1/3B4/3p3P/P7/1pN5/2pn1K2/N1k4r/R4n2 b - - 0 1", "turn": chess.BLACK, }, - { - "fen": "2N5/3R4/p3k3/1R3pKp/3qp1b1/8/P6P/5NQn w - - 0 1", - "turn": chess.WHITE - }, + {"fen": "2N5/3R4/p3k3/1R3pKp/3qp1b1/8/P6P/5NQn w - - 0 1", "turn": chess.WHITE}, ) for position in fen_strings: @@ -123,7 +120,7 @@ def test_board_move_gen(): def test_board_generate_fen(): - with open('tests/FEN/endgames.txt', 'r') as endgames_fen_text_file: + with open("tests/FEN/endgames.txt", "r") as endgames_fen_text_file: for fen in endgames_fen_text_file.readlines(): board = Chessboard(fen) assert board.generate_fen().strip() == fen.strip() @@ -131,17 +128,67 @@ def test_board_generate_fen(): def test_board_is_square_attacked(): fens = { - '8/5K2/8/7k/8/8/8/8 w - - 0 1': ( - 38, 39, 54, 70, 71 - ), - '3Q4/3p2p1/4pp2/K7/bPp1BP1B/2P2p2/8/2k3r1 w - - 0 1': ( - 34, 38, 39, 49, 51, 52, 53, 54, 70, 81, 83, 86, 97, - 98, 99, 100, 102, 113, 115, 116, 117, 119 + "8/5K2/8/7k/8/8/8/8 w - - 0 1": (38, 39, 54, 70, 71), + "3Q4/3p2p1/4pp2/K7/bPp1BP1B/2P2p2/8/2k3r1 w - - 0 1": ( + 34, + 38, + 39, + 49, + 51, + 52, + 53, + 54, + 70, + 81, + 83, + 86, + 97, + 98, + 99, + 100, + 102, + 113, + 115, + 116, + 117, + 119, ), - '3B4/PP2r3/1rp5/P3k1Pb/2p4p/3P2qN/8/K7 w - - 0 1': ( - 4, 17, 18, 19, 21, 22, 23, 32, 35, 36, 37, 38, 49, - 51, 53, 54, 65, 67, 68, 69, 70, 81, 83, 84, 85, 87, - 97, 100, 101, 102, 103, 113, 115, 116, 118 + "3B4/PP2r3/1rp5/P3k1Pb/2p4p/3P2qN/8/K7 w - - 0 1": ( + 4, + 17, + 18, + 19, + 21, + 22, + 23, + 32, + 35, + 36, + 37, + 38, + 49, + 51, + 53, + 54, + 65, + 67, + 68, + 69, + 70, + 81, + 83, + 84, + 85, + 87, + 97, + 100, + 101, + 102, + 103, + 113, + 115, + 116, + 118, ), } From 6928b07e6ec86db4b4d181c50d0b303413938c43 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Sun, 1 Nov 2020 19:19:26 +0000 Subject: [PATCH 09/11] Restyled by isort --- skaak/board.py | 1 + tests/test_board.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/skaak/board.py b/skaak/board.py index e22b83c..27c4196 100644 --- a/skaak/board.py +++ b/skaak/board.py @@ -1,5 +1,6 @@ from itertools import count from typing import List + import skaak.chess as chess diff --git a/tests/test_board.py b/tests/test_board.py index 9510799..ce8d5aa 100644 --- a/tests/test_board.py +++ b/tests/test_board.py @@ -1,7 +1,7 @@ -from skaak import Chessboard -from skaak import chess import os +from skaak import Chessboard, chess + def test_board_set_starting_fen_by_default(): board = Chessboard() From aafb4698b51114686c91d6dea866c00a75810b89 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Sun, 1 Nov 2020 19:19:27 +0000 Subject: [PATCH 10/11] Restyled by reorder-python-imports --- tests/test_board.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_board.py b/tests/test_board.py index ce8d5aa..d583292 100644 --- a/tests/test_board.py +++ b/tests/test_board.py @@ -1,6 +1,7 @@ import os -from skaak import Chessboard, chess +from skaak import chess +from skaak import Chessboard def test_board_set_starting_fen_by_default(): From d4fa3d64c80fb272109f4786d52c9878d349dd27 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Sun, 1 Nov 2020 19:19:31 +0000 Subject: [PATCH 11/11] Restyled by yapf --- skaak/board.py | 104 +++++++++++++++++--------------------------- tests/test_board.py | 59 ++++++++++++------------- 2 files changed, 69 insertions(+), 94 deletions(-) diff --git a/skaak/board.py b/skaak/board.py index 27c4196..5d66872 100644 --- a/skaak/board.py +++ b/skaak/board.py @@ -97,9 +97,8 @@ def is_square_attacked(self, square: int) -> bool: if square & 0x88 != 0: return None - if (self.board[square] != chess.EMPTY) and self.get_square_color( - square - ) != self.turn: + if (self.board[square] != + chess.EMPTY) and self.get_square_color(square) != self.turn: return None for direction in chess.MOVES["r"]: @@ -107,8 +106,7 @@ def is_square_attacked(self, square: int) -> bool: if (i & 0x88) != 0: break if (self.board[i].isupper() and self.turn == chess.WHITE) or ( - self.board[i].islower() and self.turn == chess.BLACK - ): + self.board[i].islower() and self.turn == chess.BLACK): break if self.board[i].lower() in "rq": @@ -122,12 +120,8 @@ def is_square_attacked(self, square: int) -> bool: for i in count(square + direction, direction): if (i & 0x88) != 0: break - if ( - self.board[i].isupper() - and self.turn == chess.WHITE - or self.board[i].islower() - and self.turn == chess.BLACK - ): + if (self.board[i].isupper() and self.turn == chess.WHITE or + self.board[i].islower() and self.turn == chess.BLACK): break if self.board[i].lower() in "bq": @@ -138,29 +132,22 @@ def is_square_attacked(self, square: int) -> bool: break if self.turn == chess.WHITE: - if ( - self.board[square + chess.NORTH + chess.EAST] == "p" - or self.board[square + chess.NORTH + chess.WEST] == "p" - ): + if (self.board[square + chess.NORTH + chess.EAST] == "p" + or self.board[square + chess.NORTH + chess.WEST] == "p"): return True elif self.turn == chess.BLACK: - if ( - self.board[square + chess.SOUTH + chess.EAST] == "P" - or self.board[square + chess.SOUTH + chess.WEST] == "P" - ): + if (self.board[square + chess.SOUTH + chess.EAST] == "P" + or self.board[square + chess.SOUTH + chess.WEST] == "P"): return True non_sliding_pieces = ["k", "n"] for piece_type in non_sliding_pieces: for i in chess.MOVES[piece_type]: if (square + i) & 0x88 == 0: - if ( - self.board[square + i] == piece_type.lower() - and self.turn == chess.WHITE - ) or ( - self.board[square + i] == piece_type.upper() - and self.turn == chess.BLACK - ): + if (self.board[square + i] == piece_type.lower() + and self.turn == chess.WHITE) or ( + self.board[square + i] == piece_type.upper() + and self.turn == chess.BLACK): return True return False @@ -185,8 +172,8 @@ def generate_fen(self) -> str: empty_square_count += 1 return ( - fen - + f' {"wb"[self.turn]} {self.castling} {self.en_pas} {self.half} {self.full}' + fen + + f' {"wb"[self.turn]} {self.castling} {self.en_pas} {self.half} {self.full}' ) def generate_legal_moves(self) -> List[chess.Move]: @@ -207,7 +194,8 @@ def generate_pseudo_moves(self) -> List[chess.Move]: elif self.turn == chess.BLACK and not piece.islower(): continue - for direction in chess.MOVES[piece.lower() if piece != "P" else piece]: + for direction in chess.MOVES[piece.lower( + ) if piece != "P" else piece]: for j in count(square + direction, direction): # Check if the square is offboard @@ -221,51 +209,41 @@ def generate_pseudo_moves(self) -> List[chess.Move]: break if piece.lower() == "p": - if ( - self.turn == chess.WHITE - and direction == (chess.NORTH * 2) - and ( - square // 16 != 6 - or self.board[square + chess.NORTH] != chess.EMPTY - ) - ) or ( - self.turn == chess.BLACK - and direction == (chess.SOUTH * 2) - and ( - square // 16 != 1 - or self.board[square + chess.SOUTH] != chess.EMPTY - ) - ): + if (self.turn == chess.WHITE + and direction == (chess.NORTH * 2) and + (square // 16 != 6 or + self.board[square + chess.NORTH] != chess.EMPTY) + ) or (self.turn == chess.BLACK + and direction == (chess.SOUTH * 2) and + (square // 16 != 1 + or self.board[square + chess.SOUTH] != + chess.EMPTY)): break if (j % 16 != square % 16) and self.board[j] in "-.": break - if (j % 16 == square % 16) and self.board[j] not in "-.": + if (j % 16 == square % + 16) and self.board[j] not in "-.": break - if (j // 8 - square // 8) ** 1 == 1 and self.board[ - j - ((j // 8 - square // 8) * 8) - ] != "": + if (j // 8 - square // 8)**1 == 1 and self.board[j - ( + (j // 8 - square // 8) * 8)] != "": break - yield ( - chess.Move( - initial_square=square, - target_square=j, - moving_piece=self.board[square], - attacked_piece=self.board[j], - capture=(self.board[j] not in "-."), - score=0, - ) - ) + yield (chess.Move( + initial_square=square, + target_square=j, + moving_piece=self.board[square], + attacked_piece=self.board[j], + capture=(self.board[j] not in "-."), + score=0, + )) if self.board[j] not in "-." or piece.lower() in "knp": break def in_check(self) -> bool: - return ( - self.is_square_attacked(self.white_king_position) - if self.turn == chess.WHITE - else self.is_square_attacked(self.black_king_position) - ) + return (self.is_square_attacked(self.white_king_position) + if self.turn == chess.WHITE else self.is_square_attacked( + self.black_king_position)) def perft(self, depth: int) -> int: nodes = 0 diff --git a/tests/test_board.py b/tests/test_board.py index d583292..70de300 100644 --- a/tests/test_board.py +++ b/tests/test_board.py @@ -13,40 +13,34 @@ def test_board_repr(): board = Chessboard() board.set_fen(chess.STARTING_FEN) - assert str(board) == ( - "\nr n b q k b n r " - "\np p p p p p p p " - "\n. . . . . . . . " - "\n. . . . . . . . " - "\n. . . . . . . . " - "\n. . . . . . . . " - "\nP P P P P P P P " - "\nR N B Q K B N R " - ) + assert str(board) == ("\nr n b q k b n r " + "\np p p p p p p p " + "\n. . . . . . . . " + "\n. . . . . . . . " + "\n. . . . . . . . " + "\n. . . . . . . . " + "\nP P P P P P P P " + "\nR N B Q K B N R ") board.set_fen("3q4/kPpP4/4b1p1/2PP1n2/K3B3/1p6/8/4N1bN w - - 0 1") - assert str(board) == ( - "\n. . . q . . . . " - "\nk P p P . . . . " - "\n. . . . b . p . " - "\n. . P P . n . . " - "\nK . . . B . . . " - "\n. p . . . . . . " - "\n. . . . . . . . " - "\n. . . . N . b N " - ) + assert str(board) == ("\n. . . q . . . . " + "\nk P p P . . . . " + "\n. . . . b . p . " + "\n. . P P . n . . " + "\nK . . . B . . . " + "\n. p . . . . . . " + "\n. . . . . . . . " + "\n. . . . N . b N ") board.set_fen("QBr3N1/1K6/R2ppP2/1n6/P3k3/6p1/1p5P/1b6 w - - 0 1") - assert str(board) == ( - "\nQ B r . . . N . " - "\n. K . . . . . . " - "\nR . . p p P . . " - "\n. n . . . . . . " - "\nP . . . k . . . " - "\n. . . . . . p . " - "\n. p . . . . . P " - "\n. b . . . . . . " - ) + assert str(board) == ("\nQ B r . . . N . " + "\n. K . . . . . . " + "\nR . . p p P . . " + "\n. n . . . . . . " + "\nP . . . k . . . " + "\n. . . . . . p . " + "\n. p . . . . . P " + "\n. b . . . . . . ") def test_board_set_fen(): @@ -61,7 +55,10 @@ def test_board_set_fen(): "fen": "r5R1/3B4/3p3P/P7/1pN5/2pn1K2/N1k4r/R4n2 b - - 0 1", "turn": chess.BLACK, }, - {"fen": "2N5/3R4/p3k3/1R3pKp/3qp1b1/8/P6P/5NQn w - - 0 1", "turn": chess.WHITE}, + { + "fen": "2N5/3R4/p3k3/1R3pKp/3qp1b1/8/P6P/5NQn w - - 0 1", + "turn": chess.WHITE + }, ) for position in fen_strings: