Exemple #1
0
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.config = config.get(f"{self.__class__.__name__}Plugin")

        self.platform = kwargs.get("platform")

        self.window_id = None
        self.window_name = kwargs.get("window_name")
        self.window_geometry = None

        self.window_controller = WindowController()

        self.is_launched = False

        self.frame_grabber_process = None
        self.game_frame_limiter = GameFrameLimiter(
            fps=self.config.get("fps", 4))

        self.api_class = None
        self.api_instance = None

        self.sprites = self._discover_sprites()

        self.redis_client = StrictRedis(**config["redis"])

        self.kwargs = kwargs
Exemple #2
0
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.config = config.get(f"{self.__class__.__name__}Plugin", dict())

        self.platform = kwargs.get("platform")

        default_input_controller_backend = InputControllers.CLIENT
        self.input_controller = kwargs.get(
            "input_controller") or default_input_controller_backend

        self.window_id = None
        self.window_name = kwargs.get("window_name")
        self.window_geometry = None

        self.dashboard_window_id = None

        self.window_controller = WindowController()

        self.is_launched = False

        self.frame_grabber_process = None
        self.frame_transformation_pipeline_string = None

        self.crossbar_process = None
        self.input_controller_process = None

        self.game_frame_limiter = GameFrameLimiter(
            fps=self.config.get("fps", 30))

        self.api_class = None
        self.api_instance = None

        self.environments = dict()
        self.environment_data = dict()

        self.sprites = self._discover_sprites()

        self.redis_client = StrictRedis(**config["redis"])

        self.pause_callback_fired = False

        self.kwargs = kwargs
