feat: maximize pylint score

This commit is contained in:
Jakub Kliszko 2023-04-08 19:54:56 +02:00
parent 474ffde3a5
commit 038ecb090e

View File

@ -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 Program that plays draughts (checkers) with user on 8x8 board using min-max with alpha-beta pruning
""" """
import re
import copy
class Game: class Game:
@ -43,12 +43,12 @@ class Game:
(x_coordinate, y_coordinate, False)) (x_coordinate, y_coordinate, False))
return black_positions 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""" """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}!") # print(f"Illegal move! Final x coordinate must be between 0 and {self.board_size-1}!")
return True 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}!") # print(f"Illegal move! Final y coordinate must be between 0 and {self.board_size-1}!")
return True return True
return False return False
@ -64,118 +64,125 @@ class Game:
return False return False
def check_piece_king(self, coords, color): 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": if color == "white":
return (*coords, True) in self.white_positions return (*coords, True) in self.white_positions
else: if color == "black":
return (*coords, True) in self.black_positions 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 return False
def check_capture(self, from_, to, color): # https://stackoverflow.com/a/2191707
"""Check if a piece was captured for a given move. Return captured piece coordinates or None""" 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 # 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 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"): if color == "white" and self.check_piece_exists(middle, "black"):
return middle 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 middle
return None 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""" """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: if give_feedback:
print("Illegal move! Final position is out of the bounds of the board") print("Illegal move! Final position is out of the bounds of the board")
return False return False
if not self.check_piece_exists(from_, color): if not self.check_piece_exists(from_, color):
if give_feedback: if give_feedback:
print( 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 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: if give_feedback:
print("Illegal move! Cannot move to position taken by another piece") print("Illegal move! Cannot move to position taken by another piece")
return False return False
capture = self.check_capture(from_, to, color) capture = self.check_capture(from_, to_, color)
if capture == None: if capture is None:
if self.check_move_piece_capable(from_, to, color): if self.check_move_piece_capable(from_, to_, color):
return True return True
else:
if give_feedback: if give_feedback:
print('Illegal move! You can only move diagonally') print('Illegal move! You can only move diagonally')
return False return False
return capture 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 a piece on the board and remove any captured pieces"""
move_legal = self.check_move_legal(from_, to, color) move_legal = self.check_move_legal(from_, to_, color)
if move_legal == False: if move_legal is False:
return 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) king = self.check_piece_king(from_, color)
if (color == "white"): if color == "white":
self.white_positions.remove((*from_, king)) self.white_positions.remove((*from_, king))
if (capture): if capture:
captured_king = self.check_piece_king(capture, "black") captured_king = self.check_piece_king(capture, "black")
self.black_positions.remove((*move_legal, captured_king)) self.black_positions.remove((*move_legal, captured_king))
if to[1] == self.board_size-1: if to_[1] == self.board_size-1:
self.white_positions.append((*to, True)) self.white_positions.append((*to_, True))
else: else:
self.white_positions.append((*to, king)) self.white_positions.append((*to_, king))
else: else:
self.black_positions.remove((*from_, king)) self.black_positions.remove((*from_, king))
if (capture): if capture:
captured_king = self.check_piece_king(capture, "white") captured_king = self.check_piece_king(capture, "white")
self.white_positions.remove((*move_legal, captured_king)) self.white_positions.remove((*move_legal, captured_king))
if to[1] == 0: if to_[1] == 0:
self.black_positions.append((*to, True)) self.black_positions.append((*to_, True))
else: else:
self.black_positions.append((*to, king)) self.black_positions.append((*to_, king))
return True return True
def print_board(self): def print_board(self):
"""Print the board in the console""" """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="") print(" ", end="")
for x in range(self.board_size): for col in range(self.board_size):
print(f" {chr(ord('a')+x)} ", end="") print(f" {chr(ord('a')+col)} ", end="")
print(" ") print(" ")
line = 0 line = 0
for y in range(self.board_size*4): for row in range(self.board_size*4):
for x in range(self.board_size): for col in range(self.board_size):
bg = "#" if x % 2 == (y//4) % 2 else " " background = "#" if col % 2 == (row//4) % 2 else " "
checker = bg checker = get_piece_code(col, row//4, background)
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 col == 0:
if (line % 4 == 2): if line % 4 == 2:
print('{:3d}'.format(y//4), end="") print(f"{row//4:3d}", end="")
else: else:
print(" ", end="") print(" ", end="")
@ -183,59 +190,61 @@ class Game:
case 0: case 0:
print("+---", end="") print("+---", end="")
case 1 | 3: case 1 | 3:
print(f"|{3*bg}", end="") print(f"|{3*background}", end="")
case 2: case 2:
print(f"|{bg}{checker}{bg}", end="") print(f"|{background}{checker}{background}", end="")
if (line % 4 == 0): if line % 4 == 0:
print("+") print("+")
else: else:
print(f"|{y//4}" if line % 4 == 2 else "|") print(f"|{row//4}" if line % 4 == 2 else "|")
line += 1 line += 1
print(" ", end="") print(" ", end="")
for x in range(self.board_size): for col in range(self.board_size):
print("+---", end="") print("+---", end="")
print("+\n ", end="") print("+\n ", end="")
for x in range(self.board_size): for col in range(self.board_size):
print(f" {chr(ord('a')+x)}", end=" ") print(f" {chr(ord('a')+col)}", end=" ")
print() print()
# Ran first in the code # Ran first in the code
def get_possible_moves_capture(self, from_, color): def get_possible_moves_capture(self, from_, color):
"""Return all possible captures for a piece"""
# all capturing moves: # all capturing moves:
legal_moves = [] legal_moves = []
move_down_left_two = (from_[0] + 2, from_[1] - 2) move_down_left_two = (from_[0] + 2, from_[1] - 2)
move_down_right_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_left_two = (from_[0] - 2, from_[1] - 2)
move_up_right_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)) 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)) 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)) 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)) legal_moves.append((from_, move_up_right_two))
return legal_moves return legal_moves
def get_possible_moves_non_capture(self, from_, color): def get_possible_moves_non_capture(self, from_, color):
"""Return all possible moves that are not captures for a piece"""
# all non-capturing moves # all non-capturing moves
legal_moves = [] legal_moves = []
move_down_left_one = (from_[0] + 1, from_[1] - 1) move_down_left_one = (from_[0] + 1, from_[1] - 1)
move_down_right_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_left_one = (from_[0] - 1, from_[1] - 1)
move_up_right_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)) 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)) 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)) 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)) legal_moves.append((from_, move_up_right_one))
return legal_moves return legal_moves
def get_possible_moves(self, color): 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 = [] legal_moves = []
captures = [] captures = []
if color == "white": if color == "white":
@ -256,13 +265,16 @@ class Game:
return captures return captures
return legal_moves + captures return legal_moves + captures
def alpha_beta(self, depth, alpha, beta, color, current_color=None): def alpha_beta(self, depth, alpha_beta, color, current_color=None):
if 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 current_color = color
if depth == 0: if depth == 0:
return self.evaluate(color), None return self.evaluate(color), None
alpha, beta = alpha_beta
opposite_color = 'white' if current_color == 'black' else 'black' opposite_color = 'white' if current_color == 'black' else 'black'
if current_color == color: if current_color == color:
max_eval = float('-inf') max_eval = float('-inf')
@ -271,42 +283,44 @@ class Game:
for move in self.get_possible_moves(current_color): for move in self.get_possible_moves(current_color):
new_state = copy.deepcopy(self) new_state = copy.deepcopy(self)
new_state.make_move(*move, current_color) new_state.make_move(*move, current_color)
eval, _ = new_state.alpha_beta( eval_, _ = new_state.alpha_beta(
depth-1, alpha, beta, color, opposite_color) depth-1, alpha_beta, color, opposite_color)
if eval > max_eval: if eval_ > max_eval:
max_eval = eval max_eval = eval_
best_move = move best_move = move
alpha = max(alpha, eval) alpha = max(alpha, eval_)
if alpha >= beta: if alpha >= beta:
break break
return max_eval, best_move return max_eval, best_move
else: if opposite_color == color:
min_eval = float('inf') min_eval = float('inf')
best_move = None best_move = None
for move in self.get_possible_moves(current_color): for move in self.get_possible_moves(current_color):
new_state = copy.deepcopy(self) new_state = copy.deepcopy(self)
new_state.make_move(*move, current_color) new_state.make_move(*move, current_color)
eval, _ = new_state.alpha_beta( eval_, _ = new_state.alpha_beta(
depth-1, alpha, beta, color, opposite_color) depth-1, alpha_beta, color, opposite_color)
if eval < min_eval: if eval_ < min_eval:
min_eval = eval min_eval = eval_
best_move = move best_move = move
beta = min(beta, eval) beta = min(beta, eval_)
if beta <= alpha: if beta <= alpha:
break break
return min_eval, best_move return min_eval, best_move
return None
def evaluate(self, color): def evaluate(self, color):
"""Evaluates the state of the board for a given color"""
white_score = 0 white_score = 0
black_score = 0 black_score = 0
@ -324,12 +338,14 @@ class Game:
return white_score - black_score if color == 'white' else black_score - white_score return white_score - black_score if color == 'white' else black_score - white_score
def input_to_coordinates(self, input): def input_to_coordinates(self, user_input):
pos_x = ord(input[0])-ord('a') """Change input from a1 form to tuple form"""
pos_y = int(input[1::]) pos_x = ord(user_input[0])-ord('a')
pos_y = int(user_input[1::])
return pos_x, pos_y return pos_x, pos_y
def handle_player_move(self, color): def handle_player_move(self, color):
"""Prompt player to move, validate their input and make move"""
has_moved = False has_moved = False
possible_moves = self.get_possible_moves(color) possible_moves = self.get_possible_moves(color)
while not has_moved: while not has_moved:
@ -354,7 +370,8 @@ class Game:
self.print_board() self.print_board()
def start_game(self, player_color='black'): 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') print('Invalid color! Color can be black or white')
return return
ai_color = 'black' if player_color == 'white' else 'white' ai_color = 'black' if player_color == 'white' else 'white'
@ -368,10 +385,12 @@ class Game:
if len(possible_moves_ai) == 0: if len(possible_moves_ai) == 0:
print(f'Game over, {player_color} wins') print(f'Game over, {player_color} wins')
return 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) game.make_move(*ai_move, ai_color)
print( 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() game.print_board()
possible_moves_player = game.get_possible_moves(player_color) possible_moves_player = game.get_possible_moves(player_color)