From f927aa1462f18c041cb3b5ceb7f8d69268245191 Mon Sep 17 00:00:00 2001 From: Yorick Barbanneau Date: Thu, 21 Dec 2023 00:30:08 +0100 Subject: [PATCH] Add classes and method documentation --- src/classes/Engines.py | 112 +++++++++++++++++++++++++++++++++++++-- src/classes/Heuristic.py | 67 +++++++++++++++++++++-- 2 files changed, 169 insertions(+), 10 deletions(-) diff --git a/src/classes/Engines.py b/src/classes/Engines.py index 338d48b..04d2e13 100644 --- a/src/classes/Engines.py +++ b/src/classes/Engines.py @@ -1,6 +1,17 @@ import random, math, time, signal +""" +Base player engine +""" 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 = {}): # init logger do display informations self.player = player @@ -12,30 +23,66 @@ class PlayerEngine: self.__class__.__name__, self.options )) - + + """ + get move + @param board: Board + """ def get_move(self, board): self.logger.info("engine: {} - player:{}".format( self.__class__.__name__, 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): moves = board.legal_moves() if self.options['randomize_moves'] is True: random.shuffle(moves) return moves - + + """ + Get player name based on his number + @param player: int + @return: string + """ @staticmethod def get_player_name(player): return 'White (O)' if player == 2 else 'Black (X)' + +""" +Random game engine +""" 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() return random.choice(moves) + +""" +Human player engine. +""" 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 @@ -46,6 +93,12 @@ class HumanPlayerEngine(PlayerEngine): move = self.validate_input(user_input, board) return move + + """ + Validate user input an verify than the move is possible + @param input: string + @return: array + """ @staticmethod def validate_input(input, board): if input == 'print': @@ -66,13 +119,31 @@ class HumanPlayerEngine(PlayerEngine): return [board._nextPlayer, x, y] + +""" +MinMax player engine +""" class MinmaxPlayerEngine(PlayerEngine): + + """ + Get move based on minmax algorithm + @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 + @param board: Board + @param depth: search depth + @return: move and max heuristic + """ def _call(self, board, depth): value = -math.inf nodes = 1 @@ -100,6 +171,13 @@ class MinmaxPlayerEngine(PlayerEngine): )) 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): nodes = 1 leafs = 0 @@ -140,6 +218,13 @@ class AlphabetaPlayerEngine(PlayerEngine): move, heuristic = self._call(board, self.options['depth']) 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): self.logger.debug("Enter AlphaBeta function") alpha = -math.inf @@ -169,7 +254,13 @@ class AlphabetaPlayerEngine(PlayerEngine): )) 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): nodes = 1 leafs = 0 @@ -206,7 +297,11 @@ class AlphabetaPlayerEngine(PlayerEngine): class MinmaxDeepeningPlayerEngine(MinmaxPlayerEngine): - + """ + Get move based on minmax algorithm with iterative deepening + @param board: Board + @return: array + """ def get_move(self, board): super().get_move(board) self.interrupt_search = False @@ -251,7 +346,11 @@ class MinmaxDeepeningPlayerEngine(MinmaxPlayerEngine): class AlphaBetaDeepeningPlayerEngine(AlphabetaPlayerEngine): - + """ + Get move based on alphabeta algorithm with iterative deepening + @param board: Board + @return: array + """ def get_move(self, board): self.interrupt_search = False @@ -284,6 +383,9 @@ class AlphaBetaDeepeningPlayerEngine(AlphabetaPlayerEngine): )) return move + """ + define an handler for the alarm signal + """ def alarm_handler(self, signal, frame): self.logger.debug("Raise SIGALMR Signal") self.interrupt_search = True diff --git a/src/classes/Heuristic.py b/src/classes/Heuristic.py index 16a4a8f..215f6b9 100644 --- a/src/classes/Heuristic.py +++ b/src/classes/Heuristic.py @@ -1,4 +1,14 @@ + +""" +Base class for heuristic object +""" 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): self.logger = logger self.options = options @@ -10,22 +20,52 @@ class HeuristicEngine: def get(): raise NotImplementedError +""" +Score based heuristic class +""" class ScoreHeuristicEngine(HeuristicEngine): + + """ + Get score + @param board: reversi board object + @return score: int + """ def get(self, board, player): return board.heuristique(player) - +""" +Weight based heuristic class +""" 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): 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'] @@ -41,7 +81,12 @@ 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)] @@ -85,7 +130,11 @@ class WeightHeuristicEngine(HeuristicEngine): return w - + """ + Create a "displayable" array of value dor the calculated weight table + @input none + @return string + """ def show_weights(self): display = "\n |" sep = "\n----" @@ -100,8 +149,16 @@ 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)