Compare commits
5 commits
3784faeaed
...
e506bcdab9
Author | SHA1 | Date | |
---|---|---|---|
e506bcdab9 | |||
a64e318c82 | |||
c8447b7a6b | |||
66b10f4deb | |||
a1812aaedf |
4 changed files with 127 additions and 48 deletions
41
README.md
41
README.md
|
@ -44,16 +44,21 @@ Voici la liste des options :
|
||||||
* `-wh` | `--black-heuristic-engine`: moteur heuristique utilisé pour
|
* `-wh` | `--black-heuristic-engine`: moteur heuristique utilisé pour
|
||||||
l'exploration de l'arbre de jeu du joueur blanc (valable pour les moteur de
|
l'exploration de l'arbre de jeu du joueur blanc (valable pour les moteur de
|
||||||
jeu `minmax` et `alphabeta`)
|
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
|
L'affichage verbeux est activé avec `-V` et les informations de débogage sont
|
||||||
affichée avec l'option `-d`.
|
affichée avec l'option `-d`.
|
||||||
|
|
||||||
## Choix d'implémentation
|
## 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
|
### Classes PlayerEngine
|
||||||
|
|
||||||
Définies dans le fichier `./src/classes/Engines.py`, les classes utilisées h
|
Définies dans le fichier `./src/classes/Engines.py`, les classes utilisées
|
||||||
eritent de la classe de base `PlayerEngines` :
|
héritent de la classe de base `PlayerEngines` :
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class PlayerEngine(Object):
|
class PlayerEngine(Object):
|
||||||
|
@ -63,8 +68,38 @@ class PlayerEngine(Object):
|
||||||
class MinmaxPlayerEngine(PlayerEngine):
|
class MinmaxPlayerEngine(PlayerEngine):
|
||||||
def get_move(board):
|
def get_move(board):
|
||||||
|
|
||||||
class RandomPlayerEngnine(PlayerEngine):
|
class RandomPlayerEngine(PlayerEngine):
|
||||||
def get_move(board):
|
def get_move(board):
|
||||||
```
|
```
|
||||||
|
|
||||||
Il est ainsi plus aisé de tester les moteur dans notre programme de base.
|
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
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import random, math
|
import random, math
|
||||||
from .Heuristic import ReversiHeuristic
|
|
||||||
|
|
||||||
class PlayerEngine:
|
class PlayerEngine:
|
||||||
def __init__(self, logger, options: dict = {}):
|
def __init__(self, player, logger, heuristic, options: dict = {}):
|
||||||
# init logger do display informations
|
# init logger do display informations
|
||||||
|
self.player = player
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
self.heuristic = heuristic
|
||||||
self.options = options
|
self.options = options
|
||||||
self.logger.info("Init engine {}, options:{}".format(
|
self.logger.info("Init engine {}, options:{}".format(
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
|
@ -14,7 +15,7 @@ class PlayerEngine:
|
||||||
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(board._nextPlayer)
|
self.get_player_name(self.player)
|
||||||
))
|
))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -33,7 +34,7 @@ class HumanPlayerEngine(PlayerEngine):
|
||||||
move = None
|
move = None
|
||||||
while move is None:
|
while move is None:
|
||||||
user_input = input("Please enter player {} move, `print` to display board and `help` possible moves : ".format(
|
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)
|
move = self.validate_input(user_input, board)
|
||||||
return move
|
return move
|
||||||
|
@ -91,11 +92,7 @@ class MinmaxPlayerEngine(PlayerEngine):
|
||||||
move = ''
|
move = ''
|
||||||
if depth == 0:
|
if depth == 0:
|
||||||
leafs +=1
|
leafs +=1
|
||||||
if self.options['heuristic'] == 'weight':
|
return self.heuristic.get(board, self.player), nodes, leafs
|
||||||
score = ReversiHeuristic(board).get()
|
|
||||||
else:
|
|
||||||
score = board.heuristique()
|
|
||||||
return score, nodes, leafs
|
|
||||||
|
|
||||||
if friend_move:
|
if friend_move:
|
||||||
value = -math.inf
|
value = -math.inf
|
||||||
|
@ -157,11 +154,7 @@ class AlphabetaPlayerEngine(PlayerEngine):
|
||||||
leafs = 0
|
leafs = 0
|
||||||
if depth == 0 :
|
if depth == 0 :
|
||||||
leafs +=1
|
leafs +=1
|
||||||
if self.options['heuristic'] == 'weight':
|
return self.heuristic.get(board, self.player), nodes, leafs
|
||||||
score = ReversiHeuristic(self.logger).get(board)
|
|
||||||
else:
|
|
||||||
score = board.heuristique()
|
|
||||||
return score, nodes, leafs
|
|
||||||
|
|
||||||
if friend_move:
|
if friend_move:
|
||||||
value = -math.inf
|
value = -math.inf
|
||||||
|
|
|
@ -1,38 +1,59 @@
|
||||||
class ReversiHeuristic():
|
class HeuristicEngine:
|
||||||
def __init__(self, logger, weight = [1, 2, 10, 20]):
|
def __init__(self, logger, options):
|
||||||
self.logger = logger
|
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):
|
def get():
|
||||||
size = board.get_board_size()
|
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
|
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_x in range(size):
|
||||||
for pos_y 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]
|
score += weights[pos_x][pos_y]
|
||||||
else:
|
else:
|
||||||
score -= weights[pos_x][pos_y]
|
score -= weights[pos_x][pos_y]
|
||||||
return score
|
return score
|
||||||
|
|
||||||
def _get_weight(self, size):
|
def _get_weight_array(self, 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
|
||||||
for pos_y in range(size):
|
for pos_y in range(size):
|
||||||
for pos_x in range(size):
|
for pos_x in range(size):
|
||||||
|
|
||||||
# Elements in the corner
|
# 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.weight[3]
|
w[pos_x][pos_y] = self.options['weight'][2]
|
||||||
|
|
||||||
# Elements on the border
|
# 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.weight[2]
|
w[pos_x][pos_y] = self.options['weight'][1]
|
||||||
|
|
||||||
# Element the center
|
# 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):
|
elif pos_x in range( center - padding, center + padding) and pos_y in range(center - padding, center + padding):
|
||||||
w[pos_x][pos_y] = self.weight[1]
|
w[pos_x][pos_y] = self.options['weight'][0]
|
||||||
|
|
||||||
else:
|
|
||||||
w[pos_x][pos_y] = self.weight[0]
|
|
||||||
return w
|
return w
|
||||||
|
|
||||||
|
class FullHeuristicEngine(WeightHeuristicEngine):
|
||||||
|
|
||||||
|
def get(self, board, player):
|
||||||
|
return self.get_weight(board, player) + board.heuristique(player)
|
||||||
|
|
58
src/game.py
58
src/game.py
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from classes.Reversi import Board
|
from classes.Reversi import Board
|
||||||
from classes.Engines import RandomPlayerEngine, HumanPlayerEngine, MinmaxPlayerEngine, AlphabetaPlayerEngine
|
from classes.Engines import RandomPlayerEngine, HumanPlayerEngine, MinmaxPlayerEngine, AlphabetaPlayerEngine
|
||||||
|
from classes.Heuristic import ScoreHeuristicEngine, WeightHeuristicEngine, FullHeuristicEngine
|
||||||
import logging as log
|
import logging as log
|
||||||
import argparse as arg
|
import argparse as arg
|
||||||
from classes.CustomFormater import CustomFormatter
|
from classes.CustomFormater import CustomFormatter
|
||||||
|
@ -12,7 +13,7 @@ Function to parse command line arguments
|
||||||
"""
|
"""
|
||||||
def parse_aguments():
|
def parse_aguments():
|
||||||
engines_choices=['random', 'human', 'minmax', 'alphabeta']
|
engines_choices=['random', 'human', 'minmax', 'alphabeta']
|
||||||
heuristic_choices=['weight',]
|
heuristic_choices=['score', 'weight', 'full']
|
||||||
parser = arg.ArgumentParser('Playing Reversi with (virtual) friend')
|
parser = arg.ArgumentParser('Playing Reversi with (virtual) friend')
|
||||||
|
|
||||||
parser.add_argument('-we', '--white-engine',
|
parser.add_argument('-we', '--white-engine',
|
||||||
|
@ -41,14 +42,21 @@ def parse_aguments():
|
||||||
|
|
||||||
parser.add_argument('-bh', '--black-heuristic-engine',
|
parser.add_argument('-bh', '--black-heuristic-engine',
|
||||||
help='Black player heutistic engine',
|
help='Black player heutistic engine',
|
||||||
choices=['board', 'weight'],
|
choices= heuristic_choices,
|
||||||
default='board',
|
default='score',
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument('-wh', '--white-heuristic-engine',
|
parser.add_argument('-wh', '--white-heuristic-engine',
|
||||||
help='White player heutistic engine',
|
help='White player heutistic engine',
|
||||||
choices=['board', 'weight'],
|
choices=heuristic_choices,
|
||||||
default='board',
|
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()
|
debug_group = parser.add_mutually_exclusive_group()
|
||||||
|
@ -65,12 +73,17 @@ def parse_aguments():
|
||||||
Main Function
|
Main Function
|
||||||
"""
|
"""
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
engines = {
|
player_engines = {
|
||||||
"random": RandomPlayerEngine,
|
"random": RandomPlayerEngine,
|
||||||
"human": HumanPlayerEngine,
|
"human": HumanPlayerEngine,
|
||||||
"minmax": MinmaxPlayerEngine,
|
"minmax": MinmaxPlayerEngine,
|
||||||
"alphabeta": AlphabetaPlayerEngine,
|
"alphabeta": AlphabetaPlayerEngine,
|
||||||
}
|
}
|
||||||
|
heuristic_engine = {
|
||||||
|
"score": ScoreHeuristicEngine,
|
||||||
|
"weight": WeightHeuristicEngine,
|
||||||
|
"full": FullHeuristicEngine,
|
||||||
|
}
|
||||||
print("Stating PyReverso...")
|
print("Stating PyReverso...")
|
||||||
args = parse_aguments()
|
args = parse_aguments()
|
||||||
logger = log.getLogger()
|
logger = log.getLogger()
|
||||||
|
@ -93,14 +106,31 @@ if __name__ == '__main__':
|
||||||
args.black_engine,
|
args.black_engine,
|
||||||
args.white_engine
|
args.white_engine
|
||||||
))
|
))
|
||||||
wplayer = engines[args.white_engine](logger, {
|
logger.debug("Weight value {}".format( args.weight ))
|
||||||
'depth': args.white_depth_exploration,
|
wplayer = player_engines[args.white_engine](
|
||||||
'heuristic': args.white_heuristic_engine
|
game._WHITE,
|
||||||
})
|
logger,
|
||||||
bplayer = engines[args.black_engine](logger, {
|
heuristic_engine[args.white_heuristic_engine](
|
||||||
'depth': args.black_depth_exploration,
|
logger, {
|
||||||
'heuristic': args.black_heuristic_engine
|
'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()):
|
while ( not game.is_game_over()):
|
||||||
if game._nextPlayer == 1:
|
if game._nextPlayer == 1:
|
||||||
move = bplayer.get_move(game)
|
move = bplayer.get_move(game)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue