480 lines
22 KiB
Python
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
|