-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgame.py
308 lines (243 loc) · 13.2 KB
/
game.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
import pygame
import sys
from brain import ai_move
from utils import make_move_for_ai, get_all_valid_moves, is_terminal, is_valid_move, available_captures # Import shared functions from utils.py
# Initialize Pygame
pygame.init()
# Screen dimensions
WIDTH, HEIGHT = 500, 500
WINDOW_WIDTH = WIDTH+200
ROWS, COLS = 9, 9
SQUARE_SIZE = WIDTH // COLS
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREY = (169, 169, 169)
BROWN = (139, 69, 19)
# Load assets
WHITE_PIECE = pygame.transform.scale(pygame.image.load('white_piece.png'), (SQUARE_SIZE, SQUARE_SIZE))
BLACK_PIECE = pygame.transform.scale(pygame.image.load('black_piece.png'), (SQUARE_SIZE, SQUARE_SIZE))
# Initialize screen
screen = pygame.display.set_mode((WINDOW_WIDTH, HEIGHT))
pygame.display.set_caption('Fianco')
# Board setup from the given image
START_POSITION = [
[1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 1, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 2, 0, 2, 0, 0, 0],
[0, 0, 2, 0, 0, 0, 2, 0, 0],
[0, 2, 0, 0, 0, 0, 0, 2, 0],
[2, 2, 2, 2, 2, 2, 2, 2, 2]
]
# Draw the board
def draw_board():
screen.fill(BROWN)
for row in range(ROWS):
for col in range(COLS):
if (row + col) % 2 == 0:
pygame.draw.rect(screen, WHITE, (col * SQUARE_SIZE, row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE))
else:
pygame.draw.rect(screen, GREY, (col * SQUARE_SIZE, row * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE))
# Draw pieces
def draw_pieces(board, selected_piece):
for row in range(ROWS):
for col in range(COLS):
if board[row][col] == 1 and (row, col) != selected_piece:
screen.blit(BLACK_PIECE, (col * SQUARE_SIZE, row * SQUARE_SIZE))
elif board[row][col] == 2 and (row, col) != selected_piece:
screen.blit(WHITE_PIECE, (col * SQUARE_SIZE, row * SQUARE_SIZE))
def draw_restart_button():
button_color = (200, 0, 0) # Red color for the button
button_rect = pygame.Rect(WIDTH + 50, HEIGHT - 50, 100, 40) # Position and size of the button
pygame.draw.rect(screen, button_color, button_rect)
# Add button text
font = pygame.font.Font(None, 24) # Default font
text = font.render("Restart", True, WHITE) # Text inside the button
screen.blit(text, (WIDTH + 75, HEIGHT - 40)) # Position the text inside the button
return button_rect # Return the button rect to detect clicks later
# Convert row and column indexes to chess-style notation (columns A-I, rows 1-9)
def index_to_notation(start, end, is_capture):
col_map = "abcdefghi" # Column letters
row_map = "123456789" # Row numbers (bottom to top)
start_col, start_row = start
end_col, end_row = end
# Notate positions (e.g., "a1" or "i9")
start_pos = f"{col_map[start_row]}{row_map[8 - start_col]}" # Row is inverted for chess notation
end_pos = f"{col_map[end_row]}{row_map[8 - end_col]}" # Row is inverted for chess notation
if is_capture:
return f"{start_pos}x{end_pos}" # Capture notation
return f"{start_pos}-{end_pos}" # Normal move notation
# Perform a move and track history and annotations
def make_move(board, start, end, move_history, annotations, current_move_index, current_player):
start_row, start_col = start
end_row, end_col = end
is_capture = abs(start_row - end_row) == 2 # Check if this is a capture
# Make the move
board[end_row][end_col] = board[start_row][start_col]
board[start_row][start_col] = 0
# Handle captures
if is_capture:
middle_row = (start_row + end_row) // 2
middle_col = (start_col + end_col) // 2
board[middle_row][middle_col] = 0
# Convert move to notation and store it
move_annotation = index_to_notation(start, end, is_capture)
# If we're replaying a game, remove future moves once we make a new move
move_history = move_history[:current_move_index + 1]
annotations = annotations[:current_move_index + 1]
# Add new move to history and update the annotations
move_history.append((start, end, is_capture))
annotations.append(move_annotation)
current_move_index += 1
return move_history, annotations, current_move_index
# Revert the board to a previous state
def undo_move(board, move_history, current_move_index):
if current_move_index < 0: # No more moves to undo, prevent going below the first move
return board, current_move_index
# Get the move to undo
start, end, is_capture = move_history[current_move_index]
# Reverse the move (move the piece back to its original position)
board[start[0]][start[1]] = board[end[0]][end[1]] # Move piece back
board[end[0]][end[1]] = 0 # Clear the destination square
# Restore the captured piece if necessary
if is_capture:
middle_row = (start[0] + end[0]) // 2
middle_col = (start[1] + end[1]) // 2
# Restore the opponent's piece at the middle position
board[middle_row][middle_col] = 3 - board[start[0]][start[1]]
# Decrease the current move index and return the updated board and index
current_move_index -= 1
return board, current_move_index
# Redo a move if available
def redo_move(board, move_history, current_move_index):
if current_move_index >= len(move_history) - 1: # No future moves to redo
return board
# Get the move to redo
start, end, is_capture = move_history[current_move_index + 1]
# Perform the move (move the piece to its new position)
board[end[0]][end[1]] = board[start[0]][start[1]]
board[start[0]][start[1]] = 0 # Clear the original square
# Handle captures
if is_capture:
middle_row = (start[0] + end[0]) // 2
middle_col = (start[1] + end[1]) // 2
# Clear the opponent's piece that was captured
board[middle_row][middle_col] = 0
# Increase the current move index and return the updated board and index
current_move_index += 1
return board, current_move_index
# Main game loop
def main():
clock = pygame.time.Clock()
def reset_game():
# Reset the game state
board = [row[:] for row in START_POSITION] # Reset the board to the starting position
selected_piece = None
current_player = 2 # White starts (AI)
dragging_piece = False # Reset dragging state
piece_drag_offset = (0, 0) # Reset offset
dragged_pos = None # Reset dragged position
move_history = [] # Clear move history
annotations = [] # Clear annotations
current_move_index = -1 # Reset move index
return board, selected_piece, current_player, dragging_piece, piece_drag_offset, dragged_pos, move_history, annotations, current_move_index
# Initialize game state
board, selected_piece, current_player, dragging_piece, piece_drag_offset, dragged_pos, move_history, annotations, current_move_index = reset_game()
def draw_moves():
font = pygame.font.Font(None, 24) # Use a default font
text_start_x = WIDTH + 30 # Position text to the right of the board
text_start_y = 20
screen.fill(BROWN, (WIDTH, 0, 200, HEIGHT)) # Background for moves area
# Loop through all the move annotations
for i, annotation in enumerate(annotations):
move_text = font.render(f"{i + 1}. {annotation}", True, BLACK)
screen.blit(move_text, (text_start_x, text_start_y + i * 20)) # Display moves with spacing
# If this is the current move, draw a small circle next to it
if i == current_move_index:
pygame.draw.circle(screen, BLACK, (text_start_x - 15, text_start_y + i * 20 + 10), 5) # (x, y) coordinates and radius
while True:
draw_board()
draw_pieces(board, selected_piece) # Pass selected_piece here
draw_moves() # Draw the move list with current move indicator
# Draw the restart button
restart_button_rect = draw_restart_button()
# If dragging, draw the selected piece following the mouse
if dragging_piece and dragged_pos:
piece_image = WHITE_PIECE if current_player == 2 else BLACK_PIECE
screen.blit(piece_image, (dragged_pos[0] - piece_drag_offset[0], dragged_pos[1] - piece_drag_offset[1]))
# AI makes a move if it's White's turn
if current_player == 2: # White is the AI
best_move = ai_move(board, current_player, depth=3, rows=ROWS, cols=COLS) # Call AI move with depth limit
if best_move:
move_history, annotations, current_move_index = make_move(
board, best_move[0], best_move[1], move_history, annotations, current_move_index, current_player
)
current_player = 3 - current_player # Switch turns to Black (Human)
# Human (Black) can make moves manually
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit() # Quit the game only if the user closes the window
sys.exit()
# Handle button clicks for restart
if event.type == pygame.MOUSEBUTTONDOWN:
if restart_button_rect.collidepoint(event.pos): # Check if restart button was clicked
board, selected_piece, current_player, dragging_piece, piece_drag_offset, dragged_pos, move_history, annotations, current_move_index = reset_game()
# Handle mouse button down for piece selection and movement (only for Human's turn)
if current_player == 1: # Black (Human) plays
if event.type == pygame.MOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
row, col = pos[1] // SQUARE_SIZE, pos[0] // SQUARE_SIZE
# Check if the click is within the bounds of the board
if 0 <= row < ROWS and 0 <= col < COLS:
# If not dragging and selecting the current player's piece
if not dragging_piece and board[row][col] == current_player:
selected_piece = (row, col)
dragging_piece = True
piece_drag_offset = (pos[0] % SQUARE_SIZE, pos[1] % SQUARE_SIZE) # Store the offset to center piece on cursor
dragged_pos = pos # Start dragging from current mouse position
# Update dragged position while dragging
if event.type == pygame.MOUSEMOTION and dragging_piece:
dragged_pos = event.pos # Update the position to follow the mouse
if event.type == pygame.MOUSEBUTTONUP and dragging_piece:
pos = pygame.mouse.get_pos()
row, col = pos[1] // SQUARE_SIZE, pos[0] // SQUARE_SIZE
# Check if the release is within the bounds of the board
if 0 <= row < ROWS and 0 <= col < COLS:
if selected_piece:
# Calculate captures before checking them
captures = available_captures(board, current_player)
# Check if captures are available
if captures:
for start, end in captures:
if selected_piece == start and (row, col) == end:
move_history, annotations, current_move_index = make_move(
board, selected_piece, (row, col), move_history, annotations, current_move_index, current_player
)
current_player = 3 - current_player # Switch players
break
else:
# Allow regular moves if no captures are available
if is_valid_move(board, selected_piece, (row, col), current_player):
move_history, annotations, current_move_index = make_move(
board, selected_piece, (row, col), move_history, annotations, current_move_index, current_player
)
current_player = 3 - current_player # Switch players
selected_piece = None
dragging_piece = False # Stop dragging after the drop
# Navigate move history with arrow keys
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT: # Go back one move
if current_move_index >= 0:
board, current_move_index = undo_move(board, move_history, current_move_index)
current_player = 3 - current_player # Switch turns after undo
elif event.key == pygame.K_RIGHT: # Go forward one move
if current_move_index < len(move_history) - 1:
board, current_move_index = redo_move(board, move_history, current_move_index)
current_player = 3 - current_player # Switch turns after redo
pygame.display.flip()
clock.tick(60)
if __name__ == "__main__":
main()