2021-11-04 17:03:13 +01:00
from math import sqrt
class Minimax :
""" Définition de l ' algorithme minimax. """
def __init__ ( self , terminale : list , humain : str , ordinateur : str ) - > None :
self . terminale = terminale
self . MIN = humain
self . MAX = ordinateur
def evaluation ( self , nbAlign : list ) - > list :
"""
` p ` - > position
` nbAlign_i ( p , J ) ` - > le nombre d ' alignements réalisables, par le joueur `J`, pour lesquels il a déjà `i` symboles posés.
f ( p ) = ( 3. nbAlign_2 ( p , humain ) + nbAlign_1 ( p , humain ) ) - ( 3. nbAlign_2 ( p , ordinateur ) + nbAlign_1 ( p , ordinateur ) )
"""
pass
def terminale ( self , etat : list ) - > bool :
""" Vrai si la partie est terminé. """
return etat in self . terminale
def casesVide ( self , p : list ) - > list : # c'est une liste de coordonées
""" Renvoie la liste des cellules vides depuis un état. """
pass
def main ( self , n : int , p : list , j : str ) - > list : # p state, n depth, j player
""" Évaluation de `p` à une profondeur `n` (joueur j). """
if self . terminale ( p ) or n == 0 : # si p terminale ou n = 0
f = self . evaluation ( p )
else : # = si n > 0
for Pm in self . casesVide ( p ) :
x , y = Pm [ 0 ] , Pm [ 1 ]
p [ x ] [ y ] = j
score = self . main ( p , n - 1 , - j )
p [ x ] [ y ] = 0
score [ 0 ] , score [ 1 ] = x , y
if j == self . MAX :
if score [ 2 ] > f [ 2 ] :
f = score
else :
if score [ 2 ] < f [ 2 ] :
f = score
return f
class Morpion ( Minimax ) :
"""
Implémentation de Minimax dans un Morpion .
Taille par défaut : ` 3 x3 ` .
Joueur A est l ' humain.
Joueur B est l ' ordinateur.
"""
def __init__ ( self , joueurA : str , joueurB : str , taille : tuple = ( 3 , 3 ) ) - > None :
self . plateau : list = self . _definitionPlateau ( taille [ 0 ] , taille [ 1 ] )
if len ( self . plateau ) == 0 : # Gestion erreur du plateau
print ( " Taille du plateau invalide. " )
return
self . nbCasesGagnantes = self . _recuperationNbCasesGagnantes ( )
if len ( joueurA ) != 1 or len ( joueurB ) != 1 : # Gestion erreur nom des joueurs
print ( " Nom des joueurs invalide. " )
return
super ( ) . __init__ ( self . _recuperationEtatsGagnants ( ) , joueurA , joueurB )
self . joueurA = joueurA
self . joueurB = joueurB
def _definitionPlateau ( self , x : int , y : int ) - > list :
"""
Renvoie un plateau en fonction de dimensions ` x ` et ` y `
Valeur = int - > Cellule vide
"""
if x < = 0 or y < = 0 :
return [ ]
plateau = [ ]
numCase = 1
for i in range ( y ) :
plateau . append ( [ ] )
for _ in range ( x ) :
plateau [ i ] . append ( numCase )
numCase + = 1
return plateau
def _recuperationNbCasesGagnantes ( self ) - > int :
"""
Renvoie le nombre de cases à aligné pour pouvoir gagner la partie .
Défini en fonction de la taille du plateau .
"""
mini = len ( self . plateau ) # mini correspond au côté le plus petit
if len ( self . plateau [ 0 ] ) < mini :
mini = len ( self . plateau [ 0 ] )
res = round ( sqrt ( mini ) * 2 ) # fonction qui grandit lentement, basé sur https://www.mathsisfun.com/sets/functions-common.html
if res > mini : # si res trouvé excède la taille du côté, alors c'est autant que la taille du côté
res = mini
return res
def _recuperationEtatsGagnants ( self ) - > list :
""" Renvoie une liste de tous les états qui permettent de terminer la partie. """
self . plateau
pass
def afficher ( self ) - > None :
""" Affiche le plateau. """
print ( " \n " * 5 ) # petit espace par rapport à ce que le terminale de l'utilisateur à afficher dernièrement
espace = f " \n { ' - ' * len ( self . plateau [ 0 ] ) * 5 } - " # taille des lignes qui séparent les valeurs du plateau
for i in range ( len ( self . plateau ) ) :
print ( f " { espace [ 1 : ] if i == 0 else espace } " , end = " \n " ) # on retire le saut de ligne à la première itération
print ( " " , end = " | " )
for j in range ( len ( self . plateau [ i ] ) ) :
n = self . plateau [ i ] [ j ]
if type ( n ) == int :
print ( f " { n : 02d } " , end = " | " ) # on rajoute un "0" au chiffre < 10 (comme en C avec `%02d`)
else :
print ( " %02c " % n , end = " | " ) # espace automatique pour pas décaler l'affichage (comme au dessus mais avec la syntaxe de python2)
print ( espace )
2021-11-04 17:24:09 +01:00
def _caseOccupee ( self , x : int , y : int ) - > bool :
""" Vrai si la case en `x` et `y` est déjà occupée par un joueur. """
return type ( self . plateau [ x ] [ y ] ) == str
2021-11-04 17:03:13 +01:00
def _terminer ( self ) - > bool :
""" Vrai si la partie est terminée. """
fini = False
for i in range ( len ( self . plateau ) ) :
for j in range ( len ( self . plateau [ i ] ) ) :
2021-11-04 17:24:09 +01:00
if self . _caseOccupee ( i , j ) : # si case occupé par un joueur
2021-11-04 17:03:13 +01:00
rechercheLigne = 0
rechercheColonne = 0
rechercheDiagonaleVersBas = 0
rechercheDiagonaleVersHaut = 0
joueur = self . plateau [ i ] [ j ]
while ( rechercheLigne != - 1 ) or ( rechercheColonne != - 1 ) or ( rechercheDiagonaleVersHaut != - 1 ) or ( rechercheDiagonaleVersBas != - 1 ) :
# -- recherche en ligne --
if len ( self . plateau [ i ] ) - j - rechercheLigne > = self . nbCasesGagnantes - rechercheLigne : # s'il y a techniquement assez de cases devant pour gagner
if self . plateau [ i ] [ j + rechercheLigne ] == joueur :
rechercheLigne + = 1
else : # si l'élément ne correspond pas au joueur c'est que il n'a pas gagné depuis cette case avec cette direction
rechercheLigne = - 1
else : # sinon c'est que ça sert de continuer pour cette ligne
rechercheLigne = - 1
if self . nbCasesGagnantes == rechercheLigne : # si on a trouvé autant de cases à la suite du même joueur qu'il en faut pour gagner
rechercheLigne = - 1
fini = True
# -- recherche colonne --
if len ( self . plateau ) - i - rechercheColonne > = self . nbCasesGagnantes - rechercheColonne : # s'il y a techniquement assez de cases devant pour gagner
if self . plateau [ i + rechercheColonne ] [ j ] == joueur :
rechercheColonne + = 1
else : # si l'élément ne correspond pas au joueur c'est que il n'a pas gagné depuis cette case avec cette direction
rechercheColonne = - 1
else : # sinon c'est que ça sert de continuer pour cette ligne
rechercheColonne = - 1
if self . nbCasesGagnantes == rechercheColonne : # si on a trouvé autant de cases à la suite du même joueur qu'il en faut pour gagner
rechercheColonne = - 1
fini = True
# -- recherche diagonale vers le bas --
if ( len ( self . plateau ) - i - rechercheDiagonaleVersBas > = self . nbCasesGagnantes - rechercheDiagonaleVersBas ) and ( len ( self . plateau [ i ] ) - j - rechercheDiagonaleVersBas > = self . nbCasesGagnantes - rechercheDiagonaleVersBas ) : # s'il y a techniquement assez de cases devant pour gagner
if self . plateau [ i + rechercheDiagonaleVersBas ] [ j + rechercheDiagonaleVersBas ] == joueur :
rechercheDiagonaleVersBas + = 1
else : # si l'élément ne correspond pas au joueur c'est que il n'a pas gagné depuis cette case avec cette direction
rechercheDiagonaleVersBas = - 1
else : # sinon c'est que ça sert de continuer pour cette ligne
rechercheDiagonaleVersBas = - 1
if self . nbCasesGagnantes == rechercheDiagonaleVersBas : # si on a trouvé autant de cases à la suite du même joueur qu'il en faut pour gagner
rechercheDiagonaleVersBas = - 1
fini = True
# -- recherche diagonale vers le haut --
if ( len ( self . plateau ) > = self . nbCasesGagnantes ) and ( len ( self . plateau [ i ] ) - j - rechercheDiagonaleVersHaut > = self . nbCasesGagnantes - rechercheDiagonaleVersHaut ) : # s'il y a techniquement assez de cases devant pour gagner
if self . plateau [ i - rechercheDiagonaleVersHaut ] [ j + rechercheDiagonaleVersHaut ] == joueur :
rechercheDiagonaleVersHaut + = 1
else : # si l'élément ne correspond pas au joueur c'est que il n'a pas gagné depuis cette case avec cette direction
rechercheDiagonaleVersHaut = - 1
else : # sinon c'est que ça sert de continuer pour cette ligne
rechercheDiagonaleVersHaut = - 1
if self . nbCasesGagnantes == rechercheDiagonaleVersHaut : # si on a trouvé autant de cases à la suite du même joueur qu'il en faut pour gagner
rechercheDiagonaleVersHaut = - 1
fini = True
if fini : # si partie finie ça ne sert a rien de continuer, donc on quitte la boucle
break
if fini : # meme chose, on quitte la boucle si on a fini
break
return fini
2021-11-04 17:24:09 +01:00
def _coordonneesCaseDepuisNumero ( self , numero : int ) - > tuple [ int , int ] :
""" Renvoie les coordonnées d ' une case. """
return ( ( numero - 1 ) / / len ( self . plateau [ 0 ] ) , ( numero - 1 ) % len ( self . plateau [ 0 ] ) )
def _placementPiece ( self , joueur : str , n : int ) - > None :
""" Place la pièce d ' un joueur dans le plateau. """
x , y = self . _coordonneesCaseDepuisNumero ( n )
self . plateau [ x ] [ y ] = joueur
2021-11-04 17:03:13 +01:00
def _demandeCase ( self ) - > int :
""" Demande au joueur sur quelle case il veut poser sa pièce. """
prefix = f " ( { self . joueurA } - { self . nbCasesGagnantes } cases successives pour gagner) "
print ( f " { prefix } Entrez le numéro de case que vous voulez jouer : " , end = " " )
reponse = - 1
while int ( reponse ) < 0 :
erreur = " "
reponse = input ( )
if not reponse . isnumeric ( ) :
erreur = " pas un nombre "
elif int ( reponse ) > len ( self . plateau ) * len ( self . plateau [ 0 ] ) :
erreur = " valeur trop grande "
elif int ( reponse ) == 0 : # < 0 pas considéré comme un nombre avec la fonction `isnumeric`
erreur = " valeur trop petite "
2021-11-04 17:24:09 +01:00
else :
x , y = self . _coordonneesCaseDepuisNumero ( int ( reponse ) )
if self . _caseOccupee ( x , y ) :
erreur = " case déjà occupée "
2021-11-04 17:03:13 +01:00
if len ( erreur ) > 0 :
2021-11-04 17:24:09 +01:00
print ( f " { prefix } ❌ Valeur invalide ( { erreur } ), réessayez : " , end = " " )
2021-11-04 17:03:13 +01:00
reponse = - 1
return int ( reponse )
def jouer ( self ) - > None :
""" Lance la partie de Morpion. """
while not self . _terminer ( ) : # tant que la partie n'est pas terminé
self . afficher ( ) # affichage du plateau
reponse = self . _demandeCase ( ) # on demande où le joueur veut posé sa pièce
self . _placementPiece ( self . joueurA , reponse ) # on place la pièce du joueur
self . afficher ( ) # affichage du plateau final
2021-11-04 17:24:09 +01:00
print ( " 🎉 Partie terminé, un joueur a gagné ! " )
2021-11-04 17:03:13 +01:00
if __name__ == ' __main__ ' : # Si on lance directement le fichier et on s'en sert pas comme module
Morpion ( ' X ' , ' O ' , ( 6 , 4 ) ) . jouer ( ) # On lance la partie à l'instanciation du Morpion