DS 2 : code

Auteur :
  • François Brucker

Le but de ce DS est de coder un Tetris.

Consignes

Durée du contrôle : 2h50min.

  1. Faites les questions dans l'ordre
  2. n'inventez pas votre propre structure, suivez celle demandée
  3. 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 de pytest pour tester les fonctions utilitaires.

Toutes les informations nécessaires pour ce DS ont été prises du site hard drop qui contient toutes les ressources nécessaires pour comprendre et devenir meilleur à Tetris.

Corrigé

Le code complet est disponible à cette adresse

Question 1

But

Construire avec pyglet une fenêtre qui doit être semblable à celle ci-dessous :

fenêtre initiale

Pour cela, vous respecterez le schéma UML suivant :

UML initial

Informations :

Vous implémenterez :

Question 2

Avant de créer les tetrominos proprement dit, on va commencer par les simuler par une grille.

Question 2.1

But

Fonctions utilitaires de conversion.

Tout au long de ce projet, il faudra jongler entre les coordonnées (x, y) de la fenêtre et les coordonnées en ligne et colonne de la grille ou du tetromino. La figure ci-dessous montre les correspondances :

xy et lc

L'origine du repère de la fenêtre est en bas à gauche et toutes les formes pyglet dépendent d'elle, c'est ce que l'on appelle les coordonnées écrans (x, y) alors que chaque grille est organisée en coordonnées matricielles (ligne, colonne).

Créez un fichier utils.py où vous copierez les deux fonctions ci-dessous :

def xy_vers_lc(x, y, origine_grille_x, origine_grille_y, dimension_case, nombre_lignes_grille):
    ligne = nombre_lignes_grille - 1 - (y - origine_grille_y) // dimension_case
    colonne = (x - origine_grille_x) // dimension_case

    return (ligne, colonne)


def lc_vers_xy(ligne, colonne, origine_grille_x, origine_grille_y, dimension_case, nombre_lignes_grille):
    x = origine_grille_x + dimension_case * colonne
    y = origine_grille_y + dimension_case * (nombre_lignes_grille - 1 - ligne)

    return (x, y)

Ces deux fonctions permettent de faire les conversions entre les coordonnées écrans et matricielles :

Ces fonctions nécessitent les paramètres suivants :

Vous testerez ces deux fonctions dans le fichier test_utils.py, en vérifiant que :

Question 2.2

But

Intégration des utilitaires.

Ajoutez à la classe Grille les deux méthodes :

Vous utiliserez pour cela les fonctions lc_vers_xy et xy_vers_lc du fichier utils.py.

Question 2.3

But

Créer un tetromino fictif qui nous permettra de mettre en place la logique interne du jeu.

Ajoutez un attribut tetromino aux objets de type Tetris. Cet attribut sera une Grille :

fenêtre 2.3

Question 2.4

But

Mettez en place le moteur du jeu qui doit s'exécuter 60 fois par seconde.

Ce moteur doit pouvoir faire descendre l'attribut tetromino des objets de type Tetris d'une case vers le bas par seconde. Lorsque l'origine du tetromino touche le sol (le bas de la grille), il disparaît au bout de 500ms (ce temps d'attente est appelé lock delay). Il est remplacé par un nouveau tetromino placé à la position d'origine et le cycle recommence.

Pour cela, il vous sera nécessaire :

fenêtre 2.4

UML 2

À la fin de la question 2, vous devez avoir l'UML suivant :

UML question 2

Question 3

Gestion des touches de déplacements horizontaux, d'accélération et de chute.

Question 3.1

But

Lorsque l'on appuie sur la barre d'espace (hard drop), le tetromino est directement placé sur le sol s'il n'y était pas déjà.

Le hard drop étant une action, après un hard drop réussi, l'accumulateur est remis à zéro.

fenêtre 3.1

Question 3.2

But

Déplacements horizontaux du tetromino avec les touches "⇦" et "⇨".

Pour cela, il vous sera nécessaire de faire en sorte que :

Le déplacement horizontal étant une action, après un déplacement horizontal réussi, l'accumulateur est remis à zéro.

