275 lines
10 KiB
Markdown
275 lines
10 KiB
Markdown
IA: jeu de Reversi
|
|
------------------
|
|
|
|
Le but de ce projet est d'implémenter plusieurs mécanismes de jeu (humain et
|
|
intelligence artificielle) pour le Reversi.
|
|
|
|
## Installation
|
|
|
|
Le programme utilise des outils standard de Python installés de base : `random`,
|
|
`math`, `argpase` et `logging`. Le projet est fourni avec un shell *Nix* dans
|
|
le répertoire `src`.
|
|
|
|
## Utilisation
|
|
|
|
J'ai choisi de créer un programme en *Python* utilisable depuis un terminal
|
|
(testé uniquement sous Linux).
|
|
|
|
Le programme propose un ensemble d'options afin de définir les paramètres des
|
|
différentes implémentations présentes dans le jeu comme le choix des moteurs de
|
|
jeu (aléatoire, MinMax etc.), les paramètres (profondeur de recherche,
|
|
temps imparti) etc. Une aide est intégrée au programme via la commande `./game.py
|
|
-h`. Voici quelques exemple de lancement:
|
|
|
|
```shell
|
|
# Lancement de base: les deux joueurs jouent avec le moteur aléatoire et les
|
|
options de base:
|
|
./game.py
|
|
|
|
# joueur noir humain et joueur blanc MinMax avec une profondeur de 5
|
|
./game.py -be human -we minmax -wd 5
|
|
```
|
|
|
|
Voyons maintenant quelques paramètres.
|
|
|
|
### Moteur de jeu
|
|
|
|
Il est possible de définir le moteur de jeu indépendamment pour chaque joueur et
|
|
ainsi faire des matches:
|
|
|
|
* `-be` | `--black-player-engine`: moteur utilisé par le joueur avec les pions
|
|
noirs
|
|
* `-we` | `--white-player-engine`: moteur utilisé par le joueur avec les pions
|
|
blancs
|
|
|
|
Le moteur de jeux par défaut est `random`.
|
|
|
|
### Profondeur d'exploration
|
|
|
|
Il est aussi possible de définir la profondeur d'exploration de l'arbre de jeu
|
|
pour chacun des joueurs:
|
|
|
|
* `-bd` | `--black-depth-exploration`: niveau d'exploration de l'arbre de jeu
|
|
pour le joueur au pions noirs, valable pour les moteurs `minmax` et
|
|
`alphabeta`. Utilisé aussi pour définit la profondeur de départ pour
|
|
l'*iterative deepening*
|
|
* `-wd` | `--white-depth-exploration`: niveau d'exploration de l'arbre de jeu
|
|
pour le joueur au pions blancs, valable pour les moteurs `minmax` et
|
|
`alphabeta`Utilisé aussi pour définit la profondeur de départ pour
|
|
l'*iterative deepening*
|
|
|
|
La profondeur par défaut est 3.
|
|
|
|
### Temps d'exploration pour l'*Iterative Deepening*
|
|
|
|
Lorsque le choix est fait d'utiliser les algorithmes utilisant l'*iterative
|
|
deepening. Il est possible de régler les temps d'exploration indépendamment pour
|
|
les deux joueurs:
|
|
|
|
* `-bt` | `--black-player-deepening-time`: temps maximum en seconde
|
|
d'exploration de l'arbre pour le joueur noir
|
|
* `-wt` | `--white-player-deepening-time`: temps maximum en seconde
|
|
d'exploration de l'arbre pour le joueur blanc
|
|
|
|
### Heuristique
|
|
|
|
Il est possible de choisir entre les 3 moteur de calcul d'heuristique inclus à
|
|
savoir *score*, avec *poids*, ou une combinaison *des deux*. Ils ne sont
|
|
utilisés que pour les moteur de jeu avec exploration de l'arbre de jeu (minmax,
|
|
alphabeta et leurs pendants avec *iterative deepening*):
|
|
|
|
* `-bh` | `--black-heuristic-engine`: moteur heuristique utilisé pour
|
|
l'exploration de l'arbre de jeu du joueur noir (valable pour les moteur de
|
|
jeu `minmax` et `alphabeta`)
|
|
* `-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`)
|
|
|
|
Pour l'utilisation des poids, il est possible de les paramétrer :
|
|
|
|
* `--weight`: scores utilisés pour le calcul des heuristiques pour les moteurs
|
|
`weight` et `full`.
|
|
|
|
### Debug et mode verbeux
|
|
|
|
L'affichage verbeux est activé avec `-V` et les informations de débogages sont
|
|
affichée avec l'option `-d`.
|
|
|
|
## Choix d'implémentation
|
|
|
|
J'ai avant tout privilégié la personnalisation des paramètres des
|
|
différents moteurs composant le jeu. Il est ainsi plus aisé de tester le
|
|
fonctionnement suivants différents scénarios.
|
|
|
|
Tout est implémenté suivant une logique objet facilitant le développement des
|
|
composants et leurs tests.
|
|
|
|
### Classes PlayerEngine
|
|
|
|
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):
|
|
def __init__(self, logger, options):
|
|
def get_move(self, board):
|
|
|
|
# [...]
|
|
|
|
class MinmaxPlayerEngine(PlayerEngine):
|
|
def get_move(board):
|
|
|
|
class AlphabetaPlayerEngine(PlayerEngine):
|
|
def get_move(board):
|
|
|
|
# [...]
|
|
```
|
|
|
|
Quatre moteur "joueurs" sont implémentés :
|
|
|
|
* `Human` pour gérer des joueurs humain, une saisie utilisateur est demandée
|
|
sous la forme `<pos_x><pos_y>`. Il est aussi possible d'afficher le plateau
|
|
avec la commande `print` ou les coups possibles avec `help`;
|
|
* `Ramdom` va choisir aléatoirement le coup à jouer en fonction de ceux
|
|
possibles;
|
|
* `Minmax` utilise *MinMax* pour déterminer le coup à jouer avec une profondeur
|
|
maximale définie;
|
|
* `AphaBeta` utilise *AlphaBeta* pour déterminer le coup à jouer avec une
|
|
profondeur maximale définie;
|
|
* `MinmaxDeepeningMinmax` utilise Minmax avec un temps maximum autorisé en
|
|
itérant sur la profondeur;
|
|
* `AlphaBetaDeepening` utilise AlphaBeta avec un temps maximum autorisé.
|
|
|
|
Le choix de ces moteur se fait en ligne de commande avec les options évoquées
|
|
plus haut.
|
|
|
|
### Classes HeuristicsEngine
|
|
|
|
Plusieurs classes implémentent plusieurs méthodes pour le calcul de
|
|
l'heuristique. Toutes les implémentations se trouvent dans le fichier
|
|
`./src/classes/Heuristic.py` 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 implementations sont 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 le centre.
|
|
|
|
Cependant certaines parties du plateau de jeu sont à éviter :
|
|
|
|
* Les cases autour des coins, car elle laisserai la possibilité au joueur
|
|
adverse de placer un de ses pions dans le coin. La case en diagonale du coin
|
|
est particulièrement sensible.
|
|
* Les lignes juste avant les bords, placer un pion à cet endroit permettrai à
|
|
l'adversaire de placer un pion sur le bord. Ce pion sera alors plus
|
|
difficilement *"capturable"*
|
|
|
|
Les poids affectés sont personnalisable via l'options `--weight`, par défaut
|
|
nous avons `[-5, 2, 10, 25]`. Ces quatre chiffres servent de base pour le
|
|
calcul de l'ensemble des poids
|
|
|
|
Une étude autour de l'heuristique de l'Othello menée par Vaishnavi Sannidhanam
|
|
et Muthukaruppan Annamalai de l'université de Washingtown propose d'autre pistes
|
|
pour améliorer son calcul. [télécharger le pdf][etude]. Mon calcul des poinds
|
|
s'en inspire grandement.
|
|
|
|
Voici le tableau des poids par défaut, il peut être affiché avec l'option
|
|
`--show-weights-table`:
|
|
|
|
```text
|
|
./game.py --show-weights-table
|
|
Starting PyReverso...
|
|
|
|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|
|
--------------------------------------------
|
|
0 |25 |-5 |10 | 8 | 8 | 8 | 8 |10 |-5 |25 |
|
|
1 |-5 |-7 |-3 |-3 |-3 |-3 |-3 |-3 |-7 |-5 |
|
|
2 |10 |-3 | 0 | 0 | 0 | 0 | 0 | 0 |-3 |10 |
|
|
3 | 8 |-3 | 0 | 2 | 2 | 2 | 2 | 0 |-3 | 8 |
|
|
4 | 8 |-3 | 0 | 2 | 2 | 2 | 2 | 0 |-3 | 8 |
|
|
5 | 8 |-3 | 0 | 2 | 2 | 2 | 2 | 0 |-3 | 8 |
|
|
6 | 8 |-3 | 0 | 2 | 2 | 2 | 2 | 0 |-3 | 8 |
|
|
7 |10 |-3 | 0 | 0 | 0 | 0 | 0 | 0 |-3 |10 |
|
|
8 |-5 |-7 |-3 |-3 |-3 |-3 |-3 |-3 |-7 |-5 |
|
|
9 |25 |-5 |10 | 8 | 8 | 8 | 8 |10 |-5 |25 |
|
|
```
|
|
|
|
### À savoir:
|
|
|
|
Les poids utilisé pour les heuristiques sont importants, ils ont été trouvés en
|
|
effectuant plusieurs tests mais peuvent être améliorés de mon point.
|
|
|
|
## mode récursions
|
|
|
|
Le programme principal inclus un mode *recursion* permettant l'exécutions de
|
|
plusieurs parties les unes à la suite des autres afin de tester les paramètres. Le
|
|
paramètre pour la ligne de commande est `-r` | `--recursions` suivi d'un nombre
|
|
entier positif.
|
|
|
|
À la fin de la passe, un récapitulatif est affiché montrant statistiques,
|
|
moteurs utilisés et leurs options:
|
|
|
|
```text
|
|
|
|
Stats
|
|
---
|
|
Games: 3 in 67.44769430160522s
|
|
Black: 0 | ratio: 0.00 | time: 38.074s | score: .......94 | engine: AlphabetaPlayerEngine
|
|
White: 3 | ratio: 100.00 | time: 29.374s | score: ......206 | engine: MinmaxPlayerEngine
|
|
Null: 0 | ratio: 0.0
|
|
---
|
|
Black player options: {'depth': 4, 'time_limit': 10, 'randomize_moves': True}
|
|
White player options: {'depth': 3, 'time_limit': 10, 'randomize_moves': True}
|
|
```
|
|
|
|
Je me suis servi ce des statistiques pour essayer d'ameliorer mes algorithmes,
|
|
mais ma machine personnelles étant limitées en terme de performances, chaque
|
|
analyse statistiques m'a pris beaucoup de temps.
|
|
|
|
## Pour conclure
|
|
|
|
Pour mon implémentation, le moteur **MinMax** avec l'**Iterative Deepening** se
|
|
montre plus performant. La logique voudrait que se soit le moteur *AlphaBeta*
|
|
avec *Iterative Deepening* le plus performant car il explore l'arbre de jeu
|
|
plus en profondeur. C'est d'ailleurs ce qui apparait dans les données affichées
|
|
en mode debug (option `-d`).
|
|
|
|
Cependant en affinant les réglages de poids (option `--weights`) avec les
|
|
paramètres `-100 2 16 32` j'obtiens de meilleurs résultat:
|
|
|
|
```text
|
|
Stats
|
|
---
|
|
Games: 5 in 1321.5769016742706s
|
|
Black: 3 | ratio: 60.00 | time: 658.454s | score: ......207 | engine: AlphaBetaDeepeningPlayerEngine
|
|
White: 2 | ratio: 40.00 | time: 663.123s | score: ......293 | engine: MinmaxDeepeningPlayerEngine
|
|
Null: 0 | ratio: 0.0
|
|
---
|
|
Black player options: {'depth': 3, 'time_limit': 3, 'randomize_moves': True}
|
|
White player options: {'depth': 3, 'time_limit': 3, 'randomize_moves': True}
|
|
```
|
|
|
|
Il est donc fort à parier que mon heuristique ne soit pas encore au point. Mais
|
|
le temps a manqué pour améliorer ce point.
|
|
|
|
J'ai aussi testé lutilisation de threads pour tenter d'améliorer les
|
|
performances de mon implémentation. J'ai d'ailleurs un branche dans mon dépôt
|
|
*git* mais elle n'est pas terminée et inutilisable pour le moment.
|
|
|
|
[reversi]:https://www.coolmathgames.com/blog/how-to-play-reversi-basics-and-best-strategies
|
|
[etude]:https://courses.cs.washington.edu/courses/cse573/04au/Project/mini1/RUSSIA/Final_Paper.pdf
|