You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
4.2 KiB
141 lines
4.2 KiB
#!/usr/bin/env python3 |
|
"""Implementazione minimale di Tetris da terminale con curses.""" |
|
|
|
import curses |
|
import random |
|
import time |
|
|
|
# Tetromini rappresentati come matrici di 0/1 |
|
SHAPES = [ |
|
[[1, 1, 1, 1]], # I |
|
[[1, 1], [1, 1]], # O |
|
[[0, 1, 0], [1, 1, 1]], # T |
|
[[0, 1, 1], [1, 1, 0]], # S |
|
[[1, 1, 0], [0, 1, 1]], # Z |
|
[[1, 0, 0], [1, 1, 1]], # J |
|
[[0, 0, 1], [1, 1, 1]], # L |
|
] |
|
|
|
COLORS = [1, 2, 3, 4, 5, 6, 7] |
|
|
|
|
|
def new_piece(): |
|
"""Restituisce un pezzo casuale (deep copy).""" |
|
return [row[:] for row in random.choice(SHAPES)] |
|
|
|
|
|
def rotate(piece): |
|
"""Ruota il pezzo di 90 gradi in senso orario.""" |
|
return [list(row) for row in zip(*piece[::-1])] |
|
|
|
|
|
def collide(grid, piece, x, y): |
|
"""Verifica se il pezzo collide con i bordi o con blocchi già presenti.""" |
|
for py, row in enumerate(piece): |
|
for px, cell in enumerate(row): |
|
if cell: |
|
nx, ny = x + px, y + py |
|
if nx < 0 or nx >= len(grid[0]) or ny >= len(grid): |
|
return True |
|
if ny >= 0 and grid[ny][nx]: |
|
return True |
|
return False |
|
|
|
|
|
def lock(grid, piece, x, y, color): |
|
"""Blocca il pezzo nella griglia.""" |
|
for py, row in enumerate(piece): |
|
for px, cell in enumerate(row): |
|
if cell: |
|
grid[y + py][x + px] = color |
|
|
|
|
|
def clear_lines(grid): |
|
"""Elimina le linee complete e ne conta quante.""" |
|
full = [i for i, row in enumerate(grid) if all(row)] |
|
for i in full: |
|
del grid[i] |
|
grid.insert(0, [0] * len(grid[0])) |
|
return len(full) |
|
|
|
|
|
def draw(stdscr, grid, piece, px, py, score): |
|
"""Disegna la griglia, il pezzo attivo e il punteggio.""" |
|
stdscr.clear() |
|
h, w = len(grid), len(grid[0]) |
|
for y, row in enumerate(grid): |
|
for x, cell in enumerate(row): |
|
if cell: |
|
stdscr.addstr(y + 1, x * 2 + 1, "[]", curses.color_pair(cell)) |
|
else: |
|
stdscr.addstr(y + 1, x * 2 + 1, " .") |
|
for y, row in enumerate(piece): |
|
for x, cell in enumerate(row): |
|
if cell and py + y >= 0: |
|
stdscr.addstr(py + y + 1, (px + x) * 2 + 1, "[]", curses.color_pair(8)) |
|
stdscr.addstr(0, w * 2 + 4, f"Score: {score}") |
|
stdscr.refresh() |
|
|
|
|
|
def main(stdscr): |
|
curses.curs_set(0) |
|
stdscr.nodelay(True) |
|
stdscr.timeout(50) |
|
|
|
# Inizializza le coppie di colori |
|
for i in range(1, 8): |
|
curses.init_pair(i, i, curses.COLOR_BLACK) |
|
curses.init_pair(8, curses.COLOR_WHITE, curses.COLOR_WHITE) |
|
|
|
grid = [[0] * 10 for _ in range(20)] |
|
piece = new_piece() |
|
px, py = 3, 0 |
|
score = 0 |
|
fall_time = 0.5 |
|
last_fall = time.time() |
|
|
|
while True: |
|
key = stdscr.getch() |
|
if key == curses.KEY_LEFT: |
|
if not collide(grid, piece, px - 1, py): |
|
px -= 1 |
|
elif key == curses.KEY_RIGHT: |
|
if not collide(grid, piece, px + 1, py): |
|
px += 1 |
|
elif key == curses.KEY_DOWN: |
|
if not collide(grid, piece, px, py + 1): |
|
py += 1 |
|
elif key == ord(" "): |
|
rotated = rotate(piece) |
|
if not collide(grid, rotated, px, py): |
|
piece = rotated |
|
elif not collide(grid, rotated, px + 1, py): |
|
piece, px = rotated, px + 1 |
|
elif not collide(grid, rotated, px - 1, py): |
|
piece, px = rotated, px - 1 |
|
elif key == ord("q"): |
|
break |
|
|
|
now = time.time() |
|
if now - last_fall > fall_time: |
|
if collide(grid, piece, px, py + 1): |
|
lock(grid, piece, px, py, random.choice(COLORS)) |
|
score += clear_lines(grid) * 100 |
|
piece = new_piece() |
|
px, py = 3, 0 |
|
if collide(grid, piece, px, py): |
|
break |
|
else: |
|
py += 1 |
|
last_fall = now |
|
|
|
draw(stdscr, grid, piece, px, py, score) |
|
|
|
stdscr.nodelay(False) |
|
stdscr.addstr(len(grid) // 2, 0, "GAME OVER - Premi un tasto per uscire") |
|
stdscr.refresh() |
|
stdscr.getch() |
|
|
|
|
|
if __name__ == "__main__": |
|
curses.wrapper(main)
|
|
|