Une remarque, il manque quelques commentaires au niveau du deuxième code: ton fichier cursesui.py
À quel niveau ?
Il n'y a que la fonction main que je n'ai pas documentée, parce que son utilité est triviale (elle sert tout bêtement à lancer le programme une fois que ncurses a été initialisé au sein la fonction curses.wrapper())...
Citation : realmagma
Pour info, je n'ai toujours pas commencé mon interface graphique, je suis en désaccord avec tkinter.
Bon courage, personnellement je m'en suis détourné au bout d'une demi-heure, en constatant à quel point ce module était peu commode à utiliser, comparativement à d'autres modules/bibliothèques de GUI comme PyGTK ou PyQt... Mais ce n'est que mon avis personnel.
PyGTK est peut-être plus direct d'accès que PyQt, mais le second framework est beaucoup plus poussé et complet.
Je ne peux pas vraiment donner de conseil, ça dépend de beaucoup de facteurs...
Si tu travailles dans un environnement Gnome (il me semble que c'est le cas), pour débuter, PyGTK est un bon choix.
Le seul inconvénient de Qt (que ce soit en C++ ou en Python) c'est que c'est un framework complet qui va (si tu veux l'utiliser "correctement") influencer complètement tes applications, pas seulement l'interface graphique. Je pense donc qu'il est plus sain d'aborder ce framework une fois que tu as des bases solides dans le langage que tu utilises, pas parce qu'il est compliqué (ce qui n'est pas le cas) mais parce qu'un framework aussi complet a souvent comme effet de "masquer" le reste du langage.
OK, je te remercie pour ces explications. Je vais regarder du côté de PyGTK, il m'a l'air plus simple que PyQT (et en plus je suis un grand débutant).
Le temps que je lise le tutoriel que je comprenne comment il fonctionne il me faudra au moins un bon mois (et encore tout dépend du temps que j'ai)
Le temps que je lise le tutoriel que je comprenne comment il fonctionne il me faudra au moins un bon mois
Soit tu te sous-estimes, soit tu te laisses impressionner par la programmation de GUI, mais je pense que pour comprendre le principe des GUI une bonne semaine est très largement suffisante, le reste n'étant que des habitudes à prendre avec la pratique.
Une fois que tu as compris ce que sont un widget et une fonction de callback (et cette dernière est beaucoup plus simple à appréhender en Python où "tout est objet" qu'en C — langage dans lequel j'ai fait mes premières armes en GTK, où ça implique la compréhension et l'utilisation de pointeurs sur fonctions... bref, beurk! —), tout roule.
Et bien je dirai que je galère un peut. Je m'entraîne sur Tkinter pour bien voir comment il fonctionne et j'aurai quelques questions + un problème (d'ordre mécanique => codage) .
Quelle sont les différences entre les méthodes:
pack()
grid()
place()
Le code présenté ci-dessous, découpe mon Canvas sous forme de cases. La taille d'un bloc équivaut à de 20x20 pixels. J'aimerai placer des images .gif de 10x10 pixels dans chacune de ces cases en les centrant.
Si les coordonnées de ma souris ce trouvent dans l'une des cases dans mon Canvas, alors on blit le gif en le centrant dans cette case.
Le problème c'est que je n'y arrive pas. Si je clic sur une case, mon image se blit. Si je déplace ma souris de quelque pixels et que je re-clic dessus, mon images se blite en décalage sur la même case.
Si quelqu'un aurait une solution (pas forcément du code, juste une idée) qu'il me le fasse savoir. Je vous remercie d'avance.
Mon code:
################################
## Les Modules Importés:(2) ##
#
from tkinter import *
import math
#-*-*- <Les Classes> || Début de <JeuTk> -*-*-#
class JeuTk(object):
def __init__(self):
self.l = []
self.tailleBloc = 20 #"Épaisseur" d'un bloc 20x20 pixels
self.largBloc = 10
self.largF = self.largBloc * self.tailleBloc #largF = largeurFenetre
for i in range(self.largBloc): #On créer 10 listes dans une liste
self.l.append([0] * self.largBloc)
#Création d'une fenetre
self.fenetre = Tk()
#Canvas
self.can = Canvas(self.fenetre, bg = "dark grey", height = self.largF, width = self.largF)
self.can.bind("<Button-1>", self.pointeur) #Clic Gauche
self.can.bind("<Button-3>", self.pointeur) #Clic Droit
self.can.pack(side = LEFT)
#Boutton
bou = Button(self.fenetre, text = "Quitter", command = self.fenetre.destroy)
bou.pack()
#Label: On affiche la grille
texte = Label(self.fenetre, text = self.l)
texte.pack()
#Photo
self.photo = PhotoImage(file = "1.gif")
def pointeur(self, event):
#On fait en sorte que se soit une grille. Que des entiers
#Grâce au print, on peut voir que je clic sur des cases qui sont des entiers
#Si je clic en haut à gauche, on me renvoie (0;0). Plus à droite=> (1;0)...
print(math.ceil(event.x / self.tailleBloc), math.ceil(event.y / self.tailleBloc))
item = self.can.create_image(math.ceil(event.x), math.ceil(event.y), image = self.photo)
if __name__ == "__main__":
p = JeuTk()
p.fenetre.mainloop()
P.S: Pour une image gif de 10x10 pixels tapez: chiffre 7 dans Google image. Il se trouve dans la première page. Lien direct:
Chez moi aussi. Le problème c'est que je ne veux pas qu'une image en chevauche une autre et qu'une seule image aille dans une seule et unique case.
Je n'ai aucune idée sur la représentation de l'algorithme.
C'est pas grave, finalement j'ai réussit à générer les cases à cliquer pour le démineur. Je n'ai plus qu'a les resserrer (C'est trop espacer).
Il me manque beaucoup de choses à faire, comme les remplacer lors d'un clic, puis implémenter tout ça sur mon vrai démineur.
Je vous tient au courant.
EDIT: Voici les algo que j'ai utilisé:
Le premier place un chiffre dans une case.
Le deuxième génère une grille
def pointeur(self, event):
#On fait en sorte que se soit une grille. Que des entiers
#Grâce au print, on peut voir que je clic sur des cases qui sont des entiers
#Si je clic en haut à gauche, on me renvoie (0;0). Plus à droite=> (1;0)...
print(math.ceil(event.x / self.tailleBloc), math.ceil(event.y / self.tailleBloc))
#Ne pas oublier '+1' pour pouvoir cliquer en 10;10 par exemple
for i in range(self.largBloc + 1):
for j in range(self.largBloc + 1):
if math.ceil(event.x / self.tailleBloc) == i and math.ceil(event.y / self.tailleBloc)==j:
#Pour centrer l'image: i*tailleBloc - largBloc
item = self.can.create_image(i * self.tailleBloc - self.largBloc , j * self.tailleBloc - self.largBloc, \
image = self.photo2)
def genereGrille(self):
for i in range(self.largBloc):
for j in range(self.largBloc):
item = self.can.create_image(i * self.tailleBloc + self.largBloc, j * self.tailleBloc + self.largBloc, \
image = self.photo)
EDIT N°2: Mon code en entier: (Pour ceux qui voudront tester)
################################
## Les Modules Importés:(2) ##
#
from tkinter import *
import math
#-*-*- <Les Classes> || Début de <JeuTk> -*-*-#
class JeuTk(object):
def __init__(self):
self.l = []
self.tailleBloc = 20
self.largBloc = 10
self.largF = self.largBloc * self.tailleBloc #largF = largeurFenetre
for i in range(self.largBloc): #On créer 5 listes dans une liste
self.l.append([0] * self.largBloc)
#Création d'une fenetre
self.fenetre = Tk()
#Canvas
self.can = Canvas(self.fenetre, bg = "dark grey", height = self.largF, width = self.largF)
self.can.bind("<Button-1>", self.pointeur) #Clic Gauche
self.can.bind("<Button-3>", self.pointeur) #Clic Droit
self.can.pack(side = LEFT)
#Boutton
bou = Button(self.fenetre, text = "Quitter", command = self.fenetre.destroy)
bou.pack()
#Boutton genererGrille
bou2 = Button(self.fenetre, text = "Nouveau", command = self.genereGrille)
bou2.pack()
#Label: On affiche la grille
#texte = Label(self.fenetre, text = self.l)
#texte.pack()
#Photo
self.photo = PhotoImage(file = "c.gif")
self.photo2 = PhotoImage(file = "1.gif")
def pointeur(self, event):
#On fait en sorte que se soit une grille. Que des entiers
#Grâce au print, on peut voir que je clic sur des cases qui sont des entiers
#Si je clic en haut à gauche, on me renvoie (0;0). Plus à droite=> (1;0)...
print(math.ceil(event.x / self.tailleBloc), math.ceil(event.y / self.tailleBloc))
#Ne pas oublier '+1' pour pouvoir cliquer en 10;10 par exemple
for i in range(self.largBloc + 1):
for j in range(self.largBloc + 1):
if math.ceil(event.x / self.tailleBloc) == i and math.ceil(event.y / self.tailleBloc)==j:
#Pour centrer l'image: i*tailleBloc - largBloc
item = self.can.create_image(i * self.tailleBloc - self.largBloc , j * self.tailleBloc - self.largBloc, \
image = self.photo2)
def genereGrille(self):
for i in range(self.largBloc):
for j in range(self.largBloc):
item = self.can.create_image(i * self.tailleBloc + self.largBloc, j * self.tailleBloc + self.largBloc, \
image = self.photo)
if __name__ == "__main__":
p = JeuTk()
p.fenetre.mainloop()
j'ai essayé de créer l'interface graphique de mon démineur, mais impossible de le faire fonctionner correctement.
En effet, il me dit que je suis en index out of range, alors que non, je ne le suis pas !
Explication de mon code:
J'ai crée une fenêtre avec un Canvas. J'ai découpé mon Canvas comme dans l'exemple du poste du haut (cf EDIT N°2).
C'est à dire sous forme de blocs. (Copier/coller le code de mon édit n°2, vous verrez de quoi je parle )
En somme, c'est le même système.
Exécuter le code ci-dessous, cliquez sur commencer/jouer puis sur afficher.
Vous verrez déjà une erreur dans la console.
Ensuite cliquez dans le Canvas... et c'est le drame. Index out of range.
Quelqu'un aurait il une idée, parce que là je suis à sec .
Merci d'avance.
j'ai résolu il y a de ça quelques jours le problème du: index is out of range mais un autre problème est survenu.
Lorsque je clic sur le bouton <rafraîchir>, un appel de la fonction afficher se fait.
Cette fonction parcourt ma liste mappCachee.
Si il y a un '*' alors on affiche telle image à telle endroit.
Si il y a un 1 alors on affiche telle image à telle endroit
Si il y a un 2 ...
Mon programme ne plante pas, mais reste figé après avoir blité les images, comme si ma mainloop ne fonctionnait pas.
Est-ce que quelqu'un aurait une idée sur ce problème ?
Je vous donne mon code en vous remerciant d'avance.
Bonjour à tous,
Je ne savais pas trop quoi faire en ce moment et j'ai trouvé cette idée de démineur plutôt sympa.
Il s'agit d'un démineur assez complet:
Affichage du temps
Affichage du nombre de drapeaux restants
Gestion des scores
3 tailles différentes
Le tout réalisé avec tkinter.
Et voici le code: api.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from random import sample
class Case:
"""classe représentant une case"""
def __init__(self, index):
self.index = index
self.type = Api.CASE_COVER
self.hasFlag = False
self.hasMine = False
self.number = 0
self.tkItem = None
def reset(self):
"""réinitialise la case"""
self.type = Api.CASE_COVER
self.hasFlag = False
self.hasMine = False
self.number = 0
@property
def hasNumber(self):
return self.number != 0
class Api:
"""la classe principale"""
#les différents type de case
CASE_COVER, CASE_VISIBLE, CASE_FLAG, CASE_MINE, CASE_EXPLOSE, CASE_FLAG_FALSE = range(6)
def __init__(self):
self.isStarted = False
self.CbCaseChange = lambda x: None
self.cbGameOver = lambda x: None
self.cbWin = lambda: None
self.cbFlagChange = lambda x: None
self.cbStart = lambda : None
def setCb(self, caseChange, flagChange, gameOver, win, start):
"""configuration des callbacks"""
self.cbCaseChange = caseChange
self.cbFlagChange = flagChange
self.cbGameOver = gameOver
self.cbWin = win
self.cbStart = start
def setConfig(self, size, nbrMines):
self.size = size
self.rows, self.cols = size
self.nbrMines = nbrMines
self.create()
def create(self):
"""créé la list représentant les cases"""
self.nbrCases = self.rows * self.cols
self.cases = [Case(i) for i in range(self.nbrCases)]
self.new()
def getCases(self):
return self.cases
def new(self):
"""réinitialise la map"""
self.isStarted = False
#le nombre de reapeau restant
self.flagRemain = self.nbrMines
#réinitialise les cases
for case in self.cases:
case.reset()
#place les mines
self.placeMine()
#nombre de case restante
self.caseRemain = self.nbrCases
self.cbFlagChange(self.flagRemain)
def placeMine(self):
"""place les mines sur la map"""
for case in sample(self.cases, self.nbrMines):
case.hasMine = True
def demine(self, case):
"""démine la case passée en argument"""
#si c'est la première case déminée
if self.caseRemain == self.nbrCases:
#on démarre la partie
self.isStarted = True
self.cbStart()
#si la partie n'est pas démarée, on s'arrête là
if not self.isStarted:
return
#si la case est encore couverte
if case.type == self.CASE_COVER:
#si la case est minée
if case.hasMine:
#c'est perdu
self.setGameOver(case)
self.isStarted = False
self.cbGameOver()
return
#le nombre de mines autour de la case
numMine = self.getMineArround(case)
#la case est déminée
case.type = self.CASE_VISIBLE
#1 case de moins
self.caseRemain -= 1
#si le nombre de cases restantes est égale au nombre de mines
if self.caseRemain == self.nbrMines:
#c'est gagnée
self.isStarted = False
self.cbWin()
#si il y a des mines autour
if numMine:
case.number = numMine
#sinon on démine les cases environnantes
else:
for c in self.getAroundCase(case):
self.demine(c)
#appéle le callback 'caseChange' pour mettre à jour le coté graphique
self.cbCaseChange(case)
def getAroundCase(self, case):
"""retourne les cases environnantes (c'est un peu tordu, doit y avoir mieux!!)"""
i = case.index
c = self.cols
return [self.cases[t] for r in (-c, 0, c) for t in (i+r-1, i+r, i+r+1) if t/c == (i+r)/c and 0<=t<len(self.cases)]
def getMineArround(self, case):
"""retourne le nombre de mines autour de la case"""
return len([0 for c in self.getAroundCase(case) if c.hasMine])
def setGameOver(self, caseExplose):
"""permet d'afficher la position des mines et les drapeaux mal placés"""
#c'est la case qui a explosée
caseExplose.type = self.CASE_EXPLOSE
self.cbCaseChange(caseExplose)
for case in self.cases:
#si la case a une mine
if case.hasMine:
#si elle n'a pas de drapeau
if case.type != self.CASE_FLAG:
#si ce n'est pas la case qui a explosée
if case != caseExplose:
#affiche la mine
case.type = self.CASE_MINE
self.cbCaseChange(case)
#si la case n'a pas de mine et un drapeau
elif case.type == self.CASE_FLAG:
#affiche un drapeau mal placé
case.type = self.CASE_FLAG_FALSE
self.cbCaseChange(case)
def switchFlag(self, case):
"""ajoute/retire un drapeau sur la case"""
#si la partie n'est pas commencé
if not self.isStarted:
return
switch = {self.CASE_FLAG: self.CASE_COVER, self.CASE_COVER: self.CASE_FLAG}
#si la case à un drapeau ou est recouverte
if switch.has_key(case.type):
case.type = switch[case.type]
#ajoute ou retire un drapeau
self.flagRemain += {self.CASE_COVER: 1, self.CASE_FLAG: -1}[case.type]
#met à jour la case (graphique)
self.cbCaseChange(case)
#met à jour le nombre de drapeaux restant
self.cbFlagChange(self.flagRemain)
demineur.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import Tkinter as tk
from api import Api
import os
import sys
import pickle
import tkFont
#la couleur de fond
BG = "#062369"
class ResourceHandler:
"""classe permettant une gestion plus aisée des images"""
def __init__(self, path="img"):
self.path = path
self.imgs = {}
self.load()
def load(self):
"""charge les images dand le dict self.imgs"""
for img in os.listdir(self.path):
path = os.path.join(self.path, img)
name = os.path.splitext(img)[0]
img = tk.PhotoImage ( file=path)
self.imgs[name] = img
def get(self, imgName):
"""retourne une image en fonction de son nom"""
return self.imgs[imgName]
def getBtn(self, btnName):
"""retourne les 3 images d'un boutons (normal, over, down)"""
capName = btnName.capitalize()
btn = self.get("btn%s"%capName)
btnDown = self.get("btn%sDown"%capName)
btnOver = self.get("btn%sOver"%capName)
return btn, btnDown, btnOver
def getCases(self):
"""retorune les images des cases"""
l = ("cover", "empty", "flag", "mine", "over", "explose", "flagfalse")
return dict([(name, self.get("case%s"%name.capitalize())) for name in l])
def getBd(self, img, clr="blue"):
"""retourne les images des bordures (rouge ou bleue)"""
clr = clr.capitalize()
img = img.capitalize()
return self.get("bg%s%s"%(img, clr))
class MyButton(tk.Label):
"""La classe des boutons personnalisés"""
def __init__(self, parent, btns, command=lambda x: None, data=None):
tk.Label.__init__(self, parent, image=btns[0], bg=BG, bd=0)
self.btns = btns
self.command = command
self.data = data
self.bind ( "<Button-1>", self.onClick)
self.bind ( "<ButtonRelease-1>", self.onRelease)
self.bind("<Enter>", self.onEnter)
self.bind("<Leave>", self.onLeave)
def onClick(self, e):
self.config(image=self.btns[1])
self.command(self.data)
def onRelease(self, e):
self.config(image=self.btns[2])
def onEnter(self, e):
self.config(image=self.btns[2])
def onLeave(self, e):
self.config(image=self.btns[0])
class TkCase(tk.Label):
"""la classe représentant une case"""
def __init__(self, parent, imgs, case, api):
tk.Label.__init__(self, parent, bd=0, padx=0, pady=0, fg="#0b2664")
self.imgs = imgs
self.api = api
self.setImg("cover")
self.case = case
self.bind("<Enter>", self.onCaseEnter)
self.bind("<Leave>", self.onCaseLeave)
self.bind("<Button-1>", self.onDemine)
self.bind("<Button-3>", self.onFlag)
def onCaseEnter(self, e):
"""lorsque le pointeur entre dans la case"""
#si la case est encore recouverte
if self.case.type == Api.CASE_COVER:
self.setImg("over")
def onCaseLeave(self, e):
"""lorsque le pointeur quitte la case"""
#si la case est encore recouverte
if self.case.type == Api.CASE_COVER:
self.setImg("cover")
def onDemine(self, e):
"""lorsque l'utilisateur fait un click gauche sur la case"""
#on démine
self.api.demine(self.case)
def onFlag(self, e):
"""lorsque l'utilisateur fait un click droit sur la case"""
#on switch le drapeau
self.api.switchFlag(self.case)
def setImg(self, img):
"""change l'image de la case"""
self.config(image=self.imgs[img])
def setNumber(self, num):
"""affiche un numero dans la case"""
self.config(compound=tk.CENTER, text=str(num))
def reset(self):
"""recouvre la case"""
self.config(text="")
self.setImg("cover")
class TkMap(tk.Frame):
"""la classe réprésentant la map"""
def __init__(self, parent, config):
tk.Frame.__init__(self, parent)
self.parent = parent
self.res = ResourceHandler()
#initialisation de l'api
self.api = Api()
#configure les callbacks
self.api.setCb(self.setCase, parent.onFlagChange, parent.onGameOver,
parent.onWin, parent.onStart)
self.size = None
#la couleur de la bordure
self.clr = "blue"
self.setConfig(config)
def setConfig(self, config):
#détruit tous les widgets
for w in self.winfo_children():
w.destroy()
#met à jour les donnée
self.size, self.nbrMines = config
self.rows, self.cols = self.size
#configure l'api
self.api.setConfig(self.size, self.nbrMines)
#créé la bordure bleu
self.createBorder()
#la frame contenant les cases
self.frameMap = tk.Frame(self)
#création des cases
self.createCases()
self.frameMap.grid(row=1, column=1, columnspan=self.cols, rowspan=self.rows)
def new(self):
"""rédémarre une partie (avec la même configuration)"""
#réinitialise l'api
self.api.new()
#réinitialise les cases
for case in self.api.getCases():
case.tkItem.reset()
def setClr(self, clr):
"""change la couleur de la bordure"""
if clr != self.clr:
self.clr = clr
for name, tkBd in self.listTkBd:
tkBd.config(image=self.res.getBd(name, clr))
def createBorder(self):
"""créé la bordure"""
row, col = self.size
#les fragment de la bordure sont stockés, permettant de changer sa couleur
#les fragment sont stockés dans le sens horaire, permettant de faire
#une animation lors d'une victoire
listBg = [("topLeft", (0, 0), None)]
for i in range(col):
listBg+=[("top", (0, i+1), tk.N)]
listBg += [("topRight", (0, col+1), None)]
for i in range(row):
listBg += [("right", (i+1, col+1), tk.E)]
listBg += [("bottomRight", (row+1, col+1), None)]
li = []
for i in range(col):
li += [("bottom", (row+1, i+1), tk.S)]
listBg += (reversed(li))
listBg += [("bottomLeft", (row+1, 0), None)]
li = []
for i in range(row):
li += [("left", (i+1, 0), tk.W)]
listBg+=(reversed(li))
self.listTkBd = []
for name, pos, sticky in listBg:
row, col = pos
lbl = tk.Label(self, image=self.res.getBd(name), bd=0)
lbl.grid(row=row, column=col, sticky=sticky)
self.listTkBd.append((name, lbl))
def anim(self, index=0, clr="red"):
"""anime la bordure"""
name, lbl = self.listTkBd[index]
lbl.config(image=self.res.getBd(name, clr))
if index+1 < len(self.listTkBd):
self.timerId = self.after(10, lambda i=index: self.anim(i+1, clr))
elif clr == "red":
self.timerId = self.after(10, lambda : self.anim(0, "blue"))
#lorsque l'animation est terminée
else:
#affiche la page de score (si besoin)
self.parent.showScore()
def createCases(self):
"""création des cases"""
for case in self.api.getCases():
#calcul les coordonnées de la cases
row, column = divmod(case.index, self.cols)
#instancie la case
tkCase = TkCase(self.frameMap, self.res.getCases(), case, self.api)
case.tkItem = tkCase
tkCase.grid(row=row, column=column)
def setCase(self, case):
"""met à jour l'image de la case"""
tkCase = case.tkItem
#si la case est déminée
if case.type == Api.CASE_VISIBLE:
tkCase.setImg("empty")
#si elle a un nombre (de mines autour)
if case.hasNumber:
tkCase.setNumber(case.number)
#si la case n'est pas déminée
else:
imgName = {Api.CASE_COVER: "cover", Api.CASE_FLAG: "flag", Api.CASE_MINE: "mine",
Api.CASE_EXPLOSE: "explose", Api.CASE_FLAG_FALSE: "flagfalse"}[case.type]
tkCase.setImg(imgName)
class TkMenu(tk.Frame):
"""classe réprésentant le menu"""
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.res = ResourceHandler()
self.clr = "blue"
self.showBorder()
def showBorder(self):
"""affiche la bordure du menu"""
listBg = [(("menuTop"), (0, 0), (1, 3), None), (("menuLeft"), (1, 0), (7, 1), tk.W),
(("menuCenter"), (4, 1), (1, 1), tk.W), (("menuBottom"), (8, 0), (1, 3), None),
(("menuRight"), (1, 2), (7, 1), tk.W)]
self.listTkBd = []
for name, pos, span, sticky in listBg:
row, column = pos
rowspan, columnspan = span
lbl = tk.Label(self, image=self.res.getBd(name), bd=0)
lbl.grid(row=row, column=column, rowspan=rowspan, columnspan=columnspan, sticky=sticky)
self.listTkBd.append((name, lbl))
def setBtns(self, info):
"""affiche et configure les boutons"""
for i, btn in enumerate(info):
i += {True: 2, False: 1}[i > 2]
name, command, data = btn
MyButton(self, btns=self.res.getBtn(name), command=command, data=data).grid(row=i, column=1, sticky=tk.N)
def setClr(self, clr):
"""change la couleur de la bordure"""
if clr != self.clr:
self.clr = clr
for name, tkBd in self.listTkBd:
tkBd.config(image=self.res.getBd(name, clr))
class TkInfo(tk.Frame):
"""classe qui affiche le temps et le nombre de drapeaux restants"""
def __init__(self, parent):
tk.Frame.__init__(self, parent, bg=BG, relief=tk.SUNKEN, bd=2, padx=5, pady=5)
self.res = ResourceHandler()
self.show()
def show(self):
"""création des widget"""
font = tkFont.Font(size=18)
tk.Label(self, image=self.res.get("clock"), bd=0, bg=BG).pack(side=tk.LEFT)
self.varTime = tk.StringVar(value="00:00")
tk.Label(self, textvariable=self.varTime, bd=0, bg=BG, fg="white", width=5, font=font).pack(side=tk.LEFT)
tk.Label(self, width=5, bg=BG).pack(side=tk.LEFT)
tk.Label(self, image=self.res.get("flag"), bd=0, bg=BG).pack(side=tk.LEFT)
self.varFlag = tk.StringVar()
tk.Label(self, textvariable=self.varFlag, bd=0, bg=BG, fg="white", width=3, font=font).pack(side=tk.LEFT)
def setFlag(self, val):
"""change le nombre de drapeaux restants"""
self.varFlag.set(str(val))
def setTime(self, time):
"""change le temps"""
timeStr = "%02i:%02i"%divmod(time, 60)
self.varTime.set(timeStr)
class TkScore(tk.Frame):
"""classe qui affiche les scores"""
def __init__(self, parent, cfgs, cfgId):
tk.Frame.__init__(self, parent, bg=BG)
self.parent = parent
self.cfgs = cfgs
self.res = ResourceHandler()
self.font = tkFont.Font(size=12)
self.numScore = 10
self.placeEntry = -1
self.create()
#charge les scores
with open("score") as f:
self.allScores = pickle.load(f)
self.setConfig(cfgId)
def create(self):
"""créé la frame qui affiche la taille de la map et le nombre de mines"""
self.frameScore = tk.Frame(self, bg=BG, relief=tk.SUNKEN, bd=2, padx=3, pady=3)
frameInfo = tk.Frame(self, bg=BG, relief=tk.SUNKEN, bd=2, padx=3, pady=3)
self.varEntry = tk.StringVar()
tk.Label(frameInfo, image=self.res.get("size"), bd=0, bg=BG).pack(side=tk.LEFT)
self.varSize = tk.StringVar()
tk.Label(frameInfo, textvariable=self.varSize, font=self.font, fg="white", bd=0, bg=BG).pack(side=tk.LEFT)
tk.Label(frameInfo, width=5, bg=BG).pack(side=tk.LEFT)
tk.Label(frameInfo, image=self.res.get("mine"), bd=0, bg=BG).pack(side=tk.LEFT)
self.varMine = tk.StringVar()
tk.Label(frameInfo, textvariable=self.varMine, font=self.font, fg="white", bd=0, bg=BG).pack(side=tk.LEFT)
frameBtn = tk.Frame(self,bg=BG)
MyButton(frameBtn, self.res.getBtn("clear"), self.onClear).pack(padx=10, side=tk.LEFT)
MyButton(frameBtn, self.res.getBtn("valid"), self.onValid).pack(padx=10, side=tk.LEFT)
frameInfo.grid(pady=5)
frameBtn.grid(row=2)
self.bind_all("<KeyPress-Return>", self.onValid)
def write(self):
"""écrit les scores dans le fichier"""
with open("score", "w") as f:
pickle.dump(self.allScores, f)
def setConfig(self, cfgId):
"""change la configuration"""
self.cfg = self.cfgs[cfgId]
self.cfgId = cfgId
self.actualScores = self.allScores[self.cfgId]
size, numMines = self.cfg
self.varSize.set("%sx%s"%size)
self.varMine.set(str(numMines))
def showScore(self):
"""affiche les scores"""
self.parent.pack_propagate(0)
#efface les scores
for w in self.frameScore.winfo_children():
w.destroy()
#cache les widget de la frame principale
for w in self.parent.winfo_children():
w.pack_forget()
#si il y a au moins un score
if self.actualScores:
for i, info in enumerate(self.actualScores):
name, time = info
timeStr = "%02i:%02i"%divmod(time, 60)
#la place
tk.Label(self.frameScore, text="%i-"%(i+1), bg=BG, fg="white",
font=self.font).grid(row=i, column=0, sticky=tk.E)
#si l'utilisateur peut rentrer son nom
if self.placeEntry == i:
entry = tk.Entry(self.frameScore, textvariable=self.varEntry,
insertbackground="white", width=15, bg=BG, fg="white", font=self.font)
entry.grid(row=i, column=1, sticky=tk.W, ipadx=15)
entry.focus_force()
#sinon on affiche le nom
else:
tk.Label(self.frameScore, text=name, bg=BG, fg="white",
font=self.font).grid(row=i, column=1, sticky=tk.W, ipadx=15)
#affiche le temps
tk.Label(self.frameScore, text=timeStr, bg=BG, fg="white",
font=self.font).grid(row=i, column=2, sticky=tk.W, ipadx=15)
#si il n'y a pas de score
else:
#un label vide
tk.Label(self.frameScore, bg=BG, width=20).grid()
self.frameScore.grid(row=1)
self.pack()
def isBest(self, time):
"""retourne True si le temps rentre dans la liste, sinon False"""
if len(self.actualScores) < self.numScore:
return True
if time < self.actualScores[-1][1]:
return True
return False
def addScore(self, time):
"""permet d'ajouter un nouveau score"""
#si il y a au moins un score
if self.actualScores:
#récupère la place du nouveau score
for i, info in enumerate(self.actualScores):
if time < info[1]:
self.placeEntry = i
self.actualScores.insert(i, ["", time])
break
#si c'est la dernière plave
else:
#on l'joute à la liste
self.actualScores.append(["", time])
self.placeEntry = i + 1
#si le nombre de scores est supérieur au nombre max de scores
if len(self.actualScores) > self.numScore:
self.actualScores.pop()
#si c'est le premier score
else:
self.placeEntry = 0
self.actualScores.append(["", time])
#affiche les scores (avec une Entry)
self.showScore()
def onValid(self, e):
"""le bouton 'valider' (ou la touche Entrée) est cliquer"""
#si il y a un nouveau score
if self.placeEntry != -1:
#récupère le nom, et sauvegarde les scores
name = self.varEntry.get()
self.actualScores[self.placeEntry][0] = name
self.write()
#efface la frame des scores
self.placeEntry = -1
self.varEntry.set("")
self.parent.pack_propagate(1)
self.pack_forget()
self.parent.packWidget()
def onClear(self, e):
"""le bouton 'clear' est cliqué"""
#détruit les labels des scores
for w in self.frameScore.winfo_children():
w.destroy()
tk.Label(self.frameScore, bg=BG, width=20).grid()
#met à jour le fichier
self.actualScores = self.allScores[self.cfgId] = []
self.write()
class Demineur(tk.Tk):
"""la classe principale du démineur"""
#ids de la taille de la map
SMALL_ID, MIDDLE_ID, BIG_ID = range(3)
def __init__(self):
tk.Tk.__init__(self)
#la couleur d'arrière plan
self.config(bg=BG)
#les 3 tailles possibles ((ligne, colonne), nombre de mines)
self.cfgs = {self.SMALL_ID: ((10, 10), 15), self.MIDDLE_ID: ((16, 16), 40), self.BIG_ID: ((24, 24), 120)}
#la config de départ
self.cfgId = self.MIDDLE_ID
self.cfg = self.cfgs[self.MIDDLE_ID]
self.time = 0
#l'id du timer, permettant d'arrêter temps
self.timerId = None
#Les scores
self.tkScore = TkScore(self, self.cfgs, self.cfgId)
#le temps et le nombre de drapeaux
self.tkInfo = TkInfo(self)
#le menu gauche
self.tkMenu = TkMenu(self)
self.createMenuBtns()
#la map
self.tkMap = TkMap(self, self.cfg)
#configure les scores
self.tkScore.setConfig(self.cfgId)
self.packWidget()
def packWidget(self):
"""affiche les widgets"""
self.tkInfo.pack()
self.tkMenu.pack(side=tk.LEFT)
self.tkMap.pack(side=tk.LEFT)
def createMenuBtns(self):
"""création et initialisation des boutons du menus"""
BtnsInfo = (("quit", self.onQuit, None), ("refresh", self.onRefresh, None),
("score", self.onScore, None), ("small", self.onResize, self.SMALL_ID),
("middle", self.onResize, self.MIDDLE_ID), ("big", self.onResize, self.BIG_ID))
self.tkMenu.setBtns(BtnsInfo)
def setClr(self, clr):
"""change la couleur des bordures"""
self.tkMap.setClr(clr)
self.tkMenu.setClr(clr)
def tick(self):
"""permet de modifier le temps"""
self.time += 1
self.tkInfo.setTime(self.time)
self.timerId = self.after(1000, self.tick)
def onQuit(self, e):
"""le bouton 'quit' est cliqué"""
sys.exit(0)
def onRefresh(self, e):
"""le bouton 'refresh' est cliqué"""
self.resetTimer()
self.setClr("blue")
self.tkMap.new()
def onScore(self, e):
"""le bouton 'score' est cliqué"""
self.tkScore.showScore()
self.tkScore.pack()
def onFlagChange(self, num):
"""le nombre de drapeau a changé"""
self.tkInfo.setFlag(num)
def onResize(self, cfgId):
"""la taille de la map a changée"""
if cfgId != self.cfgId:
self.setClr("blue")
self.resetTimer()
self.cfgId = cfgId
self.cfg = self.cfgs[cfgId]
self.tkScore.setConfig(cfgId)
self.tkMap.setConfig(self.cfg)
def onGameOver(self):
"""la partie est perdu"""
self.after_cancel(self.timerId)
self.setClr("red")
def onWin(self):
"""la partie est gagnée"""
self.after_cancel(self.timerId)
self.tkMap.anim()
def showScore(self):
"""affiche les scores si besoin"""
if self.tkScore.isBest(self.time):
self.tkScore.addScore(self.time)
def resetTimer(self):
"""remet le temps à zero"""
self.time = 0
self.tkInfo.setTime(0)
if self.timerId:
self.after_cancel(self.timerId)
def onStart(self):
"""la patie commence, affiche le temps"""
self.timerId = self.after(1000, self.tick)
demineur = Demineur()
demineur.mainloop()
Et voici le zip contenant les fichiers et les images: demineur.zip
J'ai programmé avec python 2.6. ça ne fonctionne pas avec python 3, mais ça doit pouvoir s'arranger facilement.
J'ai testé sur Ubuntu, ça à l'air de fonctionner correctement.
nyko77,
d'habitude je trouve les rendus Tk plutôt môches, mais là ...
respect pour le design ...
+1
Très jolie rendu, c'est incroyable ce que l'on peut faire avec Tkinter ... quand on s'y connaît .
Ton démineur n'est pas mal non plus Josmiley. J'adore particulièrement le backGround. Pourquoi ne pas charger un backGround différent à chaque partie gagnée ou perdue ?
Je vais paraître ridicule avec mon petitdémineur fait avec Tkinter (Mon rendu est... nul ! ... et le mot 'nul' est faible )
Félicitations pour vos contributions, en particulier nyko77 pour le très beau rendu avec tkinter.
J'ai toujours pas eu le temps de faire mon interface Pygame, je voulais m'amuser avec et créer de jolies animations pour avoir un truc un peu sympa, mais la charge de boulot que j'ai actuellement ne me permet malheureusement pas de m'y coller (surtout que je perds du temps à jouer au démineur en console au taff... ).
=> Ici se sont bien des variables globales, on est d'accord ?
En faite pas tout à fait. Ce sont plutôt des attributs de classe. Ils ne sont pas accessibles partout dans mon programme. Si je veux y accéder depuis le classe API, je peux utiliser self.CASE_COVER ou API.CASE_COVER, en dehors de la classe uniquement avec API.CASE_COVER. L'avantage de les mettre en dehors du __init__ est que je peux y accéder sans instancier la classe.
Citation : realmagma
class MyButton(tk.Label):
"""La classe des boutons personnalisés"""
def __init__(self, parent, btns, command=lambda x: None, data=None):
=> Pareil, je n'ai pas tout compris.
L'argument command attend une fonction, ou une méthode (c'est la fonction appelé lorsque l'on clique sur le bouton). si un jour (je ne sais pour quelle raison), je souhaite créer un bouton qui n'appèle pas de fonction, et que j'ai simplement mis command=None, j'aurais un beau message d'erreur lors du clique sur le bouton, du type:
TypeError: 'NoneType' object is not callable
lambda sert à créer simplement de petites fonctions:
lambda x: None
est équivalent à:
def maFonction(x):
return None
C'est le même principe pour mes attributs de callbacks.
Citation : josmiley
nyko77,
d'habitude je trouve les rendus Tk plutôt môches, mais là ...
respect pour le design ...
En faite j'ai passé plus de temps à utiliser (et comprendre) gimp, qu'à programmer le démineur.
Je trouve également l'aspect de tkinter un peu vieillot. Je m'en sert d'habitude uniquement pour me faire une interface rapidement, pour tester mes programmes.
wxpython ou pyqt sont quand même plus recommandés pour faire des application digne de ce nom.
Bonjour! C'est exactement le mini projet qu'il me fallait pour bien comprendre la POO ! J'y suis pas encore mais j'ai reussi à faire la phase 1 Avant de continuer j'aimerais avoir votre analyse de mon code et des remarques , améliorations etc (si vous avez le temps et l'envie)..... Merci beaucoup!
#!/usr/bin/env python
# -*- coding: cp1252 -*-
#Exercice du SDZ : Démineur phase 1 by FIVETAG
import random
#Pour avoir un terrain aléatoire
#Defintion des variables globales qui vont représenter les différentes cases
global MINE , DEMINE , TERRE , EXPLOSE
MINE='x'
TERRE='o'
DEMINE='+'
EXPLOSE='@'
def terrain_alea(longeur,maxMines):
'''fonction qui sert a faire un terrain de maniére aléatoire avec longeur et un nombre maximum de mines'''
chaine=""
for x in range(longeur):
m=0
if random.randint(0,3)==1 and m<=maxMines:
chaine+=MINE
m+=1
else:
chaine+=TERRE
return chaine
#CODE
class Plateau :
'''Plateau de jeu'''
def __init__(self,terrain,hauteur):
self.cases=[] #cases du terrain
self.terrain=terrain #terrain ce présentant sous une chaine de caractére
self.hauteur=hauteur #hauteur et largeur du terrain
x,y=0,0 #coordonnées successives des différentes cases
for c in terrain :
self.cases.append(Case(str(c),(x,y)))
#c= etat de la case , x= coordonnée en abscisse , y = coordonnées en ordonnée
x+=1 #et un de plus en abscisse
if(x%(hauteur)==0):
# on incremente l'ordonnée y de 1 et on remet l'abscisse x à 0 si le nombre de cases alignées a atteint la hauteur (j'aurait du mettre la cote)
x=0
y+=1
def __iter__(self):
'''methode speciale, a=Plateau(),
cette methode me permet de faire :
"for x in a :
..."
ici elle devra retourner chaque elements de self.plateau'''
for case in self.cases:
yield case #retourne chaque case c'est un generator
def reset(self):
self.__init__(self.terrain,self.hauteur) #:B
#Question : si j'enléve object et les paranthéses ça change quoi?
class Case(object):
'''Une case du jeu'''
def __init__(self,etat,coords):
'''etat : miné , déminé , découvert ou pas , coords: coordonnées de la case'''
self.etat=etat
self.coords=coords
self.x=coords[0] #abscisse
self.y=coords[1] #ordonnée
def setEtat(self,newEtat): #c'est un mutateur (j'ai bien appris mon cours!)
self.etat=newEtat
def __getitem__(self, key):
'''methode speciale, , a=Plateau(),
cette methode permet de faire par exemple :
"x=a[0]"
ici elle devra retourner l'element de self.plateau demande'''
return self.coords[key] #retourne la case demande
def __repr__(self):
'''lorsque qu'on retourne l'objet , l'etat est affiché'''
return self.etat
#INTERFACE
class Interface :
'''Interface rudimentaire du démineur'''
def __init__ (self,plateau):
print "-"*25+"DEMINEUR"+"-"*25+"\n"+"-"*60+"\n\
Menu Principal : %c:mine , %c:terre , %c:terre déminée\n"%(EXPLOSE,TERRE,DEMINE)+"-"*60+"\n"
self.plateau=plateau
def getPlateau(self):
'''c'est un ascessseur qui permet d'affichezr le plateau et de l'actualiser!'''
chaine=""
nc=0
for c in self.plateau :
if (nc==int(c[1])): #je met tout dans une chaine comme ça ça m'affichera un tableau , c[1] c'est la coordonnée en Y et c[0] en x
nc+=1
chaine+="\n"+" "*30 #donc a chaque fois que l'ordonnée change on saute une ligne!
if str(c)==MINE: #pour ne pas afficher les mines au joueur ! On affiche alors de la terre pour qu'il se fasse piégé *vv*
chaine+=TERRE
else:
chaine+=str(c) #bon sion on affiche ce qu'il y a donc logiquement de la terre!
chaine+="\n" #enfin je rajoute un saut de ligne pour que ça soit un peut aéré!
print chaine
def aide(self):
'''Permet d'afficher l'aide au joueur'''
print "AIDE : Le but est de dévoiler toutes les cases sans mines \n"
def creuser(self,reponse=False):
'''La méthode qui nous permet de faire l'action principale : creuser !'''
if reponse: #reponse permet si elle est vraie (True) donner la solution de la partie
for c in self.plateau: #on parcours toutes les cases du plateau courant
if str(c)==MINE : #si la case est une mine on la met a l'état explosé (oui sinon dans getPlateau les mines ne peuvent pas être affiché .. ça serait peut être mieux de faire la langochat au niveau de getPlateau?
c.setEtat(EXPLOSE)
else :
c.setEtat(DEMINE) #Ca c'est facultatif mais c'est pour suivre ma logique : si j'afffiche les cases explosés alors il faut montrer celle qui ne le sont pas (parce que la terre par definition peut cacher des mines)
else:
coords=raw_input("Veuillez rentrez la valeur de l'abscisse et de l'ordonnée de la case à creuser sous la forme : X*Y \n")
coordsDe=coords.split('*') #On décompose la chaine rentré dans une liste avec le séparateur * donc :
x=int(coordsDe[0]) #l'absciesse c'est le premier element de la liste , on n'oublie pas de la mettre en entier
y=int(coordsDe[1]) #l'ordonnée est le second et dernier élement de la liste , on n'oublie pas encor de le mettre en entier
for c in self.plateau: #on parcours les cases du plateau
if c[0]==x and c[1]==y and str(c)==MINE or str(c)==EXPLOSE : #si la valeur d'abscissse correspond à la valeur d'abscisse d'une instance de Case (idem pour y) et si l'etat est miné (ou explosé au cas ou si on creuse une autre fois la mine)
chaine= "vous etes tombez sur une mine :(" #on affiche un bien triste message
c.setEtat(EXPLOSE) #et on met l'etat explosé a notre case
break #et on sort de cette boucle (element trouvé!
elif c[0]==x and c[1]==y:
chaine= "pas de mine en vue!"
c.setEtat(DEMINE) #ben là on a de la chance , pas de mine donc on met l'état deminé
break
else :
chaine="La case demandée n'existe pas :B" #et sinon le joueur est mauvais en math dans ce cas on affiche ceci
print chaine #et on affiche la reponse
#PRG PRINCIPAL
#Initialisation du pateau de jeu et de l'interface par instanciation de classes
jeu=Plateau(terrain_alea(100,5),10)
interface=Interface(jeu)
interface.getPlateau()
#On fait une boucle infini qui attend a chaque fois la réponse de l'utilisateur
while(1):
attente=raw_input("(a)ide \n(c)reuser\n(l)angochat\n(r)ecommencer\n(*q)uitter\n ")
#en fonction de la reponse on execute une action
if attente=='a':
interface.aide()
interface.getPlateau()
elif attente=='c':
interface.creuser()
interface.getPlateau()
elif attente=='l':
interface.creuser(True) #True pour le parametre reponse du module creuser
interface.getPlateau()
elif attente=='r':
jeu.reset()
interface.getPlateau()
else :
break; #Byebye
#Je débute en POO , et j'aimerais voir les améliorations possibles , les critiques et les erreurs relatives mon code pour que je m'améliore par la suite , je trouve difficile mais j'en vois l'utilité de séparé le code de l'interface \
#et ce qui me titille c'est que par exemple le module creuser est dans l'INTERFACE mais devrait il pas être dans la partie CODE , merci de votre aide!
Ah je ne voyez pas les règles de la même façon ( dans la règle du démineur il n'est pas obligatoire de ce dép lasser de 1 en 1 on peut se "téléporter" et le démineur n'apparait pas sur la carte enfin ces "pas" aparaissent )
j'ai continué mon petit démineur en reprenant tes images Nyko77 (ne m'en veux pas, elles sont super bien faites )
Je rencontre un problème, d'ordre technique qui est le suivant:
Dans ma classe <JeuTk>, lorsque je clic-gauche ou clic-droit, j'appelle deux fonctions où l'une est associée au clic-G et l'autre au clic-D.
J'aimerai qu'elles appellent une seule et même fonction qui, pourrait savoir si tel ou tel bouton à été pressé. (Clic-G ? Clic-D ?)
Auriez-vous une solution à me proposer?
Merci d'avance
P.S: Je fais ça pour éviter de rafraîchir à chaque fois que je clic sur une case.
EDIT: Ma question est: Comment cette fonction (qui est appelée grâce au clicG ou clicD) peut savoir si c'est soit le clic-G ou soit le clic-D qui a été pressé?
Un bout de mon code:
self.can.bind("<Button-1>", self.a_dePlateau.creuser) #Creuser
self.can.bind("<Button-3>", self.a_dePlateau.poserDrapeau) #Pose un drapeau
Je préférerai ceci:
self.can.bind("<Button-1>", self.afficher)
self.can.bind("<Button-3>", self.afficher)
def afficher(self):
Si c'est un clic-G:
#Exécuter ce bout de code
Sinon si c'est un clic-D:
#Exécuter ce bout de code
En tout cas merci, ça marche.
Voici mon code complet, bien entendu l'ergonomie est plutôt moche par rapport à Nyko77 et Josmiley; mais ont s'y fait vite.
Il me reste plus qu'a trouver comment mettre une image sur les boutons et à afficher "gagner" ou "perdu".
################################
## Les Modules Importés:(2) ##
#
from tkinter import *
import random
import math
#-*-*- <Les Classes> || Début de <Jeu> -*-*-#
CLICK_G, CLICK_D = range(2)
class Jeu(object):
"""Permet de gérer la partie."""
def __init__(self):
"""
Instancie un objet du type <Plateau>.
Déclare les variables nécessaires.
- a_dePlateau (type:-Instance-)
- partieEnCours (type:-Booleen-)
Créer les événements:
- Boutons
- Canvas
- Gestion des clics
"""
#Variables nécessaires pour le déroulement de la partie
self.a_dePlateau = Plateau()
#self.partieEnCours = True
#Création des fenêtres
self.fenetre = Tk()
#Canvas
self.can = Canvas(self.fenetre, bg = "#004899", height = self.a_dePlateau.largF, \
width = self.a_dePlateau.largF, relief = GROOVE, bd = 3)
self.can.bind("<Button-1>", lambda event: self.afficher(event, CLICK_G))
self.can.bind("<Button-3>", lambda event: self.afficher(event, CLICK_D))
self.can.pack(side = LEFT)
#Boutton Quitter
bouQuitter = Button(self.fenetre, text = "Quitter", command = self.fenetre.destroy)
bouQuitter.pack(side = BOTTOM)
#Boutton Jouer
bouJouer = Button(self.fenetre, text = "Grille (10 Mines)", command = self.jouer)
bouJouer.pack(side = BOTTOM)
#Boutton Jouer
bouJouer_25 = Button(self.fenetre, text = "Grille (20 Mines)", command = self.partie_20Mines)
bouJouer_25.pack(side = BOTTOM)
#Boutton Regle
bouRegle = Button(self.fenetre, text = "Règles", command = self.regle)
bouRegle.pack(side = BOTTOM)
#Images -Chargement
self.p_case = PhotoImage(file = "caseCover.gif")
self.p_vide = PhotoImage(file = "caseEmpty.gif")
self.p_croix = PhotoImage(file = "caseFlag.gif")
self.p_1 = PhotoImage(file = "numero_1.gif")
self.p_2 = PhotoImage(file = "numero_2.gif")
self.p_3 = PhotoImage(file = "numero_3.gif")
self.p_4 = PhotoImage(file = "numero_4.gif")
self.p_5 = PhotoImage(file = "numero_5.gif")
self.p_6 = PhotoImage(file = "numero_6.gif")
self.p_7 = PhotoImage(file = "numero_7.gif")
self.p_8 = PhotoImage(file = "numero_8.gif")
def jouer(self):
"""Toutes les fonctions qui doivent être
appellées pour jouer sont mises ici.
"""
self.can.delete(ALL)
self.a_dePlateau.mappCachee = []
self.a_dePlateau.mapp = []
self.a_dePlateau.nombreMines = 0
self.a_dePlateau.genererGrille()
self.a_dePlateau.cacherGrille()
def partie_20Mines(self):
"""Permet de jouer une partie avec 20 mines"""
self.a_dePlateau.placementMines = 20
self.jouer()
def etatDuJeu(self):
"""Le joueur a t-il gagner ?
Appel de la fméthode *initialisation*
"""
if self.a_dePlateau.perdu == False:
if self.a_dePlateau.nombreMines <= 0:
#Si /mapp/ et /mappCachee/ sont identiques alors c'est gagné !
if self.a_dePlateau.mappCachee == self.a_dePlateau.mapp:
print("Gagne")
self.initialisation()
elif self.a_dePlateau.mappCachee != self.a_dePlateau.mapp or self.a_dePlateau.perdu == True:
print("Perdu")
self.initialisation()
else:
print("Perdu")
self.initialisation()
def initialisation(self):
"""Initialise les données du jeu.
Cette méthode est appelée dans *etatDuJeu*
"""
self.can.delete(ALL)
self.a_dePlateau.mappCachee = []
self.a_dePlateau.mapp = []
self.a_dePlateau.nombreMines = 0
self.a_dePlateau.perdu = False
self.jouer()
def afficher(self, event, clic):
"""Affichage graphique avec Tkinter
Appel de la méthode *etatDuJeu*
"""
if clic == CLICK_G:
self.a_dePlateau.creuser(event)
elif clic == CLICK_D:
self.a_dePlateau.poserDrapeau(event)
for i in range(self.a_dePlateau.largBloc):
for j in range(self.a_dePlateau.largBloc):
if self.a_dePlateau.mappCachee[i][j] == 10:
item = self.can.create_image((i * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
(j * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
image = self.p_case)
elif self.a_dePlateau.mappCachee[i][j] == 0:
item = self.can.create_image((i * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
(j * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
image = self.p_vide)
elif self.a_dePlateau.mappCachee[i][j] == 1:
item = self.can.create_image((i * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
(j * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
image = self.p_1)
elif self.a_dePlateau.mappCachee[i][j] == 2:
item = self.can.create_image((i * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
(j * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
image = self.p_2)
elif self.a_dePlateau.mappCachee[i][j] == 3:
item = self.can.create_image((i * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
(j * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
image = self.p_3)
elif self.a_dePlateau.mappCachee[i][j] == 4:
item = self.can.create_image((i * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
(j * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
image = self.p_4)
elif self.a_dePlateau.mappCachee[i][j] == 5:
item = self.can.create_image((i * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
(j * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
image = self.p_5)
elif self.a_dePlateau.mappCachee[i][j] == 6:
item = self.can.create_image((i * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
(j * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
image = self.p_6)
elif self.a_dePlateau.mappCachee[i][j] == 7:
item = self.can.create_image((i * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
(j * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
image = self.p_7)
elif self.a_dePlateau.mappCachee[i][j] == 8:
item = self.can.create_image((i * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
(j * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
image = self.p_8)
elif self.a_dePlateau.mappCachee[i][j] == 9:
item = self.can.create_image((i * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
(j * self.a_dePlateau.tailleBloc + self.a_dePlateau.largBloc), \
image = self.p_croix)
self.etatDuJeu()
def regle(self):
a = """
Le but du jeu du démineur est de trouver toutes les mines présentes sur le terrain.
Composé de cases, il vous suffit de cliquer dessus pour dévoiler cette même case.
Le terrain est chiffré, et chaque chiffre indique le nombre de bombes présentes dans les huit cases qui l'entoure:\n\n
Action:
<Clic-Gauche> = Dévoiler une case
<Clic-Droit> = Poser un drapeau
0123456789 = nombre de bombes dans les huit cases adjacentes
"""
#Fenêtre
fenetreRegle = Tk()
#Label
texteRegle = Label(fenetreRegle, text = a)
texteRegle.pack()
fenetreRegle.mainloop()
#-*-*- Fin de <Jeu> || Début de <Plateau> -*-*-#
class Plateau(object):
"""Permet de tenir à jour le plateau du jeu 'démineur'."""
def __init__(self):
"""
Déclare les variables nécessaires.
- mapp (type:-Liste-)
- longueur (type:-Int-) (Nombre de cases: longueur²)
- nombreMines (type:-Int-)
- mappCachee (type:-Liste-)
- nombreDrapeau (type:-Int-)
"""
self.mapp = []
self.mappCachee = []
self.nombreMines = 0
self.placementMines = 10 #Combien de mines doit on placer dans une partie
#Variables pour l'interface graphique
self.tailleBloc = 30
self.largBloc = 10 #Longueur de la grille: 10²
self.largF = self.largBloc * self.tailleBloc
self.perdu = False
def genererGrille(self):
"""Génère une grille de démineur.
Appel *placerBombe()*
Appel *cacherGrille()*
"""
#On construit un tableau de 0 de /longueur/²
for i in range(self.largBloc):
self.mapp.append([0] * self.largBloc)
for i in range(self.placementMines):
#Appel de la fonction: placerBombe() en mettant à jour: /mapp/
self.mapp = self.placerBombe()
def placerBombe(self):
"""Génère des nombres aléatoires
pour placer une bombe et appel *chiffrerMap()*.
"""
#On incrémente /nombreMines/ à chaque instance
self.nombreMines += 1
#On pose une bombe 9 aléatoirement dans la /mapp/ suivant la longueur de celle-ci ->(self.largBloc)
#Le numéro 9 (type:int) représente une bombe
self.col, self.ligne, self.bombe = random.randrange(0, self.largBloc), random.randrange(0, self.largBloc), 9
#On place la bombe aux coordonnées trouvées aléatoirement
self.mapp[self.col][self.ligne] = self.bombe
#Appel de la fonction chiffrerMap
self.mapp = self.chiffrerMap(self.col, self.ligne)
return self.mapp
def chiffrerMap(self, col, ligne):
"""Permet de chiffrer ma /mapp/ pour
savoir où se trouvent les bombes.
"""
#Implémentation de l'algorithme pour chiffrer ma map
#[[0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0],
# [0, 0, 9, 0, 0],
# [0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0]]
#On parcours notre mapp à l'aide d'une double boucle
#On ajoute dans une liste à part les 8 cases entourant la case actuelle
#On regarde combien il y a de 9 dans cette /newL/.
#La case actuelle prendra la valeur du nombre d'occurences de 9 dans ma /newL/
for i in range(self.largBloc):
for j in range(self.largBloc):
newL = []
for ii in range(max(0, i-1), min(i+2, self.largBloc)):
for jj in range(max(0, j-1), min(j+2, self.largBloc)):
newL.append(self.mapp[ii][jj])
if self.mapp[i][j] != 9: #Le numéro 9 (type:int) représente une bombe
self.mapp[i][j] = newL.count(9)
newL = []
return self.mapp
def cacherGrille(self):
"""Cache la /mapp/ de jeu en créant une mapp-like
avec des 10 pour valeur (représente '*').
"""
#On construit un tableau de 10 (*) de /largBloc/²
for i in range(self.largBloc):
self.mappCachee.append([10] * self.largBloc)
def poserDrapeau(self, event):
"""Permet de poser un drapeau sur une case donnée.
Si: La case est cachée:
- On place le drapeaux '@'
Sinon si: La cases contient déjà un drapeau:
- On enlève le drapeau '@' et on la met en cachée 10 ('*')
Sinon: La case à déjà été découverte:
- Ne rien faire 'pass'
"""
#On créer une variable /var/ qui prend pour valeur une
#...céllule rentrée par l'utilisateur dans la fonction *action*
#Sans oublier d'incrémenter ou de décrémenter /nombreMines/
var = self.mappCachee[math.ceil(event.x / self.tailleBloc)-1][math.ceil(event.y / self.tailleBloc)-1]
if var == 10:
var = 9
self.nombreMines -= 1
elif var == 9:
var = 10
self.nombreMines += 1
else:
pass
#On change le '*' de /mappCachee/ par la valeur de /var/ (type:string)
self.mappCachee[math.ceil(event.x / self.tailleBloc)-1][math.ceil(event.y / self.tailleBloc)-1] = var #####
def creuser(self, event):
"""Permet de dévoiler une case dans
la variable /mappCachee/
"""
#Je créer une variable /var/
#Qui prend comme valeur les coordonées de ma souris dans /mapp[event.x][event.y]/
#On divise par /tailleBloc/ pour obtenir un nombre entier en /larBloc/ pour la longueur et /largBloc/ pour la hauteur
#- si la case contient une bombe:
# - STOP
#- si la case est déjà creusée:
# - Game over puis on stop la fonction
#- sinon on retire le masque de la case str() pour pouvoir l'afficher
#- Si la case vaut 0
# - On regarde autour de cette case s'il y a d'autres 0
# - Appel récursif de la fonction
#
#-1 car sinon lors du clic index is out of range -> [math.ceil(event.x / self.tailleBloc)-1][math.ceil(event.y / self.tailleBloc)-1]
var = self.mapp[math.ceil(event.x / self.tailleBloc)-1][math.ceil(event.y / self.tailleBloc)-1]
if var == 9:
self.mappCachee[math.ceil(event.x / self.tailleBloc)-1][math.ceil(event.y / self.tailleBloc)-1] = var
self.perdu = True
return
if self.mappCachee[math.ceil(event.x / self.tailleBloc)-1][math.ceil(event.y / self.tailleBloc)-1] == var:
return
else:
self.mappCachee[math.ceil(event.x / self.tailleBloc)-1][math.ceil(event.y / self.tailleBloc)-1] = var
if var == 0:
#On regarde autour de cette case s'il y a d'autres 0
for ii in range(max(0, math.ceil(event.x / self.tailleBloc)-1), min(math.ceil(event.x / self.tailleBloc)+2, self.largBloc)):
for jj in range(max(0, math.ceil(event.y / self.tailleBloc)-1), min(math.ceil(event.y / self.tailleBloc)+2, self.largBloc)):
if self.mapp[ii][jj] == 0:
self.creuser2(ii, jj)
def creuser2(self, l, c):
"""Pour l'appelle récursif:
l = ligne
c = colonne
"""
var = self.mapp[l][c]
if var == 9:
self.mappCachee[l][c] = var
self.perdu = True
return
if self.mappCachee[l][c] == var:
return
else:
self.mappCachee[l][c] = var
if var == 0:
#On regarde autour de cette case s'il y a d'autres 0
for ii in range(max(0, l-1), min(l+2, self.largBloc)):
for jj in range(max(0, c-1), min(c+2, self.largBloc)):
if self.mapp[ii][jj] == 0:
self.creuser2(ii, jj)
if __name__ == '__main__':
p = Jeu()
p.fenetre.mainloop()
× 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.
Python c'est bon, mangez-en.
Python c'est bon, mangez-en.