mirror of
https://github.com/kristoferssolo/Tetris.git
synced 2025-10-21 20:00:35 +00:00
232 lines
7.1 KiB
Python
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)
|