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()
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.click_next_wave.config(state='disabled') self.click_play.config(state='disabled') # Task 1.4 (Dialogs): show game over dialog here if self._won: messagebox.showinfo("Game Over!", "You Won") else: messagebox.showinfo("Game Over!", "You Lost") self._highscore = highscore = HighScoreManager() # Call highscore manager if highscore.does_score_qualify(self._score): self._name = askstring('High Score Achieved!', 'Please tell us your name!') while self._name == None or self._name == '': # Asking for a valid name(if none or empty string) self._name = askstring('High Score Achieved!', 'Please Enter a valid name!') highscore.add_entry(self._name, self._score) highscore.save(filename = 'high_scores.json') else: return None
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)
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) self._highscore = highscore = HighScoreManager() highscore.load(filename='high_scores.json') self._game = game = TowerGame() master.title("Tower of Defence by Minjae Lee") self.setup_menu(master) # 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) # Task 1.3 (Status Bar): instantiate status bar self._status_bar = StatusBar(master) self._status_bar.pack(fill = tk.X) # Task 1.5 (Play Controls): instantiate widgets here self.play_control = tk.Frame(master, bg = 'white') self.play_control.pack(side = tk.BOTTOM, fill = tk.Y) self.click_next_wave = tk.Button(self.play_control, text = "Next Wave", command = self.next_wave) self.click_next_wave.pack(side = tk.LEFT) self.click_play = tk.Button(self.play_control, text = "Play", command =self._toggle_paused) self.click_play.pack(side = tk.LEFT) # 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('<Button-1>', self._left_click) self._view.bind('<Motion>', self._move) self._view.bind('<Leave>', self._mouse_leave) self._view.bind('<Button-3>', self._right_click) # Level self._level = MyLevel() self.select_tower(SimpleTower) view.draw_borders(game.grid.get_border_coordinates()) # Get ready for the game self._setup_game() towers = [ ] for positions, tower in towers: for position in positions: self._game.place(position, tower_type=tower) ## # Task 2.3 (ShopTowerView): instantiate gui here self.towers = [ SimpleTower, MissileTower, EnergyTower, PulseTower, TurretTower, CoinTower ] shop = tk.Frame(master) shop.pack(fill=tk.X) # Create views for each tower & store to update if availability changes self._tower_views = [] for tower_class in self.towers: tower = tower_class(self._game.grid.cell_size // 2) self._shop_view = view= ShopTowerView(shop, tower, bg=BACKGROUND_COLOUR, highlight="#4b3b4a", click_command=lambda class_=tower_class: self.select_tower(class_)) view.pack(fill=tk.X) self._tower_views.append((tower, view)) # Can use to check if tower is affordable when refreshing view
def __init__(self, master: tk.Tk, delay: int = 30): """Construct a tower defence game in a root window Parameters: master (tk.Tk): Window to place the game into """ self._master = master master.title('Towers') super().__init__(master, delay=delay) 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, fill=tk.BOTH, expand=False) # # Task 1.3 (Status Bar): instantiate status bar self._level = MyLevel() self._status_bar = StatusBar(master, self._level.get_max_wave()) self._status_bar.pack(fill=tk.BOTH, side=tk.TOP, expand=False) self._play_and_upgrades = tk.Frame(master, bg='white', height=40, width=200) self._play_and_upgrades.pack(side=tk.BOTTOM, expand=True, fill=tk.BOTH) self._pause_button = tk.Button(self._play_and_upgrades, text='Pause', command=self._toggle_paused) self._pause_button.pack(side=tk.LEFT, anchor=tk.N, expand=False) self._pause_button.place(x=160, y=0) self._next_wave_button = tk.Button(self._play_and_upgrades, text='Next Wave', command=self.next_wave) self._next_wave_button.pack(side=tk.LEFT, anchor=tk.N, expand=False) self._next_wave_button.place(x=90, y=0) #highscores object self._high_scores = HighScoreManager() #Used for fixing bug where clicking New Game would make game faster try: if self._new_game_called: pass except AttributeError: #raised when new_game hasn't been called self._new_game_count = 0 #whether upgrades has been called before. Whether to clear upgrade buttons #or not. self._called_before = 0 self._speed_upgrade = 0 self._simple_upgrade = 0 self._no_coins_ice = 0 self._no_coins_speed = 0 self._missile_upgrade = 0 self._no_coins_simple = 0 self._ice_upgrade = 0 self._confirm_upgrades = 0 #Shop Class instantiation self._towers = towers = [ MissileTower, IceTower, PulseTower, EnergyTower ] self._shop = tk.Frame(master, bg='purple') self._shop.pack(fill=tk.BOTH, side=tk.BOTTOM, expand=False) # ## Create views for each tower & store to update if availability changes # self._tower_views = [] # col = 0 # for tower_class in towers: # tower = tower_class(self._game.grid.cell_size) # self._shop_view = ShopTowerView(self._shop, tower_class, #highlight="#4b3b4a", # click_command=lambda class_=tower_class: self.select_tower(class_), bg=colours[col]) # self._shop_view.pack(fill=tk.BOTH, side=tk.TOP, expand=True) # self._tower_views.append((tower, self._shop_view)) # Can use to check if tower is affordable when refreshing view # col+=1 self._tower_views = [] col = 0 for tower_class in towers: # tower = tower_class(self._game.grid.cell_size // 2) tower = tower_class(30) view = ShopTowerView( self._shop, tower, bg='purple', #, highlight="#4b3b4a", click_command=lambda class_=tower_class: self.select_tower( class_)) view.pack(fill=tk.X) self._tower_views.append((tower, view)) col += 1 # Task 1.5 (Play Controls): instantiate widgets here # 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("<Button-1>", self._left_click) self._view.bind("<Motion>", self._move) self._view.bind("<Leave>", self._mouse_leave) self._view.bind("<Button-3>", self._right_click) # Level self._level = MyLevel() self.select_tower(MissileTower) self._view.draw_borders(game.grid.get_border_coordinates()) # Get ready for the game self._setup_game()
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) 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) # Task 1.3 (Status Bar): instantiate status bar # ... self._player = tk.Frame(master, bg='pink') self._player.pack(side=tk.LEFT, expand=True, fill=tk.BOTH) self._status = StatusBar(self._player) # Task 1.5 (Play Controls): instantiate widgets here # ... self._controlsArea = tk.Frame(self._player) self._controlsArea.pack(side=tk.BOTTOM) self._nextWave = tk.Button(self._controlsArea, text='Next wave', command=self.next_wave) self._nextWave.pack(side=tk.LEFT) self._pause = tk.Button(self._controlsArea, text='Play', command=self._toggle_paused) self._pause.pack(side=tk.LEFT) self._highscorefile = HighScoreManager() # 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) self._view.bind("<Button-3>",self._right_click) # Level self._level = MyLevel() self.select_tower(SimpleTower) view.draw_borders(game.grid.get_border_coordinates()) # Get ready for the game self._setup_game() # Remove the relevant lines while attempting the corresponding section # Hint: Comment them out to keep for reference # Task 1.2 (Tower Placement): remove these lines towers = [ ([(2, 2), (3, 0), (4, 1), (4, 2), (4, 3)], MissileTower), ([(2, 5)], EnergyTower) ] for positions, tower in towers: for position in positions: game.place(position, tower_type=tower) # Task 1.5 (Tower Placement): remove these lines #game.queue_wave([], clear=True) #self.next_wave() # Task 1.5 (Play Controls): remove this line #self.start() #shop towers = [ SimpleTower, MissileTower, EnergyTower ] # 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) # bg=BACKGROUND_COLOUR, highlight="#4b3b4a", shopView = ShopTowerView(self._player, tower, click_command=lambda class_=tower_class: self.select_tower(class_)) shopView.pack(side=tk.TOP) # Can use to check if tower is affordable when refreshing view self._tower_views.append((tower, shopView)) self.refresh_view()
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()
class TowerGameApp(Stepper): """Top-level GUI application for a simple tower defence game""" # pylint: disable=too-many-instance-attributes # I couldn't figure out how to fix certain bugs without defining a bunch of # variables at the start. # 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 def __init__(self, master: tk.Tk, delay: int = 30): """Construct a tower defence game in a root window Parameters: master (tk.Tk): Window to place the game into """ self._master = master master.title('Towers') super().__init__(master, delay=delay) 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, fill=tk.BOTH, expand=False) # # Task 1.3 (Status Bar): instantiate status bar self._level = MyLevel() self._status_bar = StatusBar(master, self._level.get_max_wave()) self._status_bar.pack(fill=tk.BOTH, side=tk.TOP, expand=False) self._play_and_upgrades = tk.Frame(master, bg='white', height=40, width=200) self._play_and_upgrades.pack(side=tk.BOTTOM, expand=True, fill=tk.BOTH) self._pause_button = tk.Button(self._play_and_upgrades, text='Pause', command=self._toggle_paused) self._pause_button.pack(side=tk.LEFT, anchor=tk.N, expand=False) self._pause_button.place(x=160, y=0) self._next_wave_button = tk.Button(self._play_and_upgrades, text='Next Wave', command=self.next_wave) self._next_wave_button.pack(side=tk.LEFT, anchor=tk.N, expand=False) self._next_wave_button.place(x=90, y=0) #highscores object self._high_scores = HighScoreManager() #Used for fixing bug where clicking New Game would make game faster try: if self._new_game_called: pass except AttributeError: #raised when new_game hasn't been called self._new_game_count = 0 #whether upgrades has been called before. Whether to clear upgrade buttons #or not. self._called_before = 0 self._speed_upgrade = 0 self._simple_upgrade = 0 self._no_coins_ice = 0 self._no_coins_speed = 0 self._missile_upgrade = 0 self._no_coins_simple = 0 self._ice_upgrade = 0 self._confirm_upgrades = 0 #Shop Class instantiation self._towers = towers = [ MissileTower, IceTower, PulseTower, EnergyTower ] self._shop = tk.Frame(master, bg='purple') self._shop.pack(fill=tk.BOTH, side=tk.BOTTOM, expand=False) # ## Create views for each tower & store to update if availability changes # self._tower_views = [] # col = 0 # for tower_class in towers: # tower = tower_class(self._game.grid.cell_size) # self._shop_view = ShopTowerView(self._shop, tower_class, #highlight="#4b3b4a", # click_command=lambda class_=tower_class: self.select_tower(class_), bg=colours[col]) # self._shop_view.pack(fill=tk.BOTH, side=tk.TOP, expand=True) # self._tower_views.append((tower, self._shop_view)) # Can use to check if tower is affordable when refreshing view # col+=1 self._tower_views = [] col = 0 for tower_class in towers: # tower = tower_class(self._game.grid.cell_size // 2) tower = tower_class(30) view = ShopTowerView( self._shop, tower, bg='purple', #, highlight="#4b3b4a", click_command=lambda class_=tower_class: self.select_tower( class_)) view.pack(fill=tk.X) self._tower_views.append((tower, view)) col += 1 # Task 1.5 (Play Controls): instantiate widgets here # 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("<Button-1>", self._left_click) self._view.bind("<Motion>", self._move) self._view.bind("<Leave>", self._mouse_leave) self._view.bind("<Button-3>", self._right_click) # Level self._level = MyLevel() self.select_tower(MissileTower) self._view.draw_borders(game.grid.get_border_coordinates()) # Get ready for the game self._setup_game() # Remove the relevant lines while attempting the corresponding section # Hint: Comment them out to keep for reference # Task 1.2 (Tower Placement): remove these lines # towers = [ # ([(2, 2), (3, 0), (4, 1), (4, 2), (4, 3)], SimpleTower), # ([(2, 5)], PulseTower) # ] # # for positions, tower in towers: # for position in positions: # self._game.place(position, tower_type=tower) # ## Task 1.5 (Tower Placement): remove these lines # self._game.queue_wave([], clear=True) # self._wave = 4 - 1 # first (next) wave will be wave 4 # self.next_wave() ## ## # Task 1.5 (Play Controls): remove this line # self.start() def setup_menu(self): """Sets up the menu bar with New Game, Exit and High Scores commands""" self._menubar = tk.Menu(self._master) self._filemenu = tk.Menu(self._menubar, tearoff=0) self._filemenu.add_command(label='New Game', command=self._new_game) self._filemenu.add_command(label='Exit', command=self._exit) self._filemenu.add_command(label="High Scores", command=self._display_high_scores) self._menubar.add_cascade(label='File', menu=self._filemenu) self._master.config(menu=self._menubar) 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 if paused: self.pause() else: self.start() if paused: self._pause_button.configure(text='Play') else: self._pause_button.configure(text='Pause') self._paused = paused def _setup_game(self): """ Sets up status_bar variables, pause/next wave buttons. Starts a game from wave 1. """ self._wave = 0 self._score = 0 self._coins = 80 self._lives = 20 self._won = False #status_bar display setup self._status_bar.set_wave_display(self._wave, self._level.get_max_wave()) self._status_bar.set_coin_display(self._coins) self._status_bar.set_lives_display(self._lives) self._status_bar.set_score_display(self._score) #activate the pause and next wave buttons self._next_wave_button.configure(state='active') self._pause_button.configure(state='active', text='Pause') self._game.reset() # Auto-start the first wave self.next_wave() self._toggle_paused(paused=False) # Task 1.4 (File Menu): Complete menu item handlers here (including docstrings!) # def _new_game(self): """ Called by clicking "New Game" in menubar. Starts a new game. """ # self._view.forget() # self._status_bar.forget() # self._shop.forget() # self._play_and_upgrades.forget() # self._new_game_count += 1 # self._new_game_called = 1 # self.__init__(self._master, delay=(30 + self._new_game_count*30)) self._setup_game() # def _exit(self): """Yes/No question confirming whether to close or not.""" if messagebox.askyesno("Confirm Exit", "Are you sure you want to exit?"): 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) 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 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: self._view.delete('shadow', 'range', 'path') 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() ] self._view.draw_preview(self._current_tower, legal) self._view.draw_path(path) def _mouse_leave(self, event): """Deletes the preview when cursor leaves game canvas.""" self._view.delete('shadow', 'range', 'path') def _left_click(self, event): """ Attempts to place self._current_tower on the clicked square. Parameter: event (tk.Event): Tkinter mouse event """ # retrieve position to place tower position = event.x, event.y cell_position = self._game.grid.pixel_to_cell(position) if self.is_paused(): return if cell_position in self._game.towers.keys(): self._get_upgrades(cell_position) return if self._current_tower is None: return if self._current_tower.get_value() > self._coins: return if self._game.place(cell_position, tower_type=self._current_tower.__class__): # Task 1.2 (Tower placement): Attempt to place the tower being previewed self._coins -= int(self._current_tower.base_cost) self._status_bar.set_coin_display(self._coins) for tower_view in self._tower_views: if tower_view[0].base_cost > self._coins: # pylint: disable=protected-access # This only has effects intended tower_view[1].update_price_colour('red') def _get_upgrades(self, cell_position): """ Adds checkboxes to upgrade area according to the tower clicked, if self._coins allows. Otherwise, shows message stating insufficient funds Parameters: cell_position (tuple): which cell was clicked """ for label in [ self._speed_upgrade, self._no_coins_ice, self._no_coins_speed, self._no_coins_simple, self._missile_upgrade, self._ice_upgrade, self._confirm_upgrades ]: try: label.destroy() except AttributeError: #attribute error raised if that upgrade option continue #hasn't been made previously self._called_before = 1 #checks type of tower clicked, current coins and displays options accordnly self._tower_to_upgrade = tower_clicked = self._game.towers[ cell_position] self._speed_clicked = tk.IntVar() self._ice_upgrade_clicked = tk.IntVar() self._range_upgrade_clicked = tk.IntVar() self._simple_upgrade_clicked = tk.IntVar() self._confirm_upgrades = tk.Button(self._play_and_upgrades, text='Confirm', command=self._upgrade_tower) self._confirm_upgrades.pack(side=tk.RIGHT) self._confirm_upgrades.place(x=238, y=55) if self._coins >= 100: self._speed_upgrade = tk.Checkbutton( self._play_and_upgrades, text='Speed Upgrade 100 coins', variable=self._speed_clicked, bg='white', offvalue=0, onvalue=1) self._speed_upgrade.pack(side=tk.LEFT, expand=False) else: self._no_coins_speed = tk.Label( self._play_and_upgrades, text='Not enough coins for speed upgrade') self._no_coins_speed.pack(side=tk.LEFT, expand=False) if tower_clicked.name == 'Ice Tower' and self._coins >= 250: self._ice_upgrade = tk.Checkbutton( self._play_and_upgrades, text='Ice Tower Upgrade 250 coins', variable=self._ice_upgrade_clicked, bg='white') self._ice_upgrade.pack(side=tk.LEFT, expand=False) self._ice_upgrade.place(x=0, y=70) elif tower_clicked.name == 'Ice Tower' and self._coins < 250: self._no_coins_ice = tk.Label( self._play_and_upgrades, text='Not enough coins for Ice Tower upgrade') self._no_coins_ice.pack(side=tk.LEFT, expand=False) self._no_coins_ice.place(x=0, y=70) if tower_clicked.name == 'Simple Tower' and self._coins >= 40: self._simple_upgrade = tk.Checkbutton( self._play_and_upgrades, text='Level Upgrade 40 coins', variable=self._simple_upgrade_clicked, bg='white') self._simple_upgrade.pack(side=tk.LEFT, expand=False) self._simple_upgrade.place(x=0, y=70) elif tower_clicked.name == 'Simple Tower' and self._coins < 40: self._no_coins_simple = tk.Label( self._play_and_upgrades, text='Not enough coins for Simple Tower upgrade') self._no_coins_simple.pack(side=tk.LEFT, expand=False) self._no_coins_simple.place(x=0, y=70) def _upgrade_tower(self): """ Checks which checkboxes were ticked at time of confirming. Upgrades accordinly. """ if self._speed_clicked.get() == 1: if self._tower_to_upgrade.cool_down_steps > 0: self._tower_to_upgrade.cool_down_steps -= 1 self._tower_to_upgrade.cool_down = Countdown( self._tower_to_upgrade.cool_down_steps) #speeds up by 10% self._speed_clicked = 0 self._coins -= 100 if self._range_upgrade_clicked.get() == 1: if self._tower_to_upgrade.range.outer_radius != 5: self._tower_to_upgrade.range.outer_radius = 5 self._coins -= 25 self._range_upgrade_clicked = 0 if self._ice_upgrade_clicked.get() == 1: self._tower_to_upgrade.upgraded = True self._tower_to_upgrade.level += 1 self._tower_to_upgrade.colour = 'blue4' self._coins -= 250 if self._simple_upgrade_clicked.get() == 1: self._tower_to_upgrade.level += 1 self._coins -= 40 self._simple_upgrade.destroy() for label in [ self._speed_upgrade, self._no_coins_ice, self._no_coins_speed, self._no_coins_simple, self._simple_upgrade, self._missile_upgrade, self._ice_upgrade, self._confirm_upgrades ]: try: label.destroy() self._simple_upgrade.destroy() except AttributeError: pass def _right_click(self, event): """ Attempts to sell tower in clicked grid Parameters: event (tk.Event): Tkinter mouse event """ position = event.x, event.y position = self._game.grid.pixel_to_cell(position) if position in self._game.towers.keys(): # Add 80% of tower's base_cost to player's wallet self._coins += int(0.8 * self._game.towers[position].base_cost) self._status_bar.set_coin_display(self._coins) del self._game.towers[position] for tower_view in self._tower_views: if tower_view[0].base_cost < self._coins: # pylint: disable=protected-access # This only has effects intended tower_view[1].update_price_colour('white') def next_wave(self): """Sends the next wave of enemies against the player""" #checks whether it should do anything if self._wave == self._level.get_max_wave(): self._next_wave_button.configure(state='disabled') return self._wave += 1 self._pause_button.configure(text='Pause') #checks to disable button but still continues with wave. if self._wave == self._level.get_max_wave(): self._next_wave_button.configure(state='disabled') # # Task 1.3 (Status Bar): Update the current wave display here self._status_bar.set_wave_display(self._wave, self._level.get_max_wave()) # 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) self.start() 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._status_bar.set_coin_display(self._coins) self._status_bar.set_score_display(self._score) for tower_view in self._tower_views: if tower_view[0].base_cost <= self._coins: # pylint: disable=protected-access # This only has effects intended tower_view[1].update_price_colour('white') 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._status_bar.set_lives_display(self._lives) # 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) # Task 1.5 (Play Controls): remove this line #self.next_wave() def _handle_game_over(self, won=False): """Handles game over Parameter: won (bool): If True, signals the game was won (otherwise lost) """ if self._high_scores.does_score_qualify(self._score): high_score_name = simpledialog.askstring("Input", "Your score qualified for "\ "the high scores, what is"\ " your name?") self._high_scores.add_entry(high_score_name, self._score) if won: win_or_lose = 'finished' else: win_or_lose = 'didn\'t finish' messagebox.showinfo( 'Game Over', 'You %s with a score of %s' % (win_or_lose, self._score)) self._high_scores.save() self.stop() def _display_high_scores(self): """Displays the current high scores""" display = '' entries = self._high_scores.get_entries() for i in range(len(self._high_scores.get_entries())): try: display += str(entries[i]['name'] + ' - Score: ' +\ str(entries[i]['score']) + entries[i]['data'] + '\n') except TypeError: if entries[i][ 'name']: #accounts for people entering nothing for their name display += str(str(entries[i]['name']) + ' - Score: ' +\ str(entries[i]['score']) + '\n') else: display += str('*NO NAME ENTERED*' + ' - Score: ' +\ str(entries[i]['score']) + '\n') messagebox.showinfo("High Scores", str(display))
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 __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 """ #Setting title of the Window master.title("Towers") self._master = master super().__init__(master, delay=delay) 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) # Task 4.1 Postgraduate advanced feature. Initialise pygame and sounds pygame.init() mixer.init() self.build_sound = pygame.mixer.Sound("sound/build.wav") self.increase_coin = pygame.mixer.Sound("sound/coin.wav") self.decrease_life = pygame.mixer.Sound("sound/heart.wav") self.wave_m = pygame.mixer.Sound("sound/next_wave.wav") self.pause_play = pygame.mixer.Sound("sound/pause_play.wav") self.quit_m = pygame.mixer.Sound("sound/quit.wav") # Task 4.1 Postgraduate advanced feature. Play music pygame.mixer.music.load("sound/game_music.mp3") pygame.mixer.music.play(-1) # Task 1.3 (Status Bar): instantiate status bar # Put the status bar in a frame self._right_frame = tk.Frame(master, bg="white") self._right_frame.pack(fill=tk.BOTH, expand=True) self._status_bar = StatusBar(self._right_frame) self._status_bar.pack(side=tk.TOP) # Task 2.4 (High Score): instantiate highscore self._high_score = HighScoreManager() # Task 1.5 (Play Controls): instantiate widgets here # Create a frame for widgets self._bottom_frame = tk.Frame(master, bg=BACKGROUND_COLOUR) self._bottom_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True) #Create button for next wave self._next_wave_btn = tk.Button(self._bottom_frame, text="Next Wave", state=tk.NORMAL, command=self.next_wave) self._next_wave_btn.pack(side=tk.LEFT, ipadx=10, padx=10, ipady=10, pady=10) #Create button for play/pause self._play_btn = tk.Button(self._bottom_frame, text="Pause", state=tk.NORMAL, command=self._toggle_paused) self._play_btn.pack(side=tk.LEFT, ipadx=10, padx=10, ipady=10) # 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 # On leaving canvas hide mouse # On left click place tower # On right click sell tower self._view.bind('<Motion>', self._move) self._view.bind('<Leave>', self._mouse_leave) self._view.bind('<Button-1>', self._left_click) self._view.bind('<Button-3>', self._right_click) # Level self._level = MyLevel() #Choose tower on initialisation self.select_tower(SimpleTower) #Draw borders view.draw_borders(game.grid.get_border_coordinates()) # Get ready for the game self._setup_game() # Task 2.3 (Shop) Creating Shop #Store towers present in the game for Shop towers = [ SimpleTower, EnergyTower, PulseTower, MissileTower, SlowTower ] #Create frmae for Shop window self._right_middle_frame = tk.Frame(master, bg=BACKGROUND_COLOUR) self._right_middle_frame.pack(fill=tk.BOTH, expand=True) #Put shop in the frame created shop = tk.Frame(self._right_middle_frame, bg=BACKGROUND_COLOUR) shop.pack(side=tk.BOTTOM, 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) view = ShopTowerView(shop, tower, bg=BACKGROUND_COLOUR, click_command=lambda class_=tower_class: self. select_tower(class_)) view.pack(fill=tk.X) self._tower_views.append( (tower, view) ) # Can use to check if tower is affordable when refreshing view # Task 1.4 On pressing delete(X) button in window prompt user before exit def callback(): """Prompt user on pressing delete(X)""" if messagebox.askokcancel("Quit", "Do you really wish to quit?"): self._high_score.save() pygame.mixer.Sound.play(self.quit_m) master.destroy() master.protocol("WM_DELETE_WINDOW", callback)
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 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 """ #Setting title of the Window master.title("Towers") self._master = master super().__init__(master, delay=delay) 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) # Task 4.1 Postgraduate advanced feature. Initialise pygame and sounds pygame.init() mixer.init() self.build_sound = pygame.mixer.Sound("sound/build.wav") self.increase_coin = pygame.mixer.Sound("sound/coin.wav") self.decrease_life = pygame.mixer.Sound("sound/heart.wav") self.wave_m = pygame.mixer.Sound("sound/next_wave.wav") self.pause_play = pygame.mixer.Sound("sound/pause_play.wav") self.quit_m = pygame.mixer.Sound("sound/quit.wav") # Task 4.1 Postgraduate advanced feature. Play music pygame.mixer.music.load("sound/game_music.mp3") pygame.mixer.music.play(-1) # Task 1.3 (Status Bar): instantiate status bar # Put the status bar in a frame self._right_frame = tk.Frame(master, bg="white") self._right_frame.pack(fill=tk.BOTH, expand=True) self._status_bar = StatusBar(self._right_frame) self._status_bar.pack(side=tk.TOP) # Task 2.4 (High Score): instantiate highscore self._high_score = HighScoreManager() # Task 1.5 (Play Controls): instantiate widgets here # Create a frame for widgets self._bottom_frame = tk.Frame(master, bg=BACKGROUND_COLOUR) self._bottom_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True) #Create button for next wave self._next_wave_btn = tk.Button(self._bottom_frame, text="Next Wave", state=tk.NORMAL, command=self.next_wave) self._next_wave_btn.pack(side=tk.LEFT, ipadx=10, padx=10, ipady=10, pady=10) #Create button for play/pause self._play_btn = tk.Button(self._bottom_frame, text="Pause", state=tk.NORMAL, command=self._toggle_paused) self._play_btn.pack(side=tk.LEFT, ipadx=10, padx=10, ipady=10) # 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 # On leaving canvas hide mouse # On left click place tower # On right click sell tower self._view.bind('<Motion>', self._move) self._view.bind('<Leave>', self._mouse_leave) self._view.bind('<Button-1>', self._left_click) self._view.bind('<Button-3>', self._right_click) # Level self._level = MyLevel() #Choose tower on initialisation self.select_tower(SimpleTower) #Draw borders view.draw_borders(game.grid.get_border_coordinates()) # Get ready for the game self._setup_game() # Task 2.3 (Shop) Creating Shop #Store towers present in the game for Shop towers = [ SimpleTower, EnergyTower, PulseTower, MissileTower, SlowTower ] #Create frmae for Shop window self._right_middle_frame = tk.Frame(master, bg=BACKGROUND_COLOUR) self._right_middle_frame.pack(fill=tk.BOTH, expand=True) #Put shop in the frame created shop = tk.Frame(self._right_middle_frame, bg=BACKGROUND_COLOUR) shop.pack(side=tk.BOTTOM, 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) view = ShopTowerView(shop, tower, bg=BACKGROUND_COLOUR, click_command=lambda class_=tower_class: self. select_tower(class_)) view.pack(fill=tk.X) self._tower_views.append( (tower, view) ) # Can use to check if tower is affordable when refreshing view # Task 1.4 On pressing delete(X) button in window prompt user before exit def callback(): """Prompt user on pressing delete(X)""" if messagebox.askokcancel("Quit", "Do you really wish to quit?"): self._high_score.save() pygame.mixer.Sound.play(self.quit_m) master.destroy() master.protocol("WM_DELETE_WINDOW", callback) def setup_menu(self): """Sets up the application menu""" # Task 1.4: construct file menu here # ... menubar = tk.Menu(self._master) self._master.config(menu=menubar) filemenu = tk.Menu(menubar) menubar.add_cascade(label="File", menu=filemenu) filemenu.add_command(label="New Game", command=self._new_game) filemenu.add_command(label="High Scores", command=self._high_scores) 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 # On pause stop music and change label text if paused: self.pause() pygame.mixer.Sound.play(self.pause_play) self._play_btn.config(text="Play") pygame.mixer.music.pause() else: # On start/unpause start music and change label text self.start() pygame.mixer.Sound.play(self.pause_play) self._play_btn.config(text="Pause") pygame.mixer.music.unpause() self._paused = paused def _setup_game(self): """Sets up the game""" self._wave = 0 self._score = 0 self._coins = 100 self._lives = 20 self._won = False # Task 1.3 (Status Bar): Update status here #Set values displayed in status bar self._status_bar.set_coins(self._coins) self._status_bar.set_wave(self._wave) self._status_bar.set_lives(self._lives) self._status_bar.set_score(self._score) # Task 1.5 (Play Controls): Re-enable the play controls here (if they were ever disabled) self._next_wave_btn.config(state=tk.NORMAL) self._play_btn.config(state=tk.NORMAL) self._game.reset() # Auto-start the first wave self.next_wave() self._toggle_paused(paused=True) # Task 1.4 (File Menu): Complete menu item handlers here (including docstrings!) # def _new_game(self): """Create a new message box file to check if player is sure and starts a new game""" reply = messagebox.askquestion( type=messagebox.YESNO, title="Exit Towers", message="Are you sure you want to start a new game?") if reply == messagebox.YES: self._game.reset() self._setup_game() self.refresh_view() self._view.draw_enemies(self._game.enemies) def _high_scores(self): """Enables user interaction when clicking on HighScores in File Menu""" #Creates a frame for highscore high_score_window = tk.Toplevel() high_score_window.title("High Scores") #Gets values stored in the file data_input = self._high_score.get_entries() Output = "Rank | Name | Score | Comment\n-----------------------------------\n" i = 1 #Load values and shows on screen for dictionary in data_input: dictionary_values = str(dictionary.values()) name, score, comment = dictionary_values.split(sep=",") Output = Output + str(i) + " |" + name[ 14:-1] + " |" + score + " |" + comment[:-2] + "\n" i += 1 #Button to returen to game msg = tk.Message(high_score_window, text=Output) msg.pack() button = tk.Button(high_score_window, text="Return to Game", command=high_score_window.destroy) button.pack() def _exit(self): """ Opens message box on player trying to exit""" reply = messagebox.askquestion(type=messagebox.YESNO, title="Quit Towers", message="Do you really wish to quit?") if reply == messagebox.YES: self._high_score.save() pygame.mixer.Sound.play(self.quit_m) self._master.destroy() def refresh_view(self): """Refreshes the game view""" #Draws towers and obstacles 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) #Sets available flag for tower_view in self._tower_views: if tower_view[0].get_value() > self._coins: tower_view[1].set_available(False) else: tower_view[1].set_available(True) 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!) # Event handlers: _move, _mouse_leave, _left_click 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() ] # Task 1.2 (Tower placement): Draw the tower preview here # Draw tower path and preview self._view.draw_preview(self._current_tower, legal) self._view.draw_path(path) 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): Delete the preview # Hint: Relevant canvas items are tagged with: 'path', 'range', 'shadow' # See tk.Canvas.delete (delete all with tag) #Deletes mouse preview self._view.delete("path") self._view.delete("range") self._view.delete("shadow") def _left_click(self, event): """ Handles the mouse left clicking on the game view canvas Parameter: event (tk.Event): Tkinter mouse event """ # retrieve position to place tower if self._current_tower is None: return #If tower value is greater than current coins doesn't do anything if self._current_tower.get_value() > self._coins: return #Takes current position position = event.x, event.y cell_position = self._game.grid.pixel_to_cell(position) if self._game.place(cell_position, tower_type=self._current_tower.__class__): #Task 2.1 (Buy and sell Towers) : On placing a tower deduct the value of tower from players wallet self._coins -= self._current_tower.get_value() #On placing tower plays sound pygame.mixer.Sound.play(self.build_sound) def _right_click(self, event): """ Handles the mouse right clicking on the game view canvas Parameter: event (tk.Event): Tkinter mouse event """ # retrieve position to sell tower position = event.x, event.y cell_position = self._game.grid.pixel_to_cell(position) self._game.remove(cell_position) """Task 2.1 (Buy and sell Towers) : player can sell a tower by right clicking on it. The tower should be removed from the grid and 80% of its value should be put back into the player's wallet.""" self._coins += (self._current_tower.get_value() * 0.8) def next_wave(self): """Sends the next wave of enemies against the player""" # If is the last wave do nothing if self._wave == self._level.get_max_wave(): return #else increase wave value self._wave += 1 #Play sound on next wave pygame.mixer.Sound.play(self.wave_m) # Task 1.3 (Status Bar): Update the current wave display here # Update wave vlaue in Status Bar self._status_bar.set_wave(self._wave) # Task 1.5 (Play Controls): Disable the add wave button here (if this is the last wave) # Disable add wave button if self._wave == self._level.get_max_wave(): self._next_wave_btn.config(state=tk.DISABLED) self._play_btn.config(state=tk.DISABLED) # Generate wave and enemy wave = self._level.get_wave(self._wave) for step, enemy in wave: enemy.set_cell_size(self._game.grid.cell_size) # Queue wave 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 pygame.mixer.Sound.play(self.increase_coin) self._score += int(enemy.points * bonus) # Task 1.3 (Status Bar): Update coins & score displays here # Update coins and score in Status bar self._status_bar.set_coins(self._coins) self._status_bar.set_score(self._score) 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) #Play sound on losing life pygame.mixer.Sound.play(self.decrease_life) if self._lives < 0: self._lives = 0 # Task 1.3 (Status Bar): Update lives display here # Update lives value in Status BAr self._status_bar.set_lives(self._lives) # 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() self._next_wave_btn.config(state=tk.DISABLED) self._play_btn.config(state=tk.DISABLED) pygame.mixer.music.pause() # Task 1.4 (Dialogs): Show game over dialog here # Task 2.4 (High Scores) On users running out of lives, check and store score #Display message on winning or losing if self._won: messagebox.showinfo("Game Over", "Congratulations!!! You Won") else: messagebox.showinfo("Game Over", "Better luck next time. You Lost") #Store high score if self._high_score.does_score_qualify(self._score): #Create window to take input high_score_entry_window = tk.Toplevel() high_score_entry_window.title("High Score Entry") label = tk.Label(high_score_entry_window, text='Enter Name: ') label.pack(side=tk.LEFT) entry = tk.Entry(high_score_entry_window, width=20) entry.pack(side=tk.LEFT) #Function to add score & close window def add(): name = entry.get() self._high_score.add_entry(name, self._score) pygame.mixer.Sound.play(self.quit_m) high_score_entry_window.destroy() calc = tk.Button(high_score_entry_window, text="Enter", command=add) calc.pack(side=tk.LEFT)
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("tkDefend") self._game = game = TowerGame() self._highscores = {} self.setup_menu() self._master.configure(menu=self._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='#000033') #was antique white view.pack(side=tk.LEFT, expand=True) #have a menu frame on the right self._right_frame = tk.Frame(self._master) self._right_frame.pack(side=tk.RIGHT, expand=True, fill=tk.Y) #Task 1.3 (Status Bar): instantiate status bar self._status_bar = StatusBar(self._right_frame) #shop goes between status bar and control frame shop = tk.Frame(self._right_frame, bg="#ff6600") shop.pack(side=tk.TOP, expand = True, anchor=tk.N, fill=tk.BOTH) self._towers = towers = [ SimpleTower, SlowTower, InfernoTower, LaserTower, MissileTower, GunTower, ] self._towers.sort(key=lambda tower_class:tower_class.base_cost) #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) #lambda class_=tower_class: self.select_tower(class_) view = ShopTowerView(shop, tower, lambda class_=tower_class: self.select_tower(class_), bg="#ff6600", highlightbackground="#4b3b4a",bd=0,highlightthickness=0) view.pack(fill=tk.X) #Used to check if tower is affordable when refreshing view self._tower_views.append((tower, view)) #Task 1.5 (Play Controls): instantiate widgets here self._control_frame = tk.Frame(self._right_frame) self._control_frame.pack(expand=True) self._wave_button = tk.Button(self._control_frame, text="next wave", command=self.next_wave) self._wave_button.pack(side=tk.LEFT) self._play_button_text = tk.StringVar() self._play_button_text.set("play") self._play_button = tk.Button(self._control_frame, textvariable=self._play_button_text, command=self._toggle_paused) self._play_button.pack(side=tk.RIGHT) #6.3 initiate upgrade dictionary to store upgrade controls for each tower self._upgrade_controls = {} #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 #Binds left click, mouse motion and mouse leave self._view.bind("<Button-1>", self._left_click) self._view.bind("<Motion>", self._move) #self._view.bind("<ButtonRelease-1>", self._mouse_leave) self._view.bind("<Leave>",self._mouse_leave) self._view.bind("<Button-2>", self._right_click) #high scores self._high_score_manager = HighScoreManager() #handling close window import sys if len(sys.argv) == 1: self._master.protocol("WM_DELETE_WINDOW", self._exit) #catching keyboard event Destroy #self._master.bind("<Destroy>", self._exit) #Level self._level = MyLevel() #self.select_tower(SimpleTower) self.select_tower(SimpleTower) self._view.draw_borders(game.grid.get_border_coordinates(), "#66ff66") #Get ready for the game self._setup_game() #laser count self._laser_count = 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 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("tkDefend") self._game = game = TowerGame() self._highscores = {} self.setup_menu() self._master.configure(menu=self._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='#000033') #was antique white view.pack(side=tk.LEFT, expand=True) #have a menu frame on the right self._right_frame = tk.Frame(self._master) self._right_frame.pack(side=tk.RIGHT, expand=True, fill=tk.Y) #Task 1.3 (Status Bar): instantiate status bar self._status_bar = StatusBar(self._right_frame) #shop goes between status bar and control frame shop = tk.Frame(self._right_frame, bg="#ff6600") shop.pack(side=tk.TOP, expand = True, anchor=tk.N, fill=tk.BOTH) self._towers = towers = [ SimpleTower, SlowTower, InfernoTower, LaserTower, MissileTower, GunTower, ] self._towers.sort(key=lambda tower_class:tower_class.base_cost) #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) #lambda class_=tower_class: self.select_tower(class_) view = ShopTowerView(shop, tower, lambda class_=tower_class: self.select_tower(class_), bg="#ff6600", highlightbackground="#4b3b4a",bd=0,highlightthickness=0) view.pack(fill=tk.X) #Used to check if tower is affordable when refreshing view self._tower_views.append((tower, view)) #Task 1.5 (Play Controls): instantiate widgets here self._control_frame = tk.Frame(self._right_frame) self._control_frame.pack(expand=True) self._wave_button = tk.Button(self._control_frame, text="next wave", command=self.next_wave) self._wave_button.pack(side=tk.LEFT) self._play_button_text = tk.StringVar() self._play_button_text.set("play") self._play_button = tk.Button(self._control_frame, textvariable=self._play_button_text, command=self._toggle_paused) self._play_button.pack(side=tk.RIGHT) #6.3 initiate upgrade dictionary to store upgrade controls for each tower self._upgrade_controls = {} #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 #Binds left click, mouse motion and mouse leave self._view.bind("<Button-1>", self._left_click) self._view.bind("<Motion>", self._move) #self._view.bind("<ButtonRelease-1>", self._mouse_leave) self._view.bind("<Leave>",self._mouse_leave) self._view.bind("<Button-2>", self._right_click) #high scores self._high_score_manager = HighScoreManager() #handling close window import sys if len(sys.argv) == 1: self._master.protocol("WM_DELETE_WINDOW", self._exit) #catching keyboard event Destroy #self._master.bind("<Destroy>", self._exit) #Level self._level = MyLevel() #self.select_tower(SimpleTower) self.select_tower(SimpleTower) self._view.draw_borders(game.grid.get_border_coordinates(), "#66ff66") #Get ready for the game self._setup_game() #laser count self._laser_count = 0 def setup_menu(self): ''' Construct a file menu ''' #Task 1.4: construct file menu here self._menu = tk.Menu(self._master) self._filemenu = tk.Menu(self._menu) self._filemenu.add_command(label="New Game", command=self._new_game) self._filemenu.add_command(label="High Scores", command=self._handle_highscores) self._filemenu.add_command(label="Exit", command=self._exit) self._menu.add_cascade(label="File", menu=self._filemenu) def _toggle_paused(self, paused=None): """Toggles or sets the paused state Parameters: paused (bool): Toggles/pauses/unpauses if None/True/False, respectively """ #automatically start the first wave if self._wave == 0: self.next_wave() if paused is None: paused = not self._paused #Task 1.5 (Play Controls): Reconfigure the pause button here if paused: self.pause() self._play_button_text.set("play") else: self.start() self._play_button_text.set("pause") self._paused = paused def _setup_game(self): '''setup the game''' self._wave = 0 self._score = 0 self._coins = 100 self._lives = 30 self._won = False #Task 1.3 (Status Bar): Update status here self._status_bar.set_wave(self._wave) self._status_bar.set_score(self._score) self._status_bar.set_coins(self._coins) self._status_bar.set_lives(self._lives) #Task 1.5 (Play Controls): Re-enable the play controls here (if they were ever disabled) if self._wave_button.cget('state') != 'normal' or self._play_button.cget('state') != 'normal': self._wave_button.config(state=tk.NORMAL) self._play_button.config(state=tk.NORMAL) #set initial availability for tower views for tower, view in self._tower_views: if self._coins < tower.get_value(): view.set_available(False) else: view.set_available(True) self._game.reset() self._toggle_paused() #to store and retrive boss images self._view._boss_images = {} self._view.laser_counts = {} self._view.total_laser_count = 0 #Task 1.4 (File Menu): Complete menu item handlers here (including docstrings!) def _new_game(self): ''' restarts the game ''' #clears the enemies self._view.delete("enemy","tower","obstacle","laser") self._game.queue_wave([], True) #clear all enemies self._setup_game() def _exit(self): ''' exits the application ''' confirm_window = tk.Toplevel(self._master) exit_label = tk.Label(confirm_window, text="Are you sure you want to exit?") exit_label.pack(pady=10) yes_button = tk.Button(confirm_window, text="Yes", command=self._master.destroy) yes_button.pack(side=tk.LEFT, padx=20) no_button = tk.Button(confirm_window, text="No", command=confirm_window.destroy) no_button.pack(side=tk.LEFT, padx=20) #quitting with keyboard def handle_return(event): '''handle quitting with keyboard''' self._master.destroy() confirm_window.bind("<Return>", handle_return) def _handle_highscores(self): '''displays highscores''' label_txt = "High scores:\n" high_score_entries = self._high_score_manager.get_entries() for entry in high_score_entries: label_txt += "%s: %s\n" %(entry['name'], entry['score']) highscore_window = tk.Toplevel(self._master) highscore_window.title("high scores") highscore_window.geometry('200x200') label = tk.Label(highscore_window, text=label_txt, padx=20) label.pack() 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) 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._view.delete('laser') self.refresh_view() return not self._won #Task 1.2 (Tower Placement): Complete event handlers here (including docstrings!) #Event handlers: _move, _mouse_leave, _left_click 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()] #Task 1.2 (Tower placement): Draw the tower preview here self._view.draw_preview(self._current_tower, legal) self._view.draw_path(path) 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): Delete the preview #Hint: Relevant canvas items are tagged with: 'path', 'range', 'shadow' # See tk.Canvas.delete (delete all with tag) self._view.delete("shadow", "range", "path") def _left_click(self, event): """ Handles the mouse left clicking in the game view canvas Parameter: event (tk.Event): Tkinter mouse event """ #retrieve position to place tower if self._current_tower is None: return position = event.x, event.y cell_position = self._game.grid.pixel_to_cell(position) #if the event position already has a tower, show the upgrades for it if cell_position in self._game.towers: tower = self._game.towers[cell_position] #hide all upgrade_controls for t in self._upgrade_controls: self._upgrade_controls[t].pack_forget() #initiate the upgrade control if it doesn't already exist if tower not in self._upgrade_controls: upgrade_control = UpgradeControl(self._right_frame, tower, self) self._upgrade_controls[tower] = upgrade_control upgrade_control.pack(expand=True) else: #pack the one with the tower tower = self._game.towers[cell_position] upgrade_control = self._upgrade_controls[tower] upgrade_control.pack(expand=True) upgrade_control.check_status() #Task 1.2 (Tower placement): Attempt to place the tower being previewed legal, grid_path = self._game.attempt_placement(position) if legal and (self._current_tower.get_value() <= self._coins): self._coins -= self._current_tower.get_value() self._status_bar.set_coins(self._coins) #refresh view upon placing a tower if self._game.place(cell_position, tower_type=self._current_tower.__class__): #delete preview after placing self._view.delete("shadow", "range", "path") for tower_type, shop_tower_view in self._tower_views: if tower_type.base_cost > self._coins: shop_tower_view.set_available(False) self.refresh_view() self._step() def _right_click(self, event): """ Handles the mouse right clicking in the game view canvas. The tower in that cell position will be removed and 80% of its value will be returned to the player. Parameter: event (tk.Event): Tkinter mouse event """ position = event.x, event.y cell_position = self._game.grid.pixel_to_cell(position) removed_tower = self._game.remove(cell_position) self._coins += removed_tower.get_value() * 0.8 #updates coins string var to display coins self._status_bar.set_coins(self._coins) #update availability for tower views for tower, view in self._tower_views: if self._coins < tower.get_value(): view.set_available(False) else: view.set_available(True) 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._status_bar.set_wave(self._wave) #Task 1.5 (Play Controls): Disable the add wave button here (if this is the last wave) if self._wave == 20: self._wave_button.config(state=tk.DISABLED) #Generate wave and enqueue wave = self._level.get_wave(self._wave, self._game) 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 """ for tower_type, shop_tower_view in self._tower_views: shop_tower_view.set_selected(False) if tower_type.__class__ == tower: shop_tower_view.set_selected(True) 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 """ # for enemy in enemies: # if type(enemy) == SuperRichardEnemy: # time.sleep(0.2) 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._status_bar.set_coins(self._coins) self._status_bar.set_score(self._score) for tower, view in self._tower_views: if self._coins < tower.get_value(): view.set_available(False) else: view.set_available(True) 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 """ for enemy in enemies: self._lives -= enemy.live_damage if self._lives < 0: self._lives = 0 #Task 1.3 (Status Bar): Update lives display here self._status_bar.set_lives(self._lives) #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 record_high_score_from_game_over(self, entry_box): '''record the highscore of that player and add it to the highscore list, then destroy the game over dialog box and show the highscore box. parameter: player (string): name of the player highscore (string): the highscore to record entry_box (tk.Entry): the entry box to get the player's name and destroy its parent from. ''' player = entry_box.get() self._high_score_manager.add_entry(player, self._score, '') self._high_score_manager.save() entry_box.master.destroy() #show the high scores self._handle_highscores() 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 dialog_box = tk.Toplevel(self._master) dialog_box.title("Game Over") if won: message = "You won! Enter your name:" else: message = "You lost! Enter your name:" label = tk.Label(dialog_box, text=message) label.pack(padx=50,pady=10) highscore_prompt = tk.Entry(dialog_box) highscore_prompt.pack(expand=True, pady=10) ok_button = tk.Button(dialog_box, text="ok", command=lambda entry_box=highscore_prompt: self.record_high_score_from_game_over(entry_box)) ok_button.pack(pady=10) #disable buttons if game over self._wave_button.config(state=tk.DISABLED) self._play_button.config(state=tk.DISABLED)