Example #1
0
 def _high_score(self):
     high_score_file = HighScoreManager()
     file_name = high_score_file._filename
     high_score_file.load(file_name)
     score_root = Tk()
     score_root.title("High Score Board")
     high_score_board = Text(score_root, height=25, width=25)
     high_score_board.pack()
     high_score = high_score_file.get_entries()
Example #2
0
    def _handle_game_over(self, won=False):
        """Handles game over
        
        Parameter:
            won (bool): If True, signals the game was won (otherwise lost)
        """
        self._won = won
        self.stop()
        self._button_pause.config(state=DISABLED)
        if self._won == True:
            message_dialog = "Congratulation! You won!"
        else:
            message_dialog = "Oops, you lost, try again"
        high_score_file = HighScoreManager()
        file_name = high_score_file._filename
        high_score_file.load(file_name)
        # high_root = Tk()
        # if(high_score_file.does_score_qualify(self._score)==True):

        msg.showinfo("Tower", message_dialog)
Example #3
0
class TowerGameApp(Stepper):
    """Top-level GUI application for a simple tower defence game"""

    # All private attributes for ease of reading
    _current_tower = None
    _paused = False
    _won = None

    _level = None
    _wave = None
    _score = None
    _coins = None
    _lives = None

    _master = None
    _game = None
    _view = None

    # Task 3.3
    _selected_tower = None

    def __init__(self, master: tk.Tk, delay: int = 20):
        """Construct a tower defence game in a root window

        Parameters:
            master (tk.Tk): Window to place the game into
        """

        self._master = master
        super().__init__(master, delay=delay)

        master.title('Towers')

        self._game = game = TowerGame()

        self.setup_menu()

        # create a game view and draw grid borders
        self._view = view = GameView(master,
                                     size=game.grid.cells,
                                     cell_size=game.grid.cell_size,
                                     bg='antique white')
        view.pack(side=tk.LEFT, expand=True)

        self._right_frame = tk.Frame(master, bg=BACKGROUND_COLOUR)
        self._right_frame.pack(side=tk.LEFT, fill=tk.Y)

        # Task 2.4 (High Scores): load files into a high score manager instance
        self._high_score_manager = HighScoreManager()
        self._high_score_manager.load(self._high_score_manager._filename)

        # Task 1.3 (Status Bar): instantiate status bar
        self._status = status = StatusBar(self._right_frame)
        status.pack(side=tk.TOP, fill=tk.X, anchor=tk.N, expand=False)

        # Task 2.3 (Shop): instantiation
        towers = [SimpleTower, MissileTower, EnergyTower, SlowTower]

        shop = tk.Frame(self._right_frame)
        shop.pack(side=tk.TOP, anchor=tk.N, fill=tk.X)

        # Create views for each tower & store to update if availability changes
        self._tower_views = []
        for tower_class in towers:
            tower = tower_class(self._game.grid.cell_size // 2)

            v = ShopTowerView(shop,
                              tower,
                              bg=BACKGROUND_COLOUR,
                              highlight="#4b3b4a",
                              click_command=lambda class_=tower_class: self.
                              select_tower(class_))
            v.pack(fill=tk.X)
            self._tower_views.append(
                (tower, v)
            )  # Can use to check if tower is affordable when refreshing view

        # Task 1.5 (Play Controls): instantiate widgets here
        self._control = control = tk.Frame(self._right_frame)
        self._next_wave = tk.Button(control,
                                    text="Next Wave",
                                    command=self.next_wave,
                                    font=("TkDefaultFont", 12, "normal"))
        self._next_wave.pack(side=tk.LEFT)
        self._play = tk.Button(control,
                               text="Play",
                               command=self._toggle_paused,
                               font=("TkDefaultFont", 12, "normal"))
        self._play.pack(side=tk.LEFT)
        self._control.pack(side=tk.BOTTOM, anchor=tk.S)

        # Task 3.3 (Upgrade Tower): GUI
        self._upgrade_button = tk.Button(self._right_frame,
                                         text="Upgrade selected tower",
                                         state="disabled",
                                         command=self.upgrade_selected_tower,
                                         font=("TkDefaultFont", 12, "normal"))
        self._upgrade_button.pack(side=tk.BOTTOM, anchor=tk.S)
        self._selected_tower_level_up_cost = tk.Label(
            self._right_frame,
            text="Cost to upgrade: N/A",
            bg=BACKGROUND_COLOUR,
            fg="white",
            font=("TkDefaultFont", 12, "normal"))
        self._selected_tower_level_up_cost.pack(side=tk.BOTTOM, anchor=tk.S)
        self._selected_tower_level = tk.Label(
            self._right_frame,
            text="Selected tower level: None",
            bg=BACKGROUND_COLOUR,
            fg="white",
            font=("TkDefaultFont", 12, "normal"))
        self._selected_tower_level.pack(side=tk.BOTTOM, anchor=tk.S)

        # bind game events
        game.on("enemy_death", self._handle_death)
        game.on("enemy_escape", self._handle_escape)
        game.on("cleared", self._handle_wave_clear)

        # Task 1.2 (Tower Placement): bind mouse events to canvas here
        self._view.bind("<Motion>", self._move)
        self._view.bind("<Button-1>", self._left_click)
        self._view.bind("<Leave>", self._mouse_leave)

        # Task 2.1: bind mouse event
        self._view.bind("<Button-2>", self._sell_tower)

        # Level
        self._level = MyLevel()

        self.select_tower(SimpleTower)

        view.draw_borders(game.grid.get_border_coordinates())

        # Get ready for the game
        self._setup_game()

        self._check_availability()

    def setup_menu(self):
        """Sets up the application menu"""
        # Task 1.4: construct file menu here
        self._menubar = tk.Menu(self._master)
        self._master.config(menu=self._menubar)

        self._filemenu = tk.Menu(self._menubar)
        self._menubar.add_cascade(label="File", menu=self._filemenu)
        self._filemenu.add_command(label="New Game", command=self._new_game)
        self._filemenu.add_command(label="High Scores",
                                   command=self._show_high_scores)
        self._filemenu.add_command(label="Exit", command=self._exit)

    def _toggle_paused(self, paused=None):
        """Toggles or sets the paused state

        Parameters:
            paused (bool): Toggles/pauses/unpauses if None/True/False, respectively
        """
        if paused is None:
            paused = not self._paused

        # Task 1.5 (Play Controls): Reconfigure the pause button here
        if paused:
            self.pause()
            self._play.configure(text="Play")
        else:
            self.start()
            self._play.configure(text="Pause")

        self._paused = paused

    def _setup_game(self):
        """Sets up the game"""
        self._wave = 0
        self._score = 0
        self._coins = 5000
        self._lives = 20

        self._won = False

        # Task 1.3 (Status Bar): Update status here
        self.refresh_status()

        # Task 1.5 (Play Controls): Re-enable the play controls here (if they were ever disabled)
        self._next_wave.configure(state="normal")
        self._play.configure(state="normal")

        self._game.reset()

        # Auto-start the first wave
        self.next_wave()
        self._toggle_paused(paused=True)

    def _new_game(self):
        """Menu handler - restarts the game"""
        self._setup_game()
        self.refresh_view()

    def _exit(self):
        """Menu handler - exits the application"""
        confirm = messagebox.askyesno(
            "", "Are you sure you want to exit the game?")
        if confirm == True:
            self._master.destroy()

    def refresh_view(self):
        """Refreshes the game view"""
        if self._step_number % 2 == 0:
            self._view.draw_enemies(self._game.enemies)
        self._view.draw_towers(self._game.towers)
        self._view.draw_obstacles(self._game.obstacles)

        self._check_availability()

    def _step(self):
        """
        Perform a step every interval

        Triggers a game step and updates the view

        Returns:
            (bool) True if the game is still running
        """
        self._game.step()
        self.refresh_view()

        return not self._won

    # Task 1.2 (Tower Placement): Complete event handlers here (including docstrings!)
    def _move(self, event):
        """
        Handles the mouse moving over the game view canvas

        Parameter:
            event (tk.Event): Tkinter mouse event
        """
        if self._current_tower.get_value() > self._coins:
            return

        # move the shadow tower to mouse position
        position = event.x, event.y
        self._current_tower.position = position

        legal, grid_path = self._game.attempt_placement(position)

        # find the best path and covert positions to pixel positions
        path = [
            self._game.grid.cell_to_pixel_centre(position)
            for position in grid_path.get_shortest()
        ]

        # draw the path
        self._view.draw_path(path)

        # check if current position is a legal position to place a tower at
        legal = True
        cell_position = self._game.grid.pixel_to_cell(position)
        if not self._game.grid.is_cell_valid(cell_position):
            legal = False
        if cell_position in self._game.towers:
            legal = False
        try:
            self._game.generate_path(cell_position)
        except KeyError:
            legal = False

        # draw the shadow tower or cross if legal == False
        self._view.draw_preview(self._current_tower, legal)

    def _mouse_leave(self, event):
        """
        Handles the mouse leaving the game view canvas

        Parameter:
            event (tk.Event): Tkinter mouse event
        """
        # Task 1.2 (Tower placement)
        self._view.delete('shadow', 'range', 'path')

    def _left_click(self, event):
        """
        Handles the mouse left clicking on the game view canvas

        Parameter:
            event (tk.Event): Tkinter mouse event
        """
        self.reset_upgrade_gui()

        if self._current_tower is None:
            return

        position = event.x, event.y
        cell_position = self._game.grid.pixel_to_cell(position)

        try:
            # Task 3.3 (Upgrade Tower): update GUI when left clicked on a tower
            self._selected_tower = self._game.towers[cell_position]
            selected_tower_level = self._selected_tower.level
            selected_tower_upgrade_cost = self._selected_tower.level_cost
            self._selected_tower_level.configure(
                text="Selected tower level:  " + str(selected_tower_level))
            self._selected_tower_level_up_cost.configure(
                text="Cost to upgrade: " + str(selected_tower_upgrade_cost))

            if selected_tower_upgrade_cost > self._coins:
                self._upgrade_button.configure(text="Not enough coins!")
            else:
                self._upgrade_button.configure(state="normal")

        except KeyError:
            if self._current_tower.get_value() > self._coins:
                return
            elif self._game.place(cell_position,
                                  tower_type=self._current_tower.__class__):
                self._coins -= self._current_tower.get_value()
                self.refresh_status()
                self.refresh_view()

    def _sell_tower(self, event):
        """
        Handles the mouse right clicking on the game view canvas

        Parameter:
            event (tk.Event): Tkinter mouse event
        """
        self.reset_upgrade_gui()

        position = event.x, event.y
        cell_position = self._game.grid.pixel_to_cell(position)

        try:
            # refund 80% of the tower's cost to player (including upgrade costs)
            # math.floor is used to keep self._coins an integer
            tower_at_position = self._game.towers[cell_position]
            self._coins += math.floor(0.8 * tower_at_position.get_value())
            self.refresh_status()

            self._game.remove(cell_position)
            self.refresh_view()
        except KeyError:
            # do nothing if a tower does not exist at the point of right click
            return

    def next_wave(self):
        """Sends the next wave of enemies against the player"""
        if self._wave == self._level.get_max_wave():
            return

        self._wave += 1

        # Task 1.3 (Status Bar): Update the current wave display here
        self.refresh_status()

        # Task 1.5 (Play Controls): Disable the add wave button here (if this is the last wave)
        if self._wave == self._level.waves:
            self._next_wave.configure(state="disabled")

        # Generate wave and enqueue
        wave = self._level.get_wave(self._wave)
        for step, enemy in wave:
            enemy.set_cell_size(self._game.grid.cell_size)

        self._game.queue_wave(wave)

    def select_tower(self, tower):
        """
        Set 'tower' as the current tower

        Parameters:
            tower (AbstractTower): The new tower type
        """
        self._current_tower = tower(self._game.grid.cell_size)

    def _handle_death(self, enemies):
        """
        Handles enemies dying

        Parameters:
            enemies (list<AbstractEnemy>): The enemies which died in a step
        """
        bonus = len(enemies)**.5
        for enemy in enemies:
            self._coins += enemy.points
            self._score += int(enemy.points * bonus)

        # Task 1.3 (Status Bar): Update coins & score displays here
        self.refresh_status()

    def _handle_escape(self, enemies):
        """
        Handles enemies escaping (not being killed before moving through the grid

        Parameters:
            enemies (list<AbstractEnemy>): The enemies which escaped in a step
        """
        self._lives -= len(enemies)
        if self._lives < 0:
            self._lives = 0

        # Task 1.3 (Status Bar): Update lives display here
        self.refresh_status()

        # Handle game over
        if self._lives == 0:
            self._handle_game_over(won=False)

    def _handle_wave_clear(self):
        """Handles an entire wave being cleared (all enemies killed)"""
        if self._wave == self._level.get_max_wave():
            self._handle_game_over(won=True)

    def _handle_game_over(self, won=False):
        """Handles game over
        
        Parameter:
            won (bool): If True, signals the game was won (otherwise lost)
        """
        self._won = won
        self.stop()

        # Task 1.4 (Dialogs): show game over dialog here
        result_message = ""
        if won:
            result_message = "You won the game."
        else:
            result_message = "You lost the game."

        messagebox.showinfo("Game over!", result_message)

        self._next_wave.configure(state="disabled")
        self._play.configure(state="disabled")

        # Task 2.4 (High Scores): asks user for name if score is high enough
        if self._high_score_manager.does_score_qualify(self._score):
            username = tk.simpledialog.askstring(
                "",
                "You are in the top 10! Enter your name below to go on the leaderboard!",
                parent=self._master)
            if username is not None:
                self._high_score_manager.add_entry(username, self._score)
                self._high_score_manager.save()

    def refresh_status(self):
        """Update the status bar (and others related)"""
        self._status.set_wave(self._wave, self._level.waves)
        self._status.set_score(self._score)
        self._status.set_coin(self._coins)
        self._status.set_lives(self._lives)

    def _check_availability(self):
        """Checks and sets availability of towers in the shop"""
        for towers in self._tower_views:
            tower_value = towers[0].get_value()
            if self._coins < tower_value:
                towers[1].set_available(False)
            else:
                towers[1].set_available(True)

    def _show_high_scores(self):
        """Handles displaying the highest scores and players."""
        high_score_root = tk.Tk()
        high_score_root.title("High Scores")
        high_score_root.resizable(False, False)

        high_score_data = self._high_score_manager.get_entries()

        # list of names and scores
        names_scores = []
        for element in high_score_data:
            name_score = element['name'], element['score']
            names_scores.append(name_score)

        title = tk.Label(high_score_root,
                         text="Top 10 Players",
                         font=("TkDefaultFont", 15, "bold"))
        title.pack(anchor=tk.N)

        # create GUI entries for each name/score
        for e in names_scores:
            name_score_frame = tk.Frame(high_score_root, width=250, height=26)
            name_score_frame.pack()
            name_score_frame.pack_propagate(False)
            name = tk.Label(name_score_frame, text=e[0])
            name.pack(side=tk.LEFT, padx=10, pady=5)
            score = tk.Label(name_score_frame, text=e[1])
            score.pack(side=tk.RIGHT, padx=10, pady=5)

        high_score_root.mainloop()

    def reset_upgrade_gui(self):
        """Resets the upgrade tower GUI in task 3.3 to initial state"""
        self._selected_tower_level_up_cost.configure(
            text="Cost to upgrade: N/A")
        self._selected_tower_level.configure(text="Selected tower level: None")
        self._selected_tower = None
        self._upgrade_button.configure(state="disabled",
                                       text="Upgrade selected tower")

    def upgrade_selected_tower(self):
        """Upgrade the currently selected tower"""
        if self._selected_tower is None:
            return
        else:
            self._coins -= self._selected_tower.level_cost
            self._selected_tower.level += 1
            self.reset_upgrade_gui()
            self.refresh_status()