pygame-tetris/tetris_pygame_hatch/tetris.py
2024-01-19 16:39:03 +08:00

102 lines
3.6 KiB
Python

from typing import List, Optional
import pygame as pg
from tetris_pygame_hatch import tetromino
from tetris_pygame_hatch.tetromino import Tetromino
from .settings import *
class Tetris:
def __init__(self, app) -> None:
from .app import App
from .tetromino import Tetromino, Block
self.app: App
self.sprite_group: pg.sprite.Group
self.tetromino: Tetromino
self.blocks: List[List[Optional[Block]]]
self.app = app
self.tetromino_shaper = TETROMINO_GENERATOR
# TODO Advanced sprites
# initialze before tetromino for dependency
self.sprite_group = pg.sprite.Group()
self.blocks = [[None for i in range(FIELD_W)] for j in range(FIELD_H)]
self.tetromino = Tetromino(self, next(self.tetromino_shaper))
def control(self, pressed_key):
'''
Game control. Note that pause is in app check_events.
Parameters
----------
pressed_key : int
pygame pressed_key
'''
if pressed_key == pg.K_LEFT or pressed_key == pg.K_a:
self.tetromino.move('l')
elif pressed_key == pg.K_RIGHT or pressed_key == pg.K_d:
self.tetromino.move('r')
elif pressed_key == pg.K_DOWN or pressed_key == pg.K_s:
self.tetromino.move('d')
elif pressed_key == pg.K_w or pressed_key == pg.K_UP:
self.tetromino.rotate()
elif pressed_key == pg.K_SPACE:
# FIXME race condition maybe
while not self.tetromino.landed:
self.tetromino.move('d')
self.app.fall_trigger = True
def remove_line(self, y: int) -> bool:
if all(self.blocks[y]):
for block_on_y in self.blocks[y]:
if block_on_y is not None:
block_on_y.dead = True
for i in range(y, 0, -1):
for j in range(FIELD_W):
block = self.blocks[i - 1][j]
if block is not None:
block.pos += vec(0, 1)
if self.blocks[i - 1][j] is not None:
self.blocks[i][j] = self.blocks[i - 1][j]
else: self.blocks[i][j] = None
return True
else: return False
def draw_grid(self):
for x in range(FIELD_W):
for y in range(FIELD_H):
pg.draw.rect(
self.app.screen, 'black',
(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE), 1)
def post_tetromino_landed(self):
if self.tetromino.landed:
# save block status into self.blocks
# Note: after removal the affect line above the operation line would fell by one line.
# this would affect the detection of block removal.
affect_line: List[int] = []
for block in self.tetromino.blocks:
x, y = tuple(map(int, block.pos))
self.blocks[y][x] = block
affect_line.append(y)
for y in affect_line:
while self.remove_line(y): pass
self.tetromino = Tetromino(self, next(self.tetromino_shaper))
def update(self):
'''
events to update,
mainly handle at every 'app.fall_timeout' seconds
'''
if self.app.fall_trigger:
self.tetromino.update()
self.post_tetromino_landed()
# update the tetromino location since it may changed by self.tetris.control in MainApp event handler
self.sprite_group.update()
def draw(self):
self.draw_grid()
self.sprite_group.draw(self.app.screen)