Example #1
0
def run_write(output_queue):
    write_pipe, lua_read_pipe = [None] * 2
    try:
        write_pipe = Pipe("env1", "write", 'w', "mame/pipes")
        lua_read_pipe = setup_pipe(write_pipe)

        write_pipe.writeln("test")
        output_queue.put(lua_read_pipe.readline())
    finally:
        close_pipes(write_pipe, lua_read_pipe)
Example #2
0
    def test_write(self):
        write_pipe, lua_read_pipe = [None] * 2
        try:
            write_pipe = Pipe("env1", "write", 'w', "mame/pipes")
            lua_read_pipe = setup_pipe(write_pipe)

            write_pipe.writeln("test")
            assert_that(lua_read_pipe.readline(), equal_to("test\n"))
        finally:
            close_pipes(write_pipe, lua_read_pipe)
Example #3
0
    def test_write_to_read_pipe(self):
        read_pipe, lua_write_pipe = [None] * 2
        try:
            read_pipe = Pipe("env1", "read", 'r', "mame/pipes")
            lua_write_pipe = setup_pipe(read_pipe)

            with self.assertRaises(IOError) as context:
                read_pipe.writeln("TEST")

            assert_that(
                str(context.exception),
                contains_string(
                    "Attempted to write to '/home/michael/dev/MAMEToolkit/test/emulator/mame/pipes/read-env1.pipe' in 'r' mode"
                ))

        finally:
            close_pipes(read_pipe, lua_write_pipe)
Example #4
0
class Emulator(object):

    # env_id - the unique id of the emulator, used for fifo pipes
    # game_id - the game id being used
    # memory_addresses - The internal memory addresses of the game which this class will return the value of at every time step
    # frame_ratio - the ratio of frames that will be returned, 3 means 1 out of every 3 frames will be returned. Note that his also effects how often memory addresses are read and actions are sent
    # See console for render, throttle & debug
    def __init__(self,
                 env_id,
                 roms_path,
                 game_id,
                 memory_addresses,
                 frame_ratio=3,
                 render=True,
                 throttle=False,
                 debug=False):
        self.memoryAddresses = memory_addresses
        self.frameRatio = frame_ratio

        # setup lua engine
        self.console = Console(roms_path,
                               game_id,
                               render=render,
                               throttle=throttle,
                               debug=debug)
        atexit.register(self.close)
        self.wait_for_resource_registration()
        self.create_lua_variables()
        bitmap_format = self.get_bitmap_format()
        screen_width = self.setup_screen_width()
        screen_height = self.setup_screen_height()
        self.screenDims = {"width": screen_width, "height": screen_height}

        # open pipes
        pipes_path = f"{os.path.dirname(os.path.abspath(__file__))}/mame/pipes"
        self.actionPipe = Pipe(env_id, "action", 'w', pipes_path)
        self.actionPipe.open(self.console)

        self.dataPipe = DataPipe(env_id, self.screenDims, bitmap_format,
                                 memory_addresses, pipes_path)
        self.dataPipe.open(self.console)

        # Connect inter process communication
        self.setup_frame_access_loop()

    def get_bitmap_format(self):
        bitmap_format = self.console.writeln('print(s:bitmap_format())',
                                             expect_output=True)
        if len(bitmap_format) != 1:
            raise IOError(
                'Expected one result from "print(s:bitmap_format())", but received: ',
                bitmap_format)
        try:
            return {
                "RGB32 - 32bpp 8-8-8 RGB": BitmapFormat.RGB32,
                "ARGB32 - 32bpp 8-8-8-8 ARGB": BitmapFormat.ARGB32
            }[bitmap_format[0]]
        except KeyError:
            self.console.close()
            raise EnvironmentError(
                "MAMEToolkit only supports RGB32 and ARGB32 frame bit format games"
            )

    def create_lua_variables(self):
        self.console.writeln('iop = manager:machine():ioport()')
        self.console.writeln('s = manager:machine().screens[":screen"]')
        self.console.writeln(
            'mem = manager:machine().devices[":maincpu"].spaces["program"]')
        self.console.writeln('releaseQueue = {}')

    def wait_for_resource_registration(self, max_attempts=10):
        screen_registered = False
        program_registered = False
        attempt = 0
        while not screen_registered or not program_registered:
            if not screen_registered:
                result = self.console.writeln(
                    'print(manager:machine().screens[":screen"])',
                    expect_output=True,
                    timeout=3,
                    raiseError=False)
                screen_registered = result is not None and result is not "nil"
            if not program_registered:
                result = self.console.writeln(
                    'print(manager:machine().devices[":maincpu"].spaces["program"])',
                    expect_output=True,
                    timeout=3,
                    raiseError=False)
                program_registered = result is not None and result is not "nil"
            if attempt == max_attempts:
                raise EnvironmentError("Failed to register MAME resources!")
            attempt += 1

    # Gets the game screen width in pixels
    def setup_screen_width(self):
        output = self.console.writeln('print(s:width())',
                                      expect_output=True,
                                      timeout=1)
        if len(output) != 1:
            raise IOError(
                'Expected one result from "print(s:width())", but received: ',
                output)
        return int(output[0])

    # Gets the game screen height in pixels
    def setup_screen_height(self):
        output = self.console.writeln('print(s:height())',
                                      expect_output=True,
                                      timeout=1)
        if len(output) != 1:
            raise IOError(
                'Expected one result from "print(s:height())"", but received: ',
                output)
        return int(output[0])

    # Pauses the emulator
    def pause_game(self):
        self.console.writeln('emu.pause()')

    # Unpauses the emulator
    def unpause_game(self):
        self.console.writeln('emu.unpause()')

    # Sets up the callback function written in Lua that the Lua engine will execute each time a frame done
    def setup_frame_access_loop(self):
        pipe_data_func = 'function pipeData() ' \
                            'if (math.fmod(tonumber(s:frame_number()),' + str(self.frameRatio) +') == 0) then ' \
                                'for i=1,#releaseQueue do ' \
                                    'releaseQueue[i](); ' \
                                    'releaseQueue[i]=nil; ' \
                                'end; ' \
                                '' + self.dataPipe.get_lua_string() + '' \
                                'actions = ' + self.actionPipe.get_lua_string() + '' \
                                'if (string.len(actions) > 1) then ' \
                                    'for action in string.gmatch(actions, "[^+]+") do ' \
                                        'actionFunc = loadstring(action..":set_value(1)"); ' \
                                        'actionFunc(); ' \
                                        'releaseFunc = loadstring(action..":set_value(0)"); ' \
                                        'table.insert(releaseQueue, releaseFunc); ' \
                                    'end; ' \
                                'end; ' \
                            'end; ' \
                        'end'
        self.console.writeln(pipe_data_func)
        self.console.writeln('emu.register_frame_done(pipeData, "data")')

    # Steps the emulator along one time step
    def step(self, actions):
        # Will need to shuffle this for self-play?, right
        data = self.dataPipe.read_data(
            timeout=10)  # gathers the frame data and memory address values
        action_string = actions_to_string(actions)
        self.actionPipe.writeln(
            action_string
        )  # sends the actions for the game to perform before the next step
        return data

    # Testing
    # Safely stops all of the processes related to running the emulator
    def close(self):
        self.console.close()
        self.actionPipe.close()
        self.dataPipe.close()