Exemple #1
0
    def __init__(self, manager):

        super().__init__(manager)

        # PyDis logo
        self.image = ImageObject(
            self,
            (0, 0),
            Paths.splash / "pydis_logo.png",
        )

        # Center the image
        image_width = self.image.surface.get_rect().width
        image_height = self.image.surface.get_rect().height

        center = (
            (Window.width / 2) - (image_width / 2),
            (Window.height / 2) - (image_height / 2),
        )

        # Move the logo to the right position, based on screen size.
        self.image.move_absolute(center)

        # What scene should be loaded after this finishes?
        self.next_scene = "jetbrains"

        # PyDis SFX
        self.sound = pygame.mixer.Sound(str(Paths.sfx / "pydis_desu.ogg"))
        self.sound.play()
Exemple #2
0
class JetBrainsSplash(Splash):
    """
    This shows "sponsored by Jetbrains",
    fading in an out splash screen style.
    """

    name = "jetbrains"

    def __init__(self, manager):

        super().__init__(manager)

        # JetBrains logo
        self.image = ImageObject(
            self,
            (0, 0),
            Paths.splash / "jetbrains_logo.png",
        )

        # Center the image
        image_width = self.image.surface.get_rect().width
        image_height = self.image.surface.get_rect().height

        center = (
            (Window.width / 2) - (image_width / 2),
            (Window.height / 2) - (image_height / 2),
        )

        # Move the logo to the right position, based on screen size.
        self.image.move_absolute(center)

        # What scene should be loaded after this finishes?
        self.next_scene = "enter_name"

        # JetBrains SFX
        self.sound = pygame.mixer.Sound(str(Paths.sfx / "jetbrains.ogg"))
        self.sound.play()
    def __init__(self, manager):
        super().__init__(manager)

        self.max_bombs = 8
        self.game_over_screen = None
        self.accuracy = None
        self.timer = None
        self.speed_multiplier = 3
        self.explosions: List[Explosion] = []
        self.texts: List[TextShootObject] = []
        self.new_missile_timer = 200
        self.new_jet_timer = random.randint(450, 1200)
        self.start_ticks = pygame.time.get_ticks()
        self.game_running = True
        self.sent_scores = False

        self.lock = None
        self.wpm = None
        self.letters_typed = 0
        self.letters_missed = 0
        self.pyjet = None

        self.restart_game_text = TextObject(
            self,
            (0, 0),
            "Play again",
            font_path=Paths.fonts / "ObelixPro-cyr.ttf",
            font_size=60
        )
        self.high_scores_text = TextObject(
            self,
            (0, 0),
            "High scores",
            font_path=Paths.fonts / "ObelixPro-cyr.ttf",
            font_size=60
        )
        self.restart_game_text.move_absolute((
            (Window.width / 2) - (self.restart_game_text.size[0] / 2),
            450
        ))
        self.high_scores_text.move_absolute((
            (Window.width / 2) - (self.high_scores_text.size[0] / 2),
            550
        ))
        self.wpm_text = None
        self.accuracy_text = None

        # Background image
        background_path = Paths.levels / random.choice(["level_bg.png", "level_bg_2.png"])

        self.background = ImageObject(
            self,
            (0, 0), background_path,
        )

        # SFX
        self.gunshot = pygame.mixer.Sound(str(Paths.sfx / "gunshot.ogg"))
        self.gunshot.set_volume(0.4)
        self.wrong = pygame.mixer.Sound(str(Paths.sfx / "wrong_letter.ogg"))
        self.you_lose_sfx = pygame.mixer.Sound(str(Paths.sfx / "you_lose.ogg"))
        self.you_win_sfx = pygame.mixer.Sound(str(Paths.sfx / "you_win.ogg"))

        # Some random NPCs
        number_of_npcs = 7

        npc_slots = [
            (123, 550),
            (211, 550),
            (284, 550),
            (450, 550),
            (570, 550),
            (800, 550),
            (990, 550),
        ]
        random.shuffle(npc_slots)
        self.npcs = []
        for _ in range(number_of_npcs):
            self.npcs.append(
                NPC(self, npc_slots.pop(-1))
            )

        # Enemies
        self.flutterdude = Flutterdude(
            self,
            (0, 75),
            1.5
        )

        # Play some music
        self.manager.play_music("pskov_loop.ogg", loop=True)
class Game(Scene):

    name = "game"

    def __init__(self, manager):
        super().__init__(manager)

        self.max_bombs = 8
        self.game_over_screen = None
        self.accuracy = None
        self.timer = None
        self.speed_multiplier = 3
        self.explosions: List[Explosion] = []
        self.texts: List[TextShootObject] = []
        self.new_missile_timer = 200
        self.new_jet_timer = random.randint(450, 1200)
        self.start_ticks = pygame.time.get_ticks()
        self.game_running = True
        self.sent_scores = False

        self.lock = None
        self.wpm = None
        self.letters_typed = 0
        self.letters_missed = 0
        self.pyjet = None

        self.restart_game_text = TextObject(
            self,
            (0, 0),
            "Play again",
            font_path=Paths.fonts / "ObelixPro-cyr.ttf",
            font_size=60
        )
        self.high_scores_text = TextObject(
            self,
            (0, 0),
            "High scores",
            font_path=Paths.fonts / "ObelixPro-cyr.ttf",
            font_size=60
        )
        self.restart_game_text.move_absolute((
            (Window.width / 2) - (self.restart_game_text.size[0] / 2),
            450
        ))
        self.high_scores_text.move_absolute((
            (Window.width / 2) - (self.high_scores_text.size[0] / 2),
            550
        ))
        self.wpm_text = None
        self.accuracy_text = None

        # Background image
        background_path = Paths.levels / random.choice(["level_bg.png", "level_bg_2.png"])

        self.background = ImageObject(
            self,
            (0, 0), background_path,
        )

        # SFX
        self.gunshot = pygame.mixer.Sound(str(Paths.sfx / "gunshot.ogg"))
        self.gunshot.set_volume(0.4)
        self.wrong = pygame.mixer.Sound(str(Paths.sfx / "wrong_letter.ogg"))
        self.you_lose_sfx = pygame.mixer.Sound(str(Paths.sfx / "you_lose.ogg"))
        self.you_win_sfx = pygame.mixer.Sound(str(Paths.sfx / "you_win.ogg"))

        # Some random NPCs
        number_of_npcs = 7

        npc_slots = [
            (123, 550),
            (211, 550),
            (284, 550),
            (450, 550),
            (570, 550),
            (800, 550),
            (990, 550),
        ]
        random.shuffle(npc_slots)
        self.npcs = []
        for _ in range(number_of_npcs):
            self.npcs.append(
                NPC(self, npc_slots.pop(-1))
            )

        # Enemies
        self.flutterdude = Flutterdude(
            self,
            (0, 75),
            1.5
        )

        # Play some music
        self.manager.play_music("pskov_loop.ogg", loop=True)

    def _build_game_over_screen(self):
        """
        Builds a screen that can be displayed
        when the game is over, showing WINNER
        or YOU LOSE graphics, score, a retry
        button and playing sfx.
        """

        # Calculate WPM
        total_letters = self.letters_typed + self.letters_missed
        self.wpm = int((total_letters / 5) / self.timer.minutes_passed)
        self.wpm_text = TextObject(
            self,
            (1045, 15),
            f"WPM: {self.wpm}",
            font_path=Paths.fonts / "ObelixPro-cyr.ttf",
            font_size=35
        )

        # Calculate accuracy
        total_letters = self.letters_typed + self.letters_missed

        if total_letters:
            self.accuracy = int(
                100 - ((self.letters_missed / total_letters) * 100)
            )
        else:
            self.accuracy = 0

        self.accuracy_text = TextObject(
            self,
            (938, 70),
            f"Accuracy: {self.accuracy}%",
            font_path=Paths.fonts / "ObelixPro-cyr.ttf",
            font_size=35
        )

        # Display the right images and play SFX
        if not self.npcs:
            self.game_over_screen = ImageObject(
                self,
                (0, 0),
                Paths.ui / "you_lose.png"
            )
            move_height = 220
            self.you_lose_sfx.play()
        else:
            self.game_over_screen = ImageObject(
                self,
                (0, 0),
                Paths.ui / "winner.png"
            )
            move_height = 100
            self.you_win_sfx.play()

        image_width = self.game_over_screen.size[0]
        center_x = (Window.width / 2) - (image_width / 2)
        self.game_over_screen.move_absolute((center_x, move_height))

        # Play game over music
        self.manager.play_music("code_jam_full.ogg")

    def _draw_timer(self):
        """
        Draws the timer that counts down from
        ten minutes in the top right corner
        """
        self.timer = Timer(
            self,
            (1080, 20),
            self.start_ticks,
            speed_multiplier=self.speed_multiplier,
            font_path=Paths.fonts / "ObelixPro-Cry-cyr.ttf"
        )
        self.timer.draw()

    def _draw_npcs(self):
        """
        Draws all the NPCs to
        the play area.
        """
        for npc in self.npcs:
            if npc.frames_until_turn <= 0:
                npc.flip()
                npc.frames_until_turn = random.randint(100, 3000)
            if npc.frames_until_walk <= 0:
                move = random.uniform(-3, 3)

                # Gotta make sure they don't walk off the screen
                if npc.location[0] > (Window.width - 300):
                    move = -abs(move)
                elif npc.location[0] < 300:
                    move = abs(move)

                npc.move(move, 0)
                npc.frames_until_walk = random.randint(10, 150)
            else:
                npc.frames_until_walk -= 1
                npc.frames_until_turn -= 1
            npc.draw()

    def _fly_pyjet(self):
        """
        Flies the pyjet across the screen!
        Also handles dropping bombs from it.
        """
        pyjet_x = self.pyjet.location[0]
        pyjet_width = self.pyjet.size[0]
        off_screen = (
            (pyjet_x + pyjet_width) <= 0
            and not self.pyjet.left_to_right
            or pyjet_x >= Window.width
            and self.pyjet.left_to_right
        )

        if off_screen:
            self.pyjet = None
        else:
            if not self.pyjet.bombs_dropped == len(self.pyjet.bomb_drop_locations):
                drop_bomb_left = (
                    (pyjet_x + (pyjet_width / 2)) >= self.pyjet.bomb_drop_locations[self.pyjet.bombs_dropped]
                    and self.pyjet.left_to_right
                )

                drop_bomb_right = (
                    (pyjet_x - (pyjet_width / 2)) <= self.pyjet.bomb_drop_locations[-(self.pyjet.bombs_dropped + 1)]
                    and not self.pyjet.left_to_right
                )

                if drop_bomb_left or drop_bomb_right:
                    new_missile = self.pyjet.create_bomb(random.uniform(0.6, 1.2))

                    self.texts.append(
                        TextShootObject(self, (0, 0), new_missile, short=True)
                    )

                    self.pyjet.bombs_dropped += 1

            self.pyjet.draw()

    def _draw_missiles(self, text):
        """
        Draws all the missiles that are
        currently in play, and explodes
        them and murders NPCs if they
        go too low.
        """

        text_x, text_y = text.location
        y_loc = text_y + text.surface.get_rect().bottomleft[1]

        if y_loc > Window.height:
            return
        elif y_loc >= 550:

            # Murder the NPC, if he's nearby.
            closest_npc = None
            for npc in self.npcs:
                if not closest_npc:
                    closest_npc = npc
                elif abs(npc.location[0] - text_x) < abs(closest_npc.location[0] - text_x):
                    closest_npc = npc

            if closest_npc and abs(closest_npc.location[0] - text_x) < 100:
                self.npcs.remove(closest_npc)

                # Explode the missile!
                self._add_explosion(
                    text.location,
                    random.choice(Explosions.ban_text),
                    size=250
                )
                self.texts.remove(text)

                # Remove it from lock if it was locked.
                if text == self.lock:
                    self.lock = None

        text.draw()

    def _add_explosion(self, location: Tuple[int, int], text: str, size: int = 175):
        """
        Adds an explosion with the provided text.

        Optionally takes a size argument to determine
        what size the graphics should scale to.
        """
        explosion = Explosion(
            self,
            location,
            Paths.fonts / "ObelixPro-Cry-cyr.ttf",
            text,
            size,
        )

        self.explosions.append(explosion)

    def _commit_score_to_api(self):
        """
        Commits the users score to the
        database via the Megalomaniac API.
        """
        if not self.sent_scores:
            requests.post(
                URLs.add_score_api,
                json={
                    "username": self.manager.player_name,
                    "wpm": self.wpm,
                    "accuracy": self.accuracy,
                }
            )
            self.sent_scores = True

    def handle_events(self, events):
        """
        Handles all game input events,
        such as mouse movement and keypresses.
        """

        for event in events:
            if not self.game_running:
                for button in (self.restart_game_text, self.high_scores_text):
                    if button.mouseover():
                        if not button.highlighted:
                            button.highlight()

                        if pygame.mouse.get_pressed()[0] and button.word == "High scores":
                            self.manager.change_scene("high_score")
                        elif pygame.mouse.get_pressed()[0] and button.word == "Play again":
                            self.manager.change_scene("game")

                    elif not self.game_running and not button.mouseover():
                        button.remove_highlight()

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    self.manager.change_scene("main_menu")
                elif event.key == pygame.K_BACKSPACE and self.lock:
                    if self.lock.typed > 0:
                        self.lock.typed -= 1
                    else:
                        self.lock = None

                if not self.lock:
                    for text in self.texts:
                        result = text.key_input(event.key)

                        # If the user hit the right key, lock the word
                        if result == TextShootState.SUCCESS:
                            self.letters_typed += 1
                            self.gunshot.play()
                            self.lock = text

                            # Move the locked element to the end so it always renders on top of everything else.
                            self.texts.append(self.texts.pop(self.texts.index(self.lock)))

                            break
                        elif result == TextShootState.WORD_END:
                            self.letters_typed += 1
                            self.gunshot.play()
                            self.texts.remove(text)
                            self._add_explosion(
                                text.location,
                                random.choice(Explosions.destroy_text),
                                size=125
                            )
                            self.lock = None

                else:
                    result = self.lock.key_input(event.key)

                    if result == TextShootState.SUCCESS:
                        self.letters_typed += 1
                        self.gunshot.play()
                    elif result == TextShootState.WORD_END:
                        self.letters_typed += 1
                        self.gunshot.play()
                        self.texts.remove(self.lock)
                        self._add_explosion(
                            self.lock.location,
                            random.choice(Explosions.destroy_text),
                            size=125
                        )
                        self.lock = None
                    elif result == TextShootState.WRONG_KEY:
                        self.letters_missed += 1
                        self.wrong.play()

    def draw(self):
        """
        Draws everything to the screen.
        """
        self.background.draw()

        if self.game_running:
            self._draw_timer()
            self._draw_npcs()
            self.flutterdude.draw()

            # Create new flutterdude missiles periodically
            if (self.new_missile_timer == 0 and len(self.texts) < self.max_bombs) or not self.texts:
                new_missile = self.flutterdude.create_bomb(random.uniform(0.1, 0.4))

                self.texts.append(
                    TextShootObject(self, (0, 0), new_missile)
                )

                if self.new_missile_timer == 0:
                    self.new_missile_timer = 200
            else:
                self.new_missile_timer -= 1

            # Create jet periodically
            if self.new_jet_timer == 0:
                self.pyjet = PyJet(
                    self,
                    left_to_right=random.choice((True, False))
                )
                self.new_jet_timer = random.randint(450, 1200)
            else:
                self.new_jet_timer -= 1

            # Fly the jet! Rocket maaan!
            if self.pyjet:
                self._fly_pyjet()

            # Draw all the explosions
            for explosion in self.explosions.copy():
                explosion.draw()

                if explosion.frame_count >= explosion.frame_length:
                    self.explosions.remove(explosion)

            # Draw the missiles
            for text in self.texts:
                self._draw_missiles(text)

            # Check if we've lost yet
            if not self.npcs:
                self.game_running = False

            # Check if we've won (timer finished)
            if self.timer.milliseconds_left <= 0:
                self.game_running = False

        # Game is over, and we need to draw some UI.
        else:
            if not self.game_over_screen:
                self._build_game_over_screen()

            self.game_over_screen.draw()
            self._commit_score_to_api()
            self.restart_game_text.draw()
            self.high_scores_text.draw()
            self.wpm_text.draw()
            self.accuracy_text.draw()
    def _build_game_over_screen(self):
        """
        Builds a screen that can be displayed
        when the game is over, showing WINNER
        or YOU LOSE graphics, score, a retry
        button and playing sfx.
        """

        # Calculate WPM
        total_letters = self.letters_typed + self.letters_missed
        self.wpm = int((total_letters / 5) / self.timer.minutes_passed)
        self.wpm_text = TextObject(
            self,
            (1045, 15),
            f"WPM: {self.wpm}",
            font_path=Paths.fonts / "ObelixPro-cyr.ttf",
            font_size=35
        )

        # Calculate accuracy
        total_letters = self.letters_typed + self.letters_missed

        if total_letters:
            self.accuracy = int(
                100 - ((self.letters_missed / total_letters) * 100)
            )
        else:
            self.accuracy = 0

        self.accuracy_text = TextObject(
            self,
            (938, 70),
            f"Accuracy: {self.accuracy}%",
            font_path=Paths.fonts / "ObelixPro-cyr.ttf",
            font_size=35
        )

        # Display the right images and play SFX
        if not self.npcs:
            self.game_over_screen = ImageObject(
                self,
                (0, 0),
                Paths.ui / "you_lose.png"
            )
            move_height = 220
            self.you_lose_sfx.play()
        else:
            self.game_over_screen = ImageObject(
                self,
                (0, 0),
                Paths.ui / "winner.png"
            )
            move_height = 100
            self.you_win_sfx.play()

        image_width = self.game_over_screen.size[0]
        center_x = (Window.width / 2) - (image_width / 2)
        self.game_over_screen.move_absolute((center_x, move_height))

        # Play game over music
        self.manager.play_music("code_jam_full.ogg")
Exemple #6
0
class MainMenu(Scene):
    """
    The screen that appears after the intro sequences.

    The game logo is displayed at the top.
    The user must press a button to start the game.
    """

    name = "main_menu"

    def __init__(self, manager):

        super().__init__(manager)

        # Main game logo
        self.logo = ImageObject(
            self,
            (0, 0),
            Paths.ui / "logo.png",
        )

        # Move the logo to the right position, based on screen size.
        image_width = self.logo.size[0]
        logo_location = (
            (Window.width / 2) - (image_width / 2),
            40,
        )
        self.logo.move_absolute(logo_location)

        # How to play
        self.how_to_play_open = False
        self.how_to_play = ImageObject(self, (0, 0),
                                       Paths.ui / "how_to_play.png")
        self.close_how_to_play = ImageObject(self, (1060, 300),
                                             Paths.ui / "close_window.png")

        # Background image
        self.background = ImageObject(
            self,
            (0, 0),
            Paths.ui / "background.png",
        )

        # Floaty dudes
        self.flutterdude = FloatingObject(
            self,
            (980, 260),
            Paths.enemies / "flutterdude.png",
            float_range=(260, 280),
            float_speed=4,
        )

        self.brainmon = FloatingObject(
            self,
            (900, 400),
            Paths.enemies / "brainmon_firing.png",
            float_range=(390, 400),
            float_speed=3,
        )

        # Text init
        self.start_game_text = TextObject(
            self,
            (90, 400),
            "Start game",
            font_path=Paths.fonts / "NANDA.TTF",
            font_size=60,
        )

        self.how_to_play_text = TextObject(
            self,
            (90, 480),
            "How to play",
            font_path=Paths.fonts / "NANDA.TTF",
            font_size=60,
        )

        self.high_scores_text = TextObject(
            self,
            (90, 560),
            "High scores",
            font_path=Paths.fonts / "NANDA.TTF",
            font_size=60,
        )

        self.quit_text = TextObject(
            self,
            (90, 640),
            "Quit game",
            font_path=Paths.fonts / "NANDA.TTF",
            font_size=60,
        )

        self.menu_items = {
            "start": self.start_game_text,
            "how_to_play": self.how_to_play_text,
            "high_scores": self.high_scores_text,
            "quit": self.quit_text,
            "close": self.close_how_to_play,
        }

        # SFX
        if self.manager.previous_scene.name == "jetbrains":
            self.sound = pygame.mixer.Sound(str(Paths.sfx /
                                                "megalomaniac.ogg"))
            self.sound.play()

        # Music
        self.manager.play_music("code_jam_loop.ogg", loop=True)

    def handle_events(self, events):

        for event in events:
            for name, item in self.menu_items.items():

                if event.type == pygame.KEYDOWN:
                    if self.how_to_play_open and event.key == pygame.K_ESCAPE:
                        self.how_to_play_open = False
                        break

                if item.mouseover():
                    if not item.highlighted:
                        item.highlight()

                        if not name == "close":
                            item.move(15, 0)

                    if pygame.mouse.get_pressed(
                    )[0]:  # Left mouse button pressed
                        # Quit button
                        if name == "quit":
                            pygame.quit()
                            sys.exit()

                        # Start game
                        elif name == "start":
                            self.manager.change_scene("game")

                        elif name == "high_scores":
                            self.manager.change_scene("high_score")

                        # How to play
                        elif name == "how_to_play":
                            self.how_to_play_open = True

                        # Close "How to play" menu
                        elif name == "close":
                            self.how_to_play_open = False
                else:
                    if item.highlighted:
                        item.remove_highlight()

                        if not name == "close":
                            item.move(-15, 0)

    def draw(self):
        # Draw the background and the logo
        self.background.draw()
        self.logo.draw()

        if not self.how_to_play_open:
            # Draw the floaty entities
            self.brainmon.draw()
            self.flutterdude.draw()

            # Draw the menu
            self.start_game_text.draw()
            self.how_to_play_text.draw()
            self.high_scores_text.draw()
            self.quit_text.draw()
        else:
            self.how_to_play.draw()
            self.close_how_to_play.draw()
Exemple #7
0
    def __init__(self, manager):

        super().__init__(manager)

        # Main game logo
        self.logo = ImageObject(
            self,
            (0, 0),
            Paths.ui / "logo.png",
        )

        # Move the logo to the right position, based on screen size.
        image_width = self.logo.size[0]
        logo_location = (
            (Window.width / 2) - (image_width / 2),
            40,
        )
        self.logo.move_absolute(logo_location)

        # How to play
        self.how_to_play_open = False
        self.how_to_play = ImageObject(self, (0, 0),
                                       Paths.ui / "how_to_play.png")
        self.close_how_to_play = ImageObject(self, (1060, 300),
                                             Paths.ui / "close_window.png")

        # Background image
        self.background = ImageObject(
            self,
            (0, 0),
            Paths.ui / "background.png",
        )

        # Floaty dudes
        self.flutterdude = FloatingObject(
            self,
            (980, 260),
            Paths.enemies / "flutterdude.png",
            float_range=(260, 280),
            float_speed=4,
        )

        self.brainmon = FloatingObject(
            self,
            (900, 400),
            Paths.enemies / "brainmon_firing.png",
            float_range=(390, 400),
            float_speed=3,
        )

        # Text init
        self.start_game_text = TextObject(
            self,
            (90, 400),
            "Start game",
            font_path=Paths.fonts / "NANDA.TTF",
            font_size=60,
        )

        self.how_to_play_text = TextObject(
            self,
            (90, 480),
            "How to play",
            font_path=Paths.fonts / "NANDA.TTF",
            font_size=60,
        )

        self.high_scores_text = TextObject(
            self,
            (90, 560),
            "High scores",
            font_path=Paths.fonts / "NANDA.TTF",
            font_size=60,
        )

        self.quit_text = TextObject(
            self,
            (90, 640),
            "Quit game",
            font_path=Paths.fonts / "NANDA.TTF",
            font_size=60,
        )

        self.menu_items = {
            "start": self.start_game_text,
            "how_to_play": self.how_to_play_text,
            "high_scores": self.high_scores_text,
            "quit": self.quit_text,
            "close": self.close_how_to_play,
        }

        # SFX
        if self.manager.previous_scene.name == "jetbrains":
            self.sound = pygame.mixer.Sound(str(Paths.sfx /
                                                "megalomaniac.ogg"))
            self.sound.play()

        # Music
        self.manager.play_music("code_jam_loop.ogg", loop=True)
    def __init__(self, manager):

        super().__init__(manager)

        # Background image
        self.background = ImageObject(
            self,
            (0, 0),
            Paths.ui / "background.png",
        )

        # High score overlay
        self.high_scores = ImageObject(self, (0, 0),
                                       Paths.ui / "high_scores.png")

        # High scores headline
        self.headline_text = TextObject(self, (0, 0),
                                        "High scores",
                                        font_path=Paths.fonts /
                                        "ObelixPro-cyr.ttf",
                                        font_size=50)
        self.headline_text.move_absolute(
            ((Window.width / 2) - (self.headline_text.size[0] / 2), 75))

        # The icons that explain what each row is for
        self.score_headline = TextObject(self, (600, 200), "Score")
        self.wpm_headline = TextObject(self, (850, 200), "WPM")
        self.accuracy_headline = TextObject(self, (1000, 200), "Accuracy")

        # Add the close button
        self.close_button = ImageObject(self, (1120, 90),
                                        Paths.ui / "close_window.png")

        # Score attributes
        self.high_score_y = 270
        self.high_score_number = 1
        self.high_score_texts = []

        # Get the scores
        r = requests.get(URLs.scores_api)
        high_scorers = r.json()

        # Sort by score and limit to 5
        high_scorers = [{
            "username": name,
            "wpm": data['wpm'],
            "accuracy": data['accuracy'],
            "score": data['score']
        } for name, data in high_scorers.items()]
        high_scorers = sorted(high_scorers,
                              key=itemgetter('score'),
                              reverse=True)[:5]

        # Build the score text objects
        for score in high_scorers:
            points = score['score']
            name = score['username']
            accuracy = score['accuracy']
            wpm = score['wpm']

            self.high_score_texts.append((
                TextObject(self, (180, self.high_score_y),
                           f"{self.high_score_number}.",
                           font_path=Paths.fonts / "NANDA.TTF",
                           font_size=60),
                TextObject(self, (230, self.high_score_y),
                           name,
                           font_path=Paths.fonts / "NANDA.TTF",
                           font_size=60),
                TextObject(self, (600, self.high_score_y),
                           str(points),
                           font_path=Paths.fonts / "NANDA.TTF",
                           font_size=60),
                TextObject(self, (850, self.high_score_y),
                           str(wpm),
                           font_path=Paths.fonts / "NANDA.TTF",
                           font_size=60),
                TextObject(self, (1000, self.high_score_y),
                           f"{accuracy}%",
                           font_path=Paths.fonts / "NANDA.TTF",
                           font_size=60),
            ))
            self.high_score_y += 80
            self.high_score_number += 1

        self.start_game_text = TextObject(
            self,
            (90, 400),
            "Start game",
            font_path=Paths.fonts / "NANDA.TTF",
            font_size=60,
        )

        # Music
        if self.manager.previous_scene and not self.manager.previous_scene.name == "main_menu":
            self.manager.play_music("code_jam_full.ogg")
class HighScore(Scene):
    """
    The screen that appears after the intro sequences.

    The game logo is displayed at the top.
    The user must press a button to start the game.
    """

    name = "high_score"

    def __init__(self, manager):

        super().__init__(manager)

        # Background image
        self.background = ImageObject(
            self,
            (0, 0),
            Paths.ui / "background.png",
        )

        # High score overlay
        self.high_scores = ImageObject(self, (0, 0),
                                       Paths.ui / "high_scores.png")

        # High scores headline
        self.headline_text = TextObject(self, (0, 0),
                                        "High scores",
                                        font_path=Paths.fonts /
                                        "ObelixPro-cyr.ttf",
                                        font_size=50)
        self.headline_text.move_absolute(
            ((Window.width / 2) - (self.headline_text.size[0] / 2), 75))

        # The icons that explain what each row is for
        self.score_headline = TextObject(self, (600, 200), "Score")
        self.wpm_headline = TextObject(self, (850, 200), "WPM")
        self.accuracy_headline = TextObject(self, (1000, 200), "Accuracy")

        # Add the close button
        self.close_button = ImageObject(self, (1120, 90),
                                        Paths.ui / "close_window.png")

        # Score attributes
        self.high_score_y = 270
        self.high_score_number = 1
        self.high_score_texts = []

        # Get the scores
        r = requests.get(URLs.scores_api)
        high_scorers = r.json()

        # Sort by score and limit to 5
        high_scorers = [{
            "username": name,
            "wpm": data['wpm'],
            "accuracy": data['accuracy'],
            "score": data['score']
        } for name, data in high_scorers.items()]
        high_scorers = sorted(high_scorers,
                              key=itemgetter('score'),
                              reverse=True)[:5]

        # Build the score text objects
        for score in high_scorers:
            points = score['score']
            name = score['username']
            accuracy = score['accuracy']
            wpm = score['wpm']

            self.high_score_texts.append((
                TextObject(self, (180, self.high_score_y),
                           f"{self.high_score_number}.",
                           font_path=Paths.fonts / "NANDA.TTF",
                           font_size=60),
                TextObject(self, (230, self.high_score_y),
                           name,
                           font_path=Paths.fonts / "NANDA.TTF",
                           font_size=60),
                TextObject(self, (600, self.high_score_y),
                           str(points),
                           font_path=Paths.fonts / "NANDA.TTF",
                           font_size=60),
                TextObject(self, (850, self.high_score_y),
                           str(wpm),
                           font_path=Paths.fonts / "NANDA.TTF",
                           font_size=60),
                TextObject(self, (1000, self.high_score_y),
                           f"{accuracy}%",
                           font_path=Paths.fonts / "NANDA.TTF",
                           font_size=60),
            ))
            self.high_score_y += 80
            self.high_score_number += 1

        self.start_game_text = TextObject(
            self,
            (90, 400),
            "Start game",
            font_path=Paths.fonts / "NANDA.TTF",
            font_size=60,
        )

        # Music
        if self.manager.previous_scene and not self.manager.previous_scene.name == "main_menu":
            self.manager.play_music("code_jam_full.ogg")

    def handle_events(self, events):
        for event in events:

            # Escape goes back to the main menu
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    self.manager.change_scene("main_menu")

        if self.close_button.mouseover():
            if not self.close_button.highlighted:
                self.close_button.highlight()

            if pygame.mouse.get_pressed()[0]:
                self.manager.change_scene("main_menu")
        else:
            if self.close_button.highlighted:
                self.close_button.remove_highlight()

    def draw(self):
        # Draw the background and the logo
        self.background.draw()
        self.high_scores.draw()
        self.headline_text.draw()
        self.close_button.draw()

        # Draw the icons
        self.wpm_headline.draw()
        self.score_headline.draw()
        self.accuracy_headline.draw()

        for score in self.high_score_texts:
            for attribute in score:
                attribute.draw()
class EnterName(Scene):
    """
    The screen that appears after the intro sequences,
    asking the player to enter their name so we can use
    it for high scores.
    """

    name = "enter_name"

    def __init__(self, manager):

        super().__init__(manager)

        # Background image
        self.background = ImageObject(
            self,
            (0, 0),
            Paths.ui / "background.png",
        )

        # Box overlay
        self.enter_name_box = ImageObject(self, (0, 0),
                                          Paths.ui / "enter_name_box.png")
        self.enter_name_box.move_absolute(
            ((Window.width / 2) - (self.enter_name_box.size[0] / 2),
             (Window.height / 2) - (self.enter_name_box.size[1] / 2)))

        # Instructions
        self.please_enter_name = TextObject(self, (0, 0),
                                            "Who are you?",
                                            font_path=Paths.fonts /
                                            "ObelixPro-cyr.ttf",
                                            font_size=50)
        self.please_enter_name.move_absolute(
            ((Window.width / 2) - (self.please_enter_name.size[0] / 2), 200))

        # InputBox
        self.input_field = TextInputObject(
            self,
            (450, 315),
            font_path=Paths.fonts / "FiraMono-Regular.ttf",
            font_size=40,
            max_length=15,
            text_color=Colors.white,
            cursor_color=Colors.white,
        )

        # Explanation text below input
        self.explanation_line_1 = TextObject(
            self, (0, 0),
            "We need this information for high scores.",
            font_path=Paths.fonts / "FiraMono-Regular.ttf",
            font_size=20)
        self.explanation_line_1.move_absolute(
            ((Window.width / 2) - (self.explanation_line_1.size[0] / 2), 400))
        self.explanation_line_2 = TextObject(
            self, (0, 0),
            "Feel free to input a nickname, or a made-up name.",
            font_path=Paths.fonts / "FiraMono-Regular.ttf",
            font_size=20)
        self.explanation_line_2.move_absolute(
            ((Window.width / 2) - (self.explanation_line_2.size[0] / 2), 440))

        # OK Button
        self.ok_button = TextObject(self, (880, 530),
                                    "OK",
                                    font_path=Paths.fonts /
                                    "ObelixPro-cyr.ttf",
                                    font_size=50,
                                    disabled=True)

        # Music
        self.manager.play_music("code_jam_loop.ogg", loop=True)

    def handle_events(self, events):
        return_event = self.input_field.update(events)

        # Check whether to enable the button
        if len(self.input_field.get_text()) >= 1 and self.ok_button.disabled:
            self.ok_button.enable()
        elif len(self.input_field.get_text()
                 ) < 1 and not self.ok_button.disabled:
            self.ok_button.disable()

        # Handle the OK button
        if not self.ok_button.disabled:

            # User hit RETURN and has typed something
            if return_event:
                self.manager.player_name = self.input_field.get_text()
                self.manager.change_scene("main_menu")

            # Handle mouse interaction with OK button
            if self.ok_button.mouseover():
                if not self.ok_button.highlighted:
                    self.ok_button.highlight()

                if pygame.mouse.get_pressed()[0]:
                    self.manager.player_name = self.input_field.get_text()
                    self.manager.change_scene("main_menu")

            # No mouseover event
            else:
                if self.ok_button.highlighted:
                    self.ok_button.remove_highlight()

    def draw(self):
        self.background.draw()
        self.enter_name_box.draw()
        self.please_enter_name.draw()
        self.explanation_line_1.draw()
        self.explanation_line_2.draw()
        self.ok_button.draw()
        self.input_field.draw()
    def __init__(self, manager):

        super().__init__(manager)

        # Background image
        self.background = ImageObject(
            self,
            (0, 0),
            Paths.ui / "background.png",
        )

        # Box overlay
        self.enter_name_box = ImageObject(self, (0, 0),
                                          Paths.ui / "enter_name_box.png")
        self.enter_name_box.move_absolute(
            ((Window.width / 2) - (self.enter_name_box.size[0] / 2),
             (Window.height / 2) - (self.enter_name_box.size[1] / 2)))

        # Instructions
        self.please_enter_name = TextObject(self, (0, 0),
                                            "Who are you?",
                                            font_path=Paths.fonts /
                                            "ObelixPro-cyr.ttf",
                                            font_size=50)
        self.please_enter_name.move_absolute(
            ((Window.width / 2) - (self.please_enter_name.size[0] / 2), 200))

        # InputBox
        self.input_field = TextInputObject(
            self,
            (450, 315),
            font_path=Paths.fonts / "FiraMono-Regular.ttf",
            font_size=40,
            max_length=15,
            text_color=Colors.white,
            cursor_color=Colors.white,
        )

        # Explanation text below input
        self.explanation_line_1 = TextObject(
            self, (0, 0),
            "We need this information for high scores.",
            font_path=Paths.fonts / "FiraMono-Regular.ttf",
            font_size=20)
        self.explanation_line_1.move_absolute(
            ((Window.width / 2) - (self.explanation_line_1.size[0] / 2), 400))
        self.explanation_line_2 = TextObject(
            self, (0, 0),
            "Feel free to input a nickname, or a made-up name.",
            font_path=Paths.fonts / "FiraMono-Regular.ttf",
            font_size=20)
        self.explanation_line_2.move_absolute(
            ((Window.width / 2) - (self.explanation_line_2.size[0] / 2), 440))

        # OK Button
        self.ok_button = TextObject(self, (880, 530),
                                    "OK",
                                    font_path=Paths.fonts /
                                    "ObelixPro-cyr.ttf",
                                    font_size=50,
                                    disabled=True)

        # Music
        self.manager.play_music("code_jam_loop.ogg", loop=True)