From d4cb446264592e8ab82c6d48bb25c86b3a9d06ec Mon Sep 17 00:00:00 2001 From: Jakub Kliszko Date: Sat, 8 Apr 2023 17:50:31 +0200 Subject: [PATCH] feat: set up alpha beta --- lab2/main.py | 362 ++++++++++++++++++++++++++++----------------------- 1 file changed, 200 insertions(+), 162 deletions(-) diff --git a/lab2/main.py b/lab2/main.py index 697a6b13..f3e45809 100644 --- a/lab2/main.py +++ b/lab2/main.py @@ -1,4 +1,5 @@ import re +import copy """ Program that plays draughts (checkers) with user on 8x8 board using min-max with alpha-beta prunning """ @@ -11,8 +12,6 @@ class Game: self.board_size = size self.white_positions = self.initialize_white() self.black_positions = self.initialize_black() - # print(self.white_positions) - # print(self.black_positions) def initialize_white(self): """Initialize white pieces""" @@ -45,14 +44,14 @@ class Game: return black_positions def check_move_out_of_bounds(self, from_, to): - """Check if the move destination is out of the bounds of the board""" - if to[0] < 0 or to[0] >= self.board_size - 1: - print(f"Illegal move! Final x coordinate must be between 0 and {self.board_size}!") - return True - if to[1] < 0 or to[1] >= self.board_size - 1: - print(f"Illegal move! Final y coordinate must be between 0 and {self.board_size}!") - return True - return False + """Check if the move destination is out of the bounds of the board""" + if to[0] < 0 or to[0] > self.board_size - 1: + # print(f"Illegal move! Final x coordinate must be between 0 and {self.board_size-1}!") + return True + if to[1] < 0 or to[1] > self.board_size - 1: + # print(f"Illegal move! Final y coordinate must be between 0 and {self.board_size-1}!") + return True + return False def check_piece_exists(self, coords, color): """Check if a piece of given color exists at a given spot""" @@ -90,7 +89,7 @@ class Game: if (abs(to[0]-from_[0]) != 2 or abs(to[1]-from_[1] != 2)): return None - middle = (abs(to[0]-from_[0])//2, abs(to[1]-from_[1])//2) + middle = (abs(to[0]+from_[0])//2, abs(to[1]+from_[1])//2) if color == "white" and self.check_piece_exists(middle, "black"): return middle @@ -101,21 +100,29 @@ class Game: def check_move_legal(self, from_, to, color): """Check if a move is legal. Return a boolean or coordinates of captured piece""" if self.check_move_out_of_bounds(from_, to): - print("Illegal move! Final position is out of the bounds of the board") + if from_ == (2, 3): + print(f"To: {to}") + print("Illegal move! Final position is out of the bounds of the board") return False if not self.check_piece_exists(from_, color): - print( - "Illegal move! There is no piece on the starting position that belongs to the player") + if from_ == (2, 3): + print(f"To: {to}") + print( + "Illegal move! There is no piece on the starting position that belongs to the player") return False if self.check_piece_exists(to, "white") or self.check_piece_exists(to, "black"): - print("Illegal move! Cannot move to position taken by another piece") + if from_ == (2, 3): + print(f"To: {to}") + print("Illegal move! Cannot move to position taken by another piece") return False capture = self.check_capture(from_, to, color) if capture == None: if self.check_move_piece_capable(from_, to, color): return True else: - print('Illegal move!') + if from_ == (2, 3): + print(f"To: {to}") + print('Illegal move!') return False return capture @@ -143,163 +150,186 @@ class Game: return True def print_board(self): - """Print the board in the console""" - print(" ", end="") - for x in range(self.board_size): - print(f" {chr(ord('a')+x)} ", end="") - print(" ") - line = 0 - for y in range(self.board_size*4): + """Print the board in the console""" + print(" ", end="") for x in range(self.board_size): - bg = "#" if x%2==(y//4)%2 else " " - checker = bg - if((x, y//4, False) in self.white_positions): - checker = "w" - elif((x, y//4, True) in self.white_positions): - checker = "W" - elif((x, y//4, False) in self.black_positions): - checker = "b" - elif((x, y//4, True) in self.black_positions): - checker = "B" + print(f" {chr(ord('a')+x)} ", end="") + print(" ") + line = 0 + for y in range(self.board_size*4): + for x in range(self.board_size): + bg = "#" if x % 2 == (y//4) % 2 else " " + checker = bg + if ((x, y//4, False) in self.white_positions): + checker = "w" + elif ((x, y//4, True) in self.white_positions): + checker = "W" + elif ((x, y//4, False) in self.black_positions): + checker = "b" + elif ((x, y//4, True) in self.black_positions): + checker = "B" - if(x==0): - if(line%4==2): - print('{:3d}'.format(y//4), end="") + if (x == 0): + if (line % 4 == 2): + print('{:3d}'.format(y//4), end="") + else: + print(" ", end="") + + match line % 4: + case 0: + print("+---", end="") + case 1 | 3: + print(f"|{3*bg}", end="") + case 2: + print(f"|{bg}{checker}{bg}", end="") + if (line % 4 == 0): + print("+") else: - print(" ", end="") - - match line%4: - case 0: - print("+---", end="") - case 1 | 3: - print(f"|{3*bg}", end="") - case 2: - print(f"|{bg}{checker}{bg}", end="") - if(line%4==0): - print("+") - else: - print(f"|{y//4}" if line%4==2 else "|") - line+=1 - print(" ", end="") - for x in range(self.board_size): - print("+---", end="") - print("+\n ", end="") - for x in range(self.board_size): - print(f" {chr(ord('a')+x)}", end=" ") - print() - -# Ran first in the code - def get_possible_move_down(self, from_, color): - # all moves "down": - legal_moves = [] - move_down_left_two = (from_[0] + 2, from_[1] - 2) - move_down_right_two = (from_[0] + 2, from_[1] + 2) - move_down_left_one = (from_[0] + 1, from_[1] - 1) - move_down_right_one = (from_[0] + 1, from_[1] + 1) - if self.check_move_legal(from_, move_down_left_two, color) != False: - legal_moves.append((from_, move_down_two)) - elif self.check_move_legal(from_, move_down_right_two, color) != False: - legal_moves.append((from_, move_down_right_two)) - elif self.check_move_legal(from_, move_down_left_one, color) != False: - legal_moves.append((from_, move_down_left_one)) - elif self.check_move_legal(from_, move_down_right_one, color) != False: - legal_moves.append((from_, move_down_right_one)) - return legal_moves + print(f"|{y//4}" if line % 4 == 2 else "|") + line += 1 + print(" ", end="") + for x in range(self.board_size): + print("+---", end="") + print("+\n ", end="") + for x in range(self.board_size): + print(f" {chr(ord('a')+x)}", end=" ") + print() - def get_possible_move(self, from_, color): - # get all possible moves from the pawn in from_ position - # "-" in first position -> move to the "left" - # "+" in first postiin -> move to the "right" - # "-" in second postion -> move "up" - # "+" in second position -> move "down" - legal_moves = self.get_possible_move_up(from_, color) - legal_moves.append(self.get_possible_move_down(from_, color)) - return legal_moves +# Ran first in the code + def get_possible_move_capture(self, from_, color): + print(f"Checking piece {from_}") + # all capturing moves: + legal_moves = [] + move_down_left_two = (from_[0] + 2, from_[1] - 2) + move_down_right_two = (from_[0] + 2, from_[1] + 2) + move_up_left_two = (from_[0] - 2, from_[1] - 2) + move_up_right_two = (from_[0] - 2, from_[1] + 2) + if self.check_move_legal(from_, move_down_left_two, color) != False: + legal_moves.append((from_, move_down_left_two)) + if self.check_move_legal(from_, move_down_right_two, color) != False: + legal_moves.append((from_, move_down_right_two)) + if self.check_move_legal(from_, move_up_left_two, color) != False: + legal_moves.append((from_, move_up_left_two)) + if self.check_move_legal(from_, move_up_right_two, color) != False: + legal_moves.append((from_, move_up_right_two)) + return legal_moves + + def get_possible_move_non_capture(self, from_, color): + # all non-capturing moves + legal_moves = [] + move_down_left_one = (from_[0] + 1, from_[1] - 1) + move_down_right_one = (from_[0] + 1, from_[1] + 1) + move_up_left_one = (from_[0] - 1, from_[1] - 1) + move_up_right_one = (from_[0] - 1, from_[1] + 1) + if self.check_move_legal(from_, move_down_left_one, color) != False: + legal_moves.append((from_, move_down_left_one)) + if self.check_move_legal(from_, move_down_right_one, color) != False: + legal_moves.append((from_, move_down_right_one)) + if self.check_move_legal(from_, move_up_left_one, color) != False: + legal_moves.append((from_, move_up_left_one)) + if self.check_move_legal(from_, move_up_right_one, color) != False: + legal_moves.append((from_, move_up_right_one)) + return legal_moves def get_possible_moves(self, color): - # toDo: get possible moves for single piece, then iterate through all pieces - legal_moves = [] - if color == "white": - for white_position in self.white_positions: - print((white_position[0], white_position[1])) - legal_moves.append(self.get_possible_move((white_position[0], white_position[1]), color)) - elif color == "black": - for black_position in self.black_positions: - legal_moves.append(self.get_possible_move((black_position[0], black_position[1]), color)) - return legal_moves - - def alpha_beta(self, depth, alpha, beta, player): - if depth == 0: - return self.evaluate(), None - - if player == 'x': - max_eval = float('-inf') - best_move = None - - for move in self.get_possible_moves(player): - new_state = copy.deepcopy(self) - new_state.move(*move) - eval, _ = alpha_beta(new_state, depth-1, alpha, beta, 'o') - - if eval > max_eval: - max_eval = eval - best_move = move - - alpha = max(alpha, eval) - - if alpha >= beta: - break - - return max_eval, best_move - - else: - min_eval = float('inf') - best_move = None - - for move in state.get_possible_moves(player): - new_state = copy.deepcopy(state) - new_state.move(*move) - eval, _ = alpha_beta(new_state, depth-1, alpha, beta, 'x') - - if eval < min_eval: - min_eval = eval - best_move = move - - beta = min(beta, eval) - - if beta <= alpha: - break - - return min_eval, best_move - + # toDo: get possible moves for single piece, then iterate through all pieces + legal_moves = [] + captures = [] + if color == "white": + for white_position in self.white_positions: + # print((white_position[0], white_position[1])) + legal_moves += self.get_possible_move_non_capture( + (white_position[0], white_position[1]), color) + captures += self.get_possible_move_capture( + (white_position[0], white_position[1]), color) + elif color == "black": + for black_position in self.black_positions: + legal_moves += self.get_possible_move_non_capture( + (black_position[0], black_position[1]), color) + captures += self.get_possible_move_capture( + (black_position[0], black_position[1]), color) + print('CAPTURES: ') + print(captures) + # if len(captures) > 0: + # print('CAPTURE POSSIBLE') + # print(captures) + # return captures + print('LEGAL MOVES') + print(legal_moves + captures) + return legal_moves + captures + + def alpha_beta(self, depth, alpha, beta, color): + if depth == 0: + return self.evaluate(), None + + if color == 'white': + max_eval = float('-inf') + best_move = None + + for move in self.get_possible_moves(color): + new_state = copy.deepcopy(self) + new_state.make_move(*move, 'white') + eval, _ = new_state.alpha_beta(depth-1, alpha, beta, 'black') + + if eval > max_eval: + max_eval = eval + best_move = move + + alpha = max(alpha, eval) + + if alpha >= beta: + break + + return max_eval, best_move + + else: + min_eval = float('inf') + best_move = None + + for move in self.get_possible_moves(color): + new_state = copy.deepcopy(self) + new_state.make_move(*move, 'black') + eval, _ = new_state.alpha_beta(depth-1, alpha, beta, 'white') + + if eval < min_eval: + min_eval = eval + best_move = move + + beta = min(beta, eval) + + if beta <= alpha: + break + + return min_eval, best_move + def evaluate(self): - white_score = 0 - black_score = 0 - - for white_position in self.white_positions: - if white_position[2]: - white_score += 2 - else: - white_score += 2 - - for black_position in self.black_positions: - if black_position[2]: - black_score += 2 - else: - black_score += 2 - - return x_score - o_score + white_score = 0 + black_score = 0 + + for white_position in self.white_positions: + if white_position[2]: + white_score += 10 + else: + white_score += 5 + + for black_position in self.black_positions: + if black_position[2]: + black_score += 10 + else: + black_score += 5 + + return white_score - black_score def input_to_coordinates(self, input): - pos_x = ord(input[0])-ord('a') - pos_y = int(input[1::]) - return pos_x, pos_y + pos_x = ord(input[0])-ord('a') + pos_y = int(input[1::]) + return pos_x, pos_y def handle_player_move(self, color): has_moved = False while not has_moved: user_input = input('How do you want to move? (format: d6 e5)\n') - regex = r"^[A-Za-z]\d+\s[A-Za-z]\d+$" + regex = r"^[a-z]\d+\s[a-z]\d+$" match = re.search(regex, user_input) if not match: print('Invalid input, try again') @@ -312,9 +342,17 @@ class Game: self.print_board() - # Ran first in the code if __name__ == "__main__": game = Game(8) - game.print_board() - game.handle_player_move('black') + while True: + game.print_board() + possible_moves_black = game.get_possible_moves('black') + if len(possible_moves_black) == 0: + print('Game over. White wins') + game.handle_player_move('black') + possible_moves_white = game.get_possible_moves('black') + if len(possible_moves_white) == 0: + print('Game over. Black wins') + _, ai_move = game.alpha_beta(1, 5, 10, 'white') + game.make_move(ai_move[0], ai_move[1], 'white')