Exemple #3
0
class Game(offshoot.Pluggable):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.config = config.get(f"{self.__class__.__name__}Plugin", dict())

        self.platform = kwargs.get("platform")

        default_input_controller_backend = InputControllers.CLIENT
        self.input_controller = kwargs.get(
            "input_controller") or default_input_controller_backend

        self.window_id = None
        self.window_name = kwargs.get("window_name")
        self.window_geometry = None

        self.dashboard_window_id = None

        self.window_controller = WindowController()

        self.is_launched = False

        self.frame_grabber_process = None
        self.frame_transformation_pipeline_string = None

        self.crossbar_process = None
        self.input_controller_process = None

        self.game_frame_limiter = GameFrameLimiter(
            fps=self.config.get("fps", 30))

        self.api_class = None
        self.api_instance = None

        self.environments = dict()
        self.environment_data = dict()

        self.sprites = self._discover_sprites()

        self.redis_client = StrictRedis(**config["redis"])

        self.pause_callback_fired = False

        self.kwargs = kwargs

    @property
    @offshoot.forbidden
    def game_name(self):
        return self.__class__.__name__.replace("Serpent",
                                               "").replace("Game", "")

    @property
    @offshoot.forbidden
    def game_launcher(self):
        return self.game_launchers.get(self.platform)

    @property
    @offshoot.forbidden
    def game_launchers(self):
        return {
            "steam": SteamGameLauncher,
            "executable": ExecutableGameLauncher,
            "web_browser": WebBrowserGameLauncher
        }

    @property
    @offshoot.expected
    def screen_regions(self):
        raise NotImplementedError()

    @property
    @offshoot.forbidden
    def api(self):
        if self.api_instance is None:
            self.api_instance = self.api_class(game=self)
        else:
            return self.api_instance

    @property
    @offshoot.forbidden
    def is_focused(self):
        return self.window_controller.is_window_focused(self.window_id)

    @offshoot.forbidden
    def launch(self, dry_run=False):
        self.before_launch()

        if not dry_run:
            self.game_launcher().launch(**self.kwargs)

        self.after_launch()

    @offshoot.forbidden
    def relaunch(self, before_relaunch=None, after_relaunch=None):
        clear_terminal()
        print("")
        print("Relaunching the game...")

        self.stop_frame_grabber()

        time.sleep(1)

        if before_relaunch is not None:
            before_relaunch()

        time.sleep(1)

        subprocess.call(shlex.split(f"serpent launch {self.game_name}"))
        self.launch(dry_run=True)

        self.start_frame_grabber()
        self.redis_client.delete(config["frame_grabber"]["redis_key"])

        while self.redis_client.llen(
                config["frame_grabber"]["redis_key"]) == 0:
            time.sleep(0.1)

        self.window_controller.focus_window(self.window_id)

        if after_relaunch is not None:
            after_relaunch()

    def before_launch(self):
        pass

    def after_launch(self):
        self.is_launched = True

        current_attempt = 1

        while current_attempt <= 100:
            self.window_id = self.window_controller.locate_window(
                self.window_name)

            if self.window_id not in [0, "0"]:
                break

            time.sleep(0.1)

        time.sleep(3)

        if self.window_id in [0, "0"]:
            raise SerpentError("Game window not found...")

        self.window_controller.move_window(self.window_id, 0, 0)

        self.dashboard_window_id = self.window_controller.locate_window(
            "Serpent.AI Dashboard")

        # TODO: Test on macOS and Linux
        if self.dashboard_window_id is not None and self.dashboard_window_id not in [
                0, "0"
        ]:
            self.window_controller.bring_window_to_top(
                self.dashboard_window_id)

        self.window_controller.focus_window(self.window_id)

        self.window_geometry = self.extract_window_geometry()

        print(self.window_geometry)

    def play(self,
             game_agent_class_name="GameAgent",
             frame_handler=None,
             **kwargs):
        if not self.is_launched:
            raise GameError(
                f"Game '{self.__class__.__name__}' is not running...")

        self.start_crossbar()
        time.sleep(3)

        self.start_input_controller()

        game_agent_class = offshoot.discover(
            "GameAgent",
            selection=game_agent_class_name).get(game_agent_class_name,
                                                 GameAgent)

        if game_agent_class is None:
            raise GameError(
                "The provided Game Agent class name does not map to an existing class..."
            )

        game_agent = game_agent_class(game=self,
                                      input_controller=InputController(
                                          game=self,
                                          backend=self.input_controller),
                                      **kwargs)

        # Look if we need to auto-append PNG to frame transformation pipeline based on given frame_handler
        png_frame_handlers = ["RECORD"]

        if frame_handler in png_frame_handlers and self.frame_transformation_pipeline_string is not None:
            if not self.frame_transformation_pipeline_string.endswith("|PNG"):
                self.frame_transformation_pipeline_string += "|PNG"

        self.start_frame_grabber()
        self.redis_client.delete(config["frame_grabber"]["redis_key"])

        while self.redis_client.llen(
                config["frame_grabber"]["redis_key"]) == 0:
            time.sleep(0.1)

        self.window_controller.focus_window(self.window_id)

        # Override FPS Config?
        if frame_handler == "RECORD":
            self.game_frame_limiter = GameFrameLimiter(fps=10)

        try:
            while True:
                self.game_frame_limiter.start()

                game_frame, game_frame_pipeline = self.grab_latest_frame()

                try:
                    if self.is_focused:
                        self.pause_callback_fired = False
                        game_agent.on_game_frame(game_frame,
                                                 game_frame_pipeline,
                                                 frame_handler=frame_handler,
                                                 **kwargs)
                    else:
                        if not self.pause_callback_fired:
                            print("PAUSED\n")

                            game_agent.on_pause(frame_handler=frame_handler,
                                                **kwargs)
                            self.pause_callback_fired = True

                        time.sleep(1)
                except Exception as e:
                    raise e
                    # print(e)
                    # time.sleep(0.1)

                self.game_frame_limiter.stop_and_delay()
        except Exception as e:
            raise e
        finally:
            self.stop_frame_grabber()
            self.stop_input_controller()
            self.stop_crossbar()

    @offshoot.forbidden
    def extract_window_geometry(self):
        if self.is_launched:
            return self.window_controller.get_window_geometry(self.window_id)

        return None

    @offshoot.forbidden
    def start_frame_grabber(self, pipeline_string=None):
        if not self.is_launched:
            raise GameError(
                f"Game '{self.__class__.__name__}' is not running...")

        if self.frame_grabber_process is not None:
            self.stop_frame_grabber()

        frame_grabber_command = f"serpent grab_frames {self.window_geometry['width']} {self.window_geometry['height']} {self.window_geometry['x_offset']} {self.window_geometry['y_offset']}"

        pipeline_string = pipeline_string or self.frame_transformation_pipeline_string

        if pipeline_string is not None:
            frame_grabber_command += f" {pipeline_string}"

        self.frame_grabber_process = subprocess.Popen(
            shlex.split(frame_grabber_command))

        signal.signal(signal.SIGINT, self._handle_signal_frame_grabber)
        signal.signal(signal.SIGTERM, self._handle_signal_frame_grabber)

        atexit.register(self._handle_signal_frame_grabber, 15, None, False)

    @offshoot.forbidden
    def stop_frame_grabber(self):
        if self.frame_grabber_process is None:
            return None

        self.frame_grabber_process.kill()
        self.frame_grabber_process = None

        atexit.unregister(self._handle_signal_frame_grabber)

    @offshoot.forbidden
    def grab_latest_frame(self):
        game_frame_buffer, game_frame_buffer_pipeline = FrameGrabber.get_frames_with_pipeline(
            [0])

        return game_frame_buffer.frames[0], game_frame_buffer_pipeline.frames[
            0]

    @offshoot.forbidden
    def start_crossbar(self):
        if self.crossbar_process is not None:
            self.stop_crossbar()

        crossbar_command = f"crossbar start --config crossbar.json"

        self.crossbar_process = subprocess.Popen(shlex.split(crossbar_command))

        signal.signal(signal.SIGINT, self._handle_signal_crossbar)
        signal.signal(signal.SIGTERM, self._handle_signal_crossbar)

        atexit.register(self._handle_signal_crossbar, 15, None, False)

    @offshoot.forbidden
    def stop_crossbar(self):
        if self.crossbar_process is None:
            return None

        self.crossbar_process.kill()
        self.crossbar_process = None

        atexit.unregister(self._handle_signal_crossbar)

    @offshoot.forbidden
    def start_input_controller(self):
        if self.input_controller_process is not None:
            self.stop_input_controller()

        self.redis_client.set("SERPENT:GAME", self.__class__.__name__)

        input_controller_command = f"python -m serpent.wamp_components.input_controller_component"

        self.input_controller_process = subprocess.Popen(
            shlex.split(input_controller_command))

        signal.signal(signal.SIGINT, self._handle_signal_input_controller)
        signal.signal(signal.SIGTERM, self._handle_signal_input_controller)

        atexit.register(self._handle_signal_input_controller, 15, None, False)

    @offshoot.forbidden
    def stop_input_controller(self):
        if self.input_controller_process is None:
            return None

        self.input_controller_process.kill()
        self.input_controller_process = None

        atexit.unregister(self._handle_signal_input_controller)

    def _discover_sprites(self):
        plugin_path = offshoot.config["file_paths"]["plugins"]
        sprites = dict()

        sprite_path = f"{plugin_path}/{self.__class__.__name__}Plugin/files/data/sprites"

        if os.path.isdir(sprite_path):
            files = os.scandir(sprite_path)

            for file in files:
                if file.name.endswith(".png"):
                    sprite_name = "_".join(
                        file.name.split("/")[-1].split("_")[:-1]).replace(
                            ".png", "").upper()

                    sprite_image_data = skimage.io.imread(
                        f"{sprite_path}/{file.name}")
                    sprite_image_data = sprite_image_data[..., np.newaxis]

                    if sprite_name not in sprites:
                        sprite = Sprite(sprite_name,
                                        image_data=sprite_image_data)
                        sprites[sprite_name] = sprite
                    else:
                        sprites[sprite_name].append_image_data(
                            sprite_image_data)

        return sprites

    def _handle_signal_frame_grabber(self,
                                     signum=15,
                                     frame=None,
                                     do_exit=True):
        if self.frame_grabber_process is not None:
            if self.frame_grabber_process.poll() is None:
                self.frame_grabber_process.send_signal(signum)

                if do_exit:
                    exit()

    def _handle_signal_crossbar(self, signum=15, frame=None, do_exit=True):
        if self.crossbar_process is not None:
            if self.crossbar_process.poll() is None:
                self.crossbar_process.send_signal(signum)

                if do_exit:
                    exit()

    def _handle_signal_input_controller(self,
                                        signum=15,
                                        frame=None,
                                        do_exit=True):
        if self.input_controller_process is not None:
            if self.input_controller_process.poll() is None:
                self.input_controller_process.send_signal(signum)

                if do_exit:
                    exit()