fenêtre 3.2

Remettre l'accumulateur à zéro après un déplacement horizontal réussi permet si on est assez rapide de se déplacer indéfiniment de gauche à droite sans jamais descendre. Prenez ça comme une feature plutôt qu'un bug.

Question 3.3

But

Lorsque l'on appuie sur la touche "⇩", la vitesse du tetromino passe à 20 cases par secondes (soft drop), c'est à dire qu'en soft drop, le tetromino descend d'une case toute les 1/20 secondes. La vitesse revient à la normale (1 case par seconde) lorsque la touche est relâchée.

Pour cela, il vous sera nécessaire :

fenêtre 3.3

UML 3

À la fin de la question 3, vous devez avoir l'UML suivant :

UML question 3

Question 4

Créer un tetromino quelconque.

Question 4.1

But

La grille doit être composée de cases.

Question 4.1.1

Nous ajoutons un attribut cases aux objets de type Grille. Cet attribut est une matrice aux dimensions de la grille permettant de gérer individuellement chaque case de la grille.

Le code suivant crée cet attribut en remplissant chaque case avec des objets None :

class Grille:
    def __init__(self, x, y, nombre_lignes, nombre_colonnes, dimension_case, couleur):
        # ..

        self.cases = []
        for i in range(nombre_lignes):
            ligne = []
            for j in range(nombre_colonnes):
                ligne.append(None)
            self.cases.append(ligne)

        # ...

Chaque élément, noté cases[ligne][colonne], sera ensuite :

Ajoutez la création de cet attribut à votre classe Grille et mettez à jour les méthodes suivantes :

On modifiera le statut des cases (libre ou occupée) après la création de l'objet. Le constructeur s'occupe juste de créer des cases libres.

Question 4.1.2

But

Création d'un tetromino.

