forked from lichess-bot-devs/lichess-bot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhomemade.py
125 lines (98 loc) · 4.6 KB
/
homemade.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
"""
Some example classes for people who want to create a homemade bot.
With these classes, bot makers will not have to implement the UCI or XBoard interfaces themselves.
"""
import chess
from chess.engine import PlayResult, Limit
import random
from lib.engine_wrapper import MinimalEngine
from lib.lichess_types import MOVE, HOMEMADE_ARGS_TYPE
import logging
# Laser-AI
import sys
sys.path.insert(1, './engines/laser_ai')
import train_model
# 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 NeuralEngine(MinimalEngine):
"""An engine running on Laser-AI in the backend"""
def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult:
# Catch AI errors
try:
ai_moves = train_model.generate_move(color=board.turn, fen=board.fen())
except Exception:
logger.error("Failed to generate move!", exc_info=True)
ai_moves = []
# Get first legal move of outputs
for ai_move in ai_moves:
try:
move = board.parse_uci(ai_move)
except ValueError:
continue
if board.is_legal(move):
return PlayResult(ai_move, None)
# Return a random legal move just in case
logger.warn("None of the AI moves were legal!")
return PlayResult(random.choice(list(board.legal_moves)), None)
class ExampleEngine(MinimalEngine):
"""An example engine that all homemade engines inherit."""
# Bot names and ideas from tom7's excellent eloWorld video
class RandomMove(ExampleEngine):
"""Get a random move."""
def search(self, board: chess.Board, *args: HOMEMADE_ARGS_TYPE) -> PlayResult: # noqa: ARG002
"""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: HOMEMADE_ARGS_TYPE) -> PlayResult: # noqa: ARG002
"""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: HOMEMADE_ARGS_TYPE) -> PlayResult: # noqa: ARG002
"""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, # noqa: ARG002
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)