Tetris en Python con curses

Tetris en modo texto para terminal, escrito en Python con el módulo curses de la biblioteca estándar. Incluye las siete piezas con rotación, eliminación de líneas, puntuación, niveles progresivos y muestra la siguiente pieza. Funciona en Linux y macOS sin instalar nada; en Windows solo requiere windows-curses.
				"""
tetris.py — Juego Tetris en modo texto

Versión Python del clásico Tetris de Víctor Barbero (2001).
Usa el módulo curses de la biblioteca estándar.

Autor: David Carrero Fernandez-Baillo
Web:   https://carrero.es

Requisitos: Python 3.8+, módulo curses (incluido en la biblioteca estándar)
  - Linux/macOS: sin dependencias adicionales
  - Windows:     pip install windows-curses
Controles: ? ? mover  ? rotar  ? bajar  Espacio caída rápida  ESC salir
Ejecutar: python tetris.py

Versión Pascal original: https://programacion.net/codigo/tetris_63
Versión PHP:             https://programacion.net/codigo/tetris-php_1895
"""

import curses
import random
import time

FILAS = 20
COLS  = 10

FORMAS = [
    [(0,0),(0,1),(0,2),(0,3)],   # I
    [(0,0),(0,1),(1,0),(1,1)],   # O
    [(0,0),(0,1),(0,2),(1,1)],   # T
    [(0,1),(0,2),(1,0),(1,1)],   # S
    [(0,0),(0,1),(1,1),(1,2)],   # Z
    [(0,0),(1,0),(1,1),(1,2)],   # J
    [(0,2),(1,0),(1,1),(1,2)],   # L
]


def rotar(forma):
    rotada = [(dc, -dr) for dr, dc in forma]
    min_r  = min(r for r, _ in rotada)
    min_c  = min(c for _, c in rotada)
    return [(r - min_r, c - min_c) for r, c in rotada]


def valida(tablero, forma, pr, pc):
    for dr, dc in forma:
        r, c = pr + dr, pc + dc
        if r < 0 or r >= FILAS or c < 0 or c >= COLS or tablero[r][c]:
            return False
    return True


def colocar(tablero, forma, pr, pc, color):
    for dr, dc in forma:
        tablero[pr + dr][pc + dc] = color


def limpiar_lineas(tablero):
    nuevas = [fila for fila in tablero if not all(fila)]
    n = FILAS - len(nuevas)
    for _ in range(n):
        nuevas.insert(0, [0] * COLS)
    tablero[:] = nuevas
    return n


def nueva_pieza():
    idx = random.randrange(len(FORMAS))
    return list(FORMAS[idx]), idx + 1


def dibujar(ven, tablero, forma, pr, pc, color, sig_forma, sig_color, puntos, nivel, lineas):
    ven.erase()
    try:
        ven.addstr(0, 0,
                   f"? ? mover  ? rotar  ? bajar  Espacio: caída  ESC: salir   "
                   f"Puntos:{puntos}  Nivel:{nivel}  Lineas:{lineas}",
                   curses.color_pair(8) | curses.A_BOLD)

        ven.addstr(FILAS + 1, 0, '+' + '--' * COLS + '+')
        for r in range(FILAS):
            ven.addch(r + 1, 0, '|')
            ven.addch(r + 1, COLS * 2 + 1, '|')

        for r in range(FILAS):
            for c in range(COLS):
                if tablero[r][c]:
                    ven.addstr(r + 1, c * 2 + 1, '[]',
                               curses.color_pair(tablero[r][c]) | curses.A_BOLD)
                else:
                    ven.addstr(r + 1, c * 2 + 1, '. ')

        for dr, dc in forma:
            r, c = pr + dr, pc + dc
            if 0 <= r < FILAS and 0 <= c < COLS:
                ven.addstr(r + 1, c * 2 + 1, '[]', curses.color_pair(color) | curses.A_BOLD)

        ven.addstr(2, COLS * 2 + 3, 'Sig:', curses.color_pair(8))
        for dr, dc in sig_forma:
            ven.addstr(3 + dr, COLS * 2 + 3 + dc * 2, '[]',
                       curses.color_pair(sig_color) | curses.A_BOLD)
    except curses.error:
        pass
    ven.refresh()


def jugar(ven):
    curses.curs_set(0)
    ven.nodelay(True)
    curses.start_color()
    curses.init_pair(1, curses.COLOR_CYAN,    curses.COLOR_BLACK)  # I
    curses.init_pair(2, curses.COLOR_YELLOW,  curses.COLOR_BLACK)  # O
    curses.init_pair(3, curses.COLOR_MAGENTA, curses.COLOR_BLACK)  # T
    curses.init_pair(4, curses.COLOR_GREEN,   curses.COLOR_BLACK)  # S
    curses.init_pair(5, curses.COLOR_RED,     curses.COLOR_BLACK)  # Z
    curses.init_pair(6, curses.COLOR_BLUE,    curses.COLOR_BLACK)  # J
    curses.init_pair(7, curses.COLOR_WHITE,   curses.COLOR_BLACK)  # L
    curses.init_pair(8, curses.COLOR_GREEN,   curses.COLOR_BLACK)  # HUD

    tablero              = [[0] * COLS for _ in range(FILAS)]
    forma, color         = nueva_pieza()
    sig_forma, sig_color = nueva_pieza()
    pr, pc               = 0, COLS // 2 - 2
    puntos = 0
    nivel  = 1
    lineas = 0
    ultimo = time.time()

    while True:
        retardo = max(0.1, 0.5 - (nivel - 1) * 0.04)
        dibujar(ven, tablero, forma, pr, pc, color, sig_forma, sig_color, puntos, nivel, lineas)

        tecla = ven.getch()
        if tecla == 27:
            return
        elif tecla == curses.KEY_LEFT and valida(tablero, forma, pr, pc - 1):
            pc -= 1
        elif tecla == curses.KEY_RIGHT and valida(tablero, forma, pr, pc + 1):
            pc += 1
        elif tecla == curses.KEY_UP:
            rot = rotar(forma)
            if valida(tablero, rot, pr, pc):
                forma = rot
        elif tecla == curses.KEY_DOWN and valida(tablero, forma, pr + 1, pc):
            pr += 1
        elif tecla == ord(' '):
            while valida(tablero, forma, pr + 1, pc):
                pr += 1

        if time.time() - ultimo >= retardo:
            ultimo = time.time()
            if valida(tablero, forma, pr + 1, pc):
                pr += 1
            else:
                colocar(tablero, forma, pr, pc, color)
                n = limpiar_lineas(tablero)
                lineas += n
                puntos += [0, 100, 300, 500, 800][n] * nivel
                nivel   = lineas // 10 + 1
                forma, color         = sig_forma, sig_color
                sig_forma, sig_color = nueva_pieza()
                pr, pc               = 0, COLS // 2 - 2

                if not valida(tablero, forma, pr, pc):
                    dibujar(ven, tablero, forma, pr, pc, color,
                            sig_forma, sig_color, puntos, nivel, lineas)
                    ven.nodelay(False)
                    try:
                        ven.addstr(FILAS // 2, 2, f"GAME OVER  Puntos: {puntos}",
                                   curses.color_pair(5) | curses.A_BOLD)
                    except curses.error:
                        pass
                    ven.refresh()
                    ven.getch()
                    return

        time.sleep(0.02)


if __name__ == '__main__':
    curses.wrapper(jugar)

			
Descargar adjuntos
COMPARTE ESTE TUTORIAL

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
TUTORIAL ANTERIOR