From 038ecb090e100b54eb8faaff97d3dd4b39280a51 Mon Sep 17 00:00:00 2001 From: Jakub Kliszko Date: Sat, 8 Apr 2023 19:54:56 +0200 Subject: [PATCH] feat: maximize pylint score --- lab2/main.py | 219 ++++++++++++++++++++++++++++----------------------- 1 file changed, 119 insertions(+), 100 deletions(-) diff --git a/lab2/main.py b/lab2/main.py index ada6ca8a..6e5f716d 100644 --- a/lab2/main.py +++ b/lab2/main.py @@ -1,8 +1,8 @@ -import re -import copy """ Program that plays draughts (checkers) with user on 8x8 board using min-max with alpha-beta pruning """ +import re +import copy class Game: @@ -43,12 +43,12 @@ class Game: (x_coordinate, y_coordinate, False)) return black_positions - def check_move_out_of_bounds(self, from_, to): + def check_move_out_of_bounds(self, 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: + 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: + 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 @@ -64,118 +64,125 @@ class Game: return False def check_piece_king(self, coords, color): - """Check if a piece of in a given spot and of a given color is a king. Return false if no piece is found""" + """Check if a piece of in a given spot and of a given color is a king. + Return false if no piece is found""" if color == "white": return (*coords, True) in self.white_positions - else: + if color == "black": return (*coords, True) in self.black_positions - - # https://stackoverflow.com/a/2191707 - def check_move_piece_capable(self, from_, to, color): - """Check if the move is exactly one square diagonally""" - if abs(to[0] - from_[0]) == 1: - if self.check_piece_king(from_, color): - return True - else: - if color == "white": - return to[1] == from_[1] + 1 - elif color == "black": - return to[1] == from_[1] - 1 return False - def check_capture(self, from_, to, color): - """Check if a piece was captured for a given move. Return captured piece coordinates or None""" + # https://stackoverflow.com/a/2191707 + def check_move_piece_capable(self, from_, to_, color): + """Check if the move is exactly one square diagonally""" + if abs(to_[0] - from_[0]) == 1: + if self.check_piece_king(from_, color): + return True + if color == "white": + return to_[1] == from_[1] + 1 + if color == "black": + return to_[1] == from_[1] - 1 + return False + + def check_capture(self, from_, to_, color): + """Check if a piece was captured for a given move. + Return captured piece coordinates or None""" # captures can only happen if the player moved twice-diagonally - if (abs(to[0]-from_[0]) != 2 or abs(to[1]-from_[1]) != 2): + 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 - elif color == "black" and self.check_piece_exists(middle, "white"): + if color == "black" and self.check_piece_exists(middle, "white"): return middle return None - def check_move_legal(self, from_, to, color, give_feedback=False): + def check_move_legal(self, from_, to_, color, give_feedback=False): """Check if a move is legal. Return a boolean or coordinates of captured piece""" - if self.check_move_out_of_bounds(from_, to): + if self.check_move_out_of_bounds(to_): if give_feedback: print("Illegal move! Final position is out of the bounds of the board") return False if not self.check_piece_exists(from_, color): if give_feedback: print( - "Illegal move! There is no piece on the starting position that belongs to the player") + "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"): + if self.check_piece_exists(to_, "white") or self.check_piece_exists(to_, "black"): if give_feedback: 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): + capture = self.check_capture(from_, to_, color) + if capture is None: + if self.check_move_piece_capable(from_, to_, color): return True - else: - if give_feedback: - print('Illegal move! You can only move diagonally') - return False + if give_feedback: + print('Illegal move! You can only move diagonally') + return False return capture - def make_move(self, from_, to, color): + def make_move(self, from_, to_, color): """Move a piece on the board and remove any captured pieces""" - move_legal = self.check_move_legal(from_, to, color) - if move_legal == False: + move_legal = self.check_move_legal(from_, to_, color) + if move_legal is False: return False - capture = move_legal if type(move_legal) is tuple else None + capture = move_legal if isinstance(move_legal, tuple) else None king = self.check_piece_king(from_, color) - if (color == "white"): + if color == "white": self.white_positions.remove((*from_, king)) - if (capture): + if capture: captured_king = self.check_piece_king(capture, "black") self.black_positions.remove((*move_legal, captured_king)) - if to[1] == self.board_size-1: - self.white_positions.append((*to, True)) + if to_[1] == self.board_size-1: + self.white_positions.append((*to_, True)) else: - self.white_positions.append((*to, king)) + self.white_positions.append((*to_, king)) else: self.black_positions.remove((*from_, king)) - if (capture): + if capture: captured_king = self.check_piece_king(capture, "white") self.white_positions.remove((*move_legal, captured_king)) - if to[1] == 0: - self.black_positions.append((*to, True)) + if to_[1] == 0: + self.black_positions.append((*to_, True)) else: - self.black_positions.append((*to, king)) + self.black_positions.append((*to_, king)) return True def print_board(self): """Print the board in the console""" + + def get_piece_code(self, pos, background): + """Return code piece""" + if (*pos, False) in self.white_positions: + return "w" + if (*pos, True) in self.white_positions: + return "W" + if (*pos, False) in self.black_positions: + return "b" + if (*pos, True) in self.black_positions: + return "B" + return background + print(" ", end="") - for x in range(self.board_size): - print(f" {chr(ord('a')+x)} ", end="") + for col in range(self.board_size): + print(f" {chr(ord('a')+col)} ", 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" + for row in range(self.board_size*4): + for col in range(self.board_size): + background = "#" if col % 2 == (row//4) % 2 else " " + checker = get_piece_code(col, row//4, background) - if (x == 0): - if (line % 4 == 2): - print('{:3d}'.format(y//4), end="") + if col == 0: + if line % 4 == 2: + print(f"{row//4:3d}", end="") else: print(" ", end="") @@ -183,59 +190,61 @@ class Game: case 0: print("+---", end="") case 1 | 3: - print(f"|{3*bg}", end="") + print(f"|{3*background}", end="") case 2: - print(f"|{bg}{checker}{bg}", end="") - if (line % 4 == 0): + print(f"|{background}{checker}{background}", end="") + if line % 4 == 0: print("+") else: - print(f"|{y//4}" if line % 4 == 2 else "|") + print(f"|{row//4}" if line % 4 == 2 else "|") line += 1 print(" ", end="") - for x in range(self.board_size): + for col in range(self.board_size): print("+---", end="") print("+\n ", end="") - for x in range(self.board_size): - print(f" {chr(ord('a')+x)}", end=" ") + for col in range(self.board_size): + print(f" {chr(ord('a')+col)}", end=" ") print() # Ran first in the code def get_possible_moves_capture(self, from_, color): + """Return all possible captures for a piece""" # 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: + if self.check_move_legal(from_, move_down_left_two, color) is not False: legal_moves.append((from_, move_down_left_two)) - if self.check_move_legal(from_, move_down_right_two, color) != False: + if self.check_move_legal(from_, move_down_right_two, color) is not False: legal_moves.append((from_, move_down_right_two)) - if self.check_move_legal(from_, move_up_left_two, color) != False: + if self.check_move_legal(from_, move_up_left_two, color) is not False: legal_moves.append((from_, move_up_left_two)) - if self.check_move_legal(from_, move_up_right_two, color) != False: + if self.check_move_legal(from_, move_up_right_two, color) is not False: legal_moves.append((from_, move_up_right_two)) return legal_moves def get_possible_moves_non_capture(self, from_, color): + """Return all possible moves that are not captures for a piece""" # 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: + if self.check_move_legal(from_, move_down_left_one, color) is not False: legal_moves.append((from_, move_down_left_one)) - if self.check_move_legal(from_, move_down_right_one, color) != False: + if self.check_move_legal(from_, move_down_right_one, color) is not False: legal_moves.append((from_, move_down_right_one)) - if self.check_move_legal(from_, move_up_left_one, color) != False: + if self.check_move_legal(from_, move_up_left_one, color) is not False: legal_moves.append((from_, move_up_left_one)) - if self.check_move_legal(from_, move_up_right_one, color) != False: + if self.check_move_legal(from_, move_up_right_one, color) is not 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 + """Return all possible moves for a given piece color""" legal_moves = [] captures = [] if color == "white": @@ -256,13 +265,16 @@ class Game: return captures return legal_moves + captures - def alpha_beta(self, depth, alpha, beta, color, current_color=None): - if current_color == None: + def alpha_beta(self, depth, alpha_beta, color, current_color=None): + """Do alpha beta pruning for given parameters + and return the best move and its evaluated points""" + if current_color is None: current_color = color if depth == 0: return self.evaluate(color), None + alpha, beta = alpha_beta opposite_color = 'white' if current_color == 'black' else 'black' if current_color == color: max_eval = float('-inf') @@ -271,42 +283,44 @@ class Game: for move in self.get_possible_moves(current_color): new_state = copy.deepcopy(self) new_state.make_move(*move, current_color) - eval, _ = new_state.alpha_beta( - depth-1, alpha, beta, color, opposite_color) + eval_, _ = new_state.alpha_beta( + depth-1, alpha_beta, color, opposite_color) - if eval > max_eval: - max_eval = eval + if eval_ > max_eval: + max_eval = eval_ best_move = move - alpha = max(alpha, eval) + alpha = max(alpha, eval_) if alpha >= beta: break return max_eval, best_move - else: + if opposite_color == color: min_eval = float('inf') best_move = None for move in self.get_possible_moves(current_color): new_state = copy.deepcopy(self) new_state.make_move(*move, current_color) - eval, _ = new_state.alpha_beta( - depth-1, alpha, beta, color, opposite_color) + eval_, _ = new_state.alpha_beta( + depth-1, alpha_beta, color, opposite_color) - if eval < min_eval: - min_eval = eval + if eval_ < min_eval: + min_eval = eval_ best_move = move - beta = min(beta, eval) + beta = min(beta, eval_) if beta <= alpha: break return min_eval, best_move + return None def evaluate(self, color): + """Evaluates the state of the board for a given color""" white_score = 0 black_score = 0 @@ -324,12 +338,14 @@ class Game: return white_score - black_score if color == 'white' else black_score - white_score - def input_to_coordinates(self, input): - pos_x = ord(input[0])-ord('a') - pos_y = int(input[1::]) + def input_to_coordinates(self, user_input): + """Change input from a1 form to tuple form""" + pos_x = ord(user_input[0])-ord('a') + pos_y = int(user_input[1::]) return pos_x, pos_y def handle_player_move(self, color): + """Prompt player to move, validate their input and make move""" has_moved = False possible_moves = self.get_possible_moves(color) while not has_moved: @@ -354,7 +370,8 @@ class Game: self.print_board() def start_game(self, player_color='black'): - if player_color != 'black' and player_color != 'white': + """Start the main loop of the game""" + if player_color not in ('black', 'white'): print('Invalid color! Color can be black or white') return ai_color = 'black' if player_color == 'white' else 'white' @@ -368,10 +385,12 @@ class Game: if len(possible_moves_ai) == 0: print(f'Game over, {player_color} wins') return - _, ai_move = game.alpha_beta(1, 5, 10, ai_color) + _, ai_move = game.alpha_beta(1, (5, 10), ai_color) game.make_move(*ai_move, ai_color) print( - f"AI's move: {chr(ord('a')+ai_move[0][0])}{ai_move[0][1]} {chr(ord('a')+ai_move[1][0])}{ai_move[1][1]}") + "AI's move: " + f"{chr(ord('a')+ai_move[0][0])}{ai_move[0][1]} " + f"{chr(ord('a')+ai_move[1][0])}{ai_move[1][1]}") game.print_board() possible_moves_player = game.get_possible_moves(player_color)