def _useless_self_hit(self, battle, order: BattleOrder): # Eliminate easy conditions in which this is not a useless self hit if not order or not order.is_move(): return False if not (order.order.damage or order.order.base_power > 0): return False if order.order.self_switch: return False # If it's a self-hit affected_targets = BattleOrder.get_affected_targets(battle, order) if affected_targets and min(affected_targets) < 0: # Get the mon who is going to be hit target_mon = battle.active_pokemon[min(affected_targets)] # Only allow this as a potential move under these conditions if target_mon.item == 'weaknesspolicy' and order.order.type.damage_multiplier( *target_mon.types) >= 2: return True elif target_mon.ability == 'Berserk': return False elif target_mon.ability == 'Justified' and order.order.type == 'DARK': return False elif target_mon.ability == 'Water Absorb' and order.order.type == 'WATER': return False elif target_mon.ability == 'Volt Absorb' and order.order.type == 'ELECTRIC': return False elif target_mon.ability == 'Flash Fire' and order.order.type == 'FIRE': return False else: return True return False
def choose_random_singles_move(self, battle: Battle) -> BattleOrder: available_orders = [BattleOrder(move) for move in battle.available_moves] available_orders.extend( [BattleOrder(switch) for switch in battle.available_switches] ) if battle.can_mega_evolve: available_orders.extend( [BattleOrder(move, mega=True) for move in battle.available_moves] ) if battle.can_dynamax: available_orders.extend( [BattleOrder(move, dynamax=True) for move in battle.available_moves] ) if battle.can_z_move and battle.active_pokemon: available_z_moves = set(battle.active_pokemon.available_z_moves) available_orders.extend( [ BattleOrder(move, z_move=True) for move in battle.available_moves if move in available_z_moves ] ) if available_orders: return available_orders[int(random.random() * len(available_orders))] else: return self.choose_default_move(battle)
def _action_to_single_move(self, action: int, index: int, battle): if action < 24: # If either there is no mon or we're forced to switch, there's nothing to do if not battle.active_pokemon[index] or battle.force_switch[index]: return None dynamax, remaining = action % 2 == 1, int(action / 2) if battle.active_pokemon[index] and int(remaining / 3) < len( battle.active_pokemon[index].moves): move, initial_target = list( battle.active_pokemon[index].moves.values())[int( remaining / 3)], remaining % 3 # If there's no target needed, we create the action as we normally would. It doesn't matter what our AI returned as target since there's only one possible target if move.deduced_target not in [ 'adjacentAlly', 'adjacentAllyOrSelf', 'any', 'normal' ]: return BattleOrder(order=move, actor=battle.active_pokemon[index], dynamax=dynamax) # If we are targeting a single mon, there are three cases: your other mon, the opponents mon or their other mon. # 2 corresponds to your mon and 0/1 correspond to the opponents mons (index in opponent_active_mon) # For the self-taret case, we ensure there's another mon on our side to hit (otherwise we leave action1 as None) elif initial_target == 2: if battle.active_pokemon[1] is not None: return BattleOrder(order=move, move_target=battle. active_pokemon_to_showdown_target( 1 - index, opp=False), actor=battle.active_pokemon[index], dynamax=dynamax) # In the last case (if initial_target is 0 or 1), we target the opponent, and we do it regardless of what slot was # chosen if there's only 1 mon left. In the following cases, we handle whether there are two mons left or one mon left elif len(battle.opponent_active_pokemon) == 2 and all( battle.opponent_active_pokemon): return BattleOrder( order=move, move_target=battle.active_pokemon_to_showdown_target( initial_target, opp=True), actor=battle.active_pokemon[index], dynamax=dynamax) elif len(battle.opponent_active_pokemon) < 2 and any( battle.opponent_active_pokemon): initial_target = 1 if battle.opponent_active_pokemon[ 0] is not None else 0 return BattleOrder( order=move, move_target=battle.active_pokemon_to_showdown_target( initial_target, opp=True), actor=battle.active_pokemon[index], dynamax=dynamax) elif 25 - action < len(battle.available_switches[index]): return BattleOrder(order=battle.available_switches[index][25 - action], actor=battle.active_pokemon[index]) return None
def choose_random_doubles_move(self, battle: DoubleBattle) -> BattleOrder: active_orders = [[], []] for ( idx, (orders, mon, switches, moves, can_mega, can_z_move, can_dynamax), ) in enumerate( zip( active_orders, battle.active_pokemon, battle.available_switches, battle.available_moves, battle.can_mega_evolve, battle.can_z_move, battle.can_dynamax, )): if mon: targets = { move: battle.get_possible_showdown_targets(move, mon) for move in moves } orders.extend([ BattleOrder(move, move_target=target) for move in moves for target in targets[move] ]) orders.extend([BattleOrder(switch) for switch in switches]) if can_mega: orders.extend([ BattleOrder(move, move_target=target, mega=True) for move in moves for target in targets[move] ]) if can_z_move: available_z_moves = set(mon.available_z_moves) orders.extend([ BattleOrder(move, move_target=target, z_move=True) for move in moves for target in targets[move] if move in available_z_moves ]) if can_dynamax: orders.extend([ BattleOrder(move, move_target=target, dynamax=True) for move in moves for target in targets[move] ]) if sum(battle.force_switch) == 1: if orders: return orders[int(random.random() * len(orders))] return self.choose_default_move() orders = DoubleBattleOrder.join_orders(*active_orders) if orders: return orders[int(random.random() * len(orders))] else: return DefaultBattleOrder()
def test_single_orders(): move = Move("flamethrower") assert BattleOrder(move).message == "/choose move flamethrower" assert BattleOrder(move, mega=True).message == "/choose move flamethrower mega" assert BattleOrder( move, z_move=True).message == "/choose move flamethrower zmove" mon = Pokemon(species="charizard") assert BattleOrder(mon).message == "/choose switch charizard"
def create_order( order: Union[Move, Pokemon], mega: bool = False, z_move: bool = False, dynamax: bool = False, move_target: int = DoubleBattle.EMPTY_TARGET_POSITION, ) -> BattleOrder: """Formats an move order corresponding to the provided pokemon or move. :param order: Move to make or Pokemon to switch to. :type order: Move or Pokemon :param mega: Whether to mega evolve the pokemon, if a move is chosen. :type mega: bool :param z_move: Whether to make a zmove, if a move is chosen. :type z_move: bool :param dynamax: Whether to dynamax, if a move is chosen. :type dynamax: bool :param move_target: Target Pokemon slot of a given move :type move_target: int :return: Formatted move order :rtype: str """ return BattleOrder( order, mega=mega, move_target=move_target, z_move=z_move, dynamax=dynamax )
def test_double_orders(): move = BattleOrder(Move("selfdestruct"), move_target=2) mon = BattleOrder(Pokemon(species="lugia")) assert (DoubleBattleOrder( move, mon).message == "/choose move selfdestruct 2, switch lugia") assert (DoubleBattleOrder( mon, move).message == "/choose switch lugia, move selfdestruct 2") assert DoubleBattleOrder(mon).message == "/choose switch lugia, default" assert (DoubleBattleOrder( None, move).message == "/choose move selfdestruct 2, default") assert DoubleBattleOrder().message == "/choose default" orders = [move, mon] both = { order.message for order in DoubleBattleOrder.join_orders(orders, orders) } first = { order.message for order in DoubleBattleOrder.join_orders(orders, []) } second = { order.message for order in DoubleBattleOrder.join_orders([], orders) } none = {order.message for order in DoubleBattleOrder.join_orders([], [])} assert both == { "/choose move selfdestruct 2, switch lugia", "/choose switch lugia, move selfdestruct 2", } assert first == { "/choose move selfdestruct 2, default", "/choose switch lugia, default", } assert second == { "/choose move selfdestruct 2, default", "/choose switch lugia, default", } assert none == {"/choose default"}
def _useless_battle_condition(self, battle, order: BattleOrder): if not order or not order.is_move(): return False if order.order.side_condition and order.order.side_condition in battle.side_conditions: return True if order.order.weather and battle.weather and order.order.weather == battle.weather: return True if order.order.terrain and battle.fields and order.order.terrain in battle.fields: return True if order.order.pseudo_weather and battle.fields and order.order.pseudo_weather in battle.fields: return True return False
def _useless_self_boost(self, order: BattleOrder): if order and order.is_move(): # Only consider self- or ally-boosting moves if you have boosts left, or if you dont, if the other pokemon has sucker punch if order.order.boosts and order.order.target == 'self': num_failed = 0 for stat in order.order.boosts: if (order.actor.boosts[stat] == 6 and order.order.boosts[stat] > 0) or ( order.order.boosts[stat] == -6 and order.order.boosts[stat] < 0): num_failed += 1 if num_failed < len(order.order.boosts): return True return False
def _convertPartialMessage(self, battle, msg, index): if msg == self._DEFAULT_ORDER: return DefaultBattleOrder() order, target, mega, dynamax, z_move = None, None, False, False, False if 'switch' in msg: order = Pokemon(species=re.search("switch\s([a-zA-Z\-\_0-9]+).*", msg).group(1).lower()) elif 'move' in msg: tokens = msg.split(" ") order = Move(tokens[1]) if len(tokens) <= 2: target = DoubleBattle.EMPTY_TARGET_POSITION else: target = tokens[2] if target.strip().isdigit(): target = int(target) elif battle.opponent_active_pokemon[0].species == target.strip( ): target = active_pokemon_to_showdown_target(0, opp=True) elif battle.opponent_active_pokemon[1].species == target.strip( ): target = active_pokemon_to_showdown_target(1, opp=True) elif battle.active_pokemon[1 - index].species == target.strip(): target = active_pokemon_to_showdown_target(1 - index, opp=False) if ' mega' in msg: mega = True if 'dynamax' in msg: dynamax = True if 'z_move' in msg: z_move = True return BattleOrder(order, move_target=target, actor=battle.active_pokemon[index], mega=mega, dynamax=dynamax, z_move=z_move)
def choose_move(self, battle): best_order = DefaultBattleOrder() # If we're not being forced to switch and are choosing our moves if not any(battle.force_switch): # Go through and get actions, filter them down to what's possible, and then eliminate ones that dont make sense orders = self.get_all_doubles_moves(battle) filtered_orders = list( filter(lambda x: x and DoubleBattleOrder.is_valid(battle, x), orders)) # Iterate through all actions to pick best short-term move. These are our stored values most_damage, best_switch_multiplier = 0, 0 # Go through every reasonable pair of actions and pick the pair that does the most high damage moves and multipliers of switch for double_order in filtered_orders: damage, switch_multiplier = 0, 0 # Add up damage I'm probably going to do and switch multipliers compared to active pokemon for order in [ double_order.first_order, double_order.second_order ]: if not order: continue # If damaging move, Go through each potential target and add up damage (subtract if self-damage) if order.is_move() and (order.order.damage or order.order.base_power > 0): targets = BattleOrder.get_affected_targets( battle, order) if targets == None: targets = [] for target in targets: stab = 1.5 if order.order.type in order.actor.types else 1 target_mon = utils.showdown_target_to_mon( battle, target) effectiveness = order.order.type.damage_multiplier( *target_mon.types ) if target_mon is not None else 1 base_power = order.order.base_power damage += base_power * stab * effectiveness * ( -1 if target < 0 else 1) if order.dynamax: damage += 1 # Calculate whether we're going to switch into an good environment (wrt types) elif order.is_switch(): switch_multiplier += np.mean([ compute_type_advantage(order.actor, opp) for opp in filter(lambda x: x is not None, battle.opponent_active_pokemon) ]) # Choose move if it does highest damage, and then if tied, the one that has the best switch if damage > most_damage: best_order, most_damage, best_switch_multiplier = double_order, damage, switch_multiplier elif damage == most_damage and switch_multiplier >= best_switch_multiplier: best_order, most_damage, best_switch_multiplier = double_order, damage, switch_multiplier # Force Switch situation; pick switch that has the best type advantage against the opponent's active mons else: orders = self.get_all_doubles_moves(battle) filtered_orders = list( filter(lambda x: DoubleBattleOrder.is_valid(battle, x), orders)) # Go through every possible switch and choose one that has best type advantage against opponent's mon multiplier = -np.inf for double_order in filtered_orders: if not double_order.first_order or not double_order.first_order.is_switch( ): continue # Store the score if there's a better switch multipliers = [] for opp in battle.opponent_active_pokemon: if opp is not None: multipliers.append( compute_type_advantage( double_order.first_order.actor, opp)) if np.mean(multipliers) > multiplier: best_order, multiplier = double_order, np.mean(multipliers) return best_order
def test_recharge_order(): recharge = SPECIAL_MOVES["recharge"] assert BattleOrder(recharge).message == "/choose move 1"
def get_all_doubles_moves(self, battle: DoubleBattle) -> List[DoubleBattleOrder]: active_orders = [[], []] for ( idx, (orders, mon, switches, moves, can_mega, can_z_move, can_dynamax), ) in enumerate( zip( active_orders, battle.active_pokemon, battle.available_switches, battle.available_moves, battle.can_mega_evolve, battle.can_z_move, battle.can_dynamax, )): if mon: targets = { move: battle.get_possible_showdown_targets(move, mon) for move in moves } orders.extend([ BattleOrder(move, actor=mon, move_target=target) for move in moves for target in targets[move] ]) orders.extend( [BattleOrder(switch, actor=mon) for switch in switches]) if can_mega: orders.extend([ BattleOrder(move, actor=mon, move_target=target, mega=True) for move in moves for target in targets[move] ]) if can_z_move: available_z_moves = set(mon.available_z_moves) orders.extend([ BattleOrder(move, actor=mon, move_target=target, z_move=True) for move in moves for target in targets[move] if move in available_z_moves ]) if can_dynamax: orders.extend([ BattleOrder(move, actor=mon, move_target=target, dynamax=True) for move in moves for target in battle.get_possible_showdown_targets( move, mon, dynamax=True) ]) if sum(battle.force_switch) == 1: if orders: return list( map( lambda x: DoubleBattleOrder(first_order=x, second_order=None), orders)) return None return DoubleBattleOrder.join_orders(*active_orders)