game / app.py
egang's picture
Upload app.py with huggingface_hub
429e5f2 verified
import random
import sys
import time
import termios
import tty
from typing import List, Tuple, Optional
# Board dimensions
WIDTH, HEIGHT = 10, 20
# Tetromino shapes
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 (ANSI)
COLORS = ["\033[31m", "\033[32m", "\033[33m", "\033[34m", "\033[35m", "\033[36m", "\033[37m"]
RESET = "\033[0m"
class Tetromino:
def __init__(self, shape: List[List[int]], color: str):
self.shape = shape
self.color = color
self.x = WIDTH // 2 - len(shape[0]) // 2
self.y = 0
def rotated(self) -> List[List[int]]:
"""Return the shape rotated 90Β° clockwise."""
return [list(row) for row in zip(*self.shape[::-1])]
class Tetris:
def __init__(self):
self.board: List[List[Optional[str]]] = [[None] * WIDTH for _ in range(HEIGHT)]
self.current: Tetromino = self.new_piece()
self.next: Tetromino = self.new_piece()
self.game_over = False
self.score = 0
self.level = 1
self.drop_time = 0.5 # seconds between drops
def new_piece(self) -> Tetromino:
shape = random.choice(SHAPES)
color = random.choice(COLORS)
return Tetromino(shape, color)
def valid(self, shape: List[List[int]], x: int, y: int) -> bool:
for dy, row in enumerate(shape):
for dx, cell in enumerate(row):
if cell:
nx, ny = x + dx, y + dy
if nx < 0 or nx >= WIDTH or ny >= HEIGHT or (ny >= 0 and self.board[ny][nx]):
return False
return True
def lock_piece(self):
shape = self.current.shape
x, y = self.current.x, self.current.y
for dy, row in enumerate(shape):
for dx, cell in enumerate(row):
if cell and y + dy >= 0:
self.board[y + dy][x + dx] = self.current.color
self.clear_lines()
self.current = self.next
self.next = self.new_piece()
if not self.valid(self.current.shape, self.current.x, self.current.y):
self.game_over = True
def clear_lines(self):
new_board = [row for row in self.board if any(cell is None for cell in row)]
lines_cleared = HEIGHT - len(new_board)
if lines_cleared:
self.score += (lines_cleared ** 2) * 100 * self.level
for _ in range(lines_cleared):
new_board.insert(0, [None] * WIDTH)
self.board = new_board
self.level = self.score // 1000 + 1
self.drop_time = max(0.05, 0.5 - (self.level - 1) * 0.05)
def move(self, dx: int, dy: int) -> bool:
new_x = self.current.x + dx
new_y = self.current.y + dy
if self.valid(self.current.shape, new_x, new_y):
self.current.x, self.current.y = new_x, new_y
return True
return False
def rotate(self):
rotated = self.current.rotated()
if self.valid(rotated, self.current.x, self.current.y):
self.current.shape = rotated
else:
# Try wall kicks (simple)
for dx in (-1, 1, -2, 2):
if self.valid(rotated, self.current.x + dx, self.current.y):
self.current.x += dx
self.current.shape = rotated
break
def hard_drop(self):
while self.move(0, 1):
pass
self.lock_piece()
def draw(self):
# Clear screen + hide cursor
print("\033[2J\033[?25l", end="")
# Draw board with border
output = []
output.append("Score: {} Level: {}".format(self.score, self.level))
output.append("β”Œ" + "─" * WIDTH + "┐")
for y in range(HEIGHT):
line = []
for x in range(WIDTH):
if (0 <= y - self.current.y < len(self.current.shape) and
0 <= x - self.current.x < len(self.current.shape[0]) and
self.current.shape[y - self.current.y][x - self.current.x]):
line.append(self.current.color + "β– " + RESET)
else:
cell = self.board[y][x]
line.append(cell + "β– " + RESET if cell else " ")
output.append("β”‚" + "".join(line) + "β”‚")
output.append("β””" + "─" * WIDTH + "β”˜")
# Draw next
output.append("Next:")
for row in self.next.shape:
line = []
for cell in row:
line.append(self.next.color + "β– " + RESET if cell else " ")
output.append(" " + "".join(line))
output.append("\nControls: ← β†’ ↓ rotate: ↑ drop: space quit: q")
print("\n".join(output), end="", flush=True)
def run(self):
last_drop = time.time()
while not self.game_over:
self.draw()
ch = self.getch()
if ch == '\x1b': # ESC sequence
ch += sys.stdin.read(2)
if ch in ('q', '\x03'): # q or Ctrl-C
break
elif ch == '\x1b[A': # up
self.rotate()
elif ch == '\x1b[B': # down
self.move(0, 1)
elif ch == '\x1b[C': # right
self.move(1, 0)
elif ch == '\x1b[D': # left
self.move(-1, 0)
elif ch == ' ':
self.hard_drop()
last_drop = time.time()
# Auto-drop
if time.time() - last_drop > self.drop_time:
if not self.move(0, 1):
self.lock_piece()
last_drop = time.time()
print("\033[?25h\033[2JGame Over! Score: {}".format(self.score))
def getch(self) -> str:
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
return ch
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
if __name__ == "__main__":
try:
Tetris().run()
except KeyboardInterrupt:
print("\033[?25h") # restore cursor