# ########################################################################### # Projet : Tic Tac Toe (jeu du Morpion) # Version 2 : implémentation d'une IA pour jouer contre l'ordinateur # Programmeurs : Fancely, Daryl et Hans # Mai 2026 # nsi.gecif.net # ########################################################################### # Importation des modules: import turtle import random # ############################################################################# # paramètre du jeu # ############################################################################# class Jeu: """ La classe jeu gère toute la logique du morpion Elle permet de : - détecter les clics du joueur - transformer les coordonnées (x,y) en case de la grille - Vérifie si une case est libre - placer le symbole(X ou O) - alterner entre l'IA et le joueur selon l'envie du joueur - propose plusieurs niveau de jeux( facile, normal et difficile) - dessiner les symboles dans la grille - détecter les lignes gagnantes - afficher un message de félicicitation si le joueur gagne - affiche un message de provocation si l'ordi gagne - detecte les matchs nuls - affiche et met à jour les scores - detecte si on ne respecte pas les règles""" def __init__(self, grille): """Le constructeur regroupe tout les instruments dont la classe grille a besoin: - prend en paramètre la grille du jeu - défini la profondeur de minimax en fonction du niveau souhaité par l'utilisateur - les dimension de la classe grille - Instancie les tortues utiles pour les symboles , message d'erreurs, les scores - La grille logique du jeu - les listes d'encouragement et de provocation - et prédéfeni les symboles utilisé par le jeu(X,0)""" self.grille = grille self.score_X = 0 self.score_O = 0 self.partie_finie = False self.ligne = self.grille.longueur # self ligne et self colone = à la dimension de la grille self.colonne = self.grille.largeur self.niveau = turtle.textinput( "Niveau IA", "Choisissez le niveau :\n\n" "f = facile\n" "n = normal\n" "d = difficile" ) # demande à l'utilisateur le niveau qu'il veut # Profondeur max de Minimax selon la taille de grille # (plus c'est grand, plus c'est lent mais plus l'IA est forte) if self.ligne == 3: if self.niveau == "f": self.profondeur_max = 1 elif self.niveau == "n": self.profondeur_max = 4 else: self.profondeur_max = 9 elif self.ligne == 4: if self.niveau == "f": self.profondeur_max = 1 elif self.niveau =="n": self.profondeur_max = 4 elif self.niveau == "d": self.profondeur_max = 6 elif self.ligne == 5: if self.niveau == "f": self.profondeur_max = 1 elif self.niveau =="n": self.profondeur_max = 3 elif self.niveau == "d": self.profondeur_max = 6 elif self.ligne == 6: if self.niveau == "f": self.profondeur_max = 1 elif self.niveau =="n": self.profondeur_max = 4 elif self.niveau == "d": self.profondeur_max = 5 # Turtle spécial messages self.message = turtle.Turtle() self.message.hideturtle() self.message.penup() # Turtle spécial score self.score = turtle.Turtle() self.score.hideturtle() self.score.penup() self.t = turtle.Turtle() self.t.hideturtle() self.t.speed(0) # Joueur actuel self.joueur = "X" self.qui_commence = turtle.numinput( "Début", "Qui commence ?\n1 = Joueur\n2 = IA", minval=1, maxval=2 ) # bibliothèque de félicitations self.encouragement= [" bien joué !!!"," quel maitrise !!!", " c'est du génie !!!", "c'est presque de la triche", " trop fort !!!", " Jolie !!!!"," Excellent", " Ohh oui !!!!!!"," trop top fort" ] self.provocation= [" C'est moux !!!"," Allez du nerfs !!!", " Bah alors mon vieux !??", "J'suis meme pas à fond, sérieux !", " Alors t'as du mal ? !!!", " Je m'ennuie !!!!"," T'as du mal", " Perdu !!!!!!"," Alors t'as du mal ?" ] random.shuffle(self.encouragement) random.shuffle(self.provocation) # Grille logique du jeu (modulable) #self.etat = [[""]*self.ligne]*self.colonne self.etat = [] for i in range(self.ligne): liste=[] for j in range(self.colonne): liste.append("") self.etat.append(liste) """self.etat = [ ["","",""], ["","",""], ["","",""] ]""" #print(self.etat==[[""]*self.ligne]*self.colonne) if int(self.qui_commence) == 2: self.joueur_IA() # ----------------------------------------------------------------------- # Affichage # ----------------------------------------------------------------------- def case_pleine(self): """ case pleine est une methode de la classe jeu permettant de savoir si une case est déja occupé par un symbole""" self.message.clear() self.message.goto(-140, self.grille.y_depart + self.grille.largeur * self.grille.taille + 40) self.message.color("red") self.message.write("Vous devez jouer dans les cases vides", font=("Arial", 11, "bold")) def afficher_score(self): """ C'est une méthode qui affiche les scores des deux joueurs""" self.score.speed(0) self.score.clear() self.score.goto(-340,230) self.score.color("red") self.score.write("Joueur :%d" %self.score_X, font=("Arial",15,"bold")) self.score.goto(280,230) self.score.color("blue") self.score.write("IA :%d" %self.score_O, font=("Arial",15,"bold")) # ################################################################# # génération autmatisée des conditions de victoires # ################################################################# # TABLEAU gagnant: c'est un tableau avec toute les combinaison gagnantes quelque soit la grille def tableau_gagnant(self): """c'est une méthode qui utile des boucle for imbriqués afin de générer automatiquement toute les conditions gagnente du jeu selon les dimensions""" tableau_gagnant= [] # ligne gagnante for i in range(self.ligne): ligne_gagnante=[] for j in range(self.ligne): ligne_gagnante.append((i,j)) tableau_gagnant.append(ligne_gagnante) # colonnes gagnantes for i in range(self.ligne): colonne_gagnante= [] for j in range(self.ligne): colonne_gagnante.append((j,i)) tableau_gagnant.append(colonne_gagnante) # diagonale gagnante 1 diag_1=[] for i in range(self.ligne): diag_1.append((i,i)) tableau_gagnant.append(diag_1) # diagonale gagnante 2 diag_2=[] for i in range(self.ligne): diag_2.append((i,(self.ligne-1)-i)) tableau_gagnant.append(diag_2) return tableau_gagnant def detections_de_victoires(self): """ méthode qui détecte les victoires et renvoie le symboole gagnant et si il n'y a pas il renvoie None""" # détections de coups gagnants for coups in self.tableau_gagnant(): # pour toute les combinaisant gagnante possible coup_gagnant = [ self.etat[l][c] for l, c in coups] # recherche le coup gagnant dans la partie en cours if coup_gagnant[0] != "" and coup_gagnant.count(coup_gagnant[0]) == self.ligne: # si la ligne n'est pas vide et que le meme motifs se répète 3* joueur_gagnant= coup_gagnant[0] return joueur_gagnant return None def annoncer_gagnant(self,symbole): """ annnoce le gagnant et un message """ if symbole== "X": # choisi un message pour féliciter le joueur félicitations= random.choice(self.encouragement) self.t.penup() self.t.goto(-100,self.grille.y_depart + self.grille.largeur * self.grille.taille + 40) self.t.color("yellow","black") self.t.begin_fill() self.t.pendown() for i in range(2): self.t.pensize(4) self.t.fd(220) self.t.lt(90) self.t.fd(50) self.t.lt(90) self.t.end_fill() self.t.penup() self.t.goto(-74, self.grille.y_depart + self.grille.largeur * self.grille.taille + 55) self.t.color("yellow") self.t.write(félicitations, font=("Arial", 11, "bold")) self.t.penup() ################### # ajoute le score: ################### self.score_X += 1 else: # choisi un message pour féliciter le joueur provocations= random.choice(self.provocation) self.t.penup() self.t.goto(-100,self.grille.y_depart + self.grille.largeur * self.grille.taille + 40) self.t.color("yellow","black") self.t.begin_fill() self.t.pendown() for i in range(2): self.t.pensize(4) self.t.fd(260) self.t.lt(90) self.t.fd(50) self.t.lt(90) self.t.end_fill() self.t.penup() self.t.goto(-74, self.grille.y_depart + self.grille.largeur * self.grille.taille + 55) self.t.color("yellow") self.t.write(provocations, font=("Arial", 11, "bold")) self.t.penup() ################### # ajoute le score: ################### self.score_O += 1 self.afficher_score() def verifie_egalite(self): #_________________________________________________# #______________ Si il y a égalité_________________ # CORRECTION : vérification HORS de la boucle for, et seulement si pas de gagnant gagnant= self.detections_de_victoires() if not gagnant: for ligne in self.etat: if "" in ligne: # dès qu'une case est vide, inutile de continuer return False return True # ----------------------------------------------------------------------- # IA Minimax # ----------------------------------------------------------------------- def minimax(self,est_ia, profondeur=3): """Algorithme Minimax : explore récursivement tous les coups possibles. - est_ia = True : c'est au tour de l'IA (elle veut MAXIMISER son score) - est_ia = False : c'est au tour de X (il veut MINIMISER le score de l'IA) Scores : +1 → l'IA (O) gagne -1 → le joueur (X) gagne 0 → match nul ou profondeur max atteinte """ gagnant= self.detections_de_victoires() if gagnant == "O": return 1 if gagnant == "X": return -1 if self.verifie_egalite(): return 0 if profondeur >= self.profondeur_max: return 0 # On arrete d'explorer ici if est_ia: # L'IA cherche le score le plus élevé possible meilleur = -1000 for lig in range(self.ligne): for col in range(self.colonne): if self.etat[lig][col] == "": self.etat[lig][col] = "O" score = self.minimax(False, profondeur + 1) self.etat[lig][col] = "" if score > meilleur: meilleur = score return meilleur else: # Le joueur cherche le score le plus BAS possible (mauvais pour l'IA) meilleur = 1000 for lig in range(self.ligne): for col in range(self.colonne): if self.etat[lig][col] == "": self.etat[lig][col] = "X" score = self.minimax(True, profondeur + 1) self.etat[lig][col] = "" if score < meilleur: meilleur = score return meilleur def joueur_IA(self): """ c'est la logique du joueur IA""" if self.niveau == "f": self.coup_aleatoire() return """L'IA choisit le meilleur coup possible grace à Minimax.""" meilleur_score = -1000 meilleur_coup = None for lig in range(self.ligne): for col in range(self.colonne): if self.etat[lig][col] == "": # Simule ce coup self.etat[lig][col] = "O" score = self.minimax(False) # au tour de X ensuite self.etat[lig][col] = "" # annule le coup if score > meilleur_score: meilleur_score = score meilleur_coup = (lig, col) # Joue le meilleur coup trouvé if meilleur_coup: lig, col = meilleur_coup self.etat[lig][col] = "O" self.dessiner_symbole(lig,col) def coup_aleatoire(self): """ détecte une case vide au hazard et place un symbole""" coups_possibles = [] for lig in range(self.ligne): for col in range(self.colonne): if self.etat[lig][col] == "": coups_possibles.append((lig,col)) if coups_possibles: lig, col = random.choice(coups_possibles) self.etat[lig][col] = "O" self.dessiner_symbole(lig,col) # ----------------------------------------------------------------------- # Gestion des clics # ----------------------------------------------------------------------- def clic(self, x, y): """ gère les clics du joueur humain sur la fenètre""" self.message.clear() taille = self.grille.taille if self.partie_finie: if self.ligne== 3: self.message.goto(-130,-self.grille.y_depart + self.grille.largeur * self.grille.taille - 660) else: self.message.goto(-130,-self.grille.y_depart + self.grille.largeur * self.grille.taille - 720) self.message.color("red") self.message.write("La partie est finie il faut recommencer", font=("Arial",11,"bold")) return col = int((x - self.grille.x_depart) // taille) # coordonné permettant de cliquer dans les cases de la grille tracée lig = int((y - self.grille.y_depart) // taille) # coordonné permettant de cliquer dans les cases de la grille tracée if not (0 <= col < self.colonne and 0 <= lig < self.ligne): self.message.goto(-140, self.grille.y_depart + self.grille.largeur * self.grille.taille + 40) self.message.color("red") self.message.write("Il faut jouer dans le tableau pas ailleurs !!!", font=("Arial", 11, "bold")) return # Case déja  occupé if self.etat[lig][col] != "": self.case_pleine() return # Remplace une case libre par les symbols # --- Tour du joueur humain (X) --- self.etat[lig][col] = self.joueur self.dessiner_symbole(lig, col) # Vérifier si X vient de gagner gagnant = self.detections_de_victoires() if gagnant: self.partie_finie = True self.annoncer_gagnant(gagnant) turtle.listen() return if self.verifie_egalite(): self.partie_finie = True self.message.goto(-48, self.grille.y_depart + self.grille.largeur * self.grille.taille + 40) self.message.color("orange") self.message.write("Match nul !!!", font=("Arial", 13, "bold")) turtle.listen() return # Change de joueur self.joueur_IA() gagnant = self.detections_de_victoires() if gagnant: self.partie_finie = True self.annoncer_gagnant(gagnant) turtle.listen() return if self.verifie_egalite(): self.partie_finie = True self.message.goto(-48, self.grille.y_depart + self.grille.largeur * self.grille.taille + 40) self.message.color("orange") self.message.write("Match nul !!!", font=("Arial", 13, "bold")) turtle.listen() return # Dessine le symbole X ou O def dessiner_symbole(self, lig, col): """ dessine les symboles""" taille= self.grille.taille marge = taille * 0.3 x = self.grille.x_depart + col * taille + taille / 2 y = self.grille.y_depart + lig * taille + taille / 2 if self.etat[lig][col] == "X": self.t.color("red") self.t.pensize(7) # diagonale 1 self.t.penup() self.t.goto(x - marge, y - marge) self.t.pendown() self.t.goto(x + marge, y + marge) # diagonale 2 self.t.penup() self.t.goto(x - marge, y + marge) self.t.pendown() self.t.goto(x + marge, y - marge) self.t.penup() else: self.t.color("blue") self.t.pensize(7) self.t.penup() self.t.goto(x, y - marge) self.t.pendown() self.t.circle(marge) self.t.penup() # efface les symboles: def efface_symbole(self): """ efface les symboles""" global grille self.t.clear() self.t.penup() self.etat = [] for i in range(self.ligne): liste=[] for j in range(self.colonne): liste.append("") self.etat.append(liste) self.partie_finie = False self.message.clear() # Configuration des composants du Jeu: ###################### # Grille du Jeu ###################### class Grille: " l'objet de la classe Grille possède deux attributs: la longueur et la largeur de celle çi" "en plus de la taille des cases qui change selon la taille des dimension demandées" "permettant ainsi de creer une liste de dimension variable" def __init__ (self,longueur,largeur): """ defini tout les instruments dont la classe grille a besoin""" self.longueur= longueur self.largeur= largeur self.t= turtle.Turtle() self.t.hideturtle() self.t.speed(0) turtle.tracer(0) if self.longueur == 3: self.taille = 100 elif self.longueur == 4: self.taille = 80 elif self.longueur == 5: self.taille = 65 elif self.longueur == 6: self.taille = 55 #self.y_depart = -(self.largeur * self.taille) + 150 largeur_pixels = self.longueur * self.taille hauteur_pixels = self.largeur * self.taille self.x_depart = -largeur_pixels / 2 self.y_depart = -hauteur_pixels / 2 def case(self): "la méthode case de la classe Grille permet de dessiner un carré de 100 pixel" self.t.pensize(6) self.t.color("yellow","lightgreen") self.t.begin_fill() for i in range(4): self.t.fd(self.taille) self.t.lt(90) self.t.end_fill() def trace_colonne(self,x): " la méthode trace_colonne de la classe Grille attend en paramètre:" " le nombre de case (x) qu'elle doit dessiner horizontalement pour faire une colonne" for i in range(x): self.case() # trace une case self.t.fd(self.taille) # se met en possition pour la prochaine case def centrer_la_grille(self): " la méthode centrer_la_grille permet d'initialiser la position du crayon avant " "de dessiner" self.t.penup() # lève le crayon pour ne pas tracer pour rien self.t.goto(self.x_depart,self.y_depart) # positionne le crayon pour absice x et ordonné y self.t.pendown() # descend le crayon def dessiner_grille(self): " la méthode dessiner_grille permet de dessiner la grille en appelant toute" " les méthodes de la classe" self.centrer_la_grille() # centre la grille x_depart , hauteur_depart = self.t.position() # garde en mémoire les coordonnées de la première case for l in range(self.largeur): # selon la valeure de la largeur la boucle superpose les colonne horizontale self.trace_colonne(self.longueur) # colonne horizontale self.t.penup()# lève le crayon self.t.goto(x_depart, hauteur_depart + (l+1)*self.taille) # positionne le crayon juste au dessus de la première case self.t.pendown()# descend le crayon # méthode qui efface la grille: def effacer_grille(self): """ efface la grille""" self.t.clear() jeu.efface_symbole() # ########################## # TITRE # ######################### def afficher_titre(): message.clear() message.penup() message.goto(-130,280) message.pencolor("yellow") message.write("Morpion - cliquez pour jouer", font=("Arial", 15, "bold")) message.penup() message.goto(-128,260) message.pencolor("yellow") message.write("r - Recommencer | c - Changer grille | q - Quitter", font=("Arial", 8, "bold")) # #################################################################### # Fonction du jeu # #################################################################### def recommencer(): """ recommmencer une partie""" jeu.efface_symbole() jeu.qui_commence = turtle.numinput( "Début", "Qui commence ?\n1 = Joueur\n2 = IA", minval=1, maxval=2 ) grille.dessiner_grille() afficher_titre() information() jeu.afficher_score() if int(jeu.qui_commence) == 2: jeu.joueur_IA() turtle.listen() def changer_grille(): """ une fonction complète qui gère le changement des grille et les requetes de l'utilisateur""" global grille global jeu global message turtle.clearscreen() turtle.colormode(255) turtle.bgcolor('darkgreen') turtle.title("Jeu du Tic Tac Toe") message = turtle.Turtle() message.hideturtle() afficher_titre() dimension = turtle.numinput( "Nouvelle grille", "Entrez une taille entre 3 et 6" ) if dimension is None: return dimension = int(dimension) if dimension < 3 or dimension > 6: message.penup() message.goto(-200,0) message.color("orange") message.write( "Erreur : seules les grilles 3x3 à 6x6 sont autorisées", font=("Arial",14,"bold") ) message.goto(-130,-40) message.write( "Appuyez sur A pour entrer une nouvelle grille", font=("Arial",12,"bold") ) turtle.onkey(changer_grille,"a") turtle.listen() return grille = Grille(dimension, dimension) grille.dessiner_grille() jeu = Jeu(grille) jeu.afficher_score() turtle.onscreenclick(jeu.clic) turtle.onkey(recommencer,"r") turtle.onkey(changer_grille,"c") turtle.onkey(changer_grille,"a") turtle.onkey(quitter,"q") afficher_titre() information() turtle.listen() def quitter(): """ fermer la fenetre""" turtle.bye() def information(): message.penup() message.goto(-220,-280) message.write("Quand vous jouez en niveau difficile cliquez et attendez un peu. L'ordi réfléchit",font=("Arial", 10, "bold")) # ############################################ # Paramètre du jeu: # ############################################ message= turtle.Turtle() message.hideturtle() turtle.title("Tic Tac Toe") fenetre = turtle.getscreen() fenetre.getcanvas().winfo_toplevel().iconbitmap("xo.ico") turtle.setup(800,600) turtle.colormode(255) turtle.hideturtle() turtle.bgcolor('Darkgreen') # couleur d'arrière plan afficher_titre() information() # #################################################### # Programme principale: # ################################################### grille = None jeu = None changer_grille() turtle.onkey(recommencer,"r") turtle.onkey(changer_grille,"c") turtle.onkey(changer_grille,"a") turtle.onkey(quitter,"q") turtle.listen() turtle.done()