From eb53a5d505f7cfc63f39af1747561a50862f33ff Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Mon, 27 May 2024 00:45:44 -0400 Subject: [PATCH 01/68] Disable cut hook. Enable cut scripting --- config.yaml | 6 +-- pokemonred_puffer/environment.py | 74 +++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/config.yaml b/config.yaml index 79a56a9..530ecee 100644 --- a/config.yaml +++ b/config.yaml @@ -7,15 +7,15 @@ debug: env: headless: False stream_wrapper: False - init_state: cut + init_state: cut3 max_steps: 1_000_000 train: device: cpu compile: False compile_mode: default - num_envs: 4 + num_envs: 1 envs_per_worker: 1 - envs_per_batch: 4 + envs_per_batch: 1 batch_size: 16 batch_rows: 4 bptt_horizon: 2 diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 4ed86db..05bcc9b 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -256,8 +256,8 @@ def register_hooks(self): ) self.pyboy.hook_register(None, "HandleBlackOut", self.blackout_hook, None) self.pyboy.hook_register(None, "SetLastBlackoutMap.done", self.blackout_update_hook, None) - self.pyboy.hook_register(None, "UsedCut.nothingToCut", self.cut_hook, context=True) - self.pyboy.hook_register(None, "UsedCut.canCut", self.cut_hook, context=False) + # self.pyboy.hook_register(None, "UsedCut.nothingToCut", self.cut_hook, context=True) + # self.pyboy.hook_register(None, "UsedCut.canCut", self.cut_hook, context=False) def update_state(self, state: bytes): self.reset(seed=random.randint(0, 10), options={"state": state}) @@ -584,6 +584,76 @@ def run_action_on_emulator(self, action): self.pyboy.send_input(VALID_ACTIONS[action]) self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) self.pyboy.tick(self.action_freq, render=True) + if self.check_if_party_has_cut(): + self.cut_if_next() + + def cut_if_next(self): + # https://github.com/pret/pokered/blob/d38cf5281a902b4bd167a46a7c9fd9db436484a7/constants/tileset_constants.asm#L11C8-L11C11 + in_erika_gym = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMapTileset")[1]] == 7 + in_overworld = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMapTileset")[1]] == 0 + if in_erika_gym or in_overworld: + wTileMap = self.pyboy.symbol_lookup("wTileMap")[1] + tileMap = self.pyboy.memory[wTileMap : wTileMap + 20 * 18] + tileMap = np.array(tileMap, dtype=np.uint8) + tileMap = np.reshape(tileMap, (18, 20)) + y, x = 8, 8 + up, down, left, right = ( + tileMap[y - 2 : y, x : x + 2], # up + tileMap[y + 2 : y + 4, x : x + 2], # down + tileMap[y : y + 2, x - 2 : x], # left + tileMap[y : y + 2, x + 2 : x + 4], # right + ) + + # Gym trees apparently get the same tile map as outside bushes + # GYM = 7 + if (in_overworld and 0x3D in up) or (in_erika_gym and 0x50 in up): + self.pyboy.send_input(WindowEvent.PRESS_ARROW_UP) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_UP, delay=8) + self.pyboy.tick(self.action_freq, render=True) + elif (in_overworld and 0x3D in down) or (in_erika_gym and 0x50 in down): + self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) + self.pyboy.tick(self.action_freq, render=True) + elif (in_overworld and 0x3D in left) or (in_erika_gym and 0x50 in left): + self.pyboy.send_input(WindowEvent.PRESS_ARROW_LEFT) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_LEFT, delay=8) + self.pyboy.tick(self.action_freq, render=True) + elif (in_overworld and 0x3D in right) or (in_erika_gym and 0x50 in right): + self.pyboy.send_input(WindowEvent.PRESS_ARROW_RIGHT) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_RIGHT, delay=8) + self.pyboy.tick(self.action_freq, render=True) + else: + return + + # open start menu + self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) + self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START, delay=8) + self.pyboy.tick(self.action_freq, render=True) + # scroll to pokemon + # 1 is the item index for pokemon + while self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] != 1: + self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) + self.pyboy.tick(self.action_freq, render=True) + self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) + self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) + self.pyboy.tick(self.action_freq, render=True) + + # find pokemon with cut + while True: + self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) + self.pyboy.tick(self.action_freq, render=True) + party_mon = self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] + _, addr = self.pyboy.symbol_lookup(f"wPartyMon{party_mon+1}Moves") + if 15 in self.pyboy.memory[addr : addr + 4]: + break + + # press a bunch of times + for _ in range(5): + self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) + self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) + self.pyboy.tick(4 * self.action_freq, render=True) def hidden_object_hook(self, *args, **kwargs): hidden_object_id = self.pyboy.memory[self.pyboy.symbol_lookup("wHiddenObjectIndex")[1]] From 90422fed1843d6e5a23b88ff896101bf05581ae9 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Fri, 31 May 2024 22:30:07 -0400 Subject: [PATCH 02/68] teach cut if possible to bulbasaur line --- pokemonred_puffer/environment.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 05bcc9b..9f34f68 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -584,8 +584,26 @@ def run_action_on_emulator(self, action): self.pyboy.send_input(VALID_ACTIONS[action]) self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) self.pyboy.tick(self.action_freq, render=True) - if self.check_if_party_has_cut(): - self.cut_if_next() + + if self.read_bit(0xD803, 0): + if not self.check_if_party_has_cut(): + self.teach_cut_to_bulba() + self.cut_if_next() + else: + self.cut_if_next() + + def teach_cut_to_bulba(self): + # find bulba and replace tackle (first skill) with cut + move_pp_addr = [0xD188, 0xD189, 0xD18A, 0xD18B] + # D164-D169 + PARTY_ADDR = [0xD164, 0xD165, 0xD166, 0xD167, 0xD168, 0xD169] + for poke_idx, poke_addr in enumerate(PARTY_ADDR): + poke = self.pyboy.memory[poke_addr] + if poke in [9, 153, 154]: # bulba line + slot = 0 # this should have tackle + self.game.memory[poke_addr + 8 + slot] = 15 + self.game.memory[move_pp_addr[slot] + 44 * poke_idx] = 30 + # fill up pp: 30/30 def cut_if_next(self): # https://github.com/pret/pokered/blob/d38cf5281a902b4bd167a46a7c9fd9db436484a7/constants/tileset_constants.asm#L11C8-L11C11 From 3171d2a164d204f65951664a6c6ae2f6b6468e63 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Fri, 31 May 2024 23:35:25 -0400 Subject: [PATCH 03/68] neater --- pokemonred_puffer/environment.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 9f34f68..88202f1 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -588,9 +588,7 @@ def run_action_on_emulator(self, action): if self.read_bit(0xD803, 0): if not self.check_if_party_has_cut(): self.teach_cut_to_bulba() - self.cut_if_next() - else: - self.cut_if_next() + self.cut_if_next() def teach_cut_to_bulba(self): # find bulba and replace tackle (first skill) with cut From b7380ac56c446916f3d376cfe1d22a556e6474b5 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Fri, 31 May 2024 23:58:09 -0400 Subject: [PATCH 04/68] self.game.memory -> self.pyboy.memory --- pokemonred_puffer/environment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 88202f1..d0a735e 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -599,8 +599,8 @@ def teach_cut_to_bulba(self): poke = self.pyboy.memory[poke_addr] if poke in [9, 153, 154]: # bulba line slot = 0 # this should have tackle - self.game.memory[poke_addr + 8 + slot] = 15 - self.game.memory[move_pp_addr[slot] + 44 * poke_idx] = 30 + self.pyboy.memory[poke_addr + 8 + slot] = 15 + self.pyboy.memory[move_pp_addr[slot] + 44 * poke_idx] = 30 # fill up pp: 30/30 def cut_if_next(self): From 9cc652def9b4ddf8c2a9cae9419cbce60a038aa0 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:42:24 -0400 Subject: [PATCH 05/68] No inf whiles --- pokemonred_puffer/environment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index d0a735e..94aca26 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -656,7 +656,8 @@ def cut_if_next(self): self.pyboy.tick(self.action_freq, render=True) # find pokemon with cut - while True: + # We run this over all pokemon so we dont end up in an infinite for loop + for _ in range(7): self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) self.pyboy.tick(self.action_freq, render=True) From aabe87ac673504caf51643d48cb3d705fe090148 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 1 Jun 2024 21:11:30 -0400 Subject: [PATCH 06/68] Comment out cut related image logging --- config.yaml | 10 +++++----- pokemonred_puffer/cleanrl_puffer.py | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/config.yaml b/config.yaml index 530ecee..f30be7b 100644 --- a/config.yaml +++ b/config.yaml @@ -28,8 +28,8 @@ debug: env_pool: False log_frequency: 5000 load_optimizer_state: False - swarm_frequency: 10 - swarm_keep_pct: .1 + # swarm_frequency: 10 + # swarm_keep_pct: .1 env: headless: True @@ -84,13 +84,13 @@ train: save_checkpoint: False checkpoint_interval: 200 save_overlay: True - overlay_interval: 200 + overlay_interval: 100 cpu_offload: True pool_kernel: [0] load_optimizer_state: False - swarm_frequency: 500 - swarm_keep_pct: .8 + # swarm_frequency: 500 + # swarm_keep_pct: .8 wrappers: baseline: diff --git a/pokemonred_puffer/cleanrl_puffer.py b/pokemonred_puffer/cleanrl_puffer.py index b74b193..3c62d3e 100644 --- a/pokemonred_puffer/cleanrl_puffer.py +++ b/pokemonred_puffer/cleanrl_puffer.py @@ -524,13 +524,13 @@ def evaluate(self): overlay = make_pokemon_red_overlay(np.stack(v, axis=0)) if self.wandb is not None: self.stats["Media/aggregate_exploration_map"] = self.wandb.Image(overlay) - elif "cut_exploration_map" in k and config.save_overlay is True: - if self.update % config.overlay_interval == 0: - overlay = make_pokemon_red_overlay(np.stack(v, axis=0)) - if self.wandb is not None: - self.stats["Media/aggregate_cut_exploration_map"] = self.wandb.Image( - overlay - ) + # elif "cut_exploration_map" in k and config.save_overlay is True: + # if self.update % config.overlay_interval == 0: + # overlay = make_pokemon_red_overlay(np.stack(v, axis=0)) + # if self.wandb is not None: + # self.stats["Media/aggregate_cut_exploration_map"] = self.wandb.Image( + # overlay + # ) elif "state" in k: pass else: From 13f16cab75cae52698678de8c853ec2093b1a59f Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 2 Jun 2024 00:51:15 -0400 Subject: [PATCH 07/68] Remove risk of infinite while --- pokemonred_puffer/environment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 94aca26..4d548e8 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -647,7 +647,9 @@ def cut_if_next(self): self.pyboy.tick(self.action_freq, render=True) # scroll to pokemon # 1 is the item index for pokemon - while self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] != 1: + for _ in range(24): + if self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] == 1: + break self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) self.pyboy.tick(self.action_freq, render=True) From 62f70df486cf92d94cfeec1ef2c7963d2302951e Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 2 Jun 2024 09:09:01 -0400 Subject: [PATCH 08/68] Fix crash possibility in script --- pokemonred_puffer/environment.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 4d548e8..78de5b8 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -581,8 +581,8 @@ def run_action_on_emulator(self, action): self.action_hist[action] += 1 # press button then release after some steps # TODO: Add video saving logic - self.pyboy.send_input(VALID_ACTIONS[action]) - self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) + # self.pyboy.send_input(VALID_ACTIONS[action]) + # self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) self.pyboy.tick(self.action_freq, render=True) if self.read_bit(0xD803, 0): @@ -647,9 +647,9 @@ def cut_if_next(self): self.pyboy.tick(self.action_freq, render=True) # scroll to pokemon # 1 is the item index for pokemon - for _ in range(24): - if self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] == 1: - break + # for _ in range(24): + while self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] != 1: + # break self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) self.pyboy.tick(self.action_freq, render=True) @@ -664,7 +664,7 @@ def cut_if_next(self): self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) self.pyboy.tick(self.action_freq, render=True) party_mon = self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] - _, addr = self.pyboy.symbol_lookup(f"wPartyMon{party_mon+1}Moves") + _, addr = self.pyboy.symbol_lookup(f"wPartyMon{party_mon%6+1}Moves") if 15 in self.pyboy.memory[addr : addr + 4]: break From e8436fccfb06df9051656667f2fec79b1e7b3ac9 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 2 Jun 2024 11:18:03 -0400 Subject: [PATCH 09/68] add back in badges obs, fix cut_if_next --- pokemonred_puffer/environment.py | 14 +++++++------- pokemonred_puffer/policies/multi_convolutional.py | 7 +++++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 78de5b8..fa95647 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -207,11 +207,11 @@ def __init__(self, env_config: pufferlib.namespace): "direction": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), # "reset_map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), "battle_type": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), - # "cut_in_party": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), + "cut_in_party": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), # "x": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), # "y": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), # "map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), - # "badges": spaces.Box(low=0, high=8, shape=(1,), dtype=np.uint8), + "badges": spaces.Box(low=0, high=0xFFFF, shape=(1,), dtype=np.uint8), } ) @@ -515,11 +515,11 @@ def _get_obs(self): ), # "reset_map_id": np.array(self.read_m("wLastBlackoutMap"), dtype=np.uint8), "battle_type": np.array(self.read_m("wIsInBattle") + 1, dtype=np.uint8), - # "cut_in_party": np.array(self.check_if_party_has_cut(), dtype=np.uint8), + "cut_in_party": np.array(self.check_if_party_has_cut(), dtype=np.uint8), # "x": np.array(player_x, dtype=np.uint8), # "y": np.array(player_y, dtype=np.uint8), # "map_id": np.array(map_n, dtype=np.uint8), - # "badges": np.array(self.get_badges(), dtype=np.uint8), + "badges": np.array(self.read_m("wObtainedBadges"), dtype=np.uint8), } def set_perfect_iv_dvs(self): @@ -647,9 +647,9 @@ def cut_if_next(self): self.pyboy.tick(self.action_freq, render=True) # scroll to pokemon # 1 is the item index for pokemon - # for _ in range(24): - while self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] != 1: - # break + for _ in range(24): + if self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] != 1: + break self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) self.pyboy.tick(self.action_freq, render=True) diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index ced205e..0d1d947 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -69,6 +69,7 @@ def __init__( self.register_buffer( "unpack_shift", torch.tensor([6, 4, 2, 0], dtype=torch.uint8), persistent=False ) + self.register_buffer("binary_mask", torch.tensor([0] + [2**i for i in range(7)])) def encode_observations(self, observations): observations = unpack_batched_obs(observations, self.unflatten_context) @@ -98,6 +99,8 @@ def encode_observations(self, observations): .flatten() .int(), ).reshape(restored_shape) + # > 0 doesn't risk a type conversion + badges = (observations["badges"] >> self.binary_mask) > 0 image_observation = torch.cat((screen, visited_mask, global_map), dim=-1) if self.channels_last: @@ -112,11 +115,11 @@ def encode_observations(self, observations): one_hot(observations["direction"].long(), 4).float().squeeze(1), # one_hot(observations["reset_map_id"].long(), 0xF7).float().squeeze(1), one_hot(observations["battle_type"].long(), 4).float().squeeze(1), - # observations["cut_in_party"].float(), + observations["cut_in_party"].float(), # observations["x"].float(), # observations["y"].float(), # one_hot(observations["map_id"].long(), 0xF7).float().squeeze(1), - # one_hot(observations["badges"].long(), 8).float().squeeze(1), + badges.float().squeeze(1), ), dim=-1, ) From 3d88d6dfac2c86d714083a0b9422b9618ff0d925 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 2 Jun 2024 11:21:08 -0400 Subject: [PATCH 10/68] Remember to send input to emulator --- pokemonred_puffer/environment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index fa95647..0f45b32 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -581,8 +581,8 @@ def run_action_on_emulator(self, action): self.action_hist[action] += 1 # press button then release after some steps # TODO: Add video saving logic - # self.pyboy.send_input(VALID_ACTIONS[action]) - # self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) + self.pyboy.send_input(VALID_ACTIONS[action]) + self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) self.pyboy.tick(self.action_freq, render=True) if self.read_bit(0xD803, 0): From d8568b10ded3aeca9d263e890acabef85c81e829 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 2 Jun 2024 19:23:15 -0400 Subject: [PATCH 11/68] Fix teach_cut --- pokemonred_puffer/environment.py | 56 +++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 0f45b32..72fc2d4 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -107,6 +107,37 @@ ] ) +CUT_SPECIES_IDS = { + 0x99, + 0x09, + 0x9A, + 0xB0, + 0xB2, + 0xB4, + 0x72, + 0x60, + 0x61, + 0xB9, + 0xBA, + 0xBB, + 0x6D, + 0x2E, + 0xBC, + 0xBD, + 0xBE, + 0x18, + 0x9B, + 0x40, + 0x4E, + 0x8A, + 0x0B, + 0x1E, + 0x1A, + 0x1D, + 0x5B, + 0x15, +} + VALID_ACTIONS = [ WindowEvent.PRESS_ARROW_DOWN, WindowEvent.PRESS_ARROW_LEFT, @@ -587,20 +618,23 @@ def run_action_on_emulator(self, action): if self.read_bit(0xD803, 0): if not self.check_if_party_has_cut(): - self.teach_cut_to_bulba() + self.teach_cut() self.cut_if_next() - def teach_cut_to_bulba(self): + def teach_cut(self): # find bulba and replace tackle (first skill) with cut - move_pp_addr = [0xD188, 0xD189, 0xD18A, 0xD18B] - # D164-D169 - PARTY_ADDR = [0xD164, 0xD165, 0xD166, 0xD167, 0xD168, 0xD169] - for poke_idx, poke_addr in enumerate(PARTY_ADDR): - poke = self.pyboy.memory[poke_addr] - if poke in [9, 153, 154]: # bulba line - slot = 0 # this should have tackle - self.pyboy.memory[poke_addr + 8 + slot] = 15 - self.pyboy.memory[move_pp_addr[slot] + 44 * poke_idx] = 30 + party_size = self.read_m("wPartyCount") + for i in range(party_size): + # PRET 1-indexes + _, species_addr = self.pyboy.symbol_lookup(f"wPartyMon{i+1}Species") + poke = self.pyboy.memory[species_addr] + # https://github.com/pret/pokered/blob/d38cf5281a902b4bd167a46a7c9fd9db436484a7/constants/pokemon_constants.asm + if poke in CUT_SPECIES_IDS: + slot = 0 + _, move_addr = self.pyboy.symbol_lookup(f"wPartyMon{i+1}Moves") + _, pp_addr = self.pyboy.symbol_lookup(f"wPartyMon{i+1}PP") + self.pyboy.memory[move_addr + slot] = 15 + self.pyboy.memory[pp_addr + slot] = 30 # fill up pp: 30/30 def cut_if_next(self): From c810a59d15e79783888949b21231ef1b7bbd2e7b Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:45:33 -0400 Subject: [PATCH 12/68] Fix cut script again --- pokemonred_puffer/environment.py | 13 +++++++------ pokemonred_puffer/policies/multi_convolutional.py | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 72fc2d4..31138a0 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -239,10 +239,11 @@ def __init__(self, env_config: pufferlib.namespace): # "reset_map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), "battle_type": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), "cut_in_party": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), - # "x": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), + # "x": spaces.Box(low=0, high=255, shape=(1,), dtype=np.u`int8), # "y": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), # "map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), - "badges": spaces.Box(low=0, high=0xFFFF, shape=(1,), dtype=np.uint8), + # "badges": spaces.Box(low=0, high=np.iinfo(np.uint16).max, shape=(1,), dtype=np.uint16), + "badges": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), } ) @@ -612,8 +613,8 @@ def run_action_on_emulator(self, action): self.action_hist[action] += 1 # press button then release after some steps # TODO: Add video saving logic - self.pyboy.send_input(VALID_ACTIONS[action]) - self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) + # self.pyboy.send_input(VALID_ACTIONS[action]) + # self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) self.pyboy.tick(self.action_freq, render=True) if self.read_bit(0xD803, 0): @@ -682,7 +683,7 @@ def cut_if_next(self): # scroll to pokemon # 1 is the item index for pokemon for _ in range(24): - if self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] != 1: + if self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] == 1: break self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) @@ -913,7 +914,7 @@ def read_event_bits(self): return self.pyboy.memory[addr : addr + EVENTS_FLAGS_LENGTH] def get_badges(self): - return self.read_m("wObtainedBadges").bit_count() + return self.read_short("wObtainedBadges").bit_count() def read_party(self): _, addr = self.pyboy.symbol_lookup("wPartySpecies") diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index 0d1d947..81f8a6e 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -69,7 +69,7 @@ def __init__( self.register_buffer( "unpack_shift", torch.tensor([6, 4, 2, 0], dtype=torch.uint8), persistent=False ) - self.register_buffer("binary_mask", torch.tensor([0] + [2**i for i in range(7)])) + self.register_buffer("binary_mask", torch.tensor([2**i for i in range(8)])) def encode_observations(self, observations): observations = unpack_batched_obs(observations, self.unflatten_context) @@ -100,7 +100,7 @@ def encode_observations(self, observations): .int(), ).reshape(restored_shape) # > 0 doesn't risk a type conversion - badges = (observations["badges"] >> self.binary_mask) > 0 + badges = (observations["badges"] & self.binary_mask) > 0 image_observation = torch.cat((screen, visited_mask, global_map), dim=-1) if self.channels_last: From b7193498110032b2f1413589e9f44b6f0eb300ed Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Mon, 3 Jun 2024 00:00:15 -0400 Subject: [PATCH 13/68] Bah remember to uncomment send input again --- pokemonred_puffer/environment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 31138a0..83e39c8 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -613,8 +613,8 @@ def run_action_on_emulator(self, action): self.action_hist[action] += 1 # press button then release after some steps # TODO: Add video saving logic - # self.pyboy.send_input(VALID_ACTIONS[action]) - # self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) + self.pyboy.send_input(VALID_ACTIONS[action]) + self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) self.pyboy.tick(self.action_freq, render=True) if self.read_bit(0xD803, 0): From 4f1f8cb4f630961b34822da1652c5240e5e8c1bf Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Mon, 3 Jun 2024 08:21:33 -0400 Subject: [PATCH 14/68] Cut event obs --- pokemonred_puffer/environment.py | 2 ++ pokemonred_puffer/policies/multi_convolutional.py | 1 + 2 files changed, 3 insertions(+) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 83e39c8..2a618f5 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -238,6 +238,7 @@ def __init__(self, env_config: pufferlib.namespace): "direction": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), # "reset_map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), "battle_type": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), + "cut_event": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), "cut_in_party": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), # "x": spaces.Box(low=0, high=255, shape=(1,), dtype=np.u`int8), # "y": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), @@ -547,6 +548,7 @@ def _get_obs(self): ), # "reset_map_id": np.array(self.read_m("wLastBlackoutMap"), dtype=np.uint8), "battle_type": np.array(self.read_m("wIsInBattle") + 1, dtype=np.uint8), + "cut_event": np.array(self.read_bit(0xD803, 0), dtype=np.uint8), "cut_in_party": np.array(self.check_if_party_has_cut(), dtype=np.uint8), # "x": np.array(player_x, dtype=np.uint8), # "y": np.array(player_y, dtype=np.uint8), diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index 81f8a6e..2da3d4a 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -115,6 +115,7 @@ def encode_observations(self, observations): one_hot(observations["direction"].long(), 4).float().squeeze(1), # one_hot(observations["reset_map_id"].long(), 0xF7).float().squeeze(1), one_hot(observations["battle_type"].long(), 4).float().squeeze(1), + observations["cut_event"].float(), observations["cut_in_party"].float(), # observations["x"].float(), # observations["y"].float(), From 2c3e8fc0863313067c20bd73251546768c13f430 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:19:12 -0400 Subject: [PATCH 15/68] fix badges again. Add rocket hideout specific event --- config.yaml | 31 ++++++------ pokemonred_puffer/environment.py | 15 +++++- .../policies/multi_convolutional.py | 4 +- pokemonred_puffer/rewards/baseline.py | 48 +++++++++++-------- 4 files changed, 57 insertions(+), 41 deletions(-) diff --git a/config.yaml b/config.yaml index f30be7b..261561b 100644 --- a/config.yaml +++ b/config.yaml @@ -7,7 +7,7 @@ debug: env: headless: False stream_wrapper: False - init_state: cut3 + init_state: cut max_steps: 1_000_000 train: device: cpu @@ -157,25 +157,24 @@ rewards: explore_npcs: 0.02 explore_hidden_objs: 0.02 - baseline.RockTunnelReplicationEnv: + baseline.CutWithObjectRewardsEnv: reward: - level: 1.0 - exploration: 0.02 - taught_cut: 10.0 - event: 3.0 - seen_pokemon: 4.0 - caught_pokemon: 4.0 + event: 1.0 + bill_saved: 5.0 moves_obtained: 4.0 + hm_count: 10.0 + badges: 10.0 + exploration: 0.02 cut_coords: 1.0 cut_tiles: 1.0 - start_menu: 0.005 - pokemon_menu: 0.05 - stats_menu: 0.05 - bag_menu: 0.05 - pokecenter: 5.0 - # Really an addition to event reward - badges: 2.0 - bill_saved: 2.0 + start_menu: 0.0 + pokemon_menu: 0.0 + stats_menu: 0.0 + bag_menu: 0.0 + taught_cut: 10.0 + explore_npcs: 0.02 + explore_hidden_objs: 0.02 + rocket_hideout_found: 5.0 diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 2a618f5..e9531a2 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -287,6 +287,12 @@ def register_hooks(self): self.pyboy.hook_register( None, "CheckForHiddenObject.foundMatchingObject", self.hidden_object_hook, None ) + """ + _, addr = self.pyboy.symbol_lookup("IsSpriteOrSignInFrontOfPlayer.retry") + self.pyboy.hook_register( + None, addr-1, self.sign_hook, None + ) + """ self.pyboy.hook_register(None, "HandleBlackOut", self.blackout_hook, None) self.pyboy.hook_register(None, "SetLastBlackoutMap.done", self.blackout_update_hook, None) # self.pyboy.hook_register(None, "UsedCut.nothingToCut", self.cut_hook, context=True) @@ -306,6 +312,7 @@ def reset(self, seed: Optional[int] = None, options: Optional[dict[str, Any]] = self.init_mem() # We only init seen hidden objs once cause they can only be found once! self.seen_hidden_objs = {} + self.seen_signs = {} if options.get("state", None) is not None: self.pyboy.load_state(io.BytesIO(options["state"])) self.reset_count += 1 @@ -553,7 +560,7 @@ def _get_obs(self): # "x": np.array(player_x, dtype=np.uint8), # "y": np.array(player_y, dtype=np.uint8), # "map_id": np.array(map_n, dtype=np.uint8), - "badges": np.array(self.read_m("wObtainedBadges"), dtype=np.uint8), + "badges": np.array(self.read_short("wObtainedBadges").bit_count(), dtype=np.uint8), } def set_perfect_iv_dvs(self): @@ -711,6 +718,12 @@ def cut_if_next(self): self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) self.pyboy.tick(4 * self.action_freq, render=True) + def sign_hook(self, *args, **kwargs): + sign_id = self.pyboy.memory[self.pyboy.symbol_lookup("hSpriteIndexOrTextID")[1]] + map_id = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMap")[1]] + # We will store this by map id, y, x, + self.seen_hidden_objs[(map_id, sign_id)] = 1 + def hidden_object_hook(self, *args, **kwargs): hidden_object_id = self.pyboy.memory[self.pyboy.symbol_lookup("wHiddenObjectIndex")[1]] map_id = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMap")[1]] diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index 2da3d4a..07f2933 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -69,7 +69,6 @@ def __init__( self.register_buffer( "unpack_shift", torch.tensor([6, 4, 2, 0], dtype=torch.uint8), persistent=False ) - self.register_buffer("binary_mask", torch.tensor([2**i for i in range(8)])) def encode_observations(self, observations): observations = unpack_batched_obs(observations, self.unflatten_context) @@ -99,8 +98,7 @@ def encode_observations(self, observations): .flatten() .int(), ).reshape(restored_shape) - # > 0 doesn't risk a type conversion - badges = (observations["badges"] & self.binary_mask) > 0 + badges = (torch.arange(8) + 1) <= observations["badges"] image_observation = torch.cat((screen, visited_mask, global_map), dim=-1) if self.channels_last: diff --git a/pokemonred_puffer/rewards/baseline.py b/pokemonred_puffer/rewards/baseline.py index c6da5d2..b914752 100644 --- a/pokemonred_puffer/rewards/baseline.py +++ b/pokemonred_puffer/rewards/baseline.py @@ -5,8 +5,6 @@ RedGymEnv, ) -import numpy as np - MUSEUM_TICKET = (0xD754, 0) @@ -165,16 +163,30 @@ def get_levels_reward(self): return 15 + (self.max_level_sum - 15) / 4 -class RockTunnelReplicationEnv(BaselineRewardEnv): +class CutWithObjectRewardsEnv(BaselineRewardEnv): def get_game_state_reward(self): return { - "level": self.reward_config["level"] * self.get_levels_reward(), - "exploration": self.reward_config["exploration"] * sum(self.seen_coords.values()), - "taught_cut": self.reward_config["taught_cut"] * int(self.taught_cut), "event": self.reward_config["event"] * self.update_max_event_rew(), - "seen_pokemon": self.reward_config["seen_pokemon"] * np.sum(self.seen_pokemon), - "caught_pokemon": self.reward_config["caught_pokemon"] * np.sum(self.caught_pokemon), - "moves_obtained": self.reward_config["moves_obtained"] * np.sum(self.moves_obtained), + "met_bill": self.reward_config["bill_saved"] * int(self.read_bit(0xD7F1, 0)), + "used_cell_separator_on_bill": ( + self.reward_config["bill_saved"] * int(self.read_bit(0xD7F2, 3)) + ), + "ss_ticket": self.reward_config["bill_saved"] * int(self.read_bit(0xD7F2, 4)), + "met_bill_2": self.reward_config["bill_saved"] * int(self.read_bit(0xD7F2, 5)), + "bill_said_use_cell_separator": ( + self.reward_config["bill_saved"] * int(self.read_bit(0xD7F2, 6)) + ), + "left_bills_house_after_helping": ( + self.reward_config["bill_saved"] * int(self.read_bit(0xD7F2, 7)) + ), + "moves_obtained": self.reward_config["moves_obtained"] * sum(self.moves_obtained), + "hm_count": self.reward_config["hm_count"] * self.get_hm_count(), + "badges": self.reward_config["badges"] * self.get_badges(), + "exploration": self.reward_config["exploration"] * sum(self.seen_coords.values()), + "explore_npcs": self.reward_config["explore_npcs"] * sum(self.seen_npcs.values()), + "explore_hidden_objs": ( + self.reward_config["explore_hidden_objs"] * sum(self.seen_hidden_objs.values()) + ), "cut_coords": self.reward_config["cut_coords"] * sum(self.cut_coords.values()), "cut_tiles": self.reward_config["cut_tiles"] * sum(self.cut_tiles), "start_menu": ( @@ -187,18 +199,12 @@ def get_game_state_reward(self): self.reward_config["stats_menu"] * self.seen_stats_menu * int(self.taught_cut) ), "bag_menu": self.reward_config["bag_menu"] * self.seen_bag_menu * int(self.taught_cut), - # "pokecenter": self.reward_config["pokecenter"] * np.sum(self.pokecenters), - "badges": self.reward_config["badges"] * self.get_badges(), - "met_bill": self.reward_config["bill_saved"] * int(self.read_bit(0xD7F1, 0)), - "used_cell_separator_on_bill": self.reward_config["bill_saved"] - * int(self.read_bit(0xD7F2, 3)), - "ss_ticket": self.reward_config["bill_saved"] * int(self.read_bit(0xD7F2, 4)), - "met_bill_2": self.reward_config["bill_saved"] * int(self.read_bit(0xD7F2, 5)), - "bill_said_use_cell_separator": self.reward_config["bill_saved"] - * int(self.read_bit(0xD7F2, 6)), - "left_bills_house_after_helping": self.reward_config["bill_saved"] - * int(self.read_bit(0xD7F2, 7)), - "rival3": self.reward_config["event"] * int(self.read_m(0xD665) == 4), + "taught_cut": self.reward_config["taught_cut"] * int(self.taught_cut), + "seen_pokemon": self.reward_config["seen_pokemon"] * sum(self.seen_pokemon), + "caught_pokemon": self.reward_config["caught_pokemon"] * sum(self.caught_pokemon), + "level": self.reward_config["level"] * self.get_levels_reward(), + "rocket_hideout_found": self.reward_config["rocket_hideout_found"] + * int(self.read_bit(0xD77E, 1)), } def get_levels_reward(self): From a252887a61931d3504fa03b48d3f7e5cea07fb3c Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:25:56 -0400 Subject: [PATCH 16/68] If in a dark cave, auto turn on flash --- config.yaml | 1 + pokemonred_puffer/environment.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/config.yaml b/config.yaml index 261561b..c5975d5 100644 --- a/config.yaml +++ b/config.yaml @@ -47,6 +47,7 @@ env: reduce_res: True two_bit: True log_frequency: 2000 + auto_flash: True train: seed: 1 diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index e9531a2..412813b 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -189,6 +189,7 @@ def __init__(self, env_config: pufferlib.namespace): self.gb_path = env_config.gb_path self.log_frequency = env_config.log_frequency self.two_bit = env_config.two_bit + self.auto_flash = env_config.auto_flash self.action_space = ACTION_SPACE # Obs space-related. TODO: avoid hardcoding? @@ -582,6 +583,10 @@ def step(self, action): if self.save_video and self.step_count == 0: self.start_video() + _, wMapPalOffset = self.pyboy.symbol_lookup("wMapPalOffset")[1] + if self.auto_flash and self.pyboy.memory[wMapPalOffset] == 6: + self.pyboy.memory[wMapPalOffset] = 0 + self.run_action_on_emulator(action) self.update_seen_coords() self.update_health() From 77b3343feb770cf877ad36edc664cb5c5fa3c060 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:30:48 -0400 Subject: [PATCH 17/68] Copy the correct cut env --- config.yaml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/config.yaml b/config.yaml index c5975d5..1fb8e6c 100644 --- a/config.yaml +++ b/config.yaml @@ -140,6 +140,7 @@ rewards: pokemon_menu: 0.1 stats_menu: 0.1 bag_menu: 0.1 + baseline.TeachCutReplicationEnvFork: reward: event: 1.0 @@ -162,19 +163,19 @@ rewards: reward: event: 1.0 bill_saved: 5.0 + seen_pokemon: 4.0 + caught_pokemon: 4.0 moves_obtained: 4.0 hm_count: 10.0 + level: 1.0 badges: 10.0 exploration: 0.02 - cut_coords: 1.0 - cut_tiles: 1.0 - start_menu: 0.0 + cut_coords: 0.0 + cut_tiles: 0.0 + start_menu: 0.00 pokemon_menu: 0.0 stats_menu: 0.0 bag_menu: 0.0 - taught_cut: 10.0 - explore_npcs: 0.02 - explore_hidden_objs: 0.02 rocket_hideout_found: 5.0 From b3a20583710ac8b1b0488a9eec2016360dc2adb1 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:32:10 -0400 Subject: [PATCH 18/68] Do the same but for the reward wrapper --- pokemonred_puffer/rewards/baseline.py | 43 ++++++++++----------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/pokemonred_puffer/rewards/baseline.py b/pokemonred_puffer/rewards/baseline.py index b914752..751eb9d 100644 --- a/pokemonred_puffer/rewards/baseline.py +++ b/pokemonred_puffer/rewards/baseline.py @@ -168,41 +168,28 @@ def get_game_state_reward(self): return { "event": self.reward_config["event"] * self.update_max_event_rew(), "met_bill": self.reward_config["bill_saved"] * int(self.read_bit(0xD7F1, 0)), - "used_cell_separator_on_bill": ( - self.reward_config["bill_saved"] * int(self.read_bit(0xD7F2, 3)) - ), + "used_cell_separator_on_bill": self.reward_config["bill_saved"] + * int(self.read_bit(0xD7F2, 3)), "ss_ticket": self.reward_config["bill_saved"] * int(self.read_bit(0xD7F2, 4)), "met_bill_2": self.reward_config["bill_saved"] * int(self.read_bit(0xD7F2, 5)), - "bill_said_use_cell_separator": ( - self.reward_config["bill_saved"] * int(self.read_bit(0xD7F2, 6)) - ), - "left_bills_house_after_helping": ( - self.reward_config["bill_saved"] * int(self.read_bit(0xD7F2, 7)) - ), + "bill_said_use_cell_separator": self.reward_config["bill_saved"] + * int(self.read_bit(0xD7F2, 6)), + "left_bills_house_after_helping": self.reward_config["bill_saved"] + * int(self.read_bit(0xD7F2, 7)), + "seen_pokemon": self.reward_config["seen_pokemon"] * sum(self.seen_pokemon), + "caught_pokemon": self.reward_config["caught_pokemon"] * sum(self.caught_pokemon), "moves_obtained": self.reward_config["moves_obtained"] * sum(self.moves_obtained), "hm_count": self.reward_config["hm_count"] * self.get_hm_count(), + "level": self.reward_config["level"] * self.get_levels_reward(), "badges": self.reward_config["badges"] * self.get_badges(), "exploration": self.reward_config["exploration"] * sum(self.seen_coords.values()), - "explore_npcs": self.reward_config["explore_npcs"] * sum(self.seen_npcs.values()), - "explore_hidden_objs": ( - self.reward_config["explore_hidden_objs"] * sum(self.seen_hidden_objs.values()) - ), "cut_coords": self.reward_config["cut_coords"] * sum(self.cut_coords.values()), - "cut_tiles": self.reward_config["cut_tiles"] * sum(self.cut_tiles), - "start_menu": ( - self.reward_config["start_menu"] * self.seen_start_menu * int(self.taught_cut) - ), - "pokemon_menu": ( - self.reward_config["pokemon_menu"] * self.seen_pokemon_menu * int(self.taught_cut) - ), - "stats_menu": ( - self.reward_config["stats_menu"] * self.seen_stats_menu * int(self.taught_cut) - ), - "bag_menu": self.reward_config["bag_menu"] * self.seen_bag_menu * int(self.taught_cut), - "taught_cut": self.reward_config["taught_cut"] * int(self.taught_cut), - "seen_pokemon": self.reward_config["seen_pokemon"] * sum(self.seen_pokemon), - "caught_pokemon": self.reward_config["caught_pokemon"] * sum(self.caught_pokemon), - "level": self.reward_config["level"] * self.get_levels_reward(), + "cut_tiles": self.reward_config["cut_tiles"] * sum(self.cut_tiles.values()), + "start_menu": self.reward_config["start_menu"] * self.seen_start_menu, + "pokemon_menu": self.reward_config["pokemon_menu"] * self.seen_pokemon_menu, + "stats_menu": self.reward_config["stats_menu"] * self.seen_stats_menu, + "bag_menu": self.reward_config["bag_menu"] * self.seen_bag_menu, + "rival3": self.reward_config["event"] * int(self.read_m(0xD665) == 4), "rocket_hideout_found": self.reward_config["rocket_hideout_found"] * int(self.read_bit(0xD77E, 1)), } From 656bc04cfc55c17b8608fb4cb5381fa426afd6e1 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:34:19 -0400 Subject: [PATCH 19/68] badge buffer for device --- pokemonred_puffer/policies/multi_convolutional.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index 07f2933..946f2e8 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -69,6 +69,9 @@ def __init__( self.register_buffer( "unpack_shift", torch.tensor([6, 4, 2, 0], dtype=torch.uint8), persistent=False ) + self.register_buffer( + "badge_buffer", torch.arange(8) + 1, dtype=torch.uint8, persistent=False + ) def encode_observations(self, observations): observations = unpack_batched_obs(observations, self.unflatten_context) @@ -98,7 +101,7 @@ def encode_observations(self, observations): .flatten() .int(), ).reshape(restored_shape) - badges = (torch.arange(8) + 1) <= observations["badges"] + badges = self.badge_buffer <= observations["badges"] image_observation = torch.cat((screen, visited_mask, global_map), dim=-1) if self.channels_last: From 03966afe5b89d8219317d0719aab1e39b6655aa3 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:46:29 -0400 Subject: [PATCH 20/68] Fix register buffer --- pokemonred_puffer/policies/multi_convolutional.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index 946f2e8..8019c53 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -69,9 +69,7 @@ def __init__( self.register_buffer( "unpack_shift", torch.tensor([6, 4, 2, 0], dtype=torch.uint8), persistent=False ) - self.register_buffer( - "badge_buffer", torch.arange(8) + 1, dtype=torch.uint8, persistent=False - ) + self.register_buffer("badge_buffer", torch.arange(8) + 1, persistent=False) def encode_observations(self, observations): observations = unpack_batched_obs(observations, self.unflatten_context) From 3c2b347f65e5d3ef61eef8a1636fdd7010f367b0 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Wed, 5 Jun 2024 21:47:57 -0400 Subject: [PATCH 21/68] Fix symbol lookup usage --- pokemonred_puffer/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 412813b..7ca9738 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -583,7 +583,7 @@ def step(self, action): if self.save_video and self.step_count == 0: self.start_video() - _, wMapPalOffset = self.pyboy.symbol_lookup("wMapPalOffset")[1] + _, wMapPalOffset = self.pyboy.symbol_lookup("wMapPalOffset") if self.auto_flash and self.pyboy.memory[wMapPalOffset] == 6: self.pyboy.memory[wMapPalOffset] = 0 From b5b0d0960661ed96e05b08fdbafeb9e8ba803ffa Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:28:55 -0400 Subject: [PATCH 22/68] Map id embeddings --- pokemonred_puffer/environment.py | 8 ++++---- pokemonred_puffer/policies/multi_convolutional.py | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 7ca9738..e4dd298 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -237,13 +237,13 @@ def __init__(self, env_config: pufferlib.namespace): ), # Discrete is more apt, but pufferlib is slower at processing Discrete "direction": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), - # "reset_map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), + "blackout_map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), "battle_type": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), "cut_event": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), "cut_in_party": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), # "x": spaces.Box(low=0, high=255, shape=(1,), dtype=np.u`int8), # "y": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), - # "map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), + "map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), # "badges": spaces.Box(low=0, high=np.iinfo(np.uint16).max, shape=(1,), dtype=np.uint16), "badges": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), } @@ -554,13 +554,13 @@ def _get_obs(self): "direction": np.array( self.read_m("wSpritePlayerStateData1FacingDirection") // 4, dtype=np.uint8 ), - # "reset_map_id": np.array(self.read_m("wLastBlackoutMap"), dtype=np.uint8), + "blackout_map_id": np.array(self.read_m("wLastBlackoutMap"), dtype=np.uint8), "battle_type": np.array(self.read_m("wIsInBattle") + 1, dtype=np.uint8), "cut_event": np.array(self.read_bit(0xD803, 0), dtype=np.uint8), "cut_in_party": np.array(self.check_if_party_has_cut(), dtype=np.uint8), # "x": np.array(player_x, dtype=np.uint8), # "y": np.array(player_y, dtype=np.uint8), - # "map_id": np.array(map_n, dtype=np.uint8), + "map_id": np.array(self.read_m(0xD35E), dtype=np.uint8), "badges": np.array(self.read_short("wObtainedBadges").bit_count(), dtype=np.uint8), } diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index 8019c53..678f664 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -71,6 +71,10 @@ def __init__( ) self.register_buffer("badge_buffer", torch.arange(8) + 1, persistent=False) + # pokemon has 0xF7 map ids + # Lets start with 4 dims for now. Could try 8 + self.map_embeddings = torch.nn.Embedding(0xF7, 4, dtype=torch.float32) + def encode_observations(self, observations): observations = unpack_batched_obs(observations, self.unflatten_context) @@ -100,6 +104,8 @@ def encode_observations(self, observations): .int(), ).reshape(restored_shape) badges = self.badge_buffer <= observations["badges"] + map_id = self.map_embeddings(observations["map_id"].long()) + blackout_map_id = self.map_embeddings(observations["blackout_map_id"].long()) image_observation = torch.cat((screen, visited_mask, global_map), dim=-1) if self.channels_last: @@ -120,6 +126,8 @@ def encode_observations(self, observations): # observations["y"].float(), # one_hot(observations["map_id"].long(), 0xF7).float().squeeze(1), badges.float().squeeze(1), + map_id.squeeze(1), + blackout_map_id.squeeze(1), ), dim=-1, ) From 8898c638eab94ef84ac6b6c796b376cb5caae16b Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:32:36 -0400 Subject: [PATCH 23/68] no cutmon check --- pokemonred_puffer/environment.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index e4dd298..5934453 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -621,6 +621,12 @@ def step(self, action): # self.caught_pokemon[6] == 1 # squirtle ) + # cut mon check + if not self.party_has_cut_capable_mon(): + reset = True + self.first = True + new_reward = -self.progress_reward + return obs, new_reward, reset, False, info def run_action_on_emulator(self, action): @@ -636,6 +642,18 @@ def run_action_on_emulator(self, action): self.teach_cut() self.cut_if_next() + def party_has_cut_capable_mon(self): + # find bulba and replace tackle (first skill) with cut + party_size = self.read_m("wPartyCount") + for i in range(party_size): + # PRET 1-indexes + _, species_addr = self.pyboy.symbol_lookup(f"wPartyMon{i+1}Species") + poke = self.pyboy.memory[species_addr] + # https://github.com/pret/pokered/blob/d38cf5281a902b4bd167a46a7c9fd9db436484a7/constants/pokemon_constants.asm + if poke in CUT_SPECIES_IDS: + return True + return False + def teach_cut(self): # find bulba and replace tackle (first skill) with cut party_size = self.read_m("wPartyCount") From ff94a8701f464080ab6fd57f5245d134b0be93ab Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:07:33 -0400 Subject: [PATCH 24/68] Wrappers for reset --- config.yaml | 20 +++++++++ pokemonred_puffer/environment.py | 9 ---- pokemonred_puffer/rewards/baseline.py | 2 + pokemonred_puffer/wrappers/exploration.py | 51 ++++++++++++++++++++++- 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/config.yaml b/config.yaml index 1fb8e6c..02eebff 100644 --- a/config.yaml +++ b/config.yaml @@ -109,16 +109,35 @@ wrappers: bag_menu: 0.998 action_bag_menu: 0.998 forgetting_frequency: 10 + - exploration.OnResetExplorationWrapper: + full_reset_frequency: 0 finite_coords: - stream_wrapper.StreamWrapper: user: thatguy - exploration.MaxLengthWrapper: capacity: 1750 + - exploration.OnResetExplorationWrapper: + full_reset_frequency: 0 stream_only: - stream_wrapper.StreamWrapper: user: thatguy + - exploration.OnresetLowerToFixedValueWrapper: + fixed_value: + coords: 0.33 + map_ids: 0.33 + npc: 0.33 + cut: 0.33 + explore: 0.33 + - exploration.OnResetExplorationWrapper: + full_reset_frequency: 0 + + fixed_reset_value: + - stream_wrapper.StreamWrapper: + user: thatguy + - exploration.OnResetExplorationWrapper: + full_reset_frequency: 0 rewards: baseline.BaselineRewardEnv: @@ -177,6 +196,7 @@ rewards: stats_menu: 0.0 bag_menu: 0.0 rocket_hideout_found: 5.0 + explore_hidden_objs: 0.02 diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 5934453..c28efb2 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -321,8 +321,6 @@ def reset(self, seed: Optional[int] = None, options: Optional[dict[str, Any]] = with open(self.init_state_path, "rb") as f: self.pyboy.load_state(f) self.reset_count = 0 - self.explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) - self.cut_explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) self.base_event_flags = sum( self.read_m(i).bit_count() for i in range(EVENT_FLAGS_START, EVENT_FLAGS_START + EVENTS_FLAGS_LENGTH) @@ -396,13 +394,6 @@ def init_mem(self): self.seen_action_bag_menu = 0 def reset_mem(self): - self.seen_coords.update((k, 0) for k, _ in self.seen_coords.items()) - self.seen_map_ids *= 0 - self.seen_npcs.update((k, 0) for k, _ in self.seen_npcs.items()) - - self.cut_coords.update((k, 0) for k, _ in self.cut_coords.items()) - self.cut_tiles.update((k, 0) for k, _ in self.cut_tiles.items()) - self.seen_start_menu = 0 self.seen_pokemon_menu = 0 self.seen_stats_menu = 0 diff --git a/pokemonred_puffer/rewards/baseline.py b/pokemonred_puffer/rewards/baseline.py index 751eb9d..d64c3b8 100644 --- a/pokemonred_puffer/rewards/baseline.py +++ b/pokemonred_puffer/rewards/baseline.py @@ -192,6 +192,8 @@ def get_game_state_reward(self): "rival3": self.reward_config["event"] * int(self.read_m(0xD665) == 4), "rocket_hideout_found": self.reward_config["rocket_hideout_found"] * int(self.read_bit(0xD77E, 1)), + "explore_hidden_objs": sum(self.seen_hidden_objs.values()) + * self.reward_config["explore_hidden_objs"], } def get_levels_reward(self): diff --git a/pokemonred_puffer/wrappers/exploration.py b/pokemonred_puffer/wrappers/exploration.py index 8c6c482..843becd 100644 --- a/pokemonred_puffer/wrappers/exploration.py +++ b/pokemonred_puffer/wrappers/exploration.py @@ -4,7 +4,7 @@ import pufferlib from pokemonred_puffer.environment import RedGymEnv -from pokemonred_puffer.global_map import local_to_global +from pokemonred_puffer.global_map import GLOBAL_MAP_SHAPE, local_to_global class LRUCache: @@ -93,3 +93,52 @@ def step(self, action): def reset(self, *args, **kwargs): self.cache.clear() return self.env.reset(*args, **kwargs) + + +class OnResetExplorationWrapper(gym.Wrapper): + def __init__(self, env: RedGymEnv, reward_config: pufferlib.namespace): + super().__init__(env) + self.full_reset_frequency = reward_config.full_reset_frequency + self.counter = 0 + + def step(self, action): + pass + + def reset(self, *args, **kwargs): + if self.counter % self.full_reset_frequency == 0: + self.counter = 0 + self.explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) + self.cut_explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) + self.seen_coords.update((k, 0) for k, _ in self.seen_coords.items()) + self.seen_map_ids *= 0 + self.seen_npcs.update((k, 0) for k, _ in self.seen_npcs.items()) + + self.cut_coords.update((k, 0) for k, _ in self.cut_coords.items()) + self.cut_tiles.update((k, 0) for k, _ in self.cut_tiles.items()) + self.counter += 1 + + +class OnResetLowerToFixedValueWrapper(gym.Wrapper): + def __init__(self, env: RedGymEnv, reward_config: pufferlib.namespace): + super().__init__(env) + self.fixed_value = reward_config.fixed_value + + def step(self, action): + pass + + def reset(self, *args, **kwargs): + self.env.unwrapped.seen_coords.update( + (k, self.fixed_value["coords"]) for k, v in self.env.unwrapped.seen_coords.items() + ) + self.env.unwrapped.seen_map_ids *= self.fixed_value["map_ids"] + self.env.unwrapped.seen_npcs.update( + (k, self.fixed_value["npc"]) for k, v in self.env.unwrapped.seen_npcs.items() + ) + self.env.unwrapped.cut_tiles.update( + (k, self.fixed_value["cut"]) for k, v in self.env.unwrapped.seen_npcs.items() + ) + self.env.unwrapped.cut_coords.update( + (k, self.fixed_value["cut"]) for k, v in self.env.unwrapped.seen_npcs.items() + ) + self.env.unwrapped.explore_map = self.fixed_value["explore"] + self.cut_explore_map = self.fixed_value["cut"] From 02a3556b637092248c66a39735f62c1a83f83b3c Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:08:46 -0400 Subject: [PATCH 25/68] Clear instead of update --- pokemonred_puffer/wrappers/exploration.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pokemonred_puffer/wrappers/exploration.py b/pokemonred_puffer/wrappers/exploration.py index 843becd..3c83638 100644 --- a/pokemonred_puffer/wrappers/exploration.py +++ b/pokemonred_puffer/wrappers/exploration.py @@ -109,12 +109,11 @@ def reset(self, *args, **kwargs): self.counter = 0 self.explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) self.cut_explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) - self.seen_coords.update((k, 0) for k, _ in self.seen_coords.items()) + self.seen_coords.clear() self.seen_map_ids *= 0 - self.seen_npcs.update((k, 0) for k, _ in self.seen_npcs.items()) - - self.cut_coords.update((k, 0) for k, _ in self.cut_coords.items()) - self.cut_tiles.update((k, 0) for k, _ in self.cut_tiles.items()) + self.seen_npcs.clear() + self.cut_coords.clear() + self.cut_tiles.clear() self.counter += 1 From d70da9897485b81b25a5ce753378c573e4f259d7 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:10:17 -0400 Subject: [PATCH 26/68] fix reset wrappers --- pokemonred_puffer/wrappers/exploration.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pokemonred_puffer/wrappers/exploration.py b/pokemonred_puffer/wrappers/exploration.py index 3c83638..edbe6b0 100644 --- a/pokemonred_puffer/wrappers/exploration.py +++ b/pokemonred_puffer/wrappers/exploration.py @@ -129,7 +129,9 @@ def reset(self, *args, **kwargs): self.env.unwrapped.seen_coords.update( (k, self.fixed_value["coords"]) for k, v in self.env.unwrapped.seen_coords.items() ) - self.env.unwrapped.seen_map_ids *= self.fixed_value["map_ids"] + self.env.unwrapped.seen_map_ids[self.env.unwrapped.seen_map_ids > 0] = self.fixed_value[ + "map_ids" + ] self.env.unwrapped.seen_npcs.update( (k, self.fixed_value["npc"]) for k, v in self.env.unwrapped.seen_npcs.items() ) @@ -139,5 +141,9 @@ def reset(self, *args, **kwargs): self.env.unwrapped.cut_coords.update( (k, self.fixed_value["cut"]) for k, v in self.env.unwrapped.seen_npcs.items() ) - self.env.unwrapped.explore_map = self.fixed_value["explore"] - self.cut_explore_map = self.fixed_value["cut"] + self.env.unwrapped.explore_map[self.env.unwrapped.explore_map > 0] = self.fixed_value[ + "explore" + ] + self.env.unwrapped.cut_explore_map[self.env.unwrapped.cut_explore_map > 0] = ( + self.fixed_value["cut"] + ) From 911f74a33c9d30e23d3bfdb93884c74e8d0c12b6 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:10:49 -0400 Subject: [PATCH 27/68] Full reset every 25 resets --- config.yaml | 2 +- pokemonred_puffer/wrappers/exploration.py | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/config.yaml b/config.yaml index 02eebff..b1af678 100644 --- a/config.yaml +++ b/config.yaml @@ -137,7 +137,7 @@ wrappers: - stream_wrapper.StreamWrapper: user: thatguy - exploration.OnResetExplorationWrapper: - full_reset_frequency: 0 + full_reset_frequency: 25 rewards: baseline.BaselineRewardEnv: diff --git a/pokemonred_puffer/wrappers/exploration.py b/pokemonred_puffer/wrappers/exploration.py index edbe6b0..e01a6b5 100644 --- a/pokemonred_puffer/wrappers/exploration.py +++ b/pokemonred_puffer/wrappers/exploration.py @@ -101,9 +101,6 @@ def __init__(self, env: RedGymEnv, reward_config: pufferlib.namespace): self.full_reset_frequency = reward_config.full_reset_frequency self.counter = 0 - def step(self, action): - pass - def reset(self, *args, **kwargs): if self.counter % self.full_reset_frequency == 0: self.counter = 0 @@ -115,6 +112,7 @@ def reset(self, *args, **kwargs): self.cut_coords.clear() self.cut_tiles.clear() self.counter += 1 + return self.env.reset(*args, **kwargs) class OnResetLowerToFixedValueWrapper(gym.Wrapper): @@ -122,24 +120,23 @@ def __init__(self, env: RedGymEnv, reward_config: pufferlib.namespace): super().__init__(env) self.fixed_value = reward_config.fixed_value - def step(self, action): - pass - def reset(self, *args, **kwargs): self.env.unwrapped.seen_coords.update( - (k, self.fixed_value["coords"]) for k, v in self.env.unwrapped.seen_coords.items() + (k, self.fixed_value["coords"]) + for k, v in self.env.unwrapped.seen_coords.items() + if v > 0 ) self.env.unwrapped.seen_map_ids[self.env.unwrapped.seen_map_ids > 0] = self.fixed_value[ "map_ids" ] self.env.unwrapped.seen_npcs.update( - (k, self.fixed_value["npc"]) for k, v in self.env.unwrapped.seen_npcs.items() + (k, self.fixed_value["npc"]) for k, v in self.env.unwrapped.seen_npcs.items() if v > 0 ) self.env.unwrapped.cut_tiles.update( - (k, self.fixed_value["cut"]) for k, v in self.env.unwrapped.seen_npcs.items() + (k, self.fixed_value["cut"]) for k, v in self.env.unwrapped.seen_npcs.items() if v > 0 ) self.env.unwrapped.cut_coords.update( - (k, self.fixed_value["cut"]) for k, v in self.env.unwrapped.seen_npcs.items() + (k, self.fixed_value["cut"]) for k, v in self.env.unwrapped.seen_npcs.items() if v > 0 ) self.env.unwrapped.explore_map[self.env.unwrapped.explore_map > 0] = self.fixed_value[ "explore" @@ -147,3 +144,4 @@ def reset(self, *args, **kwargs): self.env.unwrapped.cut_explore_map[self.env.unwrapped.cut_explore_map > 0] = ( self.fixed_value["cut"] ) + return self.env.reset(*args, **kwargs) From 2d7519337082931d8a247f33209d0127d8e2a3f3 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:21:41 -0400 Subject: [PATCH 28/68] Update config --- config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.yaml b/config.yaml index b1af678..418a8f2 100644 --- a/config.yaml +++ b/config.yaml @@ -123,7 +123,7 @@ wrappers: stream_only: - stream_wrapper.StreamWrapper: user: thatguy - - exploration.OnresetLowerToFixedValueWrapper: + - exploration.OnResetLowerToFixedValueWrapper: fixed_value: coords: 0.33 map_ids: 0.33 @@ -131,7 +131,7 @@ wrappers: cut: 0.33 explore: 0.33 - exploration.OnResetExplorationWrapper: - full_reset_frequency: 0 + full_reset_frequency: 25 fixed_reset_value: - stream_wrapper.StreamWrapper: From 25bc516ac54ceb0fd6aa1b46d17daedafff19d90 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:24:07 -0400 Subject: [PATCH 29/68] fix indents --- config.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/config.yaml b/config.yaml index 418a8f2..575af92 100644 --- a/config.yaml +++ b/config.yaml @@ -110,7 +110,7 @@ wrappers: action_bag_menu: 0.998 forgetting_frequency: 10 - exploration.OnResetExplorationWrapper: - full_reset_frequency: 0 + full_reset_frequency: 0 finite_coords: - stream_wrapper.StreamWrapper: @@ -118,26 +118,26 @@ wrappers: - exploration.MaxLengthWrapper: capacity: 1750 - exploration.OnResetExplorationWrapper: - full_reset_frequency: 0 + full_reset_frequency: 0 stream_only: - stream_wrapper.StreamWrapper: user: thatguy - exploration.OnResetLowerToFixedValueWrapper: - fixed_value: - coords: 0.33 - map_ids: 0.33 - npc: 0.33 - cut: 0.33 - explore: 0.33 + fixed_value: + coords: 0.33 + map_ids: 0.33 + npc: 0.33 + cut: 0.33 + explore: 0.33 - exploration.OnResetExplorationWrapper: - full_reset_frequency: 25 + full_reset_frequency: 25 fixed_reset_value: - stream_wrapper.StreamWrapper: user: thatguy - exploration.OnResetExplorationWrapper: - full_reset_frequency: 25 + full_reset_frequency: 25 rewards: baseline.BaselineRewardEnv: From 6be64a8868e77320374fcfff55e4b1d96cfa2c77 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:34:53 -0400 Subject: [PATCH 30/68] Wrappers gonna wrap --- pokemonred_puffer/environment.py | 6 ++++-- pokemonred_puffer/wrappers/exploration.py | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index c28efb2..f3d39e4 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -277,6 +277,8 @@ def __init__(self, env_config: pufferlib.namespace): RedGymEnv.env_id.buf[2] = (env_id >> 8) & 0xFF RedGymEnv.env_id.buf[3] = (env_id) & 0xFF + self.init_mem() + def register_hooks(self): self.pyboy.hook_register(None, "DisplayStartMenu", self.start_menu_hook, None) self.pyboy.hook_register(None, "RedisplayStartMenu", self.start_menu_hook, None) @@ -310,7 +312,6 @@ def reset(self, seed: Optional[int] = None, options: Optional[dict[str, Any]] = if self.first or options.get("state", None) is not None: self.recent_screens = deque() self.recent_actions = deque() - self.init_mem() # We only init seen hidden objs once cause they can only be found once! self.seen_hidden_objs = {} self.seen_signs = {} @@ -380,7 +381,8 @@ def init_mem(self): # Maybe I should preallocate a giant matrix for all map ids # All map ids have the same size, right? self.seen_coords = {} - # self.seen_global_coords = np.zeros(GLOBAL_MAP_SHAPE) + self.explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) + self.cut_explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) self.seen_map_ids = np.zeros(256) self.seen_npcs = {} diff --git a/pokemonred_puffer/wrappers/exploration.py b/pokemonred_puffer/wrappers/exploration.py index e01a6b5..cd76183 100644 --- a/pokemonred_puffer/wrappers/exploration.py +++ b/pokemonred_puffer/wrappers/exploration.py @@ -104,13 +104,13 @@ def __init__(self, env: RedGymEnv, reward_config: pufferlib.namespace): def reset(self, *args, **kwargs): if self.counter % self.full_reset_frequency == 0: self.counter = 0 - self.explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) - self.cut_explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) - self.seen_coords.clear() - self.seen_map_ids *= 0 - self.seen_npcs.clear() - self.cut_coords.clear() - self.cut_tiles.clear() + self.env.unwrapped.explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) + self.env.unwrapped.cut_explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) + self.env.unwrapped.seen_coords.clear() + self.env.unwrapped.seen_map_ids *= 0 + self.env.unwrapped.seen_npcs.clear() + self.env.unwrapped.cut_coords.clear() + self.env.unwrapped.cut_tiles.clear() self.counter += 1 return self.env.reset(*args, **kwargs) From d1613ad2800a9f07a04d536f0115994c97ddb411 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Fri, 7 Jun 2024 23:33:34 -0400 Subject: [PATCH 31/68] Obs for joypad ignore, noop action, rewards for using an item in the bag --- config.yaml | 3 ++- pokemonred_puffer/environment.py | 18 ++++++++++++------ .../policies/multi_convolutional.py | 1 + pokemonred_puffer/rewards/baseline.py | 2 ++ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/config.yaml b/config.yaml index 575af92..dfeee75 100644 --- a/config.yaml +++ b/config.yaml @@ -194,9 +194,10 @@ rewards: start_menu: 0.00 pokemon_menu: 0.0 stats_menu: 0.0 - bag_menu: 0.0 + bag_menu: 0.1 rocket_hideout_found: 5.0 explore_hidden_objs: 0.02 + seen_action_bag_menu: 0.1 diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index f3d39e4..d250d41 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -146,6 +146,7 @@ WindowEvent.PRESS_BUTTON_A, WindowEvent.PRESS_BUTTON_B, WindowEvent.PRESS_BUTTON_START, + WindowEvent.PASS, ] VALID_RELEASE_ACTIONS = [ @@ -156,6 +157,7 @@ WindowEvent.RELEASE_BUTTON_A, WindowEvent.RELEASE_BUTTON_B, WindowEvent.RELEASE_BUTTON_START, + WindowEvent.PASS, ] VALID_ACTIONS_STR = ["down", "left", "right", "up", "a", "b", "start"] @@ -246,6 +248,7 @@ def __init__(self, env_config: pufferlib.namespace): "map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), # "badges": spaces.Box(low=0, high=np.iinfo(np.uint16).max, shape=(1,), dtype=np.uint16), "badges": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), + "wJoyIgnore": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), } ) @@ -555,6 +558,7 @@ def _get_obs(self): # "y": np.array(player_y, dtype=np.uint8), "map_id": np.array(self.read_m(0xD35E), dtype=np.uint8), "badges": np.array(self.read_short("wObtainedBadges").bit_count(), dtype=np.uint8), + "wJoyIgnore": np.array(self.read_m("wJoyIgnore"), dtype=np.uint8), } def set_perfect_iv_dvs(self): @@ -626,8 +630,8 @@ def run_action_on_emulator(self, action): self.action_hist[action] += 1 # press button then release after some steps # TODO: Add video saving logic - self.pyboy.send_input(VALID_ACTIONS[action]) - self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) + # self.pyboy.send_input(VALID_ACTIONS[action]) + # self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) self.pyboy.tick(self.action_freq, render=True) if self.read_bit(0xD803, 0): @@ -755,8 +759,8 @@ def start_menu_hook(self, *args, **kwargs): self.seen_start_menu = 1 def item_menu_hook(self, *args, **kwargs): - if self.read_m("wIsInBattle") == 0: - self.seen_bag_menu = 1 + # if self.read_m("wIsInBattle") == 0: + self.seen_bag_menu = 1 def pokemon_menu_hook(self, *args, **kwargs): if self.read_m("wIsInBattle") == 0: @@ -767,8 +771,8 @@ def chose_stats_hook(self, *args, **kwargs): self.seen_stats_menu = 1 def chose_item_hook(self, *args, **kwargs): - if self.read_m("wIsInBattle") == 0: - self.seen_action_bag_menu = 1 + # if self.read_m("wIsInBattle") == 0: + self.seen_action_bag_menu = 1 def blackout_hook(self, *args, **kwargs): self.blackout_count += 1 @@ -853,6 +857,8 @@ def agent_stats(self, action): "reset_count": self.reset_count, "blackout_count": self.blackout_count, "pokecenter": np.sum(self.pokecenters), + "rival3": int(self.read_m(0xD665) == 4), + "rocket_hideout_found": int(self.read_bit(0xD77E, 1)), }, "reward": self.get_game_state_reward(), "reward/reward_sum": sum(self.get_game_state_reward().values()), diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index 678f664..87ad8a7 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -128,6 +128,7 @@ def encode_observations(self, observations): badges.float().squeeze(1), map_id.squeeze(1), blackout_map_id.squeeze(1), + observations["wJoyIgnore"].float(), ), dim=-1, ) diff --git a/pokemonred_puffer/rewards/baseline.py b/pokemonred_puffer/rewards/baseline.py index d64c3b8..23f48db 100644 --- a/pokemonred_puffer/rewards/baseline.py +++ b/pokemonred_puffer/rewards/baseline.py @@ -194,6 +194,8 @@ def get_game_state_reward(self): * int(self.read_bit(0xD77E, 1)), "explore_hidden_objs": sum(self.seen_hidden_objs.values()) * self.reward_config["explore_hidden_objs"], + "seen_action_bag_menu": self.seen_action_bag_menu + * self.reward_config["seen_action_bag_menu"], } def get_levels_reward(self): From 1ae70b704c7be70180228707a442963f5a52bce1 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Fri, 7 Jun 2024 23:37:21 -0400 Subject: [PATCH 32/68] Fix no bulba reset --- pokemonred_puffer/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index d250d41..88e4fcc 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -622,7 +622,7 @@ def step(self, action): if not self.party_has_cut_capable_mon(): reset = True self.first = True - new_reward = -self.progress_reward + new_reward = -self.total_reward * 0.5 return obs, new_reward, reset, False, info From 98e2ae5fcfc911c3c94dc47826ea1d8de781267d Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 8 Jun 2024 00:38:35 -0400 Subject: [PATCH 33/68] Uncomment action ignore --- pokemonred_puffer/environment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 88e4fcc..3f4fa5e 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -630,8 +630,8 @@ def run_action_on_emulator(self, action): self.action_hist[action] += 1 # press button then release after some steps # TODO: Add video saving logic - # self.pyboy.send_input(VALID_ACTIONS[action]) - # self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) + self.pyboy.send_input(VALID_ACTIONS[action]) + self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) self.pyboy.tick(self.action_freq, render=True) if self.read_bit(0xD803, 0): From 8168ba4002fe932d8f1c8a64d10067a991314c67 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 8 Jun 2024 23:13:07 -0400 Subject: [PATCH 34/68] Better debug mode, add no wild encounters mode --- config.yaml | 5 +++++ pokemonred_puffer/environment.py | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/config.yaml b/config.yaml index dfeee75..5f87f58 100644 --- a/config.yaml +++ b/config.yaml @@ -9,6 +9,8 @@ debug: stream_wrapper: False init_state: cut max_steps: 1_000_000 + disable_wild_encounters: True + disable_ai_actions: True train: device: cpu compile: False @@ -48,6 +50,9 @@ env: two_bit: True log_frequency: 2000 auto_flash: True + disable_wild_encounters: False + disable_ai_actions: False + train: seed: 1 diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 3f4fa5e..d3bc01a 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -192,6 +192,8 @@ def __init__(self, env_config: pufferlib.namespace): self.log_frequency = env_config.log_frequency self.two_bit = env_config.two_bit self.auto_flash = env_config.auto_flash + self.disable_wild_encounters = env_config.disable_wild_encounters + self.disable_ai_actions = env_config.disable_ai_actions self.action_space = ACTION_SPACE # Obs space-related. TODO: avoid hardcoding? @@ -303,6 +305,13 @@ def register_hooks(self): self.pyboy.hook_register(None, "SetLastBlackoutMap.done", self.blackout_update_hook, None) # self.pyboy.hook_register(None, "UsedCut.nothingToCut", self.cut_hook, context=True) # self.pyboy.hook_register(None, "UsedCut.canCut", self.cut_hook, context=False) + if self.disable_wild_encounters: + self.pyboy.hook_register( + None, + "TryDoWildEncounter.gotWildEncounterType", + self.disable_wild_encounter_hook, + None, + ) def update_state(self, state: bytes): self.reset(seed=random.randint(0, 10), options={"state": state}) @@ -630,8 +639,9 @@ def run_action_on_emulator(self, action): self.action_hist[action] += 1 # press button then release after some steps # TODO: Add video saving logic - self.pyboy.send_input(VALID_ACTIONS[action]) - self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) + if not self.disable_ai_actions: + self.pyboy.send_input(VALID_ACTIONS[action]) + self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) self.pyboy.tick(self.action_freq, render=True) if self.read_bit(0xD803, 0): @@ -811,6 +821,10 @@ def cut_hook(self, context): self.cut_explore_map[local_to_global(y, x, map_id)] = 1 self.cut_tiles[wTileInFrontOfPlayer] = 1 + def disable_wild_encounter_hook(self, *args, **kwargs): + self.pyboy.memory[self.pyboy.symbol_lookup("wRepelRemainingSteps")[1]] = 100 + self.pyboy.memory[self.pyboy.symbol_lookup("wCurEnemyLVL")[1]] = 1 + def agent_stats(self, action): levels = [self.read_m(f"wPartyMon{i+1}Level") for i in range(self.read_m("wPartyCount"))] return { From bc971449ff6d20db14ea85ac034a633f4780b2a3 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 8 Jun 2024 23:59:15 -0400 Subject: [PATCH 35/68] Add teach surf/strength routines and flags --- config.yaml | 4 + pokemonred_puffer/environment.py | 544 ++++++++++++++++++++++++------- 2 files changed, 429 insertions(+), 119 deletions(-) diff --git a/config.yaml b/config.yaml index 5f87f58..1a2e7f8 100644 --- a/config.yaml +++ b/config.yaml @@ -52,6 +52,10 @@ env: auto_flash: True disable_wild_encounters: False disable_ai_actions: False + auto_teach_cut: True + auto_use_cut: True + auto_teach_surf: True + auto_teach_strength: True train: diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index d3bc01a..c4e5bc5 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -26,116 +26,407 @@ VISITED_MASK_SHAPE = (144 // 16, 160 // 16, 1) -TM_HM_MOVES = set( - [ - 5, # Mega punch - 0xD, # Razor wind - 0xE, # Swords dance - 0x12, # Whirlwind - 0x19, # Mega kick - 0x5C, # Toxic - 0x20, # Horn drill - 0x22, # Body slam - 0x24, # Take down - 0x26, # Double edge - 0x3D, # Bubble beam - 0x37, # Water gun - 0x3A, # Ice beam - 0x3B, # Blizzard - 0x3F, # Hyper beam - 0x06, # Pay day - 0x42, # Submission - 0x44, # Counter - 0x45, # Seismic toss - 0x63, # Rage - 0x48, # Mega drain - 0x4C, # Solar beam - 0x52, # Dragon rage - 0x55, # Thunderbolt - 0x57, # Thunder - 0x59, # Earthquake - 0x5A, # Fissure - 0x5B, # Dig - 0x5E, # Psychic - 0x64, # Teleport - 0x66, # Mimic - 0x68, # Double team - 0x73, # Reflect - 0x75, # Bide - 0x76, # Metronome - 0x78, # Selfdestruct - 0x79, # Egg bomb - 0x7E, # Fire blast - 0x81, # Swift - 0x82, # Skull bash - 0x87, # Softboiled - 0x8A, # Dream eater - 0x8F, # Sky attack - 0x9C, # Rest - 0x56, # Thunder wave - 0x95, # Psywave - 0x99, # Explosion - 0x9D, # Rock slide - 0xA1, # Tri attack - 0xA4, # Substitute - 0x0F, # Cut - 0x13, # Fly - 0x39, # Surf - 0x46, # Strength - 0x94, # Flash - ] -) - -HM_ITEM_IDS = set([0xC4, 0xC5, 0xC6, 0xC7, 0xC8]) - -RESET_MAP_IDS = set( - [ - 0x0, # Pallet Town - 0x1, # Viridian City - 0x2, # Pewter City - 0x3, # Cerulean City - 0x4, # Lavender Town - 0x5, # Vermilion City - 0x6, # Celadon City - 0x7, # Fuchsia City - 0x8, # Cinnabar Island - 0x9, # Indigo Plateau - 0xA, # Saffron City - 0xF, # Route 4 (Mt Moon) - 0x10, # Route 10 (Rock Tunnel) - 0xE9, # Silph Co 9F (Heal station) - ] -) +TM_HM_MOVES = { + 5, # Mega punch + 0xD, # Razor wind + 0xE, # Swords dance + 0x12, # Whirlwind + 0x19, # Mega kick + 0x5C, # Toxic + 0x20, # Horn drill + 0x22, # Body slam + 0x24, # Take down + 0x26, # Double edge + 0x3D, # Bubble beam + 0x37, # Water gun + 0x3A, # Ice beam + 0x3B, # Blizzard + 0x3F, # Hyper beam + 0x06, # Pay day + 0x42, # Submission + 0x44, # Counter + 0x45, # Seismic toss + 0x63, # Rage + 0x48, # Mega drain + 0x4C, # Solar beam + 0x52, # Dragon rage + 0x55, # Thunderbolt + 0x57, # Thunder + 0x59, # Earthquake + 0x5A, # Fissure + 0x5B, # Dig + 0x5E, # Psychic + 0x64, # Teleport + 0x66, # Mimic + 0x68, # Double team + 0x73, # Reflect + 0x75, # Bide + 0x76, # Metronome + 0x78, # Selfdestruct + 0x79, # Egg bomb + 0x7E, # Fire blast + 0x81, # Swift + 0x82, # Skull bash + 0x87, # Softboiled + 0x8A, # Dream eater + 0x8F, # Sky attack + 0x9C, # Rest + 0x56, # Thunder wave + 0x95, # Psywave + 0x99, # Explosion + 0x9D, # Rock slide + 0xA1, # Tri attack + 0xA4, # Substitute + 0x0F, # Cut + 0x13, # Fly + 0x39, # Surf + 0x46, # Strength + 0x94, # Flash +} + +HM_ITEM_IDS = {0xC4, 0xC5, 0xC6, 0xC7, 0xC8} + +RESET_MAP_IDS = { + 0x0, # Pallet Town + 0x1, # Viridian City + 0x2, # Pewter City + 0x3, # Cerulean City + 0x4, # Lavender Town + 0x5, # Vermilion City + 0x6, # Celadon City + 0x7, # Fuchsia City + 0x8, # Cinnabar Island + 0x9, # Indigo Plateau + 0xA, # Saffron City + 0xF, # Route 4 (Mt Moon) + 0x10, # Route 10 (Rock Tunnel) + 0xE9, # Silph Co 9F (Heal station) +} + +SPECIES_IDS = { + "RHYDON": 0x01, + "KANGASKHAN": 0x02, + "NIDORAN_M": 0x03, + "CLEFAIRY": 0x04, + "SPEAROW": 0x05, + "VOLTORB": 0x06, + "NIDOKING": 0x07, + "SLOWBRO": 0x08, + "IVYSAUR": 0x09, + "EXEGGUTOR": 0x0A, + "LICKITUNG": 0x0B, + "EXEGGCUTE": 0x0C, + "GRIMER": 0x0D, + "GENGAR": 0x0E, + "NIDORAN_F": 0x0F, + "NIDOQUEEN": 0x10, + "CUBONE": 0x11, + "RHYHORN": 0x12, + "LAPRAS": 0x13, + "ARCANINE": 0x14, + "MEW": 0x15, + "GYARADOS": 0x16, + "SHELLDER": 0x17, + "TENTACOOL": 0x18, + "GASTLY": 0x19, + "SCYTHER": 0x1A, + "STARYU": 0x1B, + "BLASTOISE": 0x1C, + "PINSIR": 0x1D, + "TANGELA": 0x1E, + "MISSINGNO_1F": 0x1F, + "MISSINGNO_20": 0x20, + "GROWLITHE": 0x21, + "ONIX": 0x22, + "FEAROW": 0x23, + "PIDGEY": 0x24, + "SLOWPOKE": 0x25, + "KADABRA": 0x26, + "GRAVELER": 0x27, + "CHANSEY": 0x28, + "MACHOKE": 0x29, + "MR_MIME": 0x2A, + "HITMONLEE": 0x2B, + "HITMONCHAN": 0x2C, + "ARBOK": 0x2D, + "PARASECT": 0x2E, + "PSYDUCK": 0x2F, + "DROWZEE": 0x30, + "GOLEM": 0x31, + "MISSINGNO_32": 0x32, + "MAGMAR": 0x33, + "MISSINGNO_34": 0x34, + "ELECTABUZZ": 0x35, + "MAGNETON": 0x36, + "KOFFING": 0x37, + "MISSINGNO_38": 0x38, + "MANKEY": 0x39, + "SEEL": 0x3A, + "DIGLETT": 0x3B, + "TAUROS": 0x3C, + "MISSINGNO_3D": 0x3D, + "MISSINGNO_3E": 0x3E, + "MISSINGNO_3F": 0x3F, + "FARFETCHD": 0x40, + "VENONAT": 0x41, + "DRAGONITE": 0x42, + "MISSINGNO_43": 0x43, + "MISSINGNO_44": 0x44, + "MISSINGNO_45": 0x45, + "DODUO": 0x46, + "POLIWAG": 0x47, + "JYNX": 0x48, + "MOLTRES": 0x49, + "ARTICUNO": 0x4A, + "ZAPDOS": 0x4B, + "DITTO": 0x4C, + "MEOWTH": 0x4D, + "KRABBY": 0x4E, + "MISSINGNO_4F": 0x4F, + "MISSINGNO_50": 0x50, + "MISSINGNO_51": 0x51, + "VULPIX": 0x52, + "NINETALES": 0x53, + "PIKACHU": 0x54, + "RAICHU": 0x55, + "MISSINGNO_56": 0x56, + "MISSINGNO_57": 0x57, + "DRATINI": 0x58, + "DRAGONAIR": 0x59, + "KABUTO": 0x5A, + "KABUTOPS": 0x5B, + "HORSEA": 0x5C, + "SEADRA": 0x5D, + "MISSINGNO_5E": 0x5E, + "MISSINGNO_5F": 0x5F, + "SANDSHREW": 0x60, + "SANDSLASH": 0x61, + "OMANYTE": 0x62, + "OMASTAR": 0x63, + "JIGGLYPUFF": 0x64, + "WIGGLYTUFF": 0x65, + "EEVEE": 0x66, + "FLAREON": 0x67, + "JOLTEON": 0x68, + "VAPOREON": 0x69, + "MACHOP": 0x6A, + "ZUBAT": 0x6B, + "EKANS": 0x6C, + "PARAS": 0x6D, + "POLIWHIRL": 0x6E, + "POLIWRATH": 0x6F, + "WEEDLE": 0x70, + "KAKUNA": 0x71, + "BEEDRILL": 0x72, + "MISSINGNO_73": 0x73, + "DODRIO": 0x74, + "PRIMEAPE": 0x75, + "DUGTRIO": 0x76, + "VENOMOTH": 0x77, + "DEWGONG": 0x78, + "MISSINGNO_79": 0x79, + "MISSINGNO_7A": 0x7A, + "CATERPIE": 0x7B, + "METAPOD": 0x7C, + "BUTTERFREE": 0x7D, + "MACHAMP": 0x7E, + "MISSINGNO_7F": 0x7F, + "GOLDUCK": 0x80, + "HYPNO": 0x81, + "GOLBAT": 0x82, + "MEWTWO": 0x83, + "SNORLAX": 0x84, + "MAGIKARP": 0x85, + "MISSINGNO_86": 0x86, + "MISSINGNO_87": 0x87, + "MUK": 0x88, + "MISSINGNO_89": 0x89, + "KINGLER": 0x8A, + "CLOYSTER": 0x8B, + "MISSINGNO_8C": 0x8C, + "ELECTRODE": 0x8D, + "CLEFABLE": 0x8E, + "WEEZING": 0x8F, + "PERSIAN": 0x90, + "MAROWAK": 0x91, + "MISSINGNO_92": 0x92, + "HAUNTER": 0x93, + "ABRA": 0x94, + "ALAKAZAM": 0x95, + "PIDGEOTTO": 0x96, + "PIDGEOT": 0x97, + "STARMIE": 0x98, + "BULBASAUR": 0x99, + "VENUSAUR": 0x9A, + "TENTACRUEL": 0x9B, + "MISSINGNO_9C": 0x9C, + "GOLDEEN": 0x9D, + "SEAKING": 0x9E, + "MISSINGNO_9F": 0x9F, + "MISSINGNO_A0": 0xA0, + "MISSINGNO_A1": 0xA1, + "MISSINGNO_A2": 0xA2, + "PONYTA": 0xA3, + "RAPIDASH": 0xA4, + "RATTATA": 0xA5, + "RATICATE": 0xA6, + "NIDORINO": 0xA7, + "NIDORINA": 0xA8, + "GEODUDE": 0xA9, + "PORYGON": 0xAA, + "AERODACTYL": 0xAB, + "MISSINGNO_AC": 0xAC, + "MAGNEMITE": 0xAD, + "MISSINGNO_AE": 0xAE, + "MISSINGNO_AF": 0xAF, + "CHARMANDER": 0xB0, + "SQUIRTLE": 0xB1, + "CHARMELEON": 0xB2, + "WARTORTLE": 0xB3, + "CHARIZARD": 0xB4, + "MISSINGNO_B5": 0xB5, + "FOSSIL_KABUTOPS": 0xB6, + "FOSSIL_AERODACTYL": 0xB7, + "MON_GHOST": 0xB8, + "ODDISH": 0xB9, + "GLOOM": 0xBA, + "VILEPLUME": 0xBB, + "BELLSPROUT": 0xBC, + "WEEPINBELL": 0xBD, + "VICTREEBEL": 0xBE, +} CUT_SPECIES_IDS = { - 0x99, - 0x09, - 0x9A, - 0xB0, - 0xB2, - 0xB4, - 0x72, - 0x60, - 0x61, - 0xB9, - 0xBA, - 0xBB, - 0x6D, - 0x2E, - 0xBC, - 0xBD, - 0xBE, - 0x18, - 0x9B, - 0x40, - 0x4E, - 0x8A, - 0x0B, - 0x1E, - 0x1A, - 0x1D, - 0x5B, - 0x15, + SPECIES_IDS["BULBASAUR"], + SPECIES_IDS["IVYSAUR"], + SPECIES_IDS["VENUSAUR"], + SPECIES_IDS["CHARMANDER"], + SPECIES_IDS["CHARMELEON"], + SPECIES_IDS["CHARIZARD"], + SPECIES_IDS["BEEDRILL"], + SPECIES_IDS["SANDSHREW"], + SPECIES_IDS["SANDSLASH"], + SPECIES_IDS["ODDISH"], + SPECIES_IDS["GLOOM"], + SPECIES_IDS["VILEPLUME"], + SPECIES_IDS["PARAS"], + SPECIES_IDS["PARASECT"], + SPECIES_IDS["BELLSPROUT"], + SPECIES_IDS["WEEPINBELL"], + SPECIES_IDS["VICTREEBEL"], + SPECIES_IDS["TENTACOOL"], + SPECIES_IDS["TENTACRUEL"], + SPECIES_IDS["FARFETCHD"], + SPECIES_IDS["KRABBY"], + SPECIES_IDS["KINGLER"], + SPECIES_IDS["LICKITUNG"], + SPECIES_IDS["TANGELA"], + SPECIES_IDS["SCYTHER"], + SPECIES_IDS["PINSIR"], + SPECIES_IDS["MEW"], +} + +SURF_SPECIES_IDS = { + SPECIES_IDS["SQUIRTLE"], + SPECIES_IDS["WARTORTLE"], + SPECIES_IDS["BLASTOISE"], + SPECIES_IDS["NIDOQUEEN"], + SPECIES_IDS["NIDOKING"], + SPECIES_IDS["PSYDUCK"], + SPECIES_IDS["GOLDUCK"], + SPECIES_IDS["POLIWAG"], + SPECIES_IDS["POLIWHIRL"], + SPECIES_IDS["POLIWRATH"], + SPECIES_IDS["TENTACOOL"], + SPECIES_IDS["TENTACRUEL"], + SPECIES_IDS["SLOWPOKE"], + SPECIES_IDS["SLOWBRO"], + SPECIES_IDS["SEEL"], + SPECIES_IDS["DEWGONG"], + SPECIES_IDS["SHELDER"], + SPECIES_IDS["CLOYSTER"], + SPECIES_IDS["KRABBY"], + SPECIES_IDS["KINGLER"], + SPECIES_IDS["LICKITUNG"], + SPECIES_IDS["RHYDON"], + SPECIES_IDS["KANGASKHAN"], + SPECIES_IDS["HORSEA"], + SPECIES_IDS["SEADRA"], + SPECIES_IDS["GOLDEEN"], + SPECIES_IDS["SEAKING"], + SPECIES_IDS["STARYU"], + SPECIES_IDS["STARMIE"], + SPECIES_IDS["GYARADOS"], + SPECIES_IDS["LAPRAS"], + SPECIES_IDS["VAPOREON"], + SPECIES_IDS["OMANYTE"], + SPECIES_IDS["OMASTAR"], + SPECIES_IDS["KABUTO"], + SPECIES_IDS["KABUTOPS"], + SPECIES_IDS["SNORLAX"], + SPECIES_IDS["DRATINI"], + SPECIES_IDS["DRAGONAIR"], + SPECIES_IDS["DRAGONITE"], + SPECIES_IDS["MEW"], +} + +STRENGTH_SPECIES_IDS = { + SPECIES_IDS["CHARMANDER"], + SPECIES_IDS["CHARMELEON"], + SPECIES_IDS["CHARIZARD"], + SPECIES_IDS["SQUIRTLE"], + SPECIES_IDS["WARTORTLE"], + SPECIES_IDS["BLASTOISE"], + SPECIES_IDS["EKANS"], + SPECIES_IDS["ARBOK"], + SPECIES_IDS["SANDSHREW"], + SPECIES_IDS["SANDSLASH"], + SPECIES_IDS["NIDOQUEEN"], + SPECIES_IDS["NIDOKING"], + SPECIES_IDS["CLEFAIRY"], + SPECIES_IDS["CLEFABLE"], + SPECIES_IDS["JIGGLYPUFF"], + SPECIES_IDS["WIGGLYTUFF"], + SPECIES_IDS["PSYDUCK"], + SPECIES_IDS["GOLDUCK"], + SPECIES_IDS["MANKEY"], + SPECIES_IDS["PRIMEAPE"], + SPECIES_IDS["POLIWHIRL"], + SPECIES_IDS["POLIWRATH"], + SPECIES_IDS["MACHOP"], + SPECIES_IDS["MACHOKE"], + SPECIES_IDS["MACHAMP"], + SPECIES_IDS["GEODUDE"], + SPECIES_IDS["GRAVELER"], + SPECIES_IDS["GOLEM"], + SPECIES_IDS["SLOWPOKE"], + SPECIES_IDS["SLOWBRO"], + SPECIES_IDS["SEEL"], + SPECIES_IDS["DEWGONG"], + SPECIES_IDS["GENGAR"], + SPECIES_IDS["ONIX"], + SPECIES_IDS["KRABBY"], + SPECIES_IDS["KINGLER"], + SPECIES_IDS["EXEGGUTOR"], + SPECIES_IDS["CUBONE"], + SPECIES_IDS["MAROWAK"], + SPECIES_IDS["HITMONLEE"], + SPECIES_IDS["HITMONCHAN"], + SPECIES_IDS["LICKITUNG"], + SPECIES_IDS["RHYHORN"], + SPECIES_IDS["RHYDON"], + SPECIES_IDS["CHANSEY"], + SPECIES_IDS["KANGASKHAN"], + SPECIES_IDS["ELECTABUZZ"], + SPECIES_IDS["MAGMAR"], + SPECIES_IDS["PINSIR"], + SPECIES_IDS["TAUROS"], + SPECIES_IDS["GYARADOS"], + SPECIES_IDS["LAPRAS"], + SPECIES_IDS["SNORLAX"], + SPECIES_IDS["DRAGONITE"], + SPECIES_IDS["MEWTWO"], + SPECIES_IDS["MEW"], } VALID_ACTIONS = [ @@ -194,6 +485,10 @@ def __init__(self, env_config: pufferlib.namespace): self.auto_flash = env_config.auto_flash self.disable_wild_encounters = env_config.disable_wild_encounters self.disable_ai_actions = env_config.disable_ai_actions + self.auto_teach_cut = env_config.auto_teach_cut + self.auto_teach_surf = env_config.auto_teach_surf + self.auto_teach_strength = env_config.auto_teach_strength + self.auto_use_cut = env_config.auto_use_cut self.action_space = ACTION_SPACE # Obs space-related. TODO: avoid hardcoding? @@ -645,9 +940,18 @@ def run_action_on_emulator(self, action): self.pyboy.tick(self.action_freq, render=True) if self.read_bit(0xD803, 0): - if not self.check_if_party_has_cut(): - self.teach_cut() - self.cut_if_next() + if self.auto_teach_cut and not self.check_if_party_has_hm(0x0F): + self.teach_hm(0x0F, 30, CUT_SPECIES_IDS) + if self.auto_use_cut: + self.cut_if_next() + + if self.read_bit(0xD78E, 0): + if self.auto_teach_surf and not self.check_if_party_has_hm(0x39): + self.teach_hm(0x39, 15, SURF_SPECIES_IDS) + + if self.read_bit(0xD857, 0): + if self.auto_teach_strength and not self.check_if_party_has_hm(0x46): + self.teach_hm(0x46, 15, STRENGTH_SPECIES_IDS) def party_has_cut_capable_mon(self): # find bulba and replace tackle (first skill) with cut @@ -661,7 +965,7 @@ def party_has_cut_capable_mon(self): return True return False - def teach_cut(self): + def teach_hm(self, tmhm: int, pp: int, pokemon_species_ids): # find bulba and replace tackle (first skill) with cut party_size = self.read_m("wPartyCount") for i in range(party_size): @@ -669,13 +973,15 @@ def teach_cut(self): _, species_addr = self.pyboy.symbol_lookup(f"wPartyMon{i+1}Species") poke = self.pyboy.memory[species_addr] # https://github.com/pret/pokered/blob/d38cf5281a902b4bd167a46a7c9fd9db436484a7/constants/pokemon_constants.asm - if poke in CUT_SPECIES_IDS: - slot = 0 - _, move_addr = self.pyboy.symbol_lookup(f"wPartyMon{i+1}Moves") - _, pp_addr = self.pyboy.symbol_lookup(f"wPartyMon{i+1}PP") - self.pyboy.memory[move_addr + slot] = 15 - self.pyboy.memory[pp_addr + slot] = 30 - # fill up pp: 30/30 + if poke in pokemon_species_ids: + for slot in range(4): + if self.read_m(f"wPartyMon{i+1}Moves") not in {0xF, 0x13, 0x39, 0x46, 0x94}: + _, move_addr = self.pyboy.symbol_lookup(f"wPartyMon{i+1}Moves") + _, pp_addr = self.pyboy.symbol_lookup(f"wPartyMon{i+1}PP") + self.pyboy.memory[move_addr + slot] = tmhm + self.pyboy.memory[pp_addr + slot] = pp + # fill up pp: 30/30 + break def cut_if_next(self): # https://github.com/pret/pokered/blob/d38cf5281a902b4bd167a46a7c9fd9db436484a7/constants/tileset_constants.asm#L11C8-L11C11 From 10025e9da83186d8cae9cc525555be27515e570e Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 9 Jun 2024 00:03:19 -0400 Subject: [PATCH 36/68] More abstraction --- pokemonred_puffer/environment.py | 12 ++++++------ pokemonred_puffer/rewards/baseline.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index c4e5bc5..1e6db18 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -655,7 +655,7 @@ def reset(self, seed: Optional[int] = None, options: Optional[dict[str, Any]] = self.update_pokedex() self.update_tm_hm_moves_obtained() - self.taught_cut = self.check_if_party_has_cut() + self.taught_cut = self.check_if_party_has_hm(0xF) self.levels_satisfied = False self.base_explore = 0 self.max_opponent_level = 0 @@ -857,7 +857,7 @@ def _get_obs(self): "blackout_map_id": np.array(self.read_m("wLastBlackoutMap"), dtype=np.uint8), "battle_type": np.array(self.read_m("wIsInBattle") + 1, dtype=np.uint8), "cut_event": np.array(self.read_bit(0xD803, 0), dtype=np.uint8), - "cut_in_party": np.array(self.check_if_party_has_cut(), dtype=np.uint8), + "cut_in_party": np.array(self.check_if_party_has_hm(0xF), dtype=np.uint8), # "x": np.array(player_x, dtype=np.uint8), # "y": np.array(player_y, dtype=np.uint8), "map_id": np.array(self.read_m(0xD35E), dtype=np.uint8), @@ -871,12 +871,12 @@ def set_perfect_iv_dvs(self): _, addr = self.pyboy.symbol_lookup(f"wPartyMon{i+1}Species") self.pyboy.memory[addr + 17 : addr + 17 + 12] = 0xFF - def check_if_party_has_cut(self) -> bool: + def check_if_party_has_hm(self, hm: int) -> bool: party_size = self.read_m("wPartyCount") for i in range(party_size): # PRET 1-indexes _, addr = self.pyboy.symbol_lookup(f"wPartyMon{i+1}Moves") - if 15 in self.pyboy.memory[addr : addr + 4]: + if hm in self.pyboy.memory[addr : addr + 4]: return True return False @@ -900,7 +900,7 @@ def step(self, action): self.update_map_progress() if self.perfect_ivs: self.set_perfect_iv_dvs() - self.taught_cut = self.check_if_party_has_cut() + self.taught_cut = self.check_if_party_has_hm(0xF) self.pokecenters[self.read_m("wLastBlackoutMap")] = 1 info = {} @@ -1164,7 +1164,7 @@ def agent_stats(self, action): "left_bills_house_after_helping": int(self.read_bit(0xD7F2, 7)), "got_hm01": int(self.read_bit(0xD803, 0)), "rubbed_captains_back": int(self.read_bit(0xD803, 1)), - "taught_cut": int(self.check_if_party_has_cut()), + "taught_cut": int(self.check_if_party_has_hm(0xF)), "cut_coords": sum(self.cut_coords.values()), "cut_tiles": len(self.cut_tiles), "start_menu": self.seen_start_menu, diff --git a/pokemonred_puffer/rewards/baseline.py b/pokemonred_puffer/rewards/baseline.py index 23f48db..4f8497a 100644 --- a/pokemonred_puffer/rewards/baseline.py +++ b/pokemonred_puffer/rewards/baseline.py @@ -32,7 +32,7 @@ def get_game_state_reward(self): # "heal": self.total_healing_rew, "explore": sum(self.seen_coords.values()) * 0.012, # "explore_maps": np.sum(self.seen_map_ids) * 0.0001, - "taught_cut": 4 * int(self.check_if_party_has_cut()), + "taught_cut": 4 * int(self.check_if_party_has_hm(0xF)), "cut_coords": sum(self.cut_coords.values()) * 1.0, "cut_tiles": sum(self.cut_tiles.values()) * 1.0, "met_bill": 5 * int(self.read_bit(0xD7F1, 0)), From 8cad7d230889510e9b547132ee957c3e633215d6 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 9 Jun 2024 00:43:45 -0400 Subject: [PATCH 37/68] Fix key --- pokemonred_puffer/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 1e6db18..20617e7 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -343,7 +343,7 @@ SPECIES_IDS["SLOWBRO"], SPECIES_IDS["SEEL"], SPECIES_IDS["DEWGONG"], - SPECIES_IDS["SHELDER"], + SPECIES_IDS["SHELLDER"], SPECIES_IDS["CLOYSTER"], SPECIES_IDS["KRABBY"], SPECIES_IDS["KINGLER"], From b16b0e25377cd25290014f280e450c16650218d1 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 9 Jun 2024 00:56:35 -0400 Subject: [PATCH 38/68] set repel every turn --- pokemonred_puffer/environment.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 20617e7..09b47cd 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -884,6 +884,9 @@ def step(self, action): if self.save_video and self.step_count == 0: self.start_video() + if self.disable_wild_encounters: + self.pyboy.memory[self.pyboy.symbol_lookup("wRepelRemainingSteps")[1]] = 100 + _, wMapPalOffset = self.pyboy.symbol_lookup("wMapPalOffset") if self.auto_flash and self.pyboy.memory[wMapPalOffset] == 6: self.pyboy.memory[wMapPalOffset] = 0 From 4d34bfdb9d180d50365c39f050d12c3091c965c0 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 9 Jun 2024 01:01:09 -0400 Subject: [PATCH 39/68] enemy level is apparently a short --- pokemonred_puffer/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 09b47cd..e5cc79d 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -1132,7 +1132,7 @@ def cut_hook(self, context): def disable_wild_encounter_hook(self, *args, **kwargs): self.pyboy.memory[self.pyboy.symbol_lookup("wRepelRemainingSteps")[1]] = 100 - self.pyboy.memory[self.pyboy.symbol_lookup("wCurEnemyLVL")[1]] = 1 + self.pyboy.memory[self.pyboy.symbol_lookup("wCurEnemyLVL")[1] + 1] = 1 def agent_stats(self, action): levels = [self.read_m(f"wPartyMon{i+1}Level") for i in range(self.read_m("wPartyCount"))] From e5d05a44fee22c3bf677a4c5e8f2c3d757156e1e Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 9 Jun 2024 01:28:21 -0400 Subject: [PATCH 40/68] Fix autorepel --- config.yaml | 2 +- pokemonred_puffer/environment.py | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/config.yaml b/config.yaml index 1a2e7f8..a83d8f3 100644 --- a/config.yaml +++ b/config.yaml @@ -7,7 +7,7 @@ debug: env: headless: False stream_wrapper: False - init_state: cut + init_state: Bulbasaur max_steps: 1_000_000 disable_wild_encounters: True disable_ai_actions: True diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index e5cc79d..0cd2edf 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -601,9 +601,11 @@ def register_hooks(self): # self.pyboy.hook_register(None, "UsedCut.nothingToCut", self.cut_hook, context=True) # self.pyboy.hook_register(None, "UsedCut.canCut", self.cut_hook, context=False) if self.disable_wild_encounters: + print("registering") + bank, addr = self.pyboy.symbol_lookup("TryDoWildEncounter.gotWildEncounterType") self.pyboy.hook_register( - None, - "TryDoWildEncounter.gotWildEncounterType", + bank, + addr + 8, self.disable_wild_encounter_hook, None, ) @@ -884,9 +886,6 @@ def step(self, action): if self.save_video and self.step_count == 0: self.start_video() - if self.disable_wild_encounters: - self.pyboy.memory[self.pyboy.symbol_lookup("wRepelRemainingSteps")[1]] = 100 - _, wMapPalOffset = self.pyboy.symbol_lookup("wMapPalOffset") if self.auto_flash and self.pyboy.memory[wMapPalOffset] == 6: self.pyboy.memory[wMapPalOffset] = 0 @@ -1131,8 +1130,8 @@ def cut_hook(self, context): self.cut_tiles[wTileInFrontOfPlayer] = 1 def disable_wild_encounter_hook(self, *args, **kwargs): - self.pyboy.memory[self.pyboy.symbol_lookup("wRepelRemainingSteps")[1]] = 100 - self.pyboy.memory[self.pyboy.symbol_lookup("wCurEnemyLVL")[1] + 1] = 1 + self.pyboy.memory[self.pyboy.symbol_lookup("wRepelRemainingSteps")[1]] = 0xFFFF + self.pyboy.memory[self.pyboy.symbol_lookup("wCurEnemyLVL")[1]] = 1 def agent_stats(self, action): levels = [self.read_m(f"wPartyMon{i+1}Level") for i in range(self.read_m("wPartyCount"))] From fa12c3d031bb39187e0717b1f88c8d9b3b1b54d4 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 9 Jun 2024 01:30:13 -0400 Subject: [PATCH 41/68] fix again --- pokemonred_puffer/environment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 0cd2edf..3d483b5 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -1130,8 +1130,8 @@ def cut_hook(self, context): self.cut_tiles[wTileInFrontOfPlayer] = 1 def disable_wild_encounter_hook(self, *args, **kwargs): - self.pyboy.memory[self.pyboy.symbol_lookup("wRepelRemainingSteps")[1]] = 0xFFFF - self.pyboy.memory[self.pyboy.symbol_lookup("wCurEnemyLVL")[1]] = 1 + self.pyboy.memory[self.pyboy.symbol_lookup("wRepelRemainingSteps")[1]] = 0xFF + self.pyboy.memory[self.pyboy.symbol_lookup("wCurEnemyLVL")[1]] = 0x01 def agent_stats(self, action): levels = [self.read_m(f"wPartyMon{i+1}Level") for i in range(self.read_m("wPartyCount"))] From 9b3f569ac097ebc9708f94880f5f00f04a554567 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Mon, 10 Jun 2024 01:22:37 -0400 Subject: [PATCH 42/68] Remove all unneeded items routine --- config.yaml | 3 +- pokemonred_puffer/environment.py | 471 +++++-------------------------- 2 files changed, 66 insertions(+), 408 deletions(-) diff --git a/config.yaml b/config.yaml index a83d8f3..2f0ed4c 100644 --- a/config.yaml +++ b/config.yaml @@ -7,7 +7,7 @@ debug: env: headless: False stream_wrapper: False - init_state: Bulbasaur + init_state: cut3 max_steps: 1_000_000 disable_wild_encounters: True disable_ai_actions: True @@ -56,6 +56,7 @@ env: auto_use_cut: True auto_teach_surf: True auto_teach_strength: True + auto_remove_all_nonuseful_items: True train: diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 3d483b5..1a846e7 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -16,418 +16,23 @@ from skimage.transform import resize import pufferlib +from pokemonred_puffer.data.events import EVENT_FLAGS_START, EVENTS_FLAGS_LENGTH, MUSEUM_TICKET +from pokemonred_puffer.data.items import ( + ITEM_NAME_TO_ID, + HM_ITEM_IDS, + KEY_ITEM_IDS, + MAX_ITEM_CAPACITY, +) +from pokemonred_puffer.data.tm_hm import ( + CUT_SPECIES_IDS, + STRENGTH_SPECIES_IDS, + SURF_SPECIES_IDS, +) from pokemonred_puffer.global_map import GLOBAL_MAP_SHAPE, local_to_global PIXEL_VALUES = np.array([0, 85, 153, 255], dtype=np.uint8) - -EVENT_FLAGS_START = 0xD747 -EVENTS_FLAGS_LENGTH = 320 -MUSEUM_TICKET = (0xD754, 0) - VISITED_MASK_SHAPE = (144 // 16, 160 // 16, 1) -TM_HM_MOVES = { - 5, # Mega punch - 0xD, # Razor wind - 0xE, # Swords dance - 0x12, # Whirlwind - 0x19, # Mega kick - 0x5C, # Toxic - 0x20, # Horn drill - 0x22, # Body slam - 0x24, # Take down - 0x26, # Double edge - 0x3D, # Bubble beam - 0x37, # Water gun - 0x3A, # Ice beam - 0x3B, # Blizzard - 0x3F, # Hyper beam - 0x06, # Pay day - 0x42, # Submission - 0x44, # Counter - 0x45, # Seismic toss - 0x63, # Rage - 0x48, # Mega drain - 0x4C, # Solar beam - 0x52, # Dragon rage - 0x55, # Thunderbolt - 0x57, # Thunder - 0x59, # Earthquake - 0x5A, # Fissure - 0x5B, # Dig - 0x5E, # Psychic - 0x64, # Teleport - 0x66, # Mimic - 0x68, # Double team - 0x73, # Reflect - 0x75, # Bide - 0x76, # Metronome - 0x78, # Selfdestruct - 0x79, # Egg bomb - 0x7E, # Fire blast - 0x81, # Swift - 0x82, # Skull bash - 0x87, # Softboiled - 0x8A, # Dream eater - 0x8F, # Sky attack - 0x9C, # Rest - 0x56, # Thunder wave - 0x95, # Psywave - 0x99, # Explosion - 0x9D, # Rock slide - 0xA1, # Tri attack - 0xA4, # Substitute - 0x0F, # Cut - 0x13, # Fly - 0x39, # Surf - 0x46, # Strength - 0x94, # Flash -} - -HM_ITEM_IDS = {0xC4, 0xC5, 0xC6, 0xC7, 0xC8} - -RESET_MAP_IDS = { - 0x0, # Pallet Town - 0x1, # Viridian City - 0x2, # Pewter City - 0x3, # Cerulean City - 0x4, # Lavender Town - 0x5, # Vermilion City - 0x6, # Celadon City - 0x7, # Fuchsia City - 0x8, # Cinnabar Island - 0x9, # Indigo Plateau - 0xA, # Saffron City - 0xF, # Route 4 (Mt Moon) - 0x10, # Route 10 (Rock Tunnel) - 0xE9, # Silph Co 9F (Heal station) -} - -SPECIES_IDS = { - "RHYDON": 0x01, - "KANGASKHAN": 0x02, - "NIDORAN_M": 0x03, - "CLEFAIRY": 0x04, - "SPEAROW": 0x05, - "VOLTORB": 0x06, - "NIDOKING": 0x07, - "SLOWBRO": 0x08, - "IVYSAUR": 0x09, - "EXEGGUTOR": 0x0A, - "LICKITUNG": 0x0B, - "EXEGGCUTE": 0x0C, - "GRIMER": 0x0D, - "GENGAR": 0x0E, - "NIDORAN_F": 0x0F, - "NIDOQUEEN": 0x10, - "CUBONE": 0x11, - "RHYHORN": 0x12, - "LAPRAS": 0x13, - "ARCANINE": 0x14, - "MEW": 0x15, - "GYARADOS": 0x16, - "SHELLDER": 0x17, - "TENTACOOL": 0x18, - "GASTLY": 0x19, - "SCYTHER": 0x1A, - "STARYU": 0x1B, - "BLASTOISE": 0x1C, - "PINSIR": 0x1D, - "TANGELA": 0x1E, - "MISSINGNO_1F": 0x1F, - "MISSINGNO_20": 0x20, - "GROWLITHE": 0x21, - "ONIX": 0x22, - "FEAROW": 0x23, - "PIDGEY": 0x24, - "SLOWPOKE": 0x25, - "KADABRA": 0x26, - "GRAVELER": 0x27, - "CHANSEY": 0x28, - "MACHOKE": 0x29, - "MR_MIME": 0x2A, - "HITMONLEE": 0x2B, - "HITMONCHAN": 0x2C, - "ARBOK": 0x2D, - "PARASECT": 0x2E, - "PSYDUCK": 0x2F, - "DROWZEE": 0x30, - "GOLEM": 0x31, - "MISSINGNO_32": 0x32, - "MAGMAR": 0x33, - "MISSINGNO_34": 0x34, - "ELECTABUZZ": 0x35, - "MAGNETON": 0x36, - "KOFFING": 0x37, - "MISSINGNO_38": 0x38, - "MANKEY": 0x39, - "SEEL": 0x3A, - "DIGLETT": 0x3B, - "TAUROS": 0x3C, - "MISSINGNO_3D": 0x3D, - "MISSINGNO_3E": 0x3E, - "MISSINGNO_3F": 0x3F, - "FARFETCHD": 0x40, - "VENONAT": 0x41, - "DRAGONITE": 0x42, - "MISSINGNO_43": 0x43, - "MISSINGNO_44": 0x44, - "MISSINGNO_45": 0x45, - "DODUO": 0x46, - "POLIWAG": 0x47, - "JYNX": 0x48, - "MOLTRES": 0x49, - "ARTICUNO": 0x4A, - "ZAPDOS": 0x4B, - "DITTO": 0x4C, - "MEOWTH": 0x4D, - "KRABBY": 0x4E, - "MISSINGNO_4F": 0x4F, - "MISSINGNO_50": 0x50, - "MISSINGNO_51": 0x51, - "VULPIX": 0x52, - "NINETALES": 0x53, - "PIKACHU": 0x54, - "RAICHU": 0x55, - "MISSINGNO_56": 0x56, - "MISSINGNO_57": 0x57, - "DRATINI": 0x58, - "DRAGONAIR": 0x59, - "KABUTO": 0x5A, - "KABUTOPS": 0x5B, - "HORSEA": 0x5C, - "SEADRA": 0x5D, - "MISSINGNO_5E": 0x5E, - "MISSINGNO_5F": 0x5F, - "SANDSHREW": 0x60, - "SANDSLASH": 0x61, - "OMANYTE": 0x62, - "OMASTAR": 0x63, - "JIGGLYPUFF": 0x64, - "WIGGLYTUFF": 0x65, - "EEVEE": 0x66, - "FLAREON": 0x67, - "JOLTEON": 0x68, - "VAPOREON": 0x69, - "MACHOP": 0x6A, - "ZUBAT": 0x6B, - "EKANS": 0x6C, - "PARAS": 0x6D, - "POLIWHIRL": 0x6E, - "POLIWRATH": 0x6F, - "WEEDLE": 0x70, - "KAKUNA": 0x71, - "BEEDRILL": 0x72, - "MISSINGNO_73": 0x73, - "DODRIO": 0x74, - "PRIMEAPE": 0x75, - "DUGTRIO": 0x76, - "VENOMOTH": 0x77, - "DEWGONG": 0x78, - "MISSINGNO_79": 0x79, - "MISSINGNO_7A": 0x7A, - "CATERPIE": 0x7B, - "METAPOD": 0x7C, - "BUTTERFREE": 0x7D, - "MACHAMP": 0x7E, - "MISSINGNO_7F": 0x7F, - "GOLDUCK": 0x80, - "HYPNO": 0x81, - "GOLBAT": 0x82, - "MEWTWO": 0x83, - "SNORLAX": 0x84, - "MAGIKARP": 0x85, - "MISSINGNO_86": 0x86, - "MISSINGNO_87": 0x87, - "MUK": 0x88, - "MISSINGNO_89": 0x89, - "KINGLER": 0x8A, - "CLOYSTER": 0x8B, - "MISSINGNO_8C": 0x8C, - "ELECTRODE": 0x8D, - "CLEFABLE": 0x8E, - "WEEZING": 0x8F, - "PERSIAN": 0x90, - "MAROWAK": 0x91, - "MISSINGNO_92": 0x92, - "HAUNTER": 0x93, - "ABRA": 0x94, - "ALAKAZAM": 0x95, - "PIDGEOTTO": 0x96, - "PIDGEOT": 0x97, - "STARMIE": 0x98, - "BULBASAUR": 0x99, - "VENUSAUR": 0x9A, - "TENTACRUEL": 0x9B, - "MISSINGNO_9C": 0x9C, - "GOLDEEN": 0x9D, - "SEAKING": 0x9E, - "MISSINGNO_9F": 0x9F, - "MISSINGNO_A0": 0xA0, - "MISSINGNO_A1": 0xA1, - "MISSINGNO_A2": 0xA2, - "PONYTA": 0xA3, - "RAPIDASH": 0xA4, - "RATTATA": 0xA5, - "RATICATE": 0xA6, - "NIDORINO": 0xA7, - "NIDORINA": 0xA8, - "GEODUDE": 0xA9, - "PORYGON": 0xAA, - "AERODACTYL": 0xAB, - "MISSINGNO_AC": 0xAC, - "MAGNEMITE": 0xAD, - "MISSINGNO_AE": 0xAE, - "MISSINGNO_AF": 0xAF, - "CHARMANDER": 0xB0, - "SQUIRTLE": 0xB1, - "CHARMELEON": 0xB2, - "WARTORTLE": 0xB3, - "CHARIZARD": 0xB4, - "MISSINGNO_B5": 0xB5, - "FOSSIL_KABUTOPS": 0xB6, - "FOSSIL_AERODACTYL": 0xB7, - "MON_GHOST": 0xB8, - "ODDISH": 0xB9, - "GLOOM": 0xBA, - "VILEPLUME": 0xBB, - "BELLSPROUT": 0xBC, - "WEEPINBELL": 0xBD, - "VICTREEBEL": 0xBE, -} - -CUT_SPECIES_IDS = { - SPECIES_IDS["BULBASAUR"], - SPECIES_IDS["IVYSAUR"], - SPECIES_IDS["VENUSAUR"], - SPECIES_IDS["CHARMANDER"], - SPECIES_IDS["CHARMELEON"], - SPECIES_IDS["CHARIZARD"], - SPECIES_IDS["BEEDRILL"], - SPECIES_IDS["SANDSHREW"], - SPECIES_IDS["SANDSLASH"], - SPECIES_IDS["ODDISH"], - SPECIES_IDS["GLOOM"], - SPECIES_IDS["VILEPLUME"], - SPECIES_IDS["PARAS"], - SPECIES_IDS["PARASECT"], - SPECIES_IDS["BELLSPROUT"], - SPECIES_IDS["WEEPINBELL"], - SPECIES_IDS["VICTREEBEL"], - SPECIES_IDS["TENTACOOL"], - SPECIES_IDS["TENTACRUEL"], - SPECIES_IDS["FARFETCHD"], - SPECIES_IDS["KRABBY"], - SPECIES_IDS["KINGLER"], - SPECIES_IDS["LICKITUNG"], - SPECIES_IDS["TANGELA"], - SPECIES_IDS["SCYTHER"], - SPECIES_IDS["PINSIR"], - SPECIES_IDS["MEW"], -} - -SURF_SPECIES_IDS = { - SPECIES_IDS["SQUIRTLE"], - SPECIES_IDS["WARTORTLE"], - SPECIES_IDS["BLASTOISE"], - SPECIES_IDS["NIDOQUEEN"], - SPECIES_IDS["NIDOKING"], - SPECIES_IDS["PSYDUCK"], - SPECIES_IDS["GOLDUCK"], - SPECIES_IDS["POLIWAG"], - SPECIES_IDS["POLIWHIRL"], - SPECIES_IDS["POLIWRATH"], - SPECIES_IDS["TENTACOOL"], - SPECIES_IDS["TENTACRUEL"], - SPECIES_IDS["SLOWPOKE"], - SPECIES_IDS["SLOWBRO"], - SPECIES_IDS["SEEL"], - SPECIES_IDS["DEWGONG"], - SPECIES_IDS["SHELLDER"], - SPECIES_IDS["CLOYSTER"], - SPECIES_IDS["KRABBY"], - SPECIES_IDS["KINGLER"], - SPECIES_IDS["LICKITUNG"], - SPECIES_IDS["RHYDON"], - SPECIES_IDS["KANGASKHAN"], - SPECIES_IDS["HORSEA"], - SPECIES_IDS["SEADRA"], - SPECIES_IDS["GOLDEEN"], - SPECIES_IDS["SEAKING"], - SPECIES_IDS["STARYU"], - SPECIES_IDS["STARMIE"], - SPECIES_IDS["GYARADOS"], - SPECIES_IDS["LAPRAS"], - SPECIES_IDS["VAPOREON"], - SPECIES_IDS["OMANYTE"], - SPECIES_IDS["OMASTAR"], - SPECIES_IDS["KABUTO"], - SPECIES_IDS["KABUTOPS"], - SPECIES_IDS["SNORLAX"], - SPECIES_IDS["DRATINI"], - SPECIES_IDS["DRAGONAIR"], - SPECIES_IDS["DRAGONITE"], - SPECIES_IDS["MEW"], -} - -STRENGTH_SPECIES_IDS = { - SPECIES_IDS["CHARMANDER"], - SPECIES_IDS["CHARMELEON"], - SPECIES_IDS["CHARIZARD"], - SPECIES_IDS["SQUIRTLE"], - SPECIES_IDS["WARTORTLE"], - SPECIES_IDS["BLASTOISE"], - SPECIES_IDS["EKANS"], - SPECIES_IDS["ARBOK"], - SPECIES_IDS["SANDSHREW"], - SPECIES_IDS["SANDSLASH"], - SPECIES_IDS["NIDOQUEEN"], - SPECIES_IDS["NIDOKING"], - SPECIES_IDS["CLEFAIRY"], - SPECIES_IDS["CLEFABLE"], - SPECIES_IDS["JIGGLYPUFF"], - SPECIES_IDS["WIGGLYTUFF"], - SPECIES_IDS["PSYDUCK"], - SPECIES_IDS["GOLDUCK"], - SPECIES_IDS["MANKEY"], - SPECIES_IDS["PRIMEAPE"], - SPECIES_IDS["POLIWHIRL"], - SPECIES_IDS["POLIWRATH"], - SPECIES_IDS["MACHOP"], - SPECIES_IDS["MACHOKE"], - SPECIES_IDS["MACHAMP"], - SPECIES_IDS["GEODUDE"], - SPECIES_IDS["GRAVELER"], - SPECIES_IDS["GOLEM"], - SPECIES_IDS["SLOWPOKE"], - SPECIES_IDS["SLOWBRO"], - SPECIES_IDS["SEEL"], - SPECIES_IDS["DEWGONG"], - SPECIES_IDS["GENGAR"], - SPECIES_IDS["ONIX"], - SPECIES_IDS["KRABBY"], - SPECIES_IDS["KINGLER"], - SPECIES_IDS["EXEGGUTOR"], - SPECIES_IDS["CUBONE"], - SPECIES_IDS["MAROWAK"], - SPECIES_IDS["HITMONLEE"], - SPECIES_IDS["HITMONCHAN"], - SPECIES_IDS["LICKITUNG"], - SPECIES_IDS["RHYHORN"], - SPECIES_IDS["RHYDON"], - SPECIES_IDS["CHANSEY"], - SPECIES_IDS["KANGASKHAN"], - SPECIES_IDS["ELECTABUZZ"], - SPECIES_IDS["MAGMAR"], - SPECIES_IDS["PINSIR"], - SPECIES_IDS["TAUROS"], - SPECIES_IDS["GYARADOS"], - SPECIES_IDS["LAPRAS"], - SPECIES_IDS["SNORLAX"], - SPECIES_IDS["DRAGONITE"], - SPECIES_IDS["MEWTWO"], - SPECIES_IDS["MEW"], -} VALID_ACTIONS = [ WindowEvent.PRESS_ARROW_DOWN, @@ -489,6 +94,7 @@ def __init__(self, env_config: pufferlib.namespace): self.auto_teach_surf = env_config.auto_teach_surf self.auto_teach_strength = env_config.auto_teach_strength self.auto_use_cut = env_config.auto_use_cut + self.auto_remove_all_nonuseful_items = env_config.auto_remove_all_nonuseful_items self.action_space = ACTION_SPACE # Obs space-related. TODO: avoid hardcoding? @@ -890,6 +496,9 @@ def step(self, action): if self.auto_flash and self.pyboy.memory[wMapPalOffset] == 6: self.pyboy.memory[wMapPalOffset] = 0 + if self.auto_remove_all_nonuseful_items: + self.remove_all_nonuseful_items() + self.run_action_on_emulator(action) self.update_seen_coords() self.update_health() @@ -1337,6 +946,54 @@ def update_tm_hm_moves_obtained(self): self.moves_obtained[move_id] = 1 """ + def remove_all_nonuseful_items(self): + _, wNumBagItems = self.pyboy.symbol_lookup("wNumBagItems") + if self.pyboy.memory[wNumBagItems] == MAX_ITEM_CAPACITY: + _, wBagItems = self.pyboy.symbol_lookup("wBagItems") + bag_items = self.pyboy.memory[wBagItems : wBagItems + MAX_ITEM_CAPACITY * 2] + # Fun fact: The way they test if an item is an hm in code is by testing the item id + # is greater than or equal to 0xC4 (the item id for HM_01) + + # TODO either remove or check if guard has been given drink + # guard given drink are 4 script pointers to check, NOT an event + new_bag_items = [ + (item, quantity) + for item, quantity in zip(bag_items[::2], bag_items[1::2]) + if (0x0 < item < 0xC4 and KEY_ITEM_IDS[item - 1]) + or ( + item + in { + ITEM_NAME_TO_ID[name] + for name in [ + "LEMONADE", + "SODA_POP", + "FRESH_WATER", + "HM_01", + "HM_02", + "HM_03", + "HM_04", + "HM_05", + ] + } + ) + ] + # Write the new count back to memory + self.pyboy.memory[wNumBagItems] = len(new_bag_items) + # 0 pad + new_bag_items += [(255, 255)] * (20 - len(new_bag_items)) + # now flatten list + new_bag_items = list(sum(new_bag_items, ())) + # now write back to list + self.pyboy.memory[wBagItems : wBagItems + len(new_bag_items)] = new_bag_items + + _, wBagSavedMenuItem = self.pyboy.symbol_lookup("wBagSavedMenuItem") + _, wListScrollOffset = self.pyboy.symbol_lookup("wListScrollOffset") + # TODO: Make this point to the location of the last removed item + # Should be something like the current location - the number of items + # that have been removed - 1 + self.pyboy.memory[wBagSavedMenuItem] = 0 + self.pyboy.memory[wListScrollOffset] = 0 + def read_hp_fraction(self): party_size = self.read_m("wPartyCount") hp_sum = sum(self.read_short(f"wPartyMon{i+1}HP") for i in range(party_size)) From 5735754504eb2460198fd7ebce6b2245e723d9ee Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Mon, 10 Jun 2024 01:24:09 -0400 Subject: [PATCH 43/68] Add forgotten data for important rom values --- pokemonred_puffer/data/events.py | 3 + pokemonred_puffer/data/items.py | 256 ++++++++++++++++++++++++++++++ pokemonred_puffer/data/map.py | 16 ++ pokemonred_puffer/data/species.py | 192 ++++++++++++++++++++++ pokemonred_puffer/data/tm_hm.py | 194 ++++++++++++++++++++++ 5 files changed, 661 insertions(+) create mode 100644 pokemonred_puffer/data/events.py create mode 100644 pokemonred_puffer/data/items.py create mode 100644 pokemonred_puffer/data/map.py create mode 100644 pokemonred_puffer/data/species.py create mode 100644 pokemonred_puffer/data/tm_hm.py diff --git a/pokemonred_puffer/data/events.py b/pokemonred_puffer/data/events.py new file mode 100644 index 0000000..c7db94a --- /dev/null +++ b/pokemonred_puffer/data/events.py @@ -0,0 +1,3 @@ +EVENT_FLAGS_START = 0xD747 +EVENTS_FLAGS_LENGTH = 320 +MUSEUM_TICKET = (0xD754, 0) diff --git a/pokemonred_puffer/data/items.py b/pokemonred_puffer/data/items.py new file mode 100644 index 0000000..c6deb7f --- /dev/null +++ b/pokemonred_puffer/data/items.py @@ -0,0 +1,256 @@ +MAX_ITEM_CAPACITY = 20 + +# Starts at 0x1 +KEY_ITEM_IDS = [ + False, # MASTER_BALL + False, # ULTRA_BALL + False, # GREAT_BALL + False, # POKE_BALL + True, # TOWN_MAP + True, # BICYCLE + True, # SURFBOARD + True, # SAFARI_BALL + True, # POKEDEX + False, # MOON_STONE + False, # ANTIDOTE + False, # BURN_HEAL + False, # ICE_HEAL + False, # AWAKENING + False, # PARLYZ_HEAL + False, # FULL_RESTORE + False, # MAX_POTION + False, # HYPER_POTION + False, # SUPER_POTION + False, # POTION + True, # BOULDERBADGE + True, # CASCADEBADGE + True, # THUNDERBADGE + True, # RAINBOWBADGE + True, # SOULBADGE + True, # MARSHBADGE + True, # VOLCANOBADGE + True, # EARTHBADGE + False, # ESCAPE_ROPE + False, # REPEL + True, # OLD_AMBER + False, # FIRE_STONE + False, # THUNDER_STONE + False, # WATER_STONE + False, # HP_UP + False, # PROTEIN + False, # IRON + False, # CARBOS + False, # CALCIUM + False, # RARE_CANDY + True, # DOME_FOSSIL + True, # HELIX_FOSSIL + True, # SECRET_KEY + True, # ITEM_2C + True, # BIKE_VOUCHER + False, # X_ACCURACY + False, # LEAF_STONE + True, # CARD_KEY + False, # NUGGET + False, # ITEM_32 + False, # POKE_DOLL + False, # FULL_HEAL + False, # REVIVE + False, # MAX_REVIVE + False, # GUARD_SPEC + False, # SUPER_REPEL + False, # MAX_REPEL + False, # DIRE_HIT + False, # COIN + False, # FRESH_WATER + False, # SODA_POP + False, # LEMONADE + True, # S_S_TICKET + True, # GOLD_TEETH + False, # X_ATTACK + False, # X_DEFEND + False, # X_SPEED + False, # X_SPECIAL + True, # COIN_CASE + True, # OAKS_PARCEL + True, # ITEMFINDER + True, # SILPH_SCOPE + True, # POKE_FLUTE + True, # LIFT_KEY + False, # EXP_ALL + True, # OLD_ROD + True, # GOOD_ROD + True, # SUPER_ROD + False, # PP_UP + False, # ETHER + False, # MAX_ETHER + False, # ELIXER + False, # MAX_ELIXER +] + +# Start = 0x1 +ITEM_NAMES = [ + "MASTER_BALL", + "ULTRA_BALL", + "GREAT_BALL", + "POKE_BALL", + "TOWN_MAP", + "BICYCLE", + "SURFBOARD", + "SAFARI_BALL", + "POKEDEX", + "MOON_STONE", + "ANTIDOTE", + "BURN_HEAL", + "ICE_HEAL", + "AWAKENING", + "PARLYZ_HEAL", + "FULL_RESTORE", + "MAX_POTION", + "HYPER_POTION", + "SUPER_POTION", + "POTION", + "BOULDERBADGE", + "CASCADEBADGE", + "THUNDERBADGE", + "RAINBOWBADGE", + "SOULBADGE", + "MARSHBADGE", + "VOLCANOBADGE", + "EARTHBADGE", + "ESCAPE_ROPE", + "REPEL", + "OLD_AMBER", + "FIRE_STONE", + "THUNDER_STONE", + "WATER_STONE", + "HP_UP", + "PROTEIN", + "IRON", + "CARBOS", + "CALCIUM", + "RARE_CANDY", + "DOME_FOSSIL", + "HELIX_FOSSIL", + "SECRET_KEY", + "UNUSED_ITEM", + "BIKE_VOUCHER", + "X_ACCURACY", + "LEAF_STONE", + "CARD_KEY", + "NUGGET", + "PP_UP_2", + "POKE_DOLL", + "FULL_HEAL", + "REVIVE", + "MAX_REVIVE", + "GUARD_SPEC", + "SUPER_REPEL", + "MAX_REPEL", + "DIRE_HIT", + "COIN", + "FRESH_WATER", + "SODA_POP", + "LEMONADE", + "S_S_TICKET", + "GOLD_TEETH", + "X_ATTACK", + "X_DEFEND", + "X_SPEED", + "X_SPECIAL", + "COIN_CASE", + "OAKS_PARCEL", + "ITEMFINDER", + "SILPH_SCOPE", + "POKE_FLUTE", + "LIFT_KEY", + "EXP_ALL", + "OLD_ROD", + "GOOD_ROD", + "SUPER_ROD", + "PP_UP", + "ETHER", + "MAX_ETHER", + "ELIXER", + "MAX_ELIXER", + "FLOOR_B2F", + "FLOOR_B1F", + "FLOOR_1F", + "FLOOR_2F", + "FLOOR_3F", + "FLOOR_4F", + "FLOOR_5F", + "FLOOR_6F", + "FLOOR_7F", + "FLOOR_8F", + "FLOOR_9F", + "FLOOR_10F", + "FLOOR_11F", + "FLOOR_B4F", +] + +# Start = 0xC4 +TM_HM_ITEM_IDS = [ + "HM_01", + "HM_02", + "HM_03", + "HM_04", + "HM_05", + "TM_01", + "TM_02", + "TM_03", + "TM_04", + "TM_05", + "TM_06", + "TM_07", + "TM_08", + "TM_09", + "TM_10", + "TM_11", + "TM_12", + "TM_13", + "TM_14", + "TM_15", + "TM_16", + "TM_17", + "TM_18", + "TM_19", + "TM_20", + "TM_21", + "TM_22", + "TM_23", + "TM_24", + "TM_25", + "TM_26", + "TM_27", + "TM_28", + "TM_29", + "TM_30", + "TM_31", + "TM_32", + "TM_33", + "TM_34", + "TM_35", + "TM_36", + "TM_37", + "TM_38", + "TM_39", + "TM_40", + "TM_41", + "TM_42", + "TM_43", + "TM_44", + "TM_45", + "TM_46", + "TM_47", + "TM_48", + "TM_49", + "TM_50", +] + +HM_ITEM_IDS = {0xC4, 0xC5, 0xC6, 0xC7, 0xC8} + +ITEM_NAME_TO_ID = ( + {v: i + 0x1 for i, v in enumerate(ITEM_NAMES)} + | {v: i + 0xC4 for i, v in enumerate(TM_HM_ITEM_IDS)} + | {"SAFARI_BAIT": 0x15, "SAFARI_ROCK": 0x16} +) diff --git a/pokemonred_puffer/data/map.py b/pokemonred_puffer/data/map.py new file mode 100644 index 0000000..a58ea30 --- /dev/null +++ b/pokemonred_puffer/data/map.py @@ -0,0 +1,16 @@ +RESET_MAP_IDS = { + 0x0, # Pallet Town + 0x1, # Viridian City + 0x2, # Pewter City + 0x3, # Cerulean City + 0x4, # Lavender Town + 0x5, # Vermilion City + 0x6, # Celadon City + 0x7, # Fuchsia City + 0x8, # Cinnabar Island + 0x9, # Indigo Plateau + 0xA, # Saffron City + 0xF, # Route 4 (Mt Moon) + 0x10, # Route 10 (Rock Tunnel) + 0xE9, # Silph Co 9F (Heal station) +} diff --git a/pokemonred_puffer/data/species.py b/pokemonred_puffer/data/species.py new file mode 100644 index 0000000..ebb0e4c --- /dev/null +++ b/pokemonred_puffer/data/species.py @@ -0,0 +1,192 @@ +SPECIES_IDS = { + "RHYDON": 0x01, + "KANGASKHAN": 0x02, + "NIDORAN_M": 0x03, + "CLEFAIRY": 0x04, + "SPEAROW": 0x05, + "VOLTORB": 0x06, + "NIDOKING": 0x07, + "SLOWBRO": 0x08, + "IVYSAUR": 0x09, + "EXEGGUTOR": 0x0A, + "LICKITUNG": 0x0B, + "EXEGGCUTE": 0x0C, + "GRIMER": 0x0D, + "GENGAR": 0x0E, + "NIDORAN_F": 0x0F, + "NIDOQUEEN": 0x10, + "CUBONE": 0x11, + "RHYHORN": 0x12, + "LAPRAS": 0x13, + "ARCANINE": 0x14, + "MEW": 0x15, + "GYARADOS": 0x16, + "SHELLDER": 0x17, + "TENTACOOL": 0x18, + "GASTLY": 0x19, + "SCYTHER": 0x1A, + "STARYU": 0x1B, + "BLASTOISE": 0x1C, + "PINSIR": 0x1D, + "TANGELA": 0x1E, + "MISSINGNO_1F": 0x1F, + "MISSINGNO_20": 0x20, + "GROWLITHE": 0x21, + "ONIX": 0x22, + "FEAROW": 0x23, + "PIDGEY": 0x24, + "SLOWPOKE": 0x25, + "KADABRA": 0x26, + "GRAVELER": 0x27, + "CHANSEY": 0x28, + "MACHOKE": 0x29, + "MR_MIME": 0x2A, + "HITMONLEE": 0x2B, + "HITMONCHAN": 0x2C, + "ARBOK": 0x2D, + "PARASECT": 0x2E, + "PSYDUCK": 0x2F, + "DROWZEE": 0x30, + "GOLEM": 0x31, + "MISSINGNO_32": 0x32, + "MAGMAR": 0x33, + "MISSINGNO_34": 0x34, + "ELECTABUZZ": 0x35, + "MAGNETON": 0x36, + "KOFFING": 0x37, + "MISSINGNO_38": 0x38, + "MANKEY": 0x39, + "SEEL": 0x3A, + "DIGLETT": 0x3B, + "TAUROS": 0x3C, + "MISSINGNO_3D": 0x3D, + "MISSINGNO_3E": 0x3E, + "MISSINGNO_3F": 0x3F, + "FARFETCHD": 0x40, + "VENONAT": 0x41, + "DRAGONITE": 0x42, + "MISSINGNO_43": 0x43, + "MISSINGNO_44": 0x44, + "MISSINGNO_45": 0x45, + "DODUO": 0x46, + "POLIWAG": 0x47, + "JYNX": 0x48, + "MOLTRES": 0x49, + "ARTICUNO": 0x4A, + "ZAPDOS": 0x4B, + "DITTO": 0x4C, + "MEOWTH": 0x4D, + "KRABBY": 0x4E, + "MISSINGNO_4F": 0x4F, + "MISSINGNO_50": 0x50, + "MISSINGNO_51": 0x51, + "VULPIX": 0x52, + "NINETALES": 0x53, + "PIKACHU": 0x54, + "RAICHU": 0x55, + "MISSINGNO_56": 0x56, + "MISSINGNO_57": 0x57, + "DRATINI": 0x58, + "DRAGONAIR": 0x59, + "KABUTO": 0x5A, + "KABUTOPS": 0x5B, + "HORSEA": 0x5C, + "SEADRA": 0x5D, + "MISSINGNO_5E": 0x5E, + "MISSINGNO_5F": 0x5F, + "SANDSHREW": 0x60, + "SANDSLASH": 0x61, + "OMANYTE": 0x62, + "OMASTAR": 0x63, + "JIGGLYPUFF": 0x64, + "WIGGLYTUFF": 0x65, + "EEVEE": 0x66, + "FLAREON": 0x67, + "JOLTEON": 0x68, + "VAPOREON": 0x69, + "MACHOP": 0x6A, + "ZUBAT": 0x6B, + "EKANS": 0x6C, + "PARAS": 0x6D, + "POLIWHIRL": 0x6E, + "POLIWRATH": 0x6F, + "WEEDLE": 0x70, + "KAKUNA": 0x71, + "BEEDRILL": 0x72, + "MISSINGNO_73": 0x73, + "DODRIO": 0x74, + "PRIMEAPE": 0x75, + "DUGTRIO": 0x76, + "VENOMOTH": 0x77, + "DEWGONG": 0x78, + "MISSINGNO_79": 0x79, + "MISSINGNO_7A": 0x7A, + "CATERPIE": 0x7B, + "METAPOD": 0x7C, + "BUTTERFREE": 0x7D, + "MACHAMP": 0x7E, + "MISSINGNO_7F": 0x7F, + "GOLDUCK": 0x80, + "HYPNO": 0x81, + "GOLBAT": 0x82, + "MEWTWO": 0x83, + "SNORLAX": 0x84, + "MAGIKARP": 0x85, + "MISSINGNO_86": 0x86, + "MISSINGNO_87": 0x87, + "MUK": 0x88, + "MISSINGNO_89": 0x89, + "KINGLER": 0x8A, + "CLOYSTER": 0x8B, + "MISSINGNO_8C": 0x8C, + "ELECTRODE": 0x8D, + "CLEFABLE": 0x8E, + "WEEZING": 0x8F, + "PERSIAN": 0x90, + "MAROWAK": 0x91, + "MISSINGNO_92": 0x92, + "HAUNTER": 0x93, + "ABRA": 0x94, + "ALAKAZAM": 0x95, + "PIDGEOTTO": 0x96, + "PIDGEOT": 0x97, + "STARMIE": 0x98, + "BULBASAUR": 0x99, + "VENUSAUR": 0x9A, + "TENTACRUEL": 0x9B, + "MISSINGNO_9C": 0x9C, + "GOLDEEN": 0x9D, + "SEAKING": 0x9E, + "MISSINGNO_9F": 0x9F, + "MISSINGNO_A0": 0xA0, + "MISSINGNO_A1": 0xA1, + "MISSINGNO_A2": 0xA2, + "PONYTA": 0xA3, + "RAPIDASH": 0xA4, + "RATTATA": 0xA5, + "RATICATE": 0xA6, + "NIDORINO": 0xA7, + "NIDORINA": 0xA8, + "GEODUDE": 0xA9, + "PORYGON": 0xAA, + "AERODACTYL": 0xAB, + "MISSINGNO_AC": 0xAC, + "MAGNEMITE": 0xAD, + "MISSINGNO_AE": 0xAE, + "MISSINGNO_AF": 0xAF, + "CHARMANDER": 0xB0, + "SQUIRTLE": 0xB1, + "CHARMELEON": 0xB2, + "WARTORTLE": 0xB3, + "CHARIZARD": 0xB4, + "MISSINGNO_B5": 0xB5, + "FOSSIL_KABUTOPS": 0xB6, + "FOSSIL_AERODACTYL": 0xB7, + "MON_GHOST": 0xB8, + "ODDISH": 0xB9, + "GLOOM": 0xBA, + "VILEPLUME": 0xBB, + "BELLSPROUT": 0xBC, + "WEEPINBELL": 0xBD, + "VICTREEBEL": 0xBE, +} diff --git a/pokemonred_puffer/data/tm_hm.py b/pokemonred_puffer/data/tm_hm.py new file mode 100644 index 0000000..df8263f --- /dev/null +++ b/pokemonred_puffer/data/tm_hm.py @@ -0,0 +1,194 @@ +from pokemonred_puffer.data.species import SPECIES_IDS + + +TM_HM_MOVES = { + 5, # Mega punch + 0xD, # Razor wind + 0xE, # Swords dance + 0x12, # Whirlwind + 0x19, # Mega kick + 0x5C, # Toxic + 0x20, # Horn drill + 0x22, # Body slam + 0x24, # Take down + 0x26, # Double edge + 0x3D, # Bubble beam + 0x37, # Water gun + 0x3A, # Ice beam + 0x3B, # Blizzard + 0x3F, # Hyper beam + 0x06, # Pay day + 0x42, # Submission + 0x44, # Counter + 0x45, # Seismic toss + 0x63, # Rage + 0x48, # Mega drain + 0x4C, # Solar beam + 0x52, # Dragon rage + 0x55, # Thunderbolt + 0x57, # Thunder + 0x59, # Earthquake + 0x5A, # Fissure + 0x5B, # Dig + 0x5E, # Psychic + 0x64, # Teleport + 0x66, # Mimic + 0x68, # Double team + 0x73, # Reflect + 0x75, # Bide + 0x76, # Metronome + 0x78, # Selfdestruct + 0x79, # Egg bomb + 0x7E, # Fire blast + 0x81, # Swift + 0x82, # Skull bash + 0x87, # Softboiled + 0x8A, # Dream eater + 0x8F, # Sky attack + 0x9C, # Rest + 0x56, # Thunder wave + 0x95, # Psywave + 0x99, # Explosion + 0x9D, # Rock slide + 0xA1, # Tri attack + 0xA4, # Substitute + 0x0F, # Cut + 0x13, # Fly + 0x39, # Surf + 0x46, # Strength + 0x94, # Flash +} + + +CUT_SPECIES_IDS = { + SPECIES_IDS["BULBASAUR"], + SPECIES_IDS["IVYSAUR"], + SPECIES_IDS["VENUSAUR"], + SPECIES_IDS["CHARMANDER"], + SPECIES_IDS["CHARMELEON"], + SPECIES_IDS["CHARIZARD"], + SPECIES_IDS["BEEDRILL"], + SPECIES_IDS["SANDSHREW"], + SPECIES_IDS["SANDSLASH"], + SPECIES_IDS["ODDISH"], + SPECIES_IDS["GLOOM"], + SPECIES_IDS["VILEPLUME"], + SPECIES_IDS["PARAS"], + SPECIES_IDS["PARASECT"], + SPECIES_IDS["BELLSPROUT"], + SPECIES_IDS["WEEPINBELL"], + SPECIES_IDS["VICTREEBEL"], + SPECIES_IDS["TENTACOOL"], + SPECIES_IDS["TENTACRUEL"], + SPECIES_IDS["FARFETCHD"], + SPECIES_IDS["KRABBY"], + SPECIES_IDS["KINGLER"], + SPECIES_IDS["LICKITUNG"], + SPECIES_IDS["TANGELA"], + SPECIES_IDS["SCYTHER"], + SPECIES_IDS["PINSIR"], + SPECIES_IDS["MEW"], +} + +SURF_SPECIES_IDS = { + SPECIES_IDS["SQUIRTLE"], + SPECIES_IDS["WARTORTLE"], + SPECIES_IDS["BLASTOISE"], + SPECIES_IDS["NIDOQUEEN"], + SPECIES_IDS["NIDOKING"], + SPECIES_IDS["PSYDUCK"], + SPECIES_IDS["GOLDUCK"], + SPECIES_IDS["POLIWAG"], + SPECIES_IDS["POLIWHIRL"], + SPECIES_IDS["POLIWRATH"], + SPECIES_IDS["TENTACOOL"], + SPECIES_IDS["TENTACRUEL"], + SPECIES_IDS["SLOWPOKE"], + SPECIES_IDS["SLOWBRO"], + SPECIES_IDS["SEEL"], + SPECIES_IDS["DEWGONG"], + SPECIES_IDS["SHELLDER"], + SPECIES_IDS["CLOYSTER"], + SPECIES_IDS["KRABBY"], + SPECIES_IDS["KINGLER"], + SPECIES_IDS["LICKITUNG"], + SPECIES_IDS["RHYDON"], + SPECIES_IDS["KANGASKHAN"], + SPECIES_IDS["HORSEA"], + SPECIES_IDS["SEADRA"], + SPECIES_IDS["GOLDEEN"], + SPECIES_IDS["SEAKING"], + SPECIES_IDS["STARYU"], + SPECIES_IDS["STARMIE"], + SPECIES_IDS["GYARADOS"], + SPECIES_IDS["LAPRAS"], + SPECIES_IDS["VAPOREON"], + SPECIES_IDS["OMANYTE"], + SPECIES_IDS["OMASTAR"], + SPECIES_IDS["KABUTO"], + SPECIES_IDS["KABUTOPS"], + SPECIES_IDS["SNORLAX"], + SPECIES_IDS["DRATINI"], + SPECIES_IDS["DRAGONAIR"], + SPECIES_IDS["DRAGONITE"], + SPECIES_IDS["MEW"], +} + +STRENGTH_SPECIES_IDS = { + SPECIES_IDS["CHARMANDER"], + SPECIES_IDS["CHARMELEON"], + SPECIES_IDS["CHARIZARD"], + SPECIES_IDS["SQUIRTLE"], + SPECIES_IDS["WARTORTLE"], + SPECIES_IDS["BLASTOISE"], + SPECIES_IDS["EKANS"], + SPECIES_IDS["ARBOK"], + SPECIES_IDS["SANDSHREW"], + SPECIES_IDS["SANDSLASH"], + SPECIES_IDS["NIDOQUEEN"], + SPECIES_IDS["NIDOKING"], + SPECIES_IDS["CLEFAIRY"], + SPECIES_IDS["CLEFABLE"], + SPECIES_IDS["JIGGLYPUFF"], + SPECIES_IDS["WIGGLYTUFF"], + SPECIES_IDS["PSYDUCK"], + SPECIES_IDS["GOLDUCK"], + SPECIES_IDS["MANKEY"], + SPECIES_IDS["PRIMEAPE"], + SPECIES_IDS["POLIWHIRL"], + SPECIES_IDS["POLIWRATH"], + SPECIES_IDS["MACHOP"], + SPECIES_IDS["MACHOKE"], + SPECIES_IDS["MACHAMP"], + SPECIES_IDS["GEODUDE"], + SPECIES_IDS["GRAVELER"], + SPECIES_IDS["GOLEM"], + SPECIES_IDS["SLOWPOKE"], + SPECIES_IDS["SLOWBRO"], + SPECIES_IDS["SEEL"], + SPECIES_IDS["DEWGONG"], + SPECIES_IDS["GENGAR"], + SPECIES_IDS["ONIX"], + SPECIES_IDS["KRABBY"], + SPECIES_IDS["KINGLER"], + SPECIES_IDS["EXEGGUTOR"], + SPECIES_IDS["CUBONE"], + SPECIES_IDS["MAROWAK"], + SPECIES_IDS["HITMONLEE"], + SPECIES_IDS["HITMONCHAN"], + SPECIES_IDS["LICKITUNG"], + SPECIES_IDS["RHYHORN"], + SPECIES_IDS["RHYDON"], + SPECIES_IDS["CHANSEY"], + SPECIES_IDS["KANGASKHAN"], + SPECIES_IDS["ELECTABUZZ"], + SPECIES_IDS["MAGMAR"], + SPECIES_IDS["PINSIR"], + SPECIES_IDS["TAUROS"], + SPECIES_IDS["GYARADOS"], + SPECIES_IDS["LAPRAS"], + SPECIES_IDS["SNORLAX"], + SPECIES_IDS["DRAGONITE"], + SPECIES_IDS["MEWTWO"], + SPECIES_IDS["MEW"], +} From dada35bd26237070884edf972ad00a639843cbfc Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Wed, 12 Jun 2024 22:59:08 -0400 Subject: [PATCH 44/68] Add infinite money --- config.yaml | 1 + pokemonred_puffer/environment.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/config.yaml b/config.yaml index 2f0ed4c..9ed4731 100644 --- a/config.yaml +++ b/config.yaml @@ -57,6 +57,7 @@ env: auto_teach_surf: True auto_teach_strength: True auto_remove_all_nonuseful_items: True + infinite_money: True train: diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 1a846e7..89fd68e 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -95,6 +95,7 @@ def __init__(self, env_config: pufferlib.namespace): self.auto_teach_strength = env_config.auto_teach_strength self.auto_use_cut = env_config.auto_use_cut self.auto_remove_all_nonuseful_items = env_config.auto_remove_all_nonuseful_items + self.infinite_money = env_config.infinite_money self.action_space = ACTION_SPACE # Obs space-related. TODO: avoid hardcoding? @@ -499,6 +500,13 @@ def step(self, action): if self.auto_remove_all_nonuseful_items: self.remove_all_nonuseful_items() + _, wPlayerMoney = self.pyboy.symbol_lookup("wPlayerMoney") + if ( + self.infinite_money + and int.from_bytes(self.pyboy.memory[wPlayerMoney : wPlayerMoney + 3], "little") < 10000 + ): + self.pyboy.memory[wPlayerMoney : wPlayerMoney + 3] = int(10000).to_bytes(3, "little") + self.run_action_on_emulator(action) self.update_seen_coords() self.update_health() From dcee463d0a520c5a5110976c1bbac04db1852b1e Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Wed, 12 Jun 2024 23:49:05 -0400 Subject: [PATCH 45/68] Add auto surf --- config.yaml | 3 +- pokemonred_puffer/data/field_moves.py | 3 + pokemonred_puffer/environment.py | 145 ++++++++++++++++++++++++-- 3 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 pokemonred_puffer/data/field_moves.py diff --git a/config.yaml b/config.yaml index 9ed4731..d9bfa26 100644 --- a/config.yaml +++ b/config.yaml @@ -7,7 +7,7 @@ debug: env: headless: False stream_wrapper: False - init_state: cut3 + init_state: surf max_steps: 1_000_000 disable_wild_encounters: True disable_ai_actions: True @@ -54,6 +54,7 @@ env: disable_ai_actions: False auto_teach_cut: True auto_use_cut: True + auto_use_surf: True auto_teach_surf: True auto_teach_strength: True auto_remove_all_nonuseful_items: True diff --git a/pokemonred_puffer/data/field_moves.py b/pokemonred_puffer/data/field_moves.py new file mode 100644 index 0000000..6d33806 --- /dev/null +++ b/pokemonred_puffer/data/field_moves.py @@ -0,0 +1,3 @@ +FIELD_MOVES = ["CUT", "FLY", "SURF", "SURF", "STRENGTH", "FLASH", "DIG", "TELEPORT", "SOFTBOILED"] + +FIELD_MOVES_MAP = {i + 1: v for i, v in enumerate(FIELD_MOVES)} diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 89fd68e..e2f317e 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -17,6 +17,7 @@ import pufferlib from pokemonred_puffer.data.events import EVENT_FLAGS_START, EVENTS_FLAGS_LENGTH, MUSEUM_TICKET +from pokemonred_puffer.data.field_moves import FIELD_MOVES_MAP from pokemonred_puffer.data.items import ( ITEM_NAME_TO_ID, HM_ITEM_IDS, @@ -94,6 +95,7 @@ def __init__(self, env_config: pufferlib.namespace): self.auto_teach_surf = env_config.auto_teach_surf self.auto_teach_strength = env_config.auto_teach_strength self.auto_use_cut = env_config.auto_use_cut + self.auto_use_surf = env_config.auto_use_surf self.auto_remove_all_nonuseful_items = env_config.auto_remove_all_nonuseful_items self.infinite_money = env_config.infinite_money self.action_space = ACTION_SPACE @@ -553,6 +555,7 @@ def run_action_on_emulator(self, action): self.action_hist[action] += 1 # press button then release after some steps # TODO: Add video saving logic + if not self.disable_ai_actions: self.pyboy.send_input(VALID_ACTIONS[action]) self.pyboy.send_input(VALID_RELEASE_ACTIONS[action], delay=8) @@ -567,6 +570,8 @@ def run_action_on_emulator(self, action): if self.read_bit(0xD78E, 0): if self.auto_teach_surf and not self.check_if_party_has_hm(0x39): self.teach_hm(0x39, 15, SURF_SPECIES_IDS) + if self.auto_use_surf: + self.surf_if_attempt(VALID_ACTIONS[action]) if self.read_bit(0xD857, 0): if self.auto_teach_strength and not self.check_if_party_has_hm(0x46): @@ -607,7 +612,7 @@ def cut_if_next(self): in_erika_gym = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMapTileset")[1]] == 7 in_overworld = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMapTileset")[1]] == 0 if in_erika_gym or in_overworld: - wTileMap = self.pyboy.symbol_lookup("wTileMap")[1] + _, wTileMap = self.pyboy.symbol_lookup("wTileMap") tileMap = self.pyboy.memory[wTileMap : wTileMap + 20 * 18] tileMap = np.array(tileMap, dtype=np.uint8) tileMap = np.reshape(tileMap, (18, 20)) @@ -664,9 +669,140 @@ def cut_if_next(self): self.pyboy.tick(self.action_freq, render=True) party_mon = self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] _, addr = self.pyboy.symbol_lookup(f"wPartyMon{party_mon%6+1}Moves") - if 15 in self.pyboy.memory[addr : addr + 4]: + if 0xF in self.pyboy.memory[addr : addr + 4]: + break + + # Enter submenu + self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) + self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) + self.pyboy.tick(4 * self.action_freq, render=True) + + # Scroll until the field move is found + _, wFieldMoves = self.pyboy.symbol_lookup("wFieldMoves") + field_moves = self.pyboy.memory[wFieldMoves : wFieldMoves + 4] + + for _ in range(10): + current_item = self.read_m("wCurrentMenuItem") + if current_item < 4 and FIELD_MOVES_MAP.get(field_moves[current_item], "") == "CUT": + break + self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) + self.pyboy.tick(self.action_freq, render=True) + + # press a bunch of times + for _ in range(5): + self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) + self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) + self.pyboy.tick(4 * self.action_freq, render=True) + + def surf_if_attempt(self, action: WindowEvent): + if not ( + self.read_m("wWalkBikeSurfState") != 2 + and self.check_if_party_has_hm(0x39) + and action + in [ + WindowEvent.PRESS_ARROW_DOWN, + WindowEvent.PRESS_ARROW_LEFT, + WindowEvent.PRESS_ARROW_RIGHT, + WindowEvent.PRESS_ARROW_UP, + ] + ): + return + + in_overworld = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMapTileset")[1]] == 0 + if in_overworld: + _, wTileMap = self.pyboy.symbol_lookup("wTileMap") + tileMap = self.pyboy.memory[wTileMap : wTileMap + 20 * 18] + tileMap = np.array(tileMap, dtype=np.uint8) + tileMap = np.reshape(tileMap, (18, 20)) + y, x = 8, 8 + # This could be made a little faster by only checking the + # direction that matters, but I decided to copy pasta the cut routine + up, down, left, right = ( + tileMap[y - 2 : y, x : x + 2], # up + tileMap[y + 2 : y + 4, x : x + 2], # down + tileMap[y : y + 2, x - 2 : x], # left + tileMap[y : y + 2, x + 2 : x + 4], # right + ) + + # down, up, left, right + direction = self.read_m("wSpritePlayerStateData1FacingDirection") + + if not ( + ( + direction == 0x4 + and action == WindowEvent.PRESS_ARROW_UP + and in_overworld + and 0x14 in up + ) + or ( + direction == 0x0 + and action == WindowEvent.PRESS_ARROW_DOWN + and in_overworld + and 0x14 in down + ) + or ( + direction == 0x8 + and action == WindowEvent.PRESS_ARROW_LEFT + and in_overworld + and 0x14 in left + ) + or ( + direction == 0xC + and action == WindowEvent.PRESS_ARROW_RIGHT + and in_overworld + and 0x14 in right + ) + ): + return + + # open start menu + self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) + self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START, delay=8) + self.pyboy.tick(self.action_freq, render=True) + # scroll to pokemon + # 1 is the item index for pokemon + for _ in range(24): + if self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] == 1: + break + self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) + self.pyboy.tick(self.action_freq, render=True) + self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) + self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) + self.pyboy.tick(self.action_freq, render=True) + + # find pokemon with surf + # We run this over all pokemon so we dont end up in an infinite for loop + for _ in range(7): + self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) + self.pyboy.tick(self.action_freq, render=True) + party_mon = self.pyboy.memory[self.pyboy.symbol_lookup("wCurrentMenuItem")[1]] + _, addr = self.pyboy.symbol_lookup(f"wPartyMon{party_mon%6+1}Moves") + if 0x39 in self.pyboy.memory[addr : addr + 4]: break + # Enter submenu + self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) + self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) + self.pyboy.tick(4 * self.action_freq, render=True) + + # Scroll until the field move is found + _, wFieldMoves = self.pyboy.symbol_lookup("wFieldMoves") + field_moves = self.pyboy.memory[wFieldMoves : wFieldMoves + 4] + + for _ in range(10): + current_item = self.read_m("wCurrentMenuItem") + if ( + current_item < 4 + and FIELD_MOVES_MAP.get(field_moves[current_item], "") == "SURF" + ): + break + self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) + self.pyboy.tick(self.action_freq, render=True) + # press a bunch of times for _ in range(5): self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) @@ -733,10 +869,7 @@ def cut_hook(self, context): self.pyboy.symbol_lookup("wTileInFrontOfPlayer")[1] ] if context: - if wTileInFrontOfPlayer in [ - 0x3D, - 0x50, - ]: + if wTileInFrontOfPlayer in [0x3D, 0x50]: self.cut_coords[coords] = 10 else: self.cut_coords[coords] = 0.001 From b78efe19b3184ea6a7a3af123c5794ee87354df1 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Thu, 13 Jun 2024 00:19:35 -0400 Subject: [PATCH 46/68] Add auto pokeflute --- config.yaml | 3 +- pokemonred_puffer/environment.py | 86 +++++++++++++++++++++++++++++++ pyboy_states/pokeflute.state | Bin 0 -> 165650 bytes 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 pyboy_states/pokeflute.state diff --git a/config.yaml b/config.yaml index d9bfa26..20353b9 100644 --- a/config.yaml +++ b/config.yaml @@ -7,7 +7,7 @@ debug: env: headless: False stream_wrapper: False - init_state: surf + init_state: pokeflute max_steps: 1_000_000 disable_wild_encounters: True disable_ai_actions: True @@ -58,6 +58,7 @@ env: auto_teach_surf: True auto_teach_strength: True auto_remove_all_nonuseful_items: True + auto_pokeflute: True infinite_money: True diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index e2f317e..e426ef8 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -97,6 +97,7 @@ def __init__(self, env_config: pufferlib.namespace): self.auto_use_cut = env_config.auto_use_cut self.auto_use_surf = env_config.auto_use_surf self.auto_remove_all_nonuseful_items = env_config.auto_remove_all_nonuseful_items + self.auto_pokeflute = env_config.auto_pokeflute self.infinite_money = env_config.infinite_money self.action_space = ACTION_SPACE @@ -577,6 +578,9 @@ def run_action_on_emulator(self, action): if self.auto_teach_strength and not self.check_if_party_has_hm(0x46): self.teach_hm(0x46, 15, STRENGTH_SPECIES_IDS) + if self.read_bit(0xD76C, 0) and self.auto_pokeflute: + self.use_pokeflute() + def party_has_cut_capable_mon(self): # find bulba and replace tackle (first skill) with cut party_size = self.read_m("wPartyCount") @@ -607,6 +611,88 @@ def teach_hm(self, tmhm: int, pp: int, pokemon_species_ids): # fill up pp: 30/30 break + def use_pokeflute(self): + in_overworld = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMapTileset")[1]] == 0 + if in_overworld: + _, wBagItems = self.pyboy.symbol_lookup("wBagItems") + bag_items = self.pyboy.memory[wBagItems : wBagItems + 40] + if ITEM_NAME_TO_ID["POKE_FLUTE"] not in bag_items[::2]: + return + pokeflute_index = bag_items[::2].index(ITEM_NAME_TO_ID["POKE_FLUTE"]) + + _, wTileMap = self.pyboy.symbol_lookup("wTileMap") + tileMap = self.pyboy.memory[wTileMap : wTileMap + 20 * 18] + tileMap = np.array(tileMap, dtype=np.uint8) + tileMap = np.reshape(tileMap, (18, 20)) + y, x = 8, 8 + up, down, left, right = ( + tileMap[y - 2 : y, x : x + 2], # up + tileMap[y + 2 : y + 4, x : x + 2], # down + tileMap[y : y + 2, x - 2 : x], # left + tileMap[y : y + 2, x + 2 : x + 4], # right + ) + + if in_overworld and 0x30 in up: + self.pyboy.send_input(WindowEvent.PRESS_ARROW_UP) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_UP, delay=8) + self.pyboy.tick(self.action_freq, render=True) + elif in_overworld and 0x30 in down: + self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) + self.pyboy.tick(self.action_freq, render=True) + elif in_overworld and 0x30 in left: + self.pyboy.send_input(WindowEvent.PRESS_ARROW_LEFT) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_LEFT, delay=8) + self.pyboy.tick(self.action_freq, render=True) + elif in_overworld and 0x30 in right: + self.pyboy.send_input(WindowEvent.PRESS_ARROW_RIGHT) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_RIGHT, delay=8) + self.pyboy.tick(self.action_freq, render=True) + else: + return + + # open start menu + self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) + self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START, delay=8) + self.pyboy.tick(self.action_freq, render=True) + # scroll to bag + # 2 is the item index for bag + for _ in range(24): + if self.read_m("wCurrentMenuItem") == 2: + break + self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) + self.pyboy.tick(self.action_freq, render=True) + self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) + self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) + self.pyboy.tick(self.action_freq, render=True) + + # Scroll until you get to pokeflute + # We'll do this by scrolling all the way up then all the way down + # There is a faster way to do it, but this is easier to think about + # Could also set the menu index manually, but there are like 4 variables + # for that + for _ in range(20): + self.pyboy.send_input(WindowEvent.PRESS_ARROW_UP) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_UP, delay=8) + self.pyboy.tick(self.action_freq, render=True) + + for _ in range(21): + if ( + self.read_m("wCurrentMenuItem") + self.read_m("wListScrollOffset") + == pokeflute_index + ): + break + self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) + self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) + self.pyboy.tick(self.action_freq, render=True) + + # press a bunch of times + for _ in range(5): + self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) + self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) + self.pyboy.tick(4 * self.action_freq, render=True) + def cut_if_next(self): # https://github.com/pret/pokered/blob/d38cf5281a902b4bd167a46a7c9fd9db436484a7/constants/tileset_constants.asm#L11C8-L11C11 in_erika_gym = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMapTileset")[1]] == 7 diff --git a/pyboy_states/pokeflute.state b/pyboy_states/pokeflute.state new file mode 100644 index 0000000000000000000000000000000000000000..2329258e288325489cf1594418643971e31e316e GIT binary patch literal 165650 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 Date: Sat, 15 Jun 2024 00:05:25 -0400 Subject: [PATCH 47/68] Fix pokeflute, add seafoam strength puzzle solving scripts --- config.yaml | 2 +- pokemonred_puffer/data/strength_puzzles.py | 96 ++++++++++ pokemonred_puffer/data/tilesets.py | 28 +++ pokemonred_puffer/environment.py | 197 +++++++++++++-------- pyboy_states/seafoam.state | Bin 0 -> 165650 bytes pyboy_states/seafoam_1f_right.state | Bin 0 -> 165650 bytes pyboy_states/with_cut_vermillion.state | Bin 0 -> 142610 bytes 7 files changed, 248 insertions(+), 75 deletions(-) create mode 100644 pokemonred_puffer/data/strength_puzzles.py create mode 100644 pokemonred_puffer/data/tilesets.py create mode 100644 pyboy_states/seafoam.state create mode 100644 pyboy_states/seafoam_1f_right.state create mode 100644 pyboy_states/with_cut_vermillion.state diff --git a/config.yaml b/config.yaml index 20353b9..79bda46 100644 --- a/config.yaml +++ b/config.yaml @@ -7,7 +7,7 @@ debug: env: headless: False stream_wrapper: False - init_state: pokeflute + init_state: pokeflute max_steps: 1_000_000 disable_wild_encounters: True disable_ai_actions: True diff --git a/pokemonred_puffer/data/strength_puzzles.py b/pokemonred_puffer/data/strength_puzzles.py new file mode 100644 index 0000000..af0c73f --- /dev/null +++ b/pokemonred_puffer/data/strength_puzzles.py @@ -0,0 +1,96 @@ +STRENGTH_SOLUTIONS = {} +# Seafoam 1F Left +STRENGTH_SOLUTIONS[(63, 14, 22, 18, 11, 192)] = [ + "UP", + "UP", + "UP", + "UP", + "UP", + "UP", + "UP", + "UP", + "UP", + "UP", + "UP", + "UP", + "UP", + "UP", + "UP", + "UP", + "RIGHT", + "UP", + "LEFT", +] +STRENGTH_SOLUTIONS[(63, 14, 22, 19, 10, 192)] = ["DOWN", "LEFT"] + STRENGTH_SOLUTIONS[ + (63, 14, 22, 18, 11, 192) +] +STRENGTH_SOLUTIONS[(63, 14, 22, 18, 9, 192)] = ["RIGHT", "DOWN"] + STRENGTH_SOLUTIONS[ + (63, 14, 22, 19, 10, 192) +] +STRENGTH_SOLUTIONS[(63, 14, 22, 17, 10, 192)] = ["UP", "RIGHT"] + STRENGTH_SOLUTIONS[ + (63, 14, 22, 18, 9, 192) +] + +# Seafoam 1F right +STRENGTH_SOLUTIONS[(63, 11, 30, 26, 8, 192)] = [ + "UP", + "RIGHT", + "UP", + "RIGHT", + "UP", + "UP", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", +] +STRENGTH_SOLUTIONS[(63, 11, 30, 27, 7, 192)] = ["DOWN", "LEFT"] + STRENGTH_SOLUTIONS[ + (63, 11, 30, 26, 8, 192) +] +STRENGTH_SOLUTIONS[(63, 11, 30, 26, 6, 192)] = ["RIGHT", "DOWN"] + STRENGTH_SOLUTIONS[ + (63, 11, 30, 27, 7, 192) +] +STRENGTH_SOLUTIONS[(63, 11, 30, 25, 7, 192)] = ["UP", "RIGHT"] + STRENGTH_SOLUTIONS[ + (63, 11, 30, 26, 6, 192) +] + +# Seafoam B1 left + +STRENGTH_SOLUTIONS[(63, 10, 21, 16, 6, 159)] = ["RIGHT"] +STRENGTH_SOLUTIONS[(63, 10, 21, 17, 5, 159)] = ["LEFT", "DOWN"] + STRENGTH_SOLUTIONS[ + (63, 10, 21, 16, 6, 159) +] +STRENGTH_SOLUTIONS[(63, 10, 21, 17, 7, 159)] = ["LEFT", "UP"] + STRENGTH_SOLUTIONS[ + (63, 10, 21, 16, 6, 159) +] + +# Seafoam B1 right + +STRENGTH_SOLUTIONS[(63, 10, 26, 21, 6, 159)] = ["RIGHT"] +STRENGTH_SOLUTIONS[(63, 10, 26, 22, 5, 159)] = ["LEFT", "DOWN"] + STRENGTH_SOLUTIONS[ + (63, 10, 26, 21, 6, 159) +] +STRENGTH_SOLUTIONS[(63, 10, 26, 22, 7, 159)] = ["LEFT", "UP"] + STRENGTH_SOLUTIONS[ + (63, 10, 26, 21, 6, 159) +] + +# Seafoam B2 left + +STRENGTH_SOLUTIONS[(63, 10, 22, 17, 6, 160)] = ["RIGHT"] +STRENGTH_SOLUTIONS[(63, 10, 22, 18, 5, 160)] = ["LEFT", "DOWN"] + STRENGTH_SOLUTIONS[ + (63, 10, 22, 17, 6, 160) +] +STRENGTH_SOLUTIONS[(63, 10, 22, 18, 7, 160)] = ["LEFT", "UP"] + STRENGTH_SOLUTIONS[ + (63, 10, 22, 17, 6, 160) +] + +# Seafoam B2 right + +STRENGTH_SOLUTIONS[(63, 10, 27, 24, 6, 160)] = ["LEFT"] +STRENGTH_SOLUTIONS[(63, 10, 27, 23, 7, 160)] = ["RIGHT", "UP"] + STRENGTH_SOLUTIONS[ + (63, 10, 27, 24, 6, 160) +] + +# We skip seafoam b3 since that is for articuno +# TODO: Articuno diff --git a/pokemonred_puffer/data/tilesets.py b/pokemonred_puffer/data/tilesets.py new file mode 100644 index 0000000..5c471a2 --- /dev/null +++ b/pokemonred_puffer/data/tilesets.py @@ -0,0 +1,28 @@ +from enum import Enum + + +class Tilesets(Enum): + OVERWORLD = 0 + REDS_HOUSE_1 = 1 + MART = 2 + FOREST = 3 + REDS_HOUSE_2 = 4 + DOJO = 5 + POKECENTER = 6 + GYM = 7 + HOUSE = 8 + FOREST_GATE = 9 + MUSEUM = 10 + UNDERGROUND = 11 + GATE = 12 + SHIP = 13 + SHIP_PORT = 14 + CEMETERY = 15 + INTERIOR = 16 + CAVERN = 17 + LOBBY = 18 + MANSION = 19 + LAB = 20 + CLUB = 21 + FACILITY = 22 + PLATEAU = 23 diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index e426ef8..a6b35a9 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -24,6 +24,8 @@ KEY_ITEM_IDS, MAX_ITEM_CAPACITY, ) +from pokemonred_puffer.data.strength_puzzles import STRENGTH_SOLUTIONS +from pokemonred_puffer.data.tilesets import Tilesets from pokemonred_puffer.data.tm_hm import ( CUT_SPECIES_IDS, STRENGTH_SPECIES_IDS, @@ -211,14 +213,20 @@ def register_hooks(self): # self.pyboy.hook_register(None, "UsedCut.nothingToCut", self.cut_hook, context=True) # self.pyboy.hook_register(None, "UsedCut.canCut", self.cut_hook, context=False) if self.disable_wild_encounters: - print("registering") - bank, addr = self.pyboy.symbol_lookup("TryDoWildEncounter.gotWildEncounterType") - self.pyboy.hook_register( - bank, - addr + 8, - self.disable_wild_encounter_hook, - None, - ) + self.setup_disable_wild_encounters() + + def setup_disable_wild_encounters(self): + bank, addr = self.pyboy.symbol_lookup("TryDoWildEncounter.gotWildEncounterType") + self.pyboy.hook_register( + bank, + addr + 8, + self.disable_wild_encounter_hook, + None, + ) + + def setup_enable_wild_ecounters(self): + bank, addr = self.pyboy.symbol_lookup("TryDoWildEncounter.gotWildEncounterType") + self.pyboy.hook_deregister(bank, addr) def update_state(self, state: bytes): self.reset(seed=random.randint(0, 10), options={"state": state}) @@ -577,6 +585,7 @@ def run_action_on_emulator(self, action): if self.read_bit(0xD857, 0): if self.auto_teach_strength and not self.check_if_party_has_hm(0x46): self.teach_hm(0x46, 15, STRENGTH_SPECIES_IDS) + self.solve_strength_puzzle() if self.read_bit(0xD76C, 0) and self.auto_pokeflute: self.use_pokeflute() @@ -612,7 +621,7 @@ def teach_hm(self, tmhm: int, pp: int, pokemon_species_ids): break def use_pokeflute(self): - in_overworld = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMapTileset")[1]] == 0 + in_overworld = self.read_m("wCurMapTileset") == Tilesets.OVERWORLD.value if in_overworld: _, wBagItems = self.pyboy.symbol_lookup("wBagItems") bag_items = self.pyboy.memory[wBagItems : wBagItems + 40] @@ -620,83 +629,86 @@ def use_pokeflute(self): return pokeflute_index = bag_items[::2].index(ITEM_NAME_TO_ID["POKE_FLUTE"]) - _, wTileMap = self.pyboy.symbol_lookup("wTileMap") - tileMap = self.pyboy.memory[wTileMap : wTileMap + 20 * 18] - tileMap = np.array(tileMap, dtype=np.uint8) - tileMap = np.reshape(tileMap, (18, 20)) - y, x = 8, 8 - up, down, left, right = ( - tileMap[y - 2 : y, x : x + 2], # up - tileMap[y + 2 : y + 4, x : x + 2], # down - tileMap[y : y + 2, x - 2 : x], # left - tileMap[y : y + 2, x + 2 : x + 4], # right - ) + # Check if we're on the snorlax coordinates - if in_overworld and 0x30 in up: - self.pyboy.send_input(WindowEvent.PRESS_ARROW_UP) - self.pyboy.send_input(WindowEvent.RELEASE_ARROW_UP, delay=8) + coords = self.get_game_coords() + if coords == (9, 62, 23): + self.pyboy.button("RIGHT", 8) self.pyboy.tick(self.action_freq, render=True) - elif in_overworld and 0x30 in down: - self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) - self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) + elif coords == (10, 63, 23): + self.pyboy.button("UP", 8) self.pyboy.tick(self.action_freq, render=True) - elif in_overworld and 0x30 in left: - self.pyboy.send_input(WindowEvent.PRESS_ARROW_LEFT) - self.pyboy.send_input(WindowEvent.RELEASE_ARROW_LEFT, delay=8) + elif coords == (10, 61, 23): + self.pyboy.button("DOWN", 8) self.pyboy.tick(self.action_freq, render=True) - elif in_overworld and 0x30 in right: - self.pyboy.send_input(WindowEvent.PRESS_ARROW_RIGHT) - self.pyboy.send_input(WindowEvent.RELEASE_ARROW_RIGHT, delay=8) + elif coords == (27, 10, 27): + self.pyboy.button("LEFT", 8) + self.pyboy.tick(self.action_freq, render=True) + elif coords == (27, 10, 25): + self.pyboy.button("RIGHT", 8) self.pyboy.tick(self.action_freq, render=True) else: return + # Then check if snorlax is a missable object + # Then trigger snorlax - # open start menu - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_START) - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_START, delay=8) - self.pyboy.tick(self.action_freq, render=True) - # scroll to bag - # 2 is the item index for bag - for _ in range(24): - if self.read_m("wCurrentMenuItem") == 2: - break - self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) - self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) - self.pyboy.tick(self.action_freq, render=True) - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) - self.pyboy.tick(self.action_freq, render=True) - - # Scroll until you get to pokeflute - # We'll do this by scrolling all the way up then all the way down - # There is a faster way to do it, but this is easier to think about - # Could also set the menu index manually, but there are like 4 variables - # for that - for _ in range(20): - self.pyboy.send_input(WindowEvent.PRESS_ARROW_UP) - self.pyboy.send_input(WindowEvent.RELEASE_ARROW_UP, delay=8) - self.pyboy.tick(self.action_freq, render=True) + _, wMissableObjectFlags = self.pyboy.symbol_lookup("wMissableObjectFlags") + _, wMissableObjectList = self.pyboy.symbol_lookup("wMissableObjectList") + missable_objects_list = self.pyboy.memory[ + wMissableObjectList : wMissableObjectList + 34 + ] + missable_objects_list = missable_objects_list[: missable_objects_list.index(0xFF)] + missable_objects_sprite_ids = missable_objects_list[::2] + missable_objects_flags = missable_objects_list[1::2] + for sprite_id in missable_objects_sprite_ids: + picture_id = self.read_m(f"wSprite{sprite_id:02}StateData1PictureID") + flags_bit = missable_objects_flags[missable_objects_sprite_ids.index(sprite_id)] + flags_byte = flags_bit // 8 + flag_bit = flags_bit % 8 + flag_byte_value = self.read_bit(wMissableObjectFlags + flags_byte, flag_bit) + if picture_id == 0x43 and not flag_byte_value: + # open start menu + self.pyboy.button("START", 8) + self.pyboy.tick(self.action_freq, render=True) + # scroll to bag + # 2 is the item index for bag + for _ in range(24): + if self.read_m("wCurrentMenuItem") == 2: + break + self.pyboy.button("DOWN", 8) + self.pyboy.tick(self.action_freq, render=True) + self.pyboy.button("A", 8) + self.pyboy.tick(self.action_freq, render=True) + + # Scroll until you get to pokeflute + # We'll do this by scrolling all the way up then all the way down + # There is a faster way to do it, but this is easier to think about + # Could also set the menu index manually, but there are like 4 variables + # for that + for _ in range(20): + self.pyboy.button("UP", 8) + self.pyboy.tick(self.action_freq, render=True) + + for _ in range(21): + if ( + self.read_m("wCurrentMenuItem") + self.read_m("wListScrollOffset") + == pokeflute_index + ): + break + self.pyboy.button("DOWN", 8) + self.pyboy.tick(self.action_freq, render=True) + + # press a bunch of times + for _ in range(5): + self.pyboy.button("A", 8) + self.pyboy.tick(4 * self.action_freq, render=True) - for _ in range(21): - if ( - self.read_m("wCurrentMenuItem") + self.read_m("wListScrollOffset") - == pokeflute_index - ): break - self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) - self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) - self.pyboy.tick(self.action_freq, render=True) - - # press a bunch of times - for _ in range(5): - self.pyboy.send_input(WindowEvent.PRESS_BUTTON_A) - self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) - self.pyboy.tick(4 * self.action_freq, render=True) def cut_if_next(self): # https://github.com/pret/pokered/blob/d38cf5281a902b4bd167a46a7c9fd9db436484a7/constants/tileset_constants.asm#L11C8-L11C11 - in_erika_gym = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMapTileset")[1]] == 7 - in_overworld = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMapTileset")[1]] == 0 + in_erika_gym = self.read_m("wCurMapTileset") == Tilesets.GYM.value + in_overworld = self.read_m("wCurMapTileset") == Tilesets.OVERWORLD.value if in_erika_gym or in_overworld: _, wTileMap = self.pyboy.symbol_lookup("wTileMap") tileMap = self.pyboy.memory[wTileMap : wTileMap + 20 * 18] @@ -795,7 +807,7 @@ def surf_if_attempt(self, action: WindowEvent): ): return - in_overworld = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMapTileset")[1]] == 0 + in_overworld = self.read_m("wCurMapTileset") == Tilesets.OVERWORLD.value if in_overworld: _, wTileMap = self.pyboy.symbol_lookup("wTileMap") tileMap = self.pyboy.memory[wTileMap : wTileMap + 20 * 18] @@ -895,6 +907,43 @@ def surf_if_attempt(self, action: WindowEvent): self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) self.pyboy.tick(4 * self.action_freq, render=True) + def solve_strength_puzzle(self): + in_cavern = self.read_m("wCurMapTileset") == Tilesets.CAVERN.value + if in_cavern: + _, wMissableObjectFlags = self.pyboy.symbol_lookup("wMissableObjectFlags") + _, wMissableObjectList = self.pyboy.symbol_lookup("wMissableObjectList") + missable_objects_list = self.pyboy.memory[ + wMissableObjectList : wMissableObjectList + 34 + ] + missable_objects_list = missable_objects_list[: missable_objects_list.index(0xFF)] + missable_objects_sprite_ids = missable_objects_list[::2] + missable_objects_flags = missable_objects_list[1::2] + + for sprite_id in missable_objects_sprite_ids: + flags_bit = missable_objects_flags[missable_objects_sprite_ids.index(sprite_id)] + flags_byte = flags_bit // 8 + flag_bit = flags_bit % 8 + flag_byte_value = self.read_bit(wMissableObjectFlags + flags_byte, flag_bit) + if not flag_byte_value: # True if missable + picture_id = self.read_m(f"wSprite{sprite_id:02}StateData1PictureID") + mapY = self.read_m(f"wSprite{sprite_id:02}StateData2MapY") + mapX = self.read_m(f"wSprite{sprite_id:02}StateData2MapX") + if solution := STRENGTH_SOLUTIONS.get( + (picture_id, mapY, mapX) + self.get_game_coords(), [] + ): + if not self.disable_wild_encounters: + self.setup_disable_wild_encounters() + # Activate strength + _, wd728 = self.pyboy.symbol_lookup("wd728") + self.pyboy.memory[wd728] |= 0b0000_0001 + # Perform solution + for button in solution: + self.pyboy.button(button, 8) + self.pyboy.tick(24, render=True) + if not self.disable_wild_encounters: + self.setup_enable_wild_ecounters() + break + def sign_hook(self, *args, **kwargs): sign_id = self.pyboy.memory[self.pyboy.symbol_lookup("hSpriteIndexOrTextID")[1]] map_id = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMap")[1]] diff --git a/pyboy_states/seafoam.state b/pyboy_states/seafoam.state new file mode 100644 index 0000000000000000000000000000000000000000..cbe4aef6cd707265d4aaa502b3d2da7385ae79fa GIT binary patch literal 165650 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%0A$frzdjGyJk_vHP`4# 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 literal 0 HcmV?d00001 diff --git a/pyboy_states/seafoam_1f_right.state b/pyboy_states/seafoam_1f_right.state new file mode 100644 index 0000000000000000000000000000000000000000..d26f2e13361cfe2b9c16ffa0654ce784570fff53 GIT binary patch literal 165650 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"No`0&a!REm3{|n1AeKOJ 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 Date: Sat, 15 Jun 2024 01:45:51 -0400 Subject: [PATCH 48/68] Add victory road strength puzzle solutions --- config.yaml | 2 +- pokemonred_puffer/data/strength_puzzles.py | 302 +++++++++++++++++++++ pokemonred_puffer/environment.py | 46 +++- pyboy_states/victory_road.state | Bin 0 -> 165650 bytes pyboy_states/victory_road_2.state | Bin 0 -> 165650 bytes pyboy_states/victory_road_3.state | Bin 0 -> 165650 bytes pyboy_states/victory_road_4.state | Bin 0 -> 165650 bytes pyboy_states/victory_road_5.state | Bin 0 -> 165650 bytes 8 files changed, 346 insertions(+), 4 deletions(-) create mode 100644 pyboy_states/victory_road.state create mode 100644 pyboy_states/victory_road_2.state create mode 100644 pyboy_states/victory_road_3.state create mode 100644 pyboy_states/victory_road_4.state create mode 100644 pyboy_states/victory_road_5.state diff --git a/config.yaml b/config.yaml index 79bda46..12af6fd 100644 --- a/config.yaml +++ b/config.yaml @@ -7,7 +7,7 @@ debug: env: headless: False stream_wrapper: False - init_state: pokeflute + init_state: victory_road_5 max_steps: 1_000_000 disable_wild_encounters: True disable_ai_actions: True diff --git a/pokemonred_puffer/data/strength_puzzles.py b/pokemonred_puffer/data/strength_puzzles.py index af0c73f..5d553c8 100644 --- a/pokemonred_puffer/data/strength_puzzles.py +++ b/pokemonred_puffer/data/strength_puzzles.py @@ -1,4 +1,9 @@ STRENGTH_SOLUTIONS = {} + +################### +# SEAFOAM ISLANDS # +################### + # Seafoam 1F Left STRENGTH_SOLUTIONS[(63, 14, 22, 18, 11, 192)] = [ "UP", @@ -94,3 +99,300 @@ # We skip seafoam b3 since that is for articuno # TODO: Articuno + +################ +# VICTORY ROAD # +################ + +# 1F Switch 1 +STRENGTH_SOLUTIONS[(63, 19, 9, 5, 14, 108)] = [ + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "LEFT", + "DOWN", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "DOWN", + "RIGHT", + "RIGHT", + "UP", + "UP", + "UP", + "UP", + "UP", + "UP", + "LEFT", + "UP", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "DOWN", + "RIGHT", + "UP", + "UP", + "UP", + "UP", + "UP", + "LEFT", + "LEFT", + "UP", + "UP", + "UP", + "UP", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "UP", + "RIGHT", + "DOWN", +] + +STRENGTH_SOLUTIONS[(63, 19, 9, 4, 15, 108)] = ["UP", "RIGHT"] + STRENGTH_SOLUTIONS[ + (63, 19, 9, 5, 14, 108) +] +STRENGTH_SOLUTIONS[(63, 19, 9, 5, 16, 108)] = ["LEFT", "UP"] + STRENGTH_SOLUTIONS[ + (63, 19, 9, 4, 15, 108) +] + +# 2F Switch 1 +STRENGTH_SOLUTIONS[(63, 18, 8, 5, 14, 194)] = [ + "LEFT", + "LEFT", + "LEFT", + "UP", + "LEFT", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "RIGHT", + "DOWN", + "LEFT", + "LEFT", + "LEFT", + "LEFT", +] + +STRENGTH_SOLUTIONS[(63, 18, 8, 4, 13, 194)] = ["RIGHT", "DOWN"] + STRENGTH_SOLUTIONS[ + (63, 18, 8, 5, 14, 194) +] +STRENGTH_SOLUTIONS[(63, 18, 8, 3, 14, 194)] = ["UP", "RIGHT"] + STRENGTH_SOLUTIONS[ + (63, 18, 8, 4, 13, 194) +] +STRENGTH_SOLUTIONS[(63, 18, 8, 4, 15, 194)] = ["LEFT", "UP"] + STRENGTH_SOLUTIONS[ + (63, 18, 8, 3, 14, 194) +] + +# 3F Switch 3 +STRENGTH_SOLUTIONS[(63, 19, 26, 22, 4, 198)] = [ + "UP", + "UP", + "RIGHT", + "UP", + "UP", + "LEFT", + "DOWN", + "DOWN", + "DOWN", + "LEFT", + "LEFT", + "UP", + "UP", + "RIGHT", + "UP", + "UP", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "UP", + "LEFT", + "DOWN", + "DOWN", + "RIGHT", + "DOWN", + "DOWN", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "UP", + "UP", + "LEFT", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "LEFT", + "DOWN", + "RIGHT", + "RIGHT", +] + +STRENGTH_SOLUTIONS[(63, 19, 26, 23, 3, 198)] = ["DOWN", "LEFT"] + STRENGTH_SOLUTIONS[ + (63, 19, 26, 22, 4, 198) +] +STRENGTH_SOLUTIONS[(63, 19, 26, 22, 2, 198)] = ["RIGHT", "DOWN"] + STRENGTH_SOLUTIONS[ + (63, 19, 26, 23, 3, 198) +] +STRENGTH_SOLUTIONS[(63, 19, 26, 21, 3, 198)] = ["UP", "RIGHT"] + STRENGTH_SOLUTIONS[ + (63, 19, 26, 22, 2, 198) +] + +# 3F Boulder in hole +STRENGTH_SOLUTIONS[(63, 16, 17, 21, 15, 198)] = ["RIGHT", "RIGHT", "RIGHT"] +STRENGTH_SOLUTIONS[(63, 16, 17, 22, 16, 198)] = ["LEFT", "UP"] + STRENGTH_SOLUTIONS[ + (63, 16, 17, 21, 15, 198) +] +STRENGTH_SOLUTIONS[(63, 16, 17, 22, 14, 198)] = ["LEFT", "DOWN"] + STRENGTH_SOLUTIONS[ + (63, 16, 17, 21, 15, 198) +] + + +# 2F final switch +STRENGTH_SOLUTIONS[(63, 20, 27, 24, 16, 194)] = [ + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", + "LEFT", +] + +STRENGTH_SOLUTIONS[(63, 20, 27, 23, 17, 194)] = ["RIGHT", "UP"] + STRENGTH_SOLUTIONS[ + (63, 20, 27, 24, 16, 194) +] +STRENGTH_SOLUTIONS[(63, 20, 27, 22, 16, 194)] = ["DOWN", "RIGHT"] + STRENGTH_SOLUTIONS[ + (63, 20, 27, 23, 17, 194) +] diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index a6b35a9..0c8a0a8 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -585,7 +585,8 @@ def run_action_on_emulator(self, action): if self.read_bit(0xD857, 0): if self.auto_teach_strength and not self.check_if_party_has_hm(0x46): self.teach_hm(0x46, 15, STRENGTH_SPECIES_IDS) - self.solve_strength_puzzle() + self.solve_missable_strength_puzzle() + self.solve_switch_strength_puzzle() if self.read_bit(0xD76C, 0) and self.auto_pokeflute: self.use_pokeflute() @@ -907,7 +908,7 @@ def surf_if_attempt(self, action: WindowEvent): self.pyboy.send_input(WindowEvent.RELEASE_BUTTON_A, delay=8) self.pyboy.tick(4 * self.action_freq, render=True) - def solve_strength_puzzle(self): + def solve_missable_strength_puzzle(self): in_cavern = self.read_m("wCurMapTileset") == Tilesets.CAVERN.value if in_cavern: _, wMissableObjectFlags = self.pyboy.symbol_lookup("wMissableObjectFlags") @@ -928,6 +929,7 @@ def solve_strength_puzzle(self): picture_id = self.read_m(f"wSprite{sprite_id:02}StateData1PictureID") mapY = self.read_m(f"wSprite{sprite_id:02}StateData2MapY") mapX = self.read_m(f"wSprite{sprite_id:02}StateData2MapX") + print((picture_id, mapY, mapX) + self.get_game_coords()) if solution := STRENGTH_SOLUTIONS.get( (picture_id, mapY, mapX) + self.get_game_coords(), [] ): @@ -937,13 +939,51 @@ def solve_strength_puzzle(self): _, wd728 = self.pyboy.symbol_lookup("wd728") self.pyboy.memory[wd728] |= 0b0000_0001 # Perform solution + current_repel_steps = self.read_m("wRepelRemainingSteps") for button in solution: + self.pyboy.memory[ + self.pyboy.symbol_lookup("wRepelRemainingSteps")[1] + ] = 0xFF self.pyboy.button(button, 8) - self.pyboy.tick(24, render=True) + self.pyboy.tick(self.action_freq * 1.5, render=True) + self.pyboy.memory[self.pyboy.symbol_lookup("wRepelRemainingSteps")[1]] = ( + current_repel_steps + ) if not self.disable_wild_encounters: self.setup_enable_wild_ecounters() break + def solve_switch_strength_puzzle(self): + in_cavern = self.read_m("wCurMapTileset") == Tilesets.CAVERN.value + if in_cavern: + for sprite_id in range(1, self.read_m("wNumSprites") + 1): + picture_id = self.read_m(f"wSprite{sprite_id:02}StateData1PictureID") + mapY = self.read_m(f"wSprite{sprite_id:02}StateData2MapY") + mapX = self.read_m(f"wSprite{sprite_id:02}StateData2MapX") + print((picture_id, mapY, mapX) + self.get_game_coords()) + if solution := STRENGTH_SOLUTIONS.get( + (picture_id, mapY, mapX) + self.get_game_coords(), [] + ): + if not self.disable_wild_encounters: + self.setup_disable_wild_encounters() + # Activate strength + _, wd728 = self.pyboy.symbol_lookup("wd728") + self.pyboy.memory[wd728] |= 0b0000_0001 + # Perform solution + current_repel_steps = self.read_m("wRepelRemainingSteps") + for button in solution: + self.pyboy.memory[self.pyboy.symbol_lookup("wRepelRemainingSteps")[1]] = ( + 0xFF + ) + self.pyboy.button(button, 8) + self.pyboy.tick(self.action_freq * 2, render=True) + self.pyboy.memory[self.pyboy.symbol_lookup("wRepelRemainingSteps")[1]] = ( + current_repel_steps + ) + if not self.disable_wild_encounters: + self.setup_enable_wild_ecounters() + break + def sign_hook(self, *args, **kwargs): sign_id = self.pyboy.memory[self.pyboy.symbol_lookup("hSpriteIndexOrTextID")[1]] map_id = self.pyboy.memory[self.pyboy.symbol_lookup("wCurMap")[1]] diff --git a/pyboy_states/victory_road.state b/pyboy_states/victory_road.state new file mode 100644 index 0000000000000000000000000000000000000000..7450277c74dc50dafd7b0ae3af78e3f656099f9f GIT binary patch literal 165650 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 literal 0 HcmV?d00001 diff --git a/pyboy_states/victory_road_2.state b/pyboy_states/victory_road_2.state new file mode 100644 index 0000000000000000000000000000000000000000..536965271bc113f9eca6a4b14f192d517a92f5b1 GIT binary patch literal 165650 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 literal 0 HcmV?d00001 diff --git a/pyboy_states/victory_road_3.state b/pyboy_states/victory_road_3.state new file mode 100644 index 0000000000000000000000000000000000000000..aec8d3258fb7259a29c3e667439fd0deeb4d9e58 GIT binary patch literal 165650 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 R$$-g#$$-f~`Wf(g|341R0hj;) literal 0 HcmV?d00001 diff --git a/pyboy_states/victory_road_4.state b/pyboy_states/victory_road_4.state new file mode 100644 index 0000000000000000000000000000000000000000..79c280fb54e7aef0dd26fccda9c0dbc7f4994aff GIT binary patch literal 165650 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�!o8Nlngc) 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 Oz+}K=ApZ;m0{ Date: Sat, 15 Jun 2024 01:52:12 -0400 Subject: [PATCH 49/68] Fix surf in plateau --- config.yaml | 2 +- pokemonred_puffer/environment.py | 32 ++++++-------------------------- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/config.yaml b/config.yaml index 12af6fd..b11b1ad 100644 --- a/config.yaml +++ b/config.yaml @@ -7,7 +7,7 @@ debug: env: headless: False stream_wrapper: False - init_state: victory_road_5 + init_state: victory_road max_steps: 1_000_000 disable_wild_encounters: True disable_ai_actions: True diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 0c8a0a8..9c055e4 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -809,7 +809,8 @@ def surf_if_attempt(self, action: WindowEvent): return in_overworld = self.read_m("wCurMapTileset") == Tilesets.OVERWORLD.value - if in_overworld: + in_plateau = self.read_m("wCurMapTileset") == Tilesets.PLATEAU.value + if in_overworld or in_plateau: _, wTileMap = self.pyboy.symbol_lookup("wTileMap") tileMap = self.pyboy.memory[wTileMap : wTileMap + 20 * 18] tileMap = np.array(tileMap, dtype=np.uint8) @@ -828,30 +829,10 @@ def surf_if_attempt(self, action: WindowEvent): direction = self.read_m("wSpritePlayerStateData1FacingDirection") if not ( - ( - direction == 0x4 - and action == WindowEvent.PRESS_ARROW_UP - and in_overworld - and 0x14 in up - ) - or ( - direction == 0x0 - and action == WindowEvent.PRESS_ARROW_DOWN - and in_overworld - and 0x14 in down - ) - or ( - direction == 0x8 - and action == WindowEvent.PRESS_ARROW_LEFT - and in_overworld - and 0x14 in left - ) - or ( - direction == 0xC - and action == WindowEvent.PRESS_ARROW_RIGHT - and in_overworld - and 0x14 in right - ) + (direction == 0x4 and action == WindowEvent.PRESS_ARROW_UP and 0x14 in up) + or (direction == 0x0 and action == WindowEvent.PRESS_ARROW_DOWN and 0x14 in down) + or (direction == 0x8 and action == WindowEvent.PRESS_ARROW_LEFT and 0x14 in left) + or (direction == 0xC and action == WindowEvent.PRESS_ARROW_RIGHT and 0x14 in right) ): return @@ -960,7 +941,6 @@ def solve_switch_strength_puzzle(self): picture_id = self.read_m(f"wSprite{sprite_id:02}StateData1PictureID") mapY = self.read_m(f"wSprite{sprite_id:02}StateData2MapY") mapX = self.read_m(f"wSprite{sprite_id:02}StateData2MapX") - print((picture_id, mapY, mapX) + self.get_game_coords()) if solution := STRENGTH_SOLUTIONS.get( (picture_id, mapY, mapX) + self.get_game_coords(), [] ): From 2873489fe5b7f2e69078c9a0aae8bb2a714d5b8d Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 15 Jun 2024 01:54:50 -0400 Subject: [PATCH 50/68] temporarily comment out global map obs --- pokemonred_puffer/environment.py | 8 ++++---- .../policies/multi_convolutional.py | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 9c055e4..4fde0f2 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -143,9 +143,9 @@ def __init__(self, env_config: pufferlib.namespace): "visited_mask": spaces.Box( low=0, high=255, shape=self.screen_output_shape, dtype=np.uint8 ), - "global_map": spaces.Box( - low=0, high=255, shape=self.screen_output_shape, dtype=np.uint8 - ), + # "global_map": spaces.Box( + # low=0, high=255, shape=self.screen_output_shape, dtype=np.uint8 + # ), # Discrete is more apt, but pufferlib is slower at processing Discrete "direction": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), "blackout_map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), @@ -464,7 +464,7 @@ def render(self): return { "screen": game_pixels_render, "visited_mask": visited_mask, - "global_map": global_map, + # "global_map": global_map, } def _get_obs(self): diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index 87ad8a7..6f412ab 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -80,7 +80,7 @@ def encode_observations(self, observations): screen = observations["screen"] visited_mask = observations["visited_mask"] - global_map = observations["global_map"] + # global_map = observations["global_map"] restored_shape = (screen.shape[0], screen.shape[1], screen.shape[2] * 4, screen.shape[3]) if self.two_bit: @@ -96,18 +96,19 @@ def encode_observations(self, observations): .flatten() .int(), ).reshape(restored_shape) - global_map = torch.index_select( - self.linear_buckets, - 0, - ((global_map.reshape((-1, 1)) & self.unpack_mask) >> self.unpack_shift) - .flatten() - .int(), - ).reshape(restored_shape) + # global_map = torch.index_select( + # self.linear_buckets, + # 0, + # ((global_map.reshape((-1, 1)) & self.unpack_mask) >> self.unpack_shift) + # .flatten() + # .int(), + # ).reshape(restored_shape) badges = self.badge_buffer <= observations["badges"] map_id = self.map_embeddings(observations["map_id"].long()) blackout_map_id = self.map_embeddings(observations["blackout_map_id"].long()) - image_observation = torch.cat((screen, visited_mask, global_map), dim=-1) + # image_observation = torch.cat((screen, visited_mask, global_map), dim=-1) + image_observation = torch.cat((screen, visited_mask), dim=-1) if self.channels_last: image_observation = image_observation.permute(0, 3, 1, 2) if self.downsample > 1: From cb934fcff4488f626586e36789d6c0bd65a16b13 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 15 Jun 2024 01:57:25 -0400 Subject: [PATCH 51/68] solve strength puzzles config --- config.yaml | 1 + pokemonred_puffer/environment.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config.yaml b/config.yaml index b11b1ad..a1790e3 100644 --- a/config.yaml +++ b/config.yaml @@ -57,6 +57,7 @@ env: auto_use_surf: True auto_teach_surf: True auto_teach_strength: True + auto_solve_strength_puzzles: True auto_remove_all_nonuseful_items: True auto_pokeflute: True infinite_money: True diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 4fde0f2..bf13f17 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -98,6 +98,7 @@ def __init__(self, env_config: pufferlib.namespace): self.auto_teach_strength = env_config.auto_teach_strength self.auto_use_cut = env_config.auto_use_cut self.auto_use_surf = env_config.auto_use_surf + self.auto_solve_strength_puzzles = env_config.auto_solve_strength_puzzles self.auto_remove_all_nonuseful_items = env_config.auto_remove_all_nonuseful_items self.auto_pokeflute = env_config.auto_pokeflute self.infinite_money = env_config.infinite_money @@ -585,8 +586,9 @@ def run_action_on_emulator(self, action): if self.read_bit(0xD857, 0): if self.auto_teach_strength and not self.check_if_party_has_hm(0x46): self.teach_hm(0x46, 15, STRENGTH_SPECIES_IDS) - self.solve_missable_strength_puzzle() - self.solve_switch_strength_puzzle() + if self.auto_solve_strength_puzzles: + self.solve_missable_strength_puzzle() + self.solve_switch_strength_puzzle() if self.read_bit(0xD76C, 0) and self.auto_pokeflute: self.use_pokeflute() From b38aab4fdd0bafcf64216aa597347f0fe3d07836 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 15 Jun 2024 11:28:26 -0400 Subject: [PATCH 52/68] Migrate most dictionaries to enums --- config.yaml | 2 +- pokemonred_puffer/data/field_moves.py | 14 +- pokemonred_puffer/data/items.py | 449 ++++++++++++-------------- pokemonred_puffer/data/species.py | 386 +++++++++++----------- pokemonred_puffer/data/tm_hm.py | 364 ++++++++++----------- pokemonred_puffer/environment.py | 55 ++-- 6 files changed, 615 insertions(+), 655 deletions(-) diff --git a/config.yaml b/config.yaml index a1790e3..fba5eb2 100644 --- a/config.yaml +++ b/config.yaml @@ -50,7 +50,7 @@ env: two_bit: True log_frequency: 2000 auto_flash: True - disable_wild_encounters: False + disable_wild_encounters: True disable_ai_actions: False auto_teach_cut: True auto_use_cut: True diff --git a/pokemonred_puffer/data/field_moves.py b/pokemonred_puffer/data/field_moves.py index 6d33806..86d2e83 100644 --- a/pokemonred_puffer/data/field_moves.py +++ b/pokemonred_puffer/data/field_moves.py @@ -1,3 +1,13 @@ -FIELD_MOVES = ["CUT", "FLY", "SURF", "SURF", "STRENGTH", "FLASH", "DIG", "TELEPORT", "SOFTBOILED"] +from enum import Enum -FIELD_MOVES_MAP = {i + 1: v for i, v in enumerate(FIELD_MOVES)} + +class FieldMoves(Enum): + CUT = 1 + FLY = 2 + SURF = 3 + SURF_2 = 4 + STRENGTH = 5 + FLASH = 6 + DIG = 7 + TELEPORT = 8 + SOFTBOILED = 9 diff --git a/pokemonred_puffer/data/items.py b/pokemonred_puffer/data/items.py index c6deb7f..10d7a5d 100644 --- a/pokemonred_puffer/data/items.py +++ b/pokemonred_puffer/data/items.py @@ -1,256 +1,205 @@ -MAX_ITEM_CAPACITY = 20 +from enum import Enum + +MAX_ITEM_CAPACITY = 20 # Starts at 0x1 -KEY_ITEM_IDS = [ - False, # MASTER_BALL - False, # ULTRA_BALL - False, # GREAT_BALL - False, # POKE_BALL - True, # TOWN_MAP - True, # BICYCLE - True, # SURFBOARD - True, # SAFARI_BALL - True, # POKEDEX - False, # MOON_STONE - False, # ANTIDOTE - False, # BURN_HEAL - False, # ICE_HEAL - False, # AWAKENING - False, # PARLYZ_HEAL - False, # FULL_RESTORE - False, # MAX_POTION - False, # HYPER_POTION - False, # SUPER_POTION - False, # POTION - True, # BOULDERBADGE - True, # CASCADEBADGE - True, # THUNDERBADGE - True, # RAINBOWBADGE - True, # SOULBADGE - True, # MARSHBADGE - True, # VOLCANOBADGE - True, # EARTHBADGE - False, # ESCAPE_ROPE - False, # REPEL - True, # OLD_AMBER - False, # FIRE_STONE - False, # THUNDER_STONE - False, # WATER_STONE - False, # HP_UP - False, # PROTEIN - False, # IRON - False, # CARBOS - False, # CALCIUM - False, # RARE_CANDY - True, # DOME_FOSSIL - True, # HELIX_FOSSIL - True, # SECRET_KEY - True, # ITEM_2C - True, # BIKE_VOUCHER - False, # X_ACCURACY - False, # LEAF_STONE - True, # CARD_KEY - False, # NUGGET - False, # ITEM_32 - False, # POKE_DOLL - False, # FULL_HEAL - False, # REVIVE - False, # MAX_REVIVE - False, # GUARD_SPEC - False, # SUPER_REPEL - False, # MAX_REPEL - False, # DIRE_HIT - False, # COIN - False, # FRESH_WATER - False, # SODA_POP - False, # LEMONADE - True, # S_S_TICKET - True, # GOLD_TEETH - False, # X_ATTACK - False, # X_DEFEND - False, # X_SPEED - False, # X_SPECIAL - True, # COIN_CASE - True, # OAKS_PARCEL - True, # ITEMFINDER - True, # SILPH_SCOPE - True, # POKE_FLUTE - True, # LIFT_KEY - False, # EXP_ALL - True, # OLD_ROD - True, # GOOD_ROD - True, # SUPER_ROD - False, # PP_UP - False, # ETHER - False, # MAX_ETHER - False, # ELIXER - False, # MAX_ELIXER -] -# Start = 0x1 -ITEM_NAMES = [ - "MASTER_BALL", - "ULTRA_BALL", - "GREAT_BALL", - "POKE_BALL", - "TOWN_MAP", - "BICYCLE", - "SURFBOARD", - "SAFARI_BALL", - "POKEDEX", - "MOON_STONE", - "ANTIDOTE", - "BURN_HEAL", - "ICE_HEAL", - "AWAKENING", - "PARLYZ_HEAL", - "FULL_RESTORE", - "MAX_POTION", - "HYPER_POTION", - "SUPER_POTION", - "POTION", - "BOULDERBADGE", - "CASCADEBADGE", - "THUNDERBADGE", - "RAINBOWBADGE", - "SOULBADGE", - "MARSHBADGE", - "VOLCANOBADGE", - "EARTHBADGE", - "ESCAPE_ROPE", - "REPEL", - "OLD_AMBER", - "FIRE_STONE", - "THUNDER_STONE", - "WATER_STONE", - "HP_UP", - "PROTEIN", - "IRON", - "CARBOS", - "CALCIUM", - "RARE_CANDY", - "DOME_FOSSIL", - "HELIX_FOSSIL", - "SECRET_KEY", - "UNUSED_ITEM", - "BIKE_VOUCHER", - "X_ACCURACY", - "LEAF_STONE", - "CARD_KEY", - "NUGGET", - "PP_UP_2", - "POKE_DOLL", - "FULL_HEAL", - "REVIVE", - "MAX_REVIVE", - "GUARD_SPEC", - "SUPER_REPEL", - "MAX_REPEL", - "DIRE_HIT", - "COIN", - "FRESH_WATER", - "SODA_POP", - "LEMONADE", - "S_S_TICKET", - "GOLD_TEETH", - "X_ATTACK", - "X_DEFEND", - "X_SPEED", - "X_SPECIAL", - "COIN_CASE", - "OAKS_PARCEL", - "ITEMFINDER", - "SILPH_SCOPE", - "POKE_FLUTE", - "LIFT_KEY", - "EXP_ALL", - "OLD_ROD", - "GOOD_ROD", - "SUPER_ROD", - "PP_UP", - "ETHER", - "MAX_ETHER", - "ELIXER", - "MAX_ELIXER", - "FLOOR_B2F", - "FLOOR_B1F", - "FLOOR_1F", - "FLOOR_2F", - "FLOOR_3F", - "FLOOR_4F", - "FLOOR_5F", - "FLOOR_6F", - "FLOOR_7F", - "FLOOR_8F", - "FLOOR_9F", - "FLOOR_10F", - "FLOOR_11F", - "FLOOR_B4F", -] -# Start = 0xC4 -TM_HM_ITEM_IDS = [ - "HM_01", - "HM_02", - "HM_03", - "HM_04", - "HM_05", - "TM_01", - "TM_02", - "TM_03", - "TM_04", - "TM_05", - "TM_06", - "TM_07", - "TM_08", - "TM_09", - "TM_10", - "TM_11", - "TM_12", - "TM_13", - "TM_14", - "TM_15", - "TM_16", - "TM_17", - "TM_18", - "TM_19", - "TM_20", - "TM_21", - "TM_22", - "TM_23", - "TM_24", - "TM_25", - "TM_26", - "TM_27", - "TM_28", - "TM_29", - "TM_30", - "TM_31", - "TM_32", - "TM_33", - "TM_34", - "TM_35", - "TM_36", - "TM_37", - "TM_38", - "TM_39", - "TM_40", - "TM_41", - "TM_42", - "TM_43", - "TM_44", - "TM_45", - "TM_46", - "TM_47", - "TM_48", - "TM_49", - "TM_50", -] +class Items(Enum): + MASTER_BALL = 0x01 + ULTRA_BALL = 0x02 + GREAT_BALL = 0x03 + POKE_BALL = 0x04 + TOWN_MAP = 0x05 + BICYCLE = 0x06 + SURFBOARD = 0x07 # + SAFARI_BALL = 0x08 + POKEDEX = 0x09 + MOON_STONE = 0x0A + ANTIDOTE = 0x0B + BURN_HEAL = 0x0C + ICE_HEAL = 0x0D + AWAKENING = 0x0E + PARLYZ_HEAL = 0x0F + FULL_RESTORE = 0x10 + MAX_POTION = 0x11 + HYPER_POTION = 0x12 + SUPER_POTION = 0x13 + POTION = 0x14 + BOULDERBADGE = 0x15 + CASCADEBADGE = 0x16 + SAFARI_BAIT = 0x15 # overload + SAFARI_ROCK = 0x16 # overload + THUNDERBADGE = 0x17 + RAINBOWBADGE = 0x18 + SOULBADGE = 0x19 + MARSHBADGE = 0x1A + VOLCANOBADGE = 0x1B + EARTHBADGE = 0x1C + ESCAPE_ROPE = 0x1D + REPEL = 0x1E + OLD_AMBER = 0x1F + FIRE_STONE = 0x20 + THUNDER_STONE = 0x21 + WATER_STONE = 0x22 + HP_UP = 0x23 + PROTEIN = 0x24 + IRON = 0x25 + CARBOS = 0x26 + CALCIUM = 0x27 + RARE_CANDY = 0x28 + DOME_FOSSIL = 0x29 + HELIX_FOSSIL = 0x2A + SECRET_KEY = 0x2B + UNUSED_ITEM = 0x2C # "?????" + BIKE_VOUCHER = 0x2D + X_ACCURACY = 0x2E + LEAF_STONE = 0x2F + CARD_KEY = 0x30 + NUGGET = 0x31 + PP_UP_2 = 0x32 + POKE_DOLL = 0x33 + FULL_HEAL = 0x34 + REVIVE = 0x35 + MAX_REVIVE = 0x36 + GUARD_SPEC = 0x37 + SUPER_REPEL = 0x38 + MAX_REPEL = 0x39 + DIRE_HIT = 0x3A + COIN = 0x3B + FRESH_WATER = 0x3C + SODA_POP = 0x3D + LEMONADE = 0x3E + S_S_TICKET = 0x3F + GOLD_TEETH = 0x40 + X_ATTACK = 0x41 + X_DEFEND = 0x42 + X_SPEED = 0x43 + X_SPECIAL = 0x44 + COIN_CASE = 0x45 + OAKS_PARCEL = 0x46 + ITEMFINDER = 0x47 + SILPH_SCOPE = 0x48 + POKE_FLUTE = 0x49 + LIFT_KEY = 0x4A + EXP_ALL = 0x4B + OLD_ROD = 0x4C + GOOD_ROD = 0x4D + SUPER_ROD = 0x4E + PP_UP = 0x4F + ETHER = 0x50 + MAX_ETHER = 0x51 + ELIXER = 0x52 + MAX_ELIXER = 0x53 + FLOOR_B2F = 0x54 + FLOOR_B1F = 0x55 + FLOOR_1F = 0x56 + FLOOR_2F = 0x57 + FLOOR_3F = 0x58 + FLOOR_4F = 0x59 + FLOOR_5F = 0x5A + FLOOR_6F = 0x5B + FLOOR_7F = 0x5C + FLOOR_8F = 0x5D + FLOOR_9F = 0x5E + FLOOR_10F = 0x5F + FLOOR_11F = 0x60 + FLOOR_B4F = 0x61 + HM_01 = 0xC4 + HM_02 = 0xC5 + HM_03 = 0xC6 + HM_04 = 0xC7 + HM_05 = 0xC8 + TM_01 = 0xC9 + TM_02 = 0xCA + TM_03 = 0xCB + TM_04 = 0xCC + TM_05 = 0xCD + TM_06 = 0xCE + TM_07 = 0xCF + TM_08 = 0xD0 + TM_09 = 0xD1 + TM_10 = 0xD2 + TM_11 = 0xD3 + TM_12 = 0xD4 + TM_13 = 0xD5 + TM_14 = 0xD6 + TM_15 = 0xD7 + TM_16 = 0xD8 + TM_17 = 0xD9 + TM_18 = 0xDA + TM_19 = 0xDB + TM_20 = 0xDC + TM_21 = 0xDD + TM_22 = 0xDE + TM_23 = 0xDF + TM_24 = 0xE0 + TM_25 = 0xE1 + TM_26 = 0xE2 + TM_27 = 0xE3 + TM_28 = 0xE4 + TM_29 = 0xE5 + TM_30 = 0xE6 + TM_31 = 0xE7 + TM_32 = 0xE8 + TM_33 = 0xE9 + TM_34 = 0xEA + TM_35 = 0xEB + TM_36 = 0xEC + TM_37 = 0xED + TM_38 = 0xEE + TM_39 = 0xEF + TM_40 = 0xF0 + TM_41 = 0xF1 + TM_42 = 0xF2 + TM_43 = 0xF3 + TM_44 = 0xF4 + TM_45 = 0xF5 + TM_46 = 0xF6 + TM_47 = 0xF7 + TM_48 = 0xF8 + TM_49 = 0xF9 + TM_50 = 0xFA + -HM_ITEM_IDS = {0xC4, 0xC5, 0xC6, 0xC7, 0xC8} +KEY_ITEM_IDS = { + Items.TOWN_MAP.value, + Items.BICYCLE.value, + Items.SURFBOARD.value, + Items.SAFARI_BALL.value, + Items.POKEDEX.value, + Items.BOULDERBADGE.value, + Items.CASCADEBADGE.value, + Items.THUNDERBADGE.value, + Items.RAINBOWBADGE.value, + Items.SOULBADGE.value, + Items.MARSHBADGE.value, + Items.VOLCANOBADGE.value, + Items.EARTHBADGE.value, + Items.OLD_AMBER.value, + Items.DOME_FOSSIL.value, + Items.HELIX_FOSSIL.value, + Items.SECRET_KEY.value, + Items.ITEM_2C.value, + Items.BIKE_VOUCHER.value, + Items.CARD_KEY.value, + Items.S_S_TICKET.value, + Items.GOLD_TEETH.value, + Items.COIN_CASE.value, + Items.OAKS_PARCEL.value, + Items.ITEMFINDER.value, + Items.SILPH_SCOPE.value, + Items.POKE_FLUTE.value, + Items.LIFT_KEY.value, + Items.OLD_ROD.value, + Items.GOOD_ROD.value, + Items.SUPER_ROD.value, +} -ITEM_NAME_TO_ID = ( - {v: i + 0x1 for i, v in enumerate(ITEM_NAMES)} - | {v: i + 0xC4 for i, v in enumerate(TM_HM_ITEM_IDS)} - | {"SAFARI_BAIT": 0x15, "SAFARI_ROCK": 0x16} -) +HM_ITEM_IDS = { + Items.HM_01.value, + Items.HM_02.value, + Items.HM_03.value, + Items.HM_04.value, + Items.HM_05.value, +} diff --git a/pokemonred_puffer/data/species.py b/pokemonred_puffer/data/species.py index ebb0e4c..8704e7e 100644 --- a/pokemonred_puffer/data/species.py +++ b/pokemonred_puffer/data/species.py @@ -1,192 +1,194 @@ -SPECIES_IDS = { - "RHYDON": 0x01, - "KANGASKHAN": 0x02, - "NIDORAN_M": 0x03, - "CLEFAIRY": 0x04, - "SPEAROW": 0x05, - "VOLTORB": 0x06, - "NIDOKING": 0x07, - "SLOWBRO": 0x08, - "IVYSAUR": 0x09, - "EXEGGUTOR": 0x0A, - "LICKITUNG": 0x0B, - "EXEGGCUTE": 0x0C, - "GRIMER": 0x0D, - "GENGAR": 0x0E, - "NIDORAN_F": 0x0F, - "NIDOQUEEN": 0x10, - "CUBONE": 0x11, - "RHYHORN": 0x12, - "LAPRAS": 0x13, - "ARCANINE": 0x14, - "MEW": 0x15, - "GYARADOS": 0x16, - "SHELLDER": 0x17, - "TENTACOOL": 0x18, - "GASTLY": 0x19, - "SCYTHER": 0x1A, - "STARYU": 0x1B, - "BLASTOISE": 0x1C, - "PINSIR": 0x1D, - "TANGELA": 0x1E, - "MISSINGNO_1F": 0x1F, - "MISSINGNO_20": 0x20, - "GROWLITHE": 0x21, - "ONIX": 0x22, - "FEAROW": 0x23, - "PIDGEY": 0x24, - "SLOWPOKE": 0x25, - "KADABRA": 0x26, - "GRAVELER": 0x27, - "CHANSEY": 0x28, - "MACHOKE": 0x29, - "MR_MIME": 0x2A, - "HITMONLEE": 0x2B, - "HITMONCHAN": 0x2C, - "ARBOK": 0x2D, - "PARASECT": 0x2E, - "PSYDUCK": 0x2F, - "DROWZEE": 0x30, - "GOLEM": 0x31, - "MISSINGNO_32": 0x32, - "MAGMAR": 0x33, - "MISSINGNO_34": 0x34, - "ELECTABUZZ": 0x35, - "MAGNETON": 0x36, - "KOFFING": 0x37, - "MISSINGNO_38": 0x38, - "MANKEY": 0x39, - "SEEL": 0x3A, - "DIGLETT": 0x3B, - "TAUROS": 0x3C, - "MISSINGNO_3D": 0x3D, - "MISSINGNO_3E": 0x3E, - "MISSINGNO_3F": 0x3F, - "FARFETCHD": 0x40, - "VENONAT": 0x41, - "DRAGONITE": 0x42, - "MISSINGNO_43": 0x43, - "MISSINGNO_44": 0x44, - "MISSINGNO_45": 0x45, - "DODUO": 0x46, - "POLIWAG": 0x47, - "JYNX": 0x48, - "MOLTRES": 0x49, - "ARTICUNO": 0x4A, - "ZAPDOS": 0x4B, - "DITTO": 0x4C, - "MEOWTH": 0x4D, - "KRABBY": 0x4E, - "MISSINGNO_4F": 0x4F, - "MISSINGNO_50": 0x50, - "MISSINGNO_51": 0x51, - "VULPIX": 0x52, - "NINETALES": 0x53, - "PIKACHU": 0x54, - "RAICHU": 0x55, - "MISSINGNO_56": 0x56, - "MISSINGNO_57": 0x57, - "DRATINI": 0x58, - "DRAGONAIR": 0x59, - "KABUTO": 0x5A, - "KABUTOPS": 0x5B, - "HORSEA": 0x5C, - "SEADRA": 0x5D, - "MISSINGNO_5E": 0x5E, - "MISSINGNO_5F": 0x5F, - "SANDSHREW": 0x60, - "SANDSLASH": 0x61, - "OMANYTE": 0x62, - "OMASTAR": 0x63, - "JIGGLYPUFF": 0x64, - "WIGGLYTUFF": 0x65, - "EEVEE": 0x66, - "FLAREON": 0x67, - "JOLTEON": 0x68, - "VAPOREON": 0x69, - "MACHOP": 0x6A, - "ZUBAT": 0x6B, - "EKANS": 0x6C, - "PARAS": 0x6D, - "POLIWHIRL": 0x6E, - "POLIWRATH": 0x6F, - "WEEDLE": 0x70, - "KAKUNA": 0x71, - "BEEDRILL": 0x72, - "MISSINGNO_73": 0x73, - "DODRIO": 0x74, - "PRIMEAPE": 0x75, - "DUGTRIO": 0x76, - "VENOMOTH": 0x77, - "DEWGONG": 0x78, - "MISSINGNO_79": 0x79, - "MISSINGNO_7A": 0x7A, - "CATERPIE": 0x7B, - "METAPOD": 0x7C, - "BUTTERFREE": 0x7D, - "MACHAMP": 0x7E, - "MISSINGNO_7F": 0x7F, - "GOLDUCK": 0x80, - "HYPNO": 0x81, - "GOLBAT": 0x82, - "MEWTWO": 0x83, - "SNORLAX": 0x84, - "MAGIKARP": 0x85, - "MISSINGNO_86": 0x86, - "MISSINGNO_87": 0x87, - "MUK": 0x88, - "MISSINGNO_89": 0x89, - "KINGLER": 0x8A, - "CLOYSTER": 0x8B, - "MISSINGNO_8C": 0x8C, - "ELECTRODE": 0x8D, - "CLEFABLE": 0x8E, - "WEEZING": 0x8F, - "PERSIAN": 0x90, - "MAROWAK": 0x91, - "MISSINGNO_92": 0x92, - "HAUNTER": 0x93, - "ABRA": 0x94, - "ALAKAZAM": 0x95, - "PIDGEOTTO": 0x96, - "PIDGEOT": 0x97, - "STARMIE": 0x98, - "BULBASAUR": 0x99, - "VENUSAUR": 0x9A, - "TENTACRUEL": 0x9B, - "MISSINGNO_9C": 0x9C, - "GOLDEEN": 0x9D, - "SEAKING": 0x9E, - "MISSINGNO_9F": 0x9F, - "MISSINGNO_A0": 0xA0, - "MISSINGNO_A1": 0xA1, - "MISSINGNO_A2": 0xA2, - "PONYTA": 0xA3, - "RAPIDASH": 0xA4, - "RATTATA": 0xA5, - "RATICATE": 0xA6, - "NIDORINO": 0xA7, - "NIDORINA": 0xA8, - "GEODUDE": 0xA9, - "PORYGON": 0xAA, - "AERODACTYL": 0xAB, - "MISSINGNO_AC": 0xAC, - "MAGNEMITE": 0xAD, - "MISSINGNO_AE": 0xAE, - "MISSINGNO_AF": 0xAF, - "CHARMANDER": 0xB0, - "SQUIRTLE": 0xB1, - "CHARMELEON": 0xB2, - "WARTORTLE": 0xB3, - "CHARIZARD": 0xB4, - "MISSINGNO_B5": 0xB5, - "FOSSIL_KABUTOPS": 0xB6, - "FOSSIL_AERODACTYL": 0xB7, - "MON_GHOST": 0xB8, - "ODDISH": 0xB9, - "GLOOM": 0xBA, - "VILEPLUME": 0xBB, - "BELLSPROUT": 0xBC, - "WEEPINBELL": 0xBD, - "VICTREEBEL": 0xBE, -} +from enum import Enum + + +class Species(Enum): + RHYDON = 0x01 + KANGASKHAN = 0x02 + NIDORAN_M = 0x03 + CLEFAIRY = 0x04 + SPEAROW = 0x05 + VOLTORB = 0x06 + NIDOKING = 0x07 + SLOWBRO = 0x08 + IVYSAUR = 0x09 + EXEGGUTOR = 0x0A + LICKITUNG = 0x0B + EXEGGCUTE = 0x0C + GRIMER = 0x0D + GENGAR = 0x0E + NIDORAN_F = 0x0F + NIDOQUEEN = 0x10 + CUBONE = 0x11 + RHYHORN = 0x12 + LAPRAS = 0x13 + ARCANINE = 0x14 + MEW = 0x15 + GYARADOS = 0x16 + SHELLDER = 0x17 + TENTACOOL = 0x18 + GASTLY = 0x19 + SCYTHER = 0x1A + STARYU = 0x1B + BLASTOISE = 0x1C + PINSIR = 0x1D + TANGELA = 0x1E + MISSINGNO_1F = 0x1F + MISSINGNO_20 = 0x20 + GROWLITHE = 0x21 + ONIX = 0x22 + FEAROW = 0x23 + PIDGEY = 0x24 + SLOWPOKE = 0x25 + KADABRA = 0x26 + GRAVELER = 0x27 + CHANSEY = 0x28 + MACHOKE = 0x29 + MR_MIME = 0x2A + HITMONLEE = 0x2B + HITMONCHAN = 0x2C + ARBOK = 0x2D + PARASECT = 0x2E + PSYDUCK = 0x2F + DROWZEE = 0x30 + GOLEM = 0x31 + MISSINGNO_32 = 0x32 + MAGMAR = 0x33 + MISSINGNO_34 = 0x34 + ELECTABUZZ = 0x35 + MAGNETON = 0x36 + KOFFING = 0x37 + MISSINGNO_38 = 0x38 + MANKEY = 0x39 + SEEL = 0x3A + DIGLETT = 0x3B + TAUROS = 0x3C + MISSINGNO_3D = 0x3D + MISSINGNO_3E = 0x3E + MISSINGNO_3F = 0x3F + FARFETCHD = 0x40 + VENONAT = 0x41 + DRAGONITE = 0x42 + MISSINGNO_43 = 0x43 + MISSINGNO_44 = 0x44 + MISSINGNO_45 = 0x45 + DODUO = 0x46 + POLIWAG = 0x47 + JYNX = 0x48 + MOLTRES = 0x49 + ARTICUNO = 0x4A + ZAPDOS = 0x4B + DITTO = 0x4C + MEOWTH = 0x4D + KRABBY = 0x4E + MISSINGNO_4F = 0x4F + MISSINGNO_50 = 0x50 + MISSINGNO_51 = 0x51 + VULPIX = 0x52 + NINETALES = 0x53 + PIKACHU = 0x54 + RAICHU = 0x55 + MISSINGNO_56 = 0x56 + MISSINGNO_57 = 0x57 + DRATINI = 0x58 + DRAGONAIR = 0x59 + KABUTO = 0x5A + KABUTOPS = 0x5B + HORSEA = 0x5C + SEADRA = 0x5D + MISSINGNO_5E = 0x5E + MISSINGNO_5F = 0x5F + SANDSHREW = 0x60 + SANDSLASH = 0x61 + OMANYTE = 0x62 + OMASTAR = 0x63 + JIGGLYPUFF = 0x64 + WIGGLYTUFF = 0x65 + EEVEE = 0x66 + FLAREON = 0x67 + JOLTEON = 0x68 + VAPOREON = 0x69 + MACHOP = 0x6A + ZUBAT = 0x6B + EKANS = 0x6C + PARAS = 0x6D + POLIWHIRL = 0x6E + POLIWRATH = 0x6F + WEEDLE = 0x70 + KAKUNA = 0x71 + BEEDRILL = 0x72 + MISSINGNO_73 = 0x73 + DODRIO = 0x74 + PRIMEAPE = 0x75 + DUGTRIO = 0x76 + VENOMOTH = 0x77 + DEWGONG = 0x78 + MISSINGNO_79 = 0x79 + MISSINGNO_7A = 0x7A + CATERPIE = 0x7B + METAPOD = 0x7C + BUTTERFREE = 0x7D + MACHAMP = 0x7E + MISSINGNO_7F = 0x7F + GOLDUCK = 0x80 + HYPNO = 0x81 + GOLBAT = 0x82 + MEWTWO = 0x83 + SNORLAX = 0x84 + MAGIKARP = 0x85 + MISSINGNO_86 = 0x86 + MISSINGNO_87 = 0x87 + MUK = 0x88 + MISSINGNO_89 = 0x89 + KINGLER = 0x8A + CLOYSTER = 0x8B + MISSINGNO_8C = 0x8C + ELECTRODE = 0x8D + CLEFABLE = 0x8E + WEEZING = 0x8F + PERSIAN = 0x90 + MAROWAK = 0x91 + MISSINGNO_92 = 0x92 + HAUNTER = 0x93 + ABRA = 0x94 + ALAKAZAM = 0x95 + PIDGEOTTO = 0x96 + PIDGEOT = 0x97 + STARMIE = 0x98 + BULBASAUR = 0x99 + VENUSAUR = 0x9A + TENTACRUEL = 0x9B + MISSINGNO_9C = 0x9C + GOLDEEN = 0x9D + SEAKING = 0x9E + MISSINGNO_9F = 0x9F + MISSINGNO_A0 = 0xA0 + MISSINGNO_A1 = 0xA1 + MISSINGNO_A2 = 0xA2 + PONYTA = 0xA3 + RAPIDASH = 0xA4 + RATTATA = 0xA5 + RATICATE = 0xA6 + NIDORINO = 0xA7 + NIDORINA = 0xA8 + GEODUDE = 0xA9 + PORYGON = 0xAA + AERODACTYL = 0xAB + MISSINGNO_AC = 0xAC + MAGNEMITE = 0xAD + MISSINGNO_AE = 0xAE + MISSINGNO_AF = 0xAF + CHARMANDER = 0xB0 + SQUIRTLE = 0xB1 + CHARMELEON = 0xB2 + WARTORTLE = 0xB3 + CHARIZARD = 0xB4 + MISSINGNO_B5 = 0xB5 + FOSSIL_KABUTOPS = 0xB6 + FOSSIL_AERODACTYL = 0xB7 + MON_GHOST = 0xB8 + ODDISH = 0xB9 + GLOOM = 0xBA + VILEPLUME = 0xBB + BELLSPROUT = 0xBC + WEEPINBELL = 0xBD + VICTREEBEL = 0xBE diff --git a/pokemonred_puffer/data/tm_hm.py b/pokemonred_puffer/data/tm_hm.py index df8263f..0916d26 100644 --- a/pokemonred_puffer/data/tm_hm.py +++ b/pokemonred_puffer/data/tm_hm.py @@ -1,194 +1,194 @@ -from pokemonred_puffer.data.species import SPECIES_IDS +from enum import Enum +from pokemonred_puffer.data.species import Species -TM_HM_MOVES = { - 5, # Mega punch - 0xD, # Razor wind - 0xE, # Swords dance - 0x12, # Whirlwind - 0x19, # Mega kick - 0x5C, # Toxic - 0x20, # Horn drill - 0x22, # Body slam - 0x24, # Take down - 0x26, # Double edge - 0x3D, # Bubble beam - 0x37, # Water gun - 0x3A, # Ice beam - 0x3B, # Blizzard - 0x3F, # Hyper beam - 0x06, # Pay day - 0x42, # Submission - 0x44, # Counter - 0x45, # Seismic toss - 0x63, # Rage - 0x48, # Mega drain - 0x4C, # Solar beam - 0x52, # Dragon rage - 0x55, # Thunderbolt - 0x57, # Thunder - 0x59, # Earthquake - 0x5A, # Fissure - 0x5B, # Dig - 0x5E, # Psychic - 0x64, # Teleport - 0x66, # Mimic - 0x68, # Double team - 0x73, # Reflect - 0x75, # Bide - 0x76, # Metronome - 0x78, # Selfdestruct - 0x79, # Egg bomb - 0x7E, # Fire blast - 0x81, # Swift - 0x82, # Skull bash - 0x87, # Softboiled - 0x8A, # Dream eater - 0x8F, # Sky attack - 0x9C, # Rest - 0x56, # Thunder wave - 0x95, # Psywave - 0x99, # Explosion - 0x9D, # Rock slide - 0xA1, # Tri attack - 0xA4, # Substitute - 0x0F, # Cut - 0x13, # Fly - 0x39, # Surf - 0x46, # Strength - 0x94, # Flash -} +class TmHmMoves(Enum): + MEGA_PUNCH = (0x5,) + RAZOR_WIND = 0xD + SWORDS_DANCE = 0xE + WHIRLWIND = 0x12 + MEGA_KICK = 0x19 + TOXIC = 0x5C + HORN_DRILL = 0x20 + BODY_SLAM = 0x22 + TAKE_DOWN = 0x24 + DOUBLE_EDGE = 0x26 + BUBBLE_BEAM = 0x3D + WATER_GUN = 0x37 + ICE_BEAM = 0x3A + BLIZZARD = 0x3B + HYPER_BEAM = 0x3F + PAY_DAY = 0x06 + SUBMISSION = 0x42 + COUNTER = 0x44 + SEISMIC_TOSS = 0x45 + RAGE = 0x63 + MEGA_DRAIN = 0x48 + SOLAR_BEAM = 0x4C + DRAGON_RAGE = 0x52 + THUNDERBOLT = 0x55 + THUNDER = 0x57 + EARTHQUAKE = 0x59 + FISSURE = 0x5A + DIG = 0x5B + PSYCHIC = 0x5E + TELEPORT = 0x64 + MIMIC = 0x66 + DOUBLE_TEAM = 0x68 + REFLECT = 0x73 + BIDE = 0x75 + METRONOME = 0x76 + SELFDESTRUCT = 0x78 + EGG_BOMB = 0x79 + FIRE_BLAST = 0x7E + SWIFT = 0x81 + SKULL_BASH = 0x82 + SOFTBOILED = 0x87 + DREAM_EATER = 0x8A + SKY_ATTACK = 0x8F + REST = 0x9C + THUNDER_WAVE = 0x56 + PSYWAVE = 0x95 + EXPLOSION = 0x99 + ROCK_SLIDE = 0x9D + TRI_ATTACK = 0xA1 + SUBSTITUTE = 0xA4 + CUT = 0x0F + FLY = 0x13 + SURF = 0x39 + STRENGTH = 0x46 + FLASH = 0x94 CUT_SPECIES_IDS = { - SPECIES_IDS["BULBASAUR"], - SPECIES_IDS["IVYSAUR"], - SPECIES_IDS["VENUSAUR"], - SPECIES_IDS["CHARMANDER"], - SPECIES_IDS["CHARMELEON"], - SPECIES_IDS["CHARIZARD"], - SPECIES_IDS["BEEDRILL"], - SPECIES_IDS["SANDSHREW"], - SPECIES_IDS["SANDSLASH"], - SPECIES_IDS["ODDISH"], - SPECIES_IDS["GLOOM"], - SPECIES_IDS["VILEPLUME"], - SPECIES_IDS["PARAS"], - SPECIES_IDS["PARASECT"], - SPECIES_IDS["BELLSPROUT"], - SPECIES_IDS["WEEPINBELL"], - SPECIES_IDS["VICTREEBEL"], - SPECIES_IDS["TENTACOOL"], - SPECIES_IDS["TENTACRUEL"], - SPECIES_IDS["FARFETCHD"], - SPECIES_IDS["KRABBY"], - SPECIES_IDS["KINGLER"], - SPECIES_IDS["LICKITUNG"], - SPECIES_IDS["TANGELA"], - SPECIES_IDS["SCYTHER"], - SPECIES_IDS["PINSIR"], - SPECIES_IDS["MEW"], + Species.BULBASAUR.value, + Species.IVYSAUR.value, + Species.VENUSAUR.value, + Species.CHARMANDER.value, + Species.CHARMELEON.value, + Species.CHARIZARD.value, + Species.BEEDRILL.value, + Species.SANDSHREW.value, + Species.SANDSLASH.value, + Species.ODDISH.value, + Species.GLOOM.value, + Species.VILEPLUME.value, + Species.PARAS.value, + Species.PARASECT.value, + Species.BELLSPROUT.value, + Species.WEEPINBELL.value, + Species.VICTREEBEL.value, + Species.TENTACOOL.value, + Species.TENTACRUEL.value, + Species.FARFETCHD.value, + Species.KRABBY.value, + Species.KINGLER.value, + Species.LICKITUNG.value, + Species.TANGELA.value, + Species.SCYTHER.value, + Species.PINSIR.value, + Species.MEW.value, } SURF_SPECIES_IDS = { - SPECIES_IDS["SQUIRTLE"], - SPECIES_IDS["WARTORTLE"], - SPECIES_IDS["BLASTOISE"], - SPECIES_IDS["NIDOQUEEN"], - SPECIES_IDS["NIDOKING"], - SPECIES_IDS["PSYDUCK"], - SPECIES_IDS["GOLDUCK"], - SPECIES_IDS["POLIWAG"], - SPECIES_IDS["POLIWHIRL"], - SPECIES_IDS["POLIWRATH"], - SPECIES_IDS["TENTACOOL"], - SPECIES_IDS["TENTACRUEL"], - SPECIES_IDS["SLOWPOKE"], - SPECIES_IDS["SLOWBRO"], - SPECIES_IDS["SEEL"], - SPECIES_IDS["DEWGONG"], - SPECIES_IDS["SHELLDER"], - SPECIES_IDS["CLOYSTER"], - SPECIES_IDS["KRABBY"], - SPECIES_IDS["KINGLER"], - SPECIES_IDS["LICKITUNG"], - SPECIES_IDS["RHYDON"], - SPECIES_IDS["KANGASKHAN"], - SPECIES_IDS["HORSEA"], - SPECIES_IDS["SEADRA"], - SPECIES_IDS["GOLDEEN"], - SPECIES_IDS["SEAKING"], - SPECIES_IDS["STARYU"], - SPECIES_IDS["STARMIE"], - SPECIES_IDS["GYARADOS"], - SPECIES_IDS["LAPRAS"], - SPECIES_IDS["VAPOREON"], - SPECIES_IDS["OMANYTE"], - SPECIES_IDS["OMASTAR"], - SPECIES_IDS["KABUTO"], - SPECIES_IDS["KABUTOPS"], - SPECIES_IDS["SNORLAX"], - SPECIES_IDS["DRATINI"], - SPECIES_IDS["DRAGONAIR"], - SPECIES_IDS["DRAGONITE"], - SPECIES_IDS["MEW"], + Species.SQUIRTLE.value, + Species.WARTORTLE.value, + Species.BLASTOISE.value, + Species.NIDOQUEEN.value, + Species.NIDOKING.value, + Species.PSYDUCK.value, + Species.GOLDUCK.value, + Species.POLIWAG.value, + Species.POLIWHIRL.value, + Species.POLIWRATH.value, + Species.TENTACOOL.value, + Species.TENTACRUEL.value, + Species.SLOWPOKE.value, + Species.SLOWBRO.value, + Species.SEEL.value, + Species.DEWGONG.value, + Species.SHELLDER.value, + Species.CLOYSTER.value, + Species.KRABBY.value, + Species.KINGLER.value, + Species.LICKITUNG.value, + Species.RHYDON.value, + Species.KANGASKHAN.value, + Species.HORSEA.value, + Species.SEADRA.value, + Species.GOLDEEN.value, + Species.SEAKING.value, + Species.STARYU.value, + Species.STARMIE.value, + Species.GYARADOS.value, + Species.LAPRAS.value, + Species.VAPOREON.value, + Species.OMANYTE.value, + Species.OMASTAR.value, + Species.KABUTO.value, + Species.KABUTOPS.value, + Species.SNORLAX.value, + Species.DRATINI.value, + Species.DRAGONAIR.value, + Species.DRAGONITE.value, + Species.MEW.value, } STRENGTH_SPECIES_IDS = { - SPECIES_IDS["CHARMANDER"], - SPECIES_IDS["CHARMELEON"], - SPECIES_IDS["CHARIZARD"], - SPECIES_IDS["SQUIRTLE"], - SPECIES_IDS["WARTORTLE"], - SPECIES_IDS["BLASTOISE"], - SPECIES_IDS["EKANS"], - SPECIES_IDS["ARBOK"], - SPECIES_IDS["SANDSHREW"], - SPECIES_IDS["SANDSLASH"], - SPECIES_IDS["NIDOQUEEN"], - SPECIES_IDS["NIDOKING"], - SPECIES_IDS["CLEFAIRY"], - SPECIES_IDS["CLEFABLE"], - SPECIES_IDS["JIGGLYPUFF"], - SPECIES_IDS["WIGGLYTUFF"], - SPECIES_IDS["PSYDUCK"], - SPECIES_IDS["GOLDUCK"], - SPECIES_IDS["MANKEY"], - SPECIES_IDS["PRIMEAPE"], - SPECIES_IDS["POLIWHIRL"], - SPECIES_IDS["POLIWRATH"], - SPECIES_IDS["MACHOP"], - SPECIES_IDS["MACHOKE"], - SPECIES_IDS["MACHAMP"], - SPECIES_IDS["GEODUDE"], - SPECIES_IDS["GRAVELER"], - SPECIES_IDS["GOLEM"], - SPECIES_IDS["SLOWPOKE"], - SPECIES_IDS["SLOWBRO"], - SPECIES_IDS["SEEL"], - SPECIES_IDS["DEWGONG"], - SPECIES_IDS["GENGAR"], - SPECIES_IDS["ONIX"], - SPECIES_IDS["KRABBY"], - SPECIES_IDS["KINGLER"], - SPECIES_IDS["EXEGGUTOR"], - SPECIES_IDS["CUBONE"], - SPECIES_IDS["MAROWAK"], - SPECIES_IDS["HITMONLEE"], - SPECIES_IDS["HITMONCHAN"], - SPECIES_IDS["LICKITUNG"], - SPECIES_IDS["RHYHORN"], - SPECIES_IDS["RHYDON"], - SPECIES_IDS["CHANSEY"], - SPECIES_IDS["KANGASKHAN"], - SPECIES_IDS["ELECTABUZZ"], - SPECIES_IDS["MAGMAR"], - SPECIES_IDS["PINSIR"], - SPECIES_IDS["TAUROS"], - SPECIES_IDS["GYARADOS"], - SPECIES_IDS["LAPRAS"], - SPECIES_IDS["SNORLAX"], - SPECIES_IDS["DRAGONITE"], - SPECIES_IDS["MEWTWO"], - SPECIES_IDS["MEW"], + Species.CHARMANDER.value, + Species.CHARMELEON.value, + Species.CHARIZARD.value, + Species.SQUIRTLE.value, + Species.WARTORTLE.value, + Species.BLASTOISE.value, + Species.EKANS.value, + Species.ARBOK.value, + Species.SANDSHREW.value, + Species.SANDSLASH.value, + Species.NIDOQUEEN.value, + Species.NIDOKING.value, + Species.CLEFAIRY.value, + Species.CLEFABLE.value, + Species.JIGGLYPUFF.value, + Species.WIGGLYTUFF.value, + Species.PSYDUCK.value, + Species.GOLDUCK.value, + Species.MANKEY.value, + Species.PRIMEAPE.value, + Species.POLIWHIRL.value, + Species.POLIWRATH.value, + Species.MACHOP.value, + Species.MACHOKE.value, + Species.MACHAMP.value, + Species.GEODUDE.value, + Species.GRAVELER.value, + Species.GOLEM.value, + Species.SLOWPOKE.value, + Species.SLOWBRO.value, + Species.SEEL.value, + Species.DEWGONG.value, + Species.GENGAR.value, + Species.ONIX.value, + Species.KRABBY.value, + Species.KINGLER.value, + Species.EXEGGUTOR.value, + Species.CUBONE.value, + Species.MAROWAK.value, + Species.HITMONLEE.value, + Species.HITMONCHAN.value, + Species.LICKITUNG.value, + Species.RHYHORN.value, + Species.RHYDON.value, + Species.CHANSEY.value, + Species.KANGASKHAN.value, + Species.ELECTABUZZ.value, + Species.MAGMAR.value, + Species.PINSIR.value, + Species.TAUROS.value, + Species.GYARADOS.value, + Species.LAPRAS.value, + Species.SNORLAX.value, + Species.DRAGONITE.value, + Species.MEWTWO.value, + Species.MEW.value, } diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index bf13f17..693b226 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -17,12 +17,12 @@ import pufferlib from pokemonred_puffer.data.events import EVENT_FLAGS_START, EVENTS_FLAGS_LENGTH, MUSEUM_TICKET -from pokemonred_puffer.data.field_moves import FIELD_MOVES_MAP +from pokemonred_puffer.data.field_moves import FieldMoves from pokemonred_puffer.data.items import ( - ITEM_NAME_TO_ID, HM_ITEM_IDS, KEY_ITEM_IDS, MAX_ITEM_CAPACITY, + Items, ) from pokemonred_puffer.data.strength_puzzles import STRENGTH_SOLUTIONS from pokemonred_puffer.data.tilesets import Tilesets @@ -30,6 +30,7 @@ CUT_SPECIES_IDS, STRENGTH_SPECIES_IDS, SURF_SPECIES_IDS, + TmHmMoves, ) from pokemonred_puffer.global_map import GLOBAL_MAP_SHAPE, local_to_global @@ -573,19 +574,19 @@ def run_action_on_emulator(self, action): if self.read_bit(0xD803, 0): if self.auto_teach_cut and not self.check_if_party_has_hm(0x0F): - self.teach_hm(0x0F, 30, CUT_SPECIES_IDS) + self.teach_hm(TmHmMoves.CUT.value, 30, CUT_SPECIES_IDS) if self.auto_use_cut: self.cut_if_next() if self.read_bit(0xD78E, 0): if self.auto_teach_surf and not self.check_if_party_has_hm(0x39): - self.teach_hm(0x39, 15, SURF_SPECIES_IDS) + self.teach_hm(TmHmMoves.SURF.value, 15, SURF_SPECIES_IDS) if self.auto_use_surf: self.surf_if_attempt(VALID_ACTIONS[action]) if self.read_bit(0xD857, 0): if self.auto_teach_strength and not self.check_if_party_has_hm(0x46): - self.teach_hm(0x46, 15, STRENGTH_SPECIES_IDS) + self.teach_hm(TmHmMoves.STRENGTH.value, 15, STRENGTH_SPECIES_IDS) if self.auto_solve_strength_puzzles: self.solve_missable_strength_puzzle() self.solve_switch_strength_puzzle() @@ -628,9 +629,9 @@ def use_pokeflute(self): if in_overworld: _, wBagItems = self.pyboy.symbol_lookup("wBagItems") bag_items = self.pyboy.memory[wBagItems : wBagItems + 40] - if ITEM_NAME_TO_ID["POKE_FLUTE"] not in bag_items[::2]: + if Items.POKE_FLUTE.value not in bag_items[::2]: return - pokeflute_index = bag_items[::2].index(ITEM_NAME_TO_ID["POKE_FLUTE"]) + pokeflute_index = bag_items[::2].index(Items.POKE_FLUTE.value) # Check if we're on the snorlax coordinates @@ -784,7 +785,7 @@ def cut_if_next(self): for _ in range(10): current_item = self.read_m("wCurrentMenuItem") - if current_item < 4 and FIELD_MOVES_MAP.get(field_moves[current_item], "") == "CUT": + if current_item < 4 and FieldMoves.CUT.value == field_moves[current_item]: break self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) self.pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN, delay=8) @@ -876,9 +877,9 @@ def surf_if_attempt(self, action: WindowEvent): for _ in range(10): current_item = self.read_m("wCurrentMenuItem") - if ( - current_item < 4 - and FIELD_MOVES_MAP.get(field_moves[current_item], "") == "SURF" + if current_item < 4 and field_moves[current_item] in ( + FieldMoves.SURF.value, + FieldMoves.SURF_2.value, ): break self.pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) @@ -1257,23 +1258,21 @@ def remove_all_nonuseful_items(self): new_bag_items = [ (item, quantity) for item, quantity in zip(bag_items[::2], bag_items[1::2]) - if (0x0 < item < 0xC4 and KEY_ITEM_IDS[item - 1]) - or ( - item - in { - ITEM_NAME_TO_ID[name] - for name in [ - "LEMONADE", - "SODA_POP", - "FRESH_WATER", - "HM_01", - "HM_02", - "HM_03", - "HM_04", - "HM_05", - ] - } - ) + if (0x0 < item < Items.HM_01.value and (item - 1) in KEY_ITEM_IDS) + or item + in { + Items[name] + for name in [ + "LEMONADE", + "SODA_POP", + "FRESH_WATER", + "HM_01", + "HM_02", + "HM_03", + "HM_04", + "HM_05", + ] + } ] # Write the new count back to memory self.pyboy.memory[wNumBagItems] = len(new_bag_items) From b2707fbc18d1117097964f5e03a7a7991595b9ac Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 15 Jun 2024 11:33:34 -0400 Subject: [PATCH 53/68] Clean up config --- config.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config.yaml b/config.yaml index fba5eb2..601bbda 100644 --- a/config.yaml +++ b/config.yaml @@ -124,7 +124,7 @@ wrappers: action_bag_menu: 0.998 forgetting_frequency: 10 - exploration.OnResetExplorationWrapper: - full_reset_frequency: 0 + full_reset_frequency: 1 finite_coords: - stream_wrapper.StreamWrapper: @@ -132,9 +132,15 @@ wrappers: - exploration.MaxLengthWrapper: capacity: 1750 - exploration.OnResetExplorationWrapper: - full_reset_frequency: 0 + full_reset_frequency: 1 stream_only: + - stream_wrapper.StreamWrapper: + user: thatguy + - exploration.OnResetExplorationWrapper: + full_reset_frequency: 1 + + fixed_reset_value: - stream_wrapper.StreamWrapper: user: thatguy - exploration.OnResetLowerToFixedValueWrapper: @@ -147,12 +153,6 @@ wrappers: - exploration.OnResetExplorationWrapper: full_reset_frequency: 25 - fixed_reset_value: - - stream_wrapper.StreamWrapper: - user: thatguy - - exploration.OnResetExplorationWrapper: - full_reset_frequency: 25 - rewards: baseline.BaselineRewardEnv: reward: From 79b49e44e63ede98ec34d0b19ceba8f485329355 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 15 Jun 2024 11:51:12 -0400 Subject: [PATCH 54/68] item obs --- pokemonred_puffer/environment.py | 12 ++++++++++++ pokemonred_puffer/policies/multi_convolutional.py | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 693b226..e6bcc0a 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -160,6 +160,10 @@ def __init__(self, env_config: pufferlib.namespace): # "badges": spaces.Box(low=0, high=np.iinfo(np.uint16).max, shape=(1,), dtype=np.uint16), "badges": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), "wJoyIgnore": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), + "bag": spaces.Box( + low=0, high=max(Items._value2member_map_.keys()), shape=(20,), dtype=np.uint8 + ), + "bag_quantity": spaces.Box(low=0, high=100, shape=(20,), dtype=np.uint8), } ) @@ -471,6 +475,12 @@ def render(self): def _get_obs(self): # player_x, player_y, map_n = self.get_game_coords() + _, wBagItems = self.pyboy.symbol_lookup("wBagItems") + bag = self.pyboy.memory[wBagItems : wBagItems + 40] + end_of_bag = list(bag[::2]).index(0xFF) + bag = np.array(bag, dtype=np.uint8) + bag[end_of_bag:] = 0 + return { **self.render(), "direction": np.array( @@ -485,6 +495,8 @@ def _get_obs(self): "map_id": np.array(self.read_m(0xD35E), dtype=np.uint8), "badges": np.array(self.read_short("wObtainedBadges").bit_count(), dtype=np.uint8), "wJoyIgnore": np.array(self.read_m("wJoyIgnore"), dtype=np.uint8), + "bag_items": bag[::2], + "bag_quantity": bag[1::2], } def set_perfect_iv_dvs(self): diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index 6f412ab..f4df506 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -4,6 +4,7 @@ import pufferlib.models from pufferlib.emulation import unpack_batched_obs +from pokemonred_puffer.data.items import Items from pokemonred_puffer.environment import PIXEL_VALUES unpack_batched_obs = torch.compiler.disable(unpack_batched_obs) @@ -74,6 +75,9 @@ def __init__( # pokemon has 0xF7 map ids # Lets start with 4 dims for now. Could try 8 self.map_embeddings = torch.nn.Embedding(0xF7, 4, dtype=torch.float32) + self.item_embeddings = torch.nn.Embedding( + len(Items), len(Items) ** 0.25, dtype=torch.float32 + ) def encode_observations(self, observations): observations = unpack_batched_obs(observations, self.unflatten_context) @@ -106,6 +110,10 @@ def encode_observations(self, observations): badges = self.badge_buffer <= observations["badges"] map_id = self.map_embeddings(observations["map_id"].long()) blackout_map_id = self.map_embeddings(observations["blackout_map_id"].long()) + # The bag quantity can be a value between 1 and 99 + items = self.item_embeddings(observations["bag_items"].long()).float() * ( + observations["bag_quantity"].float() / 100.0 + ) # image_observation = torch.cat((screen, visited_mask, global_map), dim=-1) image_observation = torch.cat((screen, visited_mask), dim=-1) @@ -130,6 +138,7 @@ def encode_observations(self, observations): map_id.squeeze(1), blackout_map_id.squeeze(1), observations["wJoyIgnore"].float(), + items.squeeze(1), ), dim=-1, ) From b4a9979b2d226b9c0cc8970271bc56f42013bfd1 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 15 Jun 2024 12:00:07 -0400 Subject: [PATCH 55/68] Add jitter to reset exploration wrapper --- config.yaml | 3 +++ pokemonred_puffer/wrappers/exploration.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/config.yaml b/config.yaml index 601bbda..88e3fd4 100644 --- a/config.yaml +++ b/config.yaml @@ -133,12 +133,14 @@ wrappers: capacity: 1750 - exploration.OnResetExplorationWrapper: full_reset_frequency: 1 + jitter: 0 stream_only: - stream_wrapper.StreamWrapper: user: thatguy - exploration.OnResetExplorationWrapper: full_reset_frequency: 1 + jitter: 2 fixed_reset_value: - stream_wrapper.StreamWrapper: @@ -152,6 +154,7 @@ wrappers: explore: 0.33 - exploration.OnResetExplorationWrapper: full_reset_frequency: 25 + jitter: 0 rewards: baseline.BaselineRewardEnv: diff --git a/pokemonred_puffer/wrappers/exploration.py b/pokemonred_puffer/wrappers/exploration.py index cd76183..747a8ad 100644 --- a/pokemonred_puffer/wrappers/exploration.py +++ b/pokemonred_puffer/wrappers/exploration.py @@ -1,4 +1,5 @@ from collections import OrderedDict +import random import gymnasium as gym import numpy as np @@ -99,10 +100,11 @@ class OnResetExplorationWrapper(gym.Wrapper): def __init__(self, env: RedGymEnv, reward_config: pufferlib.namespace): super().__init__(env) self.full_reset_frequency = reward_config.full_reset_frequency + self.jitter = reward_config.jitter self.counter = 0 def reset(self, *args, **kwargs): - if self.counter % self.full_reset_frequency == 0: + if (self.counter + random.randint(0, self.jitter)) >= self.full_reset_frequency: self.counter = 0 self.env.unwrapped.explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) self.env.unwrapped.cut_explore_map = np.zeros(GLOBAL_MAP_SHAPE, dtype=np.float32) From 4ec4f72b04da614a53a6d5b9d21b70a96291d2ce Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 15 Jun 2024 17:46:31 -0400 Subject: [PATCH 56/68] Fix bag obs --- config.yaml | 2 +- pokemonred_puffer/data/items.py | 2 +- pokemonred_puffer/environment.py | 2 +- pokemonred_puffer/policies/multi_convolutional.py | 11 +++++++---- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/config.yaml b/config.yaml index 88e3fd4..fe8373c 100644 --- a/config.yaml +++ b/config.yaml @@ -140,7 +140,7 @@ wrappers: user: thatguy - exploration.OnResetExplorationWrapper: full_reset_frequency: 1 - jitter: 2 + jitter: 1 fixed_reset_value: - stream_wrapper.StreamWrapper: diff --git a/pokemonred_puffer/data/items.py b/pokemonred_puffer/data/items.py index 10d7a5d..16640df 100644 --- a/pokemonred_puffer/data/items.py +++ b/pokemonred_puffer/data/items.py @@ -180,7 +180,7 @@ class Items(Enum): Items.DOME_FOSSIL.value, Items.HELIX_FOSSIL.value, Items.SECRET_KEY.value, - Items.ITEM_2C.value, + # Items.ITEM_2C.value, Items.BIKE_VOUCHER.value, Items.CARD_KEY.value, Items.S_S_TICKET.value, diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index e6bcc0a..808356b 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -160,7 +160,7 @@ def __init__(self, env_config: pufferlib.namespace): # "badges": spaces.Box(low=0, high=np.iinfo(np.uint16).max, shape=(1,), dtype=np.uint16), "badges": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), "wJoyIgnore": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), - "bag": spaces.Box( + "bag_items": spaces.Box( low=0, high=max(Items._value2member_map_.keys()), shape=(20,), dtype=np.uint8 ), "bag_quantity": spaces.Box(low=0, high=100, shape=(20,), dtype=np.uint8), diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index f4df506..d510162 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -75,8 +75,10 @@ def __init__( # pokemon has 0xF7 map ids # Lets start with 4 dims for now. Could try 8 self.map_embeddings = torch.nn.Embedding(0xF7, 4, dtype=torch.float32) + # N.B. This is an overestimate + item_count = max(Items._value2member_map_.keys()) self.item_embeddings = torch.nn.Embedding( - len(Items), len(Items) ** 0.25, dtype=torch.float32 + item_count, int(item_count**0.25 + 1), dtype=torch.float32 ) def encode_observations(self, observations): @@ -111,8 +113,9 @@ def encode_observations(self, observations): map_id = self.map_embeddings(observations["map_id"].long()) blackout_map_id = self.map_embeddings(observations["blackout_map_id"].long()) # The bag quantity can be a value between 1 and 99 - items = self.item_embeddings(observations["bag_items"].long()).float() * ( - observations["bag_quantity"].float() / 100.0 + # TODO: Should items be positionally encoded? I dont think it matters + items = self.item_embeddings(observations["bag_items"].squeeze(1).long()).float() * ( + observations["bag_quantity"].squeeze(1).float().unsqueeze(-1) / 100.0 ) # image_observation = torch.cat((screen, visited_mask, global_map), dim=-1) @@ -138,7 +141,7 @@ def encode_observations(self, observations): map_id.squeeze(1), blackout_map_id.squeeze(1), observations["wJoyIgnore"].float(), - items.squeeze(1), + items.flatten(start_dim=1), ), dim=-1, ) From fe06f48b58b8d194d46f4c8dc7db498912b9317f Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sat, 15 Jun 2024 17:50:38 -0400 Subject: [PATCH 57/68] add per badge stats --- pokemonred_puffer/environment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 808356b..bb5cf65 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -1055,6 +1055,7 @@ def disable_wild_encounter_hook(self, *args, **kwargs): def agent_stats(self, action): levels = [self.read_m(f"wPartyMon{i+1}Level") for i in range(self.read_m("wPartyCount"))] + badges = self.read_short("wObtainedBadges") return { "stats": { "step": self.step_count + self.reset_count * self.max_steps, @@ -1101,7 +1102,8 @@ def agent_stats(self, action): "pokecenter": np.sum(self.pokecenters), "rival3": int(self.read_m(0xD665) == 4), "rocket_hideout_found": int(self.read_bit(0xD77E, 1)), - }, + } + | {f"badge_{i+1}": badges & (1 << i) for i in range(8)}, "reward": self.get_game_state_reward(), "reward/reward_sum": sum(self.get_game_state_reward().values()), "pokemon_exploration_map": self.explore_map, From 2a6c71ad1d178100872af1a0311eba06f3ebf8c9 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 16 Jun 2024 00:17:27 -0400 Subject: [PATCH 58/68] Fix badges and items --- pokemonred_puffer/environment.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index bb5cf65..deaef2c 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -477,7 +477,10 @@ def _get_obs(self): # player_x, player_y, map_n = self.get_game_coords() _, wBagItems = self.pyboy.symbol_lookup("wBagItems") bag = self.pyboy.memory[wBagItems : wBagItems + 40] - end_of_bag = list(bag[::2]).index(0xFF) + try: + end_of_bag = 2 * list(bag[::2]).index(0xFF) + except ValueError: + end_of_bag = len(bag) bag = np.array(bag, dtype=np.uint8) bag[end_of_bag:] = 0 @@ -1055,7 +1058,8 @@ def disable_wild_encounter_hook(self, *args, **kwargs): def agent_stats(self, action): levels = [self.read_m(f"wPartyMon{i+1}Level") for i in range(self.read_m("wPartyCount"))] - badges = self.read_short("wObtainedBadges") + badges = self.read_m("wObtainedBadges") + breakpoint() return { "stats": { "step": self.step_count + self.reset_count * self.max_steps, @@ -1103,7 +1107,7 @@ def agent_stats(self, action): "rival3": int(self.read_m(0xD665) == 4), "rocket_hideout_found": int(self.read_bit(0xD77E, 1)), } - | {f"badge_{i+1}": badges & (1 << i) for i in range(8)}, + | {f"badge_{i+1}": bool(badges & (1 << i)) for i in range(8)}, "reward": self.get_game_state_reward(), "reward/reward_sum": sum(self.get_game_state_reward().values()), "pokemon_exploration_map": self.explore_map, From f4140544d4474521f610d3d4cddd5de724b3679b Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 16 Jun 2024 00:19:24 -0400 Subject: [PATCH 59/68] rm breakpoint --- pokemonred_puffer/environment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index deaef2c..77bfd15 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -1059,7 +1059,6 @@ def disable_wild_encounter_hook(self, *args, **kwargs): def agent_stats(self, action): levels = [self.read_m(f"wPartyMon{i+1}Level") for i in range(self.read_m("wPartyCount"))] badges = self.read_m("wObtainedBadges") - breakpoint() return { "stats": { "step": self.step_count + self.reset_count * self.max_steps, From 289f539725424ef1298791055d17d0f5b26b0754 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 16 Jun 2024 00:23:00 -0400 Subject: [PATCH 60/68] Simpler bag items obs --- pokemonred_puffer/environment.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 77bfd15..4d15864 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -476,13 +476,9 @@ def render(self): def _get_obs(self): # player_x, player_y, map_n = self.get_game_coords() _, wBagItems = self.pyboy.symbol_lookup("wBagItems") - bag = self.pyboy.memory[wBagItems : wBagItems + 40] - try: - end_of_bag = 2 * list(bag[::2]).index(0xFF) - except ValueError: - end_of_bag = len(bag) - bag = np.array(bag, dtype=np.uint8) - bag[end_of_bag:] = 0 + bag = np.array(self.pyboy.memory[wBagItems : wBagItems + 40]) + numBagItems = self.pyboy.symbol_lookup("wNumBagItems") + bag[2 * numBagItems :] = 0 return { **self.render(), From 37cfe6054d116e6d93faf750199bf30e4622cc67 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 16 Jun 2024 00:25:22 -0400 Subject: [PATCH 61/68] fix simpler bag obs --- pokemonred_puffer/environment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 4d15864..c2355db 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -477,7 +477,8 @@ def _get_obs(self): # player_x, player_y, map_n = self.get_game_coords() _, wBagItems = self.pyboy.symbol_lookup("wBagItems") bag = np.array(self.pyboy.memory[wBagItems : wBagItems + 40]) - numBagItems = self.pyboy.symbol_lookup("wNumBagItems") + numBagItems = self.read_m("wNumBagItems") + # item ids start at 1 so using 0 as the nothing value is okay bag[2 * numBagItems :] = 0 return { From 093dd979c76a5fb2219e247c442cbc17fed10fe4 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 16 Jun 2024 00:45:39 -0400 Subject: [PATCH 62/68] read_m not read_short --- pokemonred_puffer/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index c2355db..dbcc52a 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -1195,7 +1195,7 @@ def read_event_bits(self): return self.pyboy.memory[addr : addr + EVENTS_FLAGS_LENGTH] def get_badges(self): - return self.read_short("wObtainedBadges").bit_count() + return self.read_m("wObtainedBadges").bit_count() def read_party(self): _, addr = self.pyboy.symbol_lookup("wPartySpecies") From 0ddb7dfccf5a9c94ab677b03e2525835ac5da0f0 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 16 Jun 2024 00:50:50 -0400 Subject: [PATCH 63/68] dtypes matater --- pokemonred_puffer/environment.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index dbcc52a..00b3169 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -476,13 +476,12 @@ def render(self): def _get_obs(self): # player_x, player_y, map_n = self.get_game_coords() _, wBagItems = self.pyboy.symbol_lookup("wBagItems") - bag = np.array(self.pyboy.memory[wBagItems : wBagItems + 40]) + bag = np.array(self.pyboy.memory[wBagItems : wBagItems + 40], dtype=np.uint8) numBagItems = self.read_m("wNumBagItems") # item ids start at 1 so using 0 as the nothing value is okay bag[2 * numBagItems :] = 0 - return { - **self.render(), + return self.render() | { "direction": np.array( self.read_m("wSpritePlayerStateData1FacingDirection") // 4, dtype=np.uint8 ), @@ -495,8 +494,8 @@ def _get_obs(self): "map_id": np.array(self.read_m(0xD35E), dtype=np.uint8), "badges": np.array(self.read_short("wObtainedBadges").bit_count(), dtype=np.uint8), "wJoyIgnore": np.array(self.read_m("wJoyIgnore"), dtype=np.uint8), - "bag_items": bag[::2], - "bag_quantity": bag[1::2], + "bag_items": bag[::2].copy(), + "bag_quantity": bag[1::2].copy(), } def set_perfect_iv_dvs(self): From 8d541bd8819a914c12aa2bf53f228dd8db6b9922 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 16 Jun 2024 11:37:27 -0400 Subject: [PATCH 64/68] Do not collect seen coordinates when the joypad is disabled --- pokemonred_puffer/environment.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 00b3169..c8da7b4 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -1148,11 +1148,12 @@ def get_game_coords(self): return (self.read_m(0xD362), self.read_m(0xD361), self.read_m(0xD35E)) def update_seen_coords(self): - x_pos, y_pos, map_n = self.get_game_coords() - self.seen_coords[(x_pos, y_pos, map_n)] = 1 - self.explore_map[local_to_global(y_pos, x_pos, map_n)] = 1 - # self.seen_global_coords[local_to_global(y_pos, x_pos, map_n)] = 1 - self.seen_map_ids[map_n] = 1 + if not self.read_m("wJoyIgnore"): + x_pos, y_pos, map_n = self.get_game_coords() + self.seen_coords[(x_pos, y_pos, map_n)] = 1 + self.explore_map[local_to_global(y_pos, x_pos, map_n)] = 1 + # self.seen_global_coords[local_to_global(y_pos, x_pos, map_n)] = 1 + self.seen_map_ids[map_n] = 1 def get_explore_map(self): explore_map = np.zeros(GLOBAL_MAP_SHAPE) From 92af0bc65de01b321e87e4b07fa902c5a55764c9 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 16 Jun 2024 20:02:26 -0400 Subject: [PATCH 65/68] Only ignore seen coords when on spinny tiles --- pokemonred_puffer/environment.py | 3 +-- pyproject.toml | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index c8da7b4..83500e9 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -924,7 +924,6 @@ def solve_missable_strength_puzzle(self): picture_id = self.read_m(f"wSprite{sprite_id:02}StateData1PictureID") mapY = self.read_m(f"wSprite{sprite_id:02}StateData2MapY") mapX = self.read_m(f"wSprite{sprite_id:02}StateData2MapX") - print((picture_id, mapY, mapX) + self.get_game_coords()) if solution := STRENGTH_SOLUTIONS.get( (picture_id, mapY, mapX) + self.get_game_coords(), [] ): @@ -1148,7 +1147,7 @@ def get_game_coords(self): return (self.read_m(0xD362), self.read_m(0xD361), self.read_m(0xD35E)) def update_seen_coords(self): - if not self.read_m("wJoyIgnore"): + if not (self.read_m("wd736") & 0b1000_0000): x_pos, y_pos, map_n = self.get_game_coords() self.seen_coords[(x_pos, y_pos, map_n)] = 1 self.explore_map[local_to_global(y_pos, x_pos, map_n)] = 1 diff --git a/pyproject.toml b/pyproject.toml index 9c0f70b..07f1871 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,13 +13,16 @@ classifiers = [ ] dependencies = [ "einops", - "opencv-python", + "mediapy", "numpy", + "opencv-python", "pyboy>=2", "pufferlib[cleanrl]>=0.7.3", + "scikit-image", "torch>=2.1", "torchvision", - "wandb" + "wandb", + "websockets" ] [tool.setuptools.packages.find] From c95ceb14ff591f72659398d9609321f698c10479 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Sun, 16 Jun 2024 20:03:17 -0400 Subject: [PATCH 66/68] Upper bound on pufferlib --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 07f1871..35de646 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "numpy", "opencv-python", "pyboy>=2", - "pufferlib[cleanrl]>=0.7.3", + "pufferlib[cleanrl]>=0.7.3,<1.0.0", "scikit-image", "torch>=2.1", "torchvision", From 9135a627a93421cbbb7bb9a39aa6b54973ba8895 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Tue, 18 Jun 2024 12:09:31 -0400 Subject: [PATCH 67/68] Global map is its own obs --- pokemonred_puffer/environment.py | 18 +++++++--- .../policies/multi_convolutional.py | 35 ++++++++++++++----- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 83500e9..60e6f12 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -13,7 +13,7 @@ from gymnasium import Env, spaces from pyboy import PyBoy from pyboy.utils import WindowEvent -from skimage.transform import resize +# from skimage.transform import resize import pufferlib from pokemonred_puffer.data.events import EVENT_FLAGS_START, EVENTS_FLAGS_LENGTH, MUSEUM_TICKET @@ -106,6 +106,7 @@ def __init__(self, env_config: pufferlib.namespace): self.action_space = ACTION_SPACE # Obs space-related. TODO: avoid hardcoding? + self.global_map_shape = GLOBAL_MAP_SHAPE if self.reduce_res: self.screen_output_shape = (72, 80, 1) else: @@ -116,6 +117,7 @@ def __init__(self, env_config: pufferlib.namespace): self.screen_output_shape[1] // 4, 1, ) + self.global_map_shape = (self.global_map_shape[0], self.global_map_shape[1] // 4, 1) self.coords_pad = 12 self.enc_freqs = 8 @@ -148,6 +150,9 @@ def __init__(self, env_config: pufferlib.namespace): # "global_map": spaces.Box( # low=0, high=255, shape=self.screen_output_shape, dtype=np.uint8 # ), + "global_map": spaces.Box( + low=0, high=255, shape=self.global_map_shape, dtype=np.uint8 + ), # Discrete is more apt, but pufferlib is slower at processing Discrete "direction": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), "blackout_map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), @@ -423,12 +428,16 @@ def render(self): ) ).astype(np.uint8) visited_mask = np.expand_dims(visited_mask, -1) - """ global_map = np.expand_dims( 255 * resize(self.explore_map, game_pixels_render.shape, anti_aliasing=False), axis=-1, ).astype(np.uint8) + """ + global_map = np.expand_dims( + 255 * self.explore_map, + axis=-1, + ).astype(np.uint8) if self.two_bit: game_pixels_render = ( @@ -464,13 +473,14 @@ def render(self): << np.array([6, 4, 2, 0], dtype=np.uint8) ) .sum(axis=1, dtype=np.uint8) - .reshape(game_pixels_render.shape) + .reshape(self.global_map_shape) + .astype(np.uint8) ) return { "screen": game_pixels_render, "visited_mask": visited_mask, - # "global_map": global_map, + "global_map": global_map, } def _get_obs(self): diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index d510162..0a170b1 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -45,6 +45,15 @@ def __init__( nn.ReLU(), nn.Flatten(), ) + self.global_map_network = nn.Sequential( + nn.LazyConv2d(32, 8, stride=4), + nn.ReLU(), + nn.LazyConv2d(64, 4, stride=2), + nn.ReLU(), + nn.LazyConv2d(64, 3, stride=1), + nn.ReLU(), + nn.Flatten(), + ) self.encode_linear = nn.Sequential( nn.LazyLinear(hidden_size), @@ -86,8 +95,14 @@ def encode_observations(self, observations): screen = observations["screen"] visited_mask = observations["visited_mask"] - # global_map = observations["global_map"] + global_map = observations["global_map"] restored_shape = (screen.shape[0], screen.shape[1], screen.shape[2] * 4, screen.shape[3]) + restored_global_map_shape = ( + global_map.shape[0], + global_map.shape[1], + global_map.shape[2] * 4, + global_map.shape[3], + ) if self.two_bit: screen = torch.index_select( @@ -102,13 +117,13 @@ def encode_observations(self, observations): .flatten() .int(), ).reshape(restored_shape) - # global_map = torch.index_select( - # self.linear_buckets, - # 0, - # ((global_map.reshape((-1, 1)) & self.unpack_mask) >> self.unpack_shift) - # .flatten() - # .int(), - # ).reshape(restored_shape) + global_map = torch.index_select( + self.linear_buckets, + 0, + ((global_map.reshape((-1, 1)) & self.unpack_mask) >> self.unpack_shift) + .flatten() + .int(), + ).reshape(restored_global_map_shape) badges = self.badge_buffer <= observations["badges"] map_id = self.map_embeddings(observations["map_id"].long()) blackout_map_id = self.map_embeddings(observations["blackout_map_id"].long()) @@ -122,13 +137,15 @@ def encode_observations(self, observations): image_observation = torch.cat((screen, visited_mask), dim=-1) if self.channels_last: image_observation = image_observation.permute(0, 3, 1, 2) + global_map = global_map.permute(0, 3, 1, 2) if self.downsample > 1: image_observation = image_observation[:, :, :: self.downsample, :: self.downsample] return self.encode_linear( torch.cat( ( - (self.screen_network(image_observation.float() / 255.0).squeeze(1)), + self.screen_network(image_observation.float() / 255.0).squeeze(1), + self.global_map_network(global_map.float() / 255.0).squeeze(1), one_hot(observations["direction"].long(), 4).float().squeeze(1), # one_hot(observations["reset_map_id"].long(), 0xF7).float().squeeze(1), one_hot(observations["battle_type"].long(), 4).float().squeeze(1), From 00d7ae5bac56ecba2d9c1d8f6dd0d70709be06a9 Mon Sep 17 00:00:00 2001 From: thatguy11325 <148832074+thatguy11325@users.noreply.github.com> Date: Tue, 18 Jun 2024 13:16:06 -0400 Subject: [PATCH 68/68] Put global map obs behind a flag --- config.yaml | 1 + pokemonred_puffer/environment.py | 97 +++++++++---------- .../policies/multi_convolutional.py | 74 ++++++++------ 3 files changed, 90 insertions(+), 82 deletions(-) diff --git a/config.yaml b/config.yaml index fe8373c..6ad611c 100644 --- a/config.yaml +++ b/config.yaml @@ -61,6 +61,7 @@ env: auto_remove_all_nonuseful_items: True auto_pokeflute: True infinite_money: True + use_global_map: False train: diff --git a/pokemonred_puffer/environment.py b/pokemonred_puffer/environment.py index 60e6f12..3af4b2d 100644 --- a/pokemonred_puffer/environment.py +++ b/pokemonred_puffer/environment.py @@ -103,6 +103,7 @@ def __init__(self, env_config: pufferlib.namespace): self.auto_remove_all_nonuseful_items = env_config.auto_remove_all_nonuseful_items self.auto_pokeflute = env_config.auto_pokeflute self.infinite_money = env_config.infinite_money + self.use_global_map = env_config.use_global_map self.action_space = ACTION_SPACE # Obs space-related. TODO: avoid hardcoding? @@ -139,38 +140,34 @@ def __init__(self, env_config: pufferlib.namespace): v: i for i, v in enumerate([40, 0, 12, 1, 13, 51, 2, 54, 14, 59, 60, 61, 15, 3, 65]) } - self.observation_space = spaces.Dict( - { - "screen": spaces.Box( - low=0, high=255, shape=self.screen_output_shape, dtype=np.uint8 - ), - "visited_mask": spaces.Box( - low=0, high=255, shape=self.screen_output_shape, dtype=np.uint8 - ), - # "global_map": spaces.Box( - # low=0, high=255, shape=self.screen_output_shape, dtype=np.uint8 - # ), - "global_map": spaces.Box( - low=0, high=255, shape=self.global_map_shape, dtype=np.uint8 - ), - # Discrete is more apt, but pufferlib is slower at processing Discrete - "direction": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), - "blackout_map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), - "battle_type": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), - "cut_event": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), - "cut_in_party": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), - # "x": spaces.Box(low=0, high=255, shape=(1,), dtype=np.u`int8), - # "y": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), - "map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), - # "badges": spaces.Box(low=0, high=np.iinfo(np.uint16).max, shape=(1,), dtype=np.uint16), - "badges": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), - "wJoyIgnore": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), - "bag_items": spaces.Box( - low=0, high=max(Items._value2member_map_.keys()), shape=(20,), dtype=np.uint8 - ), - "bag_quantity": spaces.Box(low=0, high=100, shape=(20,), dtype=np.uint8), - } - ) + obs_dict = { + "screen": spaces.Box(low=0, high=255, shape=self.screen_output_shape, dtype=np.uint8), + "visited_mask": spaces.Box( + low=0, high=255, shape=self.screen_output_shape, dtype=np.uint8 + ), + # Discrete is more apt, but pufferlib is slower at processing Discrete + "direction": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), + "blackout_map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), + "battle_type": spaces.Box(low=0, high=4, shape=(1,), dtype=np.uint8), + "cut_event": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), + "cut_in_party": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), + # "x": spaces.Box(low=0, high=255, shape=(1,), dtype=np.u`int8), + # "y": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), + "map_id": spaces.Box(low=0, high=0xF7, shape=(1,), dtype=np.uint8), + # "badges": spaces.Box(low=0, high=np.iinfo(np.uint16).max, shape=(1,), dtype=np.uint16), + "badges": spaces.Box(low=0, high=255, shape=(1,), dtype=np.uint8), + "wJoyIgnore": spaces.Box(low=0, high=1, shape=(1,), dtype=np.uint8), + "bag_items": spaces.Box( + low=0, high=max(Items._value2member_map_.keys()), shape=(20,), dtype=np.uint8 + ), + "bag_quantity": spaces.Box(low=0, high=100, shape=(20,), dtype=np.uint8), + } + + if self.use_global_map: + obs_dict["global_map"] = spaces.Box( + low=0, high=255, shape=self.global_map_shape, dtype=np.uint8 + ) + self.observation_space = spaces.Dict(obs_dict) self.pyboy = PyBoy( env_config.gb_path, @@ -434,10 +431,11 @@ def render(self): axis=-1, ).astype(np.uint8) """ - global_map = np.expand_dims( - 255 * self.explore_map, - axis=-1, - ).astype(np.uint8) + if self.use_global_map: + global_map = np.expand_dims( + 255 * self.explore_map, + axis=-1, + ).astype(np.uint8) if self.two_bit: game_pixels_render = ( @@ -463,25 +461,24 @@ def render(self): .reshape(game_pixels_render.shape) .astype(np.uint8) ) - global_map = ( - ( - np.digitize( - global_map.reshape((-1, 4)), - np.array([0, 64, 128, 255], dtype=np.uint8), - right=True, - ).astype(np.uint8) - << np.array([6, 4, 2, 0], dtype=np.uint8) + if self.use_global_map: + global_map = ( + ( + np.digitize( + global_map.reshape((-1, 4)), + np.array([0, 64, 128, 255], dtype=np.uint8), + right=True, + ).astype(np.uint8) + << np.array([6, 4, 2, 0], dtype=np.uint8) + ) + .sum(axis=1, dtype=np.uint8) + .reshape(self.global_map_shape) ) - .sum(axis=1, dtype=np.uint8) - .reshape(self.global_map_shape) - .astype(np.uint8) - ) return { "screen": game_pixels_render, "visited_mask": visited_mask, - "global_map": global_map, - } + } | ({"global_map": global_map} if self.use_global_map else {}) def _get_obs(self): # player_x, player_y, map_n = self.get_game_coords() diff --git a/pokemonred_puffer/policies/multi_convolutional.py b/pokemonred_puffer/policies/multi_convolutional.py index 0a170b1..f65ec29 100644 --- a/pokemonred_puffer/policies/multi_convolutional.py +++ b/pokemonred_puffer/policies/multi_convolutional.py @@ -64,6 +64,7 @@ def __init__( self.value_fn = nn.LazyLinear(1) self.two_bit = env.unwrapped.env.two_bit + self.use_global_map = env.unwrapped.env.use_global_map self.register_buffer( "screen_buckets", torch.tensor(PIXEL_VALUES, dtype=torch.uint8), persistent=False @@ -95,14 +96,15 @@ def encode_observations(self, observations): screen = observations["screen"] visited_mask = observations["visited_mask"] - global_map = observations["global_map"] restored_shape = (screen.shape[0], screen.shape[1], screen.shape[2] * 4, screen.shape[3]) - restored_global_map_shape = ( - global_map.shape[0], - global_map.shape[1], - global_map.shape[2] * 4, - global_map.shape[3], - ) + if self.use_global_map: + global_map = observations["global_map"] + restored_global_map_shape = ( + global_map.shape[0], + global_map.shape[1], + global_map.shape[2] * 4, + global_map.shape[3], + ) if self.two_bit: screen = torch.index_select( @@ -117,13 +119,14 @@ def encode_observations(self, observations): .flatten() .int(), ).reshape(restored_shape) - global_map = torch.index_select( - self.linear_buckets, - 0, - ((global_map.reshape((-1, 1)) & self.unpack_mask) >> self.unpack_shift) - .flatten() - .int(), - ).reshape(restored_global_map_shape) + if self.use_global_map: + global_map = torch.index_select( + self.linear_buckets, + 0, + ((global_map.reshape((-1, 1)) & self.unpack_mask) >> self.unpack_shift) + .flatten() + .int(), + ).reshape(restored_global_map_shape) badges = self.badge_buffer <= observations["badges"] map_id = self.map_embeddings(observations["map_id"].long()) blackout_map_id = self.map_embeddings(observations["blackout_map_id"].long()) @@ -137,32 +140,39 @@ def encode_observations(self, observations): image_observation = torch.cat((screen, visited_mask), dim=-1) if self.channels_last: image_observation = image_observation.permute(0, 3, 1, 2) - global_map = global_map.permute(0, 3, 1, 2) + if self.use_global_map: + global_map = global_map.permute(0, 3, 1, 2) if self.downsample > 1: image_observation = image_observation[:, :, :: self.downsample, :: self.downsample] - return self.encode_linear( - torch.cat( + cat_obs = torch.cat( + ( + self.screen_network(image_observation.float() / 255.0).squeeze(1), + one_hot(observations["direction"].long(), 4).float().squeeze(1), + # one_hot(observations["reset_map_id"].long(), 0xF7).float().squeeze(1), + one_hot(observations["battle_type"].long(), 4).float().squeeze(1), + observations["cut_event"].float(), + observations["cut_in_party"].float(), + # observations["x"].float(), + # observations["y"].float(), + # one_hot(observations["map_id"].long(), 0xF7).float().squeeze(1), + badges.float().squeeze(1), + map_id.squeeze(1), + blackout_map_id.squeeze(1), + observations["wJoyIgnore"].float(), + items.flatten(start_dim=1), + ), + dim=-1, + ) + if self.use_global_map: + cat_obs = torch.cat( ( - self.screen_network(image_observation.float() / 255.0).squeeze(1), + cat_obs, self.global_map_network(global_map.float() / 255.0).squeeze(1), - one_hot(observations["direction"].long(), 4).float().squeeze(1), - # one_hot(observations["reset_map_id"].long(), 0xF7).float().squeeze(1), - one_hot(observations["battle_type"].long(), 4).float().squeeze(1), - observations["cut_event"].float(), - observations["cut_in_party"].float(), - # observations["x"].float(), - # observations["y"].float(), - # one_hot(observations["map_id"].long(), 0xF7).float().squeeze(1), - badges.float().squeeze(1), - map_id.squeeze(1), - blackout_map_id.squeeze(1), - observations["wJoyIgnore"].float(), - items.flatten(start_dim=1), ), dim=-1, ) - ), None + return self.encode_linear(cat_obs), None def decode_actions(self, flat_hidden, lookup, concat=None): action = self.actor(flat_hidden)