Mostly? finished the halma part 2 HW

This commit is contained in:
Charles Descamps 2025-04-20 16:36:43 -07:00
parent 5f87072b1b
commit 2780fc2eed

View file

@ -93,6 +93,8 @@ class Halma:
self.setup_board()
# Show the UI
self.create_ui()
# Start the game loop
self.handle_turn()
def setup_board(self):
# Determine the number of rows/columns for the goal area based on the board size
@ -176,45 +178,41 @@ class Halma:
self.canvas.create_oval(x1 + 5, y1 + 5, x2 - 5, y2 - 5, fill=PLAYER_2_COLOR)
def on_click(self, event):
# Bound function to the left mouse button, controls the game
# Calculate where the click happened
# calculates the clicked position on the board
clicked_column = (event.x - 50) // self.cell_size
clicked_row = (event.y - 50) // self.cell_size
# Ensure the click is within the board boundaries
# ensures the click is within the board boundaries
if not (0 <= clicked_column < self.board_size and 0 <= clicked_row < self.board_size):
return
# Check if the clicked piece belongs to the current player
if not hasattr(self, "selected_piece") or self.selected_piece is None:
if self.board[clicked_row][clicked_column] == self.current_player:
self.selected_piece = (clicked_column, clicked_row)
self.valid_moves = self.move_handler.generate_moves(self.board, (clicked_column, clicked_row))
self.highlight_piece(clicked_column, clicked_row)
# Redraw the board to show valid moves
self.draw_board()
else:
# Attempt to move the selected piece
start_position = self.selected_piece
end_position = (clicked_column, clicked_row)
if end_position in self.valid_moves and self.move_handler.is_valid_move(self.board, start_position, end_position):
# Handle the move
result = self.make_move(start_position, end_position)
if result == "continue":
# Allow the player to continue jumping
self.status_label.config(text="Jump Possible.")
return
elif result:
# Move completed, check the winner
self.check_winner()
# Switch to the other player
self.current_player = 3 - self.current_player
current_color = PLAYER_1_COLOR.capitalize() if self.current_player == 1 else PLAYER_2_COLOR.capitalize()
self.status_label.config(text=f"Player {self.current_player}'s turn ({current_color})")
# checks if it's the human player's turn
if self.current_player == self.human_player_color:
# handles human player's logic
if not hasattr(self, "selected_piece") or self.selected_piece is None:
if self.board[clicked_row][clicked_column] == self.current_player:
self.selected_piece = (clicked_column, clicked_row)
self.valid_moves = self.move_handler.generate_moves(self.board, (clicked_column, clicked_row))
self.highlight_piece(clicked_column, clicked_row)
self.draw_board()
else:
print(f"Invalid Move. The move you attempted is invalid. Try again.")
# Reset the selected piece and valid moves after attempting a move
self.selected_piece = None
self.valid_moves = []
self.draw_board()
start_position = self.selected_piece
end_position = (clicked_column, clicked_row)
if end_position in self.valid_moves and self.move_handler.is_valid_move(self.board, start_position, end_position):
result = self.make_move(start_position, end_position)
if result == "continue":
self.status_label.config(text="Jump Possible.")
return
elif result:
self.check_winner()
self.current_player = 3 - self.current_player
current_color = PLAYER_1_COLOR.capitalize() if self.current_player == 1 else PLAYER_2_COLOR.capitalize()
self.status_label.config(text=f"Player {self.current_player}'s turn ({current_color})")
else:
print(f"invalid move. the move you attempted is invalid. try again.")
self.selected_piece = None
self.valid_moves = []
self.draw_board()
# triggers the next turn
self.handle_turn()
def highlight_piece(self, column, row):
x1, y1 = column * self.cell_size + 50, row * self.cell_size + 50
@ -303,6 +301,148 @@ class Halma:
print(f"\tPlayer 1 Jumps: {self.player_1_jumps}\n\tPlayer 2 Jumps: {self.player_2_jumps}")
self.window.quit()
def ai_move(self):
best_score = float('-inf') if self.current_player == 1 else float('inf')
best_move = None
for y in range(self.board_size):
for x in range(self.board_size):
if self.board[y][x] == self.current_player:
moves = self.move_handler.generate_moves(self.board, (x, y))
for move in moves:
new_board = [row[:] for row in self.board]
new_board[y][x] = 0
new_board[move[1]][move[0]] = self.current_player
score = alpha_beta_pruning(
new_board, depth=3, alpha=float('-inf'), beta=float('inf'),
is_maximizing=(self.current_player == 1),
move_handler=self.move_handler, board_size=self.board_size
)
if (self.current_player == 1 and score > best_score) or (self.current_player == 2 and score < best_score):
best_score = score
best_move = ((x, y), move)
if best_move:
self.make_move(best_move[0], best_move[1])
def handle_turn(self):
# checks whose turn it is
if self.current_player == self.human_player_color:
# waits for the human player to make a move
return
else:
# triggers the ai's move
self.ai_move()
self.check_winner()
# switches to the next player
self.current_player = 3 - self.current_player
# updates the status label with the current player's turn
current_color = PLAYER_1_COLOR.capitalize() if self.current_player == 1 else PLAYER_2_COLOR.capitalize()
self.status_label.config(text=f"Player {self.current_player}'s turn ({current_color})")
self.draw_board()
# schedules the next turn to check if it's the ai's turn again
self.window.after(100, self.handle_turn)
def util(board, board_size):
# defines the goal areas for both players
player_1_goal = [(board_size - 1 - y, board_size - 1 - x)
for y in range(3) for x in range(3) if x + y < 3]
player_2_goal = [(y, x) for y in range(3) for x in range(3) if x + y < 3]
# calculates the score for player 1 based on distance to their goal
player_1_score = sum(min(abs(y - gy) + abs(x - gx) for gy, gx in player_1_goal)
for y in range(board_size) for x in range(board_size) if board[y][x] == 1)
# calculates the score for player 2 based on distance to their goal
player_2_score = sum(min(abs(y - gy) + abs(x - gx) for gy, gx in player_2_goal)
for y in range(board_size) for x in range(board_size) if board[y][x] == 2)
# returns the difference in scores (lower is better for player 2, higher for player 1)
return player_2_score - player_1_score
def minimax(board, depth, is_maximizing, move_handler, board_size):
# returns the utility score if the maximum depth is reached
if depth == 0:
return util(board, board_size)
if is_maximizing:
# maximizes the score for player 1
max_eval = float('-inf')
for y in range(board_size):
for x in range(board_size):
if board[y][x] == 1:
moves = move_handler.generate_moves(board, (x, y))
for move in moves:
# creates a copy of the board and applies the move
new_board = [row[:] for row in board]
new_board[y][x] = 0
new_board[move[1]][move[0]] = 1
# recursively evaluates the move
eval = minimax(new_board, depth - 1, False, move_handler, board_size)
max_eval = max(max_eval, eval)
return max_eval
else:
# minimizes the score for player 2
min_eval = float('inf')
for y in range(board_size):
for x in range(board_size):
if board[y][x] == 2:
moves = move_handler.generate_moves(board, (x, y))
for move in moves:
# creates a copy of the board and applies the move
new_board = [row[:] for row in board]
new_board[y][x] = 0
new_board[move[1]][move[0]] = 2
# recursively evaluates the move
eval = minimax(new_board, depth - 1, True, move_handler, board_size)
min_eval = min(min_eval, eval)
return min_eval
def alpha_beta_pruning(board, depth, alpha, beta, is_maximizing, move_handler, board_size):
# returns the utility score if the maximum depth is reached
if depth == 0:
return util(board, board_size)
if is_maximizing:
# maximizes the score for player 1
max_eval = float('-inf')
for y in range(board_size):
for x in range(board_size):
if board[y][x] == 1:
moves = move_handler.generate_moves(board, (x, y))
for move in moves:
# creates a copy of the board and applies the move
new_board = [row[:] for row in board]
new_board[y][x] = 0
new_board[move[1]][move[0]] = 1
# recursively evaluates the move with alpha-beta pruning
eval = alpha_beta_pruning(new_board, depth - 1, alpha, beta, False, move_handler, board_size)
max_eval = max(max_eval, eval)
alpha = max(alpha, eval)
# prunes the branch if beta is less than or equal to alpha
if beta <= alpha:
break
return max_eval
else:
# minimizes the score for player 2
min_eval = float('inf')
for y in range(board_size):
for x in range(board_size):
if board[y][x] == 2:
moves = move_handler.generate_moves(board, (x, y))
for move in moves:
# creates a copy of the board and applies the move
new_board = [row[:] for row in board]
new_board[y][x] = 0
new_board[move[1]][move[0]] = 2
# recursively evaluates the move with alpha-beta pruning
eval = alpha_beta_pruning(new_board, depth - 1, alpha, beta, True, move_handler, board_size)
min_eval = min(min_eval, eval)
beta = min(beta, eval)
# prunes the branch if beta is less than or equal to alpha
if beta <= alpha:
break
return min_eval
if __name__ == "__main__":
# Default values for the game
board_size = 8