diff --git a/config.yaml b/config.yaml index 766c5e9..947502f 100644 --- a/config.yaml +++ b/config.yaml @@ -4,9 +4,13 @@ window: title: "Tetri5" sound: - main-music: "resource/sound/main_music.ogg" - row-completion: "resource/sound/row_completion.wav" - piece-set: "resource/sound/piece_set_3.wav" + theme-music: "resource/sound/theme_music.ogg" + option-change: "resource/sound/option_change.wav" + piece-rotate: "resource/sound/piece_rotate.ogg" + piece-set: "resource/sound/piece_set.ogg" + line-complete: "resource/sound/line_complete.ogg" + four-lines-complete: "resource/sound/four_lines_complete.ogg" + level-up: "resource/sound/level_up.ogg" image: title-screen: "resource/image/title_screen.png" diff --git a/resource/sound/four_lines_complete.ogg b/resource/sound/four_lines_complete.ogg new file mode 100644 index 0000000..58ac5a0 Binary files /dev/null and b/resource/sound/four_lines_complete.ogg differ diff --git a/resource/sound/level_up.ogg b/resource/sound/level_up.ogg new file mode 100644 index 0000000..37b3a3d Binary files /dev/null and b/resource/sound/level_up.ogg differ diff --git a/resource/sound/line_complete.ogg b/resource/sound/line_complete.ogg new file mode 100644 index 0000000..bd866d8 Binary files /dev/null and b/resource/sound/line_complete.ogg differ diff --git a/resource/sound/main_music.wav b/resource/sound/main_music.wav deleted file mode 100644 index f9bc25e..0000000 Binary files a/resource/sound/main_music.wav and /dev/null differ diff --git a/resource/sound/piece_set_3.wav b/resource/sound/option_change.wav similarity index 100% rename from resource/sound/piece_set_3.wav rename to resource/sound/option_change.wav diff --git a/resource/sound/piece_rotate.ogg b/resource/sound/piece_rotate.ogg new file mode 100644 index 0000000..b83c7be Binary files /dev/null and b/resource/sound/piece_rotate.ogg differ diff --git a/resource/sound/piece_set.ogg b/resource/sound/piece_set.ogg new file mode 100644 index 0000000..39379f1 Binary files /dev/null and b/resource/sound/piece_set.ogg differ diff --git a/resource/sound/row_completion.wav b/resource/sound/row_completion.wav deleted file mode 100644 index 9ee10d2..0000000 Binary files a/resource/sound/row_completion.wav and /dev/null differ diff --git a/resource/sound/main_music.ogg b/resource/sound/theme_music.ogg similarity index 100% rename from resource/sound/main_music.ogg rename to resource/sound/theme_music.ogg diff --git a/tetri5/entity.py b/tetri5/entity.py index 2526c4e..e9438b8 100644 --- a/tetri5/entity.py +++ b/tetri5/entity.py @@ -6,6 +6,7 @@ from typing import List, Tuple from types import FunctionType from tetri5.util import ConfigurationManager from tetri5.util import Controller +from tetri5.util import SoundManager """ TODO description @@ -85,7 +86,6 @@ class Piece(Entity): super().__init__(Piece._get_points(shape, position), color, border_color) self._inner_border_color = inner_border_color self._center = self._get_center(shape, position) - self._piece_set_sound = mixer.Channel(2) self._previous_points = None self._previous_center = None @@ -118,6 +118,8 @@ class Piece(Entity): self.rotate() if well and self.collide(well) or stack and self.collide(stack): self.revert() + else: + SoundManager.play_piece_rotate_sfx() if Controller.key_down(pygame.K_LEFT): self.move((-self._tile_size, 0)) if well and self.collide(well) or stack and self.collide(stack): @@ -223,7 +225,7 @@ class Piece(Entity): if self._current_set_time >= self._set_time: self._current_set_time = 0 if self._entity_is_below(well) or self._entity_is_below(stack): - self._play_piece_set_sound() + SoundManager.play_piece_set_sfx() stack.add_piece(self) clear_current_piece() else: @@ -240,10 +242,6 @@ class Piece(Entity): return mimic_points - def _play_piece_set_sound(self) -> None: - piece_set_sound_file = ConfigurationManager.get("sound", "piece-set") - self._piece_set_sound.play(mixer.Sound(piece_set_sound_file)) - def _entity_is_below(self, entity: Entity) -> bool: mimic_points = self._mimic_move((0, self._tile_size)) return entity and entity._collide(mimic_points) @@ -275,7 +273,6 @@ class Stack(Entity): super().__init__([], color, border_color) self.total_lines = 0 self.lines_completed_last = 0 - self.line_completed_sound = mixer.Channel(1) def update(self, elapsed_time: int) -> None: # TODO remove scene argument super().update(elapsed_time) @@ -305,7 +302,11 @@ class Stack(Entity): if not squares_to_exclude: return 0 - self._play_line_completed_sound() + if len(rows_completed) == 4: + SoundManager.play_four_lines_complete_sfx() + else: + SoundManager.play_line_complete_sfx() + new_points = [] for square in self._points: if square not in squares_to_exclude: @@ -314,17 +315,12 @@ class Stack(Entity): vertex[1] <= row_completed for row_completed in rows_completed ) - vertex[1] += self._tile_size * distance_to_move new_points.append(square) self._points = new_points return len(rows_completed) - def _play_line_completed_sound(self) -> None: - line_completed_sound_file = ConfigurationManager.get("sound", "row-completion") - self.line_completed_sound.play(mixer.Sound(line_completed_sound_file)) - """ TODO description """ diff --git a/tetri5/game.py b/tetri5/game.py index c0976b2..ac4e0b3 100644 --- a/tetri5/game.py +++ b/tetri5/game.py @@ -2,21 +2,19 @@ import sys import pygame from tetri5.util import ConfigurationManager from tetri5.util import TextGenerator +from tetri5.util import SoundManager from tetri5.scene import Scene, TitleScene # TODO improve game assets https://www.spriters-resource.com/nes/tetris/ -# TODO should be a singleton and refactor the whole file? +# TODO create a util that manages sfx class Game: _current_scene = None - @classmethod - def change_scene(cls, scene: Scene) -> None: - cls._current_scene = scene - @classmethod def init(cls) -> None: pygame.init() + SoundManager.init() TextGenerator.init(ConfigurationManager.get("image", "font"), (20, 20)) cls._current_scene = TitleScene(Game.change_scene) @@ -32,7 +30,6 @@ class Game: pygame.display.set_caption(win_title) pygame.display.set_icon(pygame.image.load(win_icon)) - # gets called from the games main loop @classmethod def update(cls) -> None: # TODO write not initialized exception @@ -49,12 +46,15 @@ class Game: @classmethod def draw(cls) -> None: # TODO write not initialized exception - if cls._current_scene: cls._current_scene.draw(cls.screen) # update display pygame.display.update() + @classmethod + def change_scene(cls, scene: Scene) -> None: + cls._current_scene = scene + \ No newline at end of file diff --git a/tetri5/scene.py b/tetri5/scene.py index 039a9b0..b92b9a7 100644 --- a/tetri5/scene.py +++ b/tetri5/scene.py @@ -1,10 +1,10 @@ import sys import pygame -from pygame import mixer 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 Well from tetri5.entity import Stack from tetri5.entity import PieceGenerator @@ -39,7 +39,6 @@ class TitleScene(Scene): self._option_one_position = ConfigurationManager.get("position", "option-one") self._option_two_position = ConfigurationManager.get("position", "option-two") self._is_multiplayer = False - self._change_scence = change_scene def draw(self, surface: pygame.Surface) -> None: @@ -53,12 +52,18 @@ class TitleScene(Scene): pygame.draw.circle(surface, self._cursor_color, self._cursor_position, self._tile_size // 3) def update(self, elapsed_time: int) -> None: - if Controller.key_pressed(pygame.K_UP): + option_change = False + if Controller.key_pressed(pygame.K_UP) and self._is_multiplayer: self._cursor_position = self._cursor_position_one self._is_multiplayer = False - if Controller.key_pressed(pygame.K_DOWN): + option_change = True + if Controller.key_pressed(pygame.K_DOWN) and not self._is_multiplayer: self._cursor_position = self._cursor_position_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: @@ -66,7 +71,7 @@ class TitleScene(Scene): self._cursor_off = not self._cursor_off if Controller.key_pressed(pygame.K_RETURN): - self._change_scence(SinglePlayerScene(self._change_scence)) + self._change_scence(SinglePlayerScene(self._change_scence)) # TODO implement multiplayer """ TODO @@ -77,17 +82,14 @@ class SinglePlayerScene(Scene): self._tile_size = ConfigurationManager.get("engine", "tile-size") self._background_color = pygame.Color(ConfigurationManager.get("color", "window-bg")) self._score = 0 - self._level = 0 + self._previous_level = 0 self._well = Well((280, 80), ConfigurationManager.get("color", "well-1"), ConfigurationManager.get("color", "well-border-1")) # TODO calculate position later and redo color config for well self._stack = Stack(ConfigurationManager.get("color", "stack-1"), ConfigurationManager.get("color", "stack-border-1")) self._current_piece = None self._next_piece = PieceGenerator.get_piece((620, 160)) - self._main_music = mixer.Channel(0) - self._main_music.set_volume(0.7) # TODO add volume to the config - self._main_music.play(mixer.Sound(ConfigurationManager.get("sound", "main-music")), -1) self._points_table = ConfigurationManager.get("engine", "points-table") - self._change_scence = change_scene + SoundManager.play_theme_music() def draw(self, surface: pygame.Surface) -> None: surface.fill(self._background_color) @@ -132,6 +134,10 @@ class SinglePlayerScene(Scene): 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: lines_per_level = ConfigurationManager.get("engine", "lines-per-level") diff --git a/tetri5/util.py b/tetri5/util.py index ed5c943..0e737a6 100644 --- a/tetri5/util.py +++ b/tetri5/util.py @@ -1,6 +1,6 @@ import yaml import pygame -from typing import KeysView, Tuple +from typing import Tuple """ TODO description @@ -22,6 +22,77 @@ class ConfigurationManager: else: return cls._configuration[key] +""" + TODO description +""" +class SoundManager: + + # Channels + _theme_music_ch = None + _option_change_sfx_ch = None + _piece_rotate_sfx_ch = None + _piece_set_sfx_ch = None + _line_complete_sfx_ch = None + _four_lines_complete_sfx_ch = None + + # Sounds + _theme_music = None + _option_change_sfx = None + _piece_rotate_sfx = None + _piece_set_sfx = None + _line_complete_sfx = None + _four_lines_complete_sfx = None + + @classmethod + def init(cls) -> None: + pygame.mixer.init() + cls._theme_music_ch = pygame.mixer.Channel(0) + cls._option_change_sfx_ch = pygame.mixer.Channel(1) + cls._piece_rotate_sfx_ch = pygame.mixer.Channel(2) + cls._piece_set_sfx_ch = pygame.mixer.Channel(3) + cls._line_complete_sfx_ch = pygame.mixer.Channel(4) + cls._four_lines_complete_sfx_ch = pygame.mixer.Channel(5) + cls._level_up_sfx_ch = pygame.mixer.Channel(6) + + cls._theme_music = pygame.mixer.Sound(ConfigurationManager.get("sound", "theme-music")) + cls._option_change_sfx = pygame.mixer.Sound(ConfigurationManager.get("sound", "option-change")) + cls._piece_rotate_sfx = pygame.mixer.Sound(ConfigurationManager.get("sound", "piece-rotate")) + cls._piece_set_sfx = pygame.mixer.Sound(ConfigurationManager.get("sound", "piece-set")) + cls._line_complete_sfx = pygame.mixer.Sound(ConfigurationManager.get("sound", "line-complete")) + cls._four_lines_complete_sfx = pygame.mixer.Sound(ConfigurationManager.get("sound", "four-lines-complete")) + cls._level_up_sfx = pygame.mixer.Sound(ConfigurationManager.get("sound", "level-up")) + + @classmethod + def play_theme_music(cls) -> None: + cls._theme_music_ch.set_volume(0.7) + cls._theme_music_ch.play(cls._theme_music, -1) + + @classmethod + def play_option_change_sfx(cls) -> None: + cls._option_change_sfx_ch.set_volume(0.8) + cls._option_change_sfx_ch.play(cls._option_change_sfx) + + @classmethod + def play_piece_rotate_sfx(cls) -> None: + cls._piece_rotate_sfx_ch.set_volume(0.7) + cls._piece_rotate_sfx_ch.play(cls._piece_rotate_sfx) + + @classmethod + def play_piece_set_sfx(cls) -> None: + cls._piece_set_sfx_ch.play(cls._piece_set_sfx) + + @classmethod + def play_line_complete_sfx(cls) -> None: + cls._line_complete_sfx_ch.play(cls._line_complete_sfx) + + @classmethod + def play_four_lines_complete_sfx(cls) -> None: + cls._four_lines_complete_sfx_ch.play(cls._four_lines_complete_sfx) + + @classmethod + def play_level_up_sfx(cls) -> None: + cls._level_up_sfx_ch.play(cls._level_up_sfx) + """ TODO description """