Tetris

Tetris est un jeu vidéo de puzzle conçu par Alekseï Pajitnov à partir de juin 1984.

 

Tetris met le joueur au défi de réaliser des lignes complètes en déplaçant des pièces de formes différentes, les tétrominos, qui défilent depuis le haut jusqu’au bas de l’écran. Les lignes complétées disparaissent tout en rapportant des points et le joueur peut de nouveau remplir les cases libérées.

Le jeu n’a pas de fin : le joueur perd la partie lorsqu’un tétromino reste bloqué en haut. Il doit donc résister le plus longtemps à la chute continue des tétrominos, afin de réaliser le meilleur score.

 

 

 

 

 

 

 

Source : https://fr.wikipedia.org/wiki/Tetris

ensemble des tétrominos du jeu Tetris

 

Travail demandé

Objectif : réaliser un jeu de Tetris complètement jouable, dans un environnement en mode texte (TUI – Text User Interface) :

Diagramme de classe

Imaginer, sous forme d’un diagramme de classe, une structure simple présentant les classes nécessaires à la programmation de ce jeu, leurs attributs, leurs méthodes et leurs relations.
Correction

Remarque : sur un diagramme de classe, les noms des attributs et des méthodes sont précédés d’un signe :

+ : attribut ou méthode publique

– : attribut ou méthode privée
rappel : en Python pour rendre un attribut ou une méthode privée, il faut faire précéder son nom de __(double underscore)

 

Classe Tetris

La boucle principale est conditionnée par un attribut booléen game_over.

Pour la gestion du rythme de jeu on choisit d’utiliser la fonction time.sleep dans la boucle principale.

L’accélération du rythme est réalisée par une diminution périodique (période PERIODE) du délai (attribut delai) en le multipliant par un coefficient COEF_ACCEL.

La méthode on_key_press est appelée par le gestionnaire d’événement clavier (de type UI_handler). Les codes des touches sont définis par des constantes (ESC, ENTER, LEFT, …) dans le module ui_handler.

Pour rafraîchir l’affichage, il suffit d’appeler la méthode afficher, qui elle doit à son tour appeler la méthode afficher du displayer.

 

Classe Grille

On décide de doter la classe Grille d’un attribut qui fait référence au jeu de tetris (objet de type Tetris). Par exemple, cela permet à la grille d’obtenir facilement une référence au Tetromino « courant » du jeu.

Grille.grille : tableau à 2 dimensions de caractères (les cases des Tetromino) ou None pour les cases vides.

Decription de quelques méthodes :

  • est_possible : renvoie True si le Tetromino « courant » peut être placé à sa position (voir attribut pos de Tetromino), c’est à dire s’il n’interfère pas avec les autres cases non vides de la Grille.

 

Classe Tetromino

Tetromino :

  • schema : tuple de coordonnées relatives de cases
    exemple : → ((0,-1), (0,0), (1,0), (1,1))
  • pos : tuple (ligne, colonne) donnant la position du Tetromino dans la Grille du jeu.

 

Interface Homme-Machine

Événements clavier

La gestion des événements clavier est définie dans le module UI_handler.

Pour la gestion des événements clavier, on utilise la bibliothèque pynput (voir pynput) et en particulier son module keyboard. On exécute par conséquent l’application principale (module tetris) dans un context manager.

La méthode UI_handler.on_key_press est un callback : elle est exécutée automatiquement à chaque fois que l’utilisateur presse une touche.

Un de ses arguments est un objet de type keboard.key. Elle doit propager l’information « une touche a été pressée » au jeu de Tetris, mais ne doit pas faire référence au module pynput.keyboard ! On veut pouvoir changer de bibliothèque sans devoir modifier la classe Tetris !!

Pour cela, on utilise la référence au jeu de Tetris qui possède lui aussi une méthode on_key_press. Mais on lui passe un code sous forme d’un entier (voir constantes « codes des touches utilisées » du module ui_handler).

 

Affichage

On a choisi de faire un affichage en mode texte : une chaîne de caractères dans le terminal (voir Afficher dans un terminal).

Pour que l’aspect soit  satisfaisant (des couleurs, pas de défilement, …) on utilise des ANSI Escape Codes : des caractères non-imprimables qui provoquent des déplacements de curseur, des effacements de caractères, des modification de couleur, …

Les ANSI Escape Codes nécessaire au jeu de Tetris sont déjà définis dans des constantes du module displayer. Pour leur utilisation, voir Afficher dans un terminal.

 

 

 

 

Réaliser l’implémentation complète du jeu en Python.
Procéder par petites étapes … en réalisant des tests fréquents …
Classe TetrisClasse TetrominoClasse GrilleClasse UI_handlerClasse Displayer
#################################################################################################
# Jeu de TETRIS
# Module principal
#################################################################################################
from tetromino import *
from ui_handler import *
from grille import *
import time

# Dimensions
W, H = 10, 20

# Durées
DELAI = 1.0  # période de descente des tetrominos (en secondes)
PERIODE = 20 # période de l'accélération (en nombre de Tetrominos posés)
DELAI_LIGNE = 0.2 # Durée de suppression de ligne 
COEF_ACCEL = 0.8 # Coefficient d'accélération du jeu (c'est à dire de réduction du délai)

class Tetris:
    def __init__(self):
        ...
        
        # Le gestionnaire des événements clavier
        self.__ui_handler = UI_handler(self)


    def run(self):
        """ Boucle principale du jeu de Tetris :
            (se termine quand game_over est vrai)
            - descenre le tetromino "courant" (s'il existe !)
            - rafraichir l'affichage
            - gérer le rythme (méthode gerer_temps)
            - attendre "delai"
        """
        while ...:
            ...

    def __enter__(self):
        self.__ui_handler.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.__ui_handler.stop()

    def get_score(self):
        ...

    def get_grille(self):
        ...

    def get_tetro(self):
        ...
    def gerer_temps(self):
        """ Mise à jour des attributs liés au temps (à chaque cycle):
             chrono : incrémentation à chaque période (DELAI)
              delai : réduction (*COEFF_ACCEL) toute les PERIODE cycles (pour accélérer le jeu)
        """
        ...

    def descendre_tetro(self):
        """ Descend le Tetromino d'une ligne vers le bas
            si ce n'est pas possible, 
            le Tetromino est remonté puis posé
        """
        ...
        
    def lacher_tetro(self):
        """ Descend le Tetromino jusqu'en bas
             et pose le Tetromino
        """
        ...


    def decaler_tetro(self, sens):
        """ Décale latéralement le Tetromino courant
            d'une distance égale à sens
            (si impossible, re-décale dans l'autre sens)
        """
        ...
        

    def tourner_tetro(self):
        """ Tourne le Tetromino courant
            (si impossible, décale pour que ça passe)
        """
        ...

    def ajouter_tetro(self):
        """ Instancie un nouveau Tetromino
            et le place en haut de la grille (si possible)
        """
        ...

    
    def poser_tetro(self):
        """ Pose le Tetromino lorsqu'il ne peut plus descendre
            et lancement de la vérification de lignes
        """
        ...

    def on_key_press(self, key):
        """ Méthode appelée quand une touche est pressée
             (voir UI_handler.py)
        """
        if key == ESC: # arrêt du jeu
            ...
            return False
        ...


    def afficher(self):
        """ Affiche le jeu :
                score
                grille
        """
        ...


#################################################################################################
# Exécution du jeu dans un "context manager"
#   (car utilisation d'un thread dans user_input)
with Tetris() as tetris:
    tetris.run() 

 

#################################################################################################
# Jeu de TETRIS
# Module tetrominos : gestion des tetrominos
#################################################################################################
from random import randint
from displayer import *

#################################################################################################
# Tous les schémas de tetrominos
#   coordonnées relatives des 
TETROS =   [[(0,-1), (0,0),  (1,0),  (1,1)],
            ...
            ]

# couleurs
COULS = [...]  # indices de couleur (voir display.py)

# caractères utilisé
CHAR = '█'          # Tetronimo
CHAR_SUPP = '░'     # Tetronimo avant suppression de ligne


