From 28a0a98e6ba5337a006ac98414fa63487c752e1b Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Tue, 7 Nov 2023 23:17:40 +0100 Subject: [PATCH 01/25] ADD: pip install in readme --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a9e259..7db7ed5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## PyAlapin, your customized chess engine +# PyAlapin, your customized chess engine Is it the best, most efficient and state of the art chess engine ? I'm pretty sure not. However, driven by passion and madness, I have developed my own chess game in Python. @@ -6,6 +6,14 @@ For your pretty eyes and your devilish smile, I share it with you. But only with Special thanks and dedication to LeMerluche, crushing its opponents on chess.com with alapin openings ❤️ +## How to install +Simply use: +```bash +pip install pyalapin +``` +You only need [numpy](https://numpy.org/) to play with with terminal interface and with Python. +You will need [kivy](https://kivy.org/) to play with the interface. + ## How to play with interface ```python from pyalapin.interface import ChessApp From 4f039d7e34a5cee959d6a1859a5182f5c4ae7440 Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Fri, 10 Nov 2023 22:25:40 +0100 Subject: [PATCH 02/25] ADD: StockFish Player support --- pyalapin/player/stockfish_player.py | 69 +++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 pyalapin/player/stockfish_player.py diff --git a/pyalapin/player/stockfish_player.py b/pyalapin/player/stockfish_player.py new file mode 100644 index 0000000..e6fa989 --- /dev/null +++ b/pyalapin/player/stockfish_player.py @@ -0,0 +1,69 @@ +from stockfish import Stockfish + +from pyalapin.player.player import Player +from pyalapin.engine.move import Move + + +class StockfishPlayer(Player): + """ + A first AI that plays totally randomly. Selects one move among all possibles and plays it. + """ + + def __init__(self, path_to_dirsave, elo=1000, **kwargs): + super().__init__(**kwargs) + self.elo = elo + self.path_to_dirsave = path_to_dirsave + self.stockfish = Stockfish() + + self.letter_to_coordinate = { + "a": 0, + "b": 1, + "c": 2, + "d": 3, + "e": 4, + "f": 5, + "g": 6, + "h": 7, + } + + def __str__(self): + """Creates a string representation of the player. + + Returns + ------- + str + String representation of the player + """ + + return "Stockfish of elo {self.elo}" + + def quit(self): + self.stockfish.send_quit_command() + + def time_to_play(self, board): + """Potential method that returns a move. + + + Parameters + ---------- + board : engine.Board on which to play. + + Returns + ------- + move.Move move to operate on board. + """ + + fen_repr = board.to_fen() + self.stockfish.set_fen_position(fen_repr) + stockfish_move = self.stockfish.get_best_move() + + print(stockfish_move) + + start_cell_coordinates = (self.letter_to_coordinate[stockfish_move[0]], + int(stockfish_move[1])-1) + end_cell_coordinates = (self.letter_to_coordinate[stockfish_move[2]], + int(stockfish_move[3])-1) + + move = Move(self, board, board.get_cell(*start_cell_coordinates), board.get_cell(*end_cell_coordinates)) + return move + From b26ef336b5018bb786c436c50d8c11f268c15fab Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Mon, 13 Nov 2023 23:09:43 +0100 Subject: [PATCH 03/25] small fixes --- pyalapin/engine/engine.py | 1 + pyalapin/player/player.py | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pyalapin/engine/engine.py b/pyalapin/engine/engine.py index bd8496a..329b38f 100644 --- a/pyalapin/engine/engine.py +++ b/pyalapin/engine/engine.py @@ -1248,6 +1248,7 @@ def update_status(self): """ game_status = self.check_pat_mat(self.player1) + self.game_status.append(game_status) # Pat if game_status == 1: return False, "black&white" diff --git a/pyalapin/player/player.py b/pyalapin/player/player.py index d775a02..4888a27 100644 --- a/pyalapin/player/player.py +++ b/pyalapin/player/player.py @@ -1,7 +1,5 @@ import numpy as np -from pyalapin.engine.move import Move - class Player: """Base class players. If human player only here to ensure that right player is playing right Pieces. From 4cd8f228c8254d25054f61dc10bfd77ce98e3604 Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Mon, 13 Nov 2023 23:10:22 +0100 Subject: [PATCH 04/25] FIX: StockfishPlayer has now right coordinates --- pyalapin/player/stockfish_player.py | 56 +++++++++++++++++------------ 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/pyalapin/player/stockfish_player.py b/pyalapin/player/stockfish_player.py index e6fa989..9da2cba 100644 --- a/pyalapin/player/stockfish_player.py +++ b/pyalapin/player/stockfish_player.py @@ -9,21 +9,30 @@ class StockfishPlayer(Player): A first AI that plays totally randomly. Selects one move among all possibles and plays it. """ - def __init__(self, path_to_dirsave, elo=1000, **kwargs): + def __init__(self, path_to_dirsave, elo=1000, depth=18, threads=1, hash_pow=4, **kwargs): super().__init__(**kwargs) self.elo = elo self.path_to_dirsave = path_to_dirsave - self.stockfish = Stockfish() + self.depth = depth + self.threads = threads + self.hash = 2**hash_pow + + params = { + "Threads": self.threads, + "Hash": self.hash, + "UCI_Elo": self.elo, + } + self.stockfish = Stockfish(path=self.path_to_dirsave, depth=self.depth, parameters=params) self.letter_to_coordinate = { - "a": 0, - "b": 1, - "c": 2, - "d": 3, - "e": 4, - "f": 5, - "g": 6, - "h": 7, + "a": 7, + "b": 6, + "c": 5, + "d": 4, + "e": 3, + "f": 2, + "g": 1, + "h": 0, } def __str__(self): @@ -40,6 +49,20 @@ def __str__(self): def quit(self): self.stockfish.send_quit_command() + def _sf_to_own_coordinates(self, coordinates): + return (int(coordinates[1])-1, self.letter_to_coordinate[coordinates[0]]) + + + def get_move_from_fen(self, fen): + self.stockfish.set_fen_position(fen) + stockfish_move = self.stockfish.get_best_move() + print(f"StockFish best move: {stockfish_move}") + start_cell_coordinates = self._sf_to_own_coordinates(stockfish_move[:2]) + end_cell_coordinates = self._sf_to_own_coordinates(stockfish_move[2:]) + + print("Transformed Coordinates", start_cell_coordinates, end_cell_coordinates) + return start_cell_coordinates, end_cell_coordinates + def time_to_play(self, board): """Potential method that returns a move. @@ -54,16 +77,5 @@ def time_to_play(self, board): """ fen_repr = board.to_fen() - self.stockfish.set_fen_position(fen_repr) - stockfish_move = self.stockfish.get_best_move() - - print(stockfish_move) - - start_cell_coordinates = (self.letter_to_coordinate[stockfish_move[0]], - int(stockfish_move[1])-1) - end_cell_coordinates = (self.letter_to_coordinate[stockfish_move[2]], - int(stockfish_move[3])-1) - - move = Move(self, board, board.get_cell(*start_cell_coordinates), board.get_cell(*end_cell_coordinates)) - return move + return self.get_move_from_fen(fen_repr) From a5a7ff6adfa18366bbaee2277fc4ba041e5f0318 Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Mon, 13 Nov 2023 23:10:53 +0100 Subject: [PATCH 05/25] ADD: local stockfish test example --- tests/stockfish_localtest.py | 21 +++++++++++++++++++++ tests/stockfish_tes.py | 3 --- 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 tests/stockfish_localtest.py delete mode 100644 tests/stockfish_tes.py diff --git a/tests/stockfish_localtest.py b/tests/stockfish_localtest.py new file mode 100644 index 0000000..f8a9818 --- /dev/null +++ b/tests/stockfish_localtest.py @@ -0,0 +1,21 @@ +if __name__=="__main__": + import sys + sys.path.append("../pyalapin") + + import os + print(os.listdir()) + print(os.listdir("pyalapin")) + + from pyalapin.player.stockfish_player import StockfishPlayer + from pyalapin.engine import ChessGame + + + sfp = StockfishPlayer("/Users/vincent.auriau/Python/stockfish/bin/stockfish", white_side=True) + game = ChessGame() + co1, co2 = sfp.get_move_from_fen(game.to_fen()) + game.board.draw() + + game.move_from_coordinates(game.player1, *co1, *co2) + game.board.draw() + + # sfp.quit() \ No newline at end of file diff --git a/tests/stockfish_tes.py b/tests/stockfish_tes.py deleted file mode 100644 index 2d9b54d..0000000 --- a/tests/stockfish_tes.py +++ /dev/null @@ -1,3 +0,0 @@ -from stockfish import Stockfish - -stockfish = Stockfish(path="../") From ba09da56cbc9b8c9708aa99a98cfa39f4241501b Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Mon, 13 Nov 2023 23:13:32 +0100 Subject: [PATCH 06/25] ADD: stockfish in dependancies --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3c08a8a..1b28371 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ ] requires-python = ">=3.8" [project.optional-dependencies] -dev = ["black", "bumpver", "isort", "pip-tools", "pytest", "kivy"] +dev = ["black", "bumpver", "isort", "pip-tools", "pytest", "kivy", "stockfish"] [project.urls] Homepage = "https://github.com/VincentAuriau/custom-chess-engine" From ef19d14685d1f98f8a668a70052c1ad1abf1cb79 Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Mon, 13 Nov 2023 23:23:00 +0100 Subject: [PATCH 07/25] env mangemetn --- envs/{environment.txt => requirements.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename envs/{environment.txt => requirements.txt} (100%) diff --git a/envs/environment.txt b/envs/requirements.txt similarity index 100% rename from envs/environment.txt rename to envs/requirements.txt From c2d9585e9265c9da99b56863cd60e86ab07ed894 Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Wed, 15 Nov 2023 22:07:29 +0100 Subject: [PATCH 08/25] ADD: black material str is now in lowercase --- pyalapin/engine/material.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pyalapin/engine/material.py b/pyalapin/engine/material.py index a76941c..2e61f86 100644 --- a/pyalapin/engine/material.py +++ b/pyalapin/engine/material.py @@ -495,7 +495,8 @@ def get_str(self): str String representation of the piece """ - return " P " + repr = " P " + return repr if self.is_white() else repr.lower() class Bishop(Piece): @@ -661,7 +662,8 @@ def get_str(self): str String representation of the piece """ - return " B " + repr = " B " + return repr if self.is_white() else repr.lower() class Rook(Piece): @@ -830,7 +832,8 @@ def get_str(self): str String representation of the piece """ - return " R " + repr = " R " + return repr if self.is_white() else repr.lower() class Knight(Piece): @@ -927,7 +930,8 @@ def get_str(self): str String representation of the piece """ - return " N " + repr = " N " + return repr if self.is_white() else repr.lower() def get_potential_moves(self, x, y): """Method to list all the possible moves from coordinates. Only uses authorized movements, no other pieces on a @@ -1178,7 +1182,8 @@ def get_str(self): str String representation of the piece """ - return " Q " + repr = " Q " + return repr if self.is_white() else repr.lower() class King(Piece): @@ -1410,7 +1415,8 @@ def get_str(self): str String representation of the piece """ - return " K " + repr = " K " + return repr if self.is_white() else repr.lower() def is_checked(self, board): """Method to verify that the king at its current position is not threatened / checked by opponent material. From 54930aaba6857116ee30347849c25d92b8ee7f91 Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Wed, 15 Nov 2023 23:16:26 +0100 Subject: [PATCH 09/25] ADD: halfmove clock reset checks in Move --- pyalapin/engine/move.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyalapin/engine/move.py b/pyalapin/engine/move.py index 196636a..f9499cb 100644 --- a/pyalapin/engine/move.py +++ b/pyalapin/engine/move.py @@ -168,11 +168,9 @@ def _is_castling(self): Whether self is castling or not. """ if not isinstance(self.moved_piece, material.King): - ###print("not castling becasuse king not moved") return False + elif self.moved_piece.castling_done or self.moved_piece.has_moved: - ###print(self.moved_piece.castling_done) - ###print(self.moved_piece.has_moved) return False else: @@ -215,7 +213,6 @@ def _is_castling(self): self.board.get_cell(self.start.x, 4), ] else: - ###print('king did not move to a castling position') return False # Verifying that the cells between rook and king are empty and that starting @@ -253,10 +250,6 @@ def _is_castling(self): return True else: - ###print('Conditions for castling:') - ###print('Rook has not moved:', rook_to_move.has_moved) - ###print('Cells in between empty:', empty_cells_check) - ###print('Cells in between not threatened:', not_threatened_cells) return False def _is_en_passant(self): @@ -348,9 +341,15 @@ def move_pieces(self): """ # Do everything from coordinates so that only board needs to be copied in self.deepcopy() ? + if isinstance(self.moved_piece, material.Pawn): + reset_halfmove_clock = True + else: + reset_halfmove_clock = False + # Kills Piece on landing Cell if needed. if self.killed_piece is not None: self.board.kill_piece_from_coordinates((self.end.x, self.end.y)) + reset_halfmove_clock = True # Moves Piece on the Board self.board.move_piece_from_coordinates( (self.start.x, self.start.y), (self.end.x, self.end.y) @@ -372,6 +371,7 @@ def move_pieces(self): # Sets the different movement related attributes of Pieces self._set_moved_attribute() + return reset_halfmove_clock def is_possible_move(self, check_chess=True): # REFONDRE, particulièrement, faire en sorte qu'on ne vérifie chaque condition qu'une seule fois From 95ce2e246fc05f37cfa51633ca95524e66a4109c Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Wed, 15 Nov 2023 23:16:57 +0100 Subject: [PATCH 10/25] ENH: FEN now exactly generated' --- pyalapin/engine/engine.py | 83 ++++++++++++++++++++++++++++++++++-- tests/stockfish_localtest.py | 1 + 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/pyalapin/engine/engine.py b/pyalapin/engine/engine.py index 329b38f..3fa624e 100644 --- a/pyalapin/engine/engine.py +++ b/pyalapin/engine/engine.py @@ -615,7 +615,7 @@ def to_fen(self): if no_piece_count > 0: fen += str(no_piece_count) fen += "/" - return fen[:-1], "KQkq" + return fen[:-1] def one_hot_encode(self, white_side=True): """Method to create a representation of the board with OneHot encode of the pieces. @@ -1017,6 +1017,7 @@ def __init__( self.history = [] self.automatic_draw = automatic_draw + self.half_move_clock = 0 def reset_game(self): """Method to reset the game. Recreates the borad, the pieces and restarts the game.""" @@ -1024,6 +1025,7 @@ def reset_game(self): self.played_moves = [] self.history = [] self.to_play_player = self.player1 + self.half_move_clock = 0 def to_fen(self): """ @@ -1034,9 +1036,76 @@ def to_fen(self): str fen representation of the board. """ - pieces, castling = self.board.to_fen() + board_fen = self.board.to_fen() color_playing = "w" if self.to_play_player.is_white_side() else "b" - return pieces + " " + color_playing + " " + castling + " - 0 1" + castling = self.is_castling_possible(True) + self.is_castling_possible(False) + full_moves_nb = len(self.played_moves) // 2 + 1 + return f"{board_fen} {color_playing} {castling} {self.fen_en_passant()} {full_moves_nb} {self.half_move_clock}" + + def is_castling_possible(self, is_white_player): + """Creates FEN representation of possible castling + + Parameters + ---------- + is_white_player: bool + If castling possible checked for white player + + Returns + ------- + str: + FEN representation of possible castling + """ + + fen_possible_castling = "" + if is_white_player: + piece = self.board.get_cell(0, 4).get_piece() + if not isinstance(piece, material.King): + return "" + elif piece.has_moved or piece.castling_done: + return "" + else: + kingside_rook = self.board.get_cell(0, 7).get_piece() + if isinstance(kingside_rook, material.Rook): + if not kingside_rook.has_moved: + fen_possible_castling += "K" + queenside_rook = self.board.get_cell(0, 0).get_piece() + if isinstance(kingside_rook, material.Rook): + if not kingside_rook.has_moved: + fen_possible_castling += "Q" + else: + piece = self.board.get_cell(0, 4).get_piece() + if not isinstance(piece, material.King): + return "" + elif piece.has_moved or piece.castling_done: + return "" + else: + kingside_rook = self.board.get_cell(7, 7).get_piece() + if isinstance(kingside_rook, material.Rook): + if not kingside_rook.has_moved: + fen_possible_castling += "k" + queenside_rook = self.board.get_cell(7, 0).get_piece() + if isinstance(kingside_rook, material.Rook): + if not kingside_rook.has_moved: + fen_possible_castling += "q" + return fen_possible_castling + + def fen_en_passant(self): + try: + last_move = self.played_moves[-1] + except: + return "-" + if not isinstance(last_move.move_piece, material.Pawn): + return "-" + + dx = last_move.start.get_x() - last_move.end.get_x() + x_cell = 8 - int(last_move.start.get_x() - last_move.end.get_x()) / 2 + y_cell = ["a", "b", "c", "d", "e", "f", "g", "h"][last_move.start.get_y()] + + if dx == 2: + return f"{y_cell}{x_cell}" + else: + return "-" + def is_finished(self): """ @@ -1148,6 +1217,8 @@ def check_pat_mat(self, player): can_player_move = self.can_player_move(player) if can_player_move: + if self.half_move_clock >= 50: + return 1 return 0 # If player cannot move any piece else: @@ -1205,7 +1276,11 @@ def move(self, move, player): else: assert moved_piece.is_white() == player.is_white_side() # Actually move pieces - move.move_pieces() + reset_half_move_clock = move.move_pieces() + if reset_half_move_clock: + self.half_move_clock = 0 + else: + self.half_move_clock += 1 # Store move self.played_moves.append(move) diff --git a/tests/stockfish_localtest.py b/tests/stockfish_localtest.py index f8a9818..852c139 100644 --- a/tests/stockfish_localtest.py +++ b/tests/stockfish_localtest.py @@ -12,6 +12,7 @@ sfp = StockfishPlayer("/Users/vincent.auriau/Python/stockfish/bin/stockfish", white_side=True) game = ChessGame() + print("FEN:", game.to_fen()) co1, co2 = sfp.get_move_from_fen(game.to_fen()) game.board.draw() From d70af827fbb34fe534eba5717702ffcdce200e3c Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Wed, 15 Nov 2023 23:19:15 +0100 Subject: [PATCH 11/25] ADD: min missing doc --- pyalapin/engine/engine.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyalapin/engine/engine.py b/pyalapin/engine/engine.py index 3fa624e..4d26ab7 100644 --- a/pyalapin/engine/engine.py +++ b/pyalapin/engine/engine.py @@ -1090,6 +1090,14 @@ def is_castling_possible(self, is_white_player): return fen_possible_castling def fen_en_passant(self): + """ + Creates the part of the FEN representation about En Passant. + + Returns + ------- + str + '-' or coordinate of en-passant cell if last move was double + """ try: last_move = self.played_moves[-1] except: From 356830e5b5e0342a9d184ddaee557f5929bf4cdb Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Fri, 17 Nov 2023 22:32:27 +0100 Subject: [PATCH 12/25] setup files go to v0.0.3 --- pyalapin/__init__.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyalapin/__init__.py b/pyalapin/__init__.py index f81330f..706d509 100644 --- a/pyalapin/__init__.py +++ b/pyalapin/__init__.py @@ -3,5 +3,5 @@ """ # from .interface import interface as interface -__version__ = "0.0.2" +__version__ = "0.0.3" __author__ = "Vincent Auriau" diff --git a/pyproject.toml b/pyproject.toml index 3c08a8a..adca9bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pyalapin" -version = "0.0.2" +version = "0.0.3" authors = [ { name = "Vincent Auriau", email = "vincent.auriau.dev@gmail.com"}, ] From f7a612d0aabb6ab2787f6309b74e6b9db2dbc76a Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Fri, 17 Nov 2023 22:41:37 +0100 Subject: [PATCH 13/25] ADD: automated release github action --- .github/workflows/release.yaml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..eaa63ca --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,29 @@ +name: PyPI Release + +on: + push: + tags: + - "*" + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: "3.6" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build the dist files + run: python setup.py sdist bdist_wheel + - name: Publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: twine upload dist/* \ No newline at end of file From a83d23bfabf9526549095ce3a50fd0185153ee7e Mon Sep 17 00:00:00 2001 From: VincentAuriau Date: Fri, 17 Nov 2023 21:46:03 +0000 Subject: [PATCH 14/25] :art: Format Python code with psf/black --- pyalapin/engine/engine.py | 3 +-- pyalapin/engine/material.py | 3 ++- pyalapin/player/stockfish_player.py | 34 +++++++++++++++------------- tests/stockfish_localtest.py | 35 ++++++++++++++++------------- 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/pyalapin/engine/engine.py b/pyalapin/engine/engine.py index 4d26ab7..c202394 100644 --- a/pyalapin/engine/engine.py +++ b/pyalapin/engine/engine.py @@ -1044,7 +1044,7 @@ def to_fen(self): def is_castling_possible(self, is_white_player): """Creates FEN representation of possible castling - + Parameters ---------- is_white_player: bool @@ -1114,7 +1114,6 @@ def fen_en_passant(self): else: return "-" - def is_finished(self): """ Method to know if the game is still active or finished (i.e. pat or mat) diff --git a/pyalapin/engine/material.py b/pyalapin/engine/material.py index a76941c..3d6971c 100644 --- a/pyalapin/engine/material.py +++ b/pyalapin/engine/material.py @@ -495,7 +495,8 @@ def get_str(self): str String representation of the piece """ - return " P " + repr = " P " + return repr if self.is_white() else repr.lower() class Bishop(Piece): diff --git a/pyalapin/player/stockfish_player.py b/pyalapin/player/stockfish_player.py index 9da2cba..3793d5d 100644 --- a/pyalapin/player/stockfish_player.py +++ b/pyalapin/player/stockfish_player.py @@ -9,7 +9,9 @@ class StockfishPlayer(Player): A first AI that plays totally randomly. Selects one move among all possibles and plays it. """ - def __init__(self, path_to_dirsave, elo=1000, depth=18, threads=1, hash_pow=4, **kwargs): + def __init__( + self, path_to_dirsave, elo=1000, depth=18, threads=1, hash_pow=4, **kwargs + ): super().__init__(**kwargs) self.elo = elo self.path_to_dirsave = path_to_dirsave @@ -18,21 +20,23 @@ def __init__(self, path_to_dirsave, elo=1000, depth=18, threads=1, hash_pow=4, * self.hash = 2**hash_pow params = { - "Threads": self.threads, - "Hash": self.hash, - "UCI_Elo": self.elo, + "Threads": self.threads, + "Hash": self.hash, + "UCI_Elo": self.elo, } - self.stockfish = Stockfish(path=self.path_to_dirsave, depth=self.depth, parameters=params) + self.stockfish = Stockfish( + path=self.path_to_dirsave, depth=self.depth, parameters=params + ) self.letter_to_coordinate = { - "a": 7, - "b": 6, - "c": 5, - "d": 4, - "e": 3, - "f": 2, - "g": 1, - "h": 0, + "a": 7, + "b": 6, + "c": 5, + "d": 4, + "e": 3, + "f": 2, + "g": 1, + "h": 0, } def __str__(self): @@ -50,8 +54,7 @@ def quit(self): self.stockfish.send_quit_command() def _sf_to_own_coordinates(self, coordinates): - return (int(coordinates[1])-1, self.letter_to_coordinate[coordinates[0]]) - + return (int(coordinates[1]) - 1, self.letter_to_coordinate[coordinates[0]]) def get_move_from_fen(self, fen): self.stockfish.set_fen_position(fen) @@ -78,4 +81,3 @@ def time_to_play(self, board): fen_repr = board.to_fen() return self.get_move_from_fen(fen_repr) - diff --git a/tests/stockfish_localtest.py b/tests/stockfish_localtest.py index 852c139..bfb6e63 100644 --- a/tests/stockfish_localtest.py +++ b/tests/stockfish_localtest.py @@ -1,22 +1,25 @@ -if __name__=="__main__": - import sys - sys.path.append("../pyalapin") +if __name__ == "__main__": + import sys - import os - print(os.listdir()) - print(os.listdir("pyalapin")) + sys.path.append("../pyalapin") - from pyalapin.player.stockfish_player import StockfishPlayer - from pyalapin.engine import ChessGame + import os + print(os.listdir()) + print(os.listdir("pyalapin")) - sfp = StockfishPlayer("/Users/vincent.auriau/Python/stockfish/bin/stockfish", white_side=True) - game = ChessGame() - print("FEN:", game.to_fen()) - co1, co2 = sfp.get_move_from_fen(game.to_fen()) - game.board.draw() + from pyalapin.player.stockfish_player import StockfishPlayer + from pyalapin.engine import ChessGame - game.move_from_coordinates(game.player1, *co1, *co2) - game.board.draw() + sfp = StockfishPlayer( + "/Users/vincent.auriau/Python/stockfish/bin/stockfish", white_side=True + ) + game = ChessGame() + print("FEN:", game.to_fen()) + co1, co2 = sfp.get_move_from_fen(game.to_fen()) + game.board.draw() - # sfp.quit() \ No newline at end of file + game.move_from_coordinates(game.player1, *co1, *co2) + game.board.draw() + + # sfp.quit() From 80c06238354b968bbff8c3276b117bcfb8126cab Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Fri, 17 Nov 2023 23:03:55 +0100 Subject: [PATCH 15/25] ADD: letters coordinates on board --- pyalapin/engine/engine.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyalapin/engine/engine.py b/pyalapin/engine/engine.py index bd8496a..47d611c 100644 --- a/pyalapin/engine/engine.py +++ b/pyalapin/engine/engine.py @@ -936,6 +936,8 @@ def draw(self, printing=True): whole_text += current_line whole_text += "\n" whole_text += boarder_line + whole_text += " | a | b | c | d | e | f | g | h |" + whole_text += boarder_line if printing: print(whole_text + "\n") return whole_text From 78f0e9c83643bd8ffac612839d9df773c23bc98d Mon Sep 17 00:00:00 2001 From: VincentAuriau Date: Fri, 17 Nov 2023 22:12:47 +0000 Subject: [PATCH 16/25] :art: Format Python code with psf/black --- pyalapin/engine/material.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyalapin/engine/material.py b/pyalapin/engine/material.py index 2e61f86..58b842a 100644 --- a/pyalapin/engine/material.py +++ b/pyalapin/engine/material.py @@ -495,7 +495,7 @@ def get_str(self): str String representation of the piece """ - repr = " P " + repr = " P " return repr if self.is_white() else repr.lower() From 946fbcb934ac77ee8755452e1ae5c4a635253632 Mon Sep 17 00:00:00 2001 From: Vincent Auriau Date: Fri, 17 Nov 2023 23:19:24 +0100 Subject: [PATCH 17/25] Update black_action.yml --- .github/workflows/black_action.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/black_action.yml b/.github/workflows/black_action.yml index edf42c1..8634823 100644 --- a/.github/workflows/black_action.yml +++ b/.github/workflows/black_action.yml @@ -1,5 +1,9 @@ name: black-action -on: [pull_request] +on: + pull_request: + branches: + - master + jobs: linter_name: name: runner / black From 918a47cc87c6fbd7fcc8e3ad6f0b312b8e5c946c Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Sun, 19 Nov 2023 11:44:33 +0100 Subject: [PATCH 18/25] FIX: board.__str__ --- pyalapin/engine/engine.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyalapin/engine/engine.py b/pyalapin/engine/engine.py index ca03a38..d7ecbd1 100644 --- a/pyalapin/engine/engine.py +++ b/pyalapin/engine/engine.py @@ -598,7 +598,7 @@ def to_fen(self): fen representation and 'KQkq' """ fen = "" - for line in self.board: + for line in reversed(self.board): no_piece_count = 0 for cell in line: piece = cell.get_piece() @@ -609,8 +609,8 @@ def to_fen(self): fen += str(no_piece_count) no_piece_count = 0 letter = piece.get_str().replace(" ", "") - if piece.is_white(): - letter = letter.lower() + # if piece.is_white(): + # letter = letter.lower() fen += letter if no_piece_count > 0: fen += str(no_piece_count) @@ -936,7 +936,10 @@ def draw(self, printing=True): whole_text += current_line whole_text += "\n" whole_text += boarder_line + whole_text += "\n" whole_text += " | a | b | c | d | e | f | g | h |" + + whole_text += "\n" whole_text += boarder_line if printing: print(whole_text + "\n") From 037cce7b0b9ab1f6990ecc3489f5152900e39c5d Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Sun, 19 Nov 2023 11:46:01 +0100 Subject: [PATCH 19/25] ADD: possibility to add StockFish player and specify players in interface --- pyalapin/interface/interface.py | 9 ++++++--- pyalapin/player/stockfish_player.py | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pyalapin/interface/interface.py b/pyalapin/interface/interface.py index 0e37ff1..34046c9 100644 --- a/pyalapin/interface/interface.py +++ b/pyalapin/interface/interface.py @@ -304,13 +304,14 @@ def click_cell(self, event): # Resets background colors of player ai_move = self.game.player2.time_to_play(self.game.board) + print(ai_move) self.game.board.draw() game_is_on = self.game.move(ai_move, self.game.player2) # Verify game is still going on # Actually we consider that an AI cannot trigger an impossible move here # Maybe should be modified ? - + print("game_is_on", game_is_on) if game_is_on[0]: self.update() ( @@ -390,17 +391,19 @@ class ChessApp(App): Main app to use to play game, by calling MyApp().buil() and then player. """ - def __init__(self, play_with_ai=False, **kwargs): + def __init__(self, play_with_ai=False, w_player=None, b_player=None, **kwargs): """ Initialization, with precision whether or not playing with AI. """ super().__init__(**kwargs) self.play_with_ai = play_with_ai + self.w_player = w_player + self.b_player = b_player def build(self): """ Builds the game and the display board along with it. """ - game = ChessGame(automatic_draw=False, ai=self.play_with_ai) + game = ChessGame(automatic_draw=False, ai=self.play_with_ai, player1=self.w_player, player2=self.b_player) print("game created") return BoardInterface(game) diff --git a/pyalapin/player/stockfish_player.py b/pyalapin/player/stockfish_player.py index 9da2cba..a3bd127 100644 --- a/pyalapin/player/stockfish_player.py +++ b/pyalapin/player/stockfish_player.py @@ -77,5 +77,8 @@ def time_to_play(self, board): """ fen_repr = board.to_fen() - return self.get_move_from_fen(fen_repr) + start, end = self.get_move_from_fen(fen_repr) + move = Move(self, board, board.get_cell(*start), board.get_cell(*end)) + print(move, board.get_cell(*start), board.get_cell(*end)) + return move From 73ba6c236f98af5efb21a09294c0140c70b7d0be Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Sun, 19 Nov 2023 11:46:19 +0100 Subject: [PATCH 20/25] ADD: stockfish example --- tests/stockfish_localtest.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/stockfish_localtest.py b/tests/stockfish_localtest.py index 852c139..9ea867a 100644 --- a/tests/stockfish_localtest.py +++ b/tests/stockfish_localtest.py @@ -5,18 +5,26 @@ import os print(os.listdir()) print(os.listdir("pyalapin")) + import stockfish from pyalapin.player.stockfish_player import StockfishPlayer + from pyalapin.player.player import Player from pyalapin.engine import ChessGame sfp = StockfishPlayer("/Users/vincent.auriau/Python/stockfish/bin/stockfish", white_side=True) game = ChessGame() print("FEN:", game.to_fen()) + print("IS VALID?", sfp.stockfish.is_fen_valid(game.to_fen())) co1, co2 = sfp.get_move_from_fen(game.to_fen()) game.board.draw() game.move_from_coordinates(game.player1, *co1, *co2) game.board.draw() - # sfp.quit() \ No newline at end of file + # sfp.quit() + + from pyalapin.interface import ChessApp + sfp = StockfishPlayer("/Users/vincent.auriau/Python/stockfish/bin/stockfish", white_side=False) + app = ChessApp(w_player=Player(True), b_player=sfp, play_with_ai=True) + app.run() From 0959a2f9cfc7ab010831734a8dc7736e8e61662a Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Sun, 19 Nov 2023 12:10:52 +0100 Subject: [PATCH 21/25] ADD: settings file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 11a3155..7049bf9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ venv/ .idea/ .DS_Store __pycache__/ +settings.py From 5c49a34df19dc6189220b1de5dfe5bd5d48b0e0e Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Sun, 19 Nov 2023 18:29:14 +0100 Subject: [PATCH 22/25] update readme --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7db7ed5..d761802 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,20 @@ if __name__ == '__main__': ).run() ``` - ![](docs/scholars_mate_interface.gif) + +You can play against Stockfish by installing the official [Python interface](https://github.com/zhelyabuzhsky/stockfish). +```python +from pyalapin.player.player import Player +from pyalapin.player.stockfish_player import StockfishPlayer +from pyalapin.interface import ChessApp + +sfp = StockfishPlayer(path_to_stockfish_engine, white_side=False) +app = ChessApp(w_player=Player(True), b_player=sfp, play_with_ai=True) +app.run() +``` + ## How to play with Python commands From 74dc6ec70a51a8ee920844982bf5e1e4fa9a9282 Mon Sep 17 00:00:00 2001 From: VincentAuriau Date: Sun, 19 Nov 2023 17:38:46 +0000 Subject: [PATCH 23/25] :art: Format Python code with psf/black --- pyalapin/interface/interface.py | 7 +++++- pyalapin/player/stockfish_player.py | 1 - tests/stockfish_localtest.py | 34 ++++++++++++++--------------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/pyalapin/interface/interface.py b/pyalapin/interface/interface.py index 34046c9..a60d0eb 100644 --- a/pyalapin/interface/interface.py +++ b/pyalapin/interface/interface.py @@ -404,6 +404,11 @@ def build(self): """ Builds the game and the display board along with it. """ - game = ChessGame(automatic_draw=False, ai=self.play_with_ai, player1=self.w_player, player2=self.b_player) + game = ChessGame( + automatic_draw=False, + ai=self.play_with_ai, + player1=self.w_player, + player2=self.b_player, + ) print("game created") return BoardInterface(game) diff --git a/pyalapin/player/stockfish_player.py b/pyalapin/player/stockfish_player.py index a922bfd..739a8eb 100644 --- a/pyalapin/player/stockfish_player.py +++ b/pyalapin/player/stockfish_player.py @@ -85,4 +85,3 @@ def time_to_play(self, board): move = Move(self, board, board.get_cell(*start), board.get_cell(*end)) print(move, board.get_cell(*start), board.get_cell(*end)) return move - diff --git a/tests/stockfish_localtest.py b/tests/stockfish_localtest.py index fd60361..7dccef4 100644 --- a/tests/stockfish_localtest.py +++ b/tests/stockfish_localtest.py @@ -1,24 +1,24 @@ if __name__ == "__main__": - import sys + import sys - sys.path.append("../pyalapin") - import os - import stockfish + sys.path.append("../pyalapin") + import os + import stockfish - from pyalapin.player.stockfish_player import StockfishPlayer - from pyalapin.player.player import Player - from pyalapin.engine import ChessGame + from pyalapin.player.stockfish_player import StockfishPlayer + from pyalapin.player.player import Player + from pyalapin.engine import ChessGame - from settings import settings + from settings import settings - sfp = StockfishPlayer(settings["stockfish_path"], white_side=True) - game = ChessGame() - co1, co2 = sfp.get_move_from_fen(game.to_fen()) - game.board.draw() + sfp = StockfishPlayer(settings["stockfish_path"], white_side=True) + game = ChessGame() + co1, co2 = sfp.get_move_from_fen(game.to_fen()) + game.board.draw() - # sfp.quit() - from pyalapin.interface import ChessApp - sfp = StockfishPlayer(settings["stockfish_path"], white_side=False) - app = ChessApp(w_player=Player(True), b_player=sfp, play_with_ai=True) - app.run() + # sfp.quit() + from pyalapin.interface import ChessApp + sfp = StockfishPlayer(settings["stockfish_path"], white_side=False) + app = ChessApp(w_player=Player(True), b_player=sfp, play_with_ai=True) + app.run() From 40d966f58d552e00725ec29f5d80ccbfb0c01077 Mon Sep 17 00:00:00 2001 From: VincentAURIAU Date: Sun, 19 Nov 2023 18:50:25 +0100 Subject: [PATCH 24/25] ADD: engine tests from new fen method --- tests/unit_test/engine_test.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/unit_test/engine_test.py b/tests/unit_test/engine_test.py index c859346..4626c8d 100644 --- a/tests/unit_test/engine_test.py +++ b/tests/unit_test/engine_test.py @@ -32,8 +32,8 @@ def test_promotion_to_rook(): game.player1, 6, 6, 7, 6, extras={"promote_into": "rook"} ) assert ( - game.board.to_fen()[0] == "rnbqkbnr/pppp1ppp/8/8/P7/7N/1PPPP2P/RNBQKBrR" - ), game.board.to_fen()[0] + game.board.to_fen() == "rnbqkbRr/1pppp2p/7n/p7/8/8/PPPP1PPP/RNBQKBNR" + ), game.board.to_fen() def test_default_promotion(): @@ -51,8 +51,8 @@ def test_default_promotion(): game.move_from_coordinates(game.player2, 5, 0, 4, 0) game.move_from_coordinates(game.player1, 6, 6, 7, 6) assert ( - game.board.to_fen()[0] == "rnbqkbnr/pppp1ppp/8/8/P7/7N/1PPPP2P/RNBQKBqR" - ), game.board.to_fen()[0] + game.board.to_fen() == "rnbqkbQr/1pppp2p/7n/p7/8/8/PPPP1PPP/RNBQKBNR" + ), game.board.to_fen() def test_working_castling(): @@ -76,9 +76,9 @@ def test_working_castling(): # big castling move game.move_from_coordinates(game.player2, 7, 4, 7, 2) assert ( - game.board.to_fen()[0] - == "r1b2rk1/ppppqppp/2n2n2/2b1p3/4P1Q1/2NP4/PPPB1PPP/2KR1BNR" - ), game.board.to_fen()[0] + game.board.to_fen() + == "2kr1bnr/pppb1ppp/2np4/4p1q1/2B1P3/2N2N2/PPPPQPPP/R1B2RK1" + ), game.board.to_fen() def test_failing_castling(): @@ -101,9 +101,9 @@ def test_failing_castling(): _, status = game.move_from_coordinates(game.player1, 0, 4, 0, 6) assert status == 0 assert ( - game.board.to_fen()[0] - == "rnbqk2r/pppp1ppp/5n2/2b1p3/4P1Q1/2N5/PPPP1PPP/R1B1KBNR" - ), game.board.to_fen()[0] + game.board.to_fen() + == "r1b1kbnr/pppp1ppp/2n5/4p1q1/2B1P3/5N2/PPPP1PPP/RNBQK2R" + ), game.board.to_fen() def test_en_passant(): @@ -115,8 +115,8 @@ def test_en_passant(): game.move_from_coordinates(game.player2, 6, 5, 4, 5) game.move_from_coordinates(game.player1, 4, 4, 5, 5) assert ( - game.board.to_fen()[0] == "rnbqkbnr/pppp1ppp/8/8/5P2/P4p2/1PPPP1PP/RNBQKBNR" - ), game.board.to_fen()[0] + game.board.to_fen() == "rnbqkbnr/1pppp1pp/p4P2/5p2/8/8/PPPP1PPP/RNBQKBNR" + ), game.board.to_fen() def test_blocked_by_mat(): From f14d124838fdfdce0ebe8aa406ef107cfdadf553 Mon Sep 17 00:00:00 2001 From: VincentAuriau Date: Sun, 19 Nov 2023 17:53:15 +0000 Subject: [PATCH 25/25] :art: Format Python code with psf/black --- tests/unit_test/engine_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit_test/engine_test.py b/tests/unit_test/engine_test.py index 4626c8d..a7c8376 100644 --- a/tests/unit_test/engine_test.py +++ b/tests/unit_test/engine_test.py @@ -101,8 +101,7 @@ def test_failing_castling(): _, status = game.move_from_coordinates(game.player1, 0, 4, 0, 6) assert status == 0 assert ( - game.board.to_fen() - == "r1b1kbnr/pppp1ppp/2n5/4p1q1/2B1P3/5N2/PPPP1PPP/RNBQK2R" + game.board.to_fen() == "r1b1kbnr/pppp1ppp/2n5/4p1q1/2B1P3/5N2/PPPP1PPP/RNBQK2R" ), game.board.to_fen()