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()
|
self.setup_board()
|
||||||
# Show the UI
|
# Show the UI
|
||||||
self.create_ui()
|
self.create_ui()
|
||||||
|
# Start the game loop
|
||||||
|
self.handle_turn()
|
||||||
|
|
||||||
def setup_board(self):
|
def setup_board(self):
|
||||||
# Determine the number of rows/columns for the goal area based on the board size
|
# 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)
|
self.canvas.create_oval(x1 + 5, y1 + 5, x2 - 5, y2 - 5, fill=PLAYER_2_COLOR)
|
||||||
|
|
||||||
def on_click(self, event):
|
def on_click(self, event):
|
||||||
# Bound function to the left mouse button, controls the game
|
# calculates the clicked position on the board
|
||||||
# Calculate where the click happened
|
|
||||||
clicked_column = (event.x - 50) // self.cell_size
|
clicked_column = (event.x - 50) // self.cell_size
|
||||||
clicked_row = (event.y - 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):
|
if not (0 <= clicked_column < self.board_size and 0 <= clicked_row < self.board_size):
|
||||||
return
|
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 not hasattr(self, "selected_piece") or self.selected_piece is None:
|
||||||
if self.board[clicked_row][clicked_column] == self.current_player:
|
if self.board[clicked_row][clicked_column] == self.current_player:
|
||||||
self.selected_piece = (clicked_column, clicked_row)
|
self.selected_piece = (clicked_column, clicked_row)
|
||||||
self.valid_moves = self.move_handler.generate_moves(self.board, (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.highlight_piece(clicked_column, clicked_row)
|
||||||
# Redraw the board to show valid moves
|
|
||||||
self.draw_board()
|
self.draw_board()
|
||||||
else:
|
else:
|
||||||
# Attempt to move the selected piece
|
|
||||||
start_position = self.selected_piece
|
start_position = self.selected_piece
|
||||||
end_position = (clicked_column, clicked_row)
|
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):
|
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)
|
result = self.make_move(start_position, end_position)
|
||||||
if result == "continue":
|
if result == "continue":
|
||||||
# Allow the player to continue jumping
|
|
||||||
self.status_label.config(text="Jump Possible.")
|
self.status_label.config(text="Jump Possible.")
|
||||||
return
|
return
|
||||||
elif result:
|
elif result:
|
||||||
# Move completed, check the winner
|
|
||||||
self.check_winner()
|
self.check_winner()
|
||||||
# Switch to the other player
|
|
||||||
self.current_player = 3 - self.current_player
|
self.current_player = 3 - self.current_player
|
||||||
current_color = PLAYER_1_COLOR.capitalize() if self.current_player == 1 else PLAYER_2_COLOR.capitalize()
|
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.status_label.config(text=f"Player {self.current_player}'s turn ({current_color})")
|
||||||
else:
|
else:
|
||||||
print(f"Invalid Move. The move you attempted is invalid. Try again.")
|
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.selected_piece = None
|
||||||
self.valid_moves = []
|
self.valid_moves = []
|
||||||
self.draw_board()
|
self.draw_board()
|
||||||
|
# triggers the next turn
|
||||||
|
self.handle_turn()
|
||||||
|
|
||||||
def highlight_piece(self, column, row):
|
def highlight_piece(self, column, row):
|
||||||
x1, y1 = column * self.cell_size + 50, row * self.cell_size + 50
|
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}")
|
print(f"\tPlayer 1 Jumps: {self.player_1_jumps}\n\tPlayer 2 Jumps: {self.player_2_jumps}")
|
||||||
self.window.quit()
|
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__":
|
if __name__ == "__main__":
|
||||||
# Default values for the game
|
# Default values for the game
|
||||||
board_size = 8
|
board_size = 8
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue