Add classes and method documentation
This commit is contained in:
parent
d2ada8734f
commit
f927aa1462
2 changed files with 169 additions and 10 deletions
|
@ -1,6 +1,17 @@
|
||||||
import random, math, time, signal
|
import random, math, time, signal
|
||||||
|
|
||||||
|
"""
|
||||||
|
Base player engine
|
||||||
|
"""
|
||||||
class PlayerEngine:
|
class PlayerEngine:
|
||||||
|
|
||||||
|
"""
|
||||||
|
init
|
||||||
|
@param player: black or white player
|
||||||
|
@param logger: loggig object (display verbose / debug messages
|
||||||
|
@param heuristic: HeuristicClass object to calculate tree heuristic
|
||||||
|
@param options: hashtable containing options
|
||||||
|
"""
|
||||||
def __init__(self, player, logger, heuristic, options: dict = {}):
|
def __init__(self, player, logger, heuristic, options: dict = {}):
|
||||||
# init logger do display informations
|
# init logger do display informations
|
||||||
self.player = player
|
self.player = player
|
||||||
|
@ -12,30 +23,66 @@ class PlayerEngine:
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
self.options
|
self.options
|
||||||
))
|
))
|
||||||
|
|
||||||
|
"""
|
||||||
|
get move
|
||||||
|
@param board: Board
|
||||||
|
"""
|
||||||
def get_move(self, board):
|
def get_move(self, board):
|
||||||
self.logger.info("engine: {} - player:{}".format(
|
self.logger.info("engine: {} - player:{}".format(
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
self.get_player_name(self.player)
|
self.get_player_name(self.player)
|
||||||
))
|
))
|
||||||
|
|
||||||
|
"""
|
||||||
|
Get possibles player move an apply a Random.shuffle on it (if needed)
|
||||||
|
@param none
|
||||||
|
@return: array
|
||||||
|
"""
|
||||||
def get_player_moves(self, board):
|
def get_player_moves(self, board):
|
||||||
moves = board.legal_moves()
|
moves = board.legal_moves()
|
||||||
if self.options['randomize_moves'] is True:
|
if self.options['randomize_moves'] is True:
|
||||||
random.shuffle(moves)
|
random.shuffle(moves)
|
||||||
return moves
|
return moves
|
||||||
|
|
||||||
|
"""
|
||||||
|
Get player name based on his number
|
||||||
|
@param player: int
|
||||||
|
@return: string
|
||||||
|
"""
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_player_name(player):
|
def get_player_name(player):
|
||||||
return 'White (O)' if player == 2 else 'Black (X)'
|
return 'White (O)' if player == 2 else 'Black (X)'
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
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):
|
def get_move(self, board):
|
||||||
super().get_move(board)
|
super().get_move(board)
|
||||||
moves = board.legal_moves()
|
moves = board.legal_moves()
|
||||||
return random.choice(moves)
|
return random.choice(moves)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
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):
|
def get_move(self, board):
|
||||||
super()
|
super()
|
||||||
move = None
|
move = None
|
||||||
|
@ -46,6 +93,12 @@ class HumanPlayerEngine(PlayerEngine):
|
||||||
move = self.validate_input(user_input, board)
|
move = self.validate_input(user_input, board)
|
||||||
return move
|
return move
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Validate user input an verify than the move is possible
|
||||||
|
@param input: string
|
||||||
|
@return: array
|
||||||
|
"""
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate_input(input, board):
|
def validate_input(input, board):
|
||||||
if input == 'print':
|
if input == 'print':
|
||||||
|
@ -66,13 +119,31 @@ class HumanPlayerEngine(PlayerEngine):
|
||||||
|
|
||||||
return [board._nextPlayer, x, y]
|
return [board._nextPlayer, x, y]
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
MinMax player engine
|
||||||
|
"""
|
||||||
class MinmaxPlayerEngine(PlayerEngine):
|
class MinmaxPlayerEngine(PlayerEngine):
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Get move based on minmax algorithm
|
||||||
|
@param board: Board
|
||||||
|
@return: array
|
||||||
|
"""
|
||||||
def get_move(self, board):
|
def get_move(self, board):
|
||||||
super().get_move(board)
|
super().get_move(board)
|
||||||
move, score = self._call(board, self.options['depth'])
|
move, score = self._call(board, self.options['depth'])
|
||||||
return move
|
return move
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
First part of the minmax algorithm, it get the best player move based on
|
||||||
|
max value
|
||||||
|
@param board: Board
|
||||||
|
@param depth: search depth
|
||||||
|
@return: move and max heuristic
|
||||||
|
"""
|
||||||
def _call(self, board, depth):
|
def _call(self, board, depth):
|
||||||
value = -math.inf
|
value = -math.inf
|
||||||
nodes = 1
|
nodes = 1
|
||||||
|
@ -100,6 +171,13 @@ class MinmaxPlayerEngine(PlayerEngine):
|
||||||
))
|
))
|
||||||
return move, value
|
return move, value
|
||||||
|
|
||||||
|
"""
|
||||||
|
recursive function to apply minmax
|
||||||
|
@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
|
||||||
|
"""
|
||||||
def checkMinMax(self, board, friend_move:bool, depth :int = 2):
|
def checkMinMax(self, board, friend_move:bool, depth :int = 2):
|
||||||
nodes = 1
|
nodes = 1
|
||||||
leafs = 0
|
leafs = 0
|
||||||
|
@ -140,6 +218,13 @@ class AlphabetaPlayerEngine(PlayerEngine):
|
||||||
move, heuristic = self._call(board, self.options['depth'])
|
move, heuristic = self._call(board, self.options['depth'])
|
||||||
return move
|
return move
|
||||||
|
|
||||||
|
"""
|
||||||
|
First part of the alphabeta algorithm, it get the best player move based on
|
||||||
|
max value
|
||||||
|
@param board: Board
|
||||||
|
@param depth: search depth
|
||||||
|
@return: move and max heuristic
|
||||||
|
"""
|
||||||
def _call(self, board, depth):
|
def _call(self, board, depth):
|
||||||
self.logger.debug("Enter AlphaBeta function")
|
self.logger.debug("Enter AlphaBeta function")
|
||||||
alpha = -math.inf
|
alpha = -math.inf
|
||||||
|
@ -169,7 +254,13 @@ class AlphabetaPlayerEngine(PlayerEngine):
|
||||||
))
|
))
|
||||||
return move, alpha
|
return move, alpha
|
||||||
|
|
||||||
|
"""
|
||||||
|
recursive function to apply alphabeta
|
||||||
|
@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
|
||||||
|
"""
|
||||||
def checkAlphaBeta(self, board, friend_move : bool, depth, alpha, beta):
|
def checkAlphaBeta(self, board, friend_move : bool, depth, alpha, beta):
|
||||||
nodes = 1
|
nodes = 1
|
||||||
leafs = 0
|
leafs = 0
|
||||||
|
@ -206,7 +297,11 @@ class AlphabetaPlayerEngine(PlayerEngine):
|
||||||
|
|
||||||
|
|
||||||
class MinmaxDeepeningPlayerEngine(MinmaxPlayerEngine):
|
class MinmaxDeepeningPlayerEngine(MinmaxPlayerEngine):
|
||||||
|
"""
|
||||||
|
Get move based on minmax algorithm with iterative deepening
|
||||||
|
@param board: Board
|
||||||
|
@return: array
|
||||||
|
"""
|
||||||
def get_move(self, board):
|
def get_move(self, board):
|
||||||
super().get_move(board)
|
super().get_move(board)
|
||||||
self.interrupt_search = False
|
self.interrupt_search = False
|
||||||
|
@ -251,7 +346,11 @@ class MinmaxDeepeningPlayerEngine(MinmaxPlayerEngine):
|
||||||
|
|
||||||
class AlphaBetaDeepeningPlayerEngine(AlphabetaPlayerEngine):
|
class AlphaBetaDeepeningPlayerEngine(AlphabetaPlayerEngine):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Get move based on alphabeta algorithm with iterative deepening
|
||||||
|
@param board: Board
|
||||||
|
@return: array
|
||||||
|
"""
|
||||||
def get_move(self, board):
|
def get_move(self, board):
|
||||||
self.interrupt_search = False
|
self.interrupt_search = False
|
||||||
|
|
||||||
|
@ -284,6 +383,9 @@ class AlphaBetaDeepeningPlayerEngine(AlphabetaPlayerEngine):
|
||||||
))
|
))
|
||||||
return move
|
return move
|
||||||
|
|
||||||
|
"""
|
||||||
|
define an handler for the alarm signal
|
||||||
|
"""
|
||||||
def alarm_handler(self, signal, frame):
|
def alarm_handler(self, signal, frame):
|
||||||
self.logger.debug("Raise SIGALMR Signal")
|
self.logger.debug("Raise SIGALMR Signal")
|
||||||
self.interrupt_search = True
|
self.interrupt_search = True
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
|
|
||||||
|
"""
|
||||||
|
Base class for heuristic object
|
||||||
|
"""
|
||||||
class HeuristicEngine:
|
class HeuristicEngine:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Init method
|
||||||
|
@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):
|
def __init__(self, logger, options):
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.options = options
|
self.options = options
|
||||||
|
@ -10,22 +20,52 @@ class HeuristicEngine:
|
||||||
def get():
|
def get():
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
"""
|
||||||
|
Score based heuristic class
|
||||||
|
"""
|
||||||
class ScoreHeuristicEngine(HeuristicEngine):
|
class ScoreHeuristicEngine(HeuristicEngine):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Get score
|
||||||
|
@param board: reversi board object
|
||||||
|
@return score: int
|
||||||
|
"""
|
||||||
def get(self, board, player):
|
def get(self, board, player):
|
||||||
return board.heuristique(player)
|
return board.heuristique(player)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Weight based heuristic class
|
||||||
|
"""
|
||||||
class WeightHeuristicEngine(HeuristicEngine):
|
class WeightHeuristicEngine(HeuristicEngine):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Init method
|
||||||
|
@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):
|
def __init__(self, logger, options):
|
||||||
super().__init__(logger, options)
|
super().__init__(logger, options)
|
||||||
self.weights = self._get_weight_array()
|
self.weights = self._get_weight_array()
|
||||||
self.logger.debug("{}".format(self.show_weights()))
|
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):
|
def get(self, board, player):
|
||||||
score = self.get_weight(board, player)
|
score = self.get_weight(board, player)
|
||||||
return score
|
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):
|
def get_weight(self, board, player):
|
||||||
score = 0
|
score = 0
|
||||||
size = self.options['size']
|
size = self.options['size']
|
||||||
|
@ -41,7 +81,12 @@ class WeightHeuristicEngine(HeuristicEngine):
|
||||||
score -= self.weights[pos_x][pos_y]
|
score -= self.weights[pos_x][pos_y]
|
||||||
w[pos_x][pos_y] = -self.weights[pos_x][pos_y]
|
w[pos_x][pos_y] = -self.weights[pos_x][pos_y]
|
||||||
return score
|
return score
|
||||||
|
|
||||||
|
"""
|
||||||
|
Get weight array calculated with weight given with options
|
||||||
|
@param : none
|
||||||
|
@return 2D array
|
||||||
|
"""
|
||||||
def _get_weight_array(self):
|
def _get_weight_array(self):
|
||||||
size = self.options['size']
|
size = self.options['size']
|
||||||
w = [[ 0 for _ in range(size)] for _ in range(size)]
|
w = [[ 0 for _ in range(size)] for _ in range(size)]
|
||||||
|
@ -85,7 +130,11 @@ class WeightHeuristicEngine(HeuristicEngine):
|
||||||
|
|
||||||
return w
|
return w
|
||||||
|
|
||||||
|
"""
|
||||||
|
Create a "displayable" array of value dor the calculated weight table
|
||||||
|
@input none
|
||||||
|
@return string
|
||||||
|
"""
|
||||||
def show_weights(self):
|
def show_weights(self):
|
||||||
display = "\n |"
|
display = "\n |"
|
||||||
sep = "\n----"
|
sep = "\n----"
|
||||||
|
@ -100,8 +149,16 @@ class WeightHeuristicEngine(HeuristicEngine):
|
||||||
display += "\n"
|
display += "\n"
|
||||||
return display
|
return display
|
||||||
|
|
||||||
|
"""
|
||||||
|
Full heuristic class
|
||||||
|
"""
|
||||||
class FullHeuristicEngine(WeightHeuristicEngine):
|
class FullHeuristicEngine(WeightHeuristicEngine):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Get score
|
||||||
|
@param board: reversi board object
|
||||||
|
@param player: int concerned player
|
||||||
|
@return int
|
||||||
|
"""
|
||||||
def get(self, board, player):
|
def get(self, board, player):
|
||||||
return self.get_weight(board, player) + board.heuristique(player)
|
return self.get_weight(board, player) + board.heuristique(player)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue