diff --git a/README.md b/README.md index d8ccb87..dfd74ff 100644 --- a/README.md +++ b/README.md @@ -8,23 +8,39 @@ Special thanks and dedication to LeMerluche, crushing its opponents on chess.com ## How to play with interface ```python -from interface.interface import MyApp +from pyalapin.interface.interface import MyApp if __name__ == '__main__': - MyApp().run() + MyApp( + play_with_ai=False # Set to True if you want to play agains AI + ).run() ``` -![](interface_example.PNG) +![](docs/scholars_mate_interface.gif) ## How to play with Python commands +<img align="right" src="docs/scholars_mate_command.gif"> + ```python import sys sys.path.append("python/") import python.engine as engine -game = engine.engine.Game() +game = engine.engine.Game( + automatic_draw=True, # Set to True if you want + # to have each turn drawn in terminal + ai=False, # set to True if you want to play agains AI + save_pgn=False # set to True if you want to + # save moves as PGN +) game.move_from_coordinates(game.player1, 1, 4, 3, 4) -game.move_from_coordinates(game.player2, 6, 0, 5, 0) +game.move_from_coordinates(game.player2, 6, 4, 4, 4) +game.move_from_coordinates(game.player1, 0, 5, 3, 2) +game.move_from_coordinates(game.player2, 6, 3, 5, 4) +game.move_from_coordinates(game.player1, 0, 3, 2, 5) +game.move_from_coordinates(game.player2, 6, 2, 4, 2) +game.move_from_coordinates(game.player2, 2, 5, 6, 5) ``` +There are colors in the command line not showing here in the GIF, though... diff --git a/docs/scholars_mate_command.gif b/docs/scholars_mate_command.gif new file mode 100644 index 0000000..8f918ca Binary files /dev/null and b/docs/scholars_mate_command.gif differ diff --git a/docs/scholars_mate_interface.gif b/docs/scholars_mate_interface.gif new file mode 100644 index 0000000..d9609eb Binary files /dev/null and b/docs/scholars_mate_interface.gif differ diff --git a/pyalapin/engine/engine.py b/pyalapin/engine/engine.py index 2b91814..c048c64 100644 --- a/pyalapin/engine/engine.py +++ b/pyalapin/engine/engine.py @@ -152,10 +152,9 @@ def is_threatened( # King + Rook + Queen # Checking direct surroundings - for i, j in [(1, 0), (0, -1), (-1, 0), (0, -1)]: + for i, j in [(1, 0), (0, -1), (-1, 0), (0, 1)]: x_to_check = self.x + i y_to_check = self.y + j - if 0 <= x_to_check < 8 and 0 <= y_to_check < 8: cell_to_check = board.get_cell(x_to_check, y_to_check) piece_to_check = cell_to_check.get_piece() @@ -964,11 +963,17 @@ class Game: List storing all the played move during the game. automatic draw: bool Whether to draw the board in the terminal at each round. + save_pgn: bool + Whether to keep track of the moves with PGN. + history: list of str + PGN representation of the past move of the game. """ game_status = [] - def __init__(self, player1=None, player2=None, automatic_draw=True, ai=False): + def __init__( + self, player1=None, player2=None, automatic_draw=True, ai=False, save_pgn=False + ): """Initialization of the cell. Parameters @@ -1007,6 +1012,9 @@ def __init__(self, player1=None, player2=None, automatic_draw=True, ai=False): self.board = Board() self.status = "ACTIVE" self.played_moves = [] + self.save_pgn = save_pgn + if self.save_pgn: + self.history = [] self.automatic_draw = automatic_draw @@ -1014,6 +1022,7 @@ def reset_game(self): """Method to reset the game. Recreates the borad, the pieces and restarts the game.""" self.board.reset() self.played_moves = [] + self.history = [] self.to_play_player = self.player1 def to_fen(self): @@ -1200,6 +1209,8 @@ def move(self, move, player): # Store move self.played_moves.append(move) + if self.save_pgn: + self.history.append(move.to_pgn()) # Change player if self.to_play_player == self.player1: @@ -1271,7 +1282,7 @@ def save(self, directory="debug_files"): draw_text = draw_text.replace("\x1b[31m", "") import os import matplotlib.pyplot as plt - + """ plt.rc("figure", figsize=(12, 7)) plt.text( 0.01, 0.05, str(draw_text), {"fontsize": 10}, fontproperties="monospace" @@ -1279,3 +1290,17 @@ def save(self, directory="debug_files"): plt.axis("off") plt.tight_layout() plt.savefig(os.path.join(directory, str(len(self.played_moves)) + ".png")) + """ + with open(os.path.join(directory, str(len(self.played_moves)) + ".txt"), "w") as f: + f.writelines(draw_text) + + def to_pgn(self): + assert self.save_pgn + pgn = "" + for i in range(len(self.history)): + if i % 2 == 0: + pgn += f"{int(i/2)+1}. " + pgn += self.history[i] + pgn += " " + + return pgn[:-1] diff --git a/pyalapin/engine/move.py b/pyalapin/engine/move.py index 718cd65..9815b9e 100644 --- a/pyalapin/engine/move.py +++ b/pyalapin/engine/move.py @@ -106,6 +106,42 @@ def _set_moved_attribute(self): else: self.moved_piece.last_move_is_double = False + def to_pgn(self): + """ + Method to return the PGN representation of the move. + + Returns + ------- + str: + pgn representation of the move + """ + rows = ["a", "b", "c", "d", "e", "f", "g", "h"] + start = f"{rows[self.start.y]}{self.start.x + 1}" + end = f"{rows[self.end.y]}{self.end.x + 1}" + piece = self.moved_piece.get_str().replace(" ", "") + if isinstance(self.moved_piece, material.Pawn): + piece = "" + if self.killed_piece is not None: + start += "x" + elif self.is_castling: + if (self.moved_piece.is_white() and self.end.y == 1) or ( + not self.moved_piece.is_white() and self.end.y == 6 + ): + piece = "O-O-O" + start = "" + end = "" + king = ( + self.board.white_king + if not self.player.white_side + else self.board.black_king + ) + if self.board.get_cell(king.x, king.y).is_threatened( + board=self.board, threaten_color=not self.player.white_side + ): + end += "+" + print(f"{piece}{start}{end}") + return f"{piece}{start}{end}" + def _set_castling_done(self): """ If self is a castling move, then when it is done this function sets the castling_done attribute @@ -140,7 +176,7 @@ def _is_castling(self): return False else: - if self.end.y == 6: # Castling in the right + if self.end.y == 6: # Castling on the right rook_to_move = self.board.get_cell(self.start.x, 7).get_piece() if not isinstance(rook_to_move, material.Rook): return False diff --git a/pyalapin/player/ai_player.py b/pyalapin/player/ai_player.py index a24775e..76290b1 100644 --- a/pyalapin/player/ai_player.py +++ b/pyalapin/player/ai_player.py @@ -340,10 +340,11 @@ def _alpha_beta( beta=beta, is_white=not is_white, ) - random_noise = np.random.randint(0, self.random_coeff) - best_move = [best_move, p_mv][ - np.argmax([best_score, score + random_noise]) - ] + if self.random_coeff > 0: + random_noise = np.random.randint(0, self.random_coeff) + else: + random_noise = 0 + best_move = [best_move, p_mv][np.argmax([best_score, score + random_noise])] best_score = np.max([best_score, score + random_noise]) if best_score >= beta: diff --git a/run_app.py b/run_app.py index 51d23d4..391f364 100644 --- a/run_app.py +++ b/run_app.py @@ -1,8 +1,4 @@ -import sys - -sys.path.append("python/") - from pyalapin.interface.interface import MyApp if __name__ == "__main__": - MyApp(play_with_ai=True).run() + MyApp(play_with_ai=False).run() diff --git a/tests/unit_test/engine_test.py b/tests/unit_test/engine_test.py index a019586..3dee9ba 100644 --- a/tests/unit_test/engine_test.py +++ b/tests/unit_test/engine_test.py @@ -142,6 +142,12 @@ def test_end_game(): print(keep_going, status) -if __name__ == "__main__": - test_en_passant() - test_end_game() +def test_pgn(): + """Tests the pgn history of the game.""" + game = engine.Game(automatic_draw=False, save_pgn=True) + game.move_from_coordinates(game.player1, 1, 4, 3, 4) + game.move_from_coordinates(game.player2, 6, 5, 4, 5) + game.move_from_coordinates(game.player1, 0, 3, 4, 7) + game.move_from_coordinates(game.player2, 6, 6, 5, 6) + print(game.to_pgn()) + assert game.to_pgn() == "1. e2e4 f7f5 2. Qd1h5+ g7g6"