Ejemplo n.º 1
0
class Bricker:
    """Contains main game logic and entry point."""
    def __init__(self) -> None:
        """Class constructor."""

        # load version
        try:
            with open("version", "r") as file:
                version = file.readline().strip()
        except Exception:
            version = "1.0"

        # init pygame framework
        pygame.init()

        # define class vars
        self.__screen_size: Tuple[int, int] = (1000, 700)
        self.__screen: Surface = pygame.display.set_mode(self.__screen_size)
        self.__clock: Clock = Clock()
        self.__renderer: Renderer = Renderer(version, self.__screen_size,
                                             self.__screen, self.__clock)
        self.__matrix: Matrix = Matrix()
        self.__stats: GameStats = GameStats()
        self.__level_drop_intervals: List[float] = []
        interval = 2.0
        for _ in range(0, 10):
            interval *= 0.8
            self.__level_drop_intervals.append(interval)

    def main(self) -> None:
        """Runs main game logic."""

        # vars
        in_game = False

        # program loop
        while True:

            # get menu selection
            menu_selection = self.menu_loop(in_game)

            # resume, run game loop
            if menu_selection == 1:
                in_game = self.game_loop()

            # start new game, run game loop
            elif menu_selection == 2:
                self.new_game()
                in_game = self.game_loop()

            # quit program
            elif menu_selection == 3:
                self.explode_spaces()
                break

    def menu_loop(self, in_game: bool) -> int:
        """The main menu loop."""

        # vars
        menu_selection = 1
        if not in_game:
            menu_selection = 2

        # loop until selection
        while True:

            # limit fps
            self.__clock.tick(60)

            # handle user events
            for event in pygame.event.get():

                # up
                if event.type == pygame.KEYDOWN and (event.key == pygame.K_LEFT
                                                     or event.key
                                                     == pygame.K_UP):
                    menu_selection -= 1
                    if in_game:
                        if menu_selection < 1:
                            menu_selection = 3
                    else:
                        if menu_selection < 2:
                            menu_selection = 3

                # down
                elif event.type == pygame.KEYDOWN and (
                        event.key == pygame.K_RIGHT
                        or event.key == pygame.K_DOWN):
                    menu_selection += 1
                    if in_game:
                        if menu_selection > 3:
                            menu_selection = 1
                    else:
                        if menu_selection > 3:
                            menu_selection = 2

                # enter
                elif event.type == pygame.KEYDOWN and (
                        event.key == pygame.K_SPACE
                        or event.key == pygame.K_RETURN):
                    return menu_selection

            # draw menu
            self.__renderer.draw_menu(self.__matrix, self.__stats,
                                      menu_selection, in_game)

    def high_score_loop(self) -> None:
        """The main menu loop."""

        # vars
        chars = list("   ")
        pos = 0
        numbers = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']
        letters = [
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
        ]
        done = False

        # loop
        while not done:

            # limit fps
            self.__clock.tick(60)

            # handle user events
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    if str(pygame.key.name(event.key)) in letters + numbers:
                        if pos < 3:
                            char = str(pygame.key.name(event.key))
                            chars[pos] = char
                            pos += 1
                    elif event.key == pygame.K_BACKSPACE:
                        pos -= 1
                        if pos < 0:
                            pos = 0
                        if pos <= 2:
                            chars[pos] = " "
                    elif event.key == pygame.K_RETURN:
                        done = True

            # draw frame
            self.__renderer.draw_initials_input(self.__matrix, self.__stats,
                                                chars)

        # add new high score
        initials = "".join(chars).lower()
        self.__stats.add_high_score(initials)

    def game_loop(self) -> bool:
        """The main game loop.  Returns true if still in game (menu opened)."""

        # vars
        game_over = False

        # event loop
        while not game_over:

            # reset hit flag
            hit = False

            # limit fps
            self.__clock.tick(60)

            # handle user events
            for event in pygame.event.get():

                # left
                if event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT:
                    self.move_brick_left()

                # right
                elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
                    self.move_brick_right()

                # down
                elif event.type == pygame.KEYDOWN and event.key == pygame.K_DOWN:
                    self.move_brick_down()

                # rotate
                elif event.type == pygame.KEYDOWN and event.key == pygame.K_UP:
                    self.rotate_brick()

                # drop
                elif event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                    self.drop_brick_to_bottom()
                    hit = True

                # menu
                elif event.type == pygame.KEYDOWN and (
                        event.key == pygame.K_ESCAPE
                        or event.key == pygame.K_q):
                    return True

                # level up
                elif event.type == pygame.KEYDOWN and event.key == pygame.K_PAGEUP:
                    if self.__renderer.debug:
                        self.__stats.level += 1
                        if self.__stats.level > 10:
                            self.__stats.level = 10

                # level down
                elif event.type == pygame.KEYDOWN and event.key == pygame.K_PAGEDOWN:
                    if self.__renderer.debug:
                        self.__stats.level -= 1
                        if self.__stats.level < 1:
                            self.__stats.level = 1

                # debug toggle
                elif event.type == pygame.KEYDOWN and event.key == pygame.K_d:
                    self.__renderer.debug = not self.__renderer.debug

            # drop brick timer?
            if self.is_drop_time():  # add drop interval
                hit = self.move_brick_down()

            # brick hit bottom?
            if hit:
                game_over = self.brick_hit()

            # draw frame
            self.__renderer.update_frame(self.__matrix, self.__stats, None)

        # game over
        self.explode_spaces()
        if self.__stats.is_high_score():
            self.high_score_loop()
        return False

    def new_game(self) -> None:
        """Resets state and starts a new game."""
        self.__stats = GameStats()
        self.__matrix.new_game()

    def move_brick_left(self) -> None:
        """Moves brick left."""
        self.__matrix.move_brick_left()

    def move_brick_right(self) -> None:
        """Moves brick right."""
        self.__matrix.move_brick_right()

    def move_brick_down(self) -> bool:
        """Moves brick down.  Returns true if brick hits bottom."""
        hit = self.__matrix.move_brick_down()
        if hit:
            self.__stats.increment_score(1)
        return hit

    def rotate_brick(self) -> None:
        """Rotates brick."""
        self.__matrix.rotate_brick()

    def drop_brick_to_bottom(self) -> None:
        """Animates a brick dropping to bottom of screen."""
        hit = False
        while not hit:
            self.__renderer.clock.tick(30)
            for _ in range(0, 3):
                hit = self.move_brick_down()
                if hit:
                    break
            self.__renderer.event_pump()
            self.__renderer.update_frame(self.__matrix, self.__stats, None)
        self.__stats.increment_score(2)

    def is_drop_time(self) -> bool:
        """Returns true if it's time for brick to drop."""
        if self.__matrix.brick is not None:
            drop_interval = self.__level_drop_intervals[self.__stats.level - 1]
            return self.__matrix.brick.is_drop_time(drop_interval)
        return False

    def brick_hit(self) -> bool:
        """Executed when brick hits bottom and comes to rest.  Spawns new brick.  Returns true on new brick collision (game over)."""
        self.__matrix.add_brick_to_matrix()
        rows_to_erase = self.__matrix.identify_solid_rows()
        if len(rows_to_erase) > 0:
            rows = len(rows_to_erase)
            self.__stats.add_lines(rows)
            points = 40
            if rows == 2:
                points = 100
            elif rows == 3:
                points = 300
            elif rows == 4:
                points = 1200
            self.__stats.increment_score(points)
            self.erase_filled_rows(rows_to_erase)
            self.drop_grid()
            self.__renderer.event_pump()
            self.__renderer.update_frame(self.__matrix, self.__stats, None)
        collision = self.__matrix.spawn_brick()
        return collision

    def erase_filled_rows(self, rows_to_erase: List[int]) -> None:
        """Animates erasure of filled rows."""
        for x in range(1, 11):
            for y in rows_to_erase:
                self.__matrix.matrix[x][y] = 0
                self.__matrix.color[x][y] = Colors.Black
            if (x % 2) == 0:
                self.__renderer.event_pump()
                self.__renderer.update_frame(self.__matrix, self.__stats, None)

    def drop_grid(self) -> None:
        """Drops hanging pieces to resting place."""
        while self.drop_grid_once():
            pass

    def drop_grid_once(self) -> bool:
        """Drops hanging pieces, bottom-most row."""
        top_filled_row = 0
        for row in range(1, 21):
            empty = True
            for x in range(1, 11):
                if self.__matrix.matrix[x][row] == 1:
                    empty = False
                    break
            if not empty:
                top_filled_row = row
                break
        if top_filled_row == 0:
            return False
        bottom_empty_row = 0
        for row in range(20, (top_filled_row - 1), -1):
            empty = True
            for x in range(1, 11):
                if self.__matrix.matrix[x][row] == 1:
                    empty = False
                    break
            if empty:
                bottom_empty_row = row
                break
        if bottom_empty_row == 0:
            return False
        for y in range(bottom_empty_row, 1, -1):
            for x in range(1, 11):
                self.__matrix.matrix[x][y] = self.__matrix.matrix[x][y - 1]
                self.__matrix.color[x][y] = self.__matrix.color[x][y - 1]
        for x in range(1, 11):
            self.__matrix.matrix[x][1] = 0
            self.__matrix.color[x][1] = Colors.Black
        return True

    def explode_spaces(self) -> None:
        """Explodes matrix spaces outwards on game over."""
        self.__matrix.add_brick_to_matrix()
        spaces: List[ExplodingSpace] = []
        for x in range(1, 11):
            for y in range(1, 21):
                if self.__matrix.matrix[x][y] == 1:
                    space_x = (((x - 1) * 33) + 2) + (
                        (self.__renderer.screen_size[0] - 333) // 2) - 1
                    space_y = (((y - 1) * 33) + 2) + (
                        (self.__renderer.screen_size[1] - 663) // 2) - 1
                    spaces.append(
                        ExplodingSpace(space_x, space_y,
                                       self.__matrix.color[x][y]))
                    self.__matrix.matrix[x][y] = 0
                    self.__matrix.color[x][y] = Colors.Black
        start_time = perf_counter()
        have_spaces = True
        while have_spaces:
            seconds = perf_counter() - start_time
            have_spaces = False
            for space in spaces:
                space.x += space.x_motion * seconds
                space.y += space.y_motion * seconds
                if (space.x > 0) and (space.x < 1000) and (space.y > 0) and (
                        space.y < 700):
                    have_spaces = True
            self.__clock.tick(30)
            self.__renderer.update_frame(self.__matrix, self.__stats, spaces)
Ejemplo n.º 2
0
class Matrix:
    """ Stores the 10x20 game matrix.  Contains game logic. """
    def __init__(self, draw):
        """ Class constructor. """
        self.draw = draw
        self.width = 12  # 10 visible slots, plus border for collision detection
        self.height = 22  # 20 visible slots, plus border for collision detection
        self.matrix = [[0 for x in range(self.height)]
                       for y in range(self.width)]
        self.color = [[(0, 0, 0) for x in range(self.height)]
                      for y in range(self.width)]
        for x in range(0, 12):
            self.matrix[x][0] = 1
            self.matrix[x][21] = 1
        for y in range(0, 22):
            self.matrix[0][y] = 1
            self.matrix[11][y] = 1
        self.stats = GameStats()
        self.brick = None
        self.next_brick = None
        self.level_drop_intervals = []
        interval = 2.0
        for i in range(0, 10):
            interval *= 0.8
            self.level_drop_intervals.append(interval)

    def new_game(self):
        """ Reset entire game over. """
        self.brick = None
        self.next_brick = None
        self.matrix = [[0 for x in range(self.height)]
                       for y in range(self.width)]
        self.color = [[(0, 0, 0) for x in range(self.height)]
                      for y in range(self.width)]
        for x in range(0, 12):
            self.matrix[x][0] = 1
            self.matrix[x][21] = 1
        for y in range(0, 22):
            self.matrix[0][y] = 1
            self.matrix[11][y] = 1
        self.stats = GameStats()
        self.spawn_brick()

    def spawn_brick(self):
        """ Spawns a new (random) brick. """
        if self.next_brick is None:
            shape_num = randint(1, 7)
            self.next_brick = Brick(shape_num)
        self.brick = self.next_brick
        shape_num = randint(1, 7)
        self.next_brick = Brick(shape_num)
        collision = self.brick.collision(self.matrix)
        return collision

    def add_brick_to_matrix(self):
        """ Moves resting brick to matrix. """
        if self.brick is not None:
            for x in range(0, self.brick.width):
                for y in range(0, self.brick.height):
                    if self.brick.grid[x][y] == 1:
                        self.matrix[x + self.brick.x][y + self.brick.y] = 1
                        self.color[x + self.brick.x][
                            y + self.brick.y] = self.brick.color
        self.brick = None

    def move_brick_left(self):
        """ Moves brick to the left. """
        if self.brick is not None:
            self.brick.move_left(self.matrix)

    def move_brick_right(self):
        """ Moves brick to the right. """
        if self.brick is not None:
            self.brick.move_right(self.matrix)

    def move_brick_down(self):
        """ Moves brick to down. """
        hit = False
        if self.brick is not None:
            hit = self.brick.move_down(self.matrix)
        if hit:
            self.stats.current_score += 1
        return hit

    def rotate_brick(self):
        """ Rotates brick. """
        if self.brick is not None:
            self.brick.rotate(self.matrix)

    def brick_hit(self):
        """ Executed when brick hits bottom and comes to rest. """
        self.add_brick_to_matrix()
        rows_to_erase = self.identify_solid_rows()
        if len(rows_to_erase) > 0:
            rows = len(rows_to_erase)
            self.stats.add_lines(rows)
            points = 40
            if rows == 2:
                points = 100
            elif rows == 3:
                points = 300
            elif rows == 4:
                points = 1200
            self.stats.current_score += points
            self.erase_filled_rows(rows_to_erase)
            self.drop_grid()
            self.draw.event_pump()
            self.draw.update_frame(self, None)
        collision = self.spawn_brick()
        return collision

    def identify_solid_rows(self):
        """ Checks matrix for solid rows, returns list of rows (to erase). """
        rows_to_erase = []
        for y in range(1, 21):
            solid = True
            for x in range(1, 11):
                if self.matrix[x][y] != 1:
                    solid = False
            if solid:
                rows_to_erase.append(y)
        return rows_to_erase

    def is_drop_time(self):
        """ Returns true if it's time for brick to drop (by timer). """
        if self.brick is not None:
            drop_interval = self.level_drop_intervals[self.stats.level - 1]
            return self.brick.is_drop_time(drop_interval)
        return False

    def drop_brick_to_bottom(self):
        """ Animates a brick dropping to bottom of screen, one motion """
        hit = False
        while not hit:
            self.draw.clock.tick(30)
            for i in range(0, 3):
                hit = self.move_brick_down()
                if hit:
                    break
            self.draw.event_pump()
            self.draw.update_frame(self, None)
        self.stats.current_score += 2
        return True

    def erase_filled_rows(self, rows_to_erase):
        """ Animates erasure of filled rows. """
        for x in range(1, 11):
            for y in rows_to_erase:
                self.matrix[x][y] = 0
                self.color[x][y] = 0, 0, 0
            if (x % 2) == 0:
                self.draw.event_pump()
                self.draw.update_frame(self, None)

    def drop_grid(self):
        """ Drops hanging pieces to resting place. """
        while self.drop_grid_once():
            pass

    def drop_grid_once(self):
        """ Drops hanging pieces, bottom-most row. """
        top_filled_row = 0
        for row in range(1, 21):
            empty = True
            for x in range(1, 11):
                if self.matrix[x][row] == 1:
                    empty = False
                    break
            if not empty:
                top_filled_row = row
                break
        if top_filled_row == 0:
            return False
        bottom_empty_row = 0
        for row in range(20, (top_filled_row - 1), -1):
            empty = True
            for x in range(1, 11):
                if self.matrix[x][row] == 1:
                    empty = False
                    break
            if empty:
                bottom_empty_row = row
                break
        if bottom_empty_row == 0:
            return False
        for y in range(bottom_empty_row, 1, -1):
            for x in range(1, 11):
                self.matrix[x][y] = self.matrix[x][y - 1]
                self.color[x][y] = self.color[x][y - 1]
        for x in range(1, 11):
            self.matrix[x][1] = 0
            self.color[x][1] = 0, 0, 0
        return True