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()
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...)
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.
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.
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.
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()
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.
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 ) 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()
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.
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.
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)
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.
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
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.
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.
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)
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).
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 ?
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.
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)
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]]))
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.
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)
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.
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)
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)
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.
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
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()
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.
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)
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.
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.
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
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)
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Découverte Python Doc Tkinter Les chaînes de caractères
Découverte Python Doc Tkinter Les chaînes de caractères
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)
Découverte Python Doc Tkinter Les chaînes de caractères
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)
Découverte Python Doc Tkinter Les chaînes de caractères
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)
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)
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)
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)
Découverte Python Doc Tkinter Les chaînes de caractères
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)