#################################################################################################
class Tetromino:
    def __init__(self, ...):
        ...

    def __str__(self):
        ...

    def get_pos(self):
        """ Retourne la position (l,c)
            du Tetromino
        """
        ...

    def set_pos(self, ...):
        ...

    def get_all_pos(self):
        """ Renvoie les positions (l,c) 
            de tous les éléments du Tetromino
            --> [(..., ...),(..., ...),(..., ...),(..., ...)]
            coordonnées de chaque élément du Tetromino
            dans le repère de la grille !
        """
        ...

    def est_dans_case(self, l, c):
        """ Renvoie True si la case (l,c) de la grille
            contient un élément du Tetromino
        """
        ...

    def descendre(self):
        ...

    def monter(self):
        ...

    def decaler(self, sens):
        """ Décale la position (colonne) du tetromino
             sens == 1 : vers la droite
             sens == -1 : vers la gauche
            Si impossible (voir méthode Grille.est_possible), alors ne pas décaler
        """
        ...

    def tourner(self):
        """ Modifie en place le schéma du tetromino
            pour qu'il rempésente un tétromino tourné de 90°
            Ne renvoie rien
        """
        ...


def get_random_tetro():
    """ Instancie et retourne un nouveau tetromino
        choisi aléatoirement parmi les TETROS
    """
    ...


#################################################################################################
if __name__ == "__main__":
    t = Tetromino(0)
    print(t)

 

#################################################################################################
# Jeu de TETRIS
# Module grille : gestion de la grille principale
#################################################################################################
from displayer import *
from tetromino import *

class Grille:
    def __init__(self, ...):
        ...

    def __str__(self):
        ...

    def est_possible(self):
        """ Renvoie True si le Tetromino "courant"
            peut être placé sur la grille
            Il faut vérifier que le tetromino "courant" du jeu (chacun de ses éléments)
            peut trouver une place sur la grille.
            Il faut vérifier pour chacun des éléments du tetromino "courant" :
             - si la case de la grille est libre
             - s'il n'est pas en dehors de la grille
        """
        ...
    
    def poser_tetro(self):
        """ Inclus le Tetromino à la grille
            Le tetromino courant du jeu est "transformé"
            en chaines de caractères dans les cases de la grille
        """
        ...

    def verif_lignes(self):
        """ Vérifie la présence de lignes complètes
            et renvoie les numéros des lignes supprimées
        """
        ...

    def previder_ligne(self, ...):
        """ Prévide la ligne d'indice l
            avant suppression
            (remplace les caractères)
        """
        ...


    def supprimer_ligne(self, ...):
        """ Supprime la ligne d'indice l
            et en rajoute une vide en haut
        """
        ...

#################################################################################################
if __name__ == "__main__":
    g = Grille()
    print(g)
#################################################################################################
# Jeu de TETRIS
# Module user_input : gestionnaire d'événements clavier
# (voir https://info.blaisepascal.fr/pynput/)
#################################################################################################
from pynput import keyboard 

# Codes des touches utilisées
ESC = 10
ENTER = 20
SPACE = 0
LEFT = 1
RIGHT = 2
DOWN = 3

class UI_handler:
    def __init__(self, ...):
        ...

    def start(self):
        ...

    def stop(self):
        ...

    def on_key_press(self, key):
        """ Méthode appelée quand une touche est pressée
            --> propage l'information au jeu de tetris
        """
        if key == keyboard.Key.esc: # arrêt du jeu
            self.__tetris.on_key_press(ESC)
        ...
    

#################################################################################################
if __name__ == "__main__":
    ui = UI_handler()
    ui.stop()
    


#################################################################################################
# Jeu de TETRIS
# Module display : gestionnaire d'affichage
# (voir  https://info.blaisepascal.fr/afficher-dans-le-terminal/)
#################################################################################################
# Couleurs
COULS =['\033[91m',     # ROUGE
        '\033[92m',     # VERT
        '\033[93m',     # JAUNE
        '\033[94m',     # BLEU
        '\033[95m',     # VIOLET
        '\033[96m',     # CYAN
        '\033[97m',     # GRIS
        ]

ENDC = '\033[00m' 
BOLD = '\033[1m' 
UNDERLINE = '\033[4m' 

HIDE = '\033[?25l'
SHOW = '\033[?25h'
HOME = '\033[H'
DEL_END = '\033[0J'
DEL_ALL = '\033[2J'

class Displayer:
    def __init__(self, tetris):
        ...

    def afficher(self):
        ...

def colorize(s, c):
    """ Retourne une représentation en couleur c
        de la chaîne s
    """
    ...

#################################################################################################
if __name__ == "__main__":
    d = Displayer()

 

Améliorer l’affichage en utilisant des caractères semi-graphiques.

 

Vous aimerez aussi...

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *