-
Notifications
You must be signed in to change notification settings - Fork 0
/
ChessMain.py
284 lines (250 loc) · 11.2 KB
/
ChessMain.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
"""
Main driver file.
Handling user input.
Displaying current GameStatus object.
"""
import pygame as p
import ChessEngine, ChessAI
import sys
from multiprocessing import Process, Queue
BOARD_WIDTH = BOARD_HEIGHT = 512
MOVE_LOG_PANEL_WIDTH = 250
MOVE_LOG_PANEL_HEIGHT = BOARD_HEIGHT
DIMENSION = 8
SQUARE_SIZE = BOARD_HEIGHT // DIMENSION
MAX_FPS = 15
IMAGES = {}
def loadImages():
"""
Initialize a global directory of images.
This will be called exactly once in the main.
"""
pieces = ['wp', 'wR', 'wN', 'wB', 'wK', 'wQ', 'bp', 'bR', 'bN', 'bB', 'bK', 'bQ']
for piece in pieces:
IMAGES[piece] = p.transform.scale(p.image.load("images/" + piece + ".png"), (SQUARE_SIZE, SQUARE_SIZE))
def main():
"""
The main driver for our code.
This will handle user input and updating the graphics.
"""
p.init()
screen = p.display.set_mode((BOARD_WIDTH + MOVE_LOG_PANEL_WIDTH, BOARD_HEIGHT))
clock = p.time.Clock()
screen.fill(p.Color("white"))
game_state = ChessEngine.GameState()
valid_moves = game_state.getValidMoves()
move_made = False # flag variable for when a move is made
animate = False # flag variable for when we should animate a move
loadImages() # do this only once before while loop
running = True
square_selected = () # no square is selected initially, this will keep track of the last click of the user (tuple(row,col))
player_clicks = [] # this will keep track of player clicks (two tuples)
game_over = False
ai_thinking = False
move_undone = False
move_finder_process = None
move_log_font = p.font.SysFont("Arial", 14, False, False)
player_one = True # if a human is playing white, then this will be True, else False
player_two = False # if a hyman is playing white, then this will be True, else False
while running:
human_turn = (game_state.white_to_move and player_one) or (not game_state.white_to_move and player_two)
for e in p.event.get():
if e.type == p.QUIT:
p.quit()
sys.exit()
# mouse handler
elif e.type == p.MOUSEBUTTONDOWN:
if not game_over:
location = p.mouse.get_pos() # (x, y) location of the mouse
col = location[0] // SQUARE_SIZE
row = location[1] // SQUARE_SIZE
if square_selected == (row, col) or col >= 8: # user clicked the same square twice
square_selected = () # deselect
player_clicks = [] # clear clicks
else:
square_selected = (row, col)
player_clicks.append(square_selected) # append for both 1st and 2nd click
if len(player_clicks) == 2 and human_turn: # after 2nd click
move = ChessEngine.Move(player_clicks[0], player_clicks[1], game_state.board)
for i in range(len(valid_moves)):
if move == valid_moves[i]:
game_state.makeMove(valid_moves[i])
move_made = True
animate = True
square_selected = () # reset user clicks
player_clicks = []
if not move_made:
player_clicks = [square_selected]
# key handler
elif e.type == p.KEYDOWN:
if e.key == p.K_z: # undo when 'z' is pressed
game_state.undoMove()
move_made = True
animate = False
game_over = False
if ai_thinking:
move_finder_process.terminate()
ai_thinking = False
move_undone = True
if e.key == p.K_r: # reset the game when 'r' is pressed
game_state = ChessEngine.GameState()
valid_moves = game_state.getValidMoves()
square_selected = ()
player_clicks = []
move_made = False
animate = False
game_over = False
if ai_thinking:
move_finder_process.terminate()
ai_thinking = False
move_undone = True
# AI move finder
if not game_over and not human_turn and not move_undone:
if not ai_thinking:
ai_thinking = True
return_queue = Queue() # used to pass data between threads
move_finder_process = Process(target=ChessAI.findBestMove, args=(game_state, valid_moves, return_queue))
move_finder_process.start()
if not move_finder_process.is_alive():
ai_move = return_queue.get()
if ai_move is None:
ai_move = ChessAI.findRandomMove(valid_moves)
game_state.makeMove(ai_move)
move_made = True
animate = True
ai_thinking = False
if move_made:
if animate:
animateMove(game_state.move_log[-1], screen, game_state.board, clock)
valid_moves = game_state.getValidMoves()
move_made = False
animate = False
move_undone = False
drawGameState(screen, game_state, valid_moves, square_selected)
if not game_over:
drawMoveLog(screen, game_state, move_log_font)
if game_state.checkmate:
game_over = True
if game_state.white_to_move:
drawEndGameText(screen, "Black wins by checkmate")
else:
drawEndGameText(screen, "White wins by checkmate")
elif game_state.stalemate:
game_over = True
drawEndGameText(screen, "Stalemate")
clock.tick(MAX_FPS)
p.display.flip()
def drawGameState(screen, game_state, valid_moves, square_selected):
"""
Responsible for all the graphics within current game state.
"""
drawBoard(screen) # draw squares on the board
highlightSquares(screen, game_state, valid_moves, square_selected)
drawPieces(screen, game_state.board) # draw pieces on top of those squares
def drawBoard(screen):
"""
Draw the squares on the board.
The top left square is always light.
"""
global colors
colors = [p.Color("white"), p.Color("gray")]
for row in range(DIMENSION):
for column in range(DIMENSION):
color = colors[((row + column) % 2)]
p.draw.rect(screen, color, p.Rect(column * SQUARE_SIZE, row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE))
def highlightSquares(screen, game_state, valid_moves, square_selected):
"""
Highlight square selected and moves for piece selected.
"""
if (len(game_state.move_log)) > 0:
last_move = game_state.move_log[-1]
s = p.Surface((SQUARE_SIZE, SQUARE_SIZE))
s.set_alpha(100)
s.fill(p.Color('green'))
screen.blit(s, (last_move.end_col * SQUARE_SIZE, last_move.end_row * SQUARE_SIZE))
if square_selected != ():
row, col = square_selected
if game_state.board[row][col][0] == (
'w' if game_state.white_to_move else 'b'): # square_selected is a piece that can be moved
# highlight selected square
s = p.Surface((SQUARE_SIZE, SQUARE_SIZE))
s.set_alpha(100) # transparency value 0 -> transparent, 255 -> opaque
s.fill(p.Color('blue'))
screen.blit(s, (col * SQUARE_SIZE, row * SQUARE_SIZE))
# highlight moves from that square
s.fill(p.Color('yellow'))
for move in valid_moves:
if move.start_row == row and move.start_col == col:
screen.blit(s, (move.end_col * SQUARE_SIZE, move.end_row * SQUARE_SIZE))
def drawPieces(screen, board):
"""
Draw the pieces on the board using the current game_state.board
"""
for row in range(DIMENSION):
for column in range(DIMENSION):
piece = board[row][column]
if piece != "--":
screen.blit(IMAGES[piece], p.Rect(column * SQUARE_SIZE, row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE))
def drawMoveLog(screen, game_state, font):
"""
Draws the move log.
"""
move_log_rect = p.Rect(BOARD_WIDTH, 0, MOVE_LOG_PANEL_WIDTH, MOVE_LOG_PANEL_HEIGHT)
p.draw.rect(screen, p.Color('black'), move_log_rect)
move_log = game_state.move_log
move_texts = []
for i in range(0, len(move_log), 2):
move_string = str(i // 2 + 1) + '. ' + str(move_log[i]) + " "
if i + 1 < len(move_log):
move_string += str(move_log[i + 1]) + " "
move_texts.append(move_string)
moves_per_row = 3
padding = 5
line_spacing = 2
text_y = padding
for i in range(0, len(move_texts), moves_per_row):
text = ""
for j in range(moves_per_row):
if i + j < len(move_texts):
text += move_texts[i + j]
text_object = font.render(text, True, p.Color('white'))
text_location = move_log_rect.move(padding, text_y)
screen.blit(text_object, text_location)
text_y += text_object.get_height() + line_spacing
def drawEndGameText(screen, text):
font = p.font.SysFont("Helvetica", 32, True, False)
text_object = font.render(text, False, p.Color("gray"))
text_location = p.Rect(0, 0, BOARD_WIDTH, BOARD_HEIGHT).move(BOARD_WIDTH / 2 - text_object.get_width() / 2,
BOARD_HEIGHT / 2 - text_object.get_height() / 2)
screen.blit(text_object, text_location)
text_object = font.render(text, False, p.Color('black'))
screen.blit(text_object, text_location.move(2, 2))
def animateMove(move, screen, board, clock):
"""
Animating a move
"""
global colors
d_row = move.end_row - move.start_row
d_col = move.end_col - move.start_col
frames_per_square = 10 # frames to move one square
frame_count = (abs(d_row) + abs(d_col)) * frames_per_square
for frame in range(frame_count + 1):
row, col = (move.start_row + d_row * frame / frame_count, move.start_col + d_col * frame / frame_count)
drawBoard(screen)
drawPieces(screen, board)
# erase the piece moved from its ending square
color = colors[(move.end_row + move.end_col) % 2]
end_square = p.Rect(move.end_col * SQUARE_SIZE, move.end_row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
p.draw.rect(screen, color, end_square)
# draw captured piece onto rectangle
if move.piece_captured != '--':
if move.is_enpassant_move:
enpassant_row = move.end_row + 1 if move.piece_captured[0] == 'b' else move.end_row - 1
end_square = p.Rect(move.end_col * SQUARE_SIZE, enpassant_row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
screen.blit(IMAGES[move.piece_captured], end_square)
# draw moving piece
screen.blit(IMAGES[move.piece_moved], p.Rect(col * SQUARE_SIZE, row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE))
p.display.flip()
clock.tick(60)
if __name__ == "__main__":
main()