-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbase_chess_env.py
259 lines (212 loc) · 8.22 KB
/
base_chess_env.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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
"""
Created on February 26 2021
@author: Andreas Spanopoulos
Defines base class for Chess environments and their basic operations.
Note for chessboard display:
In order for the display to work, you have to add the the python instruction 'global gameboard' to
line 56 of the chessboard.display.py file, so that it becomes:
def start(fen=''):
global gameboard <----
pygame.init()
You can also comment line 41:
def terminate():
pygame.quit()
# sys.exit()
if you want the terminate function to just close the display but not quit the execution.
"""
import copy
from abc import abstractmethod
from chessboard import display
from time import sleep
from src.utils.error_utils import GameIsNotOverError
class ChessEnv:
""" Base class used to define a chess environment """
def __init__(self, _board, _repetitions_count, _nn_input, t_history, n):
"""
:param BoardT _board: A Chess board object containing an already initialized
game.
:param dict _repetitions_count: The dictionary containing the number of repetitions
per position.
:param list _nn_input: List containing the most current state representation of
the board.
:param int t_history: The length of the historic (previous) positions of the
board to store. Used to take threefold repetition into
account. In the paper, the default values is T = 8.
:param int n: Chess game dimension (should always be 8).
"""
self._board = _board
self._repetitions_count = _repetitions_count or {self._board.board_fen(): 1}
self._nn_input = _nn_input
self._t_history = t_history
self._n = n
self._compute_current_state_representation()
@abstractmethod
def copy(self):
"""
:return: A copy of the current environment.
:rtype: ChessEnv
"""
pass
@property
def current_state_representation(self):
"""
:return: A list of n x n lists, used as input for the Neural Network.
:rtype: list[list[list[int]]
"""
return copy.deepcopy(self._nn_input)
@property
def starting_fen(self):
"""
:return: The starting FEN position of the chess variant that inherits from this base class.
:rtype: str
"""
return self._board.starting_fen
@property
def fen(self):
"""
:return: The FEN representation for the current state of the board.
:rtype: str
"""
return self._board.fen()
@property
def side_to_move(self):
"""
:return: 'w' or 'b', depending on whether white or black is to play the next move.
:rtype: str
"""
_, side, _, _, _, _ = self.fen.split()
return side
@property
def moves(self):
"""
:return: The number of moves played in the game thus far.
:rtype: int
"""
_, _, _, _, _, total_moves = self.fen.split()
return int(total_moves)
@property
def legal_moves(self):
"""
:return: A list containing all the legal moves (as strings) in UCI notation that can be
played in the current position (state).
:rtype: list[str]
"""
return [self._board.uci(uci) for uci in list(self._board.legal_moves)]
@property
def legal_moves_san(self):
"""
:return: A list containing all the legal moves (as string) in SAN notation that can be
play in the current position (state).
:rtype: list[str]
"""
return [self._board.san(self._board.parse_uci(uci)) for uci in self.legal_moves]
@property
def is_finished(self):
"""
:return: True if the game has finished (loss/draw/win); Else False.
:rtype: Bool
"""
return self._repetitions_count[self._board.board_fen()] == 3 or \
self._board.is_variant_end() or len(list(self._board.legal_moves)) == 0
@property
def winner(self):
"""
:return: Returns 1 if white has won the game, -1 if black won or 0 if it was drawn.
:rtype: int
:raises:
GameIsNotOverError: If the game has not ended, then this error is raised.
"""
if self._repetitions_count[self._board.board_fen()] == 3:
return 0
result = self._board.result()
if result == '*':
raise GameIsNotOverError
return 1 if result == '1-0' else -1 if result == '0-1' else 0
def san_from_uci(self, uci):
"""
:param str uci: A string representing a chess move in UCI notation.
:return: A string representing the same move in Standard Algebraic Notation (san).
:rtype: str
"""
return self._board.san(self._board.parse_uci(uci))
def is_terminal_move(self, move):
"""
:param str move: The move that could possibly lead to the game ending
:return: True if the move makes the game end. Else False
:rtype: bool
"""
# push the move and check if it leads to a terminal state
self._board.push_san(move)
if self._repetitions_count.get(self._board.board_fen(), 0) == 2 or \
self._board.is_variant_end() or len(list(self._board.legal_moves)) == 0:
result = True
else:
result = False
# restore the previous board setup and return the result
self._board.pop()
return result
def reset(self):
"""
:return: None
:rtype: None
Resets the board to the initial position.
"""
self._board.reset()
self._repetitions_count = {self._board.board_fen(): 1}
self._compute_current_state_representation()
def set_fen(self, fen):
"""
:param str fen: The FEN string that describes a Racing Kings Chess position, which should
be set in the current environment.
:return: None
:rtype: None
:raises:
ValueError: If the FEN provided is invalid for the Racing Kings Chess variant.
Sets the current board position (state) to the one described by the input fen.
"""
self.reset()
self._board.set_fen(fen)
self._repetitions_count = {self._board.board_fen(): 1}
def play_move(self, move):
"""
:param str move: The move to be played in the current position (state), e.g.: 'Nxc2'.
:return: None
:rtype: None
Plays a specific move and updates the playing board accordingly.
"""
self._board.push_san(move)
board_fen = self._board.board_fen()
self._repetitions_count[board_fen] = self._repetitions_count.get(board_fen, 0) + 1
self._compute_current_state_representation()
@abstractmethod
def _compute_current_state_representation(self):
"""
:return: None
:rtype: None
Computes the list of n x n lists that will be used for input in the Neural Network.
"""
pass
def display_current_board(self, delay=3):
"""
:param double delay: Amount of seconds to display the board.
:return: None
:rtype: None
Display the current position (state) of the board for a fixed amount of time
(parameter: seconds).
"""
display.start(self.fen)
sleep(delay)
display.terminate()
@staticmethod
@abstractmethod
def simulate_game(ply_list, ply_delay=0.5):
"""
:param list ply_list: List containing the plies (individual moves) of each player,
sequentially. E.g.: ['Kh3', 'Ka3', 'Bd4', 'Ka4', ...]
:param double ply_delay: Delay time between plies shown in the display.
:return: The FEN configuration of the last position.
:rtype: str
Simulates a game from a given ply list, displaying the moves along the way. Then, the last
FEN position is returned.
"""
pass