Files
tetri5/tetri5/scene.py

480 lines
22 KiB
Python

import pygame
from types import FunctionType
from tetri5.util import ConfigurationManager
from tetri5.util import TextGenerator
from tetri5.util import Controller
from tetri5.util import SoundManager
from tetri5.entity import SquareColor, Well
from tetri5.entity import Stack
from tetri5.entity import PieceGenerator
from tetri5.online import *
from tetri5.modal import GameOverModal
"""
TODO
"""
class Scene:
pass
"""
TODO
"""
class TitleScene(Scene):
# Title screen options
ONE_PLAYER = "1 PLAYER"
TWO_PLAYER = "2 PLAYER"
def __init__(self, change_scene: FunctionType) -> None:
self._tile_size = ConfigurationManager.get("engine", "tile-size")
self._background_color = pygame.Color(ConfigurationManager.get("color", "window-bg"))
self._logo_image = pygame.image.load(ConfigurationManager.get("image", "title-screen"))
self._cursor_pos_one = ConfigurationManager.get("position", "cursor-option-one")
self._cursor_pos_two = ConfigurationManager.get("position", "cursor-option-two")
self._cursor_pos = self._cursor_pos_one
self._cursor_blink_interval = ConfigurationManager.get("engine", "cursor-blink-interval")
self._cursor_blink_time = 0
self._cursor_color = pygame.Color(ConfigurationManager.get("color", "cursor"))
self._cursor_off = False
self._logo_pos = ConfigurationManager.get("position", "title-logo")
self._option_one_pos = ConfigurationManager.get("position", "option-one")
self._option_two_pos = ConfigurationManager.get("position", "option-two")
self._is_multiplayer = False
self._change_scence = change_scene
def draw(self, surface: pygame.Surface) -> None:
surface.fill(self._background_color)
surface.blit(self._logo_image, self._logo_pos)
TextGenerator.draw(TitleScene.ONE_PLAYER, self._option_one_pos, surface)
TextGenerator.draw(TitleScene.TWO_PLAYER, self._option_two_pos, surface)
if self._cursor_off:
pygame.draw.circle(surface, self._cursor_color, self._cursor_pos, self._tile_size // 3)
def update(self, elapsed_time: int) -> None:
option_change = False
if Controller.key_pressed(pygame.K_UP) and self._is_multiplayer:
self._cursor_pos = self._cursor_pos_one
self._is_multiplayer = False
option_change = True
if Controller.key_pressed(pygame.K_DOWN) and not self._is_multiplayer:
self._cursor_pos = self._cursor_pos_two
self._is_multiplayer = True
option_change = True
if option_change:
SoundManager.play_option_change_sfx() # TODO add cool down
self._cursor_blink_time += elapsed_time
if self._cursor_blink_time >= self._cursor_blink_interval:
self._cursor_blink_time = 0
self._cursor_off = not self._cursor_off
if Controller.key_pressed(pygame.K_RETURN):
self._change_scence(SinglePlayerScene(self._change_scence) if not self._is_multiplayer else ConnectionScene(self._change_scence))
"""
TODO
"""
class SinglePlayerScene(Scene):
def __init__(self, change_scene: FunctionType) -> None:
self._top_label_pos = ConfigurationManager.get("position", "top-label")
self._top_value_pos = ConfigurationManager.get("position", "top-value")
self._score_label_pos = ConfigurationManager.get("position", "score-label")
self._score_value_pos = ConfigurationManager.get("position", "score-value")
self._lines_label_pos = ConfigurationManager.get("position", "lines-label")
self._next_label_pos = ConfigurationManager.get("position", "next-label")
self._level_label_pos = ConfigurationManager.get("position", "level-label")
self._next_piece_pos = ConfigurationManager.get("position", "next-piece")
self._spawn_piece_shift = ConfigurationManager.get("position", "spawn-piece")
self._tile_size = ConfigurationManager.get("engine", "tile-size")
self._background_color = pygame.Color(ConfigurationManager.get("color", "window-bg"))
self._points_table = ConfigurationManager.get("engine", "points-table")
self._lines_per_level = ConfigurationManager.get("engine", "lines-per-level")
self._score = 0
self._previous_level = 0
self._current_piece = None
self._next_piece = PieceGenerator.get_piece(self._next_piece_pos)
self._well = Well(ConfigurationManager.get("position", "well"),\
ConfigurationManager.get("color", "well-1"),\
ConfigurationManager.get("color", "well-border-1"))
self._stack = Stack()
self._game_over_modal = GameOverModal()
self._change_scence = change_scene
SoundManager.play_theme_music_single()
def draw(self, surface: pygame.Surface) -> None:
if self._game_over_modal.is_open:
self._game_over_modal.draw(surface)
return
surface.fill(self._background_color)
if self._stack:
self._stack.draw(surface)
if self._current_piece:
self._current_piece.draw(surface, self._well, self._stack)
if self._next_piece:
self._next_piece.draw(surface)
if self._well:
self._well.draw(surface)
score = str(self._score).zfill(6)
lines = str(self._stack.total_lines).zfill(4)
level = str(self._get_level()).zfill(2)
TextGenerator.draw("Top", self._top_label_pos, surface)
TextGenerator.draw("000000", self._top_value_pos, surface)
TextGenerator.draw("Score", self._score_label_pos, surface)
TextGenerator.draw(score, self._score_value_pos, surface)
TextGenerator.draw("Lines " + lines, self._lines_label_pos, surface)
TextGenerator.draw("Next", self._next_label_pos, surface)
TextGenerator.draw("LVL " + level, self._level_label_pos, surface)
def update(self, elapsed_time: int) -> None:
if self._game_over_modal.is_open:
self._game_over_modal.update(elapsed_time)
return
if self._current_piece:
self._current_piece.update(elapsed_time,\
self._well,\
self._stack,\
self._get_level(),\
self._clear_current_piece)
else:
self._current_piece = self._next_piece
self._current_piece.move(self._spawn_piece_shift)
self._next_piece = PieceGenerator.get_piece(self._next_piece_pos)
# TODO create game over scene
if self._stack and self._current_piece.collide(self._stack):
SoundManager.stop_theme_music_single()
self._game_over_modal.show(False)
if self._stack:
self._stack.update(elapsed_time)
self._score += self._points_table[self._stack.lines_completed_last] * (self._get_level() + 1)
if self._previous_level != self._get_level():
self._previous_level = self._get_level()
SoundManager.play_level_up_sfx()
def _get_level(self) -> int:
return 0 if self._stack is None else self._stack.total_lines // self._lines_per_level
def _clear_current_piece(self) -> None:
self._current_piece = None
class ConnectionScene(Scene):
MAX_GAME_ID_LEN = 5
MAX_PERIOD_COUNT = 3
def __init__(self, change_scene: FunctionType) -> None:
self._background_color = pygame.Color(ConfigurationManager.get("color", "window-bg"))
self._is_connecting = False
self._waiting_for_opponent = False
self._game_id_label_pos = ConfigurationManager.get("position", "game-id-label")
self._game_id_label = "Enter Game Id: "
self._game_id = "_"
self._cursor_blink_interval = ConfigurationManager.get("engine", "cursor-blink-interval")
self._cursor_blink_acc = 0 # blink accumulator
self._connecting_label_pos = ConfigurationManager.get("position", "connecting-label")
self._connecting_label = "Connecting"
self._waiting_for_opponent_label_pos = ConfigurationManager.get("position", "waiting-for-opponent-label")
self._waiting_for_opponent_label = "Waiting for opponent"
self._period_blink_interval = ConfigurationManager.get("engine", "period-blink-interval")
self._period_blink_acc = 0 # period accumulator
self._ping_interval = ConfigurationManager.get("engine", "ping-interval")
self._ping_acc = 0 # ping accumulator
self._change_scene = change_scene
def draw(self, surface: pygame.Surface) -> None:
surface.fill(self._background_color)
if not self._is_connecting and not self._waiting_for_opponent:
TextGenerator.draw(self._game_id_label + self._game_id, self._game_id_label_pos, surface)
elif self._is_connecting and not self._waiting_for_opponent:
TextGenerator.draw(self._connecting_label, self._connecting_label_pos, surface)
else:
TextGenerator.draw(self._waiting_for_opponent_label, self._waiting_for_opponent_label_pos, surface)
def update(self, elapsed_time: int) -> None:
# cursor blink logic
self._cursor_blink_acc += elapsed_time
if self._cursor_blink_acc >= self._cursor_blink_interval:
self._cursor_blink_acc = 0
if self._game_id.find("_") != -1:
self._game_id = self._game_id.replace("_", "")
else:
self._game_id += "_"
# period ellipsis logic for connecting
if self._is_connecting and not self._waiting_for_opponent:
self._period_blink_acc += elapsed_time
if self._period_blink_acc >= self._period_blink_interval:
self._period_blink_acc = 0
period_count = self._connecting_label.count(".")
if period_count < self.MAX_PERIOD_COUNT:
self._connecting_label += "."
else:
self._connecting_label = self._connecting_label.replace(".", "")
# period ellipsis logic for waiting on opponent
if self._waiting_for_opponent:
self._period_blink_acc += elapsed_time
if self._period_blink_acc >= self._period_blink_interval:
self._period_blink_acc = 0
period_count = self._waiting_for_opponent_label.count(".")
if period_count < self.MAX_PERIOD_COUNT:
self._waiting_for_opponent_label += "."
else:
self._waiting_for_opponent_label = self._waiting_for_opponent_label.replace(".", "")
# keyboard input
for event in pygame.event.get(pygame.KEYDOWN):
# user input logic
if not self._is_connecting:
self._game_id = self._game_id.replace("_", "")
if event.key == pygame.K_BACKSPACE:
self._game_id = self._game_id[:-1]
elif (event.unicode.isalpha() or event.unicode.isdigit()) and\
len(self._game_id) <= self.MAX_GAME_ID_LEN:
self._game_id += event.unicode.upper()
# connection logic
if event.key == pygame.K_RETURN and len(self._game_id) > 0 and not self._is_connecting:
self._is_connecting = True
MultiplayerService.init()
MultiplayerService.enter_game(self._game_id)
# server messaging logic
self._ping_acc += elapsed_time
message = MultiplayerService.try_receive_message();
if message: # TODO remove later, only for testing
print(message)
if message == MultiplayerService.WAIT_FOR_OPPONENT:
self._waiting_for_opponent = True
if message == MultiplayerService.START_GAME:
self._change_scene(MultiplayerScene(self._change_scene))
if message is None and self._ping_acc >= self._ping_interval:
self._ping_acc = 0
MultiplayerService.send_message("ping")
"""
TODO
"""
class MultiplayerScene(Scene):
def __init__(self, change_scene: FunctionType) -> None:
self._tile_size = ConfigurationManager.get("engine", "tile-size")
self._background_color = pygame.Color(ConfigurationManager.get("color", "window-bg"))
self._lines_per_level = ConfigurationManager.get("engine", "lines-per-level")
self._points_table = ConfigurationManager.get("engine", "points-table")
self._score_player_one = 0
self._score_player_two = 0
self._total_lines_player_two = 0
# wells init
self._well_player_one = Well(ConfigurationManager.get("position", "well-player-1"),\
ConfigurationManager.get("color", "well-1"),\
ConfigurationManager.get("color", "well-border-1"))
self._well_player_two = Well(ConfigurationManager.get("position", "well-player-2"),\
ConfigurationManager.get("color", "well-1"),\
ConfigurationManager.get("color", "well-border-1"))
# stacks init
self._stack_player_one = Stack()
self._stack_player_two = Stack()
# score positions
self._score_label_player_one_pos = ConfigurationManager.get("position", "score-label-player-1")
self._score_label_player_two_pos = ConfigurationManager.get("position", "score-label-player-2")
self._score_value_player_one_pos = ConfigurationManager.get("position", "score-value-player-1")
self._score_value_player_two_pos = ConfigurationManager.get("position", "score-value-player-2")
# lines positions
self._lines_label_player_one_pos = ConfigurationManager.get("position", "lines-label-player-1")
self._lines_label_player_two_pos = ConfigurationManager.get("position", "lines-label-player-2")
# next positions
self._next_label_player_one_pos = ConfigurationManager.get("position", "next-label-player-1")
# piece positions
self._next_piece_player_one_pos = ConfigurationManager.get("position", "next-piece-player-1")
self._spawn_piece_shift_player_one = ConfigurationManager.get("position", "spawn-piece-player-1")
self._spawn_piece_shift_player_two = ConfigurationManager.get("position", "spawn-piece-player-2")
# entities
self._next_piece_player_one = PieceGenerator.get_piece(self._next_piece_player_one_pos)
self._current_piece_player_one = None
self._current_piece_player_two = None
self._game_over_modal = GameOverModal()
self._well_full = False
self._change_scence = change_scene
SoundManager.play_theme_music_multi()
def draw(self, surface: pygame.Surface) -> None:
if self._game_over_modal.is_open:
self._game_over_modal.draw(surface)
return
surface.fill(self._background_color)
# stacks
if self._stack_player_one is not None:
self._stack_player_one.draw(surface)
if self._stack_player_two is not None:
self._stack_player_two.draw(surface)
# pieces
if self._current_piece_player_one is not None:
self._current_piece_player_one.draw(surface, self._well_player_one, self._stack_player_one)
if self._current_piece_player_two is not None:
self._current_piece_player_two.draw(surface, self._well_player_two, self._stack_player_two, True)
if self._next_piece_player_one is not None:
self._next_piece_player_one.draw(surface)
# wells
if self._well_player_one is not None:
self._well_player_one.draw(surface)
if self._well_player_two is not None:
self._well_player_two.draw(surface)
score_player_one = str(self._score_player_one).zfill(6)
score_player_two = str(self._score_player_two).zfill(6)
lines_player_one = str(self._stack_player_one.total_lines).zfill(4)
lines_player_two = str(self._total_lines_player_two).zfill(4)
# scores
TextGenerator.draw("Score", self._score_label_player_one_pos, surface)
TextGenerator.draw(score_player_one, self._score_value_player_one_pos, surface)
TextGenerator.draw("Score", self._score_label_player_two_pos, surface)
TextGenerator.draw(score_player_two, self._score_value_player_two_pos, surface)
# lines
TextGenerator.draw("Lines " + lines_player_one, self._lines_label_player_one_pos, surface)
TextGenerator.draw("Lines " + lines_player_two, self._lines_label_player_two_pos, surface)
# next
TextGenerator.draw("NEXT", self._next_label_player_one_pos, surface)
def update(self, elapsed_time: int) -> None:
if self._game_over_modal.is_open:
self._game_over_modal.update(elapsed_time)
return
self._update_piece_player_one(elapsed_time)
self._update_piece_player_two()
if self._stack_player_one is not None:
self._stack_player_one.update(elapsed_time)
self._update_stack_player_two()
self._score_player_one += self._points_table[self._stack_player_one.lines_completed_last] * (self._get_level_player_one() + 1)
opponent_stats = MultiplayerService.try_receive_stats()
if opponent_stats is not None:
self._score_player_two = opponent_stats.score
self._total_lines_player_two = opponent_stats.lines
if opponent_stats.is_well_full:
SoundManager.stop_theme_music_multi()
self._game_over_modal.show(True)
if self._current_piece_player_one is not None:
MultiplayerService.send_piece(PieceDto(self._current_piece_player_one._points,\
self._current_piece_player_one._center,\
self._current_piece_player_one._base_color,\
self._current_piece_player_one._inner_color,\
self._current_piece_player_one._outer_color))
if self._stack_player_one is not None:
MultiplayerService.send_stack(
StackDto(
self._stack_player_one._points,
[
x.__dict__
for x in self._stack_player_one._square_colors
],
)
)
MultiplayerService.send_stats(StatsDto(self._score_player_one, self._stack_player_one.total_lines, self._well_full))
def _update_piece_player_one(self, elapsed_time: int) -> None:
if self._current_piece_player_one is not None:
self._current_piece_player_one.update(elapsed_time,\
self._well_player_one,\
self._stack_player_one,\
self._get_level_player_one(),\
self._clear_current_piece_player_one)
else:
self._current_piece_player_one = self._next_piece_player_one
self._current_piece_player_one.move(self._spawn_piece_shift_player_one)
self._next_piece_player_one = PieceGenerator.get_piece(self._next_piece_player_one_pos)
# TODO create game over scene
if self._stack_player_one and self._current_piece_player_one.collide(self._stack_player_one):
self._well_full = True
SoundManager.stop_theme_music_multi()
self._game_over_modal.show(False)
def _update_piece_player_two(self) -> None:
if self._current_piece_player_two is not None:
# get opponent piece from server and modify for current client
opponent_piece = MultiplayerService.try_receive_piece()
if opponent_piece is not None:
for square in opponent_piece.points:
for vertex in square:
vertex[0] += 400
opponent_piece.center[0] += 400
# load opponent piece into game
self._current_piece_player_two._points = opponent_piece.points
self._current_piece_player_two._center = opponent_piece.center
self._current_piece_player_two._base_color = opponent_piece.base_color.replace("player-1", "player-2")
if opponent_piece.inner_color is not None:
self._current_piece_player_two._inner_color = opponent_piece.inner_color.replace("player-1", "player-2")
else:
self._current_piece_player_two._inner_color = None
self._current_piece_player_two._outer_color = opponent_piece.outer_color.replace("player-1", "player-2")
else:
self._current_piece_player_two = PieceGenerator.get_opponent_piece(None, None, None)
def _update_stack_player_two(self) -> None:
if self._stack_player_two is not None:
# get opponent stack from server and modify for current client
opponent_stack = MultiplayerService.try_receive_stack()
if opponent_stack is not None:
for square in opponent_stack.points:
for vertex in square:
vertex[0] += 400
# load opponent stack into game
self._stack_player_two._points = opponent_stack.points
self._stack_player_two._square_colors = [
SquareColor(x["base_color"].replace("player-1", "player-2"),\
x["inner_color"].replace("player-1", "player-2") if x["inner_color"] else None,\
x["outer_color"].replace("player-1", "player-2"))
for x in opponent_stack.square_colors
]
def _get_level_player_one(self) -> int:
return 0 if self._stack_player_one is None else self._stack_player_one.total_lines // self._lines_per_level
def _get_level_player_two(self) -> int:
return 0 if self._stack_player_two is None else self._stack_player_two.total_lines // self._lines_per_level
def _clear_current_piece_player_one(self) -> None:
self._current_piece_player_one = None
def _clear_current_piece_player_two(self) -> None:
self._current_piece_player_two = None