diff --git a/data/text/dialog.py b/data/text/dialog.py index 9c1c682..0ea1c1e 100644 --- a/data/text/dialog.py +++ b/data/text/dialog.py @@ -4,66 +4,71 @@ def confirmation_prompt(command_menu, prompt_line, yes_path_function, no_path_function, config, show_arrow, color, finally_function=None, skip_text=False): - command_menu.show_line_in_dialog_box(prompt_line, skip_text=True, hide_arrow=True, letter_by_letter=True) - command_menu.window_drop_down_effect(5, 2, 4, 3) + def display_prompt(): + command_menu.show_line_in_dialog_box(prompt_line, skip_text=True, hide_arrow=True, letter_by_letter=True) + command_menu.window_drop_down_effect(5, 2, 4, 3) + window_surface = graphics.create_window(5, 2, 4, 3, directories.CONFIRMATION_BACKGROUND_PATH, + command_menu.screen, color) + display.update(window_surface.get_rect()) + # for some reason it needs this wait() call to actually play the sound + if not config['NO_WAIT']: + time.wait(300) + sound.play_sound(directories.confirmation_sfx) + + def update_blinking_graphics(blinking_yes, show_arrow): + background_path = directories.CONFIRMATION_YES_BACKGROUND_PATH if blinking_yes else directories.CONFIRMATION_NO_BACKGROUND_PATH + graphics.blink_switch(command_menu.screen, background_path, directories.CONFIRMATION_BACKGROUND_PATH, + x=5, y=2, width=4, height=3, tile_size=tile_size, color=color, show_arrow=show_arrow) + + def handle_keydown_event(current_event, blinking_yes): + nonlocal blinking + if current_event.key in (K_DOWN, K_UP, K_w, K_s): + return not blinking_yes + elif (blinking_yes and current_event.unicode in ('\r', 'k')) or current_event.unicode == 'y': + finalize_choice(directories.CONFIRMATION_YES_BACKGROUND_PATH, yes_path_function) + blinking = False + elif (not blinking_yes and current_event.unicode in ('\r', 'k')) or current_event.unicode in ('n', 'j'): + finalize_choice(directories.CONFIRMATION_NO_BACKGROUND_PATH, no_path_function) + blinking = False + return blinking_yes + + def finalize_choice(background_path, path_function): + graphics.create_window(5, 2, 4, 3, background_path, command_menu.screen, color) + sound.play_sound(directories.menu_button_sfx) + event.pump() + path_function() + + def handle_events(blinking_yes): + nonlocal show_arrow + for current_event in get(): + if current_event.type == KEYDOWN: + blinking_yes = handle_keydown_event(current_event, blinking_yes) + elif current_event.type == arrow_fade: + show_arrow = not show_arrow + event.pump() + return blinking_yes + + # Initialization graphics = command_menu.graphics directories = command_menu.directories sound = command_menu.sound - window_surface = graphics.create_window(5, 2, 4, 3, directories.CONFIRMATION_BACKGROUND_PATH, command_menu.screen, - color) - display.update(window_surface.get_rect()) - # for some reason it needs this wait() call to actually play the sound - if not config['NO_WAIT']: - time.wait(300) + tile_size = config["TILE_SIZE"] + + display_prompt() - sound.play_sound(directories.confirmation_sfx) blinking = True blinking_yes = True arrow_fade = USEREVENT + 1 time.set_timer(arrow_fade, 530) + while blinking: - tile_size = config["TILE_SIZE"] - if blinking_yes and not config['NO_WAIT']: - graphics.blink_switch(command_menu.screen, directories.CONFIRMATION_YES_BACKGROUND_PATH, - directories.CONFIRMATION_BACKGROUND_PATH, - x=5, y=2, width=4, height=3, tile_size=tile_size, color=color, show_arrow=show_arrow) - else: - graphics.blink_switch(command_menu.screen, directories.CONFIRMATION_NO_BACKGROUND_PATH, - directories.CONFIRMATION_BACKGROUND_PATH, - x=5, y=2, - width=4, height=3, tile_size=tile_size, color=color, show_arrow=show_arrow) if skip_text: sound.play_sound(directories.menu_button_sfx) yes_path_function() blinking = False - for current_event in get(): - if current_event.type == KEYDOWN: - if current_event.key in (K_DOWN, K_UP, K_w, K_s): - if blinking_yes: - graphics.create_window(5, 2, 4, 3, directories.CONFIRMATION_NO_BACKGROUND_PATH, - command_menu.screen, color) - blinking_yes = False - else: - graphics.create_window(5, 2, 4, 3, directories.CONFIRMATION_YES_BACKGROUND_PATH, - command_menu.screen, color) - blinking_yes = True - elif (blinking_yes and current_event.unicode in ('\r', 'k')) or current_event.unicode == 'y': - graphics.create_window(5, 2, 4, 3, directories.CONFIRMATION_YES_BACKGROUND_PATH, - command_menu.screen, color) - sound.play_sound(directories.menu_button_sfx) - event.pump() - yes_path_function() - blinking = False - elif (not blinking_yes and current_event.unicode in ('\r', 'k')) or current_event.unicode in ('n', 'j'): - graphics.create_window(5, 2, 4, 3, directories.CONFIRMATION_NO_BACKGROUND_PATH, command_menu.screen, - color) - sound.play_sound(directories.menu_button_sfx) - event.pump() - no_path_function() - blinking = False - elif current_event.type == arrow_fade: - show_arrow = not show_arrow - event.pump() + else: + update_blinking_graphics(blinking_yes, show_arrow) + blinking_yes = handle_events(blinking_yes) if finally_function is not None: finally_function() diff --git a/src/battle.py b/src/battle.py index 2a2a8e5..57b515d 100644 --- a/src/battle.py +++ b/src/battle.py @@ -13,6 +13,13 @@ from src.player.player_stats import levels_list from src.sound import Sound +# Constants for probabilities and thresholds +EXCELLENT_MOVE_PROBABILITY = 0 +EXCELLENT_MOVE_THRESHOLD = 31 +GROUP_FACTOR_LOOKUP = {1: 0.25, 2: 0.375, 3: 0.5, 4: 1.0} +MISS_SFX_PROBABILITY = 0.5 +MIN_DAMAGE = 1 + def has_final_consonant(char: str) -> bool: """Check if a Korean character has a final consonant.""" @@ -38,7 +45,7 @@ def __init__(self, config, enemy_name: str, current_map: DragonWarriorMap): self.config = config self.directories = Directories(config) self.sound = Sound(config) - self._ = _ = set_gettext_language(config['LANGUAGE']) + self._ = set_gettext_language(config['LANGUAGE']) self.turn = 0 self.last_turn = 0 self.enemy = enemy_string_lookup[enemy_name]() @@ -53,18 +60,9 @@ def play_battle_music(self): def transition_battle_background_image_effect(self, screen): """Spiral effect to introduce battle background.""" - if not self.current_map.is_dark: - battle_background_image = scale(image.load(self.directories.BATTLE_BACKGROUND_PATH), - (7 * self.tile_size, 7 * self.tile_size)) - else: - black_surface = Surface((7 * self.tile_size, 7 * self.tile_size)) - black_surface.fill(BLACK) - battle_background_image = black_surface - spiral_tile_coordinates = ((3, 3), (3, 4), (2, 4), (2, 3), (2, 2), (3, 2), (4, 2), (4, 3), (4, 4), (4, 5), - (3, 5), (2, 5), (1, 5), (1, 4), (1, 3), (1, 2), (1, 1), (2, 1), (3, 1), (4, 1), - (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (5, 6), (4, 6), (3, 6), (2, 6), (1, 6), - (0, 6), (0, 5), (0, 4), (0, 3), (0, 2), (0, 1), (0, 0), (1, 0), (2, 0), (3, 0), - (4, 0), (5, 0), (6, 0), (6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6)) + battle_background_image = self.get_battle_background_image() + spiral_tile_coordinates = self.get_spiral_tile_coordinates() + for tile in spiral_tile_coordinates: screen.blit(battle_background_image.subsurface( (tile[0] * self.tile_size, tile[1] * self.tile_size, self.tile_size, self.tile_size)), @@ -72,131 +70,117 @@ def transition_battle_background_image_effect(self, screen): display.update() time.wait(20) + def get_battle_background_image(self): + """Get the appropriate battle background image.""" + if not self.current_map.is_dark: + return scale(image.load(self.directories.BATTLE_BACKGROUND_PATH), (7 * self.tile_size, 7 * self.tile_size)) + black_surface = Surface((7 * self.tile_size, 7 * self.tile_size)) + black_surface.fill(BLACK) + return black_surface + + @staticmethod + def get_spiral_tile_coordinates(): + """Return the coordinates for the spiral effect.""" + return ( + (3, 3), (3, 4), (2, 4), (2, 3), (2, 2), (3, 2), (4, 2), (4, 3), (4, 4), (4, 5), + (3, 5), (2, 5), (1, 5), (1, 4), (1, 3), (1, 2), (1, 1), (2, 1), (3, 1), (4, 1), + (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (5, 6), (4, 6), (3, 6), (2, 6), (1, 6), + (0, 6), (0, 5), (0, 4), (0, 3), (0, 2), (0, 1), (0, 0), (1, 0), (2, 0), (3, 0), + (4, 0), (5, 0), (6, 0), (6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6) + ) + def battle_run(self, cmd_menu: CommandMenu, player: Player, current_battle): - """Attempt to run from a battle. The formula is as follows: - If HeroAgility * Random # < EnemyAgility * Random # * GroupFactor, then the - enemy will block you. (according to https://gamefaqs.gamespot.com/nes/563408-dragon-warrior/faqs/61640)""" + """Attempt to run from a battle.""" self.sound.play_sound(self.directories.stairs_down_sfx) cmd_menu.show_line_in_dialog_box(self._("{} started to run away.\n").format(player.name), add_quotes=False, hide_arrow=True, disable_sound=True) random_number = random.randint(0, 255) - group_factor = 1 - group_factor_lookup = { - 1: 0.25, - 2: 0.375, - 3: 0.5, - 4: 1.0, - } - for group_number, group in enemy_groups.items(): - if current_battle.enemy.name in group: - group_factor = group_number + group_factor = self.get_group_factor(current_battle.enemy.name) + if current_battle.enemy.is_asleep: return True - else: - if (player.agility * random_number < - current_battle.enemy.speed * random_number * group_factor_lookup[group_factor]): - cmd_menu.show_line_in_dialog_box(self._("But was blocked in front.").format(current_battle.enemy.name), - add_quotes=False, - hide_arrow=True, disable_sound=True) - cmd_menu.game.enemy_move(current_battle) - if player.current_hp <= 0: - player.is_dead = True - else: - cmd_menu.show_line_in_dialog_box(self._("Command?\n"), add_quotes=False, hide_arrow=True, - disable_sound=True) - return False + + if player.agility * random_number < current_battle.enemy.speed * random_number * GROUP_FACTOR_LOOKUP[ + group_factor]: + cmd_menu.show_line_in_dialog_box(self._("But was blocked in front.").format(current_battle.enemy.name), + add_quotes=False, hide_arrow=True, disable_sound=True) + cmd_menu.game.enemy_move(current_battle) + if player.current_hp <= 0: + player.is_dead = True else: - return True + cmd_menu.show_line_in_dialog_box(self._("Command?\n"), add_quotes=False, hide_arrow=True, + disable_sound=True) + return False + return True + + @staticmethod + def get_group_factor(enemy_name): + """Get the group factor for a given enemy.""" + for group_number, group in enemy_groups.items(): + if enemy_name in group: + return group_number + return 1 def missed_attack(self, cmd_menu): + """Handle a missed attack.""" missed_sfx_number = random.randint(1, 2) - if missed_sfx_number == 1: - self.sound.play_sound(self.directories.missed_sfx) - else: - self.sound.play_sound(self.directories.missed_2_sfx) + missed_sound = self.directories.missed_sfx if missed_sfx_number == 1 else self.directories.missed_2_sfx + self.sound.play_sound(missed_sound) cmd_menu.show_line_in_dialog_box(self._("A miss! No damage hath been scored!"), add_quotes=False, disable_sound=True, hide_arrow=True) def calculate_attack_damage(self, cmd_menu, player, enemy): - excellent_move_probability = random.randint(0, 31) - if excellent_move_probability == 0 and enemy.name not in ('Dragonlord', 'Dragonlord 2'): + """Calculate the damage of an attack.""" + if random.randint(0, EXCELLENT_MOVE_THRESHOLD) == EXCELLENT_MOVE_PROBABILITY and enemy.name not in ( + 'Dragonlord', 'Dragonlord 2'): self.sound.play_sound(self.directories.excellent_move_sfx) cmd_menu.show_line_in_dialog_box(self._("Excellent move!\n"), add_quotes=False, disable_sound=True) - attack_damage = random.randint(player.attack_power // 2, - player.attack_power) - else: - # (HeroAttack - EnemyAgility / 2) / 4, - # - # to: - # - # (HeroAttack - EnemyAgility / 2) / 2 - lower_bound = round((player.attack_power - (enemy.defense / 2)) / 2) - upper_bound = round((player.attack_power - (enemy.defense / 2)) * 2) - # print(f'lower_bound: {lower_bound}\n' - # f'upper_bound: {upper_bound}\n' - # f'player.attack_power: {player.attack_power}\n' - # f'enemy.speed: {enemy.defense}') - attack_damage = select_random_attack_damage_value(lower_bound, upper_bound) - return round(attack_damage) + return round(random.randint(player.attack_power // 2, player.attack_power)) + + lower_bound = round((player.attack_power - (enemy.defense / 2)) / 2) + upper_bound = round((player.attack_power - (enemy.defense / 2)) * 2) + return round(select_random_attack_damage_value(lower_bound, upper_bound)) def battle_spell(self, cmd_menu: CommandMenu, player: Player, current_battle): + """Handle casting a spell in battle.""" self.sound.play_sound(self.directories.menu_button_sfx) - # the implementation of this will vary upon which spell is being cast. if not player.spells: cmd_menu.show_line_in_dialog_box(self._("{} cannot yet use the spell.").format(player.name), - skip_text=cmd_menu.skip_text, - add_quotes=False, - hide_arrow=True, + skip_text=cmd_menu.skip_text, add_quotes=False, hide_arrow=True, disable_sound=True) current_battle.no_op = True - else: cmd_menu.display_item_menu('spells') - # cmd_menu.show_line_in_dialog_box(_("{} cannot yet use the spell.").format(player.name) + "\n" + - # _("Command?\n"), add_quotes=False, - # hide_arrow=True, - # disable_sound=True, skip_text=True) def enemy_defeated(self, cmd_menu, screen, player, music_enabled, enemy): - if self.config['LANGUAGE'] == 'Korean': - ko_enemy_name = self._(enemy.name) - ko_enemy_name += get_postposition(ko_enemy_name, "을", "를") - enemy_defeated_string = f"{ko_enemy_name} 물리쳤다!\n" - elif self.config['LANGUAGE'] == 'English': - enemy_defeated_string = self._("Thou hast done well in defeating the {}.\n").format(self._(enemy.name)) - else: - enemy_defeated_string = self._("Thou hast done well in defeating the {}.\n").format(self._(enemy.name)) - cmd_menu.show_line_in_dialog_box(enemy_defeated_string, add_quotes=False, - disable_sound=True, hide_arrow=True) + """Handle an enemy being defeated.""" + enemy_defeated_string = self.get_enemy_defeated_string(enemy) + cmd_menu.show_line_in_dialog_box(enemy_defeated_string, add_quotes=False, disable_sound=True, hide_arrow=True) mixer.music.stop() self.sound.play_sound(self.directories.victory_sfx) self.make_enemy_image_disappear(screen) exp_and_gold = self._("Thy experience increases by {}.\n").format(enemy.xp) + self._( - "Thy GOLD increases by {}.\n").format( - enemy.gold) - cmd_menu.show_line_in_dialog_box(exp_and_gold, - add_quotes=False, - disable_sound=True, - hide_arrow=True) + "Thy GOLD increases by {}.\n").format(enemy.gold) + cmd_menu.show_line_in_dialog_box(exp_and_gold, add_quotes=False, disable_sound=True, hide_arrow=True) player.total_experience += enemy.xp player.gold += enemy.gold + self.check_player_level_up(cmd_menu, player, music_enabled) - if player.level + 1 < 30 and \ - player.total_experience >= levels_list[player.level + 1]['total_exp']: + def get_enemy_defeated_string(self, enemy): + """Get the enemy defeated string based on language.""" + if self.config['LANGUAGE'] == 'Korean': + ko_enemy_name = self._(enemy.name) + ko_enemy_name += get_postposition(ko_enemy_name, "을", "를") + return f"{ko_enemy_name} 물리쳤다!\n" + return self._("Thou hast done well in defeating the {}.\n").format(self._(enemy.name)) + + def check_player_level_up(self, cmd_menu, player, music_enabled): + """Check if the player levels up.""" + if player.level + 1 < 30 and player.total_experience >= levels_list[player.level + 1]['total_exp']: self.sound.play_sound(self.directories.improvement_sfx) time.wait(2000) - if self.config['LANGUAGE'] == 'English': - cmd_menu.show_line_in_dialog_box("Courage and wit have served thee well.\n" - "Thou hast been promoted to the next level.\n", add_quotes=False, - disable_sound=True) - elif self.config['LANGUAGE'] == 'Korean': - cmd_menu.show_line_in_dialog_box(f"{player.name}은 {player.level + 1}레벨로 올랐다!", add_quotes=False) - old_power = player.strength - old_agility = player.agility - old_max_hp = player.max_hp - old_max_mp = player.max_mp - old_spells = player.spells - + self.display_level_up_message(cmd_menu, player) + old_stats = self.get_player_stats(player) player.level += 1 player.set_stats_by_level(player.level) player.update_attack_power() @@ -205,63 +189,76 @@ def enemy_defeated(self, cmd_menu, screen, player, music_enabled, enemy): if music_enabled: mixer.music.load(self.current_map.music_file_path) mixer.music.play(-1) + self.display_stat_increases(cmd_menu, player, old_stats) - if player.strength > old_power: - cmd_menu.show_line_in_dialog_box( - self._("Thy power increases by {}.\n").format(player.strength - old_power), - add_quotes=False, disable_sound=True) - if player.agility > old_agility: - cmd_menu.show_line_in_dialog_box( - self._("Thy Response Speed increases by {}.\n").format(player.agility - old_agility), - add_quotes=False, - disable_sound=True) - if player.max_hp > old_max_hp: - cmd_menu.show_line_in_dialog_box( - self._("Thy Maximum Hit Points increase by {}.\n").format(player.max_hp - old_max_hp), - add_quotes=False, - disable_sound=True) - if player.max_mp > old_max_mp: - cmd_menu.show_line_in_dialog_box( - self._("Thy Maximum Magic Points increase by {}.\n").format(player.max_mp - old_max_mp), - add_quotes=False, - disable_sound=True) - if len(player.spells) > len(old_spells): - cmd_menu.show_line_in_dialog_box("Thou hast learned a new spell.\n", add_quotes=False, - disable_sound=True) + def display_level_up_message(self, cmd_menu, player): + """Display the level up message.""" + if self.config['LANGUAGE'] == 'English': + cmd_menu.show_line_in_dialog_box( + "Courage and wit have served thee well.\nThou hast been promoted to the next level.\n", + add_quotes=False, disable_sound=True) + elif self.config['LANGUAGE'] == 'Korean': + cmd_menu.show_line_in_dialog_box(f"{player.name}은 {player.level + 1}레벨로 올랐다!", add_quotes=False) + + @staticmethod + def get_player_stats(player): + """Get the current stats of the player.""" + return { + "strength": player.strength, + "agility": player.agility, + "max_hp": player.max_hp, + "max_mp": player.max_mp, + "spells": player.spells.copy() + } + + def display_stat_increases(self, cmd_menu, player, old_stats): + """Display the increases in player's stats.""" + if player.strength > old_stats["strength"]: + cmd_menu.show_line_in_dialog_box( + self._("Thy power increases by {}.\n").format(player.strength - old_stats["strength"]), + add_quotes=False, disable_sound=True) + if player.agility > old_stats["agility"]: + cmd_menu.show_line_in_dialog_box( + self._("Thy Response Speed increases by {}.\n").format(player.agility - old_stats["agility"]), + add_quotes=False, disable_sound=True) + if player.max_hp > old_stats["max_hp"]: + cmd_menu.show_line_in_dialog_box( + self._("Thy Maximum Hit Points increase by {}.\n").format(player.max_hp - old_stats["max_hp"]), + add_quotes=False, disable_sound=True) + if player.max_mp > old_stats["max_mp"]: + cmd_menu.show_line_in_dialog_box( + self._("Thy Maximum Magic Points increase by {}.\n").format(player.max_mp - old_stats["max_mp"]), + add_quotes=False, disable_sound=True) + if len(player.spells) > len(old_stats["spells"]): + cmd_menu.show_line_in_dialog_box("Thou hast learned a new spell.\n", add_quotes=False, disable_sound=True) def make_enemy_image_disappear(self, screen): - if self.current_map.is_dark: - black_surface = Surface((7 * self.tile_size, 7 * self.tile_size)) - black_surface.fill(BLACK) - battle_background_image = black_surface - else: - battle_background_image = scale(image.load(self.directories.BATTLE_BACKGROUND_PATH), - (7 * self.tile_size, 7 * self.tile_size)) + """Make the enemy image disappear from the screen.""" + battle_background_image = self.get_battle_background_image() screen.blit(battle_background_image, (5 * self.tile_size, 4 * self.tile_size)) display.update(battle_background_image.get_rect()) def get_enemy_draws_near_string(self): - """Get string for when an enemy draws near (e.g. 'A Slime draws near!')""" + """Get string for when an enemy draws near.""" if self.enemy.name == 'Dragonlord 2': - enemy_draws_near_string = 'The Dragonlord revealed his true self!\n' - else: - if self.config['LANGUAGE'] == 'English': - enemy_draws_near_string = self._('{} draws near!\n').format(self._(self.enemy.name)) - vowels = 'AEIOU' - if self.enemy.name[0] in vowels: - enemy_draws_near_string = self._('An {}').format(enemy_draws_near_string) - else: - enemy_draws_near_string = self._('A {}').format(enemy_draws_near_string) - elif self.config['LANGUAGE'] == 'Korean': - ko_enemy_name = self._(self.enemy.name) - ko_enemy_name += get_postposition(ko_enemy_name, "이", "가") - enemy_draws_near_string = self._('{} draws near!\n').format(ko_enemy_name) - else: - enemy_draws_near_string = self._('{} draws near!\n') - enemy_draws_near_string += self._("Command?\n") - return enemy_draws_near_string + return 'The Dragonlord revealed his true self!\n' + self._("Command?\n") + + if self.config['LANGUAGE'] == 'English': + enemy_draws_near_string = self._('{} draws near!\n').format(self._(self.enemy.name)) + vowels = 'AEIOU' + if self.enemy.name[0] in vowels: + return self._('An {}').format(enemy_draws_near_string) + self._("Command?\n") + return self._('A {}').format(enemy_draws_near_string) + self._("Command?\n") + + if self.config['LANGUAGE'] == 'Korean': + ko_enemy_name = self._(self.enemy.name) + ko_enemy_name += get_postposition(ko_enemy_name, "이", "가") + return self._('{} draws near!\n').format(ko_enemy_name) + self._("Command?\n") + + return self._('{} draws near!\n').format(self._(self.enemy.name)) + self._("Command?\n") def display_battle_window(self, screen, drawer, cmd_menu, graphics, directories, color, player): + """Display the battle window.""" self.transition_battle_background_image_effect(screen) drawer.show_enemy_image(screen, self.enemy.name) cmd_menu.show_line_in_battle_dialog_box(self.get_enemy_draws_near_string()) @@ -275,26 +272,21 @@ def display_battle_window(self, screen, drawer, cmd_menu, graphics, directories, def select_random_attack_damage_value(lower_bound: int, upper_bound: int) -> int: - if not isinstance(lower_bound, int): - lower_bound = int(lower_bound) - if not isinstance(upper_bound, int): - upper_bound = int(upper_bound) + """Select a random attack damage value within the given bounds.""" + lower_bound, upper_bound = int(lower_bound), int(upper_bound) if lower_bound > upper_bound: attack_damage = random.randint(upper_bound, lower_bound) elif lower_bound == upper_bound: attack_damage = lower_bound else: attack_damage = random.randint(lower_bound, upper_bound) - if attack_damage < 1: - # fifty-fifty chance of doing 1 damage - if random.random() < .5: - attack_damage = 1 - else: - attack_damage = 0 + if attack_damage < MIN_DAMAGE: + return MIN_DAMAGE if random.random() < MISS_SFX_PROBABILITY else 0 return attack_damage def calculate_enemy_attack_damage(player, enemy): + """Calculate the damage of an enemy's attack.""" lower_bound = (enemy.attack - player.agility / 2) // 4 upper_bound = (enemy.attack - player.agility / 2) // 2 return select_random_attack_damage_value(lower_bound, upper_bound) diff --git a/tests/test_game.py b/tests/test_game.py index 60d1ff3..4b19ae5 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -55,7 +55,17 @@ def setup_roaming_character(row, column, direction): class TestGame(TestCase): - def setUp(self) -> None: + @classmethod + def setUpClass(cls): + pygame.init() + pygame.mixer.init() + cls.screen = pygame.display.set_mode((800, 600)) + + @classmethod + def tearDownClass(cls): + pygame.quit() + + def setUp(self): test_config['NO_WAIT'] = True test_config['RENDER_TEXT'] = False test_config['NO_BLIT'] = True @@ -65,17 +75,14 @@ def setUp(self) -> None: self.game.cmd_menu.dialog_lookup = DialogLookup(self.game.cmd_menu, self.game.game_state.config) self.game.current_map = MockMap(self.game.config) unarmed_hero_sheet = load_extended(self.game.directories.UNARMED_HERO_PATH) - self.game.player = Player((0, 0), parse_animated_sprite_sheet(scale(unarmed_hero_sheet, - (unarmed_hero_sheet.get_width() * self.game.config['SCALE'], - unarmed_hero_sheet.get_height() * self.game.config['SCALE'])), - self.game.game_state.config), - self.game.current_map, god_mode=self.game.game_state.config['GOD_MODE']) - # self.camera = Camera(self.game.current_map, self.initial_hero_location, speed=2) + self.game.player = Player((0, 0), parse_animated_sprite_sheet( + scale(unarmed_hero_sheet, ( + unarmed_hero_sheet.get_width() * self.game.config['SCALE'], + unarmed_hero_sheet.get_height() * self.game.config['SCALE'])), + self.game.game_state.config), self.game.current_map, god_mode=self.game.game_state.config['GOD_MODE']) tile_size = self.game.game_state.config['TILE_SIZE'] - self.camera = Camera((self.game.player.rect.y // tile_size, - self.game.player.rect.x // tile_size), - self.game.current_map, - self.game.screen, tile_size) + self.camera = Camera((self.game.player.rect.y // tile_size, self.game.player.rect.x // tile_size), + self.game.current_map, self.screen, tile_size) def test_get_initial_camera_position(self): self.assertEqual((288.0, 256.0), self.camera.pos) @@ -211,7 +218,8 @@ def test_convert_numeric_tile_list_to_unique_tile_values(self): convert_numeric_tile_list_to_unique_tile_values(self.game.current_map, [1, 2, 3, 4, 5, 6, 7, 8, 9])) - def test_handle_menu_launch(self): + @patch('pygame.mixer.music') + def test_handle_menu_launch(self, mock_music): self.game.cmd_menu.launch_signaled = True self.game.drawer.handle_menu_launch(self.game.screen, self.game.cmd_menu, self.game.cmd_menu) self.assertTrue(self.game.cmd_menu.menu.is_enabled()) @@ -505,22 +513,27 @@ def test_move_and_handle_sides_collision(self): self.assertEqual(64, self.game.player.rect.y) def test_move_player_up(self): + """Test moving the player up.""" pygame.key.get_pressed = create_move_player_key_mock(pygame.K_w) self.game.current_map.player_sprites = LayeredDirty(self.game.player) self.assertEqual(self.game.player.rect.y, -16) self.game.move_player(pygame.key.get_pressed()) + # I'm not sure why this is -18, need to investigate self.assertEqual(self.game.player.rect.y, -18) self.assertEqual(Direction.UP.value, self.game.player.direction_value) def test_move_player_left(self): + """Test moving the player left.""" pygame.key.get_pressed = create_move_player_key_mock(pygame.K_a) self.game.current_map.player_sprites = LayeredDirty(self.game.player) - self.assertEqual(self.game.player.rect.x, -16) + initial_x = self.game.player.rect.x self.game.move_player(pygame.key.get_pressed()) - self.assertEqual(self.game.player.rect.x, 0) + self.assertEqual(initial_x + 16, self.game.player.rect.x) self.assertEqual(Direction.LEFT.value, self.game.player.direction_value) def test_move_player_down(self): + """Test moving the player down. + The player should not move if there is an impassable object in the way.""" pygame.key.get_pressed = create_move_player_key_mock(pygame.K_s) self.game.current_map.player_sprites = LayeredDirty(self.game.player) self.assertEqual(self.game.player.rect.y, -16) @@ -530,11 +543,12 @@ def test_move_player_down(self): self.assertEqual(Direction.DOWN.value, self.game.player.direction_value) def test_move_player_right(self): + """Test moving the player right.""" pygame.key.get_pressed = create_move_player_key_mock(pygame.K_d) self.game.current_map.player_sprites = LayeredDirty(self.game.player) - self.assertEqual(self.game.player.rect.x, -16) + initial_x = self.game.player.rect.x self.game.move_player(pygame.key.get_pressed()) - self.assertEqual(self.game.player.rect.x, 0) + self.assertEqual(self.game.player.rect.x, initial_x + 16) self.assertEqual(Direction.RIGHT.value, self.game.player.direction_value) @patch('src.game.Game.set_screen')