From 619cb8ac10440df3076741ef737f299fc01bd661 Mon Sep 17 00:00:00 2001 From: leanke Date: Mon, 11 Dec 2023 05:07:00 +0000 Subject: [PATCH 01/29] pokemon_red party update --- pokegym/environment.py | 125 +++++++++++++++------ pokegym/pyboy_binding.py | 2 +- pokegym/ram_map.py | 230 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 321 insertions(+), 36 deletions(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index 245685f..08993df 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -63,7 +63,7 @@ def __init__(self, rom_path='pokemon_red.gb', state_path=None, headless=True, quiet=False, **kwargs): '''Creates a PokemonRed environment''' if state_path is None: - state_path = __file__.rstrip('environment.py') + 'has_pokedex_nballs.state' + state_path = __file__.rstrip('environment.py') + 'has_pokedex_nballs.state' # 'mtmoon.state' self.game, self.screen = make_env( rom_path, headless, quiet, **kwargs) @@ -116,12 +116,14 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.seen_coords = set() self.seen_maps = set() - self.death_count = 0 self.total_healing = 0 - self.last_hp = 1.0 self.last_party_size = 1 self.last_reward = None + self.lvl = 0 + self.last_hp = [0] * 6 + self.death = 0 + return self.render()[::2, ::2], {} def step(self, action, fast_video=True): @@ -133,7 +135,10 @@ def step(self, action, fast_video=True): r, c, map_n = ram_map.position(self.game) self.seen_coords.add((r, c, map_n)) self.seen_maps.add(map_n) - exploration_reward = 0.01 * len(self.seen_coords) + coord_reward = 0.01 * len(self.seen_coords) + map_reward = 1.0 * len(self.seen_maps) + exploration_reward = coord_reward + map_reward + glob_r, glob_c = game_map.local_to_global(r, c, map_n) try: self.counts_map[glob_r, glob_c] += 1 @@ -142,40 +147,46 @@ def step(self, action, fast_video=True): # Level reward party, party_size, party_levels = ram_map.party(self.game) - self.max_level_sum = max(self.max_level_sum, sum(party_levels)) - if self.max_level_sum < 30: - level_reward = 1 * self.max_level_sum - else: - level_reward = 30 + (self.max_level_sum - 30)/4 - # Healing and death rewards - hp = ram_map.hp(self.game) - hp_delta = hp - self.last_hp + # Party rewards party_size_constant = party_size == self.last_party_size - # Only reward if not reviving at pokecenter - if hp_delta > 0 and party_size_constant and not self.is_dead: - self.total_healing += hp_delta - - # Dead if hp is zero - if hp <= 0 and self.last_hp > 0: - self.death_count += 1 - self.is_dead = True - elif hp > 0.01: # TODO: Check if this matters - self.is_dead = False + poke, type1, type2, level, hp = ram_map.pokemon(self.game) # status, + # Level + level_rewards = [] + for lvl in level: + if lvl < 15: + level_reward = .5 * lvl + else: + level_reward = 7.5 + (lvl - 15) / 4 + level_rewards.append(level_reward) + # HP / Death + assert len(hp) == len(self.last_hp) + for h, i in zip(hp, self.last_hp): + hp_delta = h - i + if hp_delta > 0 and party_size_constant: + self.total_healing += hp_delta + if h <= 0 and i > 0: + self.death += 1 + i = h + lvl_rew = sum(level_rewards) + healing_reward = self.total_healing + self.max_level_sum = sum(level) + self.last_party_size = party_size + death_reward = -0.05 * self.death - # Update last known values for next iteration + # # Update last known values for next iteration self.last_hp = hp self.last_party_size = party_size - # Set rewards - healing_reward = self.total_healing - death_reward = -0.05 * self.death_count + # # Set rewards + # healing_reward = self.total_healing + # death_reward = -0.05 * self.death_count # Opponent level reward - max_opponent_level = max(ram_map.opponent(self.game)) - self.max_opponent_level = max(self.max_opponent_level, max_opponent_level) - opponent_level_reward = 0.2 * self.max_opponent_level + # max_opponent_level = max(ram_map.opponent(self.game)) + # self.max_opponent_level = max(self.max_opponent_level, max_opponent_level) + # opponent_level_reward = 0.2 * self.max_opponent_level # Badge reward badges = ram_map.badges(self.game) @@ -186,11 +197,55 @@ def step(self, action, fast_video=True): self.max_events = max(self.max_events, events) event_reward = self.max_events + #money reward money = ram_map.money(self.game) - reward = self.reward_scale * (event_reward + level_reward + - opponent_level_reward + death_reward + badges_reward + - healing_reward + exploration_reward) + #sum reward + reward = self.reward_scale * (event_reward + lvl_rew + death_reward + badges_reward + exploration_reward + healing_reward) # + healing_reward + + reward1 = (event_reward + lvl_rew + badges_reward + exploration_reward + healing_reward) # + healing_reward + + if death_reward == 0: + neg_reward = 1 + else: + neg_reward = death_reward + + #print rewards + if self.headless == False: + print(f'-------------Counter-------------') + print(f'Steps:',self.time,) + print(f'Sum Reward:',reward) + print(f'Events:',self.max_events) + print(f'Total Level:',self.max_level_sum) + print(f'Levels:',level) + print(f'HP:',hp) + print(f'Deaths:',self.death) + print(f'Total Heal:',self.total_healing) + print(f'Party Size:',self.last_party_size) + print(f'-------------Rewards-------------') + print(f'Total:',reward1) + print(f'Explore:',exploration_reward,'--%',100 * (exploration_reward/reward1)) + print(f'Healing:',healing_reward,'--%',100 * (healing_reward/reward1)) + print(f'Badges:',badges_reward,'--%',100 * (badges_reward/reward1)) + #print(f'Op Level:',opponent_level_reward,'--%',100 * (opponent_level_reward/reward1)) + print(f'Level:',lvl_rew,'--%',100 * (lvl_rew/reward1)) + print(f'Events:',event_reward,'--%',100 * (event_reward/reward1)) + print(f'-------------Negatives-------------') + print(f'Total:',neg_reward) + print(f'Deaths:',death_reward, '--%', 100 * (death_reward/neg_reward)) + # print(f'-------------Party-------------') + # print(f'P1--','Lvl:',p1lvl,', Status:',p1status,', HP:',self.last_p1hp,', Deaths:',self.p1death) + # print(f'P2--','Lvl:',p2lvl,', Status:',p2status,', HP:',self.last_p2hp,', Deaths:',self.p2death) + # print(f'P3--','Lvl:',p3lvl,', Status:',p3status,', HP:',self.last_p3hp,', Deaths:',self.p3death) + # print(f'P4--','Lvl:',p4lvl,', Status:',p4status,', HP:',self.last_p4hp,', Deaths:',self.p4death) + # print(f'P5--','Lvl:',p5lvl,', Status:',p5status,', HP:',self.last_p5hp,', Deaths:',self.p5death) + # print(f'P6--','Lvl:',p6lvl,', Status:',p6status,', HP:',self.last_p6hp,', Deaths:',self.p6death) + # print(f'Coords:',self.seen_coords) + # print(f'Dest_status:',self.dest_reward,'--%',100 * (self.dest_reward/neg_reward)) + # print(f'-------------Test-------------') + # print(f'Last Health:',self.last_health) + # print(f'Current Health:',cur_health) + # print(f'Heal Amount',self.heal_amount) # Subtract previous reward # TODO: Don't record large cumulative rewards in the first place @@ -210,7 +265,7 @@ def step(self, action, fast_video=True): 'delta': reward, 'event': event_reward, 'level': level_reward, - 'opponent_level': opponent_level_reward, + #'opponent_level': opponent_level_reward, 'death': death_reward, 'badges': badges_reward, 'healing': healing_reward, @@ -220,7 +275,7 @@ def step(self, action, fast_video=True): 'party_size': party_size, 'highest_pokemon_level': max(party_levels), 'total_party_level': sum(party_levels), - 'deaths': self.death_count, + 'deaths': self.death, 'badge_1': float(badges == 1), 'badge_2': float(badges > 1), 'event': events, @@ -235,7 +290,7 @@ def step(self, action, fast_video=True): f'level_Reward: {level_reward}', f'healing: {healing_reward}', f'death: {death_reward}', - f'op_level: {opponent_level_reward}', + #f'op_level: {opponent_level_reward}', f'badges reward: {badges_reward}', f'event reward: {event_reward}', f'money: {money}', diff --git a/pokegym/pyboy_binding.py b/pokegym/pyboy_binding.py index 0e9a4fd..61c2581 100644 --- a/pokegym/pyboy_binding.py +++ b/pokegym/pyboy_binding.py @@ -56,7 +56,7 @@ def make_env(gb_path, headless=True, quiet=False, **kwargs): screen = game.botsupport_manager().screen() if not headless: - game.set_emulation_speed(6) + game.set_emulation_speed(10) return game, screen diff --git a/pokegym/ram_map.py b/pokegym/ram_map.py index 28b2a4a..9fad91f 100644 --- a/pokegym/ram_map.py +++ b/pokegym/ram_map.py @@ -25,6 +25,40 @@ MONEY_ADDR_10000 = 0xD349 + +#Trainer Moves/PP counter if 00 then no move is present +P1MOVES = [0xD173, 0xD174, 0xD175, 0xD176] +P2MOVES = [0xD19F, 0xD1A0, 0xD1A1, 0xD1A2] +P3MOVES = [0xD1CB, 0xD1CC, 0xD1CD, 0xD1CE] +P4MOVES = [0xD1F7, 0xD1F8, 0xD1F9, 0xD1FA] +P5MOVES = [0xD223, 0xD224, 0xD225, 0xD226] +P6MOVES = [0xD24F, 0xD250, 0xD251, 0xD252] + +P1MOVEPP = [0xD188, 0xD189, 0xD18A, 0xD18B] +P2MOVEPP = [0xD1B4, 0xD1B5, 0xD1B6, 0xD1B7] +P3MOVEPP = [0xD1E0, 0xD1E1, 0xD1E2, 0xD1E3] +P4MOVEPP = [0xD20C, 0xD20D, 0xD20E, 0xD20F] +P5MOVEPP = [0xD238, 0xD239, 0xD23A, 0xD23B] +P6MOVEPP = [0xD264, 0xD265, 0xD266, 0xD267] + +POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] # - Pokémon (Again) +STATUS = [0xD16F, 0xD19B, 0xD1C7, 0xD1F3, 0xD21F, 0xD24B] # - Status (Poisoned, Paralyzed, etc.) +TYPE1 = [0xD170, 0xD19C, 0xD1C8, 0xD1F4, 0xD220, 0xD24C] # - Type 1 +TYPE2 = [0xD171, 0xD19D, 0xD1C9, 0xD1F5, 0xD221, 0xD24D] # - Type 2 +LEVEL = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] # - Level (actual level) +MAXHP = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] # - Max HP if = 01 + 256 to MAXHP2 value +CHP = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] # - Current HP if = 01 + 256 + + + +STATUSDICT = { + 0x03: 'Poison', + 0x04: 'Burn', + 0x05: 'Frozen', + 0x06: 'Paralyze', + 0x00: 'None' +} + def bcd(num): return 10 * ((num >> 4) & 0x0f) + (num & 0x0f) @@ -47,6 +81,130 @@ def position(game): map_n = game.get_memory_value(MAP_N_ADDR) return r_pos, c_pos, map_n +#start new functions + +def pokemon(game): + poke = [game.get_memory_value(a) for a in POKE] + stat = [game.get_memory_value(a) for a in STATUS] + # status = STATUSDICT.get(stat, 'Unknown') + type1 = [game.get_memory_value(a) for a in TYPE1] + type2 = [game.get_memory_value(a) for a in TYPE2] + level = [game.get_memory_value(a) for a in LEVEL] + mhp = [read_uint16(game, a) for a in MAXHP] + chp = [read_uint16(game, a) for a in CHP] + hp = [] + # moves = [game.get_memory_value(addr) for addr in P1MOVES] + # movepp = [game.get_memory_value(addr) for addr in P1MOVEPP] + assert len(mhp) == len(chp) + for h, i in zip(mhp, chp): + if h == 0: + j = 1 + else: + j = i / h + hp.append(j) + return poke, type1, type2, level, hp #, status + +# def p1(game): +# p1chp = read_uint16(game, P1CHP) +# p1mhp = read_uint16(game, P1MHP) +# p1moves = [game.get_memory_value(addr) for addr in P1MOVES] +# p1movepp = [game.get_memory_value(addr) for addr in P1MOVEPP] +# p1lvl = game.get_memory_value(P1LVL) +# p1stat = game.get_memory_value(P1STAT) +# p1status = STATUSDICT.get(p1stat, 'Unknown') +# p1t1 = game.get_memory_value(P1T1) +# p1t2 = game.get_memory_value(P1T2) +# if p1mhp == 0: +# p1hp = 1 +# else: +# p1hp = p1chp / p1mhp +# return p1moves, p1movepp, p1lvl, p1status, p1t1, p1t2, p1hp + +# def p2(game): +# p2chp = read_uint16(game, P2CHP) +# p2mhp = read_uint16(game, P2MHP) +# p2moves = [game.get_memory_value(addr) for addr in P2MOVES] +# p2movepp = [game.get_memory_value(addr) for addr in P2MOVEPP] +# p2lvl = game.get_memory_value(P2LVL) +# p2stat = game.get_memory_value(P2STAT) +# p2status = STATUSDICT.get(p2stat, 'Unknown') +# p2t1 = game.get_memory_value(P2T1) +# p2t2 = game.get_memory_value(P2T2) +# if p2mhp == 0: +# p2hp = 1 +# else: +# p2hp = p2chp / p2mhp +# return p2moves, p2movepp, p2lvl, p2status, p2t1, p2t2, p2hp + +# def p3(game): +# p3chp = read_uint16(game, P3CHP) +# p3mhp = read_uint16(game, P3MHP) +# p3moves = [game.get_memory_value(addr) for addr in P3MOVES] +# p3movepp = [game.get_memory_value(addr) for addr in P3MOVEPP] +# p3lvl = game.get_memory_value(P3LVL) +# p3stat = game.get_memory_value(P3STAT) +# p3status = STATUSDICT.get(p3stat, 'Unknown') +# p3t1 = game.get_memory_value(P3T1) +# p3t2 = game.get_memory_value(P3T2) +# if p3mhp == 0: +# p3hp = 1 +# else: +# p3hp = p3chp / p3mhp +# return p3moves, p3movepp, p3lvl, p3status, p3t1, p3t2, p3hp + +# def p4(game): +# p4chp = read_uint16(game, P4CHP) +# p4mhp = read_uint16(game, P4MHP) +# p4moves = [game.get_memory_value(addr) for addr in P4MOVES] +# p4movepp = [game.get_memory_value(addr) for addr in P4MOVEPP] +# p4lvl = game.get_memory_value(P4LVL) +# p4stat = game.get_memory_value(P4STAT) +# p4status = STATUSDICT.get(p4stat, 'Unknown') +# p4t1 = game.get_memory_value(P4T1) +# p4t2 = game.get_memory_value(P4T2) +# if p4mhp == 0: +# p4hp = 1 +# else: +# p4hp = p4chp / p4mhp +# return p4moves, p4movepp, p4lvl, p4status, p4t1, p4t2, p4hp + +# def p5(game): +# p5chp = read_uint16(game, P5CHP) +# p5mhp = read_uint16(game, P5MHP) +# p5moves = [game.get_memory_value(addr) for addr in P5MOVES] +# p5movepp = [game.get_memory_value(addr) for addr in P5MOVEPP] +# p5lvl = game.get_memory_value(P5LVL) +# p5stat = game.get_memory_value(P5STAT) +# p5status = STATUSDICT.get(p5stat, 'Unknown') +# p5t1 = game.get_memory_value(P5T1) +# p5t2 = game.get_memory_value(P5T2) +# if p5mhp == 0: +# p5hp = 1 +# else: +# p5hp = p5chp / p5mhp +# return p5moves, p5movepp, p5lvl, p5status, p5t1, p5t2, p5hp + +# def p6(game): +# p6chp = read_uint16(game, P6CHP) +# p6mhp = read_uint16(game, P6MHP) +# p6moves = [game.get_memory_value(addr) for addr in P6MOVES] +# p6movepp = [game.get_memory_value(addr) for addr in P6MOVEPP] +# p6lvl = game.get_memory_value(P6LVL) +# p6stat = game.get_memory_value(P6STAT) +# p6status = STATUSDICT.get(p6stat, 'Unknown') +# p6t1 = game.get_memory_value(P6T1) +# p6t2 = game.get_memory_value(P6T2) +# if p6mhp == 0: +# p6hp = 1 +# else: +# p6hp = p6chp / p6mhp +# return p6moves, p6movepp, p6lvl, p6status, p6t1, p6t2, p6hp + +# # def move_check(game): + + +#end new functions + def party(game): party = [game.get_memory_value(addr) for addr in PARTY_ADDR] party_size = game.get_memory_value(PARTY_SIZE_ADDR) @@ -99,3 +257,75 @@ def events(game): # Omit 13 events by default return max(num_events - 13 - museum_ticket, 0) + + + +# Menu Data + +# Coordinates of the position of the cursor for the top menu item (id 0) +# CC24 : Y position +# CC25 : X position + +# CC26 - Currently selected menu item (topmost is 0) +# CC27 - Tile "hidden" by the menu cursor +# CC28 - ID of the last menu item +# CC29 - bitmask applied to the key port for the current menu +# CC2A - ID of the previously selected menu item +# CC2B - Last position of the cursor on the party / Bill's PC screen +# CC2C - Last position of the cursor on the item screen +# CC2D - Last position of the cursor on the START / battle menu +# CC2F - Index (in party) of the Pokémon currently sent out +# CC30~CC31 - Pointer to cursor tile in C3A0 buffer +# CC36 - ID of the first displayed menu item +# CC35 - Item highlighted with Select (01 = first item, 00 = no item, etc.) +# CC3A and CC3B are unused + + +# Pokémon Mart + +# JPN addr. INT addr. Description +# CF62 CF7B Total Items +# CF63 CF7C Item 1 +# CF64 CF7D Item 2 +# CF65 CF7E Item 3 +# CF66 CF7F Item 4 +# CF67 CF80 Item 5 +# CF68 CF81 Item 6 +# CF69 CF82 Item 7 +# CF70 CF83 Item 8 +# CF71 CF84 Item 9 +# CF72 CF85 Item 10 + + +# Event Flags + +# D5A6 to D5C5 : Missable Objects Flags (flags for every (dis)appearing sprites, like the guard in Cerulean City or the Pokéballs in Oak's Lab) +# D5AB - Starters Back? +# D5C0(bit 1) - 0=Mewtwo appears, 1=Doesn't (See D85F) +# D5F3 - Have Town map? +# D60D - Have Oak's Parcel? +# D700 - Bike Speed +# D70B - Fly Anywhere Byte 1 +# D70C - Fly Anywhere Byte 2 +# D70D - Safari Zone Time Byte 1 +# D70E - Safari Zone Time Byte 2 +# D710 - Fossilized Pokémon? +# D714 - Position in Air +# D72E - Did you get Lapras Yet? +# D732 - Debug New Game +# D751 - Fought Giovanni Yet? +# D755 - Fought Brock Yet? +# D75E - Fought Misty Yet? +# D773 - Fought Lt. Surge Yet? +# D77C - Fought Erika Yet? +# D782 - Fought Articuno Yet? +# D790 - If bit 7 is set, Safari Game over +# D792 - Fought Koga Yet? +# D79A - Fought Blaine Yet? +# D7B3 - Fought Sabrina Yet? +# D7D4 - Fought Zapdos Yet? +# D7D8 - Fought Snorlax Yet (Vermilion) +# D7E0 - Fought Snorlax Yet? (Celadon) +# D7EE - Fought Moltres Yet? +# D803 - Is SS Anne here? +# D85F - Mewtwo can be caught if bit 2 clear - Needs D5C0 bit 1 clear, too \ No newline at end of file From b425401457b1445032ef4531fd150a923198828f Mon Sep 17 00:00:00 2001 From: leanke Date: Tue, 12 Dec 2023 01:49:26 +0000 Subject: [PATCH 02/29] fixed screen --- pokegym/environment.py | 123 +++++++++++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 18 deletions(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index 08993df..c3782e1 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -1,7 +1,9 @@ from pdb import set_trace as T from gymnasium import Env, spaces import numpy as np -import os +from collections import defaultdict +import io, os +import random from pokegym.pyboy_binding import (ACTIONS, make_env, open_state_file, load_pyboy_state, run_action_on_emulator) @@ -59,32 +61,94 @@ def play(): print(f"new Reward: {reward}\n") class Base: - def __init__(self, rom_path='pokemon_red.gb', - state_path=None, headless=True, quiet=False, **kwargs): - '''Creates a PokemonRed environment''' + def __init__( + self, + rom_path="pokemon_red.gb", + state_path=None, + headless=True, + quiet=False, + **kwargs, + ): + """Creates a PokemonRed environment""" if state_path is None: - state_path = __file__.rstrip('environment.py') + 'has_pokedex_nballs.state' # 'mtmoon.state' + state_path = __file__.rstrip("environment.py") + "has_pokedex_nballs.state" - self.game, self.screen = make_env( - rom_path, headless, quiet, **kwargs) + self.game, self.screen = make_env(rom_path, headless, quiet, **kwargs) - self.initial_state = open_state_file(state_path) + self.initial_states = [open_state_file(state_path)] self.headless = headless + self.mem_padding = 2 + self.memory_shape = 80 + self.use_screen_memory = True R, C = self.screen.raw_screen_buffer_dims() + self.obs_size = (R // 2, C // 2) + + if self.use_screen_memory: + self.screen_memory = defaultdict( + lambda: np.zeros((255, 255, 1), dtype=np.uint8) + ) + self.obs_size += (4,) + else: + self.obs_size += (3,) self.observation_space = spaces.Box( - low=0, high=255, dtype=np.uint8, - shape=(R//2, C//2, 3), + low=0, high=255, dtype=np.uint8, shape=self.obs_size ) self.action_space = spaces.Discrete(len(ACTIONS)) + def save_state(self): + state = io.BytesIO() + state.seek(0) + self.game.save_state(state) + self.initial_states.append(state) + + def load_random_state(self): + rand_idx = random.randint(0, len(self.initial_states) - 1) + return self.initial_states[rand_idx] + def reset(self, seed=None, options=None): - '''Resets the game. Seeding is NOT supported''' - load_pyboy_state(self.game, self.initial_state) + """Resets the game. Seeding is NOT supported""" + load_pyboy_state(self.game, self.load_random_state()) return self.screen.screen_ndarray(), {} + def get_fixed_window(self, arr, y, x, window_size): + height, width, _ = arr.shape + h_w, w_w = window_size[0] // 2, window_size[1] // 2 + + y_min = max(0, y - h_w) + y_max = min(height, y + h_w + (window_size[0] % 2)) + x_min = max(0, x - w_w) + x_max = min(width, x + w_w + (window_size[1] % 2)) + + window = arr[y_min:y_max, x_min:x_max] + + pad_top = h_w - (y - y_min) + pad_bottom = h_w + (window_size[0] % 2) - 1 - (y_max - y - 1) + pad_left = w_w - (x - x_min) + pad_right = w_w + (window_size[1] % 2) - 1 - (x_max - x - 1) + + return np.pad( + window, + ((pad_top, pad_bottom), (pad_left, pad_right), (0, 0)), + mode="constant", + ) + def render(self): - return self.screen.screen_ndarray() + if self.use_screen_memory: + r, c, map_n = ram_map.position(self.game) + # Update tile map + mmap = self.screen_memory[map_n] + mmap[r, c] = 255 + + return np.concatenate( + ( + self.screen.screen_ndarray()[::2, ::2], + self.get_fixed_window(mmap, r, c, self.observation_space.shape), + ), + axis=2, + ) + else: + return self.screen.screen_ndarray()[::2, ::2] def step(self, action): run_action_on_emulator(self.game, self.screen, ACTIONS[action], self.headless) @@ -93,7 +157,6 @@ def step(self, action): def close(self): self.game.stop(False) - class Environment(Base): def __init__(self, rom_path='pokemon_red.gb', state_path=None, headless=True, quiet=False, verbose=False, **kwargs): @@ -102,12 +165,18 @@ def __init__(self, rom_path='pokemon_red.gb', self.verbose = verbose def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): - '''Resets the game. Seeding is NOT supported''' - load_pyboy_state(self.game, self.initial_state) + """Resets the game. Seeding is NOT supported""" + load_pyboy_state(self.game, self.load_random_state()) + + if self.use_screen_memory: + self.screen_memory = defaultdict( + lambda: np.zeros((255, 255, 1), dtype=np.uint8) + ) self.time = 0 self.max_episode_steps = max_episode_steps self.reward_scale = reward_scale + self.prev_map_n = None self.max_events = 0 self.max_level_sum = 0 @@ -124,13 +193,25 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.last_hp = [0] * 6 self.death = 0 - return self.render()[::2, ::2], {} + return self.render(), {} def step(self, action, fast_video=True): run_action_on_emulator(self.game, self.screen, ACTIONS[action], self.headless, fast_video=fast_video) self.time += 1 + + + # # Exploration reward + # r, c, map_n = ram_map.position(self.game) + # self.seen_coords.add((r, c, map_n)) + # exploration_reward = 0.01 * len(self.seen_coords) + # glob_r, glob_c = game_map.local_to_global(r, c, map_n) + # try: + # self.counts_map[glob_r, glob_c] += 1 + # except: + # pass + # Exploration reward r, c, map_n = ram_map.position(self.game) self.seen_coords.add((r, c, map_n)) @@ -139,6 +220,12 @@ def step(self, action, fast_video=True): map_reward = 1.0 * len(self.seen_maps) exploration_reward = coord_reward + map_reward + if map_n != self.prev_map_n: + self.prev_map_n = map_n + if map_n not in self.seen_maps: + self.seen_maps.add(map_n) + self.save_state() + glob_r, glob_c = game_map.local_to_global(r, c, map_n) try: self.counts_map[glob_r, glob_c] += 1 @@ -298,4 +385,4 @@ def step(self, action, fast_video=True): f'Info: {info}', ) - return self.render()[::2, ::2], reward, done, done, info + return self.render(), reward, done, done, info From 37a5ddb73fe542bcc4e71db9f8aff875224015fd Mon Sep 17 00:00:00 2001 From: leanke Date: Thu, 14 Dec 2023 16:43:48 +0000 Subject: [PATCH 03/29] video recording --- pokegym/environment.py | 163 ++++++++++++++++++------------- pokegym/map_data.json | 0 pokegym/pyboy_binding.py | 2 + pokegym/ram_map.py | 203 ++++++++++++++++----------------------- 4 files changed, 181 insertions(+), 187 deletions(-) mode change 100755 => 100644 pokegym/map_data.json diff --git a/pokegym/environment.py b/pokegym/environment.py index c3782e1..43201c7 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -1,15 +1,20 @@ from pdb import set_trace as T +import uuid from gymnasium import Env, spaces import numpy as np from collections import defaultdict import io, os import random +from pathlib import Path +import mediapy as media from pokegym.pyboy_binding import (ACTIONS, make_env, open_state_file, load_pyboy_state, run_action_on_emulator) from pokegym import ram_map, game_map + + def play(): '''Creates an environment and plays it''' env = Environment(rom_path='pokemon_red.gb', state_path=None, headless=False, @@ -66,6 +71,7 @@ def __init__( rom_path="pokemon_red.gb", state_path=None, headless=True, + save_video=False, quiet=False, **kwargs, ): @@ -73,13 +79,17 @@ def __init__( if state_path is None: state_path = __file__.rstrip("environment.py") + "has_pokedex_nballs.state" - self.game, self.screen = make_env(rom_path, headless, quiet, **kwargs) + self.game, self.screen = make_env(rom_path, headless, quiet, save_video=True, **kwargs) self.initial_states = [open_state_file(state_path)] + self.save_video = save_video self.headless = headless self.mem_padding = 2 self.memory_shape = 80 self.use_screen_memory = True + self.s_path = Path(f'session_{str(uuid.uuid4())[:8]}') + self.instance_id = str(uuid.uuid4())[:8] + self.reset_count = 0 R, C = self.screen.raw_screen_buffer_dims() self.obs_size = (R // 2, C // 2) @@ -136,9 +146,9 @@ def get_fixed_window(self, arr, y, x, window_size): def render(self): if self.use_screen_memory: r, c, map_n = ram_map.position(self.game) - # Update tile map mmap = self.screen_memory[map_n] - mmap[r, c] = 255 + if 0 <= r < mmap.shape[0] and 0 <= c < mmap.shape[1]: + mmap[r, c] = 255 return np.concatenate( ( @@ -149,6 +159,10 @@ def render(self): ) else: return self.screen.screen_ndarray()[::2, ::2] + + def video(self): + video = self.screen.screen_ndarray() + return video def step(self, action): run_action_on_emulator(self.game, self.screen, ACTIONS[action], self.headless) @@ -159,15 +173,28 @@ def close(self): class Environment(Base): def __init__(self, rom_path='pokemon_red.gb', - state_path=None, headless=True, quiet=False, verbose=False, **kwargs): - super().__init__(rom_path, state_path, headless, quiet, **kwargs) + state_path=None, headless=True, save_video=True, quiet=False, verbose=False, **kwargs): + super().__init__(rom_path, state_path, headless, save_video, quiet, **kwargs) self.counts_map = np.zeros((444, 365)) self.verbose = verbose + def add_video_frame(self): + self.full_frame_writer.add_image(self.video()) + + def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" load_pyboy_state(self.game, self.load_random_state()) + if self.save_video: + base_dir = self.s_path + base_dir.mkdir(parents=True, exist_ok=True) + full_name = Path(f'reset_{self.reset_count}').with_suffix('.mp4') + self.full_frame_writer = media.VideoWriter(base_dir / full_name, (144, 160), fps=60) + self.full_frame_writer.__enter__() + + + if self.use_screen_memory: self.screen_memory = defaultdict( lambda: np.zeros((255, 255, 1), dtype=np.uint8) @@ -177,53 +204,52 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.max_episode_steps = max_episode_steps self.reward_scale = reward_scale self.prev_map_n = None - + self.max_events = 0 self.max_level_sum = 0 self.max_opponent_level = 0 - self.seen_coords = set() self.seen_maps = set() - self.total_healing = 0 self.last_party_size = 1 self.last_reward = None - - self.lvl = 0 self.last_hp = [0] * 6 self.death = 0 + self.qty = 0 + self.reset_count += 1 return self.render(), {} def step(self, action, fast_video=True): run_action_on_emulator(self.game, self.screen, ACTIONS[action], self.headless, fast_video=fast_video) + self.time += 1 + if self.save_video: + self.add_video_frame() - - - # # Exploration reward - # r, c, map_n = ram_map.position(self.game) - # self.seen_coords.add((r, c, map_n)) - # exploration_reward = 0.01 * len(self.seen_coords) - # glob_r, glob_c = game_map.local_to_global(r, c, map_n) - # try: - # self.counts_map[glob_r, glob_c] += 1 - # except: - # pass - - # Exploration reward + # Functions / Variables r, c, map_n = ram_map.position(self.game) + party, party_size, party_levels = ram_map.party(self.game) + poke, type1, type2, level, hp, status = ram_map.pokemon(self.game) self.seen_coords.add((r, c, map_n)) - self.seen_maps.add(map_n) coord_reward = 0.01 * len(self.seen_coords) - map_reward = 1.0 * len(self.seen_maps) - exploration_reward = coord_reward + map_reward + badges = ram_map.badges(self.game) + # Constants + map_check = set({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 41, 58, 64, 68, 89}) + item_check = ({1, 2, 3, 4, 6, 11, 16, 17, 18, 19, 20, 41, 42, 72, 73, 196, 197, 198, 199, 200, 53, 54}) + party_size_constant = party_size == self.last_party_size + level_rewards = [] + + # Math + + # Save and Map if map_n != self.prev_map_n: self.prev_map_n = map_n - if map_n not in self.seen_maps: + if map_n not in self.seen_maps and map_n in map_check: self.seen_maps.add(map_n) + # print(f'Map saved:', map_n) self.save_state() glob_r, glob_c = game_map.local_to_global(r, c, map_n) @@ -232,72 +258,71 @@ def step(self, action, fast_video=True): except: pass - # Level reward - party, party_size, party_levels = ram_map.party(self.game) - - # Party rewards - party_size_constant = party_size == self.last_party_size - - poke, type1, type2, level, hp = ram_map.pokemon(self.game) # status, - # Level - level_rewards = [] + #Levels for lvl in level: if lvl < 15: level_reward = .5 * lvl else: level_reward = 7.5 + (lvl - 15) / 4 level_rewards.append(level_reward) + # HP / Death assert len(hp) == len(self.last_hp) for h, i in zip(hp, self.last_hp): hp_delta = h - i - if hp_delta > 0 and party_size_constant: + if hp_delta > 0.25 and party_size_constant: #updated from 0 to 0.25 self.total_healing += hp_delta if h <= 0 and i > 0: self.death += 1 i = h + + # Reward / Random Values lvl_rew = sum(level_rewards) - healing_reward = self.total_healing + healing_reward = self.total_healing/party_size self.max_level_sum = sum(level) - self.last_party_size = party_size - death_reward = -0.05 * self.death + death_reward = -0.01 * self.death + map_reward = 1.0 * len(self.seen_maps) + exploration_reward = coord_reward + map_reward + badges_reward = 5 * badges - # # Update last known values for next iteration + # Update Values self.last_hp = hp self.last_party_size = party_size + + + # Testing + # # Items + # item, qty = ram_map.item_bag(self.game) + # assert len(item) == len(qty) + # for i, q in zip(item, qty): + # if i in item_check: + # self.qty += .01 * q - # # Set rewards - # healing_reward = self.total_healing - # death_reward = -0.05 * self.death_count + #TODO - # Opponent level reward + # # Opponent level reward # max_opponent_level = max(ram_map.opponent(self.game)) # self.max_opponent_level = max(self.max_opponent_level, max_opponent_level) # opponent_level_reward = 0.2 * self.max_opponent_level - # Badge reward - badges = ram_map.badges(self.game) - badges_reward = 5 * badges - # Event reward - events = ram_map.events(self.game) - self.max_events = max(self.max_events, events) - event_reward = self.max_events + # # Event reward + # events = ram_map.events(self.game) + # self.max_events = max(self.max_events, events) + # event_reward = self.max_events - #money reward - money = ram_map.money(self.game) + # # money reward + # money = ram_map.money(self.game) - #sum reward - reward = self.reward_scale * (event_reward + lvl_rew + death_reward + badges_reward + exploration_reward + healing_reward) # + healing_reward - - reward1 = (event_reward + lvl_rew + badges_reward + exploration_reward + healing_reward) # + healing_reward - + # sum reward + reward = self.reward_scale * (lvl_rew + death_reward + badges_reward + exploration_reward + healing_reward) # + healing_reward + reward1 = (lvl_rew + badges_reward + exploration_reward + healing_reward) # + healing_reward if death_reward == 0: neg_reward = 1 else: neg_reward = death_reward - #print rewards + # print rewards if self.headless == False: print(f'-------------Counter-------------') print(f'Steps:',self.time,) @@ -306,6 +331,7 @@ def step(self, action, fast_video=True): print(f'Total Level:',self.max_level_sum) print(f'Levels:',level) print(f'HP:',hp) + print(f'Status:',status) print(f'Deaths:',self.death) print(f'Total Heal:',self.total_healing) print(f'Party Size:',self.last_party_size) @@ -314,9 +340,8 @@ def step(self, action, fast_video=True): print(f'Explore:',exploration_reward,'--%',100 * (exploration_reward/reward1)) print(f'Healing:',healing_reward,'--%',100 * (healing_reward/reward1)) print(f'Badges:',badges_reward,'--%',100 * (badges_reward/reward1)) - #print(f'Op Level:',opponent_level_reward,'--%',100 * (opponent_level_reward/reward1)) print(f'Level:',lvl_rew,'--%',100 * (lvl_rew/reward1)) - print(f'Events:',event_reward,'--%',100 * (event_reward/reward1)) + # print(f'Events:',event_reward,'--%',100 * (event_reward/reward1)) print(f'-------------Negatives-------------') print(f'Total:',neg_reward) print(f'Deaths:',death_reward, '--%', 100 * (death_reward/neg_reward)) @@ -327,7 +352,7 @@ def step(self, action, fast_video=True): # print(f'P4--','Lvl:',p4lvl,', Status:',p4status,', HP:',self.last_p4hp,', Deaths:',self.p4death) # print(f'P5--','Lvl:',p5lvl,', Status:',p5status,', HP:',self.last_p5hp,', Deaths:',self.p5death) # print(f'P6--','Lvl:',p6lvl,', Status:',p6status,', HP:',self.last_p6hp,', Deaths:',self.p6death) - # print(f'Coords:',self.seen_coords) + # print(f'Coords:',self.seen_maps) # print(f'Dest_status:',self.dest_reward,'--%',100 * (self.dest_reward/neg_reward)) # print(f'-------------Test-------------') # print(f'Last Health:',self.last_health) @@ -346,11 +371,13 @@ def step(self, action, fast_video=True): info = {} done = self.time >= self.max_episode_steps + if self.save_video and done: + self.full_frame_writer.close() if done: info = { 'reward': { 'delta': reward, - 'event': event_reward, + # 'event': event_reward, 'level': level_reward, #'opponent_level': opponent_level_reward, 'death': death_reward, @@ -365,8 +392,8 @@ def step(self, action, fast_video=True): 'deaths': self.death, 'badge_1': float(badges == 1), 'badge_2': float(badges > 1), - 'event': events, - 'money': money, + # 'event': events, + # 'money': money, 'pokemon_exploration_map': self.counts_map, } @@ -379,8 +406,8 @@ def step(self, action, fast_video=True): f'death: {death_reward}', #f'op_level: {opponent_level_reward}', f'badges reward: {badges_reward}', - f'event reward: {event_reward}', - f'money: {money}', + # f'event reward: {event_reward}', + # f'money: {money}', f'ai reward: {reward}', f'Info: {info}', ) diff --git a/pokegym/map_data.json b/pokegym/map_data.json old mode 100755 new mode 100644 diff --git a/pokegym/pyboy_binding.py b/pokegym/pyboy_binding.py index 61c2581..cbd9002 100644 --- a/pokegym/pyboy_binding.py +++ b/pokegym/pyboy_binding.py @@ -5,6 +5,8 @@ from pyboy import logger from pyboy.utils import WindowEvent + + logger.logger.setLevel('ERROR') diff --git a/pokegym/ram_map.py b/pokegym/ram_map.py index 9fad91f..abaf014 100644 --- a/pokegym/ram_map.py +++ b/pokegym/ram_map.py @@ -23,7 +23,7 @@ MONEY_ADDR_1 = 0xD347 MONEY_ADDR_100 = 0xD348 MONEY_ADDR_10000 = 0xD349 - +BAG = [0xD31E, 0xD320, 0xD322, 0xD324, 0xD326, 0xD328, 0xD32A, 0xD32C, 0xD32E, 0xD330, 0xD332, 0xD334, 0xD336, 0xD338, 0xD33A, 0xD33C, 0xD33E, 0xD340, 0xD342, 0xD344] #Trainer Moves/PP counter if 00 then no move is present @@ -52,11 +52,11 @@ STATUSDICT = { - 0x03: 'Poison', - 0x04: 'Burn', - 0x05: 'Frozen', - 0x06: 'Paralyze', - 0x00: 'None' + 0x08: 'Poison', + # 0x04: 'Burn', + # 0x05: 'Frozen', + # 0x06: 'Paralyze', + 0x00: 'None', } def bcd(num): @@ -84,15 +84,19 @@ def position(game): #start new functions def pokemon(game): + status = [] poke = [game.get_memory_value(a) for a in POKE] stat = [game.get_memory_value(a) for a in STATUS] - # status = STATUSDICT.get(stat, 'Unknown') + for i in stat: + s = STATUSDICT.get(i, 'Unknown') + status.append(s) type1 = [game.get_memory_value(a) for a in TYPE1] type2 = [game.get_memory_value(a) for a in TYPE2] level = [game.get_memory_value(a) for a in LEVEL] mhp = [read_uint16(game, a) for a in MAXHP] chp = [read_uint16(game, a) for a in CHP] hp = [] + # moves = [game.get_memory_value(addr) for addr in P1MOVES] # movepp = [game.get_memory_value(addr) for addr in P1MOVEPP] assert len(mhp) == len(chp) @@ -102,105 +106,8 @@ def pokemon(game): else: j = i / h hp.append(j) - return poke, type1, type2, level, hp #, status - -# def p1(game): -# p1chp = read_uint16(game, P1CHP) -# p1mhp = read_uint16(game, P1MHP) -# p1moves = [game.get_memory_value(addr) for addr in P1MOVES] -# p1movepp = [game.get_memory_value(addr) for addr in P1MOVEPP] -# p1lvl = game.get_memory_value(P1LVL) -# p1stat = game.get_memory_value(P1STAT) -# p1status = STATUSDICT.get(p1stat, 'Unknown') -# p1t1 = game.get_memory_value(P1T1) -# p1t2 = game.get_memory_value(P1T2) -# if p1mhp == 0: -# p1hp = 1 -# else: -# p1hp = p1chp / p1mhp -# return p1moves, p1movepp, p1lvl, p1status, p1t1, p1t2, p1hp - -# def p2(game): -# p2chp = read_uint16(game, P2CHP) -# p2mhp = read_uint16(game, P2MHP) -# p2moves = [game.get_memory_value(addr) for addr in P2MOVES] -# p2movepp = [game.get_memory_value(addr) for addr in P2MOVEPP] -# p2lvl = game.get_memory_value(P2LVL) -# p2stat = game.get_memory_value(P2STAT) -# p2status = STATUSDICT.get(p2stat, 'Unknown') -# p2t1 = game.get_memory_value(P2T1) -# p2t2 = game.get_memory_value(P2T2) -# if p2mhp == 0: -# p2hp = 1 -# else: -# p2hp = p2chp / p2mhp -# return p2moves, p2movepp, p2lvl, p2status, p2t1, p2t2, p2hp - -# def p3(game): -# p3chp = read_uint16(game, P3CHP) -# p3mhp = read_uint16(game, P3MHP) -# p3moves = [game.get_memory_value(addr) for addr in P3MOVES] -# p3movepp = [game.get_memory_value(addr) for addr in P3MOVEPP] -# p3lvl = game.get_memory_value(P3LVL) -# p3stat = game.get_memory_value(P3STAT) -# p3status = STATUSDICT.get(p3stat, 'Unknown') -# p3t1 = game.get_memory_value(P3T1) -# p3t2 = game.get_memory_value(P3T2) -# if p3mhp == 0: -# p3hp = 1 -# else: -# p3hp = p3chp / p3mhp -# return p3moves, p3movepp, p3lvl, p3status, p3t1, p3t2, p3hp - -# def p4(game): -# p4chp = read_uint16(game, P4CHP) -# p4mhp = read_uint16(game, P4MHP) -# p4moves = [game.get_memory_value(addr) for addr in P4MOVES] -# p4movepp = [game.get_memory_value(addr) for addr in P4MOVEPP] -# p4lvl = game.get_memory_value(P4LVL) -# p4stat = game.get_memory_value(P4STAT) -# p4status = STATUSDICT.get(p4stat, 'Unknown') -# p4t1 = game.get_memory_value(P4T1) -# p4t2 = game.get_memory_value(P4T2) -# if p4mhp == 0: -# p4hp = 1 -# else: -# p4hp = p4chp / p4mhp -# return p4moves, p4movepp, p4lvl, p4status, p4t1, p4t2, p4hp - -# def p5(game): -# p5chp = read_uint16(game, P5CHP) -# p5mhp = read_uint16(game, P5MHP) -# p5moves = [game.get_memory_value(addr) for addr in P5MOVES] -# p5movepp = [game.get_memory_value(addr) for addr in P5MOVEPP] -# p5lvl = game.get_memory_value(P5LVL) -# p5stat = game.get_memory_value(P5STAT) -# p5status = STATUSDICT.get(p5stat, 'Unknown') -# p5t1 = game.get_memory_value(P5T1) -# p5t2 = game.get_memory_value(P5T2) -# if p5mhp == 0: -# p5hp = 1 -# else: -# p5hp = p5chp / p5mhp -# return p5moves, p5movepp, p5lvl, p5status, p5t1, p5t2, p5hp - -# def p6(game): -# p6chp = read_uint16(game, P6CHP) -# p6mhp = read_uint16(game, P6MHP) -# p6moves = [game.get_memory_value(addr) for addr in P6MOVES] -# p6movepp = [game.get_memory_value(addr) for addr in P6MOVEPP] -# p6lvl = game.get_memory_value(P6LVL) -# p6stat = game.get_memory_value(P6STAT) -# p6status = STATUSDICT.get(p6stat, 'Unknown') -# p6t1 = game.get_memory_value(P6T1) -# p6t2 = game.get_memory_value(P6T2) -# if p6mhp == 0: -# p6hp = 1 -# else: -# p6hp = p6chp / p6mhp -# return p6moves, p6movepp, p6lvl, p6status, p6t1, p6t2, p6hp - -# # def move_check(game): + return poke, type1, type2, level, hp, status + #end new functions @@ -228,18 +135,6 @@ def pokemon_caught(game): caught_bytes = [game.get_memory_value(addr) for addr in CAUGHT_POKE_ADDR] return sum([bit_count(b) for b in caught_bytes]) -def hp(game): - '''Percentage of total party HP''' - party_hp = [read_uint16(game, addr) for addr in HP_ADDR] - party_max_hp = [read_uint16(game, addr) for addr in MAX_HP_ADDR] - - # Avoid division by zero if no pokemon - sum_max_hp = sum(party_max_hp) - if sum_max_hp == 0: - return 1 - - return sum(party_hp) / sum_max_hp - def money(game): return (100 * 100 * bcd(game.get_memory_value(MONEY_ADDR_1)) + 100 * bcd(game.get_memory_value(MONEY_ADDR_100)) @@ -250,6 +145,10 @@ def badges(game): return bit_count(badges) def events(game): +# D710 - Fossilized Pokémon? +# D7D8 - Fought Snorlax Yet (Vermilion) +# D7E0 - Fought Snorlax Yet? (Celadon) +# D803 - Is SS Anne here? '''Adds up all event flags, exclude museum ticket''' num_events = sum(bit_count(game.get_memory_value(i)) for i in range(EVENT_FLAGS_START_ADDR, EVENT_FLAGS_END_ADDR)) @@ -279,6 +178,7 @@ def events(game): # CC36 - ID of the first displayed menu item # CC35 - Item highlighted with Select (01 = first item, 00 = no item, etc.) # CC3A and CC3B are unused +# cc51 and cc52 both read 00 when menu is closed # Pokémon Mart @@ -328,4 +228,69 @@ def events(game): # D7E0 - Fought Snorlax Yet? (Celadon) # D7EE - Fought Moltres Yet? # D803 - Is SS Anne here? -# D85F - Mewtwo can be caught if bit 2 clear - Needs D5C0 bit 1 clear, too \ No newline at end of file +# D85F - Mewtwo can be caught if bit 2 clear - Needs D5C0 bit 1 clear, too + + +# def items(game, start_addr): +# item = game.get_memory_value(start_addr) +# qty = game.get_memory_value(start_addr + 1) +# return item, qty + +# def item_bag(game): +# item, qty = [items(game, a) for a in BAG] +# return item, qty + + +# 1, 2, 3, 4, 6, 11, 16, 17, 18, 19, 20, 41, 42, 72, 73, 196, 197, 198, 199, 200, 53, 54 + + +# 001 0x01 Master Ball +# 002 0x02 Ultra Ball +# 003 0x03 Great Ball +# 004 0x04 Poké Ball +# 006 0x06 Bicycle +# 011 0x0B Antidote +# 016 0x10 Full Restore +# 017 0x11 Max Potion +# 018 0x12 Hyper Potion +# 019 0x13 Super Potion +# 020 0x14 Potion +# 041 0x29 Dome Fossil +# 042 0x2A Helix Fossil +# 072 0x48 Silph Scope +# 073 0x49 Poké Flute +# 196 0xC4 HM01 +# 197 0xC5 HM02 +# 198 0xC6 HM03 +# 199 0xC7 HM04 +# 200 0xC8 HM05 +# 053 0x35 Revive +# 054 0x36 Max Revive + + + + + + + # 0xD31D - Total Items + # 0xD31E - Item 1 + # 0xD320 - Item 2 + # 0xD322 - Item 3 + # 0xD324 - Item 4 + # 0xD326 - Item 5 + # 0xD328 - Item 6 + # 0xD32A - Item 7 + # 0xD32C - Item 8 + # 0xD32E - Item 9 + # 0xD330 - Item 10 + # 0xD332 - Item 11 + # 0xD334 - Item 12 + # 0xD336 - Item 13 + # 0xD338 - Item 14 + # 0xD33A - Item 15 + # 0xD33C - Item 16 + # 0xD33E - Item 17 + # 0xD340 - Item 18 + # 0xD342 - Item 19 + # 0xD344 - Item 20 + # 0xD346 - Item End of List \ No newline at end of file From 30705dba599c35e37f376f4d62a01f276fa23390 Mon Sep 17 00:00:00 2001 From: leanke Date: Wed, 10 Jan 2024 20:36:35 +0000 Subject: [PATCH 04/29] cleanup/logging --- pokegym/States/3rdTownWin.state | Bin 0 -> 142610 bytes pokegym/States/Bill.state | Bin 0 -> 142610 bytes pokegym/{ => States}/Bulbasaur.state | Bin pokegym/States/Celedon.state | Bin 0 -> 142610 bytes pokegym/States/CeledonFly.state | Bin 0 -> 142610 bytes pokegym/States/CeruleanBill.state | Bin 0 -> 142610 bytes pokegym/{ => States}/Charmander.state | Bin pokegym/States/EndFlashCave.state | Bin 0 -> 142610 bytes pokegym/States/FlashCaveCenter.state | Bin 0 -> 142610 bytes pokegym/States/LavenderPre.state | Bin 0 -> 142610 bytes pokegym/States/LtSurge.state | Bin 0 -> 142610 bytes .../{Squirtle.state => States/Pallet.state} | Bin pokegym/States/Pewter.state | Bin 0 -> 142610 bytes pokegym/States/Squirtle.state | Bin 0 -> 142610 bytes pokegym/States/VermillionCut.state | Bin 0 -> 142610 bytes pokegym/States/ViridianPre.state | Bin 0 -> 142610 bytes pokegym/States/cut_no_cut.state | Bin 0 -> 142610 bytes pokegym/{ => States}/has_pokedex_nballs.state | Bin pokegym/States/misty.state | Bin 0 -> 142610 bytes pokegym/States/mtmoon.state | Bin 0 -> 142610 bytes pokegym/States/ssanne.state | Bin 0 -> 142610 bytes pokegym/data.py | 388 +++++ pokegym/environment.py | 263 +-- pokegym/map_data.json | 1522 ++++++++++++++++- pokegym/notes | 601 +++++++ pokegym/ram_map.py | 364 ++-- 26 files changed, 2734 insertions(+), 404 deletions(-) create mode 100644 pokegym/States/3rdTownWin.state create mode 100644 pokegym/States/Bill.state rename pokegym/{ => States}/Bulbasaur.state (100%) create mode 100644 pokegym/States/Celedon.state create mode 100644 pokegym/States/CeledonFly.state create mode 100644 pokegym/States/CeruleanBill.state rename pokegym/{ => States}/Charmander.state (100%) create mode 100644 pokegym/States/EndFlashCave.state create mode 100644 pokegym/States/FlashCaveCenter.state create mode 100644 pokegym/States/LavenderPre.state create mode 100644 pokegym/States/LtSurge.state rename pokegym/{Squirtle.state => States/Pallet.state} (100%) mode change 100755 => 100644 create mode 100644 pokegym/States/Pewter.state create mode 100755 pokegym/States/Squirtle.state create mode 100644 pokegym/States/VermillionCut.state create mode 100644 pokegym/States/ViridianPre.state create mode 100644 pokegym/States/cut_no_cut.state rename pokegym/{ => States}/has_pokedex_nballs.state (100%) create mode 100644 pokegym/States/misty.state create mode 100644 pokegym/States/mtmoon.state create mode 100644 pokegym/States/ssanne.state create mode 100644 pokegym/data.py create mode 100644 pokegym/notes diff --git a/pokegym/States/3rdTownWin.state b/pokegym/States/3rdTownWin.state new file mode 100644 index 0000000000000000000000000000000000000000..4b74b09d0eabb1ed0f9a979f4bcfb790ee98f4f1 GIT binary patch literal 142610 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 W3P=T{0#X5~fK)&#Fdz!J-TxoZY|>l+ literal 0 HcmV?d00001 diff --git a/pokegym/Bulbasaur.state b/pokegym/States/Bulbasaur.state similarity index 100% rename from pokegym/Bulbasaur.state rename to pokegym/States/Bulbasaur.state diff --git a/pokegym/States/Celedon.state b/pokegym/States/Celedon.state new file mode 100644 index 0000000000000000000000000000000000000000..9a354b6db85618a8b244b2a738e4e105dd768d7b GIT binary patch literal 142610 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 literal 0 HcmV?d00001 diff --git a/pokegym/States/CeruleanBill.state b/pokegym/States/CeruleanBill.state new file mode 100644 index 0000000000000000000000000000000000000000..f7d3dfd71c22f8f0c5c074f1f208accbeb5525a2 GIT binary patch literal 142610 zcmeHv4R}=5x$c^uWG2ZZGe95#hRgs#qF@t*N`RO>jTB;~m|7|J>XAtM96jgSL;N`! zb<9irbtk-Y;kl8k@(G#H=poZETU9c91f@30z! z#~U}edTL^Og7)~<_S!j%gX`5>+I^L^4Kt^By*;nL*Z8cuzbaPQ)A0Tu>+8{eSe0KH z-{|$BeOJ3Zz9Lv3o1(OY)~YvzDt%LxiHg_z$9Qq#9(7@&GBzXrOuW9{!2F7(6DrUTx1G-|WC z-9AdB{-vS7vP&b8?(P^RQvY)a>%q?{b*%rbm!$M_vHqJ5K)+ObP5otyQ|&eN55A{N zQ>NZ{N!yZotlz5mjOP!O9rh;IPj9bKBK1AK@^pKWe`DP1^SGUEcWGH^nWM0P^p90W z>t5`BvF*jSXzP~f(D&crApOVqJj#q^q<`bIXq5Ed8gs^{9whyPt3u7{Ly5I&S7;fp zQe$Icxcb-7J?gN;L0+X`P;1ZYrz~3%ny=Jp?Rov;2M;ISj=Q*he7(BH*j{;7Ei$%u z7nAbCS^tVky?s0w4?cAzT|y0Rr^8X=qR#>cCAT{k>#plwL$A8-Hj+Pz_#L|O9}ERT z6O4BQZPvI@)7=T147^^zrR+ zy83DNh&A`#e`43hcExI9HTwQT(NpEC^7!f%?Fw$9=xL9&J7eCsb_F*nbycGk<(kl~ z3l+M~tE%Fz_6_m)-o&OG;&h$2x9e9w$B&}4FAfFttDoa19YVO8qb77De86mpQ; zvn`RRO2jG3wDuLgiiyhnP~$ZAAMInbhx0zk{=41oveIHlA&=Ev7YR_#+u3hJ@M+x zW8I$a?$%a|rO;7YHfmh8!mkMmw|~6t+O5@be$9;!^HutkT4gHQcf~4W@t*h$w0Afv zeGQW@U3PtFR_z6~%9O_+Z{O9h$J_LS)niR{+YV|HqzaT_fI{3E>dv+(eGdD;;H+Oz5=8%-MyAANwwGa&%<+OHq?eL z4qcA+ufN~WlXy8XCbK;)-YmL?d+ndWjY<8B-G=?M`^D}Sx_Y(!lkdtL!^5ih4&+z3 z%rV&2&UYYtyneFc=bqgzn~iPWJ#jlNt&`Ry0q59pjpI8mRF|vaNaUS~@BT$S z6gqPAwu6lauTsP6@{S7|$B*^8iyRIi4qkPz@#bxzBkG}d|MJd6?gMvlIsqu579ns4 zrV~IsQ5yr${3v>*nhNV{ohLP z{abI(`mYRaRPR&25IRq(Q}p})EcGSz-o%2qGgcqd+h?8txX089;I*1qSk+GeKJWBT zvH#7qx2=jV@3<#4Qa!kz{ZHM&wG#lZzJ3ksCxB&_s*iVEG*cNnPCq{#{9N1>d@%9$ z8$W+4sb8wS7Qu%0S_Cg!to|&tV7gN2)6Z`Q-=pDYyzzDwogj4mGfx1<_9XvC)}OEZ zahm=vT2wj#v_}1OeoE?}8h?oTuk9eI69C5_>7P0QG)_}$LW?`T5L!qlB%j+ITov~o z-h0*C@fERM-QD4^cKy-5gcCrred+{2fm5^Omf*#qW<}xak8Vuf!}}Y5pD2#^(7l`@ zkjGD-0MH&MfQm|-0DN8^KOWq-A=XT<4cs~!O~)^v0ETA#;||Ox0DbpMp8)7ortLqp z`IPbgL-DWeKXh|z*7u*x69Bh2o&c^}61tJ^{#B#hF5Z96KOEm3Z%Dk{u`b?#_{aFP zh~*d`CxBl2Pjk12P5}GT`%nM(Z(n8O_>!6bzVNosUvevt-}ZRS8F*^ro~P!<=U#JF z$7g&KmFlW-1!XphpWQco_w}}}t<5cuM_cvi^HnN-Izk0#rw=DOqtRGp+)hUz>j%SB z%Msi5$eUY!^ruDt+H}VxWsJ|^IOO<8$y>fJ{j@l7S$u0uk00X+fX6qU0O(vgym6LN zOZRU&NAUfhPXKhtVg0rJlfRjjxV&R)DAcFX{fkckE#14?=h6wv9-p9ief70UE$Lsz z?d`O4dg$a6q%*{8G2SgRPXIz}-FoGoPaPO_c(giN9r>{`g--ybbcHChFQ6;%>mUE= z~%I|0!C`SXs8l$pNKsr_>S@1F@e0pR{)JOLQm>-*=D zPktG^M49gMCEFiPo&XMHwoje_wD?Kx-+cSh_D?>6`NmDA`f-wOVw_4SY4cN~a0BK0C79KtCoKIqy|7OHa1h6}%I`Hkqb{KjHQ zB<%Kt^FvRM_haA3^nJdgWAx|($7);1Ry*Q@SKh07?WgZ=eC-F(x^RSN&HeK$g_gwm zhZlZW+v9tCPBgS}lNt_PxA?ZzJ+-mnN4) zw0V8e?SawX_l!(d z(~rOT$KXfGi|wy%egCOMP>|^5WPf+5cj<}CtP%Z3A3|*IUKw9`=)RtnvFCbr#{SlG#HziPs_uk6u}ghr zdq8{5Q@ca{&2W3AjTcYow?VZlEN z4;Jw1Y)>UpnO5gWImz#9vh_ z;uY%OHplNIXP;7)zp49_ed?=+lwYe3y6*n&JER;^|NOyUeSd!JubW?ctkz%a_wyHp zTG~}>l=>cfkKN8Us~&F;ePQyZo$>knR5x9Y#q3n~%`GoK4*{pu=NP77gtNa z*oRK_w@>RYwS&w2)@ul{J)HSELYJoT6@Td8u23$pm029L!;p82+sEECvwm#3!)4LF zUwu;Wk^9|oFU;C}Ywe*+4sAZ9{PpJljz1M?3rA^(3bQX^ep$k{up?X+_Nvqgy~37= zC2DE2)Q7#qI>>6_dStC9HcHko*JD?yk96X)M!}E@-DD;mx_VBfZG*n$(bmDA+>z~w zYT>PcKPlRUmHY3c7yqYabX3(Q(U}g1RIa}My6gDv!1oO%`Cg*mPvUVd2ZMX}Mkwvw ztKTnTvE945{_fo|?f#ue@coBt==U!!gW>ia9nt9X&+pi=cQ0RKLSQUL-CmhYY5V^D z!C-y8$HVquFd7X8%gUaAULU_TmGt_{m+#oIe}5wJU;m}|CxqTk|KuJ7ok><`skN0$ z{Mp^@q}0}Cwc724l$htlo%{WPfL5T7;lrz|&!>d)b(E^Bo10PAktdMJtXr2Z`~9(4 zb#;6DPkwUKP0Y5n2@#8VJYW6lh7DZy`+It-tJ~Us@B{9*vhv(>J35-0CQTYMrku_b z94~szaGvcy`3c*<``ybguj!S_%g2mCd-kcO=JLx?-|QpzR!fP$y%^KH`R)lNuHxbe z^nEp({Y#4%jv4D6GiJ;six%nj5?5(ix`!(aNw<&nj;lJFeah4;t~{E3PD9%2^BKCo zBAb6FVxLC|V`OVTpHeP6`(RAdr#B?i^yx+(`SNnu%gezTaVXiRYAKXVda3tkZu>b? z_ys$o&fs(Q9DW(?I)hK6h&v*_`^a>pi4fRaZ|;rpc3amU}STXOW%XlPAyA z65I70f6k~bDCA%Jokf$+*V#<_%o!s`RZyxNeg4c@T4KAN(?2JjRXm}%xaMqdX8V=g zyrSZtX3b(tItfSFNbkgS*_^XD-SkthnN63iMV^YPN$IjVXDc37={w8Jrpxw{;iLVN z(`Cv{t5@cI{<14{uD~^7%=nrN`+2hh^A|3z7{h6;5Bvta1JYb1%O1%dHxD9a?sCGToA4zxB3W>2_mQXnpCF0@|O;;>Eaa*3&H zO2v$>GV6>rZrc_*Bjoe_&Yl;eoomR6HS{IfIm}8qAzmD=Bc0zJY?B{SgVQ7qwD`{7 z$HNalOkcVAH@~cJA?>HPKKYA0ZDfu+8DnSeHJIn{6WI4;44#=Wc&fyo+y~Fh`@pFZ zf7t(k*cXA|f#8AQf#8AQf#8AQf#8AQf#8AQf#8AQf#8AQf#8AQf#8AQf#8AQf#8AQ zf#8AQf#8AQf#8AQfhj)E0my#z^2zj_L)tU%&8L$dc@95e)-rWnBx6w8Gh^?x(PU*c|6WS-(7o zpZxtV`Tu=}at!rn4>?uhuRn9ZesHF);ZUCcsSJf(L>Jf(L>Jf(L>J zf(L>Jf(L>Jf(L>Jf(L>Jf(L>Jf(L>Jf(L>Jf(L>Jf(L>Jf(L>Jf(NGfJO?2A(aR^( zc}Lna&xfaz9(fMGoZo+;F%Z`a5Ihh(5Ihh(5Ihh(5Ihh(5Ihh(5Ihh(5Ihh(5Ihh( z5Ihh(5Ihh(5Ihh(5Ihh(5Ihh(5Ihh(5PZJl5B_9B_?XScoU~`|i)NjMfREMdC!>easngmaAIIZaS}m2li)v z+{fwMK9{zG#rJoO*iZCvvM^Vl&;;Wc>z&Rq!t))d`9afsA=dIl=ZF|!eRA%BY%|(X z&W7A!k~jKh%xhZleoHBXr*_7&IX_Tqh;fEu|C*M0{4i^mOGEI$RJ&vhFwgM^9;n5n z$(G`hai+E9e6p`p+oT<3Mq^!LefR}5wm~~1O1u8N4{MmulxS&n^;eaLuoT}l|+7*9v@SJuzGm+LC#@srloxOT{64T0c+;EgemEB?^NjJ@_j z?kpL9Py}swyo~y&&|m-0^!Cs(Pl)x+hKR9jHs-XLAiq=jT>D%uo@2`RW(`d7my(6+ zu?_W;hFreECuz_ATrKoT>6@<%t!Ka1i2P2?C0FbmZElDu)G_s;_06^%@yC3>{6Ev% zL-#x(V&_EHkljWfT8t#iN9!+D-#BNEFHRhPrWn-t9Px*~jy9f*(cIUlrfnWSux=;1 zM(jFb0P&|G;>Z{`%o)qv7@Rr&(4OmX%GQqP{`!BWx7W1I;|KId3>nMjJjXyUE*S$_ zd>CQ~^*FvbpAEHX8{-e{*?{;1BL0jv%3~aDj4b|1d;0vBYyDW#d?l<8d;@|9f(NE} za-8$bYj2(iVg<344VmKv8`=XkW73W~%)y>)pYnrv_yNv{+-|7ZA%W3v%)>ah{@5`_ zzR>79=477Z(aP*M`rvxUJrBed;>wK14vjWL{2k3d_=dTil4au@4SD!%pfa*4JL!V_ z@y;)!!+aD>zGEI(Qy_RCcy<|cW^T~l$@qgG@CyhY2p$L?2p$L?2p$L?2p$L?2p$NY z5gM{h3xQ+gwE8Du{j@Rj^Z`AxAvDTn!`$=ac52vMzQMntV&@6LXDudkT+3P=L*4$) zFC}EYGC8gv%&X5BWWRYYI?;E3=iOibPoegj6dZS)7>#dmgjgFb7W?Zz_nXJm-#MHV z*IDP|jbCmK==m)4+IYc4+IYc4+IYc4+IYc4+Ia?V@7YiP8g zF6#7W{#w8J{GfSb9!A8RfJPhV!5du%&$e7|tqwQh*fVO*<>Pr9+M0cXA4W9SN%`O3 z`S-UyYr%6d%qNv+8&a-1Cu4k!kq0!di?NI~GIGXzO1I=b#{xQl&5YUX{hmKM%@gt*`#T4%5Aq**{>M_9K)pz%%SVLUEM<95hnb!tF{4r*GXNf;^Kj!|WJu`Mrx;}kJ z$biPdU{*o0QcqEE)U<)c7x0e`dMvCJV` zJ8-i7DXwomV$Luo$BB%QQ#?inaE~yALSC|RpE|RD1J6s)83slC$$jw5ybl;cAun0P zAMU|G@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD@IdfD z@IdfD@IdfD@Idgu6rY!-PnlrzOz>bgksX04??^Sn6Z z^~qD%VOYddHq50h>Pma&z6f1_LqQ+JLEbRO{x_5~gs<6fD8xWEzrhW-|9J~MePEvF zfX`9J_$m7vf1blH^|w4S28aH=6nkbi%(cF!O8m)v@XWjqoGS5$>jP-MhQI>{(w<9` zqg^BL3pkMenfo^SHPpt>rS)L(M(v=hS;N6BYxAb_B9GbYO~zBr-=Xf$V8vO^adLb& zK+FLVn?UeD@IdfD@IdfD=8qbCh(}Wl8r!39t*_*~q&@q&7P{n&i$O9N*UX%Uk7gd_ zlWjkq_+uYV_V}0=@J#4`kisnYFgfAZ%{|Gb%A7H9sk+Ra zgg1OLqR~GikNjz4&k%X=3(zwgLgQ>U=1zv4``}q?{bBman8LVPon*a?x&st@#&MC) zh9_hG+4==H82v-?ydkY2=9>)%MFT@@rF1aIR?3jfnC<;2XPY;;!I(eB%7z0zzFF5n zF7uoRe2x?w*|f#jJihkkxpO{Of(`Xg0>Xz}{+;gjTv87f58d+>gQ?id7F!U(7-!5G z%l*vfd;a+z9}?#aF@MH<#9*&jG75p%%ErOgPt5oH^F6*%+I-|Of5aXTJPC@d}A!+B~R;bzLt%em}3!j z!2`hq!2`hq!KeQI;5h$o;H0(7_x$rceqMquJt*QK8zM%s*_az>d#-T@iy!FuoeKS@ zBSGe*0#X5~fK)&#AQg}bNCl(=kP1izqyka_ zsen{KDj*e*3P=T{0#X5~fK)&#AQg}bNCl(= zkP1izqyka_sen{KDj*e*3P=T{0#X5~fK)&#AQg}bNCl(=kP1izqyka_sen{KDj*e*3P=T{0#X5~fK)&#AQg}bNCl(=kP1izqyka_sen{KDj*e*3P=T{0#X5~fK)&#AQg}bNCl(< zQUR%eR6r^q6_5%@1*8H}0jYpgKq?>=kP1izqyka_sen{KDlmi<*eQf6mi+sqn}2k3 z?pf^IN!2J>oSAhZ;mk5h?F3Fqnf@xi59#%XGg`p1jq<%Jewa=BbuQ3${Ipht*; z0=ux=ZQ>Wd^o#%Vj9*xUc+esOMZ#(|4P_B_(I5gs*aP%oO%zx?!Yjg5%g-OeWl~)QZb{e%sOL@+qQ+Km(O=CC7&tf=1N?pW$DU#y|LbLRc3C=)GMwub8{NfjL(;Q z3}dZyy?K;iLEHJ1j2!c)a{BDd`dPC-t0gT5UwthWt6QIlmP_%NToU`{d#?|-@%ehM zkQ5ObU|S$)mGo7WBeP+L3p`)H}OuVgPMbm(o~9jpr;;Q@O`q=kbWTN{_$ZGhEbH4)+JB9H4SDm8q@2h3dCZ z{mZHT<&|goTRr8XmHNAu`n#6;6Cx6c;AG0f>FI3tM0-!2uOCpWVM-!>RWLEBVx^cX z&JpK{vqg;~nMA3m7Ug23C=+8+91YTEsuVxVtYf<`yl!1fm{ZHz2f}Mx)^*)~_j%=< z&Y9BCaO-W!bi2;Yp3~3}3?)-AT`w3~{lLBd@6HtuCAp*5TlK*Geb(z*zhV`Q(`UW4 zEAC`>veuiPnn&>cb>XkC;CdQWp6Q>?@qB}!2Uf4|df@)P>#caGYkg*acR;I-bbk#E z=S-PO>6|I)aT^+D&tY!%oV&EOP=$j!`U<75g+2<2q8mduQ@SOjI&2gZmMf@|Mf*ml zeNnZVzAhF{bQF3l!s@b$0!xX7K5Zp7`Yb3Zpil9dSkcni^5Fd~t6N%1O0@A-ulUCL z6>CGwyVh@L>0JB3@_V(9mdD=v-+y?|ZZC1W4DmbemCipn=RHwC9#||-2!{|ON1i)= z>e`u0M~QQuFKzO?Rrc}lZxsJ{sGdKdXm1`icvOoKKZnv%Mj1yM4Wrx%&Lpkg<4qZv1uH8Xi29$4FQ4_|`C^g2qie0?f!DV2UvWGfWbA|Vzp8uN2Om)h5^5&yys zf2+5yuODu=SuNs!nmpg>`u6uHKQ{RZtL=$7O3UoekU*>d`Dd*kUOj&DT4DQ{;@)+q z^E{fX5Y(Mu?WV2|FTeM`6$?8l#us)z*x5<>Z-jYG?tbw8g)Q2LK1{DqnrS7Gv<{Zm z(&`JjSBqt8pnS4k^V_`|BnjSJ>~yKLgh~E;!pjSvc%-`ek-Jo*tT~r7x%oUoGEh>- z(_mEBvQTUg|14IC_2To7UNfTHRGGj3fRuD|}pqOS)gEe}|& z;egY_`+%^QSl_dpW0_|94G1$cV$-zF`yO1;a(8&`hL(GNO;_;JQFO<=eE3cWMQ(MK z$8In9ml@ao>or2XV}AFJ;^K)1i*~8!HP`Q=n_97Lr@DXMWjkHPVuW?4>S>;|vxwv^ z-D$6Ml@(a06*?}MFW$VC7^}l7XsZ(Hun{6KqDp*bbg5_>Jz88?Vi$Xg##$RoiY$AJ zio{-{Yo}MaIR$XF@fJhpP`agk8!}$RGSDN z>mZ+q78~6G?mpJ~gI3SkR&;%7wd={!CtVMgJz4yqBj~){_GI;w`n5)GB#$q2bFu$D z?bU`ZzE2uXH2RvPZibn4`DR#3cd-$4Ga4y8bSygNet*by3+(FnW!eqS)GyuPxL>ar zFI-xXaP5WIT?A8_eZYxty%(fHfwyvCXf{70VkIOu=wyB&eie(wBg?c8-WAGr#?#Q!CaZfn>` zCo>2AWJBLb-~6k1Cj6~JAufz`iCwL09ul5!#`kOIv~K+_^a7pWoF2Qw%cqX4Hzq#k z9ZzZy?OJumNjnZ&tX7Maf2`^|*_>YAr^Zj5vgo=+kAADc+qh)gtsh?Ru{iDYW0l=z zvpEXONSbsanF?9hXiy2MA@vjWmX0fKKcwCstX?|d-6d~a*0^o|o);?Kt*&lrvbtBV zw#}P6cdmWaD$fLKlj!W!`Uyv((eCc7i5aDnjKp&ilp(OnN z=$h`d17}{<gE@NuM^G^ZR)LuKJ31}+cVu_cNWBKbqmFu-Xb71%)MLB@Rc9-^uy+bNXqtu+ZVK zJ8U*VD;-QI6fr8lL!X4yU)?gNWBL^}w-+5MQNMEg{NMxC*DQT|!rSkDe@WLH3ohH; zxNh6J{loW6c;VR!@4IR>y_)D{^|_s^yHkBCs_YpxBE9!T~MZlJTZR9(BMNhy)^9h6`E}6p_Sgi6WCy zl%liyjE>K=+A=2fiLHp62{qb-DgnQ1r7R@M(T5=u)n7N;S9t*Oh9t+aD52Q!ozpfk#j|GLv&r|WSpwW&t=k+e_v7lmlEU2{F`Jn4T z>+Nmo!`N{g4t4w&v`2D^x2AR9&^3`B$te?O2{%3JGeWr9*ygaQHM#3fqMB}cEXehU z-VZ%{yL1yhZd6*E(Qj3i+fDx<06j}9#)-0r**3~{?)Y;r8bx(obs2SQii?MdbE$5n zsG!;dlnnQ;KC=Vlg1GEErk-u0ZWgW_lj&HB{1ATnpJfxH9Q3SZKx?Rf_Ej!4)wi3< zg;tfG)S|vz9<4(=l^aA0{cC3d`Vis*Q~ln54vqli+MN1Xrtxzp-bS3x5pLAs=jxr# z%A(A2g~>jq40e7P-sv2dvRk#XpX`;|gR;7PBH5=q$s}}pQL4TEZyB&C?^ z`iJQFpFx|ZJ%6*z+wuo|B1K7*z$rz#bY3P=T{0#X5~fK)&#AQg}b zNCl(=kP1izqyka_sen{KDj*e*3P=T{0#X5~ zfK)&#AQg}bNCl(=kP1izqyka_sen{KDj*e* W3P=T{0#X5~fK)&#Fdz!J-TxoZY|>l+ literal 0 HcmV?d00001 diff --git a/pokegym/Charmander.state b/pokegym/States/Charmander.state similarity index 100% rename from pokegym/Charmander.state rename to pokegym/States/Charmander.state diff --git a/pokegym/States/EndFlashCave.state b/pokegym/States/EndFlashCave.state new file mode 100644 index 0000000000000000000000000000000000000000..cdbca96144fcf326af980c252fbbe230465a2836 GIT binary patch literal 142610 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 afM!55pc&8%Xa+O`ngPu~Z($%e_x}OW3vGG; literal 0 HcmV?d00001 diff --git a/pokegym/States/FlashCaveCenter.state b/pokegym/States/FlashCaveCenter.state new file mode 100644 index 0000000000000000000000000000000000000000..fabd6004735c43ee6d61755809359db52f1605ce GIT binary patch literal 142610 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 G_5VL_wa^9t literal 0 HcmV?d00001 diff --git a/pokegym/States/LavenderPre.state b/pokegym/States/LavenderPre.state new file mode 100644 index 0000000000000000000000000000000000000000..5fc0683601fa1e03cf3709df2d1b32160b2c6e37 GIT binary patch literal 142610 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 U&w$T>&w$T>&%g(Tf!f;t4@pckHvj+t literal 0 HcmV?d00001 diff --git a/pokegym/States/LtSurge.state b/pokegym/States/LtSurge.state new file mode 100644 index 0000000000000000000000000000000000000000..7f49bfe7d755c66558edb1f3bf7f6ec53b43cc29 GIT binary patch literal 142610 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 I1nlNwGx%9QHGoW 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�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 CBZk8O literal 0 HcmV?d00001 diff --git a/pokegym/States/mtmoon.state b/pokegym/States/mtmoon.state new file mode 100644 index 0000000000000000000000000000000000000000..981f75c68159fb0a3473834a03061dacd5801b0c GIT binary patch literal 142610 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 literal 0 HcmV?d00001 diff --git a/pokegym/States/ssanne.state b/pokegym/States/ssanne.state new file mode 100644 index 0000000000000000000000000000000000000000..ccaec98065ec8a456ee2a308ab71fcb4be72a1fb GIT binary patch literal 142610 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 literal 0 HcmV?d00001 diff --git a/pokegym/data.py b/pokegym/data.py new file mode 100644 index 0000000..d341b92 --- /dev/null +++ b/pokegym/data.py @@ -0,0 +1,388 @@ + +pokemon_data = [ + {'hex': '1', 'decimal': '1', 'name': 'Rhydon'}, + {'hex': '2', 'decimal': '2', 'name': 'Kangaskhan'}, + {'hex': '3', 'decimal': '3', 'name': 'Nidoran♂'}, + {'hex': '4', 'decimal': '4', 'name': 'Clefairy'}, + {'hex': '5', 'decimal': '5', 'name': 'Spearow'}, + {'hex': '6', 'decimal': '6', 'name': 'Voltorb', 'type': 'Electric'}, + {'hex': '7', 'decimal': '7', 'name': 'Nidoking'}, + {'hex': '8', 'decimal': '8', 'name': 'Slowbro'}, + {'hex': '9', 'decimal': '9', 'name': 'Ivysaur'}, + {'hex': 'A', 'decimal': '10', 'name': 'Exeggutor'}, + {'hex': 'B', 'decimal': '11', 'name': 'Lickitung'}, + {'hex': 'C', 'decimal': '12', 'name': 'Exeggcute'}, + {'hex': 'D', 'decimal': '13', 'name': 'Grimer'}, + {'hex': 'E', 'decimal': '14', 'name': 'Gengar', 'type': 'Ghost'}, + {'hex': 'F', 'decimal': '15', 'name': 'Nidoran♀'}, + {'hex': '10', 'decimal': '16', 'name': 'Nidoqueen'}, + {'hex': '11', 'decimal': '17', 'name': 'Cubone'}, + {'hex': '12', 'decimal': '18', 'name': 'Rhyhorn'}, + {'hex': '13', 'decimal': '19', 'name': 'Lapras', 'type': 'Ice'}, + {'hex': '14', 'decimal': '20', 'name': 'Arcanine'}, + {'hex': '15', 'decimal': '21', 'name': 'Mew'}, + {'hex': '16', 'decimal': '22', 'name': 'Gyarados'}, + {'hex': '17', 'decimal': '23', 'name': 'Shellder'}, + {'hex': '18', 'decimal': '24', 'name': 'Tentacool'}, + {'hex': '19', 'decimal': '25', 'name': 'Gastly', 'type': 'Ghost'}, + {'hex': '1A', 'decimal': '26', 'name': 'Scyther', 'type': 'Bug'}, + {'hex': '1B', 'decimal': '27', 'name': 'Staryu'}, + {'hex': '1C', 'decimal': '28', 'name': 'Blastoise'}, + {'hex': '1D', 'decimal': '29', 'name': 'Pinsir', 'type': 'Bug'}, + {'hex': '1E', 'decimal': '30', 'name': 'Tangela'}, + {'hex': '1F', 'decimal': '31', 'name': 'MissingNo. (Scizor)'}, + {'hex': '20', 'decimal': '32', 'name': 'MissingNo. (Shuckle)'}, + {'hex': '21', 'decimal': '33', 'name': 'Growlithe'}, + {'hex': '22', 'decimal': '34', 'name': 'Onix'}, + {'hex': '23', 'decimal': '35', 'name': 'Fearow'}, + {'hex': '24', 'decimal': '36', 'name': 'Pidgey'}, + {'hex': '25', 'decimal': '37', 'name': 'Slowpoke'}, + {'hex': '26', 'decimal': '38', 'name': 'Kadabra'}, + {'hex': '27', 'decimal': '39', 'name': 'Graveler'}, + {'hex': '28', 'decimal': '40', 'name': 'Chansey'}, + {'hex': '29', 'decimal': '41', 'name': 'Machoke'}, + {'hex': '2A', 'decimal': '42', 'name': 'Mr. Mime'}, + {'hex': '2B', 'decimal': '43', 'name': 'Hitmonlee'}, + {'hex': '2C', 'decimal': '44', 'name': 'Hitmonchan'}, + {'hex': '2D', 'decimal': '45', 'name': 'Arbok'}, + {'hex': '2E', 'decimal': '46', 'name': 'Parasect', 'type': 'Bug'}, + {'hex': '2F', 'decimal': '47', 'name': 'Psyduck'}, + {'hex': '30', 'decimal': '48', 'name': 'Drowzee'}, + {'hex': '31', 'decimal': '49', 'name': 'Golem'}, + {'hex': '32', 'decimal': '50', 'name': 'MissingNo. (Heracross)'}, + {'hex': '33', 'decimal': '51', 'name': 'Magmar'}, + {'hex': '34', 'decimal': '52', 'name': 'MissingNo. (Ho-Oh)'}, + {'hex': '35', 'decimal': '53', 'name': 'Electabuzz', 'type': 'Electric'}, + {'hex': '36', 'decimal': '54', 'name': 'Magneton', 'type': 'Electric'}, + {'hex': '37', 'decimal': '55', 'name': 'Koffing'}, + {'hex': '38', 'decimal': '56', 'name': 'MissingNo. (Sneasel)'}, + {'hex': '39', 'decimal': '57', 'name': 'Mankey'}, + {'hex': '3A', 'decimal': '58', 'name': 'Seel'}, + {'hex': '3B', 'decimal': '59', 'name': 'Diglett'}, + {'hex': '3C', 'decimal': '60', 'name': 'Tauros'}, + {'hex': '3D', 'decimal': '61', 'name': 'MissingNo. (Teddiursa)'}, + {'hex': '3E', 'decimal': '62', 'name': 'MissingNo. (Ursaring)'}, + {'hex': '3F', 'decimal': '63', 'name': 'MissingNo. (Slugma)'}, + {'hex': '40', 'decimal': '64', 'name': 'Farfetch\'d'}, + {'hex': '41', 'decimal': '65', 'name': 'Venonat', 'type': 'Bug'}, + {'hex': '42', 'decimal': '66', 'name': 'Dragonite', 'type': 'Dragon'}, + {'hex': '43', 'decimal': '67', 'name': 'MissingNo. (Magcargo)'}, + {'hex': '44', 'decimal': '68', 'name': 'MissingNo. (Swinub)'}, + {'hex': '45', 'decimal': '69', 'name': 'MissingNo. (Piloswine)'}, + {'hex': '46', 'decimal': '70', 'name': 'Doduo'}, + {'hex': '47', 'decimal': '71', 'name': 'Poliwag'}, + {'hex': '48', 'decimal': '72', 'name': 'Jynx', 'type': 'Ice'}, + {'hex': '49', 'decimal': '73', 'name': 'Moltres'}, + {'hex': '4A', 'decimal': '74', 'name': 'Articuno', 'type': 'Ice'}, + {'hex': '4B', 'decimal': '75', 'name': 'Zapdos', 'type': 'Electric'}, + {'hex': '4C', 'decimal': '76', 'name': 'Ditto'}, + {'hex': '4D', 'decimal': '77', 'name': 'Meowth'}, + {'hex': '4E', 'decimal': '78', 'name': 'Krabby'}, + {'hex': '4F', 'decimal': '79', 'name': 'MissingNo. (Corsola)'}, + {'hex': '50', 'decimal': '80', 'name': 'MissingNo. (Remoraid)'}, + {'hex': '51', 'decimal': '81', 'name': 'MissingNo. (Octillery)'}, + {'hex': '52', 'decimal': '82', 'name': 'Vulpix'}, + {'hex': '53', 'decimal': '83', 'name': 'Ninetales'}, + {'hex': '54', 'decimal': '84', 'name': 'Pikachu', 'type': 'Electric'}, + {'hex': '55', 'decimal': '85', 'name': 'Raichu', 'type': 'Electric'}, + {'hex': '56', 'decimal': '86', 'name': 'MissingNo. (Deli)'}, + {'hex': '57', 'decimal': '87', 'name': 'MissingNo. (Mantine)'}, + {'hex': '58', 'decimal': '88', 'name': 'Dratini', 'type': 'Dragon'}, + {'hex': '59', 'decimal': '89', 'name': 'Dragonair', 'type': 'Dragon'}, + {'hex': '5A', 'decimal': '90', 'name': 'Kabuto'}, + {'hex': '5B', 'decimal': '91', 'name': 'Kabutops'}, + {'hex': '5C', 'decimal': '92', 'name': 'Horsea'}, + {'hex': '5D', 'decimal': '93', 'name': 'Seadra'}, + {'hex': '5E', 'decimal': '94', 'name': 'MissingNo. (Skarmory)'}, + {'hex': '5F', 'decimal': '95', 'name': 'MissingNo. (Houndour)'}, + {'hex': '60', 'decimal': '96', 'name': 'Sandshrew'}, + {'hex': '61', 'decimal': '97', 'name': 'Sandslash'}, + {'hex': '62', 'decimal': '98', 'name': 'Omanyte'}, + {'hex': '63', 'decimal': '99', 'name': 'Omastar'}, + {'hex': '64', 'decimal': '100', 'name': 'Jigglypuff'}, + {'hex': '65', 'decimal': '101', 'name': 'Wigglytuff'}, + {'hex': '66', 'decimal': '102', 'name': 'Eevee'}, + {'hex': '67', 'decimal': '103', 'name': 'Flareon'}, + {'hex': '68', 'decimal': '104', 'name': 'Jolteon', 'type': 'Electric'}, + {'hex': '69', 'decimal': '105', 'name': 'Vaporeon'}, + {'hex': '6A', 'decimal': '106', 'name': 'Machop'}, + {'hex': '6B', 'decimal': '107', 'name': 'Zubat'}, + {'hex': '6C', 'decimal': '108', 'name': 'Ekans'}, + {'hex': '6D', 'decimal': '109', 'name': 'Paras', 'type': 'Bug'}, + {'hex': '6E', 'decimal': '110', 'name': 'Poliwhirl'}, + {'hex': '6F', 'decimal': '111', 'name': 'Poliwrath'}, + {'hex': '70', 'decimal': '112', 'name': 'Weedle', 'type': 'Bug'}, + {'hex': '71', 'decimal': '113', 'name': 'Kakuna', 'type': 'Bug'}, + {'hex': '72', 'decimal': '114', 'name': 'Beedrill', 'type': 'Bug'}, + {'hex': '73', 'decimal': '115', 'name': 'MissingNo. (Houndoom)'}, + {'hex': '74', 'decimal': '116', 'name': 'Dodrio'}, + {'hex': '75', 'decimal': '117', 'name': 'Primeape'}, + {'hex': '76', 'decimal': '118', 'name': 'Dugtrio'}, + {'hex': '77', 'decimal': '119', 'name': 'Venomoth', 'type': 'Bug'}, + {'hex': '78', 'decimal': '120', 'name': 'Dewgong', 'type': 'Ice'}, + {'hex': '79', 'decimal': '121', 'name': 'MissingNo. (Kingdra)'}, + {'hex': '7A', 'decimal': '122', 'name': 'MissingNo. (Phanpy)'}, + {'hex': '7B', 'decimal': '123', 'name': 'Caterpie', 'type': 'Bug'}, + {'hex': '7C', 'decimal': '124', 'name': 'Metapod', 'type': 'Bug'}, + {'hex': '7D', 'decimal': '125', 'name': 'Butterfree', 'type': 'Bug'}, + {'hex': '7E', 'decimal': '126', 'name': 'Machamp'}, + {'hex': '7F', 'decimal': '127', 'name': 'MissingNo. (Donphan)'}, + {'hex': '80', 'decimal': '128', 'name': 'Golduck'}, + {'hex': '81', 'decimal': '129', 'name': 'Hypno'}, + {'hex': '82', 'decimal': '130', 'name': 'Golbat'}, + {'hex': '83', 'decimal': '131', 'name': 'Mewtwo'}, + {'hex': '84', 'decimal': '132', 'name': 'Snorlax'}, + {'hex': '85', 'decimal': '133', 'name': 'Magikarp'}, + {'hex': '86', 'decimal': '134', 'name': 'MissingNo. (Porygon2)'}, + {'hex': '87', 'decimal': '135', 'name': 'MissingNo. (Stantler)'}, + {'hex': '88', 'decimal': '136', 'name': 'Muk'}, + {'hex': '89', 'decimal': '137', 'name': 'MissingNo. (Smeargle)'}, + {'hex': '8A', 'decimal': '138', 'name': 'Kingler'}, + {'hex': '8B', 'decimal': '139', 'name': 'Cloyster'}, + {'hex': '8D', 'decimal': '141', 'name': 'Electrode'}, + {'hex': '8E', 'decimal': '142', 'name': 'Clefable'}, + {'hex': '8F', 'decimal': '143', 'name': 'Weezing'}, + {'hex': '90', 'decimal': '144', 'name': 'Persian'}, + {'hex': '91', 'decimal': '145', 'name': 'Marowak'}, + {'hex': '93', 'decimal': '147', 'name': 'Haunter'}, + {'hex': '94', 'decimal': '148', 'name': 'Abra'}, + {'hex': '95', 'decimal': '149', 'name': 'Alakazam'}, + {'hex': '96', 'decimal': '150', 'name': 'Pidgeotto'}, + {'hex': '97', 'decimal': '151', 'name': 'Pidgeot'}, + {'hex': '98', 'decimal': '152', 'name': 'Starmie'}, + {'hex': '99', 'decimal': '153', 'name': 'Bulbasaur'}, + {'hex': '9A', 'decimal': '154', 'name': 'Venusaur'}, + {'hex': '9B', 'decimal': '155', 'name': 'Tentacruel'}, + {'hex': '9D', 'decimal': '157', 'name': 'Goldeen'}, + {'hex': '9E', 'decimal': '158', 'name': 'Seaking'}, + {'hex': 'A3', 'decimal': '163', 'name': 'Ponyta'}, + {'hex': 'A4', 'decimal': '164', 'name': 'Rapidash'}, + {'hex': 'A5', 'decimal': '165', 'name': 'Rattata'}, + {'hex': 'A6', 'decimal': '166', 'name': 'Raticate'}, + {'hex': 'A7', 'decimal': '167', 'name': 'Nidorino'}, + {'hex': 'A8', 'decimal': '168', 'name': 'Nidorina'}, + {'hex': 'A9', 'decimal': '169', 'name': 'Geodude'}, + {'hex': 'AA', 'decimal': '170', 'name': 'Porygon'}, + {'hex': 'AB', 'decimal': '171', 'name': 'Aerodactyl'}, + {'hex': 'AD', 'decimal': '173', 'name': 'Magnemite'}, + {'hex': 'B0', 'decimal': '176', 'name': 'Charmander'}, + {'hex': 'B1', 'decimal': '177', 'name': 'Squirtle'}, + {'hex': 'B2', 'decimal': '178', 'name': 'Charmeleon'}, + {'hex': 'B3', 'decimal': '179', 'name': 'Wartortle'}, + {'hex': 'B4', 'decimal': '180', 'name': 'Charizard'}, + {'hex': 'B9', 'decimal': '185', 'name': 'Oddish'}, + {'hex': 'BA', 'decimal': '186', 'name': 'Gloom'}, + {'hex': 'BB', 'decimal': '187', 'name': 'Vileplume'}, + {'hex': 'BC', 'decimal': '188', 'name': 'Bellsprout'}, + {'hex': 'BD', 'decimal': '189', 'name': 'Weepinbell'}, + {'hex': 'BE', 'decimal': '190', 'name': 'Victreebel'} +] + +moves_dict = { + 1: {"Move": "Pound", "Type": "Normal", "Phy/Spec": "Physical", "PP": 35, "Power": 40, "Acc": "100%"}, + 2: {"Move": "Karate Chop", "Type": "Fighting", "Phy/Spec": "Physical", "PP": 25, "Power": 50, "Acc": "100%"}, + 3: {"Move": "Double Slap", "Type": "Normal", "Phy/Spec": "Physical", "PP": 10, "Power": 15, "Acc": "85%"}, + 4: {"Move": "Comet Punch", "Type": "Normal", "Phy/Spec": "Physical", "PP": 15, "Power": 18, "Acc": "85%"}, + 5: {"Move": "Mega Punch", "Type": "Normal", "Phy/Spec": "Physical", "PP": 20, "Power": 80, "Acc": "85%"}, + 6: {"Move": "Pay Day", "Type": "Normal", "Phy/Spec": "Physical", "PP": 20, "Power": 40, "Acc": "100%"}, + 7: {"Move": "Fire Punch", "Type": "Fire", "Phy/Spec": "Physical", "PP": 15, "Power": 75, "Acc": "100%"}, + 8: {"Move": "Ice Punch", "Type": "Ice", "Phy/Spec": "Physical", "PP": 15, "Power": 75, "Acc": "100%"}, + 9: {"Move": "Thunder Punch", "Type": "Electric", "Phy/Spec": "Physical", "PP": 15, "Power": 75, "Acc": "100%"}, + 10: {"Move": "Scratch", "Type": "Normal", "Phy/Spec": "Physical", "PP": 35, "Power": 40, "Acc": "100%"}, + 11: {"Move": "Vise Grip", "Type": "Normal", "Phy/Spec": "Physical", "PP": 30, "Power": 55, "Acc": "100%"}, + 12: {"Move": "Guillotine", "Type": "Normal", "Phy/Spec": "Physical", "PP": 5, "Power": "—", "Acc": "30%"}, + 13: {"Move": "Razor Wind", "Type": "Normal", "Phy/Spec": "Special", "PP": 10, "Power": 80, "Acc": "100%"}, + 14: {"Move": "Swords Dance", "Type": "Normal", "Phy/Spec": "Status", "PP": 20, "Power": "—", "Acc": "—%"}, + 15: {"Move": "Cut", "Type": "Normal", "Phy/Spec": "Physical", "PP": 30, "Power": 50, "Acc": "95%"}, + 16: {"Move": "Gust", "Type": "Flying", "Phy/Spec": "Special", "PP": 35, "Power": 40, "Acc": "100%"}, + 17: {"Move": "Wing Attack", "Type": "Flying", "Phy/Spec": "Physical", "PP": 35, "Power": 60, "Acc": "100%"}, + 18: {"Move": "Whirlwind", "Type": "Normal", "Phy/Spec": "Status", "PP": 20, "Power": "—", "Acc": "—%"}, + 19: {"Move": "Fly", "Type": "Flying", "Phy/Spec": "Physical", "PP": 15, "Power": 90, "Acc": "95%"}, + 20: {"Move": "Bind", "Type": "Normal", "Phy/Spec": "Physical", "PP": 20, "Power": 15, "Acc": "85%"}, + 21: {"Move": "Slam", "Type": "Normal", "Phy/Spec": "Physical", "PP": 20, "Power": 80, "Acc": "75%"}, + 22: {"Move": "Vine Whip", "Type": "Grass", "Phy/Spec": "Physical", "PP": 25, "Power": 45, "Acc": "100%"}, + 23: {"Move": "Stomp", "Type": "Normal", "Phy/Spec": "Physical", "PP": 20, "Power": 65, "Acc": "100%"}, + 24: {"Move": "Double Kick", "Type": "Fighting", "Phy/Spec": "Physical", "PP": 30, "Power": 30, "Acc": "100%"}, + 25: {"Move": "Mega Kick", "Type": "Normal", "Phy/Spec": "Physical", "PP": 5, "Power": 120, "Acc": "75%"}, + 26: {"Move": "Jump Kick", "Type": "Fighting", "Phy/Spec": "Physical", "PP": 10, "Power": 100, "Acc": "95%"}, + 27: {"Move": "Rolling Kick", "Type": "Fighting", "Phy/Spec": "Physical", "PP": 15, "Power": 60, "Acc": "85%"}, + 28: {"Move": "Sand Attack", "Type": "Ground", "Phy/Spec": "Status", "PP": 15, "Power": "—", "Acc": "100%"}, + 29: {"Move": "Headbutt", "Type": "Normal", "Phy/Spec": "Physical", "PP": 15, "Power": 70, "Acc": "100%"}, + 30: {"Move": "Horn Attack", "Type": "Normal", "Phy/Spec": "Physical", "PP": 25, "Power": 65, "Acc": "100%"}, + 31: {"Move": "Fury Attack", "Type": "Normal", "Phy/Spec": "Physical", "PP": 20, "Power": 15, "Acc": "85%"}, + 32: {"Move": "Horn Drill", "Type": "Normal", "Phy/Spec": "Physical", "PP": 5, "Power": "—", "Acc": "30%"}, + 33: {"Move": "Tackle", "Type": "Normal", "Phy/Spec": "Physical", "PP": 35, "Power": 40, "Acc": "100%"}, + 34: {"Move": "Body Slam", "Type": "Normal", "Phy/Spec": "Physical", "PP": 15, "Power": 85, "Acc": "100%"}, + 35: {"Move": "Wrap", "Type": "Normal", "Phy/Spec": "Physical", "PP": 20, "Power": 15, "Acc": "90%"}, + 36: {"Move": "Take Down", "Type": "Normal", "Phy/Spec": "Physical", "PP": 20, "Power": 90, "Acc": "85%"}, + 37: {"Move": "Thrash", "Type": "Normal", "Phy/Spec": "Physical", "PP": 10, "Power": 120, "Acc": "100%"}, + 38: {"Move": "Double-Edge", "Type": "Normal", "Phy/Spec": "Physical", "PP": 15, "Power": 120, "Acc": "100%"}, + 39: {"Move": "Tail Whip", "Type": "Normal", "Phy/Spec": "Status", "PP": 30, "Power": "—", "Acc": "100%"}, + 40: {"Move": "Poison Sting", "Type": "Poison", "Phy/Spec": "Physical", "PP": 35, "Power": 15, "Acc": "100%"}, + 41: {"Move": "Twineedle", "Type": "Bug", "Phy/Spec": "Physical", "PP": 20, "Power": 25, "Acc": "100%"}, + 42: {"Move": "Pin Missile", "Type": "Bug", "Phy/Spec": "Physical", "PP": 20, "Power": 25, "Acc": "95%"}, + 43: {"Move": "Leer", "Type": "Normal", "Phy/Spec": "Status", "PP": 30, "Power": "—", "Acc": "100%"}, + 44: {"Move": "Bite", "Type": "Dark", "Phy/Spec": "Physical", "PP": 25, "Power": 60, "Acc": "100%"}, + 45: {"Move": "Growl", "Type": "Normal", "Phy/Spec": "Status", "PP": 40, "Power": "—", "Acc": "100%"}, + 46: {"Move": "Roar", "Type": "Normal", "Phy/Spec": "Status", "PP": 20, "Power": "—", "Acc": "—%"}, + 47: {"Move": "Sing", "Type": "Normal", "Phy/Spec": "Status", "PP": 15, "Power": "—", "Acc": "55%"}, + 48: {"Move": "Supersonic", "Type": "Normal", "Phy/Spec": "Status", "PP": 20, "Power": "—", "Acc": "55%"}, + 49: {"Move": "Sonic Boom", "Type": "Normal", "Phy/Spec": "Special", "PP": 20, "Power": "—", "Acc": "90%"}, + 50: {"Move": "Disable", "Type": "Normal", "Phy/Spec": "Status", "PP": 20, "Power": "—", "Acc": "100%"}, + 51: {"Move": "Acid", "Type": "Poison", "Phy/Spec": "Special", "PP": 30, "Power": 40, "Acc": "100%"}, + 52: {"Move": "Ember", "Type": "Fire", "Phy/Spec": "Special", "PP": 25, "Power": 40, "Acc": "100%"}, + 53: {"Move": "Flamethrower", "Type": "Fire", "Phy/Spec": "Special", "PP": 15, "Power": 90, "Acc": "100%"}, + 54: {"Move": "Mist", "Type": "Ice", "Phy/Spec": "Status", "PP": 30, "Power": "—", "Acc": "—%"}, + 55: {"Move": "Water Gun", "Type": "Water", "Phy/Spec": "Special", "PP": 25, "Power": 40, "Acc": "100%"}, + 56: {"Move": "Hydro Pump", "Type": "Water", "Phy/Spec": "Special", "PP": 5, "Power": 110, "Acc": "80%"}, + 57: {"Move": "Surf", "Type": "Water", "Phy/Spec": "Special", "PP": 15, "Power": 90, "Acc": "100%"}, + 58: {"Move": "Ice Beam", "Type": "Ice", "Phy/Spec": "Special", "PP": 10, "Power": 90, "Acc": "100%"}, + 59: {"Move": "Blizzard", "Type": "Ice", "Phy/Spec": "Special", "PP": 5, "Power": 110, "Acc": "70%"}, + 60: {"Move": "Psybeam", "Type": "Psychic", "Phy/Spec": "Special", "PP": 20, "Power": 65, "Acc": "100%"}, + 61: {"Move": "Bubble Beam", "Type": "Water", "Phy/Spec": "Special", "PP": 20, "Power": 65, "Acc": "100%"}, + 62: {"Move": "Aurora Beam", "Type": "Ice", "Phy/Spec": "Special", "PP": 20, "Power": 65, "Acc": "100%"}, + 63: {"Move": "Hyper Beam", "Type": "Normal", "Phy/Spec": "Special", "PP": 5, "Power": 150, "Acc": "90%"}, + 64: {"Move": "Peck", "Type": "Flying", "Phy/Spec": "Physical", "PP": 35, "Power": 35, "Acc": "100%"}, + 65: {"Move": "Drill Peck", "Type": "Flying", "Phy/Spec": "Physical", "PP": 20, "Power": 80, "Acc": "100%"}, + 66: {"Move": "Submission", "Type": "Fighting", "Phy/Spec": "Physical", "PP": 20, "Power": 80, "Acc": "80%"}, + 67: {"Move": "Low Kick", "Type": "Fighting", "Phy/Spec": "Physical", "PP": 20, "Power": "—", "Acc": "100%"}, + 68: {"Move": 'Counter', 'Type': 'Fighting', 'Category': 'Physical', 'PP': 20, 'Power': '—', 'Accuracy': '100%'}, + 69: {"Move": 'Seismic Toss', 'Type': 'Fighting', 'Category': 'Physical', 'PP': 20, 'Power': '—', 'Accuracy': '100%'}, + 70: {"Move": 'Strength', 'Type': 'Normal', 'Category': 'Physical', 'PP': 15, 'Power': 80, 'Accuracy': '100%'}, + 71: {"Move": 'Absorb', 'Type': 'Grass', 'Category': 'Special', 'PP': 25, 'Power': 20, 'Accuracy': '100%'}, + 72: {"Move": 'Mega Drain', 'Type': 'Grass', 'Category': 'Special', 'PP': 15, 'Power': 40, 'Accuracy': '100%'}, + 73: {"Move": 'Leech Seed', 'Type': 'Grass', 'Category': 'Status', 'PP': 10, 'Power': '—', 'Accuracy': '90%'}, + 74: {"Move": 'Growth', 'Type': 'Normal', 'Category': 'Status', 'PP': 20, 'Power': '—', 'Accuracy': '—%'}, + 75: {"Move": 'Razor Leaf', 'Type': 'Grass', 'Category': 'Physical', 'PP': 25, 'Power': 55, 'Accuracy': '95%'}, + 76: {"Move": 'Solar Beam', 'Type': 'Grass', 'Category': 'Special', 'PP': 10, 'Power': 120, 'Accuracy': '100%'}, + 77: {"Move": 'Poison Powder', 'Type': 'Poison', 'Category': 'Status', 'PP': 35, 'Power': '—', 'Accuracy': '75%'}, + 78: {"Move": 'Stun Spore', 'Type': 'Grass', 'Category': 'Status', 'PP': 30, 'Power': '—', 'Accuracy': '75%'}, + 79: {"Move": 'Sleep Powder', 'Type': 'Grass', 'Category': 'Status', 'PP': 15, 'Power': '—', 'Accuracy': '75%'}, + 80: {"Move": 'Petal Dance', 'Type': 'Grass', 'Category': 'Special', 'PP': 10, 'Power': 120, 'Accuracy': '100%'}, + 81: {"Move": 'String Shot', 'Type': 'Bug', 'Category': 'Status', 'PP': 40, 'Power': '—', 'Accuracy': '95%'}, + 82: {"Move": 'Dragon Rage', 'Type': 'Dragon', 'Category': 'Special', 'PP': 10, 'Power': '—', 'Accuracy': '100%'}, + 83: {"Move": 'Fire Spin', 'Type': 'Fire', 'Category': 'Special', 'PP': 15, 'Power': 35, 'Accuracy': '85%'}, + 84: {"Move": 'Thunder Shock', 'Type': 'Electric', 'Category': 'Special', 'PP': 30, 'Power': 40, 'Accuracy': '100%'}, + 85: {"Move": 'Thunderbolt', 'Type': 'Electric', 'Category': 'Special', 'PP': 15, 'Power': 90, 'Accuracy': '100%'}, + 86: {"Move": 'Thunder Wave', 'Type': 'Electric', 'Category': 'Status', 'PP': 20, 'Power': '—', 'Accuracy': '90%'}, + 87: {"Move": 'Thunder', 'Type': 'Electric', 'Category': 'Special', 'PP': 10, 'Power': 110, 'Accuracy': '70%'}, + 88: {"Move": 'Rock Throw', 'Type': 'Rock', 'Category': 'Physical', 'PP': 15, 'Power': 50, 'Accuracy': '90%'}, + 89: {"Move": 'Earthquake', 'Type': 'Ground', 'Category': 'Physical', 'PP': 10, 'Power': 100, 'Accuracy': '100%'}, + 90: {"Move": 'Fissure', 'Type': 'Ground', 'Category': 'Physical', 'PP': 5, 'Power': '—', 'Accuracy': '30%'}, + 91: {"Move": 'Dig', 'Type': 'Ground', 'Category': 'Physical', 'PP': 10, 'Power': 80, 'Accuracy': '100%'}, + 92: {"Move": 'Toxic', 'Type': 'Poison', 'Category': 'Status', 'PP': 10, 'Power': '—', 'Accuracy': '90%'}, + 93: {"Move": 'Confusion', 'Type': 'Psychic', 'Category': 'Special', 'PP': 25, 'Power': 50, 'Accuracy': '100%'}, + 94: {"Move": 'Psychic', 'Type': 'Psychic', 'Category': 'Special', 'PP': 10, 'Power': 90, 'Accuracy': '100%'}, + 95: {"Move": 'Hypnosis', 'Type': 'Psychic', 'Category': 'Status', 'PP': 20, 'Power': '—', 'Accuracy': '60%'}, + 96: {"Move": 'Meditate', 'Type': 'Psychic', 'Category': 'Status', 'PP': 40, 'Power': '—', 'Accuracy': '—%'}, + 97: {"Move": 'Agility', 'Type': 'Psychic', 'Category': 'Status', 'PP': 30, 'Power': '—', 'Accuracy': '—%'}, + 98: {"Move": 'Quick Attack', 'Type': 'Normal', 'Category': 'Physical', 'PP': 30, 'Power': 40, 'Accuracy': '100%'}, + 99: {"Move": 'Rage', 'Type': 'Normal', 'Category': 'Physical', 'PP': 20, 'Power': 20, 'Accuracy': '100%'}, + 100: {"Move": 'Teleport', 'Type': 'Psychic', 'Category': 'Status', 'PP': 20, 'Power': '—', 'Accuracy': '—%'}, + 101: {"Move": 'Night Shade', 'Type': 'Ghost', 'Category': 'Special', 'PP': 15, 'Power': '—', 'Accuracy': '100%'}, + 102: {"Move": 'Mimic', 'Type': 'Normal', 'Category': 'Status', 'PP': 10, 'Power': '—', 'Accuracy': '—%'}, + 103: {"Move": 'Screech', 'Type': 'Normal', 'Category': 'Status', 'PP': 40, 'Power': '—', 'Accuracy': '85%'}, + 104: {"Move": 'Double Team', 'Type': 'Normal', 'Category': 'Status', 'PP': 15, 'Power': '—', 'Accuracy': '—%'}, + 105: {"Move": 'Recover', 'Type': 'Normal', 'Category': 'Status', 'PP': 5, 'Power': '—', 'Accuracy': '—%'}, + 106: {"Move": 'Harden', 'Type': 'Normal', 'Category': 'Status', 'PP': 30, 'Power': '—', 'Accuracy': '—%'}, + 107: {"Move": 'Minimize', 'Type': 'Normal', 'Category': 'Status', 'PP': 10, 'Power': '—', 'Accuracy': '—%'}, + 108: {"Move": 'Smokescreen', 'Type': 'Normal', 'Category': 'Status', 'PP': 20, 'Power': '—', 'Accuracy': '100%'}, + 109: {"Move": 'Confuse Ray', 'Type': 'Ghost', 'Category': 'Status', 'PP': 10, 'Power': '—', 'Accuracy': '100%'}, + 110: {"Move": 'Withdraw', 'Type': 'Water', 'Category': 'Status', 'PP': 40, 'Power': '—', 'Accuracy': '—%'}, + 111: {"Move": 'Defense Curl', 'Type': 'Normal', 'Category': 'Status', 'PP': 40, 'Power': '—', 'Accuracy': '—%'}, + 112: {"Move": 'Barrier', 'Type': 'Psychic', 'Category': 'Status', 'PP': 20, 'Power': '—', 'Accuracy': '—%'}, + 113: {"Move": 'Light Screen', 'Type': 'Psychic', 'Category': 'Status', 'PP': 30, 'Power': '—', 'Accuracy': '—%'}, + 114: {"Move": 'Haze', 'Type': 'Ice', 'Category': 'Status', 'PP': 30, 'Power': '—', 'Accuracy': '—%'}, + 115: {"Move": 'Reflect', 'Type': 'Psychic', 'Category': 'Status', 'PP': 20, 'Power': '—', 'Accuracy': '—%'}, + 116: {"Move": 'Focus Energy', 'Type': 'Normal', 'Category': 'Status', 'PP': 30, 'Power': '—', 'Accuracy': '—%'}, + 117: {"Move": 'Bide', 'Type': 'Normal', 'Category': 'Physical', 'PP': 10, 'Power': '—', 'Accuracy': '—%'}, + 118: {"Move": 'Metronome', 'Type': 'Normal', 'Category': 'Status', 'PP': 10, 'Power': '—', 'Accuracy': '—%'}, + 119: {"Move": 'Mirror Move', 'Type': 'Flying', 'Category': 'Status', 'PP': 20, 'Power': '—', 'Accuracy': '—%'}, + 120: {"Move": 'Self-Destruct', 'Type': 'Normal', 'Category': 'Physical', 'PP': 5, 'Power': 200, 'Accuracy': '100%'}, + 121: {"Move": 'Egg Bomb', 'Type': 'Normal', 'Category': 'Physical', 'PP': 10, 'Power': 100, 'Accuracy': '75%'}, + 122: {"Move": 'Lick', 'Type': 'Ghost', 'Category': 'Physical', 'PP': 30, 'Power': 30, 'Accuracy': '100%'}, + 123: {"Move": 'Smog', 'Type': 'Poison', 'Category': 'Special', 'PP': 20, 'Power': 30, 'Accuracy': '70%'}, + 124: {"Move": 'Sludge', 'Type': 'Poison', 'Category': 'Special', 'PP': 20, 'Power': 65, 'Accuracy': '100%'}, + 125: {"Move": 'Bone Club', 'Type': 'Ground', 'Category': 'Physical', 'PP': 20, 'Power': 65, 'Accuracy': '85%'}, + 126: {"Move": 'Fire Blast', 'Type': 'Fire', 'Category': 'Special', 'PP': 5, 'Power': 110, 'Accuracy': '85%'}, + 127: {"Move": 'Waterfall', 'Type': 'Water', 'Category': 'Physical', 'PP': 15, 'Power': 80, 'Accuracy': '100%'}, + 128: {"Move": 'Clamp', 'Type': 'Water', 'Category': 'Physical', 'PP': 15, 'Power': 35, 'Accuracy': '85%'}, + 129: {"Move": 'Swift', 'Type': 'Normal', 'Category': 'Special', 'PP': 20, 'Power': 60, 'Accuracy': '—%'}, + 130: {"Move": 'Skull Bash', 'Type': 'Normal', 'Category': 'Physical', 'PP': 10, 'Power': 130, 'Accuracy': '100%'}, + 131: {"Move": 'Spike Cannon', 'Type': 'Normal', 'Category': 'Physical', 'PP': 15, 'Power': 20, 'Accuracy': '100%'}, + 132: {"Move": 'Constrict', 'Type': 'Normal', 'Category': 'Physical', 'PP': 35, 'Power': 10, 'Accuracy': '100%'}, + 133: {"Move": 'Amnesia', 'Type': 'Psychic', 'Category': 'Status', 'PP': 20, 'Power': '—', 'Accuracy': '—%'}, + 134: {"Move": 'Kinesis', 'Type': 'Psychic', 'Category': 'Status', 'PP': 15, 'Power': '—', 'Accuracy': '80%'}, + 135: {"Move": 'Soft-Boiled', 'Type': 'Normal', 'Category': 'Status', 'PP': 5, 'Power': '—', 'Accuracy': '—%'}, + 136: {"Move": 'High Jump Kick', 'Type': 'Fighting', 'Category': 'Physical', 'PP': 10, 'Power': 130, 'Accuracy': '90%'}, + 137: {"Move": 'Glare', 'Type': 'Normal', 'Category': 'Status', 'PP': 30, 'Power': '—', 'Accuracy': '100%'}, + 138: {"Move": 'Dream Eater', 'Type': 'Psychic', 'Category': 'Special', 'PP': 15, 'Power': 100, 'Accuracy': '100%'}, + 139: {"Move": 'Poison Gas', 'Type': 'Poison', 'Category': 'Status', 'Power': 40, 'Accuracy': 90}, + 140: {"Move": 'Barrage', 'Type': 'Normal', 'Category': 'Physical', 'Power': 20, 'PP': 15, 'Accuracy': 85}, + 141: {"Move": 'Leech Life', 'Type': 'Bug', 'Category': 'Physical', 'Power': 10, 'PP': 80, 'Accuracy': 100}, + 142: {"Move": 'Lovely Kiss', 'Type': 'Normal', 'Category': 'Status', 'Power': 10, 'Accuracy': 75}, + 143: {"Move": 'Sky Attack', 'Type': 'Flying', 'Category': 'Physical', 'Power': 5, 'PP': 140, 'Accuracy': 90}, + 144: {"Move": 'Transform', 'Type': 'Normal', 'Category': 'Status', 'Power': 10}, + 145: {"Move": 'Bubble', 'Type': 'Water', 'Category': 'Special', 'Power': 30, 'PP': 40, 'Accuracy': 100}, + 146: {"Move": 'Dizzy Punch', 'Type': 'Normal', 'Category': 'Physical', 'Power': 10, 'PP': 70, 'Accuracy': 100}, + 147: {"Move": 'Spore', 'Type': 'Grass', 'Category': 'Status', 'Power': 15, 'Accuracy': 100}, + 148: {"Move": 'Flash', 'Type': 'Normal', 'Category': 'Status', 'Power': 20, 'Accuracy': 100}, + 149: {"Move": 'Psywave', 'Type': 'Psychic', 'Category': 'Special', 'Power': 15, 'Accuracy': 100}, + 150: {"Move": 'Splash', 'Type': 'Normal', 'Category': 'Status', 'Power': 40}, + 151: {"Move": 'Acid Armor', 'Type': 'Poison', 'Category': 'Status', 'Power': 20}, + 152: {"Move": 'Crabhammer', 'Type': 'Water', 'Category': 'Physical', 'Power': 10, 'PP': 100, 'Accuracy': 90}, + 153: {"Move": 'Explosion', 'Type': 'Normal', 'Category': 'Physical', 'Power': 5, 'PP': 250, 'Accuracy': 100}, + 154: {"Move": 'Fury Swipes', 'Type': 'Normal', 'Category': 'Physical', 'Power': 15, 'PP': 18, 'Accuracy': 80}, + 155: {"Move": 'Bonemerang', 'Type': 'Ground', 'Category': 'Physical', 'Power': 10, 'PP': 50, 'Accuracy': 90}, + 156: {"Move": 'Rest', 'Type': 'Psychic', 'Category': 'Status', 'Power': 5}, + 157: {"Move": 'Rock Slide', 'Type': 'Rock', 'Category': 'Physical', 'Power': 10, 'PP': 75, 'Accuracy': 90}, + 158: {"Move": 'Hyper Fang', 'Type': 'Normal', 'Category': 'Physical', 'Power': 15, 'PP': 80, 'Accuracy': 90}, + 159: {"Move": 'Sharpen', 'Type': 'Normal', 'Category': 'Status', 'Power': 30}, + 160: {"Move": 'Conversion', 'Type': 'Normal', 'Category': 'Status', 'Power': 30}, + 161: {"Move": 'Tri Attack', 'Type': 'Normal', 'Category': 'Special', 'Power': 10, 'PP': 80, 'Accuracy': 100}, + 162: {"Move": 'Super Fang', 'Type': 'Normal', 'Category': 'Physical', 'Power': 10, 'Accuracy': 90}, + 163: {"Move": 'Slash', 'Type': 'Normal', 'Category': 'Physical', 'Power': 20, 'PP': 70, 'Accuracy': 100}, + 164: {"Move": 'Substitute', 'Type': 'Normal', 'Category': 'Status', 'Power': 10}, + 165: {"Move": 'Struggle', 'Type': 'Normal', 'Category': 'Physical', 'Power': 1, 'PP': 50} +} + +status_dict = { + 0x08: 'Poison', + # 0x04: 'Burn', + # 0x05: 'Frozen', + # 0x06: 'Paralyze', + 0x00: 'None', +} + + +POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] # - Pokémon (Again) +STATUS = [0xD16F, 0xD19B, 0xD1C7, 0xD1F3, 0xD21F, 0xD24B] # - Status (Poisoned, Paralyzed, etc.) +TYPE1 = [0xD170, 0xD19C, 0xD1C8, 0xD1F4, 0xD220, 0xD24C] # - Type 1 +TYPE2 = [0xD171, 0xD19D, 0xD1C9, 0xD1F5, 0xD221, 0xD24D] # - Type 2 +LEVEL = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] # - Level (actual level) +MAXHP = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] # - Max HP if = 01 + 256 to MAXHP2 value +CHP = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] # - Current HP if = 01 + 256 + +MOVE1 = [0xD173, 0xD19F, 0xD1CB, 0xD1F7, 0xD223, 0xD24F] +MOVE2 = [0xD174, 0xD1A0, 0xD1CC, 0xD1F8, 0xD224, 0xD250] +MOVE3 = [0xD175, 0xD1A1, 0xD1CD, 0xD1F9, 0xD225, 0xD251] +MOVE4 = [0xD176, 0xD1A2, 0xD1CE, 0xD1FA, 0xD226, 0xD252] + +def pokemon_l(game): + pokemon_info = [{"slot": str(i + 1), "name": "", "level": "0", "moves": []} for i in range(6)] + for i in range(6): + p, l = game.get_memory_value(POKE[i]), game.get_memory_value(LEVEL[i]) + hex_value = hex(int(p))[2:].upper() + matching_pokemon = next((entry for entry in pokemon_data if entry.get('hex') == hex_value), None) + if matching_pokemon: + pokemon_info[i]["name"] = matching_pokemon["name"] + pokemon_info[i]["level"] = str(l) + moves_addresses = [MOVE1[i], MOVE2[i], MOVE3[i], MOVE4[i]] + pokemon_info[i]["moves"] = [] + for moves_address in moves_addresses: + move_value = game.get_memory_value(moves_address) + if move_value != 0x00: + move_info = moves_dict.get(move_value, {}) + move_name = move_info.get("Move", "") + pokemon_info[i]["moves"].append(move_name) + return pokemon_info \ No newline at end of file diff --git a/pokegym/environment.py b/pokegym/environment.py index 43201c7..48ce8bf 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -1,3 +1,4 @@ +import csv from pdb import set_trace as T import uuid from gymnasium import Env, spaces @@ -8,62 +9,18 @@ from pathlib import Path import mediapy as media + from pokegym.pyboy_binding import (ACTIONS, make_env, open_state_file, load_pyboy_state, run_action_on_emulator) -from pokegym import ram_map, game_map - - - - -def play(): - '''Creates an environment and plays it''' - env = Environment(rom_path='pokemon_red.gb', state_path=None, headless=False, - disable_input=False, sound=False, sound_emulated=False, verbose=True - ) - - env.reset() - env.game.set_emulation_speed(1) - - # Display available actions - print("Available actions:") - for idx, action in enumerate(ACTIONS): - print(f"{idx}: {action}") - - # Create a mapping from WindowEvent to action index - window_event_to_action = { - 'PRESS_ARROW_DOWN': 0, - 'PRESS_ARROW_LEFT': 1, - 'PRESS_ARROW_RIGHT': 2, - 'PRESS_ARROW_UP': 3, - 'PRESS_BUTTON_A': 4, - 'PRESS_BUTTON_B': 5, - 'PRESS_BUTTON_START': 6, - 'PRESS_BUTTON_SELECT': 7, - # Add more mappings if necessary - } - - while True: - # Get input from pyboy's get_input method - input_events = env.game.get_input() - env.game.tick() - env.render() - if len(input_events) == 0: - continue - - for event in input_events: - event_str = str(event) - if event_str in window_event_to_action: - action_index = window_event_to_action[event_str] - observation, reward, done, _, info = env.step( - action_index, fast_video=False) - - # Check for game over - if done: - print(f"{done}") - break - - # Additional game logic or information display can go here - print(f"new Reward: {reward}\n") +from pokegym import ram_map, game_map, data + +STATE_PATH = __file__.rstrip("environment.py") + "States" + +def get_random_state(): + state_files = [f for f in os.listdir(STATE_PATH) if f.endswith(".state")] + if not state_files: + raise FileNotFoundError("No State files found in the specified directory.") + return random.choice(state_files) class Base: def __init__( @@ -77,33 +34,33 @@ def __init__( ): """Creates a PokemonRed environment""" if state_path is None: - state_path = __file__.rstrip("environment.py") + "has_pokedex_nballs.state" + state_path = __file__.rstrip("environment.py") + "States/has_pokedex_nballs.state" # "has_pokedex_nballs.state" or self.randstate + with open("experiments/current_exp.txt", "r") as file: + exp_name = file.read() + R, C = self.screen.raw_screen_buffer_dims() + self.game, self.screen = make_env(rom_path, headless, quiet, save_video=True, **kwargs) - + self.state_file = get_random_state() + self.randstate = os.path.join(STATE_PATH, self.state_file) self.initial_states = [open_state_file(state_path)] self.save_video = save_video self.headless = headless self.mem_padding = 2 self.memory_shape = 80 self.use_screen_memory = True - self.s_path = Path(f'session_{str(uuid.uuid4())[:8]}') - self.instance_id = str(uuid.uuid4())[:8] + self.exp_path = Path(f'experiments/{str(exp_name)}') + self.env_id = Path(f'session_{str(uuid.uuid4())[:4]}') + self.s_path = Path(f'{str(self.exp_path)}/sessions/{str(self.env_id)}') self.reset_count = 0 - - R, C = self.screen.raw_screen_buffer_dims() self.obs_size = (R // 2, C // 2) if self.use_screen_memory: - self.screen_memory = defaultdict( - lambda: np.zeros((255, 255, 1), dtype=np.uint8) - ) + self.screen_memory = defaultdict(lambda: np.zeros((255, 255, 1), dtype=np.uint8)) self.obs_size += (4,) else: self.obs_size += (3,) - self.observation_space = spaces.Box( - low=0, high=255, dtype=np.uint8, shape=self.obs_size - ) + self.observation_space = spaces.Box(low=0, high=255, dtype=np.uint8, shape=self.obs_size) self.action_space = spaces.Discrete(len(ACTIONS)) def save_state(self): @@ -175,45 +132,64 @@ class Environment(Base): def __init__(self, rom_path='pokemon_red.gb', state_path=None, headless=True, save_video=True, quiet=False, verbose=False, **kwargs): super().__init__(rom_path, state_path, headless, save_video, quiet, **kwargs) - self.counts_map = np.zeros((444, 365)) + self.counts_map = np.zeros((444, 436)) self.verbose = verbose + self.log = True def add_video_frame(self): self.full_frame_writer.add_image(self.video()) + def write_to_log(self): + pokemon_info = data.pokemon_l(self.game) + x, y ,map_n = ram_map.position(self.game) + session_path = self.s_path + base_dir = self.exp_path + reset = self.reset_count + env_id = self.env_id + base_dir.mkdir(parents=True, exist_ok=True) + session_path.mkdir(parents=True, exist_ok=True) + csv_file_path = base_dir / "unique_positions.csv" + with open(session_path / self.full_name_log, 'w') as f: + for pokemon in pokemon_info: + f.write(f"Slot: {pokemon['slot']}\n") + f.write(f"Name: {pokemon['name']}\n") + f.write(f"Level: {pokemon['level']}\n") + f.write(f"Moves: {', '.join(pokemon['moves'])}\n") + f.write("\n") # Add a newline between Pokémon + with open(csv_file_path, 'a') as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow([env_id, reset, x, y, map_n]) def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" load_pyboy_state(self.game, self.load_random_state()) if self.save_video: - base_dir = self.s_path - base_dir.mkdir(parents=True, exist_ok=True) + env_path = self.s_path + env_path.mkdir(parents=True, exist_ok=True) full_name = Path(f'reset_{self.reset_count}').with_suffix('.mp4') - self.full_frame_writer = media.VideoWriter(base_dir / full_name, (144, 160), fps=60) + self.full_frame_writer = media.VideoWriter(env_path / full_name, (144, 160), fps=60) self.full_frame_writer.__enter__() - - if self.use_screen_memory: - self.screen_memory = defaultdict( - lambda: np.zeros((255, 255, 1), dtype=np.uint8) - ) + self.screen_memory = defaultdict(lambda: np.zeros((255, 255, 1), dtype=np.uint8)) self.time = 0 self.max_episode_steps = max_episode_steps self.reward_scale = reward_scale self.prev_map_n = None - self.max_events = 0 self.max_level_sum = 0 self.max_opponent_level = 0 self.seen_coords = set() self.seen_maps = set() + self.healing = 0 self.total_healing = 0 self.last_party_size = 1 self.last_reward = None - self.last_hp = [0] * 6 + self.last_hp = [1] * 6 + self.hp = [0] * 6 + self.hp_delta = [0] * 6 self.death = 0 self.qty = 0 self.reset_count += 1 @@ -227,30 +203,42 @@ def step(self, action, fast_video=True): self.time += 1 if self.save_video: self.add_video_frame() + - # Functions / Variables - r, c, map_n = ram_map.position(self.game) - party, party_size, party_levels = ram_map.party(self.game) - poke, type1, type2, level, hp, status = ram_map.pokemon(self.game) - self.seen_coords.add((r, c, map_n)) - coord_reward = 0.01 * len(self.seen_coords) - badges = ram_map.badges(self.game) + if self.log: + self.full_name_log = Path(f'party_log').with_suffix('.txt') + self.full_name_csv = Path(f'position_csv').with_suffix('.csv') + self.write_to_log() # Constants - map_check = set({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 41, 58, 64, 68, 89}) - item_check = ({1, 2, 3, 4, 6, 11, 16, 17, 18, 19, 20, 41, 42, 72, 73, 196, 197, 198, 199, 200, 53, 54}) + pokecenters = [41, 58, 64, 68, 81, 89, 133, 141, 154, 171, 147, 182] + towns = set({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) + item_check = [1, 2, 3, 4, 6, 11, 16, 17, 18, 19, 20, 41, 42, 72, 73, 196, 197, 198, 199, 200, 53, 54] + + # Functions / Variables + + party, party_size, party_levels = ram_map.party(self.game) party_size_constant = party_size == self.last_party_size - level_rewards = [] + + - # Math + # Exploration - # Save and Map + # Map Reward + r, c, map_n = ram_map.position(self.game) + self.seen_coords.add((r, c, map_n)) if map_n != self.prev_map_n: self.prev_map_n = map_n - if map_n not in self.seen_maps and map_n in map_check: - self.seen_maps.add(map_n) - # print(f'Map saved:', map_n) + if map_n not in self.seen_maps and map_n in pokecenters: self.save_state() + if map_n not in self.seen_maps: + self.seen_maps.add(map_n) + + coord_reward = 0.001 * len(self.seen_coords) + map_reward = 0.01 * len(self.seen_maps) + exploration_reward = coord_reward + map_reward + + #Plot Map glob_r, glob_c = game_map.local_to_global(r, c, map_n) try: @@ -258,37 +246,60 @@ def step(self, action, fast_video=True): except: pass + # Pokemon + poke, type1, type2, level, hp, status, death = ram_map.pokemon(self.game) + level_rewards = [] + #Levels for lvl in level: - if lvl < 15: + if lvl < 20: level_reward = .5 * lvl else: - level_reward = 7.5 + (lvl - 15) / 4 + level_reward = 7.5 + (lvl - 20) / 4 level_rewards.append(level_reward) - - # HP / Death - assert len(hp) == len(self.last_hp) - for h, i in zip(hp, self.last_hp): - hp_delta = h - i - if hp_delta > 0.25 and party_size_constant: #updated from 0 to 0.25 - self.total_healing += hp_delta - if h <= 0 and i > 0: - self.death += 1 - i = h - - # Reward / Random Values lvl_rew = sum(level_rewards) - healing_reward = self.total_healing/party_size self.max_level_sum = sum(level) - death_reward = -0.01 * self.death - map_reward = 1.0 * len(self.seen_maps) - exploration_reward = coord_reward + map_reward - badges_reward = 5 * badges + + # HP / Death + self.hp = hp + assert len(self.hp) == len(self.last_hp) + for h, i in zip(self.hp, self.last_hp): + # hp_delta = h - i + # if hp_delta > 0.25 and party_size_constant: #updated from 0 to 0.25 to .50 + # self.total_healing += hp_delta + # i = h + # self.death = sum(death) + # # if h <= 0 and i > 0 and dead_delta > 0: + # # j = 1 + # # elif h > 0.01: + # # j = 0 + # # if (sum(hp)) <= 0 and (sum(self.last_hp)) > 0: + last_hp = i + cur_health = h + if (cur_health > last_hp and party_size_constant): + if last_hp > 0: + heal_amount = cur_health - last_hp + if heal_amount > 0.1: + self.total_healing += heal_amount * 2 + else: + self.death += 1 + self.last_hp = self.hp + healing_reward = self.total_healing + death_reward = -1.0 * self.death + + # Update Values - self.last_hp = hp self.last_party_size = party_size + # Badges + badges = ram_map.badges(self.game) + badges_reward = 5 * badges + + # # Event reward + events = ram_map.events(self.game) + self.max_events = events + event_reward = self.max_events # Testing # # Items @@ -306,33 +317,31 @@ def step(self, action, fast_video=True): # opponent_level_reward = 0.2 * self.max_opponent_level - # # Event reward - # events = ram_map.events(self.game) - # self.max_events = max(self.max_events, events) - # event_reward = self.max_events + # # money reward # money = ram_map.money(self.game) # sum reward - reward = self.reward_scale * (lvl_rew + death_reward + badges_reward + exploration_reward + healing_reward) # + healing_reward - reward1 = (lvl_rew + badges_reward + exploration_reward + healing_reward) # + healing_reward + reward = self.reward_scale * (lvl_rew + badges_reward + exploration_reward + healing_reward + event_reward) # + death_reward + reward1 = (lvl_rew + badges_reward + exploration_reward + healing_reward + event_reward) # + healing_reward if death_reward == 0: neg_reward = 1 else: neg_reward = death_reward # print rewards - if self.headless == False: + if self.headless == False or self.save_video == True: print(f'-------------Counter-------------') print(f'Steps:',self.time,) print(f'Sum Reward:',reward) - print(f'Events:',self.max_events) + print(f'Coords:',len(self.seen_maps)) print(f'Total Level:',self.max_level_sum) print(f'Levels:',level) print(f'HP:',hp) print(f'Status:',status) print(f'Deaths:',self.death) + print(f'Is Dead:', death) print(f'Total Heal:',self.total_healing) print(f'Party Size:',self.last_party_size) print(f'-------------Rewards-------------') @@ -341,7 +350,7 @@ def step(self, action, fast_video=True): print(f'Healing:',healing_reward,'--%',100 * (healing_reward/reward1)) print(f'Badges:',badges_reward,'--%',100 * (badges_reward/reward1)) print(f'Level:',lvl_rew,'--%',100 * (lvl_rew/reward1)) - # print(f'Events:',event_reward,'--%',100 * (event_reward/reward1)) + print(f'Events:',event_reward,'--%',100 * (event_reward/reward1)) print(f'-------------Negatives-------------') print(f'Total:',neg_reward) print(f'Deaths:',death_reward, '--%', 100 * (death_reward/neg_reward)) @@ -373,11 +382,11 @@ def step(self, action, fast_video=True): done = self.time >= self.max_episode_steps if self.save_video and done: self.full_frame_writer.close() - if done: + if done: info = { 'reward': { 'delta': reward, - # 'event': event_reward, + 'event': event_reward, 'level': level_reward, #'opponent_level': opponent_level_reward, 'death': death_reward, @@ -390,10 +399,10 @@ def step(self, action, fast_video=True): 'highest_pokemon_level': max(party_levels), 'total_party_level': sum(party_levels), 'deaths': self.death, - 'badge_1': float(badges == 1), + 'badge_1': float(badges >= 1), 'badge_2': float(badges > 1), - # 'event': events, - # 'money': money, + 'event': events, + 'healing': self.total_healing, 'pokemon_exploration_map': self.counts_map, } @@ -406,7 +415,7 @@ def step(self, action, fast_video=True): f'death: {death_reward}', #f'op_level: {opponent_level_reward}', f'badges reward: {badges_reward}', - # f'event reward: {event_reward}', + f'event reward: {event_reward}', # f'money: {money}', f'ai reward: {reward}', f'Info: {info}', diff --git a/pokegym/map_data.json b/pokegym/map_data.json index 6cd9b76..2859c5a 100644 --- a/pokegym/map_data.json +++ b/pokegym/map_data.json @@ -8,7 +8,7 @@ 0 ], "tileSize": [ - 365, + 436, 444 ] }, @@ -494,7 +494,7 @@ }, { "id": "41", - "name": "Pokemon Center", + "name": "Pokemon Center Viridian", "coordinates": [ 95, 274 @@ -506,7 +506,7 @@ }, { "id": "42", - "name": "Pokemart", + "name": "Pokemart Viridian", "coordinates": [ 110, 274 @@ -578,7 +578,7 @@ }, { "id": "48", - "name": "Trader House", + "name": "Trader House Mr Mime Trade", "coordinates": [ 55, 193 @@ -662,7 +662,7 @@ }, { "id": "55", - "name": "Trainer House", + "name": "Trainer House Trading Tips", "coordinates": [ 45, 161 @@ -686,7 +686,7 @@ }, { "id": "57", - "name": "Trainer House", + "name": "Trainer House Catching Tips", "coordinates": [ 55, 175 @@ -698,7 +698,7 @@ }, { "id": "58", - "name": "Pokecenter", + "name": "Pokemon Center Pewter", "coordinates": [ 39, 170 @@ -746,7 +746,7 @@ }, { "id": "62", - "name": "House Breakin v1", + "name": "House Breakin", "coordinates": [ 280, 104 @@ -758,7 +758,7 @@ }, { "id": "63", - "name": "Trader House", + "name": "Trader House Jynx", "coordinates": [ 225, 119 @@ -770,7 +770,7 @@ }, { "id": "64", - "name": "Pokemon Center", + "name": "Pokemon Center Cerulean", "coordinates": [ 265, 104 @@ -796,7 +796,7 @@ "id": "66", "name": "Bike Shop", "coordinates": [ - 216, + 225, 147 ], "tileSize": [ @@ -806,10 +806,10 @@ }, { "id": "67", - "name": "Pokemart", + "name": "Pokemart Cerulean", "coordinates": [ 225, - 147 + 156 ], "tileSize": [ 8, @@ -818,7 +818,7 @@ }, { "id": "68", - "name": "Pokecenter", + "name": "Pokemon Center Route 4", "coordinates": [ 129, 128 @@ -830,7 +830,7 @@ }, { "id": "69", - "name": "House Breakin v2", + "name": "House Breakin V2", "coordinates": [ 280, 104 @@ -842,26 +842,26 @@ }, { "id": "70", - "name": "Underground Entrance", + "name": "Saffron City Gate North", "coordinates": [ 265, - 157 + 165 ], "tileSize": [ 8, - 8 + 6 ] }, { "id": "71", - "name": "Underground Entrance", + "name": "Underground Entrance North", "coordinates": [ - 265, - 166 + 274, + 163 ], "tileSize": [ 8, - 6 + 8 ] }, { @@ -890,28 +890,112 @@ }, { "id": "74", - "name": "Underground Path", + "name": "Underground Entrance South", "coordinates": [ - 276, - 147 + 265, + 255 ], "tileSize": [ 8, - 48 + 8 + ] + }, + { + "id": "76", + "name": "Saffron City Gate", + "coordinates": [ + 227, + 219 + ], + "tileSize": [ + 6, + 8 ] }, { - "id": "75", + "id": "77", "name": "Underground Entrance", "coordinates": [ - 265, - 255 + 216, + 219 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "78", + "name": "Underground Entrance V2", + "coordinates": [ + 216, + 219 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "79", + "name": "Underground Entrance V2", + "coordinates": [ + 275, + 219 + ], + "tileSize": [ + 6, + 8 + ] + }, + { + "id": "80", + "name": "Underground Entrance V2", + "coordinates": [ + 282, + 219 ], "tileSize": [ 8, 8 ] }, + { + "id": "81", + "name": "Pokemon Center Route 10", + "coordinates": [ + 355, + 136 + ], + "tileSize": [ + 14, + 8 + ] + }, + { + "id": "82", + "name": "Rock Tunnel 1F", + "coordinates": [ + 355, + 145 + ], + "tileSize": [ + 40, + 36 + ] + }, + { + "id": "83", + "name": "Power Plant", + "coordinates": [ + 293, + 147 + ], + "tileSize": [ + 40, + 36 + ] + }, { "id": "84", "name": "Gate 1F", @@ -948,6 +1032,18 @@ 8 ] }, + { + "id": "87", + "name": "Gate 1F", + "coordinates": [ + 355, + 232 + ], + "tileSize": [ + 10, + 8 + ] + }, { "id": "88", "name": "Bills Lab", @@ -962,7 +1058,7 @@ }, { "id": "89", - "name": "Pokemon Center", + "name": "Pokemon Center Vermilion", "coordinates": [ 219, 264 @@ -986,7 +1082,7 @@ }, { "id": "91", - "name": "Pokemart", + "name": "Pokemart Vermilion", "coordinates": [ 225, 282 @@ -1010,7 +1106,7 @@ }, { "id": "93", - "name": "Trader House", + "name": "Trader House Pidgy Letter", "coordinates": [ 225, 273 @@ -1188,6 +1284,18 @@ 8 ] }, + { + "id": "119", + "name": "Underground Path Route 5 to Route 6", + "coordinates": [ + 356, + 241 + ], + "tileSize": [ + 8, + 48 + ] + }, { "id": "120", "name": "Champions Room", @@ -1201,71 +1309,143 @@ ] }, { - "id": "163", - "name": "Fishing Guru", + "id": "121", + "name": "Undeground Path", "coordinates": [ - 210, - 264 + 355, + 287 ], "tileSize": [ - 8, + 49, 8 ] }, { - "id": "174", - "name": "Indigo Plateau Lobby", + "id": "122", + "name": "Department Store F1", "coordinates": [ - 16, - 72 + 170, + 183 ], "tileSize": [ - 16, - 12 + 20, + 8 ] }, { - "id": "177", - "name": "Fighting Dojo", + "id": "123", + "name": "Department Store F2", "coordinates": [ - 265, + 170, + 174 + ], + "tileSize": [ + 20, + 8 + ] + }, + { + "id": "124", + "name": "Department Store F3", + "coordinates": [ + 170, + 165 + ], + "tileSize": [ + 20, + 8 + ] + }, + { + "id": "125", + "name": "Department Store F4", + "coordinates": [ + 170, + 156 + ], + "tileSize": [ + 20, + 8 + ] + }, + { + "id": "126", + "name": "Department Store Roof", + "coordinates": [ + 149, + 174 + ], + "tileSize": [ + 20, + 8 + ] + }, + { + "id": "127", + "name": "Department Store Lift", + "coordinates": [ + 159, + 192 + ], + "tileSize": [ + 4, + 5 + ] + }, + { + "id": "128", + "name": "Office Building F1", + "coordinates": [ + 191, 179 ], "tileSize": [ - 10, + 8, 12 ] }, { - "id": "193", - "name": "Badge Gate House", + "id": "129", + "name": "Office Building F2", "coordinates": [ - 17, - 246 + 191, + 166 ], "tileSize": [ - 10, - 8 + 8, + 12 ] }, { - "id": "194", - "name": "Victory Road F2", + "id": "130", + "name": "Office Building F3", "coordinates": [ - 35, - 97 + 191, + 153 ], "tileSize": [ - 30, - 18 + 8, + 12 ] }, { - "id": "196", - "name": "Move Advice House", + "id": "131", + "name": "Office Building F1", "coordinates": [ - 225, - 291 + 200, + 170 + ], + "tileSize": [ + 8, + 12 + ] + }, + { + "id": "132", + "name": "Office Building Roof Room", + "coordinates": [ + 200, + 161 ], "tileSize": [ 8, @@ -1273,77 +1453,1229 @@ ] }, { - "id": "197", - "name": "DIgletts Cave", + "id": "133", + "name": "Pokemon Center Celedon", "coordinates": [ - 284, - 235 + 200, + 183 ], "tileSize": [ - 40, - 36 + 14, + 8 ] }, { - "id": "198", - "name": "Victory Road F3", + "id": "134", + "name": "Celadon Gym", "coordinates": [ - 35, - 78 + 153, + 219 ], "tileSize": [ - 30, + 10, 18 ] }, { - "id": "226", - "name": "Cerulean Cave 2F", + "id": "135", + "name": "Rocket Game Corner", "coordinates": [ - 213, - 81 + 164, + 238 ], "tileSize": [ - 30, + 20, 18 ] }, { - "id": "227", - "name": "Cerulean Cave B1F", + "id": "136", + "name": "Department Store F5", "coordinates": [ - 213, - 62 + 149, + 183 ], "tileSize": [ - 30, - 18 + 20, + 8 ] }, { - "id": "228", - "name": "Cerulean Cave Entrance", + "id": "137", + "name": "Prize Corner", "coordinates": [ - 213, - 100 + 169, + 229 ], "tileSize": [ - 30, - 18 + 10, + 8 ] }, { - "id": "230", - "name": "Badge Man House", + "id": "138", + "name": "Restaurant", "coordinates": [ - 216, - 119 + 180, + 229 + ], + "tileSize": [ + 10, + 8 + ] + }, + { + "id": "139", + "name": "Rocket House", + "coordinates": [ + 191, + 229 ], "tileSize": [ 8, 8 ] }, + { + "id": "140", + "name": "Hotel", + "coordinates": [ + 200, + 229 + ], + "tileSize": [ + 14, + 8 + ] + }, + { + "id": "141", + "name": "Pokemon Center Lavender", + "coordinates": [ + 310, + 191 + ], + "tileSize": [ + 14, + 8 + ] + }, + { + "id": "142", + "name": "Pokemon Tower F1", + "coordinates": [ + 387, + 239 + ], + "tileSize": [ + 20, + 18 + ] + }, + { + "id": "143", + "name": "Pokemon Tower F2", + "coordinates": [ + 366, + 239 + ], + "tileSize": [ + 20, + 18 + ] + }, + { + "id": "144", + "name": "Pokemon Tower F3", + "coordinates": [ + 387, + 220 + ], + "tileSize": [ + 20, + 18 + ] + }, + { + "id": "145", + "name": "Pokemon Tower F4", + "coordinates": [ + 366, + 220 + ], + "tileSize": [ + 20, + 18 + ] + }, + { + "id": "146", + "name": "Pokemon Tower F5", + "coordinates": [ + 387, + 201 + ], + "tileSize": [ + 20, + 18 + ] + }, + { + "id": "147", + "name": "Pokemon Tower F6", + "coordinates": [ + 366, + 201 + ], + "tileSize": [ + 20, + 18 + ] + }, + { + "id": "148", + "name": "Pokemon Tower F7", + "coordinates": [ + 366, + 182 + ], + "tileSize": [ + 20, + 18 + ] + }, + { + "id": "149", + "name": "Mr Fuji House", + "coordinates": [ + 325, + 191 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "150", + "name": "Pokemart Lavender", + "coordinates": [ + 355, + 210 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "151", + "name": "Cubone House", + "coordinates": [ + 316, + 219 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "152", + "name": "Pokemart Fuchsia", + "coordinates": [ + 165, + 353 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "153", + "name": "Bills Mom", + "coordinates": [ + 165, + 381 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "154", + "name": "Pokemon Center Fuchsia", + "coordinates": [ + 165, + 391 + ], + "tileSize": [ + 14, + 8 + ] + }, + { + "id": "155", + "name": "Wardens House", + "coordinates": [ + 205, + 391 + ], + "tileSize": [ + 10, + 8 + ] + }, + { + "id": "156", + "name": "Safari Gate", + "coordinates": [ + 189, + 348 + ], + "tileSize": [ + 8, + 6 + ] + }, + { + "id": "157", + "name": "Fuchsia Gym", + "coordinates": [ + 154, + 381 + ], + "tileSize": [ + 10, + 18 + ] + }, + { + "id": "158", + "name": "Meeting Room", + "coordinates": [ + 215, + 353 + ], + "tileSize": [ + 14, + 8 + ] + }, + { + "id": "159", + "name": "Sea Foam Islands 1F", + "coordinates": [ + 116, + 407 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "160", + "name": "Sea Foam Islands B1F", + "coordinates": [ + 116, + 388 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "161", + "name": "Sea Foam Islands B2F", + "coordinates": [ + 85, + 388 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "162", + "name": "Sea Foam Islands B3F", + "coordinates": [ + 85, + 369 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "163", + "name": "Fishing Guru Vermilion", + "coordinates": [ + 210, + 264 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "164", + "name": "Fishing Guru Fuchsia", + "coordinates": [ + 215, + 381 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "165", + "name": "Pokemon Mansion F1", + "coordinates": [ + 33, + 389 + ], + "tileSize": [ + 30, + 28 + ] + }, + { + "id": "166", + "name": "Cinnabar Gym", + "coordinates": [ + 85, + 407 + ], + "tileSize": [ + 20, + 18 + ] + }, + { + "id": "167", + "name": "Pokemon Lab", + "coordinates": [ + 45, + 427 + ], + "tileSize": [ + 18, + 8 + ] + }, + { + "id": "168", + "name": "Lab Room 1", + "coordinates": [ + 37, + 418 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "169", + "name": "Lab Room 2", + "coordinates": [ + 46, + 418 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "170", + "name": "Lab Room 3", + "coordinates": [ + 55, + 418 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "171", + "name": "Pokemon Center Cinnabar", + "coordinates": [ + 40, + 436 + ], + "tileSize": [ + 14, + 8 + ] + }, + { + "id": "172", + "name": "Pokemart Cinnabar", + "coordinates": [ + 55, + 436 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "173", + "name": "Pokemart V2", + "coordinates": [ + 55, + 436 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "174", + "name": "Indigo Plateau Lobby", + "coordinates": [ + 16, + 72 + ], + "tileSize": [ + 16, + 12 + ] + }, + { + "id": "175", + "name": "Coppy Cat F1", + "coordinates": [ + 215, + 191 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "176", + "name": "Coppy Cat F2", + "coordinates": [ + 215, + 182 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "177", + "name": "Fighting Dojo", + "coordinates": [ + 233, + 179 + ], + "tileSize": [ + 10, + 12 + ] + }, + { + "id": "178", + "name": "Saffron Gym", + "coordinates": [ + 265, + 172 + ], + "tileSize": [ + 20, + 18 + ] + }, + { + "id": "179", + "name": "Trainer House", + "coordinates": [ + 224, + 191 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "180", + "name": "Pokemart Saffron", + "coordinates": [ + 275, + 191 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "181", + "name": "Silph Co 1F", + "coordinates": [ + 325, + 109 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "182", + "name": "Pokemon Center Saffron", + "coordinates": [ + 229, + 229 + ], + "tileSize": [ + 14, + 8 + ] + }, + { + "id": "183", + "name": "Mr Psychics", + "coordinates": [ + 265, + 229 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "184", + "name": "Gate House F1", + "coordinates": [ + 230, + 351 + ], + "tileSize": [ + 8, + 10 + ] + }, + { + "id": "185", + "name": "Gate House F2", + "coordinates": [ + 230, + 342 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "186", + "name": "Cycling Gate F1", + "coordinates": [ + 140, + 185 + ], + "tileSize": [ + 8, + 14 + ] + }, + { + "id": "187", + "name": "Cycling Gate F2", + "coordinates": [ + 140, + 176 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "188", + "name": "Secret House", + "coordinates": [ + 128, + 191 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "189", + "name": "Fishing Guru Route 12", + "coordinates": [ + 325, + 291 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "190", + "name": "Cycling Gate F1", + "coordinates": [ + 156, + 351 + ], + "tileSize": [ + 8, + 10 + ] + }, + { + "id": "191", + "name": "Cycling Gate F2", + "coordinates": [ + 156, + 342 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "192", + "name": "Sea Foam Islands B4F", + "coordinates": [ + 85, + 350 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "193", + "name": "Badge Gate House", + "coordinates": [ + 17, + 246 + ], + "tileSize": [ + 10, + 8 + ] + }, + { + "id": "194", + "name": "Victory Road F2", + "coordinates": [ + 35, + 97 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "195", + "name": "Gate 2F", + "coordinates": [ + 355, + 223 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "196", + "name": "Trainer House Farfetch'd Trade", + "coordinates": [ + 225, + 291 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "197", + "name": "DIgletts Cave", + "coordinates": [ + 284, + 235 + ], + "tileSize": [ + 40, + 36 + ] + }, + { + "id": "198", + "name": "Victory Road F3", + "coordinates": [ + 35, + 78 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "199", + "name": "Rocket Hideout B1F", + "coordinates": [ + 97, + 117 + ], + "tileSize": [ + 30, + 28 + ] + }, + { + "id": "200", + "name": "Rocket Hideout B2F", + "coordinates": [ + 97, + 88 + ], + "tileSize": [ + 30, + 28 + ] + }, + { + "id": "201", + "name": "Rocket Hideout B3F", + "coordinates": [ + 97, + 59 + ], + "tileSize": [ + 30, + 28 + ] + }, + { + "id": "202", + "name": "Rocket Hideout B4F", + "coordinates": [ + 97, + 34 + ], + "tileSize": [ + 30, + 24 + ] + }, + { + "id": "203", + "name": "Rocket Hideout Lift", + "coordinates": [ + 121, + 137 + ], + "tileSize": [ + 6, + 8 + ] + }, + { + "id": "207", + "name": "Silph Co 2F", + "coordinates": [ + 325, + 90 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "208", + "name": "Silph Co 3F", + "coordinates": [ + 325, + 71 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "209", + "name": "Silph Co 4F", + "coordinates": [ + 325, + 52 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "210", + "name": "Silph Co 5F", + "coordinates": [ + 325, + 33 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "211", + "name": "Silph Co 6F", + "coordinates": [ + 325, + 14 + ], + "tileSize": [ + 26, + 18 + ] + }, + { + "id": "212", + "name": "Silph Co 7F", + "coordinates": [ + 356, + 109 + ], + "tileSize": [ + 26, + 18 + ] + }, + { + "id": "213", + "name": "Silph Co 8F", + "coordinates": [ + 356, + 90 + ], + "tileSize": [ + 26, + 18 + ] + }, + { + "id": "214", + "name": "Pokemon Mansion F2", + "coordinates": [ + 33, + 360 + ], + "tileSize": [ + 30, + 28 + ] + }, + { + "id": "215", + "name": "Pokemon Mansion F3", + "coordinates": [ + 33, + 341 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "216", + "name": "Pokemon Mansion B1F", + "coordinates": [ + 2, + 389 + ], + "tileSize": [ + 30, + 28 + ] + }, + { + "id": "217", + "name": "Safari West", + "coordinates": [ + 148, + 310 + ], + "tileSize": [ + 30, + 26 + ] + }, + { + "id": "218", + "name": "Safari East", + "coordinates": [ + 208, + 310 + ], + "tileSize": [ + 30, + 26 + ] + }, + { + "id": "219", + "name": "Safari North", + "coordinates": [ + 166, + 274 + ], + "tileSize": [ + 40, + 36 + ] + }, + { + "id": "220", + "name": "Safari South", + "coordinates": [ + 178, + 322 + ], + "tileSize": [ + 30, + 26 + ] + }, + { + "id": "221", + "name": "Rest House South", + "coordinates": [ + 209, + 337 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "222", + "name": "Secret House", + "coordinates": [ + 148, + 301 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "223", + "name": "Rest House", + "coordinates": [ + 157, + 301 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "224", + "name": "Rest House East", + "coordinates": [ + 230, + 301 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "225", + "name": "Rest House North", + "coordinates": [ + 207, + 273 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "226", + "name": "Cerulean Cave 2F", + "coordinates": [ + 213, + 81 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "227", + "name": "Cerulean Cave B1F", + "coordinates": [ + 213, + 62 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "228", + "name": "Cerulean Cave Entrance", + "coordinates": [ + 213, + 100 + ], + "tileSize": [ + 30, + 18 + ] + }, + { + "id": "229", + "name": "Name Rater House", + "coordinates": [ + 325, + 219 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "230", + "name": "Badge Man House", + "coordinates": [ + 216, + 119 + ], + "tileSize": [ + 8, + 8 + ] + }, + { + "id": "232", + "name": "Rock Tunnel 1F", + "coordinates": [ + 396, + 145 + ], + "tileSize": [ + 40, + 36 + ] + }, + { + "id": "233", + "name": "Silph Co 9F", + "coordinates": [ + 356, + 71 + ], + "tileSize": [ + 26, + 18 + ] + }, + { + "id": "234", + "name": "Silph Co 10F", + "coordinates": [ + 356, + 52 + ], + "tileSize": [ + 16, + 18 + ] + }, + { + "id": "235", + "name": "Silph Co 11F", + "coordinates": [ + 356, + 32 + ], + "tileSize": [ + 18, + 19 + ] + }, + { + "id": "236", + "name": "Silph Co Lift", + "coordinates": [ + 355, + 128 + ], + "tileSize": [ + 4, + 5 + ] + }, + { + "id": "239", + "name": "Trade Center", + "coordinates": [ + 85, + 283 + ], + "tileSize": [ + 10, + 8 + ] + }, + { + "id": "240", + "name": "Colosseum Club", + "coordinates": [ + 96, + 283 + ], + "tileSize": [ + 10, + 8 + ] + }, { "id": "245", "name": "Loreleis Room", diff --git a/pokegym/notes b/pokegym/notes new file mode 100644 index 0000000..aa58f9f --- /dev/null +++ b/pokegym/notes @@ -0,0 +1,601 @@ +import csv +from pdb import set_trace as T +import uuid +from gymnasium import Env, spaces +import numpy as np +from collections import defaultdict +import io, os +import random +from pathlib import Path +import mediapy as media + + +from pokegym.pyboy_binding import (ACTIONS, make_env, open_state_file, + load_pyboy_state, run_action_on_emulator) +from pokegym import ram_map, game_map, data +from environment import Base + +# ################################################################################################################## +# # this is env_logging: +# ################################################################################################################## + + + with open("experiments/current_exp.txt", "r") as file: + exp_name = file.read() + self.exp_path = Path(f'experiments/{str(exp_name)}') + self.s_path = Path(f'{str(self.exp_path)}/sessions/{str(self.env_id)}') + self.env_id = str(uuid.uuid4())[:4] + + def write_to_log(self): + pokemon_info = data.pokemon_l(self.game) + x, y ,map_n = ram_map.position(self.game) + session_path = self.s_path + base_dir = self.exp_path + reset = self.reset_count + env_id = self.env_id + csv_file_path = base_dir / "unique_positions.csv" + base_dir.mkdir(parents=True, exist_ok=True) + session_path.mkdir(parents=True, exist_ok=True) + with open(session_path / self.full_name_log, 'w') as f: + for pokemon in pokemon_info: + f.write(f"Slot: {pokemon['slot']}\n") + f.write(f"Name: {pokemon['name']}\n") + f.write(f"Level: {pokemon['level']}\n") + f.write(f"Moves: {', '.join(pokemon['moves'])}\n") + f.write("\n") # Add a newline between Pokémon + with open(csv_file_path, 'a') as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow([env_id, reset, x, y, map_n]) + + exp_path = Path(f'current_exp').with_suffix('.txt') + with open(data.config.data_dir / exp_path, 'w') as file: + file.write(f"{data.exp_name}") + + +# ################################################################################################################## +# # Useful functions +# ################################################################################################################## + + STATE_PATH = __file__.rstrip("environment.py") + "States" + + def get_random_state(): + state_files = [f for f in os.listdir(STATE_PATH) if f.endswith(".state")] + if not state_files: + raise FileNotFoundError("No State files found in the specified directory.") + return random.choice(state_files) + + state_file = get_random_state() + randstate = os.path.join(STATE_PATH, state_file) + +# ################################################################################################################## +# # TODO Pull Functions from this +# ################################################################################################################## + class Environment(Base): + def __init__( + self, + rom_path="pokemon_red.gb", + state_path=None, + headless=True, + save_video=True, + quiet=False, + verbose=False, + **kwargs, + ): + super().__init__(rom_path, state_path, headless, save_video, quiet, **kwargs) + self.counts_map = np.zeros((444, 436)) + self.verbose = verbose + self.screenshot_counter = 0 + self.include_conditions = [] + self.seen_maps_difference = set() + self.seen_maps = 0 + self.current_maps = [] + self.exclude_map_n = {37, 38, 39, 43, 52, 53, 55, 57} + self.exclude_map_n = set() + # self.exclude_map_n_moon = {0, 1, 2, 12, 13, 14, 15, 33, 34, 37, 38, 39, 40, 41, 42, 43, 44, 47, 50, 51, 52, 53, 54, 55, 56, 57, 58, 193, 68} + self.is_dead = False + self.talk_to_npc_reward = 0 + self.talk_to_npc_count = {} + self.already_got_npc_reward = set() + self.ss_anne_state = False + self.seen_npcs = set() + self.explore_npc_weight = 1 + self.last_map = -1 + self.init_hidden_obj_mem() + self.seen_pokemon = np.zeros(152, dtype=np.uint8) + self.caught_pokemon = np.zeros(152, dtype=np.uint8) + self.moves_obtained = np.zeros(0xA5, dtype=np.uint8) + self.log = True + + def update_pokedex(self): + for i in range(0xD30A - 0xD2F7): + caught_mem = self.game.get_memory_value(i + 0xD2F7) + seen_mem = self.game.get_memory_value(i + 0xD30A) + for j in range(8): + self.caught_pokemon[8*i + j] = 1 if caught_mem & (1 << j) else 0 + self.seen_pokemon[8*i + j] = 1 if seen_mem & (1 << j) else 0 + + def update_moves_obtained(self): + # Scan party + for i in [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247]: + if self.game.get_memory_value(i) != 0: + for j in range(4): + move_id = self.game.get_memory_value(i + j + 8) + if move_id != 0: + if move_id != 0: + self.moves_obtained[move_id] = 1 + # Scan current box (since the box doesn't auto increment in pokemon red) + num_moves = 4 + box_struct_length = 25 * num_moves * 2 + for i in range(self.game.get_memory_value(0xda80)): + offset = i*box_struct_length + 0xda96 + if self.game.get_memory_value(offset) != 0: + for j in range(4): + move_id = self.game.get_memory_value(offset + j + 8) + if move_id != 0: + self.moves_obtained[move_id] = 1 + + def get_items_in_bag(self, one_indexed=0): + first_item = 0xD31E + # total 20 items + # item1, quantity1, item2, quantity2, ... + item_ids = [] + for i in range(0, 20, 2): + item_id = self.game.get_memory_value(first_item + i) + if item_id == 0 or item_id == 0xff: + break + item_ids.append(item_id + one_indexed) + return item_ids + + def get_hm_rewards(self): + hm_ids = [0xC4, 0xC5, 0xC6, 0xC7, 0xC8] + items = self.get_items_in_bag() + total_hm_cnt = 0 + for hm_id in hm_ids: + if hm_id in items: + total_hm_cnt += 1 + return total_hm_cnt * 1 + + def update_heat_map(self, r, c, current_map): + ''' + Updates the heat map based on the agent's current position. + + Args: + r (int): global y coordinate of the agent's position. + c (int): global x coordinate of the agent's position. + current_map (int): ID of the current map (map_n) + + Updates the counts_map to track the frequency of visits to each position on the map. + ''' + # Convert local position to global position + try: + glob_r, glob_c = game_map.local_to_global(r, c, current_map) + except IndexError: + print(f'IndexError: index {glob_r} or {glob_c} for {current_map} is out of bounds for axis 0 with size 444.') + glob_r = 0 + glob_c = 0 + + # Update heat map based on current map + if self.last_map == current_map or self.last_map == -1: + # Increment count for current global position + try: + self.counts_map[glob_r, glob_c] += 1 + except: + pass + else: + # Reset count for current global position if it's a new map for warp artifacts + self.counts_map[(glob_r, glob_c)] = -1 + + # Update last_map for the next iteration + self.last_map = current_map + + def find_neighboring_npc(self, npc_bank, npc_id, player_direction, player_x, player_y) -> int: + + npc_y = ram_map.npc_y(self.game, npc_id, npc_bank) + npc_x = ram_map.npc_x(self.game, npc_id, npc_bank) + if ( + (player_direction == 0 and npc_x == player_x and npc_y > player_y) or + (player_direction == 4 and npc_x == player_x and npc_y < player_y) or + (player_direction == 8 and npc_y == player_y and npc_x < player_x) or + (player_direction == 0xC and npc_y == player_y and npc_x > player_x) + ): + # Manhattan distance + return abs(npc_y - player_y) + abs(npc_x - player_x) + + return 1000 + + def step(self, action, fast_video=True): + run_action_on_emulator( + self.game, + self.screen, + ACTIONS[action], + self.headless, + fast_video=fast_video, + ) + self.time += 1 + if self.save_video: + self.add_video_frame() + + # Exploration reward + r, c, map_n = ram_map.position(self.game) + # Convert local position to global position + try: + glob_r, glob_c = game_map.local_to_global(r, c, map_n) + except IndexError: + print(f'IndexError: index {glob_r} or {glob_c} is out of bounds for axis 0 with size 444.') + glob_r = 0 + glob_c = 0 + + # Only reward for specified coordinates, not all coordinates seen + if self.rewardable_coords(glob_c, glob_r): + self.seen_coords.add((r, c, map_n)) + else: + self.seen_coords_no_reward.add((glob_c, glob_r, map_n)) + + if map_n != self.prev_map_n: + self.prev_map_n = map_n + if map_n not in self.seen_maps: + self.seen_maps.add(map_n) + self.talk_to_npc_count[map_n] = 0 # Initialize NPC talk count for this new map + self.save_state() + + self.update_pokedex() + self.update_moves_obtained() + + exploration_reward = 0.01 * len(self.seen_coords) + self.update_heat_map(r, c, map_n) + + # Level reward + party_size, party_levels = ram_map.party(self.game) + self.max_level_sum = max(self.max_level_sum, sum(party_levels)) + if self.max_level_sum < 30: + level_reward = 1 * self.max_level_sum + else: + level_reward = 30 + (self.max_level_sum - 30) / 4 + + # Healing and death rewards + hp = ram_map.hp(self.game) + hp_delta = hp - self.last_hp + party_size_constant = party_size == self.last_party_size + + # Only reward if not reviving at pokecenter + if hp_delta > 0 and party_size_constant and not self.is_dead: + self.total_healing += hp_delta + + # Dead if hp is zero + if hp <= 0 and self.last_hp > 0: + self.death_count += 1 + self.is_dead = True + elif hp > 0.01: # TODO: Check if this matters + self.is_dead = False + + # Update last known values for next iteration + self.last_hp = hp + self.last_party_size = party_size + death_reward = 0 # -0.08 * self.death_count # -0.05 + + # Set rewards + healing_reward = self.total_healing + + # Opponent level reward + max_opponent_level = max(ram_map.opponent(self.game)) + self.max_opponent_level = max(self.max_opponent_level, max_opponent_level) + opponent_level_reward = 0 # 0.2 * self.max_opponent_level + + # Badge reward + badges = ram_map.badges(self.game) + badges_reward = 5 * badges + + # Save Bill + bill_state = ram_map.saved_bill(self.game) + bill_reward = 10 * bill_state + + # SS Anne appeared + ss_anne_state = ram_map.ss_anne_appeared(self.game) + if ss_anne_state: + ss_anne_state_reward = 10 # 5 + ss_anne_obtained = 1 + else: + ss_anne_state_reward = 0 + ss_anne_obtained = 0 + + # HM reward + hm_count = self.get_hm_rewards() + hm_reward = hm_count * 10 # 5 + + # Event reward + events = ram_map.events(self.game) + self.max_events = max(self.max_events, events) + event_reward = self.max_events + + money = ram_map.money(self.game) + + # Explore NPCs + # check if the font is loaded + if ram_map.mem_val(self.game, 0xCFC4): + # check if we are talking to a hidden object: + if ram_map.mem_val(self.game, 0xCD3D) == 0x0 and ram_map.mem_val(self.game, 0xCD3E) == 0x0: + # add hidden object to seen hidden objects + self.seen_hidden_objs.add((ram_map.mem_val(self.game, 0xD35E), ram_map.mem_val(self.game, 0xCD3F))) + else: + # check if we are talking to someone + # if ram_map.if_font_is_loaded(self.game): + # get information for player + player_direction = ram_map.player_direction(self.game) + player_y = ram_map.player_y(self.game) + player_x = ram_map.player_x(self.game) + # get the npc who is closest to the player and facing them + # we go through all npcs because there are npcs like + # nurse joy who can be across a desk and still talk to you + mindex = (0, 0) + minv = 1000 + for npc_bank in range(1): + + for npc_id in range(1, ram_map.sprites(self.game) + 15): + npc_dist = self.find_neighboring_npc(npc_bank, npc_id, player_direction, player_x, player_y) + if npc_dist < minv: + mindex = (npc_bank, npc_id) + minv = npc_dist + self.seen_npcs.add((ram_map.map_n(self.game), mindex[0], mindex[1])) + + explore_npcs_reward = self.reward_scale * self.explore_npc_weight * len(self.seen_npcs) * 0.00015 + seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) * 0.00010 + caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) * 0.00010 + moves_obtained_reward = self.reward_scale * sum(self.moves_obtained) * 0.00010 + explore_hidden_objs_reward = self.reward_scale * self.explore_hidden_obj_weight * len(self.seen_hidden_objs) * 0.00015 + + reward = self.reward_scale * ( + event_reward + + explore_npcs_reward # Doesn't reset on reset but maybe should? + + seen_pokemon_reward + + caught_pokemon_reward + + moves_obtained_reward + + explore_hidden_objs_reward # Resets on reset + + bill_reward + + hm_reward + + level_reward + + opponent_level_reward + + death_reward # Resets on reset + + badges_reward + + healing_reward # Resets each step + + exploration_reward # Resets on reset + ) + + # Subtract previous reward + # TODO: Don't record large cumulative rewards in the first place + if self.last_reward is None: + reward = 0 + self.last_reward = 0 + else: + nxt_reward = reward + reward -= self.last_reward + self.last_reward = nxt_reward + + info = {} + done = self.time >= self.max_episode_steps + if self.save_video and done: + self.full_frame_writer.close() + if done: + info = { + "reward": { + "delta": reward, + "event": event_reward, + "level": level_reward, + "opponent_level": opponent_level_reward, + "death": death_reward, + "badges": badges_reward, + "bill_saved_reward": bill_reward, + "hm_count_reward": hm_reward, + "ss_anne_done_reward": ss_anne_state_reward, + "healing": healing_reward, + "exploration": exploration_reward, + "explore_npcs_reward": explore_npcs_reward, + "seen_pokemon_reward": seen_pokemon_reward, + "caught_pokemon_reward": caught_pokemon_reward, + "moves_obtained_reward": moves_obtained_reward, + "hidden_obj_count_reward": explore_hidden_objs_reward, + }, + "maps_explored": len(self.seen_maps), + "party_size": party_size, + "highest_pokemon_level": max(party_levels), + "total_party_level": sum(party_levels), + "deaths": self.death_count, + "bill_saved": bill_state, + "hm_count": hm_count, + "ss_anne_obtained": ss_anne_obtained, + "badge_1": float(badges >= 1), + "badge_2": float(badges >= 2), + "event": events, + "money": money, + "pokemon_exploration_map": self.counts_map, + "seen_npcs_count": len(self.seen_npcs), + "seen_pokemon": sum(self.seen_pokemon), + "caught_pokemon": sum(self.caught_pokemon), + "moves_obtained": sum(self.moves_obtained), + "hidden_obj_count": len(self.seen_hidden_objs), + } + + if self.verbose: + print( + f'number of signs: {ram_map.signs(self.game)}, number of sprites: {ram_map.sprites(self.game)}\n', + f"steps: {self.time}\n", + f"seen_npcs #: {len(self.seen_npcs)}\n", + f"seen_npcs set: {self.seen_npcs}\n", + # f"is_in_battle: {ram_map.is_in_battle(self.game)}", + f"exploration reward: {exploration_reward}\n", + f"explore_npcs reward: {explore_npcs_reward}\n", + f"level_Reward: {level_reward}\n", + f"healing: {healing_reward}\n", + f"death: {death_reward}\n", + f"op_level: {opponent_level_reward}\n", + f"badges reward: {badges_reward}\n", + f"event reward: {event_reward}\n", + f"money: {money}\n", + f"ai reward: {reward}\n", + f"Info: {info}\n", + ) + + return self.render(), reward, done, done, info + +# ################################################################################################################## +# # Battle Functions +# ################################################################################################################## + + self.ap_party_level = 0 # The sum of player (agent) Pokémon levels in the party + self.wp_party_level = 0 # The sum of enemy (wild) Pokémon levels in the party + self.ap_level = 0 # The level of the player (agent) Pokémon in the lead position + self.wp_level = 0 # The level of the enemy (wild) Pokémon in the lead position + self.ap_party_size = 0 # The battle_reward test initialize variable current player (agent) party size + # (how many Pokémon in party) + self.wp_party_size = 0 # The battle_reward test initialize variable current enemy (wild) party size + # (how many Pokémon in party) + self.ap_health = self.get_ap_health_sum() # The sum of player (agent) Pokémon health in the party + self.wp_health = 0 # The sum of enemy (wild) Pokémon health in the party + self.in_battle = 0 # Flag of current battle state (-1 white-out, 0 normal, 1 wild, 2 trainer) + self.battle_rewarded = False # Was a reward given from last battle? + self.battle_reward_value = 0 # points rewarded from last battle + self.total_battle_reward = 0 # total points rewarded + self.levels_satisfied = False + + def get_ap_level_sum(self): + sum_levels = sum(self.read_m(a) for a in [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268]) + if sum_levels < 0: + return 1 + else: + return sum_levels + + def get_wp_level_sum(self): + sum_levels = sum(self.read_m(a) for a in [0xCFF3, 0xD8F1, 0xD91D, 0xD949, 0xD975, 0xD9A1]) + if sum_levels < 0: + return 1 + else: + return sum_levels + + def read_low_high(self, low_adder, high_adder): + low = self.read_m(low_adder) + high = self.read_m(high_adder) + return low + high + + def get_ap_health_sum(self): + # HP addresses for each Pokémon in the party + hp_addresses = [ + (0xD015, 0xD016), # Pokemon 1 + (0xD198, 0xD199), # Pokemon 2 + (0xD1C4, 0xD1C5), # Pokemon 3 + (0xD1F0, 0xD1F1), # Pokemon 4 + (0xD21C, 0xD21D), # Pokemon 5 + (0xD248, 0xD249), # Pokemon 6 + ] + + sum_hp = 0 + + # Iterate through each pair of addresses and sum the HP values + for low_adder, high_adder in hp_addresses: + sum_hp += self.read_low_high(low_adder, high_adder) + + return sum_hp + + def get_wp_health_sum(self): + # HP addresses for each Pokémon in the party + hp_addresses = [ + (0xD8A5, 0xCFE7), # Pokemon 1 + (0xD8D1, 0xD8D2), # Pokemon 2 + (0xD8FD, 0xD8FE), # Pokemon 3 + (0xD929, 0xD92A), # Pokemon 4 + (0xD955, 0xD956), # Pokemon 5 + (0xD981, 0xD982), # Pokemon 6 + ] + + sum_hp = 0 + + # Iterate through each pair of addresses and sum the HP values + for low_adder, high_adder in hp_addresses: + sum_hp += self.read_low_high(low_adder, high_adder) + + return sum_hp + + def update_battle_values(self): + self.ap_party_size = self.read_m(0xD163) + self.wp_party_size = self.read_m(0xD89C) + self.ap_health = self.get_ap_health_sum() + self.wp_health = self.get_wp_health_sum() + self.ap_party_level = self.get_ap_level_sum() + self.wp_party_level = self.get_wp_level_sum() + self.ap_level = self.read_m(0xCD0F) + self.wp_level = self.read_m(0xCD23) + self.in_battle = self.read_m(0xD057) + + print( + f"Update Battle values Function\n" + f"Battle Flag: {self.in_battle}\n" + f"Last Battle Rewarded: {self.battle_rewarded}\n" + f"Level Difference: {self.ap_level - self.wp_level}\n" + f"Reward: {self.battle_reward_value}\n\n" + ) + + def has_battle_been_won(self): + + is_in_battle = self.in_battle >= 1 + + fled = self.read_m(0xD078) # needed for console only + pokeball_catch = self.read_m(0xD11C) # needed for console only + + if is_in_battle: + enemy_party_hp = self.get_wp_health_sum() + + if enemy_party_hp <= 0 and self.in_battle != -1: + return True + + # Required to print to Console only + if self.in_battle == -1 or fled != 0 or pokeball_catch != 0: # whiteout, run/flee, capture + self.battle_rewarded = False + self.battle_reward_value = 0 + return False + else: + return False + + def calculate_battle_reward(self): + # ap = Agent Party, wp = Wild Pokémon + if not self.has_battle_been_won(): + return 0 + + bonus = 0 # Resets bonus for calculation + + trainer_battle = self.in_battle == 2 + + # Calculate level difference for wild battles + if not trainer_battle: + level_difference = self.ap_level - self.wp_level + else: + ap_avg_level = self.ap_party_level / self.ap_party_size # if self.ap_party_size > 0 else 0 + wp_avg_level = self.wp_party_level / self.wp_party_size # if self.wp_party_size > 0 else 0 + level_difference = ap_avg_level - wp_avg_level + + # Assign base rewards based on the level difference + if level_difference == 4: + reward = 0.2 + elif level_difference == 3: + reward = 0.4 + elif level_difference == 2: + reward = 0.6 + elif level_difference == 1: + reward = 0.8 + elif level_difference == 0: + reward = 1 + elif level_difference == -1: + reward = 1.5 + elif level_difference <= -2: + reward = 2 + else: + reward = 0 # Default case, if needed + + # Add a bonus for winning against a larger team in trainer battles + if trainer_battle and self.ap_party_size <= self.wp_party_size: + team_size_difference = abs(self.wp_party_size - self.ap_party_size) + bonus = 0.5 * team_size_difference + 0.5 + reward += bonus + + self.battle_rewarded = True + self.battle_reward_value = reward + self.total_battle_reward += reward # to keep record of total reward + return reward + diff --git a/pokegym/ram_map.py b/pokegym/ram_map.py index abaf014..0793929 100644 --- a/pokegym/ram_map.py +++ b/pokegym/ram_map.py @@ -1,7 +1,11 @@ -# addresses from https://datacrystal.romhacking.net/wiki/Pok%C3%A9mon_Red/Blue:RAM_map -# https://github.com/pret/pokered/blob/91dc3c9f9c8fd529bb6e8307b58b96efa0bec67e/constants/event_constants.asm -HP_ADDR = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] -MAX_HP_ADDR = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] +# ###################################################################################### +# Ram_map +# ###################################################################################### + +# Data Crystal - https://datacrystal.romhacking.net/wiki/Pok%C3%A9mon_Red/Blue:RAM_map +# No Comments - https://github.com/pret/pokered/blob/91dc3c9f9c8fd529bb6e8307b58b96efa0bec67e/constants/event_constants.asm +# Comments - https://github.com/luckytyphlosion/pokered/blob/master/constants/event_constants.asm + PARTY_SIZE_ADDR = 0xD163 PARTY_ADDR = [0xD164, 0xD165, 0xD166, 0xD167, 0xD168, 0xD169] PARTY_LEVEL_ADDR = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] @@ -24,23 +28,9 @@ MONEY_ADDR_100 = 0xD348 MONEY_ADDR_10000 = 0xD349 BAG = [0xD31E, 0xD320, 0xD322, 0xD324, 0xD326, 0xD328, 0xD32A, 0xD32C, 0xD32E, 0xD330, 0xD332, 0xD334, 0xD336, 0xD338, 0xD33A, 0xD33C, 0xD33E, 0xD340, 0xD342, 0xD344] - - -#Trainer Moves/PP counter if 00 then no move is present -P1MOVES = [0xD173, 0xD174, 0xD175, 0xD176] -P2MOVES = [0xD19F, 0xD1A0, 0xD1A1, 0xD1A2] -P3MOVES = [0xD1CB, 0xD1CC, 0xD1CD, 0xD1CE] -P4MOVES = [0xD1F7, 0xD1F8, 0xD1F9, 0xD1FA] -P5MOVES = [0xD223, 0xD224, 0xD225, 0xD226] -P6MOVES = [0xD24F, 0xD250, 0xD251, 0xD252] - -P1MOVEPP = [0xD188, 0xD189, 0xD18A, 0xD18B] -P2MOVEPP = [0xD1B4, 0xD1B5, 0xD1B6, 0xD1B7] -P3MOVEPP = [0xD1E0, 0xD1E1, 0xD1E2, 0xD1E3] -P4MOVEPP = [0xD20C, 0xD20D, 0xD20E, 0xD20F] -P5MOVEPP = [0xD238, 0xD239, 0xD23A, 0xD23B] -P6MOVEPP = [0xD264, 0xD265, 0xD266, 0xD267] - +EVENTS = [0xD710, 0xD7D8, 0xD7E0, 0xD803, 0xD5F3, 0xD60D] +BIRDS = [0xD782, 0xD7D4, 0xD7EE] +GYMS = [0xD755, 0xD75E, 0xD773, 0xD77C, 0xD792, 0xD7B3, 0xD79A, 0xD751] POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] # - Pokémon (Again) STATUS = [0xD16F, 0xD19B, 0xD1C7, 0xD1F3, 0xD21F, 0xD24B] # - Status (Poisoned, Paralyzed, etc.) TYPE1 = [0xD170, 0xD19C, 0xD1C8, 0xD1F4, 0xD220, 0xD24C] # - Type 1 @@ -48,8 +38,14 @@ LEVEL = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] # - Level (actual level) MAXHP = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] # - Max HP if = 01 + 256 to MAXHP2 value CHP = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] # - Current HP if = 01 + 256 - - +MOVE1PP = [0xD188, 0xD1B4, 0xD1E0, 0xD20C, 0xD238, 0xD264] +MOVE2PP = [0xD189, 0xD1B5, 0xD1E1, 0xD20D, 0xD239, 0xD265] +MOVE3PP = [0xD18A, 0xD1B6, 0xD1E2, 0xD20E, 0xD23A, 0xD266] +MOVE4PP = [0xD18B, 0xD1B7, 0xD1E3, 0xD20F, 0xD23B, 0xD267] +MOVE1 = [0xD173, 0xD19F, 0xD1CB, 0xD1F7, 0xD223, 0xD24F] +MOVE2 = [0xD174, 0xD1A0, 0xD1CC, 0xD1F8, 0xD224, 0xD250] +MOVE3 = [0xD175, 0xD1A1, 0xD1CD, 0xD1F9, 0xD225, 0xD251] +MOVE4 = [0xD176, 0xD1A2, 0xD1CE, 0xD1FA, 0xD226, 0xD252] STATUSDICT = { 0x08: 'Poison', @@ -59,30 +55,7 @@ 0x00: 'None', } -def bcd(num): - return 10 * ((num >> 4) & 0x0f) + (num & 0x0f) - -def bit_count(bits): - return bin(bits).count('1') - -def read_bit(game, addr, bit) -> bool: - # add padding so zero will read '0b100000000' instead of '0b0' - return bin(256 + game.get_memory_value(addr))[-bit-1] == '1' - -def read_uint16(game, start_addr): - '''Read 2 bytes''' - val_256 = game.get_memory_value(start_addr) - val_1 = game.get_memory_value(start_addr + 1) - return 256*val_256 + val_1 - -def position(game): - r_pos = game.get_memory_value(Y_POS_ADDR) - c_pos = game.get_memory_value(X_POS_ADDR) - map_n = game.get_memory_value(MAP_N_ADDR) - return r_pos, c_pos, map_n - -#start new functions - +# start new functions def pokemon(game): status = [] poke = [game.get_memory_value(a) for a in POKE] @@ -95,22 +68,78 @@ def pokemon(game): level = [game.get_memory_value(a) for a in LEVEL] mhp = [read_uint16(game, a) for a in MAXHP] chp = [read_uint16(game, a) for a in CHP] - hp = [] - # moves = [game.get_memory_value(addr) for addr in P1MOVES] # movepp = [game.get_memory_value(addr) for addr in P1MOVEPP] + hp = [] + death = [] assert len(mhp) == len(chp) for h, i in zip(mhp, chp): if h == 0: - j = 1 + j = 0 else: j = i / h hp.append(j) - return poke, type1, type2, level, hp, status - + if i == 0 and h != 0: + dead = 1 + else: + dead = 0 + death.append(dead) + return poke, type1, type2, level, hp, status, death + +def items(game, start_addr): + i = [] + q = [] + item = game.get_memory_value(start_addr) + qty = game.get_memory_value(start_addr + 1) + i.append(item) + q.append(qty) + return i, q + +def item_bag(game): + items_list = [items(game, a) for a in BAG] + item, qty = zip(*items_list) + print(f'Items:', item, qty,'List:', items_list) + return item, qty +def events(game): + #0xd7f2 + num_events = sum(bit_count(game.get_memory_value(i)) for i in EVENTS) + museum_ticket = int(read_bit(game, MUSEUM_TICKET_ADDR, 0)) + birds = sum(bit_count(game.get_memory_value(i)) for i in BIRDS) + gyms = sum(bit_count(game.get_memory_value(i)) for i in GYMS) -#end new functions + # Omit 13 events by default + return num_events + museum_ticket + (birds * 10) + (gyms * 5) +# end new functions + +# Start Utilities +def bcd(num): + return 10 * ((num >> 4) & 0x0f) + (num & 0x0f) + +def read_m(game, val): + addr = game.get_memory_value(val) + return addr + +def bit_count(bits): + return bin(bits).count('1') + +def read_bit(game, addr, bit) -> bool: + # add padding so zero will read '0b100000000' instead of '0b0' + return bin(256 + game.get_memory_value(addr))[-bit-1] == '1' + +def read_uint16(game, start_addr): + '''Read 2 bytes''' + val_256 = game.get_memory_value(start_addr) + val_1 = game.get_memory_value(start_addr + 1) + return 256*val_256 + val_1 +# End Utilities + +# TODO +def position(game): + r_pos = game.get_memory_value(Y_POS_ADDR) + c_pos = game.get_memory_value(X_POS_ADDR) + map_n = game.get_memory_value(MAP_N_ADDR) + return r_pos, c_pos, map_n def party(game): party = [game.get_memory_value(addr) for addr in PARTY_ADDR] @@ -143,135 +172,106 @@ def money(game): def badges(game): badges = game.get_memory_value(BADGE_1_ADDR) return bit_count(badges) - -def events(game): -# D710 - Fossilized Pokémon? -# D7D8 - Fought Snorlax Yet (Vermilion) -# D7E0 - Fought Snorlax Yet? (Celadon) -# D803 - Is SS Anne here? - '''Adds up all event flags, exclude museum ticket''' - num_events = sum(bit_count(game.get_memory_value(i)) - for i in range(EVENT_FLAGS_START_ADDR, EVENT_FLAGS_END_ADDR)) - museum_ticket = int(read_bit(game, MUSEUM_TICKET_ADDR, 0)) - - # Omit 13 events by default - return max(num_events - 13 - museum_ticket, 0) - - - -# Menu Data - -# Coordinates of the position of the cursor for the top menu item (id 0) -# CC24 : Y position -# CC25 : X position - -# CC26 - Currently selected menu item (topmost is 0) -# CC27 - Tile "hidden" by the menu cursor -# CC28 - ID of the last menu item -# CC29 - bitmask applied to the key port for the current menu -# CC2A - ID of the previously selected menu item -# CC2B - Last position of the cursor on the party / Bill's PC screen -# CC2C - Last position of the cursor on the item screen -# CC2D - Last position of the cursor on the START / battle menu -# CC2F - Index (in party) of the Pokémon currently sent out -# CC30~CC31 - Pointer to cursor tile in C3A0 buffer -# CC36 - ID of the first displayed menu item -# CC35 - Item highlighted with Select (01 = first item, 00 = no item, etc.) -# CC3A and CC3B are unused -# cc51 and cc52 both read 00 when menu is closed - - -# Pokémon Mart - -# JPN addr. INT addr. Description -# CF62 CF7B Total Items -# CF63 CF7C Item 1 -# CF64 CF7D Item 2 -# CF65 CF7E Item 3 -# CF66 CF7F Item 4 -# CF67 CF80 Item 5 -# CF68 CF81 Item 6 -# CF69 CF82 Item 7 -# CF70 CF83 Item 8 -# CF71 CF84 Item 9 -# CF72 CF85 Item 10 - - -# Event Flags - -# D5A6 to D5C5 : Missable Objects Flags (flags for every (dis)appearing sprites, like the guard in Cerulean City or the Pokéballs in Oak's Lab) -# D5AB - Starters Back? -# D5C0(bit 1) - 0=Mewtwo appears, 1=Doesn't (See D85F) -# D5F3 - Have Town map? -# D60D - Have Oak's Parcel? -# D700 - Bike Speed -# D70B - Fly Anywhere Byte 1 -# D70C - Fly Anywhere Byte 2 -# D70D - Safari Zone Time Byte 1 -# D70E - Safari Zone Time Byte 2 -# D710 - Fossilized Pokémon? -# D714 - Position in Air -# D72E - Did you get Lapras Yet? -# D732 - Debug New Game -# D751 - Fought Giovanni Yet? -# D755 - Fought Brock Yet? -# D75E - Fought Misty Yet? -# D773 - Fought Lt. Surge Yet? -# D77C - Fought Erika Yet? -# D782 - Fought Articuno Yet? -# D790 - If bit 7 is set, Safari Game over -# D792 - Fought Koga Yet? -# D79A - Fought Blaine Yet? -# D7B3 - Fought Sabrina Yet? -# D7D4 - Fought Zapdos Yet? -# D7D8 - Fought Snorlax Yet (Vermilion) -# D7E0 - Fought Snorlax Yet? (Celadon) -# D7EE - Fought Moltres Yet? -# D803 - Is SS Anne here? -# D85F - Mewtwo can be caught if bit 2 clear - Needs D5C0 bit 1 clear, too - - -# def items(game, start_addr): -# item = game.get_memory_value(start_addr) -# qty = game.get_memory_value(start_addr + 1) -# return item, qty - -# def item_bag(game): -# item, qty = [items(game, a) for a in BAG] -# return item, qty - - -# 1, 2, 3, 4, 6, 11, 16, 17, 18, 19, 20, 41, 42, 72, 73, 196, 197, 198, 199, 200, 53, 54 - - -# 001 0x01 Master Ball -# 002 0x02 Ultra Ball -# 003 0x03 Great Ball -# 004 0x04 Poké Ball -# 006 0x06 Bicycle -# 011 0x0B Antidote -# 016 0x10 Full Restore -# 017 0x11 Max Potion -# 018 0x12 Hyper Potion -# 019 0x13 Super Potion -# 020 0x14 Potion -# 041 0x29 Dome Fossil -# 042 0x2A Helix Fossil -# 072 0x48 Silph Scope -# 073 0x49 Poké Flute -# 196 0xC4 HM01 -# 197 0xC5 HM02 -# 198 0xC6 HM03 -# 199 0xC7 HM04 -# 200 0xC8 HM05 -# 053 0x35 Revive -# 054 0x36 Max Revive - - - - - - +# End TODO + +# ################################################################################################################## +# # Notes +# ################################################################################################################## + +## Misc + # 0xc4f2 check for EE hex for text box arrow is present + +## Menu Data + # Coordinates of the position of the cursor for the top menu item (id 0) + # CC24 : Y position + # CC25 : X position + # CC26 - Currently selected menu item (topmost is 0) + # CC27 - Tile "hidden" by the menu cursor + # CC28 - ID of the last menu item + # CC29 - bitmask applied to the key port for the current menu + # CC2A - ID of the previously selected menu item + # CC2B - Last position of the cursor on the party / Bill's PC screen + # CC2C - Last position of the cursor on the item screen + # CC2D - Last position of the cursor on the START / battle menu + # CC2F - Index (in party) of the Pokémon currently sent out + # CC30~CC31 - Pointer to cursor tile in C3A0 buffer + # CC36 - ID of the first displayed menu item + # CC35 - Item highlighted with Select (01 = first item, 00 = no item, etc.) + # CC3A and CC3B are unused + # cc51 and cc52 both read 00 when menu is closed + +## Pokémon Mart + # JPN addr. INT addr. Description + # CF62 CF7B Total Items + # CF63 CF7C Item 1 + # CF64 CF7D Item 2 + # CF65 CF7E Item 3 + # CF66 CF7F Item 4 + # CF67 CF80 Item 5 + # CF68 CF81 Item 6 + # CF69 CF82 Item 7 + # CF70 CF83 Item 8 + # CF71 CF84 Item 9 + # CF72 CF85 Item 10 + +## Event Flags + # D751 - Fought Giovanni Yet? + # D755 - Fought Brock Yet? + # D75E - Fought Misty Yet? + # D773 - Fought Lt. Surge Yet? + # D77C - Fought Erika Yet? + # D792 - Fought Koga Yet? + # D79A - Fought Blaine Yet? + # D7B3 - Fought Sabrina Yet? + # D782 - Fought Articuno Yet? + # D7D4 - Fought Zapdos Yet? + # D7EE - Fought Moltres Yet? + # D710 - Fossilized Pokémon? + # D7D8 - Fought Snorlax Yet (Vermilion) + # D7E0 - Fought Snorlax Yet? (Celadon) + # D803 - Is SS Anne here + # D5F3 - Have Town map? + # D60D - Have Oak's Parcel? + # D5A6 to D5C5 : Missable Objects Flags (flags for every (dis)appearing sprites, like the guard in Cerulean City or the Pokéballs in Oak's Lab) + # D5AB - Starters Back? + # D5C0(bit 1) - 0=Mewtwo appears, 1=Doesn't (See D85F) + # D700 - Bike Speed + # D70B - Fly Anywhere Byte 1 + # D70C - Fly Anywhere Byte 2 + # D70D - Safari Zone Time Byte 1 + # D70E - Safari Zone Time Byte 2 + # D714 - Position in Air + # D72E - Did you get Lapras Yet? + # D732 - Debug New Game + # D790 - If bit 7 is set, Safari Game over + # D85F - Mewtwo can be caught if bit 2 clear - Needs D5C0 bit 1 clear, too + +## Item IDs & String + # 1, 2, 3, 4, 6, 11, 16, 17, 18, 19, 20, 41, 42, 72, 73, 196, 197, 198, 199, 200, 53, 54 + # 001 0x01 Master Ball + # 002 0x02 Ultra Ball + # 003 0x03 Great Ball + # 004 0x04 Poké Ball + # 006 0x06 Bicycle + # 011 0x0B Antidote + # 016 0x10 Full Restore + # 017 0x11 Max Potion + # 018 0x12 Hyper Potion + # 019 0x13 Super Potion + # 020 0x14 Potion + # 041 0x29 Dome Fossil + # 042 0x2A Helix Fossil + # 072 0x48 Silph Scope + # 073 0x49 Poké Flute + # 196 0xC4 HM01 + # 197 0xC5 HM02 + # 198 0xC6 HM03 + # 199 0xC7 HM04 + # 200 0xC8 HM05 + # 053 0x35 Revive + # 054 0x36 Max Revive + +## Item Bag # 0xD31D - Total Items # 0xD31E - Item 1 # 0xD320 - Item 2 From dc49ad26afd5c781a34fc16a367e2d49202a5c33 Mon Sep 17 00:00:00 2001 From: leanke Date: Wed, 10 Jan 2024 20:38:48 +0000 Subject: [PATCH 05/29] cleanup/logging --- pokegym/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index 48ce8bf..367159f 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -34,7 +34,7 @@ def __init__( ): """Creates a PokemonRed environment""" if state_path is None: - state_path = __file__.rstrip("environment.py") + "States/has_pokedex_nballs.state" # "has_pokedex_nballs.state" or self.randstate + state_path = STATE_PATH + "has_pokedex_nballs.state" # STATE_PATH + "has_pokedex_nballs.state" or self.randstate with open("experiments/current_exp.txt", "r") as file: exp_name = file.read() R, C = self.screen.raw_screen_buffer_dims() From b82b7aee9045a6e57255cc51d0e57340d3648159 Mon Sep 17 00:00:00 2001 From: leanke Date: Wed, 10 Jan 2024 23:29:18 +0000 Subject: [PATCH 06/29] state file fix --- pokegym/environment.py | 65 ++++++++++++++++++++++-------------------- pokegym/notes | 6 +++- pokegym/ram_map.py | 27 ++++++++++++++++-- 3 files changed, 63 insertions(+), 35 deletions(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index 367159f..3257779 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -14,8 +14,7 @@ load_pyboy_state, run_action_on_emulator) from pokegym import ram_map, game_map, data -STATE_PATH = __file__.rstrip("environment.py") + "States" - +STATE_PATH = __file__.rstrip("environment.py") + "States/" def get_random_state(): state_files = [f for f in os.listdir(STATE_PATH) if f.endswith(".state")] if not state_files: @@ -34,13 +33,12 @@ def __init__( ): """Creates a PokemonRed environment""" if state_path is None: - state_path = STATE_PATH + "has_pokedex_nballs.state" # STATE_PATH + "has_pokedex_nballs.state" or self.randstate + state_path = STATE_PATH + "Bulbasaur.state" # STATE_PATH + "has_pokedex_nballs.state" or self.randstate with open("experiments/current_exp.txt", "r") as file: exp_name = file.read() - R, C = self.screen.raw_screen_buffer_dims() - self.game, self.screen = make_env(rom_path, headless, quiet, save_video=True, **kwargs) + R, C = self.screen.raw_screen_buffer_dims() self.state_file = get_random_state() self.randstate = os.path.join(STATE_PATH, self.state_file) self.initial_states = [open_state_file(state_path)] @@ -223,7 +221,6 @@ def step(self, action, fast_video=True): # Exploration - # Map Reward r, c, map_n = ram_map.position(self.game) self.seen_coords.add((r, c, map_n)) @@ -239,7 +236,6 @@ def step(self, action, fast_video=True): exploration_reward = coord_reward + map_reward #Plot Map - glob_r, glob_c = game_map.local_to_global(r, c, map_n) try: self.counts_map[glob_r, glob_c] += 1 @@ -287,8 +283,6 @@ def step(self, action, fast_video=True): healing_reward = self.total_healing death_reward = -1.0 * self.death - - # Update Values self.last_party_size = party_size @@ -301,13 +295,17 @@ def step(self, action, fast_video=True): self.max_events = events event_reward = self.max_events + # HM reward + hm_count = ram_map.get_hm_rewards(self.game) + hm_reward = hm_count * 10 # 5 + # Testing # # Items - # item, qty = ram_map.item_bag(self.game) - # assert len(item) == len(qty) - # for i, q in zip(item, qty): - # if i in item_check: - # self.qty += .01 * q + # items = ram_map.get_items_in_bag(self.game) + # if items[0] in item_check: + # self.qty += .01 * items[1] + # print(f'Items:{items}') + # print(f'Debug:{items[0]}, {items[1]}') #TODO @@ -323,8 +321,8 @@ def step(self, action, fast_video=True): # money = ram_map.money(self.game) # sum reward - reward = self.reward_scale * (lvl_rew + badges_reward + exploration_reward + healing_reward + event_reward) # + death_reward - reward1 = (lvl_rew + badges_reward + exploration_reward + healing_reward + event_reward) # + healing_reward + reward = self.reward_scale * (lvl_rew + badges_reward + exploration_reward + healing_reward + event_reward + hm_reward) # + death_reward + reward1 = (lvl_rew + badges_reward + exploration_reward + healing_reward + event_reward + hm_reward) # + healing_reward if death_reward == 0: neg_reward = 1 else: @@ -336,6 +334,9 @@ def step(self, action, fast_video=True): print(f'Steps:',self.time,) print(f'Sum Reward:',reward) print(f'Coords:',len(self.seen_maps)) + print(f'HM Count:',hm_count) + # print(f'Items:',items) + # print(f'Items Reward::',self.qty) print(f'Total Level:',self.max_level_sum) print(f'Levels:',level) print(f'HP:',hp) @@ -346,6 +347,7 @@ def step(self, action, fast_video=True): print(f'Party Size:',self.last_party_size) print(f'-------------Rewards-------------') print(f'Total:',reward1) + print(f'HM Reward:',hm_reward) print(f'Explore:',exploration_reward,'--%',100 * (exploration_reward/reward1)) print(f'Healing:',healing_reward,'--%',100 * (healing_reward/reward1)) print(f'Badges:',badges_reward,'--%',100 * (badges_reward/reward1)) @@ -388,7 +390,7 @@ def step(self, action, fast_video=True): 'delta': reward, 'event': event_reward, 'level': level_reward, - #'opponent_level': opponent_level_reward, + 'hm_count': hm_count, 'death': death_reward, 'badges': badges_reward, 'healing': healing_reward, @@ -399,6 +401,7 @@ def step(self, action, fast_video=True): 'highest_pokemon_level': max(party_levels), 'total_party_level': sum(party_levels), 'deaths': self.death, + 'hm': hm_reward, 'badge_1': float(badges >= 1), 'badge_2': float(badges > 1), 'event': events, @@ -406,19 +409,19 @@ def step(self, action, fast_video=True): 'pokemon_exploration_map': self.counts_map, } - if self.verbose: - print( - f'steps: {self.time}', - f'exploration reward: {exploration_reward}', - f'level_Reward: {level_reward}', - f'healing: {healing_reward}', - f'death: {death_reward}', - #f'op_level: {opponent_level_reward}', - f'badges reward: {badges_reward}', - f'event reward: {event_reward}', - # f'money: {money}', - f'ai reward: {reward}', - f'Info: {info}', - ) + # if self.verbose: + # print( + # f'steps: {self.time}', + # f'exploration reward: {exploration_reward}', + # f'level_Reward: {level_reward}', + # f'healing: {healing_reward}', + # f'death: {death_reward}', + # #f'op_level: {opponent_level_reward}', + # f'badges reward: {badges_reward}', + # f'event reward: {event_reward}', + # # f'money: {money}', + # f'ai reward: {reward}', + # f'Info: {info}', + # ) return self.render(), reward, done, done, info diff --git a/pokegym/notes b/pokegym/notes index aa58f9f..14a5f36 100644 --- a/pokegym/notes +++ b/pokegym/notes @@ -1,3 +1,7 @@ +# ################################################################################################################## +# # Imports because syntax +# ################################################################################################################## + import csv from pdb import set_trace as T import uuid @@ -16,7 +20,7 @@ from pokegym import ram_map, game_map, data from environment import Base # ################################################################################################################## -# # this is env_logging: +# # this is env_logging: # ################################################################################################################## diff --git a/pokegym/ram_map.py b/pokegym/ram_map.py index 0793929..888d9b5 100644 --- a/pokegym/ram_map.py +++ b/pokegym/ram_map.py @@ -110,15 +110,36 @@ def events(game): # Omit 13 events by default return num_events + museum_ticket + (birds * 10) + (gyms * 5) + +def get_items_in_bag(game, one_indexed=0): + first_item = 0xD31E + # total 20 items + # item1, quantity1, item2, quantity2, ... + item_ids = [] + for i in range(0, 20, 2): + item_id = game.get_memory_value(first_item + i) + if item_id == 0 or item_id == 0xff: + break + item_ids.append(item_id + one_indexed) + return item_ids + +def get_hm_rewards(game): + hm_ids = [0xC4, 0xC5, 0xC6, 0xC7, 0xC8] + items = get_items_in_bag(game) + total_hm_cnt = 0 + for hm_id in hm_ids: + if hm_id in items: + total_hm_cnt += 1 + return total_hm_cnt * 1 # end new functions # Start Utilities def bcd(num): return 10 * ((num >> 4) & 0x0f) + (num & 0x0f) -def read_m(game, val): - addr = game.get_memory_value(val) - return addr +def read_m(game, addr): + val = game.get_memory_value(addr) + return val def bit_count(bits): return bin(bits).count('1') From 68740debcc18057a24cd356bbc9949aa6257a352 Mon Sep 17 00:00:00 2001 From: leanke Date: Thu, 18 Jan 2024 12:09:00 +0000 Subject: [PATCH 07/29] 0.6 logging / baseline set --- pokegym/States/3rdTownWin.state | Bin 142610 -> 0 bytes pokegym/States/Bill.state | Bin 142610 -> 0 bytes pokegym/States/Celedon.state | Bin 142610 -> 0 bytes pokegym/States/CeledonFly.state | Bin 142610 -> 0 bytes pokegym/States/CeruleanBill.state | Bin 142610 -> 0 bytes pokegym/States/EndFlashCave.state | Bin 142610 -> 0 bytes pokegym/States/LavenderPre.state | Bin 142610 -> 0 bytes pokegym/States/LtSurge.state | Bin 142610 -> 0 bytes pokegym/States/Pallet.state | Bin 142610 -> 0 bytes pokegym/States/Pewter.state | Bin 142610 -> 0 bytes pokegym/States/VermillionCut.state | Bin 142610 -> 0 bytes pokegym/States/ViridianPre.state | Bin 142610 -> 0 bytes pokegym/States/misty.state | Bin 142610 -> 0 bytes pokegym/States/ssanne.state | Bin 142610 -> 0 bytes pokegym/data.py | 145 ++++++ pokegym/environment.py | 795 +++++++++++++++++++---------- pokegym/game_map.py | 9 +- pokegym/map_data.json | 0 pokegym/notes | 605 ---------------------- pokegym/pyboy_binding.py | 4 +- pokegym/ram_map.py | 397 ++++++-------- 21 files changed, 836 insertions(+), 1119 deletions(-) delete mode 100644 pokegym/States/3rdTownWin.state delete mode 100644 pokegym/States/Bill.state delete mode 100644 pokegym/States/Celedon.state delete mode 100644 pokegym/States/CeledonFly.state delete mode 100644 pokegym/States/CeruleanBill.state delete mode 100644 pokegym/States/EndFlashCave.state delete mode 100644 pokegym/States/LavenderPre.state delete mode 100644 pokegym/States/LtSurge.state delete mode 100644 pokegym/States/Pallet.state delete mode 100644 pokegym/States/Pewter.state delete mode 100644 pokegym/States/VermillionCut.state delete mode 100644 pokegym/States/ViridianPre.state delete mode 100644 pokegym/States/misty.state delete mode 100644 pokegym/States/ssanne.state mode change 100644 => 100755 pokegym/map_data.json delete mode 100644 pokegym/notes diff --git a/pokegym/States/3rdTownWin.state b/pokegym/States/3rdTownWin.state deleted file mode 100644 index 4b74b09d0eabb1ed0f9a979f4bcfb790ee98f4f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142610 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 W3P=T{0#X5~fK)&#Fdz!J-TxoZY|>l+ diff --git a/pokegym/States/Celedon.state b/pokegym/States/Celedon.state deleted file mode 100644 index 9a354b6db85618a8b244b2a738e4e105dd768d7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142610 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 diff --git a/pokegym/States/CeruleanBill.state b/pokegym/States/CeruleanBill.state deleted file mode 100644 index f7d3dfd71c22f8f0c5c074f1f208accbeb5525a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142610 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 W3P=T{0#X5~fK)&#Fdz!J-TxoZY|>l+ diff --git a/pokegym/States/EndFlashCave.state b/pokegym/States/EndFlashCave.state deleted file mode 100644 index cdbca96144fcf326af980c252fbbe230465a2836..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142610 zcmeHP3w%`7ojx;@%!Du`lMrBlkQq#npjZ+C6H3fPL_kG|3PQC?q^??}B_h=bV`g}0 z<6Fck_D3z-)~@S&`$TI?RxLi(_0`(CwJT8(kX1}6;0TkM{r}Is-_4oJBq2Z)$T{P^ z-#P#Pd4A_Rj~T!|f`Vh+z*Zp=&$Vs(WzgXe{e;8g$;$Nwe1VBop^8XtWKv6uuQe-{ z75jTt{F6j>Vp8bj>IspVk(m=h0|R-1oSgXd@Y=-A#Ll(xD`QQu`g)Wv3sr_9^CB}* z-WxBBC-x?GhQ?Nfmd?B3lB&=kqx`E?@%Q#VxTGq+O(|bl5k9><6etK3pnPGRLu^gJ z*VpUuxF}?JG6-9J+ZtYqZL8c?+1eUxX>R6Gxm>xq0Scj0LuH(9Zfa(w!td)9ya`;W3>t#{Yeaek|OC=}wfRi1hf8WS2ja~hZT z8|630U)`C{<^8tuRCr>*XD`oV%FXh4GTknxC)4GMuZ{U*l`p-t?y`Fqx2$V!Hu%dA zj68KrW$5J4$;SAn#Wp{?t1v!2KK*MGDngN~Bhx}<0Uy`1cI`UWjk5V*a^(i@S=*d6Alh=ULo+k~k~j+;di_jP-NFOl$tVte*#W zV*ai2QauajSmmX9_G}N8helsJvwmJB^mluF+-o~?_7v+6yh57nvF=ljT`^@jZJJAH=;} zKE676w^}~*a$fn@ z{cmkeDPLnN?~Ho`{t(X$@o$um`Qtn@#J^GA;qbV*e>4JLU|_&U{FiO>Q~Me5%i;xb z8WXQy?m10EuE!Il?;QoD*K1yuKmeA5&FvVqxK~zi+R8Ik}@K=HFHI$)77LQ9iFI7#dz(7(n@^hK%^i zaAoYoP;F#Ya#6$|7#%7O6&C(Eo}IWQIV0hZjUydbR(idG{)PR0fnaEUdy|?aG>FQpvhH+I^x?OTQB$=kofs^t+aJe_p@F z{(oi&`?s`wPOPXJ6FECF7v=x{N!6~zTZtj5#l6ITd`sfNMR7VK8XAn=^ZE^i8sjH%lYEg#LxdLq zmuFuhQIy~XAj=O53@Q#yjZ~NO{)zH2Iz#!KGW(y?FWckhwpz=YYiQZlt*dLPWB&QP z;5|66h!q?(&!4i^rUq7UM!a%lD9Fs@dR;EgTevx%K~-l_JJ z11AXG078*kA#j4w4IrM#j!cRyYWzkhG&GQt+lSU~Vp4qi;>M5gr;|5{e z0NTqNHvrmut_{zd7#du}0;KXg<9q|y`QW0exG@5wecS-r%hL=};9Ze-kU*d$)SvDj zu{C@H*cOYe*|m0a-0$ZeC@1)-M%p?Bo~7eY-T(&Q-*lHe|GmZyV9m!jyxiJqIye4NL=Gyk2BPyHwsOD#`%C)(Pl^OIGJ5O$jvIh8u`DV1ryBs>Ug!ema{XuIwB-BAL?X6^HkZqo z0qX>pHvok@c>`E@Uh=8NGb=(vhZ*<3J!i$e;d>Jwy!VS|&H1;=%N;~1FL#iIbCUlN znLak;r^AKspL@2)=?3uL2Ss#)P;Y<4y}SXa<(X^lzhqzP$4jE58$ex4kj@WNKXM1O zZUD?5tzqj1P+cA>jm&90H!_3jr}Hy>d%SS(mP zRJx^w`B!fMJbv{CAostj$h?Mmq0oS#RG#-gx&ajJjME0j?WdHlv6bij&(AlAhMC6r zY5((6`MpJZC$;~m&e`1Yp zjji>q^<{K{ z0|*q3{TlB-(_)iJfXf?ii3~{Y*}?k{-GG!E0Q0Bb0O($qUp*l-hR$y~$N2o`8vq?r zwEmuw`wxG!>FX^VT1tH^r~QX-0JW_zHB6?Pc1HZTP+_2QOlVBeVB(O<^ZNDC$rYwM z#5;2TNxcCGv3~tGH-ByCz`cW$gOdY351q&}(2q{g(AQ5T1^?jlw?F@2ezdu{rnUYl zsUJ(f@&*9?k$&Y3fY$FX8qW+>1O{8{cRH`%1l<6zf2%hDrM$6z=k5D#cxGs9AYhi? zYu*4ze`sr+H%L+cU^E2SD9^SX&doz}tGxG*+ zzy7IMR?V|(H$OW$KKY7E8cz!phf0cuW#+hO{l2>RXMeA6T0gD!sg^op_0#$Y@+M0s zZ8Wj0rG?fFjDquXud=REBpnVb-QL~IgEZrYu_H*-Txl%!JMIyYAJ z$v1a3Zg2bRGFmVqfB8u{?|ZKm)yw=MnG|mdTDF9@F`9wywQhe=-F1Aw^$)FE8huyD z4`riVMt(+CyZ;^cdk)i&@?@{`H@Lr>6phK7mff;q^-T}m_VWFW$$9G{8|pS(v7vN* z>H1OYPguVAi z@ek1`ek@*({wDQfjGEt$cio!?Z(^Rz4*?}G?suc_*6mxf&+(nq59ejZxDaO_xgofr z^r?~b32q2&NH%}cRUF4 zjEUSA3SdIPys8+wq_L<;)L1_{;21K56{@Tpq`g zCC@$g?z=C)d;_g^((~k+(&K#8x8p>?vMfn;vTAGfN0&cz*+XK@e{Hzpz0jvc`-bhS z-g?!hKWk&i|;>i=EYAIeIEb6u}_}1>DnEQ zyHDNzP07dG%VHn=)=ivxwOoRh1aUw3Dp;yuM*)xxCGoT=;Yu`!bdJu>M6 z=R@8Ly+6-A*KDUjgL@x<(1oSmuctEe)hx@ue|cuV?Y17 z;lJgUzy0meM{lKIq$?^OdE^(r;4%^V0#H^Ki6o=Zb*%eYZGE?j_CIIbDa7l+*MqdT zIdgOUG6W3px3(IQn3;~q^&ZJ+%xuhzxFRAU^`8hfu4`PEXikXT7e2as_j^|-H$*Pk z@X#d}c^*&vwQ)z{#>C$E;=TXA_}vzVQ)Ied&U_^JVE*(sMqlP9x_K$m%h(Uz{^III zzxnmWfBWJ8{>zrPmj3&0-eWE}YsRFjri}ZppL!d8iboxZoGZ?g`gWb@der4=OMZLk ze@{F!^qX92m_{a3 z@)x5%A&hdyn`_`Z)k+zR-+TGp+L}e7zTWA~$Z%7T+33ZU2ZJ>=NayF5l$=5V=>-%@ zN~TRi+CUybs^y-0+S9>cELKv|(D2w}ix;!CzMj@~%;&r8vNdZs9SrW;RZ>!4|M0_H zuit;tNsWz@CXE;|WJm$shgkjm@SB6*|JY;f|Jl#x&Mn<96ch{@g7Vy_($cwek>B1& zt}QQ3h{%x1VNe=(p5ga#Nar^FK^f9zFv@r2A79bAzhrzD{*vzaE6gzMOvJ{el!tZh zHxR2Br`A6qO*lCJl&Pr7V8(dEHetdAa}ZDOpD>}g$U(t5p(B6MNS=)nXGi{#hQFjc zexprp*Wobcx+JB%(Wa_`R6n;>UM{spFJ=y{XZJ-cy?@A1_R|luu2jF%#qLysGk;vf6<)27Y5;NnQ+uBH_?ue@{h>N`{1k#Q3z zomo5Q62ti2SUlAmiHxs0{j786&Wl8@nSb@wSO3emzV)5d9H*Q*`Rwz)QOCx;U;H!D zmbf<6Ur~L=^z$#gG_vqIGhCnIzhTLKVX2xStR{?jiYdHm<%-D9pZH~@b?=@%>HJ-l zf9Q$-i2UZ^#~*+A@tfALPUoyzmuq_EEHS-i zwwP6Ufpb>P9M?L!BYc(5hnwGMS#~#FdGDqlU$XM(=1<{`d;=ZA=ee8id-|n0I5u?f zzyl8m^KtNvUrF2J+OPSsYeG#Qv_59fo}IQV*=7COulY&0HytK@*tN%L)uzT-kJ_*K zQ8l5)4@Mu@L!m2secEr_Q8Yisx;w1;z#gjZYp^jQuKn%%J~+|*9J)Tt?;BX#mfpaz z_RH^QnjiDd0bKwOqdsuXrqf``o{P4D=H95wo4+N17og@xo*fiuL?fTUG92!5ge$#3X>stFYKl=B-ls~6Y*NYnG{fX16 zO^s{+k;f0t6wI4_zO-Ne{->_n!>SLQvFg478zbV{-@dPc6V1<|>qEIaOI@qnnm$$g zmL8E$m$&ZTnm_vc8PEl=8~VUGo34(OJr{KYsSWK{{%#C-0cw6a*N4KBr9o4-GOhiG z_WPFRuXFyCJuNwZFPt?{D<*zKs4NjX1Fy{YH0_ABSB z=EwLgOwpRu2hu>&7qpq;XWdaAXbOn6&1u!9#yOAnYkn+Eu+|W;zrm(z1vW){RvqSg zhOX>(yDqgq&0eT*-K+Rfb;a6ZEdj{~O{JX4oX7N8XRN}XW#hW};r!a)zVCw*&5xlE z<^h@mnjFcs2)^KBJP__o-F2uv?Kkd09j(pYzz?(s`C$zpKzl${XKEgM8AV4Hho&xNTKg@XX?~2mn8kytD~>~Zs>akjs;-dV zw59BUYCi3^?!OlAnjhotK^l^}ftDCm?NL0%&py@9JR*;sQ_iRTnxD?}VQR_Jn3-l7 z%Aoo=u0EWB^kv`oIIY^$IP$ADHLm@=?DtVsZ@gEj!Q8tn6VfQdY2{%Z;Zf~~!)Mo< z_8UC*N`9avh>Z2b0oa+Jef*dj1b>!I`!zoYq7TlaY7W|Cgof;XrC!!wTJ4#-)PBuR zhx&l_fY2V&c2xcBGd0HbBc8JVY5)G;eZd=$IO?_hL0|U$O>T(v>dMdhQh2m9XzEU; zwclE&njhn?Yw-Y`v0gX;Ro$s}yI;{+${JSeXiNK*GeYxoV88#7_M~o94XWB?9x0tx zTiExsU-P5h&#?AbAN;5qGt+F7p({VtW!E03Rht^;y-@o#Kb9s`y|FIU!0E31oX32i zE30nBr|M4o)1Sk=l0Q{ntUcC@iU;Di>CgH;!e#l<1l9xxAjiA%b6$lfOM`63qy5$$#Nq_p^mhJOV;q%1`>TYmd{a zO^vf2wO{jNX+qT-?^S9r_b$tn&d)aUfv&8&6`!g*?N5IW_e%a${jt!XG2nstZThpm zk8oLjw7=uuSTsM@pTX^?J=P60lunzbuGGEF(woAo_A7S|@B-{?ZT1F!nFkI)9awdZp;IYgPj}d`2!2!teuKb)=;mOh<+wo|>bqBFH z0XMy!Kh_ur=0QbOcd8xvRKLn0w5I*3>ssXnd`ZITg|r=elRurt%<;>!QS~d@f=?pb z;Ui?5_S3maLGwdAbfpg_O3@D|k%o?tmOgq}8bdj=4D7&^a*8&Oc0cc3JO(*F-oNbz2X>(9OIOFy?#DO}1dqk>ACCEB?zo<=v zv@{00nFn^DRqhz`^O}_0^0>MYWEoY1y_CPM^pV0Z_CJNIwBAqn)3x zp#PQzxow1WbHhHfe`&RMH25(-tQStJ@pR);%Wz)%13zQB@?F*6oBgWJz#ou(2+Wb1 z2G2)}pZlrmMolB0ZhX*&S~guAbue-`+W6ryr5nF78iTu@@S{%f4DQe_qn&TncQp8^ zkJfzzV^R8T?f=jJa+xUq?I288+uPozA2U^G})W z<4utY<=fBCWtCv}>$LG(-NEp{`yQ7A+UtNFcqICzG;I2UT;$j|Ve?}g(r#w9@}jVU zst;wQ?HJ%s9fvwL)&6D8KiWB(P|Ae$MZwM=()K*o_{=h$l~wDIbHbTzy3^^@ee6K_ z0JlJUIoN?ms$bPlXK{P~)x6di$(Sy#+E5Q{K-ht>17Qck4ul;DI}mmt>_FIoumfQS z!VZKT2s;pVAnZWcfv^K%2f}`|qpG6=iL2w~{2XBGEUx;HU*o~w|Cl>y5oqTVcHoid zXI*eW-?*=8e+_TDANd&JJCO2f9@K$$fUpB$2f_}79SA!Rb|CCP*nzMEVF$tvgdGSw z5OyH!K-ht>17Qck4ul;DI}mmt>_FIoumfQS!VZKT2s;pVAnZU@6RMWfc)Gl7JCL%d zr^7mIcR}O>qTfK+fv^K%2f_}79SA!Rb|CCP*nzMEVF$tvgdGSw5OyH!K-iBJv}?#d zuFm{+AGDSZQLkDCHjRgN1|RMGS$Z+Q2lHB!4?AF^Xu5G)Wsz6OpHh#RFwyQeQZUq@3~YN(SJYof#P za5>f(9@K+0qnW4EdMKavcfzxcAFNZLeSTmE9`SxfH{53=52XLdD-k>?Mp+;1CZb;8 z5zo&N-hc3@@!;+=nz!!rrq^uhnQ7OM=123xnt%?pK2rA>@B%#A__6m_r&H%w^P%}T zQZ=A_J?U2TNFC3Y-7oCfKzsYJ1GPVOFOZ@$|2}W00!)*KxdgAk-p((0(3pDX?uZYm zAyasE?Wk!>oAmn!{tbpKYx_N znFBkpxALdz4;oal!?-EH-p(J!ud)41?G6{vUw6uIUi<#n-SRsB!OhpP#vAl!KVR+R zRQEylY5le9r?;M+(8!^TKb`(mt*LRTJM*5X>hDn6mvtUvcOyF=I-PnqR{4PbpydO_ z&h)3&pH5%Ln%~ZueSF&AepaN_1H{xF)W82(f9_+~mrkeZtt&n{TCb{|<+4|NXJ zHJYw2?eE#UM>@{CvPnJRcR$=KAu@AcwdSn{{b;hY{#yMo`)ki@_v>`3-g=r3`+Rhk z#(Xn!zMk$|m-XAUov!a))nPAV&#(QdXJk+FW9J)bReRhA_2E2f+8*yQKlBGQ*9H2V z4!LjWPddN0rRwcS-~-x|^Tr9xAk5E_-?H?|jW3 zDnE9<)wDXN&>H)@)RtYB(4>m?JkTq!D?hXc#3ST}ACr-Qx~bKZjc0J|Ait z^O>SOHrlm;G_(VR9SA!Rb|CCP*nzM!@`J#B4ko6Ifoz-xPpVJHQ?+;`@nOxUGJlvu z%LhI1yr+A5(l0yFt~qEAx;&D&)P3om&M~!?&J+zYm#W>;WIFwsHd*Fy`>7Ktec?{2 zd!p&@Z0^;*r^~1Pou3_t%1>I(c^+9S2#Rjxd@E_5@BM7-$G+E-ep73*eeSE7Mz?|K z#t568Kb=n1TTkFPlr zf%g2c1GPVuQ}6;jP=1tYYX5FQMlR1jpv-4#joZMZwga1rJ>pmO#~p(Pf$#&(aj1RK z@l^eM6?{ldnR6rKG=Jy^r#a4ZE0GO|^Stt0tNCGLM7$^c+=khw{SbXn{S?R8fapIE zb|CCP*nzMEVF$tvgdGSw5OyH!K-ht>17Qck4ul;DI}mmt>_FIoumfQS!VZL;QO=n; zC+tTa>n^3*(!`Np)&;!*mHa*8hxUNn9tWiDX!_Oh%YKnvMeR@B6EVlY!^sa%7r00L zY~wf(ezePW*sL;~pC6uc6)CRfJ*tz9{1}VIRIPoPd|2~k(}>iIbkd9G zRq>;LR$1uH%!m9I_M~6!BgeT998}$L8g`)R$5=FeH5%lpk-^lC)E&ho!pGQ=-?XzI zWs%p8J>sXXS$*ZcaIk5wGe7#IBFEK79h+)LT>IPS9-OGy89#Eq_|=h)kf9?#+iAYJ zJ+%%QXC3skU)jfGS8dvZ7G?i9gUyeAYgE2p(wu#%d?*@2yKLgPOn2gEeOYB#Yi3%t zsqw?^SM|j0tAW#9__=TP2Ym!;e=0}d1$Zd@@N{u`73CC~T2k%Em*Pi0LbV^Y7IF8perbUamOUlt!cUECJ*jQK17Qck4ul;DI}mmt>_FIoumfQS z!VZKTXxT9@3jfH%$Zm-p`8kj2<2o(9DZ1-XKePu#pOLns&2RBwjzR4!d~#gcZ{4$0 zUJwUwK-ht>11-C$FHWmIHcMMqex6TdZaAJ!gQ)+o`=KwhpA51M8KGAfe(<32t2=w; zd?^~jTwn@2^{aa0c)Eu*kj@UT z20M^_rd{=^HZ_jARht?=C_i<<>MQq!gH3y#`O&6|99JLiqxQG!Lghx~1w3`fFXxM2 z9q9-eI`Xrf<{SM`>rv0)9`l=8ll607ZQ4V+BR~43v0Y!#(U*pb#xM_T;b0&FxQ4u0+&BBvBYstX+%axf#V-76pL9G`XI~Z{JYC!t^o;pK`17Qck4ul;DI}mmt>_FIo zumfQS!VZKT2s;pVAnZWcfv^K%2f_}t?3fpYf8=3gx5SS8oX7NWotEC9v7Yurdq8fF z1JZW1`7IvIF{opKPmW9bd*nNqoU1h4vR(4dkeWW1gnVxEn|wtpf!yt_e9puf${|HVNTRE;;0XGBK?*5 zRlV_AQ3I#D@MEk8f@;<3c++v9{q5)Kf$)PomzKXidi1XxA3f+!n`yumfQS z!VZKT2s;pVAnZWcfv^K%2f_}79SA!Rb|CCP*nzMEVF!|3<=5ivXz+6%5qNwYfE@3_ z->1(VxwiR)drW zWwoC)XL6?EQOOVKBSj0W7n}Av^RqAABW1@NAt-#w`jj-cx1WvuSl8Nr?DAu3P0l}S z#Y{ssNO$CCyZTt=C<41>BTdPASX-u_*RN$`yO~yPYFzsbzEn=s_)*7?sy9;?s$QT! zX-nhhvE#AIam3UTMY`Jp2$7kQAu=*t;@2+* z#jjonaaNMJUyvdVJMN1m;$tW!NmXUg>Ce54Kku)auVN&jiK{9h;kK=M#s z@_i(|rcV=MdaXmun&x!Qs&%=hSI!dCYi5gCl@~Z?)y#3Nqt1SnPu@X?@fG_QEg7H2 zR+7$EVTNgEA~voBvAN$stYX}RZigvT_b+n6oc(PRCKMO(ns%nRibis8OHzy@4O@wt z5jLX^uE*gp##vHg)S-^LBn=0u|3Kp7B`N#oERl{h_L9*QI@=f!Tv+}Q1yi;nCSQvb)k$iS!=Q7<3C(c1Q zK^7c!Izp+DzSME44FAIEIdeuhviov4N2bfm%Tv`7X(~Q`_U!gF9qHqh@}ttEsYrW%qkW@4JS1wLdazXX z&uo9r38Sp^s9?W-!%LO)m66D|)pW&;UvE^?OK(`ZG$p-s>C%!B?e$TT1KJxEFv8ai ze$D<+m$+|MAXbd|ko>skjL4pD<{w3zXO)*{XDjLQ>WO1KNDrY&VAhddQbG)m@Qgv) z;3Ip9nKtLc%Fi=0VvM(64*a7>Em^YUd)Hj2q|2+XjC|YdE7eWoZ%;1`4qtjhX~m6= zrhjR85%Dp{jqzAR)Dg|Vm%%`~JvZHc>&FYkUQw#$OPe z=Hts^GFVIBA!@1oTq-}8%GddVqRt--Ug{GUD5%p8 zQO;1YAX@{k!p3_ zvTEDvRnew9R<7DzTe~WH^UB)QD}z?Jfp!xamRhD@IAVsdYP;*sTc266^8OTCNBM5Q z^NzInM#a0Fmfutywy%rsVvpPB5KgaCWIFmf=ecS47_uRXRW`6Iro{WN`zTTW% ze0du7r)B@0HRZlc!FSdB#EDdaGwCawpFe+~sO;D8BHzpXmgfa>PFi$-ajvg`m0B)N zV5JH%L1btD)8P}PB3V42-$mlSyP8(qymCz~hZ6a{+^B3#lcMM1+@gZglS(U#lNB=i zovT;hSzD{Nz36_WA#T?#n$8d_R^55u11nc@-I;Qv!OcPy_g(yr`&L?%SB7}L3kv-CVw@-x zgQ&$5)hXz3`pu2%X)&sw80bn)JttRC-mK>xxsLUYZ#aG;_9lKEgrjbduq zGX+J(BAYUp`?W&NK}Hq%DYCtD#fm0koI}(KwJVx#zHQ~|)ofjPYxItLI8s~N96f`^ z#xo_N{G!GG{BJJ4fAT}OHs3R4*>g?4haF_f-tIWTk>M$K>~@44n;kDW{^V$J(9i!| z!WOQqTXx&MD{F6#u3A%ji^C%v^9TOZDF!_L(kZl)49FHG*%zEX^ZZlJcPDS0+PX12 zyLeBZmojdux#;C=($yCm3kFa1zu2cpOg!Pm{oPb?uoX z#ORFveZ{$XS>n9$Sf*^buQpE^%4zpv*d3 zFAq5>89U@!4xxgK{+|Bciaw(<2lpK?G?zqY51y<(*}lAfmf0PTz2$h*`IhUgj5jjh zaR15kMpm*f0wt z`%PB){z}d7aND90_0;U9Toc>7`_;vW#{I(>)AX8nfNpLB>_5yh&pGs1)H|gNjH?10 zpQIhosNw|%M^uPcjkNlXicMV>G2@pP|!;@o|7RNf396A_?m z{8uwWmswoq`rN+E9CzOZ{W5bG@;5z5&67yf1aewI`8ttaTB~ZgDBdlpE zE*)H8+-L^U&E5Yr+vQ<6V&^L#EGu7Yno0|bbMt8iimBFtVtBjj@W#4xS{~k(+iN@j(e}8E<%?l_ zGdkFix!|UUN*=QBQwv0Xu5z8|howoaY$)XG#7!bElkPFe`^ZAmA=b9Hf0=`>CgLHH z%Zo8NoNjBJVQ71X-xt!INR)^|>Niim%;2mSuZY*gZyhf2ljxMYEoR-$p9B-n(I0LPT<7>zFtg{6{iHK`*e0O*`IafwrKbklUiJ+TUPo)Ntg2r8 z`R&=WXww$6>7;jvx*-2&T-|(kbIWwa7)U?ah_wZqJ{B)Tf8-Y8^yVhY&e7~>af>f9J$v>AhUex5@^bP#Svgts z8*D~KhSN#oOdq1u59$#KF4z)`)-_Y8X`w`YeQRqh7GIp$e)3yeFP|yIr0-8zxpVpB z|MS`ZzO{4Rx+s@xw)LOJQ&U5u^Z7`Z{%0TCGiBj-=T%3FLO%Y^*R5cyxm#s*X3cGy|Fe&46Y=GoTsJ3}^;41DXNNfM!55pc&8%Xa+O`ngPv#WGy|Fe&46Y=GoTsJ3}^;41DXNNfM!55pc&8%Xa+O`ngPv# zWGy|Fe&46Y=GoTsJ3}^;41DXNNfM!55pc&8% zXa+O`ngPv#WGy|Fe&46Y=GoTsJ3}^;41DXNN afM!55pc&8%Xa+O`ngPu~Z($%e_x}OW3vGG; diff --git a/pokegym/States/LavenderPre.state b/pokegym/States/LavenderPre.state deleted file mode 100644 index 5fc0683601fa1e03cf3709df2d1b32160b2c6e37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142610 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 U&w$T>&w$T>&%g(Tf!f;t4@pckHvj+t diff --git a/pokegym/States/LtSurge.state b/pokegym/States/LtSurge.state deleted file mode 100644 index 7f49bfe7d755c66558edb1f3bf7f6ec53b43cc29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142610 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 I1nl%5JaQ zyCR@0Q(M(lk%&9$j5%Y!4`?66C&pJP7d9VIroKs(0a8<%yjUZQxX zdZ#jct%fPK$Lp?kIUEuso5Kcq)P1b$mDsVyV~x?MFA@$*Qn%Wyo~vA>+}WnKc5DcT z{owx^?IkUKNqpj|Pbd7pQdvHumh=Bt+TLT0=)WYnAby|XQ*LYPYU^kVhnwQoxJ`3u zbNlD^pXyg{Q&#kBQ9rM&NZ=Q0zwdc8eu?&bhG+A8z39I&zfH4gJGB?OVl4yxk8O>; z6048Z8~!^SN~3qWcc$XszOiR@$A(BG>W&?Yc{JBs3wmAz|NT9Gt3Rwi*mJ8g&+Bb! z(p)_c>3`Dyv*$JKrC6-H+u@L%uzYUC53ASGu`v?SYGL`DnhREM%BcmHHmF@6|5$%U zZNHS_f7%qHnVRtyHU@kf8 zjz17k z$8CE*(Bf~#r)aL26UK@#{oo4V#nvAg#LK7nmF^DpN`}XkX*w1E6~i0rSFZ$?dCy7b zA6HIdhSBw6;(^?HE6&#G5+Yzc>>QHUJ)kKY)7;MAka z(x&Cx*Q;0H{9e@m&E0$7IuU;W=Z{hS03MQp>W7QsW3iZq>W52NrZjE`WdeRO#ebmH zzSSR}2>4xyM@|8L4)l7wGf@99KgB2X??L^e{~&*=e}n(1A66lX{3qsLu3n`!Q~enD zy|Dh)r2el^*z;#RzYl21=a2J$eMehretddQ^{-eKSgc;DZf5x3e-Jns|5bceI$lwf z<)}i`59j~h?!BmgS(YctHI6Df_#g8`(DT-4XQVw8LH->!RIuXjXjLy${7iqE>(s>f z7d6-1nMwfnpGE~)mgTNJS^)Q-S`Ar>M$_=kCcIU1!OGH=VR%?sx-txJu{f&0L;4KC zbLa;D{l`4;{MfV`w5i%u!)Dd zNgfZM^quhZx14BcYwx?SBii7ql^obb`m2sSdrI#0H-;m$jyDT%u-Mb)Uhi&e$9fQ+ z!IPeT=Lu(jl^(q5^fKG*sx=lL@j#z$dGn(We7|i!H)x}Ew6BtZKOa2*t$GmaFhVfg z{O-Kd(>|W`+5ffliiegrS9y&N!0VH~*w;gXiFKNvIU^|Jy)pf{eEL-ihnx3uIGXH373nS!p%(>o&c}NbR;qr@VF;+wsozCx8r8S#qaRPv6_tx&V70S%HF1Z@sTzd=j7g_ngfC-imMkxie)K`oFYa>xCWYS3M7C0mJ{){IEey zW%J|O;R#@X|1I!#-3o8`r2pwpAjZ$106I3{2|&H3X`V9Q>!tp?zW!*uzx$|m^tZna z29baM1b}${1kkY&P5_&?{~f-+=EDAYY5_d~{7Ih>uX&~uBf$AnCjj)HKLIfR9gYC( zz%?~5fdA3X*fH%paA}Q=Z2gVrtnF)T{n!bh?Q4&nIrx!!6V))hf02FA{|m8}Sc{=y zrXS<|&+x|6W7C@9bl0rH+46Gm(dYHrG-u2cgS{8l&%mdj0C@hb@f|w#A5H+072cmV zN&1DlQh!H}$76fI{~Ka4)DOG?lP3Vg^Cy6gE$Y`%Ki=8!aHD=6#S;MPKcye;4ceB# z!@I$F�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 CBZk8O diff --git a/pokegym/States/ssanne.state b/pokegym/States/ssanne.state deleted file mode 100644 index ccaec98065ec8a456ee2a308ab71fcb4be72a1fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142610 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 diff --git a/pokegym/data.py b/pokegym/data.py index d341b92..c7fca07 100644 --- a/pokegym/data.py +++ b/pokegym/data.py @@ -354,6 +354,151 @@ 0x00: 'None', } +items_dict = { + 1: {'decimal': 1, 'hex': '0x01', 'Item': 'Master Ball'}, + 2: {'decimal': 2, 'hex': '0x02', 'Item': 'Ultra Ball'}, + 3: {'decimal': 3, 'hex': '0x03', 'Item': 'Great Ball'}, + 4: {'decimal': 4, 'hex': '0x04', 'Item': 'Poké Ball'}, + 5: {'decimal': 5, 'hex': '0x05', 'Item': 'Town Map'}, + 6: {'decimal': 6, 'hex': '0x06', 'Item': 'Bicycle'}, + 7: {'decimal': 7, 'hex': '0x07', 'Item': '?????'}, + 8: {'decimal': 8, 'hex': '0x08', 'Item': 'Safari Ball'}, + 9: {'decimal': 9, 'hex': '0x09', 'Item': 'Pokédex'}, + 10: {'decimal': 10, 'hex': '0x0A', 'Item': 'Moon Stone'}, + 11: {'decimal': 11, 'hex': '0x0B', 'Item': 'Antidote'}, + 12: {'decimal': 12, 'hex': '0x0C', 'Item': 'Burn Heal'}, + 13: {'decimal': 13, 'hex': '0x0D', 'Item': 'Ice Heal'}, + 14: {'decimal': 14, 'hex': '0x0E', 'Item': 'Awakening'}, + 15: {'decimal': 15, 'hex': '0x0F', 'Item': 'Parlyz Heal'}, + 16: {'decimal': 16, 'hex': '0x10', 'Item': 'Full Restore'}, + 17: {'decimal': 17, 'hex': '0x11', 'Item': 'Max Potion'}, + 18: {'decimal': 18, 'hex': '0x12', 'Item': 'Hyper Potion'}, + 19: {'decimal': 19, 'hex': '0x13', 'Item': 'Super Potion'}, + 20: {'decimal': 20, 'hex': '0x14', 'Item': 'Potion'}, + 21: {'decimal': 21, 'hex': '0x15', 'Item': 'BoulderBadge'}, + 22: {'decimal': 22, 'hex': '0x16', 'Item': 'CascadeBadge'}, + 23: {'decimal': 23, 'hex': '0x17', 'Item': 'ThunderBadge'}, + 24: {'decimal': 24, 'hex': '0x18', 'Item': 'RainbowBadge'}, + 25: {'decimal': 25, 'hex': '0x19', 'Item': 'SoulBadge'}, + 26: {'decimal': 26, 'hex': '0x1A', 'Item': 'MarshBadge'}, + 27: {'decimal': 27, 'hex': '0x1B', 'Item': 'VolcanoBadge'}, + 28: {'decimal': 28, 'hex': '0x1C', 'Item': 'EarthBadge'}, + 29: {'decimal': 29, 'hex': '0x1D', 'Item': 'Escape Rope'}, + 30: {'decimal': 30, 'hex': '0x1E', 'Item': 'Repel'}, + 31: {'decimal': 31, 'hex': '0x1F', 'Item': 'Old Amber'}, + 32: {'decimal': 32, 'hex': '0x20', 'Item': 'Fire Stone'}, + 33: {'decimal': 33, 'hex': '0x21', 'Item': 'Thunderstone'}, + 34: {'decimal': 34, 'hex': '0x22', 'Item': 'Water Stone'}, + 35: {'decimal': 35, 'hex': '0x23', 'Item': 'HP Up'}, + 36: {'decimal': 36, 'hex': '0x24', 'Item': 'Protein'}, + 37: {'decimal': 37, 'hex': '0x25', 'Item': 'Iron'}, + 38: {'decimal': 38, 'hex': '0x26', 'Item': 'Carbos'}, + 39: {'decimal': 39, 'hex': '0x27', 'Item': 'Calcium'}, + 40: {'decimal': 40, 'hex': '0x28', 'Item': 'Rare Candy'}, + 41: {'decimal': 41, 'hex': '0x29', 'Item': 'Dome Fossil'}, + 42: {'decimal': 42, 'hex': '0x2A', 'Item': 'Helix Fossil'}, + 43: {'decimal': 43, 'hex': '0x2B', 'Item': 'Secret Key'}, + 44: {'decimal': 44, 'hex': '0x2C', 'Item': '?????'}, + 45: {'decimal': 45, 'hex': '0x2D', 'Item': 'Bike Voucher'}, + 46: {'decimal': 46, 'hex': '0x2E', 'Item': 'X Accuracy'}, + 47: {'decimal': 47, 'hex': '0x2F', 'Item': 'Leaf Stone'}, + 48: {'decimal': 48, 'hex': '0x30', 'Item': 'Card Key'}, + 49: {'decimal': 49, 'hex': '0x31', 'Item': 'Nugget'}, + 50: {'decimal': 50, 'hex': '0x32', 'Item': 'PP Up*'}, + 51: {'decimal': 51, 'hex': '0x33', 'Item': 'Poké Doll'}, + 52: {'decimal': 52, 'hex': '0x34', 'Item': 'Full Heal'}, + 53: {'decimal': 53, 'hex': '0x35', 'Item': 'Revive'}, + 54: {'decimal': 54, 'hex': '0x36', 'Item': 'Max Revive'}, + 55: {'decimal': 55, 'hex': '0x37', 'Item': 'Guard Spec.'}, + 56: {'decimal': 56, 'hex': '0x38', 'Item': 'Super Repel'}, + 57: {'decimal': 57, 'hex': '0x39', 'Item': 'Max Repel'}, + 58: {'decimal': 58, 'hex': '0x3A', 'Item': 'Dire Hit'}, + 59: {'decimal': 59, 'hex': '0x3B', 'Item': 'Coin'}, + 60: {'decimal': 60, 'hex': '0x3C', 'Item': 'Fresh Water'}, + 61: {'decimal': 61, 'hex': '0x3D', 'Item': 'Soda Pop'}, + 62: {'decimal': 62, 'hex': '0x3E', 'Item': 'Lemonade'}, + 63: {'decimal': 63, 'hex': '0x3F', 'Item': 'S.S. Ticket'}, + 64: {'decimal': 64, 'hex': '0x40', 'Item': 'Gold Teeth'}, + 65: {'decimal': 65, 'hex': '0x41', 'Item': 'X Attack'}, + 66: {'decimal': 66, 'hex': '0x42', 'Item': 'X Defend'}, + 67: {'decimal': 67, 'hex': '0x43', 'Item': 'X Speed'}, + 68: {'decimal': 68, 'hex': '0x44', 'Item': 'X Special'}, + 69: {'decimal': 69, 'hex': '0x45', 'Item': 'Coin Case'}, + 70: {'decimal': 70, 'hex': '0x46', 'Item': "Oak's Parcel"}, + 71: {'decimal': 71, 'hex': '0x47', 'Item': 'Itemfinder'}, + 72: {'decimal': 72, 'hex': '0x48', 'Item': 'Silph Scope'}, + 73: {'decimal': 73, 'hex': '0x49', 'Item': 'Poké Flute'}, + 74: {'decimal': 74, 'hex': '0x4A', 'Item': 'Lift Key'}, + 75: {'decimal': 75, 'hex': '0x4B', 'Item': 'Exp. All'}, + 76: {'decimal': 76, 'hex': '0x4C', 'Item': 'Old Rod'}, + 77: {'decimal': 77, 'hex': '0x4D', 'Item': 'Good Rod'}, + 78: {'decimal': 78, 'hex': '0x4E', 'Item': 'Super Rod'}, + 79: {'decimal': 79, 'hex': '0x4F', 'Item': 'PP Up'}, + 80: {'decimal': 80, 'hex': '0x50', 'Item': 'Ether'}, + 81: {'decimal': 81, 'hex': '0x51', 'Item': 'Max Ether'}, + 82: {'decimal': 82, 'hex': '0x52', 'Item': 'Elixer'}, + 83: {'decimal': 83, 'hex': '0x53', 'Item': 'Max Elixer'}, + 196: {'decimal': 196, 'hex': '0xC4', 'Item': 'HM01'}, + 197: {'decimal': 197, 'hex': '0xC5', 'Item': 'HM02'}, + 198: {'decimal': 198, 'hex': '0xC6', 'Item': 'HM03'}, + 199: {'decimal': 199, 'hex': '0xC7', 'Item': 'HM04'}, + 200: {'decimal': 200, 'hex': '0xC8', 'Item': 'HM05'}, + 201: {'decimal': 201, 'hex': '0xC9', 'Item': 'TM01'}, + 202: {'decimal': 202, 'hex': '0xCA', 'Item': 'TM02'}, + 203: {'decimal': 203, 'hex': '0xCB', 'Item': 'TM03'}, + 204: {'decimal': 204, 'hex': '0xCC', 'Item': 'TM04'}, + 205: {'decimal': 205, 'hex': '0xCD', 'Item': 'TM05'}, + 206: {'decimal': 206, 'hex': '0xCE', 'Item': 'TM06'}, + 207: {'decimal': 207, 'hex': '0xCF', 'Item': 'TM07'}, + 208: {'decimal': 208, 'hex': '0xD0', 'Item': 'TM08'}, + 209: {'decimal': 209, 'hex': '0xD1', 'Item': 'TM09'}, + 210: {'decimal': 210, 'hex': '0xD2', 'Item': 'TM10'}, + 211: {'decimal': 211, 'hex': '0xD3', 'Item': 'TM11'}, + 212: {'decimal': 212, 'hex': '0xD4', 'Item': 'TM12'}, + 213: {'decimal': 213, 'hex': '0xD5', 'Item': 'TM13'}, + 214: {'decimal': 214, 'hex': '0xD6', 'Item': 'TM14'}, + 215: {'decimal': 215, 'hex': '0xD7', 'Item': 'TM15'}, + 216: {'decimal': 216, 'hex': '0xD8', 'Item': 'TM16'}, + 217: {'decimal': 217, 'hex': '0xD9', 'Item': 'TM17'}, + 218: {'decimal': 218, 'hex': '0xDA', 'Item': 'TM18'}, + 219: {'decimal': 219, 'hex': '0xDB', 'Item': 'TM19'}, + 220: {'decimal': 220, 'hex': '0xDC', 'Item': 'TM20'}, + 221: {'decimal': 221, 'hex': '0xDD', 'Item': 'TM21'}, + 222: {'decimal': 222, 'hex': '0xDE', 'Item': 'TM22'}, + 223: {'decimal': 223, 'hex': '0xDF', 'Item': 'TM23'}, + 224: {'decimal': 224, 'hex': '0xE0', 'Item': 'TM24'}, + 225: {'decimal': 225, 'hex': '0xE1', 'Item': 'TM25'}, + 226: {'decimal': 226, 'hex': '0xE2', 'Item': 'TM26'}, + 227: {'decimal': 227, 'hex': '0xE3', 'Item': 'TM27'}, + 228: {'decimal': 228, 'hex': '0xE4', 'Item': 'TM28'}, + 229: {'decimal': 229, 'hex': '0xE5', 'Item': 'TM29'}, + 230: {'decimal': 230, 'hex': '0xE6', 'Item': 'TM30'}, + 231: {'decimal': 231, 'hex': '0xE7', 'Item': 'TM31'}, + 232: {'decimal': 232, 'hex': '0xE8', 'Item': 'TM32'}, + 233: {'decimal': 233, 'hex': '0xE9', 'Item': 'TM33'}, + 234: {'decimal': 234, 'hex': '0xEA', 'Item': 'TM34'}, + 235: {'decimal': 235, 'hex': '0xEB', 'Item': 'TM35'}, + 236: {'decimal': 236, 'hex': '0xEC', 'Item': 'TM36'}, + 237: {'decimal': 237, 'hex': '0xED', 'Item': 'TM37'}, + 238: {'decimal': 238, 'hex': '0xEE', 'Item': 'TM38'}, + 239: {'decimal': 239, 'hex': '0xEF', 'Item': 'TM39'}, + 240: {'decimal': 240, 'hex': '0xF0', 'Item': 'TM40'}, + 241: {'decimal': 241, 'hex': '0xF1', 'Item': 'TM41'}, + 242: {'decimal': 242, 'hex': '0xF2', 'Item': 'TM42'}, + 243: {'decimal': 243, 'hex': '0xF3', 'Item': 'TM43'}, + 244: {'decimal': 244, 'hex': '0xF4', 'Item': 'TM44'}, + 245: {'decimal': 245, 'hex': '0xF5', 'Item': 'TM45'}, + 246: {'decimal': 246, 'hex': '0xF6', 'Item': 'TM46'}, + 247: {'decimal': 247, 'hex': '0xF7', 'Item': 'TM47'}, + 248: {'decimal': 248, 'hex': '0xF8', 'Item': 'TM48'}, + 249: {'decimal': 249, 'hex': '0xF9', 'Item': 'TM49'}, + 250: {'decimal': 250, 'hex': '0xFA', 'Item': 'TM50'}, + 251: {'decimal': 251, 'hex': '0xFB', 'Item': 'TM51'}, + 252: {'decimal': 252, 'hex': '0xFC', 'Item': 'TM52'}, + 253: {'decimal': 253, 'hex': '0xFD', 'Item': 'TM53'}, + 254: {'decimal': 254, 'hex': '0xFE', 'Item': 'TM54'}, + 255: {'decimal': 255, 'hex': '0xFF', 'Item': 'TM55'}, +} POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] # - Pokémon (Again) STATUS = [0xD16F, 0xD19B, 0xD1C7, 0xD1F3, 0xD21F, 0xD24B] # - Status (Poisoned, Paralyzed, etc.) diff --git a/pokegym/environment.py b/pokegym/environment.py index 3257779..d2eaa7b 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -1,25 +1,89 @@ import csv +from pathlib import Path from pdb import set_trace as T +import types import uuid from gymnasium import Env, spaces import numpy as np +import pandas as pd +from skimage.transform import resize + from collections import defaultdict import io, os import random + +import matplotlib.pyplot as plt from pathlib import Path import mediapy as media - -from pokegym.pyboy_binding import (ACTIONS, make_env, open_state_file, - load_pyboy_state, run_action_on_emulator) +from pokegym.pyboy_binding import ( + ACTIONS, + make_env, + open_state_file, + load_pyboy_state, + run_action_on_emulator, +) from pokegym import ram_map, game_map, data + +def play(): + """Creates an environment and plays it""" + env = Environment( + rom_path="pokemon_red.gb", + state_path=None, + headless=False, + disable_input=False, + sound=False, + sound_emulated=False, + verbose=True, + ) + + env.reset() + env.game.set_emulation_speed(0) + + # Display available actions + print("Available actions:") + for idx, action in enumerate(ACTIONS): + print(f"{idx}: {action}") + + # Create a mapping from WindowEvent to action index + window_event_to_action = { + "PRESS_ARROW_DOWN": 0, + "PRESS_ARROW_LEFT": 1, + "PRESS_ARROW_RIGHT": 2, + "PRESS_ARROW_UP": 3, + "PRESS_BUTTON_A": 4, + "PRESS_BUTTON_B": 5, + "PRESS_BUTTON_START": 6, + "PRESS_BUTTON_SELECT": 7, + # Add more mappings if necessary + } + + while True: + # Get input from pyboy's get_input method + input_events = env.game.get_input() + env.game.tick() + env.render() + if len(input_events) == 0: + continue + + for event in input_events: + event_str = str(event) + if event_str in window_event_to_action: + action_index = window_event_to_action[event_str] + observation, reward, done, _, info = env.step( + action_index, fast_video=False + ) + + # Check for game over + if done: + print(f"{done}") + break + + # Additional game logic or information display can go here + print(f"new Reward: {reward}\n") + STATE_PATH = __file__.rstrip("environment.py") + "States/" -def get_random_state(): - state_files = [f for f in os.listdir(STATE_PATH) if f.endswith(".state")] - if not state_files: - raise FileNotFoundError("No State files found in the specified directory.") - return random.choice(state_files) class Base: def __init__( @@ -33,51 +97,65 @@ def __init__( ): """Creates a PokemonRed environment""" if state_path is None: - state_path = STATE_PATH + "Bulbasaur.state" # STATE_PATH + "has_pokedex_nballs.state" or self.randstate - with open("experiments/current_exp.txt", "r") as file: - exp_name = file.read() + state_path = STATE_PATH + "Bulbasaur.state" # STATE_PATH + "has_pokedex_nballs.state" + self.game, self.screen = make_env(rom_path, headless, quiet, save_video=False, **kwargs) + - self.game, self.screen = make_env(rom_path, headless, quiet, save_video=True, **kwargs) - R, C = self.screen.raw_screen_buffer_dims() - self.state_file = get_random_state() - self.randstate = os.path.join(STATE_PATH, self.state_file) self.initial_states = [open_state_file(state_path)] self.save_video = save_video self.headless = headless self.mem_padding = 2 self.memory_shape = 80 self.use_screen_memory = True - self.exp_path = Path(f'experiments/{str(exp_name)}') + self.screenshot_counter = 0 self.env_id = Path(f'session_{str(uuid.uuid4())[:4]}') - self.s_path = Path(f'{str(self.exp_path)}/sessions/{str(self.env_id)}') + self.video_path = Path(f'./videos') + self.video_path.mkdir(parents=True, exist_ok=True) + self.csv_path = Path(f'./csv') + self.csv_path.mkdir(parents=True, exist_ok=True) self.reset_count = 0 + self.explore_hidden_obj_weight = 1 + self.csv = True + + R, C = self.screen.raw_screen_buffer_dims() self.obs_size = (R // 2, C // 2) if self.use_screen_memory: - self.screen_memory = defaultdict(lambda: np.zeros((255, 255, 1), dtype=np.uint8)) + self.screen_memory = defaultdict( + lambda: np.zeros((255, 255, 1), dtype=np.uint8) + ) self.obs_size += (4,) else: self.obs_size += (3,) - self.observation_space = spaces.Box(low=0, high=255, dtype=np.uint8, shape=self.obs_size) + self.observation_space = spaces.Box( + low=0, high=255, dtype=np.uint8, shape=self.obs_size + ) self.action_space = spaces.Discrete(len(ACTIONS)) - + + def save_screenshot(self, event, map_n): + self.screenshot_counter += 1 + ss_dir = Path('screenshots') + ss_dir.mkdir(exist_ok=True) + plt.imsave( + ss_dir / Path(f'{self.screenshot_counter}_{event}_{map_n}.jpeg'), + self.screen.screen_ndarray()) # (144, 160, 3) + def save_state(self): state = io.BytesIO() state.seek(0) self.game.save_state(state) self.initial_states.append(state) - - def load_random_state(self): - rand_idx = random.randint(0, len(self.initial_states) - 1) - return self.initial_states[rand_idx] + + def load_last_state(self): + return self.initial_states[len(self.initial_states) - 1] def reset(self, seed=None, options=None): """Resets the game. Seeding is NOT supported""" - load_pyboy_state(self.game, self.load_random_state()) return self.screen.screen_ndarray(), {} def get_fixed_window(self, arr, y, x, window_size): height, width, _ = arr.shape + h_w, w_w = window_size[0], window_size[1] h_w, w_w = window_size[0] // 2, window_size[1] // 2 y_min = max(0, y - h_w) @@ -101,10 +179,10 @@ def get_fixed_window(self, arr, y, x, window_size): def render(self): if self.use_screen_memory: r, c, map_n = ram_map.position(self.game) + mmap = self.screen_memory[map_n] - if 0 <= r < mmap.shape[0] and 0 <= c < mmap.shape[1]: + if 0 <= r <= 254 and 0 <= c <= 254: mmap[r, c] = 255 - return np.concatenate( ( self.screen.screen_ndarray()[::2, ::2], @@ -114,261 +192,431 @@ def render(self): ) else: return self.screen.screen_ndarray()[::2, ::2] - - def video(self): - video = self.screen.screen_ndarray() - return video def step(self, action): run_action_on_emulator(self.game, self.screen, ACTIONS[action], self.headless) return self.render(), 0, False, False, {} + + def video(self): + video = self.screen.screen_ndarray() + return video def close(self): self.game.stop(False) - + class Environment(Base): - def __init__(self, rom_path='pokemon_red.gb', - state_path=None, headless=True, save_video=True, quiet=False, verbose=False, **kwargs): + def __init__( + self, + rom_path="pokemon_red.gb", + state_path=None, + headless=True, + save_video=True, + quiet=False, + verbose=False, + **kwargs, + ): super().__init__(rom_path, state_path, headless, save_video, quiet, **kwargs) self.counts_map = np.zeros((444, 436)) self.verbose = verbose - self.log = True - - def add_video_frame(self): - self.full_frame_writer.add_image(self.video()) - - def write_to_log(self): - pokemon_info = data.pokemon_l(self.game) + + self.include_conditions = [] + self.talk_to_npc_count = {} + self.seen_npcs = set() + self.explore_npc_weight = 1 + self.seen_pokemon = np.zeros(152, dtype=np.uint8) + self.caught_pokemon = np.zeros(152, dtype=np.uint8) + self.moves_obtained = np.zeros(0xA5, dtype=np.uint8) + + def update_pokedex(self): + for i in range(0xD30A - 0xD2F7): + caught_mem = self.game.get_memory_value(i + 0xD2F7) + seen_mem = self.game.get_memory_value(i + 0xD30A) + for j in range(8): + self.caught_pokemon[8*i + j] = 1 if caught_mem & (1 << j) else 0 + self.seen_pokemon[8*i + j] = 1 if seen_mem & (1 << j) else 0 + + def write_to_csv(self): x, y ,map_n = ram_map.position(self.game) - session_path = self.s_path - base_dir = self.exp_path reset = self.reset_count env_id = self.env_id - base_dir.mkdir(parents=True, exist_ok=True) - session_path.mkdir(parents=True, exist_ok=True) - csv_file_path = base_dir / "unique_positions.csv" - with open(session_path / self.full_name_log, 'w') as f: - for pokemon in pokemon_info: - f.write(f"Slot: {pokemon['slot']}\n") - f.write(f"Name: {pokemon['name']}\n") - f.write(f"Level: {pokemon['level']}\n") - f.write(f"Moves: {', '.join(pokemon['moves'])}\n") - f.write("\n") # Add a newline between Pokémon + csv_file_path = Path(f'./csv/agent_position.csv') with open(csv_file_path, 'a') as csv_file: csv_writer = csv.writer(csv_file) csv_writer.writerow([env_id, reset, x, y, map_n]) + def update_moves_obtained(self): + # Scan party + for i in [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247]: + if self.game.get_memory_value(i) != 0: + for j in range(4): + move_id = self.game.get_memory_value(i + j + 8) + if move_id != 0: + if move_id != 0: + self.moves_obtained[move_id] = 1 + # Scan current box (since the box doesn't auto increment in pokemon red) + num_moves = 4 + box_struct_length = 25 * num_moves * 2 + for i in range(self.game.get_memory_value(0xda80)): + offset = i*box_struct_length + 0xda96 + if self.game.get_memory_value(offset) != 0: + for j in range(4): + move_id = self.game.get_memory_value(offset + j + 8) + if move_id != 0: + self.moves_obtained[move_id] = 1 + + def get_items_in_bag(self, one_indexed=0): + first_item = 0xD31E + item_names = [] + for i in range(0, 20, 2): + item_id = self.game.get_memory_value(first_item + i) + if item_id == 0 or item_id == 0xff: + break + item_id_key = item_id + one_indexed + item_name = data.items_dict.get(item_id_key, {}).get('Item', f'Unknown Item {item_id_key}') + item_names.append(item_name) + return item_names + + def get_hm_rewards(self): + hm_ids = [0xC4, 0xC5, 0xC6, 0xC7, 0xC8] + items = self.get_items_in_bag() + total_hm_cnt = 0 + for hm_id in hm_ids: + if hm_id in items: + total_hm_cnt += 1 + return total_hm_cnt * 1 + + def add_video_frame(self): + self.full_frame_writer.add_image(self.video()) + + def update_heat_map(self, r, c, current_map): + ''' + Updates the heat map based on the agent's current position. + + Args: + r (int): global y coordinate of the agent's position. + c (int): global x coordinate of the agent's position. + current_map (int): ID of the current map (map_n) + + Updates the counts_map to track the frequency of visits to each position on the map. + ''' + # Convert local position to global position + try: + glob_r, glob_c = game_map.local_to_global(r, c, current_map) + except IndexError: + print(f'IndexError: index {glob_r} or {glob_c} for {current_map} is out of bounds for axis 0 with size 444.') + glob_r = 0 + glob_c = 0 + + # Update heat map based on current map + if self.last_map == current_map or self.last_map == -1: + # Increment count for current global position + try: + self.counts_map[glob_r, glob_c] += 1 + except: + pass + else: + # Reset count for current global position if it's a new map for warp artifacts + self.counts_map[(glob_r, glob_c)] = -1 + + # Update last_map for the next iteration + self.last_map = current_map + + def find_neighboring_npc(self, npc_bank, npc_id, player_direction, player_x, player_y) -> int: + + npc_y = ram_map.npc_y(self.game, npc_id, npc_bank) + npc_x = ram_map.npc_x(self.game, npc_id, npc_bank) + if ( + (player_direction == 0 and npc_x == player_x and npc_y > player_y) or + (player_direction == 4 and npc_x == player_x and npc_y < player_y) or + (player_direction == 8 and npc_y == player_y and npc_x < player_x) or + (player_direction == 0xC and npc_y == player_y and npc_x > player_x) + ): + # Manhattan distance + return abs(npc_y - player_y) + abs(npc_x - player_x) + + return 1000 + + def rewardable_coords(self, glob_c, glob_r): + self.include_conditions = [ + # (80 >= glob_c >= 72) and (294 < glob_r <= 320), + # (69 < glob_c < 74) and (313 >= glob_r >= 295), + # (73 >= glob_c >= 72) and (220 <= glob_r <= 330), + # (75 >= glob_c >= 74) and (310 >= glob_r <= 319), + # # (glob_c >= 75 and glob_r <= 310), + # (81 >= glob_c >= 73) and (294 < glob_r <= 313), + # (73 <= glob_c <= 81) and (294 < glob_r <= 308), + # (80 >= glob_c >= 74) and (330 >= glob_r >= 284), + # (90 >= glob_c >= 89) and (336 >= glob_r >= 328), + # # New below + # # Viridian Pokemon Center + # (282 >= glob_r >= 277) and glob_c == 98, + # # Pewter Pokemon Center + # (173 <= glob_r <= 178) and glob_c == 42, + # # Route 4 Pokemon Center + # (131 <= glob_r <= 136) and glob_c == 132, + # (75 <= glob_c <= 76) and (271 < glob_r < 273), + # (82 >= glob_c >= 74) and (284 <= glob_r <= 302), + # (74 <= glob_c <= 76) and (284 >= glob_r >= 277), + # (76 >= glob_c >= 70) and (266 <= glob_r <= 277), + # (76 <= glob_c <= 78) and (274 >= glob_r >= 272), + # (74 >= glob_c >= 71) and (218 <= glob_r <= 266), + # (71 >= glob_c >= 67) and (218 <= glob_r <= 235), + # (106 >= glob_c >= 103) and (228 <= glob_r <= 244), + # (116 >= glob_c >= 106) and (228 <= glob_r <= 232), + # (116 >= glob_c >= 113) and (196 <= glob_r <= 232), + # (113 >= glob_c >= 89) and (208 >= glob_r >= 196), + # (97 >= glob_c >= 89) and (188 <= glob_r <= 214), + # (102 >= glob_c >= 97) and (189 <= glob_r <= 196), + # (89 <= glob_c <= 91) and (188 >= glob_r >= 181), + # (74 >= glob_c >= 67) and (164 <= glob_r <= 184), + # (68 >= glob_c >= 67) and (186 >= glob_r >= 184), + # (64 <= glob_c <= 71) and (151 <= glob_r <= 159), + # (71 <= glob_c <= 73) and (151 <= glob_r <= 156), + # (73 <= glob_c <= 74) and (151 <= glob_r <= 164), + # (103 <= glob_c <= 74) and (157 <= glob_r <= 156), + # (80 <= glob_c <= 111) and (155 <= glob_r <= 156), + # (111 <= glob_c <= 99) and (155 <= glob_r <= 150), + # (111 <= glob_c <= 154) and (150 <= glob_r <= 153), + # (138 <= glob_c <= 154) and (153 <= glob_r <= 160), + # (153 <= glob_c <= 154) and (153 <= glob_r <= 154), + # (143 <= glob_c <= 144) and (153 <= glob_r <= 154), + # (154 <= glob_c <= 158) and (134 <= glob_r <= 145), + # (152 <= glob_c <= 156) and (145 <= glob_r <= 150), + # (42 <= glob_c <= 43) and (173 <= glob_r <= 178), + # (158 <= glob_c <= 163) and (134 <= glob_r <= 135), + # (161 <= glob_c <= 163) and (114 <= glob_r <= 128), + # (163 <= glob_c <= 169) and (114 <= glob_r <= 115), + # (114 <= glob_c <= 169) and (167 <= glob_r <= 102), + # (169 <= glob_c <= 179) and (102 <= glob_r <= 103), + # (178 <= glob_c <= 179) and (102 <= glob_r <= 95), + # (178 <= glob_c <= 163) and (95 <= glob_r <= 96), + # (164 <= glob_c <= 163) and (110 <= glob_r <= 96), + # (163 <= glob_c <= 151) and (110 <= glob_r <= 109), + # (151 <= glob_c <= 154) and (101 <= glob_r <= 109), + # (151 <= glob_c <= 152) and (101 <= glob_r <= 97), + # (153 <= glob_c <= 154) and (97 <= glob_r <= 101), + # (151 <= glob_c <= 154) and (97 <= glob_r <= 98), + # (152 <= glob_c <= 155) and (69 <= glob_r <= 81), + # (155 <= glob_c <= 169) and (80 <= glob_r <= 81), + # (168 <= glob_c <= 184) and (39 <= glob_r <= 43), + # (183 <= glob_c <= 178) and (43 <= glob_r <= 51), + # (179 <= glob_c <= 183) and (48 <= glob_r <= 59), + # (179 <= glob_c <= 158) and (59 <= glob_r <= 57), + # (158 <= glob_c <= 161) and (57 <= glob_r <= 30), + # (158 <= glob_c <= 150) and (30 <= glob_r <= 31), + # (153 <= glob_c <= 150) and (34 <= glob_r <= 31), + # (168 <= glob_c <= 254) and (134 <= glob_r <= 140), + # (282 >= glob_r >= 277) and (436 >= glob_c >= 0), # Include Viridian Pokecenter everywhere + # (173 <= glob_r <= 178) and (436 >= glob_c >= 0), # Include Pewter Pokecenter everywhere + # (131 <= glob_r <= 136) and (436 >= glob_c >= 0), # Include Route 4 Pokecenter everywhere + # (137 <= glob_c <= 197) and (82 <= glob_r <= 142), # Mt Moon Route 3 + # (137 <= glob_c <= 187) and (53 <= glob_r <= 103), # Mt Moon B1F + # (137 <= glob_c <= 197) and (16 <= glob_r <= 66), # Mt Moon B2F + # (137 <= glob_c <= 436) and (82 <= glob_r <= 444), # Most of the rest of map after Mt Moon + (0 <= glob_c <= 436) and (0 <= glob_r <= 444), # Whole map included + ] + return any(self.include_conditions) + def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" - load_pyboy_state(self.game, self.load_random_state()) - + load_pyboy_state(self.game, self.load_last_state()) + if self.save_video: - env_path = self.s_path - env_path.mkdir(parents=True, exist_ok=True) - full_name = Path(f'reset_{self.reset_count}').with_suffix('.mp4') - self.full_frame_writer = media.VideoWriter(env_path / full_name, (144, 160), fps=60) + base_dir = self.video_path + base_dir.mkdir(parents=True, exist_ok=True) + full_name = Path(f'{self.env_id}_reset_{self.reset_count}').with_suffix('.mp4') + self.full_frame_writer = media.VideoWriter(base_dir / full_name, (144, 160), fps=60) self.full_frame_writer.__enter__() if self.use_screen_memory: - self.screen_memory = defaultdict(lambda: np.zeros((255, 255, 1), dtype=np.uint8)) - + self.screen_memory = defaultdict( + lambda: np.zeros((255, 255, 1), dtype=np.uint8) + ) + self.time = 0 self.max_episode_steps = max_episode_steps self.reward_scale = reward_scale self.prev_map_n = None + #BET ADDED self.max_events = 0 self.max_level_sum = 0 self.max_opponent_level = 0 + self.seen_hidden_objs = set() self.seen_coords = set() self.seen_maps = set() - self.healing = 0 + ########################################################### Moved from Environment_Init() + self.is_dead = False + self.last_map = -1 + #End + self.death_count = 0 self.total_healing = 0 + self.last_hp = 1.0 self.last_party_size = 1 self.last_reward = None - self.last_hp = [1] * 6 - self.hp = [0] * 6 - self.hp_delta = [0] * 6 - self.death = 0 - self.qty = 0 + self.seen_coords_no_reward = set() self.reset_count += 1 - + return self.render(), {} def step(self, action, fast_video=True): - run_action_on_emulator(self.game, self.screen, ACTIONS[action], - self.headless, fast_video=fast_video) - + run_action_on_emulator( + self.game, + self.screen, + ACTIONS[action], + self.headless, + fast_video=fast_video, + ) self.time += 1 if self.save_video: self.add_video_frame() - - - if self.log: - self.full_name_log = Path(f'party_log').with_suffix('.txt') - self.full_name_csv = Path(f'position_csv').with_suffix('.csv') - self.write_to_log() - # Constants - pokecenters = [41, 58, 64, 68, 81, 89, 133, 141, 154, 171, 147, 182] - towns = set({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) - item_check = [1, 2, 3, 4, 6, 11, 16, 17, 18, 19, 20, 41, 42, 72, 73, 196, 197, 198, 199, 200, 53, 54] - - # Functions / Variables - - party, party_size, party_levels = ram_map.party(self.game) - party_size_constant = party_size == self.last_party_size + if self.csv: + self.write_to_csv() + # Exploration reward + r, c, map_n = ram_map.position(self.game) + # Convert local position to global position + try: + glob_r, glob_c = game_map.local_to_global(r, c, map_n) + except IndexError: + print(f'IndexError: index {glob_r} or {glob_c} is out of bounds for axis 0 with size 444.') + glob_r = 0 + glob_c = 0 + # Only reward for specified coordinates, not all coordinates seen + if self.rewardable_coords(glob_c, glob_r): + self.seen_coords.add((r, c, map_n)) + else: + self.seen_coords_no_reward.add((glob_c, glob_r, map_n)) - # Exploration - # Map Reward - r, c, map_n = ram_map.position(self.game) - self.seen_coords.add((r, c, map_n)) if map_n != self.prev_map_n: self.prev_map_n = map_n - if map_n not in self.seen_maps and map_n in pokecenters: + if map_n not in self.seen_maps: + self.seen_maps.add(map_n) + self.talk_to_npc_count[map_n] = 0 # Initialize NPC talk count for this new map self.save_state() - if map_n not in self.seen_maps: - self.seen_maps.add(map_n) - - coord_reward = 0.001 * len(self.seen_coords) - map_reward = 0.01 * len(self.seen_maps) + + + self.update_pokedex() + self.update_moves_obtained() + # obs = self.render() + + ############################################################### Added map_n into explore reward + coord_reward = 0.01 * len(self.seen_coords) + map_reward = 0.1 * len(self.seen_maps) exploration_reward = coord_reward + map_reward + self.update_heat_map(r, c, map_n) - #Plot Map - glob_r, glob_c = game_map.local_to_global(r, c, map_n) - try: - self.counts_map[glob_r, glob_c] += 1 - except: - pass - - # Pokemon - poke, type1, type2, level, hp, status, death = ram_map.pokemon(self.game) - level_rewards = [] - - #Levels - for lvl in level: - if lvl < 20: - level_reward = .5 * lvl - else: - level_reward = 7.5 + (lvl - 20) / 4 - level_rewards.append(level_reward) - lvl_rew = sum(level_rewards) - self.max_level_sum = sum(level) - - # HP / Death - self.hp = hp - assert len(self.hp) == len(self.last_hp) - for h, i in zip(self.hp, self.last_hp): - # hp_delta = h - i - # if hp_delta > 0.25 and party_size_constant: #updated from 0 to 0.25 to .50 - # self.total_healing += hp_delta - # i = h - # self.death = sum(death) - # # if h <= 0 and i > 0 and dead_delta > 0: - # # j = 1 - # # elif h > 0.01: - # # j = 0 - # # if (sum(hp)) <= 0 and (sum(self.last_hp)) > 0: - last_hp = i - cur_health = h - if (cur_health > last_hp and party_size_constant): - if last_hp > 0: - heal_amount = cur_health - last_hp - if heal_amount > 0.1: - self.total_healing += heal_amount * 2 - else: - self.death += 1 - self.last_hp = self.hp - healing_reward = self.total_healing - death_reward = -1.0 * self.death - - # Update Values + + # Level reward + party_size, party_levels = ram_map.party(self.game) + self.max_level_sum = max(self.max_level_sum, sum(party_levels)) + if self.max_level_sum < 30: + level_reward = 1 * self.max_level_sum + else: + level_reward = 30 + (self.max_level_sum - 30) / 4 + + # Healing and death rewards + hp = ram_map.hp(self.game) + hp_delta = hp - self.last_hp + party_size_constant = party_size == self.last_party_size + + # Only reward if not reviving at pokecenter + if hp_delta > 0 and party_size_constant and not self.is_dead: + self.total_healing += hp_delta + + # Dead if hp is zero + if hp <= 0 and self.last_hp > 0: + self.death_count += 1 + self.is_dead = True + elif hp > 0.01: # TODO: Check if this matters + self.is_dead = False + + # Update last known values for next iteration + self.last_hp = hp self.last_party_size = party_size + death_reward = 0 # -0.08 * self.death_count # -0.05 - # Badges + # Set rewards + healing_reward = self.total_healing + + # Opponent level reward + max_opponent_level = max(ram_map.opponent(self.game)) + self.max_opponent_level = max(self.max_opponent_level, max_opponent_level) + opponent_level_reward = 0 # 0.2 * self.max_opponent_level + + # Badge reward badges = ram_map.badges(self.game) badges_reward = 5 * badges + + # Save Bill + bill_state = ram_map.saved_bill(self.game) + bill_reward = 10 * bill_state + + # SS Anne appeared + ss_anne_state = ram_map.ss_anne_appeared(self.game) + if ss_anne_state: + ss_anne_state_reward = 5 + else: + ss_anne_state_reward = 0 + + # HM reward + hm_count = self.get_hm_rewards() + hm_reward = hm_count * 5 - # # Event reward + # Event reward events = ram_map.events(self.game) - self.max_events = events + self.max_events = max(self.max_events, events) event_reward = self.max_events - # HM reward - hm_count = ram_map.get_hm_rewards(self.game) - hm_reward = hm_count * 10 # 5 - - # Testing - # # Items - # items = ram_map.get_items_in_bag(self.game) - # if items[0] in item_check: - # self.qty += .01 * items[1] - # print(f'Items:{items}') - # print(f'Debug:{items[0]}, {items[1]}') - - #TODO - - # # Opponent level reward - # max_opponent_level = max(ram_map.opponent(self.game)) - # self.max_opponent_level = max(self.max_opponent_level, max_opponent_level) - # opponent_level_reward = 0.2 * self.max_opponent_level - - + # Money + money = ram_map.money(self.game) - # # money reward - # money = ram_map.money(self.game) + if ram_map.mem_val(self.game, 0xCFC4): + # check if we are talking to a hidden object: + if ram_map.mem_val(self.game, 0xCD3D) == 0x0 and ram_map.mem_val(self.game, 0xCD3E) == 0x0: + # add hidden object to seen hidden objects + self.seen_hidden_objs.add((ram_map.mem_val(self.game, 0xD35E), ram_map.mem_val(self.game, 0xCD3F))) + else: - # sum reward - reward = self.reward_scale * (lvl_rew + badges_reward + exploration_reward + healing_reward + event_reward + hm_reward) # + death_reward - reward1 = (lvl_rew + badges_reward + exploration_reward + healing_reward + event_reward + hm_reward) # + healing_reward - if death_reward == 0: - neg_reward = 1 - else: - neg_reward = death_reward - - # print rewards - if self.headless == False or self.save_video == True: - print(f'-------------Counter-------------') - print(f'Steps:',self.time,) - print(f'Sum Reward:',reward) - print(f'Coords:',len(self.seen_maps)) - print(f'HM Count:',hm_count) - # print(f'Items:',items) - # print(f'Items Reward::',self.qty) - print(f'Total Level:',self.max_level_sum) - print(f'Levels:',level) - print(f'HP:',hp) - print(f'Status:',status) - print(f'Deaths:',self.death) - print(f'Is Dead:', death) - print(f'Total Heal:',self.total_healing) - print(f'Party Size:',self.last_party_size) - print(f'-------------Rewards-------------') - print(f'Total:',reward1) - print(f'HM Reward:',hm_reward) - print(f'Explore:',exploration_reward,'--%',100 * (exploration_reward/reward1)) - print(f'Healing:',healing_reward,'--%',100 * (healing_reward/reward1)) - print(f'Badges:',badges_reward,'--%',100 * (badges_reward/reward1)) - print(f'Level:',lvl_rew,'--%',100 * (lvl_rew/reward1)) - print(f'Events:',event_reward,'--%',100 * (event_reward/reward1)) - print(f'-------------Negatives-------------') - print(f'Total:',neg_reward) - print(f'Deaths:',death_reward, '--%', 100 * (death_reward/neg_reward)) - # print(f'-------------Party-------------') - # print(f'P1--','Lvl:',p1lvl,', Status:',p1status,', HP:',self.last_p1hp,', Deaths:',self.p1death) - # print(f'P2--','Lvl:',p2lvl,', Status:',p2status,', HP:',self.last_p2hp,', Deaths:',self.p2death) - # print(f'P3--','Lvl:',p3lvl,', Status:',p3status,', HP:',self.last_p3hp,', Deaths:',self.p3death) - # print(f'P4--','Lvl:',p4lvl,', Status:',p4status,', HP:',self.last_p4hp,', Deaths:',self.p4death) - # print(f'P5--','Lvl:',p5lvl,', Status:',p5status,', HP:',self.last_p5hp,', Deaths:',self.p5death) - # print(f'P6--','Lvl:',p6lvl,', Status:',p6status,', HP:',self.last_p6hp,', Deaths:',self.p6death) - # print(f'Coords:',self.seen_maps) - # print(f'Dest_status:',self.dest_reward,'--%',100 * (self.dest_reward/neg_reward)) - # print(f'-------------Test-------------') - # print(f'Last Health:',self.last_health) - # print(f'Current Health:',cur_health) - # print(f'Heal Amount',self.heal_amount) + player_direction = ram_map.player_direction(self.game) + player_y = ram_map.player_y(self.game) + player_x = ram_map.player_x(self.game) + + mindex = (0, 0) + minv = 1000 + for npc_bank in range(1): + + for npc_id in range(1, ram_map.sprites(self.game) + 15): + npc_dist = self.find_neighboring_npc(npc_bank, npc_id, player_direction, player_x, player_y) + if npc_dist < minv: + mindex = (npc_bank, npc_id) + minv = npc_dist + self.seen_npcs.add((ram_map.map_n(self.game), mindex[0], mindex[1])) + + explore_npcs_reward = self.reward_scale * self.explore_npc_weight * len(self.seen_npcs) * 0.00015 + seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) * 0.00010 + caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) * 0.00010 + moves_obtained_reward = self.reward_scale * sum(self.moves_obtained) * 0.00010 + explore_hidden_objs_reward = self.reward_scale * self.explore_hidden_obj_weight * len(self.seen_hidden_objs) * 0.00015 + + reward = self.reward_scale * ( + event_reward + + explore_npcs_reward + + seen_pokemon_reward + + caught_pokemon_reward + + moves_obtained_reward + + explore_hidden_objs_reward + + bill_reward + + hm_reward + + level_reward + # + opponent_level_reward + # + death_reward + + badges_reward + + healing_reward + + exploration_reward + ) # Subtract previous reward # TODO: Don't record large cumulative rewards in the first place @@ -384,44 +632,79 @@ def step(self, action, fast_video=True): done = self.time >= self.max_episode_steps if self.save_video and done: self.full_frame_writer.close() - if done: + if done: + pokemon_info = data.pokemon_l(self.game) + x, y ,map_n = ram_map.position(self.game) + items = self.get_items_in_bag() + reset = self.reset_count + pokemon = [] + for p in pokemon_info: + pokemon.append({ + 'env_id': self.env_id, + 'slot': p['slot'], + 'name': p['name'], + 'level': p['level'], + 'moves': p['moves'], + 'items': items, + }) info = { - 'reward': { - 'delta': reward, - 'event': event_reward, - 'level': level_reward, - 'hm_count': hm_count, - 'death': death_reward, - 'badges': badges_reward, - 'healing': healing_reward, - 'exploration': exploration_reward, + "reward": { + "delta": reward, + "event": event_reward, + "level": level_reward, + "opponent_level": opponent_level_reward, + "death": death_reward, + "badges": badges_reward, + "bill_saved_reward": bill_reward, + "hm_count_award": hm_reward, + "ss_anne_present": ss_anne_state_reward, + "healing": healing_reward, + "exploration": exploration_reward, + "explore_npcs_reward": explore_npcs_reward, + "seen_pokemon_reward": seen_pokemon_reward, + "caught_pokemon_reward": caught_pokemon_reward, + "moves_obtained_reward": moves_obtained_reward, + "hidden_obj_count_reward": explore_hidden_objs_reward, }, - 'maps_explored': len(self.seen_maps), - 'party_size': party_size, - 'highest_pokemon_level': max(party_levels), - 'total_party_level': sum(party_levels), - 'deaths': self.death, - 'hm': hm_reward, - 'badge_1': float(badges >= 1), - 'badge_2': float(badges > 1), - 'event': events, - 'healing': self.total_healing, - 'pokemon_exploration_map': self.counts_map, + "maps_explored": len(self.seen_maps), + "party_size": party_size, + "highest_pokemon_level": max(party_levels), + "total_party_level": sum(party_levels), + "deaths": self.death_count, + "bill_saved": bill_state, + "hm_count": hm_count, + "ss_anne_state": ss_anne_state, + "badge_1": float(badges >= 1), + "badge_2": float(badges >= 2), + "event": events, + "money": money, + "pokemon_exploration_map": self.counts_map, + "seen_npcs_count": len(self.seen_npcs), + "seen_pokemon": sum(self.seen_pokemon), + "caught_pokemon": sum(self.caught_pokemon), + "moves_obtained": sum(self.moves_obtained), + "hidden_obj_count": len(self.seen_hidden_objs), + "logging": pokemon, } - # if self.verbose: - # print( - # f'steps: {self.time}', - # f'exploration reward: {exploration_reward}', - # f'level_Reward: {level_reward}', - # f'healing: {healing_reward}', - # f'death: {death_reward}', - # #f'op_level: {opponent_level_reward}', - # f'badges reward: {badges_reward}', - # f'event reward: {event_reward}', - # # f'money: {money}', - # f'ai reward: {reward}', - # f'Info: {info}', - # ) - - return self.render(), reward, done, done, info + if self.verbose: + print( + f'number of signs: {ram_map.signs(self.game)}, number of sprites: {ram_map.sprites(self.game)}\n', + f"steps: {self.time}\n", + f"seen_npcs #: {len(self.seen_npcs)}\n", + f"seen_npcs set: {self.seen_npcs}\n", + # f"is_in_battle: {ram_map.is_in_battle(self.game)}", + f"exploration reward: {exploration_reward}\n", + f"explore_npcs reward: {explore_npcs_reward}\n", + f"level_Reward: {level_reward}\n", + f"healing: {healing_reward}\n", + f"death: {death_reward}\n", + f"op_level: {opponent_level_reward}\n", + f"badges reward: {badges_reward}\n", + f"event reward: {event_reward}\n", + f"money: {money}\n", + f"ai reward: {reward}\n", + f"Info: {info}\n", + ) + + return self.render(), reward, done, done, info \ No newline at end of file diff --git a/pokegym/game_map.py b/pokegym/game_map.py index 70a098a..55a81ec 100644 --- a/pokegym/game_map.py +++ b/pokegym/game_map.py @@ -8,6 +8,11 @@ MAP_DATA = json.load(open(MAP_PATH, 'r'))['regions'] MAP_DATA = {int(e['id']): e for e in MAP_DATA} +# Handle KeyErrors def local_to_global(r, c, map_n): - map_x, map_y,= MAP_DATA[map_n]['coordinates'] - return r + map_y, c + map_x + try: + map_x, map_y,= MAP_DATA[map_n]['coordinates'] + return r + map_y, c + map_x + except KeyError: + print(f'Map id {map_n} not found in map_data.json.') + return r + 0, c + 0 \ No newline at end of file diff --git a/pokegym/map_data.json b/pokegym/map_data.json old mode 100644 new mode 100755 diff --git a/pokegym/notes b/pokegym/notes deleted file mode 100644 index 14a5f36..0000000 --- a/pokegym/notes +++ /dev/null @@ -1,605 +0,0 @@ -# ################################################################################################################## -# # Imports because syntax -# ################################################################################################################## - -import csv -from pdb import set_trace as T -import uuid -from gymnasium import Env, spaces -import numpy as np -from collections import defaultdict -import io, os -import random -from pathlib import Path -import mediapy as media - - -from pokegym.pyboy_binding import (ACTIONS, make_env, open_state_file, - load_pyboy_state, run_action_on_emulator) -from pokegym import ram_map, game_map, data -from environment import Base - -# ################################################################################################################## -# # this is env_logging: -# ################################################################################################################## - - - with open("experiments/current_exp.txt", "r") as file: - exp_name = file.read() - self.exp_path = Path(f'experiments/{str(exp_name)}') - self.s_path = Path(f'{str(self.exp_path)}/sessions/{str(self.env_id)}') - self.env_id = str(uuid.uuid4())[:4] - - def write_to_log(self): - pokemon_info = data.pokemon_l(self.game) - x, y ,map_n = ram_map.position(self.game) - session_path = self.s_path - base_dir = self.exp_path - reset = self.reset_count - env_id = self.env_id - csv_file_path = base_dir / "unique_positions.csv" - base_dir.mkdir(parents=True, exist_ok=True) - session_path.mkdir(parents=True, exist_ok=True) - with open(session_path / self.full_name_log, 'w') as f: - for pokemon in pokemon_info: - f.write(f"Slot: {pokemon['slot']}\n") - f.write(f"Name: {pokemon['name']}\n") - f.write(f"Level: {pokemon['level']}\n") - f.write(f"Moves: {', '.join(pokemon['moves'])}\n") - f.write("\n") # Add a newline between Pokémon - with open(csv_file_path, 'a') as csv_file: - csv_writer = csv.writer(csv_file) - csv_writer.writerow([env_id, reset, x, y, map_n]) - - exp_path = Path(f'current_exp').with_suffix('.txt') - with open(data.config.data_dir / exp_path, 'w') as file: - file.write(f"{data.exp_name}") - - -# ################################################################################################################## -# # Useful functions -# ################################################################################################################## - - STATE_PATH = __file__.rstrip("environment.py") + "States" - - def get_random_state(): - state_files = [f for f in os.listdir(STATE_PATH) if f.endswith(".state")] - if not state_files: - raise FileNotFoundError("No State files found in the specified directory.") - return random.choice(state_files) - - state_file = get_random_state() - randstate = os.path.join(STATE_PATH, state_file) - -# ################################################################################################################## -# # TODO Pull Functions from this -# ################################################################################################################## - class Environment(Base): - def __init__( - self, - rom_path="pokemon_red.gb", - state_path=None, - headless=True, - save_video=True, - quiet=False, - verbose=False, - **kwargs, - ): - super().__init__(rom_path, state_path, headless, save_video, quiet, **kwargs) - self.counts_map = np.zeros((444, 436)) - self.verbose = verbose - self.screenshot_counter = 0 - self.include_conditions = [] - self.seen_maps_difference = set() - self.seen_maps = 0 - self.current_maps = [] - self.exclude_map_n = {37, 38, 39, 43, 52, 53, 55, 57} - self.exclude_map_n = set() - # self.exclude_map_n_moon = {0, 1, 2, 12, 13, 14, 15, 33, 34, 37, 38, 39, 40, 41, 42, 43, 44, 47, 50, 51, 52, 53, 54, 55, 56, 57, 58, 193, 68} - self.is_dead = False - self.talk_to_npc_reward = 0 - self.talk_to_npc_count = {} - self.already_got_npc_reward = set() - self.ss_anne_state = False - self.seen_npcs = set() - self.explore_npc_weight = 1 - self.last_map = -1 - self.init_hidden_obj_mem() - self.seen_pokemon = np.zeros(152, dtype=np.uint8) - self.caught_pokemon = np.zeros(152, dtype=np.uint8) - self.moves_obtained = np.zeros(0xA5, dtype=np.uint8) - self.log = True - - def update_pokedex(self): - for i in range(0xD30A - 0xD2F7): - caught_mem = self.game.get_memory_value(i + 0xD2F7) - seen_mem = self.game.get_memory_value(i + 0xD30A) - for j in range(8): - self.caught_pokemon[8*i + j] = 1 if caught_mem & (1 << j) else 0 - self.seen_pokemon[8*i + j] = 1 if seen_mem & (1 << j) else 0 - - def update_moves_obtained(self): - # Scan party - for i in [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247]: - if self.game.get_memory_value(i) != 0: - for j in range(4): - move_id = self.game.get_memory_value(i + j + 8) - if move_id != 0: - if move_id != 0: - self.moves_obtained[move_id] = 1 - # Scan current box (since the box doesn't auto increment in pokemon red) - num_moves = 4 - box_struct_length = 25 * num_moves * 2 - for i in range(self.game.get_memory_value(0xda80)): - offset = i*box_struct_length + 0xda96 - if self.game.get_memory_value(offset) != 0: - for j in range(4): - move_id = self.game.get_memory_value(offset + j + 8) - if move_id != 0: - self.moves_obtained[move_id] = 1 - - def get_items_in_bag(self, one_indexed=0): - first_item = 0xD31E - # total 20 items - # item1, quantity1, item2, quantity2, ... - item_ids = [] - for i in range(0, 20, 2): - item_id = self.game.get_memory_value(first_item + i) - if item_id == 0 or item_id == 0xff: - break - item_ids.append(item_id + one_indexed) - return item_ids - - def get_hm_rewards(self): - hm_ids = [0xC4, 0xC5, 0xC6, 0xC7, 0xC8] - items = self.get_items_in_bag() - total_hm_cnt = 0 - for hm_id in hm_ids: - if hm_id in items: - total_hm_cnt += 1 - return total_hm_cnt * 1 - - def update_heat_map(self, r, c, current_map): - ''' - Updates the heat map based on the agent's current position. - - Args: - r (int): global y coordinate of the agent's position. - c (int): global x coordinate of the agent's position. - current_map (int): ID of the current map (map_n) - - Updates the counts_map to track the frequency of visits to each position on the map. - ''' - # Convert local position to global position - try: - glob_r, glob_c = game_map.local_to_global(r, c, current_map) - except IndexError: - print(f'IndexError: index {glob_r} or {glob_c} for {current_map} is out of bounds for axis 0 with size 444.') - glob_r = 0 - glob_c = 0 - - # Update heat map based on current map - if self.last_map == current_map or self.last_map == -1: - # Increment count for current global position - try: - self.counts_map[glob_r, glob_c] += 1 - except: - pass - else: - # Reset count for current global position if it's a new map for warp artifacts - self.counts_map[(glob_r, glob_c)] = -1 - - # Update last_map for the next iteration - self.last_map = current_map - - def find_neighboring_npc(self, npc_bank, npc_id, player_direction, player_x, player_y) -> int: - - npc_y = ram_map.npc_y(self.game, npc_id, npc_bank) - npc_x = ram_map.npc_x(self.game, npc_id, npc_bank) - if ( - (player_direction == 0 and npc_x == player_x and npc_y > player_y) or - (player_direction == 4 and npc_x == player_x and npc_y < player_y) or - (player_direction == 8 and npc_y == player_y and npc_x < player_x) or - (player_direction == 0xC and npc_y == player_y and npc_x > player_x) - ): - # Manhattan distance - return abs(npc_y - player_y) + abs(npc_x - player_x) - - return 1000 - - def step(self, action, fast_video=True): - run_action_on_emulator( - self.game, - self.screen, - ACTIONS[action], - self.headless, - fast_video=fast_video, - ) - self.time += 1 - if self.save_video: - self.add_video_frame() - - # Exploration reward - r, c, map_n = ram_map.position(self.game) - # Convert local position to global position - try: - glob_r, glob_c = game_map.local_to_global(r, c, map_n) - except IndexError: - print(f'IndexError: index {glob_r} or {glob_c} is out of bounds for axis 0 with size 444.') - glob_r = 0 - glob_c = 0 - - # Only reward for specified coordinates, not all coordinates seen - if self.rewardable_coords(glob_c, glob_r): - self.seen_coords.add((r, c, map_n)) - else: - self.seen_coords_no_reward.add((glob_c, glob_r, map_n)) - - if map_n != self.prev_map_n: - self.prev_map_n = map_n - if map_n not in self.seen_maps: - self.seen_maps.add(map_n) - self.talk_to_npc_count[map_n] = 0 # Initialize NPC talk count for this new map - self.save_state() - - self.update_pokedex() - self.update_moves_obtained() - - exploration_reward = 0.01 * len(self.seen_coords) - self.update_heat_map(r, c, map_n) - - # Level reward - party_size, party_levels = ram_map.party(self.game) - self.max_level_sum = max(self.max_level_sum, sum(party_levels)) - if self.max_level_sum < 30: - level_reward = 1 * self.max_level_sum - else: - level_reward = 30 + (self.max_level_sum - 30) / 4 - - # Healing and death rewards - hp = ram_map.hp(self.game) - hp_delta = hp - self.last_hp - party_size_constant = party_size == self.last_party_size - - # Only reward if not reviving at pokecenter - if hp_delta > 0 and party_size_constant and not self.is_dead: - self.total_healing += hp_delta - - # Dead if hp is zero - if hp <= 0 and self.last_hp > 0: - self.death_count += 1 - self.is_dead = True - elif hp > 0.01: # TODO: Check if this matters - self.is_dead = False - - # Update last known values for next iteration - self.last_hp = hp - self.last_party_size = party_size - death_reward = 0 # -0.08 * self.death_count # -0.05 - - # Set rewards - healing_reward = self.total_healing - - # Opponent level reward - max_opponent_level = max(ram_map.opponent(self.game)) - self.max_opponent_level = max(self.max_opponent_level, max_opponent_level) - opponent_level_reward = 0 # 0.2 * self.max_opponent_level - - # Badge reward - badges = ram_map.badges(self.game) - badges_reward = 5 * badges - - # Save Bill - bill_state = ram_map.saved_bill(self.game) - bill_reward = 10 * bill_state - - # SS Anne appeared - ss_anne_state = ram_map.ss_anne_appeared(self.game) - if ss_anne_state: - ss_anne_state_reward = 10 # 5 - ss_anne_obtained = 1 - else: - ss_anne_state_reward = 0 - ss_anne_obtained = 0 - - # HM reward - hm_count = self.get_hm_rewards() - hm_reward = hm_count * 10 # 5 - - # Event reward - events = ram_map.events(self.game) - self.max_events = max(self.max_events, events) - event_reward = self.max_events - - money = ram_map.money(self.game) - - # Explore NPCs - # check if the font is loaded - if ram_map.mem_val(self.game, 0xCFC4): - # check if we are talking to a hidden object: - if ram_map.mem_val(self.game, 0xCD3D) == 0x0 and ram_map.mem_val(self.game, 0xCD3E) == 0x0: - # add hidden object to seen hidden objects - self.seen_hidden_objs.add((ram_map.mem_val(self.game, 0xD35E), ram_map.mem_val(self.game, 0xCD3F))) - else: - # check if we are talking to someone - # if ram_map.if_font_is_loaded(self.game): - # get information for player - player_direction = ram_map.player_direction(self.game) - player_y = ram_map.player_y(self.game) - player_x = ram_map.player_x(self.game) - # get the npc who is closest to the player and facing them - # we go through all npcs because there are npcs like - # nurse joy who can be across a desk and still talk to you - mindex = (0, 0) - minv = 1000 - for npc_bank in range(1): - - for npc_id in range(1, ram_map.sprites(self.game) + 15): - npc_dist = self.find_neighboring_npc(npc_bank, npc_id, player_direction, player_x, player_y) - if npc_dist < minv: - mindex = (npc_bank, npc_id) - minv = npc_dist - self.seen_npcs.add((ram_map.map_n(self.game), mindex[0], mindex[1])) - - explore_npcs_reward = self.reward_scale * self.explore_npc_weight * len(self.seen_npcs) * 0.00015 - seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) * 0.00010 - caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) * 0.00010 - moves_obtained_reward = self.reward_scale * sum(self.moves_obtained) * 0.00010 - explore_hidden_objs_reward = self.reward_scale * self.explore_hidden_obj_weight * len(self.seen_hidden_objs) * 0.00015 - - reward = self.reward_scale * ( - event_reward - + explore_npcs_reward # Doesn't reset on reset but maybe should? - + seen_pokemon_reward - + caught_pokemon_reward - + moves_obtained_reward - + explore_hidden_objs_reward # Resets on reset - + bill_reward - + hm_reward - + level_reward - + opponent_level_reward - + death_reward # Resets on reset - + badges_reward - + healing_reward # Resets each step - + exploration_reward # Resets on reset - ) - - # Subtract previous reward - # TODO: Don't record large cumulative rewards in the first place - if self.last_reward is None: - reward = 0 - self.last_reward = 0 - else: - nxt_reward = reward - reward -= self.last_reward - self.last_reward = nxt_reward - - info = {} - done = self.time >= self.max_episode_steps - if self.save_video and done: - self.full_frame_writer.close() - if done: - info = { - "reward": { - "delta": reward, - "event": event_reward, - "level": level_reward, - "opponent_level": opponent_level_reward, - "death": death_reward, - "badges": badges_reward, - "bill_saved_reward": bill_reward, - "hm_count_reward": hm_reward, - "ss_anne_done_reward": ss_anne_state_reward, - "healing": healing_reward, - "exploration": exploration_reward, - "explore_npcs_reward": explore_npcs_reward, - "seen_pokemon_reward": seen_pokemon_reward, - "caught_pokemon_reward": caught_pokemon_reward, - "moves_obtained_reward": moves_obtained_reward, - "hidden_obj_count_reward": explore_hidden_objs_reward, - }, - "maps_explored": len(self.seen_maps), - "party_size": party_size, - "highest_pokemon_level": max(party_levels), - "total_party_level": sum(party_levels), - "deaths": self.death_count, - "bill_saved": bill_state, - "hm_count": hm_count, - "ss_anne_obtained": ss_anne_obtained, - "badge_1": float(badges >= 1), - "badge_2": float(badges >= 2), - "event": events, - "money": money, - "pokemon_exploration_map": self.counts_map, - "seen_npcs_count": len(self.seen_npcs), - "seen_pokemon": sum(self.seen_pokemon), - "caught_pokemon": sum(self.caught_pokemon), - "moves_obtained": sum(self.moves_obtained), - "hidden_obj_count": len(self.seen_hidden_objs), - } - - if self.verbose: - print( - f'number of signs: {ram_map.signs(self.game)}, number of sprites: {ram_map.sprites(self.game)}\n', - f"steps: {self.time}\n", - f"seen_npcs #: {len(self.seen_npcs)}\n", - f"seen_npcs set: {self.seen_npcs}\n", - # f"is_in_battle: {ram_map.is_in_battle(self.game)}", - f"exploration reward: {exploration_reward}\n", - f"explore_npcs reward: {explore_npcs_reward}\n", - f"level_Reward: {level_reward}\n", - f"healing: {healing_reward}\n", - f"death: {death_reward}\n", - f"op_level: {opponent_level_reward}\n", - f"badges reward: {badges_reward}\n", - f"event reward: {event_reward}\n", - f"money: {money}\n", - f"ai reward: {reward}\n", - f"Info: {info}\n", - ) - - return self.render(), reward, done, done, info - -# ################################################################################################################## -# # Battle Functions -# ################################################################################################################## - - self.ap_party_level = 0 # The sum of player (agent) Pokémon levels in the party - self.wp_party_level = 0 # The sum of enemy (wild) Pokémon levels in the party - self.ap_level = 0 # The level of the player (agent) Pokémon in the lead position - self.wp_level = 0 # The level of the enemy (wild) Pokémon in the lead position - self.ap_party_size = 0 # The battle_reward test initialize variable current player (agent) party size - # (how many Pokémon in party) - self.wp_party_size = 0 # The battle_reward test initialize variable current enemy (wild) party size - # (how many Pokémon in party) - self.ap_health = self.get_ap_health_sum() # The sum of player (agent) Pokémon health in the party - self.wp_health = 0 # The sum of enemy (wild) Pokémon health in the party - self.in_battle = 0 # Flag of current battle state (-1 white-out, 0 normal, 1 wild, 2 trainer) - self.battle_rewarded = False # Was a reward given from last battle? - self.battle_reward_value = 0 # points rewarded from last battle - self.total_battle_reward = 0 # total points rewarded - self.levels_satisfied = False - - def get_ap_level_sum(self): - sum_levels = sum(self.read_m(a) for a in [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268]) - if sum_levels < 0: - return 1 - else: - return sum_levels - - def get_wp_level_sum(self): - sum_levels = sum(self.read_m(a) for a in [0xCFF3, 0xD8F1, 0xD91D, 0xD949, 0xD975, 0xD9A1]) - if sum_levels < 0: - return 1 - else: - return sum_levels - - def read_low_high(self, low_adder, high_adder): - low = self.read_m(low_adder) - high = self.read_m(high_adder) - return low + high - - def get_ap_health_sum(self): - # HP addresses for each Pokémon in the party - hp_addresses = [ - (0xD015, 0xD016), # Pokemon 1 - (0xD198, 0xD199), # Pokemon 2 - (0xD1C4, 0xD1C5), # Pokemon 3 - (0xD1F0, 0xD1F1), # Pokemon 4 - (0xD21C, 0xD21D), # Pokemon 5 - (0xD248, 0xD249), # Pokemon 6 - ] - - sum_hp = 0 - - # Iterate through each pair of addresses and sum the HP values - for low_adder, high_adder in hp_addresses: - sum_hp += self.read_low_high(low_adder, high_adder) - - return sum_hp - - def get_wp_health_sum(self): - # HP addresses for each Pokémon in the party - hp_addresses = [ - (0xD8A5, 0xCFE7), # Pokemon 1 - (0xD8D1, 0xD8D2), # Pokemon 2 - (0xD8FD, 0xD8FE), # Pokemon 3 - (0xD929, 0xD92A), # Pokemon 4 - (0xD955, 0xD956), # Pokemon 5 - (0xD981, 0xD982), # Pokemon 6 - ] - - sum_hp = 0 - - # Iterate through each pair of addresses and sum the HP values - for low_adder, high_adder in hp_addresses: - sum_hp += self.read_low_high(low_adder, high_adder) - - return sum_hp - - def update_battle_values(self): - self.ap_party_size = self.read_m(0xD163) - self.wp_party_size = self.read_m(0xD89C) - self.ap_health = self.get_ap_health_sum() - self.wp_health = self.get_wp_health_sum() - self.ap_party_level = self.get_ap_level_sum() - self.wp_party_level = self.get_wp_level_sum() - self.ap_level = self.read_m(0xCD0F) - self.wp_level = self.read_m(0xCD23) - self.in_battle = self.read_m(0xD057) - - print( - f"Update Battle values Function\n" - f"Battle Flag: {self.in_battle}\n" - f"Last Battle Rewarded: {self.battle_rewarded}\n" - f"Level Difference: {self.ap_level - self.wp_level}\n" - f"Reward: {self.battle_reward_value}\n\n" - ) - - def has_battle_been_won(self): - - is_in_battle = self.in_battle >= 1 - - fled = self.read_m(0xD078) # needed for console only - pokeball_catch = self.read_m(0xD11C) # needed for console only - - if is_in_battle: - enemy_party_hp = self.get_wp_health_sum() - - if enemy_party_hp <= 0 and self.in_battle != -1: - return True - - # Required to print to Console only - if self.in_battle == -1 or fled != 0 or pokeball_catch != 0: # whiteout, run/flee, capture - self.battle_rewarded = False - self.battle_reward_value = 0 - return False - else: - return False - - def calculate_battle_reward(self): - # ap = Agent Party, wp = Wild Pokémon - if not self.has_battle_been_won(): - return 0 - - bonus = 0 # Resets bonus for calculation - - trainer_battle = self.in_battle == 2 - - # Calculate level difference for wild battles - if not trainer_battle: - level_difference = self.ap_level - self.wp_level - else: - ap_avg_level = self.ap_party_level / self.ap_party_size # if self.ap_party_size > 0 else 0 - wp_avg_level = self.wp_party_level / self.wp_party_size # if self.wp_party_size > 0 else 0 - level_difference = ap_avg_level - wp_avg_level - - # Assign base rewards based on the level difference - if level_difference == 4: - reward = 0.2 - elif level_difference == 3: - reward = 0.4 - elif level_difference == 2: - reward = 0.6 - elif level_difference == 1: - reward = 0.8 - elif level_difference == 0: - reward = 1 - elif level_difference == -1: - reward = 1.5 - elif level_difference <= -2: - reward = 2 - else: - reward = 0 # Default case, if needed - - # Add a bonus for winning against a larger team in trainer battles - if trainer_battle and self.ap_party_size <= self.wp_party_size: - team_size_difference = abs(self.wp_party_size - self.ap_party_size) - bonus = 0.5 * team_size_difference + 0.5 - reward += bonus - - self.battle_rewarded = True - self.battle_reward_value = reward - self.total_battle_reward += reward # to keep record of total reward - return reward - diff --git a/pokegym/pyboy_binding.py b/pokegym/pyboy_binding.py index cbd9002..0e9a4fd 100644 --- a/pokegym/pyboy_binding.py +++ b/pokegym/pyboy_binding.py @@ -5,8 +5,6 @@ from pyboy import logger from pyboy.utils import WindowEvent - - logger.logger.setLevel('ERROR') @@ -58,7 +56,7 @@ def make_env(gb_path, headless=True, quiet=False, **kwargs): screen = game.botsupport_manager().screen() if not headless: - game.set_emulation_speed(10) + game.set_emulation_speed(6) return game, screen diff --git a/pokegym/ram_map.py b/pokegym/ram_map.py index 888d9b5..9a941dd 100644 --- a/pokegym/ram_map.py +++ b/pokegym/ram_map.py @@ -1,11 +1,7 @@ -# ###################################################################################### -# Ram_map -# ###################################################################################### - -# Data Crystal - https://datacrystal.romhacking.net/wiki/Pok%C3%A9mon_Red/Blue:RAM_map -# No Comments - https://github.com/pret/pokered/blob/91dc3c9f9c8fd529bb6e8307b58b96efa0bec67e/constants/event_constants.asm -# Comments - https://github.com/luckytyphlosion/pokered/blob/master/constants/event_constants.asm - +# addresses from https://datacrystal.romhacking.net/wiki/Pok%C3%A9mon_Red/Blue:RAM_map +# https://github.com/pret/pokered/blob/91dc3c9f9c8fd529bb6e8307b58b96efa0bec67e/constants/event_constants.asm +HP_ADDR = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] +MAX_HP_ADDR = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] PARTY_SIZE_ADDR = 0xD163 PARTY_ADDR = [0xD164, 0xD165, 0xD166, 0xD167, 0xD168, 0xD169] PARTY_LEVEL_ADDR = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] @@ -22,296 +18,191 @@ OPPONENT_LEVEL = 0xCFF3 ENEMY_POKE_COUNT = 0xD89C EVENT_FLAGS_START_ADDR = 0xD747 -EVENT_FLAGS_END_ADDR = 0xD761 +EVENT_FLAGS_END_ADDR = 0xD886 # 0xD761 MUSEUM_TICKET_ADDR = 0xD754 +USED_CELL_SEPARATOR_ADDR = 0xD7F2 MONEY_ADDR_1 = 0xD347 MONEY_ADDR_100 = 0xD348 MONEY_ADDR_10000 = 0xD349 -BAG = [0xD31E, 0xD320, 0xD322, 0xD324, 0xD326, 0xD328, 0xD32A, 0xD32C, 0xD32E, 0xD330, 0xD332, 0xD334, 0xD336, 0xD338, 0xD33A, 0xD33C, 0xD33E, 0xD340, 0xD342, 0xD344] -EVENTS = [0xD710, 0xD7D8, 0xD7E0, 0xD803, 0xD5F3, 0xD60D] -BIRDS = [0xD782, 0xD7D4, 0xD7EE] -GYMS = [0xD755, 0xD75E, 0xD773, 0xD77C, 0xD792, 0xD7B3, 0xD79A, 0xD751] -POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] # - Pokémon (Again) -STATUS = [0xD16F, 0xD19B, 0xD1C7, 0xD1F3, 0xD21F, 0xD24B] # - Status (Poisoned, Paralyzed, etc.) -TYPE1 = [0xD170, 0xD19C, 0xD1C8, 0xD1F4, 0xD220, 0xD24C] # - Type 1 -TYPE2 = [0xD171, 0xD19D, 0xD1C9, 0xD1F5, 0xD221, 0xD24D] # - Type 2 -LEVEL = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] # - Level (actual level) -MAXHP = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] # - Max HP if = 01 + 256 to MAXHP2 value -CHP = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] # - Current HP if = 01 + 256 -MOVE1PP = [0xD188, 0xD1B4, 0xD1E0, 0xD20C, 0xD238, 0xD264] -MOVE2PP = [0xD189, 0xD1B5, 0xD1E1, 0xD20D, 0xD239, 0xD265] -MOVE3PP = [0xD18A, 0xD1B6, 0xD1E2, 0xD20E, 0xD23A, 0xD266] -MOVE4PP = [0xD18B, 0xD1B7, 0xD1E3, 0xD20F, 0xD23B, 0xD267] -MOVE1 = [0xD173, 0xD19F, 0xD1CB, 0xD1F7, 0xD223, 0xD24F] -MOVE2 = [0xD174, 0xD1A0, 0xD1CC, 0xD1F8, 0xD224, 0xD250] -MOVE3 = [0xD175, 0xD1A1, 0xD1CD, 0xD1F9, 0xD225, 0xD251] -MOVE4 = [0xD176, 0xD1A2, 0xD1CE, 0xD1FA, 0xD226, 0xD252] - -STATUSDICT = { - 0x08: 'Poison', - # 0x04: 'Burn', - # 0x05: 'Frozen', - # 0x06: 'Paralyze', - 0x00: 'None', -} - -# start new functions -def pokemon(game): - status = [] - poke = [game.get_memory_value(a) for a in POKE] - stat = [game.get_memory_value(a) for a in STATUS] - for i in stat: - s = STATUSDICT.get(i, 'Unknown') - status.append(s) - type1 = [game.get_memory_value(a) for a in TYPE1] - type2 = [game.get_memory_value(a) for a in TYPE2] - level = [game.get_memory_value(a) for a in LEVEL] - mhp = [read_uint16(game, a) for a in MAXHP] - chp = [read_uint16(game, a) for a in CHP] - # moves = [game.get_memory_value(addr) for addr in P1MOVES] - # movepp = [game.get_memory_value(addr) for addr in P1MOVEPP] - hp = [] - death = [] - assert len(mhp) == len(chp) - for h, i in zip(mhp, chp): - if h == 0: - j = 0 - else: - j = i / h - hp.append(j) - if i == 0 and h != 0: - dead = 1 - else: - dead = 0 - death.append(dead) - return poke, type1, type2, level, hp, status, death - -def items(game, start_addr): - i = [] - q = [] - item = game.get_memory_value(start_addr) - qty = game.get_memory_value(start_addr + 1) - i.append(item) - q.append(qty) - return i, q - -def item_bag(game): - items_list = [items(game, a) for a in BAG] - item, qty = zip(*items_list) - print(f'Items:', item, qty,'List:', items_list) - return item, qty - -def events(game): - #0xd7f2 - num_events = sum(bit_count(game.get_memory_value(i)) for i in EVENTS) - museum_ticket = int(read_bit(game, MUSEUM_TICKET_ADDR, 0)) - birds = sum(bit_count(game.get_memory_value(i)) for i in BIRDS) - gyms = sum(bit_count(game.get_memory_value(i)) for i in GYMS) +# MAP_TEXT_POINTER_TABLE_NPC = 0xD36C - 0xD36D +TEXT_BOX_ARROW_BLINK = 0xC4F2 +BATTLE_FLAG = 0xD057 +SS_ANNE = 0xD803 +IF_FONT_IS_LOADED = 0xCFC4 # text box is up +# get information for player +PLAYER_DIRECTION = 0xC109 +PLAYER_Y = 0xC104 +PLAYER_X = 0xC106 +WNUMSPRITES = 0xD4E1 +WNUMSIGNS = 0xD4B0 - # Omit 13 events by default - return num_events + museum_ticket + (birds * 10) + (gyms * 5) - -def get_items_in_bag(game, one_indexed=0): - first_item = 0xD31E - # total 20 items - # item1, quantity1, item2, quantity2, ... - item_ids = [] - for i in range(0, 20, 2): - item_id = game.get_memory_value(first_item + i) - if item_id == 0 or item_id == 0xff: - break - item_ids.append(item_id + one_indexed) - return item_ids - -def get_hm_rewards(game): - hm_ids = [0xC4, 0xC5, 0xC6, 0xC7, 0xC8] - items = get_items_in_bag(game) - total_hm_cnt = 0 - for hm_id in hm_ids: - if hm_id in items: - total_hm_cnt += 1 - return total_hm_cnt * 1 -# end new functions - -# Start Utilities def bcd(num): - return 10 * ((num >> 4) & 0x0f) + (num & 0x0f) + return 10 * ((num >> 4) & 0x0F) + (num & 0x0F) -def read_m(game, addr): - val = game.get_memory_value(addr) - return val def bit_count(bits): - return bin(bits).count('1') + return bin(bits).count("1") + def read_bit(game, addr, bit) -> bool: # add padding so zero will read '0b100000000' instead of '0b0' - return bin(256 + game.get_memory_value(addr))[-bit-1] == '1' + return bin(256 + game.get_memory_value(addr))[-bit - 1] == "1" + +def mem_val(game, addr): + mem = game.get_memory_value(addr) + return mem def read_uint16(game, start_addr): - '''Read 2 bytes''' + """Read 2 bytes""" val_256 = game.get_memory_value(start_addr) val_1 = game.get_memory_value(start_addr + 1) - return 256*val_256 + val_1 -# End Utilities + return 256 * val_256 + val_1 + -# TODO def position(game): r_pos = game.get_memory_value(Y_POS_ADDR) c_pos = game.get_memory_value(X_POS_ADDR) map_n = game.get_memory_value(MAP_N_ADDR) + if r_pos >= 443: + r_pos = 444 + if r_pos <= 0: + r_pos = 0 + if c_pos >= 443: + c_pos = 444 + if c_pos <= 0: + c_pos = 0 + if map_n > 247: + map_n = 247 + if map_n < -1: + map_n = -1 return r_pos, c_pos, map_n + def party(game): - party = [game.get_memory_value(addr) for addr in PARTY_ADDR] + # party = [game.get_memory_value(addr) for addr in PARTY_ADDR] party_size = game.get_memory_value(PARTY_SIZE_ADDR) party_levels = [game.get_memory_value(addr) for addr in PARTY_LEVEL_ADDR] - return party, party_size, party_levels + return party_size, party_levels # [x for x in party_levels if x > 0] + def opponent(game): return [game.get_memory_value(addr) for addr in OPPONENT_LEVEL_ADDR] + def oak_parcel(game): - return read_bit(game, OAK_PARCEL_ADDR, 1) + return read_bit(game, OAK_PARCEL_ADDR, 1) + def pokedex_obtained(game): return read_bit(game, OAK_POKEDEX_ADDR, 5) - + + def pokemon_seen(game): seen_bytes = [game.get_memory_value(addr) for addr in SEEN_POKE_ADDR] return sum([bit_count(b) for b in seen_bytes]) + def pokemon_caught(game): caught_bytes = [game.get_memory_value(addr) for addr in CAUGHT_POKE_ADDR] return sum([bit_count(b) for b in caught_bytes]) + +def hp(game): + """Percentage of total party HP""" + party_hp = [read_uint16(game, addr) for addr in HP_ADDR] + party_max_hp = [read_uint16(game, addr) for addr in MAX_HP_ADDR] + + # Avoid division by zero if no pokemon + sum_max_hp = sum(party_max_hp) + if sum_max_hp == 0: + return 1 + + return sum(party_hp) / sum_max_hp + + def money(game): - return (100 * 100 * bcd(game.get_memory_value(MONEY_ADDR_1)) + return ( + 100 * 100 * bcd(game.get_memory_value(MONEY_ADDR_1)) + 100 * bcd(game.get_memory_value(MONEY_ADDR_100)) - + bcd(game.get_memory_value(MONEY_ADDR_10000))) + + bcd(game.get_memory_value(MONEY_ADDR_10000)) + ) + def badges(game): badges = game.get_memory_value(BADGE_1_ADDR) return bit_count(badges) -# End TODO - -# ################################################################################################################## -# # Notes -# ################################################################################################################## - -## Misc - # 0xc4f2 check for EE hex for text box arrow is present - -## Menu Data - # Coordinates of the position of the cursor for the top menu item (id 0) - # CC24 : Y position - # CC25 : X position - # CC26 - Currently selected menu item (topmost is 0) - # CC27 - Tile "hidden" by the menu cursor - # CC28 - ID of the last menu item - # CC29 - bitmask applied to the key port for the current menu - # CC2A - ID of the previously selected menu item - # CC2B - Last position of the cursor on the party / Bill's PC screen - # CC2C - Last position of the cursor on the item screen - # CC2D - Last position of the cursor on the START / battle menu - # CC2F - Index (in party) of the Pokémon currently sent out - # CC30~CC31 - Pointer to cursor tile in C3A0 buffer - # CC36 - ID of the first displayed menu item - # CC35 - Item highlighted with Select (01 = first item, 00 = no item, etc.) - # CC3A and CC3B are unused - # cc51 and cc52 both read 00 when menu is closed - -## Pokémon Mart - # JPN addr. INT addr. Description - # CF62 CF7B Total Items - # CF63 CF7C Item 1 - # CF64 CF7D Item 2 - # CF65 CF7E Item 3 - # CF66 CF7F Item 4 - # CF67 CF80 Item 5 - # CF68 CF81 Item 6 - # CF69 CF82 Item 7 - # CF70 CF83 Item 8 - # CF71 CF84 Item 9 - # CF72 CF85 Item 10 - -## Event Flags - # D751 - Fought Giovanni Yet? - # D755 - Fought Brock Yet? - # D75E - Fought Misty Yet? - # D773 - Fought Lt. Surge Yet? - # D77C - Fought Erika Yet? - # D792 - Fought Koga Yet? - # D79A - Fought Blaine Yet? - # D7B3 - Fought Sabrina Yet? - # D782 - Fought Articuno Yet? - # D7D4 - Fought Zapdos Yet? - # D7EE - Fought Moltres Yet? - # D710 - Fossilized Pokémon? - # D7D8 - Fought Snorlax Yet (Vermilion) - # D7E0 - Fought Snorlax Yet? (Celadon) - # D803 - Is SS Anne here - # D5F3 - Have Town map? - # D60D - Have Oak's Parcel? - # D5A6 to D5C5 : Missable Objects Flags (flags for every (dis)appearing sprites, like the guard in Cerulean City or the Pokéballs in Oak's Lab) - # D5AB - Starters Back? - # D5C0(bit 1) - 0=Mewtwo appears, 1=Doesn't (See D85F) - # D700 - Bike Speed - # D70B - Fly Anywhere Byte 1 - # D70C - Fly Anywhere Byte 2 - # D70D - Safari Zone Time Byte 1 - # D70E - Safari Zone Time Byte 2 - # D714 - Position in Air - # D72E - Did you get Lapras Yet? - # D732 - Debug New Game - # D790 - If bit 7 is set, Safari Game over - # D85F - Mewtwo can be caught if bit 2 clear - Needs D5C0 bit 1 clear, too - -## Item IDs & String - # 1, 2, 3, 4, 6, 11, 16, 17, 18, 19, 20, 41, 42, 72, 73, 196, 197, 198, 199, 200, 53, 54 - # 001 0x01 Master Ball - # 002 0x02 Ultra Ball - # 003 0x03 Great Ball - # 004 0x04 Poké Ball - # 006 0x06 Bicycle - # 011 0x0B Antidote - # 016 0x10 Full Restore - # 017 0x11 Max Potion - # 018 0x12 Hyper Potion - # 019 0x13 Super Potion - # 020 0x14 Potion - # 041 0x29 Dome Fossil - # 042 0x2A Helix Fossil - # 072 0x48 Silph Scope - # 073 0x49 Poké Flute - # 196 0xC4 HM01 - # 197 0xC5 HM02 - # 198 0xC6 HM03 - # 199 0xC7 HM04 - # 200 0xC8 HM05 - # 053 0x35 Revive - # 054 0x36 Max Revive - -## Item Bag - # 0xD31D - Total Items - # 0xD31E - Item 1 - # 0xD320 - Item 2 - # 0xD322 - Item 3 - # 0xD324 - Item 4 - # 0xD326 - Item 5 - # 0xD328 - Item 6 - # 0xD32A - Item 7 - # 0xD32C - Item 8 - # 0xD32E - Item 9 - # 0xD330 - Item 10 - # 0xD332 - Item 11 - # 0xD334 - Item 12 - # 0xD336 - Item 13 - # 0xD338 - Item 14 - # 0xD33A - Item 15 - # 0xD33C - Item 16 - # 0xD33E - Item 17 - # 0xD340 - Item 18 - # 0xD342 - Item 19 - # 0xD344 - Item 20 - # 0xD346 - Item End of List \ No newline at end of file + + +def saved_bill(game): + """Restored Bill from his experiment""" + return int(read_bit(game, USED_CELL_SEPARATOR_ADDR, 3)) + +def ss_anne_appeared(game): + """ + D803 - True is SS Anne is here + """ + return game.get_memory_value(SS_ANNE) + + +def events(game): + """Adds up all event flags, exclude museum ticket""" + num_events = sum( + bit_count(game.get_memory_value(i)) + for i in range(EVENT_FLAGS_START_ADDR, EVENT_FLAGS_END_ADDR) + ) + museum_ticket = int(read_bit(game, MUSEUM_TICKET_ADDR, 0)) + + # Omit 13 events by default + return max(num_events - 13 - museum_ticket, 0) + +def talk_to_npc(game): + """ + Talk to NPC + 238 is text box arrow blink on + 127 is no text box arrow + """ + return game.get_memory_value(TEXT_BOX_ARROW_BLINK) + +def is_in_battle(game): + # D057 + # 0 not in battle + # 1 wild battle + # 2 trainer battle + # -1 lost battle + bflag = game.get_memory_value(BATTLE_FLAG) + if bflag > 0: + return True + else: + return False + +def if_font_is_loaded(game): + return game.get_memory_value(IF_FONT_IS_LOADED) + + # get information for player +def player_direction(game): + return game.get_memory_value(PLAYER_DIRECTION) + +def player_y(game): + return game.get_memory_value(PLAYER_Y) + +def player_x(game): + return game.get_memory_value(PLAYER_X) + +def map_n(game): + return game.get_memory_value(MAP_N_ADDR) + +def npc_y(game, npc_id, npc_bank): + npc_id = npc_id * 0x10 + npc_bank = (npc_bank + 1) * 0x100 + return game.get_memory_value(0xC004 + npc_id + npc_bank) + +def npc_x(game, npc_id, npc_bank): + npc_id = npc_id * 0x10 + npc_bank = (npc_bank + 1) * 0x100 + return game.get_memory_value(0xC006 + npc_id + npc_bank) + +def sprites(game): + return game.get_memory_value(WNUMSPRITES) + +def signs(game): + return game.get_memory_value(WNUMSIGNS) \ No newline at end of file From 0e116825a5c49db2574e918d222e6e9bf7a7b68f Mon Sep 17 00:00:00 2001 From: leanke Date: Mon, 29 Jan 2024 02:00:50 +0000 Subject: [PATCH 08/29] baseline teaches cut --- pokegym/environment.py | 226 ++++++++++++----------------------------- pokegym/ram_map.py | 76 ++++++++++---- 2 files changed, 126 insertions(+), 176 deletions(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index d2eaa7b..083f11a 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -1,11 +1,9 @@ -import csv from pathlib import Path from pdb import set_trace as T import types import uuid from gymnasium import Env, spaces import numpy as np -import pandas as pd from skimage.transform import resize from collections import defaultdict @@ -25,66 +23,16 @@ ) from pokegym import ram_map, game_map, data - -def play(): - """Creates an environment and plays it""" - env = Environment( - rom_path="pokemon_red.gb", - state_path=None, - headless=False, - disable_input=False, - sound=False, - sound_emulated=False, - verbose=True, - ) - - env.reset() - env.game.set_emulation_speed(0) - - # Display available actions - print("Available actions:") - for idx, action in enumerate(ACTIONS): - print(f"{idx}: {action}") - - # Create a mapping from WindowEvent to action index - window_event_to_action = { - "PRESS_ARROW_DOWN": 0, - "PRESS_ARROW_LEFT": 1, - "PRESS_ARROW_RIGHT": 2, - "PRESS_ARROW_UP": 3, - "PRESS_BUTTON_A": 4, - "PRESS_BUTTON_B": 5, - "PRESS_BUTTON_START": 6, - "PRESS_BUTTON_SELECT": 7, - # Add more mappings if necessary - } - - while True: - # Get input from pyboy's get_input method - input_events = env.game.get_input() - env.game.tick() - env.render() - if len(input_events) == 0: - continue - - for event in input_events: - event_str = str(event) - if event_str in window_event_to_action: - action_index = window_event_to_action[event_str] - observation, reward, done, _, info = env.step( - action_index, fast_video=False - ) - - # Check for game over - if done: - print(f"{done}") - break - - # Additional game logic or information display can go here - print(f"new Reward: {reward}\n") - STATE_PATH = __file__.rstrip("environment.py") + "States/" +def get_random_state(): + state_files = [f for f in os.listdir(STATE_PATH) if f.endswith(".state")] + if not state_files: + raise FileNotFoundError("No State files found in the specified directory.") + return random.choice(state_files) +state_file = get_random_state() +randstate = os.path.join(STATE_PATH, state_file) + class Base: def __init__( self, @@ -98,9 +46,8 @@ def __init__( """Creates a PokemonRed environment""" if state_path is None: state_path = STATE_PATH + "Bulbasaur.state" # STATE_PATH + "has_pokedex_nballs.state" - self.game, self.screen = make_env(rom_path, headless, quiet, save_video=False, **kwargs) - - + # Make the environment + self.game, self.screen = make_env(rom_path, headless, quiet, save_video=True, **kwargs) self.initial_states = [open_state_file(state_path)] self.save_video = save_video self.headless = headless @@ -113,9 +60,8 @@ def __init__( self.video_path.mkdir(parents=True, exist_ok=True) self.csv_path = Path(f'./csv') self.csv_path.mkdir(parents=True, exist_ok=True) - self.reset_count = 0 + self.reset_count = 0 self.explore_hidden_obj_weight = 1 - self.csv = True R, C = self.screen.raw_screen_buffer_dims() self.obs_size = (R // 2, C // 2) @@ -131,15 +77,19 @@ def __init__( low=0, high=255, dtype=np.uint8, shape=self.obs_size ) self.action_space = spaces.Discrete(len(ACTIONS)) + + def init_hidden_obj_mem(self): + self.seen_hidden_objs = set() def save_screenshot(self, event, map_n): self.screenshot_counter += 1 ss_dir = Path('screenshots') ss_dir.mkdir(exist_ok=True) plt.imsave( + # ss_dir / Path(f'ss_{x}_y_{y}_steps_{steps}_{comment}.jpeg'), ss_dir / Path(f'{self.screenshot_counter}_{event}_{map_n}.jpeg'), self.screen.screen_ndarray()) # (144, 160, 3) - + def save_state(self): state = io.BytesIO() state.seek(0) @@ -179,10 +129,13 @@ def get_fixed_window(self, arr, y, x, window_size): def render(self): if self.use_screen_memory: r, c, map_n = ram_map.position(self.game) - + # Update tile map mmap = self.screen_memory[map_n] if 0 <= r <= 254 and 0 <= c <= 254: mmap[r, c] = 255 + + # Downsamples the screen and retrieves a fixed window from mmap, + # then concatenates along the 3rd-dimensional axis (image channel) return np.concatenate( ( self.screen.screen_ndarray()[::2, ::2], @@ -203,14 +156,14 @@ def video(self): def close(self): self.game.stop(False) - + class Environment(Base): def __init__( self, rom_path="pokemon_red.gb", state_path=None, headless=True, - save_video=True, + save_video=False, quiet=False, verbose=False, **kwargs, @@ -218,14 +171,25 @@ def __init__( super().__init__(rom_path, state_path, headless, save_video, quiet, **kwargs) self.counts_map = np.zeros((444, 436)) self.verbose = verbose - + self.screenshot_counter = 0 self.include_conditions = [] + self.seen_maps_difference = set() + self.current_maps = [] + self.exclude_map_n = {37, 38, 39, 43, 52, 53, 55, 57} + self.exclude_map_n = set() + self.is_dead = False + self.talk_to_npc_reward = 0 self.talk_to_npc_count = {} + self.already_got_npc_reward = set() + self.ss_anne_state = False self.seen_npcs = set() self.explore_npc_weight = 1 + self.last_map = -1 + self.init_hidden_obj_mem() self.seen_pokemon = np.zeros(152, dtype=np.uint8) self.caught_pokemon = np.zeros(152, dtype=np.uint8) self.moves_obtained = np.zeros(0xA5, dtype=np.uint8) + self.log = True def update_pokedex(self): for i in range(0xD30A - 0xD2F7): @@ -233,17 +197,8 @@ def update_pokedex(self): seen_mem = self.game.get_memory_value(i + 0xD30A) for j in range(8): self.caught_pokemon[8*i + j] = 1 if caught_mem & (1 << j) else 0 - self.seen_pokemon[8*i + j] = 1 if seen_mem & (1 << j) else 0 - - def write_to_csv(self): - x, y ,map_n = ram_map.position(self.game) - reset = self.reset_count - env_id = self.env_id - csv_file_path = Path(f'./csv/agent_position.csv') - with open(csv_file_path, 'a') as csv_file: - csv_writer = csv.writer(csv_file) - csv_writer.writerow([env_id, reset, x, y, map_n]) - + self.seen_pokemon[8*i + j] = 1 if seen_mem & (1 << j) else 0 + def update_moves_obtained(self): # Scan party for i in [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247]: @@ -263,27 +218,6 @@ def update_moves_obtained(self): move_id = self.game.get_memory_value(offset + j + 8) if move_id != 0: self.moves_obtained[move_id] = 1 - - def get_items_in_bag(self, one_indexed=0): - first_item = 0xD31E - item_names = [] - for i in range(0, 20, 2): - item_id = self.game.get_memory_value(first_item + i) - if item_id == 0 or item_id == 0xff: - break - item_id_key = item_id + one_indexed - item_name = data.items_dict.get(item_id_key, {}).get('Item', f'Unknown Item {item_id_key}') - item_names.append(item_name) - return item_names - - def get_hm_rewards(self): - hm_ids = [0xC4, 0xC5, 0xC6, 0xC7, 0xC8] - items = self.get_items_in_bag() - total_hm_cnt = 0 - for hm_id in hm_ids: - if hm_id in items: - total_hm_cnt += 1 - return total_hm_cnt * 1 def add_video_frame(self): self.full_frame_writer.add_image(self.video()) @@ -320,7 +254,7 @@ def update_heat_map(self, r, c, current_map): # Update last_map for the next iteration self.last_map = current_map - + def find_neighboring_npc(self, npc_bank, npc_id, player_direction, player_x, player_y) -> int: npc_y = ram_map.npc_y(self.game, npc_id, npc_bank) @@ -335,7 +269,7 @@ def find_neighboring_npc(self, npc_bank, npc_id, player_direction, player_x, pla return abs(npc_y - player_y) + abs(npc_x - player_x) return 1000 - + def rewardable_coords(self, glob_c, glob_r): self.include_conditions = [ # (80 >= glob_c >= 72) and (294 < glob_r <= 320), @@ -422,9 +356,9 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 load_pyboy_state(self.game, self.load_last_state()) if self.save_video: - base_dir = self.video_path + base_dir = self.s_path base_dir.mkdir(parents=True, exist_ok=True) - full_name = Path(f'{self.env_id}_reset_{self.reset_count}').with_suffix('.mp4') + full_name = Path(f'reset_{self.reset_count}').with_suffix('.mp4') self.full_frame_writer = media.VideoWriter(base_dir / full_name, (144, 160), fps=60) self.full_frame_writer.__enter__() @@ -432,22 +366,17 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.screen_memory = defaultdict( lambda: np.zeros((255, 255, 1), dtype=np.uint8) ) - + self.time = 0 self.max_episode_steps = max_episode_steps self.reward_scale = reward_scale self.prev_map_n = None - #BET ADDED + self.init_hidden_obj_mem() self.max_events = 0 self.max_level_sum = 0 self.max_opponent_level = 0 - self.seen_hidden_objs = set() self.seen_coords = set() - self.seen_maps = set() - ########################################################### Moved from Environment_Init() - self.is_dead = False - self.last_map = -1 - #End + self.seen_maps = set() # np.zeros(256,dtype=np.uint8) self.death_count = 0 self.total_healing = 0 self.last_hp = 1.0 @@ -469,9 +398,6 @@ def step(self, action, fast_video=True): self.time += 1 if self.save_video: self.add_video_frame() - - if self.csv: - self.write_to_csv() # Exploration reward r, c, map_n = ram_map.position(self.game) @@ -495,19 +421,13 @@ def step(self, action, fast_video=True): self.seen_maps.add(map_n) self.talk_to_npc_count[map_n] = 0 # Initialize NPC talk count for this new map self.save_state() - self.update_pokedex() self.update_moves_obtained() - # obs = self.render() - ############################################################### Added map_n into explore reward - coord_reward = 0.01 * len(self.seen_coords) - map_reward = 0.1 * len(self.seen_maps) - exploration_reward = coord_reward + map_reward + exploration_reward = 0.01 * len(self.seen_coords) self.update_heat_map(r, c, map_n) - # Level reward party_size, party_levels = ram_map.party(self.game) self.max_level_sum = max(self.max_level_sum, sum(party_levels)) @@ -548,7 +468,7 @@ def step(self, action, fast_video=True): # Badge reward badges = ram_map.badges(self.game) badges_reward = 5 * badges - + # Save Bill bill_state = ram_map.saved_bill(self.game) bill_reward = 10 * bill_state @@ -559,9 +479,9 @@ def step(self, action, fast_video=True): ss_anne_state_reward = 5 else: ss_anne_state_reward = 0 - + # HM reward - hm_count = self.get_hm_rewards() + hm_count = ram_map.get_hm_count() hm_reward = hm_count * 5 # Event reward @@ -572,18 +492,23 @@ def step(self, action, fast_video=True): # Money money = ram_map.money(self.game) - + # Explore NPCs + # check if the font is loaded if ram_map.mem_val(self.game, 0xCFC4): # check if we are talking to a hidden object: if ram_map.mem_val(self.game, 0xCD3D) == 0x0 and ram_map.mem_val(self.game, 0xCD3E) == 0x0: # add hidden object to seen hidden objects self.seen_hidden_objs.add((ram_map.mem_val(self.game, 0xD35E), ram_map.mem_val(self.game, 0xCD3F))) else: - + # check if we are talking to someone + # if ram_map.if_font_is_loaded(self.game): + # get information for player player_direction = ram_map.player_direction(self.game) player_y = ram_map.player_y(self.game) player_x = ram_map.player_x(self.game) - + # get the npc who is closest to the player and facing them + # we go through all npcs because there are npcs like + # nurse joy who can be across a desk and still talk to you mindex = (0, 0) minv = 1000 for npc_bank in range(1): @@ -595,6 +520,9 @@ def step(self, action, fast_video=True): minv = npc_dist self.seen_npcs.add((ram_map.map_n(self.game), mindex[0], mindex[1])) + #change seen_npc to np.zeros(256*16, dtype=np.uint8) ^^ set seen_npc(map_n*16+mindex=1) to get length use np.sum() + #possibly do the same with hidden obj but some bs about not an even number so gg + explore_npcs_reward = self.reward_scale * self.explore_npc_weight * len(self.seen_npcs) * 0.00015 seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) * 0.00010 caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) * 0.00010 @@ -603,19 +531,19 @@ def step(self, action, fast_video=True): reward = self.reward_scale * ( event_reward - + explore_npcs_reward + + explore_npcs_reward # Doesn't reset on reset but maybe should? + seen_pokemon_reward + caught_pokemon_reward + moves_obtained_reward - + explore_hidden_objs_reward + + explore_hidden_objs_reward # Resets on reset + bill_reward + hm_reward + level_reward - # + opponent_level_reward - # + death_reward + + opponent_level_reward + + death_reward # Resets on reset + badges_reward - + healing_reward - + exploration_reward + + healing_reward # Resets each step + + exploration_reward # Resets on reset ) # Subtract previous reward @@ -635,7 +563,7 @@ def step(self, action, fast_video=True): if done: pokemon_info = data.pokemon_l(self.game) x, y ,map_n = ram_map.position(self.game) - items = self.get_items_in_bag() + items = ram_map.get_items_in_bag() reset = self.reset_count pokemon = [] for p in pokemon_info: @@ -666,7 +594,7 @@ def step(self, action, fast_video=True): "moves_obtained_reward": moves_obtained_reward, "hidden_obj_count_reward": explore_hidden_objs_reward, }, - "maps_explored": len(self.seen_maps), + "maps_explored": np.sum(self.seen_maps), "party_size": party_size, "highest_pokemon_level": max(party_levels), "total_party_level": sum(party_levels), @@ -686,25 +614,5 @@ def step(self, action, fast_video=True): "hidden_obj_count": len(self.seen_hidden_objs), "logging": pokemon, } - - if self.verbose: - print( - f'number of signs: {ram_map.signs(self.game)}, number of sprites: {ram_map.sprites(self.game)}\n', - f"steps: {self.time}\n", - f"seen_npcs #: {len(self.seen_npcs)}\n", - f"seen_npcs set: {self.seen_npcs}\n", - # f"is_in_battle: {ram_map.is_in_battle(self.game)}", - f"exploration reward: {exploration_reward}\n", - f"explore_npcs reward: {explore_npcs_reward}\n", - f"level_Reward: {level_reward}\n", - f"healing: {healing_reward}\n", - f"death: {death_reward}\n", - f"op_level: {opponent_level_reward}\n", - f"badges reward: {badges_reward}\n", - f"event reward: {event_reward}\n", - f"money: {money}\n", - f"ai reward: {reward}\n", - f"Info: {info}\n", - ) - return self.render(), reward, done, done, info \ No newline at end of file + return self.render(), reward, done, done, info diff --git a/pokegym/ram_map.py b/pokegym/ram_map.py index 9a941dd..ae89a86 100644 --- a/pokegym/ram_map.py +++ b/pokegym/ram_map.py @@ -1,5 +1,8 @@ # addresses from https://datacrystal.romhacking.net/wiki/Pok%C3%A9mon_Red/Blue:RAM_map # https://github.com/pret/pokered/blob/91dc3c9f9c8fd529bb6e8307b58b96efa0bec67e/constants/event_constants.asm +from pokegym import data + + HP_ADDR = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] MAX_HP_ADDR = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] PARTY_SIZE_ADDR = 0xD163 @@ -36,14 +39,26 @@ WNUMSPRITES = 0xD4E1 WNUMSIGNS = 0xD4B0 +# Moves 1-4 for Poke1, Poke2, Poke3, Poke4, Poke5, Poke6 +MOVE1 = [0xD173, 0xD19F, 0xD1CB, 0xD1F7, 0xD223, 0xD24F] +MOVE2 = [0xD174, 0xD1A0, 0xD1CC, 0xD1F8, 0xD224, 0xD250] +MOVE3 = [0xD175, 0xD1A1, 0xD1CD, 0xD1F9, 0xD225, 0xD251] +MOVE4 = [0xD176, 0xD1A2, 0xD1CE, 0xD1FA, 0xD226, 0xD252] +POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] # - Pokémon (Again) +STATUS = [0xD16F, 0xD19B, 0xD1C7, 0xD1F3, 0xD21F, 0xD24B] # - Status (Poisoned, Paralyzed, etc.) +TYPE1 = [0xD170, 0xD19C, 0xD1C8, 0xD1F4, 0xD220, 0xD24C] # - Type 1 +TYPE2 = [0xD171, 0xD19D, 0xD1C9, 0xD1F5, 0xD221, 0xD24D] # - Type 2 +LEVEL = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] # - Level (actual level) +MAXHP = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] # - Max HP if = 01 + 256 to MAXHP2 value +CHP = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] # - Current HP if = 01 + 256 + + def bcd(num): return 10 * ((num >> 4) & 0x0F) + (num & 0x0F) - def bit_count(bits): return bin(bits).count("1") - def read_bit(game, addr, bit) -> bool: # add padding so zero will read '0b100000000' instead of '0b0' return bin(256 + game.get_memory_value(addr))[-bit - 1] == "1" @@ -58,6 +73,47 @@ def read_uint16(game, start_addr): val_1 = game.get_memory_value(start_addr + 1) return 256 * val_256 + val_1 +def pokemon(game): + # Get memory values from the list POKE and LEVEL + memory_values = [game.get_memory_value(a) for a in POKE] + levels = [game.get_memory_value(a) for a in LEVEL] + + # Use memory values to get corresponding names from pokemon_data + names = [entry['name'] for entry in data.pokemon_data if entry.get('decimal') and int(entry['decimal']) in memory_values] + + # Create an initial dictionary with names as keys and levels as values + party_dict = dict(zip(names, levels)) + + return party_dict + +def update_pokemon_level(pokemon_dict, pokemon_name, new_level): + if pokemon_name in pokemon_dict: + # Update the level for the specified Pokémon + pokemon_dict[pokemon_name] = new_level + else: + # Add a new entry for the Pokémon + pokemon_dict[pokemon_name] = new_level + +def get_hm_count(game): + hm_ids = [0xC4, 0xC5, 0xC6, 0xC7, 0xC8] + items = get_items_in_bag() + total_hm_cnt = 0 + for hm_id in hm_ids: + if hm_id in items: + total_hm_cnt += 1 + return total_hm_cnt + +def get_items_in_bag(game, one_indexed=0): + first_item = 0xD31E + item_names = [] + for i in range(0, 20, 2): + item_id = game.get_memory_value(first_item + i) + if item_id == 0 or item_id == 0xff: + break + item_id_key = item_id + one_indexed + item_name = data.items_dict.get(item_id_key, {}).get('Item', f'Unknown Item {item_id_key}') + item_names.append(item_name) + return item_names def position(game): r_pos = game.get_memory_value(Y_POS_ADDR) @@ -77,49 +133,39 @@ def position(game): map_n = -1 return r_pos, c_pos, map_n - def party(game): # party = [game.get_memory_value(addr) for addr in PARTY_ADDR] party_size = game.get_memory_value(PARTY_SIZE_ADDR) - party_levels = [game.get_memory_value(addr) for addr in PARTY_LEVEL_ADDR] + party_levels = [x for x in [game.get_memory_value(addr) for addr in PARTY_LEVEL_ADDR] if x > 0] return party_size, party_levels # [x for x in party_levels if x > 0] - def opponent(game): return [game.get_memory_value(addr) for addr in OPPONENT_LEVEL_ADDR] - def oak_parcel(game): return read_bit(game, OAK_PARCEL_ADDR, 1) - def pokedex_obtained(game): return read_bit(game, OAK_POKEDEX_ADDR, 5) - def pokemon_seen(game): seen_bytes = [game.get_memory_value(addr) for addr in SEEN_POKE_ADDR] return sum([bit_count(b) for b in seen_bytes]) - def pokemon_caught(game): caught_bytes = [game.get_memory_value(addr) for addr in CAUGHT_POKE_ADDR] return sum([bit_count(b) for b in caught_bytes]) - def hp(game): """Percentage of total party HP""" party_hp = [read_uint16(game, addr) for addr in HP_ADDR] party_max_hp = [read_uint16(game, addr) for addr in MAX_HP_ADDR] - # Avoid division by zero if no pokemon sum_max_hp = sum(party_max_hp) if sum_max_hp == 0: return 1 - return sum(party_hp) / sum_max_hp - def money(game): return ( 100 * 100 * bcd(game.get_memory_value(MONEY_ADDR_1)) @@ -127,12 +173,10 @@ def money(game): + bcd(game.get_memory_value(MONEY_ADDR_10000)) ) - def badges(game): badges = game.get_memory_value(BADGE_1_ADDR) return bit_count(badges) - def saved_bill(game): """Restored Bill from his experiment""" return int(read_bit(game, USED_CELL_SEPARATOR_ADDR, 3)) @@ -143,7 +187,6 @@ def ss_anne_appeared(game): """ return game.get_memory_value(SS_ANNE) - def events(game): """Adds up all event flags, exclude museum ticket""" num_events = sum( @@ -178,7 +221,6 @@ def is_in_battle(game): def if_font_is_loaded(game): return game.get_memory_value(IF_FONT_IS_LOADED) - # get information for player def player_direction(game): return game.get_memory_value(PLAYER_DIRECTION) From f775073f421c0741e11741026cc5aa0f9411aef6 Mon Sep 17 00:00:00 2001 From: leanke Date: Thu, 1 Feb 2024 08:25:17 +0000 Subject: [PATCH 09/29] fixes --- pokegym/States/FlashCaveCenter.state | Bin 142610 -> 0 bytes pokegym/States/cut_no_cut.state | Bin 142610 -> 0 bytes pokegym/States/has_pokedex_nballs.state | Bin 142586 -> 0 bytes pokegym/States/mtmoon.state | Bin 142610 -> 0 bytes pokegym/data.py | 61 +++++++++++++++++++++++- pokegym/environment.py | 29 ++++++----- pokegym/ram_map.py | 2 +- 7 files changed, 75 insertions(+), 17 deletions(-) delete mode 100644 pokegym/States/FlashCaveCenter.state delete mode 100644 pokegym/States/cut_no_cut.state delete mode 100644 pokegym/States/has_pokedex_nballs.state delete mode 100644 pokegym/States/mtmoon.state diff --git a/pokegym/States/FlashCaveCenter.state b/pokegym/States/FlashCaveCenter.state deleted file mode 100644 index fabd6004735c43ee6d61755809359db52f1605ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142610 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 G_5VL_wa^9t diff --git a/pokegym/States/cut_no_cut.state b/pokegym/States/cut_no_cut.state deleted file mode 100644 index 799a224e6954d51bfe0eb6a646208ee8046c2097..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142610 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 diff --git a/pokegym/data.py b/pokegym/data.py index c7fca07..7420e0f 100644 --- a/pokegym/data.py +++ b/pokegym/data.py @@ -500,6 +500,65 @@ 255: {'decimal': 255, 'hex': '0xFF', 'Item': 'TM55'}, } +poke_dict = { + 3: {'hex': '3', 'decimal': '3', 'name': 'Nidoran♂'}, + 4: {'hex': '4', 'decimal': '4', 'name': 'Clefairy'}, + 5: {'hex': '5', 'decimal': '5', 'name': 'Spearow'}, + 9: {'hex': '9', 'decimal': '9', 'name': 'Ivysaur'}, + 15: {'hex': 'F', 'decimal': '15', 'name': 'Nidoran♀'}, + 16: {'hex': '10', 'decimal': '16', 'name': 'Nidoqueen'}, + 28: {'hex': '1C', 'decimal': '28', 'name': 'Blastoise'}, + 35: {'hex': '23', 'decimal': '35', 'name': 'Fearow'}, + 36: {'hex': '24', 'decimal': '36', 'name': 'Pidgey'}, + 38: {'hex': '26', 'decimal': '38', 'name': 'Kadabra'}, + 39: {'hex': '27', 'decimal': '39', 'name': 'Graveler'}, + 46: {'hex': '2E', 'decimal': '46', 'name': 'Parasect', 'type': 'Bug'}, + 48: {'hex': '30', 'decimal': '48', 'name': 'Drowzee'}, + 49: {'hex': '31', 'decimal': '49', 'name': 'Golem'}, + 57: {'hex': '39', 'decimal': '57', 'name': 'Mankey'}, + 59: {'hex': '3B', 'decimal': '59', 'name': 'Diglett'}, + 70: {'hex': '46', 'decimal': '70', 'name': 'Doduo'}, + 84: {'hex': '54', 'decimal': '84', 'name': 'Pikachu', 'type': 'Electric'}, + 85: {'hex': '55', 'decimal': '85', 'name': 'Raichu', 'type': 'Electric'}, + 100: {'hex': '64', 'decimal': '100', 'name': 'Jigglypuff'}, + 101: {'hex': '65', 'decimal': '101', 'name': 'Wigglytuff'}, + 107: {'hex': '6B', 'decimal': '107', 'name': 'Zubat'}, + 108: {'hex': '6C', 'decimal': '108', 'name': 'Ekans'}, + 109: {'hex': '6D', 'decimal': '109', 'name': 'Paras', 'type': 'Bug'}, + 112: {'hex': '70', 'decimal': '112', 'name': 'Weedle', 'type': 'Bug'}, + 113: {'hex': '71', 'decimal': '113', 'name': 'Kakuna', 'type': 'Bug'}, + 114: {'hex': '72', 'decimal': '114', 'name': 'Beedrill', 'type': 'Bug'}, + 116: {'hex': '74', 'decimal': '116', 'name': 'Dodrio'}, + 117: {'hex': '75', 'decimal': '117', 'name': 'Primeape'}, + 118: {'hex': '76', 'decimal': '118', 'name': 'Dugtrio'}, + 123: {'hex': '7B', 'decimal': '123', 'name': 'Caterpie', 'type': 'Bug'}, + 124: {'hex': '7C', 'decimal': '124', 'name': 'Metapod', 'type': 'Bug'}, + 125: {'hex': '7D', 'decimal': '125', 'name': 'Butterfree', 'type': 'Bug'}, + 129: {'hex': '81', 'decimal': '129', 'name': 'Hypno'}, + 130: {'hex': '82', 'decimal': '130', 'name': 'Golbat'}, + 133: {'hex': '85', 'decimal': '133', 'name': 'Magikarp'}, + 142: {'hex': '8E', 'decimal': '142', 'name': 'Clefable'}, + 148: {'hex': '94', 'decimal': '148', 'name': 'Abra'}, + 149: {'hex': '95', 'decimal': '149', 'name': 'Alakazam'}, + 150: {'hex': '96', 'decimal': '150', 'name': 'Pidgeotto'}, + 151: {'hex': '97', 'decimal': '151', 'name': 'Pidgeot'}, + 153: {'hex': '99', 'decimal': '153', 'name': 'Bulbasaur'}, + 154: {'hex': '9A', 'decimal': '154', 'name': 'Venusaur'}, + 165: {'hex': 'A5', 'decimal': '165', 'name': 'Rattata'}, + 166: {'hex': 'A6', 'decimal': '166', 'name': 'Raticate'}, + 167: {'hex': 'A7', 'decimal': '167', 'name': 'Nidorino'}, + 168: {'hex': 'A8', 'decimal': '168', 'name': 'Nidorina'}, + 169: {'hex': 'A9', 'decimal': '169', 'name': 'Geodude'}, + 176: {'hex': 'B0', 'decimal': '176', 'name': 'Charmander'}, + 177: {'hex': 'B1', 'decimal': '177', 'name': 'Squirtle'}, + 178: {'hex': 'B2', 'decimal': '178', 'name': 'Charmeleon'}, + 179: {'hex': 'B3', 'decimal': '179', 'name': 'Wartortle'}, + 180: {'hex': 'B4', 'decimal': '180', 'name': 'Charizard'}, + 185: {'hex': 'B9', 'decimal': '185', 'name': 'Oddish'}, + 186: {'hex': 'BA', 'decimal': '186', 'name': 'Gloom'}, + 187: {'hex': 'BB', 'decimal': '187', 'name': 'Vileplume'} +} + POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] # - Pokémon (Again) STATUS = [0xD16F, 0xD19B, 0xD1C7, 0xD1F3, 0xD21F, 0xD24B] # - Status (Poisoned, Paralyzed, etc.) TYPE1 = [0xD170, 0xD19C, 0xD1C8, 0xD1F4, 0xD220, 0xD24C] # - Type 1 @@ -530,4 +589,4 @@ def pokemon_l(game): move_info = moves_dict.get(move_value, {}) move_name = move_info.get("Move", "") pokemon_info[i]["moves"].append(move_name) - return pokemon_info \ No newline at end of file + return pokemon_info diff --git a/pokegym/environment.py b/pokegym/environment.py index 083f11a..6b3c52d 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -23,6 +23,7 @@ ) from pokegym import ram_map, game_map, data + STATE_PATH = __file__.rstrip("environment.py") + "States/" def get_random_state(): @@ -43,9 +44,11 @@ def __init__( quiet=False, **kwargs, ): + self.state_file = get_random_state() + self.randstate = os.path.join(STATE_PATH, self.state_file) """Creates a PokemonRed environment""" if state_path is None: - state_path = STATE_PATH + "Bulbasaur.state" # STATE_PATH + "has_pokedex_nballs.state" + state_path = self.randstate # STATE_PATH + "has_pokedex_nballs.state" # Make the environment self.game, self.screen = make_env(rom_path, headless, quiet, save_video=True, **kwargs) self.initial_states = [open_state_file(state_path)] @@ -422,9 +425,8 @@ def step(self, action, fast_video=True): self.talk_to_npc_count[map_n] = 0 # Initialize NPC talk count for this new map self.save_state() - self.update_pokedex() - self.update_moves_obtained() - + # map_reward = 0.1 * len(self.seen_maps) + # coord_reward = 0.01 len(self.seen_coords) exploration_reward = 0.01 * len(self.seen_coords) self.update_heat_map(r, c, map_n) @@ -440,24 +442,16 @@ def step(self, action, fast_video=True): hp = ram_map.hp(self.game) hp_delta = hp - self.last_hp party_size_constant = party_size == self.last_party_size - - # Only reward if not reviving at pokecenter if hp_delta > 0 and party_size_constant and not self.is_dead: self.total_healing += hp_delta - - # Dead if hp is zero if hp <= 0 and self.last_hp > 0: self.death_count += 1 self.is_dead = True elif hp > 0.01: # TODO: Check if this matters self.is_dead = False - - # Update last known values for next iteration self.last_hp = hp self.last_party_size = party_size death_reward = 0 # -0.08 * self.death_count # -0.05 - - # Set rewards healing_reward = self.total_healing # Opponent level reward @@ -481,7 +475,7 @@ def step(self, action, fast_video=True): ss_anne_state_reward = 0 # HM reward - hm_count = ram_map.get_hm_count() + hm_count = ram_map.get_hm_count(self.game) hm_reward = hm_count * 5 # Event reward @@ -491,7 +485,12 @@ def step(self, action, fast_video=True): # Money money = ram_map.money(self.game) - + + # Misc + self.update_pokedex() + self.update_moves_obtained() + + # Explore NPCs # check if the font is loaded if ram_map.mem_val(self.game, 0xCFC4): @@ -563,7 +562,7 @@ def step(self, action, fast_video=True): if done: pokemon_info = data.pokemon_l(self.game) x, y ,map_n = ram_map.position(self.game) - items = ram_map.get_items_in_bag() + items = ram_map.get_items_in_bag(self.game) reset = self.reset_count pokemon = [] for p in pokemon_info: diff --git a/pokegym/ram_map.py b/pokegym/ram_map.py index ae89a86..e1ca400 100644 --- a/pokegym/ram_map.py +++ b/pokegym/ram_map.py @@ -96,7 +96,7 @@ def update_pokemon_level(pokemon_dict, pokemon_name, new_level): def get_hm_count(game): hm_ids = [0xC4, 0xC5, 0xC6, 0xC7, 0xC8] - items = get_items_in_bag() + items = get_items_in_bag(game) total_hm_cnt = 0 for hm_id in hm_ids: if hm_id in items: From 102d1062af268a43768fc7d9c451aacbabc5d0e4 Mon Sep 17 00:00:00 2001 From: leanke Date: Thu, 7 Mar 2024 09:46:03 +0000 Subject: [PATCH 10/29] menu_cut --- pokegym/environment.py | 398 ++++++++++++++++++++--------------------- pokegym/ram_map.py | 155 +++++++++++++++- 2 files changed, 341 insertions(+), 212 deletions(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index 6b3c52d..9f6c0f0 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -6,9 +6,10 @@ import numpy as np from skimage.transform import resize -from collections import defaultdict +from collections import defaultdict, deque import io, os import random +from pyboy.utils import WindowEvent import matplotlib.pyplot as plt from pathlib import Path @@ -25,6 +26,14 @@ STATE_PATH = __file__.rstrip("environment.py") + "States/" +CUT_SEQ = [ + ((0x3D, 1, 1, 0, 4, 1), (0x3D, 1, 1, 0, 1, 1)), + ((0x50, 1, 1, 0, 4, 1), (0x50, 1, 1, 0, 1, 1)), +] + +CUT_GRASS_SEQ = deque([(0x52, 255, 1, 0, 1, 1), (0x52, 255, 1, 0, 1, 1), (0x52, 1, 1, 0, 1, 1)]) +CUT_FAIL_SEQ = deque([(-1, 255, 0, 0, 4, 1), (-1, 255, 0, 0, 1, 1), (-1, 255, 0, 0, 1, 1)]) + def get_random_state(): state_files = [f for f in os.listdir(STATE_PATH) if f.endswith(".state")] @@ -48,7 +57,7 @@ def __init__( self.randstate = os.path.join(STATE_PATH, self.state_file) """Creates a PokemonRed environment""" if state_path is None: - state_path = self.randstate # STATE_PATH + "has_pokedex_nballs.state" + state_path = STATE_PATH + "Bulbasaur.state" # STATE_PATH + "has_pokedex_nballs.state" # Make the environment self.game, self.screen = make_env(rom_path, headless, quiet, save_video=True, **kwargs) self.initial_states = [open_state_file(state_path)] @@ -67,7 +76,7 @@ def __init__( self.explore_hidden_obj_weight = 1 R, C = self.screen.raw_screen_buffer_dims() - self.obs_size = (R // 2, C // 2) + self.obs_size = (R // 2, C // 2) # 72, 80, 3 if self.use_screen_memory: self.screen_memory = defaultdict( @@ -80,9 +89,6 @@ def __init__( low=0, high=255, dtype=np.uint8, shape=self.obs_size ) self.action_space = spaces.Discrete(len(ACTIONS)) - - def init_hidden_obj_mem(self): - self.seen_hidden_objs = set() def save_screenshot(self, event, map_n): self.screenshot_counter += 1 @@ -101,6 +107,13 @@ def save_state(self): def load_last_state(self): return self.initial_states[len(self.initial_states) - 1] + + def load_first_state(self): + return self.initial_states[0] + + def load_random_state(self): + rand_idx = random.randint(0, len(self.initial_states) - 1) + return self.initial_states[rand_idx] def reset(self, seed=None, options=None): """Resets the game. Seeding is NOT supported""" @@ -173,6 +186,7 @@ def __init__( ): super().__init__(rom_path, state_path, headless, save_video, quiet, **kwargs) self.counts_map = np.zeros((444, 436)) + self.death_count = 0 self.verbose = verbose self.screenshot_counter = 0 self.include_conditions = [] @@ -182,17 +196,16 @@ def __init__( self.exclude_map_n = set() self.is_dead = False self.talk_to_npc_reward = 0 - self.talk_to_npc_count = {} self.already_got_npc_reward = set() self.ss_anne_state = False self.seen_npcs = set() self.explore_npc_weight = 1 self.last_map = -1 - self.init_hidden_obj_mem() self.seen_pokemon = np.zeros(152, dtype=np.uint8) self.caught_pokemon = np.zeros(152, dtype=np.uint8) self.moves_obtained = np.zeros(0xA5, dtype=np.uint8) self.log = True + # self.seen_coords = set() ## moved from reset def update_pokedex(self): for i in range(0xD30A - 0xD2F7): @@ -211,6 +224,8 @@ def update_moves_obtained(self): if move_id != 0: if move_id != 0: self.moves_obtained[move_id] = 1 + if move_id == 15: + self.cut = 1 # Scan current box (since the box doesn't auto increment in pokemon red) num_moves = 4 box_struct_length = 25 * num_moves * 2 @@ -224,6 +239,54 @@ def update_moves_obtained(self): def add_video_frame(self): self.full_frame_writer.add_image(self.video()) + + def get_game_coords(self): + return (ram_map.mem_val(self.game, 0xD362), ram_map.mem_val(self.game, 0xD361), ram_map.mem_val(self.game, 0xD35E)) + + def check_if_in_start_menu(self) -> bool: + return ( + ram_map.mem_val(self.game, 0xD057) == 0 + and ram_map.mem_val(self.game, 0xCF13) == 0 + and ram_map.mem_val(self.game, 0xFF8C) == 6 + and ram_map.mem_val(self.game, 0xCF94) == 0 + ) + + def check_if_in_pokemon_menu(self) -> bool: + return ( + ram_map.mem_val(self.game, 0xD057) == 0 + and ram_map.mem_val(self.game, 0xCF13) == 0 + and ram_map.mem_val(self.game, 0xFF8C) == 6 + and ram_map.mem_val(self.game, 0xCF94) == 2 + ) + + def check_if_in_stats_menu(self) -> bool: + return ( + ram_map.mem_val(self.game, 0xD057) == 0 + and ram_map.mem_val(self.game, 0xCF13) == 0 + and ram_map.mem_val(self.game, 0xFF8C) == 6 + and ram_map.mem_val(self.game, 0xCF94) == 1 + ) + + def check_if_in_bag_menu(self) -> bool: + return ( + ram_map.mem_val(self.game, 0xD057) == 0 + and ram_map.mem_val(self.game, 0xCF13) == 0 + # and ram_map.mem_val(self.game, 0xFF8C) == 6 # only sometimes + and ram_map.mem_val(self.game, 0xCF94) == 3 + ) + + def check_if_cancel_bag_menu(self, action) -> bool: + return ( + action == WindowEvent.PRESS_BUTTON_A + and ram_map.mem_val(self.game, 0xD057) == 0 + and ram_map.mem_val(self.game, 0xCF13) == 0 + # and ram_map.mem_val(self.game, 0xFF8C) == 6 + and ram_map.mem_val(self.game, 0xCF94) == 3 + and ram_map.mem_val(self.game, 0xD31D) == ram_map.mem_val(self.game, 0xCC36) + ram_map.mem_val(self.game, 0xCC26) + ) + + def check_if_in_overworld(self) -> bool: + return ram_map.mem_val(self.game, 0xD057) == 0 and ram_map.mem_val(self.game, 0xCF13) == 0 and ram_map.mem_val(self.game, 0xFF8C) == 0 def update_heat_map(self, r, c, current_map): ''' @@ -272,90 +335,12 @@ def find_neighboring_npc(self, npc_bank, npc_id, player_direction, player_x, pla return abs(npc_y - player_y) + abs(npc_x - player_x) return 1000 - - def rewardable_coords(self, glob_c, glob_r): - self.include_conditions = [ - # (80 >= glob_c >= 72) and (294 < glob_r <= 320), - # (69 < glob_c < 74) and (313 >= glob_r >= 295), - # (73 >= glob_c >= 72) and (220 <= glob_r <= 330), - # (75 >= glob_c >= 74) and (310 >= glob_r <= 319), - # # (glob_c >= 75 and glob_r <= 310), - # (81 >= glob_c >= 73) and (294 < glob_r <= 313), - # (73 <= glob_c <= 81) and (294 < glob_r <= 308), - # (80 >= glob_c >= 74) and (330 >= glob_r >= 284), - # (90 >= glob_c >= 89) and (336 >= glob_r >= 328), - # # New below - # # Viridian Pokemon Center - # (282 >= glob_r >= 277) and glob_c == 98, - # # Pewter Pokemon Center - # (173 <= glob_r <= 178) and glob_c == 42, - # # Route 4 Pokemon Center - # (131 <= glob_r <= 136) and glob_c == 132, - # (75 <= glob_c <= 76) and (271 < glob_r < 273), - # (82 >= glob_c >= 74) and (284 <= glob_r <= 302), - # (74 <= glob_c <= 76) and (284 >= glob_r >= 277), - # (76 >= glob_c >= 70) and (266 <= glob_r <= 277), - # (76 <= glob_c <= 78) and (274 >= glob_r >= 272), - # (74 >= glob_c >= 71) and (218 <= glob_r <= 266), - # (71 >= glob_c >= 67) and (218 <= glob_r <= 235), - # (106 >= glob_c >= 103) and (228 <= glob_r <= 244), - # (116 >= glob_c >= 106) and (228 <= glob_r <= 232), - # (116 >= glob_c >= 113) and (196 <= glob_r <= 232), - # (113 >= glob_c >= 89) and (208 >= glob_r >= 196), - # (97 >= glob_c >= 89) and (188 <= glob_r <= 214), - # (102 >= glob_c >= 97) and (189 <= glob_r <= 196), - # (89 <= glob_c <= 91) and (188 >= glob_r >= 181), - # (74 >= glob_c >= 67) and (164 <= glob_r <= 184), - # (68 >= glob_c >= 67) and (186 >= glob_r >= 184), - # (64 <= glob_c <= 71) and (151 <= glob_r <= 159), - # (71 <= glob_c <= 73) and (151 <= glob_r <= 156), - # (73 <= glob_c <= 74) and (151 <= glob_r <= 164), - # (103 <= glob_c <= 74) and (157 <= glob_r <= 156), - # (80 <= glob_c <= 111) and (155 <= glob_r <= 156), - # (111 <= glob_c <= 99) and (155 <= glob_r <= 150), - # (111 <= glob_c <= 154) and (150 <= glob_r <= 153), - # (138 <= glob_c <= 154) and (153 <= glob_r <= 160), - # (153 <= glob_c <= 154) and (153 <= glob_r <= 154), - # (143 <= glob_c <= 144) and (153 <= glob_r <= 154), - # (154 <= glob_c <= 158) and (134 <= glob_r <= 145), - # (152 <= glob_c <= 156) and (145 <= glob_r <= 150), - # (42 <= glob_c <= 43) and (173 <= glob_r <= 178), - # (158 <= glob_c <= 163) and (134 <= glob_r <= 135), - # (161 <= glob_c <= 163) and (114 <= glob_r <= 128), - # (163 <= glob_c <= 169) and (114 <= glob_r <= 115), - # (114 <= glob_c <= 169) and (167 <= glob_r <= 102), - # (169 <= glob_c <= 179) and (102 <= glob_r <= 103), - # (178 <= glob_c <= 179) and (102 <= glob_r <= 95), - # (178 <= glob_c <= 163) and (95 <= glob_r <= 96), - # (164 <= glob_c <= 163) and (110 <= glob_r <= 96), - # (163 <= glob_c <= 151) and (110 <= glob_r <= 109), - # (151 <= glob_c <= 154) and (101 <= glob_r <= 109), - # (151 <= glob_c <= 152) and (101 <= glob_r <= 97), - # (153 <= glob_c <= 154) and (97 <= glob_r <= 101), - # (151 <= glob_c <= 154) and (97 <= glob_r <= 98), - # (152 <= glob_c <= 155) and (69 <= glob_r <= 81), - # (155 <= glob_c <= 169) and (80 <= glob_r <= 81), - # (168 <= glob_c <= 184) and (39 <= glob_r <= 43), - # (183 <= glob_c <= 178) and (43 <= glob_r <= 51), - # (179 <= glob_c <= 183) and (48 <= glob_r <= 59), - # (179 <= glob_c <= 158) and (59 <= glob_r <= 57), - # (158 <= glob_c <= 161) and (57 <= glob_r <= 30), - # (158 <= glob_c <= 150) and (30 <= glob_r <= 31), - # (153 <= glob_c <= 150) and (34 <= glob_r <= 31), - # (168 <= glob_c <= 254) and (134 <= glob_r <= 140), - # (282 >= glob_r >= 277) and (436 >= glob_c >= 0), # Include Viridian Pokecenter everywhere - # (173 <= glob_r <= 178) and (436 >= glob_c >= 0), # Include Pewter Pokecenter everywhere - # (131 <= glob_r <= 136) and (436 >= glob_c >= 0), # Include Route 4 Pokecenter everywhere - # (137 <= glob_c <= 197) and (82 <= glob_r <= 142), # Mt Moon Route 3 - # (137 <= glob_c <= 187) and (53 <= glob_r <= 103), # Mt Moon B1F - # (137 <= glob_c <= 197) and (16 <= glob_r <= 66), # Mt Moon B2F - # (137 <= glob_c <= 436) and (82 <= glob_r <= 444), # Most of the rest of map after Mt Moon - (0 <= glob_c <= 436) and (0 <= glob_r <= 444), # Whole map included - ] - return any(self.include_conditions) def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" + # if self.reset_count % 5 == 0: + # load_pyboy_state(self.game, self.load_first_state()) + # else: load_pyboy_state(self.game, self.load_last_state()) if self.save_video: @@ -370,73 +355,62 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 lambda: np.zeros((255, 255, 1), dtype=np.uint8) ) + self.reset_count += 1 self.time = 0 self.max_episode_steps = max_episode_steps self.reward_scale = reward_scale self.prev_map_n = None - self.init_hidden_obj_mem() self.max_events = 0 self.max_level_sum = 0 self.max_opponent_level = 0 + # if self.reset_count % 5 == 0: ## resets every 5 to 0 moved seen_coords to init self.seen_coords = set() - self.seen_maps = set() # np.zeros(256,dtype=np.uint8) - self.death_count = 0 + self.seen_maps = set() self.total_healing = 0 self.last_hp = 1.0 self.last_party_size = 1 self.last_reward = None self.seen_coords_no_reward = set() - self.reset_count += 1 + self.hm_count = 0 + self.cut = 0 + + self.cut_coords = {} + self.cut_tiles = set([]) + self.cut_state = deque(maxlen=3) + + self.seen_start_menu = 0 + self.seen_pokemon_menu = 0 + self.seen_stats_menu = 0 + self.seen_bag_menu = 0 + self.seen_cancel_bag_menu = 0 return self.render(), {} def step(self, action, fast_video=True): - run_action_on_emulator( - self.game, - self.screen, - ACTIONS[action], - self.headless, - fast_video=fast_video, - ) + run_action_on_emulator(self.game, self.screen, ACTIONS[action], self.headless, fast_video=fast_video,) self.time += 1 + if self.save_video: self.add_video_frame() # Exploration reward r, c, map_n = ram_map.position(self.game) - # Convert local position to global position - try: - glob_r, glob_c = game_map.local_to_global(r, c, map_n) - except IndexError: - print(f'IndexError: index {glob_r} or {glob_c} is out of bounds for axis 0 with size 444.') - glob_r = 0 - glob_c = 0 - - # Only reward for specified coordinates, not all coordinates seen - if self.rewardable_coords(glob_c, glob_r): - self.seen_coords.add((r, c, map_n)) - else: - self.seen_coords_no_reward.add((glob_c, glob_r, map_n)) + self.seen_coords.add((r, c, map_n)) + exploration_reward = 0.01 * len(self.seen_coords) if map_n != self.prev_map_n: self.prev_map_n = map_n if map_n not in self.seen_maps: self.seen_maps.add(map_n) - self.talk_to_npc_count[map_n] = 0 # Initialize NPC talk count for this new map self.save_state() - - # map_reward = 0.1 * len(self.seen_maps) - # coord_reward = 0.01 len(self.seen_coords) - exploration_reward = 0.01 * len(self.seen_coords) - self.update_heat_map(r, c, map_n) # Level reward party_size, party_levels = ram_map.party(self.game) self.max_level_sum = max(self.max_level_sum, sum(party_levels)) if self.max_level_sum < 30: - level_reward = 1 * self.max_level_sum + level_reward = .5 * self.max_level_sum else: - level_reward = 30 + (self.max_level_sum - 30) / 4 + level_reward = 15 + (self.max_level_sum - 30) / 4 # Healing and death rewards hp = ram_map.hp(self.game) @@ -454,95 +428,120 @@ def step(self, action, fast_video=True): death_reward = 0 # -0.08 * self.death_count # -0.05 healing_reward = self.total_healing - # Opponent level reward - max_opponent_level = max(ram_map.opponent(self.game)) - self.max_opponent_level = max(self.max_opponent_level, max_opponent_level) - opponent_level_reward = 0 # 0.2 * self.max_opponent_level - # Badge reward badges = ram_map.badges(self.game) badges_reward = 5 * badges # Save Bill bill_state = ram_map.saved_bill(self.game) - bill_reward = 10 * bill_state + bill_reward = 5 * bill_state - # SS Anne appeared - ss_anne_state = ram_map.ss_anne_appeared(self.game) - if ss_anne_state: - ss_anne_state_reward = 5 - else: - ss_anne_state_reward = 0 - # HM reward hm_count = ram_map.get_hm_count(self.game) + if hm_count >= 1 and self.hm_count == 0: + self.save_state() + self.hm_count = 1 hm_reward = hm_count * 5 + cut_rew = self.cut * 5 # Event reward events = ram_map.events(self.game) self.max_events = max(self.max_events, events) event_reward = self.max_events - # Money - money = ram_map.money(self.game) + # #Item Reward + # items = ram_map.get_items_in_bag(self.game) + # item_reward = len(items) + + + # Cut check + # 0xCFC6 - wTileInFrontOfPlayer + # 0xCFCB - wUpdateSpritesEnabled + if ram_map.mem_val(self.game, 0xD057) == 0: # is_in_battle if 1 + if self.cut == 1: + player_direction = self.game.get_memory_value(0xC109) + x, y, map_id = self.get_game_coords() # x, y, map_id + if player_direction == 0: # down + coords = (x, y + 1, map_id) + if player_direction == 4: + coords = (x, y - 1, map_id) + if player_direction == 8: + coords = (x - 1, y, map_id) + if player_direction == 0xC: + coords = (x + 1, y, map_id) + self.cut_state.append( + ( + self.game.get_memory_value(0xCFC6), + self.game.get_memory_value(0xCFCB), + self.game.get_memory_value(0xCD6A), + self.game.get_memory_value(0xD367), + self.game.get_memory_value(0xD125), + self.game.get_memory_value(0xCD3D), + ) + ) + if tuple(list(self.cut_state)[1:]) in CUT_SEQ: + self.cut_coords[coords] = 10 + self.cut_tiles[self.cut_state[-1][0]] = 1 + elif self.cut_state == CUT_GRASS_SEQ: + self.cut_coords[coords] = 0.01 + self.cut_tiles[self.cut_state[-1][0]] = 1 + elif deque([(-1, *elem[1:]) for elem in self.cut_state]) == CUT_FAIL_SEQ: + self.cut_coords[coords] = 0.01 + self.cut_tiles[self.cut_state[-1][0]] = 1 + + + if int(ram_map.read_bit(self.game, 0xD803, 0)): + if self.check_if_in_start_menu(): + self.seen_start_menu = 1 + + if self.check_if_in_pokemon_menu(): + self.seen_pokemon_menu = 1 + + if self.check_if_in_stats_menu(): + self.seen_stats_menu = 1 + + if self.check_if_in_bag_menu(): + self.seen_bag_menu = 1 + + if self.check_if_cancel_bag_menu(action): + self.seen_cancel_bag_menu = 1 + # Misc self.update_pokedex() self.update_moves_obtained() + bill_capt_rew = ram_map.bill_capt(self.game) + + start_menu = self.seen_start_menu * 0.01 + pokemon_menu = self.seen_pokemon_menu * 0.1 + stats_menu = self.seen_stats_menu * 0.1 + bag_menu = self.seen_bag_menu * 0.1 + # "cancel_bag_menu": self.seen_cancel_bag_menu * 0.1, + cut_coords = sum(self.cut_coords.values()) * 1.0 + cut_tiles = len(self.cut_tiles) * 1.0 + that_guy = (start_menu + pokemon_menu + stats_menu + bag_menu + cut_coords + cut_tiles) + - # Explore NPCs - # check if the font is loaded - if ram_map.mem_val(self.game, 0xCFC4): - # check if we are talking to a hidden object: - if ram_map.mem_val(self.game, 0xCD3D) == 0x0 and ram_map.mem_val(self.game, 0xCD3E) == 0x0: - # add hidden object to seen hidden objects - self.seen_hidden_objs.add((ram_map.mem_val(self.game, 0xD35E), ram_map.mem_val(self.game, 0xCD3F))) - else: - # check if we are talking to someone - # if ram_map.if_font_is_loaded(self.game): - # get information for player - player_direction = ram_map.player_direction(self.game) - player_y = ram_map.player_y(self.game) - player_x = ram_map.player_x(self.game) - # get the npc who is closest to the player and facing them - # we go through all npcs because there are npcs like - # nurse joy who can be across a desk and still talk to you - mindex = (0, 0) - minv = 1000 - for npc_bank in range(1): - - for npc_id in range(1, ram_map.sprites(self.game) + 15): - npc_dist = self.find_neighboring_npc(npc_bank, npc_id, player_direction, player_x, player_y) - if npc_dist < minv: - mindex = (npc_bank, npc_id) - minv = npc_dist - self.seen_npcs.add((ram_map.map_n(self.game), mindex[0], mindex[1])) - - #change seen_npc to np.zeros(256*16, dtype=np.uint8) ^^ set seen_npc(map_n*16+mindex=1) to get length use np.sum() - #possibly do the same with hidden obj but some bs about not an even number so gg - - explore_npcs_reward = self.reward_scale * self.explore_npc_weight * len(self.seen_npcs) * 0.00015 seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) * 0.00010 caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) * 0.00010 moves_obtained_reward = self.reward_scale * sum(self.moves_obtained) * 0.00010 - explore_hidden_objs_reward = self.reward_scale * self.explore_hidden_obj_weight * len(self.seen_hidden_objs) * 0.00015 reward = self.reward_scale * ( event_reward - + explore_npcs_reward # Doesn't reset on reset but maybe should? + + bill_capt_rew + seen_pokemon_reward + caught_pokemon_reward + moves_obtained_reward - + explore_hidden_objs_reward # Resets on reset + bill_reward + hm_reward + level_reward - + opponent_level_reward - + death_reward # Resets on reset + + death_reward + badges_reward - + healing_reward # Resets each step - + exploration_reward # Resets on reset + + healing_reward + + exploration_reward + + cut_rew + + that_guy ) # Subtract previous reward @@ -560,58 +559,41 @@ def step(self, action, fast_video=True): if self.save_video and done: self.full_frame_writer.close() if done: - pokemon_info = data.pokemon_l(self.game) - x, y ,map_n = ram_map.position(self.game) - items = ram_map.get_items_in_bag(self.game) - reset = self.reset_count - pokemon = [] - for p in pokemon_info: - pokemon.append({ - 'env_id': self.env_id, - 'slot': p['slot'], - 'name': p['name'], - 'level': p['level'], - 'moves': p['moves'], - 'items': items, - }) info = { "reward": { "delta": reward, "event": event_reward, "level": level_reward, - "opponent_level": opponent_level_reward, - "death": death_reward, "badges": badges_reward, "bill_saved_reward": bill_reward, - "hm_count_award": hm_reward, - "ss_anne_present": ss_anne_state_reward, + "hm_count_reward": hm_reward, "healing": healing_reward, "exploration": exploration_reward, - "explore_npcs_reward": explore_npcs_reward, "seen_pokemon_reward": seen_pokemon_reward, "caught_pokemon_reward": caught_pokemon_reward, "moves_obtained_reward": moves_obtained_reward, - "hidden_obj_count_reward": explore_hidden_objs_reward, }, - "maps_explored": np.sum(self.seen_maps), - "party_size": party_size, - "highest_pokemon_level": max(party_levels), - "total_party_level": sum(party_levels), - "deaths": self.death_count, "bill_saved": bill_state, "hm_count": hm_count, - "ss_anne_state": ss_anne_state, + "cut_taught": self.cut, "badge_1": float(badges >= 1), "badge_2": float(badges >= 2), "event": events, - "money": money, - "pokemon_exploration_map": self.counts_map, - "seen_npcs_count": len(self.seen_npcs), - "seen_pokemon": sum(self.seen_pokemon), - "caught_pokemon": sum(self.caught_pokemon), + "maps_explored": np.sum(self.seen_maps), + "party_size": party_size, + # "pokemon_exploration_map": self.counts_map, "moves_obtained": sum(self.moves_obtained), - "hidden_obj_count": len(self.seen_hidden_objs), - "logging": pokemon, + "deaths": self.death_count, + "bill_capt": (bill_capt_rew/5), + # "highest_pokemon_level": max(party_levels), + # "total_party_level": sum(party_levels), + # "money": money, + # "ss_anne_state": ss_anne_state, + # "seen_npcs_count": len(self.seen_npcs), + # "seen_pokemon": sum(self.seen_pokemon), + # "caught_pokemon": sum(self.caught_pokemon), + # "hidden_obj_count": len(self.seen_hidden_objs), + # "logging": pokemon, } return self.render(), reward, done, done, info diff --git a/pokegym/ram_map.py b/pokegym/ram_map.py index e1ca400..35a7e4f 100644 --- a/pokegym/ram_map.py +++ b/pokegym/ram_map.py @@ -1,5 +1,10 @@ -# addresses from https://datacrystal.romhacking.net/wiki/Pok%C3%A9mon_Red/Blue:RAM_map -# https://github.com/pret/pokered/blob/91dc3c9f9c8fd529bb6e8307b58b96efa0bec67e/constants/event_constants.asm +# ###################################################################################### +# Ram_map +# ###################################################################################### + +# Data Crystal - https://datacrystal.romhacking.net/wiki/Pok%C3%A9mon_Red/Blue:RAM_map +# No Comments - https://github.com/pret/pokered/blob/91dc3c9f9c8fd529bb6e8307b58b96efa0bec67e/constants/event_constants.asm +# Comments - https://github.com/luckytyphlosion/pokered/blob/master/constants/event_constants.asm from pokegym import data @@ -101,9 +106,19 @@ def get_hm_count(game): for hm_id in hm_ids: if hm_id in items: total_hm_cnt += 1 - return total_hm_cnt + return total_hm_cnt * 1 def get_items_in_bag(game, one_indexed=0): + first_item = 0xD31E + item_ids = [] + for i in range(0, 20, 2): + item_id = game.get_memory_value(first_item + i) + if item_id == 0 or item_id == 0xff: + break + item_ids.append(item_id + one_indexed) + return item_ids + +def get_items_names(game, one_indexed=0): first_item = 0xD31E item_names = [] for i in range(0, 20, 2): @@ -247,4 +262,136 @@ def sprites(game): return game.get_memory_value(WNUMSPRITES) def signs(game): - return game.get_memory_value(WNUMSIGNS) \ No newline at end of file + return game.get_memory_value(WNUMSIGNS) + +def bill_capt(game): + met_bill = 5 * int(read_bit(game, 0xD7F1, 0)) + used_cell_separator_on_bill = 5 * int(read_bit(game, 0xD7F2, 3)) + ss_ticket = 5 * int(read_bit(game, 0xD7F2, 4)) + met_bill_2 = 5 * int(read_bit(game, 0xD7F2, 5)) + bill_said_use_cell_separator = 5 * int(read_bit(game, 0xD7F2, 6)) + left_bills_house_after_helping = 5 * int(read_bit(game, 0xD7F2, 7)) + got_hm01 = 5 * int(read_bit(game, 0xD803, 0)) + rubbed_captains_back = 5 * int(read_bit(game, 0xD803, 1)) + return sum([met_bill, used_cell_separator_on_bill, ss_ticket, met_bill_2, bill_said_use_cell_separator, left_bills_house_after_helping, got_hm01, rubbed_captains_back]) + +# ################################################################################################################## +# # Notes +# ################################################################################################################## + +## Misc + # 0xc4f2 check for EE hex for text box arrow is present + +## Menu Data + # Coordinates of the position of the cursor for the top menu item (id 0) + # CC24 : Y position + # CC25 : X position + # CC26 - Currently selected menu item (topmost is 0) + # CC27 - Tile "hidden" by the menu cursor + # CC28 - ID of the last menu item + # CC29 - bitmask applied to the key port for the current menu + # CC2A - ID of the previously selected menu item + # CC2B - Last position of the cursor on the party / Bill's PC screen + # CC2C - Last position of the cursor on the item screen + # CC2D - Last position of the cursor on the START / battle menu + # CC2F - Index (in party) of the Pokémon currently sent out + # CC30~CC31 - Pointer to cursor tile in C3A0 buffer + # CC36 - ID of the first displayed menu item + # CC35 - Item highlighted with Select (01 = first item, 00 = no item, etc.) + # CC3A and CC3B are unused + # cc51 and cc52 both read 00 when menu is closed + +## Pokémon Mart + # JPN addr. INT addr. Description + # CF62 CF7B Total Items + # CF63 CF7C Item 1 + # CF64 CF7D Item 2 + # CF65 CF7E Item 3 + # CF66 CF7F Item 4 + # CF67 CF80 Item 5 + # CF68 CF81 Item 6 + # CF69 CF82 Item 7 + # CF70 CF83 Item 8 + # CF71 CF84 Item 9 + # CF72 CF85 Item 10 + +## Event Flags + # D751 - Fought Giovanni Yet? + # D755 - Fought Brock Yet? + # D75E - Fought Misty Yet? + # D773 - Fought Lt. Surge Yet? + # D77C - Fought Erika Yet? + # D792 - Fought Koga Yet? + # D79A - Fought Blaine Yet? + # D7B3 - Fought Sabrina Yet? + # D782 - Fought Articuno Yet? + # D7D4 - Fought Zapdos Yet? + # D7EE - Fought Moltres Yet? + # D710 - Fossilized Pokémon? + # D7D8 - Fought Snorlax Yet (Vermilion) + # D7E0 - Fought Snorlax Yet? (Celadon) + # D803 - Is SS Anne here + # D5F3 - Have Town map? + # D60D - Have Oak's Parcel? + # D5A6 to D5C5 : Missable Objects Flags (flags for every (dis)appearing sprites, like the guard in Cerulean City or the Pokéballs in Oak's Lab) + # D5AB - Starters Back? + # D5C0(bit 1) - 0=Mewtwo appears, 1=Doesn't (See D85F) + # D700 - Bike Speed + # D70B - Fly Anywhere Byte 1 + # D70C - Fly Anywhere Byte 2 + # D70D - Safari Zone Time Byte 1 + # D70E - Safari Zone Time Byte 2 + # D714 - Position in Air + # D72E - Did you get Lapras Yet? + # D732 - Debug New Game + # D790 - If bit 7 is set, Safari Game over + # D85F - Mewtwo can be caught if bit 2 clear - Needs D5C0 bit 1 clear, too + +## Item IDs & String + # 1, 2, 3, 4, 6, 11, 16, 17, 18, 19, 20, 41, 42, 72, 73, 196, 197, 198, 199, 200, 53, 54 + # 001 0x01 Master Ball + # 002 0x02 Ultra Ball + # 003 0x03 Great Ball + # 004 0x04 Poké Ball + # 006 0x06 Bicycle + # 011 0x0B Antidote + # 016 0x10 Full Restore + # 017 0x11 Max Potion + # 018 0x12 Hyper Potion + # 019 0x13 Super Potion + # 020 0x14 Potion + # 041 0x29 Dome Fossil + # 042 0x2A Helix Fossil + # 072 0x48 Silph Scope + # 073 0x49 Poké Flute + # 196 0xC4 HM01 + # 197 0xC5 HM02 + # 198 0xC6 HM03 + # 199 0xC7 HM04 + # 200 0xC8 HM05 + # 053 0x35 Revive + # 054 0x36 Max Revive + +## Item Bag + # 0xD31D - Total Items + # 0xD31E - Item 1 + # 0xD320 - Item 2 + # 0xD322 - Item 3 + # 0xD324 - Item 4 + # 0xD326 - Item 5 + # 0xD328 - Item 6 + # 0xD32A - Item 7 + # 0xD32C - Item 8 + # 0xD32E - Item 9 + # 0xD330 - Item 10 + # 0xD332 - Item 11 + # 0xD334 - Item 12 + # 0xD336 - Item 13 + # 0xD338 - Item 14 + # 0xD33A - Item 15 + # 0xD33C - Item 16 + # 0xD33E - Item 17 + # 0xD340 - Item 18 + # 0xD342 - Item 19 + # 0xD344 - Item 20 + # 0xD346 - Item End of List \ No newline at end of file From 0009d220c3d90b2894e88d103391106ffa6cf721 Mon Sep 17 00:00:00 2001 From: leanke Date: Sun, 10 Mar 2024 23:53:08 +0000 Subject: [PATCH 11/29] reset edits --- pokegym/environment.py | 144 +++++++++++++---------------------------- pokegym/ram_map.py | 8 +++ 2 files changed, 53 insertions(+), 99 deletions(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index 9f6c0f0..6b57c92 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -26,15 +26,9 @@ STATE_PATH = __file__.rstrip("environment.py") + "States/" -CUT_SEQ = [ - ((0x3D, 1, 1, 0, 4, 1), (0x3D, 1, 1, 0, 1, 1)), - ((0x50, 1, 1, 0, 4, 1), (0x50, 1, 1, 0, 1, 1)), -] - CUT_GRASS_SEQ = deque([(0x52, 255, 1, 0, 1, 1), (0x52, 255, 1, 0, 1, 1), (0x52, 1, 1, 0, 1, 1)]) CUT_FAIL_SEQ = deque([(-1, 255, 0, 0, 4, 1), (-1, 255, 0, 0, 1, 1), (-1, 255, 0, 0, 1, 1)]) - - +CUT_SEQ = [((0x3D, 1, 1, 0, 4, 1), (0x3D, 1, 1, 0, 1, 1)), ((0x50, 1, 1, 0, 4, 1), (0x50, 1, 1, 0, 1, 1)),] def get_random_state(): state_files = [f for f in os.listdir(STATE_PATH) if f.endswith(".state")] if not state_files: @@ -188,22 +182,11 @@ def __init__( self.counts_map = np.zeros((444, 436)) self.death_count = 0 self.verbose = verbose - self.screenshot_counter = 0 self.include_conditions = [] self.seen_maps_difference = set() self.current_maps = [] - self.exclude_map_n = {37, 38, 39, 43, 52, 53, 55, 57} - self.exclude_map_n = set() self.is_dead = False - self.talk_to_npc_reward = 0 - self.already_got_npc_reward = set() - self.ss_anne_state = False - self.seen_npcs = set() - self.explore_npc_weight = 1 self.last_map = -1 - self.seen_pokemon = np.zeros(152, dtype=np.uint8) - self.caught_pokemon = np.zeros(152, dtype=np.uint8) - self.moves_obtained = np.zeros(0xA5, dtype=np.uint8) self.log = True # self.seen_coords = set() ## moved from reset @@ -285,63 +268,12 @@ def check_if_cancel_bag_menu(self, action) -> bool: and ram_map.mem_val(self.game, 0xD31D) == ram_map.mem_val(self.game, 0xCC36) + ram_map.mem_val(self.game, 0xCC26) ) - def check_if_in_overworld(self) -> bool: - return ram_map.mem_val(self.game, 0xD057) == 0 and ram_map.mem_val(self.game, 0xCF13) == 0 and ram_map.mem_val(self.game, 0xFF8C) == 0 - - def update_heat_map(self, r, c, current_map): - ''' - Updates the heat map based on the agent's current position. - - Args: - r (int): global y coordinate of the agent's position. - c (int): global x coordinate of the agent's position. - current_map (int): ID of the current map (map_n) - - Updates the counts_map to track the frequency of visits to each position on the map. - ''' - # Convert local position to global position - try: - glob_r, glob_c = game_map.local_to_global(r, c, current_map) - except IndexError: - print(f'IndexError: index {glob_r} or {glob_c} for {current_map} is out of bounds for axis 0 with size 444.') - glob_r = 0 - glob_c = 0 - - # Update heat map based on current map - if self.last_map == current_map or self.last_map == -1: - # Increment count for current global position - try: - self.counts_map[glob_r, glob_c] += 1 - except: - pass - else: - # Reset count for current global position if it's a new map for warp artifacts - self.counts_map[(glob_r, glob_c)] = -1 - - # Update last_map for the next iteration - self.last_map = current_map - - def find_neighboring_npc(self, npc_bank, npc_id, player_direction, player_x, player_y) -> int: - - npc_y = ram_map.npc_y(self.game, npc_id, npc_bank) - npc_x = ram_map.npc_x(self.game, npc_id, npc_bank) - if ( - (player_direction == 0 and npc_x == player_x and npc_y > player_y) or - (player_direction == 4 and npc_x == player_x and npc_y < player_y) or - (player_direction == 8 and npc_y == player_y and npc_x < player_x) or - (player_direction == 0xC and npc_y == player_y and npc_x > player_x) - ): - # Manhattan distance - return abs(npc_y - player_y) + abs(npc_x - player_x) - - return 1000 - def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" - # if self.reset_count % 5 == 0: - # load_pyboy_state(self.game, self.load_first_state()) - # else: - load_pyboy_state(self.game, self.load_last_state()) + if self.reset_count % 7 == 0: + load_pyboy_state(self.game, self.load_first_state()) + else: + load_pyboy_state(self.game, self.load_last_state()) if self.save_video: base_dir = self.s_path @@ -359,30 +291,32 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.time = 0 self.max_episode_steps = max_episode_steps self.reward_scale = reward_scale - self.prev_map_n = None - self.max_events = 0 - self.max_level_sum = 0 - self.max_opponent_level = 0 - # if self.reset_count % 5 == 0: ## resets every 5 to 0 moved seen_coords to init - self.seen_coords = set() - self.seen_maps = set() - self.total_healing = 0 - self.last_hp = 1.0 - self.last_party_size = 1 self.last_reward = None - self.seen_coords_no_reward = set() - self.hm_count = 0 - self.cut = 0 - - self.cut_coords = {} - self.cut_tiles = set([]) - self.cut_state = deque(maxlen=3) - - self.seen_start_menu = 0 - self.seen_pokemon_menu = 0 - self.seen_stats_menu = 0 - self.seen_bag_menu = 0 - self.seen_cancel_bag_menu = 0 + + if self.reset_count % 7 == 0: ## resets every 5 to 0 moved seen_coords to init + self.prev_map_n = None + self.max_events = 0 + self.max_level_sum = 0 + self.max_opponent_level = 0 + self.seen_coords = set() + self.seen_maps = set() + self.total_healing = 0 + self.last_hp = 1.0 + self.last_party_size = 1 + self.hm_count = 0 + self.cut = 0 + self.used_cut = 0 + self.cut_coords = {} + self.cut_tiles = {} # set([]) + self.cut_state = deque(maxlen=3) + self.seen_start_menu = 0 + self.seen_pokemon_menu = 0 + self.seen_stats_menu = 0 + self.seen_bag_menu = 0 + self.seen_cancel_bag_menu = 0 + self.seen_pokemon = np.zeros(152, dtype=np.uint8) + self.caught_pokemon = np.zeros(152, dtype=np.uint8) + self.moves_obtained = np.zeros(0xA5, dtype=np.uint8) return self.render(), {} @@ -441,8 +375,8 @@ def step(self, action, fast_video=True): if hm_count >= 1 and self.hm_count == 0: self.save_state() self.hm_count = 1 - hm_reward = hm_count * 5 - cut_rew = self.cut * 5 + hm_reward = hm_count * 10 + cut_rew = self.cut * 10 # Event reward events = ram_map.events(self.game) @@ -512,7 +446,11 @@ def step(self, action, fast_video=True): self.update_moves_obtained() bill_capt_rew = ram_map.bill_capt(self.game) + if ram_map.used_cut(self.game) == 61: + ram_map.write_mem(self.game, 0xCD4D, 00) # address, byte to write + self.used_cut += 1 + used_cut_rew = self.used_cut * 5 start_menu = self.seen_start_menu * 0.01 pokemon_menu = self.seen_pokemon_menu * 0.1 stats_menu = self.seen_stats_menu * 0.1 @@ -521,7 +459,6 @@ def step(self, action, fast_video=True): cut_coords = sum(self.cut_coords.values()) * 1.0 cut_tiles = len(self.cut_tiles) * 1.0 that_guy = (start_menu + pokemon_menu + stats_menu + bag_menu + cut_coords + cut_tiles) - seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) * 0.00010 caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) * 0.00010 @@ -542,6 +479,7 @@ def step(self, action, fast_video=True): + exploration_reward + cut_rew + that_guy + + used_cut_rew ) # Subtract previous reward @@ -578,6 +516,7 @@ def step(self, action, fast_video=True): "cut_taught": self.cut, "badge_1": float(badges >= 1), "badge_2": float(badges >= 2), + "badge_3": float(badges >= 3), "event": events, "maps_explored": np.sum(self.seen_maps), "party_size": party_size, @@ -585,6 +524,13 @@ def step(self, action, fast_video=True): "moves_obtained": sum(self.moves_obtained), "deaths": self.death_count, "bill_capt": (bill_capt_rew/5), + 'cut_coords': cut_coords, + 'cut_tiles': cut_tiles, + 'bag_menu': bag_menu, + 'stats_menu': stats_menu, + 'pokemon_menu': pokemon_menu, + 'start_menu': start_menu, + 'used_cut': self.used_cut, # "highest_pokemon_level": max(party_levels), # "total_party_level": sum(party_levels), # "money": money, diff --git a/pokegym/ram_map.py b/pokegym/ram_map.py index 35a7e4f..9700a51 100644 --- a/pokegym/ram_map.py +++ b/pokegym/ram_map.py @@ -43,6 +43,7 @@ PLAYER_X = 0xC106 WNUMSPRITES = 0xD4E1 WNUMSIGNS = 0xD4B0 +WCUTTILE = 0xCD4D # 61 if Cut used; 0 default. resets to default on map_n change or battle. # Moves 1-4 for Poke1, Poke2, Poke3, Poke4, Poke5, Poke6 MOVE1 = [0xD173, 0xD19F, 0xD1CB, 0xD1F7, 0xD223, 0xD24F] @@ -275,6 +276,13 @@ def bill_capt(game): rubbed_captains_back = 5 * int(read_bit(game, 0xD803, 1)) return sum([met_bill, used_cell_separator_on_bill, ss_ticket, met_bill_2, bill_said_use_cell_separator, left_bills_house_after_helping, got_hm01, rubbed_captains_back]) +def used_cut(game): + return game.get_memory_value(WCUTTILE) + +def write_mem(game, addr, value): + mem = game.set_memory_value(addr, value) + return mem + # ################################################################################################################## # # Notes # ################################################################################################################## From 41f532d10a6a5a80e80e078d80e3792a4fcffb10 Mon Sep 17 00:00:00 2001 From: leanke Date: Mon, 11 Mar 2024 11:14:31 +0000 Subject: [PATCH 12/29] reset edits --- pokegym/environment.py | 79 ++++++++++++++++++++++++++-------------- pokegym/pyboy_binding.py | 71 ++++++++++++++++++++++++++++-------- 2 files changed, 108 insertions(+), 42 deletions(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index 6b57c92..6440b62 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -188,7 +188,33 @@ def __init__( self.is_dead = False self.last_map = -1 self.log = True + self.map_check = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # self.seen_coords = set() ## moved from reset + + # #for reseting at 7 + # self.prev_map_n = None + # self.max_events = 0 + # self.max_level_sum = 0 + # self.max_opponent_level = 0 + # self.seen_coords = set() + # self.seen_maps = set() + # self.total_healing = 0 + # self.last_hp = 1.0 + # self.last_party_size = 1 + # self.hm_count = 0 + # self.cut = 0 + # self.used_cut = 0 + # self.cut_coords = {} + # self.cut_tiles = {} # set([]) + # self.cut_state = deque(maxlen=3) + # self.seen_start_menu = 0 + # self.seen_pokemon_menu = 0 + # self.seen_stats_menu = 0 + # self.seen_bag_menu = 0 + # self.seen_cancel_bag_menu = 0 + # self.seen_pokemon = np.zeros(152, dtype=np.uint8) + # self.caught_pokemon = np.zeros(152, dtype=np.uint8) + # self.moves_obtained = np.zeros(0xA5, dtype=np.uint8) def update_pokedex(self): for i in range(0xD30A - 0xD2F7): @@ -270,7 +296,7 @@ def check_if_cancel_bag_menu(self, action) -> bool: def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" - if self.reset_count % 7 == 0: + if self.reset_count % 7 == 0: ## resets every 5 to 0 moved seen_coords to init load_pyboy_state(self.game, self.load_first_state()) else: load_pyboy_state(self.game, self.load_last_state()) @@ -293,31 +319,30 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.reward_scale = reward_scale self.last_reward = None - if self.reset_count % 7 == 0: ## resets every 5 to 0 moved seen_coords to init - self.prev_map_n = None - self.max_events = 0 - self.max_level_sum = 0 - self.max_opponent_level = 0 - self.seen_coords = set() - self.seen_maps = set() - self.total_healing = 0 - self.last_hp = 1.0 - self.last_party_size = 1 - self.hm_count = 0 - self.cut = 0 - self.used_cut = 0 - self.cut_coords = {} - self.cut_tiles = {} # set([]) - self.cut_state = deque(maxlen=3) - self.seen_start_menu = 0 - self.seen_pokemon_menu = 0 - self.seen_stats_menu = 0 - self.seen_bag_menu = 0 - self.seen_cancel_bag_menu = 0 - self.seen_pokemon = np.zeros(152, dtype=np.uint8) - self.caught_pokemon = np.zeros(152, dtype=np.uint8) - self.moves_obtained = np.zeros(0xA5, dtype=np.uint8) - + self.prev_map_n = None + self.max_events = 0 + self.max_level_sum = 0 + self.max_opponent_level = 0 + self.seen_coords = set() + self.seen_maps = set() + self.total_healing = 0 + self.last_hp = 1.0 + self.last_party_size = 1 + self.hm_count = 0 + self.cut = 0 + self.used_cut = 0 + self.cut_coords = {} + self.cut_tiles = {} # set([]) + self.cut_state = deque(maxlen=3) + self.seen_start_menu = 0 + self.seen_pokemon_menu = 0 + self.seen_stats_menu = 0 + self.seen_bag_menu = 0 + self.seen_cancel_bag_menu = 0 + self.seen_pokemon = np.zeros(152, dtype=np.uint8) + self.caught_pokemon = np.zeros(152, dtype=np.uint8) + self.moves_obtained = np.zeros(0xA5, dtype=np.uint8) + return self.render(), {} def step(self, action, fast_video=True): @@ -352,7 +377,7 @@ def step(self, action, fast_video=True): party_size_constant = party_size == self.last_party_size if hp_delta > 0 and party_size_constant and not self.is_dead: self.total_healing += hp_delta - if hp <= 0 and self.last_hp > 0: + if hp <= 0.2 and self.last_hp > 0: self.death_count += 1 self.is_dead = True elif hp > 0.01: # TODO: Check if this matters diff --git a/pokegym/pyboy_binding.py b/pokegym/pyboy_binding.py index 0e9a4fd..d953509 100644 --- a/pokegym/pyboy_binding.py +++ b/pokegym/pyboy_binding.py @@ -1,9 +1,12 @@ from pdb import set_trace as T from io import BytesIO +import numpy as np from pyboy import PyBoy from pyboy import logger from pyboy.utils import WindowEvent +from pokegym import ram_map + logger.logger.setLevel('ERROR') @@ -40,8 +43,15 @@ class Select: PRESS = WindowEvent.PRESS_BUTTON_SELECT RELEASE = WindowEvent.RELEASE_BUTTON_SELECT +class Cut: + PRESS = WindowEvent.PRESS_BUTTON_START + RELEASE = WindowEvent.RELEASE_BUTTON_START + + + + # TODO: Add start button to actions when we need it -ACTIONS = (Down, Left, Right, Up, A, B, Start, Select) +ACTIONS = (Down, Left, Right, Up, A, B, Start, Select) # Cut def make_env(gb_path, headless=True, quiet=False, **kwargs): gb_path='pokemon_red.gb' @@ -77,19 +87,50 @@ def run_action_on_emulator(pyboy, screen, action, '''Sends actions to PyBoy''' press, release = action.PRESS, action.RELEASE pyboy.send_input(press) - - if headless or fast_video: - pyboy._rendering(False) - - frames = [] - for i in range(frame_skip): - if i == 8: # Release button after 8 frames - pyboy.send_input(release) - if not fast_video: # Save every frame + # print(action) + if action == Cut: + # print('action was cut---------------------------------') + pressing(pyboy) + else: + if headless or fast_video: + pyboy._rendering(False) + + frames = [] + for i in range(frame_skip): + if i == 8: # Release button after 8 frames + pyboy.send_input(release) + if not fast_video: # Save every frame + frames.append(screen.screen_ndarray()) + if i == frame_skip - 1: + pyboy._rendering(True) + pyboy.tick() + + if fast_video: # Save only the last frame frames.append(screen.screen_ndarray()) - if i == frame_skip - 1: - pyboy._rendering(True) - pyboy.tick() - if fast_video: # Save only the last frame - frames.append(screen.screen_ndarray()) +def pressing(pyboy): + # pyboy.send_input(WindowEvent.PRESS_BUTTON_START) + for i in range(24): + if i == 8: + pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) + # print('1') + pyboy.send_input(WindowEvent.PRESS_ARROW_DOWN) + for i in range(24): + if i == 8: + pyboy.send_input(WindowEvent.RELEASE_ARROW_DOWN) + # print('2') + pyboy.send_input(WindowEvent.PRESS_BUTTON_A) + for i in range(24): + if i == 8: + pyboy.send_input(WindowEvent.RELEASE_BUTTON_A) + # print('3') + pyboy.send_input(WindowEvent.PRESS_BUTTON_A) + for i in range(24): + if i == 8: + pyboy.send_input(WindowEvent.RELEASE_BUTTON_A) + # print('4') + pyboy.send_input(WindowEvent.PRESS_BUTTON_A) + for i in range(24): + if i == 8: + pyboy.send_input(WindowEvent.RELEASE_BUTTON_A) + # print('5') From 08a4c76c7aea2a01245c97ac6ad34b80b9d5878e Mon Sep 17 00:00:00 2001 From: leanke Date: Fri, 29 Mar 2024 19:18:16 +0000 Subject: [PATCH 13/29] new ram map --- pokegym/newram_map.py | 1041 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1041 insertions(+) create mode 100644 pokegym/newram_map.py diff --git a/pokegym/newram_map.py b/pokegym/newram_map.py new file mode 100644 index 0000000..2bb8ac8 --- /dev/null +++ b/pokegym/newram_map.py @@ -0,0 +1,1041 @@ +from pokegym import data + +HP_ADDR = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] +MAX_HP_ADDR = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] +PARTY_SIZE_ADDR = 0xD163 +PARTY_ADDR = [0xD164, 0xD165, 0xD166, 0xD167, 0xD168, 0xD169] +PARTY_LEVEL_ADDR = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] +POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] # - Pokémon (Again) +X_POS_ADDR = 0xD362 +Y_POS_ADDR = 0xD361 +MAP_N_ADDR = 0xD35E +BADGE_1_ADDR = 0xD356 +WCUTTILE = 0xCD4D # 61 if Cut used; 0 default. resets to default on map_n change or battle. + + +GYM_LEADER = 5 +GYM_TRAINER = 2 +GYM_TASK = 2 +TRAINER = 1 +HM = 5 +TM = 2 +TASK = 2 +POKEMON = 3 +ITEM = 5 +BILL_CAPT = 5 +RIVAL = 3 +QUEST = 5 +EVENT = 1 +BAD = -1 + +def bulba(game): + # Get memory values from the list POKE and LEVEL + poke = [game.get_memory_value(a) for a in POKE] + if any(x in poke for x in [153, 9, 154]): + reward = 0 + else: + reward = -5 + return reward + +def silph_co(game): + Beat_Silph_Co_2F_Trainer_0 = TRAINER * int(read_bit(game, 0xD825, 2)) + Beat_Silph_Co_2F_Trainer_1 = TRAINER * int(read_bit(game, 0xD825, 3)) + Beat_Silph_Co_2F_Trainer_2 = TRAINER * int(read_bit(game, 0xD825, 4)) + Beat_Silph_Co_2F_Trainer_3 = TRAINER * int(read_bit(game, 0xD825, 5)) + Silph_Co_2_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD826, 5)) + Silph_Co_2_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD826, 6)) + Beat_Silph_Co_3F_Trainer_0 = TRAINER * int(read_bit(game, 0xD827, 2)) + Beat_Silph_Co_3F_Trainer_1 = TRAINER * int(read_bit(game, 0xD827, 3)) + Silph_Co_3_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD828, 0)) + Silph_Co_3_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD828, 1)) + Beat_Silph_Co_4F_Trainer_0 = TRAINER * int(read_bit(game, 0xD829, 2)) + Beat_Silph_Co_4F_Trainer_1 = TRAINER * int(read_bit(game, 0xD829, 3)) + Beat_Silph_Co_4F_Trainer_2 = TRAINER * int(read_bit(game, 0xD829, 4)) + Silph_Co_4_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD82A, 0)) + Silph_Co_4_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD82A, 1)) + Beat_Silph_Co_5F_Trainer_0 = TRAINER * int(read_bit(game, 0xD82B, 2)) + Beat_Silph_Co_5F_Trainer_1 = TRAINER * int(read_bit(game, 0xD82B, 3)) + Beat_Silph_Co_5F_Trainer_2 = TRAINER * int(read_bit(game, 0xD82B, 4)) + Beat_Silph_Co_5F_Trainer_3 = TRAINER * int(read_bit(game, 0xD82B, 5)) + Silph_Co_5_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD82C, 0)) + Silph_Co_5_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD82C, 1)) + Silph_Co_5_Unlocked_Door3 = QUEST * int(read_bit(game, 0xD82C, 2)) + Beat_Silph_Co_6F_Trainer_0 = TRAINER * int(read_bit(game, 0xD82D, 6)) + Beat_Silph_Co_6F_Trainer_1 = TRAINER * int(read_bit(game, 0xD82D, 7)) + Beat_Silph_Co_6F_Trainer_2 = TRAINER * int(read_bit(game, 0xD82E, 0)) + Silph_Co_6_Unlocked_Door = QUEST * int(read_bit(game, 0xD82E, 7)) + Beat_Silph_Co_7F_Trainer_0 = TRAINER * int(read_bit(game, 0xD82F, 5)) + Beat_Silph_Co_7F_Trainer_1 = TRAINER * int(read_bit(game, 0xD82F, 6)) + Beat_Silph_Co_7F_Trainer_2 = TRAINER * int(read_bit(game, 0xD82F, 7)) + Beat_Silph_Co_7F_Trainer_3 = TRAINER * int(read_bit(game, 0xD830, 0)) + Silph_Co_7_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD830, 4)) + Silph_Co_7_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD830, 5)) + Silph_Co_7_Unlocked_Door3 = QUEST * int(read_bit(game, 0xD830, 6)) + Beat_Silph_Co_8F_Trainer_0 = TRAINER * int(read_bit(game, 0xD831, 2)) + Beat_Silph_Co_8F_Trainer_1 = TRAINER * int(read_bit(game, 0xD831, 3)) + Beat_Silph_Co_8F_Trainer_2 = TRAINER * int(read_bit(game, 0xD831, 4)) + Silph_Co_8_Unlocked_Door = QUEST * int(read_bit(game, 0xD832, 0)) + Beat_Silph_Co_9F_Trainer_0 = TRAINER * int(read_bit(game, 0xD833, 2)) + Beat_Silph_Co_9F_Trainer_1 = TRAINER * int(read_bit(game, 0xD833, 3)) + Beat_Silph_Co_9F_Trainer_2 = TRAINER * int(read_bit(game, 0xD833, 4)) + Silph_Co_9_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD834, 0)) + Silph_Co_9_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD834, 1)) + Silph_Co_9_Unlocked_Door3 = QUEST * int(read_bit(game, 0xD834, 2)) + Silph_Co_9_Unlocked_Door4 = QUEST * int(read_bit(game, 0xD834, 3)) + Beat_Silph_Co_10F_Trainer_0 = TRAINER * int(read_bit(game, 0xD835, 1)) + Beat_Silph_Co_10F_Trainer_1 = TRAINER * int(read_bit(game, 0xD835, 2)) + Silph_Co_10_Unlocked_Door = QUEST * int(read_bit(game, 0xD836, 0)) + Beat_Silph_Co_11F_Trainer_0 = TRAINER * int(read_bit(game, 0xD837, 4)) + Beat_Silph_Co_11F_Trainer_1 = TRAINER * int(read_bit(game, 0xD837, 5)) + Silph_Co_11_Unlocked_Door = QUEST * int(read_bit(game, 0xD838, 0)) + Got_Master_Ball = ITEM * int(read_bit(game, 0xD838, 5)) + Beat_Silph_Co_Giovanni = GYM_LEADER * int(read_bit(game, 0xD838, 7)) + Silph_Co_Receptionist_At_Desk = TASK * int(read_bit(game, 0xD7B9, 7)) + return sum([Beat_Silph_Co_2F_Trainer_0, Beat_Silph_Co_2F_Trainer_1, Beat_Silph_Co_2F_Trainer_2, Beat_Silph_Co_2F_Trainer_3, Silph_Co_2_Unlocked_Door1, + Silph_Co_2_Unlocked_Door2, Beat_Silph_Co_3F_Trainer_0, Beat_Silph_Co_3F_Trainer_1, Silph_Co_3_Unlocked_Door1, Silph_Co_3_Unlocked_Door2, + Beat_Silph_Co_4F_Trainer_0, Beat_Silph_Co_4F_Trainer_1, Beat_Silph_Co_4F_Trainer_2, Silph_Co_4_Unlocked_Door1, Silph_Co_4_Unlocked_Door2, + Beat_Silph_Co_5F_Trainer_0, Beat_Silph_Co_5F_Trainer_1, Beat_Silph_Co_5F_Trainer_2, Beat_Silph_Co_5F_Trainer_3, Silph_Co_5_Unlocked_Door1, + Silph_Co_5_Unlocked_Door2, Silph_Co_5_Unlocked_Door3, Beat_Silph_Co_6F_Trainer_0, Beat_Silph_Co_6F_Trainer_1, Beat_Silph_Co_6F_Trainer_2, + Silph_Co_6_Unlocked_Door, Beat_Silph_Co_7F_Trainer_0, Beat_Silph_Co_7F_Trainer_1, Beat_Silph_Co_7F_Trainer_2, Beat_Silph_Co_7F_Trainer_3, + Silph_Co_7_Unlocked_Door1, Silph_Co_7_Unlocked_Door2, Silph_Co_7_Unlocked_Door3, Beat_Silph_Co_8F_Trainer_0, Beat_Silph_Co_8F_Trainer_1, + Beat_Silph_Co_8F_Trainer_2, Silph_Co_8_Unlocked_Door, Beat_Silph_Co_9F_Trainer_0, Beat_Silph_Co_9F_Trainer_1, Beat_Silph_Co_9F_Trainer_2, + Silph_Co_9_Unlocked_Door1, Silph_Co_9_Unlocked_Door2, Silph_Co_9_Unlocked_Door3, Silph_Co_9_Unlocked_Door4, Beat_Silph_Co_10F_Trainer_0, + Beat_Silph_Co_10F_Trainer_1, Silph_Co_10_Unlocked_Door, Beat_Silph_Co_11F_Trainer_0, Beat_Silph_Co_11F_Trainer_1, Silph_Co_11_Unlocked_Door, + Got_Master_Ball, Beat_Silph_Co_Giovanni, Silph_Co_Receptionist_At_Desk]) + +def rock_tunnel(game): + Beat_Rock_Tunnel_1_Trainer_0 = TRAINER * int(read_bit(game, 0xD7D2, 1)) + Beat_Rock_Tunnel_1_Trainer_1 = TRAINER * int(read_bit(game, 0xD7D2, 2)) + Beat_Rock_Tunnel_1_Trainer_2 = TRAINER * int(read_bit(game, 0xD7D2, 3)) + Beat_Rock_Tunnel_1_Trainer_3 = TRAINER * int(read_bit(game, 0xD7D2, 4)) + Beat_Rock_Tunnel_1_Trainer_4 = TRAINER * int(read_bit(game, 0xD7D2, 5)) + Beat_Rock_Tunnel_1_Trainer_5 = TRAINER * int(read_bit(game, 0xD7D2, 6)) + Beat_Rock_Tunnel_1_Trainer_6 = TRAINER * int(read_bit(game, 0xD7D2, 7)) + Beat_Rock_Tunnel_2_Trainer_0 = TRAINER * int(read_bit(game, 0xD87D, 1)) + Beat_Rock_Tunnel_2_Trainer_1 = TRAINER * int(read_bit(game, 0xD87D, 2)) + Beat_Rock_Tunnel_2_Trainer_2 = TRAINER * int(read_bit(game, 0xD87D, 3)) + Beat_Rock_Tunnel_2_Trainer_3 = TRAINER * int(read_bit(game, 0xD87D, 4)) + Beat_Rock_Tunnel_2_Trainer_4 = TRAINER * int(read_bit(game, 0xD87D, 5)) + Beat_Rock_Tunnel_2_Trainer_5 = TRAINER * int(read_bit(game, 0xD87D, 6)) + Beat_Rock_Tunnel_2_Trainer_6 = TRAINER * int(read_bit(game, 0xD87D, 7)) + Beat_Rock_Tunnel_2_Trainer_7 = TRAINER * int(read_bit(game, 0xD87E, 0)) + return sum([Beat_Rock_Tunnel_1_Trainer_0, Beat_Rock_Tunnel_1_Trainer_1, Beat_Rock_Tunnel_1_Trainer_2, Beat_Rock_Tunnel_1_Trainer_3, + Beat_Rock_Tunnel_1_Trainer_4, Beat_Rock_Tunnel_1_Trainer_5, Beat_Rock_Tunnel_1_Trainer_6, Beat_Rock_Tunnel_2_Trainer_0, + Beat_Rock_Tunnel_2_Trainer_1, Beat_Rock_Tunnel_2_Trainer_2, Beat_Rock_Tunnel_2_Trainer_3, Beat_Rock_Tunnel_2_Trainer_4, + Beat_Rock_Tunnel_2_Trainer_5, Beat_Rock_Tunnel_2_Trainer_6, Beat_Rock_Tunnel_2_Trainer_7]) + +def ssanne(game): + Beat_Ss_Anne_5_Trainer_0 = TRAINER * int(read_bit(game, 0xD7FF, 4)) + Beat_Ss_Anne_5_Trainer_1 = TRAINER * int(read_bit(game, 0xD7FF, 5)) + Rubbed_Captains_Back = BILL_CAPT * int(read_bit(game, 0xD803, 1)) + Ss_Anne_Left = BILL_CAPT * int(read_bit(game, 0xD803, 2)) + Walked_Past_Guard_After_Ss_Anne_Left = BILL_CAPT * int(read_bit(game, 0xD803, 3)) + Started_Walking_Out_Of_Dock = BILL_CAPT * int(read_bit(game, 0xD803, 4)) + Walked_Out_Of_Dock = BILL_CAPT * int(read_bit(game, 0xD803, 5)) + Beat_Ss_Anne_8_Trainer_0 = TRAINER * int(read_bit(game, 0xD805, 1)) + Beat_Ss_Anne_8_Trainer_1 = TRAINER * int(read_bit(game, 0xD805, 2)) + Beat_Ss_Anne_8_Trainer_2 = TRAINER * int(read_bit(game, 0xD805, 3)) + Beat_Ss_Anne_8_Trainer_3 = TRAINER * int(read_bit(game, 0xD805, 4)) + Beat_Ss_Anne_9_Trainer_0 = TRAINER * int(read_bit(game, 0xD807, 1)) + Beat_Ss_Anne_9_Trainer_1 = TRAINER * int(read_bit(game, 0xD807, 2)) + Beat_Ss_Anne_9_Trainer_2 = TRAINER * int(read_bit(game, 0xD807, 3)) + Beat_Ss_Anne_9_Trainer_3 = TRAINER * int(read_bit(game, 0xD807, 4)) + Beat_Ss_Anne_10_Trainer_0 = TRAINER * int(read_bit(game, 0xD809, 1)) + Beat_Ss_Anne_10_Trainer_1 = TRAINER * int(read_bit(game, 0xD809, 2)) + Beat_Ss_Anne_10_Trainer_2 = TRAINER * int(read_bit(game, 0xD809, 3)) + Beat_Ss_Anne_10_Trainer_3 = TRAINER * int(read_bit(game, 0xD809, 4)) + Beat_Ss_Anne_10_Trainer_4 = TRAINER * int(read_bit(game, 0xD809, 5)) + Beat_Ss_Anne_10_Trainer_5 = TRAINER * int(read_bit(game, 0xD809, 6)) + return sum([Beat_Ss_Anne_5_Trainer_0, Beat_Ss_Anne_5_Trainer_1, Rubbed_Captains_Back, Ss_Anne_Left, + Walked_Past_Guard_After_Ss_Anne_Left, Started_Walking_Out_Of_Dock, Walked_Out_Of_Dock, Beat_Ss_Anne_8_Trainer_0, + Beat_Ss_Anne_8_Trainer_1, Beat_Ss_Anne_8_Trainer_2, Beat_Ss_Anne_8_Trainer_3, Beat_Ss_Anne_9_Trainer_0, + Beat_Ss_Anne_9_Trainer_1, Beat_Ss_Anne_9_Trainer_2, Beat_Ss_Anne_9_Trainer_3, Beat_Ss_Anne_10_Trainer_0, + Beat_Ss_Anne_10_Trainer_1, Beat_Ss_Anne_10_Trainer_2, Beat_Ss_Anne_10_Trainer_3, Beat_Ss_Anne_10_Trainer_4, Beat_Ss_Anne_10_Trainer_5]) + +def mtmoon(game): + Beat_Mt_Moon_1_Trainer_1 = TRAINER * int(read_bit(game, 0xD7F5, 1)) + Beat_Mt_Moon_1_Trainer_2 = TRAINER * int(read_bit(game, 0xD7F5, 2)) + Beat_Mt_Moon_1_Trainer_3 = TRAINER * int(read_bit(game, 0xD7F5, 3)) + Beat_Mt_Moon_1_Trainer_4 = TRAINER * int(read_bit(game, 0xD7F5, 4)) + Beat_Mt_Moon_1_Trainer_5 = TRAINER * int(read_bit(game, 0xD7F5, 5)) + Beat_Mt_Moon_1_Trainer_6 = TRAINER * int(read_bit(game, 0xD7F5, 6)) + Beat_Mt_Moon_1_Trainer_7 = TRAINER * int(read_bit(game, 0xD7F5, 7)) + Beat_Mt_Moon_Super_Nerd = TRAINER * int(read_bit(game, 0xD7F6, 1)) + Beat_Mt_Moon_3_Trainer_0 = TRAINER * int(read_bit(game, 0xD7F6, 2)) + Beat_Mt_Moon_3_Trainer_1 = TRAINER * int(read_bit(game, 0xD7F6, 3)) + Beat_Mt_Moon_3_Trainer_2 = TRAINER * int(read_bit(game, 0xD7F6, 4)) + Beat_Mt_Moon_3_Trainer_3 = TRAINER * int(read_bit(game, 0xD7F6, 5)) + Got_Dome_Fossil = TASK * int(read_bit(game, 0xD7F6, 6)) + Got_Helix_Fossil = TASK * int(read_bit(game, 0xD7F6, 7)) + return sum([Beat_Mt_Moon_1_Trainer_1, Beat_Mt_Moon_1_Trainer_2, Beat_Mt_Moon_1_Trainer_3, Beat_Mt_Moon_1_Trainer_4, + Beat_Mt_Moon_1_Trainer_5, Beat_Mt_Moon_1_Trainer_6, Beat_Mt_Moon_1_Trainer_7, Beat_Mt_Moon_Super_Nerd, + Beat_Mt_Moon_3_Trainer_0, Beat_Mt_Moon_3_Trainer_1, Beat_Mt_Moon_3_Trainer_2, Beat_Mt_Moon_3_Trainer_3, + Got_Dome_Fossil, Got_Helix_Fossil]) + +def routes(game): + # "0xD7C3-2": "Beat Route 3 Trainer 0", + # "0xD7C3-3": "Beat Route 3 Trainer 1", + # "0xD7C3-4": "Beat Route 3 Trainer 2", + # "0xD7C3-5": "Beat Route 3 Trainer 3", + # "0xD7C3-6": "Beat Route 3 Trainer 4", + # "0xD7C3-7": "Beat Route 3 Trainer 5", + # "0xD7C4-0": "Beat Route 3 Trainer 6", + # "0xD7C4-1": "Beat Route 3 Trainer 7", + # "0xD7C5-2": "Beat Route 4 Trainer 0", + # "0xD7EF-1": "Beat Route24 Rocket", + # "0xD7EF-2": "Beat Route 24 Trainer 0", + # "0xD7EF-3": "Beat Route 24 Trainer 1", + # "0xD7EF-4": "Beat Route 24 Trainer 2", + # "0xD7EF-5": "Beat Route 24 Trainer 3", + # "0xD7EF-6": "Beat Route 24 Trainer 4", + # "0xD7EF-7": "Beat Route 24 Trainer 5", + # "0xD7F1-1": "Beat Route 25 Trainer 0", + # "0xD7F1-2": "Beat Route 25 Trainer 1", + # "0xD7F1-3": "Beat Route 25 Trainer 2", + # "0xD7F1-4": "Beat Route 25 Trainer 3", + # "0xD7F1-5": "Beat Route 25 Trainer 4", + # "0xD7F1-6": "Beat Route 25 Trainer 5", + # "0xD7F1-7": "Beat Route 25 Trainer 6", + # "0xD7F2-0": "Beat Route 25 Trainer 7", + # "0xD7F2-1": "Beat Route 25 Trainer 8", + # "0xD7CF-1": "Beat Route 9 Trainer 0", + # "0xD7CF-2": "Beat Route 9 Trainer 1", + # "0xD7CF-3": "Beat Route 9 Trainer 2", + # "0xD7CF-4": "Beat Route 9 Trainer 3", + # "0xD7CF-5": "Beat Route 9 Trainer 4", + # "0xD7CF-6": "Beat Route 9 Trainer 5", + # "0xD7CF-7": "Beat Route 9 Trainer 6", + # "0xD7D0-0": "Beat Route 9 Trainer 7", + # "0xD7D0-1": "Beat Route 9 Trainer 8", + # "0xD7C9-1": "Beat Route 6 Trainer 0", + # "0xD7C9-2": "Beat Route 6 Trainer 1", + # "0xD7C9-3": "Beat Route 6 Trainer 2", + # "0xD7C9-4": "Beat Route 6 Trainer 3", + # "0xD7C9-5": "Beat Route 6 Trainer 4", + # "0xD7C9-6": "Beat Route 6 Trainer 5", + # "0xD7D5-1": "Beat Route 11 Trainer 0", + # "0xD7D5-2": "Beat Route 11 Trainer 1", + # "0xD7D5-3": "Beat Route 11 Trainer 2", + # "0xD7D5-4": "Beat Route 11 Trainer 3", + # "0xD7D5-5": "Beat Route 11 Trainer 4", + # "0xD7D5-6": "Beat Route 11 Trainer 5", + # "0xD7D5-7": "Beat Route 11 Trainer 6", + # "0xD7D6-0": "Beat Route 11 Trainer 7", + # "0xD7D6-1": "Beat Route 11 Trainer 8", + # "0xD7D6-2": "Beat Route 11 Trainer 9", + # "0xD7CD-1": "Beat Route 8 Trainer 0", + # "0xD7CD-2": "Beat Route 8 Trainer 1", + # "0xD7CD-3": "Beat Route 8 Trainer 2", + # "0xD7CD-4": "Beat Route 8 Trainer 3", + # "0xD7CD-5": "Beat Route 8 Trainer 4", + # "0xD7CD-6": "Beat Route 8 Trainer 5", + # "0xD7CD-7": "Beat Route 8 Trainer 6", + # "0xD7CE-0": "Beat Route 8 Trainer 7", + # "0xD7CE-1": "Beat Route 8 Trainer 8", + # "0xD7D1-1": "Beat Route 10 Trainer 0", + # "0xD7D1-2": "Beat Route 10 Trainer 1", + # "0xD7D1-3": "Beat Route 10 Trainer 2", + # "0xD7D1-4": "Beat Route 10 Trainer 3", + # "0xD7D1-5": "Beat Route 10 Trainer 4", + # "0xD7D1-6": "Beat Route 10 Trainer 5", + # "0xD7D7-2": "Beat Route 12 Trainer 0", + # "0xD7D7-3": "Beat Route 12 Trainer 1", + # "0xD7D7-4": "Beat Route 12 Trainer 2", + # "0xD7D7-5": "Beat Route 12 Trainer 3", + # "0xD7D7-6": "Beat Route 12 Trainer 4", + # "0xD7D7-7": "Beat Route 12 Trainer 5", + # "0xD7D8-0": "Beat Route 12 Trainer 6", + # "0xD7DF-1": "Beat Route 16 Trainer 0", + # "0xD7DF-2": "Beat Route 16 Trainer 1", + # "0xD7DF-3": "Beat Route 16 Trainer 2", + # "0xD7DF-4": "Beat Route 16 Trainer 3", + # "0xD7DF-5": "Beat Route 16 Trainer 4", + # "0xD7DF-6": "Beat Route 16 Trainer 5", + # "0xD7E1-1": "Beat Route 17 Trainer 0", + # "0xD7E1-2": "Beat Route 17 Trainer 1", + # "0xD7E1-3": "Beat Route 17 Trainer 2", + # "0xD7E1-4": "Beat Route 17 Trainer 3", + # "0xD7E1-5": "Beat Route 17 Trainer 4", + # "0xD7E1-6": "Beat Route 17 Trainer 5", + # "0xD7E1-7": "Beat Route 17 Trainer 6", + # "0xD7E2-0": "Beat Route 17 Trainer 7", + # "0xD7E2-1": "Beat Route 17 Trainer 8", + # "0xD7E2-2": "Beat Route 17 Trainer 9", + # "0xD7D9-1": "Beat Route 13 Trainer 0", + # "0xD7D9-2": "Beat Route 13 Trainer 1", + # "0xD7D9-3": "Beat Route 13 Trainer 2", + # "0xD7D9-4": "Beat Route 13 Trainer 3", + # "0xD7D9-5": "Beat Route 13 Trainer 4", + # "0xD7D9-6": "Beat Route 13 Trainer 5", + # "0xD7D9-7": "Beat Route 13 Trainer 6", + # "0xD7DA-0": "Beat Route 13 Trainer 7", + # "0xD7DA-1": "Beat Route 13 Trainer 8", + # "0xD7DA-2": "Beat Route 13 Trainer 9", + # "0xD7DB-1": "Beat Route 14 Trainer 0", + # "0xD7DB-2": "Beat Route 14 Trainer 1", + # "0xD7DB-3": "Beat Route 14 Trainer 2", + # "0xD7DB-4": "Beat Route 14 Trainer 3", + # "0xD7DB-5": "Beat Route 14 Trainer 4", + # "0xD7DB-6": "Beat Route 14 Trainer 5", + # "0xD7DB-7": "Beat Route 14 Trainer 6", + # "0xD7DC-0": "Beat Route 14 Trainer 7", + # "0xD7DC-1": "Beat Route 14 Trainer 8", + # "0xD7DC-2": "Beat Route 14 Trainer 9", + # "0xD7DD-1": "Beat Route 15 Trainer 0", + # "0xD7DD-2": "Beat Route 15 Trainer 1", + # "0xD7DD-3": "Beat Route 15 Trainer 2", + # "0xD7DD-4": "Beat Route 15 Trainer 3", + # "0xD7DD-5": "Beat Route 15 Trainer 4", + # "0xD7DD-6": "Beat Route 15 Trainer 5", + # "0xD7DD-7": "Beat Route 15 Trainer 6", + # "0xD7DE-0": "Beat Route 15 Trainer 7", + # "0xD7DE-1": "Beat Route 15 Trainer 8", + # "0xD7DE-2": "Beat Route 15 Trainer 9", + # "0xD7E3-1": "Beat Route 18 Trainer 0", + # "0xD7E3-2": "Beat Route 18 Trainer 1", + # "0xD7E3-3": "Beat Route 18 Trainer 2", + # "0xD7E5-1": "Beat Route 19 Trainer 0", + # "0xD7E5-2": "Beat Route 19 Trainer 1", + # "0xD7E5-3": "Beat Route 19 Trainer 2", + # "0xD7E5-4": "Beat Route 19 Trainer 3", + # "0xD7E5-5": "Beat Route 19 Trainer 4", + # "0xD7E5-6": "Beat Route 19 Trainer 5", + # "0xD7E5-7": "Beat Route 19 Trainer 6", + # "0xD7E6-0": "Beat Route 19 Trainer 7", + # "0xD7E6-1": "Beat Route 19 Trainer 8", + # "0xD7E6-2": "Beat Route 19 Trainer 9", + # "0xD7E7-1": "Beat Route 20 Trainer 0", + # "0xD7E7-2": "Beat Route 20 Trainer 1", + # "0xD7E7-3": "Beat Route 20 Trainer 2", + # "0xD7E7-4": "Beat Route 20 Trainer 3", + # "0xD7E7-5": "Beat Route 20 Trainer 4", + # "0xD7E7-6": "Beat Route 20 Trainer 5", + # "0xD7E7-7": "Beat Route 20 Trainer 6", + # "0xD7E8-0": "Beat Route 20 Trainer 7", + # "0xD7E8-1": "Beat Route 20 Trainer 8", + # "0xD7E8-2": "Beat Route 20 Trainer 9", + # "0xD7E9-1": "Beat Route 21 Trainer 0", + # "0xD7E9-2": "Beat Route 21 Trainer 1", + # "0xD7E9-3": "Beat Route 21 Trainer 2", + # "0xD7E9-4": "Beat Route 21 Trainer 3", + # "0xD7E9-5": "Beat Route 21 Trainer 4", + # "0xD7E9-6": "Beat Route 21 Trainer 5", + # "0xD7E9-7": "Beat Route 21 Trainer 6", + # "0xD7EA-0": "Beat Route 21 Trainer 7", + # "0xD7EA-1": "Beat Route 21 Trainer 8", + route3_0 = TRAINER * int(read_bit(game, 0xD7C3, 2)) + route3_1 = TRAINER * int(read_bit(game, 0xD7C3, 3)) + route3_2 = TRAINER * int(read_bit(game, 0xD7C3, 4)) + route3_3 = TRAINER * int(read_bit(game, 0xD7C3, 5)) + route3_4 = TRAINER * int(read_bit(game, 0xD7C3, 6)) + route3_5 = TRAINER * int(read_bit(game, 0xD7C3, 7)) + route3_6 = TRAINER * int(read_bit(game, 0xD7C4, 0)) + route3_7 = TRAINER * int(read_bit(game, 0xD7C4, 1)) + + route4_0 = TRAINER * int(read_bit(game, 0xD7C5, 2)) + + route24_rocket = TRAINER * int(read_bit(game, 0xD7EF, 1)) + route24_0 = TRAINER * int(read_bit(game, 0xD7EF, 2)) + route24_1 = TRAINER * int(read_bit(game, 0xD7EF, 3)) + route24_2 = TRAINER * int(read_bit(game, 0xD7EF, 4)) + route24_3 = TRAINER * int(read_bit(game, 0xD7EF, 5)) + route24_4 = TRAINER * int(read_bit(game, 0xD7EF, 6)) + route24_5 = TRAINER * int(read_bit(game, 0xD7EF, 7)) + + route25_0 = TRAINER * int(read_bit(game, 0xD7F1, 1)) + route25_1 = TRAINER * int(read_bit(game, 0xD7F1, 2)) + route25_2 = TRAINER * int(read_bit(game, 0xD7F1, 3)) + route25_3 = TRAINER * int(read_bit(game, 0xD7F1, 4)) + route25_4 = TRAINER * int(read_bit(game, 0xD7F1, 5)) + route25_5 = TRAINER * int(read_bit(game, 0xD7F1, 6)) + route25_6 = TRAINER * int(read_bit(game, 0xD7F1, 7)) + route25_7 = TRAINER * int(read_bit(game, 0xD7F2, 0)) + route25_8 = TRAINER * int(read_bit(game, 0xD7F2, 1)) + + route9_0 = TRAINER * int(read_bit(game, 0xD7CF, 1)) + route9_1 = TRAINER * int(read_bit(game, 0xD7CF, 2)) + route9_2 = TRAINER * int(read_bit(game, 0xD7CF, 3)) + route9_3 = TRAINER * int(read_bit(game, 0xD7CF, 4)) + route9_4 = TRAINER * int(read_bit(game, 0xD7CF, 5)) + route9_5 = TRAINER * int(read_bit(game, 0xD7CF, 6)) + route9_6 = TRAINER * int(read_bit(game, 0xD7CF, 7)) + route9_7 = TRAINER * int(read_bit(game, 0xD7D0, 0)) + route9_8 = TRAINER * int(read_bit(game, 0xD7D0, 1)) + + route6_0 = TRAINER * int(read_bit(game, 0xD7C9, 1)) + route6_1 = TRAINER * int(read_bit(game, 0xD7C9, 2)) + route6_2 = TRAINER * int(read_bit(game, 0xD7C9, 3)) + route6_3 = TRAINER * int(read_bit(game, 0xD7C9, 4)) + route6_4 = TRAINER * int(read_bit(game, 0xD7C9, 5)) + route6_5 = TRAINER * int(read_bit(game, 0xD7C9, 6)) + + route11_0 = TRAINER * int(read_bit(game, 0xD7D5, 1)) + route11_1 = TRAINER * int(read_bit(game, 0xD7D5, 2)) + route11_2 = TRAINER * int(read_bit(game, 0xD7D5, 3)) + route11_3 = TRAINER * int(read_bit(game, 0xD7D5, 4)) + route11_4 = TRAINER * int(read_bit(game, 0xD7D5, 5)) + route11_5 = TRAINER * int(read_bit(game, 0xD7D5, 6)) + route11_6 = TRAINER * int(read_bit(game, 0xD7D5, 7)) + route11_7 = TRAINER * int(read_bit(game, 0xD7D6, 0)) + route11_8 = TRAINER * int(read_bit(game, 0xD7D6, 1)) + route11_9 = TRAINER * int(read_bit(game, 0xD7D6, 2)) + + route8_0 = TRAINER * int(read_bit(game, 0xD7CD, 1)) + route8_1 = TRAINER * int(read_bit(game, 0xD7CD, 2)) + route8_2 = TRAINER * int(read_bit(game, 0xD7CD, 3)) + route8_3 = TRAINER * int(read_bit(game, 0xD7CD, 4)) + route8_4 = TRAINER * int(read_bit(game, 0xD7CD, 5)) + route8_5 = TRAINER * int(read_bit(game, 0xD7CD, 6)) + route8_6 = TRAINER * int(read_bit(game, 0xD7CD, 7)) + route8_7 = TRAINER * int(read_bit(game, 0xD7CE, 0)) + route8_8 = TRAINER * int(read_bit(game, 0xD7CE, 1)) + + route10_0 = TRAINER * int(read_bit(game, 0xD7D1, 1)) + route10_1 = TRAINER * int(read_bit(game, 0xD7D1, 2)) + route10_2 = TRAINER * int(read_bit(game, 0xD7D1, 3)) + route10_3 = TRAINER * int(read_bit(game, 0xD7D1, 4)) + route10_4 = TRAINER * int(read_bit(game, 0xD7D1, 5)) + route10_5 = TRAINER * int(read_bit(game, 0xD7D1, 6)) + + route12_0 = TRAINER * int(read_bit(game, 0xD7D7, 2)) + route12_1 = TRAINER * int(read_bit(game, 0xD7D7, 3)) + route12_2 = TRAINER * int(read_bit(game, 0xD7D7, 4)) + route12_3 = TRAINER * int(read_bit(game, 0xD7D7, 5)) + route12_4 = TRAINER * int(read_bit(game, 0xD7D7, 6)) + route12_5 = TRAINER * int(read_bit(game, 0xD7D7, 7)) + route12_6 = TRAINER * int(read_bit(game, 0xD7D8, 0)) + + route16_0 = TRAINER * int(read_bit(game, 0xD7DF, 1)) + route16_1 = TRAINER * int(read_bit(game, 0xD7DF, 2)) + route16_2 = TRAINER * int(read_bit(game, 0xD7DF, 3)) + route16_3 = TRAINER * int(read_bit(game, 0xD7DF, 4)) + route16_4 = TRAINER * int(read_bit(game, 0xD7DF, 5)) + route16_5 = TRAINER * int(read_bit(game, 0xD7DF, 6)) + + route17_0 = TRAINER * int(read_bit(game, 0xD7E1, 1)) + route17_1 = TRAINER * int(read_bit(game, 0xD7E1, 2)) + route17_2 = TRAINER * int(read_bit(game, 0xD7E1, 3)) + route17_3 = TRAINER * int(read_bit(game, 0xD7E1, 4)) + route17_4 = TRAINER * int(read_bit(game, 0xD7E1, 5)) + route17_5 = TRAINER * int(read_bit(game, 0xD7E1, 6)) + route17_6 = TRAINER * int(read_bit(game, 0xD7E1, 7)) + route17_7 = TRAINER * int(read_bit(game, 0xD7E2, 0)) + route17_8 = TRAINER * int(read_bit(game, 0xD7E2, 1)) + route17_9 = TRAINER * int(read_bit(game, 0xD7E2, 2)) + + route13_0 = TRAINER * int(read_bit(game, 0xD7D9, 1)) + route13_1 = TRAINER * int(read_bit(game, 0xD7D9, 2)) + route13_2 = TRAINER * int(read_bit(game, 0xD7D9, 3)) + route13_3 = TRAINER * int(read_bit(game, 0xD7D9, 4)) + route13_4 = TRAINER * int(read_bit(game, 0xD7D9, 5)) + route13_5 = TRAINER * int(read_bit(game, 0xD7D9, 6)) + route13_6 = TRAINER * int(read_bit(game, 0xD7D9, 7)) + route13_7 = TRAINER * int(read_bit(game, 0xD7DA, 0)) + route13_8 = TRAINER * int(read_bit(game, 0xD7DA, 1)) + route13_9 = TRAINER * int(read_bit(game, 0xD7DA, 2)) + + route14_0 = TRAINER * int(read_bit(game, 0xD7DB, 1)) + route14_1 = TRAINER * int(read_bit(game, 0xD7DB, 2)) + route14_2 = TRAINER * int(read_bit(game, 0xD7DB, 3)) + route14_3 = TRAINER * int(read_bit(game, 0xD7DB, 4)) + route14_4 = TRAINER * int(read_bit(game, 0xD7DB, 5)) + route14_5 = TRAINER * int(read_bit(game, 0xD7DB, 6)) + route14_6 = TRAINER * int(read_bit(game, 0xD7DB, 7)) + route14_7 = TRAINER * int(read_bit(game, 0xD7DC, 0)) + route14_8 = TRAINER * int(read_bit(game, 0xD7DC, 1)) + route14_9 = TRAINER * int(read_bit(game, 0xD7DC, 2)) + + route15_0 = TRAINER * int(read_bit(game, 0xD7DD, 1)) + route15_1 = TRAINER * int(read_bit(game, 0xD7DD, 2)) + route15_2 = TRAINER * int(read_bit(game, 0xD7DD, 3)) + route15_3 = TRAINER * int(read_bit(game, 0xD7DD, 4)) + route15_4 = TRAINER * int(read_bit(game, 0xD7DD, 5)) + route15_5 = TRAINER * int(read_bit(game, 0xD7DD, 6)) + route15_6 = TRAINER * int(read_bit(game, 0xD7DD, 7)) + route15_7 = TRAINER * int(read_bit(game, 0xD7DE, 0)) + route15_8 = TRAINER * int(read_bit(game, 0xD7DE, 1)) + route15_9 = TRAINER * int(read_bit(game, 0xD7DE, 2)) + + route18_0 = TRAINER * int(read_bit(game, 0xD7E3, 1)) + route18_1 = TRAINER * int(read_bit(game, 0xD7E3, 2)) + route18_2 = TRAINER * int(read_bit(game, 0xD7E3, 3)) + + route19_0 = TRAINER * int(read_bit(game, 0xD7E5, 1)) + route19_1 = TRAINER * int(read_bit(game, 0xD7E5, 2)) + route19_2 = TRAINER * int(read_bit(game, 0xD7E5, 3)) + route19_3 = TRAINER * int(read_bit(game, 0xD7E5, 4)) + route19_4 = TRAINER * int(read_bit(game, 0xD7E5, 5)) + route19_5 = TRAINER * int(read_bit(game, 0xD7E5, 6)) + route19_6 = TRAINER * int(read_bit(game, 0xD7E5, 7)) + route19_7 = TRAINER * int(read_bit(game, 0xD7E6, 0)) + route19_8 = TRAINER * int(read_bit(game, 0xD7E6, 1)) + route19_9 = TRAINER * int(read_bit(game, 0xD7E6, 2)) + + route20_0 = TRAINER * int(read_bit(game, 0xD7E7, 1)) + route20_1 = TRAINER * int(read_bit(game, 0xD7E7, 2)) + route20_2 = TRAINER * int(read_bit(game, 0xD7E7, 3)) + route20_3 = TRAINER * int(read_bit(game, 0xD7E7, 4)) + route20_4 = TRAINER * int(read_bit(game, 0xD7E7, 5)) + route20_5 = TRAINER * int(read_bit(game, 0xD7E7, 6)) + route20_6 = TRAINER * int(read_bit(game, 0xD7E7, 7)) + route20_7 = TRAINER * int(read_bit(game, 0xD7E8, 0)) + route20_8 = TRAINER * int(read_bit(game, 0xD7E8, 1)) + route20_9 = TRAINER * int(read_bit(game, 0xD7E8, 2)) + + route21_0 = TRAINER * int(read_bit(game, 0xD7E9, 1)) + route21_1 = TRAINER * int(read_bit(game, 0xD7E9, 2)) + route21_2 = TRAINER * int(read_bit(game, 0xD7E9, 3)) + route21_3 = TRAINER * int(read_bit(game, 0xD7E9, 4)) + route21_4 = TRAINER * int(read_bit(game, 0xD7E9, 5)) + route21_5 = TRAINER * int(read_bit(game, 0xD7E9, 6)) + route21_6 = TRAINER * int(read_bit(game, 0xD7E9, 7)) + route21_7 = TRAINER * int(read_bit(game, 0xD7EA, 0)) + route21_8 = TRAINER * int(read_bit(game, 0xD7EA, 1)) + + return sum([ route3_0, route3_1, route3_2, route3_3, route3_4, route3_5, route3_6, route3_7, + route4_0, route24_rocket, route24_0, route24_1, route24_2, route24_3, route24_4, + route24_5, route25_0, route25_1, route25_2, route25_3, route25_4, route25_5, route25_6, + route25_7, route25_8, route9_0, route9_1, route9_2, route9_3, route9_4, route9_5, + route9_6, route9_7, route9_8, route6_0, route6_1, route6_2, route6_3, route6_4, + route6_5, route11_0, route11_1, route11_2, route11_3, route11_4, route11_5, route11_6, + route11_7, route11_8, route11_9, route8_0, route8_1, route8_2, route8_3, route8_4, route8_5, + route8_6, route8_7, route8_8, route10_0, route10_1, route10_2, route10_3, route10_4, route10_5, + route12_0, route12_1, route12_2, route12_3, route12_4, route12_5, route12_6, route16_0, + route16_1, route16_2, route16_3, route16_4, route16_5, route17_0, route17_1, route17_2, + route17_3, route17_4, route17_5, route17_6, route17_7, route17_8, route17_9, route13_0, + route13_1, route13_2, route13_3, route13_4, route13_5, route13_6, route13_7, route13_8, + route13_9, route14_0, route14_1, route14_2, route14_3, route14_4, route14_5, route14_6, + route14_7, route14_8, route14_9, route15_0, route15_1, route15_2, route15_3, route15_4, + route15_5, route15_6, route15_7, route15_8, route15_9, route18_0, route18_1, route18_2, + route19_0, route19_1, route19_2, route19_3, route19_4, route19_5, route19_6, route19_7, + route19_8, route19_9, route20_0, route20_1, route20_2, route20_3, route20_4, route20_5, + route20_6, route20_7, route20_8, route20_9, route21_0, route21_1, route21_2, route21_3, + route21_4, route21_5, route21_6, route21_7, route21_8]) + +def misc(game): + # "0xD7C6-7": "Bought Magikarp", + # "0xD747-3": "Hall Of Fame Dex Rating", + # "0xD74A-2": "Daisy Walking", + # "0xD754-0": "Bought Museum Ticket", + # "0xD754-1": "Got Old Amber", + # "0xD771-1": "Got Bike Voucher", + # "0xD77E-2": "Got 10 Coins", + # "0xD77E-3": "Got 20 Coins", + # "0xD77E-4": "Got 20 Coins 2", + # "0xD783-0": "Got Coin Case", + # "0xD7BF-0": "Got Potion Sample", + # "0xD7D6-7": "Got Itemfinder", + # "0xD7DD-0": "Got Exp All", + # "0xD7E0-7": "Rescued Mr Fuji", + # "0xD85F-1": "Beat Mewtwo", + # "0xD769-7": "Rescued Mr Fuji 2", + one = TASK * int(read_bit(game, 0xD7C6, 7)) + two = TASK * int(read_bit(game, 0xD747, 3)) + three = TASK * int(read_bit(game, 0xD74A, 2)) +# four = TASK * int(read_bit(game, 0xD754, 0)) + five = TASK * int(read_bit(game, 0xD754, 1)) + six = TASK * int(read_bit(game, 0xD771, 1)) + seven = TASK * int(read_bit(game, 0xD77E, 2)) + eight = TASK * int(read_bit(game, 0xD77E, 3)) + nine = TASK * int(read_bit(game, 0xD77E, 4)) + ten = TASK * int(read_bit(game, 0xD783, 0)) + eleven = TASK * int(read_bit(game, 0xD7BF, 0)) + twelve = TASK * int(read_bit(game, 0xD7D6, 7)) + thirteen = TASK * int(read_bit(game, 0xD7DD, 0)) + fourteen = TASK * int(read_bit(game, 0xD7E0, 7)) + fifteen = TASK * int(read_bit(game, 0xD85F, 1)) + sixteen = TASK * int(read_bit(game, 0xD769, 7)) + + return sum([one, two, three, five, six, seven, eight, nine, ten, eleven, twelve, thirteen, fourteen, fifteen, sixteen]) + +def snorlax(game): + # "0xD7D8-6": "Fight Route12 Snorlax", + # "0xD7D8-7": "Beat Route12 Snorlax", + # "0xD7E0-0": "Fight Route16 Snorlax", + # "0xD7E0-1": "Beat Route16 Snorlax", + route12_snorlax_fight = POKEMON * int(read_bit(game, 0xD7D8, 6)) + route12_snorlax_beat = POKEMON * int(read_bit(game, 0xD7D8, 7)) + route16_snorlax_fight = POKEMON * int(read_bit(game, 0xD7E0, 0)) + route16_snorlax_beat = POKEMON * int(read_bit(game, 0xD7E0, 1)) + + return sum([route12_snorlax_fight, route12_snorlax_beat, route16_snorlax_fight, route16_snorlax_beat]) + +def hmtm(game): + # "0xD803-0": "Got Hm01", + # "0xD7E0-6": "Got Hm02", + # "0xD857-0": "Got Hm03", + # "0xD78E-0": "Got Hm04", + # "0xD7C2-0": "Got Hm05", + + # "0xD755-6": "Got Tm34", + # "0xD75E-6": "Got Tm11", + # "0xD777-0": "Got Tm41", + # "0xD778-4": "Got Tm13", + # "0xD778-5": "Got Tm48", + # "0xD778-6": "Got Tm49", + # "0xD778-7": "Got Tm18", + # "0xD77C-0": "Got Tm21", + # "0xD792-0": "Got Tm06", + # "0xD773-6": "Got Tm24", + # "0xD7BD-0": "Got Tm29", + # "0xD7AF-0": "Got Tm31", + # "0xD7A1-7": "Got Tm35", + # "0xD826-7": "Got Tm36", + # "0xD79A-0": "Got Tm38", + # "0xD751-0": "Got Tm27", + # "0xD74C-1": "Got Tm42", + # "0xD7B3-0": "Got Tm46", + # "0xD7D7-0": "Got Tm39", + hm01 = HM * int(read_bit(game, 0xD803, 0)) + hm02 = HM * int(read_bit(game, 0xD7E0, 6)) + hm03 = HM * int(read_bit(game, 0xD857, 0)) + hm04 = HM * int(read_bit(game, 0xD78E, 0)) + hm05 = HM * int(read_bit(game, 0xD7C2, 0)) + + tm34 = TM * int(read_bit(game, 0xD755, 6)) + tm11 = TM * int(read_bit(game, 0xD75E, 6)) + tm41 = TM * int(read_bit(game, 0xD777, 0)) + tm13 = TM * int(read_bit(game, 0xD778, 4)) + tm48 = TM * int(read_bit(game, 0xD778, 5)) + tm49 = TM * int(read_bit(game, 0xD778, 6)) + tm18 = TM * int(read_bit(game, 0xD778, 7)) + tm21 = TM * int(read_bit(game, 0xD77C, 0)) + tm06 = TM * int(read_bit(game, 0xD792, 0)) + tm24 = TM * int(read_bit(game, 0xD773, 6)) + tm29 = TM * int(read_bit(game, 0xD7BD, 0)) + tm31 = TM * int(read_bit(game, 0xD7AF, 0)) + tm35 = TM * int(read_bit(game, 0xD7A1, 7)) + tm36 = TM * int(read_bit(game, 0xD826, 7)) + tm38 = TM * int(read_bit(game, 0xD79A, 0)) + tm27 = TM * int(read_bit(game, 0xD751, 0)) + tm42 = TM * int(read_bit(game, 0xD74C, 1)) + tm46 = TM * int(read_bit(game, 0xD7B3, 0)) + tm39 = TM * int(read_bit(game, 0xD7D7, 0)) + + + return sum([hm01, hm02, hm03, hm04, hm05, tm34, tm11, tm41, tm13, tm48, tm49, tm18, tm21, tm06, tm24, tm29, tm31, tm35, tm36, tm38, tm27, tm42, tm46, tm39]) + +def bill(game): + # "0xD7F1-0": "Met Bill", + # "0xD7F2-3": "Used Cell Separator On Bill", + # "0xD7F2-4": "Got Ss Ticket", + # "0xD7F2-5": "Met Bill 2", + # "0xD7F2-6": "Bill Said Use Cell Separator", + # "0xD7F2-7": "Left Bills House After Helping", + met_bill = BILL_CAPT * int(read_bit(game, 0xD7F1, 0)) + used_cell_separator_on_bill = BILL_CAPT * int(read_bit(game, 0xD7F2, 3)) + got_ss_ticket = BILL_CAPT * int(read_bit(game, 0xD7F2, 4)) + met_bill_2 = BILL_CAPT * int(read_bit(game, 0xD7F2, 5)) + bill_said_use_cell_separator = BILL_CAPT * int(read_bit(game, 0xD7F2, 6)) + left_bills_house_after_helping = BILL_CAPT * int(read_bit(game, 0xD7F2, 7)) + + + return sum([met_bill, used_cell_separator_on_bill, got_ss_ticket, met_bill_2, bill_said_use_cell_separator, left_bills_house_after_helping]) + +def oak(game): + # "0xD74B-7": "Oak Appeared In Pallet", + # "0xD747-0": "Followed Oak Into Lab", + # "0xD74B-1": "Oak Asked To Choose Mon", + # "0xD74B-2": "Got Starter", + # "0xD74B-0": "Followed Oak Into Lab 2", + # "0xD74B-5": "Got Pokedex", + # "0xD74E-1": "Got Oaks Parcel", + # "0xD747-6": "Pallet After Getting Pokeballs", + # "0xD74E-0": "Oak Got Parcel", + # "0xD74B-4": "Got Pokeballs From Oak", + # "0xD74B-6": "Pallet After Getting Pokeballs 2", + oak_appeared_in_pallet = TASK * int(read_bit(game, 0xD74B, 7)) + followed_oak_into_lab = TASK * int(read_bit(game, 0xD747, 0)) + oak_asked_to_choose_mon = TASK * int(read_bit(game, 0xD74B, 1)) + got_starter = TASK * int(read_bit(game, 0xD74B, 2)) + followed_oak_into_lab_2 = TASK * int(read_bit(game, 0xD74B, 0)) + got_pokedex = QUEST * int(read_bit(game, 0xD74B, 5)) + got_oaks_parcel = QUEST * int(read_bit(game, 0xD74E, 1)) + pallet_after_getting_pokeballs = QUEST * int(read_bit(game, 0xD747, 6)) + oak_got_parcel = QUEST * int(read_bit(game, 0xD74E, 0)) + got_pokeballs_from_oak = TASK * int(read_bit(game, 0xD74B, 4)) + pallet_after_getting_pokeballs_2 = TASK * int(read_bit(game, 0xD74B, 6)) + + return sum([oak_appeared_in_pallet, followed_oak_into_lab, oak_asked_to_choose_mon, got_starter, followed_oak_into_lab_2, got_pokedex, + got_oaks_parcel, pallet_after_getting_pokeballs, oak_got_parcel, got_pokeballs_from_oak, pallet_after_getting_pokeballs_2]) + +def towns(game): + # "0xD74A-0": "Got Town Map", + # "0xD74A-1": "Entered Blues House", + # "0xD7F3-2": "Beat Viridian Forest Trainer 0", + # "0xD7F3-3": "Beat Viridian Forest Trainer 1", + # "0xD7F3-4": "Beat Viridian Forest Trainer 2", + # "0xD7EF-0": "Got Nugget", + # "0xD7F0-1": "Nugget Reward Available", + # "0xD75B-7": "Beat Cerulean Rocket Thief", + # "0xD75F-0": "Got Bicycle", + # "0xD771-6": "Seel Fan Boast", + # "0xD771-7": "Pikachu Fan Boast", + # "0xD76C-0": "Got Poke Flute", + got_town_map = TASK * int(read_bit(game, 0xD74A, 0)) + entered_blues_house = TASK * int(read_bit(game, 0xD74A, 1)) + beat_viridian_forest_trainer_0 = TRAINER * int(read_bit(game, 0xD7F3, 2)) + beat_viridian_forest_trainer_1 = TRAINER * int(read_bit(game, 0xD7F3, 3)) + beat_viridian_forest_trainer_2 = TRAINER * int(read_bit(game, 0xD7F3, 4)) + got_nugget = TASK * int(read_bit(game, 0xD7EF, 0)) + nugget_reward_available = TASK * int(read_bit(game, 0xD7F0, 1)) + beat_cerulean_rocket_thief = TRAINER * int(read_bit(game, 0xD75B, 7)) + got_bicycle = QUEST * int(read_bit(game, 0xD75F, 0)) + seel_fan_boast = TASK * int(read_bit(game, 0xD771, 6)) + pikachu_fan_boast = TASK * int(read_bit(game, 0xD771, 7)) + got_poke_flute = QUEST * int(read_bit(game, 0xD76C, 0)) + + return sum([got_town_map, entered_blues_house, beat_viridian_forest_trainer_0, + beat_viridian_forest_trainer_1, beat_viridian_forest_trainer_2, got_nugget, + nugget_reward_available, beat_cerulean_rocket_thief, got_bicycle, + seel_fan_boast, pikachu_fan_boast, got_poke_flute]) + +def lab(game): + # "0xD7A3-0": "Gave Fossil To Lab", + # "0xD7A3-1": "Lab Still Reviving Fossil", + # "0xD7A3-2": "Lab Handing Over Fossil Mon", + gave_fossil_to_lab = TASK * int(read_bit(game, 0xD7A3, 0)) + lab_still_reviving_fossil = TASK * int(read_bit(game, 0xD7A3, 1)) + lab_handing_over_fossil_mon = TASK * int(read_bit(game, 0xD7A3, 2)) + + return sum([gave_fossil_to_lab, lab_still_reviving_fossil, lab_handing_over_fossil_mon]) + +def mansion(game): + # "0xD847-1": "Beat Mansion 2 Trainer 0", + # "0xD849-1": "Beat Mansion 3 Trainer 0", + # "0xD849-2": "Beat Mansion 3 Trainer 1", + # "0xD84B-1": "Beat Mansion 4 Trainer 0", + # "0xD84B-2": "Beat Mansion 4 Trainer 1", + # "0xD796-0": "Mansion Switch On", + # "0xD798-1": "Beat Mansion 1 Trainer 0", + beat_mansion_2_trainer_0 = TRAINER * int(read_bit(game, 0xD847, 1)) + beat_mansion_3_trainer_0 = TRAINER * int(read_bit(game, 0xD849, 1)) + beat_mansion_3_trainer_1 = TRAINER * int(read_bit(game, 0xD849, 2)) + beat_mansion_4_trainer_0 = TRAINER * int(read_bit(game, 0xD84B, 1)) + beat_mansion_4_trainer_1 = TRAINER * int(read_bit(game, 0xD84B, 2)) + mansion_switch_on = QUEST * int(read_bit(game, 0xD796, 0)) + beat_mansion_1_trainer_0 = TRAINER * int(read_bit(game, 0xD798, 1)) + + + return sum([beat_mansion_2_trainer_0, beat_mansion_3_trainer_0, beat_mansion_3_trainer_1, + beat_mansion_4_trainer_0, beat_mansion_4_trainer_1, mansion_switch_on, beat_mansion_1_trainer_0]) + +def safari(game): + # "0xD78E-1": "Gave Gold Teeth", + # "0xD790-6": "Safari Game Over", + # "0xD790-7": "In Safari Zone", + gave_gold_teeth = QUEST * int(read_bit(game, 0xD78E, 1)) + safari_game_over = EVENT * int(read_bit(game, 0xD790, 6)) + in_safari_zone = EVENT * int(read_bit(game, 0xD790, 7)) + + return sum([gave_gold_teeth, safari_game_over, in_safari_zone]) + +def dojo(game): + # "0xD7B1-0": "Defeated Fighting Dojo", + # "0xD7B1-1": "Beat Karate Master", + # "0xD7B1-2": "Beat Fighting Dojo Trainer 0", + # "0xD7B1-3": "Beat Fighting Dojo Trainer 1", + # "0xD7B1-4": "Beat Fighting Dojo Trainer 2", + # "0xD7B1-5": "Beat Fighting Dojo Trainer 3", + # "0xD7B1-6": "Got Hitmonlee", + # "0xD7B1-7": "Got Hitmonchan", + defeated_fighting_dojo = BAD * int(read_bit(game, 0xD7B1, 0)) + beat_karate_master = GYM_LEADER * int(read_bit(game, 0xD7B1, 1)) + beat_dojo_trainer_0 = TRAINER * int(read_bit(game, 0xD7B1, 2)) + beat_dojo_trainer_1 = TRAINER * int(read_bit(game, 0xD7B1, 3)) + beat_dojo_trainer_2 = TRAINER * int(read_bit(game, 0xD7B1, 4)) + beat_dojo_trainer_3 = TRAINER * int(read_bit(game, 0xD7B1, 5)) + got_hitmonlee = POKEMON * int(read_bit(game, 0xD7B1, 6)) + got_hitmonchan = POKEMON * int(read_bit(game, 0xD7B1, 7)) + + return sum([defeated_fighting_dojo, beat_karate_master, beat_dojo_trainer_0, + beat_dojo_trainer_1, beat_dojo_trainer_2, beat_dojo_trainer_3, + got_hitmonlee, got_hitmonchan]) + +def hideout(game): + # "0xD815-1": "Beat Rocket Hideout 1 Trainer 0", + # "0xD815-2": "Beat Rocket Hideout 1 Trainer 1", + # "0xD815-3": "Beat Rocket Hideout 1 Trainer 2", + # "0xD815-4": "Beat Rocket Hideout 1 Trainer 3", + # "0xD815-5": "Beat Rocket Hideout 1 Trainer 4", + # "0xD817-1": "Beat Rocket Hideout 2 Trainer 0", + # "0xD819-1": "Beat Rocket Hideout 3 Trainer 0", + # "0xD819-2": "Beat Rocket Hideout 3 Trainer 1", + # "0xD81B-2": "Beat Rocket Hideout 4 Trainer 0", + # "0xD81B-3": "Beat Rocket Hideout 4 Trainer 1", + # "0xD81B-4": "Beat Rocket Hideout 4 Trainer 2", + # "0xD81B-5": "Rocket Hideout 4 Door Unlocked", + # "0xD81B-6": "Rocket Dropped Lift Key", + # "0xD81B-7": "Beat Rocket Hideout Giovanni", + # "0xD77E-1": "Found Rocket Hideout", + beat_rocket_hideout_1_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD815, 1)) + beat_rocket_hideout_1_trainer_1 = GYM_TRAINER * int(read_bit(game, 0xD815, 2)) + beat_rocket_hideout_1_trainer_2 = GYM_TRAINER * int(read_bit(game, 0xD815, 3)) + beat_rocket_hideout_1_trainer_3 = GYM_TRAINER * int(read_bit(game, 0xD815, 4)) + beat_rocket_hideout_1_trainer_4 = GYM_TRAINER * int(read_bit(game, 0xD815, 5)) + beat_rocket_hideout_2_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD817, 1)) + beat_rocket_hideout_3_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD819, 1)) + beat_rocket_hideout_3_trainer_1 = GYM_TRAINER * int(read_bit(game, 0xD819, 2)) + beat_rocket_hideout_4_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD81B, 2)) + beat_rocket_hideout_4_trainer_1 = GYM_TRAINER * int(read_bit(game, 0xD81B, 3)) + beat_rocket_hideout_4_trainer_2 = GYM_TRAINER * int(read_bit(game, 0xD81B, 4)) + rocket_hideout_4_door_unlocked = QUEST * int(read_bit(game, 0xD81B, 5)) + rocket_dropped_lift_key = QUEST * int(read_bit(game, 0xD81B, 6)) + beat_rocket_hideout_giovanni = GYM_LEADER * int(read_bit(game, 0xD81B, 7)) + found_rocket_hideout = QUEST * int(read_bit(game, 0xD77E, 1)) + + return sum([beat_rocket_hideout_1_trainer_0, beat_rocket_hideout_1_trainer_1, beat_rocket_hideout_1_trainer_2, beat_rocket_hideout_1_trainer_3, + beat_rocket_hideout_1_trainer_4, beat_rocket_hideout_2_trainer_0, beat_rocket_hideout_3_trainer_0, beat_rocket_hideout_3_trainer_1, + beat_rocket_hideout_4_trainer_0, beat_rocket_hideout_4_trainer_1, beat_rocket_hideout_4_trainer_2, rocket_hideout_4_door_unlocked, + rocket_dropped_lift_key, beat_rocket_hideout_giovanni, found_rocket_hideout]) + +def poke_tower(game): + # "0xD765-1": "Beat Pokemontower 3 Trainer 0", + # "0xD765-2": "Beat Pokemontower 3 Trainer 1", + # "0xD765-3": "Beat Pokemontower 3 Trainer 2", + # "0xD766-1": "Beat Pokemontower 4 Trainer 0", + # "0xD766-2": "Beat Pokemontower 4 Trainer 1", + # "0xD766-3": "Beat Pokemontower 4 Trainer 2", + # "0xD767-2": "Beat Pokemontower 5 Trainer 0", + # "0xD767-3": "Beat Pokemontower 5 Trainer 1", + # "0xD767-4": "Beat Pokemontower 5 Trainer 2", + # "0xD767-5": "Beat Pokemontower 5 Trainer 3", + # "0xD767-7": "In Purified Zone", + # "0xD768-1": "Beat Pokemontower 6 Trainer 0", + # "0xD768-2": "Beat Pokemontower 6 Trainer 1", + # "0xD768-3": "Beat Pokemontower 6 Trainer 2", + # "0xD768-7": "Beat Ghost Marowak", + # "0xD769-1": "Beat Pokemontower 7 Trainer 0", + # "0xD769-2": "Beat Pokemontower 7 Trainer 1", + # "0xD769-3": "Beat Pokemontower 7 Trainer 2", + beat_pokemontower_3_trainer_0 = TRAINER * int(read_bit(game, 0xD765, 1)) + beat_pokemontower_3_trainer_1 = TRAINER * int(read_bit(game, 0xD765, 2)) + beat_pokemontower_3_trainer_2 = TRAINER * int(read_bit(game, 0xD765, 3)) + beat_pokemontower_4_trainer_0 = TRAINER * int(read_bit(game, 0xD766, 1)) + beat_pokemontower_4_trainer_1 = TRAINER * int(read_bit(game, 0xD766, 2)) + beat_pokemontower_4_trainer_2 = TRAINER * int(read_bit(game, 0xD766, 3)) + beat_pokemontower_5_trainer_0 = TRAINER * int(read_bit(game, 0xD767, 2)) + beat_pokemontower_5_trainer_1 = TRAINER * int(read_bit(game, 0xD767, 3)) + beat_pokemontower_5_trainer_2 = TRAINER * int(read_bit(game, 0xD767, 4)) + beat_pokemontower_5_trainer_3 = TRAINER * int(read_bit(game, 0xD767, 5)) +# in_purified_zone = EVENT * int(read_bit(game, 0xD767, 7)) # purified zone + beat_pokemontower_6_trainer_0 = TRAINER * int(read_bit(game, 0xD768, 1)) + beat_pokemontower_6_trainer_1 = TRAINER * int(read_bit(game, 0xD768, 2)) + beat_pokemontower_6_trainer_2 = TRAINER * int(read_bit(game, 0xD768, 3)) + beat_ghost_marowak = QUEST * int(read_bit(game, 0xD768, 7)) + beat_pokemontower_7_trainer_0 = TRAINER * int(read_bit(game, 0xD769, 1)) + beat_pokemontower_7_trainer_1 = TRAINER * int(read_bit(game, 0xD769, 2)) + beat_pokemontower_7_trainer_2 = TRAINER * int(read_bit(game, 0xD769, 3)) + + return sum([beat_pokemontower_3_trainer_0, beat_pokemontower_3_trainer_1, beat_pokemontower_3_trainer_2, beat_pokemontower_4_trainer_0, + beat_pokemontower_4_trainer_1, beat_pokemontower_4_trainer_2, beat_pokemontower_5_trainer_0, beat_pokemontower_5_trainer_1, + beat_pokemontower_5_trainer_2, beat_pokemontower_5_trainer_3, beat_pokemontower_6_trainer_0, + beat_pokemontower_6_trainer_1, beat_pokemontower_6_trainer_2, beat_ghost_marowak, beat_pokemontower_7_trainer_0, + beat_pokemontower_7_trainer_1, beat_pokemontower_7_trainer_2]) # in_purified_zone, + +def gym1(game): + #gym 1 Pewter + one = GYM_LEADER * int(read_bit(game, 0xD755, 7)) + g1_1 = GYM_TRAINER * int(read_bit(game, 0xD755, 2)) # "0xD755-2": "Beat Pewter Gym Trainer 0", + return sum([one, g1_1, ]) + +def gym2(game): + #gym 2 Cerulean + two = GYM_LEADER * int(read_bit(game, 0xD75E, 7)) + g2_1 = GYM_TRAINER * int(read_bit(game, 0xD75E, 2)) # "0xD75E-2": "Beat Cerulean Gym Trainer 0", + g2_2 = GYM_TRAINER * int(read_bit(game, 0xD75E, 3)) # "0xD75E-3": "Beat Cerulean Gym Trainer 1", + return sum([two, g2_1, g2_2, ]) + +def gym3(game): + #gym 3 Vermilion + lock_one = GYM_TASK * int(read_bit(game, 0xD773, 1)) # "0xD773-1": "1S Lock Opened", + lock_two = GYM_TASK * int(read_bit(game, 0xD773, 0))# "0xD773-0": "2Nd Lock Opened", + three = GYM_LEADER * int(read_bit(game, 0xD773, 7)) + g3_1 = GYM_TRAINER * int(read_bit(game, 0xD773, 2)) # "0xD773-2": "Beat Vermilion Gym Trainer 0", + g3_2 = GYM_TRAINER * int(read_bit(game, 0xD773, 3)) # "0xD773-3": "Beat Vermilion Gym Trainer 1", + g3_3 = GYM_TRAINER * int(read_bit(game, 0xD773, 4)) # "0xD773-4": "Beat Vermilion Gym Trainer 2", + return sum([three, g3_1, g3_2, g3_3, lock_one, lock_two]) + +def gym4(game): + #gym 4 Celadon + four = GYM_LEADER * int(read_bit(game, 0xD792, 1)) + g4_1 = GYM_TRAINER * int(read_bit(game, 0xD77C, 2)) # "0xD77C-2": "Beat Celadon Gym Trainer 0", + g4_2 = GYM_TRAINER * int(read_bit(game, 0xD77C, 3)) # "0xD77C-3": "Beat Celadon Gym Trainer 1", + g4_3 = GYM_TRAINER * int(read_bit(game, 0xD77C, 4)) # "0xD77C-4": "Beat Celadon Gym Trainer 2", + g4_4 = GYM_TRAINER * int(read_bit(game, 0xD77C, 5)) # "0xD77C-5": "Beat Celadon Gym Trainer 3", + g4_5 = GYM_TRAINER * int(read_bit(game, 0xD77C, 6)) # "0xD77C-6": "Beat Celadon Gym Trainer 4", + g4_6 = GYM_TRAINER * int(read_bit(game, 0xD77C, 7)) # "0xD77C-7": "Beat Celadon Gym Trainer 5", + g4_7 = GYM_TRAINER * int(read_bit(game, 0xD77D, 0)) # "0xD77D-0": "Beat Celadon Gym Trainer 6", + return sum([four, g4_1, g4_2, g4_3, g4_4, g4_5, g4_6, g4_7, ]) + +def gym5(game): + #gym 5 Fuchsia + five = GYM_LEADER * int(read_bit(game, 0xD7B3, 1)) + g5_1 = GYM_TRAINER * int(read_bit(game, 0xD792, 2)) # "0xD792-2": "Beat Fuchsia Gym Trainer 0", + g5_2 = GYM_TRAINER * int(read_bit(game, 0xD792, 3)) # "0xD792-3": "Beat Fuchsia Gym Trainer 1", + g5_3 = GYM_TRAINER * int(read_bit(game, 0xD792, 4)) # "0xD792-4": "Beat Fuchsia Gym Trainer 2", + g5_4 = GYM_TRAINER * int(read_bit(game, 0xD792, 5)) # "0xD792-5": "Beat Fuchsia Gym Trainer 3", + g5_5 = GYM_TRAINER * int(read_bit(game, 0xD792, 6)) # "0xD792-6": "Beat Fuchsia Gym Trainer 4", + g5_6 = GYM_TRAINER * int(read_bit(game, 0xD792, 7)) # "0xD792-7": "Beat Fuchsia Gym Trainer 5", + return sum([five, g5_1, g5_2, g5_3, g5_4, g5_5, g5_6, ]) + +def gym6(game): + #gym 6 Saffron + six = GYM_LEADER * int(read_bit(game, 0xD7B3, 1)) + g6_1 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 2)) # "0xD7B3-2": "Beat Saffron Gym Trainer 0", + g6_2 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 3)) # "0xD7B3-3": "Beat Saffron Gym Trainer 1", + g6_3 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 4)) # "0xD7B3-4": "Beat Saffron Gym Trainer 2", + g6_4 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 5)) # "0xD7B3-5": "Beat Saffron Gym Trainer 3", + g6_5 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 6)) # "0xD7B3-6": "Beat Saffron Gym Trainer 4", + g6_6 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 7)) # "0xD7B3-7": "Beat Saffron Gym Trainer 5", + g6_7 = GYM_TRAINER * int(read_bit(game, 0xD7B4, 0)) # "0xD7B4-0": "Beat Saffron Gym Trainer 6", + return sum([six, g6_1, g6_2, g6_3, g6_4, g6_5, g6_6, g6_7, ]) + +def gym7(game): + #gym 7 Cinnabar + # "0xD79C-0": "Cinnabar Gym Gate0 Unlocked", + # "0xD79C-1": "Cinnabar Gym Gate1 Unlocked", + # "0xD79C-2": "Cinnabar Gym Gate2 Unlocked", + # "0xD79C-3": "Cinnabar Gym Gate3 Unlocked", + # "0xD79C-4": "Cinnabar Gym Gate4 Unlocked", + # "0xD79C-5": "Cinnabar Gym Gate5 Unlocked", + # "0xD79C-6": "Cinnabar Gym Gate6 Unlocked", + seven = GYM_LEADER * int(read_bit(game, 0xD79A, 1)) + g7_1 = GYM_TRAINER * int(read_bit(game, 0xD79A, 2)) # "0xD79A-2": "Beat Cinnabar Gym Trainer 0", + g7_2 = GYM_TRAINER * int(read_bit(game, 0xD79A, 3)) # "0xD79A-3": "Beat Cinnabar Gym Trainer 1", + g7_3 = GYM_TRAINER * int(read_bit(game, 0xD79A, 4)) # "0xD79A-4": "Beat Cinnabar Gym Trainer 2", + g7_4 = GYM_TRAINER * int(read_bit(game, 0xD79A, 5)) # "0xD79A-5": "Beat Cinnabar Gym Trainer 3", + g7_5 = GYM_TRAINER * int(read_bit(game, 0xD79A, 6)) # "0xD79A-6": "Beat Cinnabar Gym Trainer 4", + g7_6 = GYM_TRAINER * int(read_bit(game, 0xD79A, 7)) # "0xD79A-7": "Beat Cinnabar Gym Trainer 5", + g7_7 = GYM_TRAINER * int(read_bit(game, 0xD79B, 0)) # "0xD79B-0": "Beat Cinnabar Gym Trainer 6", + + return sum([seven, g7_1, g7_2, g7_3, g7_4, g7_5, g7_6, g7_7, ]) + +def gym8(game): + #gym 8 Viridian + # "0xD74C-0": "Viridian Gym Open", + gym_door = GYM_TASK * int(read_bit(game, 0xD74C, 0)) + eight = GYM_LEADER * int(read_bit(game, 0xD751, 1)) + g8_1 = GYM_TRAINER * int(read_bit(game, 0xD751, 2)) # "0xD751-2": "Beat Viridian Gym Trainer 0", + g8_2 = GYM_TRAINER * int(read_bit(game, 0xD751, 3)) # "0xD751-3": "Beat Viridian Gym Trainer 1", + g8_3 = GYM_TRAINER * int(read_bit(game, 0xD751, 4)) # "0xD751-4": "Beat Viridian Gym Trainer 2", + g8_4 = GYM_TRAINER * int(read_bit(game, 0xD751, 5)) # "0xD751-5": "Beat Viridian Gym Trainer 3", + g8_5 = GYM_TRAINER * int(read_bit(game, 0xD751, 6)) # "0xD751-6": "Beat Viridian Gym Trainer 4", + g8_6 = GYM_TRAINER * int(read_bit(game, 0xD751, 7)) # "0xD751-7": "Beat Viridian Gym Trainer 5", + g8_7 = GYM_TRAINER * int(read_bit(game, 0xD752, 0)) # "0xD752-0": "Beat Viridian Gym Trainer 6", + g8_8 = GYM_TRAINER * int(read_bit(game, 0xD752, 1)) # "0xD752-1": "Beat Viridian Gym Trainer 7", + return sum([eight, g8_1, g8_2, g8_3, g8_4, g8_5, g8_6, g8_7, g8_8, gym_door]) + +def rival(game): + one = RIVAL * int(read_bit(game, 0xD74B, 3)) + two = RIVAL * int(read_bit(game, 0xD7EB, 0)) + three = RIVAL * int(read_bit(game, 0xD7EB, 1)) + four = RIVAL * int(read_bit(game, 0xD7EB, 5)) + five = RIVAL * int(read_bit(game, 0xD7EB, 6)) + six = RIVAL * int(read_bit(game, 0xD75A, 0)) + seven = RIVAL * int(read_bit(game, 0xD764, 6)) + eight = RIVAL * int(read_bit(game, 0xD764, 7)) + nine = RIVAL * int(read_bit(game, 0xD7EB, 7)) + Beat_Silph_Co_Rival = RIVAL * int(read_bit(game, 0xD82F, 0)) + + return sum([one, two, three, four, five, six, seven, eight, nine, Beat_Silph_Co_Rival]) + +###################################################################################################### + +def bcd(num): + return 10 * ((num >> 4) & 0x0F) + (num & 0x0F) + +def bit_count(bits): + return bin(bits).count("1") + +def read_bit(game, addr, bit) -> bool: + # add padding so zero will read '0b100000000' instead of '0b0' + return bin(256 + game.get_memory_value(addr))[-bit - 1] == "1" + +def mem_val(game, addr): + mem = game.get_memory_value(addr) + return mem + +def read_uint16(game, start_addr): + """Read 2 bytes""" + val_256 = game.get_memory_value(start_addr) + val_1 = game.get_memory_value(start_addr + 1) + return 256 * val_256 + val_1 + +###################################################################################################### + +def get_hm_count(game): + hm_ids = [0xC4, 0xC5, 0xC6, 0xC7, 0xC8] + items = get_items_in_bag(game) + total_hm_cnt = 0 + for hm_id in hm_ids: + if hm_id in items: + total_hm_cnt += 1 + return total_hm_cnt * 1 + +def get_items_in_bag(game, one_indexed=0): + first_item = 0xD31E + item_ids = [] + for i in range(0, 40, 2): + item_id = game.get_memory_value(first_item + i) + if item_id == 0 or item_id == 0xff: + break + item_ids.append(item_id + one_indexed) + return item_ids + +def position(game): + r_pos = game.get_memory_value(Y_POS_ADDR) + c_pos = game.get_memory_value(X_POS_ADDR) + map_n = game.get_memory_value(MAP_N_ADDR) + if r_pos >= 443: + r_pos = 444 + if r_pos <= 0: + r_pos = 0 + if c_pos >= 443: + c_pos = 444 + if c_pos <= 0: + c_pos = 0 + if map_n > 247: + map_n = 247 + if map_n < -1: + map_n = -1 + return r_pos, c_pos, map_n + +def party(game): + # party = [game.get_memory_value(addr) for addr in PARTY_ADDR] + party_size = game.get_memory_value(PARTY_SIZE_ADDR) + party_levels = [x for x in [game.get_memory_value(addr) for addr in PARTY_LEVEL_ADDR] if x > 0] + return party_size, party_levels # [x for x in party_levels if x > 0] + +def hp(game): + """Percentage of total party HP""" + party_hp = [read_uint16(game, addr) for addr in HP_ADDR] + party_max_hp = [read_uint16(game, addr) for addr in MAX_HP_ADDR] + # Avoid division by zero if no pokemon + sum_max_hp = sum(party_max_hp) + if sum_max_hp == 0: + return 1 + return sum(party_hp) / sum_max_hp + +def used_cut(game): + return game.get_memory_value(WCUTTILE) + +def write_mem(game, addr, value): + mem = game.set_memory_value(addr, value) + return mem + +def badges(game): + badges = game.get_memory_value(BADGE_1_ADDR) + return bit_count(badges) + + + + + + + + + + + + From 16c438f16be869da4ca6a76bedd6e8599b881fbf Mon Sep 17 00:00:00 2001 From: leanke Date: Sun, 31 Mar 2024 10:53:44 +0000 Subject: [PATCH 14/29] new ram_map --- pokegym/States/bulba/Bulbasaur.state | Bin 0 -> 142610 bytes pokegym/States/bulba/Pewter.state | Bin 0 -> 142610 bytes pokegym/States/bulba/bill.state | Bin 0 -> 142610 bytes pokegym/States/bulba/celedon.state | Bin 0 -> 142610 bytes pokegym/States/bulba/cerulean.state | Bin 0 -> 142610 bytes pokegym/States/bulba/cut_surf_strength.state | Bin 0 -> 142610 bytes pokegym/States/bulba/fuschia.state | Bin 0 -> 142610 bytes pokegym/States/bulba/lavender.state | Bin 0 -> 142610 bytes pokegym/States/bulba/mt_moon.state | Bin 0 -> 142610 bytes pokegym/States/bulba/poke_flute.state | Bin 0 -> 142610 bytes pokegym/States/bulba/rock_tunnel.state | Bin 0 -> 142610 bytes pokegym/States/bulba/saffron.state | Bin 0 -> 142610 bytes pokegym/States/bulba/vermilion.state | Bin 0 -> 142610 bytes pokegym/environment.py | 356 ++++++++++--------- pokegym/glitch/a_key_map.txt | 8 + pokegym/newram_map.py | 264 +------------- 16 files changed, 195 insertions(+), 433 deletions(-) create mode 100644 pokegym/States/bulba/Bulbasaur.state create mode 100644 pokegym/States/bulba/Pewter.state create mode 100644 pokegym/States/bulba/bill.state create mode 100644 pokegym/States/bulba/celedon.state create mode 100644 pokegym/States/bulba/cerulean.state create mode 100644 pokegym/States/bulba/cut_surf_strength.state create mode 100644 pokegym/States/bulba/fuschia.state create mode 100644 pokegym/States/bulba/lavender.state create mode 100644 pokegym/States/bulba/mt_moon.state create mode 100644 pokegym/States/bulba/poke_flute.state create mode 100644 pokegym/States/bulba/rock_tunnel.state create mode 100644 pokegym/States/bulba/saffron.state create mode 100644 pokegym/States/bulba/vermilion.state create mode 100644 pokegym/glitch/a_key_map.txt diff --git a/pokegym/States/bulba/Bulbasaur.state b/pokegym/States/bulba/Bulbasaur.state new file mode 100644 index 0000000000000000000000000000000000000000..c2457671378b27d3a04060cfdd56775eaa7e8405 GIT binary patch literal 142610 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 literal 0 HcmV?d00001 diff --git a/pokegym/States/bulba/bill.state b/pokegym/States/bulba/bill.state new file mode 100644 index 0000000000000000000000000000000000000000..f40b309960d2b19334667f116884beaf2c1e402d GIT binary patch literal 142610 zcmeHw4PaEowg2o#vdL!22S!=Kk_{wCEGR)xLW$WJsGuSQ9|#pCsrst5DADqlC~NMb z*hWP`)QWx7rhRIyichd=qMX%NuWARO1_>`ERWWf7tcNEj8gi#J_ryf9iOjx7b^Z{+=+j z&^m8np3C8|@n(0}IqfXm)ACwqPxYSa&d#!6M~BVk;_;90mDi4|RT`FD+|g0R$6Z{9domr|{Hz=FPKb<0e?i2`@vnmVxp}sce;3rxBL^`5 zHk0H;G9&s>An z&SoBd&|*H8VE#MKfKX(urjpMZx3&388>CV|NiK*7O#6;&7?6N z&%qD&*1jIyUm9{Btl9T&bv61Im6rKN)Ox(=-`2wSkiR-K#@C?S7rjw&d&m09eIC!d zVQ1vd=&Xo4G%@_!aCNoI%@zf*puJaWJj4hC&Dzfz}+^iA~L%6A#=-)Vg8$XCh!z0fy#h=-g% z?}XRysfP0>wtts4wlpr``?oe?jo8DkaA{X**WoT@sc%Z_*OkkBQ*{5a;SXAW5cz8O z1N6uFc@l;FyQw8a_Aj14p=&~~g(^dp+WEt)r_@_o=&gqRd$Cf_tEVN@k`wa4{=I;o zpH-zpeZH%en`im>J}E5?yIR(T!@DAn+!*Hjq@_ixeyAUxuVubcr&TZ1PdHqTnRXI!;+a3IhWT+_L3&s87neDR=%uYWL)f3&Zn#0&c< zJ3BOk$GOdfa6INz9!8(Uth+37i09EQ&WVPDx0cV0n(i+?!rdfyH2 zxu&k8qa)bf)DH0*`){%`2hJa3|E=e5FF1d+_t*J8eE(?gueD+0{i9`oW6a-POBxM- z?fmJQGO1>qa-p&S{Xf`Ob1?Ewq$J*-CvP&}!wKh)f4z>svw)mG{QPg=yH`Db;H?Zb zTvQ4>u*?TMyhGYrU?TBHXf?S7#vBQfF*WUD!(jEPL_uoG*hcWOC z&TjxdrGYVg1M?dIe}gLZ%8jjG_xXl-^9ynd9F9nRc;?deJcCje&mO=u#|A8>v01iBIV@+6Ffi^$h0Al`p2k{1{BHRGH-U{DI{QVbN z2RDE{q0qX6>vx9TZVkQI4M0OLb_3w2x*C6eF5d%r4#(jO;06;u$S)BywD@Bu2;2Z- z{@M+orBPYNvp>n}9$pIfhtjUdUVZ~O5E&fy2LiBu%^z<7)E{mDM*N;hXF&X$X7GK} z%y%h||KL7|U)$e!17J+s-*^K+{Jmbji{tPQ8TiLWP#OWh*a(p>j(_v=%i!&=`yU9y z4S?U^!Uq1Z1K^B-cWAsn#UIAd@Ymw!Z&w|EeFv#G09e0v25C2dB^O6Gww^!9H*C0e z|Ks?({0~Gv`tZ3I_4(s(0K{M2flKB`f3D1&;B$Mm`{&`kVSWSn@S{?GgV5?H{sut( zdHJjd{NdypuGWu>7bU*|GzH7}`%kYQ^#n0)0I+@@zi|Vo9q+4D=C{sKW@#AuSA{)Y zyB2*EUKx6=vlA-7IDym~fbMVH0DM)x%Eo2>3zZo@AM8K=V)AtDul;?*89vD0<@`1d z{;?YX`r{2?h#PMJUJv9j?BBC4G=qQEfp;*-@ATvb5LDm4u^WIR`oqZTBlGe{-5TEG4)Gf_)GzwO`SZ63yuUSnIDfvUtdHIuouiz|m!ZA?Cr96o-W8b{ z&Iwhk(K8l*1Aq~Y8^HUOp+Ho-0eC$V)cT(hn#L1&TkD<5$)LP))(_6l zU$DIX=?#8UZ2#i7o@+SSiS+`s?`(~KGd1EN2?#1;xUz`@6 zcJ-pxbG_xhiqhd(`8HlZTbDliL37)t84VkQOBYhrk^*G-+ zj(_@|%yJ-yz-)LAAu+02te}>9U=?-1Wt&*3M~ zgP*VWUZ|pzy27vpApMmNPfS*6lwVuz<@1e%|GZW6A2)_Z~`-k2D zh`)CJG#>etf3|Oe*Q@(?={JCc^HaS6sP&_tfAIQJ&mXvfd54eY?=Khs>nG%H3exk( z@87Z|Kfk3V7>w}U$5?Bt%T-;SoxNp?-w(XUQ&Y2e@z$-8h|2{$#2pUD^nO3^TX@s# zBatmzTrRh}iZ`%_@!>(e)^Bai&erU~Cf1_S$^IwV4tC2Ohn!tmo9hA`>gLV`p_+Y{ z9c>mwM{mkFnMjZWfKQw;4ZnIK3OF-uS&R+g;jp6WkBo!Js)xpGtG zrqeckWm8vHq&vHNSNGiE|22H+@PotApVFF|R^Dz|bmOcWXZh{%pTD0G|M`?Xa_$T4 zV|K+IzjOYTufLVOX!(ovWBYFQM&S?3YU^G*bbHq`H|==(@Z5cW+y3$=E7<4kU;Ohq z^RpkZ-Rxh1=i@)xs^QzRJ$uK{9e|Vm;e<1|?(Ko?O-I%pv0NAb0b4)}oWOka=CaL| z8%Og`*=FD7Xvea|9qx{*cSO2&_^T^F-|IPh;^J>Ctp8+1ZCU(BjYXZ} z)o2aGR`bd?SG>YMyI+a!ZjII`0c|ueF>AkhcK!}!zjDa8_wvt_|Ly*B;Q1B5`tGkD z-n={Vqma^)pK~?PU^Eb7K^EWx9QvOl9XDO^?9-P_`y}wrZU0!YOZnUzaYQX%=3o}C zw_La4lW%mVdp+8rVFR z#iOj`AATD=^`)j~nxDAIebda^a<9jhe`c-qtJ%)8U1z&%f&ePd8Qp(RkEaMMNulkw;4+dfVbLK2uxMRnIyfOByV`lZH!Z}sHZn|LU zp=(>mKiF|?^RFisUwYcu@B24|Z}~tiJGN!Zlf3=@_p4T^y!FvXpLyo@zkmAa#~;`9 zci#EezdrTUUA$@hq)AUc`P_5hqwou8Rh6Pd1A+FyFZtafvO79_Sa&pPvrVx5%4P#R zel)sqv#(Lv?BfLveCY4pUEw2PsP}FjZYwYT=-Az3cYnHjdksJTtYIr-Z?osV}r!*%mN zv%UQ9L~ls9xOxBK+dTU+bvM~x~eDdzVHsD60h8=Bkw^wXe!^w9+iDid09aY+gKLrj&G3l^Zg z7$b~UWYW%?UNADx<#dkZfA5*7i!PZzt7Mo5s%hRl&{tGoJ&hg>{6f++JyaY#iTX*r zO{~buhF>>xaz~#HJaJzL{G^E|pE86u_fSdnSKDt#Z%KS6D{ z9*t8-D^?roHIAs26Rn5(r11bRwp4ns*K9OTaX%#LWBWm^Z~mqEYn-?rKuYy=KWG&_ zd9tQQE`C3NG+rOu51JgmnIr#XPdRdl)RI%cf9g$?qr{q9IHYtGa*5RDEOL8KO_Za= zo;P@C*=XccI=-bHg4YO*ZXNhKpH^!iX(P%CHEOub;vf<{HT;finbcIB1V#aoHI8uL{Z}Psr(;%=I6@uPi)xm#D>Rrn~Z;JLU^~dCJrejnTo(ne&d+g zU}1A-Sgms#Y_|3`7i(WLh_$!nTie$Z*xLCW;Y2U_fGg z$<~8%?w85A!`J~|HE%jaIns32L+aGn_1w`urnSeq1HWWV&okm+M%A%<@K33yb&KQZ zYe5#{r)yLD+(4xnoIkVp#d=F;9&%)s7d3Zc9%-(zulvIo_!#Ro(V-1em9Uta5 z>X1j3Djuni|6AtE#KWi|dj8?NDpM(4i@v}w6VoqaE<7ucigThIX}ak#9;`p)fkMog z{KTy#N&R77kWwGSfYNl=<2o{hBA&AT;_uj*mOmqpcpuj(%8`oeLOIeCr5~vEhqxm> zQTQLP`Ei^~qga1{f4_W^yniz-PiFDBl^u3M~2JXl8 z7l#{XL|ncsy*MxBjTkWZdYocjz>fO$)Q_t_td~?2CJ@xi|U|HRBAV!^kQttV~og)w6FEzd~rO~2yZI3BqwTqbUDV?6E&^BxQPgZ zbqOMvlf-~?7`3PCz!Y^rMLpH^)Q54@ImuelUe%d=Nxk-7Hi1SJ0X}MnU@Bsql-CDU z#}NIw&cI&Oi9D|pBMy^#!tu>)#QKY?EtrSI7&i{ggL*L@fd@Zh?w)iw8KH5$M2_9G z(jy)aoR9}K-agX9*ebqy9UvwmRbwJHYE0-WB1&d{kH{w1sM_O#Cvn|;Ty<_7U$8-Y)7sL<0+*ijsp3ODjss)iW2)0}1beABd3Ol-P@E92 z-cU6kI>wj}gA(jhjg_qaU@Y)8#)P~>BQ>h_X`QJCLnJiN>&=Kc)%eFt z5AzCRAx3IQs3U8T`re;^c$Q#ZktWm~trrfwYKPb()#au-v_VS8!`vmKI3H55!*TkE z!Wz_d@Hxe~Om!H$9%F)Un(Ea!0Yk)8h`+D&04w5nG!*=x8FA74>+;@=XVfk!55RYS=UmXkAsE`g| z=Yl#6t|JxIYe@CMeosDhUg-B^dpvazpfDcfXU zY6okBM%Sq0W5j`amYJ=F4l5dqzNSpl(^%7~A6J7|hnQom9h2OMXGKyT$2!_EzNp{P z&eMZ^I_2q$#zON8V^AJ*Cd!dVy`=nn?x9ZlyAJC?q_}QTjy&kG4-_9!j(jpbjzQ-` zTcoHH$C7&ejz;PiF`+b_@e^~3yhw39RE~YB0~yMZCex!FsAx-dQr|!E5pxbaK4v*b z9reS$sH1Wm3vG}GihZg>Ia1W&m?%do_3?Q_T#%+SKA2N6Mkd{uD$@D0f zG*&|c2@9+fTr=__HPeeWz@rT8u}>-X>3ATc59)9Xs+0bSXF;aogSn&>`L^WRBlX(;rkK%wCgDS4g+AqBO_Ue)$Wuyvs2uyK2cBYveYBJM{*4c< z12N~Adn(61^~1hMsV(Tl2lbK;%zGc^U921Ad!t8NT3g^}Du;i^WAg8I(3Z|e$CLX0 zbKk?-p!pSL$cq$xs2uxL2eJe?sk@$P5XUj2;+Ue1)SJJn5vO#-M~n%0d?4;rdTN7y z=wtBs?^*TpPnAQT-2Dt&B17D84z!_i?4yp3i8|?@-sdOgOw1vbo8=Jglj$)3c;2DC zO0gzMy~pXL+&BmNJ1#xd53Ic>J?bdMeq5dD=bf1h?8S#-F6!dOm*-_h|9*hEq!jzK z_D}{C`>01gsSe+}0!8~|l#Xv^BlVeg-=le?`4jt;2Or8q-;75&>X64d)&J&GfA8X$ zXp1&T(O2rtYplQEL+e0X6Xu@Eu}}T5W<)C5q7PEkOB(x|Zy;iU^&nEjLX;y9dg$XL zo^OU+w9|R&k8@EO`U92vSdCI#u%F5Jh&e}Iq;#!V6NZewS83m*_7E%dMT+wwFH)(G z-|zhaA2Fv`8z@JLYc zQLmqO)ZeJdUW^^*2em7o0fj-2wUw%Vu=e2W!@40d9GgBMT+a8a_mzb$WV?nnI7dpMO&(q`u>TJm~-Iq zG0QpXs2}!49hKu)XoEaZ>{A`ek)jUAL^)EakIx(8f;64+!JLXQQaP-{Oiyi4kG3LB zrbl_dq4lORyw!XfM zkFFK^l!tjKFY1w}l=@IP_E8VKxGt*8B>mCUAL5Wn>!W!(Uh#{$#&J=O6y->%9Qz^_ zZIkKIE~BYheEOHko>JE)*kts2@jO2cRICf+5g+753VJGoKIMC>M;|&Lj)fF;I3~)G zo^ZWhTM!Q|Guo#*j2r0*kDt^L#zbjf#wX?!d68l~RE~YB0~yMZCex!FsAx-d>8ytu zQ{&UA7&qm6vdz@>ZuLNfr@hE(FS>-*rz&_BSjsKjdG+X zQjax-u>hqq+D98vb|T|f;}!CbaU$)@_%Ns998_)=Bibj^p&Y4bFUnD$krWDwH&~Op zt{(cTA<1_}uJ<^`FV+F_B1N32oc2KvJj#$4Db=G6{g8qjACyswx^(eZYfAsZI59?p zr$!mJK3oG*T-&kY598q&NKuBoNJTyJK(UWLR7U%v9(9zavPl;`loiHJOU$evXGiuc z$|+5k_0gDU|9Hkn$AUiPVNS}6dgLjkK2(l<)B{h~gnhKj1pTqpUnZ;%b3*Cy$`6c5 z$HG3!k)j+am1AF|)E0E&gZh3!>0yWOR+Nc6$WbQgQT?q7xM7{qKCP{!a^b&^7JV=$ zRE~Yr;n*lgnihJjt7LTtwZ; zYQyUg<&=s(RHypswJ*w%KjHpZe|jFV3=t=lCaJ%SSYIFDFXoJ5Wrm5Ur#7O_;PrYl z{x?dHA2b_Keqv&Gi91*GST^bkm(X8SEee_Xzs6CSh?UPYDhMA4jo9|7;DwFV`u|l8n zupY{bdgLiZpEy6g1}Q#Nk8co!aa|}!igKh>4t=vnv_&7JsFyVM zE**$iU_FQwu@L3RgC6?$;5!z1qzQIC-4}&1=@>K~lp{r*IG5DN--*%@A2BB6MM`rC zeblRc{%$q&v@g^X_~3)~Na=Veqg3kS_dCS~`k9E27&Gwrn5_wQ=%?~}z2W(XGHQck zsXDzTQE#RL8GX=?`bd2YqfEvJb1JTZ%FSxT&}03P+J#y~AEY=w@<>rGX>9KgBrL=_ zKwhMXNt_((j8oJbwTC)+-o>5eh#%qwy!fCFsHp2t{x~Ctou^^rd5oeH>GiBQN#l@9K2MN6Z=42Fj7rHJ}ZZK_BJlV@A;i?dcd) zhjOH-!!c2gRO(~zaKwbtbjAmBD#l3Vuog2twLv}FiZq!X<&wr~Xdq#Mb%JX~UZiGv z(FS;wfj#yq#XcPmWb{EDjzM+OKk+QcRD3X(lwu!qFH%vDJW%YT50%lrs7D>8scfV^ z^X_}e@=J3GeaeFmG^}R(rgVN)6dbg*JGRmm1c7O;G7U^5Ni+5kaX7THHYUpzWIM{5iB z6!l4WJ;ppxX}aepReX4^iDw$ELFlKep2sEL>F};c?|x_lvVNt{^!%CC7Sya*w_t~| zbkf7V7|2hi=MQU%P~)s1GPU+}KHc>+)`8eh_x$t}Ur##7W!mWn^4SM#7tV;Z%TIrg zPp?0~9q)EV4oGeGTYW#vZ(5#lJz-30Y(U-v1#B`cf9ko=!y3|EFTQ^U8lUd@NflqF z?U#i5lILIg-N}XX3~Df09nyTJvP(-nuRn}SU8By^cX7YglU(CEX-;WB-SrS_y2qFh zpFIEK{%;2);6v&&6Y`r0F{Z+p?)ib(PlZo9>(e2MzBe%8kOf?-1(Pj`BTpgp}y!E(p?X+4&*1@^OGt*T5HMj2xBB0=Y;#q^Dpy$ zvq*Agq;;L~m`&s3^@nnGkLbMo{V(yofH^k9?jdRWgue$qWZsp1ps(JYrRMzV2ExW7FA%zu|6`!cQbmqBrw z;>ha{__>l7@9cnC9e3=kqZ`P1( zj}HWSDtyvepU$zF?hSeV^{ewQQ|k}nmcReW-~UX{sJ^^|$n!7m{QJ`J7jk>F^Q-4Z z4`Zae9@aaMpLEYps`!q!Ud?h1b0nLm&&QYNpZxt#KZ8z49rF6adDT6j^YZt<#Pb@}?s1yN$LkN}K=BSJfB#E-FCb3odd{5ivyAVB zqsB;{e{uJZqsEV%KPM#rHpZgtphd556S7I@KK)aMKKsrYjQ zd&pLNJ64{K2598uWDmwM0j->z+*9KD91rqNRqrzNklmadM+NFN|D4m1*Yr6qZhw5+%o&W$Y_PDoGpyFR4K`bQn~Sxt8N}M#@~!P_3T*9sx)b?K zR@<%u`ZQG8?#g z^Tv){D73L-$JzwZLM}41>#gh<#+#tF8og;;vD#3kamQPR$eJMyx_GoFPu36?5!WzJ zL>+K9MIL}QK}!{zqMdY{J|17o#Doc$1>`XsC`TS?y2t^yFS(7GFSJqBlPf?piU*>r zMqTf`-UW^Wy>z9IcM3jWfxDK%5BCka60qy@|@0@GiN%lUwr-2rTib>U>`f3 z7il(^I-t#5JkxpI;_IDDmo8CF;%&xj?YhOz>t;H+&2?Zi-0Ph?*Xu3S#+hT<{(b7y zxpSvZy-Krj*7_3Kl#8*=R^xG2CZc?r*J#OYrjo{oFdIi4*;z4P6eaLls7cLGWN6Is)T>NzxQuk-5d%0p^&7Nuvx5%oxv(uwR*1d zFE)q|P|Qwd`K-i{@kyXnX~g`yj6EE$u-zS4ySCxE_5abpn6mcnz^aDt-xuJK=4qds zZ_(Rw#)^h%AHSkEKVQbE$*QGaS$*$azrKCtLtHlTtQysl+MPQ3Y|U;T*sZ$v9&Y!Q zF(nChWh2T)z|4N7?f#Xk_&nUMYJAmrn3;|@vV3H@CC}yZEBF8OQDs&825xuegfk}; z4fA-Y9WK_dtPMQ4QX4NOi>)IjyLa{dZTH>-^I|g}Z{Ziv z(LL0y0Y0n*+wN+})BaSsZRLFr+`Tezk8)ev{p%W5wXNm<@XoV)zx{{3c6(ld z@s|*v<;u5L{2}K&9;by_vU1r(#!j)FKH{SDPx;mxtU5GkYf)LzfnBAA!&sCq_>HF!?m$<*Rr8)&=x-uoKvgze|#pB|d! z!4b2$x}}W&rJl_!_V?NRc_q&=zSVwyHTyO=zM^?oR}Z$^tQNMtzVJWWe*DwX&yLQu z+Hz}ZeKidnpnJYKreXe^5vQNeY>(QiJ8U`Ge64%~@dvQG2(oea%2})LZCiWqJ*)5i z{_QL8n|1ffmA8FQS-W!O?XwLQIDg(7l;?nS>E$s zd%!;GwmPddP@hxy1iT}h_N+Y1Udy`{r{yb_t(HGp##;{Y-($?$(%Vu2~o#lsfUyGhOqx!YNV&=5HTE73h zxv#pM>?G@}(ZU&{Ud=6KY|yKAw<|x(IzHQR&UEW@eDjXA*iL5)95xm$I*HYtRLagB zI*8Q|9m=NW+1bwAVbO*v1Sm8d_3RJT&j*VMB%{t|@O&enDZ8 z%TPE!n7+;Owso8BkM?)6wq^g(@pjH1bED2}c~Mt%kY@FI*6Z1Su)dM|y5kM!+t#;j zZ`-$XMKOCv4YuwpsS*h$YNq+)9X{oO##?=2D9UtdDymFV2+R z<371Q;3ZEABWJV8cHB9FsyD3V4q={t8EbY^5xw^K)vGc4BPXBq4Cc}P*7kID$ zO-GK#5eaInKPl5jxTiMPdaCuF+Q;APxZ$4KGRxl@_Rl(hW;=gnKJ)UFijHEd-6WLb zD~ehJ$8EscSAyr&R-RMV{W|}n$z>_1tJ~Jj-hGFis@}U!g)3Lq&G3(Qb*owfGxM8S zG5(1G|bTiFi&15eQJQ=&@rNOW22!sVYwm-{QO7`eOg!@0FD?ce#k!k%z#q%rzZ^ybh1*?B`};RK64C)b{BwK8i~7XMFXY&@JSM^<)TexAdT z%H_GA=S8nvKHYzB#no4Q zH1ea}KW%LLaOT{twQFBmyMOS`k#D^|#IrkE!9Vr!=IVspY2En_78%0HAdp zbscI47_~88kzA@aUYWcFd3AOU==zMIEqf9HiGV~vA|Mfv2uK7Z0ulj zTbrjnJ)9nXw^G|1$%st!pHnqHI6XLhynnDa$D5t4O>J5eIS@IpMw=U65pHO}_;P=R zKR72i9phbEp%yt3Ip9CH(tqci#n)H*hv@NJDz$$ed2&gm_5q2n7}s?1Sid*Vn}_j* z8np0guP4*xbUJuTai(zD?)ji`bNGXb4=UQ*eQm9+P?f{sc6)jAU*s=`acgUPdzd%v zCC%j@;Xh~ErNbZl&5u1^_kgrCZIU+iwud%+_?v&Hhuc@y*Mq+q@Avy*Y{v5p{A2v* zPM-|%`Fi|LZOegNh|ibedGG|UM~;V@-04ndYMR6DOm#T4HR1el#pcb;H$Jkkt+^}t zdvmh~3>#80%2j4}VuGyzb7jn{-#@lQ!qY7b7@tdM*fQN4u^~Lx71%b&N~pWZfz~+{MBQZ zYs)pxpDZVb-(;|hO9gU4K?gWFt{}^{75uN|RtN+!q_hi4?qgble})A}%A|nZh&6^m>Nm^V`tV-rgA2nj(SU1|mi9aisL5_3MrUofmoN(DsK>jG6zvHd_>;D!!iaady`G$+ zs$zdd^b@R;K?ri?8i%h3E8x$YP8quusiHF8(ih>lf(B$ zejj`$_~d~?t*wopoJHOukC(H1T=0tEM7}$X;l}hZXZPO7W&HdvFB^T?q7@#^Jfs8^KkyVIrLc>&NNtypFP!_HgNjXx3-2t`s_i@5B>S2 z&mQ6Yh<|?hVR`cW;?EY|;Q1AsL4SV5W)Syp`GiWYpJT?m-mT>NBe8hM3M%9t;`tSe z=RV~5#ZMve{A#P`Cr>&5w8I??&kH!ia*BWfKI8dQ-oBy{7?7e>jPm>7-g3Jf4)879 zsikme(%}q-v9Elbe}aEV#hA+ASGTsTZ%=XU)!x-UC_Gphxi|CZA@`j6IrX6{qW}a^^rgN8^D~~aR2ZPV9sq% z9&Y(0_zl5f{`>{@?9{>=!jr?3bq*u{^yfdu<1@TE2p|4#c}cLE-wECne(LfUs4zbq zjQIFB0N6Wx1F%PyMg@QP8vuX%;xCX6$M1qQ(f6W}NO-mOh;}3FVEh|^zW0scw=0Y{ zfSOwyqN_h^`QS5td+_IfV43~~;N4$zVDDboZF2|t84#R9xdYG8;B?NOt9Q4Ae_S7| z;%5->Ka%@{{lQh)5qN$;JbeRTEdC7u-kRVIpgkIn27~eUrPM_rJ}uIYkMs}!P|WL z`HfF-cmrsQ!t*!w2H=c-Kl0$`S2dmMAIDEez?xPQlka)Ha0M+8B#Hzu^slG4lN7zhLqC z1@F%A78iSdh0W(zYzDZ0c?RMQ1>Pax{tY(Vc`ZEsjrao^yaDiEz?%8|qHh54@$~rx zbLh{n_6B}Nuh!OR;`!Cov}sdzc4K2(TSPpM0|A$-q9QGA)25~-*v-Pi%F6lkw`_^< zy9LIuuBOH8O-(S~#GCFPiEP^Ba^>fj^9KGfKU`?n`+-1On(hxiu@;SvZ+e#PU`y^f zlyM}rr8*Si``LbVxN`5c2LpSKzO|GSj^!>pEBigy?W}5PK8r@#Ul^x7m-mA-n$4D$ zmS0qV2ba$LVfA;0wlneJZrn@v2N??0x8=W`_D`E($9S%<&u>gy5M_brJxlMq`{8@m z-v8QTf#{s(U`u_=O)aGxN;iz&@Wl;BQX@yxj_x`-bNJ7PFC2bwIL1?1SsCDM$@RBQ zyKP!iO8n>FXUBg&VNcfliv5e-aL?~E-r@BJ*h{v*+y9laoBhG_eP31e=0kTMS$D^d z=Rcjf_r2{ef4r1w>@fdmtcv}a{f8Y6{UZLOR}J5`?P)uP?f{&O4;OrZb#D)Cum61Y z=eAqoKi~_9K@hl)YVoy{zA%b^d@cT#XzSjid4~$K4)RaRQSE3{yd%=u;jKSA+go_S zdGo(Id*a7St9M*N!0#t{Z%`H`+|r3 zd#?K|_|eg~Lcdu0%X@$Mc+2j{55mF5?DU&Jwnal>*2Y47f3MZc|Gnv|n?8wN7ksvKoA%#t zUiz>1->loaH}IRQp^ha>UVeG|_Sarp%ug%M=SkJ2BRo8}5iDw2#hRU$f)(eSt$4?YUOqv2AJi{rzve^PQbf9C@+v6aQP=o||-ier4f>l~*;b zpYU1qz1aR6Jyy%=SSru5lz;eX_J!ZpuWNYvj{G~OR*m!)I(2i}|^ek_$zy81jVr+lv zsdejKfBpIAfAk~We&2oXyz|_1-{wspj~n;wvp@YQ#02>PT3#LuMnj?I(2M-p7TFyg zKI~{T>TsOv_@%=Ec>G}0oEHC_V2hs<9LC{)A3dUdt^wbV@^k~7{L!-AWxGGwy}gp3 ze|F8z*q?oW_VKgL?sn&k=G6s%uhlzYV;mIsdoU%C8b}Q~f-EBVAMpj61I>}v2s?Dm zbB7N7^Bd8Y;PowQub=JwN#w1-zQ|qt2j0RX&n(>DX0x-@wAWIf^*xz8b!*v;`5bPz z75SHb9&9`OjoW_l;&tyl@sF?V`s<7o8eVSk)(ss9&lzA!Hw{#nv;{*EJmJ{r{`+R>vCE&6=)s?XOSHp(zXlvOYf zzoP7JKHuF8>-Oe*z5E^98#X-oOEUa~4rgSPj-E1QiZ>%8!&^{LFnuI%`ug=| z)BTq%+q7xlzDVTFH}&}#)8q7Sm;v61mW!tUy%)aQ+tYb#Xt3K;Qqp)6Zu|=-_xY-; zah#i5QgQ)rIG)d2Ny+5NIMyXp5U+XYq1f2x3x`We8XKQ~e&IsU!h3T#?D5=q~?1FRrW`I7}pMM_gPd#<@)ukO;US2@~ z#zRh}rB`2#{&J2m+w2alx+WLeHP;By!92Tm#t>-NT`RT)EePy>rH^nv4wHp6Qx0g|0Q^Iz68Am@|1=!LY)D0+3@eb-c~Gs;&++ z9UIKiS*ztIaAytTt?-99h}ZG&IE$YDl~?NcceF!n?Tp-99siDYJ-)2W%C5(kl}!`S z%$`lrguY&TSsBhO+x6OGc0igoJwDTg@qjdKdi=0Lv?KRW>iO%lfRBzN@;Ub!J%4?c z*|XsukQ?kj#>+Ms1Aei1&*;(%CzO`j9i>LE6fi&*^3!N^>19(&OLGT;9eUgj@j5PG zpFH()=LMOzOwqg4mYJOm@j52Lo;EnEU|7yDZciJ0Y0j{^I*8XXVJs!J0*?D5ik`C=oI)4)#=>0fV)AybV{&?N`r-DCQ z7yRi{Pk~3gJz4yQbw3X_|ET%tpX^4@xcIv|q_-r1j!K^X2u$T~98+s;Z02OUeP*r0 zF~4DPunJS9tLFTj10|#y*Z7Mg+GNSNEzXw?z;=r5kgv!Y@{+JNZIT{b zP1`BX|9J5;>*%C@fe*m|W2mIqQ9bsL$1Y+iS9mTsFGxsTH1IKOrsM|dEBw*N2s@Q_ zTwA0j{ z+Kx9~%q``Ma|+3ALP8PXXK)a8AVuHE-SiPf$MC`2Po~|>4RVoqX4)h@xSBTF?}hcn z<(eQrT1TS#W!)(m8{EUSA!q1g?qY201Jn!rK_3`mUr54Fut7VOc34}i4~a-TGi{O{ zbH+F!<-JABPH}yhEAE$hZyWiUHG-p5C#?(pP*PviQQ1>=m=jVHc~kBn-jFls5pR@e zhgj^>c=S8PcEk+QpqOFKqJF56UfflV6Mnt0KCH_sKe-NCC)%te#VC?e|tYa%`Bx?>mn0$+eDG~gae)CmQ#8Dm`cbdVxti^va|Z3Mv6hA znopG7Y^gKW;McOo9-ZyQM9(+64dTD*;9$4gO)&WIHb%IUS zosuzd=~x>08~bPGYf=BHwkN|s;T~`-%sAY$URqx=e9bywf3X%rV(sWdCH19x?Dv@6 z;F5XvQ67Q~F4~Ac=5jhnQ9=UzA$N+U=yT`Op{8TWv9tQE<^e3MY@qK6yTF@p%H$jB zV9$_)sN<5ZFXoW;7Gf~>Q*9Ty!Ujk=TIMJ5PmUAgUQ}PQ{5q>2Vi5-_>6}!L{hqSJ ze3*kdC-6*epfmOlV=xYL>djJ=(@p+$R+qRpVP1j{;$$h&7p0V!Wn2Qk&T^wRtNM>+ ztrO)7dBWT%W$NKsMtw}*1el0CIkuhpoFqHsAo75sq+Cs#qz6|^8~S4Y$RP+imIm`c z?D00tN0vr@W*xAOOq-+!*A6zT{80}K17E5Dwwi=0GqMt*<~u{3S+9Ng8? zCi@y367vIQon6Mf;4AhXV=ZFL96GVfHOXUh4p>ym9c>~PVxxNO(^%9|*(O5q1NP#5_app%djE*I`byre52J8VL3jOIa_jARq~IkqbF@feFDR-e1Sh-zXOJ} z=HAo?te{DlLH-H%0DZeygO~$iy66wC9k4?k<|@yX%r5)WG0vHwe%wdI0wu)?byW74 z9rA>{K*|kkpnBpD`A}1@?n~HH+$ol3zpKS?Ji3E}h>rxb+AIJ$*mGzTlix(*2@(>??bW z(})R`eH|aD)rvcagR&>Sw-i3H-+_olBH0;#`uhE)>g%27E8Q2>kL{7d;^bjrbyngY zD0|1hZy+`x6+Yd^ClPk2v%*%{kN??eF4j+o8>c?@lt)CR8TF2Yjut#d(xHG4~Wcv3nS?NF+PsPcnWg z`|;i13ctkoQCw6%hLOVJ{*I@Lrkbd8%V0hzOu)-ikMKD z3?J~d!WQRI_Qc#%_{8pE#3GUGj6ccvsqDvhe=GbF<418({TN0Hi<5_i)me#qpzPLv zzXFUDKHbMB5q7Av!dBUj|9uBARQT#=Eb@s;Jo~ILQuf&MiQu=R@kC%U$b}0J>;asLBt}F>}&YQHTFb(R&#(JFL_-UqpdH+9}EBbV*PZVPiB2| zt$y1VvIlBRxz@h_y zx`NvJT>XSoa&4mSPV9mk@c%r;|L>{6Nu0-o6YLUS(FfO{W4Vv9vL45$s~zxx8c{a8bH^2ZpEfC-M}Ifc!z3!cgxg?Qt_SSryb&yPN*vmIc;I#5y_?I*H> z?sTsIc=H#v8)qNlgL4YWZ9)Q&#u*3_eJFPQwh!3w2ACjLR(;_}?$>^c8Dd~?4{<W6)t3nc2O>}z&`i}8hZVvXjQIwAJ`1RulzCB+6gMb9?mjqn40B1chL z;D+;q#5t&r_WOz*@WZ+&#S-=7{E}eUSMb4_WhuA?_aQdu1HY&Q8*&aMSHWBuFV9PD zRv2PzU$6s2hynCFYjJ*nGrv$rWnaRF)`fkn55}U8pc$A59q=Smt1$cxFIYr03PwU2h zpQzuc7i*%@sy+b?o^gq|hj>ySf~UL&9m{>lLDtdnX>SKykaLJz0v)ZH_D}ozyRpAW zC4n0ZD9o$I9?7|aSXX8OM`oGf2;dB@jC^*FYz3tQrV5O3(v7c90Z-LC;k-AM13o6qOYmQzqX~2WKmC5OrJvMtBb+UR0vZiet(idv~F@VZXoN1N9I;c z_@hr>wu{<~vk&V9iC6&N#283uoPnke_^2+0!SQgw;2!dbau4|k)T10kALJ=x zN*oj>zQ7h#j@DRC(oP#kogQI3F`YWm4 z1G~ck;1%-A;Ffs~f(anlviO+kWQXIbkL*yXXUt1CAlT zP*NTAX#*dFZ%W3v)hn=|`$YS_b-yUzXuei`gL`=H;XOCF}Pl`~{B*r%b+~4$f@k zAnLe;8t@*#dZk?B zuwErQ{x@q~#e{CtvGlt-|B3XY+=F!lhvXv2d51n!VjuYjWBEcGu1D>tLrHDWM?Feq zkH;5qk+&?_m|}>yqNKKyg}->l;<;y>ahQi$i*W|hoY2Qg*A+X~t)%`w*c}|qJ(4T3U2s+AC?7+t98c%z%#MDE@Srwjw|*Yr79n0R zmixj7_f*8OhJI)M%HB16#2)c4#1?TAdTC$L2XRM< zdX%U~iF%Z%M~Qlrs7Hx^J^oxk zj1U*GM;2ccXN*H>##`*!iL%4IX1dUqI4Eq$Ih0(9wP0RY2TCz7%-2ahRyLeN$zy*W z<6a_m@)-5KNeZrDq6TM#4)}(g13fm-8N3pHH0Buly$OfDkPB`R)*_El-&>@~J>-|c zE%O|N0+53!QD<-vYN0$#R9k0uu;Cn5vC4jI?|#U;I7#-4;(RLc7x^Kt5^<065_yM3 zN%deIv^LtevZG$f`1`Qic;NoXQo7H(J%10*W2TshXQ)Fb1xdruLP1FW`v{~7y zKl-4gHk^w}WsiSP6jg#w2 z-cY`o1nZ<@a4*?Cgq%!-p=^V3viX#~Ywv(oc=ty9%-} zKDA>XCG~?o>QO3t{5grZpgdjhfqdgQh8*C; z1W1vi%su2A765*Phi3htH!jpsi8fp>&W3YK5`84KIaGH2%rpU!I;Raj$SIyb<_r?K z0(=u=sL$Y@sUy6j9P4V|oL9mQhL}qNlTa!9>CZpG6W|mya(+P{_ZK;WQsy4@!#>U> z&m-H-Ibm&VMn0x5=BDgA0(}u5s0l9MfguGqaBS2pYz7~YA6C+ed+3jL0V=E`t!4Td z>nXdjH^yG{1$@Mw>UWiX{z)7ZCYgI&3AKw@&KbmkO0fepN;$Q#1|HL^2+(UjD+`?zDpe1q;B`668Wgfy4MavarLH=xh2>9qk))#KkE4X^WqEuEB;T zI^$3WcG{4i$cJu9!Cjb_$BRD9W2|X%QrY_~KISuzaw}Qi;2!b?c|xUmjv2h7HjFbl zha4x|0}f&g@-Z$~W$zRCh`puzPPqrXKyIKO#)d!g!%8B*x?+!8hj0sHm7U{`tq~G0T=8auaq&y z<@&0W>y^iSwQj7dL%qiN1Uv$p;1fEdBFryB8=q=ZMcqX7jqcvC9?Ge9l~i< zOESAv-07mXs!wnOo~>{}lb)FvC+vi$a@}&=G24xWXq?E6uX(!4xLOj_m&|^;){S|0 zr9SWx{(z6nJ>;8#o$v#mCh!wBNssHJO^!pm$RWZ0(74mFpRB%Q_S3Ounsc)H#J+RR z;Mo`FkKDuOFYv?Q9!N75;=6gptw%YI^TB$j^M!SQG#ag$_R$`P<>_2Ild&@iRQqZ;`YPfAl;kZW{<1ID3I!C zzdzP5_6rM#{ggN;OvpKuTq*XP>_5ek+GLKQALWUuGxESON;4;8Jvp|w+s!&G>Pu$t z?e!(gFKKuSj~;Gz1?o+X;EJ?dvC8VS$>_> zCt?8?jU)*NRU@XJ#pNFx}i}Yq2ONzHi)4-Mm@@;c4J=j2MIA47uUyJd$~QX zmIU=Bv&Yp;YwaWTBb7v*FrYkxdTB$Op^L-Lzy`TOZO}&?t?a_5i}RZI2U(NO+nekZ z2ZWO9Xg`rX5pKPyzGV3&t&egF`zQer+DCv;FYQ0grK|m<`H?ly*J|$GXvdsDB79Uw z`-$wdR`jJh+V72a%n77reO4U909k@R=Y^QlkaQi?BQ~;)+NlkfiPIVU6EV`dIKP=o zFSN(iViZVqwBH}=H_!4!90Z-LH})9x6uU%y#uV+-fWU76p|Dc!-;QR zZDHx2+GDrdXmhz-V#thyQd3h{N{WL$@^c^i`9>deFt*ahCVH9OE-eTZRkCXSUCqBz zBk8s%+sa4G_&!4#XGvq(E|14Z{;AS9Umn}nuxnX8W3GDs1*v2E&P#-`TV*v!dx`^;L0V}8Rz-~74X^et?-W5mL_i$*p#`dD+Qm^C+!us4TB zI+}SpJ${|nAc4Wf(^^tCg%zk$Ac3PrrSW|{z9Mm{RZaWx8BPC z5f_LBhTT^Tp!l+i#f1Rfg66q5`?9i%=LT=C9Jc@^QXk*8Y)y?BByzq9}%Hzh4wTWomw77hLW7ZfnnT3a+gu z=DKM!FX#F5LLfW$0v;`#ao(G}^LTPifjY>Kn+tqAn5Sm6{ELV3$!9I9gC)*i;7)If zGozc3yza4M!R#w`Wo2cbQU>Cnp!ytK+LS@eh2PW--@dh@`WdX6G2E zXb>H1rrF%k41MFzxd5bT=9?SeE)YY=S~Z10uRx%y&&G5Y3)=WAG_hyYHPDhy!TCwmmgT?PT z=_}_rT6RimdV2cMyg~Ux^D}Mm8I}Vdy&p|;04HYi%Vrb5dc>wek2KpzXPU>x>@GV? zu;jqy4{5L7TC1BkgRy>mtXI?5# zxM3%|H}t^L`82bWx&#rrl}p)YTgP_tj)k5+vc`vxMVJpALQf)6zR1SYjT z!T&VF+Y=l}%PocTAZ|+`qHi_=O*ac9^y}sf@P`cA_?~%xd);GoYrp+Y?w6Ooyu$M| zJR@>bQZsFPY;V{yY+tl(vHi_9)^^C|x9zlTw4G&}YHPFYvd!Rszv0b(aqiOQ|G^!1 zIGOF{KfG>dX>Y#j8qI8JLs{<79o1j|%B-(AqRo>&+LV!T2^;-d)N{#muV(PSaU9+> z?9nN|dv!owc+}umb7xfluYvRs2k4JsZqEZ#jM)0LI&_T=Om>H%91GTUG5HpkYKt*L)a`lr>WHiE$xVr&a^ z;+viAh*uop;qOAj$!qoM74Ql(n4QmxSOI@SFSrB> z=%O!wEdpQhT4XV=`R4x&_@<^ri*LOea>yZ@xO&CNQa2mO#;`B2vl;YGP$Lh{q}>yg z0=oj<*=WmR!!H<1701@M-4l148Nv6koK)Dw@9{B5o2`jWy7iON*g*9Z0w)Ura?72p zB~RKq4}|Vr@$Jwm{>xSR6?D=v=;sUVJioyP*G*OXFW-4Sd|LUq)#-%qI4yd_w8GQy zX|?~iFBUQQ>))xu*}hZdvrU?@Z`!3(SMtBjJ^Av4lGZf4<$FSpZAa7|>an@1UTOPs zRp~{(qpx{#nak#ORu5gt-gu{OCj4=-ZYI2iO&SdU=&x?&D`92+ijijH-)r)Ae8@J2 zzR!!lxOD{!UC;ka+4_X-LT0mPvUdF;^k;a3^Vre~;jKfc;048X90fG<|F{4e|06;t zvpCY6*||ko4tt8z_@hH7&R}vlT|)~BhL2dge%<%3x~$}H3m%RH{r)kFFL% zI{8t+AxL5Aww!?jGyN0(QMKdjsB=-v?Csm;&D(R`TQAOgbo8Dp-dOw0k?117*YAIo zEZo4^(H7P;Em-&VzOTL)Ik59^>kq&6#}^N6Tf1(*`^d9vxBd0(|7}~jdfT>74t~_cDNK0|1W@qQ-7UgDTI~;aP~_p@E&gKv*fC>@i*s@Y=DM>}_;$mF^DR&3 zf2y`u23O`hxr+3;yetS!Ltay?ANl`ezoetNdt!HtE%6_q_7* z;=?0$Ev>2fUEA+&n0Uj*U;EOH8{3v|Uh&#}FK28_2|w}v`+6Mn|l;(sLO z3~*$+T#r05YgS3g4UYfVqS=R6HMVPI6{3bp!pYvtcBtSt95cq-m)dw-ez{r4YO!{TR%%?Wc?S655+ zq$?>0g@8gpA)pXY2q**;0tx|zfI>hapb$_9Cy)5Kssx1QY@a0fm4< zKp~(IPzWdl6aoqXg@8gpA)pXY2q**;0tx|zfI>hapb$_9Cy)5Kssx z1QY@a0fm4hapb$_9Cy)5Kssx1QY@a0fm4hapb$_9 gCy)5Kssx1QY@a0foTHLBQ?)|2C6pPyhe` literal 0 HcmV?d00001 diff --git a/pokegym/States/bulba/cerulean.state b/pokegym/States/bulba/cerulean.state new file mode 100644 index 0000000000000000000000000000000000000000..bec4211ea52449f8557d44d0a6686c347565026f GIT binary patch literal 142610 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 literal 0 HcmV?d00001 diff --git a/pokegym/States/bulba/vermilion.state b/pokegym/States/bulba/vermilion.state new file mode 100644 index 0000000000000000000000000000000000000000..23b3e881a46297fda78d61490e83cd91f3746f4a GIT binary patch literal 142610 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 bool: return ( - ram_map.mem_val(self.game, 0xD057) == 0 - and ram_map.mem_val(self.game, 0xCF13) == 0 - and ram_map.mem_val(self.game, 0xFF8C) == 6 - and ram_map.mem_val(self.game, 0xCF94) == 0 + newram_map.mem_val(self.game, 0xD057) == 0 + and newram_map.mem_val(self.game, 0xCF13) == 0 + and newram_map.mem_val(self.game, 0xFF8C) == 6 + and newram_map.mem_val(self.game, 0xCF94) == 0 ) def check_if_in_pokemon_menu(self) -> bool: return ( - ram_map.mem_val(self.game, 0xD057) == 0 - and ram_map.mem_val(self.game, 0xCF13) == 0 - and ram_map.mem_val(self.game, 0xFF8C) == 6 - and ram_map.mem_val(self.game, 0xCF94) == 2 + newram_map.mem_val(self.game, 0xD057) == 0 + and newram_map.mem_val(self.game, 0xCF13) == 0 + and newram_map.mem_val(self.game, 0xFF8C) == 6 + and newram_map.mem_val(self.game, 0xCF94) == 2 ) def check_if_in_stats_menu(self) -> bool: return ( - ram_map.mem_val(self.game, 0xD057) == 0 - and ram_map.mem_val(self.game, 0xCF13) == 0 - and ram_map.mem_val(self.game, 0xFF8C) == 6 - and ram_map.mem_val(self.game, 0xCF94) == 1 + newram_map.mem_val(self.game, 0xD057) == 0 + and newram_map.mem_val(self.game, 0xCF13) == 0 + and newram_map.mem_val(self.game, 0xFF8C) == 6 + and newram_map.mem_val(self.game, 0xCF94) == 1 ) def check_if_in_bag_menu(self) -> bool: return ( - ram_map.mem_val(self.game, 0xD057) == 0 - and ram_map.mem_val(self.game, 0xCF13) == 0 - # and ram_map.mem_val(self.game, 0xFF8C) == 6 # only sometimes - and ram_map.mem_val(self.game, 0xCF94) == 3 + newram_map.mem_val(self.game, 0xD057) == 0 + and newram_map.mem_val(self.game, 0xCF13) == 0 + # and newram_map.mem_val(self.game, 0xFF8C) == 6 # only sometimes + and newram_map.mem_val(self.game, 0xCF94) == 3 ) def check_if_cancel_bag_menu(self, action) -> bool: return ( action == WindowEvent.PRESS_BUTTON_A - and ram_map.mem_val(self.game, 0xD057) == 0 - and ram_map.mem_val(self.game, 0xCF13) == 0 - # and ram_map.mem_val(self.game, 0xFF8C) == 6 - and ram_map.mem_val(self.game, 0xCF94) == 3 - and ram_map.mem_val(self.game, 0xD31D) == ram_map.mem_val(self.game, 0xCC36) + ram_map.mem_val(self.game, 0xCC26) + and newram_map.mem_val(self.game, 0xD057) == 0 + and newram_map.mem_val(self.game, 0xCF13) == 0 + # and newram_map.mem_val(self.game, 0xFF8C) == 6 + and newram_map.mem_val(self.game, 0xCF94) == 3 + and newram_map.mem_val(self.game, 0xD31D) == newram_map.mem_val(self.game, 0xCC36) + newram_map.mem_val(self.game, 0xCC26) ) def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" - if self.reset_count % 7 == 0: ## resets every 5 to 0 moved seen_coords to init - load_pyboy_state(self.game, self.load_first_state()) - else: - load_pyboy_state(self.game, self.load_last_state()) + self.reset_count += 1 if self.save_video: base_dir = self.s_path @@ -313,7 +290,6 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 lambda: np.zeros((255, 255, 1), dtype=np.uint8) ) - self.reset_count += 1 self.time = 0 self.max_episode_steps = max_episode_steps self.reward_scale = reward_scale @@ -330,7 +306,6 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.last_party_size = 1 self.hm_count = 0 self.cut = 0 - self.used_cut = 0 self.cut_coords = {} self.cut_tiles = {} # set([]) self.cut_state = deque(maxlen=3) @@ -341,7 +316,11 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.seen_cancel_bag_menu = 0 self.seen_pokemon = np.zeros(152, dtype=np.uint8) self.caught_pokemon = np.zeros(152, dtype=np.uint8) - self.moves_obtained = np.zeros(0xA5, dtype=np.uint8) + self.moves_obtained = {} # np.zeros(255, dtype=np.uint8) + self.town = 1 + self.gymthree = 0 + self.gymfour = 0 + self.expl = 0 return self.render(), {} @@ -353,31 +332,40 @@ def step(self, action, fast_video=True): self.add_video_frame() # Exploration reward - r, c, map_n = ram_map.position(self.game) + r, c, map_n = newram_map.position(self.game) + if self.cut == 1: + if r == 0x1f and c == 0x23 and map_n == 0x06: + if (r, c, map_n) not in self.seen_coords: + self.expl += 5 + if r == 0x11 and c == 0x0F and map_n == 0x05: + if (r, c, map_n) not in self.seen_coords: + self.expl += 5 + if r == 0x12 and c == 0x0E and map_n == 0x05: + if (r, c, map_n) not in self.seen_coords: + self.expl += 5 self.seen_coords.add((r, c, map_n)) - exploration_reward = 0.01 * len(self.seen_coords) + exploration_reward = (0.02 * len(self.seen_coords)) + self.expl - if map_n != self.prev_map_n: - self.prev_map_n = map_n - if map_n not in self.seen_maps: - self.seen_maps.add(map_n) - self.save_state() + if map_n == 92: + self.gymthree = 1 + if map_n == 134: + self.gymfour = 1 # Level reward - party_size, party_levels = ram_map.party(self.game) + party_size, party_levels = newram_map.party(self.game) self.max_level_sum = max(self.max_level_sum, sum(party_levels)) - if self.max_level_sum < 30: - level_reward = .5 * self.max_level_sum + if self.max_level_sum < 15: + level_reward = 1 * self.max_level_sum else: - level_reward = 15 + (self.max_level_sum - 30) / 4 + level_reward = 15 + (self.max_level_sum - 15) / 4 # Healing and death rewards - hp = ram_map.hp(self.game) + hp = newram_map.hp(self.game) hp_delta = hp - self.last_hp party_size_constant = party_size == self.last_party_size - if hp_delta > 0 and party_size_constant and not self.is_dead: + if hp_delta > 0.5 and party_size_constant and not self.is_dead: self.total_healing += hp_delta - if hp <= 0.2 and self.last_hp > 0: + if hp <= 0 and self.last_hp > 0: self.death_count += 1 self.is_dead = True elif hp > 0.01: # TODO: Check if this matters @@ -386,37 +374,18 @@ def step(self, action, fast_video=True): self.last_party_size = party_size death_reward = 0 # -0.08 * self.death_count # -0.05 healing_reward = self.total_healing - - # Badge reward - badges = ram_map.badges(self.game) - badges_reward = 5 * badges - - # Save Bill - bill_state = ram_map.saved_bill(self.game) - bill_reward = 5 * bill_state # HM reward - hm_count = ram_map.get_hm_count(self.game) + hm_count = newram_map.get_hm_count(self.game) if hm_count >= 1 and self.hm_count == 0: - self.save_state() self.hm_count = 1 - hm_reward = hm_count * 10 - cut_rew = self.cut * 10 - - # Event reward - events = ram_map.events(self.game) - self.max_events = max(self.max_events, events) - event_reward = self.max_events - - # #Item Reward - # items = ram_map.get_items_in_bag(self.game) - # item_reward = len(items) - + # hm_reward = hm_count * 10 + # Cut check # 0xCFC6 - wTileInFrontOfPlayer # 0xCFCB - wUpdateSpritesEnabled - if ram_map.mem_val(self.game, 0xD057) == 0: # is_in_battle if 1 + if newram_map.mem_val(self.game, 0xD057) == 0: # is_in_battle if 1 if self.cut == 1: player_direction = self.game.get_memory_value(0xC109) x, y, map_id = self.get_game_coords() # x, y, map_id @@ -439,72 +408,89 @@ def step(self, action, fast_video=True): ) ) if tuple(list(self.cut_state)[1:]) in CUT_SEQ: - self.cut_coords[coords] = 10 + self.cut_coords[coords] = 5 # from 14 self.cut_tiles[self.cut_state[-1][0]] = 1 elif self.cut_state == CUT_GRASS_SEQ: - self.cut_coords[coords] = 0.01 + self.cut_coords[coords] = 0.001 self.cut_tiles[self.cut_state[-1][0]] = 1 elif deque([(-1, *elem[1:]) for elem in self.cut_state]) == CUT_FAIL_SEQ: - self.cut_coords[coords] = 0.01 + self.cut_coords[coords] = 0.001 self.cut_tiles[self.cut_state[-1][0]] = 1 - - - if int(ram_map.read_bit(self.game, 0xD803, 0)): + if int(newram_map.read_bit(self.game, 0xD803, 0)): if self.check_if_in_start_menu(): self.seen_start_menu = 1 - if self.check_if_in_pokemon_menu(): self.seen_pokemon_menu = 1 - if self.check_if_in_stats_menu(): self.seen_stats_menu = 1 - if self.check_if_in_bag_menu(): self.seen_bag_menu = 1 - if self.check_if_cancel_bag_menu(action): self.seen_cancel_bag_menu = 1 + if newram_map.used_cut(self.game) == 61: + newram_map.write_mem(self.game, 0xCD4D, 00) # address, byte to write resets tile check + self.used_cut += 1 # Misc + badges = newram_map.badges(self.game) self.update_pokedex() self.update_moves_obtained() + + silph = newram_map.silph_co(self.game) + rock_tunnel = newram_map.rock_tunnel(self.game) + ssanne = newram_map.ssanne(self.game) + mtmoon = newram_map.mtmoon(self.game) + routes = newram_map.routes(self.game) + misc = newram_map.misc(self.game) + snorlax = newram_map.snorlax(self.game) + hmtm = newram_map.hmtm(self.game) + bill = newram_map.bill(self.game) + oak = newram_map.oak(self.game) + towns = newram_map.towns(self.game) + lab = newram_map.lab(self.game) + mansion = newram_map.mansion(self.game) + safari = newram_map.safari(self.game) + dojo = newram_map.dojo(self.game) + hideout = newram_map.hideout(self.game) + tower = newram_map.poke_tower(self.game) + gym1 = newram_map.gym1(self.game) + gym2 = newram_map.gym2(self.game) + gym3 = newram_map.gym3(self.game) + gym4 = newram_map.gym4(self.game) + gym5 = newram_map.gym5(self.game) + gym6 = newram_map.gym6(self.game) + gym7 = newram_map.gym7(self.game) + gym8 = newram_map.gym8(self.game) + rival = newram_map.rival(self.game) - bill_capt_rew = ram_map.bill_capt(self.game) - if ram_map.used_cut(self.game) == 61: - ram_map.write_mem(self.game, 0xCD4D, 00) # address, byte to write - self.used_cut += 1 - - used_cut_rew = self.used_cut * 5 + cut_rew = self.cut * 10 + event_reward = sum([silph, rock_tunnel, ssanne, mtmoon, routes, misc, snorlax, hmtm, bill, oak, towns, lab, mansion, safari, dojo, hideout, tower, gym1, gym2, gym3, gym4, gym5, gym6, gym7, gym8, rival]) + seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) + caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) + moves_obtained_reward = self.reward_scale * sum(self.moves_obtained) + used_cut_rew = self.used_cut * 0.1 + cut_coords = sum(self.cut_coords.values()) * 1.0 + cut_tiles = len(self.cut_tiles) * 1.0 start_menu = self.seen_start_menu * 0.01 pokemon_menu = self.seen_pokemon_menu * 0.1 stats_menu = self.seen_stats_menu * 0.1 bag_menu = self.seen_bag_menu * 0.1 - # "cancel_bag_menu": self.seen_cancel_bag_menu * 0.1, - cut_coords = sum(self.cut_coords.values()) * 1.0 - cut_tiles = len(self.cut_tiles) * 1.0 - that_guy = (start_menu + pokemon_menu + stats_menu + bag_menu + cut_coords + cut_tiles) + that_guy = (start_menu + pokemon_menu + stats_menu + bag_menu ) / 2 - seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) * 0.00010 - caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) * 0.00010 - moves_obtained_reward = self.reward_scale * sum(self.moves_obtained) * 0.00010 - reward = self.reward_scale * ( - event_reward - + bill_capt_rew - + seen_pokemon_reward - + caught_pokemon_reward - + moves_obtained_reward - + bill_reward - + hm_reward + level_reward - + death_reward - + badges_reward + healing_reward + exploration_reward + cut_rew - + that_guy + + event_reward + + seen_pokemon_reward + + caught_pokemon_reward + + moves_obtained_reward + used_cut_rew + + cut_coords + + cut_tiles + + that_guy ) # Subtract previous reward @@ -522,33 +508,70 @@ def step(self, action, fast_video=True): if self.save_video and done: self.full_frame_writer.close() if done: + poke = self.game.get_memory_value(0xD16B) + level = self.game.get_memory_value(0xD18C) + if poke == 57 and level == 0: + self.glitch_state() info = { - "reward": { - "delta": reward, - "event": event_reward, - "level": level_reward, - "badges": badges_reward, - "bill_saved_reward": bill_reward, - "hm_count_reward": hm_reward, - "healing": healing_reward, - "exploration": exploration_reward, - "seen_pokemon_reward": seen_pokemon_reward, - "caught_pokemon_reward": caught_pokemon_reward, - "moves_obtained_reward": moves_obtained_reward, + "Events": { + "silph": silph, + "rock_tunnel": rock_tunnel, + "ssanne": ssanne, + "mtmoon": mtmoon, + "routes": routes, + "misc": misc, + "snorlax": snorlax, + "hmtm": hmtm, + "bill": bill, + "oak": oak, + "towns": towns, + "lab": lab, + "mansion": mansion, + "safari": safari, + "dojo": dojo, + "hideout": hideout, + "tower": tower, + "gym1": gym1, + "gym2": gym2, + "gym3": gym3, + "gym4": gym4, + "gym5": gym5, + "gym6": gym6, + "gym7": gym7, + "gym8": gym8, + "rival": rival, + }, + "BET": { + "Reward_Delta": reward, + "Seen_Poke": seen_pokemon_reward, + "Caught_Poke": caught_pokemon_reward, + "Moves_Obtain": moves_obtained_reward, + # "Get_HM": hm_reward, + "Level": level_reward, + "Death": death_reward, + "Healing": healing_reward, + "Exploration": exploration_reward, + "Taught_Cut": cut_rew, + "Menuing": that_guy, + "Used_Cut": used_cut_rew, + "Cut_Coords": cut_coords, + "Cut_Tiles": cut_tiles, }, - "bill_saved": bill_state, "hm_count": hm_count, "cut_taught": self.cut, "badge_1": float(badges >= 1), "badge_2": float(badges >= 2), "badge_3": float(badges >= 3), - "event": events, + "badge_4": float(badges >= 4), + "badge_5": float(badges >= 5), + "badge_6": float(badges >= 6), + "badge_7": float(badges >= 7), + "badge_8": float(badges >= 8), + "badges": float(badges), "maps_explored": np.sum(self.seen_maps), "party_size": party_size, - # "pokemon_exploration_map": self.counts_map, "moves_obtained": sum(self.moves_obtained), "deaths": self.death_count, - "bill_capt": (bill_capt_rew/5), 'cut_coords': cut_coords, 'cut_tiles': cut_tiles, 'bag_menu': bag_menu, @@ -556,15 +579,8 @@ def step(self, action, fast_video=True): 'pokemon_menu': pokemon_menu, 'start_menu': start_menu, 'used_cut': self.used_cut, - # "highest_pokemon_level": max(party_levels), - # "total_party_level": sum(party_levels), - # "money": money, - # "ss_anne_state": ss_anne_state, - # "seen_npcs_count": len(self.seen_npcs), - # "seen_pokemon": sum(self.seen_pokemon), - # "caught_pokemon": sum(self.caught_pokemon), - # "hidden_obj_count": len(self.seen_hidden_objs), - # "logging": pokemon, + 'gym_three': self.gymthree, + 'gym_four': self.gymfour, } - return self.render(), reward, done, done, info + return self.render(), reward, done, done, info \ No newline at end of file diff --git a/pokegym/glitch/a_key_map.txt b/pokegym/glitch/a_key_map.txt new file mode 100644 index 0000000..dcf9dde --- /dev/null +++ b/pokegym/glitch/a_key_map.txt @@ -0,0 +1,8 @@ +, 7, +, 6, +, 5, +, 4, +, 3, +, 2, +, 1, +, 0, \ No newline at end of file diff --git a/pokegym/newram_map.py b/pokegym/newram_map.py index 2bb8ac8..15861ec 100644 --- a/pokegym/newram_map.py +++ b/pokegym/newram_map.py @@ -173,156 +173,6 @@ def mtmoon(game): Got_Dome_Fossil, Got_Helix_Fossil]) def routes(game): - # "0xD7C3-2": "Beat Route 3 Trainer 0", - # "0xD7C3-3": "Beat Route 3 Trainer 1", - # "0xD7C3-4": "Beat Route 3 Trainer 2", - # "0xD7C3-5": "Beat Route 3 Trainer 3", - # "0xD7C3-6": "Beat Route 3 Trainer 4", - # "0xD7C3-7": "Beat Route 3 Trainer 5", - # "0xD7C4-0": "Beat Route 3 Trainer 6", - # "0xD7C4-1": "Beat Route 3 Trainer 7", - # "0xD7C5-2": "Beat Route 4 Trainer 0", - # "0xD7EF-1": "Beat Route24 Rocket", - # "0xD7EF-2": "Beat Route 24 Trainer 0", - # "0xD7EF-3": "Beat Route 24 Trainer 1", - # "0xD7EF-4": "Beat Route 24 Trainer 2", - # "0xD7EF-5": "Beat Route 24 Trainer 3", - # "0xD7EF-6": "Beat Route 24 Trainer 4", - # "0xD7EF-7": "Beat Route 24 Trainer 5", - # "0xD7F1-1": "Beat Route 25 Trainer 0", - # "0xD7F1-2": "Beat Route 25 Trainer 1", - # "0xD7F1-3": "Beat Route 25 Trainer 2", - # "0xD7F1-4": "Beat Route 25 Trainer 3", - # "0xD7F1-5": "Beat Route 25 Trainer 4", - # "0xD7F1-6": "Beat Route 25 Trainer 5", - # "0xD7F1-7": "Beat Route 25 Trainer 6", - # "0xD7F2-0": "Beat Route 25 Trainer 7", - # "0xD7F2-1": "Beat Route 25 Trainer 8", - # "0xD7CF-1": "Beat Route 9 Trainer 0", - # "0xD7CF-2": "Beat Route 9 Trainer 1", - # "0xD7CF-3": "Beat Route 9 Trainer 2", - # "0xD7CF-4": "Beat Route 9 Trainer 3", - # "0xD7CF-5": "Beat Route 9 Trainer 4", - # "0xD7CF-6": "Beat Route 9 Trainer 5", - # "0xD7CF-7": "Beat Route 9 Trainer 6", - # "0xD7D0-0": "Beat Route 9 Trainer 7", - # "0xD7D0-1": "Beat Route 9 Trainer 8", - # "0xD7C9-1": "Beat Route 6 Trainer 0", - # "0xD7C9-2": "Beat Route 6 Trainer 1", - # "0xD7C9-3": "Beat Route 6 Trainer 2", - # "0xD7C9-4": "Beat Route 6 Trainer 3", - # "0xD7C9-5": "Beat Route 6 Trainer 4", - # "0xD7C9-6": "Beat Route 6 Trainer 5", - # "0xD7D5-1": "Beat Route 11 Trainer 0", - # "0xD7D5-2": "Beat Route 11 Trainer 1", - # "0xD7D5-3": "Beat Route 11 Trainer 2", - # "0xD7D5-4": "Beat Route 11 Trainer 3", - # "0xD7D5-5": "Beat Route 11 Trainer 4", - # "0xD7D5-6": "Beat Route 11 Trainer 5", - # "0xD7D5-7": "Beat Route 11 Trainer 6", - # "0xD7D6-0": "Beat Route 11 Trainer 7", - # "0xD7D6-1": "Beat Route 11 Trainer 8", - # "0xD7D6-2": "Beat Route 11 Trainer 9", - # "0xD7CD-1": "Beat Route 8 Trainer 0", - # "0xD7CD-2": "Beat Route 8 Trainer 1", - # "0xD7CD-3": "Beat Route 8 Trainer 2", - # "0xD7CD-4": "Beat Route 8 Trainer 3", - # "0xD7CD-5": "Beat Route 8 Trainer 4", - # "0xD7CD-6": "Beat Route 8 Trainer 5", - # "0xD7CD-7": "Beat Route 8 Trainer 6", - # "0xD7CE-0": "Beat Route 8 Trainer 7", - # "0xD7CE-1": "Beat Route 8 Trainer 8", - # "0xD7D1-1": "Beat Route 10 Trainer 0", - # "0xD7D1-2": "Beat Route 10 Trainer 1", - # "0xD7D1-3": "Beat Route 10 Trainer 2", - # "0xD7D1-4": "Beat Route 10 Trainer 3", - # "0xD7D1-5": "Beat Route 10 Trainer 4", - # "0xD7D1-6": "Beat Route 10 Trainer 5", - # "0xD7D7-2": "Beat Route 12 Trainer 0", - # "0xD7D7-3": "Beat Route 12 Trainer 1", - # "0xD7D7-4": "Beat Route 12 Trainer 2", - # "0xD7D7-5": "Beat Route 12 Trainer 3", - # "0xD7D7-6": "Beat Route 12 Trainer 4", - # "0xD7D7-7": "Beat Route 12 Trainer 5", - # "0xD7D8-0": "Beat Route 12 Trainer 6", - # "0xD7DF-1": "Beat Route 16 Trainer 0", - # "0xD7DF-2": "Beat Route 16 Trainer 1", - # "0xD7DF-3": "Beat Route 16 Trainer 2", - # "0xD7DF-4": "Beat Route 16 Trainer 3", - # "0xD7DF-5": "Beat Route 16 Trainer 4", - # "0xD7DF-6": "Beat Route 16 Trainer 5", - # "0xD7E1-1": "Beat Route 17 Trainer 0", - # "0xD7E1-2": "Beat Route 17 Trainer 1", - # "0xD7E1-3": "Beat Route 17 Trainer 2", - # "0xD7E1-4": "Beat Route 17 Trainer 3", - # "0xD7E1-5": "Beat Route 17 Trainer 4", - # "0xD7E1-6": "Beat Route 17 Trainer 5", - # "0xD7E1-7": "Beat Route 17 Trainer 6", - # "0xD7E2-0": "Beat Route 17 Trainer 7", - # "0xD7E2-1": "Beat Route 17 Trainer 8", - # "0xD7E2-2": "Beat Route 17 Trainer 9", - # "0xD7D9-1": "Beat Route 13 Trainer 0", - # "0xD7D9-2": "Beat Route 13 Trainer 1", - # "0xD7D9-3": "Beat Route 13 Trainer 2", - # "0xD7D9-4": "Beat Route 13 Trainer 3", - # "0xD7D9-5": "Beat Route 13 Trainer 4", - # "0xD7D9-6": "Beat Route 13 Trainer 5", - # "0xD7D9-7": "Beat Route 13 Trainer 6", - # "0xD7DA-0": "Beat Route 13 Trainer 7", - # "0xD7DA-1": "Beat Route 13 Trainer 8", - # "0xD7DA-2": "Beat Route 13 Trainer 9", - # "0xD7DB-1": "Beat Route 14 Trainer 0", - # "0xD7DB-2": "Beat Route 14 Trainer 1", - # "0xD7DB-3": "Beat Route 14 Trainer 2", - # "0xD7DB-4": "Beat Route 14 Trainer 3", - # "0xD7DB-5": "Beat Route 14 Trainer 4", - # "0xD7DB-6": "Beat Route 14 Trainer 5", - # "0xD7DB-7": "Beat Route 14 Trainer 6", - # "0xD7DC-0": "Beat Route 14 Trainer 7", - # "0xD7DC-1": "Beat Route 14 Trainer 8", - # "0xD7DC-2": "Beat Route 14 Trainer 9", - # "0xD7DD-1": "Beat Route 15 Trainer 0", - # "0xD7DD-2": "Beat Route 15 Trainer 1", - # "0xD7DD-3": "Beat Route 15 Trainer 2", - # "0xD7DD-4": "Beat Route 15 Trainer 3", - # "0xD7DD-5": "Beat Route 15 Trainer 4", - # "0xD7DD-6": "Beat Route 15 Trainer 5", - # "0xD7DD-7": "Beat Route 15 Trainer 6", - # "0xD7DE-0": "Beat Route 15 Trainer 7", - # "0xD7DE-1": "Beat Route 15 Trainer 8", - # "0xD7DE-2": "Beat Route 15 Trainer 9", - # "0xD7E3-1": "Beat Route 18 Trainer 0", - # "0xD7E3-2": "Beat Route 18 Trainer 1", - # "0xD7E3-3": "Beat Route 18 Trainer 2", - # "0xD7E5-1": "Beat Route 19 Trainer 0", - # "0xD7E5-2": "Beat Route 19 Trainer 1", - # "0xD7E5-3": "Beat Route 19 Trainer 2", - # "0xD7E5-4": "Beat Route 19 Trainer 3", - # "0xD7E5-5": "Beat Route 19 Trainer 4", - # "0xD7E5-6": "Beat Route 19 Trainer 5", - # "0xD7E5-7": "Beat Route 19 Trainer 6", - # "0xD7E6-0": "Beat Route 19 Trainer 7", - # "0xD7E6-1": "Beat Route 19 Trainer 8", - # "0xD7E6-2": "Beat Route 19 Trainer 9", - # "0xD7E7-1": "Beat Route 20 Trainer 0", - # "0xD7E7-2": "Beat Route 20 Trainer 1", - # "0xD7E7-3": "Beat Route 20 Trainer 2", - # "0xD7E7-4": "Beat Route 20 Trainer 3", - # "0xD7E7-5": "Beat Route 20 Trainer 4", - # "0xD7E7-6": "Beat Route 20 Trainer 5", - # "0xD7E7-7": "Beat Route 20 Trainer 6", - # "0xD7E8-0": "Beat Route 20 Trainer 7", - # "0xD7E8-1": "Beat Route 20 Trainer 8", - # "0xD7E8-2": "Beat Route 20 Trainer 9", - # "0xD7E9-1": "Beat Route 21 Trainer 0", - # "0xD7E9-2": "Beat Route 21 Trainer 1", - # "0xD7E9-3": "Beat Route 21 Trainer 2", - # "0xD7E9-4": "Beat Route 21 Trainer 3", - # "0xD7E9-5": "Beat Route 21 Trainer 4", - # "0xD7E9-6": "Beat Route 21 Trainer 5", - # "0xD7E9-7": "Beat Route 21 Trainer 6", - # "0xD7EA-0": "Beat Route 21 Trainer 7", - # "0xD7EA-1": "Beat Route 21 Trainer 8", route3_0 = TRAINER * int(read_bit(game, 0xD7C3, 2)) route3_1 = TRAINER * int(read_bit(game, 0xD7C3, 3)) route3_2 = TRAINER * int(read_bit(game, 0xD7C3, 4)) @@ -532,7 +382,7 @@ def misc(game): one = TASK * int(read_bit(game, 0xD7C6, 7)) two = TASK * int(read_bit(game, 0xD747, 3)) three = TASK * int(read_bit(game, 0xD74A, 2)) -# four = TASK * int(read_bit(game, 0xD754, 0)) + four = BAD * int(read_bit(game, 0xD754, 0)) five = TASK * int(read_bit(game, 0xD754, 1)) six = TASK * int(read_bit(game, 0xD771, 1)) seven = TASK * int(read_bit(game, 0xD77E, 2)) @@ -549,10 +399,6 @@ def misc(game): return sum([one, two, three, five, six, seven, eight, nine, ten, eleven, twelve, thirteen, fourteen, fifteen, sixteen]) def snorlax(game): - # "0xD7D8-6": "Fight Route12 Snorlax", - # "0xD7D8-7": "Beat Route12 Snorlax", - # "0xD7E0-0": "Fight Route16 Snorlax", - # "0xD7E0-1": "Beat Route16 Snorlax", route12_snorlax_fight = POKEMON * int(read_bit(game, 0xD7D8, 6)) route12_snorlax_beat = POKEMON * int(read_bit(game, 0xD7D8, 7)) route16_snorlax_fight = POKEMON * int(read_bit(game, 0xD7E0, 0)) @@ -561,31 +407,6 @@ def snorlax(game): return sum([route12_snorlax_fight, route12_snorlax_beat, route16_snorlax_fight, route16_snorlax_beat]) def hmtm(game): - # "0xD803-0": "Got Hm01", - # "0xD7E0-6": "Got Hm02", - # "0xD857-0": "Got Hm03", - # "0xD78E-0": "Got Hm04", - # "0xD7C2-0": "Got Hm05", - - # "0xD755-6": "Got Tm34", - # "0xD75E-6": "Got Tm11", - # "0xD777-0": "Got Tm41", - # "0xD778-4": "Got Tm13", - # "0xD778-5": "Got Tm48", - # "0xD778-6": "Got Tm49", - # "0xD778-7": "Got Tm18", - # "0xD77C-0": "Got Tm21", - # "0xD792-0": "Got Tm06", - # "0xD773-6": "Got Tm24", - # "0xD7BD-0": "Got Tm29", - # "0xD7AF-0": "Got Tm31", - # "0xD7A1-7": "Got Tm35", - # "0xD826-7": "Got Tm36", - # "0xD79A-0": "Got Tm38", - # "0xD751-0": "Got Tm27", - # "0xD74C-1": "Got Tm42", - # "0xD7B3-0": "Got Tm46", - # "0xD7D7-0": "Got Tm39", hm01 = HM * int(read_bit(game, 0xD803, 0)) hm02 = HM * int(read_bit(game, 0xD7E0, 6)) hm03 = HM * int(read_bit(game, 0xD857, 0)) @@ -616,12 +437,6 @@ def hmtm(game): return sum([hm01, hm02, hm03, hm04, hm05, tm34, tm11, tm41, tm13, tm48, tm49, tm18, tm21, tm06, tm24, tm29, tm31, tm35, tm36, tm38, tm27, tm42, tm46, tm39]) def bill(game): - # "0xD7F1-0": "Met Bill", - # "0xD7F2-3": "Used Cell Separator On Bill", - # "0xD7F2-4": "Got Ss Ticket", - # "0xD7F2-5": "Met Bill 2", - # "0xD7F2-6": "Bill Said Use Cell Separator", - # "0xD7F2-7": "Left Bills House After Helping", met_bill = BILL_CAPT * int(read_bit(game, 0xD7F1, 0)) used_cell_separator_on_bill = BILL_CAPT * int(read_bit(game, 0xD7F2, 3)) got_ss_ticket = BILL_CAPT * int(read_bit(game, 0xD7F2, 4)) @@ -633,17 +448,6 @@ def bill(game): return sum([met_bill, used_cell_separator_on_bill, got_ss_ticket, met_bill_2, bill_said_use_cell_separator, left_bills_house_after_helping]) def oak(game): - # "0xD74B-7": "Oak Appeared In Pallet", - # "0xD747-0": "Followed Oak Into Lab", - # "0xD74B-1": "Oak Asked To Choose Mon", - # "0xD74B-2": "Got Starter", - # "0xD74B-0": "Followed Oak Into Lab 2", - # "0xD74B-5": "Got Pokedex", - # "0xD74E-1": "Got Oaks Parcel", - # "0xD747-6": "Pallet After Getting Pokeballs", - # "0xD74E-0": "Oak Got Parcel", - # "0xD74B-4": "Got Pokeballs From Oak", - # "0xD74B-6": "Pallet After Getting Pokeballs 2", oak_appeared_in_pallet = TASK * int(read_bit(game, 0xD74B, 7)) followed_oak_into_lab = TASK * int(read_bit(game, 0xD747, 0)) oak_asked_to_choose_mon = TASK * int(read_bit(game, 0xD74B, 1)) @@ -660,18 +464,6 @@ def oak(game): got_oaks_parcel, pallet_after_getting_pokeballs, oak_got_parcel, got_pokeballs_from_oak, pallet_after_getting_pokeballs_2]) def towns(game): - # "0xD74A-0": "Got Town Map", - # "0xD74A-1": "Entered Blues House", - # "0xD7F3-2": "Beat Viridian Forest Trainer 0", - # "0xD7F3-3": "Beat Viridian Forest Trainer 1", - # "0xD7F3-4": "Beat Viridian Forest Trainer 2", - # "0xD7EF-0": "Got Nugget", - # "0xD7F0-1": "Nugget Reward Available", - # "0xD75B-7": "Beat Cerulean Rocket Thief", - # "0xD75F-0": "Got Bicycle", - # "0xD771-6": "Seel Fan Boast", - # "0xD771-7": "Pikachu Fan Boast", - # "0xD76C-0": "Got Poke Flute", got_town_map = TASK * int(read_bit(game, 0xD74A, 0)) entered_blues_house = TASK * int(read_bit(game, 0xD74A, 1)) beat_viridian_forest_trainer_0 = TRAINER * int(read_bit(game, 0xD7F3, 2)) @@ -691,9 +483,6 @@ def towns(game): seel_fan_boast, pikachu_fan_boast, got_poke_flute]) def lab(game): - # "0xD7A3-0": "Gave Fossil To Lab", - # "0xD7A3-1": "Lab Still Reviving Fossil", - # "0xD7A3-2": "Lab Handing Over Fossil Mon", gave_fossil_to_lab = TASK * int(read_bit(game, 0xD7A3, 0)) lab_still_reviving_fossil = TASK * int(read_bit(game, 0xD7A3, 1)) lab_handing_over_fossil_mon = TASK * int(read_bit(game, 0xD7A3, 2)) @@ -701,13 +490,6 @@ def lab(game): return sum([gave_fossil_to_lab, lab_still_reviving_fossil, lab_handing_over_fossil_mon]) def mansion(game): - # "0xD847-1": "Beat Mansion 2 Trainer 0", - # "0xD849-1": "Beat Mansion 3 Trainer 0", - # "0xD849-2": "Beat Mansion 3 Trainer 1", - # "0xD84B-1": "Beat Mansion 4 Trainer 0", - # "0xD84B-2": "Beat Mansion 4 Trainer 1", - # "0xD796-0": "Mansion Switch On", - # "0xD798-1": "Beat Mansion 1 Trainer 0", beat_mansion_2_trainer_0 = TRAINER * int(read_bit(game, 0xD847, 1)) beat_mansion_3_trainer_0 = TRAINER * int(read_bit(game, 0xD849, 1)) beat_mansion_3_trainer_1 = TRAINER * int(read_bit(game, 0xD849, 2)) @@ -721,9 +503,6 @@ def mansion(game): beat_mansion_4_trainer_0, beat_mansion_4_trainer_1, mansion_switch_on, beat_mansion_1_trainer_0]) def safari(game): - # "0xD78E-1": "Gave Gold Teeth", - # "0xD790-6": "Safari Game Over", - # "0xD790-7": "In Safari Zone", gave_gold_teeth = QUEST * int(read_bit(game, 0xD78E, 1)) safari_game_over = EVENT * int(read_bit(game, 0xD790, 6)) in_safari_zone = EVENT * int(read_bit(game, 0xD790, 7)) @@ -731,14 +510,6 @@ def safari(game): return sum([gave_gold_teeth, safari_game_over, in_safari_zone]) def dojo(game): - # "0xD7B1-0": "Defeated Fighting Dojo", - # "0xD7B1-1": "Beat Karate Master", - # "0xD7B1-2": "Beat Fighting Dojo Trainer 0", - # "0xD7B1-3": "Beat Fighting Dojo Trainer 1", - # "0xD7B1-4": "Beat Fighting Dojo Trainer 2", - # "0xD7B1-5": "Beat Fighting Dojo Trainer 3", - # "0xD7B1-6": "Got Hitmonlee", - # "0xD7B1-7": "Got Hitmonchan", defeated_fighting_dojo = BAD * int(read_bit(game, 0xD7B1, 0)) beat_karate_master = GYM_LEADER * int(read_bit(game, 0xD7B1, 1)) beat_dojo_trainer_0 = TRAINER * int(read_bit(game, 0xD7B1, 2)) @@ -753,21 +524,6 @@ def dojo(game): got_hitmonlee, got_hitmonchan]) def hideout(game): - # "0xD815-1": "Beat Rocket Hideout 1 Trainer 0", - # "0xD815-2": "Beat Rocket Hideout 1 Trainer 1", - # "0xD815-3": "Beat Rocket Hideout 1 Trainer 2", - # "0xD815-4": "Beat Rocket Hideout 1 Trainer 3", - # "0xD815-5": "Beat Rocket Hideout 1 Trainer 4", - # "0xD817-1": "Beat Rocket Hideout 2 Trainer 0", - # "0xD819-1": "Beat Rocket Hideout 3 Trainer 0", - # "0xD819-2": "Beat Rocket Hideout 3 Trainer 1", - # "0xD81B-2": "Beat Rocket Hideout 4 Trainer 0", - # "0xD81B-3": "Beat Rocket Hideout 4 Trainer 1", - # "0xD81B-4": "Beat Rocket Hideout 4 Trainer 2", - # "0xD81B-5": "Rocket Hideout 4 Door Unlocked", - # "0xD81B-6": "Rocket Dropped Lift Key", - # "0xD81B-7": "Beat Rocket Hideout Giovanni", - # "0xD77E-1": "Found Rocket Hideout", beat_rocket_hideout_1_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD815, 1)) beat_rocket_hideout_1_trainer_1 = GYM_TRAINER * int(read_bit(game, 0xD815, 2)) beat_rocket_hideout_1_trainer_2 = GYM_TRAINER * int(read_bit(game, 0xD815, 3)) @@ -790,24 +546,6 @@ def hideout(game): rocket_dropped_lift_key, beat_rocket_hideout_giovanni, found_rocket_hideout]) def poke_tower(game): - # "0xD765-1": "Beat Pokemontower 3 Trainer 0", - # "0xD765-2": "Beat Pokemontower 3 Trainer 1", - # "0xD765-3": "Beat Pokemontower 3 Trainer 2", - # "0xD766-1": "Beat Pokemontower 4 Trainer 0", - # "0xD766-2": "Beat Pokemontower 4 Trainer 1", - # "0xD766-3": "Beat Pokemontower 4 Trainer 2", - # "0xD767-2": "Beat Pokemontower 5 Trainer 0", - # "0xD767-3": "Beat Pokemontower 5 Trainer 1", - # "0xD767-4": "Beat Pokemontower 5 Trainer 2", - # "0xD767-5": "Beat Pokemontower 5 Trainer 3", - # "0xD767-7": "In Purified Zone", - # "0xD768-1": "Beat Pokemontower 6 Trainer 0", - # "0xD768-2": "Beat Pokemontower 6 Trainer 1", - # "0xD768-3": "Beat Pokemontower 6 Trainer 2", - # "0xD768-7": "Beat Ghost Marowak", - # "0xD769-1": "Beat Pokemontower 7 Trainer 0", - # "0xD769-2": "Beat Pokemontower 7 Trainer 1", - # "0xD769-3": "Beat Pokemontower 7 Trainer 2", beat_pokemontower_3_trainer_0 = TRAINER * int(read_bit(game, 0xD765, 1)) beat_pokemontower_3_trainer_1 = TRAINER * int(read_bit(game, 0xD765, 2)) beat_pokemontower_3_trainer_2 = TRAINER * int(read_bit(game, 0xD765, 3)) From 918ecefb67804fbad66bb17ea7b3431b89b8458e Mon Sep 17 00:00:00 2001 From: leanke Date: Tue, 2 Apr 2024 04:47:09 +0000 Subject: [PATCH 15/29] heal and respawn rewards --- pokegym/environment.py | 83 ++++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index 58e9c0b..265da78 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -22,7 +22,7 @@ load_pyboy_state, run_action_on_emulator, ) -from pokegym import newram_map, game_map, data +from pokegym import newram_map, data STATE_PATH = __file__.rstrip("environment.py") + "States/" @@ -63,6 +63,7 @@ def __init__( self.use_screen_memory = True self.screenshot_counter = 0 self.env_id = Path(f'{str(uuid.uuid4())[:4]}') + self.s_path = __file__.rstrip("environment.py") + "Video/" self.reset_count = 0 self.explore_hidden_obj_weight = 1 @@ -173,10 +174,10 @@ def close(self): class Environment(Base): def __init__(self,rom_path="pokemon_red.gb",state_path=None,headless=True,save_video=False,quiet=False,verbose=False,**kwargs,): - super().__init__(rom_path, state_path, headless, save_video, quiet, **kwargs) + load_pyboy_state(self.game, self.load_last_state()) self.counts_map = np.zeros((444, 436)) - self.death_count = 0 + self.verbose = verbose self.include_conditions = [] self.seen_maps_difference = set() @@ -185,10 +186,12 @@ def __init__(self,rom_path="pokemon_red.gb",state_path=None,headless=True,save_v self.last_map = -1 self.log = True self.used_cut = 0 + self.cut_reset = 0 # self.seen_coords = set() self.map_check = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - load_pyboy_state(self.game, self.load_last_state()) - + + coord_rewards = [] + def update_pokedex(self): for i in range(0xD30A - 0xD2F7): caught_mem = self.game.get_memory_value(i + 0xD2F7) @@ -215,6 +218,7 @@ def update_moves_obtained(self): self.moves_obtained[move_id] = 1 if move_id == 15: self.cut = 1 + self.cut_reset = 1 # Scan current box (since the box doesn't auto increment in pokemon red) num_moves = 4 box_struct_length = 25 * num_moves * 2 @@ -273,11 +277,30 @@ def check_if_cancel_bag_menu(self, action) -> bool: and newram_map.mem_val(self.game, 0xCF94) == 3 and newram_map.mem_val(self.game, 0xD31D) == newram_map.mem_val(self.game, 0xCC36) + newram_map.mem_val(self.game, 0xCC26) ) + + def heal_reward(self): + cur_health = newram_map.hp(self.game) + # if health increased and party size did not change + if (cur_health > self.last_hp and newram_map.read_m(0xD163) == self.last_party_size): + if self.last_hp > 0: + heal_amount = cur_health - self.last_hp + if heal_amount > 0.5: + print(f'healed: {heal_amount}') + self.total_healing += heal_amount * 4 + else: + self.death_count += 1 def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" self.reset_count += 1 - + self.cut = 0 + # if self.reset_count % 10 == 0: + # load_pyboy_state(self.game, self.load_first_state()) + # self.seen_coords = set() + # full reset + # if self.reset_count % 51 == 0 and self.cut_reset == 0: + # load_pyboy_state(self.game, self.load_first_state()) # full reset if no cut at 100m total step 1m agent step + if self.save_video: base_dir = self.s_path base_dir.mkdir(parents=True, exist_ok=True) @@ -305,7 +328,6 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.last_hp = 1.0 self.last_party_size = 1 self.hm_count = 0 - self.cut = 0 self.cut_coords = {} self.cut_tiles = {} # set([]) self.cut_state = deque(maxlen=3) @@ -320,7 +342,9 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.town = 1 self.gymthree = 0 self.gymfour = 0 + self.death_count = 0 self.expl = 0 + self.respawn = set() return self.render(), {} @@ -334,7 +358,7 @@ def step(self, action, fast_video=True): # Exploration reward r, c, map_n = newram_map.position(self.game) if self.cut == 1: - if r == 0x1f and c == 0x23 and map_n == 0x06: + if r == 0x1F and c == 0x23 and map_n == 0x06: if (r, c, map_n) not in self.seen_coords: self.expl += 5 if r == 0x11 and c == 0x0F and map_n == 0x05: @@ -346,6 +370,13 @@ def step(self, action, fast_video=True): self.seen_coords.add((r, c, map_n)) exploration_reward = (0.02 * len(self.seen_coords)) + self.expl + # respawn reward + center = self.game.get_memory_value(0xD719) + self.respawn.add(center) + respawn_reward = len(self.respawn) * 5 + + + # map check if map_n == 92: self.gymthree = 1 if map_n == 134: @@ -363,17 +394,18 @@ def step(self, action, fast_video=True): hp = newram_map.hp(self.game) hp_delta = hp - self.last_hp party_size_constant = party_size == self.last_party_size - if hp_delta > 0.5 and party_size_constant and not self.is_dead: + if hp_delta > 0.4 and party_size_constant and not self.is_dead: self.total_healing += hp_delta if hp <= 0 and self.last_hp > 0: self.death_count += 1 self.is_dead = True + # self.seen_coords = set() elif hp > 0.01: # TODO: Check if this matters self.is_dead = False self.last_hp = hp self.last_party_size = party_size - death_reward = 0 # -0.08 * self.death_count # -0.05 - healing_reward = self.total_healing + + # HM reward hm_count = newram_map.get_hm_count(self.game) @@ -399,12 +431,12 @@ def step(self, action, fast_video=True): coords = (x + 1, y, map_id) self.cut_state.append( ( - self.game.get_memory_value(0xCFC6), - self.game.get_memory_value(0xCFCB), - self.game.get_memory_value(0xCD6A), - self.game.get_memory_value(0xD367), - self.game.get_memory_value(0xD125), - self.game.get_memory_value(0xCD3D), + self.game.get_memory_value(0xCFC6), # background tile number in front of the player (either 1 or 2 steps ahead) + self.game.get_memory_value(0xCFCB), # $00 = causes sprites to be hidden and the value to change to $ff + self.game.get_memory_value(0xCD6A), # When the player tries to use an item or use certain field moves, 0 is stored + self.game.get_memory_value(0xD367), # wCurMapTileset + self.game.get_memory_value(0xD125), # wTextBoxID + self.game.get_memory_value(0xCD3D), # wFallingObjectsMovementData up to 20 bytes (one byte for each falling object) ) ) if tuple(list(self.cut_state)[1:]) in CUT_SEQ: @@ -436,6 +468,7 @@ def step(self, action, fast_video=True): badges = newram_map.badges(self.game) self.update_pokedex() self.update_moves_obtained() + # self.heal_reward() silph = newram_map.silph_co(self.game) rock_tunnel = newram_map.rock_tunnel(self.game) @@ -463,13 +496,14 @@ def step(self, action, fast_video=True): gym7 = newram_map.gym7(self.game) gym8 = newram_map.gym8(self.game) rival = newram_map.rival(self.game) + bulba_check = newram_map.bulba(self.game) cut_rew = self.cut * 10 event_reward = sum([silph, rock_tunnel, ssanne, mtmoon, routes, misc, snorlax, hmtm, bill, oak, towns, lab, mansion, safari, dojo, hideout, tower, gym1, gym2, gym3, gym4, gym5, gym6, gym7, gym8, rival]) seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) moves_obtained_reward = self.reward_scale * sum(self.moves_obtained) - used_cut_rew = self.used_cut * 0.1 + used_cut_rew = self.used_cut * 0.05 cut_coords = sum(self.cut_coords.values()) * 1.0 cut_tiles = len(self.cut_tiles) * 1.0 start_menu = self.seen_start_menu * 0.01 @@ -477,10 +511,14 @@ def step(self, action, fast_video=True): stats_menu = self.seen_stats_menu * 0.1 bag_menu = self.seen_bag_menu * 0.1 that_guy = (start_menu + pokemon_menu + stats_menu + bag_menu ) / 2 + + death_reward = -0.02 * self.death_count # -0.05 + healing_reward = self.total_healing * 2 reward = self.reward_scale * ( - + level_reward + level_reward + healing_reward + + death_reward + exploration_reward + cut_rew + event_reward @@ -491,6 +529,8 @@ def step(self, action, fast_video=True): + cut_coords + cut_tiles + that_guy + + bulba_check + + respawn_reward ) # Subtract previous reward @@ -556,6 +596,8 @@ def step(self, action, fast_video=True): "Used_Cut": used_cut_rew, "Cut_Coords": cut_coords, "Cut_Tiles": cut_tiles, + "Bulba_Check": bulba_check, + "Respawn": respawn_reward }, "hm_count": hm_count, "cut_taught": self.cut, @@ -581,6 +623,7 @@ def step(self, action, fast_video=True): 'used_cut': self.used_cut, 'gym_three': self.gymthree, 'gym_four': self.gymfour, + "respawn_coord_len": len(self.respawn) } - return self.render(), reward, done, done, info \ No newline at end of file + return self.render(), reward, done, done, info From 5f3697dffc40df504428e096c5ee375c8eba89ff Mon Sep 17 00:00:00 2001 From: leanke Date: Sun, 5 May 2024 13:27:33 +0000 Subject: [PATCH 16/29] moved things --- pokegym/data.py | 270 ++-- pokegym/environment.py | 347 ++--- pokegym/game_map.py | 18 - pokegym/map_data.json | 2716 -------------------------------------- pokegym/newram_map.py | 779 ----------- pokegym/pyboy_binding.py | 4 - pokegym/ram_map.py | 985 +++++++++++--- 7 files changed, 1121 insertions(+), 3998 deletions(-) delete mode 100644 pokegym/game_map.py delete mode 100755 pokegym/map_data.json delete mode 100644 pokegym/newram_map.py diff --git a/pokegym/data.py b/pokegym/data.py index 7420e0f..2290b75 100644 --- a/pokegym/data.py +++ b/pokegym/data.py @@ -501,63 +501,183 @@ } poke_dict = { - 3: {'hex': '3', 'decimal': '3', 'name': 'Nidoran♂'}, - 4: {'hex': '4', 'decimal': '4', 'name': 'Clefairy'}, - 5: {'hex': '5', 'decimal': '5', 'name': 'Spearow'}, - 9: {'hex': '9', 'decimal': '9', 'name': 'Ivysaur'}, - 15: {'hex': 'F', 'decimal': '15', 'name': 'Nidoran♀'}, - 16: {'hex': '10', 'decimal': '16', 'name': 'Nidoqueen'}, - 28: {'hex': '1C', 'decimal': '28', 'name': 'Blastoise'}, - 35: {'hex': '23', 'decimal': '35', 'name': 'Fearow'}, - 36: {'hex': '24', 'decimal': '36', 'name': 'Pidgey'}, - 38: {'hex': '26', 'decimal': '38', 'name': 'Kadabra'}, - 39: {'hex': '27', 'decimal': '39', 'name': 'Graveler'}, - 46: {'hex': '2E', 'decimal': '46', 'name': 'Parasect', 'type': 'Bug'}, - 48: {'hex': '30', 'decimal': '48', 'name': 'Drowzee'}, - 49: {'hex': '31', 'decimal': '49', 'name': 'Golem'}, - 57: {'hex': '39', 'decimal': '57', 'name': 'Mankey'}, - 59: {'hex': '3B', 'decimal': '59', 'name': 'Diglett'}, - 70: {'hex': '46', 'decimal': '70', 'name': 'Doduo'}, - 84: {'hex': '54', 'decimal': '84', 'name': 'Pikachu', 'type': 'Electric'}, - 85: {'hex': '55', 'decimal': '85', 'name': 'Raichu', 'type': 'Electric'}, - 100: {'hex': '64', 'decimal': '100', 'name': 'Jigglypuff'}, - 101: {'hex': '65', 'decimal': '101', 'name': 'Wigglytuff'}, - 107: {'hex': '6B', 'decimal': '107', 'name': 'Zubat'}, - 108: {'hex': '6C', 'decimal': '108', 'name': 'Ekans'}, - 109: {'hex': '6D', 'decimal': '109', 'name': 'Paras', 'type': 'Bug'}, - 112: {'hex': '70', 'decimal': '112', 'name': 'Weedle', 'type': 'Bug'}, - 113: {'hex': '71', 'decimal': '113', 'name': 'Kakuna', 'type': 'Bug'}, - 114: {'hex': '72', 'decimal': '114', 'name': 'Beedrill', 'type': 'Bug'}, - 116: {'hex': '74', 'decimal': '116', 'name': 'Dodrio'}, - 117: {'hex': '75', 'decimal': '117', 'name': 'Primeape'}, - 118: {'hex': '76', 'decimal': '118', 'name': 'Dugtrio'}, - 123: {'hex': '7B', 'decimal': '123', 'name': 'Caterpie', 'type': 'Bug'}, - 124: {'hex': '7C', 'decimal': '124', 'name': 'Metapod', 'type': 'Bug'}, - 125: {'hex': '7D', 'decimal': '125', 'name': 'Butterfree', 'type': 'Bug'}, - 129: {'hex': '81', 'decimal': '129', 'name': 'Hypno'}, - 130: {'hex': '82', 'decimal': '130', 'name': 'Golbat'}, - 133: {'hex': '85', 'decimal': '133', 'name': 'Magikarp'}, - 142: {'hex': '8E', 'decimal': '142', 'name': 'Clefable'}, - 148: {'hex': '94', 'decimal': '148', 'name': 'Abra'}, - 149: {'hex': '95', 'decimal': '149', 'name': 'Alakazam'}, - 150: {'hex': '96', 'decimal': '150', 'name': 'Pidgeotto'}, - 151: {'hex': '97', 'decimal': '151', 'name': 'Pidgeot'}, - 153: {'hex': '99', 'decimal': '153', 'name': 'Bulbasaur'}, - 154: {'hex': '9A', 'decimal': '154', 'name': 'Venusaur'}, - 165: {'hex': 'A5', 'decimal': '165', 'name': 'Rattata'}, - 166: {'hex': 'A6', 'decimal': '166', 'name': 'Raticate'}, - 167: {'hex': 'A7', 'decimal': '167', 'name': 'Nidorino'}, - 168: {'hex': 'A8', 'decimal': '168', 'name': 'Nidorina'}, - 169: {'hex': 'A9', 'decimal': '169', 'name': 'Geodude'}, - 176: {'hex': 'B0', 'decimal': '176', 'name': 'Charmander'}, - 177: {'hex': 'B1', 'decimal': '177', 'name': 'Squirtle'}, - 178: {'hex': 'B2', 'decimal': '178', 'name': 'Charmeleon'}, - 179: {'hex': 'B3', 'decimal': '179', 'name': 'Wartortle'}, - 180: {'hex': 'B4', 'decimal': '180', 'name': 'Charizard'}, - 185: {'hex': 'B9', 'decimal': '185', 'name': 'Oddish'}, - 186: {'hex': 'BA', 'decimal': '186', 'name': 'Gloom'}, - 187: {'hex': 'BB', 'decimal': '187', 'name': 'Vileplume'} -} + 1: {'hex': '1', 'decimal': '1', 'name': 'Rhydon'}, + 2: {'hex': '2', 'decimal': '2', 'name': 'Kangaskhan'}, + 3: {'hex': '3', 'decimal': '3', 'name': 'Nidoran♂'}, + 4: {'hex': '4', 'decimal': '4', 'name': 'Clefairy'}, + 5: {'hex': '5', 'decimal': '5', 'name': 'Spearow'}, + 6: {'hex': '6', 'decimal': '6', 'name': 'Voltorb', 'type': 'Electric'}, + 7: {'hex': '7', 'decimal': '7', 'name': 'Nidoking'}, + 8: {'hex': '8', 'decimal': '8', 'name': 'Slowbro'}, + 9: {'hex': '9', 'decimal': '9', 'name': 'Ivysaur'}, + 10: {'hex': 'A', 'decimal': '10', 'name': 'Exeggutor'}, + 11: {'hex': 'B', 'decimal': '11', 'name': 'Lickitung'}, + 12: {'hex': 'C', 'decimal': '12', 'name': 'Exeggcute'}, + 13: {'hex': 'D', 'decimal': '13', 'name': 'Grimer'}, + 14: {'hex': 'E', 'decimal': '14', 'name': 'Gengar', 'type': 'Ghost'}, + 15: {'hex': 'F', 'decimal': '15', 'name': 'Nidoran♀'}, + 16: {'hex': '10', 'decimal': '16', 'name': 'Nidoqueen'}, + 17: {'hex': '11', 'decimal': '17', 'name': 'Cubone'}, + 18: {'hex': '12', 'decimal': '18', 'name': 'Rhyhorn'}, + 19: {'hex': '13', 'decimal': '19', 'name': 'Lapras', 'type': 'Ice'}, + 20: {'hex': '14', 'decimal': '20', 'name': 'Arcanine'}, + 21: {'hex': '15', 'decimal': '21', 'name': 'Mew'}, + 22: {'hex': '16', 'decimal': '22', 'name': 'Gyarados'}, + 23: {'hex': '17', 'decimal': '23', 'name': 'Shellder'}, + 24: {'hex': '18', 'decimal': '24', 'name': 'Tentacool'}, + 25: {'hex': '19', 'decimal': '25', 'name': 'Gastly', 'type': 'Ghost'}, + 26: {'hex': '1A', 'decimal': '26', 'name': 'Scyther', 'type': 'Bug'}, + 27: {'hex': '1B', 'decimal': '27', 'name': 'Staryu'}, + 28: {'hex': '1C', 'decimal': '28', 'name': 'Blastoise'}, + 29: {'hex': '1D', 'decimal': '29', 'name': 'Pinsir', 'type': 'Bug'}, + 30: {'hex': '1E', 'decimal': '30', 'name': 'Tangela'}, + 31: {'hex': '1F', 'decimal': '31', 'name': 'MissingNo. (Scizor)'}, + 32: {'hex': '20', 'decimal': '32', 'name': 'MissingNo. (Shuckle)'}, + 33: {'hex': '21', 'decimal': '33', 'name': 'Growlithe'}, + 34: {'hex': '22', 'decimal': '34', 'name': 'Onix'}, + 35: {'hex': '23', 'decimal': '35', 'name': 'Fearow'}, + 36: {'hex': '24', 'decimal': '36', 'name': 'Pidgey'}, + 37: {'hex': '25', 'decimal': '37', 'name': 'Slowpoke'}, + 38: {'hex': '26', 'decimal': '38', 'name': 'Kadabra'}, + 39: {'hex': '27', 'decimal': '39', 'name': 'Graveler'}, + 40: {'hex': '28', 'decimal': '40', 'name': 'Chansey'}, + 41: {'hex': '29', 'decimal': '41', 'name': 'Machoke'}, + 42: {'hex': '2A', 'decimal': '42', 'name': 'Mr. Mime'}, + 43: {'hex': '2B', 'decimal': '43', 'name': 'Hitmonlee'}, + 44: {'hex': '2C', 'decimal': '44', 'name': 'Hitmonchan'}, + 45: {'hex': '2D', 'decimal': '45', 'name': 'Arbok'}, + 46: {'hex': '2E', 'decimal': '46', 'name': 'Parasect', 'type': 'Bug'}, + 47: {'hex': '2F', 'decimal': '47', 'name': 'Psyduck'}, + 48: {'hex': '30', 'decimal': '48', 'name': 'Drowzee'}, + 49: {'hex': '31', 'decimal': '49', 'name': 'Golem'}, + 50: {'hex': '32', 'decimal': '50', 'name': 'MissingNo. (Heracross)'}, + 51: {'hex': '33', 'decimal': '51', 'name': 'Magmar'}, + 52: {'hex': '34', 'decimal': '52', 'name': 'MissingNo. (Ho-Oh)'}, + 53: {'hex': '35', 'decimal': '53', 'name': 'Electabuzz', 'type': 'Electric'}, + 54: {'hex': '36', 'decimal': '54', 'name': 'Magneton', 'type': 'Electric'}, + 55: {'hex': '37', 'decimal': '55', 'name': 'Koffing'}, + 56: {'hex': '38', 'decimal': '56', 'name': 'MissingNo. (Sneasel)'}, + 57: {'hex': '39', 'decimal': '57', 'name': 'Mankey'}, + 58: {'hex': '3A', 'decimal': '58', 'name': 'Seel'}, + 59: {'hex': '3B', 'decimal': '59', 'name': 'Diglett'}, + 60: {'hex': '3C', 'decimal': '60', 'name': 'Tauros'}, + 61: {'hex': '3D', 'decimal': '61', 'name': 'MissingNo. (Teddiursa)'}, + 62: {'hex': '3E', 'decimal': '62', 'name': 'MissingNo. (Ursaring)'}, + 63: {'hex': '3F', 'decimal': '63', 'name': 'MissingNo. (Slugma)'}, + 64: {'hex': '40', 'decimal': '64', 'name': "Farfetch'd"}, + 65: {'hex': '41', 'decimal': '65', 'name': 'Venonat', 'type': 'Bug'}, + 66: {'hex': '42', 'decimal': '66', 'name': 'Dragonite', 'type': 'Dragon'}, + 67: {'hex': '43', 'decimal': '67', 'name': 'MissingNo. (Magcargo)'}, + 68: {'hex': '44', 'decimal': '68', 'name': 'MissingNo. (Swinub)'}, + 69: {'hex': '45', 'decimal': '69', 'name': 'MissingNo. (Piloswine)'}, + 70: {'hex': '46', 'decimal': '70', 'name': 'Doduo'}, + 71: {'hex': '47', 'decimal': '71', 'name': 'Poliwag'}, + 72: {'hex': '48', 'decimal': '72', 'name': 'Jynx', 'type': 'Ice'}, + 73: {'hex': '49', 'decimal': '73', 'name': 'Moltres'}, + 74: {'hex': '4A', 'decimal': '74', 'name': 'Articuno', 'type': 'Ice'}, + 75: {'hex': '4B', 'decimal': '75', 'name': 'Zapdos', 'type': 'Electric'}, + 76: {'hex': '4C', 'decimal': '76', 'name': 'Ditto'}, + 77: {'hex': '4D', 'decimal': '77', 'name': 'Meowth'}, + 78: {'hex': '4E', 'decimal': '78', 'name': 'Krabby'}, + 79: {'hex': '4F', 'decimal': '79', 'name': 'MissingNo. (Corsola)'}, + 80: {'hex': '50', 'decimal': '80', 'name': 'MissingNo. (Remoraid)'}, + 81: {'hex': '51', 'decimal': '81', 'name': 'MissingNo. (Octillery)'}, + 82: {'hex': '52', 'decimal': '82', 'name': 'Vulpix'}, + 83: {'hex': '53', 'decimal': '83', 'name': 'Ninetales'}, + 84: {'hex': '54', 'decimal': '84', 'name': 'Pikachu', 'type': 'Electric'}, + 85: {'hex': '55', 'decimal': '85', 'name': 'Raichu', 'type': 'Electric'}, + 86: {'hex': '56', 'decimal': '86', 'name': 'MissingNo. (Deli)'}, + 87: {'hex': '57', 'decimal': '87', 'name': 'MissingNo. (Mantine)'}, + 88: {'hex': '58', 'decimal': '88', 'name': 'Dratini', 'type': 'Dragon'}, + 89: {'hex': '59', 'decimal': '89', 'name': 'Dragonair', 'type': 'Dragon'}, + 90: {'hex': '5A', 'decimal': '90', 'name': 'Kabuto'}, + 91: {'hex': '5B', 'decimal': '91', 'name': 'Kabutops'}, + 92: {'hex': '5C', 'decimal': '92', 'name': 'Horsea'}, + 93: {'hex': '5D', 'decimal': '93', 'name': 'Seadra'}, + 94: {'hex': '5E', 'decimal': '94', 'name': 'MissingNo. (Skarmory)'}, + 95: {'hex': '5F', 'decimal': '95', 'name': 'MissingNo. (Houndour)'}, + 96: {'hex': '60', 'decimal': '96', 'name': 'Sandshrew'}, + 97: {'hex': '61', 'decimal': '97', 'name': 'Sandslash'}, + 98: {'hex': '62', 'decimal': '98', 'name': 'Omanyte'}, + 99: {'hex': '63', 'decimal': '99', 'name': 'Omastar'}, + 100: {'hex': '64', 'decimal': '100', 'name': 'Jigglypuff'}, + 101: {'hex': '65', 'decimal': '101', 'name': 'Wigglytuff'}, + 102: {'hex': '66', 'decimal': '102', 'name': 'Eevee'}, + 103: {'hex': '67', 'decimal': '103', 'name': 'Flareon'}, + 104: {'hex': '68', 'decimal': '104', 'name': 'Jolteon', 'type': 'Electric'}, + 105: {'hex': '69', 'decimal': '105', 'name': 'Vaporeon'}, + 106: {'hex': '6A', 'decimal': '106', 'name': 'Machop'}, + 107: {'hex': '6B', 'decimal': '107', 'name': 'Zubat'}, + 108: {'hex': '6C', 'decimal': '108', 'name': 'Ekans'}, + 109: {'hex': '6D', 'decimal': '109', 'name': 'Paras', 'type': 'Bug'}, + 110: {'hex': '6E', 'decimal': '110', 'name': 'Poliwhirl'}, + 111: {'hex': '6F', 'decimal': '111', 'name': 'Poliwrath'}, + 112: {'hex': '70', 'decimal': '112', 'name': 'Weedle', 'type': 'Bug'}, + 113: {'hex': '71', 'decimal': '113', 'name': 'Kakuna', 'type': 'Bug'}, + 114: {'hex': '72', 'decimal': '114', 'name': 'Beedrill', 'type': 'Bug'}, + 115: {'hex': '73', 'decimal': '115', 'name': 'MissingNo. (Houndoom)'}, + 116: {'hex': '74', 'decimal': '116', 'name': 'Dodrio'}, + 117: {'hex': '75', 'decimal': '117', 'name': 'Primeape'}, + 118: {'hex': '76', 'decimal': '118', 'name': 'Dugtrio'}, + 119: {'hex': '77', 'decimal': '119', 'name': 'Venomoth', 'type': 'Bug'}, + 120: {'hex': '78', 'decimal': '120', 'name': 'Dewgong', 'type': 'Ice'}, + 121: {'hex': '79', 'decimal': '121', 'name': 'MissingNo. (Kingdra)'}, + 122: {'hex': '7A', 'decimal': '122', 'name': 'MissingNo. (Phanpy)'}, + 123: {'hex': '7B', 'decimal': '123', 'name': 'Caterpie', 'type': 'Bug'}, + 124: {'hex': '7C', 'decimal': '124', 'name': 'Metapod', 'type': 'Bug'}, + 125: {'hex': '7D', 'decimal': '125', 'name': 'Butterfree', 'type': 'Bug'}, + 126: {'hex': '7E', 'decimal': '126', 'name': 'Machamp'}, + 127: {'hex': '7F', 'decimal': '127', 'name': 'MissingNo. (Donphan)'}, + 128: {'hex': '80', 'decimal': '128', 'name': 'Golduck'}, + 129: {'hex': '81', 'decimal': '129', 'name': 'Hypno'}, + 130: {'hex': '82', 'decimal': '130', 'name': 'Golbat'}, + 131: {'hex': '83', 'decimal': '131', 'name': 'Mewtwo'}, + 132: {'hex': '84', 'decimal': '132', 'name': 'Snorlax'}, + 133: {'hex': '85', 'decimal': '133', 'name': 'Magikarp'}, + 134: {'hex': '86', 'decimal': '134', 'name': 'MissingNo. (Porygon2)'}, + 135: {'hex': '87', 'decimal': '135', 'name': 'MissingNo. (Stantler)'}, + 136: {'hex': '88', 'decimal': '136', 'name': 'Muk'}, + 137: {'hex': '89', 'decimal': '137', 'name': 'MissingNo. (Smeargle)'}, + 138: {'hex': '8A', 'decimal': '138', 'name': 'Kingler'}, + 139: {'hex': '8B', 'decimal': '139', 'name': 'Cloyster'}, + 141: {'hex': '8D', 'decimal': '141', 'name': 'Electrode'}, + 142: {'hex': '8E', 'decimal': '142', 'name': 'Clefable'}, + 143: {'hex': '8F', 'decimal': '143', 'name': 'Weezing'}, + 144: {'hex': '90', 'decimal': '144', 'name': 'Persian'}, + 145: {'hex': '91', 'decimal': '145', 'name': 'Marowak'}, + 147: {'hex': '93', 'decimal': '147', 'name': 'Haunter'}, + 148: {'hex': '94', 'decimal': '148', 'name': 'Abra'}, + 149: {'hex': '95', 'decimal': '149', 'name': 'Alakazam'}, + 150: {'hex': '96', 'decimal': '150', 'name': 'Pidgeotto'}, + 151: {'hex': '97', 'decimal': '151', 'name': 'Pidgeot'}, + 152: {'hex': '98', 'decimal': '152', 'name': 'Starmie'}, + 153: {'hex': '99', 'decimal': '153', 'name': 'Bulbasaur'}, + 154: {'hex': '9A', 'decimal': '154', 'name': 'Venusaur'}, + 155: {'hex': '9B', 'decimal': '155', 'name': 'Tentacruel'}, + 157: {'hex': '9D', 'decimal': '157', 'name': 'Goldeen'}, + 158: {'hex': '9E', 'decimal': '158', 'name': 'Seaking'}, + 163: {'hex': 'A3', 'decimal': '163', 'name': 'Ponyta'}, + 164: {'hex': 'A4', 'decimal': '164', 'name': 'Rapidash'}, + 165: {'hex': 'A5', 'decimal': '165', 'name': 'Rattata'}, + 166: {'hex': 'A6', 'decimal': '166', 'name': 'Raticate'}, + 167: {'hex': 'A7', 'decimal': '167', 'name': 'Nidorino'}, + 168: {'hex': 'A8', 'decimal': '168', 'name': 'Nidorina'}, + 169: {'hex': 'A9', 'decimal': '169', 'name': 'Geodude'}, + 170: {'hex': 'AA', 'decimal': '170', 'name': 'Porygon'}, + 171: {'hex': 'AB', 'decimal': '171', 'name': 'Aerodactyl'}, + 173: {'hex': 'AD', 'decimal': '173', 'name': 'Magnemite'}, + 176: {'hex': 'B0', 'decimal': '176', 'name': 'Charmander'}, + 177: {'hex': 'B1', 'decimal': '177', 'name': 'Squirtle'}, + 178: {'hex': 'B2', 'decimal': '178', 'name': 'Charmeleon'}, + 179: {'hex': 'B3', 'decimal': '179', 'name': 'Wartortle'}, + 180: {'hex': 'B4', 'decimal': '180', 'name': 'Charizard'}, + 185: {'hex': 'B9', 'decimal': '185', 'name': 'Oddish'}, + 186: {'hex': 'BA', 'decimal': '186', 'name': 'Gloom'}, + 187: {'hex': 'BB', 'decimal': '187', 'name': 'Vileplume'}, + 188: {'hex': 'BC', 'decimal': '188', 'name': 'Bellsprout'}, + 189: {'hex': 'BD', 'decimal': '189', 'name': 'Weepinbell'}, + 190: {'hex': 'BE', 'decimal': '190', 'name': 'Victreebel'} + } POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] # - Pokémon (Again) STATUS = [0xD16F, 0xD19B, 0xD1C7, 0xD1F3, 0xD21F, 0xD24B] # - Status (Poisoned, Paralyzed, etc.) @@ -572,21 +692,21 @@ MOVE3 = [0xD175, 0xD1A1, 0xD1CD, 0xD1F9, 0xD225, 0xD251] MOVE4 = [0xD176, 0xD1A2, 0xD1CE, 0xD1FA, 0xD226, 0xD252] -def pokemon_l(game): - pokemon_info = [{"slot": str(i + 1), "name": "", "level": "0", "moves": []} for i in range(6)] - for i in range(6): - p, l = game.get_memory_value(POKE[i]), game.get_memory_value(LEVEL[i]) - hex_value = hex(int(p))[2:].upper() - matching_pokemon = next((entry for entry in pokemon_data if entry.get('hex') == hex_value), None) - if matching_pokemon: - pokemon_info[i]["name"] = matching_pokemon["name"] - pokemon_info[i]["level"] = str(l) - moves_addresses = [MOVE1[i], MOVE2[i], MOVE3[i], MOVE4[i]] - pokemon_info[i]["moves"] = [] - for moves_address in moves_addresses: - move_value = game.get_memory_value(moves_address) - if move_value != 0x00: - move_info = moves_dict.get(move_value, {}) - move_name = move_info.get("Move", "") - pokemon_info[i]["moves"].append(move_name) - return pokemon_info +def logs(game): + data_dict = {} + for i in range(len(POKE)): # 6 + p = game.get_memory_value(POKE[i]) + l = game.get_memory_value(LEVEL[i]) + moves_addresses = [MOVE1[i], MOVE2[i], MOVE3[i], MOVE4[i]] + n = poke_dict.get(p, {}) + name = n.get('name', '') + slot_data = {'name': name,'level': l,'moves': []} + for moves_address in moves_addresses: + move_value = game.get_memory_value(moves_address) + if move_value != 0x00: + move_info = moves_dict.get(move_value, {}) + move_name = move_info.get("Move", "") + slot_data["moves"].append(move_name) + data_dict[f'slot{i+1}'] = slot_data + + return data_dict diff --git a/pokegym/environment.py b/pokegym/environment.py index 265da78..55cb5e7 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -1,5 +1,7 @@ +import multiprocessing from pathlib import Path from pdb import set_trace as T +import sqlite3 import types import uuid from gymnasium import Env, spaces @@ -22,7 +24,7 @@ load_pyboy_state, run_action_on_emulator, ) -from pokegym import newram_map, data +from pokegym import ram_map, data STATE_PATH = __file__.rstrip("environment.py") + "States/" @@ -37,8 +39,12 @@ def get_random_state(): return random.choice(state_files) state_file = get_random_state() randstate = os.path.join(STATE_PATH, state_file) +db_name = Path(f'{str(uuid.uuid4())[:4]}') class Base: + counter_lock = multiprocessing.Lock() + counter = multiprocessing.Value('i', 1) + def __init__( self, rom_path="pokemon_red.gb", @@ -48,6 +54,9 @@ def __init__( quiet=False, **kwargs, ): + with Base.counter_lock: + env_id = Base.counter.value + Base.counter.value += 1 self.state_file = get_random_state() self.randstate = os.path.join(STATE_PATH, self.state_file) """Creates a PokemonRed environment""" @@ -62,8 +71,8 @@ def __init__( self.memory_shape = 80 self.use_screen_memory = True self.screenshot_counter = 0 - self.env_id = Path(f'{str(uuid.uuid4())[:4]}') - self.s_path = __file__.rstrip("environment.py") + "Video/" + self.env_id = env_id + self.first = True self.reset_count = 0 self.explore_hidden_obj_weight = 1 @@ -143,7 +152,7 @@ def get_fixed_window(self, arr, y, x, window_size): def render(self): if self.use_screen_memory: - r, c, map_n = newram_map.position(self.game) + r, c, map_n = ram_map.position(self.game) # Update tile map mmap = self.screen_memory[map_n] if 0 <= r <= 254 and 0 <= c <= 254: @@ -185,115 +194,42 @@ def __init__(self,rom_path="pokemon_red.gb",state_path=None,headless=True,save_v self.is_dead = False self.last_map = -1 self.log = True - self.used_cut = 0 self.cut_reset = 0 # self.seen_coords = set() self.map_check = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] coord_rewards = [] - - def update_pokedex(self): - for i in range(0xD30A - 0xD2F7): - caught_mem = self.game.get_memory_value(i + 0xD2F7) - seen_mem = self.game.get_memory_value(i + 0xD30A) - for j in range(8): - self.caught_pokemon[8*i + j] = 1 if caught_mem & (1 << j) else 0 - self.seen_pokemon[8*i + j] = 1 if seen_mem & (1 << j) else 0 - - def town_state(self): - state = io.BytesIO() - state.seek(0) - self.game.save_state(state) - self.initial_states.append(state) - return - - def update_moves_obtained(self): - # Scan party - for i in [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247]: - if self.game.get_memory_value(i) != 0: - for j in range(4): - move_id = self.game.get_memory_value(i + j + 8) - if move_id != 0: - if move_id != 0: - self.moves_obtained[move_id] = 1 - if move_id == 15: - self.cut = 1 - self.cut_reset = 1 - # Scan current box (since the box doesn't auto increment in pokemon red) - num_moves = 4 - box_struct_length = 25 * num_moves * 2 - for i in range(self.game.get_memory_value(0xda80)): - offset = i*box_struct_length + 0xda96 - if self.game.get_memory_value(offset) != 0: - for j in range(4): - move_id = self.game.get_memory_value(offset + j + 8) - if move_id != 0: - self.moves_obtained[move_id] = 1 def add_video_frame(self): self.full_frame_writer.add_image(self.video()) - def get_game_coords(self): - return (newram_map.mem_val(self.game, 0xD362), newram_map.mem_val(self.game, 0xD361), newram_map.mem_val(self.game, 0xD35E)) - - def check_if_in_start_menu(self) -> bool: - return ( - newram_map.mem_val(self.game, 0xD057) == 0 - and newram_map.mem_val(self.game, 0xCF13) == 0 - and newram_map.mem_val(self.game, 0xFF8C) == 6 - and newram_map.mem_val(self.game, 0xCF94) == 0 - ) + def save_to_database(self): + db_dir = self.db_path + conn = sqlite3.connect(f'{db_dir}/{db_name}.db') + cursor = conn.cursor() - def check_if_in_pokemon_menu(self) -> bool: - return ( - newram_map.mem_val(self.game, 0xD057) == 0 - and newram_map.mem_val(self.game, 0xCF13) == 0 - and newram_map.mem_val(self.game, 0xFF8C) == 6 - and newram_map.mem_val(self.game, 0xCF94) == 2 - ) + cursor.execute("CREATE TABLE IF NOT EXISTS environment (env_id TEXT PRIMARY KEY,hm_count INTEGER,cut INTEGER)") + cursor.execute("INSERT OR REPLACE INTO environment VALUES (?, ?, ?)", (str(self.env_id), self.hm_count, self.cut)) - def check_if_in_stats_menu(self) -> bool: - return ( - newram_map.mem_val(self.game, 0xD057) == 0 - and newram_map.mem_val(self.game, 0xCF13) == 0 - and newram_map.mem_val(self.game, 0xFF8C) == 6 - and newram_map.mem_val(self.game, 0xCF94) == 1 - ) + conn.commit() + conn.close() - def check_if_in_bag_menu(self) -> bool: - return ( - newram_map.mem_val(self.game, 0xD057) == 0 - and newram_map.mem_val(self.game, 0xCF13) == 0 - # and newram_map.mem_val(self.game, 0xFF8C) == 6 # only sometimes - and newram_map.mem_val(self.game, 0xCF94) == 3 - ) + def read_database(self): + db_dir = self.db_path + conn = sqlite3.connect(f'{db_dir}/{db_name}.db') + cursor = conn.cursor() - def check_if_cancel_bag_menu(self, action) -> bool: - return ( - action == WindowEvent.PRESS_BUTTON_A - and newram_map.mem_val(self.game, 0xD057) == 0 - and newram_map.mem_val(self.game, 0xCF13) == 0 - # and newram_map.mem_val(self.game, 0xFF8C) == 6 - and newram_map.mem_val(self.game, 0xCF94) == 3 - and newram_map.mem_val(self.game, 0xD31D) == newram_map.mem_val(self.game, 0xCC36) + newram_map.mem_val(self.game, 0xCC26) - ) - - def heal_reward(self): - cur_health = newram_map.hp(self.game) - # if health increased and party size did not change - if (cur_health > self.last_hp and newram_map.read_m(0xD163) == self.last_party_size): - if self.last_hp > 0: - heal_amount = cur_health - self.last_hp - if heal_amount > 0.5: - print(f'healed: {heal_amount}') - self.total_healing += heal_amount * 4 - else: - self.death_count += 1 + cursor.execute("SELECT COUNT(*) FROM environment WHERE cut = 1") + count_cut_1 = cursor.fetchone()[0] + cursor.execute("SELECT COUNT(*) FROM environment") + total_instances = cursor.fetchone()[0] + percentage = (count_cut_1 / total_instances) * 100 + # print(f"Percentage of instances with hm_count = 1: {percentage:.2f}%") + conn.close() + return percentage def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" - self.reset_count += 1 - self.cut = 0 # if self.reset_count % 10 == 0: # load_pyboy_state(self.game, self.load_first_state()) # self.seen_coords = set() @@ -318,6 +254,7 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.reward_scale = reward_scale self.last_reward = None + self.reset_count += 1 self.prev_map_n = None self.max_events = 0 self.max_level_sum = 0 @@ -340,12 +277,13 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.caught_pokemon = np.zeros(152, dtype=np.uint8) self.moves_obtained = {} # np.zeros(255, dtype=np.uint8) self.town = 1 + self.used_cut = 0 self.gymthree = 0 self.gymfour = 0 self.death_count = 0 self.expl = 0 + self.cut = 0 self.respawn = set() - return self.render(), {} def step(self, action, fast_video=True): @@ -355,35 +293,18 @@ def step(self, action, fast_video=True): if self.save_video: self.add_video_frame() - # Exploration reward - r, c, map_n = newram_map.position(self.game) - if self.cut == 1: - if r == 0x1F and c == 0x23 and map_n == 0x06: - if (r, c, map_n) not in self.seen_coords: - self.expl += 5 - if r == 0x11 and c == 0x0F and map_n == 0x05: - if (r, c, map_n) not in self.seen_coords: - self.expl += 5 - if r == 0x12 and c == 0x0E and map_n == 0x05: - if (r, c, map_n) not in self.seen_coords: - self.expl += 5 + # Exploration + r, c, map_n = ram_map.position(self.game) # this is [y, x, z] self.seen_coords.add((r, c, map_n)) - exploration_reward = (0.02 * len(self.seen_coords)) + self.expl + exploration = ram_map.explore(self.game, self.seen_coords, map_n) - # respawn reward - center = self.game.get_memory_value(0xD719) - self.respawn.add(center) - respawn_reward = len(self.respawn) * 5 - - - # map check if map_n == 92: self.gymthree = 1 if map_n == 134: self.gymfour = 1 # Level reward - party_size, party_levels = newram_map.party(self.game) + party_size, party_levels = ram_map.party(self.game) self.max_level_sum = max(self.max_level_sum, sum(party_levels)) if self.max_level_sum < 15: level_reward = 1 * self.max_level_sum @@ -391,134 +312,87 @@ def step(self, action, fast_video=True): level_reward = 15 + (self.max_level_sum - 15) / 4 # Healing and death rewards - hp = newram_map.hp(self.game) + hp = ram_map.hp(self.game) hp_delta = hp - self.last_hp party_size_constant = party_size == self.last_party_size - if hp_delta > 0.4 and party_size_constant and not self.is_dead: + if hp_delta > 0.5 and party_size_constant and not self.is_dead: self.total_healing += hp_delta if hp <= 0 and self.last_hp > 0: self.death_count += 1 self.is_dead = True - # self.seen_coords = set() elif hp > 0.01: # TODO: Check if this matters self.is_dead = False self.last_hp = hp self.last_party_size = party_size - - - - # HM reward - hm_count = newram_map.get_hm_count(self.game) - if hm_count >= 1 and self.hm_count == 0: - self.hm_count = 1 - # hm_reward = hm_count * 10 + death_reward = 0 # -0.08 * self.death_count # -0.05 + healing_reward = self.total_healing - # Cut check - # 0xCFC6 - wTileInFrontOfPlayer - # 0xCFCB - wUpdateSpritesEnabled - if newram_map.mem_val(self.game, 0xD057) == 0: # is_in_battle if 1 - if self.cut == 1: - player_direction = self.game.get_memory_value(0xC109) - x, y, map_id = self.get_game_coords() # x, y, map_id - if player_direction == 0: # down - coords = (x, y + 1, map_id) - if player_direction == 4: - coords = (x, y - 1, map_id) - if player_direction == 8: - coords = (x - 1, y, map_id) - if player_direction == 0xC: - coords = (x + 1, y, map_id) - self.cut_state.append( - ( - self.game.get_memory_value(0xCFC6), # background tile number in front of the player (either 1 or 2 steps ahead) - self.game.get_memory_value(0xCFCB), # $00 = causes sprites to be hidden and the value to change to $ff - self.game.get_memory_value(0xCD6A), # When the player tries to use an item or use certain field moves, 0 is stored - self.game.get_memory_value(0xD367), # wCurMapTileset - self.game.get_memory_value(0xD125), # wTextBoxID - self.game.get_memory_value(0xCD3D), # wFallingObjectsMovementData up to 20 bytes (one byte for each falling object) - ) - ) - if tuple(list(self.cut_state)[1:]) in CUT_SEQ: - self.cut_coords[coords] = 5 # from 14 - self.cut_tiles[self.cut_state[-1][0]] = 1 - elif self.cut_state == CUT_GRASS_SEQ: - self.cut_coords[coords] = 0.001 - self.cut_tiles[self.cut_state[-1][0]] = 1 - elif deque([(-1, *elem[1:]) for elem in self.cut_state]) == CUT_FAIL_SEQ: - self.cut_coords[coords] = 0.001 - self.cut_tiles[self.cut_state[-1][0]] = 1 - if int(newram_map.read_bit(self.game, 0xD803, 0)): - if self.check_if_in_start_menu(): - self.seen_start_menu = 1 - if self.check_if_in_pokemon_menu(): - self.seen_pokemon_menu = 1 - if self.check_if_in_stats_menu(): - self.seen_stats_menu = 1 - if self.check_if_in_bag_menu(): - self.seen_bag_menu = 1 - if self.check_if_cancel_bag_menu(action): - self.seen_cancel_bag_menu = 1 - - if newram_map.used_cut(self.game) == 61: - newram_map.write_mem(self.game, 0xCD4D, 00) # address, byte to write resets tile check + if self.cut == 1: + cut_coords, cut_tiles, seen_start_menu, seen_pokemon_menu, seen_stats_menu, seen_bag_menu = ram_map.cut_array(self.game) + self.cut_coords = cut_coords + self.cut_tiles = cut_tiles + self.seen_start_menu = seen_start_menu + self.seen_pokemon_menu = seen_pokemon_menu + self.seen_stats_menu = seen_stats_menu + self.seen_bag_menu = seen_bag_menu + + if ram_map.used_cut(self.game): self.used_cut += 1 # Misc - badges = newram_map.badges(self.game) - self.update_pokedex() - self.update_moves_obtained() - # self.heal_reward() + badges = ram_map.badges(self.game) + seen_pokemon, caught_pokemon = ram_map.update_pokedex(self.game) + moves_obtained, cut = ram_map.update_moves_obtained(self.game) + self.cut = cut + + hm_count = ram_map.get_hm_count(self.game) + silph = ram_map.silph_co(self.game) + rock_tunnel = ram_map.rock_tunnel(self.game) + ssanne = ram_map.ssanne(self.game) + mtmoon = ram_map.mtmoon(self.game) + routes = ram_map.routes(self.game) + misc = ram_map.misc(self.game) + snorlax = ram_map.snorlax(self.game) + hmtm = ram_map.hmtm(self.game) + bill = ram_map.bill(self.game) + oak = ram_map.oak(self.game) + towns = ram_map.towns(self.game) + lab = ram_map.lab(self.game) + mansion = ram_map.mansion(self.game) + safari = ram_map.safari(self.game) + dojo = ram_map.dojo(self.game) + hideout = ram_map.hideout(self.game) + tower = ram_map.poke_tower(self.game) + gym1 = ram_map.gym1(self.game) + gym2 = ram_map.gym2(self.game) + gym3 = ram_map.gym3(self.game) + gym4 = ram_map.gym4(self.game) + gym5 = ram_map.gym5(self.game) + gym6 = ram_map.gym6(self.game) + gym7 = ram_map.gym7(self.game) + gym8 = ram_map.gym8(self.game) + rival = ram_map.rival(self.game) - silph = newram_map.silph_co(self.game) - rock_tunnel = newram_map.rock_tunnel(self.game) - ssanne = newram_map.ssanne(self.game) - mtmoon = newram_map.mtmoon(self.game) - routes = newram_map.routes(self.game) - misc = newram_map.misc(self.game) - snorlax = newram_map.snorlax(self.game) - hmtm = newram_map.hmtm(self.game) - bill = newram_map.bill(self.game) - oak = newram_map.oak(self.game) - towns = newram_map.towns(self.game) - lab = newram_map.lab(self.game) - mansion = newram_map.mansion(self.game) - safari = newram_map.safari(self.game) - dojo = newram_map.dojo(self.game) - hideout = newram_map.hideout(self.game) - tower = newram_map.poke_tower(self.game) - gym1 = newram_map.gym1(self.game) - gym2 = newram_map.gym2(self.game) - gym3 = newram_map.gym3(self.game) - gym4 = newram_map.gym4(self.game) - gym5 = newram_map.gym5(self.game) - gym6 = newram_map.gym6(self.game) - gym7 = newram_map.gym7(self.game) - gym8 = newram_map.gym8(self.game) - rival = newram_map.rival(self.game) - bulba_check = newram_map.bulba(self.game) + exploration_reward = exploration cut_rew = self.cut * 10 event_reward = sum([silph, rock_tunnel, ssanne, mtmoon, routes, misc, snorlax, hmtm, bill, oak, towns, lab, mansion, safari, dojo, hideout, tower, gym1, gym2, gym3, gym4, gym5, gym6, gym7, gym8, rival]) - seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) - caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) - moves_obtained_reward = self.reward_scale * sum(self.moves_obtained) - used_cut_rew = self.used_cut * 0.05 - cut_coords = sum(self.cut_coords.values()) * 1.0 - cut_tiles = len(self.cut_tiles) * 1.0 + seen_pokemon_reward = self.reward_scale * seen_pokemon + caught_pokemon_reward = self.reward_scale * caught_pokemon + moves_obtained_reward = self.reward_scale * moves_obtained + used_cut_rew = self.used_cut * 0.03 + cut_coords_rew = sum(self.cut_coords.values()) * 1.0 + cut_tiles_rew = len(self.cut_tiles) * 1.0 start_menu = self.seen_start_menu * 0.01 pokemon_menu = self.seen_pokemon_menu * 0.1 stats_menu = self.seen_stats_menu * 0.1 bag_menu = self.seen_bag_menu * 0.1 that_guy = (start_menu + pokemon_menu + stats_menu + bag_menu ) / 2 - - death_reward = -0.02 * self.death_count # -0.05 - healing_reward = self.total_healing * 2 reward = self.reward_scale * ( - level_reward + + level_reward + healing_reward - + death_reward + exploration_reward + cut_rew + event_reward @@ -526,11 +400,9 @@ def step(self, action, fast_video=True): + caught_pokemon_reward + moves_obtained_reward + used_cut_rew - + cut_coords - + cut_tiles + + cut_coords_rew + + cut_tiles_rew + that_guy - + bulba_check - + respawn_reward ) # Subtract previous reward @@ -548,6 +420,11 @@ def step(self, action, fast_video=True): if self.save_video and done: self.full_frame_writer.close() if done: + state = io.BytesIO() + self.game.save_state(state) + state.seek(0) + info["state"] = state.read() + # self.save_to_database() poke = self.game.get_memory_value(0xD16B) level = self.game.get_memory_value(0xD18C) if poke == 57 and level == 0: @@ -580,6 +457,14 @@ def step(self, action, fast_video=True): "gym7": gym7, "gym8": gym8, "rival": rival, + "lift_key": int(ram_map.read_bit(self.game, 0xD81B, 6)), + "beat_hideout": int(ram_map.read_bit(self.game, 0xD81B, 7)), + "beat_marowak": int(ram_map.read_bit(self.game, 0xD768, 7)), + "got_pokeflute": int(ram_map.read_bit(self.game, 0xD76C, 0)), + "Got_Bicycle": int(ram_map.read_bit(self.game, 0xD75F, 0)), + "route_12_snorlax": int(ram_map.read_bit(self.game, 0xD7D8, 7)), + "route_16_snorlax": int(ram_map.read_bit(self.game, 0xD7E0, 1)), + "Beat_Silph_Co_Giovanni": int(ram_map.read_bit(self.game, 0xD838, 7)), }, "BET": { "Reward_Delta": reward, @@ -594,10 +479,10 @@ def step(self, action, fast_video=True): "Taught_Cut": cut_rew, "Menuing": that_guy, "Used_Cut": used_cut_rew, - "Cut_Coords": cut_coords, - "Cut_Tiles": cut_tiles, - "Bulba_Check": bulba_check, - "Respawn": respawn_reward + "Cut_Coords": cut_coords_rew, + "Cut_Tiles": cut_tiles_rew, + # "Bulba_Check": bulba_check, + # "Respawn": respawn_reward }, "hm_count": hm_count, "cut_taught": self.cut, @@ -614,8 +499,8 @@ def step(self, action, fast_video=True): "party_size": party_size, "moves_obtained": sum(self.moves_obtained), "deaths": self.death_count, - 'cut_coords': cut_coords, - 'cut_tiles': cut_tiles, + 'cut_coords': cut_coords_rew, + 'cut_tiles': cut_tiles_rew, 'bag_menu': bag_menu, 'stats_menu': stats_menu, 'pokemon_menu': pokemon_menu, @@ -623,7 +508,7 @@ def step(self, action, fast_video=True): 'used_cut': self.used_cut, 'gym_three': self.gymthree, 'gym_four': self.gymfour, - "respawn_coord_len": len(self.respawn) + # "respawn_coord_len": len(self.respawn) } return self.render(), reward, done, done, info diff --git a/pokegym/game_map.py b/pokegym/game_map.py deleted file mode 100644 index 55a81ec..0000000 --- a/pokegym/game_map.py +++ /dev/null @@ -1,18 +0,0 @@ -from pdb import set_trace as T -import numpy as np -import json - - -MAP_PATH = __file__.rstrip('game_map.py') + 'map_data.json' - -MAP_DATA = json.load(open(MAP_PATH, 'r'))['regions'] -MAP_DATA = {int(e['id']): e for e in MAP_DATA} - -# Handle KeyErrors -def local_to_global(r, c, map_n): - try: - map_x, map_y,= MAP_DATA[map_n]['coordinates'] - return r + map_y, c + map_x - except KeyError: - print(f'Map id {map_n} not found in map_data.json.') - return r + 0, c + 0 \ No newline at end of file diff --git a/pokegym/map_data.json b/pokegym/map_data.json deleted file mode 100755 index 2859c5a..0000000 --- a/pokegym/map_data.json +++ /dev/null @@ -1,2716 +0,0 @@ -{ - "regions": [ - { - "id": "-1", - "name": "Kanto", - "coordinates": [ - 0, - 0 - ], - "tileSize": [ - 436, - 444 - ] - }, - { - "id": "0", - "name": "Pallet Town", - "coordinates": [ - 64, - 318 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "1", - "name": "Viridian City", - "coordinates": [ - 54, - 246 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "2", - "name": "Pewter City", - "coordinates": [ - 54, - 138 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "3", - "name": "Cerulean City", - "coordinates": [ - 234, - 120 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "4", - "name": "Lavender Town", - "coordinates": [ - 334, - 200 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "5", - "name": "Vermilion City", - "coordinates": [ - 234, - 264 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "6", - "name": "Celadon City", - "coordinates": [ - 164, - 192 - ], - "tileSize": [ - 50, - 36 - ] - }, - { - "id": "7", - "name": "Fuchsia City", - "coordinates": [ - 174, - 354 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "8", - "name": "Cinnabar island", - "coordinates": [ - 64, - 426 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "9", - "name": "Indigo Plateau", - "coordinates": [ - 14, - 84 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "10", - "name": "Saffrdon City", - "coordinates": [ - 234, - 192 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "12", - "name": "Route 1", - "coordinates": [ - 64, - 282 - ], - "tileSize": [ - 20, - 36 - ] - }, - { - "id": "13", - "name": "Route 2", - "coordinates": [ - 64, - 174 - ], - "tileSize": [ - 20, - 72 - ] - }, - { - "id": "14", - "name": "Route 3", - "coordinates": [ - 94, - 146 - ], - "tileSize": [ - 70, - 18 - ] - }, - { - "id": "15", - "name": "Route 4", - "coordinates": [ - 144, - 128 - ], - "tileSize": [ - 90, - 18 - ] - }, - { - "id": "16", - "name": "Route 5", - "coordinates": [ - 244, - 156 - ], - "tileSize": [ - 20, - 36 - ] - }, - { - "id": "17", - "name": "Route 6", - "coordinates": [ - 244, - 228 - ], - "tileSize": [ - 20, - 36 - ] - }, - { - "id": "18", - "name": "Route 7", - "coordinates": [ - 214, - 200 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "19", - "name": "Route 8", - "coordinates": [ - 274, - 200 - ], - "tileSize": [ - 60, - 18 - ] - }, - { - "id": "20", - "name": "Route 9", - "coordinates": [ - 274, - 128 - ], - "tileSize": [ - 60, - 18 - ] - }, - { - "id": "21", - "name": "Route 10", - "coordinates": [ - 334, - 128 - ], - "tileSize": [ - 20, - 72 - ] - }, - { - "id": "22", - "name": "Route 11", - "coordinates": [ - 274, - 272 - ], - "tileSize": [ - 60, - 18 - ] - }, - { - "id": "23", - "name": "Route 12", - "coordinates": [ - 334, - 218 - ], - "tileSize": [ - 20, - 108 - ] - }, - { - "id": "24", - "name": "Route 13", - "coordinates": [ - 294, - 326 - ], - "tileSize": [ - 60, - 18 - ] - }, - { - "id": "25", - "name": "Route 14", - "coordinates": [ - 274, - 326 - ], - "tileSize": [ - 20, - 54 - ] - }, - { - "id": "26", - "name": "Route 15", - "coordinates": [ - 214, - 362 - ], - "tileSize": [ - 60, - 18 - ] - }, - { - "id": "27", - "name": "Route 16", - "coordinates": [ - 124, - 200 - ], - "tileSize": [ - 40, - 18 - ] - }, - { - "id": "28", - "name": "Route 17", - "coordinates": [ - 124, - 218 - ], - "tileSize": [ - 20, - 144 - ] - }, - { - "id": "29", - "name": "Route 18", - "coordinates": [ - 124, - 362 - ], - "tileSize": [ - 50, - 18 - ] - }, - { - "id": "30", - "name": "Sea Route 19", - "coordinates": [ - 184, - 390 - ], - "tileSize": [ - 20, - 54 - ] - }, - { - "id": "31", - "name": "Sea Route 20", - "coordinates": [ - 84, - 426 - ], - "tileSize": [ - 100, - 18 - ] - }, - { - "id": "32", - "name": "Sea Route 21", - "coordinates": [ - 64, - 336 - ], - "tileSize": [ - 20, - 90 - ] - }, - { - "id": "33", - "name": "Route 22", - "coordinates": [ - 14, - 254 - ], - "tileSize": [ - 40, - 18 - ] - }, - { - "id": "34", - "name": "Route 23", - "coordinates": [ - 14, - 102 - ], - "tileSize": [ - 20, - 144 - ] - }, - { - "id": "35", - "name": "Route 24", - "coordinates": [ - 244, - 84 - ], - "tileSize": [ - 20, - 36 - ] - }, - { - "id": "36", - "name": "Route 25", - "coordinates": [ - 264, - 84 - ], - "tileSize": [ - 60, - 18 - ] - }, - { - "id": "37", - "name": "Players House First Floor", - "coordinates": [ - 55, - 328 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "38", - "name": "Players House Second Floor", - "coordinates": [ - 55, - 319 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "39", - "name": "Rivals House", - "coordinates": [ - 85, - 316 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "40", - "name": "Oaks Lab", - "coordinates": [ - 85, - 325 - ], - "tileSize": [ - 10, - 12 - ] - }, - { - "id": "41", - "name": "Pokemon Center Viridian", - "coordinates": [ - 95, - 274 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "42", - "name": "Pokemart Viridian", - "coordinates": [ - 110, - 274 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "43", - "name": "Pokemon Academy", - "coordinates": [ - 95, - 265 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "44", - "name": "Nickname House", - "coordinates": [ - 104, - 265 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "45", - "name": "Viridian Gym", - "coordinates": [ - 95, - 246 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "46", - "name": "Digletts Cave Entrance", - "coordinates": [ - 55, - 184 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "47", - "name": "Route 2 Gate Pewter", - "coordinates": [ - 85, - 180 - ], - "tileSize": [ - 10, - 8 - ] - }, - { - "id": "48", - "name": "Trader House Mr Mime Trade", - "coordinates": [ - 55, - 193 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "49", - "name": "Route 2 Gate Oaks Aid", - "coordinates": [ - 53, - 202 - ], - "tileSize": [ - 10, - 8 - ] - }, - { - "id": "50", - "name": "Route 2 Gate Viridian", - "coordinates": [ - 100, - 236 - ], - "tileSize": [ - 10, - 8 - ] - }, - { - "id": "51", - "name": "Viridian Forest", - "coordinates": [ - 88, - 188 - ], - "tileSize": [ - 34, - 48 - ] - }, - { - "id": "52", - "name": "Museum F1", - "coordinates": [ - 57, - 130 - ], - "tileSize": [ - 20, - 8 - ] - }, - { - "id": "53", - "name": "Museum F2", - "coordinates": [ - 57, - 122 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "54", - "name": "Pewter Gym", - "coordinates": [ - 43, - 145 - ], - "tileSize": [ - 10, - 15 - ] - }, - { - "id": "55", - "name": "Trainer House Trading Tips", - "coordinates": [ - 45, - 161 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "56", - "name": "Pokemart", - "coordinates": [ - 36, - 161 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "57", - "name": "Trainer House Catching Tips", - "coordinates": [ - 55, - 175 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "58", - "name": "Pokemon Center Pewter", - "coordinates": [ - 39, - 170 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "59", - "name": "Mt Moon Route 3", - "coordinates": [ - 147, - 92 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "60", - "name": "Mt Moon B1F", - "coordinates": [ - 147, - 63 - ], - "tileSize": [ - 28, - 28 - ] - }, - { - "id": "61", - "name": "Mt Moon B2F", - "coordinates": [ - 147, - 26 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "62", - "name": "House Breakin", - "coordinates": [ - 280, - 104 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "63", - "name": "Trader House Jynx", - "coordinates": [ - 225, - 119 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "64", - "name": "Pokemon Center Cerulean", - "coordinates": [ - 265, - 104 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "65", - "name": "Cerulean Gym", - "coordinates": [ - 275, - 113 - ], - "tileSize": [ - 10, - 14 - ] - }, - { - "id": "66", - "name": "Bike Shop", - "coordinates": [ - 225, - 147 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "67", - "name": "Pokemart Cerulean", - "coordinates": [ - 225, - 156 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "68", - "name": "Pokemon Center Route 4", - "coordinates": [ - 129, - 128 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "69", - "name": "House Breakin V2", - "coordinates": [ - 280, - 104 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "70", - "name": "Saffron City Gate North", - "coordinates": [ - 265, - 165 - ], - "tileSize": [ - 8, - 6 - ] - }, - { - "id": "71", - "name": "Underground Entrance North", - "coordinates": [ - 274, - 163 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "72", - "name": "Daycare", - "coordinates": [ - 235, - 157 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "73", - "name": "Underground Entrance", - "coordinates": [ - 265, - 248 - ], - "tileSize": [ - 8, - 6 - ] - }, - { - "id": "74", - "name": "Underground Entrance South", - "coordinates": [ - 265, - 255 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "76", - "name": "Saffron City Gate", - "coordinates": [ - 227, - 219 - ], - "tileSize": [ - 6, - 8 - ] - }, - { - "id": "77", - "name": "Underground Entrance", - "coordinates": [ - 216, - 219 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "78", - "name": "Underground Entrance V2", - "coordinates": [ - 216, - 219 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "79", - "name": "Underground Entrance V2", - "coordinates": [ - 275, - 219 - ], - "tileSize": [ - 6, - 8 - ] - }, - { - "id": "80", - "name": "Underground Entrance V2", - "coordinates": [ - 282, - 219 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "81", - "name": "Pokemon Center Route 10", - "coordinates": [ - 355, - 136 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "82", - "name": "Rock Tunnel 1F", - "coordinates": [ - 355, - 145 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "83", - "name": "Power Plant", - "coordinates": [ - 293, - 147 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "84", - "name": "Gate 1F", - "coordinates": [ - 325, - 261 - ], - "tileSize": [ - 8, - 10 - ] - }, - { - "id": "85", - "name": "Digletts Cave Entrance", - "coordinates": [ - 275, - 264 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "86", - "name": "Gate 2F", - "coordinates": [ - 325, - 252 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "87", - "name": "Gate 1F", - "coordinates": [ - 355, - 232 - ], - "tileSize": [ - 10, - 8 - ] - }, - { - "id": "88", - "name": "Bills Lab", - "coordinates": [ - 307, - 76 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "89", - "name": "Pokemon Center Vermilion", - "coordinates": [ - 219, - 264 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "90", - "name": "Pokemon Fan Club", - "coordinates": [ - 216, - 273 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "91", - "name": "Pokemart Vermilion", - "coordinates": [ - 225, - 282 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "92", - "name": "Vermilion Gym", - "coordinates": [ - 214, - 282 - ], - "tileSize": [ - 10, - 18 - ] - }, - { - "id": "93", - "name": "Trader House Pidgy Letter", - "coordinates": [ - 225, - 273 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "94", - "name": "Vermilion Harbor", - "coordinates": [ - 239, - 300 - ], - "tileSize": [ - 28, - 12 - ] - }, - { - "id": "95", - "name": "SS Anne F1", - "coordinates": [ - 298, - 345 - ], - "tileSize": [ - 40, - 18 - ] - }, - { - "id": "96", - "name": "SS Anne F2", - "coordinates": [ - 298, - 377 - ], - "tileSize": [ - 40, - 18 - ] - }, - { - "id": "97", - "name": "SS Anne F3", - "coordinates": [ - 279, - 387 - ], - "tileSize": [ - 20, - 6 - ] - }, - { - "id": "98", - "name": "SS Anne B1F", - "coordinates": [ - 308, - 362 - ], - "tileSize": [ - 30, - 8 - ] - }, - { - "id": "99", - "name": "SS Anne Deck", - "coordinates": [ - 265, - 384 - ], - "tileSize": [ - 20, - 14 - ] - }, - { - "id": "100", - "name": "SS Anne Kitchen", - "coordinates": [ - 295, - 363 - ], - "tileSize": [ - 14, - 16 - ] - }, - { - "id": "101", - "name": "SS Anne Captains Office", - "coordinates": [ - 334, - 371 - ], - "tileSize": [ - 6, - 8 - ] - }, - { - "id": "102", - "name": "SS Anne Cabins 1F", - "coordinates": [ - 341, - 345 - ], - "tileSize": [ - 24, - 16 - ] - }, - { - "id": "103", - "name": "SS Anne Cabins 2F", - "coordinates": [ - 341, - 379 - ], - "tileSize": [ - 24, - 16 - ] - }, - { - "id": "104", - "name": "SS Anne Cabins B1F", - "coordinates": [ - 341, - 362 - ], - "tileSize": [ - 24, - 16 - ] - }, - { - "id": "108", - "name": "Victory Road Entrance", - "coordinates": [ - 35, - 116 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "113", - "name": "Lances Room", - "coordinates": [ - 0, - 16 - ], - "tileSize": [ - 26, - 26 - ] - }, - { - "id": "118", - "name": "Hall of Fame", - "coordinates": [ - 1, - 0 - ], - "tileSize": [ - 10, - 8 - ] - }, - { - "id": "119", - "name": "Underground Path Route 5 to Route 6", - "coordinates": [ - 356, - 241 - ], - "tileSize": [ - 8, - 48 - ] - }, - { - "id": "120", - "name": "Champions Room", - "coordinates": [ - 2, - 8 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "121", - "name": "Undeground Path", - "coordinates": [ - 355, - 287 - ], - "tileSize": [ - 49, - 8 - ] - }, - { - "id": "122", - "name": "Department Store F1", - "coordinates": [ - 170, - 183 - ], - "tileSize": [ - 20, - 8 - ] - }, - { - "id": "123", - "name": "Department Store F2", - "coordinates": [ - 170, - 174 - ], - "tileSize": [ - 20, - 8 - ] - }, - { - "id": "124", - "name": "Department Store F3", - "coordinates": [ - 170, - 165 - ], - "tileSize": [ - 20, - 8 - ] - }, - { - "id": "125", - "name": "Department Store F4", - "coordinates": [ - 170, - 156 - ], - "tileSize": [ - 20, - 8 - ] - }, - { - "id": "126", - "name": "Department Store Roof", - "coordinates": [ - 149, - 174 - ], - "tileSize": [ - 20, - 8 - ] - }, - { - "id": "127", - "name": "Department Store Lift", - "coordinates": [ - 159, - 192 - ], - "tileSize": [ - 4, - 5 - ] - }, - { - "id": "128", - "name": "Office Building F1", - "coordinates": [ - 191, - 179 - ], - "tileSize": [ - 8, - 12 - ] - }, - { - "id": "129", - "name": "Office Building F2", - "coordinates": [ - 191, - 166 - ], - "tileSize": [ - 8, - 12 - ] - }, - { - "id": "130", - "name": "Office Building F3", - "coordinates": [ - 191, - 153 - ], - "tileSize": [ - 8, - 12 - ] - }, - { - "id": "131", - "name": "Office Building F1", - "coordinates": [ - 200, - 170 - ], - "tileSize": [ - 8, - 12 - ] - }, - { - "id": "132", - "name": "Office Building Roof Room", - "coordinates": [ - 200, - 161 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "133", - "name": "Pokemon Center Celedon", - "coordinates": [ - 200, - 183 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "134", - "name": "Celadon Gym", - "coordinates": [ - 153, - 219 - ], - "tileSize": [ - 10, - 18 - ] - }, - { - "id": "135", - "name": "Rocket Game Corner", - "coordinates": [ - 164, - 238 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "136", - "name": "Department Store F5", - "coordinates": [ - 149, - 183 - ], - "tileSize": [ - 20, - 8 - ] - }, - { - "id": "137", - "name": "Prize Corner", - "coordinates": [ - 169, - 229 - ], - "tileSize": [ - 10, - 8 - ] - }, - { - "id": "138", - "name": "Restaurant", - "coordinates": [ - 180, - 229 - ], - "tileSize": [ - 10, - 8 - ] - }, - { - "id": "139", - "name": "Rocket House", - "coordinates": [ - 191, - 229 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "140", - "name": "Hotel", - "coordinates": [ - 200, - 229 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "141", - "name": "Pokemon Center Lavender", - "coordinates": [ - 310, - 191 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "142", - "name": "Pokemon Tower F1", - "coordinates": [ - 387, - 239 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "143", - "name": "Pokemon Tower F2", - "coordinates": [ - 366, - 239 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "144", - "name": "Pokemon Tower F3", - "coordinates": [ - 387, - 220 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "145", - "name": "Pokemon Tower F4", - "coordinates": [ - 366, - 220 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "146", - "name": "Pokemon Tower F5", - "coordinates": [ - 387, - 201 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "147", - "name": "Pokemon Tower F6", - "coordinates": [ - 366, - 201 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "148", - "name": "Pokemon Tower F7", - "coordinates": [ - 366, - 182 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "149", - "name": "Mr Fuji House", - "coordinates": [ - 325, - 191 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "150", - "name": "Pokemart Lavender", - "coordinates": [ - 355, - 210 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "151", - "name": "Cubone House", - "coordinates": [ - 316, - 219 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "152", - "name": "Pokemart Fuchsia", - "coordinates": [ - 165, - 353 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "153", - "name": "Bills Mom", - "coordinates": [ - 165, - 381 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "154", - "name": "Pokemon Center Fuchsia", - "coordinates": [ - 165, - 391 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "155", - "name": "Wardens House", - "coordinates": [ - 205, - 391 - ], - "tileSize": [ - 10, - 8 - ] - }, - { - "id": "156", - "name": "Safari Gate", - "coordinates": [ - 189, - 348 - ], - "tileSize": [ - 8, - 6 - ] - }, - { - "id": "157", - "name": "Fuchsia Gym", - "coordinates": [ - 154, - 381 - ], - "tileSize": [ - 10, - 18 - ] - }, - { - "id": "158", - "name": "Meeting Room", - "coordinates": [ - 215, - 353 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "159", - "name": "Sea Foam Islands 1F", - "coordinates": [ - 116, - 407 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "160", - "name": "Sea Foam Islands B1F", - "coordinates": [ - 116, - 388 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "161", - "name": "Sea Foam Islands B2F", - "coordinates": [ - 85, - 388 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "162", - "name": "Sea Foam Islands B3F", - "coordinates": [ - 85, - 369 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "163", - "name": "Fishing Guru Vermilion", - "coordinates": [ - 210, - 264 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "164", - "name": "Fishing Guru Fuchsia", - "coordinates": [ - 215, - 381 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "165", - "name": "Pokemon Mansion F1", - "coordinates": [ - 33, - 389 - ], - "tileSize": [ - 30, - 28 - ] - }, - { - "id": "166", - "name": "Cinnabar Gym", - "coordinates": [ - 85, - 407 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "167", - "name": "Pokemon Lab", - "coordinates": [ - 45, - 427 - ], - "tileSize": [ - 18, - 8 - ] - }, - { - "id": "168", - "name": "Lab Room 1", - "coordinates": [ - 37, - 418 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "169", - "name": "Lab Room 2", - "coordinates": [ - 46, - 418 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "170", - "name": "Lab Room 3", - "coordinates": [ - 55, - 418 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "171", - "name": "Pokemon Center Cinnabar", - "coordinates": [ - 40, - 436 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "172", - "name": "Pokemart Cinnabar", - "coordinates": [ - 55, - 436 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "173", - "name": "Pokemart V2", - "coordinates": [ - 55, - 436 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "174", - "name": "Indigo Plateau Lobby", - "coordinates": [ - 16, - 72 - ], - "tileSize": [ - 16, - 12 - ] - }, - { - "id": "175", - "name": "Coppy Cat F1", - "coordinates": [ - 215, - 191 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "176", - "name": "Coppy Cat F2", - "coordinates": [ - 215, - 182 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "177", - "name": "Fighting Dojo", - "coordinates": [ - 233, - 179 - ], - "tileSize": [ - 10, - 12 - ] - }, - { - "id": "178", - "name": "Saffron Gym", - "coordinates": [ - 265, - 172 - ], - "tileSize": [ - 20, - 18 - ] - }, - { - "id": "179", - "name": "Trainer House", - "coordinates": [ - 224, - 191 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "180", - "name": "Pokemart Saffron", - "coordinates": [ - 275, - 191 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "181", - "name": "Silph Co 1F", - "coordinates": [ - 325, - 109 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "182", - "name": "Pokemon Center Saffron", - "coordinates": [ - 229, - 229 - ], - "tileSize": [ - 14, - 8 - ] - }, - { - "id": "183", - "name": "Mr Psychics", - "coordinates": [ - 265, - 229 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "184", - "name": "Gate House F1", - "coordinates": [ - 230, - 351 - ], - "tileSize": [ - 8, - 10 - ] - }, - { - "id": "185", - "name": "Gate House F2", - "coordinates": [ - 230, - 342 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "186", - "name": "Cycling Gate F1", - "coordinates": [ - 140, - 185 - ], - "tileSize": [ - 8, - 14 - ] - }, - { - "id": "187", - "name": "Cycling Gate F2", - "coordinates": [ - 140, - 176 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "188", - "name": "Secret House", - "coordinates": [ - 128, - 191 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "189", - "name": "Fishing Guru Route 12", - "coordinates": [ - 325, - 291 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "190", - "name": "Cycling Gate F1", - "coordinates": [ - 156, - 351 - ], - "tileSize": [ - 8, - 10 - ] - }, - { - "id": "191", - "name": "Cycling Gate F2", - "coordinates": [ - 156, - 342 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "192", - "name": "Sea Foam Islands B4F", - "coordinates": [ - 85, - 350 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "193", - "name": "Badge Gate House", - "coordinates": [ - 17, - 246 - ], - "tileSize": [ - 10, - 8 - ] - }, - { - "id": "194", - "name": "Victory Road F2", - "coordinates": [ - 35, - 97 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "195", - "name": "Gate 2F", - "coordinates": [ - 355, - 223 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "196", - "name": "Trainer House Farfetch'd Trade", - "coordinates": [ - 225, - 291 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "197", - "name": "DIgletts Cave", - "coordinates": [ - 284, - 235 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "198", - "name": "Victory Road F3", - "coordinates": [ - 35, - 78 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "199", - "name": "Rocket Hideout B1F", - "coordinates": [ - 97, - 117 - ], - "tileSize": [ - 30, - 28 - ] - }, - { - "id": "200", - "name": "Rocket Hideout B2F", - "coordinates": [ - 97, - 88 - ], - "tileSize": [ - 30, - 28 - ] - }, - { - "id": "201", - "name": "Rocket Hideout B3F", - "coordinates": [ - 97, - 59 - ], - "tileSize": [ - 30, - 28 - ] - }, - { - "id": "202", - "name": "Rocket Hideout B4F", - "coordinates": [ - 97, - 34 - ], - "tileSize": [ - 30, - 24 - ] - }, - { - "id": "203", - "name": "Rocket Hideout Lift", - "coordinates": [ - 121, - 137 - ], - "tileSize": [ - 6, - 8 - ] - }, - { - "id": "207", - "name": "Silph Co 2F", - "coordinates": [ - 325, - 90 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "208", - "name": "Silph Co 3F", - "coordinates": [ - 325, - 71 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "209", - "name": "Silph Co 4F", - "coordinates": [ - 325, - 52 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "210", - "name": "Silph Co 5F", - "coordinates": [ - 325, - 33 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "211", - "name": "Silph Co 6F", - "coordinates": [ - 325, - 14 - ], - "tileSize": [ - 26, - 18 - ] - }, - { - "id": "212", - "name": "Silph Co 7F", - "coordinates": [ - 356, - 109 - ], - "tileSize": [ - 26, - 18 - ] - }, - { - "id": "213", - "name": "Silph Co 8F", - "coordinates": [ - 356, - 90 - ], - "tileSize": [ - 26, - 18 - ] - }, - { - "id": "214", - "name": "Pokemon Mansion F2", - "coordinates": [ - 33, - 360 - ], - "tileSize": [ - 30, - 28 - ] - }, - { - "id": "215", - "name": "Pokemon Mansion F3", - "coordinates": [ - 33, - 341 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "216", - "name": "Pokemon Mansion B1F", - "coordinates": [ - 2, - 389 - ], - "tileSize": [ - 30, - 28 - ] - }, - { - "id": "217", - "name": "Safari West", - "coordinates": [ - 148, - 310 - ], - "tileSize": [ - 30, - 26 - ] - }, - { - "id": "218", - "name": "Safari East", - "coordinates": [ - 208, - 310 - ], - "tileSize": [ - 30, - 26 - ] - }, - { - "id": "219", - "name": "Safari North", - "coordinates": [ - 166, - 274 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "220", - "name": "Safari South", - "coordinates": [ - 178, - 322 - ], - "tileSize": [ - 30, - 26 - ] - }, - { - "id": "221", - "name": "Rest House South", - "coordinates": [ - 209, - 337 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "222", - "name": "Secret House", - "coordinates": [ - 148, - 301 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "223", - "name": "Rest House", - "coordinates": [ - 157, - 301 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "224", - "name": "Rest House East", - "coordinates": [ - 230, - 301 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "225", - "name": "Rest House North", - "coordinates": [ - 207, - 273 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "226", - "name": "Cerulean Cave 2F", - "coordinates": [ - 213, - 81 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "227", - "name": "Cerulean Cave B1F", - "coordinates": [ - 213, - 62 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "228", - "name": "Cerulean Cave Entrance", - "coordinates": [ - 213, - 100 - ], - "tileSize": [ - 30, - 18 - ] - }, - { - "id": "229", - "name": "Name Rater House", - "coordinates": [ - 325, - 219 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "230", - "name": "Badge Man House", - "coordinates": [ - 216, - 119 - ], - "tileSize": [ - 8, - 8 - ] - }, - { - "id": "232", - "name": "Rock Tunnel 1F", - "coordinates": [ - 396, - 145 - ], - "tileSize": [ - 40, - 36 - ] - }, - { - "id": "233", - "name": "Silph Co 9F", - "coordinates": [ - 356, - 71 - ], - "tileSize": [ - 26, - 18 - ] - }, - { - "id": "234", - "name": "Silph Co 10F", - "coordinates": [ - 356, - 52 - ], - "tileSize": [ - 16, - 18 - ] - }, - { - "id": "235", - "name": "Silph Co 11F", - "coordinates": [ - 356, - 32 - ], - "tileSize": [ - 18, - 19 - ] - }, - { - "id": "236", - "name": "Silph Co Lift", - "coordinates": [ - 355, - 128 - ], - "tileSize": [ - 4, - 5 - ] - }, - { - "id": "239", - "name": "Trade Center", - "coordinates": [ - 85, - 283 - ], - "tileSize": [ - 10, - 8 - ] - }, - { - "id": "240", - "name": "Colosseum Club", - "coordinates": [ - 96, - 283 - ], - "tileSize": [ - 10, - 8 - ] - }, - { - "id": "245", - "name": "Loreleis Room", - "coordinates": [ - 20, - 60 - ], - "tileSize": [ - 10, - 12 - ] - }, - { - "id": "246", - "name": "Brunos Room", - "coordinates": [ - 20, - 48 - ], - "tileSize": [ - 10, - 12 - ] - }, - { - "id": "247", - "name": "Agathas Room", - "coordinates": [ - 20, - 36 - ], - "tileSize": [ - 10, - 12 - ] - } - ] -} \ No newline at end of file diff --git a/pokegym/newram_map.py b/pokegym/newram_map.py deleted file mode 100644 index 15861ec..0000000 --- a/pokegym/newram_map.py +++ /dev/null @@ -1,779 +0,0 @@ -from pokegym import data - -HP_ADDR = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] -MAX_HP_ADDR = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] -PARTY_SIZE_ADDR = 0xD163 -PARTY_ADDR = [0xD164, 0xD165, 0xD166, 0xD167, 0xD168, 0xD169] -PARTY_LEVEL_ADDR = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] -POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] # - Pokémon (Again) -X_POS_ADDR = 0xD362 -Y_POS_ADDR = 0xD361 -MAP_N_ADDR = 0xD35E -BADGE_1_ADDR = 0xD356 -WCUTTILE = 0xCD4D # 61 if Cut used; 0 default. resets to default on map_n change or battle. - - -GYM_LEADER = 5 -GYM_TRAINER = 2 -GYM_TASK = 2 -TRAINER = 1 -HM = 5 -TM = 2 -TASK = 2 -POKEMON = 3 -ITEM = 5 -BILL_CAPT = 5 -RIVAL = 3 -QUEST = 5 -EVENT = 1 -BAD = -1 - -def bulba(game): - # Get memory values from the list POKE and LEVEL - poke = [game.get_memory_value(a) for a in POKE] - if any(x in poke for x in [153, 9, 154]): - reward = 0 - else: - reward = -5 - return reward - -def silph_co(game): - Beat_Silph_Co_2F_Trainer_0 = TRAINER * int(read_bit(game, 0xD825, 2)) - Beat_Silph_Co_2F_Trainer_1 = TRAINER * int(read_bit(game, 0xD825, 3)) - Beat_Silph_Co_2F_Trainer_2 = TRAINER * int(read_bit(game, 0xD825, 4)) - Beat_Silph_Co_2F_Trainer_3 = TRAINER * int(read_bit(game, 0xD825, 5)) - Silph_Co_2_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD826, 5)) - Silph_Co_2_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD826, 6)) - Beat_Silph_Co_3F_Trainer_0 = TRAINER * int(read_bit(game, 0xD827, 2)) - Beat_Silph_Co_3F_Trainer_1 = TRAINER * int(read_bit(game, 0xD827, 3)) - Silph_Co_3_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD828, 0)) - Silph_Co_3_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD828, 1)) - Beat_Silph_Co_4F_Trainer_0 = TRAINER * int(read_bit(game, 0xD829, 2)) - Beat_Silph_Co_4F_Trainer_1 = TRAINER * int(read_bit(game, 0xD829, 3)) - Beat_Silph_Co_4F_Trainer_2 = TRAINER * int(read_bit(game, 0xD829, 4)) - Silph_Co_4_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD82A, 0)) - Silph_Co_4_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD82A, 1)) - Beat_Silph_Co_5F_Trainer_0 = TRAINER * int(read_bit(game, 0xD82B, 2)) - Beat_Silph_Co_5F_Trainer_1 = TRAINER * int(read_bit(game, 0xD82B, 3)) - Beat_Silph_Co_5F_Trainer_2 = TRAINER * int(read_bit(game, 0xD82B, 4)) - Beat_Silph_Co_5F_Trainer_3 = TRAINER * int(read_bit(game, 0xD82B, 5)) - Silph_Co_5_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD82C, 0)) - Silph_Co_5_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD82C, 1)) - Silph_Co_5_Unlocked_Door3 = QUEST * int(read_bit(game, 0xD82C, 2)) - Beat_Silph_Co_6F_Trainer_0 = TRAINER * int(read_bit(game, 0xD82D, 6)) - Beat_Silph_Co_6F_Trainer_1 = TRAINER * int(read_bit(game, 0xD82D, 7)) - Beat_Silph_Co_6F_Trainer_2 = TRAINER * int(read_bit(game, 0xD82E, 0)) - Silph_Co_6_Unlocked_Door = QUEST * int(read_bit(game, 0xD82E, 7)) - Beat_Silph_Co_7F_Trainer_0 = TRAINER * int(read_bit(game, 0xD82F, 5)) - Beat_Silph_Co_7F_Trainer_1 = TRAINER * int(read_bit(game, 0xD82F, 6)) - Beat_Silph_Co_7F_Trainer_2 = TRAINER * int(read_bit(game, 0xD82F, 7)) - Beat_Silph_Co_7F_Trainer_3 = TRAINER * int(read_bit(game, 0xD830, 0)) - Silph_Co_7_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD830, 4)) - Silph_Co_7_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD830, 5)) - Silph_Co_7_Unlocked_Door3 = QUEST * int(read_bit(game, 0xD830, 6)) - Beat_Silph_Co_8F_Trainer_0 = TRAINER * int(read_bit(game, 0xD831, 2)) - Beat_Silph_Co_8F_Trainer_1 = TRAINER * int(read_bit(game, 0xD831, 3)) - Beat_Silph_Co_8F_Trainer_2 = TRAINER * int(read_bit(game, 0xD831, 4)) - Silph_Co_8_Unlocked_Door = QUEST * int(read_bit(game, 0xD832, 0)) - Beat_Silph_Co_9F_Trainer_0 = TRAINER * int(read_bit(game, 0xD833, 2)) - Beat_Silph_Co_9F_Trainer_1 = TRAINER * int(read_bit(game, 0xD833, 3)) - Beat_Silph_Co_9F_Trainer_2 = TRAINER * int(read_bit(game, 0xD833, 4)) - Silph_Co_9_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD834, 0)) - Silph_Co_9_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD834, 1)) - Silph_Co_9_Unlocked_Door3 = QUEST * int(read_bit(game, 0xD834, 2)) - Silph_Co_9_Unlocked_Door4 = QUEST * int(read_bit(game, 0xD834, 3)) - Beat_Silph_Co_10F_Trainer_0 = TRAINER * int(read_bit(game, 0xD835, 1)) - Beat_Silph_Co_10F_Trainer_1 = TRAINER * int(read_bit(game, 0xD835, 2)) - Silph_Co_10_Unlocked_Door = QUEST * int(read_bit(game, 0xD836, 0)) - Beat_Silph_Co_11F_Trainer_0 = TRAINER * int(read_bit(game, 0xD837, 4)) - Beat_Silph_Co_11F_Trainer_1 = TRAINER * int(read_bit(game, 0xD837, 5)) - Silph_Co_11_Unlocked_Door = QUEST * int(read_bit(game, 0xD838, 0)) - Got_Master_Ball = ITEM * int(read_bit(game, 0xD838, 5)) - Beat_Silph_Co_Giovanni = GYM_LEADER * int(read_bit(game, 0xD838, 7)) - Silph_Co_Receptionist_At_Desk = TASK * int(read_bit(game, 0xD7B9, 7)) - return sum([Beat_Silph_Co_2F_Trainer_0, Beat_Silph_Co_2F_Trainer_1, Beat_Silph_Co_2F_Trainer_2, Beat_Silph_Co_2F_Trainer_3, Silph_Co_2_Unlocked_Door1, - Silph_Co_2_Unlocked_Door2, Beat_Silph_Co_3F_Trainer_0, Beat_Silph_Co_3F_Trainer_1, Silph_Co_3_Unlocked_Door1, Silph_Co_3_Unlocked_Door2, - Beat_Silph_Co_4F_Trainer_0, Beat_Silph_Co_4F_Trainer_1, Beat_Silph_Co_4F_Trainer_2, Silph_Co_4_Unlocked_Door1, Silph_Co_4_Unlocked_Door2, - Beat_Silph_Co_5F_Trainer_0, Beat_Silph_Co_5F_Trainer_1, Beat_Silph_Co_5F_Trainer_2, Beat_Silph_Co_5F_Trainer_3, Silph_Co_5_Unlocked_Door1, - Silph_Co_5_Unlocked_Door2, Silph_Co_5_Unlocked_Door3, Beat_Silph_Co_6F_Trainer_0, Beat_Silph_Co_6F_Trainer_1, Beat_Silph_Co_6F_Trainer_2, - Silph_Co_6_Unlocked_Door, Beat_Silph_Co_7F_Trainer_0, Beat_Silph_Co_7F_Trainer_1, Beat_Silph_Co_7F_Trainer_2, Beat_Silph_Co_7F_Trainer_3, - Silph_Co_7_Unlocked_Door1, Silph_Co_7_Unlocked_Door2, Silph_Co_7_Unlocked_Door3, Beat_Silph_Co_8F_Trainer_0, Beat_Silph_Co_8F_Trainer_1, - Beat_Silph_Co_8F_Trainer_2, Silph_Co_8_Unlocked_Door, Beat_Silph_Co_9F_Trainer_0, Beat_Silph_Co_9F_Trainer_1, Beat_Silph_Co_9F_Trainer_2, - Silph_Co_9_Unlocked_Door1, Silph_Co_9_Unlocked_Door2, Silph_Co_9_Unlocked_Door3, Silph_Co_9_Unlocked_Door4, Beat_Silph_Co_10F_Trainer_0, - Beat_Silph_Co_10F_Trainer_1, Silph_Co_10_Unlocked_Door, Beat_Silph_Co_11F_Trainer_0, Beat_Silph_Co_11F_Trainer_1, Silph_Co_11_Unlocked_Door, - Got_Master_Ball, Beat_Silph_Co_Giovanni, Silph_Co_Receptionist_At_Desk]) - -def rock_tunnel(game): - Beat_Rock_Tunnel_1_Trainer_0 = TRAINER * int(read_bit(game, 0xD7D2, 1)) - Beat_Rock_Tunnel_1_Trainer_1 = TRAINER * int(read_bit(game, 0xD7D2, 2)) - Beat_Rock_Tunnel_1_Trainer_2 = TRAINER * int(read_bit(game, 0xD7D2, 3)) - Beat_Rock_Tunnel_1_Trainer_3 = TRAINER * int(read_bit(game, 0xD7D2, 4)) - Beat_Rock_Tunnel_1_Trainer_4 = TRAINER * int(read_bit(game, 0xD7D2, 5)) - Beat_Rock_Tunnel_1_Trainer_5 = TRAINER * int(read_bit(game, 0xD7D2, 6)) - Beat_Rock_Tunnel_1_Trainer_6 = TRAINER * int(read_bit(game, 0xD7D2, 7)) - Beat_Rock_Tunnel_2_Trainer_0 = TRAINER * int(read_bit(game, 0xD87D, 1)) - Beat_Rock_Tunnel_2_Trainer_1 = TRAINER * int(read_bit(game, 0xD87D, 2)) - Beat_Rock_Tunnel_2_Trainer_2 = TRAINER * int(read_bit(game, 0xD87D, 3)) - Beat_Rock_Tunnel_2_Trainer_3 = TRAINER * int(read_bit(game, 0xD87D, 4)) - Beat_Rock_Tunnel_2_Trainer_4 = TRAINER * int(read_bit(game, 0xD87D, 5)) - Beat_Rock_Tunnel_2_Trainer_5 = TRAINER * int(read_bit(game, 0xD87D, 6)) - Beat_Rock_Tunnel_2_Trainer_6 = TRAINER * int(read_bit(game, 0xD87D, 7)) - Beat_Rock_Tunnel_2_Trainer_7 = TRAINER * int(read_bit(game, 0xD87E, 0)) - return sum([Beat_Rock_Tunnel_1_Trainer_0, Beat_Rock_Tunnel_1_Trainer_1, Beat_Rock_Tunnel_1_Trainer_2, Beat_Rock_Tunnel_1_Trainer_3, - Beat_Rock_Tunnel_1_Trainer_4, Beat_Rock_Tunnel_1_Trainer_5, Beat_Rock_Tunnel_1_Trainer_6, Beat_Rock_Tunnel_2_Trainer_0, - Beat_Rock_Tunnel_2_Trainer_1, Beat_Rock_Tunnel_2_Trainer_2, Beat_Rock_Tunnel_2_Trainer_3, Beat_Rock_Tunnel_2_Trainer_4, - Beat_Rock_Tunnel_2_Trainer_5, Beat_Rock_Tunnel_2_Trainer_6, Beat_Rock_Tunnel_2_Trainer_7]) - -def ssanne(game): - Beat_Ss_Anne_5_Trainer_0 = TRAINER * int(read_bit(game, 0xD7FF, 4)) - Beat_Ss_Anne_5_Trainer_1 = TRAINER * int(read_bit(game, 0xD7FF, 5)) - Rubbed_Captains_Back = BILL_CAPT * int(read_bit(game, 0xD803, 1)) - Ss_Anne_Left = BILL_CAPT * int(read_bit(game, 0xD803, 2)) - Walked_Past_Guard_After_Ss_Anne_Left = BILL_CAPT * int(read_bit(game, 0xD803, 3)) - Started_Walking_Out_Of_Dock = BILL_CAPT * int(read_bit(game, 0xD803, 4)) - Walked_Out_Of_Dock = BILL_CAPT * int(read_bit(game, 0xD803, 5)) - Beat_Ss_Anne_8_Trainer_0 = TRAINER * int(read_bit(game, 0xD805, 1)) - Beat_Ss_Anne_8_Trainer_1 = TRAINER * int(read_bit(game, 0xD805, 2)) - Beat_Ss_Anne_8_Trainer_2 = TRAINER * int(read_bit(game, 0xD805, 3)) - Beat_Ss_Anne_8_Trainer_3 = TRAINER * int(read_bit(game, 0xD805, 4)) - Beat_Ss_Anne_9_Trainer_0 = TRAINER * int(read_bit(game, 0xD807, 1)) - Beat_Ss_Anne_9_Trainer_1 = TRAINER * int(read_bit(game, 0xD807, 2)) - Beat_Ss_Anne_9_Trainer_2 = TRAINER * int(read_bit(game, 0xD807, 3)) - Beat_Ss_Anne_9_Trainer_3 = TRAINER * int(read_bit(game, 0xD807, 4)) - Beat_Ss_Anne_10_Trainer_0 = TRAINER * int(read_bit(game, 0xD809, 1)) - Beat_Ss_Anne_10_Trainer_1 = TRAINER * int(read_bit(game, 0xD809, 2)) - Beat_Ss_Anne_10_Trainer_2 = TRAINER * int(read_bit(game, 0xD809, 3)) - Beat_Ss_Anne_10_Trainer_3 = TRAINER * int(read_bit(game, 0xD809, 4)) - Beat_Ss_Anne_10_Trainer_4 = TRAINER * int(read_bit(game, 0xD809, 5)) - Beat_Ss_Anne_10_Trainer_5 = TRAINER * int(read_bit(game, 0xD809, 6)) - return sum([Beat_Ss_Anne_5_Trainer_0, Beat_Ss_Anne_5_Trainer_1, Rubbed_Captains_Back, Ss_Anne_Left, - Walked_Past_Guard_After_Ss_Anne_Left, Started_Walking_Out_Of_Dock, Walked_Out_Of_Dock, Beat_Ss_Anne_8_Trainer_0, - Beat_Ss_Anne_8_Trainer_1, Beat_Ss_Anne_8_Trainer_2, Beat_Ss_Anne_8_Trainer_3, Beat_Ss_Anne_9_Trainer_0, - Beat_Ss_Anne_9_Trainer_1, Beat_Ss_Anne_9_Trainer_2, Beat_Ss_Anne_9_Trainer_3, Beat_Ss_Anne_10_Trainer_0, - Beat_Ss_Anne_10_Trainer_1, Beat_Ss_Anne_10_Trainer_2, Beat_Ss_Anne_10_Trainer_3, Beat_Ss_Anne_10_Trainer_4, Beat_Ss_Anne_10_Trainer_5]) - -def mtmoon(game): - Beat_Mt_Moon_1_Trainer_1 = TRAINER * int(read_bit(game, 0xD7F5, 1)) - Beat_Mt_Moon_1_Trainer_2 = TRAINER * int(read_bit(game, 0xD7F5, 2)) - Beat_Mt_Moon_1_Trainer_3 = TRAINER * int(read_bit(game, 0xD7F5, 3)) - Beat_Mt_Moon_1_Trainer_4 = TRAINER * int(read_bit(game, 0xD7F5, 4)) - Beat_Mt_Moon_1_Trainer_5 = TRAINER * int(read_bit(game, 0xD7F5, 5)) - Beat_Mt_Moon_1_Trainer_6 = TRAINER * int(read_bit(game, 0xD7F5, 6)) - Beat_Mt_Moon_1_Trainer_7 = TRAINER * int(read_bit(game, 0xD7F5, 7)) - Beat_Mt_Moon_Super_Nerd = TRAINER * int(read_bit(game, 0xD7F6, 1)) - Beat_Mt_Moon_3_Trainer_0 = TRAINER * int(read_bit(game, 0xD7F6, 2)) - Beat_Mt_Moon_3_Trainer_1 = TRAINER * int(read_bit(game, 0xD7F6, 3)) - Beat_Mt_Moon_3_Trainer_2 = TRAINER * int(read_bit(game, 0xD7F6, 4)) - Beat_Mt_Moon_3_Trainer_3 = TRAINER * int(read_bit(game, 0xD7F6, 5)) - Got_Dome_Fossil = TASK * int(read_bit(game, 0xD7F6, 6)) - Got_Helix_Fossil = TASK * int(read_bit(game, 0xD7F6, 7)) - return sum([Beat_Mt_Moon_1_Trainer_1, Beat_Mt_Moon_1_Trainer_2, Beat_Mt_Moon_1_Trainer_3, Beat_Mt_Moon_1_Trainer_4, - Beat_Mt_Moon_1_Trainer_5, Beat_Mt_Moon_1_Trainer_6, Beat_Mt_Moon_1_Trainer_7, Beat_Mt_Moon_Super_Nerd, - Beat_Mt_Moon_3_Trainer_0, Beat_Mt_Moon_3_Trainer_1, Beat_Mt_Moon_3_Trainer_2, Beat_Mt_Moon_3_Trainer_3, - Got_Dome_Fossil, Got_Helix_Fossil]) - -def routes(game): - route3_0 = TRAINER * int(read_bit(game, 0xD7C3, 2)) - route3_1 = TRAINER * int(read_bit(game, 0xD7C3, 3)) - route3_2 = TRAINER * int(read_bit(game, 0xD7C3, 4)) - route3_3 = TRAINER * int(read_bit(game, 0xD7C3, 5)) - route3_4 = TRAINER * int(read_bit(game, 0xD7C3, 6)) - route3_5 = TRAINER * int(read_bit(game, 0xD7C3, 7)) - route3_6 = TRAINER * int(read_bit(game, 0xD7C4, 0)) - route3_7 = TRAINER * int(read_bit(game, 0xD7C4, 1)) - - route4_0 = TRAINER * int(read_bit(game, 0xD7C5, 2)) - - route24_rocket = TRAINER * int(read_bit(game, 0xD7EF, 1)) - route24_0 = TRAINER * int(read_bit(game, 0xD7EF, 2)) - route24_1 = TRAINER * int(read_bit(game, 0xD7EF, 3)) - route24_2 = TRAINER * int(read_bit(game, 0xD7EF, 4)) - route24_3 = TRAINER * int(read_bit(game, 0xD7EF, 5)) - route24_4 = TRAINER * int(read_bit(game, 0xD7EF, 6)) - route24_5 = TRAINER * int(read_bit(game, 0xD7EF, 7)) - - route25_0 = TRAINER * int(read_bit(game, 0xD7F1, 1)) - route25_1 = TRAINER * int(read_bit(game, 0xD7F1, 2)) - route25_2 = TRAINER * int(read_bit(game, 0xD7F1, 3)) - route25_3 = TRAINER * int(read_bit(game, 0xD7F1, 4)) - route25_4 = TRAINER * int(read_bit(game, 0xD7F1, 5)) - route25_5 = TRAINER * int(read_bit(game, 0xD7F1, 6)) - route25_6 = TRAINER * int(read_bit(game, 0xD7F1, 7)) - route25_7 = TRAINER * int(read_bit(game, 0xD7F2, 0)) - route25_8 = TRAINER * int(read_bit(game, 0xD7F2, 1)) - - route9_0 = TRAINER * int(read_bit(game, 0xD7CF, 1)) - route9_1 = TRAINER * int(read_bit(game, 0xD7CF, 2)) - route9_2 = TRAINER * int(read_bit(game, 0xD7CF, 3)) - route9_3 = TRAINER * int(read_bit(game, 0xD7CF, 4)) - route9_4 = TRAINER * int(read_bit(game, 0xD7CF, 5)) - route9_5 = TRAINER * int(read_bit(game, 0xD7CF, 6)) - route9_6 = TRAINER * int(read_bit(game, 0xD7CF, 7)) - route9_7 = TRAINER * int(read_bit(game, 0xD7D0, 0)) - route9_8 = TRAINER * int(read_bit(game, 0xD7D0, 1)) - - route6_0 = TRAINER * int(read_bit(game, 0xD7C9, 1)) - route6_1 = TRAINER * int(read_bit(game, 0xD7C9, 2)) - route6_2 = TRAINER * int(read_bit(game, 0xD7C9, 3)) - route6_3 = TRAINER * int(read_bit(game, 0xD7C9, 4)) - route6_4 = TRAINER * int(read_bit(game, 0xD7C9, 5)) - route6_5 = TRAINER * int(read_bit(game, 0xD7C9, 6)) - - route11_0 = TRAINER * int(read_bit(game, 0xD7D5, 1)) - route11_1 = TRAINER * int(read_bit(game, 0xD7D5, 2)) - route11_2 = TRAINER * int(read_bit(game, 0xD7D5, 3)) - route11_3 = TRAINER * int(read_bit(game, 0xD7D5, 4)) - route11_4 = TRAINER * int(read_bit(game, 0xD7D5, 5)) - route11_5 = TRAINER * int(read_bit(game, 0xD7D5, 6)) - route11_6 = TRAINER * int(read_bit(game, 0xD7D5, 7)) - route11_7 = TRAINER * int(read_bit(game, 0xD7D6, 0)) - route11_8 = TRAINER * int(read_bit(game, 0xD7D6, 1)) - route11_9 = TRAINER * int(read_bit(game, 0xD7D6, 2)) - - route8_0 = TRAINER * int(read_bit(game, 0xD7CD, 1)) - route8_1 = TRAINER * int(read_bit(game, 0xD7CD, 2)) - route8_2 = TRAINER * int(read_bit(game, 0xD7CD, 3)) - route8_3 = TRAINER * int(read_bit(game, 0xD7CD, 4)) - route8_4 = TRAINER * int(read_bit(game, 0xD7CD, 5)) - route8_5 = TRAINER * int(read_bit(game, 0xD7CD, 6)) - route8_6 = TRAINER * int(read_bit(game, 0xD7CD, 7)) - route8_7 = TRAINER * int(read_bit(game, 0xD7CE, 0)) - route8_8 = TRAINER * int(read_bit(game, 0xD7CE, 1)) - - route10_0 = TRAINER * int(read_bit(game, 0xD7D1, 1)) - route10_1 = TRAINER * int(read_bit(game, 0xD7D1, 2)) - route10_2 = TRAINER * int(read_bit(game, 0xD7D1, 3)) - route10_3 = TRAINER * int(read_bit(game, 0xD7D1, 4)) - route10_4 = TRAINER * int(read_bit(game, 0xD7D1, 5)) - route10_5 = TRAINER * int(read_bit(game, 0xD7D1, 6)) - - route12_0 = TRAINER * int(read_bit(game, 0xD7D7, 2)) - route12_1 = TRAINER * int(read_bit(game, 0xD7D7, 3)) - route12_2 = TRAINER * int(read_bit(game, 0xD7D7, 4)) - route12_3 = TRAINER * int(read_bit(game, 0xD7D7, 5)) - route12_4 = TRAINER * int(read_bit(game, 0xD7D7, 6)) - route12_5 = TRAINER * int(read_bit(game, 0xD7D7, 7)) - route12_6 = TRAINER * int(read_bit(game, 0xD7D8, 0)) - - route16_0 = TRAINER * int(read_bit(game, 0xD7DF, 1)) - route16_1 = TRAINER * int(read_bit(game, 0xD7DF, 2)) - route16_2 = TRAINER * int(read_bit(game, 0xD7DF, 3)) - route16_3 = TRAINER * int(read_bit(game, 0xD7DF, 4)) - route16_4 = TRAINER * int(read_bit(game, 0xD7DF, 5)) - route16_5 = TRAINER * int(read_bit(game, 0xD7DF, 6)) - - route17_0 = TRAINER * int(read_bit(game, 0xD7E1, 1)) - route17_1 = TRAINER * int(read_bit(game, 0xD7E1, 2)) - route17_2 = TRAINER * int(read_bit(game, 0xD7E1, 3)) - route17_3 = TRAINER * int(read_bit(game, 0xD7E1, 4)) - route17_4 = TRAINER * int(read_bit(game, 0xD7E1, 5)) - route17_5 = TRAINER * int(read_bit(game, 0xD7E1, 6)) - route17_6 = TRAINER * int(read_bit(game, 0xD7E1, 7)) - route17_7 = TRAINER * int(read_bit(game, 0xD7E2, 0)) - route17_8 = TRAINER * int(read_bit(game, 0xD7E2, 1)) - route17_9 = TRAINER * int(read_bit(game, 0xD7E2, 2)) - - route13_0 = TRAINER * int(read_bit(game, 0xD7D9, 1)) - route13_1 = TRAINER * int(read_bit(game, 0xD7D9, 2)) - route13_2 = TRAINER * int(read_bit(game, 0xD7D9, 3)) - route13_3 = TRAINER * int(read_bit(game, 0xD7D9, 4)) - route13_4 = TRAINER * int(read_bit(game, 0xD7D9, 5)) - route13_5 = TRAINER * int(read_bit(game, 0xD7D9, 6)) - route13_6 = TRAINER * int(read_bit(game, 0xD7D9, 7)) - route13_7 = TRAINER * int(read_bit(game, 0xD7DA, 0)) - route13_8 = TRAINER * int(read_bit(game, 0xD7DA, 1)) - route13_9 = TRAINER * int(read_bit(game, 0xD7DA, 2)) - - route14_0 = TRAINER * int(read_bit(game, 0xD7DB, 1)) - route14_1 = TRAINER * int(read_bit(game, 0xD7DB, 2)) - route14_2 = TRAINER * int(read_bit(game, 0xD7DB, 3)) - route14_3 = TRAINER * int(read_bit(game, 0xD7DB, 4)) - route14_4 = TRAINER * int(read_bit(game, 0xD7DB, 5)) - route14_5 = TRAINER * int(read_bit(game, 0xD7DB, 6)) - route14_6 = TRAINER * int(read_bit(game, 0xD7DB, 7)) - route14_7 = TRAINER * int(read_bit(game, 0xD7DC, 0)) - route14_8 = TRAINER * int(read_bit(game, 0xD7DC, 1)) - route14_9 = TRAINER * int(read_bit(game, 0xD7DC, 2)) - - route15_0 = TRAINER * int(read_bit(game, 0xD7DD, 1)) - route15_1 = TRAINER * int(read_bit(game, 0xD7DD, 2)) - route15_2 = TRAINER * int(read_bit(game, 0xD7DD, 3)) - route15_3 = TRAINER * int(read_bit(game, 0xD7DD, 4)) - route15_4 = TRAINER * int(read_bit(game, 0xD7DD, 5)) - route15_5 = TRAINER * int(read_bit(game, 0xD7DD, 6)) - route15_6 = TRAINER * int(read_bit(game, 0xD7DD, 7)) - route15_7 = TRAINER * int(read_bit(game, 0xD7DE, 0)) - route15_8 = TRAINER * int(read_bit(game, 0xD7DE, 1)) - route15_9 = TRAINER * int(read_bit(game, 0xD7DE, 2)) - - route18_0 = TRAINER * int(read_bit(game, 0xD7E3, 1)) - route18_1 = TRAINER * int(read_bit(game, 0xD7E3, 2)) - route18_2 = TRAINER * int(read_bit(game, 0xD7E3, 3)) - - route19_0 = TRAINER * int(read_bit(game, 0xD7E5, 1)) - route19_1 = TRAINER * int(read_bit(game, 0xD7E5, 2)) - route19_2 = TRAINER * int(read_bit(game, 0xD7E5, 3)) - route19_3 = TRAINER * int(read_bit(game, 0xD7E5, 4)) - route19_4 = TRAINER * int(read_bit(game, 0xD7E5, 5)) - route19_5 = TRAINER * int(read_bit(game, 0xD7E5, 6)) - route19_6 = TRAINER * int(read_bit(game, 0xD7E5, 7)) - route19_7 = TRAINER * int(read_bit(game, 0xD7E6, 0)) - route19_8 = TRAINER * int(read_bit(game, 0xD7E6, 1)) - route19_9 = TRAINER * int(read_bit(game, 0xD7E6, 2)) - - route20_0 = TRAINER * int(read_bit(game, 0xD7E7, 1)) - route20_1 = TRAINER * int(read_bit(game, 0xD7E7, 2)) - route20_2 = TRAINER * int(read_bit(game, 0xD7E7, 3)) - route20_3 = TRAINER * int(read_bit(game, 0xD7E7, 4)) - route20_4 = TRAINER * int(read_bit(game, 0xD7E7, 5)) - route20_5 = TRAINER * int(read_bit(game, 0xD7E7, 6)) - route20_6 = TRAINER * int(read_bit(game, 0xD7E7, 7)) - route20_7 = TRAINER * int(read_bit(game, 0xD7E8, 0)) - route20_8 = TRAINER * int(read_bit(game, 0xD7E8, 1)) - route20_9 = TRAINER * int(read_bit(game, 0xD7E8, 2)) - - route21_0 = TRAINER * int(read_bit(game, 0xD7E9, 1)) - route21_1 = TRAINER * int(read_bit(game, 0xD7E9, 2)) - route21_2 = TRAINER * int(read_bit(game, 0xD7E9, 3)) - route21_3 = TRAINER * int(read_bit(game, 0xD7E9, 4)) - route21_4 = TRAINER * int(read_bit(game, 0xD7E9, 5)) - route21_5 = TRAINER * int(read_bit(game, 0xD7E9, 6)) - route21_6 = TRAINER * int(read_bit(game, 0xD7E9, 7)) - route21_7 = TRAINER * int(read_bit(game, 0xD7EA, 0)) - route21_8 = TRAINER * int(read_bit(game, 0xD7EA, 1)) - - return sum([ route3_0, route3_1, route3_2, route3_3, route3_4, route3_5, route3_6, route3_7, - route4_0, route24_rocket, route24_0, route24_1, route24_2, route24_3, route24_4, - route24_5, route25_0, route25_1, route25_2, route25_3, route25_4, route25_5, route25_6, - route25_7, route25_8, route9_0, route9_1, route9_2, route9_3, route9_4, route9_5, - route9_6, route9_7, route9_8, route6_0, route6_1, route6_2, route6_3, route6_4, - route6_5, route11_0, route11_1, route11_2, route11_3, route11_4, route11_5, route11_6, - route11_7, route11_8, route11_9, route8_0, route8_1, route8_2, route8_3, route8_4, route8_5, - route8_6, route8_7, route8_8, route10_0, route10_1, route10_2, route10_3, route10_4, route10_5, - route12_0, route12_1, route12_2, route12_3, route12_4, route12_5, route12_6, route16_0, - route16_1, route16_2, route16_3, route16_4, route16_5, route17_0, route17_1, route17_2, - route17_3, route17_4, route17_5, route17_6, route17_7, route17_8, route17_9, route13_0, - route13_1, route13_2, route13_3, route13_4, route13_5, route13_6, route13_7, route13_8, - route13_9, route14_0, route14_1, route14_2, route14_3, route14_4, route14_5, route14_6, - route14_7, route14_8, route14_9, route15_0, route15_1, route15_2, route15_3, route15_4, - route15_5, route15_6, route15_7, route15_8, route15_9, route18_0, route18_1, route18_2, - route19_0, route19_1, route19_2, route19_3, route19_4, route19_5, route19_6, route19_7, - route19_8, route19_9, route20_0, route20_1, route20_2, route20_3, route20_4, route20_5, - route20_6, route20_7, route20_8, route20_9, route21_0, route21_1, route21_2, route21_3, - route21_4, route21_5, route21_6, route21_7, route21_8]) - -def misc(game): - # "0xD7C6-7": "Bought Magikarp", - # "0xD747-3": "Hall Of Fame Dex Rating", - # "0xD74A-2": "Daisy Walking", - # "0xD754-0": "Bought Museum Ticket", - # "0xD754-1": "Got Old Amber", - # "0xD771-1": "Got Bike Voucher", - # "0xD77E-2": "Got 10 Coins", - # "0xD77E-3": "Got 20 Coins", - # "0xD77E-4": "Got 20 Coins 2", - # "0xD783-0": "Got Coin Case", - # "0xD7BF-0": "Got Potion Sample", - # "0xD7D6-7": "Got Itemfinder", - # "0xD7DD-0": "Got Exp All", - # "0xD7E0-7": "Rescued Mr Fuji", - # "0xD85F-1": "Beat Mewtwo", - # "0xD769-7": "Rescued Mr Fuji 2", - one = TASK * int(read_bit(game, 0xD7C6, 7)) - two = TASK * int(read_bit(game, 0xD747, 3)) - three = TASK * int(read_bit(game, 0xD74A, 2)) - four = BAD * int(read_bit(game, 0xD754, 0)) - five = TASK * int(read_bit(game, 0xD754, 1)) - six = TASK * int(read_bit(game, 0xD771, 1)) - seven = TASK * int(read_bit(game, 0xD77E, 2)) - eight = TASK * int(read_bit(game, 0xD77E, 3)) - nine = TASK * int(read_bit(game, 0xD77E, 4)) - ten = TASK * int(read_bit(game, 0xD783, 0)) - eleven = TASK * int(read_bit(game, 0xD7BF, 0)) - twelve = TASK * int(read_bit(game, 0xD7D6, 7)) - thirteen = TASK * int(read_bit(game, 0xD7DD, 0)) - fourteen = TASK * int(read_bit(game, 0xD7E0, 7)) - fifteen = TASK * int(read_bit(game, 0xD85F, 1)) - sixteen = TASK * int(read_bit(game, 0xD769, 7)) - - return sum([one, two, three, five, six, seven, eight, nine, ten, eleven, twelve, thirteen, fourteen, fifteen, sixteen]) - -def snorlax(game): - route12_snorlax_fight = POKEMON * int(read_bit(game, 0xD7D8, 6)) - route12_snorlax_beat = POKEMON * int(read_bit(game, 0xD7D8, 7)) - route16_snorlax_fight = POKEMON * int(read_bit(game, 0xD7E0, 0)) - route16_snorlax_beat = POKEMON * int(read_bit(game, 0xD7E0, 1)) - - return sum([route12_snorlax_fight, route12_snorlax_beat, route16_snorlax_fight, route16_snorlax_beat]) - -def hmtm(game): - hm01 = HM * int(read_bit(game, 0xD803, 0)) - hm02 = HM * int(read_bit(game, 0xD7E0, 6)) - hm03 = HM * int(read_bit(game, 0xD857, 0)) - hm04 = HM * int(read_bit(game, 0xD78E, 0)) - hm05 = HM * int(read_bit(game, 0xD7C2, 0)) - - tm34 = TM * int(read_bit(game, 0xD755, 6)) - tm11 = TM * int(read_bit(game, 0xD75E, 6)) - tm41 = TM * int(read_bit(game, 0xD777, 0)) - tm13 = TM * int(read_bit(game, 0xD778, 4)) - tm48 = TM * int(read_bit(game, 0xD778, 5)) - tm49 = TM * int(read_bit(game, 0xD778, 6)) - tm18 = TM * int(read_bit(game, 0xD778, 7)) - tm21 = TM * int(read_bit(game, 0xD77C, 0)) - tm06 = TM * int(read_bit(game, 0xD792, 0)) - tm24 = TM * int(read_bit(game, 0xD773, 6)) - tm29 = TM * int(read_bit(game, 0xD7BD, 0)) - tm31 = TM * int(read_bit(game, 0xD7AF, 0)) - tm35 = TM * int(read_bit(game, 0xD7A1, 7)) - tm36 = TM * int(read_bit(game, 0xD826, 7)) - tm38 = TM * int(read_bit(game, 0xD79A, 0)) - tm27 = TM * int(read_bit(game, 0xD751, 0)) - tm42 = TM * int(read_bit(game, 0xD74C, 1)) - tm46 = TM * int(read_bit(game, 0xD7B3, 0)) - tm39 = TM * int(read_bit(game, 0xD7D7, 0)) - - - return sum([hm01, hm02, hm03, hm04, hm05, tm34, tm11, tm41, tm13, tm48, tm49, tm18, tm21, tm06, tm24, tm29, tm31, tm35, tm36, tm38, tm27, tm42, tm46, tm39]) - -def bill(game): - met_bill = BILL_CAPT * int(read_bit(game, 0xD7F1, 0)) - used_cell_separator_on_bill = BILL_CAPT * int(read_bit(game, 0xD7F2, 3)) - got_ss_ticket = BILL_CAPT * int(read_bit(game, 0xD7F2, 4)) - met_bill_2 = BILL_CAPT * int(read_bit(game, 0xD7F2, 5)) - bill_said_use_cell_separator = BILL_CAPT * int(read_bit(game, 0xD7F2, 6)) - left_bills_house_after_helping = BILL_CAPT * int(read_bit(game, 0xD7F2, 7)) - - - return sum([met_bill, used_cell_separator_on_bill, got_ss_ticket, met_bill_2, bill_said_use_cell_separator, left_bills_house_after_helping]) - -def oak(game): - oak_appeared_in_pallet = TASK * int(read_bit(game, 0xD74B, 7)) - followed_oak_into_lab = TASK * int(read_bit(game, 0xD747, 0)) - oak_asked_to_choose_mon = TASK * int(read_bit(game, 0xD74B, 1)) - got_starter = TASK * int(read_bit(game, 0xD74B, 2)) - followed_oak_into_lab_2 = TASK * int(read_bit(game, 0xD74B, 0)) - got_pokedex = QUEST * int(read_bit(game, 0xD74B, 5)) - got_oaks_parcel = QUEST * int(read_bit(game, 0xD74E, 1)) - pallet_after_getting_pokeballs = QUEST * int(read_bit(game, 0xD747, 6)) - oak_got_parcel = QUEST * int(read_bit(game, 0xD74E, 0)) - got_pokeballs_from_oak = TASK * int(read_bit(game, 0xD74B, 4)) - pallet_after_getting_pokeballs_2 = TASK * int(read_bit(game, 0xD74B, 6)) - - return sum([oak_appeared_in_pallet, followed_oak_into_lab, oak_asked_to_choose_mon, got_starter, followed_oak_into_lab_2, got_pokedex, - got_oaks_parcel, pallet_after_getting_pokeballs, oak_got_parcel, got_pokeballs_from_oak, pallet_after_getting_pokeballs_2]) - -def towns(game): - got_town_map = TASK * int(read_bit(game, 0xD74A, 0)) - entered_blues_house = TASK * int(read_bit(game, 0xD74A, 1)) - beat_viridian_forest_trainer_0 = TRAINER * int(read_bit(game, 0xD7F3, 2)) - beat_viridian_forest_trainer_1 = TRAINER * int(read_bit(game, 0xD7F3, 3)) - beat_viridian_forest_trainer_2 = TRAINER * int(read_bit(game, 0xD7F3, 4)) - got_nugget = TASK * int(read_bit(game, 0xD7EF, 0)) - nugget_reward_available = TASK * int(read_bit(game, 0xD7F0, 1)) - beat_cerulean_rocket_thief = TRAINER * int(read_bit(game, 0xD75B, 7)) - got_bicycle = QUEST * int(read_bit(game, 0xD75F, 0)) - seel_fan_boast = TASK * int(read_bit(game, 0xD771, 6)) - pikachu_fan_boast = TASK * int(read_bit(game, 0xD771, 7)) - got_poke_flute = QUEST * int(read_bit(game, 0xD76C, 0)) - - return sum([got_town_map, entered_blues_house, beat_viridian_forest_trainer_0, - beat_viridian_forest_trainer_1, beat_viridian_forest_trainer_2, got_nugget, - nugget_reward_available, beat_cerulean_rocket_thief, got_bicycle, - seel_fan_boast, pikachu_fan_boast, got_poke_flute]) - -def lab(game): - gave_fossil_to_lab = TASK * int(read_bit(game, 0xD7A3, 0)) - lab_still_reviving_fossil = TASK * int(read_bit(game, 0xD7A3, 1)) - lab_handing_over_fossil_mon = TASK * int(read_bit(game, 0xD7A3, 2)) - - return sum([gave_fossil_to_lab, lab_still_reviving_fossil, lab_handing_over_fossil_mon]) - -def mansion(game): - beat_mansion_2_trainer_0 = TRAINER * int(read_bit(game, 0xD847, 1)) - beat_mansion_3_trainer_0 = TRAINER * int(read_bit(game, 0xD849, 1)) - beat_mansion_3_trainer_1 = TRAINER * int(read_bit(game, 0xD849, 2)) - beat_mansion_4_trainer_0 = TRAINER * int(read_bit(game, 0xD84B, 1)) - beat_mansion_4_trainer_1 = TRAINER * int(read_bit(game, 0xD84B, 2)) - mansion_switch_on = QUEST * int(read_bit(game, 0xD796, 0)) - beat_mansion_1_trainer_0 = TRAINER * int(read_bit(game, 0xD798, 1)) - - - return sum([beat_mansion_2_trainer_0, beat_mansion_3_trainer_0, beat_mansion_3_trainer_1, - beat_mansion_4_trainer_0, beat_mansion_4_trainer_1, mansion_switch_on, beat_mansion_1_trainer_0]) - -def safari(game): - gave_gold_teeth = QUEST * int(read_bit(game, 0xD78E, 1)) - safari_game_over = EVENT * int(read_bit(game, 0xD790, 6)) - in_safari_zone = EVENT * int(read_bit(game, 0xD790, 7)) - - return sum([gave_gold_teeth, safari_game_over, in_safari_zone]) - -def dojo(game): - defeated_fighting_dojo = BAD * int(read_bit(game, 0xD7B1, 0)) - beat_karate_master = GYM_LEADER * int(read_bit(game, 0xD7B1, 1)) - beat_dojo_trainer_0 = TRAINER * int(read_bit(game, 0xD7B1, 2)) - beat_dojo_trainer_1 = TRAINER * int(read_bit(game, 0xD7B1, 3)) - beat_dojo_trainer_2 = TRAINER * int(read_bit(game, 0xD7B1, 4)) - beat_dojo_trainer_3 = TRAINER * int(read_bit(game, 0xD7B1, 5)) - got_hitmonlee = POKEMON * int(read_bit(game, 0xD7B1, 6)) - got_hitmonchan = POKEMON * int(read_bit(game, 0xD7B1, 7)) - - return sum([defeated_fighting_dojo, beat_karate_master, beat_dojo_trainer_0, - beat_dojo_trainer_1, beat_dojo_trainer_2, beat_dojo_trainer_3, - got_hitmonlee, got_hitmonchan]) - -def hideout(game): - beat_rocket_hideout_1_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD815, 1)) - beat_rocket_hideout_1_trainer_1 = GYM_TRAINER * int(read_bit(game, 0xD815, 2)) - beat_rocket_hideout_1_trainer_2 = GYM_TRAINER * int(read_bit(game, 0xD815, 3)) - beat_rocket_hideout_1_trainer_3 = GYM_TRAINER * int(read_bit(game, 0xD815, 4)) - beat_rocket_hideout_1_trainer_4 = GYM_TRAINER * int(read_bit(game, 0xD815, 5)) - beat_rocket_hideout_2_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD817, 1)) - beat_rocket_hideout_3_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD819, 1)) - beat_rocket_hideout_3_trainer_1 = GYM_TRAINER * int(read_bit(game, 0xD819, 2)) - beat_rocket_hideout_4_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD81B, 2)) - beat_rocket_hideout_4_trainer_1 = GYM_TRAINER * int(read_bit(game, 0xD81B, 3)) - beat_rocket_hideout_4_trainer_2 = GYM_TRAINER * int(read_bit(game, 0xD81B, 4)) - rocket_hideout_4_door_unlocked = QUEST * int(read_bit(game, 0xD81B, 5)) - rocket_dropped_lift_key = QUEST * int(read_bit(game, 0xD81B, 6)) - beat_rocket_hideout_giovanni = GYM_LEADER * int(read_bit(game, 0xD81B, 7)) - found_rocket_hideout = QUEST * int(read_bit(game, 0xD77E, 1)) - - return sum([beat_rocket_hideout_1_trainer_0, beat_rocket_hideout_1_trainer_1, beat_rocket_hideout_1_trainer_2, beat_rocket_hideout_1_trainer_3, - beat_rocket_hideout_1_trainer_4, beat_rocket_hideout_2_trainer_0, beat_rocket_hideout_3_trainer_0, beat_rocket_hideout_3_trainer_1, - beat_rocket_hideout_4_trainer_0, beat_rocket_hideout_4_trainer_1, beat_rocket_hideout_4_trainer_2, rocket_hideout_4_door_unlocked, - rocket_dropped_lift_key, beat_rocket_hideout_giovanni, found_rocket_hideout]) - -def poke_tower(game): - beat_pokemontower_3_trainer_0 = TRAINER * int(read_bit(game, 0xD765, 1)) - beat_pokemontower_3_trainer_1 = TRAINER * int(read_bit(game, 0xD765, 2)) - beat_pokemontower_3_trainer_2 = TRAINER * int(read_bit(game, 0xD765, 3)) - beat_pokemontower_4_trainer_0 = TRAINER * int(read_bit(game, 0xD766, 1)) - beat_pokemontower_4_trainer_1 = TRAINER * int(read_bit(game, 0xD766, 2)) - beat_pokemontower_4_trainer_2 = TRAINER * int(read_bit(game, 0xD766, 3)) - beat_pokemontower_5_trainer_0 = TRAINER * int(read_bit(game, 0xD767, 2)) - beat_pokemontower_5_trainer_1 = TRAINER * int(read_bit(game, 0xD767, 3)) - beat_pokemontower_5_trainer_2 = TRAINER * int(read_bit(game, 0xD767, 4)) - beat_pokemontower_5_trainer_3 = TRAINER * int(read_bit(game, 0xD767, 5)) -# in_purified_zone = EVENT * int(read_bit(game, 0xD767, 7)) # purified zone - beat_pokemontower_6_trainer_0 = TRAINER * int(read_bit(game, 0xD768, 1)) - beat_pokemontower_6_trainer_1 = TRAINER * int(read_bit(game, 0xD768, 2)) - beat_pokemontower_6_trainer_2 = TRAINER * int(read_bit(game, 0xD768, 3)) - beat_ghost_marowak = QUEST * int(read_bit(game, 0xD768, 7)) - beat_pokemontower_7_trainer_0 = TRAINER * int(read_bit(game, 0xD769, 1)) - beat_pokemontower_7_trainer_1 = TRAINER * int(read_bit(game, 0xD769, 2)) - beat_pokemontower_7_trainer_2 = TRAINER * int(read_bit(game, 0xD769, 3)) - - return sum([beat_pokemontower_3_trainer_0, beat_pokemontower_3_trainer_1, beat_pokemontower_3_trainer_2, beat_pokemontower_4_trainer_0, - beat_pokemontower_4_trainer_1, beat_pokemontower_4_trainer_2, beat_pokemontower_5_trainer_0, beat_pokemontower_5_trainer_1, - beat_pokemontower_5_trainer_2, beat_pokemontower_5_trainer_3, beat_pokemontower_6_trainer_0, - beat_pokemontower_6_trainer_1, beat_pokemontower_6_trainer_2, beat_ghost_marowak, beat_pokemontower_7_trainer_0, - beat_pokemontower_7_trainer_1, beat_pokemontower_7_trainer_2]) # in_purified_zone, - -def gym1(game): - #gym 1 Pewter - one = GYM_LEADER * int(read_bit(game, 0xD755, 7)) - g1_1 = GYM_TRAINER * int(read_bit(game, 0xD755, 2)) # "0xD755-2": "Beat Pewter Gym Trainer 0", - return sum([one, g1_1, ]) - -def gym2(game): - #gym 2 Cerulean - two = GYM_LEADER * int(read_bit(game, 0xD75E, 7)) - g2_1 = GYM_TRAINER * int(read_bit(game, 0xD75E, 2)) # "0xD75E-2": "Beat Cerulean Gym Trainer 0", - g2_2 = GYM_TRAINER * int(read_bit(game, 0xD75E, 3)) # "0xD75E-3": "Beat Cerulean Gym Trainer 1", - return sum([two, g2_1, g2_2, ]) - -def gym3(game): - #gym 3 Vermilion - lock_one = GYM_TASK * int(read_bit(game, 0xD773, 1)) # "0xD773-1": "1S Lock Opened", - lock_two = GYM_TASK * int(read_bit(game, 0xD773, 0))# "0xD773-0": "2Nd Lock Opened", - three = GYM_LEADER * int(read_bit(game, 0xD773, 7)) - g3_1 = GYM_TRAINER * int(read_bit(game, 0xD773, 2)) # "0xD773-2": "Beat Vermilion Gym Trainer 0", - g3_2 = GYM_TRAINER * int(read_bit(game, 0xD773, 3)) # "0xD773-3": "Beat Vermilion Gym Trainer 1", - g3_3 = GYM_TRAINER * int(read_bit(game, 0xD773, 4)) # "0xD773-4": "Beat Vermilion Gym Trainer 2", - return sum([three, g3_1, g3_2, g3_3, lock_one, lock_two]) - -def gym4(game): - #gym 4 Celadon - four = GYM_LEADER * int(read_bit(game, 0xD792, 1)) - g4_1 = GYM_TRAINER * int(read_bit(game, 0xD77C, 2)) # "0xD77C-2": "Beat Celadon Gym Trainer 0", - g4_2 = GYM_TRAINER * int(read_bit(game, 0xD77C, 3)) # "0xD77C-3": "Beat Celadon Gym Trainer 1", - g4_3 = GYM_TRAINER * int(read_bit(game, 0xD77C, 4)) # "0xD77C-4": "Beat Celadon Gym Trainer 2", - g4_4 = GYM_TRAINER * int(read_bit(game, 0xD77C, 5)) # "0xD77C-5": "Beat Celadon Gym Trainer 3", - g4_5 = GYM_TRAINER * int(read_bit(game, 0xD77C, 6)) # "0xD77C-6": "Beat Celadon Gym Trainer 4", - g4_6 = GYM_TRAINER * int(read_bit(game, 0xD77C, 7)) # "0xD77C-7": "Beat Celadon Gym Trainer 5", - g4_7 = GYM_TRAINER * int(read_bit(game, 0xD77D, 0)) # "0xD77D-0": "Beat Celadon Gym Trainer 6", - return sum([four, g4_1, g4_2, g4_3, g4_4, g4_5, g4_6, g4_7, ]) - -def gym5(game): - #gym 5 Fuchsia - five = GYM_LEADER * int(read_bit(game, 0xD7B3, 1)) - g5_1 = GYM_TRAINER * int(read_bit(game, 0xD792, 2)) # "0xD792-2": "Beat Fuchsia Gym Trainer 0", - g5_2 = GYM_TRAINER * int(read_bit(game, 0xD792, 3)) # "0xD792-3": "Beat Fuchsia Gym Trainer 1", - g5_3 = GYM_TRAINER * int(read_bit(game, 0xD792, 4)) # "0xD792-4": "Beat Fuchsia Gym Trainer 2", - g5_4 = GYM_TRAINER * int(read_bit(game, 0xD792, 5)) # "0xD792-5": "Beat Fuchsia Gym Trainer 3", - g5_5 = GYM_TRAINER * int(read_bit(game, 0xD792, 6)) # "0xD792-6": "Beat Fuchsia Gym Trainer 4", - g5_6 = GYM_TRAINER * int(read_bit(game, 0xD792, 7)) # "0xD792-7": "Beat Fuchsia Gym Trainer 5", - return sum([five, g5_1, g5_2, g5_3, g5_4, g5_5, g5_6, ]) - -def gym6(game): - #gym 6 Saffron - six = GYM_LEADER * int(read_bit(game, 0xD7B3, 1)) - g6_1 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 2)) # "0xD7B3-2": "Beat Saffron Gym Trainer 0", - g6_2 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 3)) # "0xD7B3-3": "Beat Saffron Gym Trainer 1", - g6_3 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 4)) # "0xD7B3-4": "Beat Saffron Gym Trainer 2", - g6_4 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 5)) # "0xD7B3-5": "Beat Saffron Gym Trainer 3", - g6_5 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 6)) # "0xD7B3-6": "Beat Saffron Gym Trainer 4", - g6_6 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 7)) # "0xD7B3-7": "Beat Saffron Gym Trainer 5", - g6_7 = GYM_TRAINER * int(read_bit(game, 0xD7B4, 0)) # "0xD7B4-0": "Beat Saffron Gym Trainer 6", - return sum([six, g6_1, g6_2, g6_3, g6_4, g6_5, g6_6, g6_7, ]) - -def gym7(game): - #gym 7 Cinnabar - # "0xD79C-0": "Cinnabar Gym Gate0 Unlocked", - # "0xD79C-1": "Cinnabar Gym Gate1 Unlocked", - # "0xD79C-2": "Cinnabar Gym Gate2 Unlocked", - # "0xD79C-3": "Cinnabar Gym Gate3 Unlocked", - # "0xD79C-4": "Cinnabar Gym Gate4 Unlocked", - # "0xD79C-5": "Cinnabar Gym Gate5 Unlocked", - # "0xD79C-6": "Cinnabar Gym Gate6 Unlocked", - seven = GYM_LEADER * int(read_bit(game, 0xD79A, 1)) - g7_1 = GYM_TRAINER * int(read_bit(game, 0xD79A, 2)) # "0xD79A-2": "Beat Cinnabar Gym Trainer 0", - g7_2 = GYM_TRAINER * int(read_bit(game, 0xD79A, 3)) # "0xD79A-3": "Beat Cinnabar Gym Trainer 1", - g7_3 = GYM_TRAINER * int(read_bit(game, 0xD79A, 4)) # "0xD79A-4": "Beat Cinnabar Gym Trainer 2", - g7_4 = GYM_TRAINER * int(read_bit(game, 0xD79A, 5)) # "0xD79A-5": "Beat Cinnabar Gym Trainer 3", - g7_5 = GYM_TRAINER * int(read_bit(game, 0xD79A, 6)) # "0xD79A-6": "Beat Cinnabar Gym Trainer 4", - g7_6 = GYM_TRAINER * int(read_bit(game, 0xD79A, 7)) # "0xD79A-7": "Beat Cinnabar Gym Trainer 5", - g7_7 = GYM_TRAINER * int(read_bit(game, 0xD79B, 0)) # "0xD79B-0": "Beat Cinnabar Gym Trainer 6", - - return sum([seven, g7_1, g7_2, g7_3, g7_4, g7_5, g7_6, g7_7, ]) - -def gym8(game): - #gym 8 Viridian - # "0xD74C-0": "Viridian Gym Open", - gym_door = GYM_TASK * int(read_bit(game, 0xD74C, 0)) - eight = GYM_LEADER * int(read_bit(game, 0xD751, 1)) - g8_1 = GYM_TRAINER * int(read_bit(game, 0xD751, 2)) # "0xD751-2": "Beat Viridian Gym Trainer 0", - g8_2 = GYM_TRAINER * int(read_bit(game, 0xD751, 3)) # "0xD751-3": "Beat Viridian Gym Trainer 1", - g8_3 = GYM_TRAINER * int(read_bit(game, 0xD751, 4)) # "0xD751-4": "Beat Viridian Gym Trainer 2", - g8_4 = GYM_TRAINER * int(read_bit(game, 0xD751, 5)) # "0xD751-5": "Beat Viridian Gym Trainer 3", - g8_5 = GYM_TRAINER * int(read_bit(game, 0xD751, 6)) # "0xD751-6": "Beat Viridian Gym Trainer 4", - g8_6 = GYM_TRAINER * int(read_bit(game, 0xD751, 7)) # "0xD751-7": "Beat Viridian Gym Trainer 5", - g8_7 = GYM_TRAINER * int(read_bit(game, 0xD752, 0)) # "0xD752-0": "Beat Viridian Gym Trainer 6", - g8_8 = GYM_TRAINER * int(read_bit(game, 0xD752, 1)) # "0xD752-1": "Beat Viridian Gym Trainer 7", - return sum([eight, g8_1, g8_2, g8_3, g8_4, g8_5, g8_6, g8_7, g8_8, gym_door]) - -def rival(game): - one = RIVAL * int(read_bit(game, 0xD74B, 3)) - two = RIVAL * int(read_bit(game, 0xD7EB, 0)) - three = RIVAL * int(read_bit(game, 0xD7EB, 1)) - four = RIVAL * int(read_bit(game, 0xD7EB, 5)) - five = RIVAL * int(read_bit(game, 0xD7EB, 6)) - six = RIVAL * int(read_bit(game, 0xD75A, 0)) - seven = RIVAL * int(read_bit(game, 0xD764, 6)) - eight = RIVAL * int(read_bit(game, 0xD764, 7)) - nine = RIVAL * int(read_bit(game, 0xD7EB, 7)) - Beat_Silph_Co_Rival = RIVAL * int(read_bit(game, 0xD82F, 0)) - - return sum([one, two, three, four, five, six, seven, eight, nine, Beat_Silph_Co_Rival]) - -###################################################################################################### - -def bcd(num): - return 10 * ((num >> 4) & 0x0F) + (num & 0x0F) - -def bit_count(bits): - return bin(bits).count("1") - -def read_bit(game, addr, bit) -> bool: - # add padding so zero will read '0b100000000' instead of '0b0' - return bin(256 + game.get_memory_value(addr))[-bit - 1] == "1" - -def mem_val(game, addr): - mem = game.get_memory_value(addr) - return mem - -def read_uint16(game, start_addr): - """Read 2 bytes""" - val_256 = game.get_memory_value(start_addr) - val_1 = game.get_memory_value(start_addr + 1) - return 256 * val_256 + val_1 - -###################################################################################################### - -def get_hm_count(game): - hm_ids = [0xC4, 0xC5, 0xC6, 0xC7, 0xC8] - items = get_items_in_bag(game) - total_hm_cnt = 0 - for hm_id in hm_ids: - if hm_id in items: - total_hm_cnt += 1 - return total_hm_cnt * 1 - -def get_items_in_bag(game, one_indexed=0): - first_item = 0xD31E - item_ids = [] - for i in range(0, 40, 2): - item_id = game.get_memory_value(first_item + i) - if item_id == 0 or item_id == 0xff: - break - item_ids.append(item_id + one_indexed) - return item_ids - -def position(game): - r_pos = game.get_memory_value(Y_POS_ADDR) - c_pos = game.get_memory_value(X_POS_ADDR) - map_n = game.get_memory_value(MAP_N_ADDR) - if r_pos >= 443: - r_pos = 444 - if r_pos <= 0: - r_pos = 0 - if c_pos >= 443: - c_pos = 444 - if c_pos <= 0: - c_pos = 0 - if map_n > 247: - map_n = 247 - if map_n < -1: - map_n = -1 - return r_pos, c_pos, map_n - -def party(game): - # party = [game.get_memory_value(addr) for addr in PARTY_ADDR] - party_size = game.get_memory_value(PARTY_SIZE_ADDR) - party_levels = [x for x in [game.get_memory_value(addr) for addr in PARTY_LEVEL_ADDR] if x > 0] - return party_size, party_levels # [x for x in party_levels if x > 0] - -def hp(game): - """Percentage of total party HP""" - party_hp = [read_uint16(game, addr) for addr in HP_ADDR] - party_max_hp = [read_uint16(game, addr) for addr in MAX_HP_ADDR] - # Avoid division by zero if no pokemon - sum_max_hp = sum(party_max_hp) - if sum_max_hp == 0: - return 1 - return sum(party_hp) / sum_max_hp - -def used_cut(game): - return game.get_memory_value(WCUTTILE) - -def write_mem(game, addr, value): - mem = game.set_memory_value(addr, value) - return mem - -def badges(game): - badges = game.get_memory_value(BADGE_1_ADDR) - return bit_count(badges) - - - - - - - - - - - - diff --git a/pokegym/pyboy_binding.py b/pokegym/pyboy_binding.py index d953509..2c910bd 100644 --- a/pokegym/pyboy_binding.py +++ b/pokegym/pyboy_binding.py @@ -3,14 +3,10 @@ import numpy as np from pyboy import PyBoy -from pyboy import logger from pyboy.utils import WindowEvent from pokegym import ram_map -logger.logger.setLevel('ERROR') - - class Down: PRESS = WindowEvent.PRESS_ARROW_DOWN RELEASE = WindowEvent.RELEASE_ARROW_DOWN diff --git a/pokegym/ram_map.py b/pokegym/ram_map.py index 9700a51..43eeb36 100644 --- a/pokegym/ram_map.py +++ b/pokegym/ram_map.py @@ -5,59 +5,662 @@ # Data Crystal - https://datacrystal.romhacking.net/wiki/Pok%C3%A9mon_Red/Blue:RAM_map # No Comments - https://github.com/pret/pokered/blob/91dc3c9f9c8fd529bb6e8307b58b96efa0bec67e/constants/event_constants.asm # Comments - https://github.com/luckytyphlosion/pokered/blob/master/constants/event_constants.asm +from collections import deque +import numpy as np from pokegym import data - +CUT_GRASS_SEQ = deque([(0x52, 255, 1, 0, 1, 1), (0x52, 255, 1, 0, 1, 1), (0x52, 1, 1, 0, 1, 1)]) +CUT_FAIL_SEQ = deque([(-1, 255, 0, 0, 4, 1), (-1, 255, 0, 0, 1, 1), (-1, 255, 0, 0, 1, 1)]) +CUT_SEQ = [((0x3D, 1, 1, 0, 4, 1), (0x3D, 1, 1, 0, 1, 1)), ((0x50, 1, 1, 0, 4, 1), (0x50, 1, 1, 0, 1, 1)),] HP_ADDR = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] MAX_HP_ADDR = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] PARTY_SIZE_ADDR = 0xD163 PARTY_ADDR = [0xD164, 0xD165, 0xD166, 0xD167, 0xD168, 0xD169] PARTY_LEVEL_ADDR = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] -POKE_XP_ADDR = [0xD179, 0xD1A5, 0xD1D1, 0xD1FD, 0xD229, 0xD255] -CAUGHT_POKE_ADDR = range(0xD2F7, 0xD309) -SEEN_POKE_ADDR = range(0xD30A, 0xD31D) -OPPONENT_LEVEL_ADDR = [0xD8C5, 0xD8F1, 0xD91D, 0xD949, 0xD975, 0xD9A1] X_POS_ADDR = 0xD362 Y_POS_ADDR = 0xD361 MAP_N_ADDR = 0xD35E BADGE_1_ADDR = 0xD356 -OAK_PARCEL_ADDR = 0xD74E -OAK_POKEDEX_ADDR = 0xD74B -OPPONENT_LEVEL = 0xCFF3 -ENEMY_POKE_COUNT = 0xD89C -EVENT_FLAGS_START_ADDR = 0xD747 -EVENT_FLAGS_END_ADDR = 0xD886 # 0xD761 -MUSEUM_TICKET_ADDR = 0xD754 -USED_CELL_SEPARATOR_ADDR = 0xD7F2 -MONEY_ADDR_1 = 0xD347 -MONEY_ADDR_100 = 0xD348 -MONEY_ADDR_10000 = 0xD349 -# MAP_TEXT_POINTER_TABLE_NPC = 0xD36C - 0xD36D -TEXT_BOX_ARROW_BLINK = 0xC4F2 -BATTLE_FLAG = 0xD057 -SS_ANNE = 0xD803 -IF_FONT_IS_LOADED = 0xCFC4 # text box is up -# get information for player -PLAYER_DIRECTION = 0xC109 -PLAYER_Y = 0xC104 -PLAYER_X = 0xC106 -WNUMSPRITES = 0xD4E1 -WNUMSIGNS = 0xD4B0 WCUTTILE = 0xCD4D # 61 if Cut used; 0 default. resets to default on map_n change or battle. -# Moves 1-4 for Poke1, Poke2, Poke3, Poke4, Poke5, Poke6 -MOVE1 = [0xD173, 0xD19F, 0xD1CB, 0xD1F7, 0xD223, 0xD24F] -MOVE2 = [0xD174, 0xD1A0, 0xD1CC, 0xD1F8, 0xD224, 0xD250] -MOVE3 = [0xD175, 0xD1A1, 0xD1CD, 0xD1F9, 0xD225, 0xD251] -MOVE4 = [0xD176, 0xD1A2, 0xD1CE, 0xD1FA, 0xD226, 0xD252] -POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] # - Pokémon (Again) -STATUS = [0xD16F, 0xD19B, 0xD1C7, 0xD1F3, 0xD21F, 0xD24B] # - Status (Poisoned, Paralyzed, etc.) -TYPE1 = [0xD170, 0xD19C, 0xD1C8, 0xD1F4, 0xD220, 0xD24C] # - Type 1 -TYPE2 = [0xD171, 0xD19D, 0xD1C9, 0xD1F5, 0xD221, 0xD24D] # - Type 2 -LEVEL = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] # - Level (actual level) -MAXHP = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] # - Max HP if = 01 + 256 to MAXHP2 value -CHP = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] # - Current HP if = 01 + 256 +GYM_LEADER = 5 +GYM_TRAINER = 2 +GYM_TASK = 2 +TRAINER = 1 +HM = 5 +TM = 2 +TASK = 2 +POKEMON = 10 +ITEM = 5 +BILL_CAPT = 5 +RIVAL = 3 +QUEST = 5 +EVENT = 1 +BAD = -1 + +# EVENT ##################################################################################################### + +def silph_co(game): + Beat_Silph_Co_2F_Trainer_0 = TRAINER * int(read_bit(game, 0xD825, 2)) + Beat_Silph_Co_2F_Trainer_1 = TRAINER * int(read_bit(game, 0xD825, 3)) + Beat_Silph_Co_2F_Trainer_2 = TRAINER * int(read_bit(game, 0xD825, 4)) + Beat_Silph_Co_2F_Trainer_3 = TRAINER * int(read_bit(game, 0xD825, 5)) + Silph_Co_2_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD826, 5)) + Silph_Co_2_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD826, 6)) + Beat_Silph_Co_3F_Trainer_0 = TRAINER * int(read_bit(game, 0xD827, 2)) + Beat_Silph_Co_3F_Trainer_1 = TRAINER * int(read_bit(game, 0xD827, 3)) + Silph_Co_3_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD828, 0)) + Silph_Co_3_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD828, 1)) + Beat_Silph_Co_4F_Trainer_0 = TRAINER * int(read_bit(game, 0xD829, 2)) + Beat_Silph_Co_4F_Trainer_1 = TRAINER * int(read_bit(game, 0xD829, 3)) + Beat_Silph_Co_4F_Trainer_2 = TRAINER * int(read_bit(game, 0xD829, 4)) + Silph_Co_4_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD82A, 0)) + Silph_Co_4_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD82A, 1)) + Beat_Silph_Co_5F_Trainer_0 = TRAINER * int(read_bit(game, 0xD82B, 2)) + Beat_Silph_Co_5F_Trainer_1 = TRAINER * int(read_bit(game, 0xD82B, 3)) + Beat_Silph_Co_5F_Trainer_2 = TRAINER * int(read_bit(game, 0xD82B, 4)) + Beat_Silph_Co_5F_Trainer_3 = TRAINER * int(read_bit(game, 0xD82B, 5)) + Silph_Co_5_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD82C, 0)) + Silph_Co_5_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD82C, 1)) + Silph_Co_5_Unlocked_Door3 = QUEST * int(read_bit(game, 0xD82C, 2)) + Beat_Silph_Co_6F_Trainer_0 = TRAINER * int(read_bit(game, 0xD82D, 6)) + Beat_Silph_Co_6F_Trainer_1 = TRAINER * int(read_bit(game, 0xD82D, 7)) + Beat_Silph_Co_6F_Trainer_2 = TRAINER * int(read_bit(game, 0xD82E, 0)) + Silph_Co_6_Unlocked_Door = QUEST * int(read_bit(game, 0xD82E, 7)) + Beat_Silph_Co_7F_Trainer_0 = TRAINER * int(read_bit(game, 0xD82F, 5)) + Beat_Silph_Co_7F_Trainer_1 = TRAINER * int(read_bit(game, 0xD82F, 6)) + Beat_Silph_Co_7F_Trainer_2 = TRAINER * int(read_bit(game, 0xD82F, 7)) + Beat_Silph_Co_7F_Trainer_3 = TRAINER * int(read_bit(game, 0xD830, 0)) + Silph_Co_7_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD830, 4)) + Silph_Co_7_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD830, 5)) + Silph_Co_7_Unlocked_Door3 = QUEST * int(read_bit(game, 0xD830, 6)) + Beat_Silph_Co_8F_Trainer_0 = TRAINER * int(read_bit(game, 0xD831, 2)) + Beat_Silph_Co_8F_Trainer_1 = TRAINER * int(read_bit(game, 0xD831, 3)) + Beat_Silph_Co_8F_Trainer_2 = TRAINER * int(read_bit(game, 0xD831, 4)) + Silph_Co_8_Unlocked_Door = QUEST * int(read_bit(game, 0xD832, 0)) + Beat_Silph_Co_9F_Trainer_0 = TRAINER * int(read_bit(game, 0xD833, 2)) + Beat_Silph_Co_9F_Trainer_1 = TRAINER * int(read_bit(game, 0xD833, 3)) + Beat_Silph_Co_9F_Trainer_2 = TRAINER * int(read_bit(game, 0xD833, 4)) + Silph_Co_9_Unlocked_Door1 = QUEST * int(read_bit(game, 0xD834, 0)) + Silph_Co_9_Unlocked_Door2 = QUEST * int(read_bit(game, 0xD834, 1)) + Silph_Co_9_Unlocked_Door3 = QUEST * int(read_bit(game, 0xD834, 2)) + Silph_Co_9_Unlocked_Door4 = QUEST * int(read_bit(game, 0xD834, 3)) + Beat_Silph_Co_10F_Trainer_0 = TRAINER * int(read_bit(game, 0xD835, 1)) + Beat_Silph_Co_10F_Trainer_1 = TRAINER * int(read_bit(game, 0xD835, 2)) + Silph_Co_10_Unlocked_Door = QUEST * int(read_bit(game, 0xD836, 0)) + Beat_Silph_Co_11F_Trainer_0 = TRAINER * int(read_bit(game, 0xD837, 4)) + Beat_Silph_Co_11F_Trainer_1 = TRAINER * int(read_bit(game, 0xD837, 5)) + Silph_Co_11_Unlocked_Door = QUEST * int(read_bit(game, 0xD838, 0)) + Got_Master_Ball = ITEM * int(read_bit(game, 0xD838, 5)) + Beat_Silph_Co_Giovanni = GYM_LEADER * int(read_bit(game, 0xD838, 7)) + Silph_Co_Receptionist_At_Desk = TASK * int(read_bit(game, 0xD7B9, 7)) + return sum([Beat_Silph_Co_2F_Trainer_0, Beat_Silph_Co_2F_Trainer_1, Beat_Silph_Co_2F_Trainer_2, Beat_Silph_Co_2F_Trainer_3, Silph_Co_2_Unlocked_Door1, + Silph_Co_2_Unlocked_Door2, Beat_Silph_Co_3F_Trainer_0, Beat_Silph_Co_3F_Trainer_1, Silph_Co_3_Unlocked_Door1, Silph_Co_3_Unlocked_Door2, + Beat_Silph_Co_4F_Trainer_0, Beat_Silph_Co_4F_Trainer_1, Beat_Silph_Co_4F_Trainer_2, Silph_Co_4_Unlocked_Door1, Silph_Co_4_Unlocked_Door2, + Beat_Silph_Co_5F_Trainer_0, Beat_Silph_Co_5F_Trainer_1, Beat_Silph_Co_5F_Trainer_2, Beat_Silph_Co_5F_Trainer_3, Silph_Co_5_Unlocked_Door1, + Silph_Co_5_Unlocked_Door2, Silph_Co_5_Unlocked_Door3, Beat_Silph_Co_6F_Trainer_0, Beat_Silph_Co_6F_Trainer_1, Beat_Silph_Co_6F_Trainer_2, + Silph_Co_6_Unlocked_Door, Beat_Silph_Co_7F_Trainer_0, Beat_Silph_Co_7F_Trainer_1, Beat_Silph_Co_7F_Trainer_2, Beat_Silph_Co_7F_Trainer_3, + Silph_Co_7_Unlocked_Door1, Silph_Co_7_Unlocked_Door2, Silph_Co_7_Unlocked_Door3, Beat_Silph_Co_8F_Trainer_0, Beat_Silph_Co_8F_Trainer_1, + Beat_Silph_Co_8F_Trainer_2, Silph_Co_8_Unlocked_Door, Beat_Silph_Co_9F_Trainer_0, Beat_Silph_Co_9F_Trainer_1, Beat_Silph_Co_9F_Trainer_2, + Silph_Co_9_Unlocked_Door1, Silph_Co_9_Unlocked_Door2, Silph_Co_9_Unlocked_Door3, Silph_Co_9_Unlocked_Door4, Beat_Silph_Co_10F_Trainer_0, + Beat_Silph_Co_10F_Trainer_1, Silph_Co_10_Unlocked_Door, Beat_Silph_Co_11F_Trainer_0, Beat_Silph_Co_11F_Trainer_1, Silph_Co_11_Unlocked_Door, + Got_Master_Ball, Beat_Silph_Co_Giovanni, Silph_Co_Receptionist_At_Desk]) + +def rock_tunnel(game): + Beat_Rock_Tunnel_1_Trainer_0 = TRAINER * int(read_bit(game, 0xD7D2, 1)) + Beat_Rock_Tunnel_1_Trainer_1 = TRAINER * int(read_bit(game, 0xD7D2, 2)) + Beat_Rock_Tunnel_1_Trainer_2 = TRAINER * int(read_bit(game, 0xD7D2, 3)) + Beat_Rock_Tunnel_1_Trainer_3 = TRAINER * int(read_bit(game, 0xD7D2, 4)) + Beat_Rock_Tunnel_1_Trainer_4 = TRAINER * int(read_bit(game, 0xD7D2, 5)) + Beat_Rock_Tunnel_1_Trainer_5 = TRAINER * int(read_bit(game, 0xD7D2, 6)) + Beat_Rock_Tunnel_1_Trainer_6 = TRAINER * int(read_bit(game, 0xD7D2, 7)) + Beat_Rock_Tunnel_2_Trainer_0 = TRAINER * int(read_bit(game, 0xD87D, 1)) + Beat_Rock_Tunnel_2_Trainer_1 = TRAINER * int(read_bit(game, 0xD87D, 2)) + Beat_Rock_Tunnel_2_Trainer_2 = TRAINER * int(read_bit(game, 0xD87D, 3)) + Beat_Rock_Tunnel_2_Trainer_3 = TRAINER * int(read_bit(game, 0xD87D, 4)) + Beat_Rock_Tunnel_2_Trainer_4 = TRAINER * int(read_bit(game, 0xD87D, 5)) + Beat_Rock_Tunnel_2_Trainer_5 = TRAINER * int(read_bit(game, 0xD87D, 6)) + Beat_Rock_Tunnel_2_Trainer_6 = TRAINER * int(read_bit(game, 0xD87D, 7)) + Beat_Rock_Tunnel_2_Trainer_7 = TRAINER * int(read_bit(game, 0xD87E, 0)) + return sum([Beat_Rock_Tunnel_1_Trainer_0, Beat_Rock_Tunnel_1_Trainer_1, Beat_Rock_Tunnel_1_Trainer_2, Beat_Rock_Tunnel_1_Trainer_3, + Beat_Rock_Tunnel_1_Trainer_4, Beat_Rock_Tunnel_1_Trainer_5, Beat_Rock_Tunnel_1_Trainer_6, Beat_Rock_Tunnel_2_Trainer_0, + Beat_Rock_Tunnel_2_Trainer_1, Beat_Rock_Tunnel_2_Trainer_2, Beat_Rock_Tunnel_2_Trainer_3, Beat_Rock_Tunnel_2_Trainer_4, + Beat_Rock_Tunnel_2_Trainer_5, Beat_Rock_Tunnel_2_Trainer_6, Beat_Rock_Tunnel_2_Trainer_7]) + +def ssanne(game): + Beat_Ss_Anne_5_Trainer_0 = TRAINER * int(read_bit(game, 0xD7FF, 4)) + Beat_Ss_Anne_5_Trainer_1 = TRAINER * int(read_bit(game, 0xD7FF, 5)) + Rubbed_Captains_Back = BILL_CAPT * int(read_bit(game, 0xD803, 1)) + Ss_Anne_Left = BILL_CAPT * int(read_bit(game, 0xD803, 2)) + Walked_Past_Guard_After_Ss_Anne_Left = BILL_CAPT * int(read_bit(game, 0xD803, 3)) + Started_Walking_Out_Of_Dock = BILL_CAPT * int(read_bit(game, 0xD803, 4)) + Walked_Out_Of_Dock = BILL_CAPT * int(read_bit(game, 0xD803, 5)) + Beat_Ss_Anne_8_Trainer_0 = TRAINER * int(read_bit(game, 0xD805, 1)) + Beat_Ss_Anne_8_Trainer_1 = TRAINER * int(read_bit(game, 0xD805, 2)) + Beat_Ss_Anne_8_Trainer_2 = TRAINER * int(read_bit(game, 0xD805, 3)) + Beat_Ss_Anne_8_Trainer_3 = TRAINER * int(read_bit(game, 0xD805, 4)) + Beat_Ss_Anne_9_Trainer_0 = TRAINER * int(read_bit(game, 0xD807, 1)) + Beat_Ss_Anne_9_Trainer_1 = TRAINER * int(read_bit(game, 0xD807, 2)) + Beat_Ss_Anne_9_Trainer_2 = TRAINER * int(read_bit(game, 0xD807, 3)) + Beat_Ss_Anne_9_Trainer_3 = TRAINER * int(read_bit(game, 0xD807, 4)) + Beat_Ss_Anne_10_Trainer_0 = TRAINER * int(read_bit(game, 0xD809, 1)) + Beat_Ss_Anne_10_Trainer_1 = TRAINER * int(read_bit(game, 0xD809, 2)) + Beat_Ss_Anne_10_Trainer_2 = TRAINER * int(read_bit(game, 0xD809, 3)) + Beat_Ss_Anne_10_Trainer_3 = TRAINER * int(read_bit(game, 0xD809, 4)) + Beat_Ss_Anne_10_Trainer_4 = TRAINER * int(read_bit(game, 0xD809, 5)) + Beat_Ss_Anne_10_Trainer_5 = TRAINER * int(read_bit(game, 0xD809, 6)) + return sum([Beat_Ss_Anne_5_Trainer_0, Beat_Ss_Anne_5_Trainer_1, Rubbed_Captains_Back, Ss_Anne_Left, + Walked_Past_Guard_After_Ss_Anne_Left, Started_Walking_Out_Of_Dock, Walked_Out_Of_Dock, Beat_Ss_Anne_8_Trainer_0, + Beat_Ss_Anne_8_Trainer_1, Beat_Ss_Anne_8_Trainer_2, Beat_Ss_Anne_8_Trainer_3, Beat_Ss_Anne_9_Trainer_0, + Beat_Ss_Anne_9_Trainer_1, Beat_Ss_Anne_9_Trainer_2, Beat_Ss_Anne_9_Trainer_3, Beat_Ss_Anne_10_Trainer_0, + Beat_Ss_Anne_10_Trainer_1, Beat_Ss_Anne_10_Trainer_2, Beat_Ss_Anne_10_Trainer_3, Beat_Ss_Anne_10_Trainer_4, Beat_Ss_Anne_10_Trainer_5]) + +def mtmoon(game): + Beat_Mt_Moon_1_Trainer_1 = TRAINER * int(read_bit(game, 0xD7F5, 1)) + Beat_Mt_Moon_1_Trainer_2 = TRAINER * int(read_bit(game, 0xD7F5, 2)) + Beat_Mt_Moon_1_Trainer_3 = TRAINER * int(read_bit(game, 0xD7F5, 3)) + Beat_Mt_Moon_1_Trainer_4 = TRAINER * int(read_bit(game, 0xD7F5, 4)) + Beat_Mt_Moon_1_Trainer_5 = TRAINER * int(read_bit(game, 0xD7F5, 5)) + Beat_Mt_Moon_1_Trainer_6 = TRAINER * int(read_bit(game, 0xD7F5, 6)) + Beat_Mt_Moon_1_Trainer_7 = TRAINER * int(read_bit(game, 0xD7F5, 7)) + Beat_Mt_Moon_Super_Nerd = TRAINER * int(read_bit(game, 0xD7F6, 1)) + Beat_Mt_Moon_3_Trainer_0 = TRAINER * int(read_bit(game, 0xD7F6, 2)) + Beat_Mt_Moon_3_Trainer_1 = TRAINER * int(read_bit(game, 0xD7F6, 3)) + Beat_Mt_Moon_3_Trainer_2 = TRAINER * int(read_bit(game, 0xD7F6, 4)) + Beat_Mt_Moon_3_Trainer_3 = TRAINER * int(read_bit(game, 0xD7F6, 5)) + Got_Dome_Fossil = TASK * int(read_bit(game, 0xD7F6, 6)) + Got_Helix_Fossil = TASK * int(read_bit(game, 0xD7F6, 7)) + return sum([Beat_Mt_Moon_1_Trainer_1, Beat_Mt_Moon_1_Trainer_2, Beat_Mt_Moon_1_Trainer_3, Beat_Mt_Moon_1_Trainer_4, + Beat_Mt_Moon_1_Trainer_5, Beat_Mt_Moon_1_Trainer_6, Beat_Mt_Moon_1_Trainer_7, Beat_Mt_Moon_Super_Nerd, + Beat_Mt_Moon_3_Trainer_0, Beat_Mt_Moon_3_Trainer_1, Beat_Mt_Moon_3_Trainer_2, Beat_Mt_Moon_3_Trainer_3, + Got_Dome_Fossil, Got_Helix_Fossil]) + +def routes(game): + route3_0 = TRAINER * int(read_bit(game, 0xD7C3, 2)) + route3_1 = TRAINER * int(read_bit(game, 0xD7C3, 3)) + route3_2 = TRAINER * int(read_bit(game, 0xD7C3, 4)) + route3_3 = TRAINER * int(read_bit(game, 0xD7C3, 5)) + route3_4 = TRAINER * int(read_bit(game, 0xD7C3, 6)) + route3_5 = TRAINER * int(read_bit(game, 0xD7C3, 7)) + route3_6 = TRAINER * int(read_bit(game, 0xD7C4, 0)) + route3_7 = TRAINER * int(read_bit(game, 0xD7C4, 1)) + + route4_0 = TRAINER * int(read_bit(game, 0xD7C5, 2)) + + route24_rocket = TRAINER * int(read_bit(game, 0xD7EF, 1)) + route24_0 = TRAINER * int(read_bit(game, 0xD7EF, 2)) + route24_1 = TRAINER * int(read_bit(game, 0xD7EF, 3)) + route24_2 = TRAINER * int(read_bit(game, 0xD7EF, 4)) + route24_3 = TRAINER * int(read_bit(game, 0xD7EF, 5)) + route24_4 = TRAINER * int(read_bit(game, 0xD7EF, 6)) + route24_5 = TRAINER * int(read_bit(game, 0xD7EF, 7)) + + route25_0 = TRAINER * int(read_bit(game, 0xD7F1, 1)) + route25_1 = TRAINER * int(read_bit(game, 0xD7F1, 2)) + route25_2 = TRAINER * int(read_bit(game, 0xD7F1, 3)) + route25_3 = TRAINER * int(read_bit(game, 0xD7F1, 4)) + route25_4 = TRAINER * int(read_bit(game, 0xD7F1, 5)) + route25_5 = TRAINER * int(read_bit(game, 0xD7F1, 6)) + route25_6 = TRAINER * int(read_bit(game, 0xD7F1, 7)) + route25_7 = TRAINER * int(read_bit(game, 0xD7F2, 0)) + route25_8 = TRAINER * int(read_bit(game, 0xD7F2, 1)) + + route9_0 = TRAINER * int(read_bit(game, 0xD7CF, 1)) + route9_1 = TRAINER * int(read_bit(game, 0xD7CF, 2)) + route9_2 = TRAINER * int(read_bit(game, 0xD7CF, 3)) + route9_3 = TRAINER * int(read_bit(game, 0xD7CF, 4)) + route9_4 = TRAINER * int(read_bit(game, 0xD7CF, 5)) + route9_5 = TRAINER * int(read_bit(game, 0xD7CF, 6)) + route9_6 = TRAINER * int(read_bit(game, 0xD7CF, 7)) + route9_7 = TRAINER * int(read_bit(game, 0xD7D0, 0)) + route9_8 = TRAINER * int(read_bit(game, 0xD7D0, 1)) + + route6_0 = TRAINER * int(read_bit(game, 0xD7C9, 1)) + route6_1 = TRAINER * int(read_bit(game, 0xD7C9, 2)) + route6_2 = TRAINER * int(read_bit(game, 0xD7C9, 3)) + route6_3 = TRAINER * int(read_bit(game, 0xD7C9, 4)) + route6_4 = TRAINER * int(read_bit(game, 0xD7C9, 5)) + route6_5 = TRAINER * int(read_bit(game, 0xD7C9, 6)) + + route11_0 = TRAINER * int(read_bit(game, 0xD7D5, 1)) + route11_1 = TRAINER * int(read_bit(game, 0xD7D5, 2)) + route11_2 = TRAINER * int(read_bit(game, 0xD7D5, 3)) + route11_3 = TRAINER * int(read_bit(game, 0xD7D5, 4)) + route11_4 = TRAINER * int(read_bit(game, 0xD7D5, 5)) + route11_5 = TRAINER * int(read_bit(game, 0xD7D5, 6)) + route11_6 = TRAINER * int(read_bit(game, 0xD7D5, 7)) + route11_7 = TRAINER * int(read_bit(game, 0xD7D6, 0)) + route11_8 = TRAINER * int(read_bit(game, 0xD7D6, 1)) + route11_9 = TRAINER * int(read_bit(game, 0xD7D6, 2)) + + route8_0 = TRAINER * int(read_bit(game, 0xD7CD, 1)) + route8_1 = TRAINER * int(read_bit(game, 0xD7CD, 2)) + route8_2 = TRAINER * int(read_bit(game, 0xD7CD, 3)) + route8_3 = TRAINER * int(read_bit(game, 0xD7CD, 4)) + route8_4 = TRAINER * int(read_bit(game, 0xD7CD, 5)) + route8_5 = TRAINER * int(read_bit(game, 0xD7CD, 6)) + route8_6 = TRAINER * int(read_bit(game, 0xD7CD, 7)) + route8_7 = TRAINER * int(read_bit(game, 0xD7CE, 0)) + route8_8 = TRAINER * int(read_bit(game, 0xD7CE, 1)) + + route10_0 = TRAINER * int(read_bit(game, 0xD7D1, 1)) + route10_1 = TRAINER * int(read_bit(game, 0xD7D1, 2)) + route10_2 = TRAINER * int(read_bit(game, 0xD7D1, 3)) + route10_3 = TRAINER * int(read_bit(game, 0xD7D1, 4)) + route10_4 = TRAINER * int(read_bit(game, 0xD7D1, 5)) + route10_5 = TRAINER * int(read_bit(game, 0xD7D1, 6)) + + route12_0 = TRAINER * int(read_bit(game, 0xD7D7, 2)) + route12_1 = TRAINER * int(read_bit(game, 0xD7D7, 3)) + route12_2 = TRAINER * int(read_bit(game, 0xD7D7, 4)) + route12_3 = TRAINER * int(read_bit(game, 0xD7D7, 5)) + route12_4 = TRAINER * int(read_bit(game, 0xD7D7, 6)) + route12_5 = TRAINER * int(read_bit(game, 0xD7D7, 7)) + route12_6 = TRAINER * int(read_bit(game, 0xD7D8, 0)) + + route16_0 = TRAINER * int(read_bit(game, 0xD7DF, 1)) + route16_1 = TRAINER * int(read_bit(game, 0xD7DF, 2)) + route16_2 = TRAINER * int(read_bit(game, 0xD7DF, 3)) + route16_3 = TRAINER * int(read_bit(game, 0xD7DF, 4)) + route16_4 = TRAINER * int(read_bit(game, 0xD7DF, 5)) + route16_5 = TRAINER * int(read_bit(game, 0xD7DF, 6)) + + route17_0 = TRAINER * int(read_bit(game, 0xD7E1, 1)) + route17_1 = TRAINER * int(read_bit(game, 0xD7E1, 2)) + route17_2 = TRAINER * int(read_bit(game, 0xD7E1, 3)) + route17_3 = TRAINER * int(read_bit(game, 0xD7E1, 4)) + route17_4 = TRAINER * int(read_bit(game, 0xD7E1, 5)) + route17_5 = TRAINER * int(read_bit(game, 0xD7E1, 6)) + route17_6 = TRAINER * int(read_bit(game, 0xD7E1, 7)) + route17_7 = TRAINER * int(read_bit(game, 0xD7E2, 0)) + route17_8 = TRAINER * int(read_bit(game, 0xD7E2, 1)) + route17_9 = TRAINER * int(read_bit(game, 0xD7E2, 2)) + + route13_0 = TRAINER * int(read_bit(game, 0xD7D9, 1)) + route13_1 = TRAINER * int(read_bit(game, 0xD7D9, 2)) + route13_2 = TRAINER * int(read_bit(game, 0xD7D9, 3)) + route13_3 = TRAINER * int(read_bit(game, 0xD7D9, 4)) + route13_4 = TRAINER * int(read_bit(game, 0xD7D9, 5)) + route13_5 = TRAINER * int(read_bit(game, 0xD7D9, 6)) + route13_6 = TRAINER * int(read_bit(game, 0xD7D9, 7)) + route13_7 = TRAINER * int(read_bit(game, 0xD7DA, 0)) + route13_8 = TRAINER * int(read_bit(game, 0xD7DA, 1)) + route13_9 = TRAINER * int(read_bit(game, 0xD7DA, 2)) + + route14_0 = TRAINER * int(read_bit(game, 0xD7DB, 1)) + route14_1 = TRAINER * int(read_bit(game, 0xD7DB, 2)) + route14_2 = TRAINER * int(read_bit(game, 0xD7DB, 3)) + route14_3 = TRAINER * int(read_bit(game, 0xD7DB, 4)) + route14_4 = TRAINER * int(read_bit(game, 0xD7DB, 5)) + route14_5 = TRAINER * int(read_bit(game, 0xD7DB, 6)) + route14_6 = TRAINER * int(read_bit(game, 0xD7DB, 7)) + route14_7 = TRAINER * int(read_bit(game, 0xD7DC, 0)) + route14_8 = TRAINER * int(read_bit(game, 0xD7DC, 1)) + route14_9 = TRAINER * int(read_bit(game, 0xD7DC, 2)) + + route15_0 = TRAINER * int(read_bit(game, 0xD7DD, 1)) + route15_1 = TRAINER * int(read_bit(game, 0xD7DD, 2)) + route15_2 = TRAINER * int(read_bit(game, 0xD7DD, 3)) + route15_3 = TRAINER * int(read_bit(game, 0xD7DD, 4)) + route15_4 = TRAINER * int(read_bit(game, 0xD7DD, 5)) + route15_5 = TRAINER * int(read_bit(game, 0xD7DD, 6)) + route15_6 = TRAINER * int(read_bit(game, 0xD7DD, 7)) + route15_7 = TRAINER * int(read_bit(game, 0xD7DE, 0)) + route15_8 = TRAINER * int(read_bit(game, 0xD7DE, 1)) + route15_9 = TRAINER * int(read_bit(game, 0xD7DE, 2)) + + route18_0 = TRAINER * int(read_bit(game, 0xD7E3, 1)) + route18_1 = TRAINER * int(read_bit(game, 0xD7E3, 2)) + route18_2 = TRAINER * int(read_bit(game, 0xD7E3, 3)) + + route19_0 = TRAINER * int(read_bit(game, 0xD7E5, 1)) + route19_1 = TRAINER * int(read_bit(game, 0xD7E5, 2)) + route19_2 = TRAINER * int(read_bit(game, 0xD7E5, 3)) + route19_3 = TRAINER * int(read_bit(game, 0xD7E5, 4)) + route19_4 = TRAINER * int(read_bit(game, 0xD7E5, 5)) + route19_5 = TRAINER * int(read_bit(game, 0xD7E5, 6)) + route19_6 = TRAINER * int(read_bit(game, 0xD7E5, 7)) + route19_7 = TRAINER * int(read_bit(game, 0xD7E6, 0)) + route19_8 = TRAINER * int(read_bit(game, 0xD7E6, 1)) + route19_9 = TRAINER * int(read_bit(game, 0xD7E6, 2)) + + route20_0 = TRAINER * int(read_bit(game, 0xD7E7, 1)) + route20_1 = TRAINER * int(read_bit(game, 0xD7E7, 2)) + route20_2 = TRAINER * int(read_bit(game, 0xD7E7, 3)) + route20_3 = TRAINER * int(read_bit(game, 0xD7E7, 4)) + route20_4 = TRAINER * int(read_bit(game, 0xD7E7, 5)) + route20_5 = TRAINER * int(read_bit(game, 0xD7E7, 6)) + route20_6 = TRAINER * int(read_bit(game, 0xD7E7, 7)) + route20_7 = TRAINER * int(read_bit(game, 0xD7E8, 0)) + route20_8 = TRAINER * int(read_bit(game, 0xD7E8, 1)) + route20_9 = TRAINER * int(read_bit(game, 0xD7E8, 2)) + + route21_0 = TRAINER * int(read_bit(game, 0xD7E9, 1)) + route21_1 = TRAINER * int(read_bit(game, 0xD7E9, 2)) + route21_2 = TRAINER * int(read_bit(game, 0xD7E9, 3)) + route21_3 = TRAINER * int(read_bit(game, 0xD7E9, 4)) + route21_4 = TRAINER * int(read_bit(game, 0xD7E9, 5)) + route21_5 = TRAINER * int(read_bit(game, 0xD7E9, 6)) + route21_6 = TRAINER * int(read_bit(game, 0xD7E9, 7)) + route21_7 = TRAINER * int(read_bit(game, 0xD7EA, 0)) + route21_8 = TRAINER * int(read_bit(game, 0xD7EA, 1)) + + return sum([ route3_0, route3_1, route3_2, route3_3, route3_4, route3_5, route3_6, route3_7, + route4_0, route24_rocket, route24_0, route24_1, route24_2, route24_3, route24_4, + route24_5, route25_0, route25_1, route25_2, route25_3, route25_4, route25_5, route25_6, + route25_7, route25_8, route9_0, route9_1, route9_2, route9_3, route9_4, route9_5, + route9_6, route9_7, route9_8, route6_0, route6_1, route6_2, route6_3, route6_4, + route6_5, route11_0, route11_1, route11_2, route11_3, route11_4, route11_5, route11_6, + route11_7, route11_8, route11_9, route8_0, route8_1, route8_2, route8_3, route8_4, route8_5, + route8_6, route8_7, route8_8, route10_0, route10_1, route10_2, route10_3, route10_4, route10_5, + route12_0, route12_1, route12_2, route12_3, route12_4, route12_5, route12_6, route16_0, + route16_1, route16_2, route16_3, route16_4, route16_5, route17_0, route17_1, route17_2, + route17_3, route17_4, route17_5, route17_6, route17_7, route17_8, route17_9, route13_0, + route13_1, route13_2, route13_3, route13_4, route13_5, route13_6, route13_7, route13_8, + route13_9, route14_0, route14_1, route14_2, route14_3, route14_4, route14_5, route14_6, + route14_7, route14_8, route14_9, route15_0, route15_1, route15_2, route15_3, route15_4, + route15_5, route15_6, route15_7, route15_8, route15_9, route18_0, route18_1, route18_2, + route19_0, route19_1, route19_2, route19_3, route19_4, route19_5, route19_6, route19_7, + route19_8, route19_9, route20_0, route20_1, route20_2, route20_3, route20_4, route20_5, + route20_6, route20_7, route20_8, route20_9, route21_0, route21_1, route21_2, route21_3, + route21_4, route21_5, route21_6, route21_7, route21_8]) + +def misc(game): + bought_magikarp = TASK * int(read_bit(game, 0xD7C6, 7)) + hall_of_fame_dex_rating = TASK * int(read_bit(game, 0xD747, 3)) + daisy_walking = TASK * int(read_bit(game, 0xD74A, 2)) + # bought_museum_ticket = TASK * int(read_bit(game, 0xD754, 0)) + got_old_amber = TASK * int(read_bit(game, 0xD754, 1)) + got_bike_voucher = TASK * int(read_bit(game, 0xD771, 1)) + got_10_coins = TASK * int(read_bit(game, 0xD77E, 2)) + got_20_coins_1 = TASK * int(read_bit(game, 0xD77E, 3)) + got_20_coins_2 = TASK * int(read_bit(game, 0xD77E, 4)) + got_coin_case = TASK * int(read_bit(game, 0xD783, 0)) + got_potion_sample = TASK * int(read_bit(game, 0xD7BF, 0)) + got_itemfinder = TASK * int(read_bit(game, 0xD7D6, 7)) + got_exp_all = TASK * int(read_bit(game, 0xD7DD, 0)) + rescued_mr_fuji = TASK * int(read_bit(game, 0xD7E0, 7)) + beat_mewtwo = TASK * int(read_bit(game, 0xD85F, 1)) + rescued_mr_fuji_2 = TASK * int(read_bit(game, 0xD769, 7)) + + return sum([bought_magikarp, hall_of_fame_dex_rating, daisy_walking, + got_old_amber, got_bike_voucher, got_10_coins, got_20_coins_1, got_20_coins_2, + got_coin_case, got_potion_sample, got_itemfinder, got_exp_all, rescued_mr_fuji, + beat_mewtwo, rescued_mr_fuji_2]) # bought_museum_ticket, + +def snorlax(game): + route12_snorlax_fight = POKEMON * int(read_bit(game, 0xD7D8, 6)) + route12_snorlax_beat = POKEMON * int(read_bit(game, 0xD7D8, 7)) + route16_snorlax_fight = POKEMON * int(read_bit(game, 0xD7E0, 0)) + route16_snorlax_beat = POKEMON * int(read_bit(game, 0xD7E0, 1)) + + return sum([route12_snorlax_fight, route12_snorlax_beat, route16_snorlax_fight, route16_snorlax_beat]) + +def hmtm(game): + hm01 = HM * int(read_bit(game, 0xD803, 0))# cut + hm02 = HM * int(read_bit(game, 0xD7E0, 6))# fly + hm03 = HM * int(read_bit(game, 0xD857, 0))# strength + hm04 = HM * int(read_bit(game, 0xD78E, 0))# surf + hm05 = HM * int(read_bit(game, 0xD7C2, 0))# flash + + tm34 = TM * int(read_bit(game, 0xD755, 6)) + tm11 = TM * int(read_bit(game, 0xD75E, 6)) + tm41 = TM * int(read_bit(game, 0xD777, 0)) + tm13 = TM * int(read_bit(game, 0xD778, 4)) + tm48 = TM * int(read_bit(game, 0xD778, 5)) + tm49 = TM * int(read_bit(game, 0xD778, 6)) + tm18 = TM * int(read_bit(game, 0xD778, 7)) + tm21 = TM * int(read_bit(game, 0xD77C, 0)) + tm06 = TM * int(read_bit(game, 0xD792, 0)) + tm24 = TM * int(read_bit(game, 0xD773, 6)) + tm29 = TM * int(read_bit(game, 0xD7BD, 0)) + tm31 = TM * int(read_bit(game, 0xD7AF, 0)) + tm35 = TM * int(read_bit(game, 0xD7A1, 7)) + tm36 = TM * int(read_bit(game, 0xD826, 7)) + tm38 = TM * int(read_bit(game, 0xD79A, 0)) + tm27 = TM * int(read_bit(game, 0xD751, 0)) + tm42 = TM * int(read_bit(game, 0xD74C, 1)) + tm46 = TM * int(read_bit(game, 0xD7B3, 0)) + tm39 = TM * int(read_bit(game, 0xD7D7, 0)) + + + return sum([hm01, hm02, hm03, hm04, hm05, tm34, tm11, tm41, tm13, tm48, tm49, tm18, tm21, tm06, tm24, tm29, tm31, tm35, tm36, tm38, tm27, tm42, tm46, tm39]) + +def bill(game): + met_bill = BILL_CAPT * int(read_bit(game, 0xD7F1, 0)) + used_cell_separator_on_bill = BILL_CAPT * int(read_bit(game, 0xD7F2, 3)) + got_ss_ticket = BILL_CAPT * int(read_bit(game, 0xD7F2, 4)) + met_bill_2 = BILL_CAPT * int(read_bit(game, 0xD7F2, 5)) + bill_said_use_cell_separator = BILL_CAPT * int(read_bit(game, 0xD7F2, 6)) + left_bills_house_after_helping = BILL_CAPT * int(read_bit(game, 0xD7F2, 7)) + + return sum([met_bill, used_cell_separator_on_bill, got_ss_ticket, met_bill_2, bill_said_use_cell_separator, left_bills_house_after_helping]) + +def oak(game): + oak_appeared_in_pallet = TASK * int(read_bit(game, 0xD74B, 7)) + followed_oak_into_lab = TASK * int(read_bit(game, 0xD747, 0)) + oak_asked_to_choose_mon = TASK * int(read_bit(game, 0xD74B, 1)) + got_starter = TASK * int(read_bit(game, 0xD74B, 2)) + followed_oak_into_lab_2 = TASK * int(read_bit(game, 0xD74B, 0)) + got_pokedex = QUEST * int(read_bit(game, 0xD74B, 5)) + got_oaks_parcel = QUEST * int(read_bit(game, 0xD74E, 1)) + pallet_after_getting_pokeballs = QUEST * int(read_bit(game, 0xD747, 6)) + oak_got_parcel = QUEST * int(read_bit(game, 0xD74E, 0)) + got_pokeballs_from_oak = TASK * int(read_bit(game, 0xD74B, 4)) + pallet_after_getting_pokeballs_2 = TASK * int(read_bit(game, 0xD74B, 6)) + + return sum([oak_appeared_in_pallet, followed_oak_into_lab, oak_asked_to_choose_mon, got_starter, followed_oak_into_lab_2, got_pokedex, + got_oaks_parcel, pallet_after_getting_pokeballs, oak_got_parcel, got_pokeballs_from_oak, pallet_after_getting_pokeballs_2]) + +def towns(game): + got_town_map = TASK * int(read_bit(game, 0xD74A, 0)) + entered_blues_house = TASK * int(read_bit(game, 0xD74A, 1)) + beat_viridian_forest_trainer_0 = TRAINER * int(read_bit(game, 0xD7F3, 2)) + beat_viridian_forest_trainer_1 = TRAINER * int(read_bit(game, 0xD7F3, 3)) + beat_viridian_forest_trainer_2 = TRAINER * int(read_bit(game, 0xD7F3, 4)) + got_nugget = TASK * int(read_bit(game, 0xD7EF, 0)) + nugget_reward_available = TASK * int(read_bit(game, 0xD7F0, 1)) + beat_cerulean_rocket_thief = TRAINER * int(read_bit(game, 0xD75B, 7)) + got_bicycle = QUEST * int(read_bit(game, 0xD75F, 0)) + seel_fan_boast = TASK * int(read_bit(game, 0xD771, 6)) + pikachu_fan_boast = TASK * int(read_bit(game, 0xD771, 7)) + got_poke_flute = QUEST * int(read_bit(game, 0xD76C, 0)) + + return sum([got_town_map, entered_blues_house, beat_viridian_forest_trainer_0, + beat_viridian_forest_trainer_1, beat_viridian_forest_trainer_2, got_nugget, + nugget_reward_available, beat_cerulean_rocket_thief, got_bicycle, + seel_fan_boast, pikachu_fan_boast, got_poke_flute]) + +def lab(game): + gave_fossil_to_lab = TASK * int(read_bit(game, 0xD7A3, 0)) + lab_still_reviving_fossil = TASK * int(read_bit(game, 0xD7A3, 1)) + lab_handing_over_fossil_mon = TASK * int(read_bit(game, 0xD7A3, 2)) + + return sum([gave_fossil_to_lab, lab_still_reviving_fossil, lab_handing_over_fossil_mon]) + +def mansion(game): + beat_mansion_2_trainer_0 = TRAINER * int(read_bit(game, 0xD847, 1)) + beat_mansion_3_trainer_0 = TRAINER * int(read_bit(game, 0xD849, 1)) + beat_mansion_3_trainer_1 = TRAINER * int(read_bit(game, 0xD849, 2)) + beat_mansion_4_trainer_0 = TRAINER * int(read_bit(game, 0xD84B, 1)) + beat_mansion_4_trainer_1 = TRAINER * int(read_bit(game, 0xD84B, 2)) + mansion_switch_on = QUEST * int(read_bit(game, 0xD796, 0)) + beat_mansion_1_trainer_0 = TRAINER * int(read_bit(game, 0xD798, 1)) + + return sum([beat_mansion_2_trainer_0, beat_mansion_3_trainer_0, beat_mansion_3_trainer_1, + beat_mansion_4_trainer_0, beat_mansion_4_trainer_1, mansion_switch_on, beat_mansion_1_trainer_0]) + +def safari(game): + gave_gold_teeth = QUEST * int(read_bit(game, 0xD78E, 1)) + safari_game_over = EVENT * int(read_bit(game, 0xD790, 6)) + in_safari_zone = EVENT * int(read_bit(game, 0xD790, 7)) + + return sum([gave_gold_teeth, safari_game_over, in_safari_zone]) + +def dojo(game): + defeated_fighting_dojo = BAD * int(read_bit(game, 0xD7B1, 0)) + beat_karate_master = GYM_LEADER * int(read_bit(game, 0xD7B1, 1)) + beat_dojo_trainer_0 = TRAINER * int(read_bit(game, 0xD7B1, 2)) + beat_dojo_trainer_1 = TRAINER * int(read_bit(game, 0xD7B1, 3)) + beat_dojo_trainer_2 = TRAINER * int(read_bit(game, 0xD7B1, 4)) + beat_dojo_trainer_3 = TRAINER * int(read_bit(game, 0xD7B1, 5)) + got_hitmonlee = POKEMON * int(read_bit(game, 0xD7B1, 6)) + got_hitmonchan = POKEMON * int(read_bit(game, 0xD7B1, 7)) + + return sum([defeated_fighting_dojo, beat_karate_master, beat_dojo_trainer_0, + beat_dojo_trainer_1, beat_dojo_trainer_2, beat_dojo_trainer_3, + got_hitmonlee, got_hitmonchan]) + +def hideout(game): + beat_rocket_hideout_1_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD815, 1)) + beat_rocket_hideout_1_trainer_1 = GYM_TRAINER * int(read_bit(game, 0xD815, 2)) + beat_rocket_hideout_1_trainer_2 = GYM_TRAINER * int(read_bit(game, 0xD815, 3)) + beat_rocket_hideout_1_trainer_3 = GYM_TRAINER * int(read_bit(game, 0xD815, 4)) + beat_rocket_hideout_1_trainer_4 = GYM_TRAINER * int(read_bit(game, 0xD815, 5)) + beat_rocket_hideout_2_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD817, 1)) + beat_rocket_hideout_3_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD819, 1)) + beat_rocket_hideout_3_trainer_1 = GYM_TRAINER * int(read_bit(game, 0xD819, 2)) + beat_rocket_hideout_4_trainer_0 = GYM_TRAINER * int(read_bit(game, 0xD81B, 2)) + beat_rocket_hideout_4_trainer_1 = GYM_TRAINER * int(read_bit(game, 0xD81B, 3)) + beat_rocket_hideout_4_trainer_2 = GYM_TRAINER * int(read_bit(game, 0xD81B, 4)) + rocket_hideout_4_door_unlocked = QUEST * int(read_bit(game, 0xD81B, 5)) + rocket_dropped_lift_key = QUEST * int(read_bit(game, 0xD81B, 6)) + beat_rocket_hideout_giovanni = GYM_LEADER * int(read_bit(game, 0xD81B, 7)) + found_rocket_hideout = 10 * int(read_bit(game, 0xD77E, 1)) + + return sum([beat_rocket_hideout_1_trainer_0, beat_rocket_hideout_1_trainer_1, beat_rocket_hideout_1_trainer_2, beat_rocket_hideout_1_trainer_3, + beat_rocket_hideout_1_trainer_4, beat_rocket_hideout_2_trainer_0, beat_rocket_hideout_3_trainer_0, beat_rocket_hideout_3_trainer_1, + beat_rocket_hideout_4_trainer_0, beat_rocket_hideout_4_trainer_1, beat_rocket_hideout_4_trainer_2, rocket_hideout_4_door_unlocked, + rocket_dropped_lift_key, beat_rocket_hideout_giovanni, found_rocket_hideout]) + +def poke_tower(game): + beat_pokemontower_3_trainer_0 = TRAINER * int(read_bit(game, 0xD765, 1)) + beat_pokemontower_3_trainer_1 = TRAINER * int(read_bit(game, 0xD765, 2)) + beat_pokemontower_3_trainer_2 = TRAINER * int(read_bit(game, 0xD765, 3)) + beat_pokemontower_4_trainer_0 = TRAINER * int(read_bit(game, 0xD766, 1)) + beat_pokemontower_4_trainer_1 = TRAINER * int(read_bit(game, 0xD766, 2)) + beat_pokemontower_4_trainer_2 = TRAINER * int(read_bit(game, 0xD766, 3)) + beat_pokemontower_5_trainer_0 = TRAINER * int(read_bit(game, 0xD767, 2)) + beat_pokemontower_5_trainer_1 = TRAINER * int(read_bit(game, 0xD767, 3)) + beat_pokemontower_5_trainer_2 = TRAINER * int(read_bit(game, 0xD767, 4)) + beat_pokemontower_5_trainer_3 = TRAINER * int(read_bit(game, 0xD767, 5)) +# in_purified_zone = EVENT * int(read_bit(game, 0xD767, 7)) # purified zone + beat_pokemontower_6_trainer_0 = TRAINER * int(read_bit(game, 0xD768, 1)) + beat_pokemontower_6_trainer_1 = TRAINER * int(read_bit(game, 0xD768, 2)) + beat_pokemontower_6_trainer_2 = TRAINER * int(read_bit(game, 0xD768, 3)) + beat_ghost_marowak = QUEST * int(read_bit(game, 0xD768, 7)) + beat_pokemontower_7_trainer_0 = TRAINER * int(read_bit(game, 0xD769, 1)) + beat_pokemontower_7_trainer_1 = TRAINER * int(read_bit(game, 0xD769, 2)) + beat_pokemontower_7_trainer_2 = TRAINER * int(read_bit(game, 0xD769, 3)) + + return sum([beat_pokemontower_3_trainer_0, beat_pokemontower_3_trainer_1, beat_pokemontower_3_trainer_2, beat_pokemontower_4_trainer_0, + beat_pokemontower_4_trainer_1, beat_pokemontower_4_trainer_2, beat_pokemontower_5_trainer_0, beat_pokemontower_5_trainer_1, + beat_pokemontower_5_trainer_2, beat_pokemontower_5_trainer_3, beat_pokemontower_6_trainer_0, + beat_pokemontower_6_trainer_1, beat_pokemontower_6_trainer_2, beat_ghost_marowak, beat_pokemontower_7_trainer_0, + beat_pokemontower_7_trainer_1, beat_pokemontower_7_trainer_2]) # in_purified_zone, + +def gym1(game): + #gym 1 Pewter + one = GYM_LEADER * int(read_bit(game, 0xD755, 7)) + g1_1 = GYM_TRAINER * int(read_bit(game, 0xD755, 2)) # "0xD755-2": "Beat Pewter Gym Trainer 0", + return sum([one, g1_1, ]) + +def gym2(game): + #gym 2 Cerulean + two = GYM_LEADER * int(read_bit(game, 0xD75E, 7)) + g2_1 = GYM_TRAINER * int(read_bit(game, 0xD75E, 2)) # "0xD75E-2": "Beat Cerulean Gym Trainer 0", + g2_2 = GYM_TRAINER * int(read_bit(game, 0xD75E, 3)) # "0xD75E-3": "Beat Cerulean Gym Trainer 1", + return sum([two, g2_1, g2_2, ]) + +def gym3(game): + #gym 3 Vermilion + lock_one = GYM_TASK * int(read_bit(game, 0xD773, 1)) # "0xD773-1": "1S Lock Opened", + lock_two = GYM_TASK * int(read_bit(game, 0xD773, 0))# "0xD773-0": "2Nd Lock Opened", + three = GYM_LEADER * int(read_bit(game, 0xD773, 7)) + g3_1 = GYM_TRAINER * int(read_bit(game, 0xD773, 2)) # "0xD773-2": "Beat Vermilion Gym Trainer 0", + g3_2 = GYM_TRAINER * int(read_bit(game, 0xD773, 3)) # "0xD773-3": "Beat Vermilion Gym Trainer 1", + g3_3 = GYM_TRAINER * int(read_bit(game, 0xD773, 4)) # "0xD773-4": "Beat Vermilion Gym Trainer 2", + return sum([three, g3_1, g3_2, g3_3, lock_one, lock_two]) + +def gym4(game): + #gym 4 Celadon + four = GYM_LEADER * int(read_bit(game, 0xD792, 1)) + g4_1 = GYM_TRAINER * int(read_bit(game, 0xD77C, 2)) # "0xD77C-2": "Beat Celadon Gym Trainer 0", + g4_2 = GYM_TRAINER * int(read_bit(game, 0xD77C, 3)) # "0xD77C-3": "Beat Celadon Gym Trainer 1", + g4_3 = GYM_TRAINER * int(read_bit(game, 0xD77C, 4)) # "0xD77C-4": "Beat Celadon Gym Trainer 2", + g4_4 = GYM_TRAINER * int(read_bit(game, 0xD77C, 5)) # "0xD77C-5": "Beat Celadon Gym Trainer 3", + g4_5 = GYM_TRAINER * int(read_bit(game, 0xD77C, 6)) # "0xD77C-6": "Beat Celadon Gym Trainer 4", + g4_6 = GYM_TRAINER * int(read_bit(game, 0xD77C, 7)) # "0xD77C-7": "Beat Celadon Gym Trainer 5", + g4_7 = GYM_TRAINER * int(read_bit(game, 0xD77D, 0)) # "0xD77D-0": "Beat Celadon Gym Trainer 6", + return sum([four, g4_1, g4_2, g4_3, g4_4, g4_5, g4_6, g4_7, ]) + +def gym5(game): + #gym 5 Fuchsia + five = GYM_LEADER * int(read_bit(game, 0xD7B3, 1)) + g5_1 = GYM_TRAINER * int(read_bit(game, 0xD792, 2)) # "0xD792-2": "Beat Fuchsia Gym Trainer 0", + g5_2 = GYM_TRAINER * int(read_bit(game, 0xD792, 3)) # "0xD792-3": "Beat Fuchsia Gym Trainer 1", + g5_3 = GYM_TRAINER * int(read_bit(game, 0xD792, 4)) # "0xD792-4": "Beat Fuchsia Gym Trainer 2", + g5_4 = GYM_TRAINER * int(read_bit(game, 0xD792, 5)) # "0xD792-5": "Beat Fuchsia Gym Trainer 3", + g5_5 = GYM_TRAINER * int(read_bit(game, 0xD792, 6)) # "0xD792-6": "Beat Fuchsia Gym Trainer 4", + g5_6 = GYM_TRAINER * int(read_bit(game, 0xD792, 7)) # "0xD792-7": "Beat Fuchsia Gym Trainer 5", + return sum([five, g5_1, g5_2, g5_3, g5_4, g5_5, g5_6, ]) + +def gym6(game): + #gym 6 Saffron + six = GYM_LEADER * int(read_bit(game, 0xD7B3, 1)) + g6_1 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 2)) # "0xD7B3-2": "Beat Saffron Gym Trainer 0", + g6_2 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 3)) # "0xD7B3-3": "Beat Saffron Gym Trainer 1", + g6_3 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 4)) # "0xD7B3-4": "Beat Saffron Gym Trainer 2", + g6_4 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 5)) # "0xD7B3-5": "Beat Saffron Gym Trainer 3", + g6_5 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 6)) # "0xD7B3-6": "Beat Saffron Gym Trainer 4", + g6_6 = GYM_TRAINER * int(read_bit(game, 0xD7B3, 7)) # "0xD7B3-7": "Beat Saffron Gym Trainer 5", + g6_7 = GYM_TRAINER * int(read_bit(game, 0xD7B4, 0)) # "0xD7B4-0": "Beat Saffron Gym Trainer 6", + return sum([six, g6_1, g6_2, g6_3, g6_4, g6_5, g6_6, g6_7, ]) + +def gym7(game): + #gym 7 Cinnabar + seven = GYM_LEADER * int(read_bit(game, 0xD79A, 1)) + g7_1 = GYM_TRAINER * int(read_bit(game, 0xD79A, 2)) # "0xD79A-2": "Beat Cinnabar Gym Trainer 0", + g7_2 = GYM_TRAINER * int(read_bit(game, 0xD79A, 3)) # "0xD79A-3": "Beat Cinnabar Gym Trainer 1", + g7_3 = GYM_TRAINER * int(read_bit(game, 0xD79A, 4)) # "0xD79A-4": "Beat Cinnabar Gym Trainer 2", + g7_4 = GYM_TRAINER * int(read_bit(game, 0xD79A, 5)) # "0xD79A-5": "Beat Cinnabar Gym Trainer 3", + g7_5 = GYM_TRAINER * int(read_bit(game, 0xD79A, 6)) # "0xD79A-6": "Beat Cinnabar Gym Trainer 4", + g7_6 = GYM_TRAINER * int(read_bit(game, 0xD79A, 7)) # "0xD79A-7": "Beat Cinnabar Gym Trainer 5", + g7_7 = GYM_TRAINER * int(read_bit(game, 0xD79B, 0)) # "0xD79B-0": "Beat Cinnabar Gym Trainer 6", + + return sum([seven, g7_1, g7_2, g7_3, g7_4, g7_5, g7_6, g7_7, ]) + +def gym8(game): + #gym 8 Viridian + # "0xD74C-0": "Viridian Gym Open", + gym_door = GYM_TASK * int(read_bit(game, 0xD74C, 0)) + eight = GYM_LEADER * int(read_bit(game, 0xD751, 1)) + g8_1 = GYM_TRAINER * int(read_bit(game, 0xD751, 2)) # "0xD751-2": "Beat Viridian Gym Trainer 0", + g8_2 = GYM_TRAINER * int(read_bit(game, 0xD751, 3)) # "0xD751-3": "Beat Viridian Gym Trainer 1", + g8_3 = GYM_TRAINER * int(read_bit(game, 0xD751, 4)) # "0xD751-4": "Beat Viridian Gym Trainer 2", + g8_4 = GYM_TRAINER * int(read_bit(game, 0xD751, 5)) # "0xD751-5": "Beat Viridian Gym Trainer 3", + g8_5 = GYM_TRAINER * int(read_bit(game, 0xD751, 6)) # "0xD751-6": "Beat Viridian Gym Trainer 4", + g8_6 = GYM_TRAINER * int(read_bit(game, 0xD751, 7)) # "0xD751-7": "Beat Viridian Gym Trainer 5", + g8_7 = GYM_TRAINER * int(read_bit(game, 0xD752, 0)) # "0xD752-0": "Beat Viridian Gym Trainer 6", + g8_8 = GYM_TRAINER * int(read_bit(game, 0xD752, 1)) # "0xD752-1": "Beat Viridian Gym Trainer 7", + return sum([eight, g8_1, g8_2, g8_3, g8_4, g8_5, g8_6, g8_7, g8_8, gym_door]) + +def rival(game): + one = RIVAL * int(read_bit(game, 0xD74B, 3)) + two = RIVAL * int(read_bit(game, 0xD7EB, 0)) + three = RIVAL * int(read_bit(game, 0xD7EB, 1)) + four = RIVAL * int(read_bit(game, 0xD7EB, 5)) + five = RIVAL * int(read_bit(game, 0xD7EB, 6)) + six = RIVAL * int(read_bit(game, 0xD75A, 0)) + seven = RIVAL * int(read_bit(game, 0xD764, 6)) + eight = RIVAL * int(read_bit(game, 0xD764, 7)) + nine = RIVAL * int(read_bit(game, 0xD7EB, 7)) + Beat_Silph_Co_Rival = RIVAL * int(read_bit(game, 0xD82F, 0)) + + return sum([one, two, three, four, five, six, seven, eight, nine, Beat_Silph_Co_Rival]) + +# UTIL ##################################################################################################### def bcd(num): return 10 * ((num >> 4) & 0x0F) + (num & 0x0F) @@ -79,26 +682,7 @@ def read_uint16(game, start_addr): val_1 = game.get_memory_value(start_addr + 1) return 256 * val_256 + val_1 -def pokemon(game): - # Get memory values from the list POKE and LEVEL - memory_values = [game.get_memory_value(a) for a in POKE] - levels = [game.get_memory_value(a) for a in LEVEL] - - # Use memory values to get corresponding names from pokemon_data - names = [entry['name'] for entry in data.pokemon_data if entry.get('decimal') and int(entry['decimal']) in memory_values] - - # Create an initial dictionary with names as keys and levels as values - party_dict = dict(zip(names, levels)) - - return party_dict - -def update_pokemon_level(pokemon_dict, pokemon_name, new_level): - if pokemon_name in pokemon_dict: - # Update the level for the specified Pokémon - pokemon_dict[pokemon_name] = new_level - else: - # Add a new entry for the Pokémon - pokemon_dict[pokemon_name] = new_level +# MISC ##################################################################################################### def get_hm_count(game): hm_ids = [0xC4, 0xC5, 0xC6, 0xC7, 0xC8] @@ -112,26 +696,14 @@ def get_hm_count(game): def get_items_in_bag(game, one_indexed=0): first_item = 0xD31E item_ids = [] - for i in range(0, 20, 2): + for i in range(0, 40, 2): item_id = game.get_memory_value(first_item + i) if item_id == 0 or item_id == 0xff: break item_ids.append(item_id + one_indexed) return item_ids -def get_items_names(game, one_indexed=0): - first_item = 0xD31E - item_names = [] - for i in range(0, 20, 2): - item_id = game.get_memory_value(first_item + i) - if item_id == 0 or item_id == 0xff: - break - item_id_key = item_id + one_indexed - item_name = data.items_dict.get(item_id_key, {}).get('Item', f'Unknown Item {item_id_key}') - item_names.append(item_name) - return item_names - -def position(game): +def position(game): # this is [y, x, z] r_pos = game.get_memory_value(Y_POS_ADDR) c_pos = game.get_memory_value(X_POS_ADDR) map_n = game.get_memory_value(MAP_N_ADDR) @@ -155,23 +727,6 @@ def party(game): party_levels = [x for x in [game.get_memory_value(addr) for addr in PARTY_LEVEL_ADDR] if x > 0] return party_size, party_levels # [x for x in party_levels if x > 0] -def opponent(game): - return [game.get_memory_value(addr) for addr in OPPONENT_LEVEL_ADDR] - -def oak_parcel(game): - return read_bit(game, OAK_PARCEL_ADDR, 1) - -def pokedex_obtained(game): - return read_bit(game, OAK_POKEDEX_ADDR, 5) - -def pokemon_seen(game): - seen_bytes = [game.get_memory_value(addr) for addr in SEEN_POKE_ADDR] - return sum([bit_count(b) for b in seen_bytes]) - -def pokemon_caught(game): - caught_bytes = [game.get_memory_value(addr) for addr in CAUGHT_POKE_ADDR] - return sum([bit_count(b) for b in caught_bytes]) - def hp(game): """Percentage of total party HP""" party_hp = [read_uint16(game, addr) for addr in HP_ADDR] @@ -182,106 +737,186 @@ def hp(game): return 1 return sum(party_hp) / sum_max_hp -def money(game): - return ( - 100 * 100 * bcd(game.get_memory_value(MONEY_ADDR_1)) - + 100 * bcd(game.get_memory_value(MONEY_ADDR_100)) - + bcd(game.get_memory_value(MONEY_ADDR_10000)) - ) +def used_cut(game): + if game.get_memory_value(WCUTTILE) == 61: + write_mem(game, 0xCD4D, 00) # address, byte to write resets tile check + return True + else: + return False + +def write_mem(game, addr, value): + mem = game.set_memory_value(addr, value) + return mem def badges(game): badges = game.get_memory_value(BADGE_1_ADDR) return bit_count(badges) -def saved_bill(game): - """Restored Bill from his experiment""" - return int(read_bit(game, USED_CELL_SEPARATOR_ADDR, 3)) - -def ss_anne_appeared(game): - """ - D803 - True is SS Anne is here - """ - return game.get_memory_value(SS_ANNE) - -def events(game): - """Adds up all event flags, exclude museum ticket""" - num_events = sum( - bit_count(game.get_memory_value(i)) - for i in range(EVENT_FLAGS_START_ADDR, EVENT_FLAGS_END_ADDR) - ) - museum_ticket = int(read_bit(game, MUSEUM_TICKET_ADDR, 0)) - - # Omit 13 events by default - return max(num_events - 13 - museum_ticket, 0) - -def talk_to_npc(game): - """ - Talk to NPC - 238 is text box arrow blink on - 127 is no text box arrow - """ - return game.get_memory_value(TEXT_BOX_ARROW_BLINK) - -def is_in_battle(game): - # D057 - # 0 not in battle - # 1 wild battle - # 2 trainer battle - # -1 lost battle - bflag = game.get_memory_value(BATTLE_FLAG) - if bflag > 0: - return True - else: - return False - -def if_font_is_loaded(game): - return game.get_memory_value(IF_FONT_IS_LOADED) - -def player_direction(game): - return game.get_memory_value(PLAYER_DIRECTION) +def update_pokedex(game): + seen_pokemon = np.zeros(152, dtype=np.uint8) + caught_pokemon = np.zeros(152, dtype=np.uint8) + for i in range(0xD30A - 0xD2F7): + caught_mem = game.get_memory_value(i + 0xD2F7) + seen_mem = game.get_memory_value(i + 0xD30A) + for j in range(8): + caught_pokemon[8*i + j] = 1 if caught_mem & (1 << j) else 0 + seen_pokemon[8*i + j] = 1 if seen_mem & (1 << j) else 0 + return sum(seen_pokemon), sum(caught_pokemon) -def player_y(game): - return game.get_memory_value(PLAYER_Y) +def update_moves_obtained(game): + # Scan party + moves_obtained = {} + cut = 0 + for i in [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247]: + if game.get_memory_value(i) != 0: + for j in range(4): + move_id = game.get_memory_value(i + j + 8) + if move_id != 0: + if move_id != 0: + moves_obtained[move_id] = 1 + if move_id == 15: + cut = 1 + # Scan current box (since the box doesn't auto increment in pokemon red) + num_moves = 4 + box_struct_length = 25 * num_moves * 2 + for i in range(game.get_memory_value(0xda80)): + offset = i*box_struct_length + 0xda96 + if game.get_memory_value(offset) != 0: + for j in range(4): + move_id = game.get_memory_value(offset + j + 8) + if move_id != 0: + moves_obtained[move_id] = 1 + return sum(moves_obtained), cut -def player_x(game): - return game.get_memory_value(PLAYER_X) +# CUT ##################################################################################################### -def map_n(game): - return game.get_memory_value(MAP_N_ADDR) +def cut_array(game): + cut_coords = {} + cut_tiles = {} # set([]) + cut_state = deque(maxlen=3) + if mem_val(game, 0xD057) == 0: # is_in_battle if 1 + player_direction = game.get_memory_value(0xC109) + y, x, map_id = position() # x, y, map_id + if player_direction == 0: # down + coords = (x, y + 1, map_id) + if player_direction == 4: + coords = (x, y - 1, map_id) + if player_direction == 8: + coords = (x - 1, y, map_id) + if player_direction == 0xC: + coords = (x + 1, y, map_id) + cut_state.append( + ( + game.get_memory_value(0xCFC6), + game.get_memory_value(0xCFCB), + game.get_memory_value(0xCD6A), + game.get_memory_value(0xD367), + game.get_memory_value(0xD125), + game.get_memory_value(0xCD3D), + ) + ) + if tuple(list(cut_state)[1:]) in CUT_SEQ: + cut_coords[coords] = 5 # from 14 + cut_tiles[cut_state[-1][0]] = 1 + elif cut_state == CUT_GRASS_SEQ: + cut_coords[coords] = 0.001 + cut_tiles[cut_state[-1][0]] = 1 + elif deque([(-1, *elem[1:]) for elem in cut_state]) == CUT_FAIL_SEQ: + cut_coords[coords] = 0.001 + cut_tiles[cut_state[-1][0]] = 1 + if int(read_bit(game, 0xD803, 0)): + if check_if_in_start_menu(game): + seen_start_menu = 1 + if check_if_in_pokemon_menu(game): + seen_pokemon_menu = 1 + if check_if_in_stats_menu(game): + seen_stats_menu = 1 + if check_if_in_bag_menu(game): + seen_bag_menu = 1 + return cut_coords, cut_tiles, seen_start_menu, seen_pokemon_menu, seen_stats_menu, seen_bag_menu -def npc_y(game, npc_id, npc_bank): - npc_id = npc_id * 0x10 - npc_bank = (npc_bank + 1) * 0x100 - return game.get_memory_value(0xC004 + npc_id + npc_bank) +def check_if_in_start_menu(game) -> bool: + return ( + mem_val(game, 0xD057) == 0 + and mem_val(game, 0xCF13) == 0 + and mem_val(game, 0xFF8C) == 6 + and mem_val(game, 0xCF94) == 0 + ) -def npc_x(game, npc_id, npc_bank): - npc_id = npc_id * 0x10 - npc_bank = (npc_bank + 1) * 0x100 - return game.get_memory_value(0xC006 + npc_id + npc_bank) +def check_if_in_pokemon_menu(game) -> bool: + return ( + mem_val(game, 0xD057) == 0 + and mem_val(game, 0xCF13) == 0 + and mem_val(game, 0xFF8C) == 6 + and mem_val(game, 0xCF94) == 2 + ) -def sprites(game): - return game.get_memory_value(WNUMSPRITES) +def check_if_in_stats_menu(game) -> bool: + return ( + mem_val(game, 0xD057) == 0 + and mem_val(game, 0xCF13) == 0 + and mem_val(game, 0xFF8C) == 6 + and mem_val(game, 0xCF94) == 1 + ) -def signs(game): - return game.get_memory_value(WNUMSIGNS) +def check_if_in_bag_menu(game) -> bool: + return ( + mem_val(game, 0xD057) == 0 + and mem_val(game, 0xCF13) == 0 + # and mem_val(game, 0xFF8C) == 6 # only sometimes + and mem_val(game, 0xCF94) == 3 + ) -def bill_capt(game): - met_bill = 5 * int(read_bit(game, 0xD7F1, 0)) - used_cell_separator_on_bill = 5 * int(read_bit(game, 0xD7F2, 3)) - ss_ticket = 5 * int(read_bit(game, 0xD7F2, 4)) - met_bill_2 = 5 * int(read_bit(game, 0xD7F2, 5)) - bill_said_use_cell_separator = 5 * int(read_bit(game, 0xD7F2, 6)) - left_bills_house_after_helping = 5 * int(read_bit(game, 0xD7F2, 7)) - got_hm01 = 5 * int(read_bit(game, 0xD803, 0)) - rubbed_captains_back = 5 * int(read_bit(game, 0xD803, 1)) - return sum([met_bill, used_cell_separator_on_bill, ss_ticket, met_bill_2, bill_said_use_cell_separator, left_bills_house_after_helping, got_hm01, rubbed_captains_back]) +# EXPL ##################################################################################################### -def used_cut(game): - return game.get_memory_value(WCUTTILE) +def explore(game, seen_coords, map_n): + seen_coords= set() + poketower = [142, 143, 144, 145, 146, 147, 148] + pokehideout = [199, 200, 201, 202, 203] + silphco = [181, 207, 208, 209, 210, 211, 212, 213, 233, 234, 235, 236] + if int(read_bit(game, 0xD81B, 7)) == 0: # pre hideout + if map_n in poketower: + exploration_reward = 0 + elif map_n in pokehideout: + exploration_reward = (0.03 * len(seen_coords)) + else: + exploration_reward = (0.02 * len(seen_coords)) + elif int(read_bit(game, 0xD7E0, 7)) == 0 and int(read_bit(game, 0xD81B, 7)) == 1: # hideout done poketower not done + if map_n in poketower: + exploration_reward = (0.03 * len(seen_coords)) + else: + exploration_reward = (0.02 * len(seen_coords)) + elif int(read_bit(game, 0xD76C, 0)) == 0 and int(read_bit(game, 0xD7E0, 7)) == 1: # tower done no flute + if map_n == 149: + exploration_reward = (0.03 * len(seen_coords)) + elif map_n in poketower: + exploration_reward = (0.01 * len(seen_coords)) + elif map_n in pokehideout: + exploration_reward = (0.01 * len(seen_coords)) + else: + exploration_reward = (0.02 * len(seen_coords)) + elif int(read_bit(game, 0xD838, 7)) == 0 and int(read_bit(game, 0xD76C, 0)) == 1: # flute gotten pre silphco + if map_n in silphco: + exploration_reward = (0.03 * len(seen_coords)) + elif map_n in poketower: + exploration_reward = (0.01 * len(seen_coords)) + elif map_n in pokehideout: + exploration_reward = (0.01 * len(seen_coords)) + else: + exploration_reward = (0.02 * len(seen_coords)) + elif int(read_bit(game, 0xD838, 7)) == 1 and int(read_bit(game, 0xD76C, 0)) == 1: # flute gotten post silphco + if map_n in silphco: + exploration_reward = (0.01 * len(seen_coords)) + elif map_n in poketower: + exploration_reward = (0.01 * len(seen_coords)) + elif map_n in pokehideout: + exploration_reward = (0.01 * len(seen_coords)) + else: + exploration_reward = (0.02 * len(seen_coords)) + else: + exploration_reward = (0.02 * len(seen_coords)) + return exploration_reward -def write_mem(game, addr, value): - mem = game.set_memory_value(addr, value) - return mem # ################################################################################################################## # # Notes From fe0f49f1db92d887290478bde97fde8037b7028a Mon Sep 17 00:00:00 2001 From: leanke Date: Sun, 5 May 2024 15:14:06 +0000 Subject: [PATCH 17/29] dirty but works --- .gitignore | 6 +- pokegym/clean_pufferl.py | 694 ++++++++++++++++++++++++++ pokegym/config.yaml | 84 ++++ pokegym/pokemon_red/__init__.py | 12 + pokegym/pokemon_red/environment.py | 23 + pokegym/pokemon_red/stream_wrapper.py | 101 ++++ pokegym/pokemon_red/torch.py | 29 ++ pokegym/run.sh | 2 + pokegym/train.py | 260 ++++++++++ setup.py | 31 +- 10 files changed, 1239 insertions(+), 3 deletions(-) create mode 100644 pokegym/clean_pufferl.py create mode 100644 pokegym/config.yaml create mode 100644 pokegym/pokemon_red/__init__.py create mode 100644 pokegym/pokemon_red/environment.py create mode 100644 pokegym/pokemon_red/stream_wrapper.py create mode 100644 pokegym/pokemon_red/torch.py create mode 100755 pokegym/run.sh create mode 100644 pokegym/train.py diff --git a/.gitignore b/.gitignore index 7ec8488..999a342 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ -# Rom files +# Rom files ect *.gb +wandb/ +experiments/ +glitch + # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/pokegym/clean_pufferl.py b/pokegym/clean_pufferl.py new file mode 100644 index 0000000..f02ed8e --- /dev/null +++ b/pokegym/clean_pufferl.py @@ -0,0 +1,694 @@ +from pdb import set_trace as T +import numpy as np +import cv2 + +import os +import random +import time +import uuid + +from collections import defaultdict +from datetime import timedelta + +import torch +import torch.nn as nn +import torch.optim as optim + +import pufferlib +import pufferlib.utils +import pufferlib.emulation +import pufferlib.vectorization +import pufferlib.frameworks.cleanrl +import pufferlib.policy_pool + + +@pufferlib.dataclass +class Performance: + total_uptime = 0 + total_updates = 0 + total_agent_steps = 0 + epoch_time = 0 + epoch_sps = 0 + evaluation_time = 0 + evaluation_sps = 0 + evaluation_memory = 0 + evaluation_pytorch_memory = 0 + env_time = 0 + env_sps = 0 + inference_time = 0 + inference_sps = 0 + train_time = 0 + train_sps = 0 + train_memory = 0 + train_pytorch_memory = 0 + misc_time = 0 + +@pufferlib.dataclass +class Losses: + policy_loss = 0 + value_loss = 0 + entropy = 0 + old_approx_kl = 0 + approx_kl = 0 + clipfrac = 0 + explained_variance = 0 + +@pufferlib.dataclass +class Charts: + global_step = 0 + SPS = 0 + learning_rate = 0 + +def create( + self: object = None, + config: pufferlib.namespace = None, + exp_name: str = None, + track: bool = False, + + # Agent + agent: nn.Module = None, + agent_creator: callable = None, + agent_kwargs: dict = None, + + # Environment + env_creator: callable = None, + env_creator_kwargs: dict = None, + vectorization: ... = pufferlib.vectorization.Serial, + + # Policy Pool options + policy_selector: callable = pufferlib.policy_pool.random_selector, + ): + if config is None: + config = pufferlib.args.CleanPuffeRL() + + if exp_name is None: + exp_name = str(uuid.uuid4())[:8] + + wandb = None + if track: + import wandb + + start_time = time.time() + seed_everything(config.seed, config.torch_deterministic) + total_updates = config.total_timesteps // config.batch_size + + device = config.device + + # Create environments, agent, and optimizer + init_profiler = pufferlib.utils.Profiler(memory=True) + with init_profiler: + pool = vectorization( + env_creator, + env_kwargs=env_creator_kwargs, + num_envs=config.num_envs, + envs_per_worker=config.envs_per_worker, + envs_per_batch=config.envs_per_batch, + env_pool=config.env_pool, + mask_agents=True, + ) + + obs_shape = pool.single_observation_space.shape + atn_shape = pool.single_action_space.shape + num_agents = pool.agents_per_env + total_agents = num_agents * config.num_envs + + # If data_dir is provided, load the resume state leanke + resume_state = {} + # path = os.path.join(config.data_dir, exp_name) + path = f'{config.data_dir}/{exp_name}' + if os.path.exists(path): + trainer_path = os.path.join(path, 'trainer_state.pt') + resume_state = torch.load(trainer_path) + model_path = os.path.join(path, resume_state["model_name"]) + agent = torch.load(model_path, map_location=device) + print(f'Resumed from update {resume_state["update"]} ' + f'with policy {resume_state["model_name"]}') + else: + agent = pufferlib.emulation.make_object( + agent, agent_creator, [pool.driver_env], agent_kwargs) + + global_step = resume_state.get("global_step", 0) + agent_step = resume_state.get("agent_step", 0) + update = resume_state.get("update", 0) + + optimizer = optim.Adam(agent.parameters(), + lr=config.learning_rate, eps=1e-5) + + uncompiled_agent = agent # Needed to save the model + if config.compile: + agent = torch.compile(agent, mode=config.compile_mode) + + if config.verbose: + n_params = sum(p.numel() for p in agent.parameters() if p.requires_grad) + print(f"Model Size: {n_params//1000} K parameters") + + opt_state = resume_state.get("optimizer_state_dict", None) + if opt_state is not None: + optimizer.load_state_dict(resume_state["optimizer_state_dict"]) + + # Create policy pool + pool_agents = num_agents * pool.envs_per_batch + policy_pool = pufferlib.policy_pool.PolicyPool( + agent, pool_agents, atn_shape, device, path, + config.pool_kernel, policy_selector, + ) + + # Allocate Storage + storage_profiler = pufferlib.utils.Profiler(memory=True, pytorch_memory=True).start() + next_lstm_state = [] + pool.async_reset(config.seed) + next_lstm_state = None + if hasattr(agent, 'lstm'): + shape = (agent.lstm.num_layers, total_agents, agent.lstm.hidden_size) + next_lstm_state = ( + torch.zeros(shape).to(device), + torch.zeros(shape).to(device), + ) + obs=torch.zeros(config.batch_size + 1, *obs_shape, pin_memory=True)# added , pin_memory=True) + actions=torch.zeros(config.batch_size + 1, *atn_shape, dtype=int) + logprobs=torch.zeros(config.batch_size + 1) + rewards=torch.zeros(config.batch_size + 1) + dones=torch.zeros(config.batch_size + 1) + truncateds=torch.zeros(config.batch_size + 1) + values=torch.zeros(config.batch_size + 1) + + obs_ary = np.asarray(obs) + actions_ary = np.asarray(actions) + logprobs_ary = np.asarray(logprobs) + rewards_ary = np.asarray(rewards) + dones_ary = np.asarray(dones) + truncateds_ary = np.asarray(truncateds) + values_ary = np.asarray(values) + + storage_profiler.stop() + + #"charts/actions": wandb.Histogram(b_actions.cpu().numpy()), + init_performance = pufferlib.namespace( + init_time = time.time() - start_time, + init_env_time = init_profiler.elapsed, + init_env_memory = init_profiler.memory, + tensor_memory = storage_profiler.memory, + tensor_pytorch_memory = storage_profiler.pytorch_memory, + ) + + return pufferlib.namespace(self, + # Agent, Optimizer, and Environment + config=config, + pool = pool, + agent = agent, + uncompiled_agent = uncompiled_agent, + optimizer = optimizer, + policy_pool = policy_pool, + + # Logging + exp_name = exp_name, + wandb = wandb, + learning_rate=config.learning_rate, + losses = Losses(), + init_performance = init_performance, + performance = Performance(), + + # Storage + sort_keys = [], + next_lstm_state = next_lstm_state, + obs = obs, + actions = actions, + logprobs = logprobs, + rewards = rewards, + dones = dones, + values = values, + obs_ary = obs_ary, + actions_ary = actions_ary, + logprobs_ary = logprobs_ary, + rewards_ary = rewards_ary, + dones_ary = dones_ary, + truncateds_ary = truncateds_ary, + values_ary = values_ary, + + # Misc + total_updates = total_updates, + update = update, + global_step = global_step, + device = device, + start_time = start_time, + ) + +@pufferlib.utils.profile +def evaluate(data): + config = data.config + # TODO: Handle update on resume + if data.wandb is not None and data.performance.total_uptime > 0: + data.wandb.log({ + 'SPS': data.SPS, + 'global_step': data.global_step, + 'learning_rate': data.optimizer.param_groups[0]["lr"], + **{f'losses/{k}': v for k, v in data.losses.items()}, + **{f'performance/{k}': v + for k, v in data.performance.items()}, + **{f'stats/{k}': v for k, v in data.stats.items()}, + **{f'skillrank/{policy}': elo + for policy, elo in data.policy_pool.ranker.ratings.items()}, + }) + + data.policy_pool.update_policies() + performance = defaultdict(list) + env_profiler = pufferlib.utils.Profiler() + inference_profiler = pufferlib.utils.Profiler() + eval_profiler = pufferlib.utils.Profiler(memory=True, pytorch_memory=True).start() + misc_profiler = pufferlib.utils.Profiler() + + ptr = step = padded_steps_collected = agent_steps_collected = 0 + infos = defaultdict(lambda: defaultdict(list)) + while True: + step += 1 + if ptr == config.batch_size + 1: + break + + with env_profiler: + o, r, d, t, i, env_id, mask = data.pool.recv() + + with misc_profiler: + i = data.policy_pool.update_scores(i, "return") + # TODO: Update this for policy pool + for ii, ee in zip(i['learner'], env_id): + ii['env_id'] = ee + + + with inference_profiler, torch.no_grad(): + o = torch.as_tensor(o) + r = torch.as_tensor(r).float().to(data.device).view(-1) + d = torch.as_tensor(d).float().to(data.device).view(-1) + + agent_steps_collected += sum(mask) + padded_steps_collected += len(mask) + + # Multiple policies will not work with new envpool + next_lstm_state = data.next_lstm_state + if next_lstm_state is not None: + next_lstm_state = ( + next_lstm_state[0][:, env_id], + next_lstm_state[1][:, env_id], + ) + + actions, logprob, value, next_lstm_state = data.policy_pool.forwards( + o.to(data.device), next_lstm_state) + + if next_lstm_state is not None: + h, c = next_lstm_state + data.next_lstm_state[0][:, env_id] = h + data.next_lstm_state[1][:, env_id] = c + + value = value.flatten() + + + with misc_profiler: + actions = actions.cpu().numpy() + + # Index alive mask with policy pool idxs... + # TODO: Find a way to avoid having to do this + learner_mask = torch.Tensor(mask * data.policy_pool.mask) + + # Ensure indices do not exceed batch size + indices = torch.where(learner_mask)[0][:config.batch_size - ptr + 1].numpy() + end = ptr + len(indices) + + # Batch indexing + data.obs_ary[ptr:end] = o.cpu().numpy()[indices] + data.values_ary[ptr:end] = value.cpu().numpy()[indices] + data.actions_ary[ptr:end] = actions[indices] + data.logprobs_ary[ptr:end] = logprob.cpu().numpy()[indices] + data.rewards_ary[ptr:end] = r.cpu().numpy()[indices] + data.dones_ary[ptr:end] = d.cpu().numpy()[indices] + data.sort_keys.extend([(env_id[i], step) for i in indices]) + + # Update pointer + ptr += len(indices) + + for policy_name, policy_i in i.items(): + for agent_i in policy_i: + for name, dat in unroll_nested_dict(agent_i): + infos[policy_name][name].append(dat) + + with env_profiler: + data.pool.send(actions) + + eval_profiler.stop() + + data.global_step += padded_steps_collected + data.reward = float(torch.mean(data.rewards)) + data.SPS = int(padded_steps_collected / eval_profiler.elapsed) + + perf = data.performance + perf.total_uptime = int(time.time() - data.start_time) + perf.total_agent_steps = data.global_step + perf.env_time = env_profiler.elapsed + perf.env_sps = int(agent_steps_collected / env_profiler.elapsed) + perf.inference_time = inference_profiler.elapsed + perf.inference_sps = int(padded_steps_collected / inference_profiler.elapsed) + perf.eval_time = eval_profiler.elapsed + perf.eval_sps = int(padded_steps_collected / eval_profiler.elapsed) + perf.eval_memory = eval_profiler.end_mem + perf.eval_pytorch_memory = eval_profiler.end_torch_mem + perf.misc_time = misc_profiler.elapsed + + data.stats = {} + infos = infos['learner'] + + if 'pokemon_exploration_map' in infos: + for idx, pmap in zip(infos['env_id'], infos['pokemon_exploration_map']): + if not hasattr(data, 'pokemon'): + import pokemon_red_eval + data.map_updater = pokemon_red_eval.map_updater() + data.map_buffer = np.zeros((data.config.num_envs, *pmap.shape)) + + data.map_buffer[idx] = pmap + + pokemon_map = np.sum(data.map_buffer, axis=0) + rendered = data.map_updater(pokemon_map) + data.stats['Media/exploration_map'] = data.wandb.Image(rendered) + + for k, v in infos.items(): + if 'Task_eval_fn' in k: + # Temporary hack for NMMO competitio + continue + try: # TODO: Better checks on log data types + data.stats[k] = np.mean(v) + except: + continue + + if config.verbose: + print_dashboard(data.stats, data.init_performance, data.performance) + + return data.stats, infos + +@pufferlib.utils.profile +def train(data): + if done_training(data): + raise RuntimeError( + f"Max training updates {data.total_updates} already reached") + + config = data.config + # assert data.num_steps % bptt_horizon == 0, "num_steps must be divisible by bptt_horizon" + train_profiler = pufferlib.utils.Profiler(memory=True, pytorch_memory=True) + train_profiler.start() + + if config.anneal_lr: + frac = 1.0 - (data.update - 1.0) / data.total_updates + lrnow = frac * config.learning_rate + data.optimizer.param_groups[0]["lr"] = lrnow + + num_minibatches = config.batch_size // config.bptt_horizon // config.batch_rows + idxs = sorted(range(len(data.sort_keys)), key=data.sort_keys.__getitem__) + data.sort_keys = [] + b_idxs = ( + torch.Tensor(idxs) + .long()[:-1] + .reshape(config.batch_rows, num_minibatches, config.bptt_horizon) + .transpose(0, 1) + ) + + # bootstrap value if not done + with torch.no_grad(): + advantages = torch.zeros(config.batch_size, device=data.device) + lastgaelam = 0 + for t in reversed(range(config.batch_size)): + i, i_nxt = idxs[t], idxs[t + 1] + nextnonterminal = 1.0 - data.dones[i_nxt] + nextvalues = data.values[i_nxt] + delta = ( + data.rewards[i_nxt] + + config.gamma * nextvalues * nextnonterminal + - data.values[i] + ) + advantages[t] = lastgaelam = ( + delta + config.gamma * config.gae_lambda * nextnonterminal * lastgaelam + ) + + # Flatten the batch + data.b_obs = b_obs = data.obs[b_idxs].to(data.device, non_blocking=True) # torch.Tensor(data.obs_ary[b_idxs]) + b_actions = torch.Tensor(data.actions_ary[b_idxs] + ).to(data.device, non_blocking=True) + b_logprobs = torch.Tensor(data.logprobs_ary[b_idxs] + ).to(data.device, non_blocking=True) + b_dones = torch.Tensor(data.dones_ary[b_idxs] + ).to(data.device, non_blocking=True) + b_values = torch.Tensor(data.values_ary[b_idxs] + ).to(data.device, non_blocking=True) + b_advantages = advantages.reshape( + config.batch_rows, num_minibatches, config.bptt_horizon + ).transpose(0, 1) + b_returns = b_advantages + b_values + + # Optimizing the policy and value network + train_time = time.time() + pg_losses, entropy_losses, v_losses, clipfracs, old_kls, kls = [], [], [], [], [], [] + + # Leanke + # mb_obs_buffer = torch.zeros_like(b_obs[0], pin_memory=(data.device=="cuda")) + + for epoch in range(config.update_epochs): + lstm_state = None + for mb in range(num_minibatches): + + # Leanke + mb_obs = b_obs[mb] + # mb_obs_buffer.copy_(b_obs[mb], non_blocking=True) + # mb_obs = mb_obs_buffer.to(data.device, non_blocking=True) + + + #mb_obs = b_obs[mb].to(data.device, non_blocking=True) + mb_actions = b_actions[mb].contiguous() + mb_values = b_values[mb].reshape(-1) + mb_advantages = b_advantages[mb].reshape(-1) + mb_returns = b_returns[mb].reshape(-1) + + if hasattr(data.agent, 'lstm'): + _, newlogprob, entropy, newvalue, lstm_state = data.agent( + mb_obs, state=lstm_state, action=mb_actions) + lstm_state = (lstm_state[0].detach(), lstm_state[1].detach()) + else: + _, newlogprob, entropy, newvalue = data.agent( + mb_obs.reshape(-1, *data.pool.single_observation_space.shape), + action=mb_actions, + ) + + logratio = newlogprob - b_logprobs[mb].reshape(-1) + ratio = logratio.exp() + + with torch.no_grad(): + # calculate approx_kl http://joschu.net/blog/kl-approx.html + old_approx_kl = (-logratio).mean() + old_kls.append(old_approx_kl.item()) + approx_kl = ((ratio - 1) - logratio).mean() + kls.append(approx_kl.item()) + clipfracs += [ + ((ratio - 1.0).abs() > config.clip_coef).float().mean().item() + ] + + mb_advantages = mb_advantages.reshape(-1) + if config.norm_adv: + mb_advantages = (mb_advantages - mb_advantages.mean()) / ( + mb_advantages.std() + 1e-8 + ) + + # Policy loss + pg_loss1 = -mb_advantages * ratio + pg_loss2 = -mb_advantages * torch.clamp( + ratio, 1 - config.clip_coef, 1 + config.clip_coef + ) + pg_loss = torch.max(pg_loss1, pg_loss2).mean() + pg_losses.append(pg_loss.item()) + + # Value loss + newvalue = newvalue.view(-1) + if config.clip_vloss: + v_loss_unclipped = (newvalue - mb_returns) ** 2 + v_clipped = mb_values + torch.clamp( + newvalue - mb_values, + -config.vf_clip_coef, + config.vf_clip_coef, + ) + v_loss_clipped = (v_clipped - mb_returns) ** 2 + v_loss_max = torch.max(v_loss_unclipped, v_loss_clipped) + v_loss = 0.5 * v_loss_max.mean() + else: + v_loss = 0.5 * ((newvalue - mb_returns) ** 2).mean() + v_losses.append(v_loss.item()) + + entropy_loss = entropy.mean() + entropy_losses.append(entropy_loss.item()) + + loss = pg_loss - config.ent_coef * entropy_loss + v_loss * config.vf_coef + data.optimizer.zero_grad() + loss.backward() + nn.utils.clip_grad_norm_(data.agent.parameters(), config.max_grad_norm) + data.optimizer.step() + + if config.target_kl is not None: + if approx_kl > config.target_kl: + break + + train_profiler.stop() + y_pred, y_true = b_values.cpu().numpy(), b_returns.cpu().numpy() + var_y = np.var(y_true) + explained_var = np.nan if var_y == 0 else 1 - np.var(y_true - y_pred) / var_y + + losses = data.losses + losses.policy_loss = np.mean(pg_losses) + losses.value_loss = np.mean(v_losses) + losses.entropy = np.mean(entropy_losses) + losses.old_approx_kl = np.mean(old_kls) + losses.approx_kl = np.mean(kls) + losses.clipfrac = np.mean(clipfracs) + losses.explained_variance = explained_var + + perf = data.performance + perf.total_uptime = int(time.time() - data.start_time) + perf.total_updates = data.update + 1 + perf.train_time = time.time() - train_time + perf.train_sps = int(config.batch_size / perf.train_time) + perf.train_memory = train_profiler.end_mem + perf.train_pytorch_memory = train_profiler.end_torch_mem + perf.epoch_time = perf.eval_time + perf.train_time + perf.epoch_sps = int(config.batch_size / perf.epoch_time) + + if config.verbose: + print_dashboard(data.stats, data.init_performance, data.performance) + + data.update += 1 + if data.update % config.checkpoint_interval == 0 or done_training(data): + save_checkpoint(data) + +def close(data): + data.pool.close() + + if data.wandb is not None: + artifact_name = f"{data.exp_name}_model" + artifact = data.wandb.Artifact(artifact_name, type="model") + model_path = save_checkpoint(data) + artifact.add_file(model_path) + data.wandb.run.log_artifact(artifact) + data.wandb.finish() + +def rollout(env_creator, env_kwargs, agent_creator, agent_kwargs, + model_path=None, device='cuda', verbose=True): + env = env_creator(**env_kwargs) + if model_path is None: + agent = agent_creator(env, **agent_kwargs) + else: + agent = torch.load(model_path, map_location=device) + + terminal = truncated = True + + while True: + if terminal or truncated: + if verbose: + print('--- Reset ---') + + ob, info = env.reset() + state = None + step = 0 + return_val = 0 + + ob = torch.tensor(ob).unsqueeze(0).to(device) + with torch.no_grad(): + if hasattr(agent, 'lstm'): + action, _, _, _, state = agent(ob, state) + else: + action, _, _, _ = agent(ob) + + ob, reward, terminal, truncated, _ = env.step(action[0].item()) + return_val += reward + + chars = env.render() + print("\033c", end="") + print(chars) + + if verbose: + print(f'Step: {step} Reward: {reward:.4f} Return: {return_val:.2f}') + + time.sleep(0.5) + step += 1 + +def done_training(data): + return data.update >= data.total_updates + +def save_checkpoint(data): + path = os.path.join(data.config.data_dir, data.exp_name) + if not os.path.exists(path): + os.makedirs(path) + + model_name = f'model_{data.update:06d}.pt' + model_path = os.path.join(path, model_name) + + # Already saved + if os.path.exists(model_path): + return model_path + + torch.save(data.uncompiled_agent, model_path) + + state = { + "optimizer_state_dict": data.optimizer.state_dict(), + "global_step": data.global_step, + "agent_step": data.global_step, + "update": data.update, + "model_name": model_name, + } + + if data.wandb: + state['exp_name'] = data.exp_name + + state_path = os.path.join(path, 'trainer_state.pt') + torch.save(state, state_path + '.tmp') + os.rename(state_path + '.tmp', state_path) + + return model_path + +def seed_everything(seed, torch_deterministic): + random.seed(seed) + np.random.seed(seed) + if seed is not None: + torch.manual_seed(seed) + torch.backends.cudnn.deterministic = torch_deterministic + +def unroll_nested_dict(d): + if not isinstance(d, dict): + return d + + for k, v in d.items(): + if isinstance(v, dict): + for k2, v2 in unroll_nested_dict(v): + yield f"{k}/{k2}", v2 + else: + yield k, v + +def print_dashboard(stats, init_performance, performance): + output = [] + data = {**stats, **init_performance, **performance} + + grouped_data = defaultdict(dict) + + for k, v in data.items(): + if k == 'total_uptime': + v = timedelta(seconds=v) + if 'memory' in k: + v = pufferlib.utils.format_bytes(v) + elif 'time' in k: + try: + v = f"{v:.2f} s" + except: + pass + + first_word, *rest_words = k.split('_') + rest_words = ' '.join(rest_words).title() + + grouped_data[first_word][rest_words] = v + + for main_key, sub_dict in grouped_data.items(): + output.append(f"{main_key.title()}") + for sub_key, sub_value in sub_dict.items(): + output.append(f" {sub_key}: {sub_value}") + + print("\033c", end="") + print('\n'.join(output)) + time.sleep(1/20) diff --git a/pokegym/config.yaml b/pokegym/config.yaml new file mode 100644 index 0000000..986c7a5 --- /dev/null +++ b/pokegym/config.yaml @@ -0,0 +1,84 @@ +train: + seed: 1 + torch_deterministic: True + device: cuda + total_timesteps: 10_000_000 + learning_rate: 2.5e-4 + num_steps: 128 + anneal_lr: True + gamma: 0.99 + gae_lambda: 0.95 + num_minibatches: 4 + update_epochs: 4 + norm_adv: True + clip_coef: 0.1 + clip_vloss: True + ent_coef: 0.01 + vf_coef: 0.5 + max_grad_norm: 0.5 + target_kl: ~ + + num_envs: 8 + envs_per_worker: 1 + envs_per_batch: ~ + env_pool: True + verbose: True + data_dir: experiments + checkpoint_interval: 200 + pool_kernel: [0] + batch_size: 1024 + batch_rows: 32 + bptt_horizon: 16 #8 + vf_clip_coef: 0.1 + compile: False + compile_mode: reduce-overhead + +sweep: + method: random + name: sweep + metric: + goal: maximize + name: episodic_return + # Nested parameters name required by WandB API + parameters: + train: + parameters: + learning_rate: { + 'distribution': 'log_uniform_values', + 'min': 1e-4, + 'max': 1e-1, + } + batch_size: { + 'values': [128, 256, 512, 1024, 2048], + } + batch_rows: { + 'values': [16, 32, 64, 128, 256], + } + bptt_horizon: { + 'values': [4, 8, 16, 32], + } + +pokemon_red: + package: pokemon_red + train: + total_timesteps: 1_500_000_000 + num_envs: 96 + envs_per_worker: 1 + envs_per_batch: 32 + update_epochs: 3 + gamma: 0.998 + batch_size: 65536 + batch_rows: 128 + compile: True + learning_rate: 2.0e-4 + anneal_lr: False + env: + name: pokemon_red +pokemon-red: + package: pokemon_red +pokemonred: + package: pokemon_red +pokemon: + package: pokemon_red +pokegym: + package: pokemon_red diff --git a/pokegym/pokemon_red/__init__.py b/pokegym/pokemon_red/__init__.py new file mode 100644 index 0000000..eff86ef --- /dev/null +++ b/pokegym/pokemon_red/__init__.py @@ -0,0 +1,12 @@ +from .environment import env_creator, make + +try: + import torch +except ImportError: + pass +else: + from .torch import Policy + try: + from .torch import Recurrent + except: + Recurrent = None diff --git a/pokegym/pokemon_red/environment.py b/pokegym/pokemon_red/environment.py new file mode 100644 index 0000000..1f16995 --- /dev/null +++ b/pokegym/pokemon_red/environment.py @@ -0,0 +1,23 @@ +from pdb import set_trace as T + +import gymnasium +import functools +import uuid + +from pokegym import Environment +import pufferlib.emulation +from .stream_wrapper import StreamWrapper + + + +def env_creator(name='pokemon_red'): + return functools.partial(make, name) + +def make(name, headless: bool = True, state_path=None): + '''Pokemon Red''' + env = Environment(headless=headless, state_path=state_path) + env = StreamWrapper( + env, + stream_metadata = {"user": f"Username\n"}) # username here + return pufferlib.emulation.GymnasiumPufferEnv(env=env, + postprocessor_cls=pufferlib.emulation.BasicPostprocessor) \ No newline at end of file diff --git a/pokegym/pokemon_red/stream_wrapper.py b/pokegym/pokemon_red/stream_wrapper.py new file mode 100644 index 0000000..3aaddcc --- /dev/null +++ b/pokegym/pokemon_red/stream_wrapper.py @@ -0,0 +1,101 @@ +import asyncio +import websockets +import json + +import gymnasium as gym +from pokegym import ram_map, data + +#colors +BLUE = "#0000FF" +GREEN = "#00A36C" +RED = "#FF0000" +PURPLE = "#800080" +PINK = "#FF00FF" +YELLOW = "#DAEE01" + +POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] +LEVEL = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] +X_POS_ADDRESS, Y_POS_ADDRESS = 0xD362, 0xD361 +MAP_N_ADDRESS = 0xD35E + +class StreamWrapper(gym.Wrapper): + def __init__(self, env, stream_metadata={}): + super().__init__(env) + self.ws_address = "wss://transdimensional.xyz/broadcast" # "ws://theleanke.com/broadcast" + self.stream_metadata = stream_metadata + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + self.websocket = self.loop.run_until_complete( + self.establish_wc_connection() + ) + self.upload_interval = 500 + self.steam_step_counter = 0 + self.env = env + self.coord_list = [] + self.leanke = {} + self.cut = 0 + + if hasattr(env, "pyboy"): + self.emulator = env.pyboy + elif hasattr(env, "game"): + self.emulator = env.game + else: + raise Exception("Could not find emulator!") + + + def step(self, action): + + x_pos = self.emulator.get_memory_value(X_POS_ADDRESS) + y_pos = self.emulator.get_memory_value(Y_POS_ADDRESS) + map_n = self.emulator.get_memory_value(MAP_N_ADDRESS) + hm = self.env.hm_count + self.coord_list.append([x_pos, y_pos, map_n]) + self.cut = self.env.cut + + poke0 = self.emulator.get_memory_value(POKE[0]) + lvl0 = self.emulator.get_memory_value(LEVEL[0]) + name_info6 = data.poke_dict.get(poke0, {}) + name0 = name_info6.get("name", "") + + if self.steam_step_counter >= self.upload_interval: + + if self.cut >= 1: + self.stream_metadata['color'] = GREEN + elif hm >= 1 and self.cut == 0: + self.stream_metadata['color'] = YELLOW + elif hm == 0 and self.cut == 0: + self.stream_metadata['color'] = RED + if int(ram_map.read_bit(self.emulator, 0xD76C, 0)) == 1: + self.stream_metadata['color'] = PURPLE + self.stream_metadata['extra'] = f"{self.env.reset_count} ~ {name0}: {lvl0}" + + self.loop.run_until_complete( + self.broadcast_ws_message( + json.dumps( + { + "metadata": self.stream_metadata, + "coords": self.coord_list, + }))) + + self.steam_step_counter = 0 + self.coord_list = [] + + self.steam_step_counter += 1 + + return self.env.step(action) + + async def broadcast_ws_message(self, message): + if self.websocket is None: + await self.establish_wc_connection() + if self.websocket is not None: + try: + await self.websocket.send(message) + except websockets.exceptions.WebSocketException as e: + self.websocket = None + + async def establish_wc_connection(self): + try: + self.websocket = await websockets.connect(self.ws_address) + except: + self.websocket = None + diff --git a/pokegym/pokemon_red/torch.py b/pokegym/pokemon_red/torch.py new file mode 100644 index 0000000..acb51b3 --- /dev/null +++ b/pokegym/pokemon_red/torch.py @@ -0,0 +1,29 @@ +import pufferlib.models + + +class Recurrent(pufferlib.models.RecurrentWrapper): + def __init__(self, env, policy, input_size=512, hidden_size=512, num_layers=1): + super().__init__(env, policy, input_size, hidden_size, num_layers) + +class Policy(pufferlib.models.Convolutional): + def __init__(self, env, input_size=512, hidden_size=512, output_size=512, + framestack=4, flat_size=64*5*6): + super().__init__( + env=env, + input_size=input_size, + hidden_size=hidden_size, + output_size=output_size, + framestack=framestack, + flat_size=flat_size, + channels_last=True, + ) + +''' +class Policy(pufferlib.models.ProcgenResnet): + def __init__(self, env, cnn_width=16, mlp_width=512): + super().__init__( + env=env, + cnn_width=cnn_width, + mlp_width=mlp_width, + ) +''' diff --git a/pokegym/run.sh b/pokegym/run.sh new file mode 100755 index 0000000..8b59b59 --- /dev/null +++ b/pokegym/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python train.py --config pokemon_red --vectorization multiprocessing --mode train # --wandb-entity WANDBUSERNAME --track \ No newline at end of file diff --git a/pokegym/train.py b/pokegym/train.py new file mode 100644 index 0000000..6ab0ec4 --- /dev/null +++ b/pokegym/train.py @@ -0,0 +1,260 @@ +from pdb import set_trace as T +import argparse +import shutil +import sys +import os + +import importlib +import inspect +import yaml + +import pufferlib +import pufferlib.utils + +import clean_pufferl + + +def load_from_config(env): + with open('config.yaml') as f: + config = yaml.safe_load(f) + + assert env in config, f'"{env}" not found in config.yaml. Uncommon environments that are part of larger packages may not have their own config. Specify these manually using the parent package, e.g. --config atari --env MontezumasRevengeNoFrameskip-v4.' + + default_keys = 'env train policy recurrent sweep_metadata sweep_metric sweep'.split() + defaults = {key: config.get(key, {}) for key in default_keys} + + # Package and subpackage (environment) configs + env_config = config[env] + pkg = env_config['package'] + pkg_config = config[pkg] + + combined_config = {} + for key in default_keys: + env_subconfig = env_config.get(key, {}) + pkg_subconfig = pkg_config.get(key, {}) + + # Override first with pkg then with env configs + combined_config[key] = {**defaults[key], **pkg_subconfig, **env_subconfig} + + return pkg, pufferlib.namespace(**combined_config) + +def make_policy(env, env_module, args): + policy = env_module.Policy(env, **args.policy) + if args.force_recurrence or env_module.Recurrent is not None: + policy = env_module.Recurrent(env, policy, **args.recurrent) + policy = pufferlib.frameworks.cleanrl.RecurrentPolicy(policy) + else: + policy = pufferlib.frameworks.cleanrl.Policy(policy) + + return policy.to(args.train.device) + +def init_wandb(args, env_module, name=None, resume=True): + #os.environ["WANDB_SILENT"] = "true" + + import wandb + return wandb.init( + id=args.exp_name or wandb.util.generate_id(), + project=args.wandb_project, + entity=args.wandb_entity, + group=args.wandb_group, + config={ + 'cleanrl': args.train, + 'env': args.env, + 'policy': args.policy, + 'recurrent': args.recurrent, + }, + name=name or args.config, + monitor_gym=True, + save_code=True, + resume=resume, + ) + +def sweep(args, env_module, make_env): + import wandb + sweep_id = wandb.sweep(sweep=args.sweep, project="pufferlib") + + def main(): + try: + args.exp_name = init_wandb(args, env_module) + if hasattr(wandb.config, 'train'): + # TODO: Add update method to namespace + print(args.train.__dict__) + print(wandb.config.train) + args.train.__dict__.update(dict(wandb.config.train)) + train(args, env_module, make_env) + except Exception as e: + import traceback + traceback.print_exc() + + wandb.agent(sweep_id, main, count=20) + +def get_init_args(fn): + if fn is None: + return {} + + sig = inspect.signature(fn) + args = {} + for name, param in sig.parameters.items(): + if name in ('self', 'env', 'policy'): + continue + if param.kind == inspect.Parameter.VAR_POSITIONAL: + continue + elif param.kind == inspect.Parameter.VAR_KEYWORD: + continue + else: + args[name] = param.default if param.default is not inspect.Parameter.empty else None + return args + +def train(args, env_module, make_env): + if args.backend == 'clean_pufferl': + data = clean_pufferl.create( + config=args.train, + agent_creator=make_policy, + agent_kwargs={'env_module': env_module, 'args': args}, + env_creator=make_env, + env_creator_kwargs=args.env, + vectorization=args.vectorization, + exp_name=args.exp_name, + track=args.track, + ) + + while not clean_pufferl.done_training(data): + clean_pufferl.evaluate(data) + clean_pufferl.train(data) + + print('Done training. Saving data...') + clean_pufferl.close(data) + print('Run complete') + elif args.backend == 'sb3': + from stable_baselines3 import PPO + from stable_baselines3.common.vec_env import DummyVecEnv, SubprocVecEnv + from stable_baselines3.common.env_util import make_vec_env + from sb3_contrib import RecurrentPPO + + envs = make_vec_env(lambda: make_env(**args.env), + n_envs=args.train.num_envs, seed=args.train.seed, vec_env_cls=DummyVecEnv) + + model = RecurrentPPO("CnnLstmPolicy", envs, verbose=1, + n_steps=args.train.batch_rows*args.train.bptt_horizon, + batch_size=args.train.batch_size, n_epochs=args.train.update_epochs, + gamma=args.train.gamma + ) + + model.learn(total_timesteps=args.train.total_timesteps) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Parse environment argument', add_help=False) + parser.add_argument('--backend', type=str, default='clean_pufferl', help='Train backend (clean_pufferl, sb3)') + parser.add_argument('--config', type=str, default='pokemon_red', help='Configuration in config.yaml to use') + parser.add_argument('--env', type=str, default=None, help='Name of specific environment to run') + parser.add_argument('--mode', type=str, default='train', choices='train sweep evaluate'.split()) + parser.add_argument('--eval-model-path', type=str, default=None, help='Path to model to evaluate') + parser.add_argument('--baseline', action='store_true', help='Baseline run') + parser.add_argument('--no-render', action='store_true', help='Disable render during evaluate') + parser.add_argument('--exp-name', type=str, default=None, help="Resume from experiment") + parser.add_argument('--vectorization', type=str, default='serial', choices='serial multiprocessing ray'.split()) + parser.add_argument('--wandb-entity', type=str, default='jsuarez', help='WandB entity') + parser.add_argument('--wandb-project', type=str, default='pufferlib', help='WandB project') + parser.add_argument('--wandb-group', type=str, default='debug', help='WandB group') + parser.add_argument('--track', action='store_true', help='Track on WandB') + parser.add_argument('--force-recurrence', action='store_true', help='Force model to be recurrent, regardless of defaults') + + clean_parser = argparse.ArgumentParser(parents=[parser]) + args = parser.parse_known_args()[0].__dict__ + pkg, config = load_from_config(args['config']) + + try: + env_module = importlib.import_module(f'{pkg}') + except: + pufferlib.utils.install_requirements(pkg) + env_module = importlib.import_module(f'{pkg}') + + # Get the make function for the environment + env_name = args['env'] or config.env.pop('name') + make_env = env_module.env_creator(env_name) + + # Update config with environment defaults + config.env = {**get_init_args(make_env), **config.env} + config.policy = {**get_init_args(env_module.Policy.__init__), **config.policy} + config.recurrent = {**get_init_args(env_module.Recurrent.__init__), **config.recurrent} + + # Generate argparse menu from config + for name, sub_config in config.items(): + args[name] = {} + for key, value in sub_config.items(): + data_key = f'{name}.{key}' + cli_key = f'--{data_key}'.replace('_', '-') + if isinstance(value, bool) and value is False: + action = 'store_false' + parser.add_argument(cli_key, default=value, action='store_true') + clean_parser.add_argument(cli_key, default=value, action='store_true') + elif isinstance(value, bool) and value is True: + data_key = f'{name}.no_{key}' + cli_key = f'--{data_key}'.replace('_', '-') + parser.add_argument(cli_key, default=value, action='store_false') + clean_parser.add_argument(cli_key, default=value, action='store_false') + else: + parser.add_argument(cli_key, default=value, type=type(value)) + clean_parser.add_argument(cli_key, default=value, metavar='', type=type(value)) + + args[name][key] = getattr(parser.parse_known_args()[0], data_key) + args[name] = pufferlib.namespace(**args[name]) + + clean_parser.parse_args(sys.argv[1:]) + args = pufferlib.namespace(**args) + + vec = args.vectorization + if vec == 'serial': + args.vectorization = pufferlib.vectorization.Serial + elif vec == 'multiprocessing': + args.vectorization = pufferlib.vectorization.Multiprocessing + elif vec == 'ray': + args.vectorization = pufferlib.vectorization.Ray + else: + raise ValueError(f'Invalid --vectorization (serial/multiprocessing/ray).') + + if args.mode == 'sweep': + args.track = True + elif args.track: + args.exp_name = init_wandb(args, env_module).id + elif args.baseline: + args.track = True + version = '.'.join(pufferlib.__version__.split('.')[:2]) + args.exp_name = f'puf-{version}-{args.config}' + args.wandb_group = f'puf-{version}-baseline' + shutil.rmtree(f'experiments/{args.exp_name}', ignore_errors=True) + run = init_wandb(args, env_module, name=args.exp_name, resume=False) + if args.mode == 'evaluate': + model_name = f'puf-{version}-{args.config}_model:latest' + artifact = run.use_artifact(model_name) + data_dir = artifact.download() + model_file = max(os.listdir(data_dir)) + args.eval_model_path = os.path.join(data_dir, model_file) + + if args.mode == 'train': + train(args, env_module, make_env) + exit(0) + elif args.mode == 'sweep': + sweep(args, env_module, make_env) + exit(0) + elif args.mode == 'evaluate' and pkg != 'pokemon_red': + rollout( + make_env, + args.env, + agent_creator=make_policy, + agent_kwargs={'env_module': env_module, 'args': args}, + model_path=args.eval_model_path, + device=args.train.device + ) + elif args.mode == 'evaluate' and pkg == 'pokemon_red': + import pokemon_red_eval + pokemon_red_eval.rollout( + make_env, + args.env, + agent_creator=make_policy, + agent_kwargs={'env_module': env_module, 'args': args}, + model_path=args.eval_model_path, + device=args.train.device, + ) + elif pkg != 'pokemon_red': + raise ValueError('Mode must be one of train, sweep, or evaluate') diff --git a/setup.py b/setup.py index 4abf195..a5505ce 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,10 @@ from setuptools import find_packages, setup from itertools import chain + +GYMNASIUM_VERSION = '0.29.1' +GYM_VERSION = '0.23' + setup( name="pokegym", description="Pokemon Red Gymnasium environment for reinforcement learning", @@ -9,9 +13,32 @@ packages=find_packages(), include_package_data=True, install_requires=[ + 'numpy==1.23.3', + 'opencv-python==3.4.17.63', + 'cython==3.0.0', + 'websockets', + 'PyYAML', + 'filelock', + 'psutil', + 'pettingzoo', + f'gym=={GYM_VERSION}', + f'gymnasium=={GYMNASIUM_VERSION}', + 'einops==0.6.1', + 'matplotlib', + 'scikit-image==0.21.0', 'pyboy<2.0.0', - 'gymnasium>=0.29', - 'numpy', + 'hnswlib==0.7.0', + 'mediapy', + 'pandas==2.0.2', + 'ray[all]==2.0.0', + 'setproctitle==1.1.10', + 'service-identity==21.1.0', + 'pydantic==1.9', + 'tensorboard==2.11.2', + 'torch', + 'wandb==0.13.7', + 'psutil==5.9.5', + 'tyro', ], entry_points = { 'console_scripts': [ From 8c26d36f711a15aeb874d6570dd0e4b0cd71cdb9 Mon Sep 17 00:00:00 2001 From: Leanke <49460411+leanke@users.noreply.github.com> Date: Sun, 5 May 2024 09:30:49 -0600 Subject: [PATCH 18/29] Update setup.py req --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index a5505ce..ba657d6 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ 'wandb==0.13.7', 'psutil==5.9.5', 'tyro', + 'pufferlib==0.7.3', ], entry_points = { 'console_scripts': [ From 37712c85916a5b67ba8969b04e9f02222c747d49 Mon Sep 17 00:00:00 2001 From: Leanke <49460411+leanke@users.noreply.github.com> Date: Sun, 5 May 2024 09:42:22 -0600 Subject: [PATCH 19/29] StreamWrapper --- pokegym/pokemon_red/environment.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pokegym/pokemon_red/environment.py b/pokegym/pokemon_red/environment.py index 1f16995..87957d9 100644 --- a/pokegym/pokemon_red/environment.py +++ b/pokegym/pokemon_red/environment.py @@ -16,8 +16,11 @@ def env_creator(name='pokemon_red'): def make(name, headless: bool = True, state_path=None): '''Pokemon Red''' env = Environment(headless=headless, state_path=state_path) - env = StreamWrapper( - env, - stream_metadata = {"user": f"Username\n"}) # username here + env = StreamWrapper(env, stream_metadata = { # stream_metadata is optional + "user": f"username\n", # your username + "color": "", # color for your text :) + "extra": "", # any extra text you put here will be displayed + } + ) return pufferlib.emulation.GymnasiumPufferEnv(env=env, - postprocessor_cls=pufferlib.emulation.BasicPostprocessor) \ No newline at end of file + postprocessor_cls=pufferlib.emulation.BasicPostprocessor) From 5a89986c5df67fb93f8c3d89d397c75e98e77e66 Mon Sep 17 00:00:00 2001 From: leanke Date: Mon, 6 May 2024 00:26:28 +0000 Subject: [PATCH 20/29] env fix --- pokegym/environment.py | 197 +++++++++++++++++++++++++++++++---------- 1 file changed, 149 insertions(+), 48 deletions(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index 55cb5e7..eb4e97f 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -195,13 +195,13 @@ def __init__(self,rom_path="pokemon_red.gb",state_path=None,headless=True,save_v self.last_map = -1 self.log = True self.cut_reset = 0 + self.poketower = [142, 143, 144, 145, 146, 147, 148] + self.pokehideout = [199, 200, 201, 202, 203] + self.silphco = [181, 207, 208, 209, 210, 211, 212, 213, 233, 234, 235, 236] # self.seen_coords = set() self.map_check = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] coord_rewards = [] - - def add_video_frame(self): - self.full_frame_writer.add_image(self.video()) def save_to_database(self): db_dir = self.db_path @@ -228,15 +228,43 @@ def read_database(self): conn.close() return percentage + def update_pokedex(self): + for i in range(0xD30A - 0xD2F7): + caught_mem = self.game.get_memory_value(i + 0xD2F7) + seen_mem = self.game.get_memory_value(i + 0xD30A) + for j in range(8): + self.caught_pokemon[8*i + j] = 1 if caught_mem & (1 << j) else 0 + self.seen_pokemon[8*i + j] = 1 if seen_mem & (1 << j) else 0 + + def update_moves_obtained(self): + # Scan party + for i in [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247]: + if self.game.get_memory_value(i) != 0: + for j in range(4): + move_id = self.game.get_memory_value(i + j + 8) + if move_id != 0: + if move_id != 0: + self.moves_obtained[move_id] = 1 + if move_id == 15: + self.cut = 1 + # Scan current box (since the box doesn't auto increment in pokemon red) + num_moves = 4 + box_struct_length = 25 * num_moves * 2 + for i in range(self.game.get_memory_value(0xda80)): + offset = i*box_struct_length + 0xda96 + if self.game.get_memory_value(offset) != 0: + for j in range(4): + move_id = self.game.get_memory_value(offset + j + 8) + if move_id != 0: + self.moves_obtained[move_id] = 1 + + def add_video_frame(self): + self.full_frame_writer.add_image(self.video()) + def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" - # if self.reset_count % 10 == 0: - # load_pyboy_state(self.game, self.load_first_state()) - # self.seen_coords = set() - # full reset - # if self.reset_count % 51 == 0 and self.cut_reset == 0: - # load_pyboy_state(self.game, self.load_first_state()) # full reset if no cut at 100m total step 1m agent step - + self.reset_count += 1 + if self.save_video: base_dir = self.s_path base_dir.mkdir(parents=True, exist_ok=True) @@ -254,7 +282,6 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.reward_scale = reward_scale self.last_reward = None - self.reset_count += 1 self.prev_map_n = None self.max_events = 0 self.max_level_sum = 0 @@ -265,6 +292,7 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.last_hp = 1.0 self.last_party_size = 1 self.hm_count = 0 + self.cut = 0 self.cut_coords = {} self.cut_tiles = {} # set([]) self.cut_state = deque(maxlen=3) @@ -277,13 +305,11 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.caught_pokemon = np.zeros(152, dtype=np.uint8) self.moves_obtained = {} # np.zeros(255, dtype=np.uint8) self.town = 1 - self.used_cut = 0 self.gymthree = 0 self.gymfour = 0 + self.used_cut = 0 self.death_count = 0 - self.expl = 0 - self.cut = 0 - self.respawn = set() + return self.render(), {} def step(self, action, fast_video=True): @@ -295,8 +321,49 @@ def step(self, action, fast_video=True): # Exploration r, c, map_n = ram_map.position(self.game) # this is [y, x, z] + # Exploration reward self.seen_coords.add((r, c, map_n)) - exploration = ram_map.explore(self.game, self.seen_coords, map_n) + if int(ram_map.read_bit(self.game, 0xD81B, 7)) == 0: # pre hideout + if map_n in self.poketower: + self.exploration_reward = 0 + elif map_n in self.pokehideout: + self.exploration_reward = (0.03 * len(self.seen_coords)) + else: + self.exploration_reward = (0.02 * len(self.seen_coords)) + elif int(ram_map.read_bit(self.game, 0xD7E0, 7)) == 0 and int(ram_map.read_bit(self.game, 0xD81B, 7)) == 1: # hideout done poketower not done + if map_n in self.poketower: + self.exploration_reward = (0.03 * len(self.seen_coords)) + else: + self.exploration_reward = (0.02 * len(self.seen_coords)) + elif int(ram_map.read_bit(self.game, 0xD76C, 0)) == 0 and int(ram_map.read_bit(self.game, 0xD7E0, 7)) == 1: # tower done no flute + if map_n == 149: + self.exploration_reward = (0.03 * len(self.seen_coords)) + elif map_n in self.poketower: + self.exploration_reward = (0.01 * len(self.seen_coords)) + elif map_n in self.pokehideout: + self.exploration_reward = (0.01 * len(self.seen_coords)) + else: + self.exploration_reward = (0.02 * len(self.seen_coords)) + elif int(ram_map.read_bit(self.game, 0xD838, 7)) == 0 and int(ram_map.read_bit(self.game, 0xD76C, 0)) == 1: # flute gotten pre silphco + if map_n in self.silphco: + self.exploration_reward = (0.03 * len(self.seen_coords)) + elif map_n in self.poketower: + self.exploration_reward = (0.01 * len(self.seen_coords)) + elif map_n in self.pokehideout: + self.exploration_reward = (0.01 * len(self.seen_coords)) + else: + self.exploration_reward = (0.02 * len(self.seen_coords)) + elif int(ram_map.read_bit(self.game, 0xD838, 7)) == 1 and int(ram_map.read_bit(self.game, 0xD76C, 0)) == 1: # flute gotten post silphco + if map_n in self.silphco: + self.exploration_reward = (0.01 * len(self.seen_coords)) + elif map_n in self.poketower: + self.exploration_reward = (0.01 * len(self.seen_coords)) + elif map_n in self.pokehideout: + self.exploration_reward = (0.01 * len(self.seen_coords)) + else: + self.exploration_reward = (0.02 * len(self.seen_coords)) + else: + self.exploration_reward = (0.02 * len(self.seen_coords)) if map_n == 92: self.gymthree = 1 @@ -327,26 +394,65 @@ def step(self, action, fast_video=True): death_reward = 0 # -0.08 * self.death_count # -0.05 healing_reward = self.total_healing + # HM reward + hm_count = ram_map.get_hm_count(self.game) + if hm_count >= 1 and self.hm_count == 0: + self.hm_count = 1 + # hm_reward = hm_count * 10 + + # Cut check - if self.cut == 1: - cut_coords, cut_tiles, seen_start_menu, seen_pokemon_menu, seen_stats_menu, seen_bag_menu = ram_map.cut_array(self.game) - self.cut_coords = cut_coords - self.cut_tiles = cut_tiles - self.seen_start_menu = seen_start_menu - self.seen_pokemon_menu = seen_pokemon_menu - self.seen_stats_menu = seen_stats_menu - self.seen_bag_menu = seen_bag_menu + # 0xCFC6 - wTileInFrontOfPlayer + # 0xCFCB - wUpdateSpritesEnabled + if ram_map.mem_val(self.game, 0xD057) == 0: # is_in_battle if 1 + if self.cut == 1: + player_direction = self.game.get_memory_value(0xC109) + y, x, map_id = ram_map.position(self.game) # this is [y, x, z] # x, y, map_id + if player_direction == 0: # down + coords = (x, y + 1, map_id) + if player_direction == 4: + coords = (x, y - 1, map_id) + if player_direction == 8: + coords = (x - 1, y, map_id) + if player_direction == 0xC: + coords = (x + 1, y, map_id) + self.cut_state.append( + ( + self.game.get_memory_value(0xCFC6), + self.game.get_memory_value(0xCFCB), + self.game.get_memory_value(0xCD6A), + self.game.get_memory_value(0xD367), + self.game.get_memory_value(0xD125), + self.game.get_memory_value(0xCD3D), + ) + ) + if tuple(list(self.cut_state)[1:]) in CUT_SEQ: + self.cut_coords[coords] = 5 # from 14 + self.cut_tiles[self.cut_state[-1][0]] = 1 + elif self.cut_state == CUT_GRASS_SEQ: + self.cut_coords[coords] = 0.001 + self.cut_tiles[self.cut_state[-1][0]] = 1 + elif deque([(-1, *elem[1:]) for elem in self.cut_state]) == CUT_FAIL_SEQ: + self.cut_coords[coords] = 0.001 + self.cut_tiles[self.cut_state[-1][0]] = 1 + if int(ram_map.read_bit(self.game, 0xD803, 0)): + if ram_map.check_if_in_start_menu(self.game): + self.seen_start_menu = 1 + if ram_map.check_if_in_pokemon_menu(self.game): + self.seen_pokemon_menu = 1 + if ram_map.check_if_in_stats_menu(self.game): + self.seen_stats_menu = 1 + if ram_map.check_if_in_bag_menu(self.game): + self.seen_bag_menu = 1 if ram_map.used_cut(self.game): self.used_cut += 1 # Misc badges = ram_map.badges(self.game) - seen_pokemon, caught_pokemon = ram_map.update_pokedex(self.game) - moves_obtained, cut = ram_map.update_moves_obtained(self.game) - self.cut = cut - - hm_count = ram_map.get_hm_count(self.game) + self.update_pokedex() + self.update_moves_obtained() + silph = ram_map.silph_co(self.game) rock_tunnel = ram_map.rock_tunnel(self.game) ssanne = ram_map.ssanne(self.game) @@ -373,17 +479,16 @@ def step(self, action, fast_video=True): gym7 = ram_map.gym7(self.game) gym8 = ram_map.gym8(self.game) rival = ram_map.rival(self.game) - - exploration_reward = exploration + exploration_reward = self.exploration_reward cut_rew = self.cut * 10 event_reward = sum([silph, rock_tunnel, ssanne, mtmoon, routes, misc, snorlax, hmtm, bill, oak, towns, lab, mansion, safari, dojo, hideout, tower, gym1, gym2, gym3, gym4, gym5, gym6, gym7, gym8, rival]) - seen_pokemon_reward = self.reward_scale * seen_pokemon - caught_pokemon_reward = self.reward_scale * caught_pokemon - moves_obtained_reward = self.reward_scale * moves_obtained - used_cut_rew = self.used_cut * 0.03 - cut_coords_rew = sum(self.cut_coords.values()) * 1.0 - cut_tiles_rew = len(self.cut_tiles) * 1.0 + seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) + caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) + moves_obtained_reward = self.reward_scale * sum(self.moves_obtained) + used_cut_rew = self.used_cut * 0.1 + cut_coords = sum(self.cut_coords.values()) * 1.0 + cut_tiles = len(self.cut_tiles) * 1.0 start_menu = self.seen_start_menu * 0.01 pokemon_menu = self.seen_pokemon_menu * 0.1 stats_menu = self.seen_stats_menu * 0.1 @@ -400,8 +505,8 @@ def step(self, action, fast_video=True): + caught_pokemon_reward + moves_obtained_reward + used_cut_rew - + cut_coords_rew - + cut_tiles_rew + + cut_coords + + cut_tiles + that_guy ) @@ -420,10 +525,6 @@ def step(self, action, fast_video=True): if self.save_video and done: self.full_frame_writer.close() if done: - state = io.BytesIO() - self.game.save_state(state) - state.seek(0) - info["state"] = state.read() # self.save_to_database() poke = self.game.get_memory_value(0xD16B) level = self.game.get_memory_value(0xD18C) @@ -479,8 +580,8 @@ def step(self, action, fast_video=True): "Taught_Cut": cut_rew, "Menuing": that_guy, "Used_Cut": used_cut_rew, - "Cut_Coords": cut_coords_rew, - "Cut_Tiles": cut_tiles_rew, + "Cut_Coords": cut_coords, + "Cut_Tiles": cut_tiles, # "Bulba_Check": bulba_check, # "Respawn": respawn_reward }, @@ -498,9 +599,9 @@ def step(self, action, fast_video=True): "maps_explored": np.sum(self.seen_maps), "party_size": party_size, "moves_obtained": sum(self.moves_obtained), - "deaths": self.death_count, - 'cut_coords': cut_coords_rew, - 'cut_tiles': cut_tiles_rew, + # "deaths": self.death_count, + 'cut_coords': cut_coords, + 'cut_tiles': cut_tiles, 'bag_menu': bag_menu, 'stats_menu': stats_menu, 'pokemon_menu': pokemon_menu, From 1f1c8f3e4a149a67b7779e1b101bcd2eed5343a2 Mon Sep 17 00:00:00 2001 From: leanke Date: Mon, 6 May 2024 02:53:12 +0000 Subject: [PATCH 21/29] toml --- pokegym/clean_pufferl.py => clean_pufferl.py | 0 pokegym/config.yaml => config.yaml | 0 pokegym/environment.py | 5 +- pokemon_red/__init__.py | 12 +++ pokemon_red/environment.py | 26 +++++ pokemon_red/stream_wrapper.py | 101 +++++++++++++++++++ pokemon_red/torch.py | 29 ++++++ pyproject.toml | 72 +++++++++++++ pokegym/run.sh => run.sh | 0 setup.py | 63 ------------ test.py | 33 ------ pokegym/train.py => train.py | 0 12 files changed, 241 insertions(+), 100 deletions(-) rename pokegym/clean_pufferl.py => clean_pufferl.py (100%) rename pokegym/config.yaml => config.yaml (100%) create mode 100644 pokemon_red/__init__.py create mode 100644 pokemon_red/environment.py create mode 100644 pokemon_red/stream_wrapper.py create mode 100644 pokemon_red/torch.py create mode 100644 pyproject.toml rename pokegym/run.sh => run.sh (100%) delete mode 100644 setup.py delete mode 100644 test.py rename pokegym/train.py => train.py (100%) diff --git a/pokegym/clean_pufferl.py b/clean_pufferl.py similarity index 100% rename from pokegym/clean_pufferl.py rename to clean_pufferl.py diff --git a/pokegym/config.yaml b/config.yaml similarity index 100% rename from pokegym/config.yaml rename to config.yaml diff --git a/pokegym/environment.py b/pokegym/environment.py index eb4e97f..1203121 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -2,16 +2,13 @@ from pathlib import Path from pdb import set_trace as T import sqlite3 -import types import uuid -from gymnasium import Env, spaces +from gymnasium import spaces import numpy as np -from skimage.transform import resize from collections import defaultdict, deque import io, os import random -from pyboy.utils import WindowEvent import matplotlib.pyplot as plt from pathlib import Path diff --git a/pokemon_red/__init__.py b/pokemon_red/__init__.py new file mode 100644 index 0000000..eff86ef --- /dev/null +++ b/pokemon_red/__init__.py @@ -0,0 +1,12 @@ +from .environment import env_creator, make + +try: + import torch +except ImportError: + pass +else: + from .torch import Policy + try: + from .torch import Recurrent + except: + Recurrent = None diff --git a/pokemon_red/environment.py b/pokemon_red/environment.py new file mode 100644 index 0000000..87957d9 --- /dev/null +++ b/pokemon_red/environment.py @@ -0,0 +1,26 @@ +from pdb import set_trace as T + +import gymnasium +import functools +import uuid + +from pokegym import Environment +import pufferlib.emulation +from .stream_wrapper import StreamWrapper + + + +def env_creator(name='pokemon_red'): + return functools.partial(make, name) + +def make(name, headless: bool = True, state_path=None): + '''Pokemon Red''' + env = Environment(headless=headless, state_path=state_path) + env = StreamWrapper(env, stream_metadata = { # stream_metadata is optional + "user": f"username\n", # your username + "color": "", # color for your text :) + "extra": "", # any extra text you put here will be displayed + } + ) + return pufferlib.emulation.GymnasiumPufferEnv(env=env, + postprocessor_cls=pufferlib.emulation.BasicPostprocessor) diff --git a/pokemon_red/stream_wrapper.py b/pokemon_red/stream_wrapper.py new file mode 100644 index 0000000..3aaddcc --- /dev/null +++ b/pokemon_red/stream_wrapper.py @@ -0,0 +1,101 @@ +import asyncio +import websockets +import json + +import gymnasium as gym +from pokegym import ram_map, data + +#colors +BLUE = "#0000FF" +GREEN = "#00A36C" +RED = "#FF0000" +PURPLE = "#800080" +PINK = "#FF00FF" +YELLOW = "#DAEE01" + +POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] +LEVEL = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] +X_POS_ADDRESS, Y_POS_ADDRESS = 0xD362, 0xD361 +MAP_N_ADDRESS = 0xD35E + +class StreamWrapper(gym.Wrapper): + def __init__(self, env, stream_metadata={}): + super().__init__(env) + self.ws_address = "wss://transdimensional.xyz/broadcast" # "ws://theleanke.com/broadcast" + self.stream_metadata = stream_metadata + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + self.websocket = self.loop.run_until_complete( + self.establish_wc_connection() + ) + self.upload_interval = 500 + self.steam_step_counter = 0 + self.env = env + self.coord_list = [] + self.leanke = {} + self.cut = 0 + + if hasattr(env, "pyboy"): + self.emulator = env.pyboy + elif hasattr(env, "game"): + self.emulator = env.game + else: + raise Exception("Could not find emulator!") + + + def step(self, action): + + x_pos = self.emulator.get_memory_value(X_POS_ADDRESS) + y_pos = self.emulator.get_memory_value(Y_POS_ADDRESS) + map_n = self.emulator.get_memory_value(MAP_N_ADDRESS) + hm = self.env.hm_count + self.coord_list.append([x_pos, y_pos, map_n]) + self.cut = self.env.cut + + poke0 = self.emulator.get_memory_value(POKE[0]) + lvl0 = self.emulator.get_memory_value(LEVEL[0]) + name_info6 = data.poke_dict.get(poke0, {}) + name0 = name_info6.get("name", "") + + if self.steam_step_counter >= self.upload_interval: + + if self.cut >= 1: + self.stream_metadata['color'] = GREEN + elif hm >= 1 and self.cut == 0: + self.stream_metadata['color'] = YELLOW + elif hm == 0 and self.cut == 0: + self.stream_metadata['color'] = RED + if int(ram_map.read_bit(self.emulator, 0xD76C, 0)) == 1: + self.stream_metadata['color'] = PURPLE + self.stream_metadata['extra'] = f"{self.env.reset_count} ~ {name0}: {lvl0}" + + self.loop.run_until_complete( + self.broadcast_ws_message( + json.dumps( + { + "metadata": self.stream_metadata, + "coords": self.coord_list, + }))) + + self.steam_step_counter = 0 + self.coord_list = [] + + self.steam_step_counter += 1 + + return self.env.step(action) + + async def broadcast_ws_message(self, message): + if self.websocket is None: + await self.establish_wc_connection() + if self.websocket is not None: + try: + await self.websocket.send(message) + except websockets.exceptions.WebSocketException as e: + self.websocket = None + + async def establish_wc_connection(self): + try: + self.websocket = await websockets.connect(self.ws_address) + except: + self.websocket = None + diff --git a/pokemon_red/torch.py b/pokemon_red/torch.py new file mode 100644 index 0000000..acb51b3 --- /dev/null +++ b/pokemon_red/torch.py @@ -0,0 +1,29 @@ +import pufferlib.models + + +class Recurrent(pufferlib.models.RecurrentWrapper): + def __init__(self, env, policy, input_size=512, hidden_size=512, num_layers=1): + super().__init__(env, policy, input_size, hidden_size, num_layers) + +class Policy(pufferlib.models.Convolutional): + def __init__(self, env, input_size=512, hidden_size=512, output_size=512, + framestack=4, flat_size=64*5*6): + super().__init__( + env=env, + input_size=input_size, + hidden_size=hidden_size, + output_size=output_size, + framestack=framestack, + flat_size=flat_size, + channels_last=True, + ) + +''' +class Policy(pufferlib.models.ProcgenResnet): + def __init__(self, env, cnn_width=16, mlp_width=512): + super().__init__( + env=env, + cnn_width=cnn_width, + mlp_width=mlp_width, + ) +''' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f6a688a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,72 @@ +[project] +name = "pokegym" +version = "0.1.8" +description = "Pokemon Red Gymnasium environment for reinforcement learning" +keywords = [] +classifiers = [ + "Natural Language :: English", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS :: MacOS X", + "Programming Language :: Python", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: Implementation :: CPython", +] +dependencies = [ + "einops", + "opencv-python", + "numpy", + "pyboy<2.0.0", + "pufferlib[cleanrl]>=0.7.3", + "torch>=2.1", + "torchvision", + "wandb", + "pettingzoo", + "gym==0.23", + "matplotlib", + "mediapy", + "websockets" +] +[tool.setuptools.packages.find] +where = ["."] + +[project.optional-dependencies] +monitoring = [ + "nvitop" +] +dev = [ + "ruff" +] + +[tool.distutils.bdist_wheel] +universal = true + +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] \ No newline at end of file diff --git a/pokegym/run.sh b/run.sh similarity index 100% rename from pokegym/run.sh rename to run.sh diff --git a/setup.py b/setup.py deleted file mode 100644 index ba657d6..0000000 --- a/setup.py +++ /dev/null @@ -1,63 +0,0 @@ -from setuptools import find_packages, setup -from itertools import chain - - -GYMNASIUM_VERSION = '0.29.1' -GYM_VERSION = '0.23' - -setup( - name="pokegym", - description="Pokemon Red Gymnasium environment for reinforcement learning", - long_description_content_type="text/markdown", - version=open('pokegym/version.py').read().split()[-1].strip("'"), - packages=find_packages(), - include_package_data=True, - install_requires=[ - 'numpy==1.23.3', - 'opencv-python==3.4.17.63', - 'cython==3.0.0', - 'websockets', - 'PyYAML', - 'filelock', - 'psutil', - 'pettingzoo', - f'gym=={GYM_VERSION}', - f'gymnasium=={GYMNASIUM_VERSION}', - 'einops==0.6.1', - 'matplotlib', - 'scikit-image==0.21.0', - 'pyboy<2.0.0', - 'hnswlib==0.7.0', - 'mediapy', - 'pandas==2.0.2', - 'ray[all]==2.0.0', - 'setproctitle==1.1.10', - 'service-identity==21.1.0', - 'pydantic==1.9', - 'tensorboard==2.11.2', - 'torch', - 'wandb==0.13.7', - 'psutil==5.9.5', - 'tyro', - 'pufferlib==0.7.3', - ], - entry_points = { - 'console_scripts': [ - 'pokegym.play = pokegym.environment:play' - ] - }, - python_requires=">=3.8", - license="MIT", - # @pdubs: Put your info here - author="Joseph Suarez", - author_email="jsuarez@mit.edu", - url="https://github.com/PufferAI/pokegym", - keywords=["Pokemon", "AI", "RL"], - classifiers=[ - "Intended Audience :: Science/Research", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - ], -) diff --git a/test.py b/test.py deleted file mode 100644 index 35add72..0000000 --- a/test.py +++ /dev/null @@ -1,33 +0,0 @@ -from pdb import set_trace as T -import gymnasium - -from pokegym import PokemonRed, PokemonRedV1 -import time - - -def play_game(steps): - game = PokemonRed(headless=False) - game.reset() - - for _ in range(steps): - game.render() - game.step(game.action_space.sample()) - -def performance_test(game_cls, steps=10000): - game = game_cls() - game.reset() - - for _ in range(1000): - game.step(0)#game.action_space.sample()) - - start = time.time() - for _ in range(steps): - game.step(0)#game.action_space.sample()) - - game.close() - end = time.time() - print('Steps per second: {}'.format(steps / (end - start))) - -if __name__ == '__main__': - performance_test(PokemonRed) - performance_test(PokemonRedV1) diff --git a/pokegym/train.py b/train.py similarity index 100% rename from pokegym/train.py rename to train.py From 159b0ca39f298ffcfb00f6f758e3e3f046526a28 Mon Sep 17 00:00:00 2001 From: Leanke <49460411+leanke@users.noreply.github.com> Date: Sun, 5 May 2024 21:20:56 -0600 Subject: [PATCH 22/29] Create README.md --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..6bc8402 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Pokegym + +Pokemon Red Gymnasium environment for reinforcement learning + +### Installation + +1. Clone the repo to your local machine and install it. +2. Fork the repo and clone your fork to your local machine. + +```sh +pip3 install -e . +``` + +### Running + +```sh +./run.sh +``` From d88b5e1c3f0d5cc5a62c0ae2911d98463ce963d2 Mon Sep 17 00:00:00 2001 From: Leanke <49460411+leanke@users.noreply.github.com> Date: Mon, 6 May 2024 01:42:16 -0600 Subject: [PATCH 23/29] Delete pokegym/pokemon_red directory --- pokegym/pokemon_red/__init__.py | 12 --- pokegym/pokemon_red/environment.py | 26 ------- pokegym/pokemon_red/stream_wrapper.py | 101 -------------------------- pokegym/pokemon_red/torch.py | 29 -------- 4 files changed, 168 deletions(-) delete mode 100644 pokegym/pokemon_red/__init__.py delete mode 100644 pokegym/pokemon_red/environment.py delete mode 100644 pokegym/pokemon_red/stream_wrapper.py delete mode 100644 pokegym/pokemon_red/torch.py diff --git a/pokegym/pokemon_red/__init__.py b/pokegym/pokemon_red/__init__.py deleted file mode 100644 index eff86ef..0000000 --- a/pokegym/pokemon_red/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from .environment import env_creator, make - -try: - import torch -except ImportError: - pass -else: - from .torch import Policy - try: - from .torch import Recurrent - except: - Recurrent = None diff --git a/pokegym/pokemon_red/environment.py b/pokegym/pokemon_red/environment.py deleted file mode 100644 index 87957d9..0000000 --- a/pokegym/pokemon_red/environment.py +++ /dev/null @@ -1,26 +0,0 @@ -from pdb import set_trace as T - -import gymnasium -import functools -import uuid - -from pokegym import Environment -import pufferlib.emulation -from .stream_wrapper import StreamWrapper - - - -def env_creator(name='pokemon_red'): - return functools.partial(make, name) - -def make(name, headless: bool = True, state_path=None): - '''Pokemon Red''' - env = Environment(headless=headless, state_path=state_path) - env = StreamWrapper(env, stream_metadata = { # stream_metadata is optional - "user": f"username\n", # your username - "color": "", # color for your text :) - "extra": "", # any extra text you put here will be displayed - } - ) - return pufferlib.emulation.GymnasiumPufferEnv(env=env, - postprocessor_cls=pufferlib.emulation.BasicPostprocessor) diff --git a/pokegym/pokemon_red/stream_wrapper.py b/pokegym/pokemon_red/stream_wrapper.py deleted file mode 100644 index 3aaddcc..0000000 --- a/pokegym/pokemon_red/stream_wrapper.py +++ /dev/null @@ -1,101 +0,0 @@ -import asyncio -import websockets -import json - -import gymnasium as gym -from pokegym import ram_map, data - -#colors -BLUE = "#0000FF" -GREEN = "#00A36C" -RED = "#FF0000" -PURPLE = "#800080" -PINK = "#FF00FF" -YELLOW = "#DAEE01" - -POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] -LEVEL = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] -X_POS_ADDRESS, Y_POS_ADDRESS = 0xD362, 0xD361 -MAP_N_ADDRESS = 0xD35E - -class StreamWrapper(gym.Wrapper): - def __init__(self, env, stream_metadata={}): - super().__init__(env) - self.ws_address = "wss://transdimensional.xyz/broadcast" # "ws://theleanke.com/broadcast" - self.stream_metadata = stream_metadata - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.loop) - self.websocket = self.loop.run_until_complete( - self.establish_wc_connection() - ) - self.upload_interval = 500 - self.steam_step_counter = 0 - self.env = env - self.coord_list = [] - self.leanke = {} - self.cut = 0 - - if hasattr(env, "pyboy"): - self.emulator = env.pyboy - elif hasattr(env, "game"): - self.emulator = env.game - else: - raise Exception("Could not find emulator!") - - - def step(self, action): - - x_pos = self.emulator.get_memory_value(X_POS_ADDRESS) - y_pos = self.emulator.get_memory_value(Y_POS_ADDRESS) - map_n = self.emulator.get_memory_value(MAP_N_ADDRESS) - hm = self.env.hm_count - self.coord_list.append([x_pos, y_pos, map_n]) - self.cut = self.env.cut - - poke0 = self.emulator.get_memory_value(POKE[0]) - lvl0 = self.emulator.get_memory_value(LEVEL[0]) - name_info6 = data.poke_dict.get(poke0, {}) - name0 = name_info6.get("name", "") - - if self.steam_step_counter >= self.upload_interval: - - if self.cut >= 1: - self.stream_metadata['color'] = GREEN - elif hm >= 1 and self.cut == 0: - self.stream_metadata['color'] = YELLOW - elif hm == 0 and self.cut == 0: - self.stream_metadata['color'] = RED - if int(ram_map.read_bit(self.emulator, 0xD76C, 0)) == 1: - self.stream_metadata['color'] = PURPLE - self.stream_metadata['extra'] = f"{self.env.reset_count} ~ {name0}: {lvl0}" - - self.loop.run_until_complete( - self.broadcast_ws_message( - json.dumps( - { - "metadata": self.stream_metadata, - "coords": self.coord_list, - }))) - - self.steam_step_counter = 0 - self.coord_list = [] - - self.steam_step_counter += 1 - - return self.env.step(action) - - async def broadcast_ws_message(self, message): - if self.websocket is None: - await self.establish_wc_connection() - if self.websocket is not None: - try: - await self.websocket.send(message) - except websockets.exceptions.WebSocketException as e: - self.websocket = None - - async def establish_wc_connection(self): - try: - self.websocket = await websockets.connect(self.ws_address) - except: - self.websocket = None - diff --git a/pokegym/pokemon_red/torch.py b/pokegym/pokemon_red/torch.py deleted file mode 100644 index acb51b3..0000000 --- a/pokegym/pokemon_red/torch.py +++ /dev/null @@ -1,29 +0,0 @@ -import pufferlib.models - - -class Recurrent(pufferlib.models.RecurrentWrapper): - def __init__(self, env, policy, input_size=512, hidden_size=512, num_layers=1): - super().__init__(env, policy, input_size, hidden_size, num_layers) - -class Policy(pufferlib.models.Convolutional): - def __init__(self, env, input_size=512, hidden_size=512, output_size=512, - framestack=4, flat_size=64*5*6): - super().__init__( - env=env, - input_size=input_size, - hidden_size=hidden_size, - output_size=output_size, - framestack=framestack, - flat_size=flat_size, - channels_last=True, - ) - -''' -class Policy(pufferlib.models.ProcgenResnet): - def __init__(self, env, cnn_width=16, mlp_width=512): - super().__init__( - env=env, - cnn_width=cnn_width, - mlp_width=mlp_width, - ) -''' From 4ab1dc1948919ac433823940e2d1510c33639ebc Mon Sep 17 00:00:00 2001 From: Leanke <49460411+leanke@users.noreply.github.com> Date: Mon, 6 May 2024 01:54:25 -0600 Subject: [PATCH 24/29] cut scale --- pokegym/environment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index 1203121..f9dc2f1 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -424,7 +424,7 @@ def step(self, action, fast_video=True): ) ) if tuple(list(self.cut_state)[1:]) in CUT_SEQ: - self.cut_coords[coords] = 5 # from 14 + self.cut_coords[coords] = 10 # from 14 or 5 with used cut never reset self.cut_tiles[self.cut_state[-1][0]] = 1 elif self.cut_state == CUT_GRASS_SEQ: self.cut_coords[coords] = 0.001 @@ -483,7 +483,7 @@ def step(self, action, fast_video=True): seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) moves_obtained_reward = self.reward_scale * sum(self.moves_obtained) - used_cut_rew = self.used_cut * 0.1 + used_cut_rew = self.used_cut * 0.02 cut_coords = sum(self.cut_coords.values()) * 1.0 cut_tiles = len(self.cut_tiles) * 1.0 start_menu = self.seen_start_menu * 0.01 From e9e150c51cfaa06dabeb59f97f807ced67705162 Mon Sep 17 00:00:00 2001 From: Leanke <49460411+leanke@users.noreply.github.com> Date: Mon, 6 May 2024 01:58:10 -0600 Subject: [PATCH 25/29] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6bc8402..50510e6 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Pokemon Red Gymnasium environment for reinforcement learning 2. Fork the repo and clone your fork to your local machine. ```sh -pip3 install -e . +pip install -e . ``` ### Running @@ -16,3 +16,8 @@ pip3 install -e . ```sh ./run.sh ``` + +### Edits + +1. /pokemon_red the policies and wrappers can be added here +2. /pokegym is the environment files can be altered here From a86b4a2b356db4be12a53b7d646b2a42c8e11313 Mon Sep 17 00:00:00 2001 From: leanke Date: Tue, 21 May 2024 04:15:14 +0000 Subject: [PATCH 26/29] hideout_baseline --- pokegym/environment.py | 185 +++++++++++----------- pokegym/ram_map.py | 340 ++++------------------------------------- 2 files changed, 122 insertions(+), 403 deletions(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index f9dc2f1..57ae726 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -1,19 +1,20 @@ -import multiprocessing from pathlib import Path from pdb import set_trace as T -import sqlite3 +import types import uuid -from gymnasium import spaces +from gymnasium import Env, spaces import numpy as np from collections import defaultdict, deque import io, os import random +from pyboy.utils import WindowEvent import matplotlib.pyplot as plt from pathlib import Path import mediapy as media +from hideout_baseline.pokegym import ram_map from pokegym.pyboy_binding import ( ACTIONS, make_env, @@ -21,7 +22,7 @@ load_pyboy_state, run_action_on_emulator, ) -from pokegym import ram_map, data +from pokegym import data STATE_PATH = __file__.rstrip("environment.py") + "States/" @@ -36,12 +37,8 @@ def get_random_state(): return random.choice(state_files) state_file = get_random_state() randstate = os.path.join(STATE_PATH, state_file) -db_name = Path(f'{str(uuid.uuid4())[:4]}') class Base: - counter_lock = multiprocessing.Lock() - counter = multiprocessing.Value('i', 1) - def __init__( self, rom_path="pokemon_red.gb", @@ -51,9 +48,6 @@ def __init__( quiet=False, **kwargs, ): - with Base.counter_lock: - env_id = Base.counter.value - Base.counter.value += 1 self.state_file = get_random_state() self.randstate = os.path.join(STATE_PATH, self.state_file) """Creates a PokemonRed environment""" @@ -68,8 +62,7 @@ def __init__( self.memory_shape = 80 self.use_screen_memory = True self.screenshot_counter = 0 - self.env_id = env_id - self.first = True + self.env_id = Path(f'{str(uuid.uuid4())[:4]}') self.reset_count = 0 self.explore_hidden_obj_weight = 1 @@ -180,10 +173,10 @@ def close(self): class Environment(Base): def __init__(self,rom_path="pokemon_red.gb",state_path=None,headless=True,save_video=False,quiet=False,verbose=False,**kwargs,): + super().__init__(rom_path, state_path, headless, save_video, quiet, **kwargs) - load_pyboy_state(self.game, self.load_last_state()) self.counts_map = np.zeros((444, 436)) - + self.death_count = 0 self.verbose = verbose self.include_conditions = [] self.seen_maps_difference = set() @@ -191,40 +184,14 @@ def __init__(self,rom_path="pokemon_red.gb",state_path=None,headless=True,save_v self.is_dead = False self.last_map = -1 self.log = True - self.cut_reset = 0 + self.used_cut = 0 + # self.seen_coords = set() + self.map_check = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] self.poketower = [142, 143, 144, 145, 146, 147, 148] self.pokehideout = [199, 200, 201, 202, 203] self.silphco = [181, 207, 208, 209, 210, 211, 212, 213, 233, 234, 235, 236] - # self.seen_coords = set() - self.map_check = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - - coord_rewards = [] - - def save_to_database(self): - db_dir = self.db_path - conn = sqlite3.connect(f'{db_dir}/{db_name}.db') - cursor = conn.cursor() - - cursor.execute("CREATE TABLE IF NOT EXISTS environment (env_id TEXT PRIMARY KEY,hm_count INTEGER,cut INTEGER)") - cursor.execute("INSERT OR REPLACE INTO environment VALUES (?, ?, ?)", (str(self.env_id), self.hm_count, self.cut)) - - conn.commit() - conn.close() - - def read_database(self): - db_dir = self.db_path - conn = sqlite3.connect(f'{db_dir}/{db_name}.db') - cursor = conn.cursor() - - cursor.execute("SELECT COUNT(*) FROM environment WHERE cut = 1") - count_cut_1 = cursor.fetchone()[0] - cursor.execute("SELECT COUNT(*) FROM environment") - total_instances = cursor.fetchone()[0] - percentage = (count_cut_1 / total_instances) * 100 - # print(f"Percentage of instances with hm_count = 1: {percentage:.2f}%") - conn.close() - return percentage - + load_pyboy_state(self.game, self.load_last_state()) + def update_pokedex(self): for i in range(0xD30A - 0xD2F7): caught_mem = self.game.get_memory_value(i + 0xD2F7) @@ -232,6 +199,13 @@ def update_pokedex(self): for j in range(8): self.caught_pokemon[8*i + j] = 1 if caught_mem & (1 << j) else 0 self.seen_pokemon[8*i + j] = 1 if seen_mem & (1 << j) else 0 + + def town_state(self): + state = io.BytesIO() + state.seek(0) + self.game.save_state(state) + self.initial_states.append(state) + return def update_moves_obtained(self): # Scan party @@ -258,7 +232,52 @@ def update_moves_obtained(self): def add_video_frame(self): self.full_frame_writer.add_image(self.video()) - def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): + def get_game_coords(self): + return (ram_map.mem_val(self.game, 0xD362), ram_map.mem_val(self.game, 0xD361), ram_map.mem_val(self.game, 0xD35E)) + + def check_if_in_start_menu(self) -> bool: + return ( + ram_map.mem_val(self.game, 0xD057) == 0 + and ram_map.mem_val(self.game, 0xCF13) == 0 + and ram_map.mem_val(self.game, 0xFF8C) == 6 + and ram_map.mem_val(self.game, 0xCF94) == 0 + ) + + def check_if_in_pokemon_menu(self) -> bool: + return ( + ram_map.mem_val(self.game, 0xD057) == 0 + and ram_map.mem_val(self.game, 0xCF13) == 0 + and ram_map.mem_val(self.game, 0xFF8C) == 6 + and ram_map.mem_val(self.game, 0xCF94) == 2 + ) + + def check_if_in_stats_menu(self) -> bool: + return ( + ram_map.mem_val(self.game, 0xD057) == 0 + and ram_map.mem_val(self.game, 0xCF13) == 0 + and ram_map.mem_val(self.game, 0xFF8C) == 6 + and ram_map.mem_val(self.game, 0xCF94) == 1 + ) + + def check_if_in_bag_menu(self) -> bool: + return ( + ram_map.mem_val(self.game, 0xD057) == 0 + and ram_map.mem_val(self.game, 0xCF13) == 0 + # and newram_map.mem_val(self.game, 0xFF8C) == 6 # only sometimes + and ram_map.mem_val(self.game, 0xCF94) == 3 + ) + + def check_if_cancel_bag_menu(self, action) -> bool: + return ( + action == WindowEvent.PRESS_BUTTON_A + and ram_map.mem_val(self.game, 0xD057) == 0 + and ram_map.mem_val(self.game, 0xCF13) == 0 + # and newram_map.mem_val(self.game, 0xFF8C) == 6 + and ram_map.mem_val(self.game, 0xCF94) == 3 + and ram_map.mem_val(self.game, 0xD31D) == ram_map.mem_val(self.game, 0xCC36) + ram_map.mem_val(self.game, 0xCC26) + ) + + def reset(self, seed=None, options=None, max_episode_steps=2048, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" self.reset_count += 1 @@ -304,8 +323,6 @@ def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4 self.town = 1 self.gymthree = 0 self.gymfour = 0 - self.used_cut = 0 - self.death_count = 0 return self.render(), {} @@ -322,45 +339,45 @@ def step(self, action, fast_video=True): self.seen_coords.add((r, c, map_n)) if int(ram_map.read_bit(self.game, 0xD81B, 7)) == 0: # pre hideout if map_n in self.poketower: - self.exploration_reward = 0 + exploration_reward = 0 elif map_n in self.pokehideout: - self.exploration_reward = (0.03 * len(self.seen_coords)) + exploration_reward = (0.03 * len(self.seen_coords)) else: - self.exploration_reward = (0.02 * len(self.seen_coords)) + exploration_reward = (0.02 * len(self.seen_coords)) elif int(ram_map.read_bit(self.game, 0xD7E0, 7)) == 0 and int(ram_map.read_bit(self.game, 0xD81B, 7)) == 1: # hideout done poketower not done if map_n in self.poketower: - self.exploration_reward = (0.03 * len(self.seen_coords)) + exploration_reward = (0.03 * len(self.seen_coords)) else: - self.exploration_reward = (0.02 * len(self.seen_coords)) + exploration_reward = (0.02 * len(self.seen_coords)) elif int(ram_map.read_bit(self.game, 0xD76C, 0)) == 0 and int(ram_map.read_bit(self.game, 0xD7E0, 7)) == 1: # tower done no flute if map_n == 149: - self.exploration_reward = (0.03 * len(self.seen_coords)) + exploration_reward = (0.03 * len(self.seen_coords)) elif map_n in self.poketower: - self.exploration_reward = (0.01 * len(self.seen_coords)) + exploration_reward = (0.01 * len(self.seen_coords)) elif map_n in self.pokehideout: - self.exploration_reward = (0.01 * len(self.seen_coords)) + exploration_reward = (0.01 * len(self.seen_coords)) else: - self.exploration_reward = (0.02 * len(self.seen_coords)) + exploration_reward = (0.02 * len(self.seen_coords)) elif int(ram_map.read_bit(self.game, 0xD838, 7)) == 0 and int(ram_map.read_bit(self.game, 0xD76C, 0)) == 1: # flute gotten pre silphco if map_n in self.silphco: - self.exploration_reward = (0.03 * len(self.seen_coords)) + exploration_reward = (0.03 * len(self.seen_coords)) elif map_n in self.poketower: - self.exploration_reward = (0.01 * len(self.seen_coords)) + exploration_reward = (0.01 * len(self.seen_coords)) elif map_n in self.pokehideout: - self.exploration_reward = (0.01 * len(self.seen_coords)) + exploration_reward = (0.01 * len(self.seen_coords)) else: - self.exploration_reward = (0.02 * len(self.seen_coords)) + exploration_reward = (0.02 * len(self.seen_coords)) elif int(ram_map.read_bit(self.game, 0xD838, 7)) == 1 and int(ram_map.read_bit(self.game, 0xD76C, 0)) == 1: # flute gotten post silphco if map_n in self.silphco: - self.exploration_reward = (0.01 * len(self.seen_coords)) + exploration_reward = (0.01 * len(self.seen_coords)) elif map_n in self.poketower: - self.exploration_reward = (0.01 * len(self.seen_coords)) + exploration_reward = (0.01 * len(self.seen_coords)) elif map_n in self.pokehideout: - self.exploration_reward = (0.01 * len(self.seen_coords)) + exploration_reward = (0.01 * len(self.seen_coords)) else: - self.exploration_reward = (0.02 * len(self.seen_coords)) + exploration_reward = (0.02 * len(self.seen_coords)) else: - self.exploration_reward = (0.02 * len(self.seen_coords)) + exploration_reward = (0.02 * len(self.seen_coords)) if map_n == 92: self.gymthree = 1 @@ -404,7 +421,7 @@ def step(self, action, fast_video=True): if ram_map.mem_val(self.game, 0xD057) == 0: # is_in_battle if 1 if self.cut == 1: player_direction = self.game.get_memory_value(0xC109) - y, x, map_id = ram_map.position(self.game) # this is [y, x, z] # x, y, map_id + x, y, map_id = self.get_game_coords() # x, y, map_id if player_direction == 0: # down coords = (x, y + 1, map_id) if player_direction == 4: @@ -424,7 +441,7 @@ def step(self, action, fast_video=True): ) ) if tuple(list(self.cut_state)[1:]) in CUT_SEQ: - self.cut_coords[coords] = 10 # from 14 or 5 with used cut never reset + self.cut_coords[coords] = 5 # from 14 self.cut_tiles[self.cut_state[-1][0]] = 1 elif self.cut_state == CUT_GRASS_SEQ: self.cut_coords[coords] = 0.001 @@ -433,16 +450,19 @@ def step(self, action, fast_video=True): self.cut_coords[coords] = 0.001 self.cut_tiles[self.cut_state[-1][0]] = 1 if int(ram_map.read_bit(self.game, 0xD803, 0)): - if ram_map.check_if_in_start_menu(self.game): + if self.check_if_in_start_menu(): self.seen_start_menu = 1 - if ram_map.check_if_in_pokemon_menu(self.game): + if self.check_if_in_pokemon_menu(): self.seen_pokemon_menu = 1 - if ram_map.check_if_in_stats_menu(self.game): + if self.check_if_in_stats_menu(): self.seen_stats_menu = 1 - if ram_map.check_if_in_bag_menu(self.game): + if self.check_if_in_bag_menu(): self.seen_bag_menu = 1 + if self.check_if_cancel_bag_menu(action): + self.seen_cancel_bag_menu = 1 - if ram_map.used_cut(self.game): + if ram_map.used_cut(self.game) == 61: + ram_map.write_mem(self.game, 0xCD4D, 00) # address, byte to write resets tile check self.used_cut += 1 # Misc @@ -477,13 +497,12 @@ def step(self, action, fast_video=True): gym8 = ram_map.gym8(self.game) rival = ram_map.rival(self.game) - exploration_reward = self.exploration_reward cut_rew = self.cut * 10 event_reward = sum([silph, rock_tunnel, ssanne, mtmoon, routes, misc, snorlax, hmtm, bill, oak, towns, lab, mansion, safari, dojo, hideout, tower, gym1, gym2, gym3, gym4, gym5, gym6, gym7, gym8, rival]) seen_pokemon_reward = self.reward_scale * sum(self.seen_pokemon) caught_pokemon_reward = self.reward_scale * sum(self.caught_pokemon) moves_obtained_reward = self.reward_scale * sum(self.moves_obtained) - used_cut_rew = self.used_cut * 0.02 + used_cut_rew = self.used_cut * 0.1 cut_coords = sum(self.cut_coords.values()) * 1.0 cut_tiles = len(self.cut_tiles) * 1.0 start_menu = self.seen_start_menu * 0.01 @@ -522,7 +541,6 @@ def step(self, action, fast_video=True): if self.save_video and done: self.full_frame_writer.close() if done: - # self.save_to_database() poke = self.game.get_memory_value(0xD16B) level = self.game.get_memory_value(0xD18C) if poke == 57 and level == 0: @@ -555,14 +573,6 @@ def step(self, action, fast_video=True): "gym7": gym7, "gym8": gym8, "rival": rival, - "lift_key": int(ram_map.read_bit(self.game, 0xD81B, 6)), - "beat_hideout": int(ram_map.read_bit(self.game, 0xD81B, 7)), - "beat_marowak": int(ram_map.read_bit(self.game, 0xD768, 7)), - "got_pokeflute": int(ram_map.read_bit(self.game, 0xD76C, 0)), - "Got_Bicycle": int(ram_map.read_bit(self.game, 0xD75F, 0)), - "route_12_snorlax": int(ram_map.read_bit(self.game, 0xD7D8, 7)), - "route_16_snorlax": int(ram_map.read_bit(self.game, 0xD7E0, 1)), - "Beat_Silph_Co_Giovanni": int(ram_map.read_bit(self.game, 0xD838, 7)), }, "BET": { "Reward_Delta": reward, @@ -579,8 +589,6 @@ def step(self, action, fast_video=True): "Used_Cut": used_cut_rew, "Cut_Coords": cut_coords, "Cut_Tiles": cut_tiles, - # "Bulba_Check": bulba_check, - # "Respawn": respawn_reward }, "hm_count": hm_count, "cut_taught": self.cut, @@ -596,7 +604,7 @@ def step(self, action, fast_video=True): "maps_explored": np.sum(self.seen_maps), "party_size": party_size, "moves_obtained": sum(self.moves_obtained), - # "deaths": self.death_count, + "deaths": self.death_count, 'cut_coords': cut_coords, 'cut_tiles': cut_tiles, 'bag_menu': bag_menu, @@ -606,7 +614,6 @@ def step(self, action, fast_video=True): 'used_cut': self.used_cut, 'gym_three': self.gymthree, 'gym_four': self.gymfour, - # "respawn_coord_len": len(self.respawn) } return self.render(), reward, done, done, info diff --git a/pokegym/ram_map.py b/pokegym/ram_map.py index 43eeb36..faf9c6d 100644 --- a/pokegym/ram_map.py +++ b/pokegym/ram_map.py @@ -1,17 +1,5 @@ -# ###################################################################################### -# Ram_map -# ###################################################################################### - -# Data Crystal - https://datacrystal.romhacking.net/wiki/Pok%C3%A9mon_Red/Blue:RAM_map -# No Comments - https://github.com/pret/pokered/blob/91dc3c9f9c8fd529bb6e8307b58b96efa0bec67e/constants/event_constants.asm -# Comments - https://github.com/luckytyphlosion/pokered/blob/master/constants/event_constants.asm -from collections import deque -import numpy as np from pokegym import data -CUT_GRASS_SEQ = deque([(0x52, 255, 1, 0, 1, 1), (0x52, 255, 1, 0, 1, 1), (0x52, 1, 1, 0, 1, 1)]) -CUT_FAIL_SEQ = deque([(-1, 255, 0, 0, 4, 1), (-1, 255, 0, 0, 1, 1), (-1, 255, 0, 0, 1, 1)]) -CUT_SEQ = [((0x3D, 1, 1, 0, 4, 1), (0x3D, 1, 1, 0, 1, 1)), ((0x50, 1, 1, 0, 4, 1), (0x50, 1, 1, 0, 1, 1)),] HP_ADDR = [0xD16C, 0xD198, 0xD1C4, 0xD1F0, 0xD21C, 0xD248] MAX_HP_ADDR = [0xD18D, 0xD1B9, 0xD1E5, 0xD211, 0xD23D, 0xD269] PARTY_SIZE_ADDR = 0xD163 @@ -31,7 +19,7 @@ HM = 5 TM = 2 TASK = 2 -POKEMON = 10 +POKEMON = 3 ITEM = 5 BILL_CAPT = 5 RIVAL = 3 @@ -39,7 +27,6 @@ EVENT = 1 BAD = -1 -# EVENT ##################################################################################################### def silph_co(game): Beat_Silph_Co_2F_Trainer_0 = TRAINER * int(read_bit(game, 0xD825, 2)) @@ -398,11 +385,11 @@ def snorlax(game): return sum([route12_snorlax_fight, route12_snorlax_beat, route16_snorlax_fight, route16_snorlax_beat]) def hmtm(game): - hm01 = HM * int(read_bit(game, 0xD803, 0))# cut - hm02 = HM * int(read_bit(game, 0xD7E0, 6))# fly - hm03 = HM * int(read_bit(game, 0xD857, 0))# strength - hm04 = HM * int(read_bit(game, 0xD78E, 0))# surf - hm05 = HM * int(read_bit(game, 0xD7C2, 0))# flash + hm01 = HM * int(read_bit(game, 0xD803, 0)) + hm02 = HM * int(read_bit(game, 0xD7E0, 6)) + hm03 = HM * int(read_bit(game, 0xD857, 0)) + hm04 = HM * int(read_bit(game, 0xD78E, 0)) + hm05 = HM * int(read_bit(game, 0xD7C2, 0)) tm34 = TM * int(read_bit(game, 0xD755, 6)) tm11 = TM * int(read_bit(game, 0xD75E, 6)) @@ -527,7 +514,7 @@ def hideout(game): rocket_hideout_4_door_unlocked = QUEST * int(read_bit(game, 0xD81B, 5)) rocket_dropped_lift_key = QUEST * int(read_bit(game, 0xD81B, 6)) beat_rocket_hideout_giovanni = GYM_LEADER * int(read_bit(game, 0xD81B, 7)) - found_rocket_hideout = 10 * int(read_bit(game, 0xD77E, 1)) + found_rocket_hideout = QUEST * int(read_bit(game, 0xD77E, 1)) return sum([beat_rocket_hideout_1_trainer_0, beat_rocket_hideout_1_trainer_1, beat_rocket_hideout_1_trainer_2, beat_rocket_hideout_1_trainer_3, beat_rocket_hideout_1_trainer_4, beat_rocket_hideout_2_trainer_0, beat_rocket_hideout_3_trainer_0, beat_rocket_hideout_3_trainer_1, @@ -660,7 +647,7 @@ def rival(game): return sum([one, two, three, four, five, six, seven, eight, nine, Beat_Silph_Co_Rival]) -# UTIL ##################################################################################################### +###################################################################################################### def bcd(num): return 10 * ((num >> 4) & 0x0F) + (num & 0x0F) @@ -682,7 +669,7 @@ def read_uint16(game, start_addr): val_1 = game.get_memory_value(start_addr + 1) return 256 * val_256 + val_1 -# MISC ##################################################################################################### +###################################################################################################### def get_hm_count(game): hm_ids = [0xC4, 0xC5, 0xC6, 0xC7, 0xC8] @@ -703,7 +690,7 @@ def get_items_in_bag(game, one_indexed=0): item_ids.append(item_id + one_indexed) return item_ids -def position(game): # this is [y, x, z] +def position(game): r_pos = game.get_memory_value(Y_POS_ADDR) c_pos = game.get_memory_value(X_POS_ADDR) map_n = game.get_memory_value(MAP_N_ADDR) @@ -738,11 +725,7 @@ def hp(game): return sum(party_hp) / sum_max_hp def used_cut(game): - if game.get_memory_value(WCUTTILE) == 61: - write_mem(game, 0xCD4D, 00) # address, byte to write resets tile check - return True - else: - return False + return game.get_memory_value(WCUTTILE) def write_mem(game, addr, value): mem = game.set_memory_value(addr, value) @@ -752,289 +735,18 @@ def badges(game): badges = game.get_memory_value(BADGE_1_ADDR) return bit_count(badges) -def update_pokedex(game): - seen_pokemon = np.zeros(152, dtype=np.uint8) - caught_pokemon = np.zeros(152, dtype=np.uint8) - for i in range(0xD30A - 0xD2F7): - caught_mem = game.get_memory_value(i + 0xD2F7) - seen_mem = game.get_memory_value(i + 0xD30A) - for j in range(8): - caught_pokemon[8*i + j] = 1 if caught_mem & (1 << j) else 0 - seen_pokemon[8*i + j] = 1 if seen_mem & (1 << j) else 0 - return sum(seen_pokemon), sum(caught_pokemon) - -def update_moves_obtained(game): - # Scan party - moves_obtained = {} - cut = 0 - for i in [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247]: - if game.get_memory_value(i) != 0: - for j in range(4): - move_id = game.get_memory_value(i + j + 8) - if move_id != 0: - if move_id != 0: - moves_obtained[move_id] = 1 - if move_id == 15: - cut = 1 - # Scan current box (since the box doesn't auto increment in pokemon red) - num_moves = 4 - box_struct_length = 25 * num_moves * 2 - for i in range(game.get_memory_value(0xda80)): - offset = i*box_struct_length + 0xda96 - if game.get_memory_value(offset) != 0: - for j in range(4): - move_id = game.get_memory_value(offset + j + 8) - if move_id != 0: - moves_obtained[move_id] = 1 - return sum(moves_obtained), cut - -# CUT ##################################################################################################### - -def cut_array(game): - cut_coords = {} - cut_tiles = {} # set([]) - cut_state = deque(maxlen=3) - if mem_val(game, 0xD057) == 0: # is_in_battle if 1 - player_direction = game.get_memory_value(0xC109) - y, x, map_id = position() # x, y, map_id - if player_direction == 0: # down - coords = (x, y + 1, map_id) - if player_direction == 4: - coords = (x, y - 1, map_id) - if player_direction == 8: - coords = (x - 1, y, map_id) - if player_direction == 0xC: - coords = (x + 1, y, map_id) - cut_state.append( - ( - game.get_memory_value(0xCFC6), - game.get_memory_value(0xCFCB), - game.get_memory_value(0xCD6A), - game.get_memory_value(0xD367), - game.get_memory_value(0xD125), - game.get_memory_value(0xCD3D), - ) - ) - if tuple(list(cut_state)[1:]) in CUT_SEQ: - cut_coords[coords] = 5 # from 14 - cut_tiles[cut_state[-1][0]] = 1 - elif cut_state == CUT_GRASS_SEQ: - cut_coords[coords] = 0.001 - cut_tiles[cut_state[-1][0]] = 1 - elif deque([(-1, *elem[1:]) for elem in cut_state]) == CUT_FAIL_SEQ: - cut_coords[coords] = 0.001 - cut_tiles[cut_state[-1][0]] = 1 - if int(read_bit(game, 0xD803, 0)): - if check_if_in_start_menu(game): - seen_start_menu = 1 - if check_if_in_pokemon_menu(game): - seen_pokemon_menu = 1 - if check_if_in_stats_menu(game): - seen_stats_menu = 1 - if check_if_in_bag_menu(game): - seen_bag_menu = 1 - return cut_coords, cut_tiles, seen_start_menu, seen_pokemon_menu, seen_stats_menu, seen_bag_menu - -def check_if_in_start_menu(game) -> bool: - return ( - mem_val(game, 0xD057) == 0 - and mem_val(game, 0xCF13) == 0 - and mem_val(game, 0xFF8C) == 6 - and mem_val(game, 0xCF94) == 0 - ) - -def check_if_in_pokemon_menu(game) -> bool: - return ( - mem_val(game, 0xD057) == 0 - and mem_val(game, 0xCF13) == 0 - and mem_val(game, 0xFF8C) == 6 - and mem_val(game, 0xCF94) == 2 - ) - -def check_if_in_stats_menu(game) -> bool: - return ( - mem_val(game, 0xD057) == 0 - and mem_val(game, 0xCF13) == 0 - and mem_val(game, 0xFF8C) == 6 - and mem_val(game, 0xCF94) == 1 - ) - -def check_if_in_bag_menu(game) -> bool: - return ( - mem_val(game, 0xD057) == 0 - and mem_val(game, 0xCF13) == 0 - # and mem_val(game, 0xFF8C) == 6 # only sometimes - and mem_val(game, 0xCF94) == 3 - ) - -# EXPL ##################################################################################################### - -def explore(game, seen_coords, map_n): - seen_coords= set() - poketower = [142, 143, 144, 145, 146, 147, 148] - pokehideout = [199, 200, 201, 202, 203] - silphco = [181, 207, 208, 209, 210, 211, 212, 213, 233, 234, 235, 236] - if int(read_bit(game, 0xD81B, 7)) == 0: # pre hideout - if map_n in poketower: - exploration_reward = 0 - elif map_n in pokehideout: - exploration_reward = (0.03 * len(seen_coords)) - else: - exploration_reward = (0.02 * len(seen_coords)) - elif int(read_bit(game, 0xD7E0, 7)) == 0 and int(read_bit(game, 0xD81B, 7)) == 1: # hideout done poketower not done - if map_n in poketower: - exploration_reward = (0.03 * len(seen_coords)) - else: - exploration_reward = (0.02 * len(seen_coords)) - elif int(read_bit(game, 0xD76C, 0)) == 0 and int(read_bit(game, 0xD7E0, 7)) == 1: # tower done no flute - if map_n == 149: - exploration_reward = (0.03 * len(seen_coords)) - elif map_n in poketower: - exploration_reward = (0.01 * len(seen_coords)) - elif map_n in pokehideout: - exploration_reward = (0.01 * len(seen_coords)) - else: - exploration_reward = (0.02 * len(seen_coords)) - elif int(read_bit(game, 0xD838, 7)) == 0 and int(read_bit(game, 0xD76C, 0)) == 1: # flute gotten pre silphco - if map_n in silphco: - exploration_reward = (0.03 * len(seen_coords)) - elif map_n in poketower: - exploration_reward = (0.01 * len(seen_coords)) - elif map_n in pokehideout: - exploration_reward = (0.01 * len(seen_coords)) - else: - exploration_reward = (0.02 * len(seen_coords)) - elif int(read_bit(game, 0xD838, 7)) == 1 and int(read_bit(game, 0xD76C, 0)) == 1: # flute gotten post silphco - if map_n in silphco: - exploration_reward = (0.01 * len(seen_coords)) - elif map_n in poketower: - exploration_reward = (0.01 * len(seen_coords)) - elif map_n in pokehideout: - exploration_reward = (0.01 * len(seen_coords)) - else: - exploration_reward = (0.02 * len(seen_coords)) - else: - exploration_reward = (0.02 * len(seen_coords)) - return exploration_reward - - -# ################################################################################################################## -# # Notes -# ################################################################################################################## - -## Misc - # 0xc4f2 check for EE hex for text box arrow is present - -## Menu Data - # Coordinates of the position of the cursor for the top menu item (id 0) - # CC24 : Y position - # CC25 : X position - # CC26 - Currently selected menu item (topmost is 0) - # CC27 - Tile "hidden" by the menu cursor - # CC28 - ID of the last menu item - # CC29 - bitmask applied to the key port for the current menu - # CC2A - ID of the previously selected menu item - # CC2B - Last position of the cursor on the party / Bill's PC screen - # CC2C - Last position of the cursor on the item screen - # CC2D - Last position of the cursor on the START / battle menu - # CC2F - Index (in party) of the Pokémon currently sent out - # CC30~CC31 - Pointer to cursor tile in C3A0 buffer - # CC36 - ID of the first displayed menu item - # CC35 - Item highlighted with Select (01 = first item, 00 = no item, etc.) - # CC3A and CC3B are unused - # cc51 and cc52 both read 00 when menu is closed - -## Pokémon Mart - # JPN addr. INT addr. Description - # CF62 CF7B Total Items - # CF63 CF7C Item 1 - # CF64 CF7D Item 2 - # CF65 CF7E Item 3 - # CF66 CF7F Item 4 - # CF67 CF80 Item 5 - # CF68 CF81 Item 6 - # CF69 CF82 Item 7 - # CF70 CF83 Item 8 - # CF71 CF84 Item 9 - # CF72 CF85 Item 10 - -## Event Flags - # D751 - Fought Giovanni Yet? - # D755 - Fought Brock Yet? - # D75E - Fought Misty Yet? - # D773 - Fought Lt. Surge Yet? - # D77C - Fought Erika Yet? - # D792 - Fought Koga Yet? - # D79A - Fought Blaine Yet? - # D7B3 - Fought Sabrina Yet? - # D782 - Fought Articuno Yet? - # D7D4 - Fought Zapdos Yet? - # D7EE - Fought Moltres Yet? - # D710 - Fossilized Pokémon? - # D7D8 - Fought Snorlax Yet (Vermilion) - # D7E0 - Fought Snorlax Yet? (Celadon) - # D803 - Is SS Anne here - # D5F3 - Have Town map? - # D60D - Have Oak's Parcel? - # D5A6 to D5C5 : Missable Objects Flags (flags for every (dis)appearing sprites, like the guard in Cerulean City or the Pokéballs in Oak's Lab) - # D5AB - Starters Back? - # D5C0(bit 1) - 0=Mewtwo appears, 1=Doesn't (See D85F) - # D700 - Bike Speed - # D70B - Fly Anywhere Byte 1 - # D70C - Fly Anywhere Byte 2 - # D70D - Safari Zone Time Byte 1 - # D70E - Safari Zone Time Byte 2 - # D714 - Position in Air - # D72E - Did you get Lapras Yet? - # D732 - Debug New Game - # D790 - If bit 7 is set, Safari Game over - # D85F - Mewtwo can be caught if bit 2 clear - Needs D5C0 bit 1 clear, too - -## Item IDs & String - # 1, 2, 3, 4, 6, 11, 16, 17, 18, 19, 20, 41, 42, 72, 73, 196, 197, 198, 199, 200, 53, 54 - # 001 0x01 Master Ball - # 002 0x02 Ultra Ball - # 003 0x03 Great Ball - # 004 0x04 Poké Ball - # 006 0x06 Bicycle - # 011 0x0B Antidote - # 016 0x10 Full Restore - # 017 0x11 Max Potion - # 018 0x12 Hyper Potion - # 019 0x13 Super Potion - # 020 0x14 Potion - # 041 0x29 Dome Fossil - # 042 0x2A Helix Fossil - # 072 0x48 Silph Scope - # 073 0x49 Poké Flute - # 196 0xC4 HM01 - # 197 0xC5 HM02 - # 198 0xC6 HM03 - # 199 0xC7 HM04 - # 200 0xC8 HM05 - # 053 0x35 Revive - # 054 0x36 Max Revive - -## Item Bag - # 0xD31D - Total Items - # 0xD31E - Item 1 - # 0xD320 - Item 2 - # 0xD322 - Item 3 - # 0xD324 - Item 4 - # 0xD326 - Item 5 - # 0xD328 - Item 6 - # 0xD32A - Item 7 - # 0xD32C - Item 8 - # 0xD32E - Item 9 - # 0xD330 - Item 10 - # 0xD332 - Item 11 - # 0xD334 - Item 12 - # 0xD336 - Item 13 - # 0xD338 - Item 14 - # 0xD33A - Item 15 - # 0xD33C - Item 16 - # 0xD33E - Item 17 - # 0xD340 - Item 18 - # 0xD342 - Item 19 - # 0xD344 - Item 20 - # 0xD346 - Item End of List \ No newline at end of file + + + + + + + + + + + + + + + From a5c065b8f6cdbd9e5a15732ac0a013d9c964f0fd Mon Sep 17 00:00:00 2001 From: leanke Date: Fri, 7 Jun 2024 19:25:42 +0000 Subject: [PATCH 27/29] puffer1.0 cleanup --- README.md | 23 -- clean_pufferl.py | 694 ---------------------------------- config.yaml | 84 ---- pokegym/environment.py | 93 +---- pokegym/ram_map.py | 32 +- pokemon_red/__init__.py | 12 - pokemon_red/environment.py | 26 -- pokemon_red/stream_wrapper.py | 101 ----- pokemon_red/torch.py | 29 -- pyproject.toml | 1 - run.sh | 2 - train.py | 260 ------------- 12 files changed, 44 insertions(+), 1313 deletions(-) delete mode 100644 clean_pufferl.py delete mode 100644 config.yaml delete mode 100644 pokemon_red/__init__.py delete mode 100644 pokemon_red/environment.py delete mode 100644 pokemon_red/stream_wrapper.py delete mode 100644 pokemon_red/torch.py delete mode 100755 run.sh delete mode 100644 train.py diff --git a/README.md b/README.md index 50510e6..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,23 +0,0 @@ -# Pokegym - -Pokemon Red Gymnasium environment for reinforcement learning - -### Installation - -1. Clone the repo to your local machine and install it. -2. Fork the repo and clone your fork to your local machine. - -```sh -pip install -e . -``` - -### Running - -```sh -./run.sh -``` - -### Edits - -1. /pokemon_red the policies and wrappers can be added here -2. /pokegym is the environment files can be altered here diff --git a/clean_pufferl.py b/clean_pufferl.py deleted file mode 100644 index f02ed8e..0000000 --- a/clean_pufferl.py +++ /dev/null @@ -1,694 +0,0 @@ -from pdb import set_trace as T -import numpy as np -import cv2 - -import os -import random -import time -import uuid - -from collections import defaultdict -from datetime import timedelta - -import torch -import torch.nn as nn -import torch.optim as optim - -import pufferlib -import pufferlib.utils -import pufferlib.emulation -import pufferlib.vectorization -import pufferlib.frameworks.cleanrl -import pufferlib.policy_pool - - -@pufferlib.dataclass -class Performance: - total_uptime = 0 - total_updates = 0 - total_agent_steps = 0 - epoch_time = 0 - epoch_sps = 0 - evaluation_time = 0 - evaluation_sps = 0 - evaluation_memory = 0 - evaluation_pytorch_memory = 0 - env_time = 0 - env_sps = 0 - inference_time = 0 - inference_sps = 0 - train_time = 0 - train_sps = 0 - train_memory = 0 - train_pytorch_memory = 0 - misc_time = 0 - -@pufferlib.dataclass -class Losses: - policy_loss = 0 - value_loss = 0 - entropy = 0 - old_approx_kl = 0 - approx_kl = 0 - clipfrac = 0 - explained_variance = 0 - -@pufferlib.dataclass -class Charts: - global_step = 0 - SPS = 0 - learning_rate = 0 - -def create( - self: object = None, - config: pufferlib.namespace = None, - exp_name: str = None, - track: bool = False, - - # Agent - agent: nn.Module = None, - agent_creator: callable = None, - agent_kwargs: dict = None, - - # Environment - env_creator: callable = None, - env_creator_kwargs: dict = None, - vectorization: ... = pufferlib.vectorization.Serial, - - # Policy Pool options - policy_selector: callable = pufferlib.policy_pool.random_selector, - ): - if config is None: - config = pufferlib.args.CleanPuffeRL() - - if exp_name is None: - exp_name = str(uuid.uuid4())[:8] - - wandb = None - if track: - import wandb - - start_time = time.time() - seed_everything(config.seed, config.torch_deterministic) - total_updates = config.total_timesteps // config.batch_size - - device = config.device - - # Create environments, agent, and optimizer - init_profiler = pufferlib.utils.Profiler(memory=True) - with init_profiler: - pool = vectorization( - env_creator, - env_kwargs=env_creator_kwargs, - num_envs=config.num_envs, - envs_per_worker=config.envs_per_worker, - envs_per_batch=config.envs_per_batch, - env_pool=config.env_pool, - mask_agents=True, - ) - - obs_shape = pool.single_observation_space.shape - atn_shape = pool.single_action_space.shape - num_agents = pool.agents_per_env - total_agents = num_agents * config.num_envs - - # If data_dir is provided, load the resume state leanke - resume_state = {} - # path = os.path.join(config.data_dir, exp_name) - path = f'{config.data_dir}/{exp_name}' - if os.path.exists(path): - trainer_path = os.path.join(path, 'trainer_state.pt') - resume_state = torch.load(trainer_path) - model_path = os.path.join(path, resume_state["model_name"]) - agent = torch.load(model_path, map_location=device) - print(f'Resumed from update {resume_state["update"]} ' - f'with policy {resume_state["model_name"]}') - else: - agent = pufferlib.emulation.make_object( - agent, agent_creator, [pool.driver_env], agent_kwargs) - - global_step = resume_state.get("global_step", 0) - agent_step = resume_state.get("agent_step", 0) - update = resume_state.get("update", 0) - - optimizer = optim.Adam(agent.parameters(), - lr=config.learning_rate, eps=1e-5) - - uncompiled_agent = agent # Needed to save the model - if config.compile: - agent = torch.compile(agent, mode=config.compile_mode) - - if config.verbose: - n_params = sum(p.numel() for p in agent.parameters() if p.requires_grad) - print(f"Model Size: {n_params//1000} K parameters") - - opt_state = resume_state.get("optimizer_state_dict", None) - if opt_state is not None: - optimizer.load_state_dict(resume_state["optimizer_state_dict"]) - - # Create policy pool - pool_agents = num_agents * pool.envs_per_batch - policy_pool = pufferlib.policy_pool.PolicyPool( - agent, pool_agents, atn_shape, device, path, - config.pool_kernel, policy_selector, - ) - - # Allocate Storage - storage_profiler = pufferlib.utils.Profiler(memory=True, pytorch_memory=True).start() - next_lstm_state = [] - pool.async_reset(config.seed) - next_lstm_state = None - if hasattr(agent, 'lstm'): - shape = (agent.lstm.num_layers, total_agents, agent.lstm.hidden_size) - next_lstm_state = ( - torch.zeros(shape).to(device), - torch.zeros(shape).to(device), - ) - obs=torch.zeros(config.batch_size + 1, *obs_shape, pin_memory=True)# added , pin_memory=True) - actions=torch.zeros(config.batch_size + 1, *atn_shape, dtype=int) - logprobs=torch.zeros(config.batch_size + 1) - rewards=torch.zeros(config.batch_size + 1) - dones=torch.zeros(config.batch_size + 1) - truncateds=torch.zeros(config.batch_size + 1) - values=torch.zeros(config.batch_size + 1) - - obs_ary = np.asarray(obs) - actions_ary = np.asarray(actions) - logprobs_ary = np.asarray(logprobs) - rewards_ary = np.asarray(rewards) - dones_ary = np.asarray(dones) - truncateds_ary = np.asarray(truncateds) - values_ary = np.asarray(values) - - storage_profiler.stop() - - #"charts/actions": wandb.Histogram(b_actions.cpu().numpy()), - init_performance = pufferlib.namespace( - init_time = time.time() - start_time, - init_env_time = init_profiler.elapsed, - init_env_memory = init_profiler.memory, - tensor_memory = storage_profiler.memory, - tensor_pytorch_memory = storage_profiler.pytorch_memory, - ) - - return pufferlib.namespace(self, - # Agent, Optimizer, and Environment - config=config, - pool = pool, - agent = agent, - uncompiled_agent = uncompiled_agent, - optimizer = optimizer, - policy_pool = policy_pool, - - # Logging - exp_name = exp_name, - wandb = wandb, - learning_rate=config.learning_rate, - losses = Losses(), - init_performance = init_performance, - performance = Performance(), - - # Storage - sort_keys = [], - next_lstm_state = next_lstm_state, - obs = obs, - actions = actions, - logprobs = logprobs, - rewards = rewards, - dones = dones, - values = values, - obs_ary = obs_ary, - actions_ary = actions_ary, - logprobs_ary = logprobs_ary, - rewards_ary = rewards_ary, - dones_ary = dones_ary, - truncateds_ary = truncateds_ary, - values_ary = values_ary, - - # Misc - total_updates = total_updates, - update = update, - global_step = global_step, - device = device, - start_time = start_time, - ) - -@pufferlib.utils.profile -def evaluate(data): - config = data.config - # TODO: Handle update on resume - if data.wandb is not None and data.performance.total_uptime > 0: - data.wandb.log({ - 'SPS': data.SPS, - 'global_step': data.global_step, - 'learning_rate': data.optimizer.param_groups[0]["lr"], - **{f'losses/{k}': v for k, v in data.losses.items()}, - **{f'performance/{k}': v - for k, v in data.performance.items()}, - **{f'stats/{k}': v for k, v in data.stats.items()}, - **{f'skillrank/{policy}': elo - for policy, elo in data.policy_pool.ranker.ratings.items()}, - }) - - data.policy_pool.update_policies() - performance = defaultdict(list) - env_profiler = pufferlib.utils.Profiler() - inference_profiler = pufferlib.utils.Profiler() - eval_profiler = pufferlib.utils.Profiler(memory=True, pytorch_memory=True).start() - misc_profiler = pufferlib.utils.Profiler() - - ptr = step = padded_steps_collected = agent_steps_collected = 0 - infos = defaultdict(lambda: defaultdict(list)) - while True: - step += 1 - if ptr == config.batch_size + 1: - break - - with env_profiler: - o, r, d, t, i, env_id, mask = data.pool.recv() - - with misc_profiler: - i = data.policy_pool.update_scores(i, "return") - # TODO: Update this for policy pool - for ii, ee in zip(i['learner'], env_id): - ii['env_id'] = ee - - - with inference_profiler, torch.no_grad(): - o = torch.as_tensor(o) - r = torch.as_tensor(r).float().to(data.device).view(-1) - d = torch.as_tensor(d).float().to(data.device).view(-1) - - agent_steps_collected += sum(mask) - padded_steps_collected += len(mask) - - # Multiple policies will not work with new envpool - next_lstm_state = data.next_lstm_state - if next_lstm_state is not None: - next_lstm_state = ( - next_lstm_state[0][:, env_id], - next_lstm_state[1][:, env_id], - ) - - actions, logprob, value, next_lstm_state = data.policy_pool.forwards( - o.to(data.device), next_lstm_state) - - if next_lstm_state is not None: - h, c = next_lstm_state - data.next_lstm_state[0][:, env_id] = h - data.next_lstm_state[1][:, env_id] = c - - value = value.flatten() - - - with misc_profiler: - actions = actions.cpu().numpy() - - # Index alive mask with policy pool idxs... - # TODO: Find a way to avoid having to do this - learner_mask = torch.Tensor(mask * data.policy_pool.mask) - - # Ensure indices do not exceed batch size - indices = torch.where(learner_mask)[0][:config.batch_size - ptr + 1].numpy() - end = ptr + len(indices) - - # Batch indexing - data.obs_ary[ptr:end] = o.cpu().numpy()[indices] - data.values_ary[ptr:end] = value.cpu().numpy()[indices] - data.actions_ary[ptr:end] = actions[indices] - data.logprobs_ary[ptr:end] = logprob.cpu().numpy()[indices] - data.rewards_ary[ptr:end] = r.cpu().numpy()[indices] - data.dones_ary[ptr:end] = d.cpu().numpy()[indices] - data.sort_keys.extend([(env_id[i], step) for i in indices]) - - # Update pointer - ptr += len(indices) - - for policy_name, policy_i in i.items(): - for agent_i in policy_i: - for name, dat in unroll_nested_dict(agent_i): - infos[policy_name][name].append(dat) - - with env_profiler: - data.pool.send(actions) - - eval_profiler.stop() - - data.global_step += padded_steps_collected - data.reward = float(torch.mean(data.rewards)) - data.SPS = int(padded_steps_collected / eval_profiler.elapsed) - - perf = data.performance - perf.total_uptime = int(time.time() - data.start_time) - perf.total_agent_steps = data.global_step - perf.env_time = env_profiler.elapsed - perf.env_sps = int(agent_steps_collected / env_profiler.elapsed) - perf.inference_time = inference_profiler.elapsed - perf.inference_sps = int(padded_steps_collected / inference_profiler.elapsed) - perf.eval_time = eval_profiler.elapsed - perf.eval_sps = int(padded_steps_collected / eval_profiler.elapsed) - perf.eval_memory = eval_profiler.end_mem - perf.eval_pytorch_memory = eval_profiler.end_torch_mem - perf.misc_time = misc_profiler.elapsed - - data.stats = {} - infos = infos['learner'] - - if 'pokemon_exploration_map' in infos: - for idx, pmap in zip(infos['env_id'], infos['pokemon_exploration_map']): - if not hasattr(data, 'pokemon'): - import pokemon_red_eval - data.map_updater = pokemon_red_eval.map_updater() - data.map_buffer = np.zeros((data.config.num_envs, *pmap.shape)) - - data.map_buffer[idx] = pmap - - pokemon_map = np.sum(data.map_buffer, axis=0) - rendered = data.map_updater(pokemon_map) - data.stats['Media/exploration_map'] = data.wandb.Image(rendered) - - for k, v in infos.items(): - if 'Task_eval_fn' in k: - # Temporary hack for NMMO competitio - continue - try: # TODO: Better checks on log data types - data.stats[k] = np.mean(v) - except: - continue - - if config.verbose: - print_dashboard(data.stats, data.init_performance, data.performance) - - return data.stats, infos - -@pufferlib.utils.profile -def train(data): - if done_training(data): - raise RuntimeError( - f"Max training updates {data.total_updates} already reached") - - config = data.config - # assert data.num_steps % bptt_horizon == 0, "num_steps must be divisible by bptt_horizon" - train_profiler = pufferlib.utils.Profiler(memory=True, pytorch_memory=True) - train_profiler.start() - - if config.anneal_lr: - frac = 1.0 - (data.update - 1.0) / data.total_updates - lrnow = frac * config.learning_rate - data.optimizer.param_groups[0]["lr"] = lrnow - - num_minibatches = config.batch_size // config.bptt_horizon // config.batch_rows - idxs = sorted(range(len(data.sort_keys)), key=data.sort_keys.__getitem__) - data.sort_keys = [] - b_idxs = ( - torch.Tensor(idxs) - .long()[:-1] - .reshape(config.batch_rows, num_minibatches, config.bptt_horizon) - .transpose(0, 1) - ) - - # bootstrap value if not done - with torch.no_grad(): - advantages = torch.zeros(config.batch_size, device=data.device) - lastgaelam = 0 - for t in reversed(range(config.batch_size)): - i, i_nxt = idxs[t], idxs[t + 1] - nextnonterminal = 1.0 - data.dones[i_nxt] - nextvalues = data.values[i_nxt] - delta = ( - data.rewards[i_nxt] - + config.gamma * nextvalues * nextnonterminal - - data.values[i] - ) - advantages[t] = lastgaelam = ( - delta + config.gamma * config.gae_lambda * nextnonterminal * lastgaelam - ) - - # Flatten the batch - data.b_obs = b_obs = data.obs[b_idxs].to(data.device, non_blocking=True) # torch.Tensor(data.obs_ary[b_idxs]) - b_actions = torch.Tensor(data.actions_ary[b_idxs] - ).to(data.device, non_blocking=True) - b_logprobs = torch.Tensor(data.logprobs_ary[b_idxs] - ).to(data.device, non_blocking=True) - b_dones = torch.Tensor(data.dones_ary[b_idxs] - ).to(data.device, non_blocking=True) - b_values = torch.Tensor(data.values_ary[b_idxs] - ).to(data.device, non_blocking=True) - b_advantages = advantages.reshape( - config.batch_rows, num_minibatches, config.bptt_horizon - ).transpose(0, 1) - b_returns = b_advantages + b_values - - # Optimizing the policy and value network - train_time = time.time() - pg_losses, entropy_losses, v_losses, clipfracs, old_kls, kls = [], [], [], [], [], [] - - # Leanke - # mb_obs_buffer = torch.zeros_like(b_obs[0], pin_memory=(data.device=="cuda")) - - for epoch in range(config.update_epochs): - lstm_state = None - for mb in range(num_minibatches): - - # Leanke - mb_obs = b_obs[mb] - # mb_obs_buffer.copy_(b_obs[mb], non_blocking=True) - # mb_obs = mb_obs_buffer.to(data.device, non_blocking=True) - - - #mb_obs = b_obs[mb].to(data.device, non_blocking=True) - mb_actions = b_actions[mb].contiguous() - mb_values = b_values[mb].reshape(-1) - mb_advantages = b_advantages[mb].reshape(-1) - mb_returns = b_returns[mb].reshape(-1) - - if hasattr(data.agent, 'lstm'): - _, newlogprob, entropy, newvalue, lstm_state = data.agent( - mb_obs, state=lstm_state, action=mb_actions) - lstm_state = (lstm_state[0].detach(), lstm_state[1].detach()) - else: - _, newlogprob, entropy, newvalue = data.agent( - mb_obs.reshape(-1, *data.pool.single_observation_space.shape), - action=mb_actions, - ) - - logratio = newlogprob - b_logprobs[mb].reshape(-1) - ratio = logratio.exp() - - with torch.no_grad(): - # calculate approx_kl http://joschu.net/blog/kl-approx.html - old_approx_kl = (-logratio).mean() - old_kls.append(old_approx_kl.item()) - approx_kl = ((ratio - 1) - logratio).mean() - kls.append(approx_kl.item()) - clipfracs += [ - ((ratio - 1.0).abs() > config.clip_coef).float().mean().item() - ] - - mb_advantages = mb_advantages.reshape(-1) - if config.norm_adv: - mb_advantages = (mb_advantages - mb_advantages.mean()) / ( - mb_advantages.std() + 1e-8 - ) - - # Policy loss - pg_loss1 = -mb_advantages * ratio - pg_loss2 = -mb_advantages * torch.clamp( - ratio, 1 - config.clip_coef, 1 + config.clip_coef - ) - pg_loss = torch.max(pg_loss1, pg_loss2).mean() - pg_losses.append(pg_loss.item()) - - # Value loss - newvalue = newvalue.view(-1) - if config.clip_vloss: - v_loss_unclipped = (newvalue - mb_returns) ** 2 - v_clipped = mb_values + torch.clamp( - newvalue - mb_values, - -config.vf_clip_coef, - config.vf_clip_coef, - ) - v_loss_clipped = (v_clipped - mb_returns) ** 2 - v_loss_max = torch.max(v_loss_unclipped, v_loss_clipped) - v_loss = 0.5 * v_loss_max.mean() - else: - v_loss = 0.5 * ((newvalue - mb_returns) ** 2).mean() - v_losses.append(v_loss.item()) - - entropy_loss = entropy.mean() - entropy_losses.append(entropy_loss.item()) - - loss = pg_loss - config.ent_coef * entropy_loss + v_loss * config.vf_coef - data.optimizer.zero_grad() - loss.backward() - nn.utils.clip_grad_norm_(data.agent.parameters(), config.max_grad_norm) - data.optimizer.step() - - if config.target_kl is not None: - if approx_kl > config.target_kl: - break - - train_profiler.stop() - y_pred, y_true = b_values.cpu().numpy(), b_returns.cpu().numpy() - var_y = np.var(y_true) - explained_var = np.nan if var_y == 0 else 1 - np.var(y_true - y_pred) / var_y - - losses = data.losses - losses.policy_loss = np.mean(pg_losses) - losses.value_loss = np.mean(v_losses) - losses.entropy = np.mean(entropy_losses) - losses.old_approx_kl = np.mean(old_kls) - losses.approx_kl = np.mean(kls) - losses.clipfrac = np.mean(clipfracs) - losses.explained_variance = explained_var - - perf = data.performance - perf.total_uptime = int(time.time() - data.start_time) - perf.total_updates = data.update + 1 - perf.train_time = time.time() - train_time - perf.train_sps = int(config.batch_size / perf.train_time) - perf.train_memory = train_profiler.end_mem - perf.train_pytorch_memory = train_profiler.end_torch_mem - perf.epoch_time = perf.eval_time + perf.train_time - perf.epoch_sps = int(config.batch_size / perf.epoch_time) - - if config.verbose: - print_dashboard(data.stats, data.init_performance, data.performance) - - data.update += 1 - if data.update % config.checkpoint_interval == 0 or done_training(data): - save_checkpoint(data) - -def close(data): - data.pool.close() - - if data.wandb is not None: - artifact_name = f"{data.exp_name}_model" - artifact = data.wandb.Artifact(artifact_name, type="model") - model_path = save_checkpoint(data) - artifact.add_file(model_path) - data.wandb.run.log_artifact(artifact) - data.wandb.finish() - -def rollout(env_creator, env_kwargs, agent_creator, agent_kwargs, - model_path=None, device='cuda', verbose=True): - env = env_creator(**env_kwargs) - if model_path is None: - agent = agent_creator(env, **agent_kwargs) - else: - agent = torch.load(model_path, map_location=device) - - terminal = truncated = True - - while True: - if terminal or truncated: - if verbose: - print('--- Reset ---') - - ob, info = env.reset() - state = None - step = 0 - return_val = 0 - - ob = torch.tensor(ob).unsqueeze(0).to(device) - with torch.no_grad(): - if hasattr(agent, 'lstm'): - action, _, _, _, state = agent(ob, state) - else: - action, _, _, _ = agent(ob) - - ob, reward, terminal, truncated, _ = env.step(action[0].item()) - return_val += reward - - chars = env.render() - print("\033c", end="") - print(chars) - - if verbose: - print(f'Step: {step} Reward: {reward:.4f} Return: {return_val:.2f}') - - time.sleep(0.5) - step += 1 - -def done_training(data): - return data.update >= data.total_updates - -def save_checkpoint(data): - path = os.path.join(data.config.data_dir, data.exp_name) - if not os.path.exists(path): - os.makedirs(path) - - model_name = f'model_{data.update:06d}.pt' - model_path = os.path.join(path, model_name) - - # Already saved - if os.path.exists(model_path): - return model_path - - torch.save(data.uncompiled_agent, model_path) - - state = { - "optimizer_state_dict": data.optimizer.state_dict(), - "global_step": data.global_step, - "agent_step": data.global_step, - "update": data.update, - "model_name": model_name, - } - - if data.wandb: - state['exp_name'] = data.exp_name - - state_path = os.path.join(path, 'trainer_state.pt') - torch.save(state, state_path + '.tmp') - os.rename(state_path + '.tmp', state_path) - - return model_path - -def seed_everything(seed, torch_deterministic): - random.seed(seed) - np.random.seed(seed) - if seed is not None: - torch.manual_seed(seed) - torch.backends.cudnn.deterministic = torch_deterministic - -def unroll_nested_dict(d): - if not isinstance(d, dict): - return d - - for k, v in d.items(): - if isinstance(v, dict): - for k2, v2 in unroll_nested_dict(v): - yield f"{k}/{k2}", v2 - else: - yield k, v - -def print_dashboard(stats, init_performance, performance): - output = [] - data = {**stats, **init_performance, **performance} - - grouped_data = defaultdict(dict) - - for k, v in data.items(): - if k == 'total_uptime': - v = timedelta(seconds=v) - if 'memory' in k: - v = pufferlib.utils.format_bytes(v) - elif 'time' in k: - try: - v = f"{v:.2f} s" - except: - pass - - first_word, *rest_words = k.split('_') - rest_words = ' '.join(rest_words).title() - - grouped_data[first_word][rest_words] = v - - for main_key, sub_dict in grouped_data.items(): - output.append(f"{main_key.title()}") - for sub_key, sub_value in sub_dict.items(): - output.append(f" {sub_key}: {sub_value}") - - print("\033c", end="") - print('\n'.join(output)) - time.sleep(1/20) diff --git a/config.yaml b/config.yaml deleted file mode 100644 index 986c7a5..0000000 --- a/config.yaml +++ /dev/null @@ -1,84 +0,0 @@ -train: - seed: 1 - torch_deterministic: True - device: cuda - total_timesteps: 10_000_000 - learning_rate: 2.5e-4 - num_steps: 128 - anneal_lr: True - gamma: 0.99 - gae_lambda: 0.95 - num_minibatches: 4 - update_epochs: 4 - norm_adv: True - clip_coef: 0.1 - clip_vloss: True - ent_coef: 0.01 - vf_coef: 0.5 - max_grad_norm: 0.5 - target_kl: ~ - - num_envs: 8 - envs_per_worker: 1 - envs_per_batch: ~ - env_pool: True - verbose: True - data_dir: experiments - checkpoint_interval: 200 - pool_kernel: [0] - batch_size: 1024 - batch_rows: 32 - bptt_horizon: 16 #8 - vf_clip_coef: 0.1 - compile: False - compile_mode: reduce-overhead - -sweep: - method: random - name: sweep - metric: - goal: maximize - name: episodic_return - # Nested parameters name required by WandB API - parameters: - train: - parameters: - learning_rate: { - 'distribution': 'log_uniform_values', - 'min': 1e-4, - 'max': 1e-1, - } - batch_size: { - 'values': [128, 256, 512, 1024, 2048], - } - batch_rows: { - 'values': [16, 32, 64, 128, 256], - } - bptt_horizon: { - 'values': [4, 8, 16, 32], - } - -pokemon_red: - package: pokemon_red - train: - total_timesteps: 1_500_000_000 - num_envs: 96 - envs_per_worker: 1 - envs_per_batch: 32 - update_epochs: 3 - gamma: 0.998 - batch_size: 65536 - batch_rows: 128 - compile: True - learning_rate: 2.0e-4 - anneal_lr: False - env: - name: pokemon_red -pokemon-red: - package: pokemon_red -pokemonred: - package: pokemon_red -pokemon: - package: pokemon_red -pokegym: - package: pokemon_red diff --git a/pokegym/environment.py b/pokegym/environment.py index 57ae726..61af6d9 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -1,6 +1,5 @@ from pathlib import Path from pdb import set_trace as T -import types import uuid from gymnasium import Env, spaces import numpy as np @@ -14,7 +13,6 @@ from pathlib import Path import mediapy as media -from hideout_baseline.pokegym import ram_map from pokegym.pyboy_binding import ( ACTIONS, make_env, @@ -22,7 +20,7 @@ load_pyboy_state, run_action_on_emulator, ) -from pokegym import data +from pokegym import data, ram_map STATE_PATH = __file__.rstrip("environment.py") + "States/" @@ -63,6 +61,7 @@ def __init__( self.use_screen_memory = True self.screenshot_counter = 0 self.env_id = Path(f'{str(uuid.uuid4())[:4]}') + self.s_path = Path(f"videos/{self.env_id}") self.reset_count = 0 self.explore_hidden_obj_weight = 1 @@ -80,15 +79,6 @@ def __init__( low=0, high=255, dtype=np.uint8, shape=self.obs_size ) self.action_space = spaces.Discrete(len(ACTIONS)) - - def save_screenshot(self, event, map_n): - self.screenshot_counter += 1 - ss_dir = Path('screenshots') - ss_dir.mkdir(exist_ok=True) - plt.imsave( - # ss_dir / Path(f'ss_{x}_y_{y}_steps_{steps}_{comment}.jpeg'), - ss_dir / Path(f'{self.screenshot_counter}_{event}_{map_n}.jpeg'), - self.screen.screen_ndarray()) # (144, 160, 3) def save_state(self): state = io.BytesIO() @@ -173,7 +163,6 @@ def close(self): class Environment(Base): def __init__(self,rom_path="pokemon_red.gb",state_path=None,headless=True,save_video=False,quiet=False,verbose=False,**kwargs,): - super().__init__(rom_path, state_path, headless, save_video, quiet, **kwargs) self.counts_map = np.zeros((444, 436)) self.death_count = 0 @@ -232,52 +221,8 @@ def update_moves_obtained(self): def add_video_frame(self): self.full_frame_writer.add_image(self.video()) - def get_game_coords(self): - return (ram_map.mem_val(self.game, 0xD362), ram_map.mem_val(self.game, 0xD361), ram_map.mem_val(self.game, 0xD35E)) - - def check_if_in_start_menu(self) -> bool: - return ( - ram_map.mem_val(self.game, 0xD057) == 0 - and ram_map.mem_val(self.game, 0xCF13) == 0 - and ram_map.mem_val(self.game, 0xFF8C) == 6 - and ram_map.mem_val(self.game, 0xCF94) == 0 - ) - - def check_if_in_pokemon_menu(self) -> bool: - return ( - ram_map.mem_val(self.game, 0xD057) == 0 - and ram_map.mem_val(self.game, 0xCF13) == 0 - and ram_map.mem_val(self.game, 0xFF8C) == 6 - and ram_map.mem_val(self.game, 0xCF94) == 2 - ) - - def check_if_in_stats_menu(self) -> bool: - return ( - ram_map.mem_val(self.game, 0xD057) == 0 - and ram_map.mem_val(self.game, 0xCF13) == 0 - and ram_map.mem_val(self.game, 0xFF8C) == 6 - and ram_map.mem_val(self.game, 0xCF94) == 1 - ) - - def check_if_in_bag_menu(self) -> bool: - return ( - ram_map.mem_val(self.game, 0xD057) == 0 - and ram_map.mem_val(self.game, 0xCF13) == 0 - # and newram_map.mem_val(self.game, 0xFF8C) == 6 # only sometimes - and ram_map.mem_val(self.game, 0xCF94) == 3 - ) - def check_if_cancel_bag_menu(self, action) -> bool: - return ( - action == WindowEvent.PRESS_BUTTON_A - and ram_map.mem_val(self.game, 0xD057) == 0 - and ram_map.mem_val(self.game, 0xCF13) == 0 - # and newram_map.mem_val(self.game, 0xFF8C) == 6 - and ram_map.mem_val(self.game, 0xCF94) == 3 - and ram_map.mem_val(self.game, 0xD31D) == ram_map.mem_val(self.game, 0xCC36) + ram_map.mem_val(self.game, 0xCC26) - ) - - def reset(self, seed=None, options=None, max_episode_steps=2048, reward_scale=4.0): + def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" self.reset_count += 1 @@ -320,9 +265,6 @@ def reset(self, seed=None, options=None, max_episode_steps=2048, reward_scale=4. self.seen_pokemon = np.zeros(152, dtype=np.uint8) self.caught_pokemon = np.zeros(152, dtype=np.uint8) self.moves_obtained = {} # np.zeros(255, dtype=np.uint8) - self.town = 1 - self.gymthree = 0 - self.gymfour = 0 return self.render(), {} @@ -379,11 +321,6 @@ def step(self, action, fast_video=True): else: exploration_reward = (0.02 * len(self.seen_coords)) - if map_n == 92: - self.gymthree = 1 - if map_n == 134: - self.gymfour = 1 - # Level reward party_size, party_levels = ram_map.party(self.game) self.max_level_sum = max(self.max_level_sum, sum(party_levels)) @@ -421,15 +358,14 @@ def step(self, action, fast_video=True): if ram_map.mem_val(self.game, 0xD057) == 0: # is_in_battle if 1 if self.cut == 1: player_direction = self.game.get_memory_value(0xC109) - x, y, map_id = self.get_game_coords() # x, y, map_id if player_direction == 0: # down - coords = (x, y + 1, map_id) + coords = (c, r + 1, map_n) if player_direction == 4: - coords = (x, y - 1, map_id) + coords = (c, r - 1, map_n) if player_direction == 8: - coords = (x - 1, y, map_id) + coords = (c - 1, r, map_n) if player_direction == 0xC: - coords = (x + 1, y, map_id) + coords = (c + 1, r, map_n) self.cut_state.append( ( self.game.get_memory_value(0xCFC6), @@ -450,16 +386,15 @@ def step(self, action, fast_video=True): self.cut_coords[coords] = 0.001 self.cut_tiles[self.cut_state[-1][0]] = 1 if int(ram_map.read_bit(self.game, 0xD803, 0)): - if self.check_if_in_start_menu(): + if ram_map.check_if_in_start_menu(self.game): self.seen_start_menu = 1 - if self.check_if_in_pokemon_menu(): + if ram_map.check_if_in_pokemon_menu(self.game): self.seen_pokemon_menu = 1 - if self.check_if_in_stats_menu(): + if ram_map.check_if_in_stats_menu(self.game): self.seen_stats_menu = 1 - if self.check_if_in_bag_menu(): + if ram_map.check_if_in_bag_menu(self.game): self.seen_bag_menu = 1 - if self.check_if_cancel_bag_menu(action): - self.seen_cancel_bag_menu = 1 + if ram_map.used_cut(self.game) == 61: ram_map.write_mem(self.game, 0xCD4D, 00) # address, byte to write resets tile check @@ -574,7 +509,7 @@ def step(self, action, fast_video=True): "gym8": gym8, "rival": rival, }, - "BET": { + "Rewards": { "Reward_Delta": reward, "Seen_Poke": seen_pokemon_reward, "Caught_Poke": caught_pokemon_reward, @@ -612,8 +547,6 @@ def step(self, action, fast_video=True): 'pokemon_menu': pokemon_menu, 'start_menu': start_menu, 'used_cut': self.used_cut, - 'gym_three': self.gymthree, - 'gym_four': self.gymfour, } return self.render(), reward, done, done, info diff --git a/pokegym/ram_map.py b/pokegym/ram_map.py index faf9c6d..0682930 100644 --- a/pokegym/ram_map.py +++ b/pokegym/ram_map.py @@ -735,7 +735,37 @@ def badges(game): badges = game.get_memory_value(BADGE_1_ADDR) return bit_count(badges) - +def check_if_in_start_menu(game) -> bool: + return ( + mem_val(game, 0xD057) == 0 + and mem_val(game, 0xCF13) == 0 + and mem_val(game, 0xFF8C) == 6 + and mem_val(game, 0xCF94) == 0 + ) + +def check_if_in_pokemon_menu(game) -> bool: + return ( + mem_val(game, 0xD057) == 0 + and mem_val(game, 0xCF13) == 0 + and mem_val(game, 0xFF8C) == 6 + and mem_val(game, 0xCF94) == 2 + ) + +def check_if_in_stats_menu(game) -> bool: + return ( + mem_val(game, 0xD057) == 0 + and mem_val(game, 0xCF13) == 0 + and mem_val(game, 0xFF8C) == 6 + and mem_val(game, 0xCF94) == 1 + ) + +def check_if_in_bag_menu(game) -> bool: + return ( + mem_val(game, 0xD057) == 0 + and mem_val(game, 0xCF13) == 0 + # and mem_val(game, 0xFF8C) == 6 # only sometimes + and mem_val(game, 0xCF94) == 3 + ) diff --git a/pokemon_red/__init__.py b/pokemon_red/__init__.py deleted file mode 100644 index eff86ef..0000000 --- a/pokemon_red/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from .environment import env_creator, make - -try: - import torch -except ImportError: - pass -else: - from .torch import Policy - try: - from .torch import Recurrent - except: - Recurrent = None diff --git a/pokemon_red/environment.py b/pokemon_red/environment.py deleted file mode 100644 index 87957d9..0000000 --- a/pokemon_red/environment.py +++ /dev/null @@ -1,26 +0,0 @@ -from pdb import set_trace as T - -import gymnasium -import functools -import uuid - -from pokegym import Environment -import pufferlib.emulation -from .stream_wrapper import StreamWrapper - - - -def env_creator(name='pokemon_red'): - return functools.partial(make, name) - -def make(name, headless: bool = True, state_path=None): - '''Pokemon Red''' - env = Environment(headless=headless, state_path=state_path) - env = StreamWrapper(env, stream_metadata = { # stream_metadata is optional - "user": f"username\n", # your username - "color": "", # color for your text :) - "extra": "", # any extra text you put here will be displayed - } - ) - return pufferlib.emulation.GymnasiumPufferEnv(env=env, - postprocessor_cls=pufferlib.emulation.BasicPostprocessor) diff --git a/pokemon_red/stream_wrapper.py b/pokemon_red/stream_wrapper.py deleted file mode 100644 index 3aaddcc..0000000 --- a/pokemon_red/stream_wrapper.py +++ /dev/null @@ -1,101 +0,0 @@ -import asyncio -import websockets -import json - -import gymnasium as gym -from pokegym import ram_map, data - -#colors -BLUE = "#0000FF" -GREEN = "#00A36C" -RED = "#FF0000" -PURPLE = "#800080" -PINK = "#FF00FF" -YELLOW = "#DAEE01" - -POKE = [0xD16B, 0xD197, 0xD1C3, 0xD1EF, 0xD21B, 0xD247] -LEVEL = [0xD18C, 0xD1B8, 0xD1E4, 0xD210, 0xD23C, 0xD268] -X_POS_ADDRESS, Y_POS_ADDRESS = 0xD362, 0xD361 -MAP_N_ADDRESS = 0xD35E - -class StreamWrapper(gym.Wrapper): - def __init__(self, env, stream_metadata={}): - super().__init__(env) - self.ws_address = "wss://transdimensional.xyz/broadcast" # "ws://theleanke.com/broadcast" - self.stream_metadata = stream_metadata - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.loop) - self.websocket = self.loop.run_until_complete( - self.establish_wc_connection() - ) - self.upload_interval = 500 - self.steam_step_counter = 0 - self.env = env - self.coord_list = [] - self.leanke = {} - self.cut = 0 - - if hasattr(env, "pyboy"): - self.emulator = env.pyboy - elif hasattr(env, "game"): - self.emulator = env.game - else: - raise Exception("Could not find emulator!") - - - def step(self, action): - - x_pos = self.emulator.get_memory_value(X_POS_ADDRESS) - y_pos = self.emulator.get_memory_value(Y_POS_ADDRESS) - map_n = self.emulator.get_memory_value(MAP_N_ADDRESS) - hm = self.env.hm_count - self.coord_list.append([x_pos, y_pos, map_n]) - self.cut = self.env.cut - - poke0 = self.emulator.get_memory_value(POKE[0]) - lvl0 = self.emulator.get_memory_value(LEVEL[0]) - name_info6 = data.poke_dict.get(poke0, {}) - name0 = name_info6.get("name", "") - - if self.steam_step_counter >= self.upload_interval: - - if self.cut >= 1: - self.stream_metadata['color'] = GREEN - elif hm >= 1 and self.cut == 0: - self.stream_metadata['color'] = YELLOW - elif hm == 0 and self.cut == 0: - self.stream_metadata['color'] = RED - if int(ram_map.read_bit(self.emulator, 0xD76C, 0)) == 1: - self.stream_metadata['color'] = PURPLE - self.stream_metadata['extra'] = f"{self.env.reset_count} ~ {name0}: {lvl0}" - - self.loop.run_until_complete( - self.broadcast_ws_message( - json.dumps( - { - "metadata": self.stream_metadata, - "coords": self.coord_list, - }))) - - self.steam_step_counter = 0 - self.coord_list = [] - - self.steam_step_counter += 1 - - return self.env.step(action) - - async def broadcast_ws_message(self, message): - if self.websocket is None: - await self.establish_wc_connection() - if self.websocket is not None: - try: - await self.websocket.send(message) - except websockets.exceptions.WebSocketException as e: - self.websocket = None - - async def establish_wc_connection(self): - try: - self.websocket = await websockets.connect(self.ws_address) - except: - self.websocket = None - diff --git a/pokemon_red/torch.py b/pokemon_red/torch.py deleted file mode 100644 index acb51b3..0000000 --- a/pokemon_red/torch.py +++ /dev/null @@ -1,29 +0,0 @@ -import pufferlib.models - - -class Recurrent(pufferlib.models.RecurrentWrapper): - def __init__(self, env, policy, input_size=512, hidden_size=512, num_layers=1): - super().__init__(env, policy, input_size, hidden_size, num_layers) - -class Policy(pufferlib.models.Convolutional): - def __init__(self, env, input_size=512, hidden_size=512, output_size=512, - framestack=4, flat_size=64*5*6): - super().__init__( - env=env, - input_size=input_size, - hidden_size=hidden_size, - output_size=output_size, - framestack=framestack, - flat_size=flat_size, - channels_last=True, - ) - -''' -class Policy(pufferlib.models.ProcgenResnet): - def __init__(self, env, cnn_width=16, mlp_width=512): - super().__init__( - env=env, - cnn_width=cnn_width, - mlp_width=mlp_width, - ) -''' diff --git a/pyproject.toml b/pyproject.toml index f6a688a..6394bc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,6 @@ dependencies = [ "opencv-python", "numpy", "pyboy<2.0.0", - "pufferlib[cleanrl]>=0.7.3", "torch>=2.1", "torchvision", "wandb", diff --git a/run.sh b/run.sh deleted file mode 100755 index 8b59b59..0000000 --- a/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -python train.py --config pokemon_red --vectorization multiprocessing --mode train # --wandb-entity WANDBUSERNAME --track \ No newline at end of file diff --git a/train.py b/train.py deleted file mode 100644 index 6ab0ec4..0000000 --- a/train.py +++ /dev/null @@ -1,260 +0,0 @@ -from pdb import set_trace as T -import argparse -import shutil -import sys -import os - -import importlib -import inspect -import yaml - -import pufferlib -import pufferlib.utils - -import clean_pufferl - - -def load_from_config(env): - with open('config.yaml') as f: - config = yaml.safe_load(f) - - assert env in config, f'"{env}" not found in config.yaml. Uncommon environments that are part of larger packages may not have their own config. Specify these manually using the parent package, e.g. --config atari --env MontezumasRevengeNoFrameskip-v4.' - - default_keys = 'env train policy recurrent sweep_metadata sweep_metric sweep'.split() - defaults = {key: config.get(key, {}) for key in default_keys} - - # Package and subpackage (environment) configs - env_config = config[env] - pkg = env_config['package'] - pkg_config = config[pkg] - - combined_config = {} - for key in default_keys: - env_subconfig = env_config.get(key, {}) - pkg_subconfig = pkg_config.get(key, {}) - - # Override first with pkg then with env configs - combined_config[key] = {**defaults[key], **pkg_subconfig, **env_subconfig} - - return pkg, pufferlib.namespace(**combined_config) - -def make_policy(env, env_module, args): - policy = env_module.Policy(env, **args.policy) - if args.force_recurrence or env_module.Recurrent is not None: - policy = env_module.Recurrent(env, policy, **args.recurrent) - policy = pufferlib.frameworks.cleanrl.RecurrentPolicy(policy) - else: - policy = pufferlib.frameworks.cleanrl.Policy(policy) - - return policy.to(args.train.device) - -def init_wandb(args, env_module, name=None, resume=True): - #os.environ["WANDB_SILENT"] = "true" - - import wandb - return wandb.init( - id=args.exp_name or wandb.util.generate_id(), - project=args.wandb_project, - entity=args.wandb_entity, - group=args.wandb_group, - config={ - 'cleanrl': args.train, - 'env': args.env, - 'policy': args.policy, - 'recurrent': args.recurrent, - }, - name=name or args.config, - monitor_gym=True, - save_code=True, - resume=resume, - ) - -def sweep(args, env_module, make_env): - import wandb - sweep_id = wandb.sweep(sweep=args.sweep, project="pufferlib") - - def main(): - try: - args.exp_name = init_wandb(args, env_module) - if hasattr(wandb.config, 'train'): - # TODO: Add update method to namespace - print(args.train.__dict__) - print(wandb.config.train) - args.train.__dict__.update(dict(wandb.config.train)) - train(args, env_module, make_env) - except Exception as e: - import traceback - traceback.print_exc() - - wandb.agent(sweep_id, main, count=20) - -def get_init_args(fn): - if fn is None: - return {} - - sig = inspect.signature(fn) - args = {} - for name, param in sig.parameters.items(): - if name in ('self', 'env', 'policy'): - continue - if param.kind == inspect.Parameter.VAR_POSITIONAL: - continue - elif param.kind == inspect.Parameter.VAR_KEYWORD: - continue - else: - args[name] = param.default if param.default is not inspect.Parameter.empty else None - return args - -def train(args, env_module, make_env): - if args.backend == 'clean_pufferl': - data = clean_pufferl.create( - config=args.train, - agent_creator=make_policy, - agent_kwargs={'env_module': env_module, 'args': args}, - env_creator=make_env, - env_creator_kwargs=args.env, - vectorization=args.vectorization, - exp_name=args.exp_name, - track=args.track, - ) - - while not clean_pufferl.done_training(data): - clean_pufferl.evaluate(data) - clean_pufferl.train(data) - - print('Done training. Saving data...') - clean_pufferl.close(data) - print('Run complete') - elif args.backend == 'sb3': - from stable_baselines3 import PPO - from stable_baselines3.common.vec_env import DummyVecEnv, SubprocVecEnv - from stable_baselines3.common.env_util import make_vec_env - from sb3_contrib import RecurrentPPO - - envs = make_vec_env(lambda: make_env(**args.env), - n_envs=args.train.num_envs, seed=args.train.seed, vec_env_cls=DummyVecEnv) - - model = RecurrentPPO("CnnLstmPolicy", envs, verbose=1, - n_steps=args.train.batch_rows*args.train.bptt_horizon, - batch_size=args.train.batch_size, n_epochs=args.train.update_epochs, - gamma=args.train.gamma - ) - - model.learn(total_timesteps=args.train.total_timesteps) - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Parse environment argument', add_help=False) - parser.add_argument('--backend', type=str, default='clean_pufferl', help='Train backend (clean_pufferl, sb3)') - parser.add_argument('--config', type=str, default='pokemon_red', help='Configuration in config.yaml to use') - parser.add_argument('--env', type=str, default=None, help='Name of specific environment to run') - parser.add_argument('--mode', type=str, default='train', choices='train sweep evaluate'.split()) - parser.add_argument('--eval-model-path', type=str, default=None, help='Path to model to evaluate') - parser.add_argument('--baseline', action='store_true', help='Baseline run') - parser.add_argument('--no-render', action='store_true', help='Disable render during evaluate') - parser.add_argument('--exp-name', type=str, default=None, help="Resume from experiment") - parser.add_argument('--vectorization', type=str, default='serial', choices='serial multiprocessing ray'.split()) - parser.add_argument('--wandb-entity', type=str, default='jsuarez', help='WandB entity') - parser.add_argument('--wandb-project', type=str, default='pufferlib', help='WandB project') - parser.add_argument('--wandb-group', type=str, default='debug', help='WandB group') - parser.add_argument('--track', action='store_true', help='Track on WandB') - parser.add_argument('--force-recurrence', action='store_true', help='Force model to be recurrent, regardless of defaults') - - clean_parser = argparse.ArgumentParser(parents=[parser]) - args = parser.parse_known_args()[0].__dict__ - pkg, config = load_from_config(args['config']) - - try: - env_module = importlib.import_module(f'{pkg}') - except: - pufferlib.utils.install_requirements(pkg) - env_module = importlib.import_module(f'{pkg}') - - # Get the make function for the environment - env_name = args['env'] or config.env.pop('name') - make_env = env_module.env_creator(env_name) - - # Update config with environment defaults - config.env = {**get_init_args(make_env), **config.env} - config.policy = {**get_init_args(env_module.Policy.__init__), **config.policy} - config.recurrent = {**get_init_args(env_module.Recurrent.__init__), **config.recurrent} - - # Generate argparse menu from config - for name, sub_config in config.items(): - args[name] = {} - for key, value in sub_config.items(): - data_key = f'{name}.{key}' - cli_key = f'--{data_key}'.replace('_', '-') - if isinstance(value, bool) and value is False: - action = 'store_false' - parser.add_argument(cli_key, default=value, action='store_true') - clean_parser.add_argument(cli_key, default=value, action='store_true') - elif isinstance(value, bool) and value is True: - data_key = f'{name}.no_{key}' - cli_key = f'--{data_key}'.replace('_', '-') - parser.add_argument(cli_key, default=value, action='store_false') - clean_parser.add_argument(cli_key, default=value, action='store_false') - else: - parser.add_argument(cli_key, default=value, type=type(value)) - clean_parser.add_argument(cli_key, default=value, metavar='', type=type(value)) - - args[name][key] = getattr(parser.parse_known_args()[0], data_key) - args[name] = pufferlib.namespace(**args[name]) - - clean_parser.parse_args(sys.argv[1:]) - args = pufferlib.namespace(**args) - - vec = args.vectorization - if vec == 'serial': - args.vectorization = pufferlib.vectorization.Serial - elif vec == 'multiprocessing': - args.vectorization = pufferlib.vectorization.Multiprocessing - elif vec == 'ray': - args.vectorization = pufferlib.vectorization.Ray - else: - raise ValueError(f'Invalid --vectorization (serial/multiprocessing/ray).') - - if args.mode == 'sweep': - args.track = True - elif args.track: - args.exp_name = init_wandb(args, env_module).id - elif args.baseline: - args.track = True - version = '.'.join(pufferlib.__version__.split('.')[:2]) - args.exp_name = f'puf-{version}-{args.config}' - args.wandb_group = f'puf-{version}-baseline' - shutil.rmtree(f'experiments/{args.exp_name}', ignore_errors=True) - run = init_wandb(args, env_module, name=args.exp_name, resume=False) - if args.mode == 'evaluate': - model_name = f'puf-{version}-{args.config}_model:latest' - artifact = run.use_artifact(model_name) - data_dir = artifact.download() - model_file = max(os.listdir(data_dir)) - args.eval_model_path = os.path.join(data_dir, model_file) - - if args.mode == 'train': - train(args, env_module, make_env) - exit(0) - elif args.mode == 'sweep': - sweep(args, env_module, make_env) - exit(0) - elif args.mode == 'evaluate' and pkg != 'pokemon_red': - rollout( - make_env, - args.env, - agent_creator=make_policy, - agent_kwargs={'env_module': env_module, 'args': args}, - model_path=args.eval_model_path, - device=args.train.device - ) - elif args.mode == 'evaluate' and pkg == 'pokemon_red': - import pokemon_red_eval - pokemon_red_eval.rollout( - make_env, - args.env, - agent_creator=make_policy, - agent_kwargs={'env_module': env_module, 'args': args}, - model_path=args.eval_model_path, - device=args.train.device, - ) - elif pkg != 'pokemon_red': - raise ValueError('Mode must be one of train, sweep, or evaluate') From 0231ea6a94a7ca2974a2df131f10e72c7359b603 Mon Sep 17 00:00:00 2001 From: leanke Date: Fri, 7 Jun 2024 19:47:46 +0000 Subject: [PATCH 28/29] puffer1.0 cleanup --- pokegym/environment.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index 61af6d9..32fcd0c 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -7,9 +7,7 @@ from collections import defaultdict, deque import io, os import random -from pyboy.utils import WindowEvent -import matplotlib.pyplot as plt from pathlib import Path import mediapy as media @@ -46,6 +44,9 @@ def __init__( quiet=False, **kwargs, ): + if rom_path is None: + raise FileNotFoundError("No ROM file found in the specified directory.") + self.state_file = get_random_state() self.randstate = os.path.join(STATE_PATH, self.state_file) """Creates a PokemonRed environment""" @@ -177,7 +178,7 @@ def __init__(self,rom_path="pokemon_red.gb",state_path=None,headless=True,save_v # self.seen_coords = set() self.map_check = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] self.poketower = [142, 143, 144, 145, 146, 147, 148] - self.pokehideout = [199, 200, 201, 202, 203] + self.pokehideout = [199, 200, 201, 202, 203, 135] self.silphco = [181, 207, 208, 209, 210, 211, 212, 213, 233, 234, 235, 236] load_pyboy_state(self.game, self.load_last_state()) @@ -221,7 +222,6 @@ def update_moves_obtained(self): def add_video_frame(self): self.full_frame_writer.add_image(self.video()) - def reset(self, seed=None, options=None, max_episode_steps=20480, reward_scale=4.0): """Resets the game. Seeding is NOT supported""" self.reset_count += 1 From f811e25978346965ee855dd445c4e55352c5afd6 Mon Sep 17 00:00:00 2001 From: Leanke <49460411+leanke@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:53:17 -0600 Subject: [PATCH 29/29] Update environment.py --- pokegym/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokegym/environment.py b/pokegym/environment.py index 32fcd0c..db35187 100644 --- a/pokegym/environment.py +++ b/pokegym/environment.py @@ -44,7 +44,7 @@ def __init__( quiet=False, **kwargs, ): - if rom_path is None: + if rom_path is None or not os.path.exists(rom_path): raise FileNotFoundError("No ROM file found in the specified directory.") self.state_file = get_random_state()

VrHJ^DrKxqm;u=!4;$T3}P`Nq@v%?DrRqW9Ez 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 KVDu^w4E}%IRTIeo literal 0 HcmV?d00001 diff --git a/pokegym/States/bulba/fuschia.state b/pokegym/States/bulba/fuschia.state new file mode 100644 index 0000000000000000000000000000000000000000..d48728fb6ff76b65acbecb8dc6294e8f16b0225b GIT binary patch literal 142610 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 literal 0 HcmV?d00001 diff --git a/pokegym/States/bulba/lavender.state b/pokegym/States/bulba/lavender.state new file mode 100644 index 0000000000000000000000000000000000000000..db825b1d356ab909d58445fa051ab5711a6b487c GIT binary patch literal 142610 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 B_;dgO literal 0 HcmV?d00001 diff --git a/pokegym/States/bulba/mt_moon.state b/pokegym/States/bulba/mt_moon.state new file mode 100644 index 0000000000000000000000000000000000000000..f42f79dd5725f416ce9e01d872161619cb60b2dd GIT binary patch literal 142610 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 literal 0 HcmV?d00001 diff --git a/pokegym/States/bulba/rock_tunnel.state b/pokegym/States/bulba/rock_tunnel.state new file mode 100644 index 0000000000000000000000000000000000000000..494500bbeee05ed996f6c02bf71085a741fbbea5 GIT binary patch literal 142610 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 {>x-Pn? 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 DK9)u! literal 0 HcmV?d00001 diff --git a/pokegym/States/bulba/saffron.state b/pokegym/States/bulba/saffron.state new file mode 100644 index 0000000000000000000000000000000000000000..e97fabbc81f7ff5da7f96b79b5d79bb05139e4c9 GIT binary patch literal 142610 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