Make pep8 happy

This commit is contained in:
Yorick Barbanneau 2023-12-22 13:08:26 +01:00
parent 807ae7daa6
commit 6e2d2c2a7b
4 changed files with 181 additions and 131 deletions

View file

@ -1,8 +1,12 @@
import random, math, time, signal
import random
import math
import signal
"""
Base player engine
"""
class PlayerEngine:
"""
@ -12,6 +16,7 @@ class PlayerEngine:
@param heuristic: HeuristicClass object to calculate tree heuristic
@param options: hashtable containing options
"""
def __init__(self, player, logger, heuristic, options: dict = {}):
# init logger do display informations
self.player = player
@ -28,9 +33,10 @@ class PlayerEngine:
get move
@param board: Board
"""
def get_move(self, board):
self.logger.info("engine: {} - player:{}".format(
self._get_class_name(),
self._get_class_name(),
self._get_player_name(self.player)
))
@ -39,6 +45,7 @@ class PlayerEngine:
@param none
@return: array
"""
def get_player_moves(self, board):
moves = board.legal_moves()
if self.options['randomize_moves'] is True:
@ -50,6 +57,7 @@ class PlayerEngine:
@param player: int
@return: string
"""
def _get_player_name(self, player):
return 'White (O)' if self.player == 2 else 'Black (X)'
@ -63,24 +71,27 @@ class PlayerEngine:
def _show_better_move(self, move, heuristic):
self.logger.debug(" -> Found a better move: {},{} | heuristic:{}".format(
chr(move[1] + 65),move[2],
chr(move[1] + 65), move[2],
heuristic
))
def _get_class_name(self):
return self.__class__.__name__
"""
Random game engine
"""
class RandomPlayerEngine(PlayerEngine):
class RandomPlayerEngine(PlayerEngine):
"""
Get move return a random move based on Board.legal_moves
@param player: int
@return: array
"""
def get_move(self, board):
super().get_move(board)
moves = board.legal_moves()
@ -90,14 +101,16 @@ class RandomPlayerEngine(PlayerEngine):
"""
Human player engine.
"""
class HumanPlayerEngine(PlayerEngine):
class HumanPlayerEngine(PlayerEngine):
"""
Get move return a move based on user input
@param board: Board
@return: array
"""
def get_move(self, board):
super()
move = None
@ -114,6 +127,7 @@ class HumanPlayerEngine(PlayerEngine):
@param input: string
@return: array
"""
def validate_input(self, input, board):
if input == 'print':
print("\n{}".format(board.show_board()))
@ -124,7 +138,7 @@ class HumanPlayerEngine(PlayerEngine):
for m in board.legal_moves():
text += " {}{}".format(chr(65+m[1]), m[2])
print(text)
return None
if len(input) != 2:
@ -141,13 +155,14 @@ class HumanPlayerEngine(PlayerEngine):
self.logger.error("Invalid input must be [A-J][0-9] (was {})".format(input))
return None
return [board._nextPlayer, x, y]
"""
MinMax player engine
"""
class MinmaxPlayerEngine(PlayerEngine):
@ -156,12 +171,12 @@ class MinmaxPlayerEngine(PlayerEngine):
@param board: Board
@return: array
"""
def get_move(self, board):
super().get_move(board)
move, score = self._call(board, self.options['depth'])
return move
"""
First part of the minmax algorithm, it get the best player move based on
max value
@ -169,6 +184,7 @@ class MinmaxPlayerEngine(PlayerEngine):
@param depth: search depth
@return: move and max heuristic
"""
def _call(self, board, depth):
value = -math.inf
nodes = 1
@ -194,12 +210,12 @@ class MinmaxPlayerEngine(PlayerEngine):
@param board: Board
@param friend_move: boolean does function maximise (player turn) or not (opponent turn)
@param depth: search depth
@return: heuristic score, nodes ans leafs processed
@return: heuristic score, nodes ans leafs processed
"""
def checkMinMax(self, board, friend_move:bool, depth :int = 2):
def checkMinMax(self, board, friend_move: bool, depth: int = 2):
nodes = 1
leafs = 0
move = []
if depth == 0 or board.is_game_over() or self.interrupt_search:
leafs +=1
return self.heuristic.get(board, self.player), nodes, leafs
@ -209,11 +225,11 @@ class MinmaxPlayerEngine(PlayerEngine):
moves = self.get_player_moves(board)
for m in moves:
board.push(m)
v, n, l = self.checkMinMax(board, False, depth - 1)
v, n, le = self.checkMinMax(board, False, depth - 1)
if v > value:
value = v
nodes += n
leafs += l
leafs += le
board.pop()
else:
@ -221,14 +237,15 @@ class MinmaxPlayerEngine(PlayerEngine):
moves = self.get_player_moves(board)
for m in moves:
board.push(m)
v, n, l = self.checkMinMax(board, True, depth - 1)
v, n, le = self.checkMinMax(board, True, depth - 1)
if v < value:
value = v
board.pop();
board.pop()
nodes += n
leafs += l
leafs += le
return value, nodes, leafs
class AlphabetaPlayerEngine(PlayerEngine):
def get_move(self, board):
@ -243,6 +260,7 @@ class AlphabetaPlayerEngine(PlayerEngine):
@param depth: search depth
@return: move and max heuristic
"""
def _call(self, board, depth):
self.logger.debug("Enter AlphaBeta function")
alpha = -math.inf
@ -253,11 +271,11 @@ class AlphabetaPlayerEngine(PlayerEngine):
moves = self.get_player_moves(board)
for m in moves:
board.push(m)
value, n, l = self.checkAlphaBeta(board, False, depth - 1, alpha, beta)
value, n, le = self.checkAlphaBeta(board, False, depth - 1, alpha, beta)
board.pop()
nodes += n
leafs += l
if value >= alpha:
leafs += le
if value >= alpha:
alpha = value
move = m
self._show_better_move(move, alpha)
@ -268,15 +286,17 @@ class AlphabetaPlayerEngine(PlayerEngine):
"""
recursive function to apply alphabeta
@param board: Board
@param friend_move: boolean does function maximise (player turn) or not (opponent turn)
@param friend_move: boolean does function maximise (player turn) or
not (opponent turn)
@param depth: search depth
@return: heuristic score, nodes ans leafs processed
@return: heuristic score, nodes ans leafs processed
"""
def checkAlphaBeta(self, board, friend_move : bool, depth, alpha, beta):
def checkAlphaBeta(self, board, friend_move: bool, depth, alpha, beta):
nodes = 1
leafs = 0
if depth == 0 or board.is_game_over() or self.interrupt_search:
leafs +=1
leafs += 1
return self.heuristic.get(board, self.player), nodes, leafs
if friend_move:
@ -284,11 +304,11 @@ class AlphabetaPlayerEngine(PlayerEngine):
moves = self.get_player_moves(board)
for m in moves:
board.push(m)
v, n, l = self.checkAlphaBeta(board, False, depth - 1, alpha, beta)
v, n, le = self.checkAlphaBeta(board, False, depth - 1, alpha, beta)
board.pop()
alpha = max(alpha,v)
alpha = max(alpha, v)
nodes += n
leafs += l
leafs += le
if alpha >= beta:
return beta, nodes, leafs
return alpha, nodes, leafs
@ -297,11 +317,11 @@ class AlphabetaPlayerEngine(PlayerEngine):
moves = self.get_player_moves(board)
for m in moves:
board.push(m)
v, n, l = self.checkAlphaBeta(board, True, depth - 1, alpha, beta)
board.pop();
v, n, le = self.checkAlphaBeta(board, True, depth - 1, alpha, beta)
board.pop()
beta = min(beta, v)
nodes += n
leafs += l
leafs += le
if alpha >= beta:
return alpha, nodes, leafs
return beta, nodes, leafs
@ -313,6 +333,7 @@ class MinmaxDeepeningPlayerEngine(MinmaxPlayerEngine):
@param board: Board
@return: array
"""
def get_move(self, board):
super().get_move(board)
self.interrupt_search = False
@ -325,7 +346,7 @@ class MinmaxDeepeningPlayerEngine(MinmaxPlayerEngine):
# We can go deeper than blank place in our board, then we must get
# numbers of avaible place
max_depth = (board.get_board_size()**2) - (
max_depth = (board.get_board_size()**2) - (
board.get_nb_pieces()[0] + board.get_nb_pieces()[1])
depth = self.options['depth'] if self.options['depth'] <= max_depth else max_depth
@ -357,7 +378,6 @@ class MinmaxDeepeningPlayerEngine(MinmaxPlayerEngine):
self.interrupt_search = True
class AlphaBetaDeepeningPlayerEngine(AlphabetaPlayerEngine):
"""
@ -365,6 +385,7 @@ class AlphaBetaDeepeningPlayerEngine(AlphabetaPlayerEngine):
@param board: Board
@return: array
"""
def get_move(self, board):
self.interrupt_search = False
@ -376,7 +397,7 @@ class AlphaBetaDeepeningPlayerEngine(AlphabetaPlayerEngine):
# We can go deeper than blank place in our board, then we must get
# numbers of avaible place
max_depth = (board.get_board_size()**2) - (
max_depth = (board.get_board_size()**2) - (
board.get_nb_pieces()[0] + board.get_nb_pieces()[1])
depth = self.options['depth'] if self.options['depth'] <= max_depth else max_depth
@ -402,6 +423,7 @@ class AlphaBetaDeepeningPlayerEngine(AlphabetaPlayerEngine):
"""
define an handler for the alarm signal
"""
def alarm_handler(self, signal, frame):
self.logger.debug("Raise SIGALMR Signal")
self.interrupt_search = True

View file

@ -1,7 +1,8 @@
"""
Base class for heuristic object
"""
class HeuristicEngine:
"""
@ -9,6 +10,7 @@ class HeuristicEngine:
@param logger: logging object (display verbose and debug messages)
@param options: hashtable contains options like board size or heuristic weight
"""
def __init__(self, logger, options):
self.logger = logger
self.options = options
@ -20,9 +22,12 @@ class HeuristicEngine:
def get():
raise NotImplementedError
"""
Score based heuristic class
"""
class ScoreHeuristicEngine(HeuristicEngine):
"""
@ -30,12 +35,15 @@ class ScoreHeuristicEngine(HeuristicEngine):
@param board: reversi board object
@return score: int
"""
def get(self, board, player):
return board.heuristique(player)
"""
Weight based heuristic class
"""
class WeightHeuristicEngine(HeuristicEngine):
"""
@ -43,22 +51,22 @@ class WeightHeuristicEngine(HeuristicEngine):
@param logger: logging object (display verbose and debug messages)
@param options: hashtable contains options like board size or heuristic weight
"""
def __init__(self, logger, options):
super().__init__(logger, options)
self.weights = self._get_weight_array()
self.logger.debug("{}".format(self.show_weights()))
"""
Get score
@param board: reversi board object
@param player: int concerned player
@return int
"""
def get(self, board, player):
score = self.get_weight(board, player)
return score
"""
Get score based on weight based on a weight-table built on object creation
@ -66,13 +74,14 @@ class WeightHeuristicEngine(HeuristicEngine):
@param player: int concerned player
@return int
"""
def get_weight(self, board, player):
score = 0
size = self.options['size']
w = [[ 0 for _ in range(size)] for _ in range(size)]
w = [[0 for _ in range(size)] for _ in range(size)]
for pos_x in range(self.options['size']):
for pos_y in range(self.options['size']):
p = board._board[pos_x][pos_y]
p = board._board[pos_x][pos_y]
if p == player:
score += self.weights[pos_x][pos_y]
w[pos_x][pos_y] = self.weights[pos_x][pos_y]
@ -81,15 +90,16 @@ class WeightHeuristicEngine(HeuristicEngine):
score -= self.weights[pos_x][pos_y]
w[pos_x][pos_y] = -self.weights[pos_x][pos_y]
return score
"""
Get weight array calculated with weight given with options
@param : none
@return 2D array
"""
def _get_weight_array(self):
size = self.options['size']
w = [[ 0 for _ in range(size)] for _ in range(size)]
w = [[0 for _ in range(size)] for _ in range(size)]
padding = size // 5
center = size // 2
full_range = range(self.options['size'])
@ -99,20 +109,20 @@ class WeightHeuristicEngine(HeuristicEngine):
for pos_x in full_range:
# Elements in the corner
if pos_x in [0, size -1] and pos_y in [0,size - 1]:
if pos_x in [0, size -1] and pos_y in [0, size - 1]:
w[pos_x][pos_y] = self.options['weight'][3]
# corners are a bad place!
elif (pos_x in [0, size - 1] and pos_y in [size - 2, 1]) or \
(pos_x in [1, size -2] and pos_y in [0, size - 1]):
(pos_x in [1, size - 2] and pos_y in [0, size - 1]):
w[pos_x][pos_y] = self.options['weight'][0]
# in diagonale of the corner too
elif pos_x in [size - 2, 1] and pos_y in [size - 2, 1]:
w[pos_x][pos_y] = int(self.options['weight'][0] * 1.5)
elif pos_x in [1,size - 2] and pos_y in range(2, size - 2) or \
pos_y in [1,size - 2] and pos_x in range(2, size - 2) :
elif pos_x in [1, size - 2] and pos_y in range(2, size - 2) or \
pos_y in [1, size - 2] and pos_x in range(2, size - 2):
w[pos_x][pos_y] = int(self.options['weight'][0] * 0.75)
# center border : cool but not so...
@ -121,13 +131,13 @@ class WeightHeuristicEngine(HeuristicEngine):
w[pos_x][pos_y] = int(self.options['weight'][2] // 1.25)
# Elements on the border
elif pos_x in [0, size -1] or pos_y in [0, size -1]:
elif pos_x in [0, size - 1] or pos_y in [0, size - 1]:
w[pos_x][pos_y] = self.options['weight'][2]
# Element the center
elif pos_x in center_range and pos_y in center_range:
w[pos_x][pos_y] = self.options['weight'][1]
return w
"""
@ -135,12 +145,13 @@ class WeightHeuristicEngine(HeuristicEngine):
@input none
@return string
"""
def show_weights(self):
display = "\n |"
sep = "\n----"
sep = "\n----"
for x in range(self.options['size']):
display += "{:^3}|".format(x)
sep += '----'
sep += '----'
display += sep + "\n"
for x in range(self.options['size']):
display += "{:^3}|".format(str(x))
@ -149,16 +160,20 @@ class WeightHeuristicEngine(HeuristicEngine):
display += "\n"
return display
"""
Full heuristic class
"""
class FullHeuristicEngine(WeightHeuristicEngine):
"""
Get score
@param board: reversi board object
@param player: int concerned player
@return int
"""
def get(self, board, player):
return self.get_weight(board, player) + board.heuristique(player)

View file

@ -227,7 +227,7 @@ class Board:
display += " {} |".format(str(x))
for y in range(self.get_board_size()):
display += " {} |".format(self._piece2str(self._board[x][y]))
display += "\n"#+sep+"\n"
display += "\n"
return display + sep + '\n'
def __str__(self):

View file

@ -12,96 +12,109 @@ from classes.CustomFormater import CustomFormatter
"""
Function to parse command line arguments
"""
def parse_aguments():
engines_choices=['random', 'human', 'minmax', 'alphabeta', 'id_minmax', 'id_alphabeta']
heuristic_choices=['score', 'weight', 'full']
engines_choices = ['random', 'human', 'minmax', 'alphabeta', 'id_minmax', 'id_alphabeta']
heuristic_choices = ['score', 'weight', 'full']
parser = arg.ArgumentParser('Playing Reversi with (virtual) friend')
parser.add_argument('-we', '--white-engine',
choices=engines_choices,
help='white player engine (random)',
default='random'
)
parser.add_argument('-we',
'--white-engine',
choices=engines_choices,
help='white player engine (random)',
default='random'
)
parser.add_argument('-be', '--black-engine',
choices=engines_choices,
help='black player engine (random)',
default='random'
)
parser.add_argument('-be',
'--black-engine',
choices=engines_choices,
help='black player engine (random)',
default='random'
)
parser.add_argument('-bd', '--black-depth-exploration',
help='Black player exploration depth (minmax or alphabeta engine)',
type=int,
default=3,
)
parser.add_argument('-bd',
'--black-depth-exploration',
help='Black player exploration depth (minmax or alphabeta engine)',
type=int,
default=3,
)
parser.add_argument('-wd', '--white-depth-exploration',
help='White player exploration depth (minmax or alphabeta engine)',
type=int,
default=3,
)
parser.add_argument('-wd',
'--white-depth-exploration',
help='White player exploration depth (minmax or alphabeta engine)',
type=int,
default=3,
)
parser.add_argument('-bh', '--black-heuristic-engine',
help='Black player heutistic engine',
choices= heuristic_choices,
default='score',
)
parser.add_argument('-bh',
'--black-heuristic-engine',
help='Black player heutistic engine',
choices= heuristic_choices,
default='score',
)
parser.add_argument('-wh', '--white-heuristic-engine',
help='White player heutistic engine',
choices=heuristic_choices,
default='score',
)
parser.add_argument('-wh',
'--white-heuristic-engine',
help='White player heutistic engine',
choices=heuristic_choices,
default='score',
)
parser.add_argument('-br', '--black-randomize-moves',
help='Apply a random function on moves list before explore the game tree - black player',
type=bool,
default=True,
)
parser.add_argument('-br',
'--black-randomize-moves',
help='Apply a random function on moves list before explore the game tree - black player',
type=bool,
default=True,
)
parser.add_argument('-wr', '--white-randomize-moves',
help='Apply a random function on moves list before explore the game tree - white player',
type=bool,
default=True,
)
parser.add_argument('-wr',
'--white-randomize-moves',
help='Apply a random function on moves list before explore the game tree - white player',
type=bool,
default=True,
)
parser.add_argument('-bt', '--black-player-deepening-time',
help='Time interval in seconds for Iterative Deepening - black player',
type=int,
default=10,
)
parser.add_argument('-bt',
'--black-player-deepening-time',
help='Time interval in seconds for Iterative Deepening - black player',
type=int,
default=10,
)
parser.add_argument('-wt', '--white-player-deepening-time',
help='Time interval in seconds for Iterative Deepening - black player',
type=int,
default=10,
)
parser.add_argument('-wt',
'--white-player-deepening-time',
help='Time interval in seconds for Iterative Deepening - black player',
type=int,
default=10,
)
parser.add_argument('--weight',
help='Weight table for weight based heuristic engines',
type=int,
nargs=4,
default=[-5, 2, 10,25]
)
help='Weight table for weight based heuristic engines',
type=int,
nargs=4,
default=[-5, 2, 10,25]
)
parser.add_argument('-r', '--recursions',
help='Number parties to play',
type=int,
default=1
)
parser.add_argument('-r',
'--recursions',
help='Number parties to play',
type=int,
default=1
)
parser.add_argument('--show-weights-table',
help='Display weight table used in \'weight\' and \'full\' heuristic calculation and exit',
action='store_true',
)
help='Display weight table used in \'weight\' and \'full\' heuristic calculation and exit',
action='store_true',
)
debug_group = parser.add_mutually_exclusive_group()
debug_group.add_argument('-V', '--verbose',
help='Verbose output',
action='store_true')
help='Verbose output',
action='store_true')
debug_group.add_argument('-d', '--debug',
help='Activate debug mode',
action='store_true')
help='Activate debug mode',
action='store_true')
return parser.parse_args()
@ -129,11 +142,11 @@ if __name__ == '__main__':
tty_handler = log.StreamHandler()
tty_handler.setFormatter(CustomFormatter())
logger.addHandler(tty_handler)
# IT shoud be better implemented but no time to make it clean
if args.show_weights_table:
print("{}".format(
heuristic_engine['weight'](logger,{
heuristic_engine['weight'](logger, {
'weight': args.weight,
'size': 10
}).show_weights()
@ -144,7 +157,7 @@ if __name__ == '__main__':
if args.verbose is True:
logger.setLevel(log.INFO)
logger.info('VERBOSE mode activated')
if args.debug is True:
logger.setLevel(log.DEBUG)
logger.debug('DEBUG mode activated')
@ -153,14 +166,14 @@ if __name__ == '__main__':
args.black_engine,
args.white_engine
))
logger.debug("Weight value {}".format( args.weight ))
logger.debug("Weight value {}".format(args.weight))
wplayer = player_engines[args.white_engine](
game._WHITE,
logger,
logger,
heuristic_engine[args.white_heuristic_engine](
logger, {
logger, {
'weight': args.weight,
'size': game.get_board_size()
'size': game.get_board_size()
}
),
{
@ -173,7 +186,7 @@ if __name__ == '__main__':
game._BLACK,
logger,
heuristic_engine[args.black_heuristic_engine](
logger, {
logger, {
'weight': args.weight,
'size': game.get_board_size()
}
@ -205,7 +218,7 @@ if __name__ == '__main__':
move[2]
))
game.push(move)
parties.append([recursions, game._nbBLACK, black_time, game._nbWHITE, white_time])
score = game._nbBLACK - game._nbWHITE
if score == 0: