Partage
  • Partager sur Facebook
  • Partager sur Twitter

Bouton Nouvelle Partie avec TkInter

    18 juin 2021 à 20:22:11

    Bonjour,

    J'ai "réalisé" une implémentation graphique d'un jeu de morpion et je ne parviens pas à faire fonctionner un bouton jouer à nouveau à l'issue d'une partie.

    J'ai bien vu qu'il y avait (au moins) deux autres discussions à ce sujet sur ce forum mais je ne parviens pas à comprendre les réponses bien que j'ai essayé d'adapter les suggestions sur mon code, je ne parviens pas à les faire tourner.
    Si quelqu'un pouvait m'expliquer plus clairement je lui en serais très reconnaissant.

    Voici mon code :

    from tkinter import *
    from tkinter.messagebox import *
    TAILLE = 4
    
    # D'abord toutes les fonctions vues en cours 
    # pour tester lignes, colonnes et diagonales
    #===================================
    def egal(tab):
       for elmt in tab:
          if elmt != tab[0]:
             return False
       return True
    
    
    def colonne(mat, n):
       colonne = []
       for i in range(len(mat)):
          colonne.append(mat[i][n])
       return colonne
    
    
    def diagonale(mat):
       diags = [[], []]
       for i in range(len(mat)):
          diags[0].append(mat[i][i])
          diags[1].append(mat[i][len(mat)-i-1])
       return diags
    
    
    def egalLigne(mat, n):
       return egal(mat[n])
    
    
    def egalColonne(mat, n):
       return egal(colonne(mat, n))
    #====================================
    
    
    def initGrille():
       return [[-1 for i in range(TAILLE)] for j in range(TAILLE)]
    
    
    def afficheCoup(x1,y1):
        if etatsJeu[0] == 1:
           Canevas.create_oval(x1-30,y1-30,x1+30,y1+30, fill ="red")
        else:
           Canevas.create_line(x1-30,y1-30,x1+30,y1+30, width = 6)
           Canevas.create_line(x1+30,y1-30,x1-30,y1+30, width = 6)    
    
    
    def aGagne(lig, col):
         return (egalColonne(grille, col) or egalLigne(grille, lig) or
                    ((lig == col) and egal(diagonale(grille)[0])) or 
                    ((lig == TAILLE - col - 1) and egal(diagonale(grille)[1])))
       
    
    def clickMouse(event):
        # recupere la position du pointeur de la souris
        lig, col  = event.x // 100, event.y // 100
        if grille[lig][col] != -1:
             showinfo(title='Erreur',message='Case déjà occupée !')
        else:
          grille[lig][col], etatsJeu[1] = etatsJeu[0], etatsJeu[1] - 1
          afficheCoup(100 * lig + 50, 100 * col + 50)
          if aGagne(lig, col):
             showinfo(title='Bravo !!! ', message=f' Joueur {etatsJeu[0]} a gagné !')
             Canevas.unbind("<Button-1>")
             
             # Là je ne sais vraiment pas comment faire !! ??
             
             # Button(Mafenetre, text ="Une autre partie ?", command = replay).pack(side=RIGHT, padx=5,pady=5)
             
             # ===============================================================
             
          elif etatsJeu[1] == 0:
             showinfo(title='Oups !!! ', message=f' Match Nul  !')
             Canevas.unbind("<Button-1>")
          else:
             etatsJeu[0] = 1 + etatsJeu[0] % 2
    
    
       
       
    # Programme Principal
    # Certaines données du jeu sont stockées dans une liste pour éviter les globales
    # etatsJeu = [numéro joueur, nbre coups restants]
    etatsJeu = [1, TAILLE * TAILLE]  
    grille = initGrille()
    
    # Création de la fenêtre principale
    Mafenetre = Tk()
    Mafenetre.title("Morpion")
    
    # Création d'un widget Canvas
    Largeur = TAILLE * 100
    Hauteur = TAILLE * 100
    Canevas = Canvas(Mafenetre, width = Largeur, height =Hauteur, bg ="white")
    
    # La méthode bind() permet de lier un événement avec une fonction :
    # un clic gauche sur la zone graphique provoquera l'appel de la fonction utilisateur clickMouse()
    Canevas.bind("<Button-1>", clickMouse)
    Canevas.pack(padx =5, pady =5)
    
    # Ici on crée les lignes et les colonnes qui délimitent les cases
    for i in range(1, TAILLE):
       Canevas.create_line(0,100 * i ,TAILLE * 100, 100 * i, fill="purple", width=4)
       Canevas.create_line(100 * i, TAILLE * 100, 100 * i, 0,fill="purple", width=4)
    
    # Création d'un widget Button (bouton Quitter)
    Button(Mafenetre, text ="Quitter", command = Mafenetre.destroy).pack(side=LEFT,padx=5,pady=5)
    
    # Boucle Principale
    Mafenetre.mainloop()


    -
    Edité par Tchae 18 juin 2021 à 20:29:36

    • Partager sur Facebook
    • Partager sur Twitter
      18 juin 2021 à 20:38:12

      replay doit remettre le jeu dans son état initial. C'est quoi l'état du jeu? Un ensemble de variable (globales?) associées à des tableaux et des widgets à afficher. Du coup ce n'est pas un problème de programmation (dites moi quel tableau réinitialiser je sais le faire...) mais de conception (quels sont les objets à réinitialiser et comment les atteindre?) qui va forcer à remettre à plat ce que vous avez déjà fait (un peu...)
      • Partager sur Facebook
      • Partager sur Twitter
        18 juin 2021 à 20:56:33

        Je ne comprends pas votre réponse,

        Quels renseignements dois je vous fournir ?
        L'état du jeu est stocké dans grille et dans etatsJeu (deux listes).
        Ce que je souhaite c'est que le programme recommence au début quand on clique sur le bouton nouvelle partie.
        Je veux bien remettre à plat (même complètement), mais je ne sais pas du tout quoi faire.
        • Partager sur Facebook
        • Partager sur Twitter
          18 juin 2021 à 22:25:38

          Tchae a écrit:

          Ce que je souhaite c'est que le programme recommence au début quand on clique sur le bouton nouvelle partie.

          J'avais compris... Je vous dis juste que ce n'est pas magique et que çà ne se fait pas en quelques ligne de code mais en retravaillant votre programme. Et, je ne vais pas faire ce boulot pour vous.

          • Partager sur Facebook
          • Partager sur Twitter
            18 juin 2021 à 23:02:11

            Tchae a écrit:

            Je ne comprends pas votre réponse,

            Quels renseignements dois je vous fournir ?
            L'état du jeu est stocké dans grille et dans etatsJeu (deux listes).
            Ce que je souhaite c'est que le programme recommence au début quand on clique sur le bouton nouvelle partie.
            Je veux bien remettre à plat (même complètement), mais je ne sais pas du tout quoi faire.


            Oui, cette étape est assez déconcertante quand on la réalise pour la première fois. Déjà, créez un bouton qui appellera la fonction de réinitialisation, puis créez votre fonction init et déplacez-y ce qui est dans votre programme principal ce qui est utile (pas tout) pour que vous repartiez de zéro, évidemment ça va poser quelques problèmes, d'accessibilité à certaines variables en particulier. 

            Le code aura plus ou moins la tête suivante :

            # Votre code de début de fichier
             
            def init():
                pass
            
                
            # Votre code
            
            btn=Button(Mafenetre, text="Nouvelle\npartie", command=init)
            btn.pack(padx=20, pady=20)
            # Votre code
            Mafenetre.mainloop() 
            

            Pour faire au plus simple, vous effacez tout le canvas avant chaque nouvelle partie et vous recréez ce qui est utile. Vous pouvez même envisager de commencer la toute première partie du jeu en appelant cette fonction init.

            Sur mon site, il y a des exemples pour le jeu memory, le taquin, le pendu et d'autres, je vous laisse chercher.

            • Partager sur Facebook
            • Partager sur Twitter
              18 juin 2021 à 23:03:15

              Merci pour votre réponse @ Pascal Ortiz.

              J'ai déjà essayé cela en m'inspirant des autres réponses aux discussions similaires.

              Mais effectivement j'ai des problèmes d'accès aux variables, j'obtiens des messages d'erreur du type la variable locale grille est évaluée avant d'être affectée.

              Je réessaye...


              ......


              En m'inspirant de vos conseils et de l'exemple du Memory sur votre site, j'ai écris le code ci-dessous, ça fonctionne (ou presque) mais je suis surpris de devoir déclarer des variables globales (on m'a toujours dit d'éviter au maximum).
              Un problème demeure cependant : la fenêtre n'est pas détruite entre deux parties et je ne parviens pas à trouver où placer le Mafenetre.destroy()

              def clickMouse(event):
                  # recupere la position du pointeur de la souris
                  lig, col  = event.x // 100, event.y // 100
                  if grille[lig][col] != -1:
                       showinfo(title='Erreur',message='Case déjà occupée !')
                  else:
                    grille[lig][col], etatsJeu[1] = etatsJeu[0], etatsJeu[1] - 1
                    afficheCoup(100 * lig + 50, 100 * col + 50)
                    if aGagne(lig, col):
                       showinfo(title='Bravo !!! ', message=f' Joueur {etatsJeu[0]} a gagné !')
                       Canevas.unbind("<Button-1>")
              
                       #=============================================================
                       
                       Button(Mafenetre, text ="Une autre partie ?", command = init).pack(side=RIGHT, padx=5,pady=5)
                       
                       # =============================================================
                       
                    elif etatsJeu[1] == 0:
                       showinfo(title='Oups !!! ', message=f' Match Nul  !')
                       Canevas.unbind("<Button-1>")
                    else:
                       etatsJeu[0] = 1 + etatsJeu[0] % 2
              
              
                 
                 
              def init():
                 # Certaines données du jeu sont stockées dans une liste pour éviter les globales
                 # etatsJeu = [numéro joueur, nbre coups restants]
                 global grille, etatsJeu, Canevas, Mafenetre
                 etatsJeu = [1, TAILLE * TAILLE]  
                 grille = initGrille()
              
                 # Création de la fenêtre principale
                 Mafenetre = Tk()
                 Mafenetre.title("Morpion")
              
                 # Création d'un widget Canvas
                 Largeur = TAILLE * 100
                 Hauteur = TAILLE * 100
                 Canevas = Canvas(Mafenetre, width = Largeur, height =Hauteur, bg ="white")
              
                 # La méthode bind() permet de lier un événement avec une fonction :
                 # un clic gauche sur la zone graphique provoquera l'appel de la fonction utilisateur clickMouse()
                 Canevas.bind("<Button-1>", clickMouse)
                 Canevas.pack(padx =5, pady =5)
              
              # Ici on crée les lignes et les colonnes qui délimitent les cases
                 for i in range(1, TAILLE):
                    Canevas.create_line(0,100 * i ,TAILLE * 100, 100 * i, fill="purple", width=4)
                    Canevas.create_line(100 * i, TAILLE * 100, 100 * i, 0,fill="purple", width=4)
              
              # Création d'un widget Button (bouton Quitter)
                 Button(Mafenetre, text ="Quitter", command = Mafenetre.destroy).pack(side=LEFT,padx=5,pady=5)
                 
              
              # Programme Principal
              init()
              Mafenetre.mainloop()
              



              -
              Edité par Tchae 18 juin 2021 à 23:35:18

              • Partager sur Facebook
              • Partager sur Twitter
                19 juin 2021 à 8:19:42

                Tchae a écrit:


                Je réessaye...

                def clickMouse(event):
                    # recupere la position du pointeur de la souris
                    lig, col  = event.x // 100, event.y // 100
                    if grille[lig][col] != -1:
                         showinfo(title='Erreur',message='Case déjà occupée !')
                    else:
                      grille[lig][col], etatsJeu[1] = etatsJeu[0], etatsJeu[1] - 1
                      afficheCoup(100 * lig + 50, 100 * col + 50)
                      if aGagne(lig, col):
                         showinfo(title='Bravo !!! ', message=f' Joueur {etatsJeu[0]} a gagné !')
                         Canevas.unbind("<Button-1>")
                
                         #=============================================================
                         
                         Button(Mafenetre, text ="Une autre partie ?", command = init).pack(side=RIGHT, padx=5,pady=5)
                         
                         # =============================================================
                         
                      elif etatsJeu[1] == 0:
                         showinfo(title='Oups !!! ', message=f' Match Nul  !')
                         Canevas.unbind("<Button-1>")
                      else:
                         etatsJeu[0] = 1 + etatsJeu[0] % 2
                
                
                   
                   
                def init():
                   # Certaines données du jeu sont stockées dans une liste pour éviter les globales
                   # etatsJeu = [numéro joueur, nbre coups restants]
                   global grille, etatsJeu, Canevas, Mafenetre
                   etatsJeu = [1, TAILLE * TAILLE]  
                   grille = initGrille()
                
                   # Création de la fenêtre principale
                   Mafenetre = Tk()
                   Mafenetre.title("Morpion")
                
                   # Création d'un widget Canvas
                   Largeur = TAILLE * 100
                   Hauteur = TAILLE * 100
                   Canevas = Canvas(Mafenetre, width = Largeur, height =Hauteur, bg ="white")
                
                   # La méthode bind() permet de lier un événement avec une fonction :
                   # un clic gauche sur la zone graphique provoquera l'appel de la fonction utilisateur clickMouse()
                   Canevas.bind("<Button-1>", clickMouse)
                   Canevas.pack(padx =5, pady =5)
                
                # Ici on crée les lignes et les colonnes qui délimitent les cases
                   for i in range(1, TAILLE):
                      Canevas.create_line(0,100 * i ,TAILLE * 100, 100 * i, fill="purple", width=4)
                      Canevas.create_line(100 * i, TAILLE * 100, 100 * i, 0,fill="purple", width=4)
                
                # Création d'un widget Button (bouton Quitter)
                   Button(Mafenetre, text ="Quitter", command = Mafenetre.destroy).pack(side=LEFT,padx=5,pady=5)
                   
                
                # Programme Principal
                init()
                Mafenetre.mainloop()
                



                Je ne vois pas en quoi le code posté répond à votre problème qui était de pouvoir rejouer une partie (en cliquant sur un un bouton) et ce qui suppose une réinitialisation des états.

                Concernant les variables globales, votre code initial en était rempli. Si ce qui vous ennuie est l'usage du déclarateur global, il n'est pas toujours simple en Tkinter de s'en priver car certaines fonctions callback ont des signatures qui vous sont imposées. Faites déjà un code qui marche avec global et ensuite, vous les retirerez mais c'est un autre exercice.

                • Partager sur Facebook
                • Partager sur Twitter
                  19 juin 2021 à 10:16:59

                  Salut,

                  Je rejoins toutes les réponses que l'on t'a données. Je te propose un code qui réalise ce que tu cherches à faire.

                  Attention... Je précise avant que certains membres (que je ne nomme pas :p) ne me collent sur un bucher et y mettent le feu, que c'est loin d'être optimum et surement pas la meilleure manière de procéder, mais ça peut te permettre de comprendre afin de déboguer et d'améliorer ton script.

                  from tkinter import *
                  from tkinter.messagebox import *
                   
                  # D'abord toutes les fonctions vues en cours
                  # pour tester lignes, colonnes et diagonales
                  #===================================
                  def egal(tab):
                     for elmt in tab:
                        if elmt != tab[0]:
                           return False
                     return True
                   
                   
                  def colonne(mat, n):
                     colonne = []
                     for i in range(len(mat)):
                        colonne.append(mat[i][n])
                     return colonne
                   
                   
                  def diagonale(mat):
                     diags = [[], []]
                     for i in range(len(mat)):
                        diags[0].append(mat[i][i])
                        diags[1].append(mat[i][len(mat)-i-1])
                     return diags
                   
                   
                  def egalLigne(mat, n):
                     return egal(mat[n])
                   
                   
                  def egalColonne(mat, n):
                     return egal(colonne(mat, n))
                  #====================================
                   
                   
                  def initGrille():
                     return [[-1 for i in range(TAILLE)] for j in range(TAILLE)]
                   
                   
                  def afficheCoup(x1,y1):
                      if etatsJeu[0] == 1:
                         Canevas.create_oval(x1-30,y1-30,x1+30,y1+30, fill ="red")
                      else:
                         Canevas.create_line(x1-30,y1-30,x1+30,y1+30, width = 6)
                         Canevas.create_line(x1+30,y1-30,x1-30,y1+30, width = 6)   
                      Canevas.update()
                   
                  def aGagne(lig, col):
                       return (egalColonne(grille, col) or egalLigne(grille, lig) or
                                  ((lig == col) and egal(diagonale(grille)[0])) or
                                  ((lig == TAILLE - col - 1) and egal(diagonale(grille)[1])))
                      
                   
                  def clickMouse(event):
                      # recupere la position du pointeur de la souris
                      lig, col  = event.x // 100, event.y // 100
                      if grille[lig][col] != -1:
                           showinfo(title='Erreur',message='Case déjà occupée !')
                      else:
                        grille[lig][col], etatsJeu[1] = etatsJeu[0], etatsJeu[1] - 1
                        afficheCoup(100 * lig + 50, 100 * col + 50)
                        if aGagne(lig, col):
                           showinfo(title='Bravo !!! ', message=f' Joueur {etatsJeu[0]} a gagné !')
                           Canevas.unbind("<Button-1>")
                   
                           #=============================================================
                           button_continu.pack(side=RIGHT, padx=5,pady=5)
                           
                            
                           # =============================================================
                            
                        elif etatsJeu[1] == 0:
                           showinfo(title='Oups !!! ', message=f' Match Nul  !')
                           Canevas.unbind("<Button-1>")
                        else:
                           etatsJeu[0] = 1 + etatsJeu[0] % 2
                   
                  def init():
                     # Certaines données du jeu sont stockées dans une liste pour éviter les globales
                     # etatsJeu = [numéro joueur, nbre coups restants]
                     global grille, etatsJeu, Canevas, button_quit, button_continu, TAILLE
                     TAILLE = 4
                  
                     try:
                           Canevas.destroy()
                           button_quit.destroy()
                           button_continu.destroy()
                     except:
                           pass
                  
                     #Mafenetre
                     etatsJeu = [1, TAILLE * TAILLE] 
                     grille = initGrille()
                   
                     # Création d'un widget Canvas
                     Largeur = TAILLE * 100
                     Hauteur = TAILLE * 100
                     Canevas = Canvas(Mafenetre, width = Largeur, height =Hauteur, bg ="white")
                   
                     # La méthode bind() permet de lier un événement avec une fonction :
                     # un clic gauche sur la zone graphique provoquera l'appel de la fonction utilisateur clickMouse()
                     Canevas.bind("<Button-1>", clickMouse)
                     Canevas.pack(padx =5, pady =5)
                   
                  # Ici on crée les lignes et les colonnes qui délimitent les cases
                     for i in range(1, TAILLE):
                        Canevas.create_line(0,100 * i ,TAILLE * 100, 100 * i, fill="purple", width=4)
                        Canevas.create_line(100 * i, TAILLE * 100, 100 * i, 0,fill="purple", width=4)
                   
                  # Création d'un widget Button (bouton Quitter)
                     button_quit = Button(Mafenetre, text ="Quitter", command = Mafenetre.destroy)
                     button_quit.pack(side=LEFT,padx=5,pady=5)
                     button_continu = Button(Mafenetre, text ="Une autre partie ?", command = init)
                     
                   
                  # Programme Principal
                  
                  # Création de la fenêtre principale
                  Mafenetre = Tk()
                  Mafenetre.title("Morpion")
                  init()
                  Mafenetre.mainloop()



                  -
                  Edité par Diablo76 19 juin 2021 à 10:18:19

                  • Partager sur Facebook
                  • Partager sur Twitter
                    19 juin 2021 à 10:39:37

                    @ PascalOrtiz a écrit:

                    Je ne vois pas en quoi le code posté répond à votre problème qui était de pouvoir rejouer une partie (en cliquant sur un un bouton) et ce qui suppose une réinitialisation des états.

                    ----------------------------------------------------------------------------------------------------------------------------------

                    Eh bien, le dernier code posté recommence une nouvelle partie quand on clique sur le bouton nouvelle partie, c'est ce que je voulais, je ne comprends pas cette réponse. Le bouton en question est à la ligne 15. les états sont initialisés dans la fonction init() (d'où son nom)

                    Ce qui me gène c'est l'utilisation de global effectivement (mais je crois comprendre entre les lignes que mon utilisation des tableaux (listes) ne semble pas vous satisfaire non plus) et le fait que je ne parviens pas à fermer la fenêtre précédente quand une nouvelle partie commence (ou placer le Mafenetre.destroy() dans ce code ?).

                    S'il y a moyen de faire disparaître les globales je suis preneur de méthodes.

                    @Diablo76

                    Merci, le try - except est probablement ce que je cherchais pour effacer mon ancienne fenêtre, effectivement !


                    ... --> Oui ça fonctionne, merci !



                    @tous

                    Vu le ton de vos messages et certaines allusions dans vos réponses j'ai bien conscience que tout cela n'est pas propre et que ce bidouillage n'est pas recommandé (ce n'est pas étonnant, je n'ai pas étudié ces compétences) . Pourriez vous me diriger vers des ressources (livres, web...) qui me permettraient de progresser ?
                    Ce morpion n'a pas d'intérêt en soi, je ne m'y attache que pour apprendre les bonnes pratiques de programmation que je vais probablement enseigner à terme.

                    Merci d'avance.

                    -
                    Edité par Tchae 19 juin 2021 à 11:14:20

                    • Partager sur Facebook
                    • Partager sur Twitter
                      19 juin 2021 à 10:56:41

                      Si tu recherches les bonnes pratiques alors le try-except-pass n'est pas ce que tu veux. Voir du côté des méthodes winfo_children pour supprimer l'ensemble des enfants liés à ta fenêtre parente.

                      Aussi, si tu souhaites apprendre les bonnes pratiques, tu pourrais commencer par un jeu complet du Morpion en console, surtout si en soit tu n'as pas été plus loin que le cours sur les fonctions.

                      • Partager sur Facebook
                      • Partager sur Twitter

                      Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                      La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                        19 juin 2021 à 11:03:16

                        Tchae a écrit:

                        @ PascalOrtiz a écrit:


                        ----------------------------------------------------------------------------------------------------------------------------------

                        Eh bien, le dernier code posté recommence une nouvelle partie quand on clique sur le bouton nouvelle partie, c'est ce que je voulais, je ne comprends pas cette réponse. Le bouton en question est à la ligne 15. les états sont initialisés dans la fonction init() (d'où son nom)


                        Effectivement, je n'avais pas repris votre version de clickMouse car je ne m'imaginais que vous alliez y créer un bouton (ce n'est pas sa place selon moi).

                        Diablo76 : tu n'aurais pas oublié d'appliquer la méthode pack pour le nouveau bouton ? Sinon, je trouve que tu mets beaucoup trop de choses dans cette fonction init pour ce qu'elle est censée faire.

                        • Partager sur Facebook
                        • Partager sur Twitter
                          19 juin 2021 à 11:06:01

                          PascalOrtiz a écrit:

                          Diablo76 : tu n'aurais pas oublié d'appliquer la méthode pack pour le nouveau bouton ? Sinon, je trouve que tu mets beaucoup trop de choses dans cette fonction init pour ce qu'elle est censée faire.


                          Non il est dans la fonction clickMouse(), ensuite je n'ai rien optimisé, juste le rajout try

                          -
                          Edité par Diablo76 19 juin 2021 à 11:08:01

                          • Partager sur Facebook
                          • Partager sur Twitter
                            19 juin 2021 à 11:28:36

                            fred1599 a écrit:
                            Si tu recherches les bonnes pratiques alors le try-except-pass n'est pas ce que tu veux. Voir du côté des méthodes winfo_children pour supprimer l'ensemble des enfants liés à ta fenêtre parente.Aussi, si tu souhaites apprendre les bonnes pratiques, tu pourrais commencer par un jeu complet du Morpion en console, surtout si en soit tu n'as pas été plus loin que le cours sur les fonctions.

                            Un jeu complet du morpion en console ?
                            C'est à dire ?

                            @PascalOrtiz
                            Je ne souhaite pas que l'on puisse recommencer une partie avant que la partie courante soit terminée.
                            C'est pour cela que je créé le bouton dans clickMouse() quand il y a un vainqueur ou un match nul.



                            -
                            Edité par Tchae 19 juin 2021 à 11:31:25

                            • Partager sur Facebook
                            • Partager sur Twitter
                              19 juin 2021 à 11:32:02

                              C'est à dire affichage en console, mais sur le principe, le plus important est le moteur de jeu, ce qui lorsque je vois ton code, n'est pas acquis car conceptuellement, l'analyse n'est pas suffisante. Mieux vaut travailler sur l'analyse d'une solution à un problème, c'est le but ultime du développeur et cela peu importe le langage.
                              • Partager sur Facebook
                              • Partager sur Twitter

                              Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                              La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                                19 juin 2021 à 11:43:23

                                Diablo76 a écrit:


                                Non il est dans la fonction clickMouse(), ensuite je n'ai rien optimisé, juste le rajout try

                                OK. En fait je vois pas l'intérêt de faire un morpion où deux joueurs jouent avec la même souris et sur le même écran. Soit on fait un morpion en réseau soit un seul joueur joue contre la machine (et, éventuellement le jeu contient une petite IA).
                                • Partager sur Facebook
                                • Partager sur Twitter
                                  19 juin 2021 à 12:04:10

                                  fred1599 a écrit:
                                  C'est à dire affichage en console, mais sur le principe, le plus important est le moteur de jeu, ce qui lorsque je vois ton code, n'est pas acquis car conceptuellement, l'analyse n'est pas suffisante. Mieux vaut travailler sur l'analyse d'une solution à un problème, c'est le but ultime du développeur et cela peu importe le langage.

                                  D'accord je comprends l'idée de la console, mais je pensais que ce stade était acquis puisque c'est la partie qu'on a traité en cours et je voulais développer la partie graphique de mon côté (non demandée par l''enseignant).
                                  Pourriez vous me dire ce qui pêche dans l'analyse conceptuelle de mon morpion ?

                                  Merci d'avance.



                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    19 juin 2021 à 12:25:38

                                    Les termes donnés donnent une indication,

                                    • Utilisation du franglais
                                    • La fonction représente une action, ça doit donc être un verbe
                                    • La fonction egal devrait plutôt utiliser egalLigne et egalColonne (tu as fais le contraire ou les fonctions sont mal nommées)
                                    • Dans initGrille devrait avoir des paramètres indiquant les critères d'initialisation
                                    • Des nombres magiques dont on ne connaît pas pourquoi ils sont là comme 100, 30, 0 etc...

                                     Bref il y a beaucoup de choses à faire avant de s'attaquer à Tkinter, à savoir qu'on aime bien apprendre la POO grâce à ce Framework et donc commencer par la notion de classe.

                                    • Partager sur Facebook
                                    • Partager sur Twitter

                                    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                                    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                                      19 juin 2021 à 14:49:20

                                      J'ai abstrait les fonctions matricielles dans un module avec des Docstring en espérant que ce sera plus clair.
                                      Je travaille sur les "nombres magiques"...
                                      Mon seul paramètre pour init() est TAILLE qui est globale/CONSTANTE, est ce vraiment pertinent de le passer en argument ?

                                      #======================================================
                                      #              module   matUtil
                                      # Liste de  toutes les fonctions vues en cours pour 
                                      # tester lignes, colonnes et diagonales d'une matrice
                                      #======================================================
                                      
                                      def egalListe(tab):
                                          """ teste si tous les élements d'une liste plate sont égaux
                                           arg : une liste plate (non vide); retour : un booléen
                                           [2, 2, 2,] --> True, [2, 1, 2,] --> False"""
                                          for elmt in tab:
                                              if elmt != tab[0]:
                                                  return False
                                          return True
                                      
                                      
                                      def extraitColonne(mat, n):
                                          """ extrait une colonne d'une matrice
                                      arg : une matrice n x p (non vide), num de colonne(int) < p; retour : une liste"""
                                          colonne = []
                                          for i in range(len(mat)):
                                              colonne.append(mat[i][n])
                                          return colonne
                                      
                                      
                                      def extraitDiagonales(mat):
                                          """ extrait les deux diagonales d'une matrice carrée
                                      arg : une matrice carrée; retour : une liste de deux listes
                                      [première diagonale, deuxième diagonale]
                                      [[1, 2], [3, 4] --> [[1, 4], [2, 3]]"""
                                          diags = [[], []]
                                          for i in range(len(mat)):
                                              diags[0].append(mat[i][i])
                                              diags[1].append(mat[i][-i-1])
                                          return diags
                                      
                                      
                                      def egalLigne(mat, n):
                                          """ teste si tous les éléments d'une ligne d'une matrice  sont égaux
                                      arg : une matrice n x p (non vide), num de ligne(int) < n; retour : un booléen """
                                          return egalListe(mat[n])
                                      
                                      
                                      def egalColonne(mat, n):
                                          """ teste si tous les élements d'une colone d'une matrice sont égaux
                                      arg : une matrice n x p (non vide), num de colonne(int) < p; retour : un booléen """
                                          return egalListe(extraitColonne(mat, n))
                                      
                                      
                                      if __name__ == "__main__":
                                          print(extraitDiagonales([[1, 2], [3, 4]]))


                                      Mise à jour dans le même message.

                                      -
                                      Edité par Tchae 20 juin 2021 à 9:46:51

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        19 juin 2021 à 15:00:32

                                        Je n'ai toujours pas de verbe concernant les fonctions.

                                        Dans tes Docstrings, met des exemples avec ce qui se passe en entrée et la sortie attendue.

                                        • Partager sur Facebook
                                        • Partager sur Twitter

                                        Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                                        La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                                          19 juin 2021 à 17:09:35

                                          Les fonctions (du module matUTIL) ont été mises à jour dans le message précédent.

                                          Voilà pour les "nombres Magiques" :

                                          from tkinter import *
                                          from tkinter.messagebox import *
                                          # on importe les fonctions utilitaires vues en cours sur les matrices
                                          from matUtil import*
                                          TAILLE = 5
                                          SIZE_SYMB = 30
                                          SIZE_CELL = 100
                                          EP_TRAIT = 6
                                          PAD = 5
                                          
                                          
                                          
                                          def initGrille():
                                             return [[-1 for i in range(TAILLE)] for j in range(TAILLE)]
                                          
                                          
                                          def afficheCoup(x1,y1):
                                              if etatsJeu[0] == 1:
                                                 Canevas.create_oval(x1-SIZE_SYMB,y1-SIZE_SYMB,x1+SIZE_SYMB,y1+SIZE_SYMB, fill ="red")
                                              else:
                                                 Canevas.create_line(x1-SIZE_SYMB,y1-SIZE_SYMB,x1+SIZE_SYMB,y1+SIZE_SYMB, width = EP_TRAIT)
                                                 Canevas.create_line(x1+SIZE_SYMB,y1-SIZE_SYMB,x1-SIZE_SYMB,y1+SIZE_SYMB, width = EP_TRAIT)    
                                          
                                          
                                          def aGagne(lig, col):
                                               return (egalColonne(grille, col) or egalLigne(grille, lig) or
                                                          ((lig == col) and egalListe(extraitDiagonales(grille)[0])) or 
                                                          ((lig == TAILLE - col - 1) and egalListe(extraitDiagonales(grille)[1])))
                                             
                                          
                                          def clickMouse(event):
                                              # recupere la position du pointeur de la souris
                                              lig, col  = event.x // SIZE_CELL, event.y // SIZE_CELL
                                              if grille[lig][col] != -1:
                                                   showinfo(title='Erreur',message='Case déjà occupée !')
                                              else:
                                                grille[lig][col], etatsJeu[1] = etatsJeu[0], etatsJeu[1] - 1
                                                afficheCoup(SIZE_CELL * lig + SIZE_CELL // 2, SIZE_CELL * col + SIZE_CELL // 2)
                                                if aGagne(lig, col):
                                                   showinfo(title='Bravo !!! ', message=f' Joueur {etatsJeu[0]} a gagné !')
                                                   Canevas.unbind("<Button-1>")
                                                   Button(Mafenetre, text ="Une autre partie ?", command = init).pack(side=RIGHT, padx=PAD,pady=PAD)
                                                elif etatsJeu[1] == 0:
                                                   showinfo(title='Oups !!! ', message=f' Match Nul  !')
                                                   Canevas.unbind("<Button-1>")
                                                   Button(Mafenetre, text ="Une autre partie ?", command = init).pack(side=RIGHT, padx=PAD,pady=PAD)
                                                else:
                                                   etatsJeu[0] = 1 + etatsJeu[0] % 2
                                             
                                             
                                          def init():
                                             global grille, etatsJeu, Canevas, Mafenetre
                                             etatsJeu = [1, TAILLE * TAILLE]  
                                             grille = initGrille()
                                          
                                             try: Mafenetre.destroy()
                                             except: pass
                                          
                                             # Création de la fenêtre principale
                                             Mafenetre = Tk()
                                             Mafenetre.title("Morpion")
                                          
                                             # Création d'un widget Canvas
                                             Largeur = TAILLE * SIZE_CELL
                                             Hauteur = TAILLE * SIZE_CELL
                                             Canevas = Canvas(Mafenetre, width = Largeur, height =Hauteur, bg ="white")
                                          
                                             # La méthode bind() permet de lier un événement avec une fonction :
                                             # un clic gauche sur la zone graphique provoquera l'appel de la fonction utilisateur clickMouse()
                                             Canevas.bind("<Button-1>", clickMouse)
                                             Canevas.pack(padx =PAD, pady =PAD)
                                          
                                          # Ici on crée les lignes et les colonnes qui délimitent les cases
                                             for i in range(1, TAILLE):
                                                Canevas.create_line(0,SIZE_CELL * i ,TAILLE * SIZE_CELL, SIZE_CELL * i, fill="purple", width=4)
                                                Canevas.create_line(SIZE_CELL * i, TAILLE * SIZE_CELL, SIZE_CELL * i, 0,fill="purple", width=4)
                                          
                                          # Création d'un bouton Quitter
                                             Button(Mafenetre, text ="Quitter", command = Mafenetre.destroy).pack(side=LEFT,padx=PAD,pady=PAD)
                                          
                                             
                                          
                                          # Programme Principal
                                          init()
                                          Mafenetre.mainloop()


                                          Maintenant je suis toujours preneur de méthodes pour se débarrasser des globales.

                                          -
                                          Edité par Tchae 19 juin 2021 à 17:12:42

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            19 juin 2021 à 17:23:59

                                            Alors, ce n'est pas grand-chose, mais j'avais rajouté Canvas.update() dans afficheCoup(), c'est quand même plus logique de dessiner la dernière croix ou rond avant le messageBox()

                                            Pour éviter les globales, @fred1599 te l'a suggéré (c'est les classes et la POO)

                                            -
                                            Edité par Diablo76 19 juin 2021 à 17:27:22

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              19 juin 2021 à 17:29:13

                                              Mauvaises pratiques (suite)

                                              • from module import *
                                              • Les lignes supérieures à 80 caractères
                                              • except pass
                                              • Partager sur Facebook
                                              • Partager sur Twitter

                                              Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                                              La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                                                19 juin 2021 à 17:32:57

                                                Diablo76 a écrit:
                                                Alors, ce n'est pas grand-chose, mais j'avais rajouté Canvas.update() dans afficheCoup(), c'est quand même plus logique de dessiner la dernière croix ou rond avant le messageBox()

                                                Merci, j'ai rajouté, mais du  coup j'ai une nouvelle question :
                                                en l'absence de ce update() à quel moment de mon programme est ce que l'affichage était rafraîchi ?
                                                Car sur mon écran je ne voyais pas de décalage entre le cochage des cases et les messages.

                                                -
                                                Edité par Tchae 19 juin 2021 à 17:50:10

                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  19 juin 2021 à 17:50:02

                                                  Tchae a écrit:

                                                  Merci, j'ai rajouté, mais du  coup j'ai une nouvelle question 0 : en l'absence de ce update() à quel moment de mon programme est ce que l'affichage était rafraîchi ?
                                                  Car sur mon écran je ne voyais pas de décalage entre le cochage des cases et les messages.


                                                  Je dirais, quand tu sors de ta fonction (du coup bloquante à cause de ton messageBox) pour retourner dans ton mainloop

                                                  -
                                                  Edité par Diablo76 19 juin 2021 à 17:51:25

                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    19 juin 2021 à 18:12:22

                                                    Pour le except: pass j'ai pris la suggestion de Diablo76, je n'ai aucune idée de comment faire autrement.

                                                    Pour le reste :

                                                    from tkinter import Button, Canvas, LEFT, RIGHT, Label, Tk
                                                    from tkinter.messagebox import showinfo
                                                    # on importe les fonctions utilitaires vues en cours sur les matrices
                                                    from matUtil import*
                                                    
                                                    TAILLE = 4
                                                    SIZE_SYMB = 30
                                                    SIZE_CELL = 100
                                                    EP_TRAIT = 6
                                                    PAD = 6
                                                    
                                                    
                                                    def initGrille():
                                                       return [[-1 for i in range(TAILLE)] for j in range(TAILLE)]
                                                    
                                                    
                                                    def afficheCoup(x1,y1):
                                                        if etatsJeu[0] == 1:
                                                           Canevas.create_oval(x1-SIZE_SYMB,y1-SIZE_SYMB,x1+SIZE_SYMB,y1+SIZE_SYMB,
                                                                               fill ="red")
                                                        else:
                                                           Canevas.create_line(x1-SIZE_SYMB,y1-SIZE_SYMB,x1+SIZE_SYMB,y1+SIZE_SYMB,
                                                                               width = EP_TRAIT)
                                                           Canevas.create_line(x1+SIZE_SYMB,y1-SIZE_SYMB,x1-SIZE_SYMB,y1+SIZE_SYMB,
                                                                               width = EP_TRAIT)    
                                                        Canevas.update()
                                                    
                                                    def aGagne(lig, col):
                                                         return (egalColonne(grille, col) or egalLigne(grille, lig) or
                                                                ((lig == col) and egalListe(extraitDiagonales(grille)[0])) or 
                                                                ((lig == TAILLE - col - 1) and egalListe(extraitDiagonales(grille)[1])))
                                                       
                                                    
                                                    def clickMouse(event):
                                                        # recupere la position du pointeur de la souris
                                                        lig, col  = event.x // SIZE_CELL, event.y // SIZE_CELL
                                                        if grille[lig][col] != -1:
                                                             showinfo(title='Erreur',message='Case déjà occupée !')
                                                        else:
                                                          grille[lig][col], etatsJeu[1] = etatsJeu[0], etatsJeu[1] - 1
                                                          afficheCoup(SIZE_CELL * lig + SIZE_CELL // 2,
                                                                               SIZE_CELL * col + SIZE_CELL // 2)
                                                          if aGagne(lig, col):
                                                             showinfo(title='Bravo !!', message=f' Joueur {etatsJeu[0]} a gagné !')
                                                             Canevas.unbind("<Button-1>")
                                                             Button(Mafenetre, text ="Une autre partie ?", command = init)\
                                                                               .pack(side=RIGHT, padx=PAD, pady=PAD)
                                                          elif etatsJeu[1] == 0:
                                                             showinfo(title='Oups !!! ', message=f' Match Nul  !')
                                                             Canevas.unbind("<Button-1>")
                                                             Button(Mafenetre, text ="Une autre partie ?", command = init)\
                                                                               .pack(side=RIGHT, padx=PAD, pady=PAD)
                                                          else:
                                                             etatsJeu[0] = 1 + etatsJeu[0] % 2
                                                       
                                                       
                                                    def init():
                                                       global grille, etatsJeu, Canevas, Mafenetre
                                                       etatsJeu = [1, TAILLE * TAILLE]  
                                                       grille = initGrille()
                                                    
                                                       try: Mafenetre.destroy()
                                                       except: pass
                                                    
                                                       # Création de la fenêtre principale
                                                       Mafenetre = Tk()
                                                       Mafenetre.title("Morpion")
                                                    
                                                       # Création d'un widget Canvas
                                                       Largeur = TAILLE * SIZE_CELL
                                                       Hauteur = TAILLE * SIZE_CELL
                                                       Canevas = Canvas(Mafenetre, width = Largeur, height =Hauteur, bg ="white")
                                                    
                                                       # La méthode bind() permet de lier un événement avec une fonction :
                                                       # un clic gauche provoquera l'appel de la fonction utilisateur clickMouse()
                                                       Canevas.bind("<Button-1>", clickMouse)
                                                       Canevas.pack(padx =PAD, pady =PAD)
                                                    
                                                    # Ici on crée les lignes et les colonnes qui délimitent les cases
                                                       for i in range(1, TAILLE):
                                                          Canevas.create_line(0,SIZE_CELL * i ,TAILLE * SIZE_CELL, SIZE_CELL * i,
                                                                              fill="purple", width=4)
                                                          Canevas.create_line(SIZE_CELL * i, TAILLE * SIZE_CELL, SIZE_CELL * i, 0,
                                                                              fill="purple", width=4)
                                                    
                                                    # Création d'un bouton Quitter
                                                       Button(Mafenetre, text ="Quitter", command = Mafenetre.destroy)\
                                                                         .pack(side=LEFT,padx=PAD,pady=PAD)
                                                    
                                                       
                                                    
                                                    # Programme Principal
                                                    init()
                                                    Mafenetre.mainloop()
                                                    


                                                    -
                                                    Edité par Tchae 19 juin 2021 à 18:21:50

                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                      19 juin 2021 à 18:22:09

                                                      Tchae a écrit:

                                                      Pour le except: pass j'ai pris la suggestion de Diablo76, je n'ai aucune idée de comment faire autrement.

                                                      Je l'ai expliqué avec winfo_children, tu n'as plus qu'à chercher, comprendre et remplacer.

                                                      • Partager sur Facebook
                                                      • Partager sur Twitter

                                                      Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                                                      La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                                                        19 juin 2021 à 18:48:17

                                                        Pour le try, ce n'était pas vraiment une suggestion, car loin d'être optimum...

                                                        -
                                                        Edité par Diablo76 19 juin 2021 à 18:50:58

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          19 juin 2021 à 18:52:02

                                                          • Le jeu de morpion peut être intéressant, la preuve Google propose le sien :  il te suffit de taper tic tac toe dans une barre de recherche et il va te le proposer. Mais ton jeu ne propose rien de tel puisque le joueur n'a pas d'adversaire si ce n'est lui-même, pas vraiment attrayant ni réaliste. Pour moi c'est le défaut n°1 de ce jeu.
                                                          • Ton jeu n'est pas jouable puisque ton module matUtil n'est pas accessible
                                                          • Ta gestion des erreurs dans le jeu (je ne parle pas dans le code) retire de la fluidité avec un popup bloquant. A nouveau, regarde comment un morpion habituel (celui de Google par exemple) se joue et fais pareil. Tu n'as pas assez réfléchi à la conception de ton application. Le code vient bien loin derrière. Pareil pour le bouton pour rejouer, il doit être présent en permanence sur le plateau.
                                                          • Ta fonction init n'a pas trop de sens : on ne va pas tout détruire juste pour effacer 3 croix et 2 ronds. 
                                                          • L'algorithme de détection d'alignement n'est pas optimisé du tout ; c'est vrai que l'optimisation ça se fait dans un 2e temps ; mais récupérer à chaque moment de la partie toutes les lignes, toutes les colonnes, toutes les diagonales montantes et descendantes juste parce qu'on a rajouté un coup, ça me paraît un peu beaucoup. Bien sûr ici, ça ne va pas ralentir ton jeu mais si un jour tu code un jeu (ou une appli) un peu moins simple avec ce genre de technique, tu peux t'attendre à une conso processeur trop importante.
                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            20 juin 2021 à 9:56:31

                                                            @Pascal Ortiz : Merci pour tous ces conseils,

                                                            Quelques réponses :

                                                            1) le module matUtil est posté ici quelques messages plus haut, j'ai décidé d'extraire ces fonctions dans un module pour améliorer la lisibilité du code principal, il a été amélioré sur les conseils de fred1599


                                                            2) ce "projet" est un exercice imposé par un enseignant (mon choix étant d'y ajouter une interface graphique) et l'algorithme de détection d'alignement était guidé dans l'exercice. Cependant il ne teste pas du tout toutes les colonnes, lignes et diagonales à chaque coup.
                                                            Il ne teste que la ligne et la colonne courante et éventuellement les diagonales uniquement si on se trouve sur l'une (ou les deux) d'entre elles (fonction aGagne() )

                                                            3) un futur développement de ce "projet" sera d'y insérer une "IA" contre laquelle on pourra jouer.

                                                            4) Je ne souhaite pas proposer une nouvelle partie avant que la partie courante soit terminée.

                                                            @fred1599 & Diablo76 :

                                                            Ma problématique avec le try except : pass est de fermer Mafenetre, uniquement si elle existe.
                                                            Si j'ai bien compris winfo_children() s'appelle depuis une une instance de Mafenetre, si elle n'a pas encore été créée ça me renvoie donc une erreur.

                                                            -
                                                            Edité par Tchae 20 juin 2021 à 10:32:30

                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                              20 juin 2021 à 10:24:21

                                                              Tchae a écrit:

                                                              Merci pour tous ces conseils,

                                                              Quelques réponses :

                                                              1) le module matUtil est posté ici quelques messages plus haut, j'ai décidé d'extraire ces fonctions dans un module pour améliorer la lisibilité du code principal, il a été amélioré sur les conseils de fred1599

                                                              Ça n'empêche pas de présenter le code, le but est de rendre testable afin de t'aider à améliorer les choses.

                                                              Dans ton code,

                                                              • Label est importé mais pas utilisé
                                                              • On a toujours from module import * pour le module maUtil
                                                              • Tes lignes sont toujours trop longues dans certains cas (mais ça peut passer à la rigueur)
                                                              • Il nous manque le module maUtil
                                                              • Partager sur Facebook
                                                              • Partager sur Twitter

                                                              Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
                                                              La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

                                                              Bouton Nouvelle Partie avec TkInter

                                                              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                              • Editeur
                                                              • Markdown