Files

156 lines
6.3 KiB
Python

"""
Some example strategies for people who want to create a custom, homemade bot.
With these classes, bot makers will not have to implement the UCI or XBoard interfaces themselves.
"""
from __future__ import annotations
import chess
from chess.engine import PlayResult, Limit
import random
from lib.engine_wrapper import MinimalEngine, MOVE
from typing import Any
import logging
import chesspp
from chesspp.engine import ClassicMctsEngine, Engine
from chesspp.engine_factory import EngineFactory, EngineEnum, StrategyEnum
# Use this logger variable to print messages to the console or log files.
# logger.info("message") will always print "message" to the console or log file.
# logger.debug("message") will only print "message" if verbose logging is enabled.
logger = logging.getLogger(__name__)
class ExampleEngine(MinimalEngine):
"""An example engine that all homemade engines inherit."""
pass
# Strategy names and ideas from tom7's excellent eloWorld video
class RandomMove(ExampleEngine):
"""Get a random move."""
def search(self, board: chess.Board, *args: Any) -> PlayResult:
"""Choose a random move."""
return PlayResult(random.choice(list(board.legal_moves)), None)
class Alphabetical(ExampleEngine):
"""Get the first move when sorted by san representation."""
def search(self, board: chess.Board, *args: Any) -> PlayResult:
"""Choose the first move alphabetically."""
moves = list(board.legal_moves)
moves.sort(key=board.san)
return PlayResult(moves[0], None)
class FirstMove(ExampleEngine):
"""Get the first move when sorted by uci representation."""
def search(self, board: chess.Board, *args: Any) -> PlayResult:
"""Choose the first move alphabetically in uci representation."""
moves = list(board.legal_moves)
moves.sort(key=str)
return PlayResult(moves[0], None)
class ComboEngine(ExampleEngine):
"""
Get a move using multiple different methods.
This engine demonstrates how one can use `time_limit`, `draw_offered`, and `root_moves`.
"""
def search(self, board: chess.Board, time_limit: Limit, ponder: bool, draw_offered: bool,
root_moves: MOVE) -> PlayResult:
"""
Choose a move using multiple different methods.
:param board: The current position.
:param time_limit: Conditions for how long the engine can search (e.g. we have 10 seconds and search up to depth 10).
:param ponder: Whether the engine can ponder after playing a move.
:param draw_offered: Whether the bot was offered a draw.
:param root_moves: If it is a list, the engine should only play a move that is in `root_moves`.
:return: The move to play.
"""
if isinstance(time_limit.time, int):
my_time = time_limit.time
my_inc = 0
elif board.turn == chess.WHITE:
my_time = time_limit.white_clock if isinstance(time_limit.white_clock, int) else 0
my_inc = time_limit.white_inc if isinstance(time_limit.white_inc, int) else 0
else:
my_time = time_limit.black_clock if isinstance(time_limit.black_clock, int) else 0
my_inc = time_limit.black_inc if isinstance(time_limit.black_inc, int) else 0
possible_moves = root_moves if isinstance(root_moves, list) else list(board.legal_moves)
if my_time / 60 + my_inc > 10:
# Choose a random move.
move = random.choice(possible_moves)
else:
# Choose the first move alphabetically in uci representation.
possible_moves.sort(key=str)
move = possible_moves[0]
return PlayResult(move, None, draw_offered=draw_offered)
# class ProbStockfish(MinimalEngine):
# def search(self, board: chess.Board, time_limit: chess.engine.Limit, ponder: bool, draw_offered: bool,
# root_moves: MOVE) -> chess.engine.PlayResult:
# moves = {}
# untried_moves = list(board.legal_moves)
# for move in untried_moves:
# mean, std = engine.simulate_stockfish_prob(board.copy(), move, 10, 2)
# moves[move] = (mean, std)
# if mean == 100_000 and std == 0:
# return chess.engine.PlayResult(move, None)
#
# return self.get_best_move(moves)
#
# def get_best_move(self, moves: dict) -> chess.engine.PlayResult:
# best_avg = max(moves.items(), key=lambda m: m[1][0])
# next_move = best_avg[0]
# return chess.engine.PlayResult(next_move, None)
class MctsEngine(MinimalEngine):
def search(self, board: chess.Board, time_limit: chess.engine.Limit, ponder: bool, draw_offered: bool,
root_moves: MOVE) -> chess.engine.PlayResult:
my_engine = ClassicMctsEngine(board.turn)
print("Color:", board.turn)
print("engine play result: ", my_engine.play(board.copy(), chesspp.engine.Limit(0.5)))
print("Engine name", my_engine)
return my_engine.play(board.copy(), chesspp.engine.Limit())
class MyBayesMctsEngine(MinimalEngine):
def __init__(self, commands: COMMANDS_TYPE, options: OPTIONS_TYPE, stderr: Optional[int],
draw_or_resign: Configuration, name: Optional[str] = None, **popen_args: str) -> None:
"""
Initialize the values of the engine that all homemade engines inherit.
:param options: The options to send to the engine.
:param draw_or_resign: Options on whether the bot should resign or offer draws.
"""
super().__init__(commands, options, stderr, draw_or_resign, name, **popen_args)
self._engine = None
def get_engine(self, color: chess.Color) -> chess.engine:
if self._engine is None:
self._engine = EngineFactory.create_engine(EngineEnum.BayesianMcts, StrategyEnum.Stockfish, color,
"/home/luke/projects/pp-project/chess-engine-pp/stockfish/stockfish-ubuntu-x86-64-avx2",
"", 1500, 4)
return self._engine
def search(self, board: chess.Board, time_limit: chess.engine.Limit, ponder: bool, draw_offered: bool,
root_moves: MOVE) -> chess.engine.PlayResult:
my_engine = self.get_engine(board.turn)
r = my_engine.play(board.copy(), chesspp.engine.Limit(2))
return r