From 4f69a94ba7dc91371be645dfc68be2c140e301f1 Mon Sep 17 00:00:00 2001 From: Haris Sahovic Date: Sun, 21 Apr 2024 22:37:33 +0200 Subject: [PATCH] Integrate doubles baselines into regular baselines --- src/poke_env/environment/move.py | 3 - src/poke_env/player/__init__.py | 2 - src/poke_env/player/baselines.py | 73 ++++++++++++ src/poke_env/player/doubles_baselines.py | 49 -------- src/poke_env/player/player.py | 115 +++++++++++-------- unit_tests/environment/test_double_battle.py | 3 - unit_tests/environment/test_move.py | 4 - unit_tests/player/test_doubles_baselines.py | 38 +++--- unit_tests/ps_client/test_ps_client.py | 10 +- 9 files changed, 163 insertions(+), 134 deletions(-) delete mode 100644 src/poke_env/player/doubles_baselines.py diff --git a/src/poke_env/environment/move.py b/src/poke_env/environment/move.py index 6196c8f5b..e69d28634 100644 --- a/src/poke_env/environment/move.py +++ b/src/poke_env/environment/move.py @@ -191,9 +191,6 @@ def category(self) -> MoveCategory: :return: The move category. :rtype: MoveCategory """ - if "category" not in self.entry: - print(self, self.entry) - if self._gen <= 3 and self.entry["category"].upper() in { "PHYSICAL", "SPECIAL", diff --git a/src/poke_env/player/__init__.py b/src/poke_env/player/__init__.py index 96c64a2df..0f88467cc 100644 --- a/src/poke_env/player/__init__.py +++ b/src/poke_env/player/__init__.py @@ -4,7 +4,6 @@ from poke_env.concurrency import POKE_LOOP from poke_env.player import env_player, openai_api, player, random_player, utils from poke_env.player.baselines import MaxBasePowerPlayer, SimpleHeuristicsPlayer -from poke_env.player.doubles_baselines import DoublesMaxBasePowerPlayer from poke_env.player.battle_order import ( BattleOrder, DefaultBattleOrder, @@ -61,5 +60,4 @@ "DoubleBattleOrder", "MaxBasePowerPlayer", "SimpleHeuristicsPlayer", - "DoublesMaxBasePowerPlayer", ] diff --git a/src/poke_env/player/baselines.py b/src/poke_env/player/baselines.py index b2fcd003b..7e74649a2 100644 --- a/src/poke_env/player/baselines.py +++ b/src/poke_env/player/baselines.py @@ -1,3 +1,4 @@ +import random from typing import List from poke_env.environment.abstract_battle import AbstractBattle @@ -5,16 +6,88 @@ from poke_env.environment.move_category import MoveCategory from poke_env.environment.pokemon import Pokemon from poke_env.environment.side_condition import SideCondition +from poke_env.environment.target import Target +from poke_env.player.battle_order import ( + BattleOrder, + DefaultBattleOrder, + DoubleBattleOrder, +) from poke_env.player.player import Player class MaxBasePowerPlayer(Player): def choose_move(self, battle: AbstractBattle): + if self.format_is_doubles: + return self.choose_doubles_move(battle) # type: ignore + else: + return self.choose_singles_move(battle) + + def choose_singles_move(self, battle: AbstractBattle): if battle.available_moves: best_move = max(battle.available_moves, key=lambda move: move.base_power) return self.create_order(best_move) return self.choose_random_move(battle) + def choose_doubles_move(self, battle: DoubleBattle): + orders = [] + switched_in = None + + if any(battle.force_switch): + return self.choose_random_doubles_move(battle) + + can_target_first_opponent = ( + battle.opponent_active_pokemon[0] + and not battle.opponent_active_pokemon[0].fainted + ) + can_target_second_opponent = ( + battle.opponent_active_pokemon[1] + and not battle.opponent_active_pokemon[1].fainted + ) + can_double_target = can_target_first_opponent and can_target_second_opponent + + for mon, moves, switches in zip( + battle.active_pokemon, battle.available_moves, battle.available_switches + ): + switches = [s for s in switches if s != switched_in] + + if not mon or mon.fainted: + orders.append(DefaultBattleOrder()) + continue + elif not moves and switches: + mon_to_switch_in = random.choice(switches) + orders.append(BattleOrder(mon_to_switch_in)) + switched_in = mon_to_switch_in + continue + elif not moves: + orders.append(DefaultBattleOrder()) + continue + + def move_power_with_double_target(move): + if move.target in {Target.NORMAL, Target.ANY} or not can_double_target: + return move.base_power + return move.base_power * 1.5 + + best_move = max(moves, key=move_power_with_double_target) + + # randomly picks between the two opponents for normal move targeting + targets = battle.get_possible_showdown_targets(best_move, mon) + opp_targets = [ + t + for t in targets + if t in {battle.OPPONENT_1_POSITION, battle.OPPONENT_2_POSITION} + ] + if opp_targets: + target = random.choice(opp_targets) + else: + target = random.choice(targets) + + orders.append(BattleOrder(best_move, move_target=target)) + + if orders[0] or orders[1]: + return DoubleBattleOrder(orders[0], orders[1]) + + return self.choose_random_move(battle) + class SimpleHeuristicsPlayer(Player): ENTRY_HAZARDS = { diff --git a/src/poke_env/player/doubles_baselines.py b/src/poke_env/player/doubles_baselines.py deleted file mode 100644 index 96b1676dc..000000000 --- a/src/poke_env/player/doubles_baselines.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -import random - -from poke_env.player.player import Player -from poke_env.player.battle_order import ( - BattleOrder, - DoubleBattleOrder, -) - - -class DoublesMaxBasePowerPlayer(Player): - def choose_move(self, battle): - slots = [0, 1] - - if battle.available_moves: - my_order = [] - for i in slots: - if battle.active_pokemon[i] and battle.available_moves[i]: - - # picks move by highest BP, takes into account spread damage modifier and hitting both targets - best_move_lambda_key = lambda move: ( - move.base_power - if move.target == "normal" - else move.base_power * 0.75 * 2 - ) - best_move_ = max( - battle.available_moves[i], key=best_move_lambda_key - ) - - # randomly picks between the two opponents for normal move targeting - targets = battle.get_possible_showdown_targets( - best_move_, battle.active_pokemon[i] - ) - opp_targets = [t for t in targets if t > 0] - if opp_targets: - target = opp_targets[int(random.random() * len(opp_targets))] - else: - target = targets[int(random.random() * len(targets))] - - my_order.append([BattleOrder(best_move_, move_target=target)]) - - else: - my_order.append([]) - - if my_order[0] or my_order[1]: - order = DoubleBattleOrder.join_orders(my_order[0], my_order[1]) - return order[0] - - return self.choose_random_doubles_move(battle) diff --git a/src/poke_env/player/player.py b/src/poke_env/player/player.py index e4be9d1e5..de30e62d6 100644 --- a/src/poke_env/player/player.py +++ b/src/poke_env/player/player.py @@ -494,6 +494,27 @@ def choose_default_move(self) -> DefaultBattleOrder: def choose_random_doubles_move(self, battle: DoubleBattle) -> BattleOrder: active_orders: List[List[BattleOrder]] = [[], []] + if any(battle.force_switch): + first_order = None + second_order = None + + if battle.force_switch[0] and battle.available_switches[0]: + first_switch_in = random.choice(battle.available_switches[0]) + first_order = BattleOrder(first_switch_in) + else: + first_switch_in = None + + if battle.force_switch[1] and battle.available_switches[1]: + available_switches = [s for s in battle.available_switches[1] if s != first_switch_in] + + if available_switches: + second_switch_in = random.choice(available_switches) + second_order = BattleOrder(second_switch_in) + + if first_order and second_order: + return DoubleBattleOrder(first_order, second_order) + return DoubleBattleOrder(first_order or second_order, None) + for ( orders, mon, @@ -513,61 +534,57 @@ def choose_random_doubles_move(self, battle: DoubleBattle) -> BattleOrder: battle.can_dynamax, battle.can_tera, ): - if mon: - targets = { - move: battle.get_possible_showdown_targets(move, mon) + if not mon: + continue + + 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) + BattleOrder(move, move_target=target, mega=True) 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 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_tera: - orders.extend( - [ - BattleOrder(move, move_target=target, terastallize=True) - for move in moves - for target in targets[move] - ] - ) + 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() + if can_tera: + orders.extend( + [ + BattleOrder(move, move_target=target, terastallize=True) + for move in moves + for target in targets[move] + ] + ) orders = DoubleBattleOrder.join_orders(*active_orders) @@ -623,10 +640,10 @@ def choose_random_move(self, battle: AbstractBattle) -> BattleOrder: :return: Move order :rtype: str """ - if isinstance(battle, Battle): - return self.choose_random_singles_move(battle) - elif isinstance(battle, DoubleBattle): + if isinstance(battle, DoubleBattle): return self.choose_random_doubles_move(battle) + elif isinstance(battle, Battle): + return self.choose_random_singles_move(battle) else: raise ValueError( "battle should be Battle or DoubleBattle. Received %d" % (type(battle)) diff --git a/unit_tests/environment/test_double_battle.py b/unit_tests/environment/test_double_battle.py index f52d63283..10c88e9f9 100644 --- a/unit_tests/environment/test_double_battle.py +++ b/unit_tests/environment/test_double_battle.py @@ -270,9 +270,6 @@ def test_one_mon_left_in_double_battles_results_in_available_move_in_the_correct ["", "swap", "p1b: Cresselia", "0", "[from] move: Ally Switch"] ) - print(battle.available_moves) - print(battle.active_pokemon) - assert battle.available_moves[0] == [] assert [m.id for m in battle.available_moves[1]] == ["recover", "haze"] assert battle.active_pokemon[0] is None diff --git a/unit_tests/environment/test_move.py b/unit_tests/environment/test_move.py index fd535acfe..ce28b4e6b 100644 --- a/unit_tests/environment/test_move.py +++ b/unit_tests/environment/test_move.py @@ -162,7 +162,6 @@ def test_flags(): flame_thrower = Move("flamethrower", gen=8) sludge_bomb = Move("sludgebomb", gen=8) - print(flame_thrower.flags) assert flame_thrower.flags == {"metronome", "protect", "mirror"} assert sludge_bomb.flags == {"bullet", "metronome", "protect", "mirror"} for move in move_generator(): @@ -343,8 +342,6 @@ def test_secondary(): assert acid_armor.secondary == [] for move in move_generator(): - if move.secondary: - print(move.id, move.secondary) assert isinstance(move.secondary, list) for secondary in move.secondary: assert isinstance(secondary, dict) @@ -714,7 +711,6 @@ def test_dynamax_moves_base_power(): } for move_name, bp in move_to_dynamax_power.items(): - print("Expecting", move_name, "to have", bp, "base power once dynamaxed") move = Move(move_name, gen=8) dynamaxed = move.dynamaxed assert dynamaxed == move.dynamaxed == move._dynamaxed_move # testing caching diff --git a/unit_tests/player/test_doubles_baselines.py b/unit_tests/player/test_doubles_baselines.py index e28373559..53ff00527 100644 --- a/unit_tests/player/test_doubles_baselines.py +++ b/unit_tests/player/test_doubles_baselines.py @@ -1,12 +1,9 @@ -from poke_env.environment import Move, Pokemon -from poke_env.player import DoublesMaxBasePowerPlayer +from poke_env.environment import DoubleBattle, Move, Pokemon +from poke_env.player import MaxBasePowerPlayer def test_doubles_max_damage_player(): - from poke_env.environment import DoubleBattle - from poke_env.player import player as player_pkg - - player = DoublesMaxBasePowerPlayer(start_listening=False) + player = MaxBasePowerPlayer(start_listening=False, battle_format="gen8doublesou") battle = DoubleBattle( battle_tag="placeholder_battle_tag", @@ -19,11 +16,8 @@ def test_doubles_max_damage_player(): active_pikachu.switch_in() battle._active_pokemon["p1a"] = active_pikachu - # player_pkg.Battle = PseudoBattle - player_pkg.Battle = DoubleBattle - # calls player.choose_random_doubles_move(battle) - assert player.choose_move(battle).message == "/choose default" + assert player.choose_move(battle).message == "/choose default, default" # calls player.choose_random_doubles_move(battle) battle._available_switches[0].append(Pokemon(species="ponyta", gen=8)) @@ -41,14 +35,14 @@ def test_doubles_max_damage_player(): active_ducklett = Pokemon(species="ducklett", gen=8) active_ducklett.switch_in() - battle._active_pokemon["p2a"] = active_ducklett + battle._opponent_active_pokemon["p2a"] = active_ducklett active_swanna = Pokemon(species="swanna", gen=8) active_swanna.switch_in() - battle._active_pokemon["p2b"] = active_swanna + battle._opponent_active_pokemon["p2b"] = active_swanna # chooses a move for p1a, other slot defaults battle._available_moves[0].append(Move("protect", gen=8)) - assert player.choose_move(battle).message == "/choose move protect, default" + assert player.choose_move(battle).message == "/choose move protect, switch rapidash" # chooses max BP move for both pokemon, targets chosen randomly battle._available_moves[0].append(Move("quickattack", gen=8)) @@ -68,6 +62,18 @@ def test_doubles_max_damage_player(): "/choose move quickattack 2, move dazzlinggleam", ] - player_pkg.Battle = ( - DoubleBattle # this is in case a test runner shares memory between tests - ) + # forced switch + battle._force_switch = [True, False] + assert player.choose_move(battle).message in [ + "/choose switch ponyta, default", + ] + + battle._force_switch = [False, True] + assert player.choose_move(battle).message in [ + "/choose default, switch rapidash", + ] + + battle._force_switch = [True, True] + assert player.choose_move(battle).message in [ + "/choose switch ponyta, switch rapidash", + ] diff --git a/unit_tests/ps_client/test_ps_client.py b/unit_tests/ps_client/test_ps_client.py index e5fa1dfbd..3373541ba 100644 --- a/unit_tests/ps_client/test_ps_client.py +++ b/unit_tests/ps_client/test_ps_client.py @@ -161,14 +161,8 @@ async def test_send_message(): client.websocket = AsyncMock() client.websocket.send = AsyncMock() - print("b4") - print(client.send_message) - res = await client.send_message("hey", "home") - print("after") - print(res) - - print(client.websocket) - print(client.websocket.send) + await client.send_message("hey", "home") + client.websocket.send.assert_called_once_with("home|hey") await client.send_message("hey", "home", "hey again")