Créer un Tetris en Python avec Pygame – Guide complet pas à pas

Ce que vous allez apprendre : créer un Tetris complet et jouable en Python avec Pygame en moins de 400 lignes. Ghost piece, hold piece, scoring, niveaux progressifs — tout y est.

Vous pouvez dés maintenant telecharger le fichier complet ici

Capture d'écran du jeu Tetris créé en Python avec Pygame : plateau de jeu avec plusieurs tétrominos colorés, panneau latéral affichant le score, le niveau, la pièce suivante et les contrôles
Tetris Python Pygame - exemple de partie

Introduction

Le Tetris est l'un des jeux les plus emblématiques de l'histoire du jeu vidéo, et c'est aussi un exercice de programmation idéal pour progresser en Python. Dans ce tutoriel, vous allez créer un Tetris complet avec Pygame en structurant proprement votre projet autour de trois classes principales.

Ce que vous obtiendrez à la fin :

  • Un jeu Tetris fonctionnel tournant à 60 FPS
  • Les 7 tétrominos officiels avec leurs couleurs
  • Ghost piece, hold piece, scoring et niveaux progressifs
  • Un code propre, commenté et réutilisable

Prérequis : Python 3.8+, notions de base en Python (classes, listes). Installez Pygame avec : pip install pygame

Sommaire

  1. Architecture du projet
  2. Configuration de la fenêtre et des couleurs
  3. Les tétrominos : formes et rotations
  4. La classe Board – le plateau de jeu
  5. La classe Game – logique principale
  6. Le rendu visuel avec Pygame
  7. Lancer le jeu
  8. Aller plus loin

1. Architecture du projet

Avant d'écrire la moindre ligne, il est essentiel de planifier l'architecture. Notre Tetris repose sur trois classes avec des responsabilités bien séparées :

ClasseResponsabilité
TetrominoReprésente une pièce : type, position, rotation. Calcule les cellules occupées.
BoardLe plateau 10×20. Valide les positions, verrouille les pièces, efface les lignes complètes.
GameOrchestre tout : spawn des pièces, gestion des événements, update, rendu Pygame.

Tout tient dans un fichier unique tetris.py. On le lance avec python tetris.py et le jeu démarre directement.

2. Configuration de la fenêtre et de la palette

On commence par les constantes globales qui définissent la taille de la fenêtre, la grille et les couleurs des pièces.

WINDOW_W, WINDOW_H = 400, 700
CELL       = 30          # taille d'une cellule en pixels
COLS, ROWS = 10, 20      # grille standard Tetris
BOARD_X    = 0
BOARD_Y    = (WINDOW_H - ROWS * CELL) // 2   # centré verticalement
PANEL_X    = COLS * CELL                       # début du panneau latéral (300px)
PANEL_W    = WINDOW_W - PANEL_X               # largeur du panneau (100px)

La palette utilise des couleurs officielles proches du Tetris Guideline (la norme Tetris officielle depuis 2001) :

COLORS = {
    'I': (0,   240, 240),   # cyan
    'O': (240, 240, 0  ),   # jaune
    'T': (160, 0,   240),   # violet
    'S': (0,   240, 0  ),   # vert
    'Z': (240, 0,   0  ),   # rouge
    'J': (0,   0,   240),   # bleu
    'L': (240, 160, 0  ),   # orange
}

Dans un petit projet de jeu, les constantes globales évitent de passer des dizaines de paramètres entre les classes. C'est une pratique courante et acceptable pour des scripts de jeu de cette taille.

3. Les tétrominos : formes et rotations

Chaque tétromino est défini par ses 4 rotations. Chaque rotation est une liste de tuples (col, row) exprimant les offsets par rapport à l'origine de la pièce :

SHAPES = {
    'I': [
        [(0,1),(1,1),(2,1),(3,1)],  # horizontal
        [(2,0),(2,1),(2,2),(2,3)],  # vertical
        [(0,2),(1,2),(2,2),(3,2)],  # horizontal (miroir)
        [(1,0),(1,1),(1,2),(1,3)],  # vertical (miroir)
    ],
    'O': [[(1,0),(2,0),(1,1),(2,1)]] * 4,  # carré : 4 rotations identiques
    # ... (T, S, Z, J, L définis de la même façon)
}

La classe Tetromino est volontairement minimaliste. Elle ne contient que les données de la pièce et une méthode pour calculer ses cellules absolues :

class Tetromino:
    def __init__(self, kind=None):
        self.kind     = kind or random.choice(PIECE_TYPES)
        self.rotation = 0
        self.x        = 3   # spawn centré horizontalement
        self.y        = 0   # spawn en haut

    def cells(self, x=None, y=None, rot=None):
        """Retourne les positions absolues des 4 cellules de la pièce."""
        x   = self.x        if x   is None else x
        y   = self.y        if y   is None else y
        rot = self.rotation if rot is None else rot % 4
        return [(x + dc, y + dr) for dc, dr in SHAPES[self.kind][rot]]

Astuce : la méthode cells() accepte des paramètres optionnels x, y, rot. Cela permet de tester une future position sans modifier l'état réel de la pièce — indispensable pour la validation de déplacement et la ghost piece.

Le système 7-bag : plutôt que de piocher aléatoirement parmi les 7 types, on mélange les 7 pièces puis on les donne dans cet ordre, puis on recommence. Cela garantit que le joueur ne se retrouve jamais avec 6 S de suite. C'est la norme officielle Tetris Guideline.

4. La classe Board – le plateau de jeu

Board gère la grille de jeu : une liste de 20 lignes, chacune étant une liste de 10 cellules. Chaque cellule contient soit None (vide), soit un tuple RGB (couleur de la pièce verrouillée).

class Board:
    def __init__(self):
        self.grid = [[None] * COLS for _ in range(ROWS)]

    def valid(self, cells):
        """Vérifie si une liste de cellules est dans les limites et non occupée."""
        for c, r in cells:
            if c < 0 or c >= COLS or r >= ROWS:
                return False
            if r >= 0 and self.grid[r][c] is not None:
                return False
        return True

    def lock(self, piece):
        """Verrouille une pièce sur la grille."""
        color = COLORS[piece.kind]
        for c, r in piece.cells():
            if 0 <= r < ROWS:
                self.grid[r][c] = color

    def clear_lines(self):
        """Supprime les lignes complètes et les remplace par des vides en haut."""
        full = [r for r in range(ROWS) if all(self.grid[r])]
        for r in full:
            del self.grid[r]
            self.grid.insert(0, [None] * COLS)
        return len(full)   # 0 à 4 lignes effacées

Dans clear_lines(), on supprime les lignes pleines et on insère autant de lignes vides en haut. C'est élégant car on évite de tout décaler manuellement.

5. La classe Game – logique principale

5.1 Initialisation et reset

Séparer reset() de __init__() permet de redémarrer une partie sans recréer la fenêtre Pygame — bonne pratique systématique.

def reset(self):
    self.board     = Board()
    self._bag      = []         # 7-bag vide, sera rempli au premier spawn
    self.current   = self._spawn()
    self.nxt       = self._spawn()
    self.held      = None
    self.can_hold  = True       # on peut hold une fois par pièce posée
    self.score     = 0
    self.lines     = 0
    self.level     = 1
    self.paused    = False
    self.game_over = False
    self.fall_ms   = 0          # accumulateur de temps de chute

5.2 Ghost piece

La ghost piece indique au joueur où la pièce actuelle va atterrir. On la calcule en descendant virtuellement la pièce jusqu'à ce qu'elle ne puisse plus descendre :

def _ghost_y(self):
    y = self.current.y
    while self.board.valid(self.current.cells(y=y + 1)):
        y += 1
    return y

Au rendu, on dessine la ghost piece en contour (outline) avec la couleur divisée par 2 (assombrie), en excluant les cellules déjà occupées par la pièce active.

5.3 Rotation avec wall kick

Le wall kick permet de pivoter une pièce même contre un mur. On teste jusqu'à 5 décalages horizontaux différents et on prend le premier valide :

def _rotate(self):
    nr = (self.current.rotation + 1) % 4
    for dx in (0, -1, 1, -2, 2):   # centre, gauche, droite, plus loin...
        kicked = self.current.cells(x=self.current.x + dx, rot=nr)
        if self.board.valid(kicked):
            self.current.rotation = nr
            self.current.x += dx
            return   # on prend le premier décalage valide

5.4 Hold piece

Le hold permet de mettre une pièce de côté pour l'utiliser plus tard. On ne peut hold qu'une seule fois par pièce posée :

def _hold(self):
    if not self.can_hold:
        return
    kind = self.current.kind
    if self.held is None:
        self.current = self.nxt      # si rien en hold : on prend la suivante
        self.nxt     = self._spawn()
    else:
        self.current = Tetromino(self.held.kind)   # échange
    self.held     = Tetromino(kind)
    self.can_hold = False   # bloqué jusqu'à la prochaine pose

