1
|
|
|
# pylint: disable=R0902 |
|
|
|
|
2
|
|
|
# disabling too many instance attributes for now. |
3
|
|
|
# once I implement enpassant and castling, |
4
|
|
|
# I shouldn't need some of the FEN attributes. |
5
|
1 |
|
import json |
6
|
|
|
|
7
|
1 |
|
from .. import standard_chess_json |
8
|
1 |
|
from .base import Board |
9
|
1 |
|
from ..piece import Piece |
10
|
|
|
|
11
|
1 |
|
from ..movement import get_all_potential_end_locations |
12
|
1 |
|
from .. import movement |
13
|
|
|
|
14
|
1 |
|
map_fen_to_piece_name = { |
15
|
|
|
'k': "king", |
16
|
|
|
'q': "queen", |
17
|
|
|
'n': "knight", |
18
|
|
|
'b': "bishop", |
19
|
|
|
'r': "rook", |
20
|
|
|
'p': "pawn" |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
|
24
|
1 |
|
class ChessBoard(Board): |
|
|
|
|
25
|
|
|
|
26
|
1 |
|
def __init__(self, existing_board=None): |
27
|
1 |
|
super().__init__(8, 8) |
28
|
1 |
|
self.pieces = [] |
29
|
1 |
|
self.players = {} |
30
|
1 |
|
self.end_game = {} |
31
|
|
|
|
32
|
1 |
|
self.standard_chess = standard_chess_json |
33
|
|
|
|
34
|
1 |
|
if not existing_board: |
35
|
1 |
|
existing_board = self.load_json() |
36
|
|
|
|
37
|
1 |
|
self.initialize_board(existing_board) |
38
|
|
|
if existing_board and existing_board['players']['current'] == "Player 1": |
39
|
|
|
self.current_players_turn = "w" |
40
|
1 |
|
else: |
41
|
1 |
|
self.current_players_turn = "b" |
42
|
1 |
|
|
43
|
1 |
|
# FEN data I should take into account |
44
|
1 |
|
self.castling_opportunities = "KQkq" |
45
|
|
|
self.en_passant_target_square = "-" |
46
|
1 |
|
self.half_move_clock = 0 |
47
|
1 |
|
self.full_move_number = 1 |
48
|
|
|
|
49
|
1 |
|
def __getitem__(self, key): |
50
|
1 |
|
return self.board[key] |
51
|
|
|
|
52
|
1 |
|
def __setitem__(self, key, value): |
53
|
1 |
|
self.board[key] = value |
54
|
1 |
|
|
55
|
|
|
def __iter__(self): |
56
|
1 |
|
for key in self.board: |
57
|
1 |
|
yield key |
58
|
|
|
|
59
|
1 |
|
def __len__(self): |
60
|
|
|
return len(self.board) |
61
|
1 |
|
|
62
|
|
|
def generate_fen(self): |
63
|
1 |
|
"""Generates a FEN representation of the board.""" |
64
|
1 |
|
board = "" |
65
|
1 |
|
# FEN notation starts in the top left |
66
|
1 |
|
for row in range(self.rows - 1, -1, -1): |
67
|
1 |
|
num_missing = 0 |
68
|
|
|
for column in range(0, self.columns): |
69
|
1 |
|
key = (row, column) |
70
|
1 |
|
piece = self[key] |
71
|
1 |
|
|
72
|
1 |
|
if piece: |
73
|
1 |
|
prepend = '' |
74
|
1 |
|
if num_missing: |
75
|
|
|
prepend = str(num_missing) |
76
|
1 |
|
board += prepend + repr(piece) |
77
|
1 |
|
num_missing = 0 |
78
|
1 |
|
else: |
79
|
1 |
|
num_missing += 1 |
80
|
|
|
if num_missing: |
81
|
1 |
|
board += str(num_missing) |
82
|
|
|
board += "/" |
83
|
|
|
|
84
|
|
|
other_info = " {cpt} {co} {epts} {hmc} {fmn}".format(cpt=self.current_players_turn, |
85
|
|
|
co=self.castling_opportunities, |
86
|
1 |
|
epts=self.en_passant_target_square, |
87
|
1 |
|
hmc=self.half_move_clock, |
88
|
|
|
fmn=self.full_move_number) |
89
|
1 |
|
fen = board[0:-1] + other_info |
90
|
1 |
|
return fen |
91
|
1 |
|
|
92
|
|
|
def import_fen_board(self, fen_board): |
|
|
|
|
93
|
|
|
json_data = self.load_json() |
94
|
1 |
|
board_data, self.current_players_turn, self.castling_opportunities,\ |
95
|
1 |
|
self.en_passant_target_square, self.half_move_clock,\ |
96
|
1 |
|
self.full_move_number = fen_board.split(' ') |
97
|
1 |
|
rows = board_data.split('/') |
98
|
1 |
|
for row in range(self.rows - 1, -1, -1): |
99
|
1 |
|
fen_row = rows[7 - row] |
100
|
1 |
|
actual_column = 0 |
101
|
1 |
|
for column in range(0, len(fen_row)): |
102
|
1 |
|
try: |
103
|
1 |
|
num_missing = int(fen_row[actual_column]) |
104
|
1 |
|
for column in range(0, num_missing): |
105
|
1 |
|
self[(row, actual_column)] = None |
106
|
1 |
|
actual_column += 1 |
107
|
1 |
|
except ValueError: |
108
|
1 |
|
name = map_fen_to_piece_name[fen_row[actual_column].lower()] |
109
|
1 |
|
color = "black" if fen_row[actual_column].islower() else "white" |
110
|
|
|
moves = self.get_piece_moves(name, json_data) |
111
|
1 |
|
self[(row, column)] = Piece(name, color, moves) |
112
|
1 |
|
actual_column += 1 |
113
|
1 |
|
|
114
|
1 |
|
def export(self): |
|
|
|
|
115
|
1 |
|
json_data = {} |
116
|
|
|
json_data['pieces'] = {} |
117
|
1 |
|
for piece in self.pieces: |
118
|
1 |
|
json_data['pieces'][piece.kind] = {'moves': piece.moves} |
119
|
1 |
|
|
120
|
|
|
json_data['players'] = self.players |
121
|
|
|
if self.current_players_turn == 'w': |
122
|
|
|
json_data['players']['current'] = "Player 1" |
123
|
1 |
|
else: |
124
|
1 |
|
json_data['players']['current'] = "Player 2" |
125
|
1 |
|
|
126
|
1 |
|
map_color_to_name = {} |
127
|
1 |
|
json_board = {} |
128
|
1 |
|
for player in self.players: |
129
|
|
|
if player != 'current': |
130
|
1 |
|
map_color_to_name[self.players[player]['color']] = player |
131
|
1 |
|
json_board[player] = {} |
132
|
1 |
|
|
133
|
1 |
|
for location in self: |
134
|
1 |
|
piece = self[location] |
135
|
1 |
|
if piece: |
136
|
|
|
player = map_color_to_name[piece.color] |
137
|
1 |
|
if piece.kind in json_board[player]: |
138
|
|
|
json_board[player][piece.kind].append(list(location)) |
139
|
1 |
|
else: |
140
|
|
|
json_board[player][piece.kind] = [list(location)] |
141
|
1 |
|
|
142
|
|
|
json_data['board'] = json_board |
143
|
1 |
|
|
144
|
1 |
|
json_data['end_game'] = self.end_game |
145
|
1 |
|
|
146
|
|
|
print("export data is:") |
147
|
1 |
|
print(json_data) |
148
|
1 |
|
return json_data |
149
|
|
|
|
150
|
1 |
|
def initialize_board(self, json_data): |
|
|
|
|
151
|
1 |
|
json_board = json_data['board'] |
152
|
1 |
|
|
153
|
1 |
|
self.end_game = json_data['end_game'] |
154
|
|
|
for player in ['Player 1', 'Player 2']: |
155
|
1 |
|
players_data = json_data['players'] |
156
|
|
|
color = players_data[player]['color'] |
157
|
1 |
|
|
158
|
1 |
|
self.players[player] = players_data[player] |
159
|
1 |
|
|
160
|
1 |
|
player_pieces = json_board[player] |
161
|
1 |
|
for piece in player_pieces: |
162
|
|
|
name = piece |
163
|
1 |
|
moves = self.get_piece_moves(name, json_data) |
164
|
|
|
a_piece = Piece(name, color, moves) |
165
|
1 |
|
|
166
|
1 |
|
self.pieces.append(a_piece) |
167
|
|
|
|
168
|
1 |
|
for location in player_pieces[piece]: |
169
|
1 |
|
self[tuple(location)] = a_piece |
170
|
|
|
|
171
|
|
|
if json_data['players']['current'] == "Player 1": |
172
|
|
|
self.current_players_turn = 'w' |
173
|
1 |
|
else: |
174
|
1 |
|
self.current_players_turn = 'b' |
175
|
1 |
|
|
176
|
|
|
def clear_board(self): |
|
|
|
|
177
|
1 |
|
for location in self: |
178
|
|
|
self[location] = None |
179
|
1 |
|
|
180
|
|
|
@staticmethod |
181
|
1 |
|
def get_piece_moves(name, json_data): |
|
|
|
|
182
|
1 |
|
return json_data['pieces'][name]['moves'] |
183
|
1 |
|
|
184
|
1 |
|
def load_json(self): |
|
|
|
|
185
|
1 |
|
filename = self.standard_chess |
186
|
|
|
data = None |
187
|
1 |
|
with open(filename) as data_file: |
188
|
|
|
data = json.load(data_file) |
189
|
1 |
|
|
190
|
1 |
|
return data |
191
|
1 |
|
|
192
|
1 |
|
def end_locations_for_piece_at_location(self, start_location): |
|
|
|
|
193
|
1 |
|
piece = self[start_location] |
194
|
1 |
|
if not piece: |
195
|
1 |
|
return [] |
196
|
|
|
player_direction = None |
197
|
1 |
|
for player in self.players: |
198
|
1 |
|
if piece.color == self.players[player]['color']: |
199
|
1 |
|
player_direction = self.players[player]['direction'] |
200
|
1 |
|
break |
201
|
1 |
|
|
202
|
1 |
|
all_end_points = [] |
203
|
1 |
|
for move in piece.moves: |
204
|
1 |
|
directions = move['directions'] |
205
|
1 |
|
conditions = [getattr(movement, condition) for condition in move['conditions'] if hasattr(movement, condition)] |
206
|
1 |
|
ends = get_all_potential_end_locations(start_location, directions, self) |
207
|
1 |
|
for condition in conditions: |
208
|
|
|
# print("ends before condition: {} are: {}".format(condition, ends)) |
209
|
1 |
|
ends = condition(self, start_location, directions, ends, player_direction) |
210
|
1 |
|
# print("ends after condition: {} are: {}".format(condition, ends)) |
211
|
1 |
|
all_end_points += ends |
212
|
1 |
|
return all_end_points |
213
|
|
|
|
214
|
1 |
|
def move(self, start_location, end_location): |
|
|
|
|
215
|
|
|
if self.is_valid_move(start_location, end_location): |
216
|
|
|
if self.current_players_turn == 'w': |
217
|
|
|
if self[start_location].color == 'black': |
218
|
|
|
print('black trying to move, but whites turn') |
219
|
|
|
return False |
220
|
1 |
|
self.current_players_turn = 'b' |
221
|
1 |
|
else: |
222
|
1 |
|
if self[start_location].color == 'white': |
223
|
1 |
|
print('white trying to move, but blacks turn') |
224
|
1 |
|
return False |
225
|
1 |
|
self.full_move_number += 1 |
226
|
|
|
self.current_players_turn = 'w' |
227
|
|
|
print("is valid move") |
228
|
1 |
|
is_capture = self[end_location] is not None |
229
|
1 |
|
self[end_location] = self[start_location] |
230
|
|
|
self[start_location] = None |
231
|
1 |
|
self[end_location].move_count += 1 |
232
|
1 |
|
if self[end_location].kind != "pawn" and not is_capture: |
233
|
1 |
|
self.half_move_clock += 1 |
234
|
1 |
|
else: |
235
|
1 |
|
self.half_move_clock = 0 |
236
|
|
|
return True |
237
|
1 |
|
print('is not valid move') |
238
|
1 |
|
return False |
239
|
|
|
|
240
|
1 |
|
def is_valid_move(self, start_location, end_location): |
|
|
|
|
241
|
|
|
possible_moves = self.valid_moves(start_location) |
242
|
1 |
|
if end_location in possible_moves: |
243
|
|
|
return True |
244
|
|
|
return False |
245
|
|
|
|
246
|
|
|
def valid_moves(self, start_location): |
|
|
|
|
247
|
|
|
return self.end_locations_for_piece_at_location(start_location) |
248
|
|
|
|
249
|
|
|
@property |
250
|
|
|
def board(self): |
251
|
|
|
return self._board |
252
|
|
|
|
The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:
If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.