added reuse of subtree for simulations (apply_move), played around with rollout depth

This commit is contained in:
2024-01-29 12:26:19 +01:00
parent a2cb3a5719
commit f1a36964df
7 changed files with 80 additions and 42 deletions

View File

@@ -1,14 +1,12 @@
import chess
from chesspp.i_mcts import * from chesspp.i_mcts import *
from chesspp.i_strategy import IStrategy from chesspp.i_strategy import IStrategy
from chesspp.util_gaussian import gaussian_ucb1, max_gaussian, min_gaussian from chesspp.util_gaussian import gaussian_ucb1, max_gaussian, min_gaussian
from chesspp.eval import score_manual from chesspp.eval import score_manual
import numpy as np
import math
class BayesianMctsNode(IMctsNode): class BayesianMctsNode(IMctsNode):
def __init__(self, board: chess.Board, strategy: IStrategy, color: chess.Color, parent: Self | None, move: chess.Move | None, def __init__(self, board: chess.Board, strategy: IStrategy, color: chess.Color, parent: Self | None,
move: chess.Move | None,
random_state: random.Random, inherit_result: int | None = None, depth: int = 0): random_state: random.Random, inherit_result: int | None = None, depth: int = 0):
super().__init__(board, strategy, parent, move, random_state) super().__init__(board, strategy, parent, move, random_state)
self.color = color # Color of the player whose turn it is self.color = color # Color of the player whose turn it is
@@ -20,7 +18,8 @@ class BayesianMctsNode(IMctsNode):
def _create_child(self, move: chess.Move) -> IMctsNode: def _create_child(self, move: chess.Move) -> IMctsNode:
copied_board = self.board.copy() copied_board = self.board.copy()
copied_board.push(move) copied_board.push(move)
return BayesianMctsNode(copied_board, self.strategy, not self.color, self, move, self.random_state, self.result, self.depth+1) return BayesianMctsNode(copied_board, self.strategy, not self.color, self, move, self.random_state, self.result,
self.depth + 1)
def _set_mu_sigma(self) -> None: def _set_mu_sigma(self) -> None:
self.mu = self.result self.mu = self.result
@@ -74,7 +73,7 @@ class BayesianMctsNode(IMctsNode):
return self._select_best_child() return self._select_best_child()
def rollout(self, rollout_depth: int = 20) -> int: def rollout(self, rollout_depth: int = 4) -> int:
copied_board = self.board.copy() copied_board = self.board.copy()
steps = self.depth steps = self.depth
for i in range(rollout_depth): for i in range(rollout_depth):
@@ -146,7 +145,7 @@ class BayesianMcts(IMcts):
def apply_move(self, move: chess.Move) -> None: def apply_move(self, move: chess.Move) -> None:
self.board.push(move) self.board.push(move)
self.color = not self.color self.color = self.board.turn
# if a child node contains the move, set this child as new root # if a child node contains the move, set this child as new root
for child in self.get_children(): for child in self.get_children():

View File

@@ -35,7 +35,7 @@ class ClassicMcts:
self.children.append(child_node) self.children.append(child_node)
return child_node return child_node
def _rollout(self, rollout_depth: int = 20) -> int: def _rollout(self, rollout_depth: int = 3) -> int:
""" """
Rolls out the node by simulating a game for a given depth. Rolls out the node by simulating a game for a given depth.
Sometimes this step is called 'simulation' or 'playout'. Sometimes this step is called 'simulation' or 'playout'.

View File

