DS 2 : code
- François Brucker
Le but de ce DS est de coder un 2048.
Consignes
Durée du contrôle : 3 heures.
- Faites les questions dans l'ordre
- n'inventez pas votre propre structure, suivez celle demandée
- rendez sur Amétice un dossier contenant tout votre code ainsi qu'un fichier texte contenant les numéros des questions auxquelles vous avez répondu
Vous aurez besoin de la bibliothèque pyglet pour coder les fenêtres et gérer les évènements claviers (avec la méthode on_key_press
) et souris (avec la méthode on_mouse_press
). Nous n'aurons pas besoin de gérer le temps dans ce DS.
Toutes les informations nécessaires pour ce DS ont été prises de :
Commencez par bien lire la page wikipedia précédente et faites une ou deux partie pour bien comprendre comment fonctionne le jeu.
Documents à rendre
Un dossier contenant les divers fichiers python dont vous avez eu besoin. Ce dossier doit contenir un fichier main.py
qui contient votre jeu.
Sujet
Le projet a été découpé en plusieurs étapes que l'on vous demande de suivre.
Vous indiquerez en commentaire de votre ficher main.py
l'étape à laquelle vous vous êtes arrêté.
Chaque étape est un jalon du projet et n'est pas triviale. Que cela vous prenne un peu de temps pour le faire est normal.
Étape 1 : le fichier main.py
Cette étape va créer l'aire de jeu. À l'exécution du fichier main.py
, vous devez obtenir la fenêtre suivante :
Les couleurs (au format RGBA, le dernier paramètre étant la transparence) sont les suivantes :
- le fond :
(185, 173, 161, 255)
- les cases :
(202, 193, 181, 255)
Les 16 cases de la grille (une matrice 4x4) sont séparées par des marges de 4% de la taille du fond (32 pixels de marge pour une taille de 800 pixels par exemple).
main.py
Le fichier qui exécutera votre programme est le suivant :
import pyglet
from grille import Grille
class DeuxMilleQuaranteHuit(pyglet.window.Window):
def __init__(self):
super().__init__(800, 800, "2048")
self.grille = Grille(800)
def on_draw(self):
self.grille.draw()
window = DeuxMilleQuaranteHuit()
pyglet.app.run()
Vous modifierez la classe DeuxMilleQuaranteHuit
lorsque vous aurez besoin de gérer les évènements.
C'est dans la classe DeuxMilleQuaranteHuit
que vous placerez les fonctions de gestion des évènements comme on_mouse_press(self, x, y, button, modifiers)
à l'étape 2 ou on_key_press(self, symbol, modifiers)
à l'étape 5.
La classe Grille
Implémentez les classes Grille
et Carré
en respectant le diagramme UML suivant :
L'attribut cases
de la grille est une matrice de Carré
. La taille de chaque carré est identique et est calculée de telle sorte que la matrice (carrés et bords) remplisse la fenêtre.
Étape 2
Pour pouvoir manipuler les cases de la grille, nous allons ajouter plusieurs méthodes. On ne pourra tester nos méthodes qu'à la toute fin de l'étape, lorsque l'on utilisera l'évènement qui gère les cliques de souris.
Commençons par pouvoir cliquer sur une case précise.
Méthode intérieur(x, y)
pour les Carré
Ajoutez une méthode intérieur(x, y)
à la classe Carré
qui rend :
True
si le point de coordonnées $(x, y)$ est dans le rectangle,False
sinon.
Vous pourrez utiliser les attributs x
y
, width
et height
de la classe pyglet.shapes.rectangle
.
Méthode intérieur(x, y)
pour la Grille
Ajoutez une méthode intérieur(x, y)
à la classe Grille
qui rend :
(i, j)
si le point de coordonnées $(x, y)$ est dans le rectangle de la casecases[i][j]
,None
si le point n'est dans aucune case de la matricecases
.
Cliquer sur une case de la matrice
Utilisez l'évènement on_mouse_press(self, x, y, button, modifiers)
pour afficher sur le terminal avec la fonction print
:
- "je suis dans la case de coordonnée i, j" si le point de coordonnée $(x, y)$ est dans
cases[i][j]
, - "je ne suis pas dans une case de la matrice" si l'on a pas cliqué sur une case.
Étape 3
Gestion des valeurs des cases.
Classe Case
Implémentez la classe Case
d'UML suivant :
L'attribut valeur peut valoir toutes les puissances de 2, allant de 1 à 2048 et est représenté graphiquement par un label de taille 40. Si la valeur vaut 1, elle n'est pas représentée graphiquement (c'est la case vide).
- Classe
pyglet.text.Label
- Les couleurs des cases pour les différentes valeurs :
(202, 193, 181, 255)
pour une valeur de 1(238, 228, 218, 255)
pour une valeur de 2(237, 224, 200, 255)
pour une valeur de 4(242, 177, 121, 255)
pour une valeur de 8(245, 149, 99, 255)
pour une valeur de 16(246, 124, 95, 255)
pour une valeur de 32(246, 94, 59, 255)
pour une valeur de 64(237, 207, 114, 255)
pour une valeur de 128(237, 204, 97, 255)
pour une valeur de 256(237, 200, 80, 255
pour une valeur de 512(237, 197, 63, 255)
pour une valeur de 1024(237, 194, 46, 255)
pour une valeur de 2048
Design pattern factory
Lors de la création de la classe Grille
on utilise des maintenant des objets de type Case
de valeur 1 et non plus des Carré
. Pour que ce soit facile à faire, faite un design pattern factory pour créer des objets de valeur 1. La signature de ce factory doit être :
vide(x, y, taille)
Vérification
Lors de la création de la fenêtre, modifiez la case de la ligne d'indice 2 et de colonne 1 de la matrice pour que ce soit un objet de la classe Case
de valeur 512 (vous pourrez tester plusieurs valeurs pour vérifier que tout fonctionne)
Étape 4
Nous allons préparer la modification dynamique des cases en utilisant l'évènement on_mouse_press(self, x, y, button, modifiers)
.
Cette étape n'est pas utile pour le jeu en lui-même, mais la coder vous aidera à avancer. Conservez cette partie même si vous allez plus loin.
Méthode set_valeur(v)
de la classe Case
Le jeu du 2048 passe son temps à modifier les valeurs des cases. Implémentez une une méthode set_valeur(v)
à la classe Case
dont la fonction est de modifier la valeur de la case puis de changer son fond et son label en conséquence.
Utilisez cette méthode dans le constructeur de Case
.
Méthodes incrémente()
et décrémente()
de la classe Case
Ajoutez les méthodes incrémente()
et décrémente()
à la classe Case
dont le rôle est de modifier la valeur de la case en l'augmentant (on multiplie la valeur par 2 si elle ne vaut pas 2048) ou la diminuant (on divise la valeur par 2 si elle ne vaut pas 1).
Cliques gauche et clique droit
Modifiez la gestion du clique de souris pour que :
- le clique gauche augmente la valeur de la case sur laquelle on clique
- le clique droit diminue la valeur de la case sur laquelle on clique
Si vous avez un mac, vous pouvez soit simuler le clique droit soit l'activer en suivant cette aide d'apple.
Étape 5
Tout est en place pour implémenter la mécanique de jeu.
Lorsque l'on appuie sur une flèche de direction, les cases de valeurs strictement plus grandes que 1 se déplacent, une à une, dans le sens de la flèche. L'ordre dans lequel les cases se déplacent est important :
- lorsque l'on appuie sur la flèche de droite, pour chaque ligne, on déplace les cases en commençant par celle le plus à droite
- lorsque l'on appuie sur la flèche de gauche, pour chaque ligne, on déplace les cases en commençant par celle le plus à gauche
- lorsque l'on appuie sur la flèche du bas, pour chaque colonne, on déplace les cases en commençant par celle la plus basse
- lorsque l'on appuie sur la flèche du haut, pour chaque colonne, on déplace les cases en commençant par celle la plus haute
Une case ne peut se déplacer que si la case vers la quelle elle se déplace est vide (de valeur 1) ou de même valeur qu'elle même.
Déplacement vers une case adjacente
Commencez par gérer le déplacement vers une case adjacente dans la direction souhaitée en utilisant l'évènement on_key_press(self, symbol, modifiers)
.
Vous tenterez de déplacer toutes les cases, une à une dans le bon ordre, d'une seule case dans la direction souhaitée. Par exemple si la matrice contient les valeurs suivante :
2114
2221
1111
1141
L'appui sur une touche de direction produira les matrices ci-dessous :
4224
1111
1141
1111
^
haut
2141 2114 1214
4211 < gauche 2221 droite > 1222
1111 1111 1111
1411 1141 1114
bas
v
1111
2114
2221
1111
Toutes les cases de valeurs strictement plus grandes que 1 se sont déplacés si possible, soit vers une case libre adjacente soit vers une case de même valeur et ont fusionné.
Ajout d'une nouvelle case
Si l'appui sur une touche de direction a effectivement déplacé une case de valeur strictement plus grande que 1, une case de valeur 1 est choisie aléatoirement puis :
- 80% du temps elle est incrémentée une fois (ce qui est equivalent à ajouter une case de valeur 2)
- 20% du temps elle est incrémentée deux fois (ce qui est equivalent à ajouter une case de valeur 4)
Vous pourrez utiliser les méthodes suivantes du module random de python
Étape 6
Déplacement complet.
Déplacement de plusieurs cases
Les cases peuvent se déplacer de plusieurs cases vides, si possible. La façon la plus simple de réaliser cette opération est d'effectuer l'opération de déplacement précédente (déplacements vers une case adjacente) 3 fois de suite.
Nombre d'incrémentations
Une même case ne peut s'incrémenter qu'une seule fois par déplacement. Par exemple un déplacement vers la droite de la ligne : 1224
donne 1144
et non pas 1118
.
Gérer cette règle.
Vous pourrez faire cela en ajoutant un attribut à la classe Case
. Cet attribut est faux avant chaque déplacement et devient vrai après un incrément.
Une case ne peut fusionner qu'avec une case de même valeur et dont ce nouvel attribut est faux.
Étape 7
Détectez après un déplacement si la partie est terminée et affichez à sur le terminal avec la fonction print
si le jeu est fini.