Exemple #4
0
    def play(self,
             game_agent_class_name="GameAgent",
             frame_handler=None,
             **kwargs):
        if not self.is_launched:
            raise GameError(
                f"Game '{self.__class__.__name__}' is not running...")

        self.start_crossbar()
        time.sleep(3)

        self.start_input_controller()

        game_agent_class = offshoot.discover(
            "GameAgent",
            selection=game_agent_class_name).get(game_agent_class_name,
                                                 GameAgent)

        if game_agent_class is None:
            raise GameError(
                "The provided Game Agent class name does not map to an existing class..."
            )

        game_agent = game_agent_class(game=self,
                                      input_controller=InputController(
                                          game=self,
                                          backend=self.input_controller),
                                      **kwargs)

        # Look if we need to auto-append PNG to frame transformation pipeline based on given frame_handler
        png_frame_handlers = ["RECORD"]

        if frame_handler in png_frame_handlers and self.frame_transformation_pipeline_string is not None:
            if not self.frame_transformation_pipeline_string.endswith("|PNG"):
                self.frame_transformation_pipeline_string += "|PNG"

        self.start_frame_grabber()
        self.redis_client.delete(config["frame_grabber"]["redis_key"])

        while self.redis_client.llen(
                config["frame_grabber"]["redis_key"]) == 0:
            time.sleep(0.1)

        self.window_controller.focus_window(self.window_id)

        # Override FPS Config?
        if frame_handler == "RECORD":
            self.game_frame_limiter = GameFrameLimiter(fps=10)

        try:
            while True:
                self.game_frame_limiter.start()

                game_frame, game_frame_pipeline = self.grab_latest_frame()

                try:
                    if self.is_focused:
                        self.pause_callback_fired = False
                        game_agent.on_game_frame(game_frame,
                                                 game_frame_pipeline,
                                                 frame_handler=frame_handler,
                                                 **kwargs)
                    else:
                        if not self.pause_callback_fired:
                            print("PAUSED\n")

                            game_agent.on_pause(frame_handler=frame_handler,
                                                **kwargs)
                            self.pause_callback_fired = True

                        time.sleep(1)
                except Exception as e:
                    raise e
                    # print(e)
                    # time.sleep(0.1)

                self.game_frame_limiter.stop_and_delay()
        except Exception as e:
            raise e
        finally:
            self.stop_frame_grabber()
            self.stop_input_controller()
            self.stop_crossbar()
Exemple #5
0
class Game(offshoot.Pluggable):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.config = config.get(f"{self.__class__.__name__}Plugin")

        self.platform = kwargs.get("platform")

        self.window_id = None
        self.window_name = kwargs.get("window_name")
        self.window_geometry = None

        self.window_controller = WindowController()

        self.is_launched = False

        self.frame_grabber_process = None
        self.game_frame_limiter = GameFrameLimiter(
            fps=self.config.get("fps", 4))

        self.api_class = None
        self.api_instance = None

        self.sprites = self._discover_sprites()

        self.redis_client = StrictRedis(**config["redis"])

        self.kwargs = kwargs

    @property
    @offshoot.forbidden
    def game_launcher(self):
        return self.game_launchers.get(self.platform)

    @property
    @offshoot.forbidden
    def game_launchers(self):
        return {
            "steam": SteamGameLauncher,
            "executable": ExecutableGameLauncher
        }

    @property
    @offshoot.expected
    def screen_regions(self):
        raise NotImplementedError()

    @property
    @offshoot.expected
    def ocr_presets(self):
        raise NotImplementedError()

    @property
    @offshoot.forbidden
    def api(self):
        if self.api_instance is None:
            self.api_instance = self.api_class(game=self)
        else:
            return self.api_instance

    @property
    @offshoot.forbidden
    def is_focused(self):
        return self.window_controller.is_window_focused(self.window_id)

    @offshoot.forbidden
    def launch(self, dry_run=False):
        self.before_launch()

        if not dry_run:
            self.game_launcher().launch(**self.kwargs)

        self.after_launch()

    def before_launch(self):
        pass

    def after_launch(self):
        self.is_launched = True

        time.sleep(5)

        self.window_id = self.window_controller.locate_window(self.window_name)

        self.window_controller.move_window(self.window_id, 0, 0)
        self.window_controller.focus_window(self.window_id)

        self.window_geometry = self.extract_window_geometry()
        print(self.window_geometry)

    def play(self, game_agent_class_name=None, frame_handler=None, **kwargs):
        if not self.is_launched:
            raise GameError(
                f"Game '{self.__class__.__name__}' is not running...")

        game_agent_class = offshoot.discover("GameAgent").get(
            game_agent_class_name, GameAgent)

        if game_agent_class is None:
            raise GameError(
                "The provided Game Agent class name does not map to an existing class..."
            )

        game_agent = game_agent_class(
            game=self, input_controller=InputController(game=self))

        self.start_frame_grabber()
        self.redis_client.delete(config["frame_grabber"]["redis_key"])

        while self.redis_client.llen(
                config["frame_grabber"]["redis_key"]) == 0:
            time.sleep(0.1)

        self.window_controller.focus_window(self.window_id)

        while True:
            self.game_frame_limiter.start()

            game_frame = self.grab_latest_frame()
            try:
                if self.is_focused:
                    game_agent.on_game_frame(game_frame,
                                             frame_handler=frame_handler,
                                             **kwargs)
                else:
                    serpent.utilities.clear_terminal()
                    print("PAUSED\n")

                    game_agent.on_pause(frame_handler=frame_handler, **kwargs)

                    time.sleep(1)
            except Exception as e:
                raise e
                # print(e)
                # time.sleep(0.1)

            self.game_frame_limiter.stop_and_delay()

    @offshoot.forbidden
    def extract_window_geometry(self):
        if self.is_launched:
            return self.window_controller.get_window_geometry(self.window_id)

        return None

    @offshoot.forbidden
    def start_frame_grabber(self):
        if not self.is_launched:
            raise GameError(
                f"Game '{self.__class__.__name__}' is not running...")

        if self.frame_grabber_process is not None:
            self.stop_frame_grabber()

        frame_grabber_command = f"serpent grab_frames {self.window_geometry['width']} {self.window_geometry['height']} {self.window_geometry['x_offset']} {self.window_geometry['y_offset']}"
        self.frame_grabber_process = subprocess.Popen(
            shlex.split(frame_grabber_command))

        signal.signal(signal.SIGINT, self._handle_signal)
        signal.signal(signal.SIGTERM, self._handle_signal)

        atexit.register(self._handle_signal, 15, None, False)

    @offshoot.forbidden
    def stop_frame_grabber(self):
        if self.frame_grabber_process is None:
            return None

        self.frame_grabber_process.kill()
        self.frame_grabber_process = None

        atexit.unregister(self._handle_signal)

    @offshoot.forbidden
    def grab_latest_frame(self):
        game_frame_buffer = FrameGrabber.get_frames(
            [0], (self.window_geometry.get("height"),
                  self.window_geometry.get("width"), 3))

        return game_frame_buffer.frames[0]

    def _discover_sprites(self):
        plugin_path = offshoot.config["file_paths"]["plugins"]
        sprites = dict()

        sprite_path = f"{plugin_path}/{self.__class__.__name__}Plugin/files/data/sprites"

        if os.path.isdir(sprite_path):
            files = os.scandir(sprite_path)

            for file in files:
                if file.name.endswith(".png"):
                    sprite_name = "_".join(
                        file.name.split("/")[-1].split("_")[:-1]).replace(
                            ".png", "").upper()

                    sprite_image_data = skimage.io.imread(
                        f"{sprite_path}/{file.name}")
                    sprite_image_data = sprite_image_data[..., np.newaxis]

                    if sprite_name not in sprites:
                        sprite = Sprite(sprite_name,
                                        image_data=sprite_image_data)
                        sprites[sprite_name] = sprite
                    else:
                        sprites[sprite_name].append_image_data(
                            sprite_image_data)

        return sprites

    def _handle_signal(self, signum=15, frame=None, do_exit=True):
        if self.frame_grabber_process is not None:
            if self.frame_grabber_process.poll() is None:
                self.frame_grabber_process.send_signal(signum)

                if do_exit:
                    exit()