Tutorial: Tetris in Python mit Pygame

1. Voraussetzungen

  • Python 3 installiert (mindestens Version 3.8).
  • Pygame Bibliothek:bashpip install pygame
  • Ein Editor deiner Wahl (z. B. VS Code, PyCharm oder einfach Notepad++).

2. Projektstruktur

Du brauchst nur eine Datei: tetris.py. Darin kommt der gesamte Code, den ich dir gegeben habe.


3. Aufbau des Codes

Der Code ist modular gegliedert:

  • Konstanten & Einstellungen Spielfeldgröße, Farben, Geschwindigkeit, Level‑Logik.
  • Tetromino‑Definitionen Alle Formen (I, O, T, S, Z, J, L) als 4×4‑Matrizen.
  • Hilfsfunktionen
    • new_piece() erzeugt einen neuen Stein.
    • get_cells() berechnet die belegten Zellen.
    • valid_position() prüft, ob ein Stein passt.
    • lock_piece() fixiert einen Stein im Grid.
    • clear_lines() entfernt volle Reihen.
  • Zeichenfunktionen Zeichnen von Spielfeld, Steinen, Panel (Score/Level).
  • Game Loop (main())
    • Eingaben verarbeiten (Pfeiltasten, Alt, Strg, Space).
    • Spiellogik (fallen lassen, Linien löschen, Level erhöhen).
    • Zeichnen und Aktualisieren des Bildschirms.

4. Steuerung

  • ← / →: Stein bewegen
  • : schneller fallen lassen (Soft Drop)
  • Alt: Drehung im Uhrzeigersinn
  • Strg: Drehung gegen den Uhrzeigersinn
  • Leertaste: Hard Drop (sofort absetzen, Bonuspunkte)
  • R: Neustart nach Game Over

5. Level & Punkte

  • Jede gelöschte Reihe bringt Punkte:
    • 1 Reihe = 100 Punkte
    • 2 Reihen = 300 Punkte
    • 3 Reihen = 500 Punkte
    • 4 Reihen = 800 Punkte
  • Nach 10 Reihen steigt das Level.
  • Mit jedem Level wird die Fallgeschwindigkeit schneller.

6. Game Over

Wenn ein neuer Stein nicht mehr ins Spielfeld passt → Game Over. Dann erscheint die Meldung und du kannst mit R neu starten.


7. Erweiterungen (für dich als kreativer Builder 😉)

  • Grafik verbessern: z. B. mit Schatten oder Animationen.
  • Soundeffekte: Pygame kann auch WAV/MP3 abspielen.
  • Highscore speichern: Punkte in einer Datei oder Datenbank ablegen.


import pygame
import random
import sys

# -----------------------------
# Einstellungen
# -----------------------------
WIDTH, HEIGHT = 400, 500
PLAY_W, PLAY_H = 200, 400   # Spielfeldgröße (10 x 20 Zellen à 20px)
BLOCK_SIZE = 20
GRID_COLS, GRID_ROWS = PLAY_W // BLOCK_SIZE, PLAY_H // BLOCK_SIZE

FPS = 60
BASE_FALL_DELAY_MS = 800   # Basis-Fallzeit (ms) für Level 1
LEVEL_SPEED_FACTOR = 0.85  # pro Level wird die Fallzeit multipliziert

# Farben
COLORS = {
    'bg': (18, 18, 18),
    'grid': (40, 40, 40),
    'text': (230, 230, 230),
    'panel': (30, 30, 30),
    'game_over': (200, 60, 60),
}

# Tetromino-Farben
PIECE_COLORS = [
    (0, 240, 240),   # I
    (240, 240, 0),   # O
    (160, 0, 240),   # T
    (0, 240, 0),     # S
    (240, 0, 0),     # Z
    (0, 0, 240),     # J
    (240, 160, 0),   # L
]

