127 lines
4.8 KiB
Python
127 lines
4.8 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
|
|
|
|
from chesspp.engine import ClassicMctsEngine
|
|
|
|
# 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()))
|
|
print("Engine name", my_engine)
|
|
return my_engine.play(board.copy())
|