Make pep8 happy
This commit is contained in:
parent
807ae7daa6
commit
6e2d2c2a7b
4 changed files with 181 additions and 131 deletions
|
@ -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,6 +33,7 @@ class PlayerEngine:
|
|||
get move
|
||||
@param board: Board
|
||||
"""
|
||||
|
||||
def get_move(self, board):
|
||||
self.logger.info("engine: {} - player:{}".format(
|
||||
self._get_class_name(),
|
||||
|
@ -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()))
|
||||
|
@ -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
|
||||
|
@ -196,10 +212,10 @@ class MinmaxPlayerEngine(PlayerEngine):
|
|||
@param depth: search depth
|
||||
@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,10 +271,10 @@ 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
|
||||
leafs += le
|
||||
if value >= alpha:
|
||||
alpha = value
|
||||
move = m
|
||||
|
@ -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
|
||||
"""
|
||||
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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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,33 +51,34 @@ 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
|
||||
@param board: reversi board object
|
||||
@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]
|
||||
|
@ -87,9 +96,10 @@ class WeightHeuristicEngine(HeuristicEngine):
|
|||
@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,7 +131,7 @@ 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
|
||||
|
@ -135,6 +145,7 @@ class WeightHeuristicEngine(HeuristicEngine):
|
|||
@input none
|
||||
@return string
|
||||
"""
|
||||
|
||||
def show_weights(self):
|
||||
display = "\n |"
|
||||
sep = "\n----"
|
||||
|
@ -149,9 +160,12 @@ class WeightHeuristicEngine(HeuristicEngine):
|
|||
display += "\n"
|
||||
return display
|
||||
|
||||
|
||||
"""
|
||||
Full heuristic class
|
||||
"""
|
||||
|
||||
|
||||
class FullHeuristicEngine(WeightHeuristicEngine):
|
||||
|
||||
"""
|
||||
|
@ -160,5 +174,6 @@ class FullHeuristicEngine(WeightHeuristicEngine):
|
|||
@param player: int concerned player
|
||||
@return int
|
||||
"""
|
||||
|
||||
def get(self, board, player):
|
||||
return self.get_weight(board, player) + board.heuristique(player)
|
||||
|
|
|
@ -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):
|
||||
|
|
43
src/game.py
43
src/game.py
|
@ -12,66 +12,78 @@ 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',
|
||||
parser.add_argument('-we',
|
||||
'--white-engine',
|
||||
choices=engines_choices,
|
||||
help='white player engine (random)',
|
||||
default='random'
|
||||
)
|
||||
|
||||
parser.add_argument('-be', '--black-engine',
|
||||
parser.add_argument('-be',
|
||||
'--black-engine',
|
||||
choices=engines_choices,
|
||||
help='black player engine (random)',
|
||||
default='random'
|
||||
)
|
||||
|
||||
parser.add_argument('-bd', '--black-depth-exploration',
|
||||
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',
|
||||
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',
|
||||
parser.add_argument('-bh',
|
||||
'--black-heuristic-engine',
|
||||
help='Black player heutistic engine',
|
||||
choices= heuristic_choices,
|
||||
default='score',
|
||||
)
|
||||
|
||||
parser.add_argument('-wh', '--white-heuristic-engine',
|
||||
parser.add_argument('-wh',
|
||||
'--white-heuristic-engine',
|
||||
help='White player heutistic engine',
|
||||
choices=heuristic_choices,
|
||||
default='score',
|
||||
)
|
||||
|
||||
parser.add_argument('-br', '--black-randomize-moves',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
parser.add_argument('-wt',
|
||||
'--white-player-deepening-time',
|
||||
help='Time interval in seconds for Iterative Deepening - black player',
|
||||
type=int,
|
||||
default=10,
|
||||
|
@ -84,7 +96,8 @@ def parse_aguments():
|
|||
default=[-5, 2, 10,25]
|
||||
)
|
||||
|
||||
parser.add_argument('-r', '--recursions',
|
||||
parser.add_argument('-r',
|
||||
'--recursions',
|
||||
help='Number parties to play',
|
||||
type=int,
|
||||
default=1
|
||||
|
@ -133,7 +146,7 @@ if __name__ == '__main__':
|
|||
# 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()
|
||||
|
@ -153,7 +166,7 @@ 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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue