class Frostbeam(PlayerMove): """Class for representing the player's frostbeam move.""" def __init__(self): super().__init__(30) self.icon = Image("player/frostbeam_move_icon_solid.png") self.icon_faded = Image("player/frostbeam_move_icon_faded.png") self.info = Image("player/frostbeam_move_info.png") self.sound = Audio("player/frostbeam_move.ogg", 0.2) self.frostbeam_start = Image("player/frostbeam_start.png", (215, 381)) self.frostbeam_middle = Image("player/frostbeam_middle.png") self.frostbeam_end = Image("player/frostbeam_end.png") self.duration = 0 self.total_duration = 2 * self.game.fps def run(self): self.opponent.display() self.user.character_frostbeam_stance.display() if self.duration < self.total_duration: if self.duration == 0: self.play_sound() elif self.duration == self.total_duration // 2: self.opponent.damage(random.randint(15, 30)) self.frostbeam_start.display() for x in range(14): self.frostbeam_middle.display(265 + 50 * x, 383 + 2 * x) self.duration += 1 else: # Resetting variables for next time self.duration = 0 self.opponent.next_move()
class KanyeSnake(Opponent): """Class representing the Kanye Snake opponent.""" def __init__(self, game, max_hp=120, max_mana=120): super().__init__(game, "Kanye Snake", max_hp, max_mana) self.x = 930 self.y = 440 self.snake_normal = Image("kanye_snake/snake_normal.png", (self.x, self.y)) self.snake_dead = Image("kanye_snake/snake_dead.png", (self.x, self.y)) self.snake_backwards = Image("kanye_snake/snake_backwards.png") self.snake_moving = Image("kanye_snake/snake_moving.png") self.snake_venom_stance = Image("kanye_snake/snake_venom_stance.png", (self.x, self.y)) self.snake_laser_stance = Image("kanye_snake/snake_laser_stance.png", (self.x, self.y)) Moves = namedtuple("Moves", "heal confuse venom laser") self.moves = Moves(OpponentHeal(1005, 230, 410), Confuse(), Venom(), Laser()) def choose_move(self): """Return the move that the snake decides to use.""" if self.current_mana < 10: # Only usable move return self.moves.confuse if self.game.player.current_hp < 20: return random.choice(self.attack_options(favour_damage=True)) if self.current_hp < self.max_hp / 5: if random.randint(1, 10) == 1: return random.choice(self.attack_options()) return self.moves.heal options = self.attack_options() if self.current_hp <= 3 * (self.max_hp / 4): options.append(self.moves.heal) return random.choice(options) def attack_options(self, *, favour_damage=False): # pylint: disable=arguments-differ """Return the options the snake can/would choose from for attacking based on his mana.""" moves = super().attack_options() if len(moves) > 1 and favour_damage: moves.remove(self.moves.confuse) return moves def _idle_display(self): self.snake_normal.display() def _dead_display(self): self.snake_dead.display()
class MemeDog(Opponent): """Class representing the Meme Dog opponent.""" def __init__(self, game, max_hp=100, max_mana=100): super().__init__(game, "Meme Dog", max_hp, max_mana) self.x = 930 self.y = 440 self.dog_normal = Image("meme_dog/dog_normal.png", (self.x, self.y)) self.dog_dead = Image("meme_dog/dog_dead.png", (self.x, self.y)) self.dog_backwards = Image("meme_dog/dog_backwards.png") self.dog_bark_stance = Image("meme_dog/dog_bark_stance.png", (self.x, self.y)) Moves = namedtuple("Moves", "heal bark bite spin") self.moves = Moves(OpponentHeal(1005, 230, 410), Bark(), Bite(), Spin()) def choose_move(self): """Return the name of the next move that the dog decides to use.""" if self.current_mana < 10: # Only usable move return self.moves.bark if self.game.player.current_hp < 15: # Try to finish the player off return random.choice(self.attack_options()) if self.current_hp < self.max_hp / 4: # Low - prefer to heal but chance of attacking if random.randint(1, 10) == 1: return random.choice(self.attack_options()) return self.moves.heal options = self.attack_options() if self.current_hp <= 3 * (self.max_hp / 4): options.append(self.moves.heal) return random.choice(options) def _idle_display(self): self.dog_normal.display() def _dead_display(self): self.dog_dead.display()
class EvilCloud(Opponent): """Class representing the Evil Cloud opponent.""" def __init__(self, game, max_hp=180, max_mana=300): super().__init__(game, "Evil Cloud", max_hp, max_mana) self.x = 930 self.y = 400 self.normal = Image("evil_cloud/evil_cloud_normal.png", (self.x, self.y)) Moves = namedtuple("Moves", "heal") self.moves = Moves(OpponentHeal(1005, 230, 410)) def choose_move(self): """Return the move that the spook cloud decides to use.""" return self.moves.heal def _idle_display(self): self.normal.display() def _dead_display(self): pass
class Heal(PlayerMove): """Class for representing the player's heal move.""" def __init__(self, heart_x, start_y, end_y): super().__init__(10) self.icon = Image("player/heal_move_icon_solid.png") self.icon_faded = Image("player/heal_move_icon_faded.png") self.info = Image("player/heal_move_info.png") self.sound = Audio("player/heal_move.ogg", 0.1) self.heart = Image("player/heal_heart.png") self.heart_x = heart_x self.start_y = start_y self.end_y = end_y self.heart_y = self.start_y self.delay_duration = 0 @property def total_delay_duration(self): return self.user.display_stat_change_duration def run(self): self.user.display() self.opponent.display() if self.heart_y < self.end_y: if self.heart_y == self.start_y: self.play_sound() self.heart.display(self.heart_x, self.heart_y) self.heart_y += 5 elif self.delay_duration < self.total_delay_duration: # Allow time for stat change to show if self.delay_duration == 0: self.user.restore_hp(random.randint(5, 15)) self.delay_duration += 1 else: self.delay_duration = 0 self.heart_y = self.start_y self.opponent.next_move()
class SpookDog(Opponent): """Class representing the Spook Dog opponent.""" def __init__(self, game, max_hp=200, max_mana=150): super().__init__(game, "Spook Dog", max_hp, max_mana) self.x = 930 self.y = 440 self.num_idle_frames = 20 self.idle_fps = 20 self.idle_frames = [ Image(f"spook_dog/ghost_dog_normal{n}.png") for n in range(self.num_idle_frames) ] self.ghost_dog_dead = Image("spook_dog/ghost_dog_dead.png", (self.x, self.y)) Moves = namedtuple("Moves", "heal teleport glide claw") self.moves = Moves(OpponentHeal(1005, 230, 410), Teleport(), Glide(), Claw()) def choose_move(self): # pylint: disable=too-many-branches,too-many-return-statements """Return the move that the ghost dog decides to use.""" if self.current_mana < 10: # Only usable move return self.moves.teleport if self.game.player.current_hp <= 10 and self.current_mana >= self.moves.claw.mana_cost: return self.moves.claw if self.game.player.current_hp <= 20: return self.moves.glide if self.game.player.current_hp <= 30: if self.current_mana < 50: options = {self.moves.teleport: 3, self.moves.glide: 6} elif self.current_mana <= 140: options = { self.moves.teleport: 1, self.moves.glide: 2, self.moves.claw: 6 } else: options = { self.moves.glide: 2.3333333333, self.moves.claw: 6.6666666667 } if self.current_hp <= 180: options[self.moves.heal] = 1 return self.random_weighted(options) if self.current_hp < 25: if self.current_mana < 50: return self.random_weighted({ self.moves.teleport: 0.1, self.moves.glide: 0.1, self.moves.heal: 0.8 }) if self.game.player.current_hp <= 40: return random.choice([self.moves.claw, self.moves.heal]) if random.random() < 0.1: return random.choice(self.attack_options()) return self.moves.heal options = self.attack_options() if self.current_hp <= 0.9 * self.max_hp: options.append(self.moves.heal) return random.choice(options) def _idle_display(self): self.idle_animation(self.x, self.y) def _dead_display(self): self.ghost_dog_dead.display()
class Player(Character): """Class representing the Player (the character controlled by the user).""" CHARACTER_1 = "character1" CHARACTER_2 = "character2" CHOOSE_CHARACTER = "choose character" CHOOSE_ABILITY = "choose ability" DEAD = "player dead" INFO_X = 10 def __init__(self, game, name="Sunni", character=None, *, level=1): super().__init__(game, name, level=level, display_stat_x=170, display_stat_y_start=360) self.calculate_stats() self.fully_restore() self.x = 150 self.y = 380 self.num_idle_frames = 4 self.idle_fps = 6 self.character = character Moves = namedtuple("Moves", "heal kick headbutt frostbeam") self.moves = Moves(Heal(160, 170, 350), Kick(), Headbutt(), Frostbeam()) self.offensive_moves = [ self.moves.kick, self.moves.headbutt, self.moves.frostbeam ] self.defensive_moves = [self.moves.heal] self.selected_moves = None @property def character(self): return self._character @character.setter def character(self, character): self._character = character if character is None: return self.idle_frames = [ Image(f"player/{character}_normal{n}.png") for n in range(self.num_idle_frames) ] self.character_normal = Image(f"player/{character}_normal1.png") self.character_backwards = Image(f"player/{character}_backwards.png") self.character_scared = Image(f"player/{character}_scared.png", (self.x, self.y)) self.character_scared_redflash = Image( f"player/{character}_scared_redflash.png", (self.x, self.y)) self.character_tilt_left = Image(f"player/{character}_tilt_left.png") self.character_tilt_right = Image(f"player/{character}_tilt_right.png") self.character_dead = Image(f"player/{character}_dead.png") self.character_headbutt_stance = Image( f"player/{character}_headbutt_stance.png") self.character_frostbeam_stance = Image( f"player/{character}_frostbeam_stance.png", (self.x, self.y)) def level_up(self, levels=1.0, restore=True): """Level the player up by the given number of levels (default 1). Restores the player to full if they pass an integer level and `restore==True` (default). """ old_level = self.level self.level += levels if int(self.level) > int(old_level): # i.e. if we actually levelled up self.calculate_stats() if restore: self.fully_restore() def calculate_stats(self): self.max_hp = 90 + 10 * int(self.level) self.max_mana = 95 + 5 * int(self.level) def use_move(self, move): try: self.change_mana(move) except NotEnoughManaError: self.game.page.show_mana_notification() else: self.selected_moves = None self.game.page.hide_mana_notification() self.game.page.current = move def next_move(self): """Continues to find out the player's next move.""" if self.is_dead: self.game.page.current = self.DEAD self.level_up(0.25, restore=False) self.game.save() return self.game.page.current = self.CHOOSE_ABILITY def _idle_display(self): self.idle_animation(self.x, self.y) def _dead_display(self): self.character_dead.display(150, 480)