diff --git a/README.md b/README.md index bcae4fa..6a86f74 100644 --- a/README.md +++ b/README.md @@ -44,16 +44,21 @@ Voici la liste des options : * `-wh` | `--black-heuristic-engine`: moteur heuristique utilisé pour l'exploration de l'arbre de jeu du joueur blanc (valable pour les moteur de jeu `minmax` et `alphabeta`) + * `--weight`: scores utilisés pour le calcul des heuristiques pour les moteurs + `weight` et `full`. L'affichage verbeux est activé avec `-V` et les informations de débogage sont affichée avec l'option `-d`. ## Choix d'implémentation +J'ai avant tout privilégié la personnalisation des différentes paramètres des +différents moteurs composant le jeu. + ### Classes PlayerEngine -Définies dans le fichier `./src/classes/Engines.py`, les classes utilisées h -eritent de la classe de base `PlayerEngines` : +Définies dans le fichier `./src/classes/Engines.py`, les classes utilisées +héritent de la classe de base `PlayerEngines` : ```python class PlayerEngine(Object): @@ -63,8 +68,38 @@ class PlayerEngine(Object): class MinmaxPlayerEngine(PlayerEngine): def get_move(board): -class RandomPlayerEngnine(PlayerEngine): +class RandomPlayerEngine(PlayerEngine): def get_move(board): ``` Il est ainsi plus aisé de tester les moteur dans notre programme de base. + +### Classes HeuristicsEngine + +Plusieurs classes impémentent plusieurs méthodes pour le calcul de +l'heuristique. Comme nous l'avons vu, les moteurs peuvent être choisis en ligne +de commande et de façon indépendante pour les joueurs blanc et noir. + +Trois implementation sond disponibles: + + 1. `ScoreHeuristicEngine`: l'heuristique se sert du score (comptage des pièces + sur le tableau) via la méthode `Board.heuristique`; + 2. `WeightHeuristicEngine`: ici on se sert de la place des pièces sur le + tableau. Chaque emplacement vaut un nombre de points; + 3. `FullHeuristicEngine`: c'est la somme de `Board.heuristique()` et du calcul + des poids. + +#### Retour sur le calcul des poids. + +Afin de définir le poids, je me suis servi de la page *Stratégie et bases de +Reversi* sur le site Cool Math Games ([lien][reversi]). Voici les poids par +ordre d'importance : + + 1. Les coins représentent les parties les plus importantes; + 2. Ensuite vient les bords; + 3. Et enfin les coins. + +Les poids affectés sont personnalisable via l'options `--weight`, par défaut +nous avons 2, 10 et 25. + +[reversi]:https://www.coolmathgames.com/blog/how-to-play-reversi-basics-and-best-strategies diff --git a/src/classes/Engines.py b/src/classes/Engines.py index 305a199..a3d6f54 100644 --- a/src/classes/Engines.py +++ b/src/classes/Engines.py @@ -1,10 +1,11 @@ import random, math -from .Heuristic import ReversiHeuristic class PlayerEngine: - def __init__(self, logger, options: dict = {}): + def __init__(self, player, logger, heuristic, options: dict = {}): # init logger do display informations + self.player = player self.logger = logger + self.heuristic = heuristic self.options = options self.logger.info("Init engine {}, options:{}".format( self.__class__.__name__, @@ -14,7 +15,7 @@ class PlayerEngine: def get_move(self, board): self.logger.info("engine: {} - player:{}".format( self.__class__.__name__, - self.get_player_name(board._nextPlayer) + self.get_player_name(self.player) )) @staticmethod @@ -33,7 +34,7 @@ class HumanPlayerEngine(PlayerEngine): move = None while move is None: user_input = input("Please enter player {} move, `print` to display board and `help` possible moves : ".format( - self.get_player_name(board._nextPlayer) + self.get_player_name(self.player) )) move = self.validate_input(user_input, board) return move @@ -91,11 +92,7 @@ class MinmaxPlayerEngine(PlayerEngine): move = '' if depth == 0: leafs +=1 - if self.options['heuristic'] == 'weight': - score = ReversiHeuristic(board).get() - else: - score = board.heuristique() - return score, nodes, leafs + return self.heuristic.get(board, self.player), nodes, leafs if friend_move: value = -math.inf @@ -157,11 +154,7 @@ class AlphabetaPlayerEngine(PlayerEngine): leafs = 0 if depth == 0 : leafs +=1 - if self.options['heuristic'] == 'weight': - score = ReversiHeuristic(self.logger).get(board) - else: - score = board.heuristique() - return score, nodes, leafs + return self.heuristic.get(board, self.player), nodes, leafs if friend_move: value = -math.inf diff --git a/src/classes/Heuristic.py b/src/classes/Heuristic.py index 13ae8ee..d7fa3ea 100644 --- a/src/classes/Heuristic.py +++ b/src/classes/Heuristic.py @@ -1,38 +1,59 @@ -class ReversiHeuristic(): - def __init__(self, logger, weight = [1, 2, 10, 20]): +class HeuristicEngine: + def __init__(self, logger, options): self.logger = logger - self.weight = weight + self.options = options + self.logger.info("Heuristic engine {}, options:{}".format( + self.__class__.__name__, + self.options + )) - def get(self, board): - size = board.get_board_size() + def get(): + raise NotImplementedError + +class ScoreHeuristicEngine(HeuristicEngine): + def get(self, board, player): + return board.heuristique(player) + + +class WeightHeuristicEngine(HeuristicEngine): + + def get(self, board, player): + score = self.get_weight(board, player) + return score + + def get_weight(self, board, player): score = 0 - weights = self._get_weight(size) + size = board.get_board_size() + weights = self._get_weight_array(size) for pos_x in range(size): for pos_y in range(size): - if board._board[pos_x][pos_y] == board._nextPlayer: + if board._board[pos_x][pos_y] == player: score += weights[pos_x][pos_y] else: score -= weights[pos_x][pos_y] return score - - def _get_weight(self, size): - w = [[ 0 for _ in range(size)] for _ in range(size)] + def _get_weight_array(self, size): + w = [[ 0 for _ in range(size)] for _ in range(size)] + padding = size // 5 + center = size // 2 for pos_y in range(size): for pos_x in range(size): # Elements in the corner if pos_x in [0, size -1] and pos_y in [0,size - 1]: - w[pos_x][pos_y] = self.weight[3] + w[pos_x][pos_y] = self.options['weight'][2] # Elements on the border elif pos_x in [0, size -1] or pos_y in [0, size -1]: - w[pos_x][pos_y] = self.weight[2] + w[pos_x][pos_y] = self.options['weight'][1] # Element the center - elif pos_x in range( int(size // 2 - 2), size // 2 + 2) and pos_y in range( size // 2 - 2, size // 2 + 2): - w[pos_x][pos_y] = self.weight[1] - - else: - w[pos_x][pos_y] = self.weight[0] + elif pos_x in range( center - padding, center + padding) and pos_y in range(center - padding, center + padding): + w[pos_x][pos_y] = self.options['weight'][0] return w + +class FullHeuristicEngine(WeightHeuristicEngine): + + def get(self, board, player): + return self.get_weight(board, player) + board.heuristique(player) diff --git a/src/game.py b/src/game.py index 7db3514..09a079d 100755 --- a/src/game.py +++ b/src/game.py @@ -2,6 +2,7 @@ from classes.Reversi import Board from classes.Engines import RandomPlayerEngine, HumanPlayerEngine, MinmaxPlayerEngine, AlphabetaPlayerEngine +from classes.Heuristic import ScoreHeuristicEngine, WeightHeuristicEngine, FullHeuristicEngine import logging as log import argparse as arg from classes.CustomFormater import CustomFormatter @@ -12,7 +13,7 @@ Function to parse command line arguments """ def parse_aguments(): engines_choices=['random', 'human', 'minmax', 'alphabeta'] - heuristic_choices=['weight',] + heuristic_choices=['score', 'weight', 'full'] parser = arg.ArgumentParser('Playing Reversi with (virtual) friend') parser.add_argument('-we', '--white-engine', @@ -41,14 +42,21 @@ def parse_aguments(): parser.add_argument('-bh', '--black-heuristic-engine', help='Black player heutistic engine', - choices=['board', 'weight'], - default='board', + choices= heuristic_choices, + default='score', ) parser.add_argument('-wh', '--white-heuristic-engine', help='White player heutistic engine', - choices=['board', 'weight'], - default='board', + choices=heuristic_choices, + default='score', + ) + + parser.add_argument('--weight', + help='Weight table for weight based heuristic engines', + type=int, + nargs=3, + default=[2,10,25] ) debug_group = parser.add_mutually_exclusive_group() @@ -65,12 +73,17 @@ def parse_aguments(): Main Function """ if __name__ == '__main__': - engines = { + player_engines = { "random": RandomPlayerEngine, "human": HumanPlayerEngine, "minmax": MinmaxPlayerEngine, "alphabeta": AlphabetaPlayerEngine, } + heuristic_engine = { + "score": ScoreHeuristicEngine, + "weight": WeightHeuristicEngine, + "full": FullHeuristicEngine, + } print("Stating PyReverso...") args = parse_aguments() logger = log.getLogger() @@ -93,14 +106,31 @@ if __name__ == '__main__': args.black_engine, args.white_engine )) - wplayer = engines[args.white_engine](logger, { - 'depth': args.white_depth_exploration, - 'heuristic': args.white_heuristic_engine - }) - bplayer = engines[args.black_engine](logger, { - 'depth': args.black_depth_exploration, - 'heuristic': args.black_heuristic_engine - }) + logger.debug("Weight value {}".format( args.weight )) + wplayer = player_engines[args.white_engine]( + game._WHITE, + logger, + heuristic_engine[args.white_heuristic_engine]( + logger, { + 'weight': args.weight + } + ), + { + 'depth': args.white_depth_exploration, + } + ) + bplayer = player_engines[args.black_engine]( + game._BLACK, + logger, + heuristic_engine[args.black_heuristic_engine]( + logger, { + 'weight': args.weight + } + ), + { + 'depth': args.black_depth_exploration, + } + ) while ( not game.is_game_over()): if game._nextPlayer == 1: move = bplayer.get_move(game)