diff --git a/README.md b/README.md index 6a86f74..bcae4fa 100644 --- a/README.md +++ b/README.md @@ -44,21 +44,16 @@ 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éritent de la classe de base `PlayerEngines` : +Définies dans le fichier `./src/classes/Engines.py`, les classes utilisées h +eritent de la classe de base `PlayerEngines` : ```python class PlayerEngine(Object): @@ -68,38 +63,8 @@ class PlayerEngine(Object): class MinmaxPlayerEngine(PlayerEngine): def get_move(board): -class RandomPlayerEngine(PlayerEngine): +class RandomPlayerEngnine(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 a3d6f54..305a199 100644 --- a/src/classes/Engines.py +++ b/src/classes/Engines.py @@ -1,11 +1,10 @@ import random, math +from .Heuristic import ReversiHeuristic class PlayerEngine: - def __init__(self, player, logger, heuristic, options: dict = {}): + def __init__(self, logger, 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__, @@ -15,7 +14,7 @@ class PlayerEngine: def get_move(self, board): self.logger.info("engine: {} - player:{}".format( self.__class__.__name__, - self.get_player_name(self.player) + self.get_player_name(board._nextPlayer) )) @staticmethod @@ -34,7 +33,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(self.player) + self.get_player_name(board._nextPlayer) )) move = self.validate_input(user_input, board) return move @@ -92,7 +91,11 @@ class MinmaxPlayerEngine(PlayerEngine): move = '' if depth == 0: leafs +=1 - return self.heuristic.get(board, self.player), nodes, leafs + if self.options['heuristic'] == 'weight': + score = ReversiHeuristic(board).get() + else: + score = board.heuristique() + return score, nodes, leafs if friend_move: value = -math.inf @@ -154,7 +157,11 @@ class AlphabetaPlayerEngine(PlayerEngine): leafs = 0 if depth == 0 : leafs +=1 - return self.heuristic.get(board, self.player), nodes, leafs + if self.options['heuristic'] == 'weight': + score = ReversiHeuristic(self.logger).get(board) + else: + score = board.heuristique() + return score, nodes, leafs if friend_move: value = -math.inf diff --git a/src/classes/Heuristic.py b/src/classes/Heuristic.py index d7fa3ea..13ae8ee 100644 --- a/src/classes/Heuristic.py +++ b/src/classes/Heuristic.py @@ -1,59 +1,38 @@ -class HeuristicEngine: - def __init__(self, logger, options): +class ReversiHeuristic(): + def __init__(self, logger, weight = [1, 2, 10, 20]): self.logger = logger - self.options = options - self.logger.info("Heuristic engine {}, options:{}".format( - self.__class__.__name__, - self.options - )) + self.weight = weight - 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 + def get(self, board): size = board.get_board_size() - weights = self._get_weight_array(size) + score = 0 + weights = self._get_weight(size) for pos_x in range(size): for pos_y in range(size): - if board._board[pos_x][pos_y] == player: + if board._board[pos_x][pos_y] == board._nextPlayer: score += weights[pos_x][pos_y] else: score -= weights[pos_x][pos_y] return score - - def _get_weight_array(self, size): + + def _get_weight(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.options['weight'][2] + w[pos_x][pos_y] = self.weight[3] # Elements on the border elif pos_x in [0, size -1] or pos_y in [0, size -1]: - w[pos_x][pos_y] = self.options['weight'][1] + w[pos_x][pos_y] = self.weight[2] # Element the center - 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] + 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] 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 09a079d..7db3514 100755 --- a/src/game.py +++ b/src/game.py @@ -2,7 +2,6 @@ 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 @@ -13,7 +12,7 @@ Function to parse command line arguments """ def parse_aguments(): engines_choices=['random', 'human', 'minmax', 'alphabeta'] - heuristic_choices=['score', 'weight', 'full'] + heuristic_choices=['weight',] parser = arg.ArgumentParser('Playing Reversi with (virtual) friend') parser.add_argument('-we', '--white-engine', @@ -42,21 +41,14 @@ def parse_aguments(): parser.add_argument('-bh', '--black-heuristic-engine', help='Black player heutistic engine', - choices= heuristic_choices, - default='score', + choices=['board', 'weight'], + default='board', ) parser.add_argument('-wh', '--white-heuristic-engine', help='White player heutistic engine', - 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] + choices=['board', 'weight'], + default='board', ) debug_group = parser.add_mutually_exclusive_group() @@ -73,17 +65,12 @@ def parse_aguments(): Main Function """ if __name__ == '__main__': - player_engines = { + 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() @@ -106,31 +93,14 @@ if __name__ == '__main__': args.black_engine, args.white_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, - } - ) + 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 + }) while ( not game.is_game_over()): if game._nextPlayer == 1: move = bplayer.get_move(game)