-
Notifications
You must be signed in to change notification settings - Fork 0
/
Chess.py
385 lines (342 loc) · 12.6 KB
/
Chess.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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
"""
AI chess program.
"""
import os
import random
import copy
from datetime import datetime
from Board import Board, COLNAMES
from Pieces import Pawn, Bishop, Knight, Rook, Queen, King
from Spinner import wait_symbol
from Player import Player
DEFAULT_HISTORY_DIR = "/tmp/"
class Game(object):
"""
One game of chess.
"""
def __init__(self, verbose=False):
self.board = Board()
self.history = []
self.snapshot = None
self.reset()
self.players = {
"WHITE": None,
"BLACK": None
}
self.snapshots = {}
self.verbose = verbose
def init_players(self):
for colour in self.players.keys():
hum = input("Hello, human! Would you like to play as {} (y/n)?"\
.format(colour))
if hum == "y":
self.players[colour] = Player(colour, False)
else:
self.players[colour] = Player(colour, True)
def add_piece(self, piece, position):
piece.current_position = position
self.board.pieces.append(piece)
def clear(self):
"""
Clear the board completely
"""
self.board.pieces = []
def reset(self):
"""
Start a new game
"""
self.board.pieces = []
for i in range(8):
self.add_piece(Pawn("WHITE"),(COLNAMES[i], 2))
self.add_piece(Pawn("BLACK"),(COLNAMES[i], 7))
row_dict = {"WHITE":1, "BLACK":8 }
for colour, row in row_dict.items():
self.add_piece(King(colour),("E",row))
self.add_piece(Queen(colour),("D",row))
for col in ["A","H"]:
self.add_piece(Rook(colour),(col,row))
for col in ["B","G"]:
self.add_piece(Knight(colour),(col,row))
for col in ["C","F"]:
self.add_piece(Bishop(colour),(col,row))
self.next_to_play = "WHITE"
self.history = []
self.update_all_pieces()
def get_history(self):
return self.history
def get_history_str(self):
history_str = ""
for move in self.history:
history_str += "{}{}{}{}".format(move[0][0],
move[0][1],
move[1][0],
move[1][1])
return history_str
def save_snapshot(self):
"""
save a snapshot of both the board and the history, and whose turn it is
"""
snapshot = {
"next_to_play": self.next_to_play,
"history": copy.deepcopy(self.history)
}
self.board.save_snapshot(self.get_history_str())
self.snapshots[self.get_history_str()] = snapshot
def load_snapshot(self, history_str):
"""
load a snapshot from the history str
"""
if not history_str in self.snapshots.keys():
raise RuntimeError("No snapshot {} found".format(history_str))
self.next_to_play = self.snapshots[history_str]["next_to_play"]
self.history = self.snapshots[history_str]["history"]
self.board.load_snapshot(history_str)
def write_history(self, filename):
"""
Write out the sequence of moves to a file.
"""
with open(filename, "w") as outfile:
for m in self.history:
outfile.write("{}\n".format(m))
def is_check(self, colour):
"""
See if the king of selected colour is in check
"""
king_pos = None
threatened_positions = []
for p in self.board.pieces:
if p.colour == colour and p.piece_type == "King":
king_pos = p.current_position
break
if not king_pos:
raise Exception("No {} king found!".format(colour))
if self.board.is_threatened(king_pos, colour):
return True
return False
def is_checkmate(self, colour):
"""
See if the selected colour king is in check, and
if so, are there any moves that would get it out.
"""
if self.next_to_play != colour:
return False
if not self.is_check(colour):
return False
## next player to play is in check.
## can they get out?
self.update_all_pieces()
possible_moves = []
for p in self.board.pieces:
if p.colour == colour:
for move in p.available_moves:
possible_moves.append((p.current_position, move))
pass
pass
pass
self.board.save_snapshot()
for m in possible_moves:
self.move(m[0], m[1], trial_move=True)
if not self.is_check(colour):
if self.verbose:
print("Can escape check with move {} to {}"\
.format(m[0], m[1]))
self.board.load_snapshot()
return False
self.board.load_snapshot()
## we have been through all possible moves, and
## the king would be in check after all of them
return True
def is_stalemate(self, colour):
"""
See if the selected colour king is in check, and
if so, are there any moves that would get it out.
"""
if self.next_to_play != colour:
return False
if self.is_check(colour):
return False
## next player to play is NOT in check.
## do they have any legal moves?
self.update_all_pieces()
possible_moves = []
for p in self.board.pieces:
if p.colour == colour:
for move in p.available_moves:
possible_moves.append((p.current_position, move))
pass
pass
pass
if len(possible_moves) == 0:
return True
self.board.save_snapshot()
for m in possible_moves:
self.move(m[0], m[1], trial_move=True)
if not self.is_check(colour):
self.board.load_snapshot()
return False
self.board.load_snapshot()
## we have been through all possible moves, and
## the king would be in check after all of them
return True
def is_legal_move(self, colour, start_pos, end_pos):
"""
determine whether a move is legal.
"""
if not colour == self.next_to_play:
print("It is {}'s turn to play".format(self.next_to_play))
return False
if self.board.is_empty(start_pos) or \
self.board.piece_at(start_pos).colour != colour:
print("{} does not have a piece at {}".format(colour, start_pos))
return False
p = self.board.piece_at(start_pos)
if not end_pos in p.available_moves:
print("Piece at {} cannot move to {}".format(start_pos,end_pos))
return False
self.board.save_snapshot()
## try the move and see if we would be in check afterwards
self.move(start_pos, end_pos, trial_move=True)
if self.is_check(colour):
if self.verbose:
print("Cannot move there - king would be in check")
self.board.load_snapshot()
return False
self.board.load_snapshot()
self.update_all_pieces()
return True
def is_castling_move(self, start_pos, end_pos):
"""
Return True if the piece at start_pos is a king, and
end_pos is more than one row away.
"""
if self.board.is_empty(start_pos):
return False
if not self.board.piece_at(start_pos).piece_type == "King":
return False
start_colnum = COLNAMES.index(start_pos[0])
end_colnum = COLNAMES.index(end_pos[0])
is_castling = abs(start_colnum - end_colnum) > 1
return is_castling
def castle(self, start_pos, end_pos):
"""
User castles by moving the king, the rook is then moved
automatically.
Assume we've already checked this is a legal move (i.e.
neither rook nor king have moved before, there's nothing
in the way).
"""
if self.board.is_empty(start_pos):
return False
k = self.board.piece_at(start_pos)
k.current_position = end_pos
## define a dict so we can get start+finish columns
## for the rook, keyed on which column the king will
## end up in.
castling_columns = { "G": ["H","F"],
"C": ["A","D"] }
rook_start = (castling_columns[end_pos[0]][0],end_pos[1])
rook_end = (castling_columns[end_pos[0]][1],end_pos[1])
## now move the rook.
if self.board.is_empty(rook_start):
return False
r = self.board.piece_at((rook_start))
r.current_position = (rook_end)
r.has_moved = True
k.has_moved = True
return True
def is_promotion_move(self, start_pos, end_pos):
"""
Figure out if the requested move would promote a pawn.
"""
if self.board.is_empty(start_pos):
return False
p = self.board.piece_at(start_pos)
if p.piece_type != "Pawn":
return False
return (p.colour=="WHITE" and end_pos[1]==8) \
or (p.colour=="BLACK" and end_pos[1]==1)
def promote(self, start_pos, end_pos):
"""
Replace a pawn by a queen.
Assume we have already checked it is a legal move.
"""
if self.board.is_empty(start_pos):
return False
pawn = self.board.piece_at(start_pos)
colour = pawn.colour
self.board.pieces.remove(pawn)
self.add_piece(Queen(colour), end_pos)
return True
def move(self, start_pos, end_pos, trial_move=False):
"""
move a piece on the board, taking the piece
at the end position if applicable
"""
if self.is_castling_move(start_pos, end_pos):
castled_ok = self.castle(start_pos, end_pos)
if not castled_ok:
return False
elif self.is_promotion_move(start_pos, end_pos):
promoted_ok = self.promote(start_pos, end_pos)
if not promoted_ok:
return False
else:
if not self.board.is_empty(end_pos):
self.board.pieces.remove(self.board.piece_at(end_pos))
p = self.board.piece_at(start_pos)
p.current_position = end_pos
p.has_moved = True
self.update_all_pieces()
if not trial_move:
self.next_player_turn()
self.history.append((start_pos, end_pos))
return True
def get_all_possible_moves(self, colour):
"""
Loop through all pieces of specified colour and return
list of all legal moves.
"""
self.update_all_pieces()
potential_moves = []
moves = []
for p in self.board.pieces:
if p.colour != colour:
continue
start_pos = p.current_position
for end_pos in p.available_moves:
potential_moves.append((start_pos, end_pos))
## now loop through potential moves to see which are legal
for move in potential_moves:
if self.is_legal_move(colour, move[0],move[1]):
moves.append(move)
return moves
def update_all_pieces(self):
for p in self.board.pieces:
p.find_available_moves(self.board)
p.find_positions_threatened(self.board)
def next_player_turn(self):
if self.next_to_play == "WHITE":
self.next_to_play = "BLACK"
else:
self.next_to_play = "WHITE"
def play(self):
self.init_players()
while not ( self.is_checkmate(self.next_to_play) or \
self.is_stalemate(self.next_to_play) ):
print("{} to play..".format(self.next_to_play))
if self.players[self.next_to_play].is_AI:
wait_symbol()
move = self.players[self.next_to_play].choose_move(self)
print("{}{} to {}{}".format(
move[0][0],move[0][1],move[1][0],move[1][1]))
else:
self.players[self.next_to_play].input_move(self)
self.update_all_pieces()
print("Checkmate!! {} loses.".format(self.next_to_play))
timestamp = datetime.now().isoformat().replace(":","-")
history_filename = os.path.join(DEFAULT_HISTORY_DIR,
"match_{}".format(timestamp))
self.write_history(history_filename)
if __name__ == "__main__":
g = Game()
g.play()