diff --git a/entity/Entity.py b/entity/Entity.py deleted file mode 100644 index b3af22b..0000000 --- a/entity/Entity.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import List, Tuple -import pygame - -from util.ConfigurationManager import ConfigurationManager - -class Entity: - def __init__(self, points: Tuple, color: str, border_color: str = None): - self.points = points - self.color = color - self.border_color = border_color - self.elapsed_time = 0 - - def update(self, elapsed_time) -> None: - self.elapsed_time += elapsed_time - - def draw(self, surface: pygame.Surface) -> None: - tile_size = ConfigurationManager.configuration["engine"]["tile-size"] - - for square in self.points: - pygame.draw.polygon(surface, pygame.Color(self.color), square, 0) - if self.border_color: - pygame.draw.polygon(surface, pygame.Color(self.border_color), square, max(tile_size // 6, 1)) - - def collide(self, entity) -> bool: # TODO figure out how to do type hint for entity param of type Entity - for square_one in self.points: - for square_two in entity.points: - for i in range(4): # 4 vertices - if square_one[i][0] == square_two[i][0] and square_one[i][1] == square_two[i][1]: - return True - return False - - def collide_points(self, points: List) -> bool: # TODO change name later - for square_one in self.points: - for square_two in points: - for i in range(4): # 4 vertices - if square_one[i][0] == square_two[i][0] and square_one[i][1] == square_two[i][1]: - return True - return False diff --git a/entity/Piece.py b/entity/Piece.py deleted file mode 100644 index da3b586..0000000 --- a/entity/Piece.py +++ /dev/null @@ -1,169 +0,0 @@ -from typing import List, Tuple -from pygame import mixer -import pygame -import copy - -from entity.Entity import Entity -from util.ConfigurationManager import ConfigurationManager - -''' - For information on the Tetris piece Tetromino go here: - https://tetris.fandom.com/wiki/Tetromino -''' -class Piece(Entity): - - def __init__(self, shape: Tuple, position: Tuple, color: str, inner_border_color: str, border_color: str): - super().__init__(self.__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 - - # Gravity - self.gravity_time = ConfigurationManager.configuration["engine"]["piece-gravity-time"] - self.current_gravity_time = 0 - self.applying_gravity = True - - # Set - self.set_time = ConfigurationManager.configuration["engine"]["piece-set-time"] - self.current_set_time = 0 - self.applying_set = False - - def update(self, elapsed_time: int, tetris) -> None: - super().update(elapsed_time) - - if self.applying_gravity: - self.applying_gravity = self.__apply_gravity(elapsed_time, tetris.well, tetris.stack) - self.applying_set = not self.applying_gravity - - if self.applying_set: - self.applying_set = self.__apply_set(elapsed_time, tetris) - self.applying_gravity = not self.applying_set - - def draw(self, surface: pygame.Surface) -> None: - super().draw(surface) - - tile_size = ConfigurationManager.configuration["engine"]["tile-size"] - for square in self.points: - if self.inner_border_color: - vertex_one = (square[0][0] + (tile_size // 10), square[0][1] + (tile_size // 10)) - vertex_two = (square[1][0] - (tile_size // 10), square[1][1] + (tile_size // 10)) - vertex_three = (square[2][0] - (tile_size // 10), square[2][1] - (tile_size // 10)) - vertex_four = (square[3][0] + (tile_size // 10), square[3][1] - (tile_size // 10)) - new_square = (vertex_one, vertex_two, vertex_three, vertex_four) - pygame.draw.polygon(surface, pygame.Color(self.inner_border_color), new_square, max(tile_size // 6, 1)) - - def move(self, vector: Tuple) -> None: - self.previous_points = copy.deepcopy(self.points) - self.previous_center = copy.deepcopy(self.center) - - self.center[0] += vector[0] - self.center[1] += vector[1] - - for square in self.points: - for vertex in square: - vertex[0] += vector[0] - vertex[1] += vector[1] - - ''' - For more information on a rotation of a piece go here: - https://gamedev.stackexchange.com/questions/17974/how-to-rotate-blocks-in-tetris - ''' - def rotate(self) -> None: - self.previous_points = copy.deepcopy(self.points) - self.previous_center = copy.deepcopy(self.center) - - new_points = [] - for square in self.points: - for vertex in square: - h = vertex[0] - self.center[0] - k = vertex[1] - self.center[1] - - vertex[0] = (k * -1) + self.center[0] - vertex[1] = h + self.center[1] - new_points.append([square[-1]] + square[0:-1]) - self.points = new_points - - def revert(self) -> None: - if self.previous_points and self.previous_center: - self.points = self.previous_points - self.center = self.previous_center - - def __mimic_move(self, vector: Tuple): # TODO figure out how to annotate return type as Piece - mimic_points = copy.deepcopy(self.points) - - for square in mimic_points: - for vertex in square: - vertex[0] += vector[0] - vertex[1] += vector[1] - - return mimic_points - - def __play_piece_set_sound(self) -> None: - piece_set_sound_file = ConfigurationManager.configuration["sound"]["piece-set"] - self.piece_set_sound.play(mixer.Sound(piece_set_sound_file)) - - def __get_points(self, shape: Tuple, position: Tuple) -> List: - tile_size = ConfigurationManager.configuration["engine"]["tile-size"] - - points = [] - for square in shape[:-1]: - sub_points = [] - for vertex in square: - point = [vertex[0] * tile_size + position[0], vertex[1] * tile_size + position[1]] - sub_points.append(point) - points.append(sub_points) - - return points - - def __get_center(self, shape: Tuple, position: Tuple) -> List: - tile_size = ConfigurationManager.configuration["engine"]["tile-size"] - center = shape[-1] - - # cast to int and avoid exception from pygame (center can be a floating point) - return [int(center[0] * tile_size + position[0]), int(center[1] * tile_size + position[1])] - - def __apply_gravity(self, elapsed_time, well, stack) -> bool: # TODO define well and stack type - tile_size = ConfigurationManager.configuration["engine"]["tile-size"] - self.current_gravity_time += elapsed_time - - if self.current_gravity_time >= self.gravity_time: - self.current_gravity_time = 0 - if not self.__entity_is_below(well) and not self.__entity_is_below(stack): - self.move((0, tile_size)) - else: - return False - - return True - - def __apply_set(self, elapsed_time, tetris) -> bool: # TODO define tetris type - self.current_set_time += elapsed_time - - if self.current_set_time >= self.set_time: - self.current_set_time = 0 - if self.__entity_is_below(tetris.well) or self.__entity_is_below(tetris.stack): - self.__play_piece_set_sound() - tetris.stack.add_piece(self) # TODO do on tetris object level? - tetris.current_piece = None # TODO turn into a method - else: - return False - - return True - - def __entity_is_below(self, entity: Entity) -> bool: # TODO - tile_size = ConfigurationManager.configuration["engine"]["tile-size"] - mimic_points = self.__mimic_move((0, tile_size)) - - return entity and entity.collide_points(mimic_points) - - - # shape attributes - I_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((2, 0), (3, 0), (3, 1), (2, 1)), ((3, 0), (4, 0), (4, 1), (3, 1)), (2, 0)) - J_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((2, 0), (3, 0), (3, 1), (2, 1)), ((2, 1), (3, 1), (3, 2), (2, 2)), (1.5, 0.5)) - L_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((2, 0), (3, 0), (3, 1), (2, 1)), ((0, 1), (1, 1), (1, 2), (0, 2)), (1.5, 0.5)) - O_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((1, 1), (2, 1), (2, 2), (1, 2)), ((0, 1), (1, 1), (1, 2), (0, 2)), (1, 1)) - S_SHAPE = (((0, 1), (1, 1), (1, 2), (0, 2)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((1, 1), (2, 1), (2, 2), (1, 2)), ((2, 0), (3, 0), (3, 1), (2, 1)), (1.5, 0.5)) - T_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((1, 1), (2, 1), (2, 2), (1, 2)), ((2, 0), (3, 0), (3, 1), (2, 1)), (1.5, 0.5)) - Z_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((1, 1), (2, 1), (2, 2), (1, 2)), ((2, 1), (3, 1), (3, 2), (2, 2)), (1.5, 0.5)) \ No newline at end of file diff --git a/entity/Stack.py b/entity/Stack.py deleted file mode 100644 index fca3217..0000000 --- a/entity/Stack.py +++ /dev/null @@ -1,60 +0,0 @@ -from pygame import mixer - -from util.ConfigurationManager import ConfigurationManager -from entity.Piece import Piece -from entity.Well import Well -from entity.Entity import Entity - -class Stack(Entity): - - def __init__(self, color: str, border_color: str): - super().__init__([], color, border_color) - self.rows_completed_count = 0 - self.row_completion_sound = mixer.Channel(1) - - def update(self, elapsed_time) -> None: - super().update(elapsed_time) - self.rows_completed_count += self.__complete_rows() - - def add_piece(self, piece: Piece) -> None: - self.points += piece.points - - def __complete_rows(self) -> int: - squares_by_row = {} - for square in self.points: - top_left_vertex = square[0] - if top_left_vertex[1] not in squares_by_row: - squares_by_row[top_left_vertex[1]] = [] - if square not in squares_by_row[top_left_vertex[1]]: - squares_by_row[top_left_vertex[1]].append(square) - - squares_to_exclude = [] - rows_completed = [] - for key in squares_by_row: - if len(squares_by_row[key]) == Well.WIDTH: - squares_to_exclude += squares_by_row[key] - rows_completed.append(key) - - if len(squares_to_exclude) == 0: - return 0 - - self.__play_row_completion_sound() - - tile_size = ConfigurationManager.configuration["engine"]["tile-size"] - new_points = [] - for square in self.points: - if square not in squares_to_exclude: - for vertex in square: - distance_to_move = 0 - for row_completed in rows_completed: - if vertex[1] <= row_completed: - distance_to_move += 1 - vertex[1] += tile_size * distance_to_move - new_points.append(square) - self.points = new_points - - return len(rows_completed) - - def __play_row_completion_sound(self) -> None: - row_completion_sound_file = ConfigurationManager.configuration["sound"]["row-completion"] - self.row_completion_sound.play(mixer.Sound(row_completion_sound_file)) \ No newline at end of file diff --git a/entity/Well.py b/entity/Well.py deleted file mode 100644 index 570a8b0..0000000 --- a/entity/Well.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import List, Tuple - -from entity.Entity import Entity -from util.ConfigurationManager import ConfigurationManager - -class Well(Entity): - - WIDTH = 10 # standard tetris well width, should not be changed - HEIGHT = 20 # standard tetris well height, should not be changed - - def __init__(self, position: Tuple, color: str, border_color: str): - super().__init__(self.__get_points(position), color, border_color) - - def __get_points(self, position: Tuple) -> List: - tile_size = ConfigurationManager.configuration["engine"]["tile-size"] - - shape = [] - for i in range(self.WIDTH + 2): - for j in range(self.HEIGHT + 2): - if i == 0 or i == self.WIDTH + 1: - shape.append(((i, j), (i + 1, j), (i + 1, j + 1), (i, j + 1))) - elif j == 0 or j == self.HEIGHT + 1: - shape.append(((i, j), (i + 1, j), (i + 1, j + 1), (i, j + 1))) - - points = [] - for square in shape: - sub_points = [] - for vertex in square: - point = [vertex[0] * tile_size + position[0], vertex[1] * tile_size + position[1]] - sub_points.append(point) - points.append(sub_points) - - return points - - - - \ No newline at end of file diff --git a/main.py b/main.py index 421e3e2..62bc0f2 100644 --- a/main.py +++ b/main.py @@ -4,20 +4,18 @@ https://tetris.com/play-tetris ''' -# TODO review imports to make sure it is being done correctly - -from Tetris import Tetris -from util.ConfigurationManager import ConfigurationManager +from tetris.game import Game +from tetris.util import ConfigurationManager def main() -> None: ConfigurationManager.load() - tetris = Tetris() - tetris.initialize() + game = Game() + game.initialize() while True: - tetris.update() - tetris.draw() + game.update() + game.draw() -# start main function -main() \ No newline at end of file +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/resource/font/gravity-font.bmp b/resource/font/gravity-font.bmp new file mode 100644 index 0000000..d68393c Binary files /dev/null and b/resource/font/gravity-font.bmp differ diff --git a/tetris/entity.py b/tetris/entity.py new file mode 100644 index 0000000..f62877a --- /dev/null +++ b/tetris/entity.py @@ -0,0 +1,348 @@ +import copy +import random +import pygame +from typing import List, Tuple +from pygame import mixer +from tetris.util import ConfigurationManager + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from tetris.game import Game + +""" + TODO description +""" +class Entity: + + def __init__(self, points: Tuple, color: str, border_color: str = None): + self.points = points + self.color = color + self.border_color = border_color + self.elapsed_time = 0 + + def update(self, elapsed_time: int) -> None: + self.elapsed_time += elapsed_time + + def draw(self, surface: pygame.Surface) -> None: + tile_size = ConfigurationManager.get("engine", "tile-size") + + for square in self.points: + pygame.draw.polygon(surface, pygame.Color(self.color), square, 0) + if self.border_color: + pygame.draw.polygon(surface, pygame.Color(self.border_color), square, max(tile_size // 6, 1)) + + def collide(self, entity: "Entity") -> bool: # TODO figure out how to do type hint for entity param of type Entity + for square_one in self.points: + for square_two in entity.points: + for i in range(4): # 4 vertices + if square_one[i][0] == square_two[i][0] and square_one[i][1] == square_two[i][1]: + return True + return False + + def _collide(self, points: List) -> bool: + for square_one in self.points: + for square_two in points: + for i in range(4): # 4 vertices + if square_one[i][0] == square_two[i][0] and square_one[i][1] == square_two[i][1]: + return True + return False + +""" + TODO description +""" +class Well(Entity): + + WIDTH = 10 # standard tetris well width, should not be changed + HEIGHT = 20 # standard tetris well height, should not be changed + + def __init__(self, position: Tuple, color: str, border_color: str): + super().__init__(self._get_points(position), color, border_color) + + def _get_points(self, position: Tuple) -> List: + tile_size = ConfigurationManager.get("engine", "tile-size") + + shape = [] + for i in range(self.WIDTH + 2): + for j in range(self.HEIGHT + 2): + if i == 0 or i == self.WIDTH + 1: + shape.append(((i, j), (i + 1, j), (i + 1, j + 1), (i, j + 1))) + elif j == 0 or j == self.HEIGHT + 1: + shape.append(((i, j), (i + 1, j), (i + 1, j + 1), (i, j + 1))) + + points = [] + for square in shape: + sub_points = [] + for vertex in square: + point = [vertex[0] * tile_size + position[0], vertex[1] * tile_size + position[1]] + sub_points.append(point) + points.append(sub_points) + + return points + +''' + For information on the Tetris piece Tetromino go here: + https://tetris.fandom.com/wiki/Tetromino +''' +class Piece(Entity): + + def __init__(self, shape: Tuple, position: Tuple, color: str, inner_border_color: str, border_color: str): + super().__init__(self._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 + + # Gravity + self.gravity_time = ConfigurationManager.get("engine", "piece-gravity-time") + self.current_gravity_time = 0 + self.applying_gravity = True + + # Set + self.set_time = ConfigurationManager.get("engine", "piece-set-time") + self.current_set_time = 0 + self.applying_set = False + + def update(self, elapsed_time: int, game: "Game") -> None: + super().update(elapsed_time) + + if self.applying_gravity: + self.applying_gravity = self._apply_gravity(elapsed_time, game.well, game.stack) + self.applying_set = not self.applying_gravity + + if self.applying_set: + self.applying_set = self._apply_set(elapsed_time, game) + self.applying_gravity = not self.applying_set + + def draw(self, surface: pygame.Surface) -> None: + super().draw(surface) + + tile_size = ConfigurationManager.get("engine", "tile-size") + for square in self.points: + if self.inner_border_color: + vertex_one = (square[0][0] + (tile_size // 10), square[0][1] + (tile_size // 10)) + vertex_two = (square[1][0] - (tile_size // 10), square[1][1] + (tile_size // 10)) + vertex_three = (square[2][0] - (tile_size // 10), square[2][1] - (tile_size // 10)) + vertex_four = (square[3][0] + (tile_size // 10), square[3][1] - (tile_size // 10)) + new_square = (vertex_one, vertex_two, vertex_three, vertex_four) + pygame.draw.polygon(surface, pygame.Color(self.inner_border_color), new_square, max(tile_size // 6, 1)) + + def move(self, vector: Tuple) -> None: + self.previous_points = copy.deepcopy(self.points) + self.previous_center = copy.deepcopy(self.center) + + self.center[0] += vector[0] + self.center[1] += vector[1] + + for square in self.points: + for vertex in square: + vertex[0] += vector[0] + vertex[1] += vector[1] + + ''' + For more information on a rotation of a piece go here: + https://gamedev.stackexchange.com/questions/17974/how-to-rotate-blocks-in-tetris + ''' + def rotate(self) -> None: + self.previous_points = copy.deepcopy(self.points) + self.previous_center = copy.deepcopy(self.center) + + new_points = [] + for square in self.points: + for vertex in square: + h = vertex[0] - self.center[0] + k = vertex[1] - self.center[1] + + vertex[0] = (k * -1) + self.center[0] + vertex[1] = h + self.center[1] + new_points.append([square[-1]] + square[0:-1]) + self.points = new_points + + def revert(self) -> None: + if self.previous_points and self.previous_center: + self.points = self.previous_points + self.center = self.previous_center + + def _mimic_move(self, vector: Tuple) -> List: + mimic_points = copy.deepcopy(self.points) + + for square in mimic_points: + for vertex in square: + vertex[0] += vector[0] + vertex[1] += vector[1] + + 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 _get_points(self, shape: Tuple, position: Tuple) -> List: + tile_size = ConfigurationManager.get("engine", "tile-size") + + points = [] + for square in shape[:-1]: + sub_points = [] + for vertex in square: + point = [vertex[0] * tile_size + position[0], vertex[1] * tile_size + position[1]] + sub_points.append(point) + points.append(sub_points) + + return points + + def _get_center(self, shape: Tuple, position: Tuple) -> List: + tile_size = ConfigurationManager.get("engine", "tile-size") + center = shape[-1] + + # cast to int and avoid exception from pygame (center can be a floating point) + return [int(center[0] * tile_size + position[0]), int(center[1] * tile_size + position[1])] + + def _apply_gravity(self, elapsed_time: int, well: Well, stack: "Stack") -> bool: + tile_size = ConfigurationManager.get("engine", "tile-size") + self.current_gravity_time += elapsed_time + + if self.current_gravity_time >= self.gravity_time: + self.current_gravity_time = 0 + if not self._entity_is_below(well) and not self._entity_is_below(stack): + self.move((0, tile_size)) + else: + return False + + return True + + def _apply_set(self, elapsed_time: int, game: "Game") -> bool: + self.current_set_time += elapsed_time + + if self.current_set_time >= self.set_time: + self.current_set_time = 0 + if self._entity_is_below(game.well) or self._entity_is_below(game.stack): + self._play_piece_set_sound() + game.stack.add_piece(self) # TODO do on tetris object level? + game.current_piece = None # TODO turn into a method + else: + return False + + return True + + def _entity_is_below(self, entity: Entity) -> bool: + tile_size = ConfigurationManager.get("engine", "tile-size") + mimic_points = self._mimic_move((0, tile_size)) + + return entity and entity._collide(mimic_points) + + # shape attributes + I_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((2, 0), (3, 0), (3, 1), (2, 1)), ((3, 0), (4, 0), (4, 1), (3, 1)), (2, 0)) + J_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((2, 0), (3, 0), (3, 1), (2, 1)), ((2, 1), (3, 1), (3, 2), (2, 2)), (1.5, 0.5)) + L_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((2, 0), (3, 0), (3, 1), (2, 1)), ((0, 1), (1, 1), (1, 2), (0, 2)), (1.5, 0.5)) + O_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((1, 1), (2, 1), (2, 2), (1, 2)), ((0, 1), (1, 1), (1, 2), (0, 2)), (1, 1)) + S_SHAPE = (((0, 1), (1, 1), (1, 2), (0, 2)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((1, 1), (2, 1), (2, 2), (1, 2)), ((2, 0), (3, 0), (3, 1), (2, 1)), (1.5, 0.5)) + T_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((1, 1), (2, 1), (2, 2), (1, 2)), ((2, 0), (3, 0), (3, 1), (2, 1)), (1.5, 0.5)) + Z_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((1, 1), (2, 1), (2, 2), (1, 2)), ((2, 1), (3, 1), (3, 2), (2, 2)), (1.5, 0.5)) + +""" + TODO description +""" +class Stack(Entity): + + def __init__(self, color: str, border_color: str): + super().__init__([], color, border_color) + self.rows_completed_count = 0 + self.row_completion_sound = mixer.Channel(1) + + def update(self, elapsed_time: int) -> None: + super().update(elapsed_time) + self.rows_completed_count += self._complete_rows() + + def add_piece(self, piece: Piece) -> None: + self.points += piece.points + + # TODO refactor into multiple functions + def _complete_rows(self) -> int: + squares_by_row = {} + for square in self.points: + top_left_vertex = square[0] + if top_left_vertex[1] not in squares_by_row: + squares_by_row[top_left_vertex[1]] = [] + if square not in squares_by_row[top_left_vertex[1]]: + squares_by_row[top_left_vertex[1]].append(square) + + squares_to_exclude = [] + rows_completed = [] + for key in squares_by_row: + if len(squares_by_row[key]) == Well.WIDTH: + squares_to_exclude += squares_by_row[key] + rows_completed.append(key) + + if len(squares_to_exclude) == 0: + return 0 + + self._play_row_completion_sound() + + tile_size = ConfigurationManager.get("engine", "tile-size") + new_points = [] + for square in self.points: + if square not in squares_to_exclude: + for vertex in square: + distance_to_move = 0 + for row_completed in rows_completed: + if vertex[1] <= row_completed: + distance_to_move += 1 + vertex[1] += tile_size * distance_to_move + new_points.append(square) + self.points = new_points + + return len(rows_completed) + + def _play_row_completion_sound(self) -> None: + row_completion_sound_file = ConfigurationManager.get("sound", "row-completion") + self.row_completion_sound.play(mixer.Sound(row_completion_sound_file)) + +""" + TODO description +""" +class PieceGenerator: + + _bucket = [] + + @classmethod + def get_piece(cls, position: Tuple) -> Piece: + if len(cls._bucket) == 0: + cls._generate_bucket() + + base_color, inner_border_color, border_color = cls._get_piece_color() + return Piece(cls._get_piece_shape(cls._bucket.pop()), position, base_color, inner_border_color, border_color) + + @classmethod + def _generate_bucket(cls) -> None: + piece_types = list(range(7)) + while len(cls._bucket) != 7: + random_number = random.randint(0, 6 - len(cls._bucket)) + cls._bucket.append(piece_types.pop(random_number)) + + def _get_piece_shape(piece_number: int) -> Tuple: + if piece_number == 0: + return Piece.I_SHAPE + if piece_number == 1: + return Piece.J_SHAPE + if piece_number == 2: + return Piece.L_SHAPE + if piece_number == 3: + return Piece.O_SHAPE + if piece_number == 4: + return Piece.S_SHAPE + if piece_number == 5: + return Piece.T_SHAPE + if piece_number == 6: + return Piece.Z_SHAPE + + return None + + def _get_piece_color() -> Tuple: + random_number = random.randint(1, 3) + + base_color = ConfigurationManager.get("color", "piece-" + str(random_number)) + inner_border_color = None if random_number != 3 else ConfigurationManager.get("color", "piece-inner-border-1") + border_color = ConfigurationManager.get("color", "piece-border-1") + + return (base_color, inner_border_color, border_color) \ No newline at end of file diff --git a/Tetris.py b/tetris/game.py similarity index 72% rename from Tetris.py rename to tetris/game.py index e2ad5d5..d58f60d 100644 --- a/Tetris.py +++ b/tetris/game.py @@ -1,14 +1,13 @@ import sys import pygame from pygame import mixer - -from util.ConfigurationManager import ConfigurationManager -from util.PieceGenerator import PieceGenerator -from entity.Well import Well -from entity.Stack import Stack +from tetris.util import ConfigurationManager +from tetris.entity import PieceGenerator +from tetris.entity import Well +from tetris.entity import Stack # TODO should be a singleton and refactor the whole file? -class Tetris: +class Game: def __init__(self): self.fps = -1 @@ -22,20 +21,19 @@ class Tetris: def initialize(self) -> None: pygame.init() - mixer.init() - win_width = ConfigurationManager.configuration["window"]["width"] - win_height = ConfigurationManager.configuration["window"]["height"] - win_icon = ConfigurationManager.configuration["window"]["icon"] - win_title = ConfigurationManager.configuration["window"]["title"] + win_width = ConfigurationManager.get("window", "width") + win_height = ConfigurationManager.get("window", "height") + win_icon = ConfigurationManager.get("window", "icon") + win_title = ConfigurationManager.get("window", "title") - self.fps = ConfigurationManager.configuration["engine"]["fps"] - self.tile_size = ConfigurationManager.configuration["engine"]["tile-size"] + self.fps = ConfigurationManager.get("engine", "fps") + self.tile_size = ConfigurationManager.get("engine", "tile-size") self.screen = pygame.display.set_mode((win_width, win_height)) self.clock = pygame.time.Clock() self.main_music = mixer.Channel(0) - self.well = Well((280, 80), ConfigurationManager.configuration["color"]["well-1"], ConfigurationManager.configuration["color"]["well-border-1"]) # TODO calculate position later and redo color config for well - self.stack = Stack(ConfigurationManager.configuration["color"]["stack-1"], ConfigurationManager.configuration["color"]["stack-border-1"]) + 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")) loaded_icon = pygame.image.load(win_icon) pygame.display.set_caption(win_title) @@ -43,7 +41,7 @@ class Tetris: self.is_pressing_down = False # TODO move into control util later - main_music_file = ConfigurationManager.configuration["sound"]["main-music"] + main_music_file = ConfigurationManager.get("sound", "main-music") self.main_music.set_volume(0.7) # TODO add volume to the config self.main_music.play(mixer.Sound(main_music_file), -1) @@ -56,7 +54,7 @@ class Tetris: self.current_piece.update(elapsed_time, self) else: self.current_piece = PieceGenerator.get_piece((360, 100)) # TODO calculate spawn position - if self.stack and self.current_piece.collide(self.stack): + if self.stack and self.current_piece.collide(self.stack): # game over pygame.quit() sys.exit() @@ -91,26 +89,26 @@ class Tetris: if event.key == pygame.K_DOWN: self.is_pressing_down = True if self.current_piece: - self.current_piece.gravity_time = ConfigurationManager.configuration["engine"]["piece-gravity-time"] / 8 - self.current_piece.set_time = ConfigurationManager.configuration["engine"]["piece-gravity-time"] / 8 + self.current_piece.gravity_time = ConfigurationManager.get("engine", "piece-gravity-time") / 8 + self.current_piece.set_time = ConfigurationManager.get("engine", "piece-gravity-time") / 8 if event.type == pygame.KEYUP: if event.key == pygame.K_DOWN: self.is_pressing_down = False if self.current_piece: - self.current_piece.gravity_time = ConfigurationManager.configuration["engine"]["piece-gravity-time"] - self.current_piece.set_time = ConfigurationManager.configuration["engine"]["piece-set-time"] + self.current_piece.gravity_time = ConfigurationManager.get("engine", "piece-gravity-time") + self.current_piece.set_time = ConfigurationManager.get("engine", "piece-set-time") if self.is_pressing_down: if self.current_piece: - self.current_piece.gravity_time = ConfigurationManager.configuration["engine"]["piece-gravity-time"] / 8 - self.current_piece.set_time = ConfigurationManager.configuration["engine"]["piece-set-time"] / 8 + self.current_piece.gravity_time = ConfigurationManager.get("engine", "piece-gravity-time") / 8 + self.current_piece.set_time = ConfigurationManager.get("engine", "piece-set-time") / 8 def draw(self) -> None: # TODO write not initialized exception # draw window bg - bg_color = pygame.Color(ConfigurationManager.configuration["color"]["window-bg"]) + bg_color = pygame.Color(ConfigurationManager.get("color", "window-bg")) self.screen.fill(bg_color) # draw all game objects @@ -121,6 +119,10 @@ class Tetris: if self.current_piece: self.current_piece.draw(self.screen) + self.sheet = pygame.image.load("resource/font/gravity-font.bmp").convert() + self.screen.blit(self.sheet, (0, 0), pygame.Rect(30, 30, 30, 30)) + # update display pygame.display.update() + \ No newline at end of file diff --git a/tetris/util.py b/tetris/util.py new file mode 100644 index 0000000..52b94d1 --- /dev/null +++ b/tetris/util.py @@ -0,0 +1,23 @@ +import random +import yaml +from typing import Tuple + +""" + TODO description +""" +class ConfigurationManager: + + CONFIG_FILE_LOCATION = "config.yaml" + configuration = [] + + @classmethod + def load(cls) -> None: + with open(cls.CONFIG_FILE_LOCATION, "r") as yaml_file: + cls.configuration = yaml.safe_load(yaml_file) + + @classmethod + def get(cls, key: str, sub_key: str = None): + if sub_key: + return cls.configuration[key][sub_key] + else: + return cls.configuration[key] \ No newline at end of file diff --git a/util/ConfigurationManager.py b/util/ConfigurationManager.py deleted file mode 100644 index 83b143d..0000000 --- a/util/ConfigurationManager.py +++ /dev/null @@ -1,13 +0,0 @@ -import yaml - -CONFIG_FILE_LOCATION = "config.yaml" - -# TODO add getter for configuration? -class ConfigurationManager: - - configuration = [] - - @classmethod - def load(cls) -> None: - with open(CONFIG_FILE_LOCATION, "r") as yaml_file: - cls.configuration = yaml.safe_load(yaml_file) \ No newline at end of file diff --git a/util/ControlsManger.py b/util/ControlsManger.py deleted file mode 100644 index e69de29..0000000 diff --git a/util/PieceGenerator.py b/util/PieceGenerator.py deleted file mode 100644 index 062c10e..0000000 --- a/util/PieceGenerator.py +++ /dev/null @@ -1,54 +0,0 @@ -from typing import Tuple -import random - -from entity.Piece import Piece -from util.ConfigurationManager import ConfigurationManager - -""" - TODO Add link that goes through random piece generation -""" -class PieceGenerator: - - __bucket = [] - - @classmethod - def get_piece(cls, position: Tuple) -> Piece: - if len(cls.__bucket) == 0: - cls.__generate_bucket() - - base_color, inner_border_color, border_color = cls.__get_piece_color() - return Piece(cls.__get_piece_shape(cls.__bucket.pop()), position, base_color, inner_border_color, border_color) - - @classmethod - def __generate_bucket(cls) -> None: - piece_types = list(range(7)) - while len(cls.__bucket) != 7: - random_number = random.randint(0, 6 - len(cls.__bucket)) - cls.__bucket.append(piece_types.pop(random_number)) - - def __get_piece_shape(piece_number: int) -> Tuple: - if piece_number == 0: - return Piece.I_SHAPE - if piece_number == 1: - return Piece.J_SHAPE - if piece_number == 2: - return Piece.L_SHAPE - if piece_number == 3: - return Piece.O_SHAPE - if piece_number == 4: - return Piece.S_SHAPE - if piece_number == 5: - return Piece.T_SHAPE - if piece_number == 6: - return Piece.Z_SHAPE - - return None - - def __get_piece_color() -> Tuple: - random_number = random.randint(1, 3) - - base_color = ConfigurationManager.configuration["color"]["piece-" + str(random_number)] - inner_border_color = None if random_number != 3 else ConfigurationManager.configuration["color"]["piece-inner-border-1"] - border_color = ConfigurationManager.configuration["color"]["piece-border-1"] - - return (base_color, inner_border_color, border_color)