From b2ce73f06bcf893529c4012f23b7a47765d45c67 Mon Sep 17 00:00:00 2001 From: luk3k Date: Tue, 30 Jan 2024 19:44:09 +0100 Subject: [PATCH 01/10] added engine factory and adapted command line parsing, added lc0_strategy.py, added stockfish and lc0 standalone engines --- chesspp/engine.py | 37 ++++++++++++++--- chesspp/engine_factory.py | 76 +++++++++++++++++++++++++++++++++++ chesspp/eval.py | 34 ++++++++++------ chesspp/i_strategy.py | 5 ++- chesspp/lc0_strategy.py | 39 ++++++++++++++++++ chesspp/random_strategy.py | 3 +- chesspp/simulation.py | 27 ++++++++----- chesspp/stockfish_strategy.py | 12 +++--- main.py | 37 ++++++++--------- 9 files changed, 214 insertions(+), 56 deletions(-) create mode 100644 chesspp/engine_factory.py create mode 100644 chesspp/lc0_strategy.py diff --git a/chesspp/engine.py b/chesspp/engine.py index da08b64..0c40a3f 100644 --- a/chesspp/engine.py +++ b/chesspp/engine.py @@ -1,5 +1,6 @@ import random import time +import os from abc import ABC, abstractmethod from torch import distributions as dist @@ -15,13 +16,13 @@ from typing import Dict class Limit: """ Class to determine when to stop searching for moves """ - time: float|None + time: float | None """ Search for `time` seconds """ - nodes: int|None + nodes: int | None """ Search for a limited number of `nodes`""" - def __init__(self, time: float|None = None, nodes: int|None = None): + def __init__(self, time: float | None = None, nodes: int | None = None): self.time = time self.nodes = nodes @@ -44,7 +45,7 @@ class Limit: def _run_time(self, func, *args, **kwargs): start = time.perf_counter_ns() - while (time.perf_counter_ns()-start)/1e9 < self.time: + while (time.perf_counter_ns() - start) / 1e9 < self.time: func(*args, **kwargs) @@ -56,7 +57,7 @@ class Engine(ABC): strategy: IStrategy """The strategy used to pick moves when simulating games.""" - def __init__(self, board: chess.Board, color: chess.Color, strategy: IStrategy): + def __init__(self, board: chess.Board, color: chess.Color, strategy: IStrategy | None): self.board = board self.color = color self.strategy = strategy @@ -138,3 +139,29 @@ class RandomEngine(Engine): def play(self, board: chess.Board, limit: Limit) -> chess.engine.PlayResult: move = random.choice(list(board.legal_moves)) return chess.engine.PlayResult(move=move, ponder=None) + + +class StockFishEngine(Engine): + def __init__(self, board: chess.Board, color: chess, path="../stockfish/stockfish-ubuntu-x86-64-avx2"): + super().__init__(board, color, None) + self.stockfish = chess.engine.SimpleEngine.popen_uci(path) + + def play(self, board: chess.Board, limit: Limit) -> chess.engine.PlayResult: + return self.stockfish.play(board, limit) + + @staticmethod + def get_name() -> str: + return "Stockfish" + + +class Lc0Engine(Engine): + def __init__(self, board: chess.Board, color: chess, path="../lc0/lc0"): + super().__init__(board, color, None) + self.lc0 = chess.engine.SimpleEngine.popen_uci(path) + + def play(self, board: chess.Board, limit: Limit) -> chess.engine.PlayResult: + return self.lc0.play(board, limit) + + @staticmethod + def get_name() -> str: + return "Lc0" diff --git a/chesspp/engine_factory.py b/chesspp/engine_factory.py new file mode 100644 index 0000000..c257ba5 --- /dev/null +++ b/chesspp/engine_factory.py @@ -0,0 +1,76 @@ +from enum import Enum + +from chesspp.engine import * +from chesspp.lc0_strategy import Lc0Strategy +from chesspp.random_strategy import RandomStrategy +from chesspp.stockfish_strategy import StockFishStrategy +from chesspp.i_strategy import IStrategy +import chess + + +class EngineEnum(Enum): + ClassicMcts = 0 + BayesianMcts = 1 + Stockfish = 2 + Lc0 = 3 + Random = 4 + + +class StrategyEnum(Enum): + Stockfish = 0 + Lc0 = 1 + Random = 2 + + +class EngineFactory: + + @staticmethod + def create_engine(engine_name: EngineEnum, strategy_name: StrategyEnum, color: chess.Color, stockfish_path: str, lc0_path: str, rollout_depth: int = 4) -> Engine: + match strategy_name: + case StrategyEnum.Stockfish: + strategy = EngineFactory._get_stockfish_strategy(stockfish_path, rollout_depth) + case StrategyEnum.Lc0: + strategy = EngineFactory._get_lc0_strategy(lc0_path, rollout_depth) + case StrategyEnum.Random: + strategy = EngineFactory._get_random_strategy(rollout_depth) + + match engine_name: + case EngineEnum.ClassicMcts: + return EngineFactory.classic_mcts(color, strategy) + + case EngineEnum.BayesianMcts: + return EngineFactory.bayesian_mcts(color, strategy) + + case EngineEnum.Stockfish: + return EngineFactory.stockfish_engine(color, stockfish_path) + + case EngineEnum.Lc0: + return EngineFactory.lc0_engine(color, lc0_path) + + @staticmethod + def stockfish_engine(color: chess.Color, engine_path: str, board: chess.Board | None = chess.Board()) -> Engine: + return StockFishEngine(board, color, engine_path) + + @staticmethod + def lc0_engine(color: chess.Color, engine_path: str, board: chess.Board | None = chess.Board()) -> Engine: + return Lc0Engine(board, color, engine_path) + + @staticmethod + def bayesian_mcts(color: chess.Color, strategy: IStrategy, board: chess.Board | None = chess.Board()) -> Engine: + return BayesMctsEngine(board, color, strategy) + + @staticmethod + def classic_mcts(color: chess.Color, strategy: IStrategy, board: chess.Board | None = chess.Board()) -> Engine: + return ClassicMctsEngine(board, color, strategy) + + @staticmethod + def _get_random_strategy(rollout_depth: int) -> IStrategy: + return RandomStrategy(random.Random(), rollout_depth) + + @staticmethod + def _get_stockfish_strategy(engine_path: str, rollout_depth: int) -> IStrategy: + return StockFishStrategy(engine_path, rollout_depth) + + @staticmethod + def _get_lc0_strategy(engine_path: str, rollout_depth: int) -> IStrategy: + return Lc0Strategy(engine_path, rollout_depth) diff --git a/chesspp/eval.py b/chesspp/eval.py index 375b1f5..ab171a7 100644 --- a/chesspp/eval.py +++ b/chesspp/eval.py @@ -76,12 +76,12 @@ king_eval = [ ] king_endgame_eval = [ 50, -30, -30, -30, -30, -30, -30, -50, - -30, -30, 0, 0, 0, 0, -30, -30, + -30, -30, 0, 0, 0, 0, -30, -30, -30, -10, 20, 30, 30, 20, -10, -30, -30, -10, 30, 40, 40, 30, -10, -30, -30, -10, 30, 40, 40, 30, -10, -30, -30, -10, 20, 30, 30, 20, -10, -30, - -30, -20, -10, 0, 0, -10, -20, -30, + -30, -20, -10, 0, 0, -10, -20, -30, -50, -40, -30, -20, -20, -30, -40, -50 ] @@ -124,18 +124,18 @@ def check_endgame(board: chess.Board) -> bool: if piece.piece_type == chess.QUEEN: if piece.color == chess.WHITE: - queens_white += 1 + queens_white += 1 else: queens_black += 1 if piece.piece_type == chess.BISHOP or piece.piece_type == chess.KNIGHT: if piece.color == chess.WHITE: - minors_white += 1 + minors_white += 1 else: minors_black += 1 return (queens_black == 0 and queens_white == 0) or ((queens_black >= 1 >= minors_black) or ( - queens_white >= 1 >= minors_white)) + queens_white >= 1 >= minors_white)) def score_manual(board: chess.Board) -> int: @@ -173,30 +173,38 @@ def score_manual(board: chess.Board) -> int: return score -def score_stockfish(board: chess.Board, stockfish: chess.engine.SimpleEngine | None = None) -> int: +def score_stockfish(board: chess.Board, stockfish: chess.engine.SimpleEngine | None = None, + limit: chess.engine.Limit = chess.engine.Limit(depth=0)) -> int: """ Calculate the score of the given board using stockfish :param board: + :param stockfish: + :param limit: :return: """ if stockfish is None: engine = chess.engine.SimpleEngine.popen_uci( "/home/luke/projects/pp-project/chess-engine-pp/stockfish/stockfish-ubuntu-x86-64-avx2") - info = engine.analyse(board, chess.engine.Limit(depth=0)) + info = engine.analyse(board, limit) engine.quit() return info['score'].white().score(mate_score=100_000) else: - info = stockfish.analyse(board, chess.engine.Limit(depth=0)) + info = stockfish.analyse(board, limit) return info['score'].white().score(mate_score=100_000) -def score_lc0(board: chess.Board) -> chess.engine.PovScore: +def score_lc0(board: chess.Board, lc0: chess.engine.SimpleEngine | None = None, + limit: chess.engine.Limit= chess.engine.Limit(depth=0)) -> int: """ Calculate the score of the given board using lc0 :param board: :return: """ - engine = chess.engine.SimpleEngine.popen_uci("/home/luke/projects/pp-project/chess-engine-pp/lc0/lc0") - info = engine.analyse(board, chess.engine.Limit(depth=4)) - engine.quit() - return info["score"] + if lc0 is None: + engine = chess.engine.SimpleEngine.popen_uci("/home/luke/projects/pp-project/chess-engine-pp/lc0/lc0") + info = engine.analyse(board, limit) + engine.quit() + return info["score"] + else: + info = lc0.analyse(board, limit) + return info['score'].white().score(mate_score=100_000) diff --git a/chesspp/i_strategy.py b/chesspp/i_strategy.py index d985b79..b37b0cb 100644 --- a/chesspp/i_strategy.py +++ b/chesspp/i_strategy.py @@ -3,8 +3,11 @@ from abc import ABC, abstractmethod import chess -# TODO extend class class IStrategy(ABC): + rollout_depth: int + + def __init__(self, rollout_depth: int = 4): + self.rollout_depth = rollout_depth @abstractmethod def pick_next_move(self, board: chess.Board) -> chess.Move: diff --git a/chesspp/lc0_strategy.py b/chesspp/lc0_strategy.py new file mode 100644 index 0000000..cb1fe11 --- /dev/null +++ b/chesspp/lc0_strategy.py @@ -0,0 +1,39 @@ +import chess +import chess.engine +import os + +from chesspp.i_strategy import IStrategy +from chesspp.eval import score_lc0 + +_DIR = os.path.abspath(os.path.dirname(__file__)) + + +class Lc0Strategy(IStrategy): + def __init__(self, path="../lc0/lc0", rollout_depth: int = 4, + limit: chess.engine.Limit = chess.engine.Limit(depth=4)): + super().__init__(rollout_depth) + self._lc0 = None + self.path = path + self.limit = limit + + def __del__(self): + if self._lc0 is not None: + self._lc0.quit() + + @property + def lc0(self) -> chess.engine.SimpleEngine: + if self._lc0 is None: + self._lc0 = self.lc0 = chess.engine.SimpleEngine.popen_uci(self.path) + return self._lc0 + + @lc0.setter + def lc0(self, value): + self._lc0 = value + + def pick_next_move(self, board: chess.Board) -> chess.Move | None: + return self.lc0.play(board, self.limit).move + + def analyze_board(self, board: chess.Board) -> int: + score = score_lc0(board, self.lc0) + print("lc0 score", score) + return score diff --git a/chesspp/random_strategy.py b/chesspp/random_strategy.py index 74193d7..28c1bb4 100644 --- a/chesspp/random_strategy.py +++ b/chesspp/random_strategy.py @@ -5,7 +5,8 @@ from chesspp.eval import score_manual class RandomStrategy(IStrategy): - def __init__(self, random_state: random.Random): + def __init__(self, random_state: random.Random, rollout_depth: int = 4): + super().__init__(rollout_depth) self.random_state = random_state def pick_next_move(self, board: chess.Board) -> chess.Move | None: diff --git a/chesspp/simulation.py b/chesspp/simulation.py index 30ae071..98cf94c 100644 --- a/chesspp/simulation.py +++ b/chesspp/simulation.py @@ -5,8 +5,8 @@ import chess.pgn from typing import Tuple, List from enum import Enum from dataclasses import dataclass -from chesspp.i_strategy import IStrategy +from chesspp.engine_factory import StrategyEnum, EngineFactory, EngineEnum from chesspp.engine import Engine, Limit @@ -36,32 +36,39 @@ def simulate_game(white: Engine, black: Engine, limit: Limit, board: chess.Board class Evaluation: - def __init__(self, engine_a: Engine.__class__, strategy_a, engine_b: Engine.__class__, strategy_b, limit: Limit): + def __init__(self, engine_a: EngineEnum, strategy_a, engine_b: EngineEnum, strategy_b, limit: Limit, + stockfish_path: str, lc0_path: str): self.engine_a = engine_a self.strategy_a = strategy_a self.engine_b = engine_b self.strategy_b = strategy_b + self.stockfish_path = stockfish_path + self.lc0_path = lc0_path self.limit = limit def run(self, n_games=100, proc=mp.cpu_count()) -> List[EvaluationResult]: proc = min(proc, mp.cpu_count()) with mp.Pool(proc) as pool: - args = [(self.engine_a, self.strategy_a, self.engine_b, self.strategy_b, self.limit) for i in range(n_games)] + args = [(self.engine_a, self.strategy_a, self.engine_b, self.strategy_b, self.limit, self.stockfish_path, self.lc0_path) for i + in + range(n_games)] return pool.map(Evaluation._test_simulate, args) @staticmethod - def _test_simulate(arg: Tuple[Engine.__class__, IStrategy, Engine.__class__, IStrategy, Limit]) -> EvaluationResult: - engine_a, strategy_a, engine_b, strategy_b, limit = arg + def _test_simulate(arg: Tuple[EngineEnum, StrategyEnum, EngineEnum, StrategyEnum, Limit, str, str]) -> EvaluationResult: + engine_a, strategy_a, engine_b, strategy_b, limit, stockfish_path, lc0_path = arg flip_engines = bool(random.getrandbits(1)) - board = chess.Board() - if flip_engines: - black, white = engine_a(board.copy(), chess.BLACK, strategy_a), engine_b(board.copy(), chess.WHITE, strategy_b) + black, white = EngineFactory.create_engine(engine_a, strategy_a, chess.BLACK, + stockfish_path, lc0_path), EngineFactory.create_engine( + engine_b, strategy_b, chess.WHITE, stockfish_path, lc0_path) else: - white, black = engine_a(board.copy(), chess.WHITE, strategy_a), engine_b(board.copy(), chess.BLACK, strategy_b) + white, black = EngineFactory.create_engine(engine_a, strategy_a, chess.WHITE, + stockfish_path, lc0_path), EngineFactory.create_engine( + engine_b, strategy_b, chess.BLACK, stockfish_path, lc0_path) - game = simulate_game(white, black, limit, board) + game = simulate_game(white, black, limit, chess.Board()) winner = game.end().board().outcome().winner result = Winner.Draw diff --git a/chesspp/stockfish_strategy.py b/chesspp/stockfish_strategy.py index 4ae3024..0a089f2 100644 --- a/chesspp/stockfish_strategy.py +++ b/chesspp/stockfish_strategy.py @@ -4,14 +4,15 @@ from chesspp.i_strategy import IStrategy from chesspp.eval import score_stockfish import chess.engine -_DIR = os.path.abspath(os.path.dirname(__file__)) - class StockFishStrategy(IStrategy): - def __init__(self, path="../stockfish/stockfish-windows-x86-64-avx2"): + def __init__(self, path="../stockfish/stockfish-windows-x86-64-avx2", rollout_depth: int = 4, + limit: chess.engine.Limit = chess.engine.Limit(depth=4)): + super().__init__(rollout_depth) self._stockfish = None self.path = path + self.limit = limit def __del__(self): if self._stockfish is not None: @@ -20,8 +21,7 @@ class StockFishStrategy(IStrategy): @property def stockfish(self) -> chess.engine.SimpleEngine: if self._stockfish is None: - self._stockfish = self.stockfish = chess.engine.SimpleEngine.popen_uci( - os.path.join(_DIR, self.path)) + self._stockfish = self.stockfish = chess.engine.SimpleEngine.popen_uci(self.path) return self._stockfish @stockfish.setter @@ -29,7 +29,7 @@ class StockFishStrategy(IStrategy): self._stockfish = stockfish def pick_next_move(self, board: chess.Board) -> chess.Move | None: - return self.stockfish.play(board, chess.engine.Limit(depth=4)).move + return self.stockfish.play(board, self.limit).move def analyze_board(self, board: chess.Board) -> int: return score_stockfish(board, self.stockfish) diff --git a/main.py b/main.py index 549ae13..80d1330 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,11 @@ import random import time - import chess import chess.engine import chess.pgn from chesspp.classic_mcts import ClassicMcts from chesspp.baysian_mcts import BayesianMcts +from chesspp.engine_factory import EngineEnum, StrategyEnum from chesspp.random_strategy import RandomStrategy from chesspp.stockfish_strategy import StockFishStrategy from chesspp import engine @@ -87,26 +87,17 @@ def analyze_results(moves: dict): def test_evaluation(): - a, b, s1, s2, n, limit, stockfish_path, proc = read_arguments() + a, b, s1, s2, n, limit, stockfish_path, lc0_path, proc = read_arguments() limit = engine.Limit(time=limit) - if s1 == StockFishStrategy: - strat1 = StockFishStrategy(stockfish_path) - else: - strat1 = s1() - if s2 == StockFishStrategy: - strat2 = StockFishStrategy(stockfish_path) - else: - strat2 = s1() - - evaluator = simulation.Evaluation(a, strat1, b, strat2, limit) + evaluator = simulation.Evaluation(a, s1, b, s2, limit, stockfish_path, lc0_path) results = evaluator.run(n, proc) a_results = len(list(filter(lambda x: x.winner == simulation.Winner.Engine_A, results))) / len(results) * 100 b_results = len(list(filter(lambda x: x.winner == simulation.Winner.Engine_B, results))) / len(results) * 100 draws = len(list(filter(lambda x: x.winner == simulation.Winner.Draw, results))) / len(results) * 100 - print(f"Engine {a.get_name()} won {a_results}% of games") - print(f"Engine {b.get_name()} won {b_results}% of games") + print(f"Engine {a} won {a_results}% of games") + print(f"Engine {b} won {b_results}% of games") print(f"{draws}% of games resulted in a draw") @@ -116,19 +107,24 @@ def read_arguments(): description='Compare two engines by playing multiple games against each other' ) - engines = {"ClassicMCTS": engine.ClassicMctsEngine, "BayesianMCTS": engine.BayesMctsEngine, "Random": engine.RandomEngine} - strategies = {"Random": RandomStrategy, "Stockfish": StockFishStrategy} + engines = {"ClassicMCTS": EngineEnum.ClassicMcts, "BayesianMCTS": EngineEnum.BayesianMcts, + "Random": EngineEnum.Random, "Stockfish": EngineEnum.Stockfish, "Lc0": EngineEnum.Lc0} + strategies = {"Random": StrategyEnum.Random, "Stockfish": StrategyEnum.Stockfish, "Lc0": StrategyEnum.Lc0} if os.name == 'nt': stockfish_default = "../stockfish/stockfish-windows-x86-64-avx2" + lc0_default = "../lc0/lc0.exe" else: stockfish_default = "../stockfish/stockfish-ubuntu-x86-64-avx2" + lc0_default = "../lc0/lc0" parser.add_argument("--proc", default=2, help="Number of processors to use for simulation, default=1") parser.add_argument("--time", default=0.5, help="Time limit for each simulation step, default=0.5") parser.add_argument("-n", default=100, help="Number of games to simulate, default=100") - parser.add_argument("--stockfish", default=stockfish_default, - help=f"Path for stockfish executable, default='{stockfish_default}'") + parser.add_argument("--stockfish_path", default=stockfish_default, + help=f"Path for engine executable, default='{stockfish_default}'") + parser.add_argument("--lc0_path", default=lc0_default, + help=f"Path for engine executable, default='{stockfish_default}'") parser.add_argument("--engine1", "--e1", help="Engine A for the simulation", choices=engines.keys(), required=True) parser.add_argument("--engine2", "--e2", help="Engine B for the simulation", choices=engines.keys(), required=True) parser.add_argument("--strategy1", "--s1", default=list(strategies.keys())[0], @@ -136,7 +132,7 @@ def read_arguments(): choices=strategies.keys()) parser.add_argument("--strategy2", "--s2", default=list(strategies.keys())[0], help="Strategy for engine B for the rollout", - choices=strategies) + choices=strategies.keys()) args = parser.parse_args() engine1 = engines[args.engine1] @@ -144,7 +140,8 @@ def read_arguments(): strategy1 = strategies[args.strategy1] strategy2 = strategies[args.strategy2] - return engine1, engine2, strategy1, strategy2, int(args.n), float(args.time), args.stockfish, int(args.proc) + print(engine1, engine2, strategy1, strategy2, int(args.n), float(args.time), args.stockfish_path, args.lc0_path, int(args.proc)) + return engine1, engine2, strategy1, strategy2, int(args.n), float(args.time), args.stockfish_path, args.lc0_path, int(args.proc) def main(): From 94ed771bb8a74f7e22220a86471e12019762aca4 Mon Sep 17 00:00:00 2001 From: DarkCider <52292032+DarkCider@users.noreply.github.com> Date: Tue, 30 Jan 2024 20:03:27 +0100 Subject: [PATCH 02/10] Fix relative default paths for stockfish and lc0 --- chesspp/lc0_strategy.py | 3 +-- main.py | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/chesspp/lc0_strategy.py b/chesspp/lc0_strategy.py index cb1fe11..d12d746 100644 --- a/chesspp/lc0_strategy.py +++ b/chesspp/lc0_strategy.py @@ -9,7 +9,7 @@ _DIR = os.path.abspath(os.path.dirname(__file__)) class Lc0Strategy(IStrategy): - def __init__(self, path="../lc0/lc0", rollout_depth: int = 4, + def __init__(self, path="lc0/lc0", rollout_depth: int = 4, limit: chess.engine.Limit = chess.engine.Limit(depth=4)): super().__init__(rollout_depth) self._lc0 = None @@ -35,5 +35,4 @@ class Lc0Strategy(IStrategy): def analyze_board(self, board: chess.Board) -> int: score = score_lc0(board, self.lc0) - print("lc0 score", score) return score diff --git a/main.py b/main.py index 80d1330..28f135e 100644 --- a/main.py +++ b/main.py @@ -112,11 +112,11 @@ def read_arguments(): strategies = {"Random": StrategyEnum.Random, "Stockfish": StrategyEnum.Stockfish, "Lc0": StrategyEnum.Lc0} if os.name == 'nt': - stockfish_default = "../stockfish/stockfish-windows-x86-64-avx2" - lc0_default = "../lc0/lc0.exe" + stockfish_default = "stockfish/stockfish-windows-x86-64-avx2" + lc0_default = "lc0/lc0.exe" else: - stockfish_default = "../stockfish/stockfish-ubuntu-x86-64-avx2" - lc0_default = "../lc0/lc0" + stockfish_default = "stockfish/stockfish-ubuntu-x86-64-avx2" + lc0_default = "lc0/lc0" parser.add_argument("--proc", default=2, help="Number of processors to use for simulation, default=1") parser.add_argument("--time", default=0.5, help="Time limit for each simulation step, default=0.5") From e2ddc3868f5447f918cdc37460174fff330851ba Mon Sep 17 00:00:00 2001 From: luk3k Date: Tue, 30 Jan 2024 23:07:00 +0100 Subject: [PATCH 03/10] fixed limit for external engines and added arguments to web.py --- chesspp/engine.py | 11 ++++++++--- chesspp/web.py | 19 ++++++++++--------- web.py | 6 ++++-- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/chesspp/engine.py b/chesspp/engine.py index 0c40a3f..d06d8ee 100644 --- a/chesspp/engine.py +++ b/chesspp/engine.py @@ -1,6 +1,5 @@ import random import time -import os from abc import ABC, abstractmethod from torch import distributions as dist @@ -48,6 +47,12 @@ class Limit: while (time.perf_counter_ns() - start) / 1e9 < self.time: func(*args, **kwargs) + def translate_to_engine_limit(self) -> chess.engine.Limit: + if self.nodes: + return chess.engine.Limit(nodes=self.nodes) + elif self.time: + return chess.engine.Limit(time=self.time) + class Engine(ABC): board: chess.Board @@ -147,7 +152,7 @@ class StockFishEngine(Engine): self.stockfish = chess.engine.SimpleEngine.popen_uci(path) def play(self, board: chess.Board, limit: Limit) -> chess.engine.PlayResult: - return self.stockfish.play(board, limit) + return self.stockfish.play(board, limit.translate_to_engine_limit()) @staticmethod def get_name() -> str: @@ -160,7 +165,7 @@ class Lc0Engine(Engine): self.lc0 = chess.engine.SimpleEngine.popen_uci(path) def play(self, board: chess.Board, limit: Limit) -> chess.engine.PlayResult: - return self.lc0.play(board, limit) + return self.lc0.play(board, limit.translate_to_engine_limit()) @staticmethod def get_name() -> str: diff --git a/chesspp/web.py b/chesspp/web.py index 9a51745..e2a8887 100644 --- a/chesspp/web.py +++ b/chesspp/web.py @@ -6,6 +6,7 @@ from aiohttp import web import chess from chesspp import engine +from chesspp.engine_factory import EngineFactory from chesspp.stockfish_strategy import StockFishStrategy _DIR = os.path.abspath(os.path.dirname(__file__)) @@ -23,12 +24,7 @@ def load_index() -> str: class Simulate: """ Run a simulation of two engines""" - def __init__(self, engine_white=None, engine_black=None): - if engine_white is None: - engine_white = engine.ClassicMctsEngine(chess.WHITE) - if engine_black is None: - engine_black = engine.ClassicMctsEngine(chess.BLACK) - + def __init__(self, engine_white, engine_black): self.white = engine_white self.black = engine_black @@ -44,9 +40,13 @@ class Simulate: class WebInterface: - def __init__(self, white_engine: engine.Engine.__class__, black_engine: engine.Engine.__class__, limit: engine.Limit): + def __init__(self, white_engine, black_engine, strategy1, strategy2, stockfish_path, lc0_path, limit: engine.Limit): self.white = white_engine self.black = black_engine + self.strategy1 = strategy1 + self.strategy2 = strategy2 + self.stockfish_path = stockfish_path + self.lc0_path = lc0_path self.limit = limit @@ -73,8 +73,9 @@ class WebInterface: async def turns(): """ Simulates the game and sends the response to the client """ - runner = Simulate(self.white(chess.Board(), chess.WHITE, StockFishStrategy()), self.black( - chess.Board(), chess.BLACK, StockFishStrategy())).run(self.limit) + white = EngineFactory.create_engine(self.white, self.strategy1, chess.WHITE, self.stockfish_path, self.lc0_path) + black = EngineFactory.create_engine(self.black, self.strategy1, chess.BLACK, self.stockfish_path, self.lc0_path) + runner = Simulate(white, black).run(self.limit) def sim(): return next(runner, None) diff --git a/web.py b/web.py index f2edfd9..5c12365 100644 --- a/web.py +++ b/web.py @@ -1,7 +1,9 @@ from chesspp import engine from chesspp import web +from main import read_arguments if __name__ == '__main__': - limit = engine.Limit(time=0.5) - web.WebInterface(engine.BayesMctsEngine, engine.ClassicMctsEngine, limit).run_app() + engine1, engine2, strategy1, strategy2, n_games, time, stockfish_path, lc0_path, n_proc = read_arguments() + limit = engine.Limit(time=6) + web.WebInterface(engine1, engine2, strategy1, strategy2, stockfish_path, lc0_path, limit).run_app() From 15ce6c5316adc4897cec39567b308a768c092a46 Mon Sep 17 00:00:00 2001 From: luk3k Date: Wed, 31 Jan 2024 10:19:49 +0100 Subject: [PATCH 04/10] added random_stockfish_strategy.py --- chesspp/engine_factory.py | 8 +++++++ chesspp/random_stockfish_strategy.py | 36 ++++++++++++++++++++++++++++ chesspp/stockfish_strategy.py | 1 - main.py | 11 +++++---- web.py | 2 +- 5 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 chesspp/random_stockfish_strategy.py diff --git a/chesspp/engine_factory.py b/chesspp/engine_factory.py index c257ba5..7112be4 100644 --- a/chesspp/engine_factory.py +++ b/chesspp/engine_factory.py @@ -4,6 +4,7 @@ from chesspp.engine import * from chesspp.lc0_strategy import Lc0Strategy from chesspp.random_strategy import RandomStrategy from chesspp.stockfish_strategy import StockFishStrategy +from chesspp.random_stockfish_strategy import RandomStockfishStrategy from chesspp.i_strategy import IStrategy import chess @@ -20,6 +21,7 @@ class StrategyEnum(Enum): Stockfish = 0 Lc0 = 1 Random = 2 + RandomStockfish = 3 class EngineFactory: @@ -33,6 +35,8 @@ class EngineFactory: strategy = EngineFactory._get_lc0_strategy(lc0_path, rollout_depth) case StrategyEnum.Random: strategy = EngineFactory._get_random_strategy(rollout_depth) + case StrategyEnum.RandomStockfish: + strategy = EngineFactory._get_random_stockfish_strategy(stockfish_path, rollout_depth) match engine_name: case EngineEnum.ClassicMcts: @@ -71,6 +75,10 @@ class EngineFactory: def _get_stockfish_strategy(engine_path: str, rollout_depth: int) -> IStrategy: return StockFishStrategy(engine_path, rollout_depth) + @staticmethod + def _get_random_stockfish_strategy(engine_path: str, rollout_depth: int) -> IStrategy: + return RandomStockfishStrategy(rollout_depth, engine_path) + @staticmethod def _get_lc0_strategy(engine_path: str, rollout_depth: int) -> IStrategy: return Lc0Strategy(engine_path, rollout_depth) diff --git a/chesspp/random_stockfish_strategy.py b/chesspp/random_stockfish_strategy.py new file mode 100644 index 0000000..f6e0708 --- /dev/null +++ b/chesspp/random_stockfish_strategy.py @@ -0,0 +1,36 @@ +import random + +import chess +import chess.engine + +from chesspp.i_strategy import IStrategy +from chesspp.eval import score_stockfish + + +class RandomStockfishStrategy(IStrategy): + def __init__(self, rollout_depth: int, path="../stockfish/stockfish-windows-x86-64-avx2", + random_seed: random.Random = random.Random()) -> None: + super().__init__(rollout_depth) + self._stockfish = None + self.path = path + self.random_seed = random_seed + + def __del__(self): + if self._stockfish is not None: + self._stockfish.quit() + + @property + def stockfish(self) -> chess.engine.SimpleEngine: + if self._stockfish is None: + self._stockfish = self.stockfish = chess.engine.SimpleEngine.popen_uci(self.path) + return self._stockfish + + @stockfish.setter + def stockfish(self, stockfish): + self._stockfish = stockfish + + def pick_next_move(self, board: chess.Board) -> chess.Move: + return self.random_seed.choice(list(board.legal_moves)) + + def analyze_board(self, board: chess.Board) -> int: + return score_stockfish(board, self.stockfish) diff --git a/chesspp/stockfish_strategy.py b/chesspp/stockfish_strategy.py index 0a089f2..dd2fdf2 100644 --- a/chesspp/stockfish_strategy.py +++ b/chesspp/stockfish_strategy.py @@ -1,4 +1,3 @@ -import os import chess from chesspp.i_strategy import IStrategy from chesspp.eval import score_stockfish diff --git a/main.py b/main.py index 28f135e..ef8d05c 100644 --- a/main.py +++ b/main.py @@ -44,7 +44,7 @@ def test_bayes_mcts(): t1 = time.time_ns() mcts.sample(1) t2 = time.time_ns() - print ((t2 - t1)/1e6) + print((t2 - t1) / 1e6) mcts.print() for move, score in mcts.get_moves().items(): print("move (mcts):", move, " with score:", score) @@ -109,7 +109,8 @@ def read_arguments(): engines = {"ClassicMCTS": EngineEnum.ClassicMcts, "BayesianMCTS": EngineEnum.BayesianMcts, "Random": EngineEnum.Random, "Stockfish": EngineEnum.Stockfish, "Lc0": EngineEnum.Lc0} - strategies = {"Random": StrategyEnum.Random, "Stockfish": StrategyEnum.Stockfish, "Lc0": StrategyEnum.Lc0} + strategies = {"Random": StrategyEnum.Random, "Stockfish": StrategyEnum.Stockfish, "Lc0": StrategyEnum.Lc0, + "RandomStockfish": StrategyEnum.RandomStockfish} if os.name == 'nt': stockfish_default = "stockfish/stockfish-windows-x86-64-avx2" @@ -140,8 +141,10 @@ def read_arguments(): strategy1 = strategies[args.strategy1] strategy2 = strategies[args.strategy2] - print(engine1, engine2, strategy1, strategy2, int(args.n), float(args.time), args.stockfish_path, args.lc0_path, int(args.proc)) - return engine1, engine2, strategy1, strategy2, int(args.n), float(args.time), args.stockfish_path, args.lc0_path, int(args.proc) + print(engine1, engine2, strategy1, strategy2, int(args.n), float(args.time), args.stockfish_path, args.lc0_path, + int(args.proc)) + return engine1, engine2, strategy1, strategy2, int(args.n), float( + args.time), args.stockfish_path, args.lc0_path, int(args.proc) def main(): diff --git a/web.py b/web.py index 5c12365..0355157 100644 --- a/web.py +++ b/web.py @@ -5,5 +5,5 @@ from main import read_arguments if __name__ == '__main__': engine1, engine2, strategy1, strategy2, n_games, time, stockfish_path, lc0_path, n_proc = read_arguments() - limit = engine.Limit(time=6) + limit = engine.Limit(time=0.5) web.WebInterface(engine1, engine2, strategy1, strategy2, stockfish_path, lc0_path, limit).run_app() From 2e3fdb62e9d576755da226ecc10131783660b574 Mon Sep 17 00:00:00 2001 From: dreistein Date: Tue, 30 Jan 2024 16:11:44 +0100 Subject: [PATCH 05/10] run games on same core for --proc 1 --- chesspp/simulation.py | 14 +++++++----- chesspp/util_gaussian.py | 48 ++++++++++++++++------------------------ main.py | 6 +++++ 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/chesspp/simulation.py b/chesspp/simulation.py index 98cf94c..52c25f2 100644 --- a/chesspp/simulation.py +++ b/chesspp/simulation.py @@ -48,11 +48,15 @@ class Evaluation: def run(self, n_games=100, proc=mp.cpu_count()) -> List[EvaluationResult]: proc = min(proc, mp.cpu_count()) - with mp.Pool(proc) as pool: - args = [(self.engine_a, self.strategy_a, self.engine_b, self.strategy_b, self.limit, self.stockfish_path, self.lc0_path) for i - in - range(n_games)] - return pool.map(Evaluation._test_simulate, args) + arg = (self.engine_a, self.strategy_a, self.engine_b, self.strategy_b, self.limit, self.stockfish_path, self.lc0_path) + if proc > 1: + with mp.Pool(proc) as pool: + args = [arg for i in range(n_games)] + return pool.map(Evaluation._test_simulate, args) + return [ + Evaluation._test_simulate(arg) + for _ in range(n_games) + ] @staticmethod def _test_simulate(arg: Tuple[EngineEnum, StrategyEnum, EngineEnum, StrategyEnum, Limit, str, str]) -> EvaluationResult: diff --git a/chesspp/util_gaussian.py b/chesspp/util_gaussian.py index e6696d3..fef0f15 100644 --- a/chesspp/util_gaussian.py +++ b/chesspp/util_gaussian.py @@ -1,12 +1,14 @@ import math +from typing import Tuple, Dict +from functools import cache import torch import torch.distributions as dist from torch import exp -F1: dict[float, float] = {} -F2: dict[float, float] = {} -CDF: dict[float, float] = {} +F1: Dict[float, float] = {} +F2: Dict[float, float] = {} +CDF: Dict[float, float] = {} lookup_count = 0 @@ -15,7 +17,17 @@ def get_lookup_count(): return lookup_count -def max_gaussian(mu1, sigma1, mu2, sigma2) -> tuple[float, float]: +@cache +def calc_cdf(alpha: float) -> Tuple[float, float, float]: + normal = dist.Normal(0, 1) + cdf_alpha = normal.cdf(torch.tensor(alpha)).item() + pdf_alpha = exp(normal.log_prob(torch.tensor(alpha))).item() + f1 = alpha * cdf_alpha + pdf_alpha + f2 = alpha ** 2 * cdf_alpha * (1 - cdf_alpha) + ( + 1 - 2 * cdf_alpha) * alpha * pdf_alpha - pdf_alpha ** 2 + return cdf_alpha, f1, f2 + +def max_gaussian(mu1, sigma1, mu2, sigma2) -> Tuple[float, float]: global lookup_count global F1 global F2 @@ -31,33 +43,11 @@ def max_gaussian(mu1, sigma1, mu2, sigma2) -> tuple[float, float]: """ # we assume independence of the two gaussians #print(mu1, sigma1, mu2, sigma2) - normal = dist.Normal(0, 1) + #normal = dist.Normal(0, 1) sigma_m = math.sqrt(sigma1 ** 2 + sigma2 ** 2) - alpha = (mu1 - mu2) / sigma_m + alpha = round((mu1 - mu2) / sigma_m, 2) - if alpha in CDF: - cdf_alpha = CDF[alpha] - lookup_count += 1 - else: - cdf_alpha = normal.cdf(torch.tensor(alpha)).item() - CDF[alpha] = cdf_alpha - - pdf_alpha = exp(normal.log_prob(torch.tensor(alpha))).item() - - if alpha in F1: - f1_alpha = F1[alpha] - lookup_count += 1 - else: - f1_alpha = alpha * cdf_alpha + pdf_alpha - F1[alpha] = f1_alpha - - if alpha in F2: - f2_alpha = F2[alpha] - lookup_count += 1 - else: - f2_alpha = alpha ** 2 * cdf_alpha * (1 - cdf_alpha) + ( - 1 - 2 * cdf_alpha) * alpha * pdf_alpha - pdf_alpha ** 2 - F2[alpha] = f2_alpha + cdf_alpha, f1_alpha, f2_alpha = calc_cdf(alpha) mu = mu2 + sigma_m * f1_alpha sigma = math.sqrt(sigma2 ** 2 + (sigma1 ** 2 - sigma2 ** 2) * cdf_alpha + sigma_m ** 2 * f2_alpha) diff --git a/main.py b/main.py index ef8d05c..a33262f 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import random +import sys import time import chess import chess.engine @@ -158,3 +159,8 @@ def main(): if __name__ == '__main__': main() + + # Note: prevent endless wait on StockFish process + # by allowing for cleanup of objects (which closes stockfish) + import gc + gc.collect() From a5393d793e9685c2ba90367c3eee81efcd792187 Mon Sep 17 00:00:00 2001 From: Stefan Steininger Date: Wed, 31 Jan 2024 11:47:40 +0100 Subject: [PATCH 06/10] remove pre-py38 type annotations --- chesspp/util_gaussian.py | 44 ++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/chesspp/util_gaussian.py b/chesspp/util_gaussian.py index fef0f15..f2170aa 100644 --- a/chesspp/util_gaussian.py +++ b/chesspp/util_gaussian.py @@ -1,38 +1,37 @@ import math -from typing import Tuple, Dict from functools import cache import torch import torch.distributions as dist from torch import exp -F1: Dict[float, float] = {} -F2: Dict[float, float] = {} -CDF: Dict[float, float] = {} -lookup_count = 0 +total_count = 0 +calculation_count = 0 def get_lookup_count(): - global lookup_count - return lookup_count + global total_count, calculation_count + return total_count - calculation_count @cache -def calc_cdf(alpha: float) -> Tuple[float, float, float]: +def calc_cdf(alpha: float) -> tuple[float, float, float]: + """ + Returns the calculated CDF and parameters f1,f2 from the input alpha + """ + global calculation_count + calculation_count += 1 + normal = dist.Normal(0, 1) cdf_alpha = normal.cdf(torch.tensor(alpha)).item() pdf_alpha = exp(normal.log_prob(torch.tensor(alpha))).item() f1 = alpha * cdf_alpha + pdf_alpha f2 = alpha ** 2 * cdf_alpha * (1 - cdf_alpha) + ( - 1 - 2 * cdf_alpha) * alpha * pdf_alpha - pdf_alpha ** 2 + 1 - 2 * cdf_alpha) * alpha * pdf_alpha - pdf_alpha ** 2 return cdf_alpha, f1, f2 -def max_gaussian(mu1, sigma1, mu2, sigma2) -> Tuple[float, float]: - global lookup_count - global F1 - global F2 - global CDF +def max_gaussian(mu1, sigma1, mu2, sigma2) -> tuple[float, float]: """ Returns the combined max gaussian of two Gaussians represented by mu1, sigma1, mu2, simga2 :param mu1: mu of the first Gaussian @@ -41,17 +40,22 @@ def max_gaussian(mu1, sigma1, mu2, sigma2) -> Tuple[float, float]: :param sigma2: sigma of the second Gaussian :return: mu and sigma maximized """ + global total_count + total_count += 1 + # we assume independence of the two gaussians - #print(mu1, sigma1, mu2, sigma2) - #normal = dist.Normal(0, 1) + # print(mu1, sigma1, mu2, sigma2) + # normal = dist.Normal(0, 1) sigma_m = math.sqrt(sigma1 ** 2 + sigma2 ** 2) + + # round to two significant digits to enable float lookup alpha = round((mu1 - mu2) / sigma_m, 2) cdf_alpha, f1_alpha, f2_alpha = calc_cdf(alpha) mu = mu2 + sigma_m * f1_alpha sigma = math.sqrt(sigma2 ** 2 + (sigma1 ** 2 - sigma2 ** 2) * cdf_alpha + sigma_m ** 2 * f2_alpha) - #sigma = math.sqrt((mu1**2 + sigma1**2) * cdf_alpha + (mu2**2 + sigma2**2) * (1 - cdf_alpha) + (mu1 + mu2) * sigma_m * pdf_alpha - mu**2) + # sigma = math.sqrt((mu1**2 + sigma1**2) * cdf_alpha + (mu2**2 + sigma2**2) * (1 - cdf_alpha) + (mu1 + mu2) * sigma_m * pdf_alpha - mu**2) return mu, sigma @@ -75,20 +79,20 @@ def min_gaussian(mu1, sigma1, mu2, sigma2) -> tuple[float, float]: pdf_alpha_neg = exp(normal.log_prob(torch.tensor(-alpha))).item() mu = mu1 * (1 - cdf_alpha) + mu2 * cdf_alpha - pdf_alpha_neg * sigma_m - sigma = math.sqrt((mu1**2 + sigma1**2) * (1 - cdf_alpha) + (mu2**2 + sigma2**2) * cdf_alpha - (mu1 + mu2) * sigma_m * pdf_alpha - mu**2) + sigma = math.sqrt((mu1 ** 2 + sigma1 ** 2) * (1 - cdf_alpha) + (mu2 ** 2 + sigma2 ** 2) * cdf_alpha - ( + mu1 + mu2) * sigma_m * pdf_alpha - mu ** 2) return mu, sigma except ValueError: print(mu1, sigma1, mu2, sigma2) - def beta_mean(alpha, beta): return alpha / (alpha + beta) def beta_std(alpha, beta): try: - return math.sqrt((alpha * beta) / ((alpha * beta)**2 * (alpha + beta + 1))) + return math.sqrt((alpha * beta) / ((alpha * beta) ** 2 * (alpha + beta + 1))) except ZeroDivisionError: print(alpha, beta) From 009fd428b2f33896f1042f0c76f8079b9ee233d0 Mon Sep 17 00:00:00 2001 From: Stefan Steininger Date: Tue, 30 Jan 2024 20:06:20 +0100 Subject: [PATCH 07/10] add experimental pesto scoring --- chesspp/engine_factory.py | 9 ++ chesspp/eval_pesto.py | 271 ++++++++++++++++++++++++++++++++++++++ main.py | 2 +- 3 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 chesspp/eval_pesto.py diff --git a/chesspp/engine_factory.py b/chesspp/engine_factory.py index 7112be4..f402157 100644 --- a/chesspp/engine_factory.py +++ b/chesspp/engine_factory.py @@ -5,6 +5,7 @@ from chesspp.lc0_strategy import Lc0Strategy from chesspp.random_strategy import RandomStrategy from chesspp.stockfish_strategy import StockFishStrategy from chesspp.random_stockfish_strategy import RandomStockfishStrategy +from chesspp.eval_pesto import PestoStrategy from chesspp.i_strategy import IStrategy import chess @@ -22,6 +23,7 @@ class StrategyEnum(Enum): Lc0 = 1 Random = 2 RandomStockfish = 3 + Pestos = 4 class EngineFactory: @@ -37,6 +39,8 @@ class EngineFactory: strategy = EngineFactory._get_random_strategy(rollout_depth) case StrategyEnum.RandomStockfish: strategy = EngineFactory._get_random_stockfish_strategy(stockfish_path, rollout_depth) + case StrategyEnum.Pestos: + strategy = EngineFactory._get_pesto_strategy(rollout_depth) match engine_name: case EngineEnum.ClassicMcts: @@ -82,3 +86,8 @@ class EngineFactory: @staticmethod def _get_lc0_strategy(engine_path: str, rollout_depth: int) -> IStrategy: return Lc0Strategy(engine_path, rollout_depth) + + @staticmethod + def _get_pesto_strategy(rollout_depth: int) -> IStrategy: + return PestoStrategy(rollout_depth) + diff --git a/chesspp/eval_pesto.py b/chesspp/eval_pesto.py new file mode 100644 index 0000000..4aed934 --- /dev/null +++ b/chesspp/eval_pesto.py @@ -0,0 +1,271 @@ +import chess.engine +import chess +from chesspp.i_strategy import IStrategy + + +# Scoring based on PeSTO (Piece-Square Tables Only) Evaluation Functions +# https://www.chessprogramming.org/PeSTO%27s_Evaluation_Function + + + +PAWN = 0 +KNIGHT = 1 +BISHOP = 2 +ROOK = 3 +QUEEN = 4 +KING = 5 + +# board representation +WHITE = 0 +BLACK = 1 + +WHITE_PAWN = (2*PAWN + WHITE) +BLACK_PAWN = (2*PAWN + BLACK) +WHITE_KNIGHT = (2*KNIGHT + WHITE) +BLACK_KNIGHT = (2*KNIGHT + BLACK) +WHITE_BISHOP = (2*BISHOP + WHITE) +BLACK_BISHOP = (2*BISHOP + BLACK) +WHITE_ROOK = (2*ROOK + WHITE) +BLACK_ROOK = (2*ROOK + BLACK) +WHITE_QUEEN = (2*QUEEN + WHITE) +BLACK_QUEEN = (2*QUEEN + BLACK) +WHITE_KING = (2*KING + WHITE) +BLACK_KING = (2*KING + BLACK) +EMPTY = (BLACK_KING + 1) + +mg_value = [82, 337, 365, 477, 1025, 0] +eg_value = [94, 281, 297, 512, 936, 0] + + +FLIP = lambda sq: (sq^56) +OTHER = lambda side: (side^1) + + +mg_pawn_table = [ + 0, 0, 0, 0, 0, 0, 0, 0, + 98, 134, 61, 95, 68, 126, 34, -11, + -6, 7, 26, 31, 65, 56, 25, -20, + -14, 13, 6, 21, 23, 12, 17, -23, + -27, -2, -5, 12, 17, 6, 10, -25, + -26, -4, -4, -10, 3, 3, 33, -12, + -35, -1, -20, -23, -15, 24, 38, -22, + 0, 0, 0, 0, 0, 0, 0, 0, +] + +eg_pawn_table = [ + 0, 0, 0, 0, 0, 0, 0, 0, + 178, 173, 158, 134, 147, 132, 165, 187, + 94, 100, 85, 67, 56, 53, 82, 84, + 32, 24, 13, 5, -2, 4, 17, 17, + 13, 9, -3, -7, -7, -8, 3, -1, + 4, 7, -6, 1, 0, -5, -1, -8, + 13, 8, 8, 10, 13, 0, 2, -7, + 0, 0, 0, 0, 0, 0, 0, 0, +] + +mg_knight_table = [ + -167, -89, -34, -49, 61, -97, -15, -107, + -73, -41, 72, 36, 23, 62, 7, -17, + -47, 60, 37, 65, 84, 129, 73, 44, + -9, 17, 19, 53, 37, 69, 18, 22, + -13, 4, 16, 13, 28, 19, 21, -8, + -23, -9, 12, 10, 19, 17, 25, -16, + -29, -53, -12, -3, -1, 18, -14, -19, + -105, -21, -58, -33, -17, -28, -19, -23, +] + +eg_knight_table = [ + -58, -38, -13, -28, -31, -27, -63, -99, + -25, -8, -25, -2, -9, -25, -24, -52, + -24, -20, 10, 9, -1, -9, -19, -41, + -17, 3, 22, 22, 22, 11, 8, -18, + -18, -6, 16, 25, 16, 17, 4, -18, + -23, -3, -1, 15, 10, -3, -20, -22, + -42, -20, -10, -5, -2, -20, -23, -44, + -29, -51, -23, -15, -22, -18, -50, -64, +] + +mg_bishop_table = [ + -29, 4, -82, -37, -25, -42, 7, -8, + -26, 16, -18, -13, 30, 59, 18, -47, + -16, 37, 43, 40, 35, 50, 37, -2, + -4, 5, 19, 50, 37, 37, 7, -2, + -6, 13, 13, 26, 34, 12, 10, 4, + 0, 15, 15, 15, 14, 27, 18, 10, + 4, 15, 16, 0, 7, 21, 33, 1, + -33, -3, -14, -21, -13, -12, -39, -21, +] + +eg_bishop_table = [ + -14, -21, -11, -8, -7, -9, -17, -24, + -8, -4, 7, -12, -3, -13, -4, -14, + 2, -8, 0, -1, -2, 6, 0, 4, + -3, 9, 12, 9, 14, 10, 3, 2, + -6, 3, 13, 19, 7, 10, -3, -9, + -12, -3, 8, 10, 13, 3, -7, -15, + -14, -18, -7, -1, 4, -9, -15, -27, + -23, -9, -23, -5, -9, -16, -5, -17, +] + +mg_rook_table = [ + 32, 42, 32, 51, 63, 9, 31, 43, + 27, 32, 58, 62, 80, 67, 26, 44, + -5, 19, 26, 36, 17, 45, 61, 16, + -24, -11, 7, 26, 24, 35, -8, -20, + -36, -26, -12, -1, 9, -7, 6, -23, + -45, -25, -16, -17, 3, 0, -5, -33, + -44, -16, -20, -9, -1, 11, -6, -71, + -19, -13, 1, 17, 16, 7, -37, -26, +] + +eg_rook_table = [ + 13, 10, 18, 15, 12, 12, 8, 5, + 11, 13, 13, 11, -3, 3, 8, 3, + 7, 7, 7, 5, 4, -3, -5, -3, + 4, 3, 13, 1, 2, 1, -1, 2, + 3, 5, 8, 4, -5, -6, -8, -11, + -4, 0, -5, -1, -7, -12, -8, -16, + -6, -6, 0, 2, -9, -9, -11, -3, + -9, 2, 3, -1, -5, -13, 4, -20, +] + +mg_queen_table = [ + -28, 0, 29, 12, 59, 44, 43, 45, + -24, -39, -5, 1, -16, 57, 28, 54, + -13, -17, 7, 8, 29, 56, 47, 57, + -27, -27, -16, -16, -1, 17, -2, 1, + -9, -26, -9, -10, -2, -4, 3, -3, + -14, 2, -11, -2, -5, 2, 14, 5, + -35, -8, 11, 2, 8, 15, -3, 1, + -1, -18, -9, 10, -15, -25, -31, -50, +] + +eg_queen_table = [ + -9, 22, 22, 27, 27, 19, 10, 20, + -17, 20, 32, 41, 58, 25, 30, 0, + -20, 6, 9, 49, 47, 35, 19, 9, + 3, 22, 24, 45, 57, 40, 57, 36, + -18, 28, 19, 47, 31, 34, 39, 23, + -16, -27, 15, 6, 9, 17, 10, 5, + -22, -23, -30, -16, -16, -23, -36, -32, + -33, -28, -22, -43, -5, -32, -20, -41, +] + +mg_king_table = [ + -65, 23, 16, -15, -56, -34, 2, 13, + 29, -1, -20, -7, -8, -4, -38, -29, + -9, 24, 2, -16, -20, 6, 22, -22, + -17, -20, -12, -27, -30, -25, -14, -36, + -49, -1, -27, -39, -46, -44, -33, -51, + -14, -14, -22, -46, -44, -30, -15, -27, + 1, 7, -8, -64, -43, -16, 9, 8, + -15, 36, 12, -54, 8, -28, 24, 14, +] + +eg_king_table = [ + -74, -35, -18, -18, -11, 15, 4, -17, + -12, 17, 14, 17, 17, 38, 23, 11, + 10, 17, 23, 15, 20, 45, 44, 13, + -8, 22, 24, 27, 26, 33, 26, 3, + -18, -4, 21, 24, 27, 23, 9, -11, + -19, -3, 11, 21, 23, 16, 7, -9, + -27, -11, 4, 13, 14, 4, -5, -17, + -53, -34, -21, -11, -28, -14, -24, -43 +] + +mg_pesto_table = [ + mg_pawn_table, + mg_knight_table, + mg_bishop_table, + mg_rook_table, + mg_queen_table, + mg_king_table +] + +eg_pesto_table = [ + eg_pawn_table, + eg_knight_table, + eg_bishop_table, + eg_rook_table, + eg_queen_table, + eg_king_table +] + +gamephaseInc = [0,0,1,1,1,1,2,2,4,4,0,0] + +mg_table = [ + [82, 82, 82, 82, 82, 82, 82, 82, 180, 216, 143, 177, 150, 208, 116, 71, 76, 89, 108, 113, 147, 138, 107, 62, 68, 95, 88, 103, 105, 94, 99, 59, 55, 80, 77, 94, 99, 88, 92, 57, 56, 78, 78, 72, 85, 85, 115, 70, 47, 81, 62, 59, 67, 106, 120, 60, 82, 82, 82, 82, 82, 82, 82, 82, ], + [82, 82, 82, 82, 82, 82, 82, 82, 47, 81, 62, 59, 67, 106, 120, 60, 56, 78, 78, 72, 85, 85, 115, 70, 55, 80, 77, 94, 99, 88, 92, 57, 68, 95, 88, 103, 105, 94, 99, 59, 76, 89, 108, 113, 147, 138, 107, 62, 180, 216, 143, 177, 150, 208, 116, 71, 82, 82, 82, 82, 82, 82, 82, 82, ], + [170, 248, 303, 288, 398, 240, 322, 230, 264, 296, 409, 373, 360, 399, 344, 320, 290, 397, 374, 402, 421, 466, 410, 381, 328, 354, 356, 390, 374, 406, 355, 359, 324, 341, 353, 350, 365, 356, 358, 329, 314, 328, 349, 347, 356, 354, 362, 321, 308, 284, 325, 334, 336, 355, 323, 318, 232, 316, 279, 304, 320, 309, 318, 314, ], + [232, 316, 279, 304, 320, 309, 318, 314, 308, 284, 325, 334, 336, 355, 323, 318, 314, 328, 349, 347, 356, 354, 362, 321, 324, 341, 353, 350, 365, 356, 358, 329, 328, 354, 356, 390, 374, 406, 355, 359, 290, 397, 374, 402, 421, 466, 410, 381, 264, 296, 409, 373, 360, 399, 344, 320, 170, 248, 303, 288, 398, 240, 322, 230, ], + [336, 369, 283, 328, 340, 323, 372, 357, 339, 381, 347, 352, 395, 424, 383, 318, 349, 402, 408, 405, 400, 415, 402, 363, 361, 370, 384, 415, 402, 402, 372, 363, 359, 378, 378, 391, 399, 377, 375, 369, 365, 380, 380, 380, 379, 392, 383, 375, 369, 380, 381, 365, 372, 386, 398, 366, 332, 362, 351, 344, 352, 353, 326, 344, ], + [332, 362, 351, 344, 352, 353, 326, 344, 369, 380, 381, 365, 372, 386, 398, 366, 365, 380, 380, 380, 379, 392, 383, 375, 359, 378, 378, 391, 399, 377, 375, 369, 361, 370, 384, 415, 402, 402, 372, 363, 349, 402, 408, 405, 400, 415, 402, 363, 339, 381, 347, 352, 395, 424, 383, 318, 336, 369, 283, 328, 340, 323, 372, 357, ], + [509, 519, 509, 528, 540, 486, 508, 520, 504, 509, 535, 539, 557, 544, 503, 521, 472, 496, 503, 513, 494, 522, 538, 493, 453, 466, 484, 503, 501, 512, 469, 457, 441, 451, 465, 476, 486, 470, 483, 454, 432, 452, 461, 460, 480, 477, 472, 444, 433, 461, 457, 468, 476, 488, 471, 406, 458, 464, 478, 494, 493, 484, 440, 451, ], + [458, 464, 478, 494, 493, 484, 440, 451, 433, 461, 457, 468, 476, 488, 471, 406, 432, 452, 461, 460, 480, 477, 472, 444, 441, 451, 465, 476, 486, 470, 483, 454, 453, 466, 484, 503, 501, 512, 469, 457, 472, 496, 503, 513, 494, 522, 538, 493, 504, 509, 535, 539, 557, 544, 503, 521, 509, 519, 509, 528, 540, 486, 508, 520, ], + [997, 1025, 1054, 1037, 1084, 1069, 1068, 1070, 1001, 986, 1020, 1026, 1009, 1082, 1053, 1079, 1012, 1008, 1032, 1033, 1054, 1081, 1072, 1082, 998, 998, 1009, 1009, 1024, 1042, 1023, 1026, 1016, 999, 1016, 1015, 1023, 1021, 1028, 1022, 1011, 1027, 1014, 1023, 1020, 1027, 1039, 1030, 990, 1017, 1036, 1027, 1033, 1040, 1022, 1026, 1024, 1007, 1016, 1035, 1010, 1000, 994, 975, ], + [1024, 1007, 1016, 1035, 1010, 1000, 994, 975, 990, 1017, 1036, 1027, 1033, 1040, 1022, 1026, 1011, 1027, 1014, 1023, 1020, 1027, 1039, 1030, 1016, 999, 1016, 1015, 1023, 1021, 1028, 1022, 998, 998, 1009, 1009, 1024, 1042, 1023, 1026, 1012, 1008, 1032, 1033, 1054, 1081, 1072, 1082, 1001, 986, 1020, 1026, 1009, 1082, 1053, 1079, 997, 1025, 1054, 1037, 1084, 1069, 1068, 1070, ], + [-65, 23, 16, -15, -56, -34, 2, 13, 29, -1, -20, -7, -8, -4, -38, -29, -9, 24, 2, -16, -20, 6, 22, -22, -17, -20, -12, -27, -30, -25, -14, -36, -49, -1, -27, -39, -46, -44, -33, -51, -14, -14, -22, -46, -44, -30, -15, -27, 1, 7, -8, -64, -43, -16, 9, 8, -15, 36, 12, -54, 8, -28, 24, 14, ], + [-15, 36, 12, -54, 8, -28, 24, 14, 1, 7, -8, -64, -43, -16, 9, 8, -14, -14, -22, -46, -44, -30, -15, -27, -49, -1, -27, -39, -46, -44, -33, -51, -17, -20, -12, -27, -30, -25, -14, -36, -9, 24, 2, -16, -20, 6, 22, -22, 29, -1, -20, -7, -8, -4, -38, -29, -65, 23, 16, -15, -56, -34, 2, 13, ] +] + +eg_table = [ + [94, 94, 94, 94, 94, 94, 94, 94, 272, 267, 252, 228, 241, 226, 259, 281, 188, 194, 179, 161, 150, 147, 176, 178, 126, 118, 107, 99, 92, 98, 111, 111, 107, 103, 91, 87, 87, 86, 97, 93, 98, 101, 88, 95, 94, 89, 93, 86, 107, 102, 102, 104, 107, 94, 96, 87, 94, 94, 94, 94, 94, 94, 94, 94, ], + [94, 94, 94, 94, 94, 94, 94, 94, 107, 102, 102, 104, 107, 94, 96, 87, 98, 101, 88, 95, 94, 89, 93, 86, 107, 103, 91, 87, 87, 86, 97, 93, 126, 118, 107, 99, 92, 98, 111, 111, 188, 194, 179, 161, 150, 147, 176, 178, 272, 267, 252, 228, 241, 226, 259, 281, 94, 94, 94, 94, 94, 94, 94, 94, ], + [223, 243, 268, 253, 250, 254, 218, 182, 256, 273, 256, 279, 272, 256, 257, 229, 257, 261, 291, 290, 280, 272, 262, 240, 264, 284, 303, 303, 303, 292, 289, 263, 263, 275, 297, 306, 297, 298, 285, 263, 258, 278, 280, 296, 291, 278, 261, 259, 239, 261, 271, 276, 279, 261, 258, 237, 252, 230, 258, 266, 259, 263, 231, 217, ], + [252, 230, 258, 266, 259, 263, 231, 217, 239, 261, 271, 276, 279, 261, 258, 237, 258, 278, 280, 296, 291, 278, 261, 259, 263, 275, 297, 306, 297, 298, 285, 263, 264, 284, 303, 303, 303, 292, 289, 263, 257, 261, 291, 290, 280, 272, 262, 240, 256, 273, 256, 279, 272, 256, 257, 229, 223, 243, 268, 253, 250, 254, 218, 182, ], + [283, 276, 286, 289, 290, 288, 280, 273, 289, 293, 304, 285, 294, 284, 293, 283, 299, 289, 297, 296, 295, 303, 297, 301, 294, 306, 309, 306, 311, 307, 300, 299, 291, 300, 310, 316, 304, 307, 294, 288, 285, 294, 305, 307, 310, 300, 290, 282, 283, 279, 290, 296, 301, 288, 282, 270, 274, 288, 274, 292, 288, 281, 292, 280, ], + [274, 288, 274, 292, 288, 281, 292, 280, 283, 279, 290, 296, 301, 288, 282, 270, 285, 294, 305, 307, 310, 300, 290, 282, 291, 300, 310, 316, 304, 307, 294, 288, 294, 306, 309, 306, 311, 307, 300, 299, 299, 289, 297, 296, 295, 303, 297, 301, 289, 293, 304, 285, 294, 284, 293, 283, 283, 276, 286, 289, 290, 288, 280, 273, ], + [525, 522, 530, 527, 524, 524, 520, 517, 523, 525, 525, 523, 509, 515, 520, 515, 519, 519, 519, 517, 516, 509, 507, 509, 516, 515, 525, 513, 514, 513, 511, 514, 515, 517, 520, 516, 507, 506, 504, 501, 508, 512, 507, 511, 505, 500, 504, 496, 506, 506, 512, 514, 503, 503, 501, 509, 503, 514, 515, 511, 507, 499, 516, 492, ], + [503, 514, 515, 511, 507, 499, 516, 492, 506, 506, 512, 514, 503, 503, 501, 509, 508, 512, 507, 511, 505, 500, 504, 496, 515, 517, 520, 516, 507, 506, 504, 501, 516, 515, 525, 513, 514, 513, 511, 514, 519, 519, 519, 517, 516, 509, 507, 509, 523, 525, 525, 523, 509, 515, 520, 515, 525, 522, 530, 527, 524, 524, 520, 517, ], + [927, 958, 958, 963, 963, 955, 946, 956, 919, 956, 968, 977, 994, 961, 966, 936, 916, 942, 945, 985, 983, 971, 955, 945, 939, 958, 960, 981, 993, 976, 993, 972, 918, 964, 955, 983, 967, 970, 975, 959, 920, 909, 951, 942, 945, 953, 946, 941, 914, 913, 906, 920, 920, 913, 900, 904, 903, 908, 914, 893, 931, 904, 916, 895, ], + [903, 908, 914, 893, 931, 904, 916, 895, 914, 913, 906, 920, 920, 913, 900, 904, 920, 909, 951, 942, 945, 953, 946, 941, 918, 964, 955, 983, 967, 970, 975, 959, 939, 958, 960, 981, 993, 976, 993, 972, 916, 942, 945, 985, 983, 971, 955, 945, 919, 956, 968, 977, 994, 961, 966, 936, 927, 958, 958, 963, 963, 955, 946, 956, ], + [-74, -35, -18, -18, -11, 15, 4, -17, -12, 17, 14, 17, 17, 38, 23, 11, 10, 17, 23, 15, 20, 45, 44, 13, -8, 22, 24, 27, 26, 33, 26, 3, -18, -4, 21, 24, 27, 23, 9, -11, -19, -3, 11, 21, 23, 16, 7, -9, -27, -11, 4, 13, 14, 4, -5, -17, -53, -34, -21, -11, -28, -14, -24, -43, ], + [-53, -34, -21, -11, -28, -14, -24, -43, -27, -11, 4, 13, 14, 4, -5, -17, -19, -3, 11, 21, 23, 16, 7, -9, -18, -4, 21, 24, 27, 23, 9, -11, -8, 22, 24, 27, 26, 33, 26, 3, 10, 17, 23, 15, 20, 45, 44, 13, -12, 17, 14, 17, 17, 38, 23, 11, -74, -35, -18, -18, -11, 15, 4, -17, ] +] + + +def score(board: chess.Board) -> int: + mg = [0, 0] + eg = [0, 0] + game_phase = 0 + + # evaluate each piece + for sq in range(64): + pc = board.piece_at(sq) + if pc is not None: + color = 0 if pc.color == chess.WHITE else 1 + piece_index = pc.piece_type-1 + mg[color] += mg_table[piece_index][sq] + eg[color] += eg_table[piece_index][sq] + game_phase += gamephaseInc[piece_index] + + # tapered eval + side2move = 0 if board.turn == chess.WHITE else 1 + mg_score = mg[side2move] - mg[OTHER(side2move)] + eg_score = eg[side2move] - eg[OTHER(side2move)] + mg_phase = game_phase + if mg_phase > 24: + # in case of early promotion + mg_phase = 24 + eg_phase = 24 - mg_phase + return (mg_score * mg_phase + eg_score * eg_phase) // 24 + + +class PestoStrategy(IStrategy): + def __init__(self, rollout_depth: int = 4): + super().__init__(rollout_depth) + + def pick_next_move(self, board: chess.Board) -> chess.Move | None: + def score_move(move: chess.Move): + bc = board.copy(stack=False) + bc.push(move) + return move, score(bc) + + moves = [score_move(move) for move in board.legal_moves] + if board.turn == chess.WHITE: + return max(moves, key=lambda m: m[1])[0] + else: + return min(moves, key=lambda m: m[1])[0] + + def analyze_board(self, board: chess.Board) -> int: + return score(board) diff --git a/main.py b/main.py index a33262f..e0a0595 100644 --- a/main.py +++ b/main.py @@ -111,7 +111,7 @@ def read_arguments(): engines = {"ClassicMCTS": EngineEnum.ClassicMcts, "BayesianMCTS": EngineEnum.BayesianMcts, "Random": EngineEnum.Random, "Stockfish": EngineEnum.Stockfish, "Lc0": EngineEnum.Lc0} strategies = {"Random": StrategyEnum.Random, "Stockfish": StrategyEnum.Stockfish, "Lc0": StrategyEnum.Lc0, - "RandomStockfish": StrategyEnum.RandomStockfish} + "RandomStockfish": StrategyEnum.RandomStockfish, "PESTO": StrategyEnum.Pestos} if os.name == 'nt': stockfish_default = "stockfish/stockfish-windows-x86-64-avx2" From 992ec1f2fe07dc52311773b7fc7fec5f26227f55 Mon Sep 17 00:00:00 2001 From: Stefan Steininger Date: Wed, 31 Jan 2024 10:28:21 +0100 Subject: [PATCH 08/10] fix pestos? --- chesspp/eval_pesto.py | 69 ++++++++++++++++++++++++++++++++++++++++--- chesspp/web.py | 1 + 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/chesspp/eval_pesto.py b/chesspp/eval_pesto.py index 4aed934..2404ebb 100644 --- a/chesspp/eval_pesto.py +++ b/chesspp/eval_pesto.py @@ -234,7 +234,7 @@ def score(board: chess.Board) -> int: pc = board.piece_at(sq) if pc is not None: color = 0 if pc.color == chess.WHITE else 1 - piece_index = pc.piece_type-1 + piece_index = (pc.piece_type-1)*2 + color mg[color] += mg_table[piece_index][sq] eg[color] += eg_table[piece_index][sq] game_phase += gamephaseInc[piece_index] @@ -250,6 +250,49 @@ def score(board: chess.Board) -> int: eg_phase = 24 - mg_phase return (mg_score * mg_phase + eg_score * eg_phase) // 24 +flip = [ + 56, 57, 58, 59, 60, 61, 62, 63, + 48, 49, 50, 51, 52, 53, 54, 55, + 40, 41, 42, 43, 44, 45, 46, 47, + 32, 33, 34, 35, 36, 37, 38, 39, + 24, 25, 26, 27, 28, 29, 30, 31, + 16, 17, 18, 19, 20, 21, 22, 23, + 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7 +] + + +def _init_tables(): + global mg_table, eg_table + + for i in range(64): + mg_table[WHITE_PAWN][i] = mg_pawn_table[i] + mg_table[BLACK_PAWN][i] = mg_pawn_table[flip[i]] + mg_table[WHITE_KNIGHT][i] = mg_knight_table[i] + mg_table[BLACK_KNIGHT][i] = mg_knight_table[flip[i]] + mg_table[WHITE_BISHOP][i] = mg_bishop_table[i] + mg_table[BLACK_BISHOP][i] = mg_bishop_table[flip[i]] + mg_table[WHITE_ROOK][i] = mg_rook_table[i] + mg_table[BLACK_ROOK][i] = mg_rook_table[flip[i]] + mg_table[WHITE_QUEEN][i] = mg_queen_table[i] + mg_table[BLACK_QUEEN][i] = mg_queen_table[flip[i]] + mg_table[WHITE_KING][i] = mg_king_table[i] + mg_table[BLACK_KING][i] = mg_knight_table[flip[i]] + + eg_table[WHITE_PAWN][i] = eg_pawn_table[i] + eg_table[BLACK_PAWN][i] = eg_pawn_table[flip[i]] + eg_table[WHITE_KNIGHT][i] = eg_knight_table[i] + eg_table[BLACK_KNIGHT][i] = eg_knight_table[flip[i]] + eg_table[WHITE_BISHOP][i] = eg_bishop_table[i] + eg_table[BLACK_BISHOP][i] = eg_bishop_table[flip[i]] + eg_table[WHITE_ROOK][i] = eg_rook_table[i] + eg_table[BLACK_ROOK][i] = eg_rook_table[flip[i]] + eg_table[WHITE_QUEEN][i] = eg_queen_table[i] + eg_table[BLACK_QUEEN][i] = eg_queen_table[flip[i]] + eg_table[WHITE_KING][i] = eg_king_table[i] + eg_table[BLACK_KING][i] = eg_knight_table[flip[i]] + +_init_tables() class PestoStrategy(IStrategy): def __init__(self, rollout_depth: int = 4): @@ -262,10 +305,28 @@ class PestoStrategy(IStrategy): return move, score(bc) moves = [score_move(move) for move in board.legal_moves] - if board.turn == chess.WHITE: - return max(moves, key=lambda m: m[1])[0] + ##print(board.turn, [m[1] for m in moves]) + if board.turn != chess.WHITE: + best_move = max(moves, key=lambda m: m[1]) else: - return min(moves, key=lambda m: m[1])[0] + best_move = min(moves, key=lambda m: m[1]) + #print(best_move) + return best_move[0] def analyze_board(self, board: chess.Board) -> int: return score(board) + +#print("WHITE_PAWN", WHITE_PAWN) +#print("BLACK_PAWN", BLACK_PAWN) +#print("WHITE_KNIGHT", WHITE_KNIGHT) +#print("BLACK_KNIGHT", BLACK_KNIGHT) +#print("WHITE_BISHOP", WHITE_BISHOP) +#print("BLACK_BISHOP", BLACK_BISHOP) +#print("WHITE_ROOK", WHITE_ROOK) +#print("BLACK_ROOK", BLACK_ROOK) +#print("WHITE_QUEEN", WHITE_QUEEN) +#print("BLACK_QUEEN", BLACK_QUEEN) +#print("WHITE_KING", WHITE_KING) +#print("BLACK_KING", BLACK_KING) +#print("EMPTY", EMPTY) + diff --git a/chesspp/web.py b/chesspp/web.py index e2a8887..8e00e26 100644 --- a/chesspp/web.py +++ b/chesspp/web.py @@ -8,6 +8,7 @@ import chess from chesspp import engine from chesspp.engine_factory import EngineFactory from chesspp.stockfish_strategy import StockFishStrategy +from chesspp.eval_pesto import PestoStrategy _DIR = os.path.abspath(os.path.dirname(__file__)) _DATA_DIR = os.path.abspath(os.path.join(_DIR, "static_data")) From a6653d0266c6be16bc9eaa50b240508248ac8e57 Mon Sep 17 00:00:00 2001 From: Stefan Steininger Date: Wed, 31 Jan 2024 12:40:59 +0100 Subject: [PATCH 09/10] refactoring --- chesspp/engine_factory.py | 2 +- chesspp/{eval_pesto.py => pesto_strategy.py} | 35 +++++++++++++++++++- chesspp/web.py | 9 ++--- web.py | 2 +- 4 files changed, 38 insertions(+), 10 deletions(-) rename chesspp/{eval_pesto.py => pesto_strategy.py} (94%) diff --git a/chesspp/engine_factory.py b/chesspp/engine_factory.py index f402157..53d9ec6 100644 --- a/chesspp/engine_factory.py +++ b/chesspp/engine_factory.py @@ -5,7 +5,7 @@ from chesspp.lc0_strategy import Lc0Strategy from chesspp.random_strategy import RandomStrategy from chesspp.stockfish_strategy import StockFishStrategy from chesspp.random_stockfish_strategy import RandomStockfishStrategy -from chesspp.eval_pesto import PestoStrategy +from chesspp.pesto_strategy import PestoStrategy from chesspp.i_strategy import IStrategy import chess diff --git a/chesspp/eval_pesto.py b/chesspp/pesto_strategy.py similarity index 94% rename from chesspp/eval_pesto.py rename to chesspp/pesto_strategy.py index 2404ebb..79c3e80 100644 --- a/chesspp/eval_pesto.py +++ b/chesspp/pesto_strategy.py @@ -1,7 +1,9 @@ import chess.engine import chess -from chesspp.i_strategy import IStrategy +from functools import cache +from chesspp.i_strategy import IStrategy +import numba # Scoring based on PeSTO (Piece-Square Tables Only) Evaluation Functions # https://www.chessprogramming.org/PeSTO%27s_Evaluation_Function @@ -224,6 +226,37 @@ eg_table = [ ] +#import chess +import sys + + +def minimax(depth, board, alpha, beta, is_maximizing): + if depth == 0 or board.is_game_over(): + return score(board) + + if is_maximizing: + best_move = -9999 + for move in board.legal_moves: + board.push(move) + best_move = max(best_move, minimax(depth - 1, board, alpha, beta, not is_maximizing)) + board.pop() + alpha = max(alpha, best_move) + if beta <= alpha: + return best_move + return best_move + else: + best_move = 9999 + for x in board.legal_moves: + move = chess.Move.from_uci(str(x)) + board.push(move) + best_move = min(best_move, minimax(depth - 1, board, alpha, beta, not is_maximizing)) + board.pop() + beta = min(beta, best_move) + if beta <= alpha: + return best_move + return best_move + + def score(board: chess.Board) -> int: mg = [0, 0] eg = [0, 0] diff --git a/chesspp/web.py b/chesspp/web.py index 8e00e26..f4c773e 100644 --- a/chesspp/web.py +++ b/chesspp/web.py @@ -8,7 +8,7 @@ import chess from chesspp import engine from chesspp.engine_factory import EngineFactory from chesspp.stockfish_strategy import StockFishStrategy -from chesspp.eval_pesto import PestoStrategy +from chesspp.pesto_strategy import PestoStrategy _DIR = os.path.abspath(os.path.dirname(__file__)) _DATA_DIR = os.path.abspath(os.path.join(_DIR, "static_data")) @@ -75,7 +75,7 @@ class WebInterface: async def turns(): """ Simulates the game and sends the response to the client """ white = EngineFactory.create_engine(self.white, self.strategy1, chess.WHITE, self.stockfish_path, self.lc0_path) - black = EngineFactory.create_engine(self.black, self.strategy1, chess.BLACK, self.stockfish_path, self.lc0_path) + black = EngineFactory.create_engine(self.black, self.strategy2, chess.BLACK, self.stockfish_path, self.lc0_path) runner = Simulate(white, black).run(self.limit) def sim(): return next(runner, None) @@ -102,8 +102,3 @@ class WebInterface: web.static('/img/chesspieces/wikipedia/', _DATA_DIR), ]) web.run_app(app) - - -def run_sample(): - limit = engine.Limit(time=1) - WebInterface(engine.BayesMctsEngine, engine.ClassicMctsEngine, limit).run_app() diff --git a/web.py b/web.py index 0355157..ffadd5e 100644 --- a/web.py +++ b/web.py @@ -5,5 +5,5 @@ from main import read_arguments if __name__ == '__main__': engine1, engine2, strategy1, strategy2, n_games, time, stockfish_path, lc0_path, n_proc = read_arguments() - limit = engine.Limit(time=0.5) + limit = engine.Limit(time=time) web.WebInterface(engine1, engine2, strategy1, strategy2, stockfish_path, lc0_path, limit).run_app() From 07eb85bb87e224d0ebbefd0c84fa457792f8463c Mon Sep 17 00:00:00 2001 From: Stefan Steininger Date: Wed, 31 Jan 2024 17:59:21 +0100 Subject: [PATCH 10/10] add piece values to pestos --- chesspp/pesto_strategy.py | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/chesspp/pesto_strategy.py b/chesspp/pesto_strategy.py index 79c3e80..bedce6e 100644 --- a/chesspp/pesto_strategy.py +++ b/chesspp/pesto_strategy.py @@ -226,37 +226,6 @@ eg_table = [ ] -#import chess -import sys - - -def minimax(depth, board, alpha, beta, is_maximizing): - if depth == 0 or board.is_game_over(): - return score(board) - - if is_maximizing: - best_move = -9999 - for move in board.legal_moves: - board.push(move) - best_move = max(best_move, minimax(depth - 1, board, alpha, beta, not is_maximizing)) - board.pop() - alpha = max(alpha, best_move) - if beta <= alpha: - return best_move - return best_move - else: - best_move = 9999 - for x in board.legal_moves: - move = chess.Move.from_uci(str(x)) - board.push(move) - best_move = min(best_move, minimax(depth - 1, board, alpha, beta, not is_maximizing)) - board.pop() - beta = min(beta, best_move) - if beta <= alpha: - return best_move - return best_move - - def score(board: chess.Board) -> int: mg = [0, 0] eg = [0, 0] @@ -325,6 +294,11 @@ def _init_tables(): eg_table[WHITE_KING][i] = eg_king_table[i] eg_table[BLACK_KING][i] = eg_knight_table[flip[i]] + for piece in range(6): + for field in range(64): + mg_table[piece][field] += mg_value[piece] + eg_table[piece][field] += eg_value[piece] + _init_tables() class PestoStrategy(IStrategy):