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 test_burn_does_not_modify_special_move(self): move = 'fire Blast' self.venusaur.status = constants.BURN dmg = calculate_damage(self.charizard, self.venusaur, move, calc_type='max') self.assertEqual([300], dmg)
def test_burn_modifier_properly_halves_physical_damage(self): move = 'rock slide' self.venusaur.status = constants.BURN dmg = calculate_damage(self.venusaur, self.charizard, move, calc_type='max') self.assertEqual([134], dmg)
def test_4x_weakness_calculates_properly(self): move = 'rock slide' dmg = calculate_damage(self.venusaur, self.charizard, move, calc_type='max') self.assertEqual([268], dmg)
def test_fire_blast_from_charizard_to_venusaur_without_modifiers(self): move = 'fire Blast' dmg = calculate_damage(self.charizard, self.venusaur, move, calc_type='max') self.assertEqual([300], dmg)
def test_stab_without_weakness_calculates_properly(self): move = 'sludge bomb' dmg = calculate_damage(self.venusaur, self.charizard, move, calc_type='max') self.assertEqual([130], dmg)
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_immunity_calculates_properly(self): move = 'earthquake' dmg = calculate_damage(self.venusaur, self.charizard, move, calc_type='max') self.assertEqual([0], dmg)
def test_flashfire_increases_fire_move_damage(self): move = 'fire Blast' self.charizard.volatile_status.add('flashfire') dmg = calculate_damage(self.charizard, self.venusaur, move, 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_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_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_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_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_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 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 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 get_state_instructions_from_move(mutator, attacking_move, defending_move, attacker, defender, first_move, instructions): instructions.frozen = False if constants.SWITCH_STRING in attacking_move: return instruction_generator.get_instructions_from_switch( mutator, attacker, attacking_move[constants.SWITCH_STRING], instructions) mutator.apply(instructions.instructions) attacking_side = instruction_generator.get_side_from_state( mutator.state, attacker) defending_side = instruction_generator.get_side_from_state( mutator.state, defender) attacking_pokemon = attacking_side.active defending_pokemon = defending_side.active active_weather = mutator.state.weather if cannot_use_move(attacking_pokemon, attacking_move): attacking_move = lookup_move(constants.DO_NOTHING_MOVE) conditions = { constants.REFLECT: defending_side.side_conditions[constants.REFLECT], constants.LIGHT_SCREEN: defending_side.side_conditions[constants.LIGHT_SCREEN], constants.AURORA_VEIL: defending_side.side_conditions[constants.AURORA_VEIL], constants.WEATHER: active_weather, constants.TERRAIN: mutator.state.field } if attacking_pokemon.hp == 0: # if the attacker is dead, remove the 'flinched' volatile-status if it has it and exit early # this triggers if the pokemon moves second but the first attack knocked it out mutator.reverse(instructions.instructions) all_instructions = instruction_generator.get_instructions_from_flinched( mutator, attacker, instructions) return all_instructions attacking_move = update_damage_calc_from_abilities_and_items( attacking_pokemon, defending_pokemon, attacking_move, defending_move, first_move, mutator.state.weather) damage_amounts = None move_status_effect = None flinch_accuracy = None boosts = None boosts_target = None boosts_chance = None side_condition = None hazard_clearing_move = None volatile_status = attacking_move.get(constants.VOLATILE_STATUS) move_accuracy = min(100, attacking_move[constants.ACCURACY]) move_status_accuracy = move_accuracy move_target = attacking_move[constants.TARGET] if move_target == constants.SELF: move_status_target = attacker else: move_status_target = defender if attacking_move[constants.ID] in constants.HAZARD_CLEARING_MOVES: hazard_clearing_move = attacking_move # move is a damaging move if attacking_move[constants.CATEGORY] in constants.DAMAGING_CATEGORIES: damage_amounts = calculate_damage(attacking_pokemon, defending_pokemon, attacking_move, conditions=conditions, calc_type=config.damage_calc_type) attacking_move_secondary = attacking_move[constants.SECONDARY] attacking_move_self = attacking_move.get(constants.SELF) if attacking_move_secondary: # flinching (iron head) if attacking_move_secondary.get( constants.VOLATILE_STATUS ) == constants.FLINCH and first_move: flinch_accuracy = attacking_move_secondary.get( constants.CHANCE) # secondary status effects (thunderbolt paralyzing) elif attacking_move_secondary.get(constants.STATUS) is not None: move_status_effect = attacking_move_secondary[constants.STATUS] move_status_accuracy = attacking_move_secondary[ constants.CHANCE] # boosts from moves that boost in secondary (charge beam) elif attacking_move_secondary.get(constants.SELF) is not None: if constants.BOOSTS in attacking_move_secondary[ constants.SELF]: boosts = attacking_move_secondary[constants.SELF][ constants.BOOSTS] boosts_target = attacker boosts_chance = attacking_move_secondary[constants.CHANCE] # boosts from secondary, but to the defender (crunch) elif attacking_move_secondary and attacking_move_secondary.get( constants.BOOSTS) is not None: boosts = attacking_move_secondary[constants.BOOSTS] boosts_target = defender boosts_chance = attacking_move_secondary[constants.CHANCE] # boosts from secondary, but it is a guaranteed boost (dracometeor) elif attacking_move_self: if constants.BOOSTS in attacking_move_self: boosts = attacking_move_self[constants.BOOSTS] boosts_target = attacker boosts_chance = 100 # guaranteed boosts from a damaging move (none in the moves JSON but items/abilities can cause this) elif constants.BOOSTS in attacking_move: boosts = attacking_move[constants.BOOSTS] boosts_target = attacker if attacking_move[ constants.TARGET] in constants.MOVE_TARGET_SELF else defender boosts_chance = 100 # move is a status move else: move_status_effect = attacking_move.get(constants.STATUS) side_condition = attacking_move.get(constants.SIDE_CONDITIONS) # boosts from moves that only boost (dragon dance) if attacking_move.get(constants.BOOSTS) is not None: boosts = attacking_move[constants.BOOSTS] boosts_target = attacker if attacking_move[ constants.TARGET] == constants.SELF else defender boosts_chance = attacking_move[constants.ACCURACY] mutator.reverse(instructions.instructions) all_instructions = instruction_generator.get_instructions_from_flinched( mutator, attacker, instructions) temp_instructions = [] for instruction_set in all_instructions: temp_instructions += instruction_generator.get_instructions_from_statuses_that_freeze_the_state( mutator, attacker, defender, attacking_move, defending_move, instruction_set) all_instructions = temp_instructions if attacking_move[ constants.ID] in instruction_generator.SPECIAL_LOGIC_MOVES: temp_instructions = [] for instruction_set in all_instructions: temp_instructions += instruction_generator.get_instructions_from_special_logic_move( mutator, attacking_move[constants.ID], instruction_set) return temp_instructions if damage_amounts is not None: temp_instructions = [] for instruction_set in all_instructions: amount_of_damage_rolls = len(damage_amounts) for dmg in damage_amounts: these_instructions = copy(instruction_set) these_instructions.update_percentage(1 / amount_of_damage_rolls) temp_instructions += instruction_generator.get_states_from_damage( mutator, defender, dmg, move_accuracy, attacking_move, these_instructions) all_instructions = temp_instructions if side_condition is not None: temp_instructions = [] for instruction_set in all_instructions: temp_instructions += instruction_generator.get_instructions_from_side_conditions( mutator, attacker, move_target, side_condition, instruction_set) all_instructions = temp_instructions if hazard_clearing_move is not None: temp_instructions = [] for instruction_set in all_instructions: temp_instructions += instruction_generator.get_instructions_from_hazard_clearing_moves( mutator, attacker, attacking_move, instruction_set) all_instructions = temp_instructions if volatile_status is not None: temp_instructions = [] for instruction_set in all_instructions: temp_instructions += instruction_generator.get_state_from_volatile_status( mutator, volatile_status, attacker, move_target, first_move, instruction_set) all_instructions = temp_instructions if move_status_effect is not None: temp_instructions = [] for instruction_set in all_instructions: temp_instructions += instruction_generator.get_states_from_status_effects( mutator, move_status_target, move_status_effect, move_status_accuracy, instruction_set) all_instructions = temp_instructions if boosts is not None: temp_instructions = [] for instruction_set in all_instructions: temp_instructions += instruction_generator.get_states_from_boosts( mutator, boosts_target, boosts, boosts_chance, instruction_set) all_instructions = temp_instructions if attacking_move.get(constants.HEAL) is not None: temp_instructions = [] for instruction_set in all_instructions: temp_instructions += instruction_generator.get_state_from_attacker_recovery( mutator, attacker, attacking_move, instruction_set) all_instructions = temp_instructions if flinch_accuracy is not None: temp_instructions = [] for instruction_set in all_instructions: temp_instructions += instruction_generator.get_states_from_flinching_moves( defender, flinch_accuracy, first_move, instruction_set) all_instructions = temp_instructions temp_instructions = [] for instruction_set in all_instructions: temp_instructions += instruction_generator.get_state_from_drag( mutator, attacking_move, attacker, move_target, instruction_set) all_instructions = temp_instructions if switch_out_move_triggered(attacking_move, damage_amounts): temp_instructions = [] for i in all_instructions: best_switch = get_best_switch_pokemon(mutator, i, attacker, attacking_side, defending_move, first_move) if best_switch is not None: temp_instructions += instruction_generator.get_instructions_from_switch( mutator, attacker, best_switch, i) else: temp_instructions.append(i) all_instructions = temp_instructions return all_instructions