5.5 Verrouillage et scoring

Quand une pièce ne peut plus descendre, on la verrouille, on efface les lignes, on met à jour le score et on spawne la suivante. Si celle-ci spawne dans une position invalide, c'est game over.

SCORE_TABLE = {1: 100, 2: 300, 3: 500, 4: 800}

def _lock(self):
    self.board.lock(self.current)
    cleared = self.board.clear_lines()
    if cleared:
        self.score += SCORE_TABLE.get(cleared, 0)
        self.lines += cleared
        self.level  = self.lines // 10 + 1   # niveau = lignes / 10 + 1
    self.current  = self.nxt
    self.nxt      = self._spawn()
    self.can_hold = True
    if not self.board.valid(self.current.cells()):
        self.game_over = True

5.6 La boucle de jeu et la gravité

La gravité est gérée par un accumulateur de temps (dt en millisecondes). Le soft drop accélère la chute d'un facteur 10 :

def update(self, dt):
    if self.game_over or self.paused:
        return
    keys  = pygame.key.get_pressed()
    soft  = keys[pygame.K_DOWN]
    delay = max(50, self._fall_delay() // (10 if soft else 1))
    self.fall_ms += dt
    if self.fall_ms >= delay:
        self.fall_ms = 0
        if not self._move(0, 1):   # tentative de descente
            self._lock()           # verrouillage si impossible

def _fall_delay(self):
    return max(80, 800 - (self.level - 1) * 70)
    # de 800ms au niveau 1 jusqu'à 80ms au niveau max

6. Le rendu visuel avec Pygame

6.1 Dessin d'un bloc

Chaque cellule est dessinée avec un léger highlight en haut/gauche pour un effet 3D subtil. En mode ghost, on dessine juste le contour :

def _draw_block(self, col, row, color, outline=False):
    r = pygame.Rect(
        BOARD_X + col * CELL + 1,
        BOARD_Y + row * CELL + 1,
        CELL - 2, CELL - 2,
    )
    if outline:   # mode ghost piece
        pygame.draw.rect(self.screen, color, r, 2)
    else:
        pygame.draw.rect(self.screen, color, r)
        hi = tuple(min(255, v + 70) for v in color)  # couleur éclaircie
        pygame.draw.line(self.screen, hi, r.topleft, r.topright)
        pygame.draw.line(self.screen, hi, r.topleft, r.bottomleft)

6.2 Overlay pause et game over

On superpose une surface semi-transparente (SRCALPHA) sur le plateau, puis on centre le texte dessus. C'est la technique standard Pygame pour les overlays :

def _overlay(self, text):
    s = pygame.Surface((COLS * CELL, ROWS * CELL), pygame.SRCALPHA)
    s.fill((0, 0, 0, 160))   # noir semi-transparent
    self.screen.blit(s, (BOARD_X, BOARD_Y))
    surf = self.font_xl.render(text, True, WHITE)
    self.screen.blit(surf, (
        BOARD_X + (COLS * CELL - surf.get_width()) // 2,
        BOARD_Y + ROWS * CELL // 2 - surf.get_height() // 2,
    ))

7. Lancer le jeu

pip install pygame
python tetris.py

Téléchargez le fichier complet ici

Contrôles :

  • ←→ : déplacer la pièce
  •  : rotation
  •  : soft drop (chute accélérée)
  • Espace : hard drop (chute instantanée)
  • C : hold piece
  • P : pause / reprendre
  • R : recommencer (après game over)

8. Aller plus loin

Ce Tetris est complet et jouable, mais voici des pistes pour continuer à progresser :

  • Système de meilleur score persistant avec json ou sqlite3
  • Animations de suppression de lignes (flash + délai)
  • Musique et effets sonores avec pygame.mixer
  • Mode multijoueur sur le même clavier
  • Export en exécutable Windows avec PyInstaller
  • Refactorisation en pattern ECS (Entity Component System)

Le code complet est disponible dans l'article. N'hésitez pas à le modifier et partager vos améliorations en commentaire !

Conclusion

Vous savez maintenant comment créer un Tetris en Python avec Pygame de A à Z. Ce projet vous a permis de pratiquer la programmation orientée objet, la gestion d'une boucle de jeu, le rendu 2D et la logique de collision. C'est une excellente base pour vos prochains projets de jeux en Python.

Si ce tutoriel vous a aidé, partagez-le et laissez un commentaire avec votre score record !

Laisser un commentaire

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