IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

pyReversi


précédentsommairesuivant

VII. Player.py

Vous pouvez visualiser le contenu du fichier en cliquant sur le lien Player.py.

VII-1. La classe Player

La classe Player identifie un joueur humain. Les paramètres donnés sont en général uniquement présents pour assurer une compatibilité avec la classe Computer. La classe Player n'a de toute façon qu'un constructeur qui initialise quelques paramètres. Mais nous nous attarderons plutôt sur la classe Computer.

 
Sélectionnez
class Player:
    def __init__(self, reversi, couleur):
        self.reversi = reversi
        self.couleur = couleur
        if couleur == 'white': self.joueur = 2
        elif couleur == 'black': self.joueur = 1
        self.isWaiting = False
        self.isEndGame = False
        self.timer = 0
        self.compteur = 0

VII-2. La classe Computer

L'intérêt de programmer ce jeu réside surtout dans le fait de pouvoir jouer contre un ordinateur. La classe Computer permet de gérer les choix de jeu de l'ordinateur. La difficulté est de ne pas se perdre dans les nombreux attributs. Les attributs de la classe Computer :

  • reversi : pointeur sur une instance de Reversi.Reversi pour avoir un accès complet au jeu ;
  • couleur : black or white ;
  • joueur : 1 (noirs) ou 2 (blancs) ;
  • isWaiting : indique si l'ordinateur est dans la fonction Waiting(self) ;
  • levelbegin : profondeur des coups de la réflexion de l'ordinateur en début de partie ;
  • levelmiddle : profondeur des coups de la réflexion de l'ordinateur en milieu de partie ;
  • levelend : profondeur des coups de la réflexion de l'ordinateur en fin de partie ;
  • level : profondeur actuelle des coups de la réflexion de l'ordinateur ;
  • BestMove : meilleurs coups à jouer donnés par la fonction Réflexion ;
  • timer : temps total de réflexion d'un joueur ;
  • IAbegin : type d'IA en début de partie ;
  • NbMoveIAbegin : durée (en coups) du début de partie ;
  • IAmiddle : type d'IA en milieu de partie ;
  • IAend : type d'IA en fin de partie ;
  • NbMoveIAend : durée (en coups de la fin de partie) ;
  • IA : IA actuelle ;
  • ThinkingDuringOther : booléen indiquant si l'ordinateur réfléchit pendant la réflexion de l'adversaire ;
  • endreflexion : force l'arrêt de la réflexion pour que l'ordinateur puisse jouer ; immédiatement ;
  • endgame : force l'arrêt du jeu pour l'ordinateur ;
  • movenext : liste des prochains coups possibles du joueur ayant la main ;
  • moveagains : liste des coups de contre aux coups possibles (-1 indiquant que l'ordinateur n'a pas encore choisi le meilleur coup possible) ;
  • scoreagainst : liste des scores correspondants à chaque coup de contre ;
  • compteur : compteur du nombre d'appels de la fonction Reflexion.

Les méthodes de la classe Computer :

  • __init__(self, reversi, couleur) : constructeur ;
  • Initialize(self) : initialise les paramètres nécessaires à chaque tour de jeu ;
  • MoveAleatory(self) : retourne un des coups possibles aléatoirement ;
  • Play(self) : lance la réflexion de l'ordinateur pour effectuer son tour de jeu ;
  • Waiting(self) : lance la réflexion de l'ordinateur pendant le tour de jeu de l'adversaire ;
  • Reflexion(self, tab, profondeur, joueur, A, B, passe, tempssomme, tempsdifference) : étude des coups par l'algorithme alpha-beta.

VII-2-1. __init__(self, reversi, couleur)

Le constructeur __init__(self, reversi, couleur) crée une instance de Computer en initialisant ces différents attributs par les variables globales du fichier Global.py.

 
Sélectionnez
    def __init__(self, reversi, couleur):
        self.reversi = reversi
        self.couleur = couleur
        if couleur == 'white': self.joueur = 2
        elif couleur == 'black': self.joueur = 1
        self.isWaiting = False
        self.levelbegin = glovar['levelbegin'+couleur]
        self.levelmiddle = glovar['levelmiddle'+couleur]
        self.level = self.levelbegin
        self.levelend = glovar['levelend'+couleur]
        self.BestMove = -1
        self.timer = 0
        self.IAbegin = glovar['iabegin' + couleur]
        self.NbMoveIAbegin = glovar['nbmovebegin' + couleur]
        self.IAmiddle = glovar['iamiddle' + couleur]
        self.IAend = glovar['iaend' + couleur]
        self.NbMoveIAend = glovar['nbmoveend' + couleur]
        self.ThinkingDuringOther = glovar['thinking'+couleur]
        self.compteur = 0
        self.Initialize()

VII-2-2. Initialize(self)

La fonction Initialize(self) est appelée à chaque tour pour réinitialiser les différents paramètres présents dans cette fonction.

 
Sélectionnez
    def Initialize(self):
        self.endreflexion = False
        self.endgame = False
        self.isWaiting = False
        self.isEndGame = False
        self.movenext = MovePossible(self.reversi.plate, 3 - self.joueur)
        self.moveagainst = len(self.movenext) * [-1]
        self.scoreagainst = len(self.movenext) * [0]
        
        if self.NbMoveIAbegin + 4 < sum(Score(self.reversi.plate)):
            if 64 - sum(Score(self.reversi.plate)) <= self.NbMoveIAend:
                self.level = self.levelend
                self.IA = self.IAend
            else:
                self.level = self.levelmiddle
                self.IA = self.IAmiddle
        else:
            self.level = self.levelbegin
            self.IA = self.IAbegin

VII-2-3. MoveAleatory(self)

Cette fonction retourne un des coups possibles de l'ordinateur aléatoirement. On réutilise ici la fonction MovePossible(Plate, joueur) du fichier Plate.py :

 
Sélectionnez
    def MoveAleatory(self):
        return random.choice(MovePossible(self.reversi.plate, self.joueur))

VII-2-4. Play(self)

Cette fonction calcule le coup que va jouer l'ordinateur. Si ce dernier a eu le temps de réfléchir pendant le temps de réflexion de son adversaire (un coup de contre doit être différent de -1 pour le dernier coup joué (self.moveagainst[self.movenext.index(self.reversi.lastMove)] != -1), l'ordinateur joue immédiatement ce coup de contre. Sinon, en fonction de son IA, il calcule son coup de contre par l'algorithme alpha bêta à travers la fonction Reflexion. Le coup est finalement joué en appelant la méthode pnlPlateMove de l'instance wReversi (qui se chargera alors de changer le tour de jeu).

 
Sélectionnez
    def Play(self):
        print self.movenext
        print self.moveagainst
        if max(self.moveagainst) == -1 or self.moveagainst[self.movenext.index(self.reversi.lastMove)] == -1:
            if self.IA == IAALEATORY:
                move = self.MoveAleatory()
                score = 0
            else:
                self.BestMove = -1
                if self.IA == IABESTSCOREMIN: score = self.Reflexion(self.reversi.plate, 0, self.joueur, -INFINITY, 1, False, 0, 100)
                elif self.IA == IAWINLOSE: score = self.Reflexion(self.reversi.plate, 0, self.joueur, -2, 1, False, 0, 100)
                else: score = self.Reflexion(self.reversi.plate, 0, self.joueur, -INFINITY, INFINITY, False, 0, 100)
                move = self.BestMove
        else:
            move = self.moveagainst[self.movenext.index(self.reversi.lastMove)]
            score = self.scoreagainst[self.movenext.index(self.reversi.lastMove)]

        if self.endgame: return
        self.movenext = [-1]
        self.moveagainst = [-1]
        wx.CallAfter(self.reversi.wReversi.pnlPlateMove, move, score)

VII-2-5. Waiting(self)

La fonction Waiting(self) permet à l'ordinateur de réfléchir pendant le tour de jeu de son adversaire (ce qui peut être utile pour gagner du temps si l'adversaire est un humain). Pour chaque coup possible (movenext), l'ordinateur va calculer le coup de contre en remplissant au fur et à mesure la liste moveagainst et la liste des scores correspondants scoreagainst. Chaque coup est calculé avec l'IA qui lui a été définie. Une fois que tous les coups ont été contrés, il recommence l'opération, mais en augmentant la profondeur de sa réflexion d'une unité. Cependant, on trie les coups dans l'ordre de la plus petite évaluation pour que l'ordinateur se concentre ensuite d'abord sur le meilleur coup pour son adversaire.
Ainsi plus l'adversaire met de temps à jouer, plus l'ordinateur a le temps de préparer un bon coup. Dès que l'adversaire a joué (endreflexion = True), l'ordinateur stoppe sa réflexion.

Comme les fonctions Waiting(self) et Play(self) sont exécutées à partir d'un thread, il est important que toutes les commandes wxPython ne soient pas envoyées directement sous peine de risque de plantage de l'application. Ainsi, on utilise la fonction sécurisée wx.CallAfter(mafonction, *mesparamètres).

 
Sélectionnez
    def Waiting(self):
        try:
            self.isWaiting = True
            if len(self.movenext) == 0 or (not self.ThinkingDuringOther): return
            wx.CallAfter(self.reversi.wReversi.lThinking.Clear)
            wx.CallAfter(self.reversi.wReversi.lThinking.Append, 'Nombre de coups : ' + str(len(self.movenext)))
            bolend = False
            random.shuffle(self.movenext)
            if self.IA == IAALEATORY: return
            else:
                plate = self.reversi.plate[:]
                levelbackup = self.level
                while True:
                    wx.CallAfter(self.reversi.wReversi.lThinking.Append, 'Profondeur en cours : ' + str(self.level)) 
                    for i in range(len(self.movenext)):
                        letab = plate[:]
                        Reverse(letab, self.movenext[i], 3 - self.joueur)
                        self.BestMove = -1
                        if self.IA == IABESTSCOREMIN: mn = self.Reflexion(letab, 0, self.joueur, -INFINITY, 1, False, 0, 100)
                        elif self.IA == IAWINLOSE: mn = self.Reflexion(letab, 0, self.joueur, -2, 1, False, 0, 100)
                        else: mn = self.Reflexion(letab, 0, self.joueur, -INFINITY, INFINITY, False, 0, 100)
                        if self.endreflexion or (not self.ThinkingDuringOther): return
                        self.moveagainst[i] = self.BestMove
                        self.scoreagainst[i] = mn
                        bm = self.BestMove
                        mni = self.movenext[i]
                        if levelbackup != self.level:
                            try: wx.CallAfter(self.reversi.wReversi.lThinking.Delete, 2)
                            except: pass
                        wx.CallAfter(self.reversi.wReversi.lThinking.Append, u"Coup %d : %s contre : %s évalutation : %d" %
                                     (i+1,  chr(mni % 10 + 64) + str(9 - mni // 10), chr(bm % 10 + 64) + str(9 - bm // 10), mn))
                    wx.CallAfter(self.reversi.wReversi.lThinking.Clear)
                    wx.CallAfter(self.reversi.wReversi.lThinking.Append, 'Nombre de coups : ' + str(len(self.movenext)))
                    wx.CallAfter(self.reversi.wReversi.lThinking.Append, 'Profondeur en cours : ' + str(self.level))
                    a = sorted(zip(self.scoreagainst, self.moveagainst, self.movenext))
                    self.moveagainst = [i[1] for i in a]
                    self.movenext = [i[2] for i in a]
                    self.scoreagainst = [i[0] for i in a]
                    for i in range(len(self.movenext)):
                        wx.CallAfter(self.reversi.wReversi.lThinking.Append, u"Coup %d : %s contre : %s évalutation : %d" %
                                     (i+1,  chr(self.movenext[i] % 10 + 64) + str(9 - self.movenext[i] // 10),
                                      chr(self.moveagainst[i] % 10 + 64) + str(9 - self.moveagainst[i] // 10), self.scoreagainst[i]))                        
                    if 64 - (sum(Score(plate)) + self.level) <= 0:
                        if bolend: return
                        else:
                            self.IA = self.IAend
                            bolend = True
                    else: self.level += 1

        finally:
            self.level = levelbackup            
            self.isWaiting = False

VII-2-6. Reflexion(self, tab, profondeur, joueur, A, B, prof, passe, tempssomme, tempsdifference)

Cette fonction a été l'une des plus intéressantes à développer. Tout repose sur l'algorithme alpha bêta. Cet algorithme est une optimisation de l'algorithme MinMax qui évite d'étudier des branches inutiles.

Le fonction de l'algorithme alpha bêta pour le jeu Reversi est le suivant :

 
Sélectionnez
Nous avons besoin de deux constantes: 
    joueurprincipal : joueur ayant initié l'algorithme
    profondeurmax : hauteur de l'arbre des coups
                    
fonction AlphaBeta(plateau, profondeur, joueur, A, B):
    si profondeur == profondeurmax: // on est sur une feuille de l'arbre
        on retourne le score (par rapport au joueur principal)
    si joueur != joueurprincipal:
        si joueur ne peut pas jouer:
            on retourne AlphaBeta(plateau, profondeur, joueurprincipal, A, B)
        beta = B
        pour chaque coup possible de joueur:
            nouveauplateau = plateau + coup
            score = AlphaBeta(nouveauplateau, profondeur+1, joueurprincipal, A, beta)
            beta = min(score, beta)
            if A >= beta: on retourne beta
        on retourne beta
    sinon:
        si joueurprincipal ne peut pas joueur:
            on retourne AlphaBeta(plateau, profondeur, joueur, A, B)
        alpha = A
        pour chaque coup possible de joueurprincipal:
            nouveauplateau = plateau + coup
            score = AlphaBeta(nouveauplateau, profondeur+1, joueur, alpha, B)
            alpha = max(score, alpha)
            if alpha >= B: on retourne alpha
        on retourne alpha

Il est aussi important de vérifier que le jeu ne s'arrête pas (c'est-à-dire qu'il n'y a plus de case libre sur le plateau ou que plus aucun joueur ne peut jouer. C'est l'utilité de la variable passe.

Les variables tempssomme et tempsdifference permettent de savoir où l'ordinateur en est dans sa réflexion.
tempsdifference est égal au temps précédent divisé par le nombre de coups possibles. Et pour chaque coup d'un même niveau, on ajoute tempsdifference à tempssomme.
Par exemple, au départ, on lance la réflexion avec tempssomme = 0 et tempsdifference = 100.
Supposons qu'au premier niveau, il y ait 4 coups possibles. Pour le premier coup à traiter, on lancera la réflexion avec tempssomme = 0 et tempsdifference = 100 / 4 = 25. Pour le deuxième coup, on lancera la réflexion avec tempssomme = 0 + 25 = 25 et tempsdifference = 100 / 4 = 25 et ainsi de suite récursivement.

 
Sélectionnez
    def Reflexion(self, tab, profondeur, joueur, A, B, passe, tempssomme, tempsdifference):
        self.compteur += 1

        if self.endreflexion and (self.BestMove >= 0 or profondeur != 0):
            return -INFINITY + 1

        joueurprinc = self.joueur
        
        if profondeur == self.level:
            if self.IA == IABADSCORE:
                s = Score(tab)
                if s[joueurprinc-1] == 0: return -INFINITY + 1
                elif s[2-joueurprinc] == 0: return INFINITY - 1   
                else: return s[2 - joueurprinc] - s[joueurprinc-1]
            elif self.IA == IABESTSCOREMOVE:
                s = ScoreMove(tab)
                return s[joueurprinc-1] - s[2-joueurprinc]
            elif self.IA == IAWINLOSE:
                s = Score(tab)
                return max(-1, s[joueurprinc-1] - s[2 - joueurprinc])
            else:
                s = Score(tab)
                if s[joueurprinc-1] == 0: return -INFINITY + 1
                elif s[2-joueurprinc] == 0: return INFINITY - 1                   
                return s[joueurprinc-1] - s[2 - joueurprinc]
        else:
            
            lMove = MovePossible(tab, joueur)
            lenlMove = len(lMove)
            
            if joueurprinc != joueur:
                if lenlMove == 0:
                    if passe:
                        if self.IA == IABADSCORE:
                            s = Score(tab)
                            if s[joueurprinc-1] == 0: return -INFINITY + 1
                            elif s[2-joueurprinc] == 0: return INFINITY - 1
                            else: return s[2 - joueurprinc] - s[joueurprinc-1]                        
                        elif self.IA == IABESTSCOREMOVE:
                            s = ScoreMove(tab)
                            return s[joueurprinc-1] - s[2-joueurprinc]
                        elif self.IA == IAWINLOSE:
                            s = Score(tab)
                            return max(-1, s[joueurprinc-1] - s[2 - joueurprinc])
                        else:
                            s = Score(tab)
                            if s[joueurprinc-1] == 0: return -INFINITY + 1
                            elif s[2-joueurprinc] == 0: return INFINITY - 1                            
                            return s[joueurprinc-1] - s[2 - joueurprinc]
                    letab = tab[:]
                    return self.Reflexion(letab, profondeur, joueurprinc, A, B, True, tempssomme, tempsdifference)
                letempsdif = tempsdifference / lenlMove
                letempssom = tempssomme
                beta = B
                for coup in lMove:
                    letab = tab[:]
                    Reverse(letab, coup, joueur)
                    res = self.Reflexion(letab, profondeur+1, joueurprinc, A, beta, False, letempssom, letempsdif)
                    letempssomancien = letempssom
                    letempssom = letempssom+ letempsdif
                    if (int(letempssomancien) != int(letempssom)):
                        if joueurprinc == 1: wx.CallAfter(self.reversi.wReversi.gBlack.SetValue, letempssom)
                        else: wx.CallAfter(self.reversi.wReversi.gWhite.SetValue, letempssom)
                    ##beta = min(beta, res)
                    ##if A >= beta: return beta
                    if res < beta:
                        beta = res
                        if A >= beta: return beta
                return beta
            else:
                troisjou = 3 - joueur
                if lenlMove == 0:
                    if passe:
                        if self.IA == IABADSCORE:
                            s = Score(tab)
                            if s[joueurprinc-1] == 0: return -INFINITY + 1
                            elif s[2-joueurprinc] == 0: return INFINITY - 1                              
                            if s[joueurprinc-1] == 0: return -INFINITY + 1
                            else: return s[2 - joueurprinc] - s[joueurprinc-1]                        
                        elif self.IA == IABESTSCOREMOVE:
                            s = ScoreMove(tab)
                            return s[joueurprinc-1] - s[2-joueurprinc]
                        elif self.IA == IAWINLOSE:
                            s = Score(tab)
                            return max(-1, s[joueurprinc-1] - s[2 - joueurprinc])
                        else:
                            s = Score(tab)
                            if s[joueurprinc-1] == 0: return -INFINITY + 1
                            elif s[2-joueurprinc] == 0: return INFINITY - 1                              
                            return s[joueurprinc-1] - s[2 - joueurprinc]                        
                    letab = tab[:]
                    return self.Reflexion(letab, profondeur, troisjou, A, B, True, tempssomme, tempsdifference)
                letempsdif = tempsdifference / lenlMove
                letempssom = tempssomme
                alpha = A
                for coup in range(lenlMove):
                    letab = tab[:]
                    Reverse(letab, lMove[coup], joueur)
                    ## Pour le dernier coup, on peut se contenter de B = alpha+1
                    if profondeur == 0 and coup == lenlMove-1:
                        res = self.Reflexion(letab, profondeur+1, troisjou, alpha, alpha+1, False, letempssom, letempsdif)
                    else: res = self.Reflexion(letab, profondeur+1, troisjou, alpha, B, False, letempssom, letempsdif)

            
                    letempssomancien = letempssom
                    letempssom = letempssom+letempsdif
                    if (int(letempssomancien) != int(letempssom)):
                        if joueurprinc == 1: wx.CallAfter(self.reversi.wReversi.gBlack.SetValue, letempssom)
                        else: wx.CallAfter(self.reversi.wReversi.gWhite.SetValue, letempssom)
                    if res > alpha:
                        if profondeur == 0:
                            ##if self.BestMove != -1:
                            ##    pass
                            self.BestMove = lMove[coup]
                            self.BestMoveValue = res
                        ##alpha = max(alpha, res)
                        ##if alpha >= B: return alpha
                        alpha = res
                        if alpha >= B: return alpha
                        
                return alpha

précédentsommairesuivant

Copyright © 2008 Guillaume Duriaud. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.