feat: conform to pep8

This commit is contained in:
Krzysztof Rudnicki 2023-04-10 22:32:51 +02:00
parent 469e1ca42b
commit a76b04b8f9
2 changed files with 158 additions and 115 deletions

View File

@ -1,3 +1,2 @@
[DESIGN] [DESIGN]
# Maximum number of statements in function / method body # Maximum number of statements in function / method body
max-statements=16

View File

@ -22,27 +22,25 @@ class Game:
for x_coordinate in range(self.board_size): for x_coordinate in range(self.board_size):
if y_coordinate % 2 == 0: if y_coordinate % 2 == 0:
if x_coordinate % 2 == 1: if x_coordinate % 2 == 1:
white_positions.append( white_positions.append((x_coordinate, y_coordinate, False))
(x_coordinate, y_coordinate, False))
else: else:
if x_coordinate % 2 == 0: if x_coordinate % 2 == 0:
white_positions.append( white_positions.append((x_coordinate, y_coordinate, False))
(x_coordinate, y_coordinate, False))
return white_positions return white_positions
def initialize_black(self): def initialize_black(self):
"""Initialize black pieces""" """Initialize black pieces"""
black_positions = [] black_positions = []
for y_coordinate in range(self.board_size - math.floor((self.board_size - 2) / 2), self.board_size): for y_coordinate in range(
self.board_size - math.floor((self.board_size - 2) / 2), self.board_size
):
for x_coordinate in range(self.board_size): for x_coordinate in range(self.board_size):
if y_coordinate % 2 == 0: if y_coordinate % 2 == 0:
if x_coordinate % 2 == 1: if x_coordinate % 2 == 1:
black_positions.append( black_positions.append((x_coordinate, y_coordinate, False))
(x_coordinate, y_coordinate, False))
else: else:
if x_coordinate % 2 == 0: if x_coordinate % 2 == 0:
black_positions.append( black_positions.append((x_coordinate, y_coordinate, False))
(x_coordinate, y_coordinate, False))
return black_positions return black_positions
def check_move_out_of_bounds(self, to_): def check_move_out_of_bounds(self, to_):
@ -58,10 +56,16 @@ class Game:
def check_piece_exists(self, coords, color): def check_piece_exists(self, coords, color):
"""Check if a piece of given color exists at a given spot""" """Check if a piece of given color exists at a given spot"""
if color == "white": if color == "white":
if any(piece in self.white_positions for piece in ((*coords, False), (*coords, True))): if any(
piece in self.white_positions
for piece in ((*coords, False), (*coords, True))
):
return True return True
else: else:
if any(piece in self.black_positions for piece in ((*coords, False), (*coords, True))): if any(
piece in self.black_positions
for piece in ((*coords, False), (*coords, True))
):
return True return True
return False return False
@ -91,10 +95,10 @@ class Game:
Return captured piece coordinates or None""" 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
@ -112,9 +116,12 @@ class Game:
if give_feedback: if give_feedback:
print( print(
"Illegal move! There is no piece on the " "Illegal move! There is no piece on the "
"starting position that belongs to the player") "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
@ -123,7 +130,7 @@ class Game:
if self.check_move_piece_capable(from_, to_, color): if self.check_move_piece_capable(from_, to_, color):
return True return True
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
@ -140,7 +147,7 @@ class Game:
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))
@ -184,15 +191,22 @@ class Game:
print_letters() print_letters()
print(" ") print(" ")
row_range = range( row_range = (
self.board_size*4) if not rotate else reversed(range(self.board_size*4)) range(self.board_size * 4)
if not rotate
else reversed(range(self.board_size * 4))
)
line = 0 line = 0
for row in row_range: for row in row_range:
for col in range(self.board_size): for col in range(self.board_size):
background = "#" if (col % 2 == (row//4) % 2) != rotate\ background = "#" if (col % 2 == (row // 4) % 2) != rotate else " "
else " " checker = (
checker = get_square_code((col, row//4), background)\ get_square_code((col, row // 4), background)
if not rotate else get_square_code((self.board_size-1-col, row//4), background) if not rotate
else get_square_code(
(self.board_size - 1 - col, row // 4), background
)
)
if col == 0: if col == 0:
if line % 4 == 2: if line % 4 == 2:
@ -219,7 +233,7 @@ class Game:
print_letters() print_letters()
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""" """Return all possible captures for a piece"""
# all capturing moves: # all capturing moves:
@ -264,15 +278,19 @@ class Game:
for white_position in self.white_positions: for white_position in self.white_positions:
# print((white_position[0], white_position[1])) # print((white_position[0], white_position[1]))
legal_moves += self.get_possible_moves_non_capture( legal_moves += self.get_possible_moves_non_capture(
(white_position[0], white_position[1]), color) (white_position[0], white_position[1]), color
)
captures += self.get_possible_moves_capture( captures += self.get_possible_moves_capture(
(white_position[0], white_position[1]), color) (white_position[0], white_position[1]), color
)
elif color == "black": elif color == "black":
for black_position in self.black_positions: for black_position in self.black_positions:
legal_moves += self.get_possible_moves_non_capture( legal_moves += self.get_possible_moves_non_capture(
(black_position[0], black_position[1]), color) (black_position[0], black_position[1]), color
)
captures += self.get_possible_moves_capture( captures += self.get_possible_moves_capture(
(black_position[0], black_position[1]), color) (black_position[0], black_position[1]), color
)
if len(captures) > 0: if len(captures) > 0:
return (captures, True) return (captures, True)
@ -288,16 +306,17 @@ class Game:
return self.evaluate(color), None return self.evaluate(color), None
alpha, beta = alpha_beta 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")
best_move = None best_move = None
for move in self.get_possible_moves(current_color)[0]: for move in self.get_possible_moves(current_color)[0]:
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_
@ -311,14 +330,15 @@ class Game:
return max_eval, best_move return max_eval, best_move
if opposite_color == color: 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)[0]: for move in self.get_possible_moves(current_color)[0]:
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_
@ -349,11 +369,13 @@ class Game:
else: else:
black_score += 5 black_score += 5
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, user_input): def input_to_coordinates(self, user_input):
"""Change input from a1 form to tuple form""" """Change input from a1 form to tuple form"""
pos_x = ord(user_input[0])-ord('a') pos_x = ord(user_input[0]) - ord("a")
pos_y = int(user_input[1::]) pos_y = int(user_input[1::])
return pos_x, pos_y return pos_x, pos_y
@ -363,135 +385,162 @@ class Game:
possible_moves = self.get_possible_moves(color)[0] possible_moves = self.get_possible_moves(color)[0]
while not has_moved: while not has_moved:
user_input = input( user_input = input(
f'You are {color}. How do you want to move? (format: d6 e5)\n') f"You are {color}. How do you want to move? (format: d6 e5)\n"
)
regex = r"^[a-z]\d+\s[a-z]\d+$" regex = r"^[a-z]\d+\s[a-z]\d+$"
match = re.search(regex, user_input) match = re.search(regex, user_input)
if not match: if not match:
print('Invalid input, try again') print("Invalid input, try again")
continue continue
[move_from, move_to] = user_input.split(' ') [move_from, move_to] = user_input.split(" ")
from_coordinates = self.input_to_coordinates(move_from) from_coordinates = self.input_to_coordinates(move_from)
to_coordinates = self.input_to_coordinates(move_to) to_coordinates = self.input_to_coordinates(move_to)
if not (from_coordinates, to_coordinates) in possible_moves: if not (from_coordinates, to_coordinates) in possible_moves:
legal_no_captures = self.check_move_legal( legal_no_captures = self.check_move_legal(
from_coordinates, to_coordinates, color, True) from_coordinates, to_coordinates, color, True
)
if legal_no_captures: if legal_no_captures:
print("Invalid move! You can capture a piece") print("Invalid move! You can capture a piece")
continue continue
has_moved = self.make_move(from_coordinates, to_coordinates, color) has_moved = self.make_move(from_coordinates, to_coordinates, color)
self.print_board(color == 'white') self.print_board(color == "white")
def start_game(self, player_color='black', algorithm_depth = 5): def start_game(self, player_color="black", algorithm_depth=5):
"""Start the main loop of the game""" """Start the main loop of the game"""
if player_color not in ('black', 'white'): 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"
game.print_board(player_color == 'white') game.print_board(player_color == "white")
if player_color == 'white': if player_color == "white":
game.handle_player_move('white') game.handle_player_move("white")
while True: while True:
ai_turn = True ai_turn = True
while ai_turn: while ai_turn:
possible_moves_ai = game.get_possible_moves(ai_color) possible_moves_ai = game.get_possible_moves(ai_color)
if len(possible_moves_ai[0]) == 0: if len(possible_moves_ai[0]) == 0:
print(f'Game over, {player_color} wins') print(f"Game over, {player_color} wins")
return return
_, ai_move = game.alpha_beta(algorithm_depth, (5, 10), ai_color) _, ai_move = game.alpha_beta(algorithm_depth, (5, 10), ai_color)
game.make_move(*ai_move, ai_color) game.make_move(*ai_move, ai_color)
print( print(
"AI's move: " "AI's move: "
f"{chr(ord('a')+ai_move[0][0])}{ai_move[0][1]} " f"{chr(ord('a')+ai_move[0][0])}{ai_move[0][1]} "
f"{chr(ord('a')+ai_move[1][0])}{ai_move[1][1]}") f"{chr(ord('a')+ai_move[1][0])}{ai_move[1][1]}"
game.print_board(player_color == 'white') )
game.print_board(player_color == "white")
ai_turn = game.get_possible_moves(ai_color)[1] and possible_moves_ai[1] ai_turn = game.get_possible_moves(ai_color)[1] and possible_moves_ai[1]
player_turn = True player_turn = True
while player_turn: while player_turn:
possible_moves_player = game.get_possible_moves(player_color) possible_moves_player = game.get_possible_moves(player_color)
if len(possible_moves_player[0]) == 0: if len(possible_moves_player[0]) == 0:
print(f'Game over, {ai_color} wins') print(f"Game over, {ai_color} wins")
return return
game.handle_player_move(player_color) game.handle_player_move(player_color)
player_turn = game.get_possible_moves(player_color)[1] and possible_moves_player[1] player_turn = (
game.get_possible_moves(player_color)[1]
and possible_moves_player[1]
)
def ai_turn(self, ai_color, algorithm_depth, possible_moves_ai, print_info): def ai_turn(self, ai_color, algorithm_depth, possible_moves_ai, print_info):
""" Calculates ai move and makes it """
if len(possible_moves_ai) == 0: if len(possible_moves_ai) == 0:
if print_info: if print_info:
print(f'Game over, {ai_color} loses') print(f"Game over, {ai_color} loses")
return True return True
_, ai_move = game.alpha_beta(algorithm_depth, (5, 10), ai_color) _, ai_move = game.alpha_beta(algorithm_depth, (5, 10), ai_color)
if ai_move == None: if ai_move is None:
ai_move = possible_moves_ai[0] ai_move = possible_moves_ai[0]
game.make_move(*ai_move, ai_color) game.make_move(*ai_move, ai_color)
if print_info: if print_info:
print( print(
"AI's move: " "AI's move: "
f"{chr(ord('a')+ai_move[0][0])}{ai_move[0][1]} " f"{chr(ord('a')+ai_move[0][0])}{ai_move[0][1]} "
f"{chr(ord('a')+ai_move[1][0])}{ai_move[1][1]}") f"{chr(ord('a')+ai_move[1][0])}{ai_move[1][1]}"
)
game.print_board(True) game.print_board(True)
return False return False
def auto_game(self, white_depth, black_depth): def auto_game(self, white_depth, black_depth):
"""Auto game mode between two bots""" """Auto game mode between two bots"""
game_turn = 0 game_turn = 0
max_turns = 250 max_turns = 250
while True and game_turn < max_turns: while game_turn < max_turns:
bot_white_turn = True bot_white_turn = True
while bot_white_turn: while bot_white_turn:
possible_moves_ai = game.get_possible_moves('white') possible_moves_ai = game.get_possible_moves("white")
if self.ai_turn('white', white_depth, possible_moves_ai[0], False): if self.ai_turn("white", white_depth, possible_moves_ai[0], False):
return 'white' return "white"
bot_white_turn = game.get_possible_moves('white')[1] and possible_moves_ai[1] bot_white_turn = (
game.get_possible_moves("white")[1] and possible_moves_ai[1]
)
bot_black_turn = True bot_black_turn = True
while bot_black_turn: while bot_black_turn:
possible_moves_ai = game.get_possible_moves('black') possible_moves_ai = game.get_possible_moves("black")
if self.ai_turn('black', black_depth, possible_moves_ai[0], False): if self.ai_turn("black", black_depth, possible_moves_ai[0], False):
return 'black' return "black"
bot_black_turn = game.get_possible_moves('black')[1] and possible_moves_ai[1] bot_black_turn = (
game.get_possible_moves("black")[1] and possible_moves_ai[1]
)
game_turn += 1 game_turn += 1
if game_turn >= max_turns: if game_turn >= max_turns:
print(f"Game ended after {max_turns} turns!") print(f"Game ended after {max_turns} turns!")
return '' return ""
return ""
def auto_simulation(white_depth, black_depth, iterations):
"""Runs iterations amount of simulations"""
print(
f"""Running {iterations} simulations with
white depth = {white_depth},black depth = {black_depth}"""
)
white_wins = 0
black_wins = 0
white_pieces_captured = 0
black_pieces_captured = 0
current_iteration = 0
while current_iteration < iterations:
result = game.auto_game(white_depth, black_depth)
if result == "white":
black_wins += 1
if result == "black":
white_wins += 1
if result == "":
break
white_pieces_captured += 16 - len(game.white_positions)
black_pieces_captured += 16 - len(game.black_positions)
current_iteration += 1
print(
f"""White wins = {white_wins}, Black wins = {black_wins},
white pieces captured in total = {white_pieces_captured},
black pieces captured in total = {black_pieces_captured}"""
)
print()
def auto_simulation(self, white_depth, black_depth, iterations):
print(f"Running {iterations} simulations with white depth = {white_depth}, black depth = {black_depth}")
white_wins = 0
black_wins = 0
white_pieces_captured = 0
black_pieces_captured = 0
i = 0
while i < iterations:
result = game.auto_game(white_depth, black_depth)
if result == 'white':
black_wins += 1
if result == 'black':
white_wins += 1
if result == '':
break
white_pieces_captured += 16 - len(game.white_positions)
black_pieces_captured += 16 - len(game.black_positions)
i += 1
print(f"White wins = {white_wins}, Black wins = {black_wins}, white pieces captured in total = {white_pieces_captured}, black pieces captured in total = {black_pieces_captured}")
print()
def print_help(): def print_help():
"""prints help""" """prints help"""
print( print(
"""python main.py [algorithm_depth] - play the game against the bot as black, if no algorithm depth is specified the default (5) will be set """python main.py [algorithm_depth] - play the game against the bot as black,
if no algorithm depth is specified the default (5) will be set
python main.py -h --help print this prompt python main.py -h --help print this prompt
python main.py -t --test [max_white_depth] [max_black_depth] non interactive (does not print moves) for testing how different bot depth play against eachother, if depths are not provided default value of 5 is set python main.py -t --test [max_white_depth] [max_black_depth] non interactive
(does not print moves) for testing how different bot depth play against eachother,
if depths are not provided default value of 5 is set
compares heuristic speed and path length compares heuristic speed and path length
python main.py -w --white [algorithm_depth] play as white pieces, if no algorithm depth is specified the default (5) will be set python main.py -w --white [algorithm_depth] play as white pieces,
python main.py -b --black [algorithm_depth] play as black pieces, if no algorithm depth is specified the default (5) will be set if no algorithm depth is specified the default (5) will be set
python main.py -b --black [algorithm_depth] play as black pieces,
if no algorithm depth is specified the default (5) will be set
""" """
) )
def default(color = 'black', algorithm_depth = 5): def default(color="black", algorithm_depth=5):
"""default program function -> allows to play a game against bot (by default as black)""" """default program function -> allows to play a game against bot (by default as black)"""
game.start_game(color, algorithm_depth) game.start_game(color, algorithm_depth)
@ -504,34 +553,29 @@ if __name__ == "__main__":
print_help() print_help()
sys.exit() sys.exit()
if sys.argv[1] == "-t" or sys.argv[1] == "--test": if sys.argv[1] == "-t" or sys.argv[1] == "--test":
max_white_depth = 4 MAX_WHITE_DEPTH = 4
max_black_depth = 4 MAX_BLACK_DEPTH = 4
if len(sys.argv) > 2: if len(sys.argv) > 2:
max_white_depth = int(sys.argv[2]) MAX_WHITE_DEPTH = int(sys.argv[2])
if len(sys.argv) > 3: if len(sys.argv) > 3:
max_black_depth = int(sys.argv[3]) MAX_BLACK_DEPTH = int(sys.argv[3])
for i in range(max_white_depth + 1): for i in range(MAX_WHITE_DEPTH + 1):
for j in range(max_black_depth + 1): for j in range(MAX_BLACK_DEPTH + 1):
game = Game(8) game = Game(8)
game.auto_simulation(i, j, 10) auto_simulation(i, j, 10)
sys.exit() sys.exit()
if sys.argv[1] == "-w" or sys.argv[1] == "--white": if sys.argv[1] == "-w" or sys.argv[1] == "--white":
algorithm_depth = 5 ALGORITHM_DEPTH = 5
if len(sys.argv) > 2: if len(sys.argv) > 2:
algorithm_depth = int(sys.argv[2]) ALGORITHM_DEPTH = int(sys.argv[2])
default('white', algorithm_depth) default("white", ALGORITHM_DEPTH)
sys.exit() sys.exit()
if sys.argv[1] == "-b" or sys.argv[1] == "--black": if sys.argv[1] == "-b" or sys.argv[1] == "--black":
algorithm_depth = 5 ALGORITHM_DEPTH = 5
if len(sys.argv) > 2: if len(sys.argv) > 2:
algorithm_depth = int(sys.argv[2]) ALGORITHM_DEPTH = int(sys.argv[2])
default('black', algorithm_depth) default("black", ALGORITHM_DEPTH)
sys.exit() sys.exit()
if len(sys.argv) > 1: if len(sys.argv) > 1:
default('black', int(sys.argv[1])) default("black", int(sys.argv[1]))
default() default()