First commit: minmax and alphabeta implemented
This commit is contained in:
commit
45abd8c7eb
7 changed files with 593 additions and 0 deletions
48
README.md
Normal file
48
README.md
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
IA: jeu de Reversi
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Le but de ce projet est d'implémenter plusieurs mécanisme de jeu (humain et
|
||||||
|
intelligence artificielle) pour le jeu de Reversi
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Le programme utilise des outils standard de Python installé de base : `random`,
|
||||||
|
`math`, `argpase` et `logging`. Le project est fourni avec un shell *Nix* dans
|
||||||
|
le répertoire `src`
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
le programme propose un emsemble d'options en ligne de commande afin de définir
|
||||||
|
les options du jeu comme le choix des implementations de jeu (aléatoine, MinMax
|
||||||
|
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
|
||||||
|
lancement:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Lancement de base: les deux joueurs jouent avec le moteur aléatoire:
|
||||||
|
./game.py
|
||||||
|
|
||||||
|
# joueur noir humain et joueur blanc MinMax avec une profondeur de 5
|
||||||
|
./game.py -b human -w minmax --depth 5
|
||||||
|
```
|
||||||
|
|
||||||
|
## Choix d'implémentation
|
||||||
|
|
||||||
|
### Classes PlayerEngine
|
||||||
|
|
||||||
|
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):
|
||||||
|
def __init__(self, logger, options):
|
||||||
|
def get_move(self, board):
|
||||||
|
|
||||||
|
class MinmaxPlayerEngine(PlayerEngine):
|
||||||
|
def get_move(board):
|
||||||
|
|
||||||
|
class RandomPlayerEngnine(PlayerEngine):
|
||||||
|
def get_move(board):
|
||||||
|
```
|
||||||
|
|
||||||
|
Il est ainsi plus aisé de tester les moteur dans notre programme de base.
|
1
src/.envrc
Normal file
1
src/.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use nix
|
24
src/classes/CustomFormater.py
Normal file
24
src/classes/CustomFormater.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
class CustomFormatter(logging.Formatter):
|
||||||
|
|
||||||
|
grey = "\x1b[0;35m"
|
||||||
|
blue = "\x1b[34;20m"
|
||||||
|
yellow = "\x1b[33;20m"
|
||||||
|
red = "\x1b[31;20m"
|
||||||
|
bold_red = "\x1b[31;1m"
|
||||||
|
reset = "\x1b[0m"
|
||||||
|
format = "%(levelname)s: %(message)s (%(filename)s:%(lineno)d)"
|
||||||
|
|
||||||
|
FORMATS = {
|
||||||
|
logging.DEBUG: blue + format + reset,
|
||||||
|
logging.INFO: grey + format + reset,
|
||||||
|
logging.WARNING: yellow + format + reset,
|
||||||
|
logging.ERROR: red + format + reset,
|
||||||
|
logging.CRITICAL: bold_red + format + reset
|
||||||
|
}
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
log_fmt = self.FORMATS.get(record.levelno)
|
||||||
|
formatter = logging.Formatter(log_fmt)
|
||||||
|
return formatter.format(record)
|
187
src/classes/Engines.py
Normal file
187
src/classes/Engines.py
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
import random, math
|
||||||
|
|
||||||
|
class PlayerEngine:
|
||||||
|
def __init__(self, logger, options: dict = {}):
|
||||||
|
# init logger do display informations
|
||||||
|
self.logger = logger
|
||||||
|
self.options = options
|
||||||
|
self.logger.info("Init engine {}".format(self.__class__.__name__))
|
||||||
|
|
||||||
|
def get_move(self, board):
|
||||||
|
self.logger.info("engine: {} - player:{}".format(
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.get_player_name(board._nextPlayer)
|
||||||
|
))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_player_name(player):
|
||||||
|
return 'White (O)' if player is 2 else 'Black (X)'
|
||||||
|
|
||||||
|
class RandomPlayerEngine(PlayerEngine):
|
||||||
|
def get_move(self, board):
|
||||||
|
super().get_move(board)
|
||||||
|
moves = board.legal_moves()
|
||||||
|
return random.choice(moves)
|
||||||
|
|
||||||
|
class HumanPlayerEngine(PlayerEngine):
|
||||||
|
def get_move(self, board):
|
||||||
|
super()
|
||||||
|
move = None
|
||||||
|
while move is None:
|
||||||
|
user_input = input("Please enter player {} move: ".format(
|
||||||
|
self.get_player_name(board._nextPlayer)
|
||||||
|
))
|
||||||
|
move = self.validate_input(user_input, board, player)
|
||||||
|
print("{}".format(move))
|
||||||
|
return move
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate_input(input, board):
|
||||||
|
if input == 'print':
|
||||||
|
print("\n{}".format(board.__str__))
|
||||||
|
return None
|
||||||
|
|
||||||
|
if input == 'help':
|
||||||
|
print('{}'.format(board.legal_moves()))
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(input) != 2:
|
||||||
|
return None
|
||||||
|
|
||||||
|
x = int(input[0])
|
||||||
|
y = int(input[1])
|
||||||
|
if not board.is_valid_move(int(player), x, y):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return [board._nextPlayer, x, y]
|
||||||
|
|
||||||
|
class MinmaxPlayerEngine(PlayerEngine):
|
||||||
|
def get_move(self, board):
|
||||||
|
super().get_move(board)
|
||||||
|
value = -math.inf
|
||||||
|
nodes = 1
|
||||||
|
leafs = 0
|
||||||
|
move = ''
|
||||||
|
for m in board.legal_moves():
|
||||||
|
board.push(m)
|
||||||
|
v, n, l = self.checkMinMax(board, False, self.options['depth'] - 1)
|
||||||
|
if v > value:
|
||||||
|
value = v
|
||||||
|
move = m
|
||||||
|
self.logger.debug("found a better move: {} (heuristic:{})".format(
|
||||||
|
move,
|
||||||
|
value
|
||||||
|
))
|
||||||
|
nodes += n
|
||||||
|
leafs += l
|
||||||
|
board.pop()
|
||||||
|
|
||||||
|
self.logger.debug("Tree statistics:\n\tnodes:{}\n\tleafs:{}".format(
|
||||||
|
nodes,
|
||||||
|
leafs
|
||||||
|
))
|
||||||
|
return move
|
||||||
|
|
||||||
|
def checkMinMax(self, board, friend_move:bool, depth :int = 2):
|
||||||
|
nodes = 1
|
||||||
|
leafs = 0
|
||||||
|
move = ''
|
||||||
|
if depth == 0:
|
||||||
|
leafs +=1
|
||||||
|
return board.heuristique(), nodes, leafs
|
||||||
|
|
||||||
|
if friend_move:
|
||||||
|
value = -math.inf
|
||||||
|
for m in board.legal_moves():
|
||||||
|
board.push(m)
|
||||||
|
v, n, l = self.checkMinMax(board, False, depth - 1)
|
||||||
|
if v > value:
|
||||||
|
value = v
|
||||||
|
nodes += n
|
||||||
|
leafs += l
|
||||||
|
board.pop()
|
||||||
|
|
||||||
|
else:
|
||||||
|
value = math.inf
|
||||||
|
for m in board.legal_moves():
|
||||||
|
board.push(m)
|
||||||
|
v, n, l = self.checkMinMax(board, True, depth - 1)
|
||||||
|
if v < value:
|
||||||
|
value = v
|
||||||
|
board.pop();
|
||||||
|
nodes += n
|
||||||
|
leafs += l
|
||||||
|
return value, nodes, leafs
|
||||||
|
|
||||||
|
class AlphabetaPlayerEngine(PlayerEngine):
|
||||||
|
|
||||||
|
def get_move(self, board):
|
||||||
|
super().get_move(board)
|
||||||
|
alpha = -math.inf
|
||||||
|
beta = math.inf
|
||||||
|
nodes = 1
|
||||||
|
leafs = 0
|
||||||
|
move = []
|
||||||
|
value = -math.inf
|
||||||
|
for m in board.legal_moves():
|
||||||
|
board.push(m)
|
||||||
|
v, n, l = self.checkAlphaBeta(board, False, self.options['depth'] - 1, alpha, beta)
|
||||||
|
board.pop()
|
||||||
|
alpha = max(alpha,v)
|
||||||
|
nodes += n
|
||||||
|
leafs += l
|
||||||
|
if alpha >= value:
|
||||||
|
value = alpha
|
||||||
|
move = m
|
||||||
|
self.logger.debug("found a better move: {} (heuristic:{})".format(
|
||||||
|
move,
|
||||||
|
alpha
|
||||||
|
))
|
||||||
|
|
||||||
|
self.logger.debug("Tree statistics:\n\tnodes:{}\n\tleafs:{}".format(
|
||||||
|
nodes,
|
||||||
|
leafs
|
||||||
|
))
|
||||||
|
return move
|
||||||
|
|
||||||
|
|
||||||
|
def checkAlphaBeta(self, board, friend_move : bool, depth, alpha, beta):
|
||||||
|
nodes = 1
|
||||||
|
leafs = 0
|
||||||
|
if depth == 0 :
|
||||||
|
leafs +=1
|
||||||
|
return board.heuristique(), nodes, leafs
|
||||||
|
|
||||||
|
if friend_move:
|
||||||
|
value = -math.inf
|
||||||
|
for m in board.legal_moves():
|
||||||
|
board.push(m)
|
||||||
|
v, n, l = self.checkAlphaBeta(board, False, depth - 1, alpha, beta)
|
||||||
|
board.pop()
|
||||||
|
alpha = max(value,v)
|
||||||
|
nodes += n
|
||||||
|
leafs += l
|
||||||
|
if alpha >= beta:
|
||||||
|
self.logger.debug("Alpha pruning - alpha:{} / beta:{}".format(
|
||||||
|
alpha,
|
||||||
|
beta
|
||||||
|
))
|
||||||
|
return beta, nodes, leafs
|
||||||
|
return alpha, nodes, leafs
|
||||||
|
|
||||||
|
else:
|
||||||
|
value = math.inf
|
||||||
|
for m in board.legal_moves():
|
||||||
|
board.push(m)
|
||||||
|
v, n, l = self.checkAlphaBeta(board, True, depth - 1, alpha, beta)
|
||||||
|
board.pop();
|
||||||
|
beta = min(beta,v)
|
||||||
|
nodes += n
|
||||||
|
leafs += l
|
||||||
|
if alpha >= beta:
|
||||||
|
self.logger.debug("Beta pruning - alpha:{} / beta:{}".format(
|
||||||
|
alpha,
|
||||||
|
beta
|
||||||
|
))
|
||||||
|
return alpha, nodes, leafs
|
||||||
|
return beta, nodes, leafs
|
232
src/classes/Reversi.py
Normal file
232
src/classes/Reversi.py
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
''' Fichier de règles du Reversi
|
||||||
|
Certaines parties de ce code sont fortement inspirée de
|
||||||
|
https://inventwithpython.com/chapter15.html
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
class Board:
|
||||||
|
_BLACK = 1
|
||||||
|
_WHITE = 2
|
||||||
|
_EMPTY = 0
|
||||||
|
|
||||||
|
# Attention, la taille du plateau est donnée en paramètre
|
||||||
|
def __init__(self, boardsize = 8):
|
||||||
|
self._nbWHITE = 2
|
||||||
|
self._nbBLACK = 2
|
||||||
|
self._nextPlayer = self._BLACK
|
||||||
|
self._boardsize = boardsize
|
||||||
|
self._board = []
|
||||||
|
for x in range(self._boardsize):
|
||||||
|
self._board.append([self._EMPTY]* self._boardsize)
|
||||||
|
_middle = int(self._boardsize / 2)
|
||||||
|
self._board[_middle-1][_middle-1] = self._BLACK
|
||||||
|
self._board[_middle-1][_middle] = self._WHITE
|
||||||
|
self._board[_middle][_middle-1] = self._WHITE
|
||||||
|
self._board[_middle][_middle] = self._BLACK
|
||||||
|
|
||||||
|
self._stack= []
|
||||||
|
self._successivePass = 0
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.__init__()
|
||||||
|
|
||||||
|
# Donne la taille du plateau
|
||||||
|
def get_board_size(self):
|
||||||
|
return self._boardsize
|
||||||
|
|
||||||
|
# Donne le nombre de pieces de blanc et noir sur le plateau
|
||||||
|
# sous forme de tuple (blancs, noirs)
|
||||||
|
# Peut être utilisé si le jeu est terminé pour déterminer le vainqueur
|
||||||
|
def get_nb_pieces(self):
|
||||||
|
return (self._nbWHITE, self._nbBLACK)
|
||||||
|
|
||||||
|
# Vérifie si player a le droit de jouer en (x,y)
|
||||||
|
def is_valid_move(self, player, x, y):
|
||||||
|
if x == -1 and y == -1:
|
||||||
|
return not self.at_least_one_legal_move(player)
|
||||||
|
return self.lazyTest_ValidMove(player,x,y)
|
||||||
|
|
||||||
|
def _isOnBoard(self,x,y):
|
||||||
|
return x >= 0 and x < self._boardsize and y >= 0 and y < self._boardsize
|
||||||
|
|
||||||
|
# Renvoie la liste des pieces a retourner si le coup est valide
|
||||||
|
# Sinon renvoie False
|
||||||
|
# Ce code est très fortement inspiré de https://inventwithpython.com/chapter15.html
|
||||||
|
# y faire référence dans tous les cas
|
||||||
|
def testAndBuild_ValidMove(self, player, xstart, ystart):
|
||||||
|
if self._board[xstart][ystart] != self._EMPTY or not self._isOnBoard(xstart, ystart):
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._board[xstart][ystart] = player # On pourra remettre _EMPTY ensuite
|
||||||
|
|
||||||
|
otherPlayer = self._flip(player)
|
||||||
|
|
||||||
|
tilesToFlip = [] # Si au moins un coup est valide, on collecte ici toutes les pieces a retourner
|
||||||
|
for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:
|
||||||
|
x, y = xstart, ystart
|
||||||
|
x += xdirection
|
||||||
|
y += ydirection
|
||||||
|
if self._isOnBoard(x, y) and self._board[x][y] == otherPlayer:
|
||||||
|
# There is a piece belonging to the other player next to our piece.
|
||||||
|
x += xdirection
|
||||||
|
y += ydirection
|
||||||
|
if not self._isOnBoard(x, y):
|
||||||
|
continue
|
||||||
|
while self._board[x][y] == otherPlayer:
|
||||||
|
x += xdirection
|
||||||
|
y += ydirection
|
||||||
|
if not self._isOnBoard(x, y): # break out of while loop, then continue in for loop
|
||||||
|
break
|
||||||
|
if not self._isOnBoard(x, y):
|
||||||
|
continue
|
||||||
|
if self._board[x][y] == player: # We are sure we can at least build this move. Let's collect
|
||||||
|
while True:
|
||||||
|
x -= xdirection
|
||||||
|
y -= ydirection
|
||||||
|
if x == xstart and y == ystart:
|
||||||
|
break
|
||||||
|
tilesToFlip.append([x, y])
|
||||||
|
|
||||||
|
self._board[xstart][ystart] = self._EMPTY # restore the empty space
|
||||||
|
if len(tilesToFlip) == 0: # If no tiles were flipped, this is not a valid move.
|
||||||
|
return False
|
||||||
|
return tilesToFlip
|
||||||
|
|
||||||
|
# Pareil que ci-dessus mais ne revoie que vrai / faux (permet de tester plus rapidement)
|
||||||
|
def lazyTest_ValidMove(self, player, xstart, ystart):
|
||||||
|
if self._board[xstart][ystart] != self._EMPTY or not self._isOnBoard(xstart, ystart):
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._board[xstart][ystart] = player # On pourra remettre _EMPTY ensuite
|
||||||
|
|
||||||
|
otherPlayer = self._flip(player)
|
||||||
|
|
||||||
|
for xdirection, ydirection in [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]:
|
||||||
|
x, y = xstart, ystart
|
||||||
|
x += xdirection
|
||||||
|
y += ydirection
|
||||||
|
if self._isOnBoard(x, y) and self._board[x][y] == otherPlayer:
|
||||||
|
# There is a piece belonging to the other player next to our piece.
|
||||||
|
x += xdirection
|
||||||
|
y += ydirection
|
||||||
|
if not self._isOnBoard(x, y):
|
||||||
|
continue
|
||||||
|
while self._board[x][y] == otherPlayer:
|
||||||
|
x += xdirection
|
||||||
|
y += ydirection
|
||||||
|
if not self._isOnBoard(x, y): # break out of while loop, then continue in for loop
|
||||||
|
break
|
||||||
|
if not self._isOnBoard(x, y): # On a au moins
|
||||||
|
continue
|
||||||
|
if self._board[x][y] == player: # We are sure we can at least build this move.
|
||||||
|
self._board[xstart][ystart] = self._EMPTY
|
||||||
|
return True
|
||||||
|
|
||||||
|
self._board[xstart][ystart] = self._EMPTY # restore the empty space
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _flip(self, player):
|
||||||
|
if player == self._BLACK:
|
||||||
|
return self._WHITE
|
||||||
|
return self._BLACK
|
||||||
|
|
||||||
|
def is_game_over(self):
|
||||||
|
if self.at_least_one_legal_move(self._nextPlayer):
|
||||||
|
return False
|
||||||
|
if self.at_least_one_legal_move(self._flip(self._nextPlayer)):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def push(self, move):
|
||||||
|
[player, x, y] = move
|
||||||
|
assert player == self._nextPlayer
|
||||||
|
if x==-1 and y==-1: # pass
|
||||||
|
self._nextPlayer = self._flip(player)
|
||||||
|
self._stack.append([move, self._successivePass, []])
|
||||||
|
self._successivePass += 1
|
||||||
|
return
|
||||||
|
toflip = self.testAndBuild_ValidMove(player,x,y)
|
||||||
|
self._stack.append([move, self._successivePass, toflip])
|
||||||
|
self._successivePass = 0
|
||||||
|
self._board[x][y] = player
|
||||||
|
for xf,yf in toflip:
|
||||||
|
self._board[xf][yf] = self._flip(self._board[xf][yf])
|
||||||
|
if player == self._BLACK:
|
||||||
|
self._nbBLACK += 1 + len(toflip)
|
||||||
|
self._nbWHITE -= len(toflip)
|
||||||
|
self._nextPlayer = self._WHITE
|
||||||
|
else:
|
||||||
|
self._nbWHITE += 1 + len(toflip)
|
||||||
|
self._nbBLACK -= len(toflip)
|
||||||
|
self._nextPlayer = self._BLACK
|
||||||
|
|
||||||
|
def pop(self):
|
||||||
|
[move, self._successivePass, toflip] = self._stack.pop()
|
||||||
|
[player,x,y] = move
|
||||||
|
self._nextPlayer = player
|
||||||
|
if len(toflip) == 0: # pass
|
||||||
|
assert x == -1 and y == -1
|
||||||
|
return
|
||||||
|
self._board[x][y] = self._EMPTY
|
||||||
|
for xf,yf in toflip:
|
||||||
|
self._board[xf][yf] = self._flip(self._board[xf][yf])
|
||||||
|
if player == self._BLACK:
|
||||||
|
self._nbBLACK -= 1 + len(toflip)
|
||||||
|
self._nbWHITE += len(toflip)
|
||||||
|
else:
|
||||||
|
self._nbWHITE -= 1 + len(toflip)
|
||||||
|
self._nbBLACK += len(toflip)
|
||||||
|
|
||||||
|
# Est-ce que on peut au moins jouer un coup ?
|
||||||
|
# Note: cette info pourrait être codée plus efficacement
|
||||||
|
def at_least_one_legal_move(self, player):
|
||||||
|
for x in range(0,self._boardsize):
|
||||||
|
for y in range(0,self._boardsize):
|
||||||
|
if self.lazyTest_ValidMove(player, x, y):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Renvoi la liste des coups possibles
|
||||||
|
# Note: cette méthode pourrait être codée plus efficacement
|
||||||
|
def legal_moves(self):
|
||||||
|
moves = []
|
||||||
|
for x in range(0,self._boardsize):
|
||||||
|
for y in range(0,self._boardsize):
|
||||||
|
if self.lazyTest_ValidMove(self._nextPlayer, x, y):
|
||||||
|
moves.append([self._nextPlayer,x,y])
|
||||||
|
if len(moves) is 0:
|
||||||
|
moves = [[self._nextPlayer, -1, -1]] # We shall pass
|
||||||
|
return moves
|
||||||
|
|
||||||
|
# Exemple d'heuristique tres simple : compte simplement les pieces
|
||||||
|
def heuristique(self, player=None):
|
||||||
|
if player is None:
|
||||||
|
player = self._nextPlayer
|
||||||
|
if player is self._WHITE:
|
||||||
|
return self._nbWHITE - self._nbBLACK
|
||||||
|
return self._nbBLACK - self._nbWHITE
|
||||||
|
|
||||||
|
def _piece2str(self, c):
|
||||||
|
if c==self._WHITE:
|
||||||
|
return 'O'
|
||||||
|
elif c==self._BLACK:
|
||||||
|
return 'X'
|
||||||
|
else:
|
||||||
|
return '.'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
toreturn=""
|
||||||
|
for l in self._board:
|
||||||
|
for c in l:
|
||||||
|
toreturn += self._piece2str(c)
|
||||||
|
toreturn += "\n"
|
||||||
|
toreturn += "Next player: " + ("BLACK" if self._nextPlayer == self._BLACK else "WHITE") + "\n"
|
||||||
|
toreturn += str(self._nbBLACK) + " blacks and " + str(self._nbWHITE) + " whites on board\n"
|
||||||
|
toreturn += "(successive pass: " + str(self._successivePass) + " )"
|
||||||
|
return toreturn
|
||||||
|
|
||||||
|
__repr__ = __str__
|
||||||
|
|
||||||
|
|
93
src/game.py
Executable file
93
src/game.py
Executable file
|
@ -0,0 +1,93 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from classes.Reversi import Board
|
||||||
|
from classes.Engines import RandomPlayerEngine, HumanPlayerEngine, MinmaxPlayerEngine, AlphabetaPlayerEngine
|
||||||
|
import logging as log
|
||||||
|
import argparse as arg
|
||||||
|
from classes.CustomFormater import CustomFormatter
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Function to parse command line arguments
|
||||||
|
"""
|
||||||
|
def parse_aguments():
|
||||||
|
engines_choices=['random', 'human', 'minmax', 'alphabeta']
|
||||||
|
parser = arg.ArgumentParser('Playing Reversi with (virtual) friend')
|
||||||
|
|
||||||
|
parser.add_argument('-w', '--white-engine',
|
||||||
|
choices=engines_choices,
|
||||||
|
help='white player engine (random)',
|
||||||
|
default='random'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument('-b', '--black-engine',
|
||||||
|
choices=engines_choices,
|
||||||
|
help='black player engine (random)',
|
||||||
|
default='random'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument('-D', '--depth',
|
||||||
|
help='Minmax exploration depth',
|
||||||
|
type=int,
|
||||||
|
default=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
debug_group = parser.add_mutually_exclusive_group()
|
||||||
|
debug_group.add_argument('-V', '--verbose',
|
||||||
|
help='Verbose output',
|
||||||
|
action='store_true')
|
||||||
|
debug_group.add_argument('-d', '--debug',
|
||||||
|
help='Activate debug mode',
|
||||||
|
action='store_true')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Main Function
|
||||||
|
"""
|
||||||
|
if __name__ == '__main__':
|
||||||
|
engines = {
|
||||||
|
"random": RandomPlayerEngine,
|
||||||
|
"human": HumanPlayerEngine,
|
||||||
|
"minmax": MinmaxPlayerEngine,
|
||||||
|
"alphabeta": AlphabetaPlayerEngine,
|
||||||
|
}
|
||||||
|
print("Stating PyReverso...")
|
||||||
|
args = parse_aguments()
|
||||||
|
logger = log.getLogger()
|
||||||
|
print(args)
|
||||||
|
# Create handler for streaming to tty (stderr / stdout)
|
||||||
|
tty_handler = log.StreamHandler()
|
||||||
|
tty_handler.setFormatter(CustomFormatter())
|
||||||
|
logger.addHandler(tty_handler)
|
||||||
|
|
||||||
|
# Activate verbose or debug mode
|
||||||
|
if args.verbose is True:
|
||||||
|
logger.setLevel(log.INFO)
|
||||||
|
logger.info('VERBOSE mode activated')
|
||||||
|
|
||||||
|
if args.debug is True:
|
||||||
|
logger.setLevel(log.DEBUG)
|
||||||
|
logger.debug('DEBUG mode activated')
|
||||||
|
|
||||||
|
game = Board(10)
|
||||||
|
logger.debug("Init players engines - black:{} / white:{}".format(
|
||||||
|
args.black_engine,
|
||||||
|
args.white_engine
|
||||||
|
))
|
||||||
|
options = {
|
||||||
|
'depth': args.depth
|
||||||
|
}
|
||||||
|
wplayer = engines[args.white_engine](logger, options)
|
||||||
|
bplayer = engines[args.black_engine](logger, options)
|
||||||
|
while ( not game.is_game_over()):
|
||||||
|
if game._nextPlayer == 1:
|
||||||
|
move = bplayer.get_move(game)
|
||||||
|
else:
|
||||||
|
move = wplayer.get_move(game)
|
||||||
|
print("Player {} move: {}".format(
|
||||||
|
game._nextPlayer,
|
||||||
|
move
|
||||||
|
))
|
||||||
|
game.push(move)
|
||||||
|
print("Game end - score black:{}, white:{}".format(game.heuristique(1), game.heuristique(2)))
|
||||||
|
print(game.__str__)
|
8
src/shell.nix
Normal file
8
src/shell.nix
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
with (import <nixpkgs> {});
|
||||||
|
mkShell {
|
||||||
|
buildInputs = [
|
||||||
|
|
||||||
|
# Defines a python + set of packages.
|
||||||
|
python3
|
||||||
|
];
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue