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.
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.
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.
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 :
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).
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).
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 :
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.
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