def poison_check(self, game_frame):
        image_data = skimage.io.imread("plugins/SerpentSlayTheSpireGamePlugin/files/data/sprites/sprite_poison_check_0.png")[..., np.newaxis]

        poison_check = Sprite("poison_check", image_data=image_data)

        # # Full game frame capture
        # print("-----------Full game frame capture-----------")
        # full_game_frame = FrameGrabber.get_frames(
        #     [0],
        #     frame_shape=(self.game.frame_height, self.game.frame_width),
        #     frame_type="PIPELINE"
        # ).frames[0]

        sprite_locator = SpriteLocator()

        poison_check_location = sprite_locator.locate(sprite=poison_check, game_frame=game_frame)
        
        print("poison_check_location: ", poison_check_location)

        if (poison_check_location != None):
            self.game_state["poison_check"].insert(0, True)
            print("POISON_CHECK == TRUE")

        else:
            self.game_state["poison_check"].insert(0, False)
            print("POISON_CHECK == FALSE")

        self.ddqn_setup(game_frame)
    def _measure_actor_hp(self, game_frame):
        hp_area_frame = serpent.cv.extract_region_from_image(
            game_frame.frame, self.game.screen_regions["HP_AREA"])
        hp_area_image = Image.fromarray(hp_area_frame)

        actor_hp = 0

        image_colors = hp_area_image.getcolors(
        )  # TODO: remove in favor of sprite detection and location
        if image_colors:
            actor_hp = len(image_colors) - 7

        for name, sprite in self.game.sprites.items():
            query_sprite = Sprite("QUERY", image_data=sprite.image_data)
            sprite_name = self.sprite_identifier.identify(
                query_sprite, mode="CONSTELLATION_OF_PIXELS"
            )  # Will be "UNKNOWN" if no match
            print(sprite_name)
            sprite_to_locate = Sprite("QUERY", image_data=sprite.image_data)

            sprite_locator = SpriteLocator()
            location = sprite_locator.locate(sprite=sprite_to_locate,
                                             game_frame=game_frame)
            print(location)
            if location:
                actor_hp = 1000000

        return actor_hp
Beispiel #3
0
    def identify_sprite(self, sprite, game_frame):
        # sprite_identifier = SpriteIdentifier()
        sprite_identify = self.sprite_identifier.identify(
            sprite=sprite, mode="SIGNATURE_COLORS")

        sprite_locator = SpriteLocator()
        location = sprite_locator.locate(sprite=sprite, game_frame=game_frame)

        output = {'sprite_name': sprite_identify, 'location': location}
        return output
Beispiel #4
0
    def check_game_state(self, game_frame):
        #game over?
        locationGO = None
        sprite_to_locate = Sprite("QUERY", image_data=self.spriteGO.image_data)
        sprite_locator = SpriteLocator()
        locationGO = sprite_locator.locate(sprite=sprite_to_locate,
                                           game_frame=game_frame)
        print("Location Game over:", locationGO)
        #won game?
        locationWO = None
        sprite_to_locate = Sprite("QUERY", image_data=self.spriteWO.image_data)
        sprite_locator = SpriteLocator()
        locationWO = sprite_locator.locate(sprite=sprite_to_locate,
                                           game_frame=game_frame.frames)
        print("Location Game won:", locationWO)

        self.gamestate.girl_alive = (locationGO == None and locationWO == None)
        self.gamestate.done = not self.gamestate.girl_alive
        self.gamestate.victory = locationWO != None

        print(f"Is alive? {self.gamestate.girl_alive}")
        print(f"Game over? {self.gamestate.lose}")
        print(f"Won? {self.gamestate.victory}")
Beispiel #5
0
    def _measure_actor_hp(self, game_frame):
        hp_area_frame = serpent.cv.extract_region_from_image(
            game_frame.frame, self.game.screen_regions["HP_AREA"])
        hp_area_image = Image.fromarray(hp_area_frame)

        actor_hp = 0

        image_colors = hp_area_image.getcolors(
        )  # TODO: remove in favor of sprite detection and location
        if image_colors:
            actor_hp = len(image_colors) - 7

        for name, sprite in self.game.sprites.items():
            sprite_to_locate = Sprite("QUERY", image_data=sprite.image_data)

            sprite_locator = SpriteLocator()
            location = sprite_locator.locate(sprite=sprite_to_locate,
                                             game_frame=game_frame)
            print(location)
            if location:
                actor_hp = 1000000

        return actor_hp
class NativeWin32InputController(InputController):
    def __init__(self, game=None, **kwargs):
        self.game = game

        self.previous_key_collection_set = set()

        self.sprite_locator = SpriteLocator()

    # Keyboard Actions
    def handle_keys(self, key_collection, **kwargs):
        key_collection_set = set(key_collection)

        keys_to_press = key_collection_set - self.previous_key_collection_set
        keys_to_release = self.previous_key_collection_set - key_collection_set

        for key in keys_to_press:
            self.press_key(key, **kwargs)

        for key in keys_to_release:
            self.release_key(key, **kwargs)

        self.previous_key_collection_set = key_collection_set

    def tap_keys(self, keys, duration=0.05, **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            for key in keys:
                self.press_key(key, **kwargs)

            time.sleep(duration)

            for key in keys:
                self.release_key(key, **kwargs)

    def tap_key(self, key, duration=0.05, **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            self.press_key(key, **kwargs)

            time.sleep(duration)

            self.release_key(key, **kwargs)

    def press_keys(self, keys, **kwargs):
        for key in keys:
            self.press_key(key, **kwargs)

    def press_key(self, key, **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            extra = ctypes.c_ulong(0)
            ii_ = Input_I()

            if keyboard_key_mapping[key.name] >= 1024:
                key = keyboard_key_mapping[key.name] - 1024
                flags = 0x0008 | 0x0001
            else:
                key = keyboard_key_mapping[key.name]
                flags = 0x0008

            ii_.ki = KeyBdInput(0, key, flags, 0, ctypes.pointer(extra))
            x = Input(ctypes.c_ulong(1), ii_)
            ctypes.windll.user32.SendInput(1, ctypes.pointer(x),
                                           ctypes.sizeof(x))

    def release_keys(self, keys, **kwargs):
        for key in keys:
            self.release_key(key, **kwargs)

    def release_key(self, key, **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            extra = ctypes.c_ulong(0)
            ii_ = Input_I()

            if keyboard_key_mapping[key.name] >= 1024:
                key = keyboard_key_mapping[key.name] - 1024
                flags = 0x0008 | 0x0001 | 0x0002
            else:
                key = keyboard_key_mapping[key.name]
                flags = 0x0008 | 0x0002

            ii_.ki = KeyBdInput(0, key, flags, 0, ctypes.pointer(extra))
            x = Input(ctypes.c_ulong(1), ii_)
            ctypes.windll.user32.SendInput(1, ctypes.pointer(x),
                                           ctypes.sizeof(x))

    def type_string(self, string, duration=0.05, **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            for character in string:
                keys = character_keyboard_key_mapping.get(character)

                if keys is not None:
                    self.tap_keys(keys, duration=duration, **kwargs)

    # Mouse Actions
    def move(self, x=None, y=None, duration=0.25, absolute=True, **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            x += self.game.window_geometry["x_offset"]
            y += self.game.window_geometry["y_offset"]

            current_pixel_coordinates = win32api.GetCursorPos()
            start_coordinates = self._to_windows_coordinates(
                *current_pixel_coordinates)

            if absolute:
                end_coordinates = self._to_windows_coordinates(x, y)
            else:
                end_coordinates = self._to_windows_coordinates(
                    current_pixel_coordinates[0] + x,
                    current_pixel_coordinates[1] + y)

            coordinates = self._interpolate_mouse_movement(
                start_windows_coordinates=start_coordinates,
                end_windows_coordinates=end_coordinates)

            for x, y in coordinates:
                extra = ctypes.c_ulong(0)
                ii_ = Input_I()
                ii_.mi = MouseInput(x, y, 0, (0x0001 | 0x8000), 0,
                                    ctypes.pointer(extra))
                x = Input(ctypes.c_ulong(0), ii_)
                ctypes.windll.user32.SendInput(1, ctypes.pointer(x),
                                               ctypes.sizeof(x))

                time.sleep(duration / 20)

    def click_down(self, button=MouseButton.LEFT, **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            extra = ctypes.c_ulong(0)
            ii_ = Input_I()
            ii_.mi = MouseInput(0, 0, 0,
                                mouse_button_down_mapping[button.name], 0,
                                ctypes.pointer(extra))
            x = Input(ctypes.c_ulong(0), ii_)
            ctypes.windll.user32.SendInput(1, ctypes.pointer(x),
                                           ctypes.sizeof(x))

    def click_up(self, button=MouseButton.LEFT, **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            extra = ctypes.c_ulong(0)
            ii_ = Input_I()
            ii_.mi = MouseInput(0, 0, 0, mouse_button_up_mapping[button.name],
                                0, ctypes.pointer(extra))
            x = Input(ctypes.c_ulong(0), ii_)
            ctypes.windll.user32.SendInput(1, ctypes.pointer(x),
                                           ctypes.sizeof(x))

    def click(self, button=MouseButton.LEFT, duration=0.05, **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            self.click_down(button=button, **kwargs)
            time.sleep(duration)
            self.click_up(button=button, **kwargs)

    def click_screen_region(self,
                            button=MouseButton.LEFT,
                            screen_region=None,
                            **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            screen_region_coordinates = self.game.screen_regions.get(
                screen_region)

            x = (screen_region_coordinates[1] +
                 screen_region_coordinates[3]) // 2
            y = (screen_region_coordinates[0] +
                 screen_region_coordinates[2]) // 2

            self.move(x, y)
            self.click(button=button, **kwargs)

    def click_sprite(self,
                     button=MouseButton.LEFT,
                     sprite=None,
                     game_frame=None,
                     **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            sprite_location = self.sprite_locator.locate(sprite=sprite,
                                                         game_frame=game_frame)

            if sprite_location is None:
                return False

            x = (sprite_location[1] + sprite_location[3]) // 2
            y = (sprite_location[0] + sprite_location[2]) // 2

            self.move(x, y)
            self.click(button=button, **kwargs)

            return True

    # Requires the Serpent OCR module
    def click_string(self,
                     query_string,
                     button=MouseButton.LEFT,
                     game_frame=None,
                     fuzziness=2,
                     ocr_preset=None,
                     **kwargs):
        import serpent.ocr

        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            string_location = serpent.ocr.locate_string(
                query_string,
                game_frame.frame,
                fuzziness=fuzziness,
                ocr_preset=ocr_preset,
                offset_x=game_frame.offset_x,
                offset_y=game_frame.offset_y)

            if string_location is not None:
                x = (string_location[1] + string_location[3]) // 2
                y = (string_location[0] + string_location[2]) // 2

                self.move(x, y)
                self.click(button=button, **kwargs)

                return True

            return False

    def drag(self,
             button=MouseButton.LEFT,
             x0=None,
             y0=None,
             x1=None,
             y1=None,
             duration=0.25,
             **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            self.move(x=x0, y=y0)
            self.click_down(button=button)
            self.move(x=x1, y=y1, duration=duration)
            self.click_up(button=button)

    def drag_screen_region_to_screen_region(self,
                                            button=MouseButton.LEFT,
                                            start_screen_region=None,
                                            end_screen_region=None,
                                            duration=1,
                                            **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            start_screen_region_coordinates = self._extract_screen_region_coordinates(
                start_screen_region)
            end_screen_region_coordinates = self._extract_screen_region_coordinates(
                end_screen_region)

            self.drag(button=button,
                      x0=start_screen_region_coordinates[0],
                      y0=start_screen_region_coordinates[1],
                      x1=end_screen_region_coordinates[0],
                      y1=end_screen_region_coordinates[1],
                      duration=duration,
                      **kwargs)

    def scroll(self, clicks=1, direction="DOWN", **kwargs):
        if ("force" in kwargs
                and kwargs["force"] is True) or self.game_is_focused:
            clicks = clicks * (1 if direction == "UP" else -1) * 120

            extra = ctypes.c_ulong(0)
            ii_ = Input_I()
            ii_.mi = MouseInput(0, 0, clicks, 0x0800, 0, ctypes.pointer(extra))
            x = Input(ctypes.c_ulong(0), ii_)
            ctypes.windll.user32.SendInput(1, ctypes.pointer(x),
                                           ctypes.sizeof(x))

    @staticmethod
    def _to_windows_coordinates(x=0, y=0):
        display_width = win32api.GetSystemMetrics(0)
        display_height = win32api.GetSystemMetrics(1)

        windows_x = (x * 65535) // display_width
        windows_y = (y * 65535) // display_height

        return windows_x, windows_y

    @staticmethod
    def _interpolate_mouse_movement(start_windows_coordinates,
                                    end_windows_coordinates,
                                    steps=20):
        x_coordinates = [
            start_windows_coordinates[0], end_windows_coordinates[0]
        ]
        y_coordinates = [
            start_windows_coordinates[1], end_windows_coordinates[1]
        ]

        if x_coordinates[0] == x_coordinates[1]:
            x_coordinates[1] += 1

        if y_coordinates[0] == y_coordinates[1]:
            y_coordinates[1] += 1

        interpolation_func = scipy.interpolate.interp1d(
            x_coordinates, y_coordinates)

        intermediate_x_coordinates = np.linspace(start_windows_coordinates[0],
                                                 end_windows_coordinates[0],
                                                 steps + 1)[1:]
        coordinates = list(
            map(lambda x: (int(round(x)), int(interpolation_func(x))),
                intermediate_x_coordinates))

        return coordinates
 def find_sprite(self, sprite_name):
     sprite_locator = SpriteLocator()
     location = sprite_locator.locate(sprite=self.game.sprites[sprite_name],
                                      game_frame=self.game_frame)
     return location  # None if not found
class PyAutoGUIInputController(InputController):

    def __init__(self, game=None, **kwargs):
        self.game = game

        self.previous_key_collection_set = set()

        self.sprite_locator = SpriteLocator()

    # Keyboard Actions
    def handle_keys(self, key_collection, **kwargs):
        key_collection_set = set(key_collection)

        keys_to_press = key_collection_set - self.previous_key_collection_set
        keys_to_release = self.previous_key_collection_set - key_collection_set

        for key in keys_to_press:
            self.press_key(key, **kwargs)

        for key in keys_to_release:
            self.release_key(key, **kwargs)

        self.previous_key_collection_set = key_collection_set

    def tap_keys(self, keys, duration=0.05, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            for key in keys:
                self.press_key(key, **kwargs)

            time.sleep(duration)

            for key in keys:
                self.release_key(key, **kwargs)

    def tap_key(self, key, duration=0.05, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            self.press_key(key, **kwargs)

            time.sleep(duration)

            self.release_key(key, **kwargs)

    def press_keys(self, keys, **kwargs):
        for key in keys:
            self.press_key(key, **kwargs)

    def press_key(self, key, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            pyautogui.keyDown(keyboard_key_mapping[key.name])

    def release_keys(self, keys, **kwargs):
        for key in keys:
            self.release_key(key, **kwargs)

    def release_key(self, key, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            pyautogui.keyUp(keyboard_key_mapping[key.name])

    def type_string(self, string, duration=0.05, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            pyautogui.typewrite(message=string, interval=duration)

    # Mouse Actions
    def move(self, x=None, y=None, duration=0.25, absolute=True, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            x += self.game.window_geometry["x_offset"]
            y += self.game.window_geometry["y_offset"]

            if absolute:
                pyautogui.moveTo(x=x, y=y, duration=duration)
            else:
                pyautogui.moveRel(xOffset=x, yOffset=y, duration=duration)

    def click_down(self, button=MouseButton.LEFT, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            pyautogui.mouseDown(button=mouse_button_mapping[button.name])

    def click_up(self, button=MouseButton.LEFT, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            pyautogui.mouseUp(button=mouse_button_mapping[button.name])

    def click(self, button=MouseButton.LEFT, duration=0.25, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            self.click_down(button=button, **kwargs)
            time.sleep(duration)
            self.click_up(button=button, **kwargs)

    def click_screen_region(self, button=MouseButton.LEFT, screen_region=None, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            screen_region_coordinates = self.game.screen_regions.get(screen_region)

            x = (screen_region_coordinates[1] + screen_region_coordinates[3]) // 2
            y = (screen_region_coordinates[0] + screen_region_coordinates[2]) // 2

            self.move(x=x, y=y)
            self.click(button=button, **kwargs)

    def click_sprite(self, button=MouseButton.LEFT, sprite=None, game_frame=None, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            sprite_location = self.sprite_locator.locate(sprite=sprite, game_frame=game_frame)

            if sprite_location is None:
                return False

            x = (sprite_location[1] + sprite_location[3]) // 2
            y = (sprite_location[0] + sprite_location[2]) // 2

            self.move(x=x, y=y)
            self.click(button=button, **kwargs)

            return True

    # Requires the Serpent OCR module
    def click_string(self, query_string, button=MouseButton.LEFT, game_frame=None, fuzziness=2, ocr_preset=None, **kwargs):
        import serpent.ocr

        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            string_location = serpent.ocr.locate_string(
                query_string,
                game_frame.frame,
                fuzziness=fuzziness,
                ocr_preset=ocr_preset,
                offset_x=game_frame.offset_x,
                offset_y=game_frame.offset_y
            )

            if string_location is not None:
                x = (string_location[1] + string_location[3]) // 2
                y = (string_location[0] + string_location[2]) // 2

                self.move(x=x, y=y)
                self.click(button=button, **kwargs)

                return True

            return False

    def drag(self, button=MouseButton.LEFT, x0=None, y0=None, x1=None, y1=None, duration=0.25, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            self.move(x=x0, y=y0)
            self.click_down(button=button, **kwargs)
            self.move(x=x1, y=y1, duration=duration)
            self.click_up(button=button, **kwargs)

    def drag_screen_region_to_screen_region(self, button=MouseButton.LEFT, start_screen_region=None, end_screen_region=None, duration=1, **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            start_screen_region_coordinates = self._extract_screen_region_coordinates(start_screen_region)
            end_screen_region_coordinates = self._extract_screen_region_coordinates(end_screen_region)

            self.drag(
                button=button,
                x0=start_screen_region_coordinates[0],
                y0=start_screen_region_coordinates[1],
                x1=end_screen_region_coordinates[0],
                y1=end_screen_region_coordinates[1],
                duration=duration,
                **kwargs
            )

    def scroll(self, clicks=1, direction="DOWN", **kwargs):
        if ("force" in kwargs and kwargs["force"] is True) or self.game_is_focused:
            clicks = clicks * (1 if direction == "DOWN" else -1)
            pyautogui.scroll(clicks)
class DonkeyKongAPI(GameAPI):
    def __init__(self, game=None):
        super().__init__(game=game)

        self.navigationGameFSM = NavigationGameFSM()

        self._prepare_sprites()

        self.WIDTH = 34
        self.HEIGHT = 32
        self.n_WIDTH = 2
        self.n_HEIGHT = 2

        self.MARIO = 1
        self.MOVING_ENTITY = 2
        self.LADDER = 3
        self.LADDER_DELTA = 18

        self.LEVEL_WIN_HEIGHT = 32

        self.sprite_locator = SpriteLocator()

        self.ladders_positions = dict()
        self.ladders_positions[0] = [453, 10000]
        self.ladders_positions[1] = [102, 249]
        self.ladders_positions[2] = [285, 452]
        self.ladders_positions[3] = [104, 194]
        self.ladders_positions[4] = [452, 10000]
        self.ladders_positions[5] = [324, 10000]

        self.on_ladders = dict()
        self.on_ladders[(0, 453)] = [374, 328]
        self.on_ladders[(1, 102)] = [312, 268]
        self.on_ladders[(1, 249)] = [318, 262]
        self.on_ladders[(2, 285)] = [258, 202]
        self.on_ladders[(2, 452)] = [252, 208]
        self.on_ladders[(3, 104)] = [192, 148]
        self.on_ladders[(3, 194)] = [196, 144]
        self.on_ladders[(4, 452)] = [133, 88]
        self.on_ladders[(5, 324)] = [81, 32]

        self.last_stage = 0
        self.max_stage = 0
        self.last_change_stage = 0
        self.useless_actions = 0

        self.level_direction = dict()
        orientation = 1
        for i in range(6):
            self.level_direction[i] = orientation
            orientation = orientation * (-1)

        self.ladders_thresholds = [384, 328, 268, 208, 148, 88]

    def _prepare_sprites(self):
        # Death Sprite
        path = './plugins/SerpentDonkeyKongGamePlugin/files/data/sprites/death_{}.png'
        image = io.imread(path.format(1))
        self.death_sprite = Sprite("DEATH", image_data=image[..., np.newaxis])
        for i in range(2, 11):
            image = io.imread(path.format(i))
            self.death_sprite.append_image_data(image[..., np.newaxis])

        # Mario Sprite
        path = './plugins/SerpentDonkeyKongGamePlugin/files/data/sprites/mario_{}.png'
        image = io.imread(path.format(1))
        self.mario_sprite = Sprite("MARIO", image_data=image[..., np.newaxis])
        for i in range(2, 30):
            image = io.imread(path.format(i))
            self.mario_sprite.append_image_data(image[..., np.newaxis])

        # Falling Barrels Sprite
        path = './plugins/SerpentDonkeyKongGamePlugin/files/data/sprites/falling_barrel_{}.png'
        image = io.imread(path.format(1))
        self.falling_barrel_sprite = Sprite("FALLING",
                                            image_data=image[..., np.newaxis])
        for i in range(2, 3):
            image = io.imread(path.format(i))
            self.falling_barrel_sprite.append_image_data(image[...,
                                                               np.newaxis])

        # Rolling Barrels Sprite
        path = './plugins/SerpentDonkeyKongGamePlugin/files/data/sprites/rolling_barrel_{}.png'
        image = io.imread(path.format(1))
        self.rolling_barrel_sprite = Sprite("ROLLING",
                                            image_data=image[..., np.newaxis])
        for i in range(2, 5):
            image = io.imread(path.format(i))
            self.rolling_barrel_sprite.append_image_data(image[...,
                                                               np.newaxis])

        # Fire Monster Sprite
        path = './plugins/SerpentDonkeyKongGamePlugin/files/data/sprites/flammy_{}.png'
        image = io.imread(path.format(1))
        self.flammy_sprite = Sprite("FLAMMY",
                                    image_data=image[..., np.newaxis])
        for i in range(2, 5):
            image = io.imread(path.format(i))
            self.flammy_sprite.append_image_data(image[..., np.newaxis])

        # Splash Screen Sprite
        image = io.imread(
            './plugins/SerpentDonkeyKongGamePlugin/files/data/sprites/sprite_main_menu_splash_screen.png'
        )
        self.splash_screen = Sprite("SPLASH",
                                    image_data=image[..., np.newaxis])

    def analyze_frame(self, game_frame):
        locations = [None, None]
        if (self.navigationGameFSM.current == "black_screen"):
            locations[0] = self.sprite_locator.locate(sprite=self.mario_sprite,
                                                      game_frame=game_frame)
            if (locations[0] != None):
                self.navigationGameFSM.play()
        elif (self.navigationGameFSM.current == "playing"):
            loc = self._multiple_locate(sprite=self.mario_sprite,
                                        game_frame=game_frame,
                                        forced=True)
            if (len(loc) != 0):
                locations[0] = loc[0]
                locations[1] = None
            else:
                locations[0] = None
                loc = self._multiple_locate(sprite=self.death_sprite,
                                            game_frame=game_frame,
                                            forced=True)
                if (len(loc) != 0):
                    locations[1] = loc[0]

            if (locations[0] != None):
                has_mario = True

            if (locations[1] != None and locations[0] == None):
                self.navigationGameFSM.die()
                has_mario = False

            if (locations[0] != None and locations[1] == None
                    and locations[0][0] <= self.LEVEL_WIN_HEIGHT):
                self.navigationGameFSM.win()

            if (self.last_change_stage == 0):
                self.last_change_stage = time.time()

        return locations

    def _get_ladders(self, mario_posY):
        level = self._get_level(mario_posY)
        return self.ladders_positions[level]

    def _get_level(self, mario_posY):
        stage = 0
        for i in range(5, -1, -1):
            if (mario_posY <= self.ladders_thresholds[i]):
                stage = i
                break

        if (stage != self.last_stage):
            self.last_stage = stage
            if (stage > self.max_stage):
                self.max_stage = stage
            self.last_change_stage = time.time()
        return stage

    def _get_moving_entities(self, game_frame):
        moving = []
        inputs = [
            self.rolling_barrel_sprite, self.falling_barrel_sprite,
            self.flammy_sprite
        ]
        with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
            futures = {
                executor.submit(self._multiple_locate, sprite, game_frame):
                sprite
                for sprite in inputs
            }
            for future in concurrent.futures.as_completed(futures):
                moving = moving + future.result()
        return moving

    def get_projection_matrix(self, game_frame, location):
        reduced_frame = None
        units_array = None
        if (location != None):
            temp = game_frame.frame[location[0] -
                                    self.HEIGHT * self.n_HEIGHT:location[2],
                                    location[1] -
                                    self.WIDTH * self.n_WIDTH:location[3] +
                                    self.WIDTH * self.n_WIDTH]
            reduced_frame = GameFrame(temp)
            units_array = self._projection(reduced_frame, location)
            nx, ny = units_array.shape
            units_array = units_array.reshape(nx * ny)
            orientation = self._get_orientation(location)
            units_array = np.insert(units_array, len(units_array), orientation)

        return (reduced_frame, units_array)

    def _get_orientation(self, location):
        level = self._get_level(location[0])
        return self.level_direction[level]

    def _projection(self, game_frame, global_mario_positions):
        mario_location = self.sprite_locator.locate(sprite=self.mario_sprite,
                                                    game_frame=game_frame)
        moving_entities = self._get_moving_entities(game_frame)

        units_array = np.zeros((1 + self.n_HEIGHT, 1 + 2 * self.n_WIDTH))
        units_array[self.n_HEIGHT][self.n_WIDTH] = self.MARIO

        ladders = self._get_ladders(global_mario_positions[0])
        for ladder in ladders:
            if (abs(ladder - global_mario_positions[1]) <= self.LADDER_DELTA):
                pos_ladder = self.n_WIDTH
            elif (ladder < global_mario_positions[1]):
                # ladder on left
                pos_ladder = self.n_WIDTH - (int(
                    (global_mario_positions[1] - ladder) / self.WIDTH) + 1)
            else:
                # ladder on right
                pos_ladder = int(
                    (ladder - global_mario_positions[1]) / self.WIDTH)
                if (pos_ladder == 0):
                    pos_ladder = 1
                pos_ladder = pos_ladder + self.n_WIDTH

            if (pos_ladder >= 0 and pos_ladder <= (2 * self.n_WIDTH)):
                units_array[self.n_HEIGHT][pos_ladder] = self.LADDER

        for entity in moving_entities:
            if (entity[3] <= mario_location[1]):
                # left
                posX = entity[3]
            elif (entity[1] >= mario_location[3]):
                # right
                posX = entity[1]
            else:
                # centered on Mario
                posX = self.n_WIDTH * self.WIDTH

            if (entity[0] >= mario_location[2]):
                # bottom
                posY = entity[0]
            elif (entity[2] <= mario_location[0]):
                # up
                posY = entity[2]
            else:
                # centered on Mario
                posY = self.n_HEIGHT * self.HEIGHT

            units_array[int(posY / self.HEIGHT)][int(
                posX / self.WIDTH)] = self.MOVING_ENTITY

        return units_array

    def _multiple_locate(self,
                         sprite=None,
                         game_frame=None,
                         screen_region=None,
                         use_global_location=True,
                         forced=False):
        constellation_of_pixel_images = sprite.generate_constellation_of_pixels_images(
        )
        locations = []

        frame = game_frame.frame

        if screen_region is not None:
            frame = serpent.cv.extract_region_from_image(frame, screen_region)

        for i in range(len(constellation_of_pixel_images)):
            constellation_of_pixels_item = list(
                sprite.constellation_of_pixels[i].items())[0]

            query_coordinates = constellation_of_pixels_item[0]
            query_rgb = constellation_of_pixels_item[1]

            rgb_coordinates = Sprite.locate_color(query_rgb, image=frame)

            rgb_coordinates = list(
                map(
                    lambda yx: (yx[0] - query_coordinates[0], yx[1] -
                                query_coordinates[1]), rgb_coordinates))

            maximum_y = frame.shape[0] - constellation_of_pixel_images[
                i].shape[0]
            maximum_x = frame.shape[1] - constellation_of_pixel_images[
                i].shape[1]

            for y, x in rgb_coordinates:
                if y < 0 or x < 0 or y > maximum_y or x > maximum_x:
                    continue

                for yx, rgb in sprite.constellation_of_pixels[i].items():
                    if tuple(frame[y + yx[0], x + yx[1], :]) != rgb:
                        break
                else:
                    locations.append(
                        (y, x, y + constellation_of_pixel_images[i].shape[0],
                         x + constellation_of_pixel_images[i].shape[1]))
            if (forced and len(locations) > 0):
                break

        if len(locations
               ) != 0 and screen_region is not None and use_global_location:
            for i in range(0, len(locations)):
                locations[i] = (locations[i][0] + screen_region[0],
                                locations[i][1] + screen_region[1],
                                locations[i][2] + screen_region[0],
                                locations[i][3] + screen_region[1])

        if len(locations) > 0:
            locations = self._epurate(locations)

        return locations

    def _epurate(self, locations):
        temp = []
        epurated = []
        for i in range(0, len(locations)):
            if (not locations[i][0] in temp and not locations[i][0] - 1 in temp
                    and not locations[i][0] + 1 in temp):
                temp.append(locations[i][0])
                epurated.append(locations[i])
        return epurated

    def get_score_value(self, location):
        values = [-1000, -1000, -1000, -1000, -1000]
        if (location != None):
            values[0] = self._get_level(location[0])
            ladders = self._get_ladders(location[0])
            minimum_distance = int(abs(ladders[0] - location[3]))
            for ladder in ladders:
                if (abs(ladder - location[1]) <= self.LADDER_DELTA):
                    distance = 0
                elif (ladder < location[1]):
                    distance = int(abs(ladder - location[1]))
                else:
                    distance = int(abs(ladder - location[3]))
                if (distance < minimum_distance):
                    minimum_distance = distance

            values[1] = -1 * minimum_distance
            values[2] = self.max_stage
            values[3] = self.useless_actions
            if (self._is_on_ladder(values[0], location)):
                values[4] = 1
            else:
                values[4] = 0

        self.last_change_stage = 0
        self.last_stage = 0
        self.max_stage = 0
        self.useless_actions = 0

        return values

    def _is_on_ladder(self, level, location):
        ladders = self.ladders_positions[level]
        for ladder in ladders:
            if (abs(ladder - location[1]) <= self.LADDER_DELTA):
                couple = (level, ladder)
                if (couple in self.on_ladders):
                    extremity = self.on_ladders[couple]
                    if (location[0] <= extremity[0]
                            and location[0] > extremity[1]):
                        return True
        return False

    def get_death_location(self, game_frame):
        return self.sprite_locator.locate(sprite=self.death_sprite,
                                          game_frame=game_frame)

    def analyze_action(self, inputs, outputs):
        ### FILTRE LES JUMPS INUTILES
        # N = self.n_WIDTH * 2 + 1
        # row = self.n_HEIGHT * N

        # has_obstacle = False

        # for i in range(N):
        #     if(inputs[row+i] == self.MOVING_ENTITY):
        #         has_obstacle = True
        #         break;

        # if (not has_obstacle and outputs[len(outputs)-1] == 1):
        #     self.useless_actions = self.useless_actions + 1
        if (outputs[len(outputs) - 1] == 1):
            self.useless_actions = self.useless_actions + 1

    """
    Methods to use the NavigationGame FSM
    """

    def not_running(self):
        return self.navigationGameFSM.current == "not_running"

    def is_in_menu(self):
        return self.navigationGameFSM.current == "menu"

    def is_playing(self):
        return self.navigationGameFSM.current == "playing"

    def is_dead(self):
        return self.navigationGameFSM.current == "dead"

    def has_won(self):
        return self.navigationGameFSM.current == "has_won"

    def run(self):
        self.navigationGameFSM.run()

    def next(self):
        self.navigationGameFSM.next()

    def win(self):
        self.navigationGameFSM.win()

    class MyAPINamespace:
        @classmethod
        def my_namespaced_api_function(cls):
            api = DonkeyKongAPI.instance
Beispiel #10
0
    def handle_play(self, game_frame):
        #self.printer.add("")
        #self.printer.add("BombermanAI")
        #self.printer.add("Reinforcement Learning: Training a PPO Agent")
        #self.printer.add("")
        #self.printer.add(f"Stage Started At: {self.started_at}")
        #self.printer.add(f"Current Run: #{self.current_attempts}")
        #self.printer.add("")
        #self.check_game_state(game_frame)

        #####################CHECK STATE###########################
        #game over?
        locationGO = None
        sprite_to_locate = Sprite("QUERY", image_data=self.spriteGO.image_data)
        sprite_locator = SpriteLocator()
        locationGO = sprite_locator.locate(sprite=sprite_to_locate,
                                           game_frame=game_frame)
        #print("Location Game over:",locationGO)

        #won game?
        locationWO = None
        sprite_to_locate = Sprite("QUERY", image_data=self.spriteWO.image_data)
        sprite_locator = SpriteLocator()
        locationWO = sprite_locator.locate(sprite=sprite_to_locate,
                                           game_frame=game_frame)
        #print("Location Game won:",locationWO)

        self.gamestate.victory = locationWO != None
        self.gamestate.lose = locationGO != None
        self.gamestate.girl_alive = (locationGO == None and locationWO == None)
        self.gamestate.done = not self.gamestate.girl_alive

        print(f"Is alive? {self.gamestate.girl_alive}")
        print(f"Game over? {self.gamestate.lose}")
        print(f"Won? {self.gamestate.victory}")
        #####################VISUAL DEBUGGER###########################
        for i, game_frame in enumerate(self.game_frame_buffer.frames):
            self.visual_debugger.store_image_data(game_frame.frame,
                                                  game_frame.frame.shape,
                                                  str(i))

        #####################MODEL###########################
        #get buffer
        frame_buffer = FrameGrabber.get_frames([0, 1, 2, 3],
                                               frame_type="PIPELINE")
        game_frame_buffer = self.extract_game_area(frame_buffer)
        state = game_frame_buffer.reshape(4, 104, 136, 1)

        if (self.gamestate.done):
            print(f"Game over, attemp {self.epoch}")
            if (self.epoch % 10) == 0:
                print("saving model")
                self.dqn_agent.save_model(
                    f"bombergirl_epoch_{self.epoch}.model")
                self.printer.save_file()
            self.printer.add(
                f"{self.gamestate.victory},{self.gamestate.lose},{self.epoch},{self.gamestate.time},{self.total_reward}"
            )
            self.total_reward = 0
            self.dqn_agent.remember(self.prev_state, self.prev_action,
                                    self.prev_reward, state, True)
            self.dqn_agent.replay()
            self.input_controller.tap_key(KeyboardKey.KEY_ENTER)
            self.epoch += 1
            self.total_reward = 0
            self.gamestate.restartState()
            self.prev_state = None
            self.prev_action = None
        else:
            #update time
            self.gamestate.updateTime()

            #print(np.stack(game_frame_buffer,axis=1).shape)
            #print(game_frame_buffer.shape)
            #print(state.shape)
            if (not (self.prev_state is None)
                    and not (self.prev_action is None)):
                self.dqn_agent.remember(self.prev_state, self.prev_action,
                                        self.prev_reward, state, False)

            #do something
            action_index = self.dqn_agent.act(state)
            #get key
            action = self.game_actions[action_index]
            #get random frame from buffer
            game_frame_rand = random.choice(frame_buffer.frames).frame
            #update enviroment accorind to frame
            ###################FUN UPDATE STATE#########################################
            game_area = \
                    serpent.cv.extract_region_from_image(game_frame_rand,self.game.screen_regions['GAME_REGION'])
            #game ...
            # 0,0
            # 32,32
            game_squares = [[None for j in range(0, 11)] for i in range(0, 15)]
            const_offset = 8
            const = 32
            #game variables
            self.gamestate.bombs = []  #{x, y}
            self.gamestate.enemies = []  #{x,y}
            #force girl to die if not found
            girl_found = False
            for i in range(0, 15):
                for j in range(0, 11):
                    izq = ((j + 1) * const - const_offset,
                           (i + 1) * const - const_offset)
                    der = ((j + 2) * const + const_offset,
                           (i + 2) * const + const_offset)
                    reg = (izq[0], izq[1], der[0], der[1])
                    square = serpent.cv.extract_region_from_image(
                        game_area, reg)
                    square = self.convert_to_rgba(square)
                    sprite_to_locate = Sprite("QUERY",
                                              image_data=square[...,
                                                                np.newaxis])
                    sprite = self.sprite_identifier.identify(
                        sprite_to_locate, mode="SIGNATURE_COLORS")
                    game_squares[i][j] = sprite
                    if ("SPRITE_BETTY" in sprite):
                        self.girl = {"x": i, "y": j}
                        girl_found = True
                    elif ("SPRITE_GEORGE" in sprite):
                        self.gamestate.enemies.append({"x": i, "y": j})
                    elif ("SPRITE_BOMB" in sprite):
                        self.gamestate.bombs.append({"x": i, "y": j})
                    elif ("SPRITE_BONUSES" in sprite):
                        self.gamestate.bonus.append({"x": i, "y": j})
            #####################CHECK STATE###########################
            #game over?
            locationGO = None
            sprite_to_locate = Sprite("QUERY",
                                      image_data=self.spriteGO.image_data)
            sprite_locator = SpriteLocator()
            locationGO = sprite_locator.locate(sprite=sprite_to_locate,
                                               game_frame=game_frame)
            #print("Location Game over:",locationGO)

            #won game?
            locationWO = None
            sprite_to_locate = Sprite("QUERY",
                                      image_data=self.spriteWO.image_data)
            sprite_locator = SpriteLocator()
            locationWO = sprite_locator.locate(sprite=sprite_to_locate,
                                               game_frame=game_frame)
            #print("Location Game won:",locationWO)

            self.gamestate.lose = locationGO != None
            self.gamestate.victory = locationWO != None
            self.gamestate.girl_alive = (locationGO == None
                                         and locationWO == None)
            self.gamestate.done = not self.gamestate.girl_alive

            print(f"Is alive? {self.gamestate.girl_alive}")
            print(f"Game over? {self.gamestate.lose}")
            print(f"Won? {self.gamestate.victory}")

            ###################REWARD#########################################

            #get reward
            reward = self.gamestate.getReward(action_index)
            self.total_reward += reward
            self.prev_state = state
            self.prev_action = action_index
            self.prev_reward = reward

            if (action):
                self.input_controller.tap_key(
                    action, 0.15 if action_index < 4 else 0.01)
            print(
                f"Action: {self.gamestate.game_inputs[action_index]}, reward: {reward}, total_reward: {self.total_reward}"
            )
    def enemy_action_capture(self, game_frame):
        final_cultist_attack = []
        attack_cultist_temp_list= []

        # Unselect anything just incase
        self.input_controller.click(button=MouseButton.RIGHT, duration=0.25)
        time.sleep(.5)

        # Home hover
        self.input_controller.move(x=636, y=375, duration=0.25, absolute=True)
        time.sleep(1)

        # Enemy hover
        self.input_controller.move(x=959, y=410, duration=0.25, absolute=True)

        time.sleep(.75)

        image_data = skimage.io.imread("plugins/SerpentSlayTheSpireGamePlugin/files/data/sprites/sprite_Attack_for_0.png")[..., np.newaxis]

        attack_for_cultist = Sprite("attack_for_cultist", image_data=image_data)

        # Full game frame capture
        # print("-----------Full game frame capture-----------")
        # full_game_frame = FrameGrabber.get_frames(
        #     [0],
        #     frame_shape=(self.game.frame_height, self.game.frame_width),
        #     frame_type="PIPELINE"
        # ).frames[0]

        # Allows for dynamic capture of enemy attack
        sprite_locator = SpriteLocator()
        
        attack_for_cultist_location = sprite_locator.locate(sprite=attack_for_cultist, game_frame=game_frame)
        
        print("attack_for_cultist_location: ", attack_for_cultist_location)

        # Tuples are immutable :(
        if (attack_for_cultist_location != None):
            attack_cultist_temp_list = list(attack_for_cultist_location)

            attack_cultist_temp_list[1] = attack_cultist_temp_list[1] + 45
            attack_cultist_temp_list[3] = attack_cultist_temp_list[3] + 15

            attack_for_cultist_location = tuple(attack_cultist_temp_list)

            print("Updated - attack_for_cultist_location: ", attack_for_cultist_location)
            time.sleep(1)

            cultist_attack = serpent.cv.extract_region_from_image(game_frame.frame, attack_for_cultist_location)
            cultist_attack_grayscale = np.array(skimage.color.rgb2gray(cultist_attack) * 255, dtype="uint8")

            cultist_attack = serpent.ocr.perform_ocr(image=cultist_attack_grayscale, scale=15, order=5, horizontal_closing=2, vertical_closing=1)
            
            # This is actually an awkward work around for limitations in how tesseract works.  By default it doesn't capture single char values so when dynamically 
            # searching and capturing the enemy attack the region it's looking for the region that includes the word "for " + attack value (i.e. "for 6").  There 
            # are ways of swapping the mode of tesseract to do a capture for single char values but because the attack values are dynamic it sometimes is 
            # less than 10 or much greater than 10 which is now multiple char's and messes with the capture. For the sake of just getting it working I did this

            # TLDR: Awkward workaround for limitation in tesseract when capturing single char values.  Likely easier way to capture then parse attack value
            for elem in cultist_attack:
                if (elem.isdigit() == True):
                    final_cultist_attack.append(elem)
                    print("final_cultist_attack", final_cultist_attack)

            final_cultist_attack = ''.join(final_cultist_attack)

            print("final_cultist_attack: ", final_cultist_attack)
            print("------------------------------------")
        
            self.game_state["final_cultist_attack"].insert(0, final_cultist_attack)

            self.poison_check(game_frame)

        else:
            return print("Failed to capture enemy attack")
class InputController:
    def __init__(self, game=None):
        self.game = game

        self.mouse_buttons = {
            MouseButton.LEFT.name: "left",
            MouseButton.MIDDLE.name: "middle",
            MouseButton.RIGHT.name: "right"
        }

        self.previous_key_collection_set = set()

        self.sprite_locator = SpriteLocator()

    @property
    def game_is_focused(self):
        return self.game.is_focused

    # Keyboard Actions
    def handle_keys(self, key_collection):
        key_collection_set = set(key_collection)

        keys_to_press = key_collection_set - self.previous_key_collection_set
        keys_to_release = self.previous_key_collection_set - key_collection_set

        for key in keys_to_press:
            self.press_key(key)

        for key in keys_to_release:
            self.release_key(key)

        self.previous_key_collection_set = key_collection_set

    def tap_keys(self, keys, duration=0.05):
        if self.game_is_focused:
            for key in keys:
                pyautogui.keyDown(key)

            time.sleep(duration)

            for key in keys:
                pyautogui.keyUp(key)

    def tap_key(self, key, duration=0.05):
        if self.game_is_focused:
            pyautogui.keyDown(key)

            time.sleep(duration)

            pyautogui.keyUp(key)

    def press_keys(self, keys):
        for key in keys:
            self.press_key(key)

    def press_key(self, key):
        if self.game_is_focused:
            pyautogui.keyDown(key)

    def release_keys(self):
        for key in self.previous_key_collection_set:
            self.release_key(key)

        self.previous_key_collection_set = set()

    def release_key(self, key):
        if self.game_is_focused:
            pyautogui.keyUp(key)

    def type_string(self, string, duration=0.05):
        if self.game_is_focused:
            pyautogui.typewrite(message=string, interval=duration)

    # Mouse Actions
    def click(self, button=MouseButton.LEFT, y=None, x=None, duration=0.25):
        if self.game_is_focused:
            pyautogui.moveTo(x, y, duration=duration)
            pyautogui.click(button=self.mouse_buttons.get(button.name, "left"))

    def click_screen_region(self, button=MouseButton.LEFT, screen_region=None):
        if self.game_is_focused:
            screen_region_coordinates = self.game.screen_regions.get(
                screen_region)

            x = (screen_region_coordinates[1] +
                 screen_region_coordinates[3]) // 2
            x += self.game.window_geometry["x_offset"]

            y = (screen_region_coordinates[0] +
                 screen_region_coordinates[2]) // 2
            y += self.game.window_geometry["y_offset"]

            self.click(button=button, y=y, x=x)

    def click_sprite(self,
                     button=MouseButton.LEFT,
                     sprite=None,
                     game_frame=None):
        if self.game_is_focused:
            sprite_location = self.sprite_locator.locate(sprite=sprite,
                                                         game_frame=game_frame)

            if sprite_location is None:
                return False

            x = (sprite_location[1] + sprite_location[3]) // 2
            x += self.game.window_geometry["x_offset"]

            y = (sprite_location[0] + sprite_location[2]) // 2
            y += self.game.window_geometry["y_offset"]

            self.click(button=button, y=y, x=x)

            return True

    def click_string(self,
                     query_string,
                     button=MouseButton.LEFT,
                     game_frame=None,
                     fuzziness=2,
                     ocr_preset=None):
        if self.game_is_focused:
            string_location = serpent.ocr.locate_string(
                query_string,
                game_frame.frame,
                fuzziness=fuzziness,
                ocr_preset=ocr_preset,
                offset_x=game_frame.offset_x,
                offset_y=game_frame.offset_y)

            if string_location is not None:
                x = (string_location[1] + string_location[3]) // 2
                x += self.game.window_geometry["x_offset"]

                y = (string_location[0] + string_location[2]) // 2
                y += self.game.window_geometry["y_offset"]

                self.click(button=button, y=y, x=x)

                return True

            return False

    def drag(self,
             button=MouseButton.LEFT,
             x0=None,
             y0=None,
             x1=None,
             y1=None,
             duration=1):
        if self.game_is_focused:
            pyautogui.moveTo(x0, y0, duration=0.2)
            pyautogui.dragTo(x1, y1, button=button, duration=duration)

    def drag_screen_region_to_screen_region(self,
                                            button=MouseButton.LEFT,
                                            start_screen_region=None,
                                            end_screen_region=None,
                                            duration=1):
        if self.game_is_focused:
            start_screen_region_coordinates = self._extract_screen_region_coordinates(
                start_screen_region)
            end_screen_region_coordinates = self._extract_screen_region_coordinates(
                end_screen_region)

            self.drag(button=button,
                      x0=start_screen_region_coordinates[0],
                      y0=start_screen_region_coordinates[1],
                      x1=end_screen_region_coordinates[0],
                      y1=end_screen_region_coordinates[1],
                      duration=duration)

    def scroll(self, y=None, x=None, clicks=1, direction="DOWN"):
        if self.game_is_focused:
            clicks = clicks * (1 if direction == "DOWN" else -1)
            pyautogui.scroll(clicks, x=x, y=y)

    def _extract_screen_region_coordinates(self, screen_region):
        screen_region_coordinates = self.game.screen_regions.get(screen_region)

        x = (screen_region_coordinates[1] + screen_region_coordinates[3]) // 2
        x += self.game.window_geometry["x_offset"]

        y = (screen_region_coordinates[0] + screen_region_coordinates[2]) // 2
        y += self.game.window_geometry["y_offset"]

        return x, y