# Formen (Rotationen als Matrix von 4x4)
TETROMINOS = {
    'I': [
        [[0,0,0,0],
         [1,1,1,1],
         [0,0,0,0],
         [0,0,0,0]],
        [[0,0,1,0],
         [0,0,1,0],
         [0,0,1,0],
         [0,0,1,0]],
    ],
    'O': [
        [[0,1,1,0],
         [0,1,1,0],
         [0,0,0,0],
         [0,0,0,0]],
    ],
    'T': [
        [[0,1,0,0],
         [1,1,1,0],
         [0,0,0,0],
         [0,0,0,0]],
        [[0,1,0,0],
         [0,1,1,0],
         [0,1,0,0],
         [0,0,0,0]],
        [[0,0,0,0],
         [1,1,1,0],
         [0,1,0,0],
         [0,0,0,0]],
        [[0,1,0,0],
         [1,1,0,0],
         [0,1,0,0],
         [0,0,0,0]],
    ],
    'S': [
        [[0,1,1,0],
         [1,1,0,0],
         [0,0,0,0],
         [0,0,0,0]],
        [[1,0,0,0],
         [1,1,0,0],
         [0,1,0,0],
         [0,0,0,0]],
    ],
    'Z': [
        [[1,1,0,0],
         [0,1,1,0],
         [0,0,0,0],
         [0,0,0,0]],
        [[0,1,0,0],
         [1,1,0,0],
         [1,0,0,0],
         [0,0,0,0]],
    ],
    'J': [
        [[1,0,0,0],
         [1,1,1,0],
         [0,0,0,0],
         [0,0,0,0]],
        [[0,1,1,0],
         [0,1,0,0],
         [0,1,0,0],
         [0,0,0,0]],
        [[0,0,0,0],
         [1,1,1,0],
         [0,0,1,0],
         [0,0,0,0]],
        [[0,1,0,0],
         [0,1,0,0],
         [1,1,0,0],
         [0,0,0,0]],
    ],
    'L': [
        [[0,0,1,0],
         [1,1,1,0],
         [0,0,0,0],
         [0,0,0,0]],
        [[0,1,0,0],
         [0,1,0,0],
         [0,1,1,0],
         [0,0,0,0]],
        [[0,0,0,0],
         [1,1,1,0],
         [1,0,0,0],
         [0,0,0,0]],
        [[1,1,0,0],
         [0,1,0,0],
         [0,1,0,0],
         [0,0,0,0]],
    ],
}

# -----------------------------
# Hilfsfunktionen
# -----------------------------
def new_piece():
    kind = random.choice(list(TETROMINOS.keys()))
    rotations = TETROMINOS[kind]
    rot_idx = 0
    color = PIECE_COLORS[list(TETROMINOS.keys()).index(kind)]
    # Startposition: oben mittig
    x = GRID_COLS // 2 - 2
    y = 0
    return {'kind': kind, 'rotations': rotations, 'rot': rot_idx, 'x': x, 'y': y, 'color': color}

def get_cells(piece, rot=None, x=None, y=None):
    r = piece['rot'] if rot is None else rot
    px = piece['x'] if x is None else x
    py = piece['y'] if y is None else y
    shape = piece['rotations'][r]
    cells = []
    for j in range(4):
        for i in range(4):
            if shape[j][i]:
                cells.append((px + i, py + j))
    return cells

def valid_position(grid, piece, rot=None, x=None, y=None):
    for cx, cy in get_cells(piece, rot, x, y):
        if cx < 0 or cx >= GRID_COLS or cy < 0 or cy >= GRID_ROWS:
            return False
        if grid[cy][cx] is not None:
            return False
    return True

def lock_piece(grid, piece):
    for cx, cy in get_cells(piece):
        if 0 <= cy < GRID_ROWS and 0 <= cx < GRID_COLS:
            grid[cy][cx] = piece['color']

def clear_lines(grid):
    cleared = 0
    new_grid = [row for row in grid if any(cell is None for cell in row)]
    cleared = GRID_ROWS - len(new_grid)
    for _ in range(cleared):
        new_grid.insert(0, [None for _ in range(GRID_COLS)])
    return new_grid, cleared

def rotate_index(piece, direction=1):
    # direction: +1 clockwise, -1 counterclockwise
    rcount = len(piece['rotations'])
    if rcount == 1:
        return piece['rot']
    return (piece['rot'] + direction) % rcount