Modifiez la méthode Tetris.nouveau_tetromino() pour que la grille du tetromino ait la forme ci-dessous ('·' signifie que la case est libre et '□' qu'elle est occupée) :

·□·
·□·
···

Pour cela, dans la méthode Tetris.nouveau_tetromino() et une fois la grille du tetromino créé, vous changerez dans les cases de son attribut cases qui doivent être occupées en leur affectant un pyglet.shapes.Rectangle

fenêtre 4.1

Pour l'instant le quadrillage de la grille du tetromino est affichée et le tetromino en lui-même disparaît alors qu'il ne touche pas lui-même le sol.

Pas d'inquiétude, nous réglerons ça bientôt.

Question 4.2

Gestion de la rotation des tetromino avec la touche "⇧".

Question 4.2.1

But

Utilitaire de rotation de matrice.

Commencez par coder une fonction rotation(matrice_carrée) dans le fichier utils qui rend la matrice carrée correspondant à la matrice donnée en entrée tournée d'un quart de tour. Ceci signifie que la ième ligne (d'indice $i-1$) devient la (n-i)ème colonne (d'indice $n-i-1$) où n est le nombre de lignes de la matrice (par exemple, la 1ère ligne devient la dernière colonne).

Vous testerez dans test_utils que si l'on donne en entrée la matrice :

[[1, 2, 3],
 [4, 5, 6],
 [7, 8, 9]]

On obtient la matrice suivante en sortie :

[[7, 4, 1],
 [8, 5, 2],
 [9, 6, 3]]

Question 4.2.2

But

Intégration de l'utilitaire à la classe Grille et gestion de la touche "⇧" dans la classe Tetris.

Utilisez la fonction rotation du fichier utils pour implémenter une méthode Grille.rotation() qui :

Utilisez cette méthode lorsque l'on appuie sur la touche "⇧" (flèche haut).

La rotation étant une action, l'accumulateur est remis à zéro après une rotation.

fenêtre 4.2

UML 4

À la fin de la question 4, vous devez avoir l'UML suivant :

UML question 4

Question 5

Gestion des collisions.

Question 5.1

But

Placer des obstacles sur la grille pour tester nos futures méthodes.

Ajouter des Rectangles aux cases (10, 0), (10, 9) et (19, 5) de l'attribut grille de la classe Tetris.

fenêtre 5.1

Question 5.2

On traite les déplacements possibles un à un.

Question 5.2.1

But

Passer du tetromino à la grille.

Créez une méthode Tetris.tetromino_vers_grille(i, j) qui prend en paramètres les coordonnées matricielles d'une case du tetromino et rend les coordonnées matricielles de la même case mais en tant que case de la grille.

Question 5.2.2

But

Gérer les descentes normales et en soft drop.

Pour chaque colonne, on regarde si la dernière case occupée de la colonne peut se déplacer (elle n'est pas sur le sol et la case en dessous d'elle est libre).

Si toutes les colonnes sont OK, on peut déplacer le tetromino vers le bas. Sinon le tetromino est bloqué et la phase de lock delay commence.

Pour cela, il vous sera nécessaire d'ajouter une méthode Tetris.déplacement_bas() qui rend True si le déplacement bas est possible et False sinon

fenêtre 5.2.2

Question 5.2.3

But

Gérer les hard drop.

Vous pourrez enchaîner les descentes d'une case dans une boucle while jusqu'à être bloqué.

fenêtre 5.2.3

Question 5.2.4

But

Gérer les mouvements de 1 case vers la gauche ou la droite.

On peut réutiliser la technique utilisée pour la descente d'une case.

Pour chaque ligne, selon le déplacement, on regarde si la première case occupée (déplacement vers la gauche) ou la dernière case occupée (déplacement vers la droite) de la ligne peut se déplacer (elle n'est pas au bord et la case vers laquelle elle veut se déplacer est libre).

Si toutes les lignes sont OK, on peut déplacer le tetromino.

Pour cela, il vous sera nécessaire :

fenêtre 5.2.4

Question 5.3

But

Gérer les rotations.

La rotation n'est possible que si toutes les cases de la grille où le tetromino tourné devraient se placer sont libre.

Pour cela, il vous sera nécessaire d'ajouter une méthode Tetris.rotation() qui rend True si la rotation est possible et False sinon

fenêtre 5.3

Question 5.4

But

Nettoyage.

Supprimez les obstacles ajoutés à la grille ainsi que le quadrillage du tetromino (vous pourrez juste vider l'attribut quadrillage du tetromino de ses éléments).

fenêtre 5.4

UML 5

À la fin de la question 5, vous devez avoir l'UML suivant :

UML question 5

Question 6

Cette question est dévolue au transfert des Rectangles du tetromino à la grille une fois que le tetromino est bloqué

Question 6.1

But

Une fois que le tetromino est bloqué ajoutez ses rectangles à la grille.

fenêtre 6.1

Question 6.2

But

On peut maintenant vérifier s'il y a une ligne dans la grille et la supprimer.

Pour cela, il vous sera nécessaire d'ajouter une méthode Grille.supprime_lignes() qui parcours toutes les lignes de la grille et si une ligne est pleine il décale vers le bas toute la grille et recommence.

fenêtre 6.2

UML 6

À la fin de la question 6, vous devez avoir l'UML suivant :

UML question 6

Question 7

Ca y'est on y est ! La dernière étape pour avoir un prototype de Tetris viable.

Question 7.1

But

Créer les 7 pièces de tetris.

Dans un fichier tetrominos.py créez 7 classes, héritant toutes de Grille, une pour chaque tetromino.

Chaque tetromino possède :

Vous pourrez utilisez les noms et couleurs suivantes pour les tetrominos :

Leur grille et leurs placement original dépend du tetromino. 5 d'entre eux sont contenus dans une matrice 3x3 :

··□    □··   ·□□   □□·   ·□·
□□□    □□□   □□·   ·□□   □□□
···    ···   ···   ···   ···
 L      J     S     Z     T

Et 2 d'entre eux dans une matrice 4x4 :

····   ····
□□□□   ·□□·
····   ·□□·
····   ····
 I      O

7.2

But

Faites en sorte que chaque nouveau tetromino soit tiré au hasard.

fenêtre 7.2

UML 7

À la fin de la question 7, vous devez avoir l'UML suivant :

UML question 7

Pour aller plus loin