import re import subprocess import sys from typing import List, Tuple, Any STOCKFISH_MOVES = {} RECHESS_ENGINE_MOVES = {} def main(): # python perft.py stockfish_exe_path, rechess_exe_path, fen, depth = cli() print(stockfish_exe_path) print(rechess_exe_path) print(fen) print(depth) print('Executing stockfish, this may take a while...') stockfish_process = stockfish_init(stockfish_exe_path) stockfish_perft(stockfish_process, fen, [], int(depth)) print('Executing rechess, this may take a while...') rechess_process = rechess_init(rechess_exe_path) rechess_perft(rechess_process, fen, [], int(depth)) print('-------------------------------------') print('Missing in rechess engine:\n') compare(STOCKFISH_MOVES, RECHESS_ENGINE_MOVES) print('-------------------------------------\n') print('Missing in stockfish:\n') compare(RECHESS_ENGINE_MOVES, STOCKFISH_MOVES) print('-------------------------------------') # We could run quit to quit it the intended way, but we just terminate now stockfish_process.terminate() rechess_process.terminate() def cli() -> (str, str, str, str): stockfish_exe_path = sys.argv[1] rechess_exe_path = sys.argv[2] fen = sys.argv[3] depth = sys.argv[4] return stockfish_exe_path, rechess_exe_path, fen, depth def stockfish_perft(stockfish_process: Any, fen: str, moves: List[str], depth: int): if depth == 0: return add_to_moves_dict(STOCKFISH_MOVES, moves) output = stockfish(stockfish_process, fen, to_moves_str(moves), depth) for move in parse_output(output): stockfish_perft(stockfish_process, fen, moves + [move[0]], depth - 1) def rechess_perft(rechess_process: Any, fen: str, moves: List[str], depth: int): if depth == 0: return add_to_moves_dict(RECHESS_ENGINE_MOVES, moves) output = rechess(rechess_process, fen, to_moves_str(moves), depth) for move in parse_output(output): rechess_perft(rechess_process, fen, moves + [move[0]], depth - 1) def stockfish_init(stockfish_exe_path: str) -> Any: return subprocess.Popen(stockfish_exe_path, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) def stockfish(stockfish_process: Any, fen: str, moves: str, depth: int) -> str: command = f'position fen {fen} moves {moves}\ngo perft {depth}\n' stockfish_process.stdin.write(command) stockfish_process.stdin.flush() text = '' while True: output = stockfish_process.stdout.readline().strip() text += output if 'Nodes searched' in output: break return text def rechess_init(rechess_exe_path: str) -> Any: return subprocess.Popen([rechess_exe_path, 'chess', 'interactive'], universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) def rechess(rechess_process: Any, fen: str, moves: str, depth: int) -> str: command = f'perft depth {depth} position fen {fen} moves {moves}\n' rechess_process.stdin.write(command) rechess_process.stdin.flush() text = '' while True: output = rechess_process.stdout.readline().strip() text += output if 'Nodes searched' in output: break return text def parse_output(text: str) -> List[Tuple[str, int]]: move_count_pattern = r"([a-h][1-8][a-h][1-8][qbkr]?): (\d+)" move_count_pairs = re.findall(move_count_pattern, text) moves = [] for move, count in move_count_pairs: moves += [(move, count)] return moves def compare(a, b, path: str = ''): for key in a.keys(): nested_path = f'{path} {key}'.strip() if key in b: compare(a[key], b[key], nested_path) else: print(f'[!] {nested_path}') def to_moves_str(moves: List[str]) -> str: return ' '.join(moves) def add_to_moves_dict(dic, moves: List[str]): d = dic for move in moves[:-1]: if move in d: d = d[move] else: d = d.setdefault(move, {}) d[moves[-1]] = {} if __name__ == '__main__': main()