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)