Tetris/src/game/game.py
2024-01-04 06:34:29 +02:00

232 lines
7.1 KiB
Python

from typing import Callable, Optional
import numpy as np
import pygame
from utils import CONFIG, Direction, Field, Figure
from .block import Block
from .log import log
from .tetromino import Tetromino
from .timer import Timer, Timers
class Game:
def __init__(
self,
get_next_figure: Callable[[], Figure],
update_score: Callable[[int, int, int], None],
) -> None:
self.surface = pygame.Surface(CONFIG.game.size)
self.dispaly_surface = pygame.display.get_surface()
self.rect = self.surface.get_rect(topleft=CONFIG.game.pos)
self.sprites: pygame.sprite.Group[Block] = pygame.sprite.Group()
self.get_next_shape = get_next_figure
self.update_score = update_score
self._create_grid_surface()
self.field = self._generate_empty_field()
self.tetromino = Tetromino(
self.sprites,
self.create_new_tetromino,
self.field,
)
self.initial_block_speed = CONFIG.game.initial_speed
self.increased_block_speed = self.initial_block_speed * 0.3
self.down_pressed = False
self.timers = Timers(
Timer(self.initial_block_speed, True, self.move_down),
Timer(CONFIG.game.movment_delay),
Timer(CONFIG.game.rotation_delay),
)
self.timers.vertical.activate()
self.level = 1
self.score = 0
self.lines = 0
def run(self) -> None:
self.dispaly_surface.blit(self.surface, CONFIG.game.pos)
self.draw()
self._timer_update()
self.handle_event()
def draw(self) -> None:
self.surface.fill(CONFIG.colors.bg_float)
self.update()
self.sprites.draw(self.surface)
self._draw_border()
self._draw_grid()
def update(self) -> None:
self.sprites.update()
def handle_event(self) -> None:
keys = pygame.key.get_pressed()
left_keys = keys[pygame.K_LEFT] or keys[pygame.K_a] or keys[pygame.K_h]
right_keys = keys[pygame.K_RIGHT] or keys[pygame.K_d] or keys[pygame.K_l]
down_keys = keys[pygame.K_DOWN] or keys[pygame.K_s] or keys[pygame.K_j]
rotate_keys = (
keys[pygame.K_SPACE]
or keys[pygame.K_r]
or keys[pygame.K_UP]
or keys[pygame.K_w]
or keys[pygame.K_k]
)
if not self.timers.horizontal.active:
if left_keys:
self.move_left()
self.timers.horizontal.activate()
elif right_keys:
self.move_right()
self.timers.horizontal.activate()
if not self.timers.rotation.active:
if rotate_keys:
self.tetromino.rotate()
self.timers.rotation.activate()
if not self.down_pressed and down_keys:
self.down_pressed = True
self.timers.vertical.duration = self.increased_block_speed
if self.down_pressed and not down_keys:
self.down_pressed = False
self.timers.vertical.duration = self.initial_block_speed
def move_down(self) -> None:
self.tetromino.move_down()
def move_left(self) -> None:
self.tetromino.move_horizontal(Direction.LEFT)
def move_right(self) -> None:
self.tetromino.move_horizontal(Direction.RIGHT)
def create_new_tetromino(self) -> None:
if self.game_over():
self.restart()
self._check_finished_rows()
self.tetromino = Tetromino(
self.sprites,
self.create_new_tetromino,
self.field,
self.get_next_shape(),
)
def game_over(self) -> bool:
for block in self.sprites:
if block.pos.y < 0:
log.info("Game over!")
return True
def restart(self) -> None:
self.sprites.empty()
self.field = self._generate_empty_field()
self.tetromino = Tetromino(
self.sprites,
self.create_new_tetromino,
self.field,
self.get_next_shape(),
)
self.level = 1
self.score = 0
self.lines = 0
self.update_score(self.lines, self.score, self.level)
def _create_grid_surface(self) -> None:
self.grid_surface = self.surface.copy()
self.grid_surface.fill("#00ff00")
self.grid_surface.set_colorkey("#00ff00")
self.grid_surface.set_alpha(100)
def _draw_grid(self) -> None:
for col in range(1, CONFIG.game.columns):
x = col * CONFIG.game.cell.width
pygame.draw.line(
self.grid_surface,
CONFIG.colors.border_highlight,
(x, 0),
(x, self.grid_surface.get_height()),
CONFIG.game.line_width,
)
for row in range(1, CONFIG.game.rows):
y = row * CONFIG.game.cell.width
pygame.draw.line(
self.grid_surface,
CONFIG.colors.border_highlight,
(0, y),
(self.grid_surface.get_width(), y),
CONFIG.game.line_width,
)
self.surface.blit(self.grid_surface, (0, 0))
def _draw_border(self) -> None:
pygame.draw.rect(
self.dispaly_surface,
CONFIG.colors.border_highlight,
self.rect,
CONFIG.game.line_width * 2,
CONFIG.game.border_radius,
)
def _timer_update(self) -> None:
for timer in self.timers:
timer.update()
def _check_finished_rows(self) -> None:
delete_rows: list[int] = []
for idx, row in enumerate(self.field):
if all(row):
delete_rows.append(idx)
self._delete_rows(delete_rows)
def _delete_rows(self, delete_rows: list[int]) -> None:
if not delete_rows:
return
self._calculate_score(len(delete_rows))
for row in delete_rows:
for block in self.field[row]:
block.kill()
self._move_rows_down(row)
self._rebuild_field()
def _move_rows_down(self, deleted_row: int) -> None:
for row in self.field:
for block in row:
if block and block.pos.y < deleted_row:
block.pos.y += 1
def _rebuild_field(self) -> None:
self.field = self._generate_empty_field()
for block in self.sprites:
self.field[int(block.pos.y), int(block.pos.x)] = block
def _generate_empty_field(self) -> np.ndarray:
return np.full((CONFIG.game.rows, CONFIG.game.columns), None, dtype=Field)
def _calculate_score(self, rows_deleted: int) -> None:
self.lines += rows_deleted
self.score += CONFIG.game.score.get(rows_deleted, 0) * self.level
# every 10 lines increase level
if self.lines // 10 + 1 > self.level:
self.level += 1
self.initial_block_speed *= 0.75
self.increased_block_speed *= 0.75
self.timers.vertical.duration = self.initial_block_speed
self.update_score(self.lines, self.score, self.level)