# -----------------------------
# Zeichnen
# -----------------------------
def draw_grid(surface, origin):
    ox, oy = origin
    for y in range(GRID_ROWS):
        for x in range(GRID_COLS):
            rect = pygame.Rect(ox + x * BLOCK_SIZE, oy + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
            pygame.draw.rect(surface, COLORS['grid'], rect, 1)

def draw_board(surface, origin, grid):
    ox, oy = origin
    for y in range(GRID_ROWS):
        for x in range(GRID_COLS):
            color = grid[y][x]
            if color:
                pygame.draw.rect(surface, color, (ox + x * BLOCK_SIZE, oy + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE))
    draw_grid(surface, origin)

def draw_piece(surface, origin, piece):
    ox, oy = origin
    for cx, cy in get_cells(piece):
        pygame.draw.rect(surface, piece['color'], (ox + cx * BLOCK_SIZE, oy + cy * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE))

def draw_panel(surface, font, score, level, lines, game_over):
    panel_rect = pygame.Rect(PLAY_W + 20, 20, WIDTH - (PLAY_W + 40), HEIGHT - 40)
    pygame.draw.rect(surface, COLORS['panel'], panel_rect, border_radius=8)
    title = font.render("Tetris", True, COLORS['text'])
    surface.blit(title, (panel_rect.x + 10, panel_rect.y + 10))
    s1 = font.render(f"Punkte: {score}", True, COLORS['text'])
    s2 = font.render(f"Level:  {level}", True, COLORS['text'])
    s3 = font.render(f"Reihen: {lines}", True, COLORS['text'])
    surface.blit(s1, (panel_rect.x + 10, panel_rect.y + 50))
    surface.blit(s2, (panel_rect.x + 10, panel_rect.y + 80))
    surface.blit(s3, (panel_rect.x + 10, panel_rect.y + 110))
    if game_over:
        go = font.render("GAME OVER", True, COLORS['game_over'])
        surface.blit(go, (panel_rect.x + 10, panel_rect.y + 160))
        hint = font.render("R für Neustart", True, COLORS['text'])
        surface.blit(hint, (panel_rect.x + 10, panel_rect.y + 190))

# -----------------------------
# Hauptspiel
# -----------------------------
def compute_fall_delay(level):
    return int(BASE_FALL_DELAY_MS * (LEVEL_SPEED_FACTOR ** (level - 1)))

def main():
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Tetris (Pfeiltasten, Alt/Strg drehen)")
    clock = pygame.time.Clock()
    font = pygame.font.SysFont("consolas", 18)

    origin = (10, 50)  # linke obere Ecke des Spielfelds

    # Spielfeld: GRID_ROWS x GRID_COLS mit None
    grid = [[None for _ in range(GRID_COLS)] for _ in range(GRID_ROWS)]
    current = new_piece()
    next_piece = new_piece()

    score = 0
    level = 1
    total_lines = 0
    lines_since_levelup = 0
    game_over = False

    fall_delay = compute_fall_delay(level)
    last_fall_time = pygame.time.get_ticks()

    # Soft drop: schneller fallen, wenn Pfeil unten gehalten wird
    soft_drop = False
    move_left = False
    move_right = False

    move_repeat_delay = 100  # ms bis Wiederholung
    last_move_time = 0

    while True:
        dt = clock.tick(FPS)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit(); sys.exit()
            if event.type == pygame.KEYDOWN:
                if game_over:
                    if event.key == pygame.K_r:
                        # Neustart
                        grid = [[None for _ in range(GRID_COLS)] for _ in range(GRID_ROWS)]
                        current = new_piece()
                        next_piece = new_piece()
                        score = 0
                        level = 1
                        total_lines = 0
                        lines_since_levelup = 0
                        game_over = False
                        fall_delay = compute_fall_delay(level)
                        last_fall_time = pygame.time.get_ticks()
                    continue

                if event.key == pygame.K_LEFT:
                    move_left = True
                    move_right = False
                    if valid_position(grid, current, x=current['x'] - 1, y=current['y']):
                        current['x'] -= 1
                    last_move_time = pygame.time.get_ticks()
                elif event.key == pygame.K_RIGHT:
                    move_right = True
                    move_left = False
                    if valid_position(grid, current, x=current['x'] + 1, y=current['y']):
                        current['x'] += 1
                    last_move_time = pygame.time.get_ticks()
                elif event.key == pygame.K_DOWN:
                    soft_drop = True
                elif event.key in (pygame.K_LALT, pygame.K_RALT):
                    # Drehung im Uhrzeigersinn
                    new_r = rotate_index(current, +1)
                    if valid_position(grid, current, rot=new_r, x=current['x'], y=current['y']):
                        current['rot'] = new_r
                    else:
                        # kleiner "Wall kick"
                        if valid_position(grid, current, rot=new_r, x=current['x'] + 1, y=current['y']):
                            current['x'] += 1; current['rot'] = new_r
                        elif valid_position(grid, current, rot=new_r, x=current['x'] - 1, y=current['y']):
                            current['x'] -= 1; current['rot'] = new_r
                elif event.key in (pygame.K_LCTRL, pygame.K_RCTRL):
                    # Drehung gegen den Uhrzeigersinn
                    new_r = rotate_index(current, -1)
                    if valid_position(grid, current, rot=new_r, x=current['x'], y=current['y']):
                        current['rot'] = new_r
                    else:
                        if valid_position(grid, current, rot=new_r, x=current['x'] + 1, y=current['y']):
                            current['x'] += 1; current['rot'] = new_r
                        elif valid_position(grid, current, rot=new_r, x=current['x'] - 1, y=current['y']):
                            current['x'] -= 1; current['rot'] = new_r
                elif event.key == pygame.K_SPACE:
                    # Hard drop
                    drop_dist = 0
                    while valid_position(grid, current, x=current['x'], y=current['y'] + 1):
                        current['y'] += 1
                        drop_dist += 1
                    score += drop_dist  # kleiner Bonus für Hard Drop
                    # Locken
                    lock_piece(grid, current)
                    grid, cleared = clear_lines(grid)
                    if cleared:
                        total_lines += cleared
                        lines_since_levelup += cleared
                        # klassisches Scoring: 1/3/5/8 pro Linie nacheinander (hier einfach * 100)
                        if cleared == 1:
                            score += 100
                        elif cleared == 2:
                            score += 300
                        elif cleared == 3:
                            score += 500
                        elif cleared >= 4:
                            score += 800
                        if lines_since_levelup >= 10:
                            level += 1
                            lines_since_levelup -= 10
                            fall_delay = compute_fall_delay(level)
                    current = next_piece
                    next_piece = new_piece()
                    # Game Over prüfen
                    if not valid_position(grid, current, x=current['x'], y=current['y']):
                        game_over = True

            if event.type == pygame.KEYUP:
                if event.key == pygame.K_LEFT:
                    move_left = False
                elif event.key == pygame.K_RIGHT:
                    move_right = False
                elif event.key == pygame.K_DOWN:
                    soft_drop = False

        if not game_over:
            # horizontale Wiederholung
            now = pygame.time.get_ticks()
            if move_left and now - last_move_time > move_repeat_delay:
                if valid_position(grid, current, x=current['x'] - 1, y=current['y']):
                    current['x'] -= 1
                last_move_time = now
            if move_right and now - last_move_time > move_repeat_delay:
                if valid_position(grid, current, x=current['x'] + 1, y=current['y']):
                    current['x'] += 1
                last_move_time = now

            # Fallen
            current_fall_delay = int(fall_delay * (0.35 if soft_drop else 1.0))
            if now - last_fall_time >= current_fall_delay:
                last_fall_time = now
                if valid_position(grid, current, x=current['x'], y=current['y'] + 1):
                    current['y'] += 1
                else:
                    # Landen und sperren
                    lock_piece(grid, current)
                    grid, cleared = clear_lines(grid)
                    if cleared:
                        total_lines += cleared
                        lines_since_levelup += cleared
                        if cleared == 1:
                            score += 100
                        elif cleared == 2:
                            score += 300
                        elif cleared == 3:
                            score += 500
                        elif cleared >= 4:
                            score += 800
                        if lines_since_levelup >= 10:
                            level += 1
                            lines_since_levelup -= 10
                            fall_delay = compute_fall_delay(level)

                    current = next_piece
                    next_piece = new_piece()

                    # Game Over, wenn neue Figur nicht passt
                    if not valid_position(grid, current, x=current['x'], y=current['y']):
                        game_over = True

        # Zeichnen
        screen.fill(COLORS['bg'])
        draw_board(screen, origin, grid)
        if not game_over:
            draw_piece(screen, origin, current)

        # Vorschau (Next) zeichnen
        preview_rect = pygame.Rect(PLAY_W + 20, HEIGHT - 140, WIDTH - (PLAY_W + 40), 120)
        pygame.draw.rect(screen, COLORS['panel'], preview_rect, border_radius=8)
        preview_label = font.render("Nächster Stein", True, COLORS['text'])
        screen.blit(preview_label, (preview_rect.x + 10, preview_rect.y + 10))
        # kleine Vorschau zeichnen
        tmp_piece = dict(next_piece)
        tmp_piece['x'] = GRID_COLS + 1  # außerhalb des Boards; wir zeichnen direkt relativ zur preview_rect
        tmp_piece['y'] = GRID_ROWS + 1
        # manuell: iteriere die Form und zeichne relativ zur preview_rect
        shape = next_piece['rotations'][next_piece['rot']]
        start_x = preview_rect.x + 10
        start_y = preview_rect.y + 40
        for j in range(4):
            for i in range(4):
                if shape[j][i]:
                    pygame.draw.rect(screen, next_piece['color'],
                                     (start_x + i * BLOCK_SIZE, start_y + j * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE))

        draw_panel(screen, font, score, level, total_lines, game_over)

        pygame.display.flip()

if __name__ == "__main__":
    main()

8. Starten

Speichere den Code als tetris.py und starte ihn mit:

python tetris.py

© 2025 MaDe-Online