コード例 #1
0
class Game:
    """ Game class controls the flow of the game.
    This class receives inputs from users and pass the inputs to other classes
    to update the state of a field.
    === Private Attributes ===
    _dim: the dimensions of a game.
    _game_over: declares if the game ended to display score view
    _field: an instance of a Field class representing a game field.
    _in_game: declares if the window displays the game view
    _view_leaderboard: declares if the window displays the leaderboard view.
    _menu_index: keeps the record of selected_menu.
    _screen: the pygame screen onto which the different scenes will display.
    _view_title: declares if the window displays the main menu view.
    === Public Attributes ===
    player: the player's alias.
    leaderboard: a LeaderBoard class instance to store the player's score.
    hand: declares handedness of player. 0 for left-handed; 1 for right-handed
    """
    # Attributes
    _dim: Tuple[int, int]
    _in_game: bool
    _game_over: bool
    _view_leaderboard: bool
    _view_title_screen: bool
    _menu_index: int
    _field: Optional[Field]
    _screen: pygame.Surface
    player: str
    _leaderboard: Optional[LeaderBoard]
    hand: int

    # Constants
    TEXT_COLOR = (50, 50, 50)
    TITLE_SCREEN_COLOR = (255, 255, 255)

    def __init__(self) -> None:
        """Initializer of game class with player <player_name>.
        """
        self._dim = (800, 800)
        self._in_game = False
        self._game_over = False
        self._menu_index = 0
        self._field = None
        self.player = None
        self._view_leaderboard = False
        self._view_title = True
        self.hand = None

    def main(self) -> None:
        """Run the game. The loop continues until
        either the snake hits the wall or eats his tail.
        """
        # Initialize pygame, generate screen and field
        self._set_player_name()
        self._leaderboard = LeaderBoard(self.player)
        self._screen = self._init_screen()
        pygame.init()
        clock = pygame.time.Clock()
        fps = 5
        while True:
            self._handle_screen(self._screen)
            pygame.display.update()
            clock.tick(fps)

    # --------------------------------------------------------
    # Helper function of main
    # --------------------------------------------------------
    def _set_player_name(self) -> None:
        """Ask a user to choose their name and controls
        they wish to use.
        """
        self.player = input("Enter your name \n")
        invalid_input = True
        while invalid_input:
            hand_input = input("If you want to play a game with WASD, press 0, or press 1 to use arrow keys \n")
            try:
                self.hand = int(hand_input)
                if self.hand not in [0, 1]:
                    print("Invalid input, Please enter 0 or 1.")
                else:
                    invalid_input = False
            except ValueError:
                print("Invalid input, Please enter 0 or 1.")

    def _init_screen(self) -> pygame.Surface:
        """Initialize title screen"""
        screen = pygame.display.set_mode(self._dim, pygame.RESIZABLE)
        screen.fill(self.TITLE_SCREEN_COLOR)
        pygame.display.set_caption("Snake game")
        self._menu_index = 0
        return screen

    def _handle_screen(self, screen: pygame.Surface):
        """Choose which screen to show"""
        # if in_leader_board and not in_title and not in_game:
        if self._view_title:
            self._display_title_screen()
        elif self._in_game:
            self._run_game(screen)
        elif self._game_over:
            self._display_score()
        else:
            self._display_scoreboard()

    # --------------------------------------------------------
    # Main function to create view
    # --------------------------------------------------------
    def _display_minimenu(self, title: str, size: int, where: int = None,
                          show_score: bool = False,
                          show_leader_board: bool = False) -> None:
        """Displays a screen with <title> and font <size>.
        Method also adds main menu options relative to <where>
        """
        # Enable exiting game and process user inputs
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                self._handle_key_input(event.key)
            elif event.type == pygame.VIDEORESIZE:
                self._dim = event.dict['size']
                self._screen = pygame.display.set_mode(self._dim, pygame.RESIZABLE)
                pygame.display.flip()
            else:
                pass
        # self._screen = pygame.display.set_mode(self._dim, RESIZABLE)
        self._screen.fill(self.TITLE_SCREEN_COLOR)
        self._create_title(title, size)
        self._create_menu_options(where)
        if show_score:
            margin_x = self._calc_margin_x(150)
            self._create_menu_label("Game Over!", (margin_x, 200))
            if self._field is None:
                results = ""
            else:
                results = self.player + ": " + str(self._field.get_score())
            result_margin = self._calc_margin_x(len(results) * 13)
            self._create_menu_label(results, (result_margin, 250))
        elif show_leader_board:
            self._format_scoreboard()


    # --------------------------------------------------------
    # Helper function of _display_mini_menu
    # --------------------------------------------------------
    def _create_menu_label(self, label_text: str,
                           location: Tuple[int, int]) -> None:
        """Draw menu label from given label text at given location"""
        menu_font = pygame.font.SysFont("Arial", 36)
        menu_label = menu_font.render(label_text, 1, self.TEXT_COLOR)
        self._screen.blit(menu_label, location)

    def _draw_menu_selection_arrow(self,
                                   x_location: int,
                                   y_location: int) -> None:
        """Draw menu selection arrow into screen"""
        menu_arrow_location = [[x_location, y_location + 2],
                               [x_location, y_location + 22],
                               [x_location + 20, y_location + 12]]
        pygame.draw.polygon(self._screen, self.TEXT_COLOR, menu_arrow_location)

    def _create_title(self, title: str, size: int,):
        """Create title label on the screen"""
        title_font = pygame.font.SysFont("monospace", size)
        title_label = title_font.render(title, 1, self.TEXT_COLOR)
        title_width, title_height = title_label.get_size()
        new_location = ((self._dim[0] - title_width) // 2, 50)
        self._screen.blit(title_label, new_location)

    def _create_menu_options(self, where: Optional[int]) -> None:
        """Create menu options with an arrow showing selected option"""
        margin_x = self._calc_margin_x(190) + 35
        if where is not None:
            self._create_menu_label("Play", (margin_x, where))
            self._create_menu_label("Leaderboard", (margin_x, where + 40) )
            self._create_menu_label("Quit", (margin_x, where + 80))
            self._draw_menu_selection_arrow(margin_x - 35,
                                            [where, where + 40, where + 80][self._menu_index])

        else:
            self._create_menu_label("Play", (margin_x, 300))
            self._create_menu_label("Leaderboard", (margin_x, 340), )
            self._create_menu_label("Quit", (margin_x, 380))
            self._draw_menu_selection_arrow(margin_x - 35,
                                        [300, 340, 380][self._menu_index])

    def _calc_margin_x(self, width: int) -> int:
        """Calculate horizontal margins based on given width"""
        margin = (self._screen.get_width() - width) // 2
        if margin < 0:
            margin *= -1
        return margin

    def _format_scoreboard(self) -> None:
        """Formats game history to display top 10 scores and players"""
        winners = self._leaderboard.get_top_ten()
        i, j = 1, 1
        for entry in winners:
            player, score = entry
            margin_x = self._calc_margin_x(700)
            self._create_menu_label(player, (margin_x, 100 + j * 40))
            self._create_menu_label(score,
                                    (self._screen.get_width() - margin_x - 50,
                                            100 + j * 40))
            i += 1
            j += 1

    # --------------------------------------------------------
    # Title Screen
    # --------------------------------------------------------
    def _display_title_screen(self) -> None:
        """Draw menu option arrow and update view"""
        # Clear current view contents
        self._display_minimenu("Snake game", 80)

    # --------------------------------------------------------
    # key_input_handler for title/score
    # --------------------------------------------------------
    def _handle_key_input(self, key_value: int) -> None:
        """Handle key inputs in title screen"""
        if key_value == pygame.K_UP:
            self._menu_index -= 1
            if self._menu_index < 0:
                self._menu_index = 2
        elif key_value == pygame.K_DOWN:
            self._menu_index += 1
            if self._menu_index > 2:
                self._menu_index = 0
        elif key_value == pygame.K_RETURN:
            self._change_view_flags()

    def _change_view_flags(self) -> None:
        """Helper function of handle_key_input"""
        if self._menu_index == 0:  # user chooses to play
            self._in_game = True
            self._view_leaderboard = False
            self._view_title = False
            self._game_over = False
            self._field = None
        elif self._menu_index == 1:  # user chooses to view leaderboard
            self._in_game = False
            self._view_leaderboard = True
            self._view_title = False
            self._game_over = False
        else:  # user chooses to exit GUI
            pygame.quit()
            sys.exit()

    # --------------------------------------------------------
    # run game
    # --------------------------------------------------------
    def _run_game(self, screen: pygame.Surface) -> None:
        """Recursively call this method until the game is over.
        This method should receive user inputs and update state of a field
        """
        key = None
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                key = event.key
        if self._field is None:
            self._field = Field(screen, self.hand)
        is_game_over = self._field.display_field(key)

        if is_game_over:
            self._game_over = True
            self._in_game = False
            score = self._field.get_score()
            self._leaderboard.set_player_score(score)
            self._display_score()

    def _display_score(self) -> None:
        """Display game instance results"""
        self._display_minimenu("Snake game", 80, None, True)

    # --------------------------------------------------------
    # Leader Board
    # --------------------------------------------------------
    def _display_scoreboard(self) -> None:
        """Display leaderboard screen with top 10 scores"""
        history_size = self._leaderboard.get_history_size()
        self._display_minimenu("LEADERBOARD", 54,
                               history_size * 55, False, True)
コード例 #2
0
ファイル: game.py プロジェクト: NElnour/snake_game
class Game:
    """ Game class controls the flow of the game.
    This class receives inputs from users and pass the inputs to other classes
    to update the state of a field.
    === Attributes ===
    _field: an instance of a field class representing a game field.
    _in_game: declares if the window displays the game view
    _menu_index: keeps the record of selected_menu
    """
    # Attributes
    _dim: Tuple[int, int]
    _in_game: bool
    _menu_index: int
    _field: Optional[Field]

    # Constants
    COLOR_WHITE = (255, 255, 255)
    TITLE_SCREEN_COLOR = (0, 0, 80)

    def __init__(self) -> None:
        """Initializes Game class
        TODO: write a doctest
        """
        self._dim = (300, 300)
        self._in_game = False
        self._menu_index = 0
        self._field = None

    def main(self) -> None:
        """Run the game. The loop continues until
        either the snake hits the wall or eats his tail
        TODO: write a doctest
        """
        # Initialize pygame, generate screen and field
        pygame.init()
        screen = self._init_screen()
        clock = pygame.time.Clock()
        fps = 30
        while True:
            if not self._in_game:
                self._display_title_screen(screen)
            else:
                self._run_game(screen)
            clock.tick(fps)

    def _init_screen(self) -> pygame.Surface:
        """Initialize title screen."""
        screen = pygame.display.set_mode((600, 600))
        screen.fill(self.TITLE_SCREEN_COLOR)
        pygame.display.set_caption("Snake game")
        self._menu_index = 0
        self._field = Field(screen)
        return screen

    def _clear_title_screen(self, screen: pygame.Surface) -> None:
        """Clear title screen and draw title and menu options."""
        screen.fill(self.TITLE_SCREEN_COLOR)
        title_font = pygame.font.SysFont("monospace", 80)
        title_label = title_font.render("Snake game", 1, (255, 255, 255))
        screen.blit(title_label, (130, 100))
        # create title select
        self._create_menu_label(screen, "Play", (270, 300))
        self._create_menu_label(screen, "Score", (270, 360))
        self._create_menu_label(screen, "Quit", (270, 420))

    def _display_title_screen(self, screen: pygame.Surface) -> None:
        """Draw menu option arrow and update view."""
        # Clear current view contents
        self._clear_title_screen(screen)
        # Receive user inputs
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == KEYDOWN:
                self._handle_key_input(event.key)
        # Draw menu selection arrow
        self._draw_menu_selection_arrow(screen, [300, 360,
                                                 420][self._menu_index])
        pygame.display.update()

    def _handle_key_input(self, key_value: int) -> None:
        """Handle key inputs in title screen"""
        if key_value == pygame.K_UP:
            self._menu_index -= 1
            if self._menu_index < 0:
                self._menu_index = 2
        elif key_value == pygame.K_DOWN:
            self._menu_index += 1
            if self._menu_index > 2:
                self._menu_index = 0
        elif key_value == pygame.K_RETURN:
            if self._menu_index == 0:
                self._in_game = True
            elif self._menu_index == 0:
                # TODO add score view
                pass
            else:
                pygame.quit()
                sys.exit()

    def _create_menu_label(self, screen: pygame.Surface, label_text: str,
                           location: Tuple[int, int]) -> None:
        """Draw menu label from given label text at given location."""
        menu_font = pygame.font.SysFont("monospace", 40)
        menu_label = menu_font.render(label_text, 1, self.COLOR_WHITE)
        screen.blit(menu_label, location)

    def _draw_menu_selection_arrow(self, screen: pygame.Surface,
                                   y_location: int) -> None:
        """Draw menu selection arrow into screen"""
        menu_arrow_location = [[200, y_location + 2], [200, y_location + 22],
                               [220, y_location + 12]]
        pygame.draw.polygon(screen, self.COLOR_WHITE, menu_arrow_location)

    def _is_game_over(self) -> bool:
        """Check the state of a field and return true
        if the game over condition is met.
        TODO: and write a doctest
        """
        boundaries = [(0, 0), (0, self._dim[0]), (0, self._dim[1]), self._dim]

        if self._field.get_snake_head() in boundaries or \
                self._field.get_snake_head() == self._field.get_snake_tail():
            return True

        return False

    def _run_game(self, screen: pygame.Surface) -> None:
        """ recursively call this method until the game is over.
        This method should receive user inputs and update state of a field
        TODO: add a method body and write a doctest
        """
        self._field.display_field()
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == KEYDOWN:
                # TODO pass key event to snake or field to draw
                self._field.display_field()

        pygame.display.update()