add main file
This commit is contained in:
parent
2efa53813f
commit
2049577ad6
1 changed files with 232 additions and 0 deletions
232
minimax.py
Normal file
232
minimax.py
Normal file
|
@ -0,0 +1,232 @@
|
|||
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 : `3x3`.
|
||||
|
||||
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)
|
||||
|
||||
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])):
|
||||
if type(self.plateau[i][j]) == str: # si case occupé par un joueur
|
||||
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
|
||||
|
||||
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"
|
||||
if len(erreur) > 0:
|
||||
print(f"{prefix} Valeur invalide ({erreur}), réessayez : ", end = "")
|
||||
reponse = -1
|
||||
|
||||
return int(reponse)
|
||||
|
||||
def _placementPiece(self, joueur: str, n: int) -> None:
|
||||
"""Place la pièce d'un joueur dans le plateau."""
|
||||
x = (n - 1) // len(self.plateau[0])
|
||||
y = (n - 1) % len(self.plateau[0])
|
||||
self.plateau[x][y] = joueur
|
||||
|
||||
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
|
||||
print("Partie terminé, un joueur a gagné !")
|
||||
|
||||
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
|
Reference in a new issue