def save(self, path=None, fps=60): logger.info("ScreenRecorder saving...") if path is None: directory = os.path.join(os.path.curdir, "recordings") if not os.path.exists(directory): os.makedirs(directory, mode=0o755) path = os.path.join( directory, time.strftime( f"{self.pyboy.cartridge_title()}-%Y.%m.%d-%H.%M.%S.gif")) if len(self.frames) > 0: self.frames[0].save(path, save_all=True, interlace=False, loop=0, optimize=True, append_images=self.frames[1:], duration=int(round(1000 / fps, -1))) logger.info("Screen recording saved in {}".format(path)) else: logger.error("Screen recording failed: no frames") self.frames = []
def openai_gym(self, observation_type="tiles", action_type="press", simultaneous_actions=False, **kwargs): """ For Reinforcement learning, it is often easier to use the standard gym environment. This method will provide one. This function requires PyBoy to implement a Game Wrapper for the loaded ROM. You can find the supported games in pyboy.plugins. Additional kwargs are passed to the start_game method of the game_wrapper. Args: observation_type (str): Define what the agent will be able to see: * `"raw"`: Gives the raw pixels color * `"tiles"`: Gives the id of the sprites in 8x8 pixel zones of the game_area defined by the game_wrapper. * `"compressed"`: Gives a more detailled but heavier representation than `"minimal"`. * `"minimal"`: Gives a minimal representation defined by the game_wrapper (recommended). action_type (str): Define how the agent will interact with button inputs * `"press"`: The agent will only press inputs for 1 frame an then release it. * `"toggle"`: The agent will toggle inputs, first time it press and second time it release. * `"all"`: The agent have acces to all inputs, press and release are separated. simultaneous_actions (bool): Allow to inject multiple input at once. This dramatically increases the action_space: \\(n \\rightarrow 2^n\\) Returns ------- `pyboy.openai_gym.PyBoyGymEnv`: A Gym environment based on the `Pyboy` object. """ if gym_enabled: return PyBoyGymEnv(self, observation_type, action_type, simultaneous_actions, **kwargs) else: logger.error(f"{__name__}: Missing dependency \"gym\". ") return None
def _handle_events(self, events): # This feeds events into the tick-loop from the window. There might already be events in the list from the API. events = self.plugin_manager.handle_events(events) for event in events: if event == WindowEvent.QUIT: self.done = True elif event == WindowEvent.RELEASE_SPEED_UP: # Switch between unlimited and 1x real-time emulation speed self.target_emulationspeed = int(bool(self.target_emulationspeed) ^ True) logger.info("Speed limit: %s" % self.target_emulationspeed) elif event == WindowEvent.STATE_SAVE: with open(self.gamerom_file + ".state", "wb") as f: self.mb.save_state(IntIOWrapper(f)) elif event == WindowEvent.STATE_LOAD: state_path = self.gamerom_file + ".state" if not os.path.isfile(state_path): logger.error(f"State file not found: {state_path}") continue with open(state_path, "rb") as f: self.mb.load_state(IntIOWrapper(f)) elif event == WindowEvent.PASS: pass # Used in place of None in Cython, when key isn't mapped to anything elif event == WindowEvent.PAUSE_TOGGLE: if self.paused: self._unpause() else: self._pause() elif event == WindowEvent.PAUSE: self._pause() elif event == WindowEvent.UNPAUSE: self._unpause() elif event == WindowEvent._INTERNAL_RENDERER_FLUSH: self.plugin_manager._post_tick_windows() else: self.mb.buttonevent(event)
def getitem(self, address): if 0x0000 <= address < 0x4000: if self.memorymodel == 1: self.rombank_selected = ( self.bank_select_register2 << 5) % self.external_rom_count else: self.rombank_selected = 0 return self.rombanks[self.rombank_selected][address] elif 0x4000 <= address < 0x8000: self.rombank_selected = \ (self.bank_select_register2 << 5) % self.external_rom_count | self.bank_select_register1 return self.rombanks[self.rombank_selected % len(self.rombanks)][address - 0x4000] elif 0xA000 <= address < 0xC000: if not self.rambank_initialized: logger.error("RAM banks not initialized: %s" % hex(address)) if not self.rambank_enabled: return 0xFF if self.memorymodel == 1: self.rambank_selected = self.bank_select_register2 else: self.rambank_selected = 0 return self.rambanks[self.rambank_selected % self.external_ram_count][address - 0xA000] else: logger.error("Reading address invalid: %s" % address)
def setitem(self, address, value): if 0x0000 <= address < 0x2000: self.rambank_enabled = (value & 0b00001111) == 0b1010 elif 0x2000 <= address < 0x4000: value &= 0b00011111 # The register cannot contain zero (0b00000) and will be initialized as 0b00001 # Attempting to write 0b00000 will write 0b00001 instead. if value == 0: value = 1 self.bank_select_register1 = value elif 0x4000 <= address < 0x6000: self.bank_select_register2 = value & 0b11 elif 0x6000 <= address < 0x8000: self.memorymodel = value & 0b1 elif 0xA000 <= address < 0xC000: if self.rambanks is None: logger.warning( "Game tries to set value 0x%0.2x at RAM address 0x%0.4x, but RAM " "banks are not initialized. Initializing %d RAM banks as " "precaution" % (value, address, self.external_ram_count)) self.init_rambanks(self.external_ram_count) if self.rambank_enabled: self.rambank_selected = self.bank_select_register2 if self.memorymodel == 1 else 0 self.rambanks[self.rambank_selected % self.external_ram_count][address - 0xA000] = value else: logger.error("Invalid writing address: %s" % hex(address))
def setitem(self, address, value): if 0x0000 <= address < 0x2000: # 8-bit register. All bits matter, so only 0b00001010 enables RAM. self.rambank_enabled = (value == 0b00001010) elif 0x2000 <= address < 0x3000: # 8-bit register used for the lower 8 bits of the ROM bank number. self.rombank_selected = (self.rombank_selected & 0b100000000) | value elif 0x3000 <= address < 0x4000: # 1-bit register used for the most significant bit of the ROM bank number. self.rombank_selected = ( (value & 0x1) << 8) | (self.rombank_selected & 0xFF) elif 0x4000 <= address < 0x6000: self.rambank_selected = value & 0xF elif 0xA000 <= address < 0xC000: if self.rambanks is None: logger.warning( "Game tries to set value 0x%0.2x at RAM address 0x%0.4x, but RAM " "banks are not initialized. Initializing %d RAM banks as " "precaution" % (value, address, self.external_ram_count)) self.init_rambanks(self.external_ram_count) if self.rambank_enabled: self.rambanks[self.rambank_selected % self.external_ram_count][address - 0xA000] = value else: logger.error("Unexpected write to 0x%0.4x, value: 0x%0.2x" % (address, value))
def enabled(self): if self.pyboy_argv.get("window_type") == "OpenGL": if opengl_enabled: return True else: logger.error( "Missing depencency \"PyOpenGL\". OpenGL window disabled") return False
def overrideitem(self, rom_bank, address, value): if 0x0000 <= address < 0x4000: logger.debug( "Performing overwrite on address: %s:%s. New value: %s Old value: %s" % (hex(rom_bank), hex(address), hex(value), self.rombanks[rom_bank][address]) ) self.rombanks[rom_bank][address] = value else: logger.error("Invalid override address: %s" % hex(address))
def getitem(self, address): if 0x0000 <= address < 0x4000: return self.rombanks[0][address] elif 0x4000 <= address < 0x8000: return self.rombanks[self.rombank_selected][address - 0x4000] elif 0xA000 <= address < 0xC000: if not self.rambank_initialized: raise logger.error("RAM banks not initialized: %s" % hex(address)) if self.rtc_enabled and 0x08 <= self.rambank_selected <= 0x0C: return self.rtc.getregister(self.rambank_selected) else: return self.rambanks[self.rambank_selected][address - 0xA000] else: raise logger.error("Reading address invalid: %s" % address)
def set_lives_left(self, amount): """ Set the amount lives to any number between 0 and 99. This should only be called when the game has started. Args: amount (int): The wanted number of lives """ if not self.game_has_started: logger.warning("Please call set_lives_left after starting the game") if 0 <= amount <= 99: tens = amount // 10 ones = amount % 10 self.pyboy.set_memory_value(ADDR_LIVES_LEFT, (tens << 4) | ones) self.pyboy.set_memory_value(ADDR_LIVES_LEFT_DISPLAY, tens) self.pyboy.set_memory_value(ADDR_LIVES_LEFT_DISPLAY + 1, ones) else: logger.error(f"{amount} is out of bounds. Only values between 0 and 99 allowed.")
def setitem(self, address, value): if 0x0000 <= address < 0x2000: if (value & 0b00001111) == 0b1010: self.rambank_enabled = True elif value == 0: self.rambank_enabled = False else: # Pan Docs: "Practically any value with 0Ah in the # lower 4 bits enables RAM, and any other value # disables RAM." self.rambank_enabled = False logger.warning( "Unexpected command for MBC3: Address: 0x%0.4x, Value: 0x%0.2x" % (address, value)) elif 0x2000 <= address < 0x4000: if value == 0: value = 1 # print "ROM Bank switch:", value & 0b01111111 self.rombank_selected = value & 0b01111111 # sets 7LSB of ROM bank address elif 0x4000 <= address < 0x6000: self.rambank_selected = value elif 0x6000 <= address < 0x8000: if self.rtc_enabled: self.rtc.writecommand(value) else: # NOTE: Pokemon Red/Blue will do this, but it can safely be ignored: # https://github.com/pret/pokered/issues/155 logger.warning( "RTC not present. Game tried to issue RTC command: 0x%0.4x, 0x%0.2x" % (address, value)) elif 0xA000 <= address < 0xC000: if self.rambank_selected <= 0x03: self.rambanks[self.rambank_selected][address - 0xA000] = value elif 0x08 <= self.rambank_selected <= 0x0C: self.rtc.setregister(self.rambank_selected, value) else: raise logger.error("Invalid RAM bank selected: 0x%0.2x" % self.rambank_selected) else: raise logger.error("Invalid writing address: 0x%0.4x" % address)
def setitem(self, address, value): if 0x0000 <= address < 0x2000: if (value & 0b00001111) == 0x0A: self.rambank_enabled = True else: self.rambank_enabled = False elif 0x2000 <= address < 0x4000: # But (when using the register below to specify the upper ROM Bank bits), the same # happens for Bank 20h, 40h, and 60h. Any attempt to address these ROM Banks will # select Bank 21h, 41h, and 61h instead. if value == 0: value = 1 # sets 5LSB of ROM bank address self.rombank_selected = self.rombank_selected & 0b11100000 | value & 0b00011111 elif 0x4000 <= address < 0x6000: # Note: 16Mbit = 2MB. 2MB/(8KB banks) = 128 banks. 128 is addressable with 7 bits if self.memorymodel == 0: # 16/8 mode # sets 2MSB of ROM bank address self.rombank_selected = self.rombank_selected & 0b00011111 | ( address & 0b11) << 5 # Note: 4Mbit = 0.5MB. 0.5MB/(8KB banks) = 32 banks. 32 is addressably with 5 bits elif self.memorymodel == 1: # 4/32 mode self.rambank_selected = value & 0b00000011 else: raise logger.error("Invalid memory model: %s" % self.memorymodel) elif 0x6000 <= address < 0x8000: self.memorymodel = value & 0x1 elif 0xA000 <= address < 0xC000: if self.rambanks is None: from . import EXTERNAL_RAM_TABLE logger.warning( "Game tries to set value 0x%0.2x at RAM address 0x%0.4x, but RAM " "banks are not initialized. Initializing %d RAM banks as " "precaution" % (value, address, EXTERNAL_RAM_TABLE[0x02])) self.init_rambanks(EXTERNAL_RAM_TABLE[0x02]) self.rambanks[self.rambank_selected][address - 0xA000] = value else: raise logger.error("Invalid writing address: %s" % hex(address))
def valid_file_path(path): if not path == INTERNAL_LOADSTATE and not os.path.isfile(path): logger.error(f"Filepath '{path}' couldn't be found, or isn't a file!") exit(1) return path