From d212b32da92b49cd7dfb28548ea97415d06ed6a8 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Thu, 22 Dec 2022 17:37:45 +0100 Subject: [PATCH 01/44] Initial commit python implementation basic class structure --- blunderboard-py/blunderboard.py | 84 ++++++++++++++++++++++++++++++++ blunderboard-py/requirements.txt | 2 + pyvenv.cfg | 3 ++ 3 files changed, 89 insertions(+) create mode 100644 blunderboard-py/blunderboard.py create mode 100644 blunderboard-py/requirements.txt create mode 100644 pyvenv.cfg diff --git a/blunderboard-py/blunderboard.py b/blunderboard-py/blunderboard.py new file mode 100644 index 0000000..7d70f09 --- /dev/null +++ b/blunderboard-py/blunderboard.py @@ -0,0 +1,84 @@ +from stockfish import Stockfish +from playsound import playsound +from pathlib import Path +import random + +settings = { + "Debug Log File": "", + "Contempt": 0, + "Min Split Depth": 0, + "Threads": 1, + # More threads will make the engine stronger, but should be kept at less than the number of logical processors on + # your computer. + "Ponder": "false", + "Hash": 16, + # Default size is 16 MB. It's recommended that you increase this value, but keep it as some power of 2. E.g., + # if you're fine using 2 GB of RAM, set Hash to 2048 (11th power of 2). + "MultiPV": 1, + "Skill Level": 20, + "Move Overhead": 10, + "Minimum Thinking Time": 20, + "Slow Mover": 100, + "UCI_Chess960": "false", + "UCI_LimitStrength": "false", + "UCI_Elo": 1350 +} + +sound_path = [Path("sounds/")] + + +class BoardReader: + """ + A class containing methods to read the board state through the GPIO matrix. + """ + + def __init__(self): + pass + + # TODO WIP + + def get_latest_move(self): + return "" + + +class Game: + """ + A class representing the game state + """ + + def __init__(self, settings: dict): + self.engine = Stockfish("/usr/local/bin/stockfish") + self.settings = settings + self.engine.update_engine_parameters(self.settings) + self.matrix = BoardReader() + self.current_evaluation = self.engine.get_evaluation() + self.evaluations = [] + self.engine.set_position() + + def make_move(move) -> None: + """ + Makes a move on the board and updates the game state + :param move: + :return: None + """ + if self.engine.is_move_correct(move): + self.engine.make_moves_from_current_position([move]) + self.current_evaluation = self.engine.get_evaluation() + self.evaluations.append(self.current_evaluation) + if move_was_blunder(): + # If the played move was a blunder play a random sound from the sound path + playsound(random.choice(sound_path)) + + def move_was_blunder() -> bool: + """ + Returns true if the last move was a blunder + :return: bool + """ + previous_evaluation = self.evaluations[-1] + return self.current_evaluation["value"] < (previous_evaluation["value"] + 3) # TODO This is not a + # particularly good way to identify a blunder + + def __str__(self): + return "" + + diff --git a/blunderboard-py/requirements.txt b/blunderboard-py/requirements.txt new file mode 100644 index 0000000..51ee0ef --- /dev/null +++ b/blunderboard-py/requirements.txt @@ -0,0 +1,2 @@ +stockfish +playsound \ No newline at end of file diff --git a/pyvenv.cfg b/pyvenv.cfg new file mode 100644 index 0000000..8810f2d --- /dev/null +++ b/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /usr/bin +include-system-site-packages = false +version = 3.10.8 From 2c06b5c3270a8887426c0208afe44ff2e4d9b9c0 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 23 Dec 2022 19:45:44 +0100 Subject: [PATCH 02/44] wip --- blunderboard-py/.idea/.gitignore | 8 ++ blunderboard-py/.idea/blunderboard-py.iml | 10 +++ .../inspectionProfiles/profiles_settings.xml | 6 ++ blunderboard-py/.idea/misc.xml | 4 + blunderboard-py/.idea/modules.xml | 8 ++ blunderboard-py/.idea/vcs.xml | 6 ++ blunderboard-py/blunderboard.py | 80 ++++++++++++------ blunderboard-py/requirements.txt | 4 +- ... Cries - #402 Kricketune [4dFn2r-iq_4].mp3 | Bin 0 -> 18261 bytes 9 files changed, 100 insertions(+), 26 deletions(-) create mode 100644 blunderboard-py/.idea/.gitignore create mode 100644 blunderboard-py/.idea/blunderboard-py.iml create mode 100644 blunderboard-py/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 blunderboard-py/.idea/misc.xml create mode 100644 blunderboard-py/.idea/modules.xml create mode 100644 blunderboard-py/.idea/vcs.xml create mode 100644 blunderboard-py/sounds/Pokemon Cries - #402 Kricketune [4dFn2r-iq_4].mp3 diff --git a/blunderboard-py/.idea/.gitignore b/blunderboard-py/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/blunderboard-py/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/blunderboard-py/.idea/blunderboard-py.iml b/blunderboard-py/.idea/blunderboard-py.iml new file mode 100644 index 0000000..74d515a --- /dev/null +++ b/blunderboard-py/.idea/blunderboard-py.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/blunderboard-py/.idea/inspectionProfiles/profiles_settings.xml b/blunderboard-py/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/blunderboard-py/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/blunderboard-py/.idea/misc.xml b/blunderboard-py/.idea/misc.xml new file mode 100644 index 0000000..e9e4ed3 --- /dev/null +++ b/blunderboard-py/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/blunderboard-py/.idea/modules.xml b/blunderboard-py/.idea/modules.xml new file mode 100644 index 0000000..8d7d182 --- /dev/null +++ b/blunderboard-py/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/blunderboard-py/.idea/vcs.xml b/blunderboard-py/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/blunderboard-py/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/blunderboard-py/blunderboard.py b/blunderboard-py/blunderboard.py index 7d70f09..dcc492e 100644 --- a/blunderboard-py/blunderboard.py +++ b/blunderboard-py/blunderboard.py @@ -2,6 +2,9 @@ from stockfish import Stockfish from playsound import playsound from pathlib import Path import random +from pgn_parser import parser, pgn + +# import RPi.GPIO as GPIO settings = { "Debug Log File": "", @@ -46,39 +49,66 @@ class Game: A class representing the game state """ - def __init__(self, settings: dict): - self.engine = Stockfish("/usr/local/bin/stockfish") - self.settings = settings + def __init__(self, engine_settings: dict): + self.engine = Stockfish("/usr/bin/stockfish") + self.settings = engine_settings self.engine.update_engine_parameters(self.settings) self.matrix = BoardReader() - self.current_evaluation = self.engine.get_evaluation() + self.current_evaluation = self.engine.get_evaluation() # This is not necessary, now that I think about it. self.evaluations = [] self.engine.set_position() - def make_move(move) -> None: - """ - Makes a move on the board and updates the game state - :param move: - :return: None - """ - if self.engine.is_move_correct(move): - self.engine.make_moves_from_current_position([move]) - self.current_evaluation = self.engine.get_evaluation() - self.evaluations.append(self.current_evaluation) - if move_was_blunder(): - # If the played move was a blunder play a random sound from the sound path - playsound(random.choice(sound_path)) - - def move_was_blunder() -> bool: - """ - Returns true if the last move was a blunder - :return: bool - """ + def move_was_blunder(self) -> bool: + """ + Returns true if the last move was a blunder + :return: bool + """ + if len(self.evaluations) > 1: previous_evaluation = self.evaluations[-1] - return self.current_evaluation["value"] < (previous_evaluation["value"] + 3) # TODO This is not a - # particularly good way to identify a blunder + return abs(self.current_evaluation["value"] - previous_evaluation["value"]) > 300 + # TODO This is not a particularly good way to identify a blunder + else: + return False + + def make_move(self, move) -> None: + """ + Makes a move on the board and updates the game state + :param move: + :return: None + """ + if self.engine.is_move_correct(move): + self.engine.make_moves_from_current_position([move]) + self.current_evaluation = self.engine.get_evaluation() + self.evaluations.append(self.current_evaluation) + if self.move_was_blunder(): + # If the played move was a blunder play a random sound from the sound path + # playsound(random.choice(sound_path)) + print("Blunder!") def __str__(self): return "" +def get_moves_from_pgn(pgn_file: Path): + """ + Returns a list of moves from a PGN file + :param pgn_file: str + :return: list + """ + with open(pgn_file, "r") as f: + pgn_file = f.read() + return parser.parse(pgn_file, actions=pgn.Actions()) + + +test_game = Game(settings) + +pgn_path = Path("../spongeboyahoy_vs_tomlx.pgn") +moves = get_moves_from_pgn(pgn_path) +for i in range(1, 250): + print(str(moves.move(i)).split(". ")[1].split(" ")[0]) + test_game.make_move(str(moves.move(i)).split(". ")[1].split(" ")[0]) + print(test_game.current_evaluation) + test_game.make_move(str(moves.move(i)).split(". ")[1].split(" ")[1]) + print(test_game.current_evaluation) + + diff --git a/blunderboard-py/requirements.txt b/blunderboard-py/requirements.txt index 51ee0ef..56a03b1 100644 --- a/blunderboard-py/requirements.txt +++ b/blunderboard-py/requirements.txt @@ -1,2 +1,4 @@ stockfish -playsound \ No newline at end of file +playsound +pygobject +pgn_parser \ No newline at end of file diff --git a/blunderboard-py/sounds/Pokemon Cries - #402 Kricketune [4dFn2r-iq_4].mp3 b/blunderboard-py/sounds/Pokemon Cries - #402 Kricketune [4dFn2r-iq_4].mp3 new file mode 100644 index 0000000000000000000000000000000000000000..65eab25b2265217b4d3e195956703fe0bbe64526 GIT binary patch literal 18261 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 i5a2)k|9?J5 Date: Fri, 23 Dec 2022 19:45:44 +0100 Subject: [PATCH 03/44] Revert "wip" This reverts commit 17587ad3d4228c120b8437ed0ff939e67289aa20. --- blunderboard-py/.idea/.gitignore | 8 -- blunderboard-py/.idea/blunderboard-py.iml | 10 --- .../inspectionProfiles/profiles_settings.xml | 6 -- blunderboard-py/.idea/misc.xml | 4 - blunderboard-py/.idea/modules.xml | 8 -- blunderboard-py/.idea/vcs.xml | 6 -- blunderboard-py/blunderboard.py | 80 ++++++------------ blunderboard-py/requirements.txt | 4 +- ... Cries - #402 Kricketune [4dFn2r-iq_4].mp3 | Bin 18261 -> 0 bytes 9 files changed, 26 insertions(+), 100 deletions(-) delete mode 100644 blunderboard-py/.idea/.gitignore delete mode 100644 blunderboard-py/.idea/blunderboard-py.iml delete mode 100644 blunderboard-py/.idea/inspectionProfiles/profiles_settings.xml delete mode 100644 blunderboard-py/.idea/misc.xml delete mode 100644 blunderboard-py/.idea/modules.xml delete mode 100644 blunderboard-py/.idea/vcs.xml delete mode 100644 blunderboard-py/sounds/Pokemon Cries - #402 Kricketune [4dFn2r-iq_4].mp3 diff --git a/blunderboard-py/.idea/.gitignore b/blunderboard-py/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/blunderboard-py/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/blunderboard-py/.idea/blunderboard-py.iml b/blunderboard-py/.idea/blunderboard-py.iml deleted file mode 100644 index 74d515a..0000000 --- a/blunderboard-py/.idea/blunderboard-py.iml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/blunderboard-py/.idea/inspectionProfiles/profiles_settings.xml b/blunderboard-py/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/blunderboard-py/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/blunderboard-py/.idea/misc.xml b/blunderboard-py/.idea/misc.xml deleted file mode 100644 index e9e4ed3..0000000 --- a/blunderboard-py/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/blunderboard-py/.idea/modules.xml b/blunderboard-py/.idea/modules.xml deleted file mode 100644 index 8d7d182..0000000 --- a/blunderboard-py/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/blunderboard-py/.idea/vcs.xml b/blunderboard-py/.idea/vcs.xml deleted file mode 100644 index 6c0b863..0000000 --- a/blunderboard-py/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/blunderboard-py/blunderboard.py b/blunderboard-py/blunderboard.py index dcc492e..7d70f09 100644 --- a/blunderboard-py/blunderboard.py +++ b/blunderboard-py/blunderboard.py @@ -2,9 +2,6 @@ from stockfish import Stockfish from playsound import playsound from pathlib import Path import random -from pgn_parser import parser, pgn - -# import RPi.GPIO as GPIO settings = { "Debug Log File": "", @@ -49,66 +46,39 @@ class Game: A class representing the game state """ - def __init__(self, engine_settings: dict): - self.engine = Stockfish("/usr/bin/stockfish") - self.settings = engine_settings + def __init__(self, settings: dict): + self.engine = Stockfish("/usr/local/bin/stockfish") + self.settings = settings self.engine.update_engine_parameters(self.settings) self.matrix = BoardReader() - self.current_evaluation = self.engine.get_evaluation() # This is not necessary, now that I think about it. + self.current_evaluation = self.engine.get_evaluation() self.evaluations = [] self.engine.set_position() - def move_was_blunder(self) -> bool: - """ - Returns true if the last move was a blunder - :return: bool - """ - if len(self.evaluations) > 1: - previous_evaluation = self.evaluations[-1] - return abs(self.current_evaluation["value"] - previous_evaluation["value"]) > 300 - # TODO This is not a particularly good way to identify a blunder - else: - return False + def make_move(move) -> None: + """ + Makes a move on the board and updates the game state + :param move: + :return: None + """ + if self.engine.is_move_correct(move): + self.engine.make_moves_from_current_position([move]) + self.current_evaluation = self.engine.get_evaluation() + self.evaluations.append(self.current_evaluation) + if move_was_blunder(): + # If the played move was a blunder play a random sound from the sound path + playsound(random.choice(sound_path)) - def make_move(self, move) -> None: - """ - Makes a move on the board and updates the game state - :param move: - :return: None - """ - if self.engine.is_move_correct(move): - self.engine.make_moves_from_current_position([move]) - self.current_evaluation = self.engine.get_evaluation() - self.evaluations.append(self.current_evaluation) - if self.move_was_blunder(): - # If the played move was a blunder play a random sound from the sound path - # playsound(random.choice(sound_path)) - print("Blunder!") + def move_was_blunder() -> bool: + """ + Returns true if the last move was a blunder + :return: bool + """ + previous_evaluation = self.evaluations[-1] + return self.current_evaluation["value"] < (previous_evaluation["value"] + 3) # TODO This is not a + # particularly good way to identify a blunder def __str__(self): return "" -def get_moves_from_pgn(pgn_file: Path): - """ - Returns a list of moves from a PGN file - :param pgn_file: str - :return: list - """ - with open(pgn_file, "r") as f: - pgn_file = f.read() - return parser.parse(pgn_file, actions=pgn.Actions()) - - -test_game = Game(settings) - -pgn_path = Path("../spongeboyahoy_vs_tomlx.pgn") -moves = get_moves_from_pgn(pgn_path) -for i in range(1, 250): - print(str(moves.move(i)).split(". ")[1].split(" ")[0]) - test_game.make_move(str(moves.move(i)).split(". ")[1].split(" ")[0]) - print(test_game.current_evaluation) - test_game.make_move(str(moves.move(i)).split(". ")[1].split(" ")[1]) - print(test_game.current_evaluation) - - diff --git a/blunderboard-py/requirements.txt b/blunderboard-py/requirements.txt index 56a03b1..51ee0ef 100644 --- a/blunderboard-py/requirements.txt +++ b/blunderboard-py/requirements.txt @@ -1,4 +1,2 @@ stockfish -playsound -pygobject -pgn_parser \ No newline at end of file +playsound \ No newline at end of file diff --git a/blunderboard-py/sounds/Pokemon Cries - #402 Kricketune [4dFn2r-iq_4].mp3 b/blunderboard-py/sounds/Pokemon Cries - #402 Kricketune [4dFn2r-iq_4].mp3 deleted file mode 100644 index 65eab25b2265217b4d3e195956703fe0bbe64526..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18261 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 i5a2)k|9?J5 Date: Fri, 23 Dec 2022 21:49:58 +0100 Subject: [PATCH 04/44] wip --- blunderboard-py/.idea/workspace.xml | 69 ++++++++++++++++++++++++ blunderboard-py/blunderboard.py | 84 +++++++++++++++++++---------- blunderboard-py/requirements.txt | 17 +++++- 3 files changed, 142 insertions(+), 28 deletions(-) create mode 100644 blunderboard-py/.idea/workspace.xml diff --git a/blunderboard-py/.idea/workspace.xml b/blunderboard-py/.idea/workspace.xml new file mode 100644 index 0000000..bc54ea6 --- /dev/null +++ b/blunderboard-py/.idea/workspace.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + 1671724416846 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/blunderboard-py/blunderboard.py b/blunderboard-py/blunderboard.py index 7d70f09..57792e3 100644 --- a/blunderboard-py/blunderboard.py +++ b/blunderboard-py/blunderboard.py @@ -1,7 +1,10 @@ from stockfish import Stockfish -from playsound import playsound +from pygame import mixer +import time from pathlib import Path import random +import os +import RPi.GPIO as GPIO settings = { "Debug Log File": "", @@ -24,7 +27,7 @@ settings = { "UCI_Elo": 1350 } -sound_path = [Path("sounds/")] +sound_path = Path("sounds") class BoardReader: @@ -41,44 +44,71 @@ class BoardReader: return "" +def play_sound() -> None: + """ + Plays a random sound from the sound path + :return: None + """ + mixer.init() + mixer.music.load("sounds/" + random.choice(os.listdir(sound_path))) + mixer.music.play() + while mixer.music.get_busy(): + time.sleep(0.1) + + class Game: """ A class representing the game state """ - def __init__(self, settings: dict): - self.engine = Stockfish("/usr/local/bin/stockfish") - self.settings = settings + def __init__(self, engine_settings: dict): + self.engine = Stockfish("/usr/bin/stockfish") + self.settings = engine_settings self.engine.update_engine_parameters(self.settings) self.matrix = BoardReader() - self.current_evaluation = self.engine.get_evaluation() + self.current_evaluation = self.engine.get_evaluation() # This is not necessary, now that I think about it. self.evaluations = [] self.engine.set_position() - def make_move(move) -> None: - """ - Makes a move on the board and updates the game state - :param move: - :return: None - """ - if self.engine.is_move_correct(move): - self.engine.make_moves_from_current_position([move]) - self.current_evaluation = self.engine.get_evaluation() - self.evaluations.append(self.current_evaluation) - if move_was_blunder(): - # If the played move was a blunder play a random sound from the sound path - playsound(random.choice(sound_path)) + def move_was_blunder(self) -> bool: + """ + Returns true if the last move was a blunder + :return: bool + """ + if len(self.evaluations) > 1: # Don't check for blunders on the first move + previous_evaluation = self.evaluations[len(self.evaluations) - 2] + return abs(self.current_evaluation["value"] - previous_evaluation["value"]) > 100 + # TODO This is not a particularly good way to identify a blunder + else: + return False - def move_was_blunder() -> bool: - """ - Returns true if the last move was a blunder - :return: bool - """ - previous_evaluation = self.evaluations[-1] - return self.current_evaluation["value"] < (previous_evaluation["value"] + 3) # TODO This is not a - # particularly good way to identify a blunder + def make_move(self, move) -> None: + """ + Makes a move on the board and updates the game state + :param move: str + :return: None + """ + if self.engine.is_move_correct(move): + self.engine.make_moves_from_current_position([move]) + self.current_evaluation = self.engine.get_evaluation() + self.evaluations.append(self.current_evaluation) + print(test_game.current_evaluation) + if self.move_was_blunder(): + # If the played move was a blunder play a random sound from the sound path + play_sound() + print("Blunder!") + else: + print("Invalid move") def __str__(self): return "" +test_game = Game(settings) + +moves_manual = ["e2e4", "e7e6", "e4e5", "d7d5", "e5d6", "c7d6", "b1c3", "b8c6", "f1b5", "a7a6", "b5a4", "b7b5", "a4b3", + "d6d5", "a2a4", "c8b7", "a4b5", "a6b5", "a1a8", "d8a8", "c3b5", "a8a5", "c2c4", "b7a6", "b5c3", "a6c4", + "b3c4", "d5c4", "d1g4", "a5a1"] +for move in moves_manual: + print(move) + test_game.make_move(move) diff --git a/blunderboard-py/requirements.txt b/blunderboard-py/requirements.txt index 51ee0ef..be4684f 100644 --- a/blunderboard-py/requirements.txt +++ b/blunderboard-py/requirements.txt @@ -1,2 +1,17 @@ stockfish -playsound \ No newline at end of file +<<<<<<< Updated upstream +playsound +======= +<<<<<<< Updated upstream +playsound +pygobject +pgn_parser +======= +<<<<<<< Updated upstream +playsound +======= +pygame +pgn_parser +>>>>>>> Stashed changes +>>>>>>> Stashed changes +>>>>>>> Stashed changes From db38497deaf50bef6d28fa08a783549b811e0010 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Tue, 27 Dec 2022 19:16:17 +0100 Subject: [PATCH 05/44] blunder.changes --- blunderboard-py/blunderboard.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/blunderboard-py/blunderboard.py b/blunderboard-py/blunderboard.py index 57792e3..b232565 100644 --- a/blunderboard-py/blunderboard.py +++ b/blunderboard-py/blunderboard.py @@ -4,17 +4,17 @@ import time from pathlib import Path import random import os -import RPi.GPIO as GPIO +#import RPi.GPIO as GPIO settings = { "Debug Log File": "", "Contempt": 0, "Min Split Depth": 0, - "Threads": 1, + "Threads": 10, # More threads will make the engine stronger, but should be kept at less than the number of logical processors on # your computer. "Ponder": "false", - "Hash": 16, + "Hash": 8100, # Default size is 16 MB. It's recommended that you increase this value, but keep it as some power of 2. E.g., # if you're fine using 2 GB of RAM, set Hash to 2048 (11th power of 2). "MultiPV": 1, @@ -24,7 +24,8 @@ settings = { "Slow Mover": 100, "UCI_Chess960": "false", "UCI_LimitStrength": "false", - "UCI_Elo": 1350 + "UCI_Elo": 1350, + "NNUE": "true", } sound_path = Path("sounds") @@ -68,6 +69,8 @@ class Game: self.matrix = BoardReader() self.current_evaluation = self.engine.get_evaluation() # This is not necessary, now that I think about it. self.evaluations = [] + self.current_wdl = self.engine.get_wdl_stats() + self.wdls = [] self.engine.set_position() def move_was_blunder(self) -> bool: @@ -75,12 +78,14 @@ class Game: Returns true if the last move was a blunder :return: bool """ - if len(self.evaluations) > 1: # Don't check for blunders on the first move - previous_evaluation = self.evaluations[len(self.evaluations) - 2] - return abs(self.current_evaluation["value"] - previous_evaluation["value"]) > 100 - # TODO This is not a particularly good way to identify a blunder - else: - return False + if len(self.wdls) > 1: # Don't check for blunders on the first move + previous_wdl = self.wdls[len(self.evaluations) - 2] + if abs(previous_wdl[0] - self.current_wdl[0]) > 300: + return True + elif abs(previous_wdl[2] - self.current_wdl[2]) > 300: + return True + else: + return False def make_move(self, move) -> None: """ @@ -92,10 +97,13 @@ class Game: self.engine.make_moves_from_current_position([move]) self.current_evaluation = self.engine.get_evaluation() self.evaluations.append(self.current_evaluation) - print(test_game.current_evaluation) + self.current_wdl = self.engine.get_wdl_stats() + self.wdls.append(self.current_wdl) + print(self.current_wdl) + print(self.current_evaluation) if self.move_was_blunder(): # If the played move was a blunder play a random sound from the sound path - play_sound() + #play_sound() print("Blunder!") else: print("Invalid move") From 7c8b703cbc02456ea39043b7df6cd8b9539c05b2 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Wed, 28 Dec 2022 15:50:26 +0100 Subject: [PATCH 06/44] cleanup --- .gitignore | 18 ---- blunderboard-py/.idea/workspace.xml | 69 -------------- blunderboard-py/requirements.txt | 17 ---- blunderboard.go | 91 ------------------- go.mod | 3 - go.sum | 3 - pyproject.toml | 6 ++ pyvenv.cfg | 3 - setup.cfg | 58 ++++++++++++ src/blunderboard/__init__.py | 2 + .../blunderboard}/blunderboard.py | 73 +++++++++++---- 11 files changed, 119 insertions(+), 224 deletions(-) delete mode 100644 .gitignore delete mode 100644 blunderboard-py/.idea/workspace.xml delete mode 100644 blunderboard-py/requirements.txt delete mode 100644 blunderboard.go delete mode 100644 go.mod delete mode 100644 go.sum create mode 100644 pyproject.toml delete mode 100644 pyvenv.cfg create mode 100644 setup.cfg create mode 100644 src/blunderboard/__init__.py rename {blunderboard-py => src/blunderboard}/blunderboard.py (75%) diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d3f3408..0000000 --- a/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -.*.swp - -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib -blunderboard - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ diff --git a/blunderboard-py/.idea/workspace.xml b/blunderboard-py/.idea/workspace.xml deleted file mode 100644 index bc54ea6..0000000 --- a/blunderboard-py/.idea/workspace.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - 1671724416846 - - - - - - - - - - - - - - \ No newline at end of file diff --git a/blunderboard-py/requirements.txt b/blunderboard-py/requirements.txt deleted file mode 100644 index be4684f..0000000 --- a/blunderboard-py/requirements.txt +++ /dev/null @@ -1,17 +0,0 @@ -stockfish -<<<<<<< Updated upstream -playsound -======= -<<<<<<< Updated upstream -playsound -pygobject -pgn_parser -======= -<<<<<<< Updated upstream -playsound -======= -pygame -pgn_parser ->>>>>>> Stashed changes ->>>>>>> Stashed changes ->>>>>>> Stashed changes diff --git a/blunderboard.go b/blunderboard.go deleted file mode 100644 index 77ace0a..0000000 --- a/blunderboard.go +++ /dev/null @@ -1,91 +0,0 @@ -package main - -import ( - "fmt" - "github.com/notnil/chess" - "github.com/notnil/chess/uci" - "math" - "os" -) - -// stolen^H^H inspired from lichess https://github.com/ornicar/lila/blob/master/modules/analyse/src/main/Advice.scala#L79 -func WinningChance(cp int) float64 { - winning_chance := 2 / (1 + math.Exp(-0.004 * float64(cp))) - 1 - return winning_chance -} - -func main() { - reader, err := os.Open("spongeboyahoy_vs_tomlx.pgn") - if err != nil { - panic(err) - } - pgn, err := chess.PGN(reader) - if err != nil { - panic(err) - } - spongeboyahoy_vs_tomlx := chess.NewGame(pgn) - fmt.Println(spongeboyahoy_vs_tomlx) - - engine, err := uci.New("stockfish") - if err != nil { - panic(err) - } - defer engine.Close() - - if err := engine.Run(uci.CmdUCI, uci.CmdIsReady, uci.CmdUCINewGame); err != nil { - panic(err) - } - - game := chess.NewGame() -// prevprev_winning_chance := 0.0 - prev_winning_chance := 0.0 - for game.Outcome() == chess.NoOutcome { - num_of_moves := len(game.Moves()) - if err := engine.Run(uci.CmdPosition{Position: game.Position()}, uci.CmdGo{Depth: 12}); err != nil { - panic(err) - } - search_results := engine.SearchResults() - cp := search_results.Info.Score.CP - if (num_of_moves % 2 == 1) { - cp *= -1 - } - winning_chance := WinningChance(cp) - if (num_of_moves > 0) { - delta := prev_winning_chance - winning_chance - if (num_of_moves % 2 == 0) { - delta *= -1; - } - if delta > 0.3 { - fmt.Print("B-b-b-blunder!!") - } else if delta > 0.2 { - fmt.Print("That was a mistake.") - } else if delta > 0.1 { - fmt.Print("Meh...") - } else { - fmt.Print("Ok") - } - fmt.Printf(" (%0.2f, %0.2f, %0.2f)\n", float64(cp) / 100, winning_chance, -delta) - } -// prevprev_winning_chance = prev_winning_chance - prev_winning_chance = winning_chance -// fmt.Println(game.Position().Board().Draw()) -// fmt.Println("Score (centipawns):", cp, "Winning chance:", winning_chance, "Best Move: ", search_results.BestMove) -// fmt.Println("Move: ", search_results.BestMove) - move := spongeboyahoy_vs_tomlx.Moves()[num_of_moves] - fmt.Print(num_of_moves / 2 + 1, move, "\t") - if err := game.Move(move); err != nil { - panic(err) - } -// for { -// var move string -// fmt.Print("Move: ") -// fmt.Scanln(&move) -// if err := game.MoveStr(move); err == nil { -// break -// } -// fmt.Println("Illegal move!") -// } - } - fmt.Println(game.Outcome()) - fmt.Println(game.Position().Board().Draw()) -} diff --git a/go.mod b/go.mod deleted file mode 100644 index fb4d781..0000000 --- a/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module git.0x90.space/0x90/blunderboard - -require github.com/notnil/chess v1.5.0 diff --git a/go.sum b/go.sum deleted file mode 100644 index 3256ec6..0000000 --- a/go.sum +++ /dev/null @@ -1,3 +0,0 @@ -github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/notnil/chess v1.5.0 h1:BcdmSGqZYhoqHsAqNpVTtPwRMOA4Sj8iZY1ZuPW4Umg= -github.com/notnil/chess v1.5.0/go.mod h1:cRuJUIBFq9Xki05TWHJxHYkC+fFpq45IWwk94DdlCrA= diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..374b58c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel" +] +build-backend = "setuptools.build_meta" diff --git a/pyvenv.cfg b/pyvenv.cfg deleted file mode 100644 index 8810f2d..0000000 --- a/pyvenv.cfg +++ /dev/null @@ -1,3 +0,0 @@ -home = /usr/bin -include-system-site-packages = false -version = 3.10.8 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..340c9f4 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,58 @@ +[metadata] +name = blunderboard +version = 0.0.1 +author = 0x90.space +author_email = people@schleuder.0x90.space +description = Blunderboard +url = https://git.0x90.space/0x90/blunderboard +project_urls = + Bug Tracker = https://git.0x90.space/0x90/blunderboard/issues +classifiers = + Programming Language :: Python :: 3 :: Only + License :: OSI Approved :: ISC License (ISCL) + Operating System :: POSIX :: Linux + Topic :: Games/Entertainment :: Board Games + +[options] +package_dir = + = src +packages = find: +python_requires = >=3.9 +install_requires = + pygame + RPi.GPIO + stockfish + +[options.packages.find] +where = src + +[options.entry_points] +console_scripts = + blunderboard = blunderboard:main + +[tox:tox] +envlist = lint +isolated_build = True + +[testenv:lint] +skip_install = True +deps = + black + flake8 + mypy +commands = + black --check --diff src + flake8 src + mypy src + +#[testenv] +#deps = +# pytest +#commands = +# pytest tests + +[flake8] +max_line_length = 88 + +[mypy] +ignore_missing_imports = True diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py new file mode 100644 index 0000000..336e825 --- /dev/null +++ b/src/blunderboard/__init__.py @@ -0,0 +1,2 @@ +def main(): + pass diff --git a/blunderboard-py/blunderboard.py b/src/blunderboard/blunderboard.py similarity index 75% rename from blunderboard-py/blunderboard.py rename to src/blunderboard/blunderboard.py index b232565..07aaca7 100644 --- a/blunderboard-py/blunderboard.py +++ b/src/blunderboard/blunderboard.py @@ -1,22 +1,22 @@ -from stockfish import Stockfish -from pygame import mixer -import time -from pathlib import Path -import random import os -#import RPi.GPIO as GPIO +from pathlib import Path +from pygame import mixer +import random +from stockfish import Stockfish +import time settings = { "Debug Log File": "", "Contempt": 0, "Min Split Depth": 0, - "Threads": 10, - # More threads will make the engine stronger, but should be kept at less than the number of logical processors on - # your computer. + "Threads": 1, + # More threads will make the engine stronger, but should be kept at less than the + # number of logical processors on your computer. "Ponder": "false", - "Hash": 8100, - # Default size is 16 MB. It's recommended that you increase this value, but keep it as some power of 2. E.g., - # if you're fine using 2 GB of RAM, set Hash to 2048 (11th power of 2). + "Hash": 16, + # Default size is 16 MB. It's recommended that you increase this value, but keep it + # as some power of 2. E.g., if you're fine using 2 GB of RAM, set Hash to 2048 + # (11th power of 2). "MultiPV": 1, "Skill Level": 20, "Move Overhead": 10, @@ -67,10 +67,12 @@ class Game: self.settings = engine_settings self.engine.update_engine_parameters(self.settings) self.matrix = BoardReader() - self.current_evaluation = self.engine.get_evaluation() # This is not necessary, now that I think about it. - self.evaluations = [] + self.current_evaluation = ( + self.engine.get_evaluation() + ) # This is not necessary, now that I think about it. + self.evaluations: list[dict] = [] self.current_wdl = self.engine.get_wdl_stats() - self.wdls = [] + self.wdls: list[tuple[int, int, int]] = [] self.engine.set_position() def move_was_blunder(self) -> bool: @@ -86,6 +88,7 @@ class Game: return True else: return False + return False def make_move(self, move) -> None: """ @@ -102,8 +105,9 @@ class Game: print(self.current_wdl) print(self.current_evaluation) if self.move_was_blunder(): - # If the played move was a blunder play a random sound from the sound path - #play_sound() + # If the played move was a blunder play a random sound from the sound + # path + # play_sound() print("Blunder!") else: print("Invalid move") @@ -114,9 +118,38 @@ class Game: test_game = Game(settings) -moves_manual = ["e2e4", "e7e6", "e4e5", "d7d5", "e5d6", "c7d6", "b1c3", "b8c6", "f1b5", "a7a6", "b5a4", "b7b5", "a4b3", - "d6d5", "a2a4", "c8b7", "a4b5", "a6b5", "a1a8", "d8a8", "c3b5", "a8a5", "c2c4", "b7a6", "b5c3", "a6c4", - "b3c4", "d5c4", "d1g4", "a5a1"] +moves_manual = [ + "e2e4", + "e7e6", + "e4e5", + "d7d5", + "e5d6", + "c7d6", + "b1c3", + "b8c6", + "f1b5", + "a7a6", + "b5a4", + "b7b5", + "a4b3", + "d6d5", + "a2a4", + "c8b7", + "a4b5", + "a6b5", + "a1a8", + "d8a8", + "c3b5", + "a8a5", + "c2c4", + "b7a6", + "b5c3", + "a6c4", + "b3c4", + "d5c4", + "d1g4", + "a5a1", +] for move in moves_manual: print(move) test_game.make_move(move) From 681da681bb92b14ab667593832e51402cac35614 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Wed, 28 Dec 2022 16:54:51 +0100 Subject: [PATCH 07/44] boardreader --- src/blunderboard/__init__.py | 6 ++++- src/blunderboard/boardreader.py | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 src/blunderboard/boardreader.py diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index 336e825..194ad79 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -1,2 +1,6 @@ +from blunderboard.boardreader import BoardReader + + def main(): - pass + reader = BoardReader() + reader.scan() diff --git a/src/blunderboard/boardreader.py b/src/blunderboard/boardreader.py new file mode 100644 index 0000000..7a9079a --- /dev/null +++ b/src/blunderboard/boardreader.py @@ -0,0 +1,40 @@ +import RPi.GPIO as gpio + + +class BoardReader: + default_gpio_mode = gpio.BCM + default_row_gpios = [4, 5, 6, 12, 13, 16, 17, 19] + default_column_gpios = [20, 21, 22, 23, 24, 25, 26, 27] + + def __init__( + self, + rows=default_row_gpios, + columns=default_column_gpios, + gpio_mode=default_gpio_mode, + ): + gpio.setmode(gpio_mode) + gpio.setup(columns, gpio.IN, pull_up_down=gpio.PUD_DOWN) + gpio.setup(rows, gpio.OUT, initial=gpio.LOW) + self.columns = columns + self.rows = rows + + def __del__(self): + gpio.cleanup() + + def scan(self) -> None: + print(" a b c d e f g h") + print(" +---------------+") + for i, row in enumerate(self.rows): + print("%d|" % (i + 1), end="") + gpio.output(row, gpio.HIGH) + for j, column in enumerate(self.columns): + if gpio.input(column): + print("x", end="") + else: + print(" ", end="") + if j == 7: + print("|", end="") + else: + print(" ", end="") + gpio.output(row, gpio.LOW) + print("") From 6605b1fe7d666a5ee0db60368a32ff4c963fdf0a Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Thu, 29 Dec 2022 15:46:38 +0100 Subject: [PATCH 08/44] add deploy script --- .gitignore | 3 +++ deploy.sh | 10 ++++++++++ 2 files changed, 13 insertions(+) create mode 100644 .gitignore create mode 100755 deploy.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8774e3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/dist/ +/venv/ +*.egg-info/ diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..2f5072a --- /dev/null +++ b/deploy.sh @@ -0,0 +1,10 @@ +set -e +[ -d venv ] || virtualenv venv +. venv/bin/activate +pip install build tox +tox +python -m build +ssh pi@blunderboard.igloo.icmp.camp rm -f 'blunderboard-*.whl' +scp dist/blunderboard-*.whl pi@blunderboard.igloo.icmp.camp: +ssh pi@blunderboard.igloo.icmp.camp blunderboard/venv/bin/pip uninstall -y blunderboard +ssh pi@blunderboard.igloo.icmp.camp blunderboard/venv/bin/pip install 'blunderboard-*.whl' From 957ead2f2df6f826c5b9869dbaec7a735a5ac8d9 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Thu, 29 Dec 2022 15:47:14 +0100 Subject: [PATCH 09/44] print non-inverted board --- setup.cfg | 1 + src/blunderboard/__init__.py | 1 + src/blunderboard/boardreader.py | 29 +++++++++++++++++++---------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/setup.cfg b/setup.cfg index 340c9f4..674fa3b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,4 +55,5 @@ commands = max_line_length = 88 [mypy] +check_untyped_defs = True ignore_missing_imports = True diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index 194ad79..0d86f8d 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -4,3 +4,4 @@ from blunderboard.boardreader import BoardReader def main(): reader = BoardReader() reader.scan() + reader.print() diff --git a/src/blunderboard/boardreader.py b/src/blunderboard/boardreader.py index 7a9079a..8706438 100644 --- a/src/blunderboard/boardreader.py +++ b/src/blunderboard/boardreader.py @@ -17,24 +17,33 @@ class BoardReader: gpio.setup(rows, gpio.OUT, initial=gpio.LOW) self.columns = columns self.rows = rows + self.board: list[list[str]] = [] + for i in range(8): + self.board.append([" "] * 8) def __del__(self): gpio.cleanup() def scan(self) -> None: - print(" a b c d e f g h") - print(" +---------------+") for i, row in enumerate(self.rows): - print("%d|" % (i + 1), end="") gpio.output(row, gpio.HIGH) for j, column in enumerate(self.columns): if gpio.input(column): - print("x", end="") + self.board[i][j] = "x" else: - print(" ", end="") - if j == 7: - print("|", end="") - else: - print(" ", end="") + self.board[i][j] = " " gpio.output(row, gpio.LOW) - print("") + + def print(self) -> None: + print(" a b c d e f g h") + print(" +---------------+") + for i, row in reversed(list(enumerate(self.board))): + print("%d|" % (i + 1), end="") + for j, field in enumerate(row): + print(field, end="") + if j == 7: + print("|%d" % (i + 1)) + else: + print(" ", end="") + print(" +---------------+") + print(" a b c d e f g h") From bbd54aefeffc5ea64a3dcc71811efd6c516f6400 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Thu, 29 Dec 2022 17:30:31 +0100 Subject: [PATCH 10/44] introduce MoveGenerator --- src/blunderboard/__init__.py | 9 ++-- src/blunderboard/boardreader.py | 76 ++++++++++++++++++++++++------- src/blunderboard/movegenerator.py | 11 +++++ 3 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 src/blunderboard/movegenerator.py diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index 0d86f8d..2c681c3 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -1,7 +1,10 @@ from blunderboard.boardreader import BoardReader +from blunderboard.movegenerator import MoveGenerator +from time import sleep def main(): - reader = BoardReader() - reader.scan() - reader.print() + reader = BoardReader(MoveGenerator()) + while True: + reader.scan() + sleep(0.1) diff --git a/src/blunderboard/boardreader.py b/src/blunderboard/boardreader.py index 8706438..5eb0523 100644 --- a/src/blunderboard/boardreader.py +++ b/src/blunderboard/boardreader.py @@ -1,3 +1,4 @@ +from blunderboard.movegenerator import MoveGenerator import RPi.GPIO as gpio @@ -8,8 +9,9 @@ class BoardReader: def __init__( self, - rows=default_row_gpios, - columns=default_column_gpios, + move_generator: MoveGenerator, + rows: list[int] = default_row_gpios, + columns: list[int] = default_column_gpios, gpio_mode=default_gpio_mode, ): gpio.setmode(gpio_mode) @@ -17,27 +19,66 @@ class BoardReader: gpio.setup(rows, gpio.OUT, initial=gpio.LOW) self.columns = columns self.rows = rows - self.board: list[list[str]] = [] - for i in range(8): - self.board.append([" "] * 8) + self.board = self._empty_board() + self.move_generator = move_generator def __del__(self): gpio.cleanup() - def scan(self) -> None: - for i, row in enumerate(self.rows): - gpio.output(row, gpio.HIGH) - for j, column in enumerate(self.columns): - if gpio.input(column): - self.board[i][j] = "x" - else: - self.board[i][j] = " " - gpio.output(row, gpio.LOW) + def _empty_board(self) -> list[list[str]]: + board = [] + for i in range(8): + board.append([" "] * 8) + return board - def print(self) -> None: + def _initial_board(self) -> list[list[str]]: + board = [] + for i in range(2): + board.append(["x"] * 8) + for i in range(2, 6): + board.append([" "] * 8) + for i in range(6, 8): + board.append(["x"] * 8) + return board + + def _is_initial_board(self, board) -> bool: + initial_board = self._initial_board() + for i, row in enumerate(board): + for j, field in enumerate(row): + if field != initial_board[i][j]: + return False + return True + + def scan(self) -> None: + board = self._initial_board() + for i, row_gpio in enumerate(self.rows): + gpio.output(row_gpio, gpio.HIGH) + for j, column_gpio in enumerate(self.columns): + if gpio.input(column_gpio): + board[i][j] = "x" + else: + board[i][j] = " " + gpio.output(row_gpio, gpio.LOW) + + if not self._is_initial_board(self.board) and self._is_initial_board(board): + self.move_generator.reset() + self.board = board + return + + for i, row in enumerate(board): + for j, field in enumerate(row): + if field == " " and self.board[i][j] == "x": + self.move_generator.take(i, j) + for i, row in enumerate(board): + for j, field in enumerate(row): + if field == "x" and self.board[i][j] == " ": + self.move_generator.put(i, j) + self.board = board + + def _print(self, board) -> None: print(" a b c d e f g h") print(" +---------------+") - for i, row in reversed(list(enumerate(self.board))): + for i, row in reversed(list(enumerate(board))): print("%d|" % (i + 1), end="") for j, field in enumerate(row): print(field, end="") @@ -47,3 +88,6 @@ class BoardReader: print(" ", end="") print(" +---------------+") print(" a b c d e f g h") + + def print(self) -> None: + self._print(self.board) diff --git a/src/blunderboard/movegenerator.py b/src/blunderboard/movegenerator.py new file mode 100644 index 0000000..5e17210 --- /dev/null +++ b/src/blunderboard/movegenerator.py @@ -0,0 +1,11 @@ +class MoveGenerator: + columns = "abcdefgh" + + def reset(self) -> None: + print("reset") + + def put(self, row: int, column: int) -> None: + print("put %c%d" % (self.columns[column], row + 1)) + + def take(self, row: int, column: int) -> None: + print("take %c%d" % (self.columns[column], row + 1)) From 0f064e33386bc07a350605b39a77898b02d7cb6c Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Thu, 29 Dec 2022 17:38:42 +0100 Subject: [PATCH 11/44] Auto stash before merge of "python" and "origin/python" --- src/blunderboard/blunderboard.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/blunderboard/blunderboard.py b/src/blunderboard/blunderboard.py index 07aaca7..490f565 100644 --- a/src/blunderboard/blunderboard.py +++ b/src/blunderboard/blunderboard.py @@ -4,6 +4,7 @@ from pygame import mixer import random from stockfish import Stockfish import time +import movegenerator settings = { "Debug Log File": "", @@ -31,20 +32,6 @@ settings = { sound_path = Path("sounds") -class BoardReader: - """ - A class containing methods to read the board state through the GPIO matrix. - """ - - def __init__(self): - pass - - # TODO WIP - - def get_latest_move(self): - return "" - - def play_sound() -> None: """ Plays a random sound from the sound path From 99eba3bc33c62496580d87de9efde69f3966de28 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Thu, 29 Dec 2022 17:39:58 +0100 Subject: [PATCH 12/44] introduce BlunderEvaluator --- src/blunderboard/__init__.py | 5 ++++- src/blunderboard/blunderevaluator.py | 6 ++++++ src/blunderboard/movegenerator.py | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/blunderboard/blunderevaluator.py diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index 2c681c3..f847e90 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -1,10 +1,13 @@ +from blunderboard.blunderevaluator import BlunderEvaluator from blunderboard.boardreader import BoardReader from blunderboard.movegenerator import MoveGenerator from time import sleep def main(): - reader = BoardReader(MoveGenerator()) + blunder_evaluator = BlunderEvaluator() + move_generator = MoveGenerator(blunder_evaluator) + reader = BoardReader(move_generator) while True: reader.scan() sleep(0.1) diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py new file mode 100644 index 0000000..e7a6c31 --- /dev/null +++ b/src/blunderboard/blunderevaluator.py @@ -0,0 +1,6 @@ +class BlunderEvaluator: + def reset(self): + pass + + def move(self, move): + pass diff --git a/src/blunderboard/movegenerator.py b/src/blunderboard/movegenerator.py index 5e17210..3cb50c9 100644 --- a/src/blunderboard/movegenerator.py +++ b/src/blunderboard/movegenerator.py @@ -1,8 +1,15 @@ +from blunderboard.blunderevaluator import BlunderEvaluator + + class MoveGenerator: columns = "abcdefgh" + def __init__(self, blunder_evaluator: BlunderEvaluator): + self.blunder_evaluator = blunder_evaluator + def reset(self) -> None: print("reset") + self.blunder_evaluator.reset() def put(self, row: int, column: int) -> None: print("put %c%d" % (self.columns[column], row + 1)) From 34f10471d3bec5f056749dd4b403778443325158 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Thu, 29 Dec 2022 17:43:03 +0100 Subject: [PATCH 13/44] cleanup --- src/blunderboard/blunderboard.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/blunderboard/blunderboard.py b/src/blunderboard/blunderboard.py index 490f565..ed923da 100644 --- a/src/blunderboard/blunderboard.py +++ b/src/blunderboard/blunderboard.py @@ -4,7 +4,6 @@ from pygame import mixer import random from stockfish import Stockfish import time -import movegenerator settings = { "Debug Log File": "", @@ -53,7 +52,6 @@ class Game: self.engine = Stockfish("/usr/bin/stockfish") self.settings = engine_settings self.engine.update_engine_parameters(self.settings) - self.matrix = BoardReader() self.current_evaluation = ( self.engine.get_evaluation() ) # This is not necessary, now that I think about it. From 49e7c9a7f9c27fd32c62cfaef8bf8b7240386c00 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Thu, 29 Dec 2022 17:43:20 +0100 Subject: [PATCH 14/44] add types to BlunderEvaluator --- src/blunderboard/blunderevaluator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py index e7a6c31..a4be47e 100644 --- a/src/blunderboard/blunderevaluator.py +++ b/src/blunderboard/blunderevaluator.py @@ -1,6 +1,6 @@ class BlunderEvaluator: - def reset(self): + def reset(self) -> None: pass - def move(self, move): + def move(self, move: str) -> None: pass From 3f7de52e8ca3ff32b00411c1d5e806c42105bcd4 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Thu, 29 Dec 2022 18:28:34 +0100 Subject: [PATCH 15/44] blunderevaluator --- sounds/blunder/tts_what_a_blunder.mp3 | Bin 0 -> 3744 bytes sounds/illegal/alarm.mp3 | Bin 0 -> 40848 bytes src/blunderboard/blunderboard.py | 2 +- src/blunderboard/blunderevaluator.py | 113 ++++++++++++++++++++++++-- 4 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 sounds/blunder/tts_what_a_blunder.mp3 create mode 100644 sounds/illegal/alarm.mp3 diff --git a/sounds/blunder/tts_what_a_blunder.mp3 b/sounds/blunder/tts_what_a_blunder.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a28a06481519123eb472d9c38213ec614a0a3adf GIT binary patch literal 3744 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 None: diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py index a4be47e..5563dfc 100644 --- a/src/blunderboard/blunderevaluator.py +++ b/src/blunderboard/blunderevaluator.py @@ -1,6 +1,109 @@ -class BlunderEvaluator: - def reset(self) -> None: - pass +import os +from pathlib import Path +from pygame import mixer +import random +from stockfish import Stockfish +import time +import movegenerator - def move(self, move: str) -> None: - pass +sound_path = Path("../../sounds") + + +settings = { # TODO Move to a config file + "Debug Log File": "stocklog.txt", + "Contempt": 0, + "Min Split Depth": 0, + "Threads": 1, + # More threads will make the engine stronger, but should be kept at less than the + # number of logical processors on your computer. + "Ponder": "false", + "Hash": 256, + # Default size is 16 MB. It's recommended that you increase this value, but keep it + # as some power of 2. E.g., if you're fine using 2 GB of RAM, set Hash to 2048 + # (11th power of 2). + "MultiPV": 1, + "Skill Level": 20, + "Move Overhead": 10, + "Minimum Thinking Time": 20, + "Slow Mover": 100, + "UCI_Chess960": "false", + "UCI_LimitStrength": "false", + "UCI_Elo": 1350, + # "NNUE": "true", # TODO Find out if NNUE can be used with the python wrapper +} + +class BlunderEvaluator: + + def __init__(self, engine_settings: dict): + self.engine = Stockfish("/usr/bin/stockfish") + self.settings = engine_settings + self.engine.update_engine_parameters(self.settings) + self.current_evaluation = ( + self.engine.get_evaluation() + ) # This is not necessary, now that I think about it. + self.evaluations: list[dict] = [] + self.current_wdl = self.engine.get_wdl_stats() + self.wdls: list[tuple[int, int, int]] = [] + self.engine.set_position() + + def reset(self): + self.engine.set_position() + + def make_move(self, move) -> None: + """ + Makes a move on the board and updates the game state + :param move: str + :return: None + """ + if self.engine.is_move_correct(move): + self.engine.make_moves_from_current_position([move]) + self.current_evaluation = self.engine.get_evaluation() + self.evaluations.append(self.current_evaluation) + self.current_wdl = self.engine.get_wdl_stats() + self.wdls.append(self.current_wdl) + print(self.current_wdl) + print(self.current_evaluation) + if self.move_was_blunder(): + # If the played move was a blunder play a random sound from the blunder path + self.play_sound("blunder") + print("Blunder!") + else: + print("Invalid move") + self.play_sound("illegal") + + def move_was_blunder(self) -> bool: + """ + Returns true if the last move was a blunder + :return: bool + """ + if len(self.wdls) > 1: # Don't check for blunders on the first move + previous_wdl = self.wdls[len(self.evaluations) - 2] + if abs(previous_wdl[0] - self.current_wdl[0]) > 300: + return True + elif abs(previous_wdl[2] - self.current_wdl[2]) > 300: + return True + else: + return False + return False + + @staticmethod + def play_sound(move_type: str) -> None: + """ + Plays a random sound for the type of move (blunder, illegal) + :param move_type: str + :return: None + """ + path = sound_path / move_type + mixer.init() + mixer.music.load("sounds/" + random.choice(os.listdir(path))) + mixer.music.play() + # while mixer.music.get_busy(): + # time.sleep(0.) + # I guess we won't want this, since it will block the main thread. + + def get_board(self) -> str: + """ + Returns the current board state + :return: str + """ + return self.engine.get_board_visual() \ No newline at end of file From a2ac1bcb39ce25b24115a5b82056c3aeb6bf895f Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Thu, 29 Dec 2022 18:56:01 +0100 Subject: [PATCH 16/44] implement MoveGenerator state machine --- src/blunderboard/__init__.py | 2 + src/blunderboard/movegenerator.py | 84 +++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index f847e90..91c05eb 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -8,6 +8,8 @@ def main(): blunder_evaluator = BlunderEvaluator() move_generator = MoveGenerator(blunder_evaluator) reader = BoardReader(move_generator) + reader.scan() + reader.print() while True: reader.scan() sleep(0.1) diff --git a/src/blunderboard/movegenerator.py b/src/blunderboard/movegenerator.py index 3cb50c9..a37d6b4 100644 --- a/src/blunderboard/movegenerator.py +++ b/src/blunderboard/movegenerator.py @@ -1,18 +1,92 @@ from blunderboard.blunderevaluator import BlunderEvaluator -class MoveGenerator: +def coords_to_field(row: int, column: int): columns = "abcdefgh" + return "%c%d" % (columns[column], row + 1) + +class MoveGenerator: def __init__(self, blunder_evaluator: BlunderEvaluator): - self.blunder_evaluator = blunder_evaluator + self.state: State = InitState(blunder_evaluator) def reset(self) -> None: print("reset") - self.blunder_evaluator.reset() + self.state = self.state.reset() def put(self, row: int, column: int) -> None: - print("put %c%d" % (self.columns[column], row + 1)) + print("put %s" % coords_to_field(row, column)) + self.state = self.state.put(row, column) def take(self, row: int, column: int) -> None: - print("take %c%d" % (self.columns[column], row + 1)) + print("take %s" % coords_to_field(row, column)) + self.state = self.state.take(row, column) + + +class State: + def __init__(self, blunder_evaluator: BlunderEvaluator): + self.blunder_evaluator = blunder_evaluator + + def reset(self) -> "State": + self.blunder_evaluator.reset() + return InitState(self.blunder_evaluator) + + def put(self, row: int, column: int) -> "State": + print("ignored invalid put") + return self + + def take(self, row: int, column: int) -> "State": + print("ignored invalid take") + return self + + +class InitState(State): + def reset(self) -> State: + super().reset() + return self + + def take(self, row: int, column: int) -> State: + return TakeState(self.blunder_evaluator, coords_to_field(row, column)) + + +class TakeState(State): + def __init__(self, blunder_evaluator: BlunderEvaluator, from_field: str): + super().__init__(blunder_evaluator) + self.from_field = from_field + + def put(self, row: int, column: int) -> State: + to_field = coords_to_field(row, column) + if self.from_field == to_field: + print("ignored self-move") + return InitState(self.blunder_evaluator) + move = self.from_field + to_field + print("move %s" % move) + self.blunder_evaluator.move(move) + return InitState(self.blunder_evaluator) + + def take(self, row: int, column: int) -> State: + return TakeTakeState( + self.blunder_evaluator, self.from_field, coords_to_field(row, column) + ) + + +class TakeTakeState(State): + def __init__( + self, blunder_evaluator: BlunderEvaluator, from_field: str, to_field: str + ): + super().__init__(blunder_evaluator) + self.from_field = from_field + self.to_field = to_field + + def put(self, row: int, column: int) -> State: + field = coords_to_field(row, column) + if self.to_field == field: + move = self.from_field + field + elif self.from_field == field: + move = self.to_field + field + else: + print("ignored invalid put") + return self + print("move %s" % move) + self.blunder_evaluator.move(move) + return InitState(self.blunder_evaluator) From f11f25f8979ae87ae3340699148f03947e79d22e Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Thu, 29 Dec 2022 19:00:48 +0100 Subject: [PATCH 17/44] cleanup --- src/blunderboard/blunderevaluator.py | 56 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py index 5563dfc..500d20f 100644 --- a/src/blunderboard/blunderevaluator.py +++ b/src/blunderboard/blunderevaluator.py @@ -3,38 +3,35 @@ from pathlib import Path from pygame import mixer import random from stockfish import Stockfish -import time -import movegenerator sound_path = Path("../../sounds") -settings = { # TODO Move to a config file - "Debug Log File": "stocklog.txt", - "Contempt": 0, - "Min Split Depth": 0, - "Threads": 1, - # More threads will make the engine stronger, but should be kept at less than the - # number of logical processors on your computer. - "Ponder": "false", - "Hash": 256, - # Default size is 16 MB. It's recommended that you increase this value, but keep it - # as some power of 2. E.g., if you're fine using 2 GB of RAM, set Hash to 2048 - # (11th power of 2). - "MultiPV": 1, - "Skill Level": 20, - "Move Overhead": 10, - "Minimum Thinking Time": 20, - "Slow Mover": 100, - "UCI_Chess960": "false", - "UCI_LimitStrength": "false", - "UCI_Elo": 1350, - # "NNUE": "true", # TODO Find out if NNUE can be used with the python wrapper -} - class BlunderEvaluator: + default_engine_settings = { + "Debug Log File": "stocklog.txt", + "Contempt": 0, + "Min Split Depth": 0, + "Threads": 1, + # More threads will make the engine stronger, but should be kept at less than + # the number of logical processors on your computer. + "Ponder": "false", + "Hash": 256, + # Default size is 16 MB. It's recommended that you increase this value, but keep + # it as some power of 2. E.g., if you're fine using 2 GB of RAM, set Hash to + # 2048 (11th power of 2). + "MultiPV": 1, + "Skill Level": 20, + "Move Overhead": 10, + "Minimum Thinking Time": 20, + "Slow Mover": 100, + "UCI_Chess960": "false", + "UCI_LimitStrength": "false", + "UCI_Elo": 1350, + # "NNUE": "true", # TODO Find out if NNUE can be used with the python wrapper + } - def __init__(self, engine_settings: dict): + def __init__(self, engine_settings: dict = default_engine_settings): self.engine = Stockfish("/usr/bin/stockfish") self.settings = engine_settings self.engine.update_engine_parameters(self.settings) @@ -49,7 +46,7 @@ class BlunderEvaluator: def reset(self): self.engine.set_position() - def make_move(self, move) -> None: + def move(self, move) -> None: """ Makes a move on the board and updates the game state :param move: str @@ -64,7 +61,8 @@ class BlunderEvaluator: print(self.current_wdl) print(self.current_evaluation) if self.move_was_blunder(): - # If the played move was a blunder play a random sound from the blunder path + # If the played move was a blunder play a random sound from the blunder + # path self.play_sound("blunder") print("Blunder!") else: @@ -106,4 +104,4 @@ class BlunderEvaluator: Returns the current board state :return: str """ - return self.engine.get_board_visual() \ No newline at end of file + return self.engine.get_board_visual() From edcd2902c2e1b38adeb7d4194c2e0f5febb58838 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Thu, 29 Dec 2022 19:10:41 +0100 Subject: [PATCH 18/44] dont hardcode stockfish path --- src/blunderboard/blunderevaluator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py index 500d20f..f07fc25 100644 --- a/src/blunderboard/blunderevaluator.py +++ b/src/blunderboard/blunderevaluator.py @@ -32,7 +32,7 @@ class BlunderEvaluator: } def __init__(self, engine_settings: dict = default_engine_settings): - self.engine = Stockfish("/usr/bin/stockfish") + self.engine = Stockfish() self.settings = engine_settings self.engine.update_engine_parameters(self.settings) self.current_evaluation = ( From ad862d61e2894a52621af21e16ac9777e850090d Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Thu, 29 Dec 2022 21:46:09 +0100 Subject: [PATCH 19/44] fix stockfish usage, reduce hashtable size, ship sound files --- setup.cfg | 4 ++++ src/blunderboard/blunderevaluator.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 674fa3b..6e32231 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,6 +26,10 @@ install_requires = [options.packages.find] where = src +[options.data_files] +sounds/blunder = sounds/blunder/tts_what_a_blunder.mp3 +sounds/illegal = sounds/illegal/alarm.mp3 + [options.entry_points] console_scripts = blunderboard = blunderboard:main diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py index f07fc25..4af8112 100644 --- a/src/blunderboard/blunderevaluator.py +++ b/src/blunderboard/blunderevaluator.py @@ -16,7 +16,7 @@ class BlunderEvaluator: # More threads will make the engine stronger, but should be kept at less than # the number of logical processors on your computer. "Ponder": "false", - "Hash": 256, + "Hash": 16, # Default size is 16 MB. It's recommended that you increase this value, but keep # it as some power of 2. E.g., if you're fine using 2 GB of RAM, set Hash to # 2048 (11th power of 2). @@ -35,13 +35,13 @@ class BlunderEvaluator: self.engine = Stockfish() self.settings = engine_settings self.engine.update_engine_parameters(self.settings) + self.engine.set_position() self.current_evaluation = ( self.engine.get_evaluation() ) # This is not necessary, now that I think about it. self.evaluations: list[dict] = [] self.current_wdl = self.engine.get_wdl_stats() self.wdls: list[tuple[int, int, int]] = [] - self.engine.set_position() def reset(self): self.engine.set_position() From b189600ecc3813d802ab6a090fe8b69a5b967b6d Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Thu, 29 Dec 2022 22:16:27 +0100 Subject: [PATCH 20/44] sound fix? --- src/blunderboard/blunderevaluator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py index 4af8112..29707c8 100644 --- a/src/blunderboard/blunderevaluator.py +++ b/src/blunderboard/blunderevaluator.py @@ -93,7 +93,7 @@ class BlunderEvaluator: """ path = sound_path / move_type mixer.init() - mixer.music.load("sounds/" + random.choice(os.listdir(path))) + mixer.music.load(path / random.choice(os.listdir(path))) mixer.music.play() # while mixer.music.get_busy(): # time.sleep(0.) From 8027f3f5371301753da59f0a235d7b4318f0c60d Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Thu, 29 Dec 2022 23:20:19 +0100 Subject: [PATCH 21/44] correct sound paths --- setup.cfg | 4 ++-- src/blunderboard/blunderevaluator.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 6e32231..e76c9da 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,8 +27,8 @@ install_requires = where = src [options.data_files] -sounds/blunder = sounds/blunder/tts_what_a_blunder.mp3 -sounds/illegal = sounds/illegal/alarm.mp3 +share/blunderboard/sounds/blunder = sounds/blunder/tts_what_a_blunder.mp3 +share/blunderboard/sounds/illegal = sounds/illegal/alarm.mp3 [options.entry_points] console_scripts = diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py index 29707c8..2c41746 100644 --- a/src/blunderboard/blunderevaluator.py +++ b/src/blunderboard/blunderevaluator.py @@ -3,8 +3,9 @@ from pathlib import Path from pygame import mixer import random from stockfish import Stockfish +import sys -sound_path = Path("../../sounds") +sound_path = Path(sys.prefix) / "share" / "blunderboard" / "sounds" class BlunderEvaluator: From 3b005ad506596ca40e25cf253212ad77598d5ba7 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Fri, 30 Dec 2022 00:15:41 +0100 Subject: [PATCH 22/44] hysteris for move detection --- src/blunderboard/boardreader.py | 69 +++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/src/blunderboard/boardreader.py b/src/blunderboard/boardreader.py index 5eb0523..2e7258d 100644 --- a/src/blunderboard/boardreader.py +++ b/src/blunderboard/boardreader.py @@ -3,6 +3,7 @@ import RPi.GPIO as gpio class BoardReader: + hysteresis = 10 default_gpio_mode = gpio.BCM default_row_gpios = [4, 5, 6, 12, 13, 16, 17, 19] default_column_gpios = [20, 21, 22, 23, 24, 25, 26, 27] @@ -10,16 +11,18 @@ class BoardReader: def __init__( self, move_generator: MoveGenerator, - rows: list[int] = default_row_gpios, - columns: list[int] = default_column_gpios, + row_gpios: list[int] = default_row_gpios, + column_gpios: list[int] = default_column_gpios, gpio_mode=default_gpio_mode, ): gpio.setmode(gpio_mode) - gpio.setup(columns, gpio.IN, pull_up_down=gpio.PUD_DOWN) - gpio.setup(rows, gpio.OUT, initial=gpio.LOW) - self.columns = columns - self.rows = rows - self.board = self._empty_board() + gpio.setup(column_gpios, gpio.IN, pull_up_down=gpio.PUD_DOWN) + gpio.setup(row_gpios, gpio.OUT, initial=gpio.LOW) + self.column_gpios = column_gpios + self.row_gpios = row_gpios + self.board_history: list[list[list[str]]] = [] + for _ in range(self.hysteresis): + self.board_history.append(self._empty_board()) self.move_generator = move_generator def __del__(self): @@ -50,30 +53,46 @@ class BoardReader: return True def scan(self) -> None: - board = self._initial_board() - for i, row_gpio in enumerate(self.rows): + next_board = self._empty_board() + for i, row_gpio in enumerate(self.row_gpios): gpio.output(row_gpio, gpio.HIGH) - for j, column_gpio in enumerate(self.columns): + for j, column_gpio in enumerate(self.column_gpios): if gpio.input(column_gpio): - board[i][j] = "x" - else: - board[i][j] = " " + next_board[i][j] = "x" gpio.output(row_gpio, gpio.LOW) + prev_board = self.board_history[-1] + self.board_history = [next_board] + self.board_history[:-1] - if not self._is_initial_board(self.board) and self._is_initial_board(board): - self.move_generator.reset() - self.board = board - return + # if the oldest board is not in inital position but all newer boards are, reset + # game state + if not self._is_initial_board(prev_board): + for board in self.board_history: + if not self._is_initial_board(board): + break + else: + self.move_generator.reset() + return - for i, row in enumerate(board): + for i, row in enumerate(prev_board): for j, field in enumerate(row): - if field == " " and self.board[i][j] == "x": - self.move_generator.take(i, j) - for i, row in enumerate(board): + # if the oldest board has a piece but no newer boards have it, the + # piece was removed + if field == "x": + for board in self.board_history: + if board[i][j] == "x": + break + else: + self.move_generator.take(i, j) + for i, row in enumerate(prev_board): for j, field in enumerate(row): - if field == "x" and self.board[i][j] == " ": - self.move_generator.put(i, j) - self.board = board + # if the oldest board doesn't have a piece but all newer boards have + # it, the piece was placed + if field == " ": + for board in self.board_history: + if board[i][j] == " ": + break + else: + self.move_generator.put(i, j) def _print(self, board) -> None: print(" a b c d e f g h") @@ -90,4 +109,4 @@ class BoardReader: print(" a b c d e f g h") def print(self) -> None: - self._print(self.board) + self._print(self.board_history[0]) From 58d3f51a619c1d248cb1bd0a824372b5f2fb41cf Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Fri, 30 Dec 2022 00:19:15 +0100 Subject: [PATCH 23/44] cleanup --- src/blunderboard/blunderboard.py | 140 ------------------------------- 1 file changed, 140 deletions(-) delete mode 100644 src/blunderboard/blunderboard.py diff --git a/src/blunderboard/blunderboard.py b/src/blunderboard/blunderboard.py deleted file mode 100644 index 2291ba1..0000000 --- a/src/blunderboard/blunderboard.py +++ /dev/null @@ -1,140 +0,0 @@ -import os -from pathlib import Path -from pygame import mixer -import random -from stockfish import Stockfish -import time - -settings = { - "Debug Log File": "", - "Contempt": 0, - "Min Split Depth": 0, - "Threads": 1, - # More threads will make the engine stronger, but should be kept at less than the - # number of logical processors on your computer. - "Ponder": "false", - "Hash": 16, - # Default size is 16 MB. It's recommended that you increase this value, but keep it - # as some power of 2. E.g., if you're fine using 2 GB of RAM, set Hash to 2048 - # (11th power of 2). - "MultiPV": 1, - "Skill Level": 20, - "Move Overhead": 10, - "Minimum Thinking Time": 20, - "Slow Mover": 100, - "UCI_Chess960": "false", - "UCI_LimitStrength": "false", - "UCI_Elo": 1350, - "NNUE": "true", -} - -sound_path = Path("../../sounds") - - -def play_sound() -> None: - """ - Plays a random sound from the sound path - :return: None - """ - mixer.init() - mixer.music.load("sounds/" + random.choice(os.listdir(sound_path))) - mixer.music.play() - while mixer.music.get_busy(): - time.sleep(0.1) - - -class Game: - """ - A class representing the game state - """ - - def __init__(self, engine_settings: dict): - self.engine = Stockfish("/usr/bin/stockfish") - self.settings = engine_settings - self.engine.update_engine_parameters(self.settings) - self.current_evaluation = ( - self.engine.get_evaluation() - ) # This is not necessary, now that I think about it. - self.evaluations: list[dict] = [] - self.current_wdl = self.engine.get_wdl_stats() - self.wdls: list[tuple[int, int, int]] = [] - self.engine.set_position() - - def move_was_blunder(self) -> bool: - """ - Returns true if the last move was a blunder - :return: bool - """ - if len(self.wdls) > 1: # Don't check for blunders on the first move - previous_wdl = self.wdls[len(self.evaluations) - 2] - if abs(previous_wdl[0] - self.current_wdl[0]) > 300: - return True - elif abs(previous_wdl[2] - self.current_wdl[2]) > 300: - return True - else: - return False - return False - - def make_move(self, move) -> None: - """ - Makes a move on the board and updates the game state - :param move: str - :return: None - """ - if self.engine.is_move_correct(move): - self.engine.make_moves_from_current_position([move]) - self.current_evaluation = self.engine.get_evaluation() - self.evaluations.append(self.current_evaluation) - self.current_wdl = self.engine.get_wdl_stats() - self.wdls.append(self.current_wdl) - print(self.current_wdl) - print(self.current_evaluation) - if self.move_was_blunder(): - # If the played move was a blunder play a random sound from the sound - # path - # play_sound() - print("Blunder!") - else: - print("Invalid move") - - def __str__(self): - return "" - - -test_game = Game(settings) - -moves_manual = [ - "e2e4", - "e7e6", - "e4e5", - "d7d5", - "e5d6", - "c7d6", - "b1c3", - "b8c6", - "f1b5", - "a7a6", - "b5a4", - "b7b5", - "a4b3", - "d6d5", - "a2a4", - "c8b7", - "a4b5", - "a6b5", - "a1a8", - "d8a8", - "c3b5", - "a8a5", - "c2c4", - "b7a6", - "b5c3", - "a6c4", - "b3c4", - "d5c4", - "d1g4", - "a5a1", -] -for move in moves_manual: - print(move) - test_game.make_move(move) From bd1e1729605bfff171a9d63f2cad1af4d952cd95 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 30 Dec 2022 00:26:59 +0100 Subject: [PATCH 24/44] blunder rest api --- request_test.py | 23 +++++++++++++++++ rest_api.py | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 request_test.py create mode 100644 rest_api.py diff --git a/request_test.py b/request_test.py new file mode 100644 index 0000000..989bb1b --- /dev/null +++ b/request_test.py @@ -0,0 +1,23 @@ +import requests + +# Replace YOUR_API_TOKEN with the API token you are using for authentication +API_TOKEN = "blunderboard-security-token" + +# Set the API endpoint URL +url = "http://5.75.138.151:5000/api/get_evaluation" + +# Set the chess position and search depth +data = {'position': 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', 'depth': 20} + +# Set the API token in the request header +headers = {'Authorization': API_TOKEN} + +# Send a POST request to the API endpoint +response = requests.post(url, json=data, headers=headers) + +# Print the response content from the server +if response.status_code == 200: + print(response.json()) + +else: + print("Error: " + response.json()['error']) \ No newline at end of file diff --git a/rest_api.py b/rest_api.py new file mode 100644 index 0000000..305c342 --- /dev/null +++ b/rest_api.py @@ -0,0 +1,65 @@ +from flask import Flask, request, jsonify +from stockfish import Stockfish + +app = Flask(__name__) + +# Replace YOUR_API_TOKEN with the API token you want to use for authentication +API_TOKEN = "blunderboard-security-token" + +default_engine_settings = { + "Debug Log File": "stocklog.txt", + "Contempt": 0, + "Min Split Depth": 0, + "Threads": 2, + # More threads will make the engine stronger, but should be kept at less than + # the number of logical processors on your computer. + "Ponder": "false", + "Hash": 516, + # Default size is 16 MB. It's recommended that you increase this value, but keep + # it as some power of 2. E.g., if you're fine using 2 GB of RAM, set Hash to + # 2048 (11th power of 2). + "MultiPV": 1, + "Skill Level": 20, + "Move Overhead": 10, + "Minimum Thinking Time": 5, + "Slow Mover": 100, + "UCI_Chess960": "false", + "UCI_LimitStrength": "false", + "UCI_Elo": 1350, + # "NNUE": "true", # TODO Find out if NNUE can be used with the python wrapper + } + +# Create a Stockfish chess engine instance +engine = Stockfish(path="/usr/bin/stockfish") + +# Set the engine settings +engine.update_engine_parameters(default_engine_settings) + +@app.route('/api/get_evaluation', methods=['POST']) +def get_evaluation(): + # Get the API token from the request header + token = request.headers.get('Authorization') + + # If the API token is not provided or is invalid, return an error + if token != API_TOKEN: + return jsonify({'error': 'Invalid API token'}), 401 + + # Get the current chess position and the desired depth from the request body + data = request.get_json() + position = data.get('position') + depth = data.get('depth') + + # Set the position and search depth in the chess engine + engine.set_fen_position(position) + engine.set_depth(depth) + + # Get the evaluation in centipawns + evaluation = engine.get_evaluation() + + # Return the evaluation to the client + return evaluation + +if __name__ == '__main__': + app.run(host="") + + From 612a639a781a6daeb1381f88497fe01e5ee517f9 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 30 Dec 2022 13:27:37 +0100 Subject: [PATCH 25/44] api work --- request_test.py | 26 ++++++++++++++-- rest_api.py | 18 +++++------ setup.cfg | 3 ++ src/blunderboard/blunderevaluator.py | 45 ++++++++++++++++++++++++---- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/request_test.py b/request_test.py index 989bb1b..5ee201d 100644 --- a/request_test.py +++ b/request_test.py @@ -5,12 +5,16 @@ API_TOKEN = "blunderboard-security-token" # Set the API endpoint URL url = "http://5.75.138.151:5000/api/get_evaluation" +wdl_api = "http://5.75.138.151:5000/api/get_wdl" # Set the chess position and search depth -data = {'position': 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1', 'depth': 20} +data = { + "position": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + "depth": 20, +} # Set the API token in the request header -headers = {'Authorization': API_TOKEN} +headers = {"Authorization": API_TOKEN} # Send a POST request to the API endpoint response = requests.post(url, json=data, headers=headers) @@ -20,4 +24,20 @@ if response.status_code == 200: print(response.json()) else: - print("Error: " + response.json()['error']) \ No newline at end of file + print("Error: " + response.json()["error"]) + +def api_wdl() -> str: + """ + Returns the current wdl from the REST API + :return: str + """ + # Send a POST request to the API endpoint + response2 = requests.post(wdl_api, json=data, headers=headers) + if response2.status_code == 200: + return response2.json() + else: + print("API Error: " + response2.json()["error"]) + return "API Error" + + +print(api_wdl()) diff --git a/rest_api.py b/rest_api.py index 305c342..5630517 100644 --- a/rest_api.py +++ b/rest_api.py @@ -27,7 +27,7 @@ default_engine_settings = { "UCI_LimitStrength": "false", "UCI_Elo": 1350, # "NNUE": "true", # TODO Find out if NNUE can be used with the python wrapper - } +} # Create a Stockfish chess engine instance engine = Stockfish(path="/usr/bin/stockfish") @@ -35,19 +35,20 @@ engine = Stockfish(path="/usr/bin/stockfish") # Set the engine settings engine.update_engine_parameters(default_engine_settings) -@app.route('/api/get_evaluation', methods=['POST']) + +@app.route("/api/get_evaluation", methods=["POST"]) def get_evaluation(): # Get the API token from the request header - token = request.headers.get('Authorization') + token = request.headers.get("Authorization") # If the API token is not provided or is invalid, return an error if token != API_TOKEN: - return jsonify({'error': 'Invalid API token'}), 401 + return jsonify({"error": "Invalid API token"}), 401 # Get the current chess position and the desired depth from the request body data = request.get_json() - position = data.get('position') - depth = data.get('depth') + position = data.get("position") + depth = data.get("depth") # Set the position and search depth in the chess engine engine.set_fen_position(position) @@ -59,7 +60,6 @@ def get_evaluation(): # Return the evaluation to the client return evaluation -if __name__ == '__main__': + +if __name__ == "__main__": app.run(host="") - - diff --git a/setup.cfg b/setup.cfg index e76c9da..e337f07 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,7 @@ install_requires = pygame RPi.GPIO stockfish + requests [options.packages.find] where = src @@ -61,3 +62,5 @@ max_line_length = 88 [mypy] check_untyped_defs = True ignore_missing_imports = True +install_types = True +non_interactive = True \ No newline at end of file diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py index 2c41746..82d40aa 100644 --- a/src/blunderboard/blunderevaluator.py +++ b/src/blunderboard/blunderevaluator.py @@ -4,8 +4,12 @@ from pygame import mixer import random from stockfish import Stockfish import sys +import requests +API_TOKEN = "blunderboard-security-token" sound_path = Path(sys.prefix) / "share" / "blunderboard" / "sounds" +api = "http://5.75.138.151:5000/api/get_evaluation" +wdl_api = "http://5.75.138.151:5000/api/get_wdl" class BlunderEvaluator: @@ -37,12 +41,11 @@ class BlunderEvaluator: self.settings = engine_settings self.engine.update_engine_parameters(self.settings) self.engine.set_position() - self.current_evaluation = ( - self.engine.get_evaluation() - ) # This is not necessary, now that I think about it. + self.current_evaluation = self.api_evaluation() self.evaluations: list[dict] = [] self.current_wdl = self.engine.get_wdl_stats() - self.wdls: list[tuple[int, int, int]] = [] + self.wdls: list = [] + self.current_fen = self.engine.get_fen_position() def reset(self): self.engine.set_position() @@ -57,7 +60,7 @@ class BlunderEvaluator: self.engine.make_moves_from_current_position([move]) self.current_evaluation = self.engine.get_evaluation() self.evaluations.append(self.current_evaluation) - self.current_wdl = self.engine.get_wdl_stats() + self.current_wdl = self.api_wdl() self.wdls.append(self.current_wdl) print(self.current_wdl) print(self.current_evaluation) @@ -70,6 +73,38 @@ class BlunderEvaluator: print("Invalid move") self.play_sound("illegal") + def api_evaluation(self) -> dict: + """ + Returns the current evaluation from the REST API + :return: str + """ + data = {"position": self.engine.get_fen_position(), "depth": 20} + # Set the API token in the request header + headers = {"Authorization": API_TOKEN} + # Send a POST request to the API endpoint + response = requests.post(api, json=data, headers=headers) + if response.status_code == 200: + return response.json()["value"] + else: + print("API Error: " + response.json()["error"]) + return {"NOPE": "PLEASE CRASH"} + + def api_wdl(self) -> str: + """ + Returns the current wdl from the REST API + :return: str + """ + data = {"position": self.engine.get_fen_position(), "depth": 20} + # Set the API token in the request header + headers = {"Authorization": API_TOKEN} + # Send a POST request to the API endpoint + response = requests.post(wdl_api, json=data, headers=headers) + if response.status_code == 200: + return response.json() + else: + print("API Error: " + response.json()["error"]) + return "API Error" + def move_was_blunder(self) -> bool: """ Returns true if the last move was a blunder From a81408ae01e4bb1097022fa61fcf278482418492 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Fri, 30 Dec 2022 13:30:31 +0100 Subject: [PATCH 26/44] start with initial board --- src/blunderboard/boardreader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blunderboard/boardreader.py b/src/blunderboard/boardreader.py index 2e7258d..0f122f1 100644 --- a/src/blunderboard/boardreader.py +++ b/src/blunderboard/boardreader.py @@ -22,7 +22,7 @@ class BoardReader: self.row_gpios = row_gpios self.board_history: list[list[list[str]]] = [] for _ in range(self.hysteresis): - self.board_history.append(self._empty_board()) + self.board_history.append(self._initial_board()) self.move_generator = move_generator def __del__(self): From 5047c13452875a4b50aed77527deac9f4f8036e0 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 30 Dec 2022 14:11:17 +0100 Subject: [PATCH 27/44] fix local eval --- src/blunderboard/blunderevaluator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py index 82d40aa..2b72370 100644 --- a/src/blunderboard/blunderevaluator.py +++ b/src/blunderboard/blunderevaluator.py @@ -58,7 +58,7 @@ class BlunderEvaluator: """ if self.engine.is_move_correct(move): self.engine.make_moves_from_current_position([move]) - self.current_evaluation = self.engine.get_evaluation() + self.current_evaluation = self.api_evaluation() self.evaluations.append(self.current_evaluation) self.current_wdl = self.api_wdl() self.wdls.append(self.current_wdl) From 362837997e846f70a91bb23a37f81210a9c6c599 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Fri, 30 Dec 2022 14:17:19 +0100 Subject: [PATCH 28/44] improved hysteresis --- setup.cfg | 7 +++-- src/blunderboard/__init__.py | 2 +- src/blunderboard/boardreader.py | 47 ++++++++++++++++++++------------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/setup.cfg b/setup.cfg index e337f07..36a1727 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,8 +28,10 @@ install_requires = where = src [options.data_files] -share/blunderboard/sounds/blunder = sounds/blunder/tts_what_a_blunder.mp3 -share/blunderboard/sounds/illegal = sounds/illegal/alarm.mp3 +share/blunderboard/sounds/blunder = + sounds/blunder/tts_what_a_blunder.mp3 +share/blunderboard/sounds/illegal = + sounds/illegal/alarm.mp3 [options.entry_points] console_scripts = @@ -58,6 +60,7 @@ commands = [flake8] max_line_length = 88 +extend_ignore = E203 [mypy] check_untyped_defs = True diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index 91c05eb..5f12fb6 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -12,4 +12,4 @@ def main(): reader.print() while True: reader.scan() - sleep(0.1) + sleep(1) diff --git a/src/blunderboard/boardreader.py b/src/blunderboard/boardreader.py index 0f122f1..94060d0 100644 --- a/src/blunderboard/boardreader.py +++ b/src/blunderboard/boardreader.py @@ -3,7 +3,7 @@ import RPi.GPIO as gpio class BoardReader: - hysteresis = 10 + hysteresis = 16 default_gpio_mode = gpio.BCM default_row_gpios = [4, 5, 6, 12, 13, 16, 17, 19] default_column_gpios = [20, 21, 22, 23, 24, 25, 26, 27] @@ -60,39 +60,50 @@ class BoardReader: if gpio.input(column_gpio): next_board[i][j] = "x" gpio.output(row_gpio, gpio.LOW) - prev_board = self.board_history[-1] self.board_history = [next_board] + self.board_history[:-1] - # if the oldest board is not in inital position but all newer boards are, reset - # game state - if not self._is_initial_board(prev_board): - for board in self.board_history: + # if the oldest half of the board history is not in inital position but all + # newer boards are, reset game state + for board in self.board_history[self.hysteresis // 2 :]: + if self._is_initial_board(board): + break + else: + for board in self.board_history[: self.hysteresis // 2]: if not self._is_initial_board(board): break else: self.move_generator.reset() + self.print() return - for i, row in enumerate(prev_board): - for j, field in enumerate(row): - # if the oldest board has a piece but no newer boards have it, the - # piece was removed - if field == "x": - for board in self.board_history: + for i in range(8): + for j in range(8): + # if the oldest half of the board history has a piece but no newer + # boards have it, the piece was removed + for board in self.board_history[self.hysteresis // 2 :]: + if board[i][j] == " ": + break + else: + for board in self.board_history[: self.hysteresis // 2]: if board[i][j] == "x": break else: self.move_generator.take(i, j) - for i, row in enumerate(prev_board): - for j, field in enumerate(row): - # if the oldest board doesn't have a piece but all newer boards have - # it, the piece was placed - if field == " ": - for board in self.board_history: + self.print() + for i in range(8): + for j in range(8): + # if the oldest half of the board history doesn't have a piece but all + # newer boards have it, the piece was placed + for board in self.board_history[self.hysteresis // 2 :]: + if board[i][j] == "x": + break + else: + for board in self.board_history[: self.hysteresis // 2]: if board[i][j] == " ": break else: self.move_generator.put(i, j) + self.print() def _print(self, board) -> None: print(" a b c d e f g h") From afafad5ccf7077e44054587c9e1eb59779daadf1 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Fri, 30 Dec 2022 14:20:32 +0100 Subject: [PATCH 29/44] fix sleep time --- src/blunderboard/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index 5f12fb6..91c05eb 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -12,4 +12,4 @@ def main(): reader.print() while True: reader.scan() - sleep(1) + sleep(0.1) From 65816bfd74819a89c0c425212ea41c6412e38ffa Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 30 Dec 2022 14:23:46 +0100 Subject: [PATCH 30/44] wdl from api --- src/blunderboard/blunderevaluator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py index 2b72370..0b8af1e 100644 --- a/src/blunderboard/blunderevaluator.py +++ b/src/blunderboard/blunderevaluator.py @@ -43,7 +43,7 @@ class BlunderEvaluator: self.engine.set_position() self.current_evaluation = self.api_evaluation() self.evaluations: list[dict] = [] - self.current_wdl = self.engine.get_wdl_stats() + self.current_wdl = self.api_wdl() self.wdls: list = [] self.current_fen = self.engine.get_fen_position() From 95bec447199b5f533f203522c77bbbcc4d484fe8 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Fri, 30 Dec 2022 15:33:12 +0100 Subject: [PATCH 31/44] fine-tune hysteresis parameter --- src/blunderboard/boardreader.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blunderboard/boardreader.py b/src/blunderboard/boardreader.py index 94060d0..b29487d 100644 --- a/src/blunderboard/boardreader.py +++ b/src/blunderboard/boardreader.py @@ -3,7 +3,7 @@ import RPi.GPIO as gpio class BoardReader: - hysteresis = 16 + hysteresis = 8 default_gpio_mode = gpio.BCM default_row_gpios = [4, 5, 6, 12, 13, 16, 17, 19] default_column_gpios = [20, 21, 22, 23, 24, 25, 26, 27] @@ -72,8 +72,8 @@ class BoardReader: if not self._is_initial_board(board): break else: - self.move_generator.reset() self.print() + self.move_generator.reset() return for i in range(8): @@ -88,8 +88,8 @@ class BoardReader: if board[i][j] == "x": break else: - self.move_generator.take(i, j) self.print() + self.move_generator.take(i, j) for i in range(8): for j in range(8): # if the oldest half of the board history doesn't have a piece but all @@ -102,8 +102,8 @@ class BoardReader: if board[i][j] == " ": break else: - self.move_generator.put(i, j) self.print() + self.move_generator.put(i, j) def _print(self, board) -> None: print(" a b c d e f g h") From bd39c075820487b7c636267638117caceff336f3 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 30 Dec 2022 15:33:28 +0100 Subject: [PATCH 32/44] profiler --- request_test.py | 1 + src/blunderboard/__init__.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/request_test.py b/request_test.py index 5ee201d..06f8540 100644 --- a/request_test.py +++ b/request_test.py @@ -26,6 +26,7 @@ if response.status_code == 200: else: print("Error: " + response.json()["error"]) + def api_wdl() -> str: """ Returns the current wdl from the REST API diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index 91c05eb..01d73cf 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -2,9 +2,10 @@ from blunderboard.blunderevaluator import BlunderEvaluator from blunderboard.boardreader import BoardReader from blunderboard.movegenerator import MoveGenerator from time import sleep +import cProfile -def main(): +def main_content(): blunder_evaluator = BlunderEvaluator() move_generator = MoveGenerator(blunder_evaluator) reader = BoardReader(move_generator) @@ -13,3 +14,11 @@ def main(): while True: reader.scan() sleep(0.1) + + +def main(): + cProfile.run("all()") + + +# run main with cProfile +cProfile.run("main()") From 45f9ede3c5005d7c081ba3924b99c9505fbc9ac4 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 30 Dec 2022 15:39:58 +0100 Subject: [PATCH 33/44] profile with less quatsch --- src/blunderboard/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index 01d73cf..24de326 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -17,7 +17,7 @@ def main_content(): def main(): - cProfile.run("all()") + cProfile.run("main_content()") # run main with cProfile From 793e8ec5ed8e5abccbbc09f3a62240e0d4446ce1 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 30 Dec 2022 15:41:24 +0100 Subject: [PATCH 34/44] profile with even less quatsch --- src/blunderboard/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index 24de326..d1da8f0 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -17,8 +17,4 @@ def main_content(): def main(): - cProfile.run("main_content()") - - -# run main with cProfile -cProfile.run("main()") + cProfile.run("main_content()") \ No newline at end of file From c2209464d7706263ca68ef45bccf75113c0a1bef Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Fri, 30 Dec 2022 15:41:37 +0100 Subject: [PATCH 35/44] dont cProfile on import --- src/blunderboard/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index d1da8f0..77642b5 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -17,4 +17,4 @@ def main_content(): def main(): - cProfile.run("main_content()") \ No newline at end of file + cProfile.run("main_content()") From e93ee7fb7b098e55a8fbebed4c13177159d3056e Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 30 Dec 2022 15:46:08 +0100 Subject: [PATCH 36/44] fix blunder eval? --- src/blunderboard/blunderevaluator.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py index 0b8af1e..766d6a4 100644 --- a/src/blunderboard/blunderevaluator.py +++ b/src/blunderboard/blunderevaluator.py @@ -46,9 +46,11 @@ class BlunderEvaluator: self.current_wdl = self.api_wdl() self.wdls: list = [] self.current_fen = self.engine.get_fen_position() + self.white_to_move = True def reset(self): self.engine.set_position() + self.white_to_move = True def move(self, move) -> None: """ @@ -69,6 +71,10 @@ class BlunderEvaluator: # path self.play_sound("blunder") print("Blunder!") + if self.white_to_move: + self.white_to_move = False + elif not self.white_to_move: + self.white_to_move = True else: print("Invalid move") self.play_sound("illegal") @@ -112,9 +118,7 @@ class BlunderEvaluator: """ if len(self.wdls) > 1: # Don't check for blunders on the first move previous_wdl = self.wdls[len(self.evaluations) - 2] - if abs(previous_wdl[0] - self.current_wdl[0]) > 300: - return True - elif abs(previous_wdl[2] - self.current_wdl[2]) > 300: + if abs(previous_wdl[0] - self.current_wdl[2]) > 300: return True else: return False From 5033991771c9f4f7c3b13061b644432cbe8c7055 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Fri, 30 Dec 2022 15:47:48 +0100 Subject: [PATCH 37/44] add .editorconfig --- .editorconfig | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b631813 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +indent_style = space +indent_size = 4 +max_line_length = 88 From 075001a2d5e9b35abac3da3f46f29e33346a7330 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Fri, 30 Dec 2022 16:01:52 +0100 Subject: [PATCH 38/44] fix profiling --- src/blunderboard/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index 77642b5..908baac 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -17,4 +17,4 @@ def main_content(): def main(): - cProfile.run("main_content()") + cProfile.runctx("main_content()", globals(), locals(), filename=__file__) From 384079ad73fd52c4353c4008779868db386b82b7 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Fri, 30 Dec 2022 16:55:19 +0100 Subject: [PATCH 39/44] better profiling --- src/blunderboard/__init__.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/blunderboard/__init__.py b/src/blunderboard/__init__.py index 908baac..6a70a10 100644 --- a/src/blunderboard/__init__.py +++ b/src/blunderboard/__init__.py @@ -1,20 +1,24 @@ from blunderboard.blunderevaluator import BlunderEvaluator from blunderboard.boardreader import BoardReader from blunderboard.movegenerator import MoveGenerator -from time import sleep import cProfile +from pstats import SortKey +from time import sleep def main_content(): - blunder_evaluator = BlunderEvaluator() - move_generator = MoveGenerator(blunder_evaluator) - reader = BoardReader(move_generator) - reader.scan() - reader.print() - while True: + try: + blunder_evaluator = BlunderEvaluator() + move_generator = MoveGenerator(blunder_evaluator) + reader = BoardReader(move_generator) reader.scan() - sleep(0.1) + reader.print() + while True: + reader.scan() + sleep(0.1) + except KeyboardInterrupt: + pass def main(): - cProfile.runctx("main_content()", globals(), locals(), filename=__file__) + cProfile.runctx("main_content()", globals(), locals(), sort=SortKey.CUMULATIVE) From ff2c74f03478f9925a944bbbc1cebbdc12a2407a Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 30 Dec 2022 16:56:04 +0100 Subject: [PATCH 40/44] Auto stash before rebase of "origin/python" --- .idea/blunderboard.iml | 7 ++ .idea/inspectionProfiles/Project_Default.xml | 19 +++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/vcs.xml | 6 + .idea/workspace.xml | 109 ++++++++++++++++++ .tox/.pkg/file.lock | 0 7 files changed, 151 insertions(+) create mode 100644 .idea/blunderboard.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100755 .tox/.pkg/file.lock diff --git a/.idea/blunderboard.iml b/.idea/blunderboard.iml new file mode 100644 index 0000000..ec63674 --- /dev/null +++ b/.idea/blunderboard.iml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..b8f86db --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..746f0a8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..d233e8a --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + { + "keyToString": { + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "WebServerToolWindowFactoryState": "false", + "last_opened_file_path": "/home/hagenest/Repos/blunderboard", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "vue.rearranger.settings.migration": "true" + } +} + + + + + + + + + + + + + + + + + + + + + + + 1672259554192 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.tox/.pkg/file.lock b/.tox/.pkg/file.lock new file mode 100755 index 0000000..e69de29 From 1a6f85465ae283c81684659590455a23204db0d1 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 30 Dec 2022 17:02:07 +0100 Subject: [PATCH 41/44] Add ZONK --- setup.cfg | 3 ++- sounds/blunder/ZONK.mp3 | Bin 0 -> 36165 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 sounds/blunder/ZONK.mp3 diff --git a/setup.cfg b/setup.cfg index 36a1727..27ea109 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,7 @@ where = src [options.data_files] share/blunderboard/sounds/blunder = sounds/blunder/tts_what_a_blunder.mp3 + sounds/blunder/ZONK.mp3 share/blunderboard/sounds/illegal = sounds/illegal/alarm.mp3 @@ -66,4 +67,4 @@ extend_ignore = E203 check_untyped_defs = True ignore_missing_imports = True install_types = True -non_interactive = True \ No newline at end of file +non_interactive = True diff --git a/sounds/blunder/ZONK.mp3 b/sounds/blunder/ZONK.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ed0e554a5382f0438e180f9db2e07571642a9859 GIT binary patch literal 36165 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 X;Qe^HkN+bhf4$!4=dXPHc=dk(7UY2$ literal 0 HcmV?d00001 From bd334c9abea732dc911032c4d2e39db3e62e2c17 Mon Sep 17 00:00:00 2001 From: Thomas Lindner Date: Fri, 30 Dec 2022 17:32:55 +0100 Subject: [PATCH 42/44] implement castling --- src/blunderboard/movegenerator.py | 100 +++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 7 deletions(-) diff --git a/src/blunderboard/movegenerator.py b/src/blunderboard/movegenerator.py index a37d6b4..925473a 100644 --- a/src/blunderboard/movegenerator.py +++ b/src/blunderboard/movegenerator.py @@ -72,18 +72,104 @@ class TakeState(State): class TakeTakeState(State): def __init__( - self, blunder_evaluator: BlunderEvaluator, from_field: str, to_field: str + self, blunder_evaluator: BlunderEvaluator, from_field: str, from2_field: str ): super().__init__(blunder_evaluator) self.from_field = from_field - self.to_field = to_field + self.from2_field = from2_field def put(self, row: int, column: int) -> State: - field = coords_to_field(row, column) - if self.to_field == field: - move = self.from_field + field - elif self.from_field == field: - move = self.to_field + field + to_field = coords_to_field(row, column) + if self.from2_field == to_field: + move = self.from_field + to_field + elif self.from_field == to_field: + move = self.from2_field + to_field + elif ( + self.from_field[1] == self.from2_field[1] + and self.from_field[1] == to_field[1] + ): + # king-side castling + if ( + self.from_field in ["e1", "e8"] + and self.from2_field in ["h1", "h8"] + and to_field in ["f1", "f8", "g1", "g8"] + ): + return KingCastleState( + self.blunder_evaluator, self.from_field, self.from2_field, to_field + ) + # queen-side castling + if ( + self.from_field in ["e1", "e8"] + and self.from2_field in ["a1", "a8"] + and to_field in ["c1", "c8", "d1", "d8"] + ): + return QueenCastleState( + self.blunder_evaluator, self.from_field, self.from2_field, to_field + ) + print("ignored invalid put") + return self + else: + print("ignored invalid put") + return self + print("move %s" % move) + self.blunder_evaluator.move(move) + return InitState(self.blunder_evaluator) + + +class KingCastleState(State): + def __init__( + self, + blunder_evaluator: BlunderEvaluator, + from_field: str, + from2_field: str, + to_field: str, + ): + super().__init__(blunder_evaluator) + self.from_field = from_field + self.from2_field = from2_field + self.to_field = to_field + + def put(self, row: int, column: int) -> State: + to2_field = coords_to_field(row, column) + if self.to_field[1] == to2_field[1]: + if to2_field in ["f1", "g1"]: + move = "e1g1" + elif to2_field in ["f8", "g8"]: + move = "e8g8" + else: + print("ignored invalid put") + return self + else: + print("ignored invalid put") + return self + print("move %s" % move) + self.blunder_evaluator.move(move) + return InitState(self.blunder_evaluator) + + +class QueenCastleState(State): + def __init__( + self, + blunder_evaluator: BlunderEvaluator, + from_field: str, + from2_field: str, + to_field: str, + ): + super().__init__(blunder_evaluator) + self.from_field = from_field + self.from2_field = from2_field + self.to_field = to_field + + def put(self, row: int, column: int) -> State: + to2_field = coords_to_field(row, column) + if self.to_field[1] == to2_field[1]: + if to2_field in ["c1", "d1"]: + move = "e1c1" + elif to2_field in ["c8", "d8"]: + move = "e8c8" + else: + print("ignored invalid put") + return self else: print("ignored invalid put") return self From 90d209e7a2585469ff55b1c4b5be8c00614ba1d2 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 30 Dec 2022 16:56:04 +0100 Subject: [PATCH 43/44] Revert "Auto stash before rebase of "origin/python"" This reverts commit ff2c74f03478f9925a944bbbc1cebbdc12a2407a. --- .idea/blunderboard.iml | 7 -- .idea/inspectionProfiles/Project_Default.xml | 19 --- .../inspectionProfiles/profiles_settings.xml | 6 - .idea/misc.xml | 4 - .idea/vcs.xml | 6 - .idea/workspace.xml | 109 ------------------ .tox/.pkg/file.lock | 0 7 files changed, 151 deletions(-) delete mode 100644 .idea/blunderboard.iml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml delete mode 100755 .tox/.pkg/file.lock diff --git a/.idea/blunderboard.iml b/.idea/blunderboard.iml deleted file mode 100644 index ec63674..0000000 --- a/.idea/blunderboard.iml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index b8f86db..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 746f0a8..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index d233e8a..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - - - - { - "keyToString": { - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "WebServerToolWindowFactoryState": "false", - "last_opened_file_path": "/home/hagenest/Repos/blunderboard", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_package_manager_path": "npm", - "vue.rearranger.settings.migration": "true" - } -} - - - - - - - - - - - - - - - - - - - - - - - 1672259554192 - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.tox/.pkg/file.lock b/.tox/.pkg/file.lock deleted file mode 100755 index e69de29..0000000 From cda48dca80db19cebdc4a823b5ebc44f0472e4a5 Mon Sep 17 00:00:00 2001 From: Christian Hagenest Date: Fri, 30 Dec 2022 20:12:35 +0100 Subject: [PATCH 44/44] Auto stash before revert of "Auto stash before rebase of "origin/python"" --- config-example.yaml | 0 src/blunderboard/blunderevaluator.py | 9 +++++---- src/blunderboard/commentator.py | 0 src/blunderboard/config.py | 0 4 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 config-example.yaml create mode 100644 src/blunderboard/commentator.py create mode 100644 src/blunderboard/config.py diff --git a/config-example.yaml b/config-example.yaml new file mode 100644 index 0000000..e69de29 diff --git a/src/blunderboard/blunderevaluator.py b/src/blunderboard/blunderevaluator.py index 766d6a4..162cfb5 100644 --- a/src/blunderboard/blunderevaluator.py +++ b/src/blunderboard/blunderevaluator.py @@ -66,15 +66,16 @@ class BlunderEvaluator: self.wdls.append(self.current_wdl) print(self.current_wdl) print(self.current_evaluation) + print(self.get_board()) if self.move_was_blunder(): # If the played move was a blunder play a random sound from the blunder # path self.play_sound("blunder") print("Blunder!") - if self.white_to_move: - self.white_to_move = False - elif not self.white_to_move: - self.white_to_move = True + if self.white_to_move: + self.white_to_move = False + elif not self.white_to_move: + self.white_to_move = True else: print("Invalid move") self.play_sound("illegal") diff --git a/src/blunderboard/commentator.py b/src/blunderboard/commentator.py new file mode 100644 index 0000000..e69de29 diff --git a/src/blunderboard/config.py b/src/blunderboard/config.py new file mode 100644 index 0000000..e69de29