Compare commits
7 commits
3d3f757f41
...
d66cd99751
Author | SHA1 | Date | |
---|---|---|---|
d66cd99751 | |||
9d792187dc | |||
23468ffe05 | |||
4a8d97c4ed | |||
f8cf9e6bff | |||
507964cd6b | |||
d846549bb7 |
4 changed files with 191 additions and 67 deletions
125
README.md
125
README.md
|
@ -1,22 +1,25 @@
|
||||||
IA: jeu de Reversi
|
IA: jeu de Reversi
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
Le but de ce projet est d'implémenter plusieurs mécanisme de jeu (humain et
|
Le but de ce projet est d'implémenter plusieurs mécanismes de jeu (humain et
|
||||||
intelligence artificielle) pour le jeu de Reversi
|
intelligence artificielle) pour le Reversi.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Le programme utilise des outils standard de Python installé de base : `random`,
|
Le programme utilise des outils standard de Python installés de base : `random`,
|
||||||
`math`, `argpase` et `logging`. Le project est fourni avec un shell *Nix* dans
|
`math`, `argpase` et `logging`. Le projet est fourni avec un shell *Nix* dans
|
||||||
le répertoire `src`
|
le répertoire `src`.
|
||||||
|
|
||||||
## Utilisation
|
## Utilisation
|
||||||
|
|
||||||
le programme propose un emsemble d'options en ligne de commande afin de définir
|
J'ai choisi de créer un programme en *Python* utilisable depuis un terminal
|
||||||
les options du jeu comme le choix des implementations de jeu (aléatoine, MinMax
|
(testé uniquement sous Linux).
|
||||||
etc.) ou encore les paramètres (profondeur de recherche). Une aide est intégrée
|
|
||||||
au programme via la commande `./game.py -h`. Voici quelques exemple de
|
Le programme propose un ensemble d'options afin de définir les paramètres des
|
||||||
lancement:
|
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
|
```shell
|
||||||
# Lancement de base: les deux joueurs jouent avec le moteur aléatoire et les
|
# Lancement de base: les deux joueurs jouent avec le moteur aléatoire et les
|
||||||
|
@ -24,38 +27,50 @@ options de base:
|
||||||
./game.py
|
./game.py
|
||||||
|
|
||||||
# joueur noir humain et joueur blanc MinMax avec une profondeur de 5
|
# joueur noir humain et joueur blanc MinMax avec une profondeur de 5
|
||||||
./game.py -be human -we minmax --white-depth-exploration 5
|
./game.py -be human -we minmax -wd 5
|
||||||
```
|
```
|
||||||
Voici la liste des options :
|
|
||||||
|
Voyons maintenant quelques paramètres.
|
||||||
|
|
||||||
### Moteur de jeu
|
### Moteur de jeu
|
||||||
|
|
||||||
Il est possible de définir le moteur de jeu indépedamment pour chaque joueur et
|
Il est possible de définir le moteur de jeu indépendamment pour chaque joueur et
|
||||||
ainsi faire des match:
|
ainsi faire des matches:
|
||||||
|
|
||||||
* `-be` | `--black-player-engine`: moteur utilisé par le joueur avec les pions
|
* `-be` | `--black-player-engine`: moteur utilisé par le joueur avec les pions
|
||||||
noirs
|
noirs
|
||||||
* `-we` | `--white-player-engine`: moteur utilisé par le joueur avec les pions
|
* `-we` | `--white-player-engine`: moteur utilisé par le joueur avec les pions
|
||||||
blancs
|
blancs
|
||||||
|
|
||||||
Le moteur de jeux par défaut est random.
|
Le moteur de jeux par défaut est `random`.
|
||||||
|
|
||||||
### Profondeur d'exploration
|
### Profondeur d'exploration
|
||||||
|
|
||||||
Il est aussi possible de définir la profindeur d'exploration de l'arbre de jeu
|
Il est aussi possible de définir la profondeur d'exploration de l'arbre de jeu
|
||||||
pour chacun des joueurs:
|
pour chacun des joueurs:
|
||||||
|
|
||||||
* `-bd` | `--black-depth-exploration`: niveau d'eploration de l'arbre de jeu
|
* `-bd` | `--black-depth-exploration`: niveau d'exploration de l'arbre de jeu
|
||||||
pour le joueur au pions noirs, valable pour les moteurs `minmax` et
|
pour le joueur au pions noirs, valable pour les moteurs `minmax` et
|
||||||
`alphabeta`. Utilisé aussi pour définit la profondeur de départ pour
|
`alphabeta`. Utilisé aussi pour définit la profondeur de départ pour
|
||||||
l'*iterative deepening*
|
l'*iterative deepening*
|
||||||
* `-wd` | `--white-depth-exploration`: niveau d'eploration de l'arbre de jeu
|
* `-wd` | `--white-depth-exploration`: niveau d'exploration de l'arbre de jeu
|
||||||
pour le joueur au pions noirs, valable pour les moteurs `minmax` et
|
pour le joueur au pions blancs, valable pour les moteurs `minmax` et
|
||||||
`alphabeta`Utilisé aussi pour définit la profondeur de départ pour
|
`alphabeta`Utilisé aussi pour définit la profondeur de départ pour
|
||||||
l'*iterative deepening*
|
l'*iterative deepening*
|
||||||
|
|
||||||
La profondeur par défaut est 3.
|
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
|
### Heuristique
|
||||||
|
|
||||||
Il est possible de choisir entre les 3 moteur de calcul d'heuristique inclus à
|
Il est possible de choisir entre les 3 moteur de calcul d'heuristique inclus à
|
||||||
|
@ -75,14 +90,19 @@ 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`: scores utilisés pour le calcul des heuristiques pour les moteurs
|
||||||
`weight` et `full`.
|
`weight` et `full`.
|
||||||
|
|
||||||
L'affichage verbeux est activé avec `-V` et les informations de débogage sont
|
### Debug et mode verbeux
|
||||||
|
|
||||||
|
L'affichage verbeux est activé avec `-V` et les informations de débogages 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
|
J'ai avant tout privilégié la personnalisation des paramètres des
|
||||||
différents moteurs composant le jeu. Il,e st ainsi plus aisé de tester le
|
différents moteurs composant le jeu. Il est ainsi plus aisé de tester le
|
||||||
fonctionnement des différents moteurs.
|
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
|
### Classes PlayerEngine
|
||||||
|
|
||||||
|
@ -107,30 +127,31 @@ class AlphabetaPlayerEngine(PlayerEngine):
|
||||||
|
|
||||||
Quatre moteur "joueurs" sont implémentés :
|
Quatre moteur "joueurs" sont implémentés :
|
||||||
|
|
||||||
* `Human` pour gérer des joueurs humain, une saisir utilisateur est demandée
|
* `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
|
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`;
|
avec la commande `print` ou les coups possibles avec `help`;
|
||||||
* `Ramdom` va choisir aléatoirement le coup à jouer en fonction des coups;
|
* `Ramdom` va choisir aléatoirement le coup à jouer en fonction de ceux
|
||||||
possibles;
|
possibles;
|
||||||
* `Minmax` utilise *MinMax* pour déterminer le coup à jouer avec une profondeur
|
* `Minmax` utilise *MinMax* pour déterminer le coup à jouer avec une profondeur
|
||||||
maximale définie;
|
maximale définie;
|
||||||
* `AphaBeta` utilise *AlphaBeta* pour déterminer le coup à jouer avec une
|
* `AphaBeta` utilise *AlphaBeta* pour déterminer le coup à jouer avec une
|
||||||
profondeur maximale définie;
|
profondeur maximale définie;
|
||||||
* `MinmaxDeepeningMinmax` utilise Minmax avec un temps maximum autorisé;
|
* `MinmaxDeepeningMinmax` utilise Minmax avec un temps maximum autorisé en
|
||||||
* `AlphaBetaDeepening` utilise AlphaBeta avec un temps maximum autorisé
|
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
|
Le choix de ces moteur se fait en ligne de commande avec les options évoquées
|
||||||
plus haut.
|
plus haut.
|
||||||
|
|
||||||
### Classes HeuristicsEngine
|
### Classes HeuristicsEngine
|
||||||
|
|
||||||
Plusieurs classes impémentent plusieurs méthodes pour le calcul de
|
Plusieurs classes implémentent plusieurs méthodes pour le calcul de
|
||||||
l'heuristique. Toutes les implémentations se trouvent dans le fichier
|
l'heuristique. Toutes les implémentations se trouvent dans le fichier
|
||||||
`./src/classes/Heuristic.py` Comme nous l'avons vu, les moteurs peuvent être
|
`./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
|
choisis en ligne de commande et de façon indépendante pour les joueurs blanc et
|
||||||
noir.
|
noir.
|
||||||
|
|
||||||
Trois implementation sond disponibles:
|
Trois implementations sont disponibles:
|
||||||
|
|
||||||
1. `ScoreHeuristicEngine`: l'heuristique se sert du score (comptage des pièces
|
1. `ScoreHeuristicEngine`: l'heuristique se sert du score (comptage des pièces
|
||||||
sur le tableau) via la méthode `Board.heuristique`;
|
sur le tableau) via la méthode `Board.heuristique`;
|
||||||
|
@ -155,17 +176,19 @@ Cependant certaines parties du plateau de jeu sont à éviter :
|
||||||
adverse de placer un de ses pions dans le coin. La case en diagonale du coin
|
adverse de placer un de ses pions dans le coin. La case en diagonale du coin
|
||||||
est particulièrement sensible.
|
est particulièrement sensible.
|
||||||
* Les lignes juste avant les bords, placer un pion à cet endroit permettrai à
|
* Les lignes juste avant les bords, placer un pion à cet endroit permettrai à
|
||||||
l'adversaire de placer un pion sur le bord. Ce ion sera alors p[lus
|
l'adversaire de placer un pion sur le bord. Ce pion sera alors plus
|
||||||
difficilement *"capturable"*
|
difficilement *"capturable"*
|
||||||
|
|
||||||
Les poids affectés sont personnalisable via l'options `--weight`, par défaut
|
Les poids affectés sont personnalisable via l'options `--weight`, par défaut
|
||||||
nous avons -5, 2, 10 et 25.
|
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
|
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 piste
|
et Muthukaruppan Annamalai de l'université de Washingtown propose d'autre pistes
|
||||||
pour maéliorer l'heuristique. [télécharger le pdf][etude]
|
pour améliorer son calcul. [télécharger le pdf][etude]. Mon calcul des poinds
|
||||||
|
s'en inspire grandement.
|
||||||
|
|
||||||
Voici le tableau des poinds par défaut, il peut être affiché avec l'option
|
Voici le tableau des poids par défaut, il peut être affiché avec l'option
|
||||||
`--show-weights-table`:
|
`--show-weights-table`:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
@ -186,11 +209,39 @@ Starting PyReverso...
|
||||||
9 |25 |-5 |10 | 8 | 8 | 8 | 8 |10 |-5 |25 |
|
9 |25 |-5 |10 | 8 | 8 | 8 | 8 |10 |-5 |25 |
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### À savoir:
|
### À savoir:
|
||||||
|
|
||||||
Les pois utilisé pour les heuristiques sont important.
|
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
|
||||||
|
---
|
||||||
|
Parties: 10
|
||||||
|
Black: 8 | ratio: 80.0 | engine: MinmaxDeepeningPlayerEngine
|
||||||
|
White: 2 | ratio: 20.0 | engine: AlphaBetaDeepeningPlayerEngine
|
||||||
|
Null: 0 | ratio: 0.0
|
||||||
|
```
|
||||||
|
## 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`).
|
||||||
|
|
||||||
|
Il est donc fort à parier que mon heuristique ne soit pas encore au point. Mais
|
||||||
|
le temps a manqué pour améliorer ce point.
|
||||||
|
|
||||||
[reversi]:https://www.coolmathgames.com/blog/how-to-play-reversi-basics-and-best-strategies
|
[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
|
[etude]:https://courses.cs.washington.edu/courses/cse573/04au/Project/mini1/RUSSIA/Final_Paper.pdf
|
||||||
|
|
|
@ -63,7 +63,7 @@ class PlayerEngine:
|
||||||
|
|
||||||
def _show_better_move(self, move, heuristic):
|
def _show_better_move(self, move, heuristic):
|
||||||
self.logger.debug(" -> Found a better move: {},{} | heuristic:{}".format(
|
self.logger.debug(" -> Found a better move: {},{} | heuristic:{}".format(
|
||||||
move[1],move[2],
|
chr(move[1] + 65),move[2],
|
||||||
heuristic
|
heuristic
|
||||||
))
|
))
|
||||||
|
|
||||||
|
@ -103,7 +103,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(self.player)
|
self._get_player_name(self.player)
|
||||||
))
|
))
|
||||||
move = self.validate_input(user_input, board)
|
move = self.validate_input(user_input, board)
|
||||||
return move
|
return move
|
||||||
|
@ -114,23 +114,33 @@ class HumanPlayerEngine(PlayerEngine):
|
||||||
@param input: string
|
@param input: string
|
||||||
@return: array
|
@return: array
|
||||||
"""
|
"""
|
||||||
@staticmethod
|
def validate_input(self, input, board):
|
||||||
def validate_input(input, board):
|
|
||||||
if input == 'print':
|
if input == 'print':
|
||||||
print(board.show_board())
|
print("\n{}".format(board.show_board()))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if input == 'help':
|
if input == 'help':
|
||||||
print('{}'.format(board.legal_moves()))
|
text = "Possible move:"
|
||||||
|
for m in board.legal_moves():
|
||||||
|
text += " {}{}".format(chr(65+m[1]), m[2])
|
||||||
|
print(text)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if len(input) != 2:
|
if len(input) != 2:
|
||||||
|
self.logger.error("Input coordinate (A1 for example), help or print")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
x = int(input[0])
|
x = ord(input[0]) - 65
|
||||||
y = int(input[1])
|
y = int(input[1])
|
||||||
|
try:
|
||||||
if not board.is_valid_move(board._nextPlayer, x, y):
|
if not board.is_valid_move(board._nextPlayer, x, y):
|
||||||
|
self.logger.error("Move is not possible at this place")
|
||||||
return None
|
return None
|
||||||
|
except IndexError:
|
||||||
|
self.logger.error("Invalid input must be [A-J][0-9] (was {})".format(input))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
return [board._nextPlayer, x, y]
|
return [board._nextPlayer, x, y]
|
||||||
|
|
||||||
|
@ -250,7 +260,7 @@ class AlphabetaPlayerEngine(PlayerEngine):
|
||||||
if value >= alpha:
|
if value >= alpha:
|
||||||
alpha = value
|
alpha = value
|
||||||
move = m
|
move = m
|
||||||
self._show_stats_info(move, alpha)
|
self._show_better_move(move, alpha)
|
||||||
|
|
||||||
self._show_stats_info(depth, nodes, leafs, value)
|
self._show_stats_info(depth, nodes, leafs, value)
|
||||||
return move, alpha
|
return move, alpha
|
||||||
|
|
|
@ -30,7 +30,7 @@ class Board:
|
||||||
self._successivePass = 0
|
self._successivePass = 0
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.__init__()
|
self.__init__(self.get_board_size())
|
||||||
|
|
||||||
# Donne la taille du plateau
|
# Donne la taille du plateau
|
||||||
def get_board_size(self):
|
def get_board_size(self):
|
||||||
|
@ -218,15 +218,17 @@ class Board:
|
||||||
|
|
||||||
def show_board(self):
|
def show_board(self):
|
||||||
display = " |"
|
display = " |"
|
||||||
|
sep = "----"
|
||||||
for x in range(self.get_board_size()):
|
for x in range(self.get_board_size()):
|
||||||
display += "{}|".format(str(x))
|
display += " {} |".format(chr(65+x))
|
||||||
display += "\n"
|
sep += '----'
|
||||||
|
display += "\n" + sep + "\n"
|
||||||
for x in range(self.get_board_size()):
|
for x in range(self.get_board_size()):
|
||||||
display += "{}|".format(str(x))
|
display += " {} |".format(str(x))
|
||||||
for y in range(self.get_board_size()):
|
for y in range(self.get_board_size()):
|
||||||
display += "{}|".format(self._piece2str(self._board[x][y]))
|
display += " {} |".format(self._piece2str(self._board[x][y]))
|
||||||
display += "\n"
|
display += "\n"#+sep+"\n"
|
||||||
return display
|
return display + sep + '\n'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
toreturn=""
|
toreturn=""
|
||||||
|
|
65
src/game.py
65
src/game.py
|
@ -83,6 +83,12 @@ def parse_aguments():
|
||||||
default=[-5, 2, 10,25]
|
default=[-5, 2, 10,25]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument('-r', '--recursions',
|
||||||
|
help='Number parties to play',
|
||||||
|
type=int,
|
||||||
|
default=1
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument('--show-weights-table',
|
parser.add_argument('--show-weights-table',
|
||||||
help='Display weight table used in \'weight\' and \'full\' heuristic calculation and exit',
|
help='Display weight table used in \'weight\' and \'full\' heuristic calculation and exit',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
|
@ -177,16 +183,71 @@ if __name__ == '__main__':
|
||||||
'randomize_moves': args.black_randomize_moves
|
'randomize_moves': args.black_randomize_moves
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
recursions = args.recursions
|
||||||
|
parties = []
|
||||||
|
while recursions > 0:
|
||||||
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)
|
||||||
else:
|
else:
|
||||||
move = wplayer.get_move(game)
|
move = wplayer.get_move(game)
|
||||||
|
# Display informations only id we are not in recurse mode
|
||||||
|
if args.recursions == 1:
|
||||||
print("Player {} move: {},{}".format(
|
print("Player {} move: {},{}".format(
|
||||||
"Black (X)" if move[0] == 2 else "White (O)",
|
"Black (X)" if move[0] == 2 else "White (O)",
|
||||||
move[1],
|
move[1],
|
||||||
move[2]
|
move[2]
|
||||||
))
|
))
|
||||||
game.push(move)
|
game.push(move)
|
||||||
print("Game end - score black:{}, white:{}\n".format(game._nbBLACK, game._nbWHITE))
|
|
||||||
print(game.show_board())
|
parties.append([recursions, game._nbBLACK, game._nbWHITE])
|
||||||
|
score = game._nbBLACK - game._nbWHITE
|
||||||
|
if score == 0:
|
||||||
|
winner = "No winner"
|
||||||
|
elif score > 0:
|
||||||
|
winner = "Black"
|
||||||
|
else:
|
||||||
|
winner = "White"
|
||||||
|
|
||||||
|
print("\nGAME OVER\n---\nWINNER: {} | black:{} | white:{}".format(
|
||||||
|
winner,
|
||||||
|
game._nbBLACK,
|
||||||
|
game._nbWHITE
|
||||||
|
))
|
||||||
|
print("\n{}".format(game.show_board()))
|
||||||
|
game.reset()
|
||||||
|
recursions -= 1
|
||||||
|
|
||||||
|
# Make somes statistics
|
||||||
|
if args.recursions > 1:
|
||||||
|
numbers = len(parties)
|
||||||
|
black = 0
|
||||||
|
white = 0
|
||||||
|
null = 0
|
||||||
|
for p in parties:
|
||||||
|
black += 1 if p[1] > p[2] else 0
|
||||||
|
white += 1 if p[1] < p[2] else 0
|
||||||
|
null += 1 if p[1] == p[2] else 0
|
||||||
|
print("Stats\n---")
|
||||||
|
print("Parties: {}".format(numbers))
|
||||||
|
print("Black: {:>2} | ratio: {:>6} | engine: {}".format(
|
||||||
|
black,
|
||||||
|
black * 100 / numbers,
|
||||||
|
bplayer._get_class_name(),
|
||||||
|
|
||||||
|
))
|
||||||
|
|
||||||
|
print("White: {:>2} | ratio: {:>6} | engine: {}".format(
|
||||||
|
white,
|
||||||
|
white * 100 / numbers,
|
||||||
|
wplayer._get_class_name(),
|
||||||
|
))
|
||||||
|
|
||||||
|
print("Null: {:>2} | ratio: {:>6}".format(
|
||||||
|
null,
|
||||||
|
null * 100 / numbers
|
||||||
|
))
|
||||||
|
print("---\nBlack player options: {}\nWhite player options: {}".format(
|
||||||
|
bplayer.options, wplayer.options
|
||||||
|
))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue