def get_affected_targets(battle, order) -> List[int]: if not order.is_move: return None potentials = [] if order.move_target == DoubleBattle.EMPTY_TARGET_POSITION: # Add all pokemon who could be affected for moves like Surf or Earthquake if order.order.deduced_target == 'allAdjacent': for i, potential_mon in enumerate(battle.active_pokemon): if potential_mon is not None and order.actor != potential_mon: potentials.append( DoubleBattle.active_pokemon_to_showdown_target( i, opp=False)) for i, potential_mon in enumerate( battle.opponent_active_pokemon): if potential_mon is not None: potentials.append( DoubleBattle.active_pokemon_to_showdown_target( i, opp=True)) # For moves like Heatwave that affect all opponents, ensure that we list all potential affected opponents elif order.order.deduced_target in ['foeSide', 'allAdjacentFoes']: for i, potential_mon in enumerate( battle.opponent_active_pokemon): if potential_mon: potentials.append( DoubleBattle.active_pokemon_to_showdown_target( i, opp=True)) # For moves that affect our side of the field elif order.order.deduced_target in [ 'allies', 'allySide', 'allyTeam' ]: for i, potential_mon in enumerate(battle.active_pokemon): if potential_mon and mon != potential_mon: potentials.append( DoubleBattle.active_pokemon_to_showdown_target( i, opp=True)) # For moves that don't have targets (like self-moves) else: return None else: # If this is a one-target move, and there is one pokemon left, technically both opponent targets work in Showdown, since there's only one valid # target. For our purposes, we only want to return the right target (where the mon is) so that we can retrieve the mon later without hassle if battle.showdown_target_to_mon(order.move_target): potentials.append(order.move_target) elif order.move_target < 0: raise ( "get_affected_targets has been given an invalid order where we're targeting an ally... but there's no ally...?" ) else: raise ( "targeting an empty slot with a one mon move... though its on the opponents side" ) return potentials
def test_choose_random_move_doubles(pseudo_random, example_doubles_request): logger = MagicMock() battle = DoubleBattle("tag", "username", logger) player = RandomPlayer() battle._parse_request(example_doubles_request) battle._switch("p2a: Tyranitar", "Tyranitar, L50, M", "48/48") pseudo_random.side_effect = lambda: 0 choice = player.choose_random_move(battle) assert choice.message == "/choose move psychic -2, move geargrind -1" pseudo_random.side_effect = lambda: 0.5 choice = player.choose_random_move(battle) assert (choice.message == "/choose switch zamazentacrowned, move geargrind dynamax -1") pseudo_random.side_effect = lambda: 0.999 choice = player.choose_random_move(battle) assert choice.message == "/choose move slackoff dynamax, switch thundurus" battle._switch("p2b: Excadrill", "Excadrill, L50, M", "48/48") pseudo_random.side_effect = lambda: 0 choice = player.choose_random_move(battle) assert choice.message == "/choose move psychic -2, move geargrind -1" pseudo_random.side_effect = lambda: 0.5 choice = player.choose_random_move(battle) assert choice.message == "/choose move slackoff, move wildcharge dynamax 2" pseudo_random.side_effect = lambda: 0.999 choice = player.choose_random_move(battle) assert choice.message == "/choose move slackoff dynamax, switch thundurus"
def test_choose_random_move_doubles(example_doubles_request): logger = MagicMock() battle = DoubleBattle("tag", "username", logger) battle._parse_request(example_doubles_request) battle._switch("p2a: Tyranitar", "Tyranitar, L50, M", "48/48") player = RandomPlayer() active_pokemon_1, active_pokemon_2 = battle.active_pokemon choice_1 = player.choose_random_move(battle) assert (any([move in choice_1 for move in active_pokemon_1.moves]) or "/choose switch" in choice_1) assert (any([move in choice_1 for move in active_pokemon_2.moves]) or ",switch" in choice_1) choices_100 = [player.choose_random_move(battle) for _ in range(100)] assert not any([" 2" in choice for choice in choices_100]) assert any(["1" in choice for choice in choices_100]) assert any(["-1" in choice for choice in choices_100]) assert any(["-2" in choice for choice in choices_100]) assert any(["dynamax" in choice for choice in choices_100]) assert not any([choice.count("dynamax") == 2 for choice in choices_100]) assert any([choice.count("switch") == 2 for choice in choices_100]) battle._switch("p2b: Charizard", "Charizard, L50, M", "48/48") choices_100 = [player.choose_random_move(battle) for _ in range(100)] assert any([" 2" in choice for choice in choices_100])
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_battle_request_parsing(example_doubles_request): logger = MagicMock() battle = DoubleBattle("tag", "username", logger) battle._parse_request(example_doubles_request) assert len(battle.team) == 6 pokemon_names = set( map(lambda pokemon: pokemon.species, battle.team.values())) assert "thundurus" in pokemon_names assert "raichualola" in pokemon_names assert "maractus" in pokemon_names assert "zamazentacrowned" in pokemon_names zamazenta = battle.get_pokemon("p1: Zamazenta") zamazenta_moves = zamazenta.moves assert (len(zamazenta_moves) == 4 and "closecombat" in zamazenta_moves and "crunch" in zamazenta_moves and "psychicfangs" in zamazenta_moves and "behemothbash" in zamazenta_moves)
async def _create_battle(self, split_message: List[str]) -> AbstractBattle: """Returns battle object corresponding to received message. :param split_message: The battle initialisation message. :type split_message: List[str] :return: The corresponding battle object. :rtype: AbstractBattle """ # We check that the battle has the correct format if split_message[1] == self._format and len(split_message) >= 2: # Battle initialisation battle_tag = "-".join(split_message)[1:] if battle_tag in self._battles: return self._battles[battle_tag] else: if self.format_is_doubles: battle = DoubleBattle( battle_tag=battle_tag, username=self.username, logger=self.logger, save_replays=self._save_replays, ) else: battle = Battle.from_format( format_=self._format, battle_tag=battle_tag, username=self.username, logger=self.logger, save_replays=self._save_replays, ) await self._battle_count_queue.put(None) if battle_tag in self._battles: self._battle_count_queue.get() return self._battles[battle_tag] async with self._battle_start_condition: self._battle_semaphore.release() self._battle_start_condition.notify_all() self._battles[battle_tag] = battle if self._start_timer_on_battle_start: await self._send_message("/timer on", battle.battle_tag) return battle return self._battles[battle_tag] else: self.logger.critical( "Unmanaged battle initialisation message received: %s", split_message ) raise ShowdownException()
def test_choose_random_move_doubles(pseudo_random, example_doubles_request): # possible_choices_memo = ( # [] # ) # this needs to be reset at each start of Player.choose_random_move # def count_substrings(substring: str, in_: List[str]) -> int: # return sum(map(lambda el: substring in el, in_)) # def choose_non_dynamax(first_orders,) -> str: # joined = DoubleBattleOrder.join_orders(possible_choices) # possible_choices_memo.append(possible_choices.copy()) # for possible_choice in possible_choices: # if " dynamax" not in possible_choice: # return possible_choice # raise ValueError(f"Only max moves are available in {possible_choices}") logger = MagicMock() battle = DoubleBattle("tag", "username", logger) player = RandomPlayer() battle._parse_request(example_doubles_request) battle._switch("p2a: Tyranitar", "Tyranitar, L50, M", "48/48") pseudo_random.side_effect = lambda: 0 choice = player.choose_random_move(battle) assert choice.message == "/choose move psychic -2, move geargrind -1" pseudo_random.side_effect = lambda: 0.5 choice = player.choose_random_move(battle) assert (choice.message == "/choose switch zamazentacrowned, move geargrind dynamax -1") pseudo_random.side_effect = lambda: 0.999 choice = player.choose_random_move(battle) assert choice.message == "/choose move slackoff dynamax, switch thundurus" battle._switch("p2b: Excadrill", "Excadrill, L50, M", "48/48") pseudo_random.side_effect = lambda: 0 choice = player.choose_random_move(battle) assert choice.message == "/choose move psychic -2, move geargrind -1" pseudo_random.side_effect = lambda: 0.5 choice = player.choose_random_move(battle) assert choice.message == "/choose move slackoff, move wildcharge dynamax 2" pseudo_random.side_effect = lambda: 0.999 choice = player.choose_random_move(battle) assert choice.message == "/choose move slackoff dynamax, switch thundurus"
def test_battle_request_parsing_and_interactions(example_doubles_request): logger = MagicMock() battle = DoubleBattle("tag", "username", logger) battle._parse_request(example_doubles_request) mr_rime, klinklang = battle.active_pokemon assert isinstance(mr_rime, Pokemon) assert isinstance(klinklang, Pokemon) assert battle.get_pokemon("p1: Mr. Rime") == mr_rime assert battle.get_pokemon("p1: Klinklang") == klinklang assert set(battle.available_moves[0]) == set( battle.active_pokemon[0].moves.values() ) assert set(battle.available_moves[1]) == set( battle.active_pokemon[1].moves.values() ) assert len(battle.available_switches) == 2 assert all(battle.can_dynamax) assert not any(battle.can_z_move) assert not any(battle.can_mega_evolve) assert not any(battle.trapped) assert not any(battle.force_switch) assert not any(battle.maybe_trapped) mr_rime._boosts = { "accuracy": -2, "atk": 1, "def": -6, "evasion": 4, "spa": -4, "spd": 2, "spe": 3, } klinklang._boosts = { "accuracy": -6, "atk": 6, "def": -1, "evasion": 1, "spa": 4, "spd": -3, "spe": 2, } battle._clear_all_boosts() cleared_boosts = { "accuracy": 0, "atk": 0, "def": 0, "evasion": 0, "spa": 0, "spd": 0, "spe": 0, } assert mr_rime.boosts == cleared_boosts assert klinklang.boosts == cleared_boosts assert battle.active_pokemon == [mr_rime, klinklang] battle._parse_message(["", "swap", "p1b: Klinklang", ""]) assert battle.active_pokemon == [klinklang, mr_rime] battle._switch("p2a: Milotic", "Milotic, L50, F", "48/48") battle._switch("p2b: Tyranitar", "Tyranitar, L50, M", "48/48") milotic, tyranitar = battle.opponent_active_pokemon assert milotic.species == "milotic" assert tyranitar.species == "tyranitar" assert all(battle.opponent_can_dynamax)
def test_end_illusion(): logger = MagicMock() battle = DoubleBattle("tag", "username", logger) battle._player_role = "p2" battle._switch("p2a: Celebi", "Celebi", "100/100") battle._switch("p2b: Ferrothorn", "Ferrothorn, M", "100/100") battle._switch("p1a: Pelipper", "Pelipper, F", "100/100") battle._switch("p1b: Kingdra", "Kingdra, F", "100/100") battle._end_illusion("p2a: Zoroark", "Zoroark, M") zoroark = battle.team["p2: Zoroark"] celebi = battle.team["p2: Celebi"] ferrothorn = battle.team["p2: Ferrothorn"] assert zoroark in battle.active_pokemon assert ferrothorn in battle.active_pokemon assert celebi not in battle.active_pokemon
def test_get_possible_showdown_targets(example_doubles_request): logger = MagicMock() battle = DoubleBattle("tag", "username", logger) battle._parse_request(example_doubles_request) mr_rime, klinklang = battle.active_pokemon psychic = mr_rime.moves["psychic"] slackoff = mr_rime.moves["slackoff"] battle._switch("p2b: Tyranitar", "Tyranitar, L50, M", "48/48") assert battle.get_possible_showdown_targets(psychic, mr_rime) == [-2, 2] battle._switch("p2a: Milotic", "Milotic, L50, F", "48/48") assert battle.get_possible_showdown_targets(psychic, mr_rime) == [-2, 1, 2] assert battle.get_possible_showdown_targets(slackoff, mr_rime) == [0] assert battle.get_possible_showdown_targets(psychic, mr_rime, dynamax=True) == [ 1, 2, ] assert battle.get_possible_showdown_targets(slackoff, mr_rime, dynamax=True) == [0]
def choose_random_doubles_move(self, battle: DoubleBattle) -> str: available_orders = [] available_z_moves = set() mega_selected = False zmove_selected = False dynamax_selected = False if any(battle.active_pokemon): pokemon_1, pokemon_2 = battle.active_pokemon if pokemon_1 and battle.can_z_move[0]: available_z_moves.update(pokemon_1.available_z_moves) if pokemon_2 and battle.can_z_move[1]: available_z_moves.update(pokemon_2.available_z_moves) pokemon_1_index = 0 if pokemon_1 is None: pokemon_1, pokemon_2 = pokemon_2, pokemon_1 pokemon_1_index = 1 if pokemon_1 is not None: for move in battle.available_moves[pokemon_1_index]: for target in battle.get_possible_showdown_targets( move, pokemon_1): available_orders.append( self.create_order(move, move_target=target)) if battle.can_mega_evolve[pokemon_1_index]: available_orders.append( self.create_order(move, mega=True, move_target=target)) if (battle.can_z_move[pokemon_1_index] and move in available_z_moves): available_orders.append( self.create_order(move, z_move=True, move_target=target)) if battle.can_dynamax[pokemon_1_index]: for target in battle.get_possible_showdown_targets( move, pokemon_1, dynamax=True): available_orders.append( self.create_order(move, dynamax=True, move_target=target)) for pokemon in battle.available_switches[pokemon_1_index]: available_orders.append(self.create_order(pokemon)) if available_orders: order = random.choice(available_orders) mega_selected = "mega" in order zmove_selected = "zmove" in order dynamax_selected = "dynamax" in order else: order = self.choose_default_move() if sum(battle.force_switch) == 1: return order if pokemon_2 is not None or sum(battle.force_switch) == 2: pokemon_2 = battle.active_pokemon[1] available_orders = [] if pokemon_2 is not None: available_z_moves = set() if battle.can_z_move: available_z_moves.update(pokemon_2.available_z_moves) for move in battle.available_moves[1]: for target in battle.get_possible_showdown_targets( move, pokemon_2): available_orders.append( self.create_order(move, move_target=target)) if not mega_selected and battle.can_mega_evolve[1]: available_orders.append( self.create_order(move, mega=True, move_target=target)) if (not zmove_selected and battle.can_z_move[1] and move in available_z_moves): available_orders.append( self.create_order(move, z_move=True, move_target=target)) if not dynamax_selected and battle.can_dynamax[1]: for target in battle.get_possible_showdown_targets( move, pokemon_2, dynamax=True): available_orders.append( self.create_order(move, dynamax=True, move_target=target)) for pokemon in battle.available_switches[1]: if order != self.create_order(pokemon): available_orders.append(self.create_order(pokemon)) if available_orders: order += random.choice(available_orders).replace( "/choose ", ",") else: order += ",default" else: return self.choose_default_move() return order
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)