@@ -1,11 +1,13 @@
from abc import ABC, abstractmethod
import chess
import chess.engine
import random import random
import time import time
from chesspp.classic_mcts import ClassicMcts from abc import ABC, abstractmethod
import chess
import chess.engine
from chesspp.baysian_mcts import BayesianMcts from chesspp.baysian_mcts import BayesianMcts
from chesspp.random_strategy import RandomStrategy from chesspp.classic_mcts import ClassicMcts
from chesspp.i_strategy import IStrategy
class Limit: class Limit:
@@ -45,11 +47,17 @@ class Limit:
class Engine(ABC): class Engine(ABC):
board: chess.Board
"""The chess board"""
color: chess.Color color: chess.Color
"""The side the engine plays (``chess.WHITE`` or ``chess.BLACK``).""" """The side the engine plays (``chess.WHITE`` or ``chess.BLACK``)."""
strategy: IStrategy
"""The strategy used to pick moves when simulating games."""
def __init__(self, color: chess.Color): def __init__(self, board: chess.Board, color: chess.Color, strategy: IStrategy):
self.board = board
self.color = color self.color = color
self.strategy = strategy
@abstractmethod @abstractmethod
def play(self, board: chess.Board, limit: Limit) -> chess.engine.PlayResult: def play(self, board: chess.Board, limit: Limit) -> chess.engine.PlayResult:
@@ -72,27 +80,32 @@ class Engine(ABC):
class BayesMctsEngine(Engine): class BayesMctsEngine(Engine):
def __init__(self, color: chess.Color): mcts: BayesianMcts
super().__init__(color) """The Bayesian MCTS"""
def __init__(self, board: chess.Board, color: chess.Color, strategy: IStrategy):
super().__init__(board, color, strategy)
self.mcts = BayesianMcts(board, self.strategy, self.color)
@staticmethod @staticmethod
def get_name() -> str: def get_name() -> str:
return "BayesMctsEngine" return "BayesMctsEngine"
def play(self, board: chess.Board, limit: Limit) -> chess.engine.PlayResult: def play(self, board: chess.Board, limit: Limit) -> chess.engine.PlayResult:
strategy = RandomStrategy(random.Random()) if len(board.move_stack) != 0: # apply previous move to mcts --> reuse previous simulation results
bayes_mcts = BayesianMcts(board, strategy, self.color) self.mcts.apply_move(board.peek())
bayes_mcts.sample(1000) self.mcts.sample()
# limit.run(lambda: mcts_root.build_tree()) # limit.run(lambda: mcts_root.build_tree())
best_move = max(bayes_mcts.get_moves().items(), key=lambda x: x[1])[0] if board.turn == chess.WHITE else ( best_move = max(self.mcts.get_moves().items(), key=lambda x: x[1])[0] if board.turn == chess.WHITE else (
min(bayes_mcts.get_moves().items(), key=lambda x: x[1])[0]) min(self.mcts.get_moves().items(), key=lambda x: x[1])[0])
print(best_move) print(best_move)
self.mcts.apply_move(best_move)
return chess.engine.PlayResult(move=best_move, ponder=None) return chess.engine.PlayResult(move=best_move, ponder=None)
class ClassicMctsEngine(Engine): class ClassicMctsEngine(Engine):
def __init__(self, color: chess.Color): def __init__(self, board: chess.Board, color: chess.Color, strategy: IStrategy):
super().__init__(color) super().__init__(board, color, strategy)
@staticmethod @staticmethod
def get_name() -> str: def get_name() -> str:
@@ -108,12 +121,12 @@ class ClassicMctsEngine(Engine):
class RandomEngine(Engine): class RandomEngine(Engine):
def __init__(self, color: chess.Color): def __init__(self, board: chess.Board, color: chess.Color, strategy: IStrategy):
super().__init__(color) super().__init__(board, color, strategy)
@staticmethod @staticmethod
def get_name() -> str: def get_name() -> str:
return "Random" return "RandomEngine"
def play(self, board: chess.Board, limit: Limit) -> chess.engine.PlayResult: def play(self, board: chess.Board, limit: Limit) -> chess.engine.PlayResult:
move = random.choice(list(board.legal_moves)) move = random.choice(list(board.legal_moves))

View File

@@ -21,11 +21,12 @@ class EvaluationResult:
game: chess.pgn.Game game: chess.pgn.Game
def simulate_game(white: Engine, black: Engine, limit: Limit) -> chess.pgn.Game: def simulate_game(white: Engine, black: Engine, limit: Limit, board: chess.Board) -> chess.pgn.Game:
board = chess.Board()
is_white_playing = True is_white_playing = True
while not board.is_game_over(): while not board.is_game_over():
print("simulation board:\n", board)
print()
print("mcts board:\n", white.mcts.board)
play_result = white.play(board, limit) if is_white_playing else black.play(board, limit) play_result = white.play(board, limit) if is_white_playing else black.play(board, limit)
board.push(play_result.move) board.push(play_result.move)
is_white_playing = not is_white_playing is_white_playing = not is_white_playing

