# -*- 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 show_board(self): display = " |" for x in range(self.get_board_size()): display += "{}|".format(str(x)) display += "\n" for x in range(self.get_board_size()): display += "{}|".format(str(x)) for y in range(self.get_board_size()): display += "{}|".format(self._piece2str(self._board[x][y])) display += "\n" return display 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__