def find_best_move(self): state = self.create_state() my_options = self.get_all_options()[0] moves = [] switches = [] for option in my_options: if option.startswith(constants.SWITCH_STRING + " "): switches.append(option) else: moves.append(option) if self.force_switch or not moves: return format_decision(self, switches[0]) most_damage = -1 choice = None for move in moves: damage_amounts = calculate_damage(state, constants.SELF, move, constants.DO_NOTHING_MOVE) damage = damage_amounts[0] if damage_amounts else 0 if damage > most_damage: choice = move most_damage = damage return format_decision(self, choice)
def test_burn_modifier_properly_halves_physical_damage(self): move = 'rockslide' self.venusaur.status = constants.BURN dmg = calculate_damage(self.venusaur, self.charizard, move, calc_type='max') self.assertEqual([134], dmg)
def test_burn_does_not_modify_special_move(self): move = 'fireblast' self.venusaur.status = constants.BURN dmg = calculate_damage(self.charizard, self.venusaur, move, calc_type='max') self.assertEqual([300], dmg)
def test_earthquake_into_levitate_does_zero_damage(self): self.state.self.active.ability = 'levitate' damage_amounts = calculate_damage(self.state, constants.OPPONENT, 'earthquake', 'splash') self.assertEqual(0, damage_amounts[0])
def test_boosts_properly_affect_damage_calculation(self): self.charizard.special_attack_boost = 2 move = 'fireblast' dmg = calculate_damage(self.charizard, self.venusaur, move, calc_type='max') self.assertEqual([597], dmg)
def check_choice_band_or_specs(battle, damage_dealt): if (battle.opponent.active is None or battle.opponent.active.item != constants.UNKNOWN_ITEM or damage_dealt.crit or damage_dealt.move in constants.WEIGHT_BASED_MOVES or damage_dealt.move in constants.SPEED_BASED_MOVES or not battle.opponent.active.can_have_choice_item): return try: move_dict = all_move_json[damage_dealt.move] except KeyError: logger.debug( "Could not find the move {}, skipping choice item check".format( move)) return if move_dict[constants.CATEGORY] == constants.PHYSICAL: choice_item = 'choiceband' spread = 'adamant', '0,252,0,0,0,0' elif move_dict[constants.CATEGORY] == constants.SPECIAL: choice_item = 'choicespecs' spread = 'modest', '0,0,0,252,0,0' else: # don't guess anything if the move was neither physical nor special return if battle.battle_type == constants.RANDOM_BATTLE: spread = 'serious', '85,85,85,85,85,85' max_damage = float('-inf') potential_battles = battle.prepare_battles(guess_mega_evo_opponent=False, join_moves_together=True) battle_copy = deepcopy(battle) battle_copy.user.from_json(battle.request_json) for b in potential_battles: if b.opponent.active.item != choice_item: b.opponent.active.set_spread(*spread) b.user.active.stats = battle_copy.user.active.stats state = b.create_state() damage = calculate_damage(state, constants.OPPONENT, damage_dealt.move, battle.user.last_used_move.move, calc_type='max')[0] max_damage = max(max_damage, damage) # dont infer if we did not find a damage amount if max_damage == float('-inf'): return if (damage_dealt.percent_damage * battle.user.active.max_hp) > ( max_damage * 1.2): # multiply to avoid rounding errors logger.debug("{} has {}".format(battle.opponent.active.name, choice_item)) battle.opponent.active.item = choice_item
def test_moldbreaker_ignores_levitate(self): self.state.self.active.ability = 'levitate' self.state.opponent.active.ability = 'moldbreaker' damage_amounts = calculate_damage(self.state, constants.OPPONENT, 'earthquake', 'splash') self.assertNotEqual(0, damage_amounts[0])
def test_bots_reflect_does_not_reduce_its_own_damage(self): self.state.opponent.side_conditions[constants.REFLECT] = 1 damage_amounts = calculate_damage(self.state, constants.OPPONENT, 'earthquake', 'splash') # should do normal damage of 68 # the attacker (opponent) having reflect up shouldn't change anything self.assertEqual(68, damage_amounts[0])
def test_sun_stab_and_2x_weakness(self): conditions = { 'weather': constants.SUN } move = 'fireblast' dmg = calculate_damage(self.charizard, self.venusaur, move, conditions, calc_type='max') self.assertEqual([450], dmg)
def test_rain_properly_amplifies_water_damage(self): conditions = { 'weather': constants.RAIN } move = 'surf' dmg = calculate_damage(self.venusaur, self.charizard, move, conditions, calc_type='max') self.assertEqual([261], dmg)
def test_aurora_veil_properly_halves_damage(self): conditions = { 'auroraveil': 1 } move = 'fireblast' dmg = calculate_damage(self.charizard, self.venusaur, move, conditions, calc_type='max') self.assertEqual([150], dmg)
def test_light_screen_properly_halves_damage(self): conditions = { 'lightscreen': 1 } move = 'psychic' dmg = calculate_damage(self.charizard, self.venusaur, move, conditions, calc_type='max') self.assertEqual([82], dmg)
def test_reflect_properly_halves_damage(self): conditions = { 'reflect': 1 } move = 'rockslide' dmg = calculate_damage(self.venusaur, self.charizard, move, conditions, calc_type='max') self.assertEqual([134], dmg)
def test_sand_increases_rock_spdef(self): self.venusaur.types = ['rock'] conditions = { 'weather': constants.SAND } move = 'fireblast' dmg = calculate_damage(self.charizard, self.venusaur, move, conditions, calc_type='max') self.assertEqual([51], dmg)
def test_damage_is_not_increased_if_attacker_is_not_grounded(self): self.charizard.types = ['fire', 'flying'] conditions = { constants.TERRAIN: constants.PSYCHIC_TERRAIN } move = 'psychic' dmg = calculate_damage(self.charizard, self.venusaur, move, conditions, calc_type='max') self.assertEqual([164], dmg)
def test_psychic_terrain_makes_priority_move_do_nothing(self): self.charizard.types = ['fire'] conditions = { constants.TERRAIN: constants.PSYCHIC_TERRAIN } move = 'machpunch' dmg = calculate_damage(self.charizard, self.venusaur, move, conditions, calc_type='max') self.assertEqual([0], dmg)
def test_sand_does_not_double_ground_spdef(self): self.venusaur.types = ['water'] conditions = { 'weather': constants.SAND } move = 'fireblast' dmg = calculate_damage(self.charizard, self.venusaur, move, conditions, calc_type='max') self.assertEqual([75], dmg)
def test_grassy_terrain_increases_grass_type_move(self): self.charizard.types = ['fire'] conditions = { constants.TERRAIN: constants.GRASSY_TERRAIN } move = 'gigadrain' dmg = calculate_damage(self.charizard, self.venusaur, move, conditions, calc_type='max') # normally this is 17 self.assertEqual([25], dmg)
def test_psychic_terrain_increases_psychic_damage(self): self.charizard.types = ['fire'] conditions = { constants.TERRAIN: constants.PSYCHIC_TERRAIN } move = 'psychic' dmg = calculate_damage(self.charizard, self.venusaur, move, conditions, calc_type='max') # normally this is 164 self.assertEqual([246], dmg)
def test_misty_terrain_halves_dragon_moves(self): self.charizard.types = ['fire'] conditions = { constants.TERRAIN: constants.MISTY_TERRAIN } move = 'outrage' dmg = calculate_damage(self.charizard, self.venusaur, move, conditions, calc_type='max') # normally this is 103 self.assertEqual([51], dmg)
def test_electric_terrain_increases_electric_damage_for_grounded_pokemon(self): self.charizard.types = ['fire'] conditions = { constants.TERRAIN: constants.ELECTRIC_TERRAIN } move = 'thunderbolt' dmg = calculate_damage(self.charizard, self.venusaur, move, conditions, calc_type='max') # normally this is 41 self.assertEqual([61], dmg)
def find_best_move(self): state = self.create_state() my_options = self.get_all_options()[0] moves = [] switches = [] for option in my_options: if option.startswith(constants.SWITCH_STRING + " "): switches.append(option) else: moves.append(option) if self.force_switch or not moves: return format_decision(self, switches[0]) conditions = { constants.REFLECT: state.opponent.side_conditions[constants.REFLECT], constants.LIGHT_SCREEN: state.opponent.side_conditions[constants.LIGHT_SCREEN], constants.AURORA_VEIL: state.opponent.side_conditions[constants.AURORA_VEIL], constants.WEATHER: state.weather, constants.TERRAIN: state.field } most_damage = -1 choice = None for move in moves: move_dict = all_move_json[move] attacking_move = update_attacking_move(state.self.active, state.opponent.active, move_dict, {}, False, state.weather) damage_amounts = calculate_damage(state.self.active, state.opponent.active, attacking_move, conditions=conditions) damage = damage_amounts[0] if damage_amounts else 0 if damage > most_damage: choice = move most_damage = damage return format_decision(self, choice)
def test_4x_resistance_calculates_properly(self): move = 'gigadrain' dmg = calculate_damage(self.venusaur, self.charizard, move, calc_type='max') self.assertEqual([27], dmg)
def test_flashfire_increases_fire_move_damage(self): move = 'fireblast' self.charizard.volatile_status.add('flashfire') dmg = calculate_damage(self.charizard, self.venusaur, move, calc_type='max') self.assertEqual([450], dmg)
def test_stab_without_weakness_calculates_properly(self): move = 'sludgebomb' dmg = calculate_damage(self.venusaur, self.charizard, move, calc_type='max') self.assertEqual([130], dmg)
def test_4x_weakness_calculates_properly(self): move = 'rockslide' dmg = calculate_damage(self.venusaur, self.charizard, move, calc_type='max') self.assertEqual([268], dmg)
def test_solarbeam_move_produces_damage_amount(self): damage_amounts = calculate_damage(self.state, constants.OPPONENT, 'solarbeam', 'splash') self.assertNotEqual(0, damage_amounts[0])
def check_choice_band_or_specs(battle, damage_dealt): if (battle.opponent.active is None or battle.opponent.active.item != constants.UNKNOWN_ITEM or damage_dealt.crit or damage_dealt.move in constants.WEIGHT_BASED_MOVES or damage_dealt.move in constants.SPEED_BASED_MOVES or not battle.opponent.active.can_have_choice_item): return try: move_dict = all_move_json[damage_dealt.move] except KeyError: logger.debug( "Could not find the move {}, skipping choice item check".format( move)) return if move_dict[constants.CATEGORY] == constants.PHYSICAL: choice_item = 'choiceband' spread = 'adamant', '0,252,0,0,0,0' elif move_dict[constants.CATEGORY] == constants.SPECIAL: choice_item = 'choicespecs' spread = 'modest', '0,0,0,252,0,0' else: # don't guess anything if the move was neither physical nor special return if battle.battle_type == constants.RANDOM_BATTLE: spread = 'serious', '85,85,85,85,85,85' min_damage_with_choice_item = float('inf') max_damage_without_choice_item = float('-inf') potential_battles = battle.prepare_battles(guess_mega_evo_opponent=False, join_moves_together=True) battle_copy = deepcopy(battle) battle_copy.user.from_json(battle.request_json) for b in potential_battles: # if the item is not the choice item - use it to find the max damage roll possible for all items if b.opponent.active.item != choice_item: b.opponent.active.set_spread(*spread) b.user.active.stats = battle_copy.user.active.stats state = b.create_state() damage = calculate_damage(state, constants.OPPONENT, damage_dealt.move, battle.user.last_used_move.move, calc_type='max')[0] max_damage_without_choice_item = max( max_damage_without_choice_item, damage) # also find the min damage roll possible for the choice-item b.opponent.active.item = choice_item b.opponent.active.set_spread(*spread) b.user.active.stats = battle_copy.user.active.stats state = b.create_state() damage = calculate_damage(state, constants.OPPONENT, damage_dealt.move, battle.user.last_used_move.move, calc_type='min')[0] min_damage_with_choice_item = min(min_damage_with_choice_item, damage) # dont infer if we did not find a damage amount if max_damage_without_choice_item == float( '-inf') or min_damage_with_choice_item == float('inf'): return actual_damage_dealt = damage_dealt.percent_damage * battle.user.active.max_hp # if the damage dealt is more than 1.2x the max-roll WITHOUT a choice item then the pkmn DOES have a choice-item if actual_damage_dealt > (max_damage_without_choice_item * 1.2): # multiply to avoid rounding errors logger.debug("{} has {}".format(battle.opponent.active.name, choice_item)) battle.opponent.active.item = choice_item # if the damage dealt is less than 0.8x the min-roll given a choice-item then the pkmn DOES NOT have a choice-item if (actual_damage_dealt < (min_damage_with_choice_item * 0.8) and # multiply to avoid rounding errors (battle.user.active.hp - actual_damage_dealt) > 1 # this is checking if the move KO-ed # if it did, we do not want to set this flag # Check for greater than 1 to avoid rounding errors ): logger.debug("{} did not do enough damage to have {}".format( battle.opponent.active.name, choice_item)) if choice_item == "choiceband": battle.opponent.active.can_not_have_band = True elif choice_item == "choicespecs": battle.opponent.active.can_not_have_specs = True else: raise ValueError("{} is neither 'choiceband' or 'choicespecs'")
def test_phantomforce_move_produces_damage_amount(self): damage_amounts = calculate_damage(self.state, constants.OPPONENT, 'phantomforce', 'splash') self.assertNotEqual(0, damage_amounts[0])
def test_immunity_calculates_properly(self): move = 'earthquake' dmg = calculate_damage(self.venusaur, self.charizard, move, calc_type='max') self.assertEqual([0], dmg)