Skip to content

Commit

Permalink
Change chess-python API to v1.5
Browse files Browse the repository at this point in the history
  • Loading branch information
karol-brejna-i committed May 13, 2021
1 parent 920a12c commit ef41509
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 68 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -39,7 +39,7 @@ You can download games from a specific user using this command:
`python3 download_games.py <lichess username>`


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**

Expand Down Expand Up @@ -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`

Expand All @@ -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.
30 changes: 13 additions & 17 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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
Expand All @@ -99,3 +93,5 @@
tactics_file.write("\n\n")

tactics_file.close()

engine.quit()
24 changes: 14 additions & 10 deletions modules/investigate/investigate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 4 additions & 4 deletions modules/puzzle/analysed.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
60 changes: 31 additions & 29 deletions modules/puzzle/position_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit ef41509

Please sign in to comment.