Mostly? finished the halma part 2 HW
This commit is contained in:
parent
5f87072b1b
commit
2780fc2eed
1 changed files with 174 additions and 34 deletions
164
Halma/halma.py
164
Halma/halma.py
|
|
@ -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
|
||||
# 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)
|
||||
# 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})")
|
||||
else:
|
||||
print(f"Invalid Move. The move you attempted is invalid. Try again.")
|
||||
# Reset the selected piece and valid moves after attempting a move
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue