def __init__(self, master): """Construct a new game of a MarioApp game. Parameters: master (tk.Tk): tkinter root widget """ #level readed self._master = master self._master.title('Mario') self.down_pressed = False self._master.update() file_path = filedialog.askopenfilename() # Openning a view to let the user choose the configuration file filename, file_extension = os.path.splitext(file_path) # Getting the extension of the selected file if file_extension != ".txt" : # Show an error message box when the extension is Wrong. The extension must be .txt messagebox.showerror("Error", "File extension should be .txt") exit() self.levels = [] self.load_configuration(file_path) # to load configurations from the configuration file world_builder = WorldBuilder(BLOCK_SIZE, gravity=(0, self.gravity), fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._world = load_world(world_builder, self.level) # Create a menu bar on the top of the game and association a function to each button on the menu with command parameter menubar = tk.Menu(self._master) menubar.add_command(label = "Load Level", command = self.showDialogInput) menubar.add_command(label = "Reset Level", command = self.resetLevel) menubar.add_command(label = "High scores", command = self.displayHighScores) menubar.add_command(label = "Exit", command = self.exitGame) self._master.config(menu = menubar) self._player = Player(name = self.character, max_health=self.max_health) self._player.invincible = False self._world.add_player(self._player, self.starting_x, self.starting_y, mass = self.mass) self._setup_collision_handlers() self._renderer = MarioViewRenderer(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) size = tuple(map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(master, size, self._renderer) self._view.pack() self.bind() # Wait for window to update before continuing master.update_idletasks() self.statusBar = StatusBar(self._master) self.step() self._master.mainloop()
def __init__(self, master: tk.Tk): """Construct a new game of a MarioApp game. Parameters: master (tk.Tk): tkinter root widget """ self._master = master # default configuration setting self._level = "level1.txt" self._gravity = (0, 300) self._max_health = 5 self._mass = 100 self._x = BLOCK_SIZE self._y = BLOCK_SIZE self._max_velocity = 500 self._config = {} self._master.update_idletasks() self.load_config() world_builder = WorldBuilder(BLOCK_SIZE, self._gravity, fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._player = Player(max_health=self._max_health) self._player.set_jumping(True) self._player.set_shoot(False) self.reset_world(self._level) self._renderer = MarioViewRenderer(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) size = tuple( map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(master, size, self._renderer) self._view.pack() self.bind() self.menu_bar() # create the character status bar self.status_bar = Status(master) self.status_bar.pack() # Wait for window to update before continuing master.update_idletasks() self.step()
def play(cfg: Config): pg.init() game = Game(**cfg.get_game_config()) controller = GameController(game) view = GameView(game, **cfg.get_view_config()) clock = pg.time.Clock() while game.is_running: clock.tick(cfg.fps) controller.handle_user_input() if not controller.pause and game.is_running: game.run() view.update() pg.quit()
def _start_game(self): path = tk.simpledialog.askstring("Mario", "Configuration file:") self._game = loader.load_level(path) if self._game == None: tk.messagebox.showerror("Error", "Configuration file wrong.") self._master.destroy() # World builder world_builder = WorldBuilder( BLOCK_SIZE, gravity=(0, int(self._game["==World=="]["gravity"])), fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._current_level = self._game["==World=="]['start'] self._world = load_world(self._builder, self._current_level) # Set tunnel/goal Flagpole._next_level = self._game[("==" + self._current_level + "==")]["goal"] Tunnel._next_level = self._game[("==" + self._current_level + "==")].get("tunnel", None) self._player = Player( max_health=int(self._game["==Player=="]["health"])) print(self._player.get_shape()) self._world.add_player(self._player, int(self._game["==Player=="]["x"]), int(self._game["==Player=="]["y"]), int(self._game["==Player=="]["mass"])) self._max_speed = int(self._game["==Player=="]["max_velocity"]) self._setup_collision_handlers() self._renderer = AnimatedMarioViewRenderer(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) # Into Animated size = tuple( map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(self._master, size, self._renderer) self._view.pack() self._status_view = StatusView(self._master, int(self._game["==Player=="]['health']), 0) self.bind()
def __init__(self, master: tk.Tk) : """Construct a new game of a MarioApp game. Parameters: master (tk.Tk): tkinter root widget """ self._master = master config_name = askopenfilename(filetypes=( ("Text file", "*.txt"),("HTML files", "*.html;*.htm"))) self.load_config(config_name) self._gravity self._level_name self._healthdeterminant world_builder = WorldBuilder(BLOCK_SIZE, (0,300), fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._player = Player(max_health= self._health) self._powerup = PowerUp() self.reset_world(self._level_name) #self._renderer = MarioViewRenderer(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) self._sprite_renderer = SpriteSheetView(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) size = tuple(map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(master, size, self._sprite_renderer) self._view.pack() self.bind() # Wait for window to update before continuing master.update_idletasks() self._status_view = StatusView(master, self._health) self._status_view.pack() self._status_view.update_score(self._player.get_score()) self._status_view.update_health(self._player.get_health()) self._setup_collision_handlers() master.update_idletasks() self._start_time = 0 self.step() self.menu = MenuBar(master, [("File", {"Reset" : self._reset, "Load Level" : self._import_new_level, "Exit" : self._close, "High Score": self._status_view.high})]) self._collide = False
def reset_world(self, new_level=None, full=False): if new_level == None: new_level = self._current_level else: self._current_level = new_level self._scores = HighScores() world_builder = WorldBuilder(BLOCK_SIZE, gravity=(0, 500), fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._world = load_world(self._builder, new_level) Flagpole._next_level = self._game[("==" + self._current_level + "==")]["goal"] Tunnel._next_level = self._game[("==" + self._current_level + "==")].get("tunnel", None) if full: # full reset player stats self._player = Player( max_health=int(self._game["==Player=="]["health"])) self._world.add_player(self._player, int(self._game["==Player=="]["x"]), int(self._game["==Player=="]["y"]), int(self._game["==Player=="]["mass"])) self._setup_collision_handlers() self._view.pack_forget() size = tuple( map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(self._master, size, self._renderer) self._view.pack() self._status_view.unpack() self._status_view = StatusView(self._master, int(self._game["==Player=="]['health']), self._player._score)
def __init__(self, master: tk.Tk): """Construct a new game of a MarioApp game. Parameters: master (tk.Tk): tkinter root widget """ self._master = master self._master.title("Mario bird") world_builder = WorldBuilder(BLOCK_SIZE, gravity=(0, 300), fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._player = Player(max_health=5) self.reset_world('level1.txt') self._level_holder = 'level1.txt' self._renderer = MarioViewRenderer(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) size = tuple(map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(master, size, self._renderer) self._view.pack() self.bind() self.menubar() #Stats controller for changing the status bar. #used in the function 'step' self._health = self._player.get_health() self._score = self._player.get_score() self._status_bar = tk.Frame(self._master) self.status_bar() # Wait for window to update before continuing master.update_idletasks() self.step() self._death_action = False
class MarioApp: """High-level app class for Mario, a 2d platformer""" def setWorldProperties(self, propertie, value): if propertie == "gravity": self.gravity = int(value) elif propertie == "start": self.level = value else: self.alertFile() def setPlayerProperties(self, propertie, value): if propertie == "character": self.character = value elif propertie == "x": self.starting_x = int(value) elif propertie == "y": self.starting_y = int(value) elif propertie == "mass": self.mass = int(value) elif propertie == "health": self.max_health = int(value) elif propertie == "max_velocity": self.max_velocity = int(value) else: self.alertFile() def setLevelProperties(self, currentLevel, propertie, value): for level in self.levels: #iterate over the levels to get the level having the name currentName if level.name == currentLevel: theLevel = level if propertie == "tunnel": theLevel.tunnel = value elif propertie == "goal": theLevel.goal = value else: self.alertFile() def alertFile(self): #the alert is shown when there is wrong properties on the configuration file or on missing properties or if the file is emply messagebox.showerror("Warning", "Wrong configuration file") exit() def __init__(self, master): """Construct a new game of a MarioApp game. Parameters: master (tk.Tk): tkinter root widget """ #level readed self._master = master self._master.title('Mario') self.down_pressed = False self._master.update() file_path = filedialog.askopenfilename() # Openning a view to let the user choose the configuration file filename, file_extension = os.path.splitext(file_path) # Getting the extension of the selected file if file_extension != ".txt" : # Show an error message box when the extension is Wrong. The extension must be .txt messagebox.showerror("Error", "File extension should be .txt") exit() self.levels = [] self.load_configuration(file_path) # to load configurations from the configuration file world_builder = WorldBuilder(BLOCK_SIZE, gravity=(0, self.gravity), fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._world = load_world(world_builder, self.level) # Create a menu bar on the top of the game and association a function to each button on the menu with command parameter menubar = tk.Menu(self._master) menubar.add_command(label = "Load Level", command = self.showDialogInput) menubar.add_command(label = "Reset Level", command = self.resetLevel) menubar.add_command(label = "High scores", command = self.displayHighScores) menubar.add_command(label = "Exit", command = self.exitGame) self._master.config(menu = menubar) self._player = Player(name = self.character, max_health=self.max_health) self._player.invincible = False self._world.add_player(self._player, self.starting_x, self.starting_y, mass = self.mass) self._setup_collision_handlers() self._renderer = MarioViewRenderer(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) size = tuple(map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(master, size, self._renderer) self._view.pack() self.bind() # Wait for window to update before continuing master.update_idletasks() self.statusBar = StatusBar(self._master) self.step() self._master.mainloop() #DEBUT def load_configuration(self, file_path): with open(file_path) as fp: line = fp.readline() while line and "==World==" not in line: line = fp.readline() if not line: self.alertFile() # World line readed line = fp.readline() while line and "==Player==" not in line: if (":" in line): self.setWorldProperties(line.split(':')[0].strip(), line.split(':')[1].strip()) line = fp.readline() if not line: self.alertFile() line = fp.readline() while line and self.level not in line: if (":" in line): self.setPlayerProperties(line.split(':')[0].strip(), line.split(':')[1].strip()) line = fp.readline() if not line: self.alertFile() current_level = self.level while (line): self.levels.append(Level(current_level)) line = fp.readline() while (line and "==" not in line): if (":" in line): self.setLevelProperties(current_level, line.split(':')[0].strip(), line.split(':')[1].strip()) line = fp.readline() if ("==" in line): current_level = line.strip() def left_key(self, event): # Move Mario on the left when pressing left keypad self._move(-50, 0) def right_key(self, event): # Move Mario on the right when pressing right keypad self._move(50, 0) def up_key(self, event): # Move Mario up when pressing up keypad self._jump() def down_key(self, event): # Move Mario down when pressing down keypad self._duck() #FIN def reset_world(self, new_level="level1.txt"): self.level = new_level self._world = load_world(self._builder, new_level) health = self._player.get_health() self._player = Player(max_health=5) self._player.invincible = False self.down_pressed = False self._player.change_health(health - self._player.get_max_health()) self._world.add_player(self._player, BLOCK_SIZE, BLOCK_SIZE) self._setup_collision_handlers() def bind(self): #LEFT self._master.bind('<Left>', self.left_key) self._master.bind('L', self.left_key) #RIGHT self._master.bind('<Right>', self.right_key) self._master.bind('R', self.right_key) #UP self._master.bind('<Up>', self.up_key) self._master.bind('W', self.up_key) self._master.bind('<space>', self.up_key) #DOWN self._master.bind('<Down>', self.down_key) self._master.bind('S', self.down_key) def redraw(self): """Redraw all the entities in the game canvas.""" self._view.delete(tk.ALL) self._view.draw_entities(self._world.get_all_things()) #Show a dialog input after load Option in the menu. #The input should be an integer which coressponds to an existing Level. In our case Level 1 and 2 # If the input is correct, we load the new level file def showDialogInput(self) : answer = simpledialog.askstring("Input", "Level number : ",parent=self._master) nameLevelTxt = "level" while(answer is None or (answer != "1" and answer !="2")) : answer = simpledialog.askstring("Input", " enter a correct Level number (1 or 2) : ",parent=self._master) nameLevelTxt = nameLevelTxt +answer + ".txt" self.reset_world(nameLevelTxt) # Reset Score and Health of Mario def resetLevel(self) : self._player.change_score(-1*self._player.get_score()) #change score method add the parameter to the current score, so to reset the score to 0 we have just to add the -1* the currentScore self._player.change_health(self._player.get_max_health() - self._player.get_health()) #same for health, to reset the health to the maxHealth we have just to add the max_health - health def displayHighScores(self): top = tk.Toplevel() top.title("High Scores") try: f = open("HS" + self.level, "r") except IOError: f= open("HS" + self.level,"w+") scores = [] f = open("HS" + self.level, "r") line = f.readline() counter = 1 message = "" # variable containing the string to show in the message while line and counter < 11: if 'SEP' in line: #we re separating the player name and his score with the SEP message += str(counter) + " - " + line.split("SEP")[0].strip() + " : " + line.split("SEP")[1].strip() + "\n" counter = counter + 1 line = f.readline() msg = Message(top, text=message, width=200) msg.config(font=("Courier", 12)) msg.pack() button = tk.Button(top, text="Ok", command=top.destroy) button.pack() pass # Closing the gameView after selecting this option of menubar def exitGame(self): exit() def scroll(self): """Scroll the view along if the player is within SCROLL_RADIUS from the edge of the screen. """ # calculate the x distance from the right edge x_position = self._player.get_position()[0] x_offset = self._view.get_offset()[0] screen_size = self._master.winfo_width() edge_distance = screen_size - (x_position + x_offset) if edge_distance < SCROLL_RADIUS: x_position -= 5 # place a backstop boundary wall on the left side of the screen # to prevent the player from going backwards in the game world_space = self._world.get_space() wall = BoundaryWall("backstop", world_space.static_body, (x_position, 0), (x_position, self._world.get_pixel_size()[1]), 5) world_space.add(wall.get_shape()) # shift the view offset by the screen size self._view.shift((-(screen_size - SCROLL_RADIUS), 0)) def step(self): """Step the world physics and redraw the canvas.""" data = (self._world, self._player) self._world.step(data) self.scroll() self.redraw() self.statusBar.scoreStr.set("score = " + str(self._player.get_score())) #to refresh the scoreLabel on each step self.statusBar._scoreLabel.pack() ratio = 1.0 * self._player.get_health() / self._player.get_max_health() #Configuring the status bar depending on health and Mario's state (Normal or invincible) if self._player.invincible == True : self.statusBar._HealthLabel.configure(bg="yellow") else : if(ratio >= 0.5): self.statusBar._HealthLabel.configure(bg="green") else: if(ratio > 0.25): self.statusBar._HealthLabel.configure(bg="orange") else: self.statusBar._HealthLabel.configure(bg="red") #in case if the health changes we have to refresh the healthLabel healthWidth = int(155.0 * ratio) # healthWidth change depending on the health self.statusBar._HealthLabel.config(width=healthWidth, height=1) self.statusBar._HealthLabel1.config(width=155 - healthWidth, height=1) self.statusBar._HealthLabel.pack(side=tk.LEFT) self.statusBar._HealthLabel1.pack(side=tk.LEFT) self._master.after(10, self.step) def _move(self, vx, vy): if(vx < 0): #moving left if(abs(vx) > self.max_velocity): # we have to check if velocity we set in inferior than the max velocity variable defined in the configuration file vx = -1 * self.max_velocity self._player.set_velocity((vx, 0)) else: #moving right if(vx > self.max_velocity): vx = self.max_velocity self._player.set_velocity((vx, 0)) def _jump(self): vy = -130 if(vy > abs(self.max_velocity)): vy = -1 * self.max_velocity self._player.set_velocity((0, vy)) def _duck(self): self.down_pressed = True vy = 130 if(vy > self.max_velocity): vy = self.max_velocity self._player.set_velocity((0, vy)) def _setup_collision_handlers(self): self._world.add_collision_handler("player", "item", on_begin=self._handle_player_collide_item) self._world.add_collision_handler("player", "block", on_begin=self._handle_player_collide_block, on_separate=self._handle_player_separate_block) self._world.add_collision_handler("player", "mob", on_begin=self._handle_player_collide_mob) self._world.add_collision_handler("mob", "block", on_begin=self._handle_mob_collide_block) self._world.add_collision_handler("mob", "mob", on_begin=self._handle_mob_collide_mob) self._world.add_collision_handler("mob", "item", on_begin=self._handle_mob_collide_item) def _handle_mob_collide_block(self, mob: Mob, block: Block, data, arbiter: pymunk.Arbiter) -> bool: if(mob.get_id() == "mushroom"): #to change the mob direction when he touch a block if get_collision_direction(block, mob) == "L" or get_collision_direction(block, mob) == "R": mob.set_tempo(-1 * mob.get_tempo()) if mob.get_id() == "fireball": if block.get_id() == "brick": self._world.remove_block(block) self._world.remove_mob(mob) return True def _handle_mob_collide_item(self, mob: Mob, block: Block, data, arbiter: pymunk.Arbiter) -> bool: return False def _handle_mob_collide_mob(self, mob1: Mob, mob2: Mob, data, arbiter: pymunk.Arbiter) -> bool: if(mob1.get_id() == "mushroom"): mob1.set_tempo(-1 * mob1.get_tempo()) #change the mob direction => reverse the tempo if(mob2.get_id() == "mushroom"): mob2.set_tempo(-1 * mob2.get_tempo()) #change the mob direction if mob1.get_id() == "fireball" or mob2.get_id() == "fireball": self._world.remove_mob(mob1) self._world.remove_mob(mob2) return False def _handle_player_collide_item(self, player: Player, dropped_item: DroppedItem, data, arbiter: pymunk.Arbiter) -> bool: """Callback to handle collision between the player and a (dropped) item. If the player has sufficient space in their to pick up the item, the item will be removed from the game world. Parameters: player (Player): The player that was involved in the collision dropped_item (DroppedItem): The (dropped) item that the player collided with data (dict): data that was added with this collision handler (see data parameter in World.add_collision_handler) arbiter (pymunk.Arbiter): Data about a collision (see http://www.pymunk.org/en/latest/pymunk.html#pymunk.Arbiter) NOTE: you probably won't need this Return: bool: False (always ignore this type of collision) (more generally, collision callbacks return True iff the collision should be considered valid; i.e. returning False makes the world ignore the collision) """ dropped_item.collect(self._player) self._world.remove_item(dropped_item) return False def getNextTunnelLevel(self, current): #return the nextLevel if the player touched the tunnel using the config file for level in self.levels: if(level.name == current): return level.tunnel def getNextLevel(self, current): #return the nextLevel using the config file for level in self.levels: if(level.name == current): return level.goal def addScore(self): name = simpledialog.askstring("Input", "Your name : ",parent=self._master) #ask the user to insert his name while(name is None) : name = simpledialog.askstring("Input", " Your name : ",parent=self._master) try: f = open("HS" + self.level, "r") except IOError: #if the file don"t exist f= open("HS" + self.level,"w+") f.close() scores = [] #we add all the scores sorted in this list respecting the order of the new score f = open("HS" + self.level, "r") line = f.readline() done = False while line :#iterate over the lines to find the line where we should insert the score if 'SEP' in line: if not done and self._player.get_score() > int(line.split("SEP")[1].strip()): scores.append((name, self._player.get_score())) done = True scores.append((line.split("SEP")[0].strip() , int(line.split("SEP")[1].strip()))) line = f.readline() if not done: #in case if the file was emply or the user's score is less than all the others scores.append((name, self._player.get_score())) f.close() f= open("HS" + self.level,"w") for sc in scores: name, score = sc f.write(name + " SEP " + str(score) + "\n") f.close def _handle_player_collide_block(self, player: Player, block: Block, data, arbiter: pymunk.Arbiter) -> bool: if(block.get_id() == "bounce"): # Propel Mario when collide to BounceBlock self._player.set_velocity((0, -180)) if(block.get_id()== "flag"): self.addScore() #to let the player enter his name to add the score on the highscore file self.level = self.getNextLevel(self.level) #get the nextLevel using the config File self.reset_world(self.level) if(get_collision_direction(player, block) == "A"): #increase the player health in case his on top of the flag player.change_health(1) if(block.get_id()=="tunnel" and get_collision_direction(player, block) == "A" and self.down_pressed is True): #GO THE NEXT LEVEL IF the player pressed up on top of the tunnel self.level = self.getNextTunnelLevel(self.level) self.reset_world(self.level) #Removing the bricks on the left and on the right of the switch if block.get_id() == "switch" and get_collision_direction(player, block) == "A": x,y = block.get_position() block1 = self._world.get_block(x-GRID_WIDTH,y-block.getCollNum()*GRID_HEIGHT) block2 = self._world.get_block(x+GRID_WIDTH,y+block.getCollNum()*GRID_HEIGHT) self._world.remove_block(block1) self._world.remove_block(block2) block.incrementcollNum() block.on_hit(arbiter, (self._world, player)) return True def _handle_player_collide_mob(self, player: Player, mob: Mob, data, arbiter: pymunk.Arbiter) -> bool: # A collision with a mob make Mario Lost a health. # When Mario is Invincible Mario Lost and gain a life. # Thus, the health don't change tough the collision heppened if player.invincible == True : player.change_health(1) if(type(mob) is MushroomMob): if get_collision_direction(player, mob) == "A": #in case if the player is above the mushroomMob player.set_velocity((0, 100)) #the player up mob.destroy() #destroy the mob else:#in case the collision is on another direction player.change_health(-1) #the player should lose health player.set_velocity((2 * mob.get_velocity()[0], 0)) , #the player should be slightly repelled away mob.on_hit(arbiter, (self._world, player)) return True def _handle_player_separate_block(self, player: Player, block: Block, data, arbiter: pymunk.Arbiter) -> bool: return True
class MarioApp: """High-level app class for Mario, a 2d platformer""" _world: World def __init__(self, master: tk.Tk): """Construct a new game of a MarioApp game. Parameters: master (tk.Tk): tkinter root widget """ self._master = master # default configuration setting self._level = "level1.txt" self._gravity = (0, 300) self._max_health = 5 self._mass = 100 self._x = BLOCK_SIZE self._y = BLOCK_SIZE self._max_velocity = 500 self._config = {} self._master.update_idletasks() self.load_config() world_builder = WorldBuilder(BLOCK_SIZE, self._gravity, fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._player = Player(max_health=self._max_health) self._player.set_jumping(True) self._player.set_shoot(False) self.reset_world(self._level) self._renderer = MarioViewRenderer(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) size = tuple( map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(master, size, self._renderer) self._view.pack() self.bind() self.menu_bar() # create the character status bar self.status_bar = Status(master) self.status_bar.pack() # Wait for window to update before continuing master.update_idletasks() self.step() def read_config(self, filename: str): """ To read the configuration data from the txt file Parameter: filename (str): filename Return (dictionary): looks like {"level":{'key':value, 'key': value},} """ config = {} with open(filename) as hand: for line in hand: line = line.rstrip() if line.startswith("==") and line.endswith("=="): # heading line heading = line[2:-2] config[heading] = {} else: # attribute line attr, _, value = line.partition(' : ') config[heading][attr] = value hand.close() return config def load_config(self): """ load the read configuration to the default settings If the configuration file is invalid, exit the game with an error message. """ config_file = filedialog.askopenfilename() try: config = self.read_config(config_file) self._config = config self._level = config['World']['start'] self._gravity = (0, int(config['World']['gravity'])) self._x = float(config['Player']['x']) self._y = float(config['Player']['y']) self._mass = int(config['Player']['mass']) self._max_health = int(config['Player']['health']) self._max_velocity = int(config['Player']['max_velocity']) except UnboundLocalError: tk.messagebox.showerror('Error', 'Bad Input') self._master.destroy() def reset_world(self, new_level): self._world = load_world(self._builder, new_level) self._world.add_player(self._player, self._x, self._y, self._mass) self._builder.clear() self._setup_collision_handlers() def menu_bar(self): """ Create a menu bar """ menubar = tk.Menu(self._master) self._master.config(menu=menubar) # within the menu bar create the file menu filemenu = tk.Menu(menubar) menubar.add_cascade(label="File", menu=filemenu) # within the file menu create the file processing options filemenu.add_command(label="Load Level", command=self.load_level) filemenu.add_command(label="Reset Level", command=self.reset_level) filemenu.add_command(label="High Score", command=self.show_scores) filemenu.add_command(label="Exit", command=self.exit) def load_level(self): """ Input a level file and load that level """ filename = filedialog.askopenfilename() if filename: self.reset_world(filename) self._level = filename def reset_level(self): """ Reset the current level and all player progress """ ans = messagebox.askokcancel('Restart Game', 'Restart Game?') if ans: self.reset_world(self._level) self._player.clear_score() self._player.change_health(self._player.get_max_health()) self.redraw_status() else: self._master.destroy() def exit(self): """ exit the game """ ans = messagebox.askokcancel('Exit Game', 'Really exit?') if ans: self._master.destroy() def game_over(self): """ See if the player is dead. If so, ask if want to start a new game or just quit. """ if self._player.is_dead(): ans = messagebox.askokcancel('Player is dead', 'Start Over?') if ans: self.reset_world('level1.txt') self._level = 'level1.txt' self._player.clear_score() self._player.change_health(self._player.get_max_health()) self.redraw_status() else: self._master.destroy() def read_score(self): """ read the score records from the text file Return: score (dictionary): looks like this {'level':[('name', int), ('name', int), ('name', int), ], } """ score = {} with open("high_score.txt") as hand: for line in hand: line = line.rstrip() if line.startswith("**") and line.endswith("**"): heading = line[2:-2] score[heading] = [] else: record = line.split(' : ') score[heading].append((record[0], int(record[1]))) hand.close() return score def update_score(self): """ ask for the name of the current player and get the current score. read the high score records and see if the current player gets into the top 10 high scores for the current level if so, update the high score text file with the lowest one replace """ name = tk.simpledialog.askstring("your name", "what's your name", parent=self._master) # 这人名字 score = self._player.get_score() score_records = self.read_score() # rank the record list of the current level from low to high by score in the dictionary score_records[self._level].sort(key=lambda x: x[1]) if len(score_records[self._level]) < 10: score_records[self._level].append((name, score)) elif score > score_records[self._level][0][1]: score_records[self._level][0] = ( name, score) # replace the one with the lowest score with open("high_score.txt", 'w') as handle: # write back to the text file for k, v in score_records.items(): handle.write('**{}**\n'.format(k)) for n in v: name, value = n handle.write('{} : {}\n'.format(name, value)) handle.close() def show_scores(self): """ Display the score records in a window """ score = self.read_score() score_window = tk.Toplevel(self._master) score_window.geometry('300x200') score_window.title( self._level.rstrip(".txt").capitalize() + ' Top 10 Scores') tk.Label(score_window, text="Top 10 Scores In This Level").pack(side=tk.TOP) tk.Label(score_window, text="\n".join( 'name:{}\tscore: {}'.format(k, v) for (k, v) in score[self._level])).pack(side=tk.TOP) def get_next_level(self): """ (str) Return the string of next level file name """ return self._config[self._level]['goal'] def load_next_level(self): """load the next level in world""" self.reset_world(self.get_next_level()) def bind(self): """Bind all the keyboard events to their event handlers.""" self._master.bind("<a>", self.key_press) self._master.bind("<Left>", self.key_press) self._master.bind("<d>", self.key_press) self._master.bind("<Right>", self.key_press) self._master.bind("<w>", self.key_press) self._master.bind("<Up>", self.key_press) self._master.bind("<space>", self.key_press) self._master.bind("<s>", self.key_press) self._master.bind("<Down>", self.key_press) self._master.bind("<b>", self.key_press) def key_press(self, e): """ What to execute when certain key is pressed """ key = e.keysym if key == 'a' or key == 'Left': self._move(-150, 0) elif key == 'd' or key == 'Right': self._move(150, 0) elif key == 'w' or key == 'Up' or key == 'space': self._jump() elif key == 's' or key == 'Down': self._duck() elif key == "b": self.shoot() def redraw_status(self): """ Redraw the player status bar with the updated health and score value """ self.status_bar.clear() self.status_bar.update_health(self._player.get_health(), self._player.is_niubi(), self._player) self.status_bar.update_score(self._player.get_score()) def redraw(self): """Redraw all the entities in the game canvas.""" self._view.delete(tk.ALL) self._view.draw_entities(self._world.get_all_things()) self.redraw_status() def scroll(self): """Scroll the view along with the player in the center unless they are near the left or right boundaries """ x_position = self._player.get_position()[0] half_screen = self._master.winfo_width() / 2 world_size = self._world.get_pixel_size()[0] - half_screen # Left side if x_position <= half_screen: self._view.set_offset((0, 0)) # Between left and right sides elif half_screen <= x_position <= world_size: self._view.set_offset((half_screen - x_position, 0)) # Right side elif x_position >= world_size: self._view.set_offset((half_screen - world_size, 0)) def step(self): """Step the world physics and redraw the canvas.""" data = (self._world, self._player) self._world.step(data) self.scroll() self.redraw() self.game_over() self._master.after(10, self.step) # refresh def _move(self, dx: int, dy: int): """ move the player Parameter: dx (int): velocity on x axis dy (int): velocity on y axis """ self._player.set_velocity((dx, dy)) def _jump(self): """ if the player is not jumping, make it jump, and change the jumping status to True. """ if not self._player.is_jumping(): self._move(0, -200) self._player.set_jumping(True) def _duck(self): """ set the duck status of the player to True """ self._player.set_duck(True) def shoot(self): """ player shoots the bullet """ x, y = self._player.get_position() vx, vy = self._player.get_velocity() if self._player.is_shoot: if vx >= 0: self._world.add_mob(BulletRight(), x + 16, y) else: self._world.add_mob(BulletLeft(), x - 16, y) else: print('不射') def _setup_collision_handlers(self): self._world.add_collision_handler( "player", "item", on_begin=self._handle_player_collide_item) self._world.add_collision_handler( "player", "block", on_begin=self._handle_player_collide_block, on_separate=self._handle_player_separate_block) self._world.add_collision_handler( "player", "mob", on_begin=self._handle_player_collide_mob) self._world.add_collision_handler( "mob", "block", on_begin=self._handle_mob_collide_block) self._world.add_collision_handler( "mob", "mob", on_begin=self._handle_mob_collide_mob) self._world.add_collision_handler( "mob", "item", on_begin=self._handle_mob_collide_item) def _handle_mob_collide_block(self, mob: Mob, block: Block, data, arbiter: pymunk.Arbiter) -> bool: if mob.get_id() == "fireball" or mob.get_id( ) == 'bullet_l' or mob.get_id() == 'bullet_r': if block.get_id() == "brick": self._world.remove_block(block) self._world.remove_mob(mob) else: self._world.remove_mob(mob) elif mob.get_id( ) == "mushroom": # mushroom bounces back a little when encountering blocks if get_collision_direction( mob, block) == "R" or get_collision_direction( mob, block) == "L": mob.set_tempo(-mob.get_tempo()) elif mob.get_id( ) == 'gang': # gang jumps over the blocks when encountering them if get_collision_direction(mob, block) == "R": mob.set_velocity((50, -350)) elif get_collision_direction(mob, block) == "L": mob.set_velocity((-50, -350)) return True def _handle_mob_collide_item(self, mob: Mob, block: Block, data, arbiter: pymunk.Arbiter) -> bool: return False def _handle_mob_collide_mob(self, mob1: Mob, mob2: Mob, data, arbiter: pymunk.Arbiter) -> bool: if mob1.get_id() == "fireball" or mob2.get_id() == "fireball": self._world.remove_mob(mob1) self._world.remove_mob(mob2) elif mob1.get_id( ) == 'bullet_l' or mob1.get_id == 'bullet_r' or mob2.get_id( ) == 'bullet_l' or mob2.get_id == 'bullet_r': self._world.remove_mob(mob1) self._world.remove_mob(mob2) elif mob1.get_id() == "gang" and mob2.get_id() == "mushroom": return False elif mob1.get_id() == "mushroom" and mob2.get_id() == "gang": return False elif mob1.get_id() == "gang" and mob2.get_id() == "gang": return False elif mob1.get_id() == "mushroom" and mob2.get_id() == "mushroom": mob1.set_tempo(-mob1.get_tempo()) mob2.set_tempo(-mob2.get_tempo()) else: self._world.remove_mob(mob1) self._world.remove_mob(mob2) return False def _handle_player_collide_item(self, player: Player, dropped_item: DroppedItem, data, arbiter: pymunk.Arbiter) -> bool: """Callback to handle collision between the player and a (dropped) item. If the player has sufficient space in their to pick up the item, the item will be removed from the game world. Parameters: player (Player): The player that was involved in the collision dropped_item (DroppedItem): The (dropped) item that the player collided with data (dict): data that was added with this collision handler (see data parameter in World.add_collision_handler) arbiter (pymunk.Arbiter): Data about a collision (see http://www.pymunk.org/en/latest/pymunk.html#pymunk.Arbiter) NOTE: you probably won't need this Return: bool: False (always ignore this type of collision) (more generally, collision callbacks return True iff the collision should be considered valid; i.e. returning False makes the world ignore the collision) """ if dropped_item.get_id() == 'coin': dropped_item.collect(self._player) self._world.remove_item(dropped_item) elif dropped_item.get_id() == 'star': dropped_item.collect(self._player) self._world.remove_item(dropped_item) elif dropped_item.get_id() == 'flower': dropped_item.collect(self._player) self._world.remove_item(dropped_item) return False def _handle_player_collide_block(self, player: Player, block: Block, data, arbiter: pymunk.Arbiter) -> bool: if get_collision_direction( player, block ) == "A": # when player touch the blocks, set jumping to false self._player.set_jumping(False) if block.get_id() == "flag": if get_collision_direction(player, block) == "A": block.on_hit(arbiter, data) else: # tell the player to input their name and see if the score records need to be updated self.update_score() if self.get_next_level( ) == 'END': # if there's no further level, ask if start over ans = messagebox.askokcancel( 'Good job, you finish the game', 'Start Over?') if ans: self.reset_world('level1.txt') self._level = 'level1.txt' self._player.clear_score() self._player.change_health( self._player.get_max_health()) self.redraw_status() else: self._master.destroy() else: self.reset_world(self.get_next_level()) self._level = self.get_next_level() elif block.get_id() == "tunnel": if get_collision_direction( player, block) == "A" and self._player.is_duck() is True: self._player.set_duck(False) self.reset_world(self.get_next_level()) elif block.get_id() == 'switches': if block.is_active(): block.on_hit(arbiter, (self._world, player)) block.on_hit(arbiter, (self._world, player)) return True def _handle_player_collide_mob(self, player: Player, mob: Mob, data, arbiter: pymunk.Arbiter) -> bool: if player.is_niubi(): self._world.remove_mob(mob) elif player.is_shoot(): player.set_shoot(False) else: mob.on_hit(arbiter, (self._world, player)) return True def _handle_player_separate_block(self, player: Player, block: Block, data, arbiter: pymunk.Arbiter) -> bool: return True
class MarioApp : """High-level app class for Mario, a 2d platformer""" _world: World def __init__(self, master: tk.Tk) : """Construct a new game of a MarioApp game. Parameters: master (tk.Tk): tkinter root widget """ self._master = master config_name = askopenfilename(filetypes=( ("Text file", "*.txt"),("HTML files", "*.html;*.htm"))) self.load_config(config_name) self._gravity self._level_name self._healthdeterminant world_builder = WorldBuilder(BLOCK_SIZE, (0,300), fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._player = Player(max_health= self._health) self._powerup = PowerUp() self.reset_world(self._level_name) #self._renderer = MarioViewRenderer(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) self._sprite_renderer = SpriteSheetView(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) size = tuple(map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(master, size, self._sprite_renderer) self._view.pack() self.bind() # Wait for window to update before continuing master.update_idletasks() self._status_view = StatusView(master, self._health) self._status_view.pack() self._status_view.update_score(self._player.get_score()) self._status_view.update_health(self._player.get_health()) self._setup_collision_handlers() master.update_idletasks() self._start_time = 0 self.step() self.menu = MenuBar(master, [("File", {"Reset" : self._reset, "Load Level" : self._import_new_level, "Exit" : self._close, "High Score": self._status_view.high})]) self._collide = False def load_config(self,file_name): """ load a configuration file, which contain the parameter of this game this file will define the new character ,level path, loading position, weight, health value and max_velocity. :param file_name: :return: """ result = [] f = open(file_name, "r") for line in f.readlines() : result.append(line.strip('\n')) for i in range(len(result)) : if result[i] == '==World==' : gravity_re = result[i + 1] gravity = re.findall(r'(\d+)', gravity_re) a = int(gravity[0]) self._gravity = range(0,a) start_level = result[i + 2] level = start_level.split(' ') self._level_name = level[2] if result[i] == '==Player==' : character_re = result[i + 1] character = character_re.split(' ') b = character[2] x_re = result[i + 2] x = re.findall(r'(\d+)', x_re) x = int(x[0]) y_re = result[i + 3] y = re.findall(r'(\d+)', y_re) y = int(y[0]) mass_re = result[i + 4] mass = re.findall(r'(\d+)', mass_re) mass = int(mass[0]) health_re = result[i + 5] health = re.findall(r'(\d+)', health_re) self._health = int(health[0]) velocity_re = result[i + 6] velocity = re.findall(r'(\d+)', velocity_re) velocity = int(velocity[0]) def reset_world(self, new_level: str): ''' reset the current world, and bind the buttons again :param new_level: :return: ''' self._world = load_world(self._builder, new_level) self._world.add_player(self._player, BLOCK_SIZE, BLOCK_SIZE) self._builder.clear() self.bind() self._setup_collision_handlers() def bind(self) : """Bind all the keyboard events to their event handlers.""" self._master.bind("<space>", lambda a : self._jump()) self._master.bind("<Up>", lambda a : self._jump()) self._master.bind("w", lambda a : self._jump()) self._master.bind("a", lambda a : self._move(-1, 0)) self._master.bind("<Left>", lambda a : self._move(-1, 0)) self._master.bind("d", lambda a : self._move(1, 0)) self._master.bind("<Right>", lambda a : self._move(1, 0)) self._master.bind("s", lambda a : self._duck()) self._master.bind("<Down>", lambda a : self._duck()) if self._powerup.get_powerup(): self._master.bind("z", lambda a : self._powerup.fire((self._world, self._player))) def _import_new_level(self) : ''' when the user click the load level entry in file menu, this widget will come out. :return: ''' def load() : self._player.change_health(self._player.get_max_health() - self._player.get_health()) self._player.change_score(-self._player.get_score()) level_name = new_name.get() self.reset_world(level_name) Level_load_new.destroy() Level_load_new = tk.Toplevel() Level_load_new.geometry('400x100') Level_load_new.title('Load New Level') new_name = tk.StringVar() new_name.set('level1.txt') tk.Label(Level_load_new, text='Please enter the level name: ').place(x=10, y=10) entry_new_name = tk.Entry(Level_load_new, textvariable=new_name) entry_new_name.place(x=200, y=10) btn_comfirm = tk.Button(Level_load_new, text='OK', command=load) btn_comfirm.place(x=180, y=80) def _reset(self) : '''reset the current level''' self._player.change_health(self._player.get_max_health() - self._player.get_health()) self._player.change_score(-self._player.get_score()) self.reset_world(level_name) def _close(self) : """ Exit the drawing application """ result = tk.messagebox.askquestion(title="Quiz Window", message="Do you really wanna quiz?") if (result == "yes") : self._master.destroy() def redraw(self) : """Redraw all the entities in the game canvas.""" self._view.delete(tk.ALL) self._view.draw_entities(self._world.get_all_things()) def scroll(self) : """Scroll the view along with the player in the center unless they are near the left or right boundaries """ x_position = self._player.get_position()[0] half_screen = self._master.winfo_width() / 2 world_size = self._world.get_pixel_size()[0] - half_screen # Left side if x_position <= half_screen : self._view.set_offset((0, 0)) # Between left and right sides elif half_screen <= x_position <= world_size : self._view.set_offset((half_screen - x_position, 0)) # Right side elif x_position >= world_size : self._view.set_offset((half_screen - world_size, 0)) def step(self) : """Step the world physics and redraw the canvas.""" data = (self._world, self._player) self._world.step(data) if self._player.get_health() == 0: result = tk.messagebox.askquestion(title="Quiz Window", message="Do you really wanna quiz?") if result == "yes": self._master.destroy() else: self._reset() self.scroll() self.redraw() self._master.after(10, self.step) self._status_view.update_score((self._player.get_score())) if time.time() - self._start_time >= 10 and self._player.get_invincible() is True : self._player.set_invincible(False) if self._player.get_invincible(): health = self._player.get_health() ratio = health/self._health self._status_view._canvas.create_rectangle(0, 0, 1080*ratio, 20, fill="yellow") self._status_view._canvas.create_rectangle(1080 * ratio* 0.2, 0, 1080, 20, fill="black") else: self._status_view.update_health(self._player.get_health()) def _move(self, dx, dy) : ''' make the mario move ''' self._player.set_velocity((dx * 60, dy * 60)) def _jump(self) : '''make the mario jump''' velocity = self._player.get_velocity() self._player.set_velocity((velocity.x, -120)) def _duck(self) : '''make the mario duck''' velocity = self._player.get_velocity() self._player.set_velocity((velocity.x, velocity.y + 120)) def _tunnel(self, block: Block): '''when the player stand on a tunnel and press the <down> or s, this function will bring him to a new level''' self.reset_world(new_level=block.get_filename()) def _setup_collision_handlers(self) : self._world.add_collision_handler("player", "item", on_begin=self._handle_player_collide_item) self._world.add_collision_handler("player", "block", on_begin=self._handle_player_collide_block, on_separate=self._handle_player_separate_block) self._world.add_collision_handler("player", "mob", on_begin=self._handle_player_collide_mob) self._world.add_collision_handler("mob", "block", on_begin=self._handle_mob_collide_block) self._world.add_collision_handler("mob", "mob", on_begin=self._handle_mob_collide_mob) self._world.add_collision_handler("mob", "item", on_begin=self._handle_mob_collide_item) def _handle_mob_collide_block(self, mob: Mob, block: Block, data, arbiter: pymunk.Arbiter) -> bool : ''' handle when the mob hit other block when the mob is fireball and hit a brick, the block will be destroyed when the mob is mushroom and hit a brick, it will change its direction ''' if mob.get_id() == "fireball" : if block.get_id() == "brick" : self._world.remove_block(block) self._world.remove_mob(mob) if mob.get_id() == "mushroom": if get_collision_direction(mob, block) == "L" or get_collision_direction(mob, block) == "R" : mob.set_tempo(-mob.get_tempo()) return True def _handle_mob_collide_item(self, mob: Mob, block: Block, data, arbiter: pymunk.Arbiter) -> bool : return False def _handle_mob_collide_mob(self, mob1: Mob, mob2: Mob, data, arbiter: pymunk.Arbiter) -> bool : ''' when the mushroom hit a fireball it will be destroyed and when he hit another mushroom it will change its direction ''' if mob1.get_id() == "fireball" or mob2.get_id() == "fireball" : self._world.remove_mob(mob1) self._world.remove_mob(mob2) if mob1.get_velocity() == "mushroom" or mob2.get_id() == "mushroom" : mob1.set_tempo(-mob1.get_tempo()) mob2.set_tempo(-mob2.get_tempo()) return False def _handle_player_collide_item(self, player: Player, dropped_item: DroppedItem, data, arbiter: pymunk.Arbiter) -> bool : """Callback to handle collision between the player and a (dropped) item. If the player has sufficient space in their to pick up the item, the item will be removed from the game world. Parameters: player (Player): The player that was involved in the collision dropped_item (DroppedItem): The (dropped) item that the player collided with data (dict): data that was added with this collision handler (see data parameter in World.add_collision_handler) arbiter (pymunk.Arbiter): Data about a collision (see http://www.pymunk.org/en/latest/pymunk.html#pymunk.Arbiter) NOTE: you probably won't need this Return: bool: False (always ignore this type of collision) (more generally, collision callbacks return True iff the collision should be considered valid; i.e. returning False makes the world ignore the collision) """ dropped_item.collect(self._player) self._world.remove_item(dropped_item) if dropped_item.get_id() == "star" : self._player.set_invincible(True) self._start_time = time.time() if dropped_item.get_id() == "flower": self._powerup.set_powerup(True) return False def _handle_player_collide_block(self, player: Player, block: Block, data, arbiter: pymunk.Arbiter) -> bool : ''' handle when the player hit some blocks, when they are colliding, the on_hit function will run''' if block.get_id() == "switch": block.on_hit(arbiter, (self._world, player)) return False if block.get_id() == "flag": if get_collision_direction(player, block) == "L" or get_collision_direction(player, block) == "R": self.reset_world(new_level=block.get_filename()) if get_collision_direction(player, block) == "A" : block.on_hit(arbiter, (self._world, player)) if block.get_id() == "tunnel": if get_collision_direction(player, block) == "A": # self._master.bind("s", lambda a : self._tunnel(block)) self._master.bind("<Down>", lambda a : self._tunnel(block)) else: block.on_hit(arbiter, (self._world, player)) return True def _handle_player_collide_mob(self, player: Player, mob: Mob, data, arbiter: pymunk.Arbiter) -> bool : ''' handle when the player hit some mob, when they are colliding, the on_hit function will run''' if mob.get_id() == "mushroom" : if get_collision_direction(player, mob) == "L" or get_collision_direction(player, mob) == "R" : if self._player.get_invincible() : self._world.remove_mob(mob) elif self._powerup.get_powerup(): self._powerup.set_powerup(False) else : mob.on_hit(arbiter, (self._world, player)) mob.set_tempo(-mob.get_tempo()) if get_collision_direction(player, mob) == "A": mob.on_hit(arbiter, (self._world, player)) if mob.get_id() == "fireball" : if self._player.get_invincible() : self._world.remove_mob(mob) elif self._powerup.get_powerup(): self._powerup.set_powerup(False) else : mob.on_hit(arbiter, (self._world, player)) self._world.remove_mob(mob) return True def _handle_player_separate_block(self, player: Player, block: Block, data, arbiter: pymunk.Arbiter) -> bool : return True
class MarioApp: """High-level app class for Mario, a 2d platformer""" _world: World def __init__(self, master: tk.Tk): """Construct a new game of a MarioApp game. Parameters: master (tk.Tk): tkinter root widget """ self._master = master self._master.title("Mario bird") world_builder = WorldBuilder(BLOCK_SIZE, gravity=(0, 300), fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._player = Player(max_health=5) self.reset_world('level1.txt') self._level_holder = 'level1.txt' self._renderer = MarioViewRenderer(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) size = tuple(map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(master, size, self._renderer) self._view.pack() self.bind() self.menubar() #Stats controller for changing the status bar. #used in the function 'step' self._health = self._player.get_health() self._score = self._player.get_score() self._status_bar = tk.Frame(self._master) self.status_bar() # Wait for window to update before continuing master.update_idletasks() self.step() self._death_action = False def reset_world(self, new_level): self._world = load_world(self._builder, new_level) self._world.add_player(self._player, BLOCK_SIZE, BLOCK_SIZE) self._builder.clear() self._setup_collision_handlers() self._level_holder = new_level self._death_action = False self._player.change_health(self._player.get_max_health()) def menubar(self): """Creates a menu bar in GUI interface with options: Load level: prompts user to select level to load Reset level: Resets current level Exit: exits game """ 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="LOAD LEVEL", command=self.load_level_menu) filemenu.add_command(label="RESET LEVEL", command=lambda: self.reset_world(self._level_holder)) filemenu.add_command(label="EXIT", command=self.exit_game) def load_level_menu(self): """Pop-up window with 3 buttons Level1: loads level1 level2: loads level2 """ popup = tk.Tk() popup.title("Level select") label1 = tk.Label(popup, text="Choose a level") label1.pack(side='top') level1_button = tk.Button(popup, text="Level1", command=lambda: [self.reset_world('level1.txt'),popup.destroy()]) level2_button = tk.Button(popup, text="level2", command=lambda: [self.reset_world('level2.txt'),popup.destroy()]) level1_button.pack(side='left', expand=True) level2_button.pack(side='right', expand=True) def exit_game(self): self._master.destroy() def bind(self): """Bind all the keyboard events to their event handlers.""" # jump self._master.bind('<Up>', lambda event: self._jump()) self._master.bind('<space>', lambda event: self._jump()) self._master.bind('<w>', lambda event: self._jump()) self._master.bind('<W>', lambda event: self._jump()) # move left self._master.bind('<a>', lambda event: self._move(-50, 0)) self._master.bind('<Left>', lambda event: self._move(-500, 0)) self._master.bind('<A>', lambda event: self._move(-500, 0)) # duck self._master.bind('<s>', lambda event: self._duck()) self._master.bind('<Down>', lambda event: self._duck()) self._master.bind('<S>', lambda event: self._duck()) # move right self._master.bind('<d>', lambda event: self._move(50, 0)) self._master.bind('<Right>', lambda event: self._move(500, 0)) self._master.bind('<D>', lambda event: self._move(500, 0)) def status_bar(self): self._status_bar.destroy() self._status_bar = tk.Frame(self._master) self._status_bar.pack(side = tk.BOTTOM) healthbar_canvas = tk.Canvas(self._status_bar, width=1080, height=25, bg='black') healthbar_canvas.pack(side="top") healthbar_width = (self._player.get_health() / self._player.get_max_health()) * 1080 # set healthbar color color_controller = healthbar_width / 1080 if color_controller >= 0.50: color = 'Green' elif 0.25 <= color_controller <= 0.50: color = 'Orange' elif color_controller < 0.25: color = 'Red' # Healthbar= shape:Rectangle healthbar_canvas.create_rectangle(0, 25, healthbar_width, 0, fill=color) # label to display score w = tk.Label(self._status_bar, text='Score: ' + str(self._player.get_score())) w.pack(side="bottom") def redraw(self): """Redraw all the entities in the game canvas.""" self._view.delete(tk.ALL) self._view.draw_entities(self._world.get_all_things()) def scroll(self): """Scroll the view along with the player in the center unless they are near the left or right boundaries """ x_position = self._player.get_position()[0] half_screen = self._master.winfo_width() / 2 world_size = self._world.get_pixel_size()[0] - half_screen # Left side if x_position <= half_screen: self._view.set_offset((0, 0)) # Between left and right sides elif half_screen <= x_position <= world_size: self._view.set_offset((half_screen - x_position, 0)) # Right side elif x_position >= world_size: self._view.set_offset((half_screen - world_size, 0)) def step(self): """Step the world physics and redraw the canvas.""" """SMOOTH OPERATOR ..... SMOOOOTH OPERATION""" data = (self._world, self._player) self._world.step(data) self.scroll() self.redraw() self._master.after(10, self.step) #updates the status bar if change in health is detected if self._player.get_health() == self._health: pass else: self._health = self._player.get_health() self.status_bar() #updates the status bar if change in score is detected if self._player.get_score() == self._score: pass else: self._score = self._player.get_score() self.status_bar() #Asking if players want to continue playing or end the game if self._player.get_health() == 0: if not self._death_action: self.on_death() else: pass def on_death(self): """ A popup window asking if player wants to continue or exit """ self._death_action = True death_popup = tk.Tk() death_popup.geometry("300x200") death_popup.configure(background="light blue") death_popup.title("You dead") label1 = tk.Label(death_popup, text="What next??") label1.pack(side='top') restart_button = tk.Button(death_popup, text="Restart", command=lambda: [self.reset_world('level1.txt'), death_popup.destroy()]) end_button = tk.Button(death_popup, text='end', command=lambda: [self.exit_game(), death_popup.destroy()]) restart_button.pack(side='left', expand=True) end_button.pack(side='right', expand=True) def _move(self, dx, dy): self._player.set_velocity((dx, dy)) def _jump(self): self._player.set_velocity((0, -150)) def _duck(self): # to be used later as if duck == True return True def _setup_collision_handlers(self): self._world.add_collision_handler("player", "item", on_begin=self._handle_player_collide_item) self._world.add_collision_handler("player", "block", on_begin=self._handle_player_collide_block, on_separate=self._handle_player_separate_block) self._world.add_collision_handler("player", "mob", on_begin=self._handle_player_collide_mob) self._world.add_collision_handler("mob", "block", on_begin=self._handle_mob_collide_block) self._world.add_collision_handler("mob", "mob", on_begin=self._handle_mob_collide_mob) self._world.add_collision_handler("mob", "item", on_begin=self._handle_mob_collide_item) def _handle_mob_collide_block(self, mob: Mob, block: Block, data, arbiter: pymunk.Arbiter) -> bool: if mob.get_id() == "fireball": if block.get_id() == "brick": self._world.remove_block(block) self._world.remove_mob(mob) return True def _handle_mob_collide_item(self, mob: Mob, block: Block, data, arbiter: pymunk.Arbiter) -> bool: return False def _handle_mob_collide_mob(self, mob1: Mob, mob2: Mob, data, arbiter: pymunk.Arbiter) -> bool: if mob1.get_id() == "fireball" or mob2.get_id() == "fireball": self._world.remove_mob(mob1) self._world.remove_mob(mob2) return False def _handle_player_collide_item(self, player: Player, dropped_item: DroppedItem, data, arbiter: pymunk.Arbiter) -> bool: """Callback to handle collision between the player and a (dropped) item. If the player has sufficient space in their to pick up the item, the item will be removed from the game world. Parameters: player (Player): The player that was involved in the collision dropped_item (DroppedItem): The (dropped) item that the player collided with data (dict): data that was added with this collision handler (see data parameter in World.add_collision_handler) arbiter (pymunk.Arbiter): Data about a collision (see http://www.pymunk.org/en/latest/pymunk.html#pymunk.Arbiter) NOTE: you probably won't need this Return: bool: False (always ignore this type of collision) (more generally, collision callbacks return True iff the collision should be considered valid; i.e. returning False makes the world ignore the collision) """ dropped_item.collect(self._player) self._world.remove_item(dropped_item) return False def _handle_player_collide_block(self, player: Player, block: Block, data, arbiter: pymunk.Arbiter) -> bool: block.on_hit(arbiter, (self._world, player)) return True def _handle_player_collide_mob(self, player: Player, mob: Mob, data, arbiter: pymunk.Arbiter) -> bool: mob.on_hit(arbiter, (self._world, player)) return True def _handle_player_separate_block(self, player: Player, block: Block, data, arbiter: pymunk.Arbiter) -> bool: return True
def __init__(self, master: tk.Tk): """Construct a new game of a MarioApp game. Parameters: master (tk.Tk): tkinter root widget """ """ ------------------------------------- task 2.3 增加函数 load_config(在MarioApp)最底部 涉及到的变量参数修改已在注释中标出 ------------------------------------- """ self._master = master # 设置了一些参数默认值 self.gravity = 300 # world_builder = WorldBuilder(BLOCK_SIZE, gravity=(0,self.gravity), fallback=create_unknown) self.level = 'level1.txt' # 直接改变level值 self.character_name = 'mario' # self._player = Player(max_health=5,name=self.character_name) self.character_x = 1 # 在add_player中改变参数 self.character_y = 1 # 在add_player中改变参数 self.character_mass = 100 # 在add_player中改变参数 self.max_velocity = 200 # 暂时不知参数位置 # 调用load config函数 self._load_config() #----------------------------------------------------------task3 fm = tk.Frame(self._master) fm.pack(padx=10, expand=1) load = tk.Button(fm, text='Load Level', command=self.load_f) load.pack(side=RIGHT, fill=Y) rest = tk.Button(fm, text='Rest Level', command=self.rest_f) rest.pack(side=RIGHT, fill=Y) Exit = tk.Button(fm, text='Exits the game', command=self._master.quit) Exit.pack(side=RIGHT, fill=Y) world_builder = WorldBuilder(BLOCK_SIZE, gravity=(0, self.gravity), fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._player = Player(max_health=5, name=self.character_name) self.reset_world(self.level) self._renderer = MarioViewRenderer(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) size = tuple( map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(master, size, self._renderer) self._view.pack() self.bind() #`````````````````````````````````````````````````` #task1.3 Health = tk.Frame(self._master) Health.pack(padx=300, pady=50) self.score_num = tk.Variable = 0 self.Score = tk.Button(Health, text='Score:' + str(self.score_num)) self.Score.pack(side=RIGHT, fill=Y) label = tk.Label(bg="black", width=self._player._max_health) label.place(relx=0, rely=0.9) self.label1 = tk.Label(bg="blue", width=self._player._health) self.label1.place(relx=0, rely=0.9) #`````````````````````````````````````````````````` # Wait for window to update before continuing master.update_idletasks() self.step() self._master.mainloop()
class MarioApp: """High-level app class for Mario, a 2d platformer""" _world: World def __init__(self, master: tk.Tk): """Construct a new game of a MarioApp game. Parameters: master (tk.Tk): tkinter root widget """ """ ------------------------------------- task 2.3 增加函数 load_config(在MarioApp)最底部 涉及到的变量参数修改已在注释中标出 ------------------------------------- """ self._master = master # 设置了一些参数默认值 self.gravity = 300 # world_builder = WorldBuilder(BLOCK_SIZE, gravity=(0,self.gravity), fallback=create_unknown) self.level = 'level1.txt' # 直接改变level值 self.character_name = 'mario' # self._player = Player(max_health=5,name=self.character_name) self.character_x = 1 # 在add_player中改变参数 self.character_y = 1 # 在add_player中改变参数 self.character_mass = 100 # 在add_player中改变参数 self.max_velocity = 200 # 暂时不知参数位置 # 调用load config函数 self._load_config() #----------------------------------------------------------task3 fm = tk.Frame(self._master) fm.pack(padx=10, expand=1) load = tk.Button(fm, text='Load Level', command=self.load_f) load.pack(side=RIGHT, fill=Y) rest = tk.Button(fm, text='Rest Level', command=self.rest_f) rest.pack(side=RIGHT, fill=Y) Exit = tk.Button(fm, text='Exits the game', command=self._master.quit) Exit.pack(side=RIGHT, fill=Y) world_builder = WorldBuilder(BLOCK_SIZE, gravity=(0, self.gravity), fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._player = Player(max_health=5, name=self.character_name) self.reset_world(self.level) self._renderer = MarioViewRenderer(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) size = tuple( map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(master, size, self._renderer) self._view.pack() self.bind() #`````````````````````````````````````````````````` #task1.3 Health = tk.Frame(self._master) Health.pack(padx=300, pady=50) self.score_num = tk.Variable = 0 self.Score = tk.Button(Health, text='Score:' + str(self.score_num)) self.Score.pack(side=RIGHT, fill=Y) label = tk.Label(bg="black", width=self._player._max_health) label.place(relx=0, rely=0.9) self.label1 = tk.Label(bg="blue", width=self._player._health) self.label1.place(relx=0, rely=0.9) #`````````````````````````````````````````````````` # Wait for window to update before continuing master.update_idletasks() self.step() self._master.mainloop() def re_health(self): if (self._player._health >= self._player._max_health * 0.5): #print(self.a) self.label1.config(width=self._player._health, bg='green') elif (self._player._health <= self._player._max_health * 0.25): self.label1.config(width=self._player._health, bg='red') else: self.label1.config(width=self._player._health, bg='orange') def re_score(self): self.Score.config(text='Score:' + str(self.score_num)) def rest_f(self): if (self.level == 'level1.txt'): self.reset_world('level1.txt') else: self.reset_world('level2.txt') def load_f(self): def read(): text = xls.get() level_num = int(text) print(level_num) if (level_num == 1): self.reset_world('level1.txt') self.level = 'level1.txt' else: self.reset_world('level2.txt') self.level = 'level2.txt' root1.destroy() root1 = tk.Tk() root1.title("load") l1 = tk.Label(root1, text="plz input level(1 or 2)") l1.pack() # 这里的side可以赋值为LEFT RTGHT TOP BOTTOM xls = tk.Entry(root1) xls.pack() tk.Button(root1, text='finish', command=read).pack() root1.mainloop() def reset_world(self, new_level): self._world = load_world(self._builder, new_level) self._world.add_player(self._player, self.character_x * BLOCK_SIZE, self.character_y * BLOCK_SIZE, mass=self.character_mass) self._builder.clear() self.level = new_level self._setup_collision_handlers() def bind(self): """Bind all the keyboard events to their event handlers.""" self._master.bind_all("<KeyPress>", self._handle) pass def redraw(self): """Redraw all the entities in the game canvas.""" self._view.delete(tk.ALL) self._view.draw_entities(self._world.get_all_things()) def scroll(self): """Scroll the view along with the player in the center unless they are near the left or right boundaries """ x_position = self._player.get_position()[0] half_screen = self._master.winfo_width() / 2 world_size = self._world.get_pixel_size()[0] - half_screen # Left side if x_position <= half_screen: self._view.set_offset((0, 0)) # Between left and right sides elif half_screen <= x_position <= world_size: self._view.set_offset((half_screen - x_position, 0)) # Right side elif x_position >= world_size: self._view.set_offset((half_screen - world_size, 0)) def step(self): """Step the world physics and redraw the canvas.""" data = (self._world, self._player) self._world.step(data) self.scroll() self.redraw() self._master.after(10, self.step) def _handle(self, event): if (event.keysym == 'Up' or event.keysym == 'w' or event.keysym == 'space'): self._jump() elif (event.keysym == 'd' or event.keysym == 'Right'): self._move(70, 0) elif (event.keysym == 'a' or event.keysym == 'Left'): self._move(-70, 0) elif (event.keysym == 's' or event.keysym == 'Down'): self._duck() else: pass def _move(self, dx, dy): self._player.set_velocity((dx, dy)) print("move") pass def _jump(self): print(self._player.set_jumping(True)) print("jump") pass def _duck(self): print("duck") pass def _setup_collision_handlers(self): self._world.add_collision_handler( "player", "item", on_begin=self._handle_player_collide_item) self._world.add_collision_handler( "player", "block", on_begin=self._handle_player_collide_block, on_separate=self._handle_player_separate_block) self._world.add_collision_handler( "player", "mob", on_begin=self._handle_player_collide_mob) self._world.add_collision_handler( "mob", "block", on_begin=self._handle_mob_collide_block) self._world.add_collision_handler( "mob", "mob", on_begin=self._handle_mob_collide_mob) self._world.add_collision_handler( "mob", "item", on_begin=self._handle_mob_collide_item) def _handle_mob_collide_block(self, mob: Mob, block: Block, data, arbiter: pymunk.Arbiter) -> bool: if mob.get_id() == "fireball": if block.get_id() == "brick": self._world.remove_block(block) self._world.remove_mob(mob) return True def _handle_mob_collide_item(self, mob: Mob, block: Block, data, arbiter: pymunk.Arbiter) -> bool: return False def _handle_mob_collide_mob(self, mob1: Mob, mob2: Mob, data, arbiter: pymunk.Arbiter) -> bool: if mob1.get_id() == "fireball" or mob2.get_id() == "fireball": self._world.remove_mob(mob1) self._world.remove_mob(mob2) return False def _handle_player_collide_item(self, player: Player, dropped_item: DroppedItem, data, arbiter: pymunk.Arbiter) -> bool: """Callback to handle collision between the player and a (dropped) item. If the player has sufficient space in their to pick up the item, the item will be removed from the game world. Parameters: player (Player): The player that was involved in the collision dropped_item (DroppedItem): The (dropped) item that the player collided with data (dict): data that was added with this collision handler (see data parameter in World.add_collision_handler) arbiter (pymunk.Arbiter): Data about a collision (see http://www.pymunk.org/en/latest/pymunk.html#pymunk.Arbiter) NOTE: you probably won't need this Return: bool: False (always ignore this type of collision) (more generally, collision callbacks return True iff the collision should be considered valid; i.e. returning False makes the world ignore the collision) """ dropped_item.collect(self._player) self._world.remove_item(dropped_item) return False def _handle_player_collide_block(self, player: Player, block: Block, data, arbiter: pymunk.Arbiter) -> bool: block.on_hit(arbiter, (self._world, player)) return True def _handle_player_collide_mob(self, player: Player, mob: Mob, data, arbiter: pymunk.Arbiter) -> bool: mob.on_hit(arbiter, (self._world, player)) return True def _handle_player_separate_block(self, player: Player, block: Block, data, arbiter: pymunk.Arbiter) -> bool: return True def _load_config(self): try: with open('config.txt') as f: t = f.read() things = re.findall(r'(.*) : (.*)', t) print(things) for i, k in things: if (i == 'gravity'): self.gravity = int(k) if (i == "start"): self.level = k if (i == 'character'): self.character_name = k if (i == 'x'): self.character_x = int(k) if (i == 'y'): self.character_y = int(k) if (i == 'mass'): self.character_mass = int(k) if (i == 'max_velocity'): self.max_velocity = int(k) except: newTk = tk.Tk() ErrorWindow = tk.Frame(newTk) newTk.geometry('1080x500') ErrorWindow.pack() tk.Label( newTk, text='load config error\n please check the config.txt').pack( padx=200, pady=200) self._master.destroy() newTk.mainloop()
class MarioApp: """High-level app class for Mario, a 2d platformer""" def __init__(self, master): """Construct a new game of a MarioApp game. Parameters: master (tk.Tk): tkinter root widget """ self._master = master self._start_game() def donothing(): filewin = tk.Toplevel(self._master) button = tk.Button(filewin, text="Do nothing button") button.pack() menubar = tk.Menu(self._master) filemenu = tk.Menu(menubar, tearoff=0) filemenu.add_command(label="Load Level", command=self._retrieve_input) filemenu.add_command(label="Reset Level", command=lambda: self.reset_world(full=True)) filemenu.add_command(label="High Scores", command=self.display_high_scores) filemenu.add_command(label="Exit", command=self._ask_quit) menubar.add_cascade(label="File", menu=filemenu) self._master.config(menu=menubar) # Special Item: Star self._player_star = PlayerStar() # Special Block: switch self._switches = [] for i in self._world.get_all_things(): try: if i._id == "switch": self._switches.append(i) except: pass self._can_jump = False # Score self._scores = HighScores() # Wait for window to update before continuing master.update_idletasks() self.step() def _start_game(self): path = tk.simpledialog.askstring("Mario", "Configuration file:") self._game = loader.load_level(path) if self._game == None: tk.messagebox.showerror("Error", "Configuration file wrong.") self._master.destroy() # World builder world_builder = WorldBuilder( BLOCK_SIZE, gravity=(0, int(self._game["==World=="]["gravity"])), fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._current_level = self._game["==World=="]['start'] self._world = load_world(self._builder, self._current_level) # Set tunnel/goal Flagpole._next_level = self._game[("==" + self._current_level + "==")]["goal"] Tunnel._next_level = self._game[("==" + self._current_level + "==")].get("tunnel", None) self._player = Player( max_health=int(self._game["==Player=="]["health"])) print(self._player.get_shape()) self._world.add_player(self._player, int(self._game["==Player=="]["x"]), int(self._game["==Player=="]["y"]), int(self._game["==Player=="]["mass"])) self._max_speed = int(self._game["==Player=="]["max_velocity"]) self._setup_collision_handlers() self._renderer = AnimatedMarioViewRenderer(BLOCK_IMAGES, ITEM_IMAGES, MOB_IMAGES) # Into Animated size = tuple( map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(self._master, size, self._renderer) self._view.pack() self._status_view = StatusView(self._master, int(self._game["==Player=="]['health']), 0) self.bind() def reset_world(self, new_level=None, full=False): if new_level == None: new_level = self._current_level else: self._current_level = new_level self._scores = HighScores() world_builder = WorldBuilder(BLOCK_SIZE, gravity=(0, 500), fallback=create_unknown) world_builder.register_builders(BLOCKS.keys(), create_block) world_builder.register_builders(ITEMS.keys(), create_item) world_builder.register_builders(MOBS.keys(), create_mob) self._builder = world_builder self._world = load_world(self._builder, new_level) Flagpole._next_level = self._game[("==" + self._current_level + "==")]["goal"] Tunnel._next_level = self._game[("==" + self._current_level + "==")].get("tunnel", None) if full: # full reset player stats self._player = Player( max_health=int(self._game["==Player=="]["health"])) self._world.add_player(self._player, int(self._game["==Player=="]["x"]), int(self._game["==Player=="]["y"]), int(self._game["==Player=="]["mass"])) self._setup_collision_handlers() self._view.pack_forget() size = tuple( map(min, zip(MAX_WINDOW_SIZE, self._world.get_pixel_size()))) self._view = GameView(self._master, size, self._renderer) self._view.pack() self._status_view.unpack() self._status_view = StatusView(self._master, int(self._game["==Player=="]['health']), self._player._score) def display_high_scores(self): print(self._current_level, self._current_level.replace(".txt", "_score.txt")) top = tk.Toplevel() top.geometry("240x260") label = tk.Label(top, text="High Scores:") label.pack() if self._scores.load_scores( self._current_level.replace(".txt", "_score.txt")) == None: tk.Label(top, text="No high scores yet").pack() return for i in range(len(self._scores.get_top_10())): top10 = self._scores.get_top_10() tk.Label(top, text=(str(i + 1) + ': ' + str(top10[i][0]) + '\t\t' + str(top10[i][1]))).pack() def bind(self): """Bind all the keyboard events to their event handlers.""" self._master.bind("a", lambda e: self._move(-1, 0)) self._master.bind("<Left>", lambda e: self._move(-1, 0)) self._master.bind("d", lambda e: self._move(1, 0)) self._master.bind("<Right>", lambda e: self._move(1, 0)) self._master.bind("w", lambda e: self._jump()) self._master.bind("<Up>", lambda e: self._jump()) self._master.bind("<space>", lambda e: self._jump()) self._master.bind("s", lambda e: self._duck()) self._master.bind("<Down>", lambda e: self._duck()) def redraw(self): """Redraw all the entities in the game canvas.""" self._view.delete(tk.ALL) self._view.draw_entities(self._world.get_all_things()) def scroll(self): """Scroll the view along if the player is within SCROLL_RADIUS from the edge of the screen. """ # calculate the x distance from the right edge x_position = self._player.get_position()[0] x_offset = self._view.get_offset()[0] screen_size = self._master.winfo_width() edge_distance = screen_size - (x_position + x_offset) if edge_distance < SCROLL_RADIUS: x_position -= 5 # place a backstop boundary wall on the left side of the screen # to prevent the player from going backwards in the game world_space = self._world.get_space() wall = BoundaryWall("backstop", world_space.static_body, (x_position, 0), (x_position, self._world.get_pixel_size()[1]), 5) world_space.add(wall.get_shape()) # shift the view offset by the screen size self._view.shift((-(screen_size - SCROLL_RADIUS), 0)) def step(self): """Step the world physics and redraw the canvas.""" data = (self._world, self._player) self._world.step(data) self.scroll() self.redraw() self._player_star.tick() if not self._player_star.activated(): self._status_view.set_health(self._player.get_health()) for i in self._switches: i.tick() self._master.after(10, self.step) def _move(self, dx, dy): if dx < 0: self._renderer.set_player_facing(-1) elif dx > 0: self._renderer.set_player_facing(1) velocity = self._player.get_velocity() sign = lambda x: 1 if x >= 0 else -1 self._player.set_velocity((self._max_speed * sign(dx), velocity.y)) def _jump(self): x, y = self._player.get_position() y += BLOCK_SIZE if self._world.get_block(x, y) != None: self._can_jump = True if self._can_jump: velocity = self._player.get_velocity() self._player.set_velocity((velocity.x * 0.8, -200)) self._can_jump = False def _duck(self): x, y = self._player.get_position() y += BLOCK_SIZE if self._world.get_block(x, y) != None: if self._world.get_block(x, y)._id == "tunnel": if self._world.get_block(x, y)._next_level != None: self.reset_world(self._world.get_block(x, y)._next_level) def _setup_collision_handlers(self): self._world.add_collision_handler( "player", "item", on_begin=self._handle_player_collide_item) self._world.add_collision_handler( "player", "block", on_begin=self._handle_player_collide_block, on_separate=self._handle_player_separate_block) self._world.add_collision_handler( "player", "mob", on_begin=self._handle_player_collide_mob) self._world.add_collision_handler( "mob", "block", on_begin=self._handle_mob_collide_block) self._world.add_collision_handler( "mob", "mob", on_begin=self._handle_mob_collide_mob) self._world.add_collision_handler( "mob", "item", on_begin=self._handle_mob_collide_item) def _handle_mob_collide_block(self, mob: Mob, block: Block, data, arbiter: pymunk.Arbiter) -> bool: if mob.get_id() == "fireball": if block.get_id() == "brick": self._world.remove_block(block) self._world.remove_mob(mob) if mob.get_id() == "mushroom": if get_collision_direction(block, mob) in ("L", "R"): mob.direction *= -1 return True def _handle_mob_collide_item(self, mob: Mob, block: Block, data, arbiter: pymunk.Arbiter) -> bool: return False def _handle_mob_collide_mob(self, mob1: Mob, mob2: Mob, data, arbiter: pymunk.Arbiter) -> bool: if mob1.get_id() == "fireball" or mob2.get_id() == "fireball": self._world.remove_mob(mob1) self._world.remove_mob(mob2) return False def _handle_player_collide_item(self, player: Player, dropped_item: DroppedItem, data, arbiter: pymunk.Arbiter) -> bool: """Callback to handle collision between the player and a (dropped) item. If the player has sufficient space in their to pick up the item, the item will be removed from the game world. Parameters: player (Player): The player that was involved in the collision dropped_item (DroppedItem): The (dropped) item that the player collided with data (dict): data that was added with this collision handler (see data parameter in World.add_collision_handler) arbiter (pymunk.Arbiter): Data about a collision (see http://www.pymunk.org/en/latest/pymunk.html#pymunk.Arbiter) NOTE: you probably won't need this Return: bool: False (always ignore this type of collision) (more generally, collision callbacks return True iff the collision should be considered valid; i.e. returning False makes the world ignore the collision) """ dropped_item.collect(self._player) if dropped_item.get_id() == "star": self._player_star.activate() self._status_view.set_health_colour("yellow") self._world.remove_item(dropped_item) self._status_view.set_score(self._player.get_score()) return False def _handle_player_collide_block(self, player: Player, block: Block, data, arbiter: pymunk.Arbiter) -> bool: if get_collision_direction(player, block) == "A": self._can_jump = True if block._id == "bouncy": if get_collision_direction(player, block) == "A": self._renderer.activate_bouncy(block.get_shape()) elif block._id == "switch": if block.activated(): return False elif block._id == "flagpole": name = self._ask_name() score_name = self._current_level.replace(".txt", "_score.txt") self._scores.load_scores(score_name) self._scores.add_score(name, self._player.get_score()) self._scores.save_scores(score_name) self.reset_world(block._next_level) block.on_hit(arbiter, (self._world, player)) return True def _handle_player_collide_mob(self, player: Player, mob: Mob, data, arbiter: pymunk.Arbiter) -> bool: if self._player_star.activated(): self._world.remove_mob(mob) return False else: if mob._id == "mushroom": if mob.dead: return False if get_collision_direction(player, mob) == "A": self._renderer.kill_mushroom(mob.get_shape()) mob.freeze() self._master.after(500, lambda: self._world.remove_mob(mob)) self._can_jump = True self._jump() return True mob.on_hit(arbiter, (self._world, player)) self._status_view.set_health(self._player.get_health()) self._status_view.set_score(self._player.get_score()) if self._player.get_health() == 0: tk.messagebox.showinfo("Gameover", "You died.") self._master.quit() return True def _handle_player_separate_block(self, player: Player, block: Block, data, arbiter: pymunk.Arbiter) -> bool: return True def _retrieve_input(self): level = tk.simpledialog.askstring("Load Level", "Please input the level name:") if level: self.reset_world(new_level=level, full=True) def _ask_quit(self): if tk.messagebox.askokcancel("Quit", "You want to quit now?"): self._master.destroy() def _ask_name(self): name = tk.simpledialog.askstring("Good Job!", "Please enter your name:") return name