diff --git a/README.md b/README.md index acd5d44..d2402fd 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ It's based on the great [https://github.com/clarkerubber/Python-Puzzle-Creator] Things that I changed: - Use a local pgn file with games as a source. - Write results to a file called tactics.pgn -- Default engine depth to 8 so it's faster. Before it was nodes=3500000 this is a depth around 20. So it took several minutes to analyze a game. With depth 8 it takes seconds. +- Default engine depth to 8, so it's faster. Before it was nodes=3500000 this is a depth around 20. So it took several minutes to analyze a game. With depth 8 it takes seconds. - You can use the `depth` argument to change the depth if you want more precision. -- chess.pop_count to chess.popcount because it was failing +- chess.pop_count to chess.popcount, because it was failing ### This is too complex, give something easy. There is another option if you don't want to install and manage python scripts @@ -39,7 +39,7 @@ You can download games from a specific user using this command: `python3 download_games.py ` -By default it will download the last 60 games from blitz, rapid and classical. +By default, it will download the last 60 games from blitz, rapid and classical. **Arguments** @@ -73,9 +73,9 @@ To execute the generator execute this command. By default it will look for the ` **Arguments** - `--quiet` to reduce the screen output. -- `--depth=8` select the stockfish depth analysis. Default is `8` and will take some seconds to analyze a game, with `--depth=18` will take around 6 minutes. +- `--depth=8` select the Stockfish depth analysis. Default is `8` and will take some seconds to analyze a game, with `--depth=18` will take around 6 minutes. - `--games=ruy_lopez.pgn` to select a specific pgn file. Default is `games.pgn` -- `strict=False` Use `False` to generate more tactics but a little more ambiguous. Default is `True` +- `--strict=False` Use `False` to generate more tactics but a little more ambiguous. Default is `True` - `--threads=4` Stockfish argument, number of engine threads, default `4` - `--memory=2048` Stockfish argument, memory in MB to use for engine hashtables, default `2048` @@ -90,7 +90,7 @@ The `result header` is the tactic result and not the game result. It can be load ## Problems? #### Stockfish errors -- If you have problems building stockfish try downloading stockfish directly https://stockfishchess.org/download/ +- If you have problems building Stockfish try downloading Stockfish directly https://stockfishchess.org/download/ ## Want to see all my chess related projects? Check [My projects](http://vitomd.com/blog/projects/) for a full detailed list. diff --git a/main.py b/main.py index f509ba8..834df16 100755 --- a/main.py +++ b/main.py @@ -7,7 +7,7 @@ import sys import chess.pgn -import chess.uci +import chess.engine from modules.api.api import post_puzzle from modules.bcolors.bcolors import bcolors @@ -44,13 +44,10 @@ logging.basicConfig(format="%(message)s", level=settings.loglevel, stream=sys.stdout) logging.getLogger("requests.packages.urllib3").setLevel(logging.WARNING) -logging.getLogger("chess.uci").setLevel(logging.WARNING) +logging.getLogger("chess.engine").setLevel(logging.WARNING) -engine = chess.uci.popen_engine(stockfish_command()) -engine.setoption({'Threads': settings.threads, 'Hash': settings.memory}) -engine.uci() -info_handler = chess.uci.InfoHandler() -engine.info_handlers.append(info_handler) +engine = chess.engine.SimpleEngine.popen_uci(stockfish_command()) +engine.configure({'Threads': settings.threads, 'Hash': settings.memory}) all_games = open(settings.games, "r") tactics_file = open("tactics.pgn", "w") @@ -65,27 +62,24 @@ logging.debug(bcolors.WARNING + "Game ID: " + str(game_id) + bcolors.ENDC) logging.debug(bcolors.WARNING + "Game headers: " + str(game) + bcolors.ENDC) - prev_score = chess.uci.Score(None, None) + prev_score = chess.engine.Cp(0) puzzles = [] logging.debug(bcolors.OKGREEN + "Game Length: " + str(game.end().board().fullmove_number)) logging.debug("Analysing Game..." + bcolors.ENDC) - engine.ucinewgame() - while not node.is_end(): next_node = node.variation(0) - engine.position(next_node.board()) - engine.go(depth=settings.depth) - cur_score = info_handler.info["score"][1] + info = engine.analyse(next_node.board(), chess.engine.Limit(depth=settings.depth)) + + cur_score = info["score"].relative logging.debug(bcolors.OKGREEN + node.board().san(next_node.move) + bcolors.ENDC) - logging.debug(bcolors.OKBLUE + " CP: " + str(cur_score.cp)) - logging.debug(" Mate: " + str(cur_score.mate) + bcolors.ENDC) + logging.debug(bcolors.OKBLUE + " CP: " + str(cur_score.score())) + logging.debug(" Mate: " + str(cur_score.mate()) + bcolors.ENDC) if investigate(prev_score, cur_score, node.board()): logging.debug(bcolors.WARNING + " Investigate!" + bcolors.ENDC) - puzzles.append( - puzzle(node.board(), next_node.move, str(game_id), engine, info_handler, game, settings.strict)) + puzzles.append(puzzle(node.board(), next_node.move, str(game_id), engine, info, game, settings.strict)) prev_score = cur_score node = next_node @@ -99,3 +93,5 @@ tactics_file.write("\n\n") tactics_file.close() + +engine.quit() diff --git a/modules/investigate/investigate.py b/modules/investigate/investigate.py index 078c74e..4204720 100644 --- a/modules/investigate/investigate.py +++ b/modules/investigate/investigate.py @@ -15,19 +15,23 @@ def material_count(board): def investigate(a, b, board): - # determine if the difference between position A and B - # is worth investigating for a puzzle. - if a.cp is not None and b.cp is not None: - if (((-110 < a.cp < 850 and 200 < b.cp < 850) - or (-850 < a.cp < 110 and -200 > b.cp > -850)) + """ + determine if the difference between position A and B + is worth investigating for a puzzle. + """ + a_cp, a_mate = a.score(), a.mate() + b_cp, b_mate = b.score(), b.mate() + + if a_cp is not None and b_cp is not None: + if (((-110 < a_cp < 850 and 200 < b_cp < 850) + or (-850 < a_cp < 110 and -200 > b_cp > -850)) and material_value(board) > 3 and material_count(board) > 6): return True - elif a.cp is not None and b.mate is not None and material_value(board) > 3: - if (a.cp < 110 and sign(b.mate) == -1) or (a.cp > -110 and sign(b.mate) == 1): + elif a_cp is not None and b_mate is not None and material_value(board) > 3: + if (a_cp < 110 and sign(b_mate) == -1) or (a_cp > -110 and sign(b_mate) == 1): return True - elif (a.mate is not None - and b.mate is not None): - if sign(a.mate) == sign(b.mate): # actually means that they're opposite + elif a_mate is not None and b_mate is not None: + if sign(a_mate) == sign(b_mate): # actually means that they're opposite return True return False diff --git a/modules/puzzle/analysed.py b/modules/puzzle/analysed.py index 934c2f9..1b28b34 100644 --- a/modules/puzzle/analysed.py +++ b/modules/puzzle/analysed.py @@ -10,9 +10,9 @@ def sign(self, val): return 1 def sort_val(self): - if self.evaluation.cp is not None: - return self.evaluation.cp - elif self.evaluation.mate is not None: - return self.sign(self.evaluation.mate) * (abs(100 + self.evaluation.mate)) * 10000 + if self.evaluation.score(): + return self.evaluation.score() + elif self.evaluation.is_mate(): + return self.sign(self.evaluation.mate()) * (abs(100 + self.evaluation.mate())) * 10000 else: return 0 diff --git a/modules/puzzle/position_list.py b/modules/puzzle/position_list.py index 8f6b1d3..572f908 100644 --- a/modules/puzzle/position_list.py +++ b/modules/puzzle/position_list.py @@ -2,7 +2,7 @@ from operator import methodcaller import chess -import chess.uci +import chess.engine from modules.bcolors.bcolors import bcolors from modules.puzzle.analysed import analysed @@ -23,11 +23,11 @@ def __init__(self, position, engine, info_handler, player_turn=True, best_move=N def move_list(self): if self.next_position is None or self.next_position.ambiguous() or self.next_position.position.is_game_over(): if self.best_move is not None: - return [self.best_move.bestmove.uci()] + return [self.best_move.move.uci()] else: return [] else: - return [self.best_move.bestmove.uci()] + self.next_position.move_list() + return [self.best_move.move.uci()] + self.next_position.move_list() def category(self): if self.next_position is None: @@ -58,19 +58,20 @@ def generate(self, depth): def evaluate_best(self, depth): logging.debug(bcolors.OKGREEN + "Evaluating Best Move...") - self.engine.position(self.position) - self.best_move = self.engine.go(depth=depth) - if self.best_move.bestmove is not None: - self.evaluation = self.info_handler.info["score"][1] + + self.best_move = self.engine.play(self.position, chess.engine.Limit(depth=depth), info=chess.engine.INFO_ALL) + + if self.best_move.move is not None: + self.evaluation = self.best_move.info['score'].relative self.next_position = position_list(self.position.copy(), self.engine, self.info_handler, not self.player_turn, strict=self.strict) - self.next_position.position.push(self.best_move.bestmove) - logging.debug("Best Move: " + self.best_move.bestmove.uci() + bcolors.ENDC) - logging.debug(bcolors.OKBLUE + " CP: " + str(self.evaluation.cp)) - logging.debug(" Mate: " + str(self.evaluation.mate) + bcolors.ENDC) + self.next_position.position.push(self.best_move.move) + logging.debug("Best Move: " + self.best_move.move.uci() + bcolors.ENDC) + logging.debug(bcolors.OKBLUE + " CP: " + str(self.evaluation.score())) + logging.debug(" Mate: " + str(self.evaluation.mate()) + bcolors.ENDC) return True else: logging.debug(bcolors.FAIL + "No best move!" + bcolors.ENDC) @@ -81,14 +82,15 @@ def evaluate_legals(self, depth): for i in self.position.legal_moves: position_copy = self.position.copy() position_copy.push(i) - self.engine.position(position_copy) - self.engine.go(depth=depth) - self.analysed_legals.append(analysed(i, self.info_handler.info["score"][1])) + + info = self.engine.analyse(position_copy, chess.engine.Limit(depth=depth)) + self.analysed_legals.append(analysed(i, info["score"].relative)) + self.analysed_legals = sorted(self.analysed_legals, key=methodcaller('sort_val')) for i in self.analysed_legals[:3]: logging.debug(bcolors.OKGREEN + "Move: " + str(i.move.uci()) + bcolors.ENDC) - logging.debug(bcolors.OKBLUE + " CP: " + str(i.evaluation.cp)) - logging.debug(" Mate: " + str(i.evaluation.mate)) + logging.debug(bcolors.OKBLUE + " CP: " + str(i.evaluation.score())) + logging.debug(" Mate: " + str(i.evaluation.mate())) logging.debug("... and " + str(max(0, len(self.analysed_legals) - 3)) + " more moves" + bcolors.ENDC) def material_difference(self): @@ -109,7 +111,7 @@ def is_complete(self, category, color, first_node, first_val): if (self.material_difference() > 0.2 and abs(self.material_difference() - first_val) > 0.1 and first_val < 2 - and self.evaluation.mate is None + and self.evaluation.mate() is None and self.material_count() > 6): return True else: @@ -118,7 +120,7 @@ def is_complete(self, category, color, first_node, first_val): if (self.material_difference() < -0.2 and abs(self.material_difference() - first_val) > 0.1 and first_val > -2 - and self.evaluation.mate is None + and self.evaluation.mate() is None and self.material_count() > 6): return True else: @@ -133,19 +135,19 @@ def ambiguous(self): # If strict == False then it will generate more tactics but more ambiguous move_number = 1 if self.strict else 2 if len(self.analysed_legals) > 1: - if (self.analysed_legals[0].evaluation.cp is not None - and self.analysed_legals[1].evaluation.cp is not None): - if (self.analysed_legals[0].evaluation.cp > -210 - or self.analysed_legals[move_number].evaluation.cp < -90): + if (self.analysed_legals[0].evaluation.score() is not None + and self.analysed_legals[1].evaluation.score() is not None): + if (self.analysed_legals[0].evaluation.score() > -210 + or self.analysed_legals[move_number].evaluation.score() < -90): return True - if (self.analysed_legals[0].evaluation.mate is not None - and self.analysed_legals[1].evaluation.mate is not None): - if (self.analysed_legals[0].evaluation.mate < 1 - and self.analysed_legals[1].evaluation.mate < 1): + if (self.analysed_legals[0].evaluation.mate() is not None + and self.analysed_legals[1].evaluation.mate() is not None): + if (self.analysed_legals[0].evaluation.mate() < 1 + and self.analysed_legals[1].evaluation.mate() < 1): return True - if self.analysed_legals[0].evaluation.mate is None or self.analysed_legals[1].evaluation.cp is None: - return - if self.analysed_legals[1].evaluation.cp < -200: + if not self.analysed_legals[0].evaluation.is_mate() or self.analysed_legals[1].evaluation.is_mate(): + return True + if self.analysed_legals[1].evaluation.score() < -200: return True return False diff --git a/requirements.txt b/requirements.txt index 9d38342..280c1c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -colorama==0.3.7 -python-chess==0.24.2 +colorama==0.4.4 +chess==1.5.0 requests==2.20.0