View File

@@ -0,0 +1,16 @@
import chess
from chesspp.i_strategy import IStrategy
import chess.engine
class StockFishStrategy(IStrategy):
stockfish: chess.engine.SimpleEngine
def __init__(self):
self.stockfish = chess.engine.SimpleEngine.popen_uci(
"/home/luke/projects/pp-project/chess-engine-pp/stockfish/stockfish-ubuntu-x86-64-avx2")
def pick_next_move(self, board: chess.Board) -> chess.Move | None:
move = self.stockfish.play(board, chess.engine.Limit(depth=4)).move
print("stockfish picked:", move)
return move

View File

@@ -1,10 +1,14 @@
import os import os
import asyncio import asyncio
import random
import aiohttp import aiohttp
from aiohttp import web from aiohttp import web
import chess import chess
from chesspp import engine from chesspp import engine
from chesspp.stockfish_strategy import StockFishStrategy
from chesspp.random_strategy import RandomStrategy
_DIR = os.path.abspath(os.path.dirname(__file__)) _DIR = os.path.abspath(os.path.dirname(__file__))
_DATA_DIR = os.path.abspath(os.path.join(_DIR, "static_data")) _DATA_DIR = os.path.abspath(os.path.join(_DIR, "static_data"))
@@ -71,7 +75,8 @@ class WebInterface:
async def turns(): async def turns():
""" Simulates the game and sends the response to the client """ """ Simulates the game and sends the response to the client """
runner = Simulate(self.white(chess.WHITE), self.black(chess.BLACK)).run(limit) runner = Simulate(self.white(chess.Board(), chess.WHITE, RandomStrategy(random.Random())), self.black(
chess.Board(), chess.BLACK, RandomStrategy(random.Random()))).run(limit)
def sim(): def sim():
return next(runner, None) return next(runner, None)
@@ -100,4 +105,4 @@ class WebInterface:
if __name__ == '__main__': if __name__ == '__main__':
limit = engine.Limit(time=0.5) limit = engine.Limit(time=0.5)
WebInterface(engine.ClassicMctsEngine, engine.ClassicMctsEngine, limit).run_app() WebInterface(engine.BayesMctsEngine, engine.ClassicMctsEngine, limit).run_app()

14
main.py
View File

@@ -5,15 +5,18 @@ import chess.pgn
from chesspp.classic_mcts import ClassicMcts from chesspp.classic_mcts import ClassicMcts
from chesspp.baysian_mcts import BayesianMcts from chesspp.baysian_mcts import BayesianMcts
from chesspp.random_strategy import RandomStrategy from chesspp.random_strategy import RandomStrategy
from chesspp.stockfish_strategy import StockFishStrategy
from chesspp import engine from chesspp import engine
from chesspp import util from chesspp import util
from chesspp import simulation, eval from chesspp import simulation, eval
def test_simulate(): def test_simulate():
white = engine.ClassicMctsEngine(chess.WHITE) board = chess.Board()
black = engine.ClassicMctsEngine(chess.BLACK) strategy = StockFishStrategy()
game = simulation.simulate_game(white, black) white = engine.BayesMctsEngine(board.copy(), chess.WHITE, strategy)
black = engine.RandomEngine(board.copy(), chess.BLACK, RandomStrategy(random.Random()))
game = simulation.simulate_game(white, black, engine.Limit(time=0.5), board)
print(game) print(game)
@@ -92,12 +95,13 @@ def test_evaluation():
def main(): def main():
test_evaluation() # test_evaluation()
# test_simulate() test_simulate()
# test_mcts() # test_mcts()
# test_stockfish() # test_stockfish()
# test_stockfish_prob() # test_stockfish_prob()
# test_bayes_mcts() # test_bayes_mcts()
if __name__ == '__main__': if __name__ == '__main__':
main() main()