mirror of
https://github.com/kuhyx/WUT_Computer_Science.git
synced 2026-07-04 17:43:12 +02:00
feat: maximize pylint score
This commit is contained in:
parent
474ffde3a5
commit
038ecb090e
219
lab2/main.py
219
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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user