Memory
Le Memory est un jeu de paires : les cartes sont posées face cachée ; le joueur en retourne deux à la fois ; si elles sont identiques, il les conserve face visible ; sinon, elles sont remises face cachée. Le but est de trouver toutes les paires.
Pré-requis
Pas de pré-requis spécifique. On manipule ici des listes, des tuples et des variables globales.
Installe metagrid dans le terminal : uv add metagrid
Grille 5 x 5
Peut-on jouer au Memory sur une grille de 5 x 5 ?
- Combien de cases contient une grille 5 x 5 ?
- Pour constituer des paires, de combien de cases a-t-on besoin ?
- Qu'est-ce que cela implique pour la taille d'une grille de Memory ?
Crée un fichier memory.py dans un dossier contenant un sous-dossier images/ avec les fichiers 0.png, 1.png, ..., 7.png fournis par le professeur.
"""
Memory - jeu de mémoire avec images.
"""
import metagrid
from random import shuffle, randint
HAUTEUR_GRILLE = 4
LARGEUR_GRILLE = 4
TAILLE_CASE = 100
NB_IMAGES = HAUTEUR_GRILLE * LARGEUR_GRILLE // 2
DUREE = 120 # nombre de frames pendant lesquels les cartes restent visibles
COULEUR_CACHEE = "#888888"
grille: list[list[int]]
cases_trouvees: list[tuple[int, int]]
cases_devoilees: list[tuple[int, int]]
timer: int
jeu: metagrid.AbstractEngine
def init():
global grille, cases_trouvees, cases_devoilees, timer
grille = [[0] * LARGEUR_GRILLE for _ in range(HAUTEUR_GRILLE)]
cases_trouvees = []
cases_devoilees = []
timer = 0
def update():
global timer, cases_devoilees
pass
def cliquer(i: int, j: int, _bouton: str):
global cases_devoilees, cases_trouvees, timer
pass
def gagne() -> bool:
pass
def draw():
for i in range(HAUTEUR_GRILLE):
for j in range(LARGEUR_GRILLE):
jeu.set_cell_image(i, j, str(grille[i][j]))
if __name__ == "__main__":
jeu = metagrid.create(HAUTEUR_GRILLE, LARGEUR_GRILLE, TAILLE_CASE, 4)
for k in range(NB_IMAGES):
jeu.load_image(str(k), f"images/{k}.png")
jeu.on_init(init)
jeu.on_update(update)
jeu.on_click(cliquer)
jeu.on_draw(draw)
jeu.start()
Étape 1 - Afficher la grille avec des images
Les images chargées avec load_image sont accessibles par leur clé (une chaîne : "0", "1", ...). La grille contient des entiers : grille[i][j] est le numéro de l'image à afficher en case (i, j).
La fonction draw est déjà écrite : elle affiche l'image str(grille[i][j]) dans chaque case.
La fonction init est déjà écrite : elle remplit toute la grille avec l'image 0.
Lance le programme. Toutes les cases doivent afficher la même image.
Exercice 1 - Images aléatoires
Modifie init pour qu'elle remplisse la grille avec des images choisies aléatoirement parmi les NB_IMAGES disponibles.
def init():
global grille, cases_trouvees, cases_devoilees, timer
grille = [[randint(..., ...) for _ in range(LARGEUR_GRILLE)]
for _ in range(HAUTEUR_GRILLE)]
cases_trouvees = []
cases_devoilees = []
timer = 0
Lance le programme : chaque case doit afficher une image aléatoire (pas forcément en paires).
Exercice 2 - Images en paires
Pour qu'il y ait exactement deux exemplaires de chaque image, on prépare une liste
[0, 0, 1, 1, 2, 2, ..., NB_IMAGES-1, NB_IMAGES-1], on la mélange avec shuffle, puis on remplit la grille case par case.
def init():
global grille, cases_trouvees, cases_devoilees, timer
paires = []
for k in range(NB_IMAGES):
paires.append(k)
paires.append(k)
shuffle(paires)
grille = []
for i in range(HAUTEUR_GRILLE):
ligne = []
for j in range(LARGEUR_GRILLE):
ligne.append(paires[...]) # quel indice utiliser ?
grille.append(ligne)
cases_trouvees = []
cases_devoilees = []
timer = 0
Quel indice utiliser dans paires[...] pour parcourir les éléments de paires dans l'ordre, case par case ?
Étape 2 - Masquer les cases
Pour l'instant, toutes les images sont visibles. Dans le vrai Memory, les cartes sont retournées face cachée. Une case doit être visible uniquement si :
- elle est dévoilée (ses coordonnées sont dans
cases_devoilees), ou - elle a été trouvée (ses coordonnées sont dans
cases_trouvees).
Sinon, on la colorie en gris avec jeu.set_cell_color(i, j, COULEUR_CACHEE).
Modifier draw
Complète la fonction draw :
def draw():
for i in range(HAUTEUR_GRILLE):
for j in range(LARGEUR_GRILLE):
if ... or ...:
jeu.set_cell_image(i, j, str(grille[i][j]))
else:
jeu.set_cell_color(i, j, COULEUR_CACHEE)
Lance le programme : toute la grille doit apparaître en gris puisqu'aucune case n'est encore dévoilée ni trouvée.
Étape 3 - Dévoiler les cases
Cliquer pour dévoiler
Une case est représentée par ses coordonnées sous forme de tuple (i, j).
Ajouter une case à cases_devoilees
Lorsqu'une case (i, j) est cliquée, on l'ajoute à cases_devoilees si elle n'est ni déjà dévoilée, ni déjà trouvée.
def cliquer(i: int, j: int, _bouton: str):
global cases_devoilees, cases_trouvees, timer
if (i, j) in cases_devoilees or (i, j) in cases_trouvees:
return
cases_devoilees.append(...)
Teste : cliquer sur une case doit la révéler.
Limiter à 2 cases dévoilées
On ne peut pas dévoiler plus de 2 cases en même temps. Ajoute une garde en début de cliquer :
def cliquer(i: int, j: int, _bouton: str):
global cases_devoilees, cases_trouvees, timer
if len(cases_devoilees) >= 2:
return
...
Teste : après avoir cliqué deux cases, un troisième clic ne doit rien faire.
Démarrer le timer
Quand deux cases sont dévoilées et différentes, elles doivent se recouvrir après un moment.
Démarrer le timer dans cliquer
Après avoir ajouté la case, si cases_devoilees contient maintenant exactement 2 éléments et que le timer est à 0, on démarre le timer :
Décrémenter le timer
Compléter update
À chaque frame, si le timer est strictement positif, on le décrémente. Quand il atteint 0, on vide cases_devoilees :
def update():
global timer, cases_devoilees
if timer > 0:
timer -= 1
if timer == 0:
cases_devoilees = []
Teste : retourner deux cases différentes, attendre quelques secondes - elles doivent se recouvrir.
Étape 4 - Découvrir les paires
Quand le joueur retourne deux cases portant la même image, elles doivent rester visibles définitivement.
Détecter une paire dans cliquer
Juste avant de démarrer le timer, si les deux cases dévoilées ont le même numéro d'image, on les ajoute à cases_trouvees et on vide cases_devoilees (pas besoin de timer dans ce cas) :
if len(cases_devoilees) == 2:
(i1, j1), (i2, j2) = cases_devoilees
if grille[i1][j1] == grille[i2][j2]:
cases_trouvees.extend(cases_devoilees)
cases_devoilees = []
elif timer == 0:
timer = DUREE
Teste : trouver une paire doit la laisser visible ; une mauvaise paire doit se recouvrir après quelques secondes.
Étape 5 - Gagner
Compléter gagne
On gagne quand toutes les cases ont été trouvées, c'est-à-dire quand cases_trouvees contient autant d'éléments que la grille.
Vérifier la victoire dans cliquer
Après avoir détecté une paire, vérifie si la partie est gagnée et affiche un message :
if grille[i1][j1] == grille[i2][j2]:
cases_trouvees.extend(cases_devoilees)
cases_devoilees = []
if gagne():
print("Bravo, vous avez gagné !")
Lance le programme et joue une partie complète. Retrouve toutes les paires pour vérifier que le message s'affiche.
Pour aller plus loin
- Ajoute la touche
rpour relancer une partie (jeu.on_key(touche)avec une fonctiondef touche(key: str)qui appelleinit()sikey == 'r'). - Ajoute un compteur de coups (nombre de paires retournées) affiché dans la console.
- Essaie avec une grille 6 x 4 en ajoutant des images supplémentaires.
- Ajoute un "dos de carte" : charge une image
"dos"et utilise-la à la place de la couleur grise pour les cases cachées.