def test_tilemap_position_list(): pyboy = PyBoy(supermarioland_rom, window_type="headless") for _ in range(100): pyboy.tick() # Start the game pyboy.send_input(WindowEvent.PRESS_BUTTON_START) pyboy.tick() pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) # Move right for 100 frame pyboy.send_input(WindowEvent.PRESS_ARROW_RIGHT) for _ in range(100): pyboy.tick() # Get screen positions, and verify the values positions = pyboy.botsupport_manager().screen().tilemap_position_list() for y in range(1, 16): assert positions[y][0] == 0 # HUD for y in range(16, 144): assert positions[y][0] == 49 # Actual screen position # Progress another 10 frames to see and increase in SCX for _ in range(10): pyboy.tick() # Get screen positions, and verify the values positions = pyboy.botsupport_manager().screen().tilemap_position_list() for y in range(1, 16): assert positions[y][0] == 0 # HUD for y in range(16, 144): assert positions[y][0] == 59 # Actual screen position pyboy.stop(save=False)
def test_dmg_acid(): # Has to be in here. Otherwise all test workers will import the file, and cause an error. dmg_acid_file = "dmg_acid2.gb" if not os.path.isfile(dmg_acid_file): print( urllib.request.urlopen( "https://pyboy.dk/mirror/LICENSE.dmg-acid2.txt").read()) dmg_acid_data = urllib.request.urlopen( "https://pyboy.dk/mirror/dmg-acid2.gb").read() with open(dmg_acid_file, "wb") as rom_file: rom_file.write(dmg_acid_data) pyboy = PyBoy(dmg_acid_file, window_type="headless") pyboy.set_emulation_speed(0) for _ in range(59): pyboy.tick() for _ in range(25): pyboy.tick() png_path = Path(f"test_results/{dmg_acid_file}.png") image = pyboy.botsupport_manager().screen().screen_image() if OVERWRITE_PNGS: png_path.parents[0].mkdir(parents=True, exist_ok=True) image.save(png_path) else: old_image = PIL.Image.open(png_path) diff = PIL.ImageChops.difference(image, old_image) if diff.getbbox() and not os.environ.get("TEST_CI"): image.show() old_image.show() diff.show() assert not diff.getbbox(), f"Images are different! {dmg_acid_file}" pyboy.stop(save=False)
def test_mooneye(clean, rom): global saved_state # Has to be in here. Otherwise all test workers will import this file, and cause an error. mooneye_dir = "mooneye" if not os.path.isdir(mooneye_dir): print( urllib.request.urlopen( "https://pyboy.dk/mirror/LICENSE.mooneye.txt").read()) mooneye_data = io.BytesIO( urllib.request.urlopen( "https://pyboy.dk/mirror/mooneye.zip").read()) with ZipFile(mooneye_data) as _zip: _zip.extractall(mooneye_dir) if saved_state is None: # HACK: We load any rom and load it until the last frame in the boot rom. # Then we save it, so we won't need to redo it. pyboy = PyBoy(default_rom, window_type="dummy") pyboy.set_emulation_speed(0) saved_state = io.BytesIO() for _ in range(59): pyboy.tick() pyboy.save_state(saved_state) pyboy.stop(save=False) pyboy = PyBoy(rom, window_type="headless") pyboy.set_emulation_speed(0) saved_state.seek(0) if clean: for _ in range(59): pyboy.tick() else: pyboy.load_state(saved_state) for _ in range(180 if "div_write" in rom else 40): pyboy.tick() png_path = Path(f"test_results/{rom}.png") image = pyboy.botsupport_manager().screen().screen_image() if OVERWRITE_PNGS: png_path.parents[0].mkdir(parents=True, exist_ok=True) image.save(png_path) else: old_image = PIL.Image.open(png_path) if "acceptance" in rom: # The registers are too volatile to depend on. We crop the top out, and only match the assertions. diff = PIL.ImageChops.difference(image.crop((0, 72, 160, 144)), old_image.crop((0, 72, 160, 144))) else: diff = PIL.ImageChops.difference(image, old_image) if diff.getbbox() and not os.environ.get("TEST_CI"): image.show() old_image.show() diff.show() assert not diff.getbbox(), f"Images are different! {rom}" pyboy.stop(save=False)
def test_screen_buffer_and_image(): cformat = "RGBA" boot_logo_hash_predigested = b"=\xff\xf9z 6\xf0\xe9\xcb\x05J`PM5\xd4rX+\x1b~z\xef1\xe0\x82\xc4t\x06\x82\x12C" boot_logo_hash_predigested = \ b"s\xd1R\x88\xe0a\x14\xd0\xd2\xecOk\xe8b\xae.\x0e\x1e\xb6R\xc2\xe9:\xa2\x0f\xae\xa2\x89M\xbf\xd8|" pyboy = PyBoy(any_rom, window_type="headless", bootrom_file=boot_rom) pyboy.set_emulation_speed(0) for n in range(275): # Iterate to boot logo pyboy.tick() assert pyboy.botsupport_manager().screen().raw_screen_buffer_dims() == (160, 144) assert pyboy.botsupport_manager().screen().raw_screen_buffer_format() == cformat boot_logo_hash = hashlib.sha256() boot_logo_hash.update(pyboy.botsupport_manager().screen().raw_screen_buffer()) assert boot_logo_hash.digest() == boot_logo_hash_predigested assert isinstance(pyboy.botsupport_manager().screen().raw_screen_buffer(), bytes) # The output of `screen_image` is supposed to be homogeneous, which means a shared hash between versions. boot_logo_png_hash_predigested = ( b"\x1b\xab\x90r^\xfb\x0e\xef\xf1\xdb\xf8\xba\xb6:^\x01" b"\xa4\x0eR&\xda9\xfcg\xf7\x0f|\xba}\x08\xb6$" ) boot_logo_png_hash = hashlib.sha256() image = pyboy.botsupport_manager().screen().screen_image() assert isinstance(image, PIL.Image.Image) image_data = io.BytesIO() image.save(image_data, format="BMP") boot_logo_png_hash.update(image_data.getvalue()) assert boot_logo_png_hash.digest() == boot_logo_png_hash_predigested # Screenshot shortcut image1 = pyboy.botsupport_manager().screen().screen_image() image2 = pyboy.screen_image() diff = ImageChops.difference(image1, image2) assert not diff.getbbox() # screen_ndarray numpy_hash = hashlib.sha256() numpy_array = np.ascontiguousarray(pyboy.botsupport_manager().screen().screen_ndarray()) assert isinstance(pyboy.botsupport_manager().screen().screen_ndarray(), np.ndarray) assert numpy_array.shape == (144, 160, 3) numpy_hash.update(numpy_array.tobytes()) assert numpy_hash.digest( ) == (b"\r\t\x87\x131\xe8\x06\x82\xcaO=\n\x1e\xa2K$" b"\xd6\x8e\x91R( H7\xd8a*B+\xc7\x1f\x19") pyboy.stop(save=False)
def test_shonumi(rom): # Has to be in here. Otherwise all test workers will import this file, and cause an error. shonumi_dir = "GB Tests" if not os.path.isdir(shonumi_dir): print( urllib.request.urlopen( "https://pyboy.dk/mirror/SOURCE.GBTests.txt").read()) shonumi_data = io.BytesIO( urllib.request.urlopen( "https://pyboy.dk/mirror/GB%20Tests.zip").read()) with ZipFile(shonumi_data) as _zip: _zip.extractall(shonumi_dir) pyboy = PyBoy(rom, window_type="headless", color_palette=(0xFFFFFF, 0x999999, 0x606060, 0x000000)) pyboy.set_emulation_speed(0) # sprite_suite.gb # 60 PyBoy Boot # 23 Loading # 50 Progress to screenshot for _ in range(60 + 23 + 50): pyboy.tick() png_path = Path(f"test_results/{rom}.png") png_path.parents[0].mkdir(parents=True, exist_ok=True) image = pyboy.botsupport_manager().screen().screen_image() old_image = PIL.Image.open(png_path) old_image = old_image.resize(image.size, resample=PIL.Image.NEAREST) diff = PIL.ImageChops.difference(image, old_image) if diff.getbbox() and not os.environ.get("TEST_CI"): image.show() old_image.show() diff.show() assert not diff.getbbox(), f"Images are different! {rom}" pyboy.stop(save=False)
def test_tiles(): pyboy = PyBoy(default_rom, window_type="headless") pyboy.set_emulation_speed(0) for _ in range(BOOTROM_FRAMES_UNTIL_LOGO): pyboy.tick() tile = pyboy.botsupport_manager().tilemap_window().tile(0, 0) assert isinstance(tile, Tile) tile = pyboy.botsupport_manager().tile(1) image = tile.image() assert isinstance(image, PIL.Image.Image) ndarray = tile.image_ndarray() assert isinstance(ndarray, np.ndarray) assert ndarray.shape == (8, 8, 4) assert ndarray.dtype == np.uint8 data = tile.image_data() assert data.shape == (8, 8) assert [[x for x in y] for y in data ] == [[0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff], [0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff], [0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff], [0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff], [0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff], [0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff], [0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff], [0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff]] for identifier in range(384): t = pyboy.botsupport_manager().tile(identifier) assert t.tile_identifier == identifier with pytest.raises(Exception): pyboy.botsupport_manager().tile(-1) with pytest.raises(Exception): pyboy.botsupport_manager().tile(385) pyboy.stop(save=False)
def test_mooneye(): # Has to be in here. Otherwise all test workers will import the file, and cause an error. mooneye_dir = "mooneye" if not os.path.isdir(mooneye_dir): print( urllib.request.urlopen( "https://pyboy.dk/mirror/LICENSE.mooneye.txt").read()) mooneye_data = io.BytesIO( urllib.request.urlopen( "https://pyboy.dk/mirror/mooneye.zip").read()) with ZipFile(mooneye_data) as _zip: _zip.extractall(mooneye_dir) test_roms = [ (False, "mooneye/misc/boot_hwio-C.gb"), (False, "mooneye/misc/boot_regs-A.gb"), (False, "mooneye/misc/bits/unused_hwio-C.gb"), (False, "mooneye/misc/boot_div-A.gb"), (False, "mooneye/misc/boot_regs-cgb.gb"), (False, "mooneye/misc/boot_div-cgbABCDE.gb"), (False, "mooneye/misc/ppu/vblank_stat_intr-C.gb"), (False, "mooneye/misc/boot_div-cgb0.gb"), (False, "mooneye/manual-only/sprite_priority.gb"), # (False, "mooneye/utils/bootrom_dumper.gb"), (False, "mooneye/utils/dump_boot_hwio.gb"), (False, "mooneye/acceptance/rapid_di_ei.gb"), (False, "mooneye/acceptance/oam_dma_start.gb"), (False, "mooneye/acceptance/boot_regs-dmgABC.gb"), (False, "mooneye/acceptance/reti_timing.gb"), (False, "mooneye/acceptance/call_timing.gb"), (False, "mooneye/acceptance/reti_intr_timing.gb"), (False, "mooneye/acceptance/boot_regs-mgb.gb"), (False, "mooneye/acceptance/ei_sequence.gb"), (False, "mooneye/acceptance/jp_timing.gb"), (False, "mooneye/acceptance/ei_timing.gb"), (False, "mooneye/acceptance/oam_dma_timing.gb"), (False, "mooneye/acceptance/call_cc_timing2.gb"), (False, "mooneye/acceptance/boot_div2-S.gb"), (False, "mooneye/acceptance/halt_ime1_timing.gb"), (False, "mooneye/acceptance/halt_ime1_timing2-GS.gb"), (False, "mooneye/acceptance/timer/tima_reload.gb"), (False, "mooneye/acceptance/timer/tma_write_reloading.gb"), (False, "mooneye/acceptance/timer/tim10.gb"), (False, "mooneye/acceptance/timer/tim00.gb"), (False, "mooneye/acceptance/timer/tim11.gb"), (False, "mooneye/acceptance/timer/tim01.gb"), (False, "mooneye/acceptance/timer/tima_write_reloading.gb"), (False, "mooneye/acceptance/timer/tim11_div_trigger.gb"), (False, "mooneye/acceptance/timer/div_write.gb"), (False, "mooneye/acceptance/timer/tim10_div_trigger.gb"), (False, "mooneye/acceptance/timer/tim00_div_trigger.gb"), (False, "mooneye/acceptance/timer/rapid_toggle.gb"), (False, "mooneye/acceptance/timer/tim01_div_trigger.gb"), (False, "mooneye/acceptance/boot_regs-sgb.gb"), (False, "mooneye/acceptance/jp_cc_timing.gb"), (False, "mooneye/acceptance/call_timing2.gb"), (False, "mooneye/acceptance/ld_hl_sp_e_timing.gb"), (False, "mooneye/acceptance/push_timing.gb"), (False, "mooneye/acceptance/boot_hwio-dmg0.gb"), (False, "mooneye/acceptance/rst_timing.gb"), (False, "mooneye/acceptance/boot_hwio-S.gb"), (False, "mooneye/acceptance/boot_div-dmgABCmgb.gb"), (False, "mooneye/acceptance/bits/mem_oam.gb"), (False, "mooneye/acceptance/bits/reg_f.gb"), (False, "mooneye/acceptance/bits/unused_hwio-GS.gb"), (False, "mooneye/acceptance/div_timing.gb"), (False, "mooneye/acceptance/ret_cc_timing.gb"), (False, "mooneye/acceptance/boot_regs-dmg0.gb"), (False, "mooneye/acceptance/interrupts/ie_push.gb"), (False, "mooneye/acceptance/boot_hwio-dmgABCmgb.gb"), (False, "mooneye/acceptance/pop_timing.gb"), (False, "mooneye/acceptance/ret_timing.gb"), (False, "mooneye/acceptance/oam_dma_restart.gb"), (False, "mooneye/acceptance/add_sp_e_timing.gb"), (False, "mooneye/acceptance/oam_dma/sources-GS.gb"), (False, "mooneye/acceptance/oam_dma/basic.gb"), (False, "mooneye/acceptance/oam_dma/reg_read.gb"), (False, "mooneye/acceptance/halt_ime0_nointr_timing.gb"), (False, "mooneye/acceptance/ppu/vblank_stat_intr-GS.gb"), (False, "mooneye/acceptance/ppu/intr_2_mode0_timing_sprites.gb"), (False, "mooneye/acceptance/ppu/stat_irq_blocking.gb"), (False, "mooneye/acceptance/ppu/intr_1_2_timing-GS.gb"), (False, "mooneye/acceptance/ppu/intr_2_mode0_timing.gb"), (False, "mooneye/acceptance/ppu/lcdon_write_timing-GS.gb"), (False, "mooneye/acceptance/ppu/hblank_ly_scx_timing-GS.gb"), (False, "mooneye/acceptance/ppu/intr_2_0_timing.gb"), (False, "mooneye/acceptance/ppu/stat_lyc_onoff.gb"), (False, "mooneye/acceptance/ppu/intr_2_mode3_timing.gb"), (False, "mooneye/acceptance/ppu/lcdon_timing-GS.gb"), (False, "mooneye/acceptance/ppu/intr_2_oam_ok_timing.gb"), (False, "mooneye/acceptance/call_cc_timing.gb"), (False, "mooneye/acceptance/halt_ime0_ei.gb"), (False, "mooneye/acceptance/intr_timing.gb"), (False, "mooneye/acceptance/instr/daa.gb"), (False, "mooneye/acceptance/if_ie_registers.gb"), (False, "mooneye/acceptance/di_timing-GS.gb"), (False, "mooneye/acceptance/serial/boot_sclk_align-dmgABCmgb.gb"), (False, "mooneye/acceptance/boot_regs-sgb2.gb"), (False, "mooneye/acceptance/boot_div-S.gb"), (False, "mooneye/acceptance/boot_div-dmg0.gb"), (True, "mooneye/emulator-only/mbc5/rom_64Mb.gb"), (True, "mooneye/emulator-only/mbc5/rom_1Mb.gb"), (True, "mooneye/emulator-only/mbc5/rom_512kb.gb"), (True, "mooneye/emulator-only/mbc5/rom_32Mb.gb"), (True, "mooneye/emulator-only/mbc5/rom_2Mb.gb"), (True, "mooneye/emulator-only/mbc5/rom_4Mb.gb"), (True, "mooneye/emulator-only/mbc5/rom_8Mb.gb"), (True, "mooneye/emulator-only/mbc5/rom_16Mb.gb"), # (True, "mooneye/emulator-only/mbc2/rom_1Mb.gb"), # (True, "mooneye/emulator-only/mbc2/ram.gb"), # (True, "mooneye/emulator-only/mbc2/bits_unused.gb"), # (True, "mooneye/emulator-only/mbc2/bits_ramg.gb"), # (True, "mooneye/emulator-only/mbc2/rom_512kb.gb"), # (True, "mooneye/emulator-only/mbc2/bits_romb.gb"), # (True, "mooneye/emulator-only/mbc2/rom_2Mb.gb"), (True, "mooneye/emulator-only/mbc1/rom_1Mb.gb"), (True, "mooneye/emulator-only/mbc1/bits_bank2.gb"), (True, "mooneye/emulator-only/mbc1/bits_ramg.gb"), (True, "mooneye/emulator-only/mbc1/rom_512kb.gb"), (True, "mooneye/emulator-only/mbc1/bits_mode.gb"), (True, "mooneye/emulator-only/mbc1/ram_64kb.gb"), (True, "mooneye/emulator-only/mbc1/bits_bank1.gb"), (True, "mooneye/emulator-only/mbc1/rom_2Mb.gb"), (True, "mooneye/emulator-only/mbc1/ram_256kb.gb"), (True, "mooneye/emulator-only/mbc1/rom_4Mb.gb"), (True, "mooneye/emulator-only/mbc1/multicart_rom_8Mb.gb"), (True, "mooneye/emulator-only/mbc1/rom_8Mb.gb"), (True, "mooneye/emulator-only/mbc1/rom_16Mb.gb"), ] # HACK: We load any rom and load it until the last frame in the boot rom. # Then we save it, so we won't need to redo it. pyboy = PyBoy(default_rom, window_type="dummy") pyboy.set_emulation_speed(0) saved_state = io.BytesIO() for _ in range(59): pyboy.tick() pyboy.save_state(saved_state) pyboy.stop(save=False) for clean, rom in test_roms: pyboy = PyBoy(rom, window_type="headless") pyboy.set_emulation_speed(0) saved_state.seek(0) if clean: for _ in range(59): pyboy.tick() else: pyboy.load_state(saved_state) for _ in range(20): pyboy.tick() png_path = Path(f"test_results/{rom}.png") image = pyboy.botsupport_manager().screen().screen_image() if OVERWRITE_PNGS: png_path.parents[0].mkdir(parents=True, exist_ok=True) image.save(png_path) else: old_image = PIL.Image.open(png_path) diff = PIL.ImageChops.difference(image, old_image) if diff.getbbox() and not os.environ.get("TEST_CI"): image.show() old_image.show() diff.show() assert not diff.getbbox(), f"Images are different! {rom}" pyboy.stop(save=False)
def test_tetris(): NEXT_TETROMINO = 0xC213 pyboy = PyBoy(tetris_rom, bootrom_file="pyboy_fast", window_type="headless", game_wrapper=True) pyboy.set_emulation_speed(0) tetris = pyboy.game_wrapper() tetris.set_tetromino("T") first_brick = False tile_map = pyboy.botsupport_manager().tilemap_window() state_data = io.BytesIO() for frame in range( 5282 ): # Enough frames to get a "Game Over". Otherwise do: `while not pyboy.tick():` pyboy.tick() assert pyboy.botsupport_manager().screen().tilemap_position() == ((0, 0), (-7, 0)) # Start game. Just press Start and A when the game allows us. # The frames are not 100% accurate. if frame == 144: pyboy.send_input(WindowEvent.PRESS_BUTTON_START) elif frame == 145: pyboy.send_input(WindowEvent.RELEASE_BUTTON_START) elif frame == 152: pyboy.send_input(WindowEvent.PRESS_BUTTON_A) elif frame == 153: pyboy.send_input(WindowEvent.RELEASE_BUTTON_A) elif frame == 156: pyboy.send_input(WindowEvent.PRESS_BUTTON_A) elif frame == 157: pyboy.send_input(WindowEvent.RELEASE_BUTTON_A) elif frame == 162: pyboy.send_input(WindowEvent.PRESS_BUTTON_A) elif frame == 163: pyboy.send_input(WindowEvent.RELEASE_BUTTON_A) # Play game. When we are passed the 168th frame, the game has begone. # The "technique" is just to move the Tetromino to the right. elif frame > 168: if frame % 2 == 0: pyboy.send_input(WindowEvent.PRESS_ARROW_RIGHT) elif frame % 2 == 1: pyboy.send_input(WindowEvent.RELEASE_ARROW_RIGHT) # Show how we can read the tile data for the screen. We can use # this to see when one of the Tetrominos touch the bottom. This # could be used to extract a matrix of the occupied squares by # iterating from the top to the bottom of the screen. # Sidenote: The currently moving Tetromino is a sprite, so it # won't show up in the tile data. The tile data shows only the # placed Tetrominos. # We could also read out the score from the screen instead of # finding the corresponding value in RAM. if not first_brick: # 17 for the bottom tile when zero-indexed # 2 because we skip the border on the left side. Then we take a slice of 10 more tiles # 303 is the white background tile index if any(filter(lambda x: x != 303, tile_map[2:12, 17])): first_brick = True print(frame) print("First brick touched the bottom!") game_board_matrix = list(tile_map[2:12, :18]) assert game_board_matrix == ([ [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 133, 133, 133], [303, 303, 303, 303, 303, 303, 303, 303, 133, 303] ]) tile_map.use_tile_objects(True) t1 = tile_map[0, 0] t2 = tile_map.tile(0, 0) t3 = tile_map.tile(1, 0) assert t1 == t2, "Testing __eq__ method of Tile object" assert t1 != t3, "Testing not __eq__ method of Tile object" game_board_matrix = [[x.tile_identifier for x in row] for row in tile_map[2:12, :18]] tile_map.use_tile_objects(False) assert game_board_matrix == ([ [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 133, 133, 133], [303, 303, 303, 303, 303, 303, 303, 303, 133, 303] ]) if frame == 1012: assert not first_brick if frame == 1013: assert first_brick s1 = pyboy.botsupport_manager().sprite(0) s2 = pyboy.botsupport_manager().sprite(1) assert s1 == s1 assert s1 != s2 assert s1.tiles[0] == s2.tiles[ 0], "Testing equal tiles of two different sprites" # Test that both ways of getting identifiers work and provides the same result. all_sprites = [ (s.x, s.y, s.tiles[0].tile_identifier, s.on_screen) for s in [pyboy.botsupport_manager().sprite(n) for n in range(40)] ] all_sprites2 = [ (s.x, s.y, s.tile_identifier, s.on_screen) for s in [pyboy.botsupport_manager().sprite(n) for n in range(40)] ] assert all_sprites == all_sprites2 # Verify data with known reference # pyboy.botsupport_manager().screen().screen_image().show() assert all_sprites == ([ (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (72, 128, 133, True), (80, 128, 133, True), (88, 128, 133, True), (80, 136, 133, True), (120, 112, 133, True), (128, 112, 133, True), (136, 112, 133, True), (128, 120, 133, True), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), (-8, -16, 0, False), ]) assert pyboy.get_memory_value(NEXT_TETROMINO) == 24 assert tetris.next_tetromino() == "T" with open("tmp.state", "wb") as f: pyboy.save_state(f) pyboy.save_state(state_data) break pre_load_game_board_matrix = None for frame in range(1015, 1865): pyboy.tick() if frame == 1864: game_board_matrix = list(tile_map[2:12, :18]) assert game_board_matrix == ([ [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 303, 303, 303, 303, 303, 303, 303], [303, 303, 303, 133, 133, 133, 303, 133, 133, 133], [303, 303, 303, 303, 133, 303, 303, 303, 133, 303] ]) pre_load_game_board_matrix = game_board_matrix state_data.seek( 0 ) # Reset to the start of the buffer. Otherwise, we call `load_state` at end of file with open("tmp.state", "rb") as f: for _f in [f, state_data ]: # Tests both file-written state and in-memory state pyboy.load_state( _f) # Reverts memory state to before we changed the Tetromino pyboy.tick() for frame in range(1015, 1865): pyboy.tick() if frame == 1864: game_board_matrix = list(tile_map[2:12, :18]) assert game_board_matrix == pre_load_game_board_matrix break os.remove("tmp.state") pyboy.stop(save=False)
target=target) # numpy array for all channels - set initial target actions = [] #Initialize PyBoy # load rom through PyBoy # if there is a state file, load it dir_path = os.path.dirname(os.path.realpath(__file__)) rom_dir = os.path.join(dir_path, 'roms') pyboy = PyBoy(os.path.join(rom_dir, 'Pokemon.gb')) if os.path.exists(os.path.join(rom_dir, 'simulated_game.state')) and load_state: l_state = open("roms/simulated_game.state", "rb") pyboy.load_state(l_state) # set up PyBoy screen support bot_sup = pyboy.botsupport_manager() scrn = bot_sup.screen() # init image screen_colors = scrn.screen_ndarray() img = Image.fromarray(screen_colors) img.save('game.jpg') # minimize PyBoy window pyboy_handle = ctypes.windll.user32.FindWindowW(None, "PyBoy") ctypes.windll.user32.ShowWindow(pyboy_handle, 6) # init controller controller = pyboy_controller(pyboy) # init command handler
class PyBoyEnv(Env): """ Operators : - increase - decrease - equal <val> - smaller <val> - bigger <val> - in <val_1>,<val_2>,...,<val_n> (no spaces between values plz) """ def __init__(self, game, window='SDL2', visible=False, colors=(0xfff6d3, 0xf9a875, 0xeb6b6f, 0x7c3f58), buttons_press_mode='toggle'): # Build game super().__init__() self._pyboy = PyBoy(game, window_type=window, color_palette=colors, disable_renderer=not visible) self._pyboy.set_emulation_speed(0) self._manager = self._pyboy.botsupport_manager() self._screen = self._manager.screen() self.actions = { 0: WindowEvent.PRESS_ARROW_UP, 1: WindowEvent.PRESS_ARROW_DOWN, 2: WindowEvent.PRESS_ARROW_LEFT, 3: WindowEvent.PRESS_ARROW_RIGHT, 4: WindowEvent.PRESS_BUTTON_A, 5: WindowEvent.PRESS_BUTTON_B, 6: WindowEvent.PRESS_BUTTON_SELECT, 7: WindowEvent.PRESS_BUTTON_START, 8: WindowEvent.RELEASE_ARROW_UP, 9: WindowEvent.RELEASE_ARROW_DOWN, 10: WindowEvent.RELEASE_ARROW_LEFT, 11: WindowEvent.RELEASE_ARROW_RIGHT, 12: WindowEvent.RELEASE_BUTTON_A, 13: WindowEvent.RELEASE_BUTTON_B, 14: WindowEvent.RELEASE_BUTTON_SELECT, 15: WindowEvent.RELEASE_BUTTON_START, 16: WindowEvent.PASS } self.action_space = Discrete(len(self.actions)) self.observation_space = Box(low=0, high=255, shape=(160, 144, 3), dtype=np.uint8) # Format : # {'addr':<address>, 'op':<operator>, 'reward':<reward>, 'val':<latest value>, 'lab':<label>} self._reward_rules = list() self._done_rules = list() self._refresh_values() def _refresh_values(self): """ Refresh values for each rule """ for x in self._reward_rules + self._done_rules: x['val'] = self._pyboy.get_memory_value(x['addr']) def _get_observation(self): """ Returns screen """ return np.asarray(self._screen.screen_ndarray(), dtype=np.uint8) def _handle_rule(self, rule): if rule['rule_type'] == 'reward': done = False reward = rule['reward'] # Set reward and label label = [rule['lab'], rule['reward']] elif rule['rule_type'] == 'done': done = True reward = 0 label = [rule['lab'], "Done"] return reward, done, label def _scan_memory(self): """ Scan memory in order to apply rules """ reward = 0 done = False label = list() for x in self._reward_rules + self._done_rules: r, d, lab = 0, False, None if 'increase' in x['op']: # INCREASE if x['val'] < self._pyboy.get_memory_value(x['addr']): r, d, lab = self._handle_rule(x) elif 'decrease' in x['op']: # DECREASE if x['val'] > self._pyboy.get_memory_value(x['addr']): r, d, lab = self._handle_rule(x) elif 'equal' in x['op']: # EQUAL if self._pyboy.get_memory_value(x['addr']) == int( x['op'].split()[1]): r, d, lab = self._handle_rule(x) elif 'bigger' in x['op'] or 'greater' in x['op']: # BIGGER if self._pyboy.get_memory_value(x['addr']) > int( x['op'].split()[1]): r, d, lab = self._handle_rule(x) elif 'smaller' in x['op'] or 'less' in x['op']: # SMALLER if self._pyboy.get_memory_value(x['addr']) < int( x['op'].split()[1]): r, d, lab = self._handle_rule(x) elif 'in' in x['op']: # IN if self._pyboy.get_memory_value( x['addr']) in x['op'].split(' ')[1].split(','): r, d, lab = self._handle_rule(x) else: raise ValueError(f"Invalid custom reward operator: {x['op']}") reward += r done = done or d label.append(lab) if lab is not None else None self._refresh_values() return reward, done, label def set_reward_rule(self, address, operator, reward, label): # More user friendly ? self._reward_rules.append({ 'rule_type': 'reward', 'addr': address, 'op': operator, 'reward': reward, 'val': 0, 'lab': label }) # No reward here but some action to pass screen etc if needed ? def set_done_rule(self, address, operator, label): self._done_rules.append({ 'rule_type': 'done', 'addr': address, 'op': operator, 'val': 0, 'lab': label }) def step(self, action_id): # same thing as gymboy (no toggle) action = self.actions[action_id] self._pyboy.send_input(action) done_tick = self._pyboy.tick() reward, done, info = self._scan_memory() obs = self._get_observation() return obs, reward, done or done_tick, info def reset(self): # ?? Done rules ?? return self._get_observation() def render(self): # there's a way to toggle visible screen?? pass
def test_tilemaps(): pyboy = PyBoy(kirby_rom, window_type="dummy") pyboy.set_emulation_speed(0) for _ in range(120): pyboy.tick() bck_tilemap = pyboy.botsupport_manager().tilemap_background() wdw_tilemap = pyboy.botsupport_manager().tilemap_window() assert bck_tilemap[0, 0] == 256 assert bck_tilemap[:5, 0] == [256, 256, 256, 256, 170] assert bck_tilemap[:20, :10] == [ [ 256, 256, 256, 256, 170, 176, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256 ], [ 256, 256, 256, 171, 173, 177, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256 ], [ 256, 256, 256, 172, 174, 178, 256, 256, 256, 256, 256, 256, 256, 256, 347, 363, 256, 256, 256, 256 ], [ 256, 256, 256, 288, 175, 179, 336, 352, 368, 268, 284, 300, 316, 332, 348, 364, 380, 256, 256, 256 ], [ 256, 257, 273, 289, 305, 321, 337, 353, 369, 269, 285, 301, 317, 333, 349, 365, 381, 256, 256, 256 ], [ 256, 258, 274, 290, 306, 322, 338, 354, 370, 270, 286, 302, 318, 334, 350, 366, 382, 256, 256, 256 ], [ 256, 259, 275, 291, 307, 323, 339, 355, 371, 271, 287, 303, 319, 335, 351, 367, 383, 256, 256, 256 ], [ 256, 256, 276, 292, 308, 324, 340, 356, 372, 272, 320, 260, 261, 262, 361, 182, 346, 256, 256, 256 ], [ 256, 256, 277, 293, 309, 325, 341, 357, 373, 128, 181, 362, 378, 299, 315, 331, 256, 256, 256, 256 ], [ 256, 256, 278, 294, 310, 326, 342, 358, 374, 129, 164, 132, 136, 140, 143, 146, 150, 167, 157, 168 ] ] assert isinstance(bck_tilemap.tile(0, 0), Tile) assert bck_tilemap.tile_identifier(0, 0) == 256 bck_tilemap.use_tile_objects(True) assert isinstance(bck_tilemap.tile(0, 0), Tile) assert bck_tilemap.tile_identifier(0, 0) == 256 assert isinstance(bck_tilemap[0, 0], Tile) assert wdw_tilemap[0, 0] == 256 assert wdw_tilemap[:5, 0] == [256, 256, 256, 256, 256] assert wdw_tilemap[:20, :10] == [ [ 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256 ], [ 256, 256, 256, 256, 256, 256, 230, 224, 236, 228, 256, 241, 242, 224, 240, 242, 256, 256, 256, 256 ], [ 256, 256, 256, 256, 256, 256, 241, 238, 243, 237, 227, 256, 242, 228, 241, 242, 256, 256, 256, 256 ], [ 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256 ], [ 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256 ], [ 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256 ], [ 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256 ], [ 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256 ], [ 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256 ], [ 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256 ] ] assert isinstance(wdw_tilemap.tile(0, 0), Tile) assert wdw_tilemap.tile_identifier(0, 0) == 256 wdw_tilemap.use_tile_objects(True) assert isinstance(wdw_tilemap.tile(0, 0), Tile) assert wdw_tilemap.tile_identifier(0, 0) == 256 assert isinstance(wdw_tilemap[0, 0], Tile) pyboy.stop(save=False)
height = 160 perf_times = [] frame_count = 0 while True: start_time = time.perf_counter() pb.tick() frame_count += 1 if frame_count % (1//skip_rate): continue # observation: 画面のRGB値(144, 160, 3) observation = np.asarray(pb.botsupport_manager( ).screen().screen_ndarray(), dtype=np.uint8) # グレースケールに変換 (144, 160) observation = cv2.cvtColor(observation, cv2.COLOR_BGR2GRAY) screen = "" for row in observation: tmp = "" for c in row: if c == 255: tmp += white elif c == 153: tmp += light_gray elif c == 85: tmp += dark_gray elif c == 0:
class simple_window(arcade.Window): # screen and image size vars screen_width = None screen_height = None game_width = None game_height = None checkerboard_size = CHECKERBOARD_SIZE padding = PADDING # textures and frequencies vars texture_list = [] flicker_frequency = [] tick = 0 last_state = [] checkerboard_pos_list = [] draw_scale = None # PyBoy vars pyboy = None bot_sup = None scrn = None # data collection vars generator = None cortex = None # controller vars command_handler = None controller = None def __init__(self, width, height, title): # scale the game width to make the game screen bigger self.game_width = GAME_WIDTH * GAME_SCALE self.game_height = GAME_HEIGHT * GAME_SCALE # set total screen size since we also need space to draw checkerboards self.screen_width = self.game_width + CHECKERBOARD_SIZE * 2 + PADDING self.screen_height = self.game_height + CHECKERBOARD_SIZE * 2 + PADDING self.draw_scale = self.screen_height / BASE_HEIGHT # let Arcade Window run it's setup super().__init__(self.screen_width, self.screen_height, title, resizable=True) # set frame rate as (1 / desired_frame_rate) self.set_update_rate(1/FREQUENCY) def print_stage(self, stage): print('-'*20) print('{} BEGINNING'.format(stage)) print('-'*20) def setup(self): self.print_stage("VISUALS SETUP") self.setup_checkerboards() self.print_stage("PYBOY SETUP") self.setup_pyboy() self.print_stage("CONTROLLER SETUP") self.setup_controller() self.print_stage("COMMAND HANDLER SETUP") # self.setup_command_handler() self.print_stage("CORTEX SETUP") # self.setup_cortex() def setup_controller(self): self.controller = pyboy_controller(self.pyboy) def setup_command_handler(self): self.command_handler = command_handler(self.controller) def setup_cortex(self): self.cortex = Cortex(None) self.cortex.do_prepare_steps() self.generator = self.cortex.sub_request(['eeg']) def setup_checkerboards(self): # load textures self.load_checkerboards() self.resize_drawing_vars() self.set_checkerboard_positions() # set all checkerboards to normal state (as opposed to inverted checkerboard) self.last_state = [0] * len(self.checkerboard_pos_list) # set flicker frequencies for each quadrant with open('flicker_patterns.txt') as f: for sequence in f: self.flicker_frequency.append(eval(sequence)) def set_checkerboard_positions(self): # set some useful vars half_width = self.screen_width / 2 half_height = self.screen_height / 2 half_checkerboard = self.checkerboard_size / 2 width_offset = (self.game_width / 2) + half_checkerboard + self.padding height_offset = (self.game_height / 2) + half_checkerboard + self.padding # empty checkerboard pos list self.checkerboard_pos_list = [] # prep x,y pairs for where to print checkerboards # ordering is: # y [top, middle, bottom] # x [left, middle, right] for y in [half_height + height_offset, half_height, half_height - height_offset]: for x in [half_width - width_offset, half_width, half_width + width_offset]: # skip drawing in the middle of board if x == half_width and y == half_height: continue self.checkerboard_pos_list.append([x, y]) # only select the non corner positions for simple window self.checkerboard_pos_list = [self.checkerboard_pos_list[i] for i in [1, 3, 4, 6]] def load_checkerboards(self): # Set up dir paths dir_path = os.path.dirname(os.path.realpath(__file__)) image_dir = os.path.join(dir_path, 'images') checkerboard_dir = os.path.join(image_dir, 'simple') icon_list = os.listdir(checkerboard_dir) # empty texture list, load new textures self.texture_list = [] for icon in icon_list: if "png" not in icon.lower(): continue self.texture_list.append(arcade.load_texture(os.path.join(checkerboard_dir, icon))) def setup_pyboy(self): # load rom through PyBoy dir_path = os.path.dirname(os.path.realpath(__file__)) rom_dir = os.path.join(dir_path, 'roms') self.pyboy = PyBoy(os.path.join(rom_dir, 'Pokemon.gb')) # set up PyBoy screen support self.bot_sup = self.pyboy.botsupport_manager() self.scrn = self.bot_sup.screen() # minimize PyBoy window pyboy_handle = ctypes.windll.user32.FindWindowW(None, "PyBoy") ctypes.windll.user32.ShowWindow(pyboy_handle, 6) def on_update(self, delta_time): self.pyboy.tick() self.on_draw() # self.exhaust() self.tick += 1 self.tick %= FREQUENCY # if self.tick % COMMAND_SEND_FREQUENCY == 0: # "Starting guess" # start = time.time() # self.command_handler.predict(self.get_eeg_data()) # print("Guess done in: {}s".format(time.time() - start)) def exhaust(self): next(self.generator) def get_eeg_data(self): # return np.ones((128, 5)) return np.asarray(list(next(self.generator).queue)) def resize_drawing_vars(self): if self.screen_height <= self.screen_width: self.draw_scale = self.screen_height / BASE_HEIGHT else: self.draw_scale = self.screen_width / BASE_WIDTH self.checkerboard_size = CHECKERBOARD_SIZE * self.draw_scale self.game_width = GAME_WIDTH * GAME_SCALE * self.draw_scale self.game_height = GAME_HEIGHT * GAME_SCALE * self.draw_scale self.padding = PADDING * self.draw_scale def on_resize(self, width, height): # Call the parent. Failing to do this will mess up the coordinates, and default to 0,0 at the center and the # edges being -1 to 1. super().on_resize(width, height) self.screen_width = width self.screen_height = height self.resize_drawing_vars() self.set_checkerboard_positions() def on_draw(self): # Start the render process arcade.start_render() # Draw game self.draw_game() # Draw checkerboards self.draw_checkerboards() # Finish the render arcade.finish_render() def draw_game(self): screen_colors = self.scrn.screen_ndarray() img = Image.fromarray(screen_colors) texture = arcade.Texture("img", img) arcade.draw_scaled_texture_rectangle(self.width / 2, self.height / 2, texture, GAME_SCALE * self.draw_scale) def draw_checkerboards(self): # Load and draw all checkerboards for ind, last_state in enumerate(self.last_state): freq = self.flicker_frequency[ind] pos = self.checkerboard_pos_list[ind] texture = self.texture_list[last_state] # if this is a switch state, change checkerboard state: if freq[self.tick % len(freq)]: self.last_state[ind] = not last_state arcade.draw_scaled_texture_rectangle(pos[0], pos[1], texture, self.draw_scale, 0) def on_key_press(self, key, key_modifiers): actions = [] print("KEY PRESSED: {}".format(key)) # switch statement to create WindowEvents (button commands for PyBoy) if (key == arcade.key.UP): actions = [WindowEvent.PRESS_ARROW_UP, WindowEvent.RELEASE_ARROW_UP] elif (key == arcade.key.DOWN): actions = [WindowEvent.PRESS_ARROW_DOWN, WindowEvent.RELEASE_ARROW_DOWN] elif (key == arcade.key.LEFT): actions = [WindowEvent.PRESS_ARROW_LEFT, WindowEvent.RELEASE_ARROW_LEFT] elif (key == arcade.key.RIGHT): actions = [WindowEvent.PRESS_ARROW_RIGHT, WindowEvent.RELEASE_ARROW_RIGHT] elif (key == arcade.key.Z): actions = [WindowEvent.PRESS_BUTTON_B, WindowEvent.RELEASE_BUTTON_B] elif (key == arcade.key.X): actions = [WindowEvent.PRESS_BUTTON_A, WindowEvent.RELEASE_BUTTON_A] elif (key == arcade.key.ENTER): actions = [WindowEvent.PRESS_BUTTON_SELECT, WindowEvent.RELEASE_BUTTON_SELECT] elif (key == arcade.key.RSHIFT): actions = [WindowEvent.PRESS_BUTTON_START, WindowEvent.RELEASE_BUTTON_START] # if actions list isn't empty, send commands to pyboy and tick the pyboy window to process each command if actions: for action in actions: self.pyboy.send_input(WindowEvent(action)) self.pyboy.tick() def main(): game = simple_window(0, 0, "Simple Window") game.setup() arcade.run()