Ejemplo n.º 1
0
 def resume(self):
     if self.transport.pid is not None:
         p = Process(self.transport.pid)
         p.resume()
         while p.status() != STATUS_RUNNING:
             print("resume {}: not running".format(self._node_dir))
     else:
         raise RuntimeError("Cannot resume Tahoe: no PID available")
class SoullessEnv(gym.Env):
    metadata = {'render.modes': ['human']}
    user32 = WinDLL("user32")
    DEATHCOUNT_PATTERN = re.compile("(\d+)")
    KEYS = (KEYS.LEFT, KEYS.RIGHT, KEYS.DELETE)
    TRANSITION_TO_ACTION = {"10": "UP", "01": "DOWN", "00": False, "11": False}

    def __init__(self):
        self.AHK = AHK()
        self.application = self.start_game()
        self.process_id = self.application.process
        self.process = Process(pid=self.process_id)
        self.dialog = self.get_window_dialog()
        self.handle = self.dialog.handle
        self.window = Window.from_pid(self.AHK, self.process_id)

        left, top, right, bot = GetClientRect(self.handle)
        w = right - left
        h = bot - top

        self.hwndDC = GetWindowDC(self.handle)
        self.mfcDC = CreateDCFromHandle(self.hwndDC)
        self.saveDC = self.mfcDC.CreateCompatibleDC()
        self.saveDC_handle = self.saveDC.GetSafeHdc()

        self.saveBitMap = CreateBitmap()
        self.saveBitMap.CreateCompatibleBitmap(self.mfcDC, w, h)
        self.bmpinfo = self.saveBitMap.GetInfo()
        self.saveDC.SelectObject(self.saveBitMap)

        self.navigate_main_menu()
        self.enter_avoidance()
        self.process.suspend()

        self.PREVIOUS_ACTION = 0
        self.deathcount = self.get_game_deathcount()
        self.action_space = spaces.Discrete(6)
        self.elapsed_time = 0.0

        self.observation_space = spaces.Dict({"window":
                                                  spaces.Box(low=0, high=255, shape=self.get_observation_space_size(),
                                                             dtype=np.uint8),
                                              "time": spaces.Box(low=0, high=900, shape=(1,))})

    def start_game(self):
        """starts the Soulless process"""
        app = Application().start(
            "D:/Users/david/PycharmProjects/reinforcement-learning/gym-soulless/Soulless 1.3HM/Soulless Hard Mode.exe")
        return app

    def get_window_dialog(self):
        """:returns the main dialog of the application, which is the entry-point for interacting with it"""
        return self._top_window()

    @try_loop(error_type=RuntimeError)
    def _top_window(self):
        return self.application.top_window()

    def navigate_main_menu(self):
        """from the title screen it navigates the menus until it enters the game"""
        self.window.send("{Shift}", blocking=False)
        sleep(3)
        self.window.send("{Shift}", blocking=False)

    def enter_avoidance(self):
        self.window.send("{Left down}", blocking=False)
        sleep(0.3)
        self.window.send("{Left up}", blocking=False)
        sleep(2)

    def capture_window(self):
        """:returns a np array image of the dialog cropped to exclude the margins for resizing the window
        https://stackoverflow.com/questions/19695214/python-screenshot-of-inactive-window-printwindow-win32gui"""

        SoullessEnv.user32.PrintWindow(HWND(self.handle), self.saveDC_handle, 3)
        bmpstr = self.saveBitMap.GetBitmapBits(True)

        im = Image.frombuffer(
            'RGB',
            (self.bmpinfo['bmWidth'], self.bmpinfo['bmHeight']),
            bmpstr, 'raw', 'BGRX', 0, 1)
        return np.array(im, copy=False)

    def step(self, action: int):
        """expects the game to be in a suspended state"""
        is_done, obs, reward = self.step_and_track_elapsed_time(action)
        obs = {"window": obs, "time": np.array([self.elapsed_time], dtype=np.float)}
        return obs, reward, is_done, {}

    def step_and_track_elapsed_time(self, action):
        self.process.resume()
        step_start_time = perf_counter()
        is_done, obs = self._step(action)
        self.process.suspend()
        step_end_time = perf_counter()
        elapsed_time = step_end_time - step_start_time
        self.elapsed_time += elapsed_time
        return is_done, obs, elapsed_time

    def _step(self, action):
        self.perform_action(action)
        obs = self.capture_window()
        is_done = self.is_done()
        if is_done:
            self.update_env_deathcount()
        return is_done, obs

    def perform_action(self, action):
        keystrokes = self.get_action_transition(self.PREVIOUS_ACTION, action)
        self.send_input_to_game(keystrokes)
        self.PREVIOUS_ACTION = action

    def is_done(self):
        return self.get_game_deathcount() > self.deathcount

    def update_env_deathcount(self):
        self.deathcount = self.get_game_deathcount()

    def get_action_transition(self, old_action: int, new_action: int):
        """:param old_action is the action performed on the previous step, or no-op if this is the first step
        :param new_action is the action performed on this step
        :returns the input to the send function needed to transition from the old action to the new action"""

        old_action, new_action = bin(old_action)[2:].zfill(3), bin(new_action)[2:].zfill(3)
        actions = map(SoullessEnv.TRANSITION_TO_ACTION.get, map("".join, zip(old_action, new_action)))

        return "".join(getattr(key, action) for key, action in zip(SoullessEnv.KEYS, actions) if action)

    def send_input_to_game(self, keystrokes: str):
        """:param keystrokes is the input to the send_keys function"""
        self.window.send(keystrokes, blocking=False)

    def reset(self):
        self.process.resume()
        self.window.send("r", delay=150)
        self.process.suspend()
        self.elapsed_time = 0.0

        return {"window": self.capture_window(), "time": np.array([self.elapsed_time], dtype=np.float)}

    def render(self, mode='human'):
        pass

    def close(self):
        DeleteObject(self.saveBitMap.GetHandle())
        self.saveDC.DeleteDC()
        self.mfcDC.DeleteDC()
        ReleaseDC(self.handle, self.hwndDC)
        self.application.kill()

    def get_observation_space_size(self):
        return self.capture_window().shape

    def get_game_deathcount(self):
        """:returns the number of times the kid has died"""
        title = self.window.title.decode("utf8")
        return int(re.search(SoullessEnv.DEATHCOUNT_PATTERN, title)[0])
Ejemplo n.º 3
0
class Engine(object):
    def __init__(self, spider):
        self.status = EngineStatus.stop
        self.spider_name = spider.name
        self.spider_logger = spider.logger
        self.spider_callback = spider.callback
        self.settings = spider.settings

    def set_process(self):
        pid = os.getpid()
        self._pid = pid
        self._process = Process(pid)

    def set_running_type(self, distributed):
        """设置运行类型,singleton和distributed有不同的处理逻辑"""

        scheduler_cls = load_object(self.settings.get('SCHEDULER'))
        self._scheduler = scheulder_cls.from_spider(self)

        if distributed:
            self._master_rpc = None
        else:
            dupefiler = load_object(self.settings.get('DUPEFILTER'))
            self._dupefilter = dupefilter.from_spider(self)
            self._scheduler = SchedulerFactory(self._scheduler,
                                               self._dupefilter)
            self._master_rpc = self._scheudler

    def set_downloader(self):
        """设置网络访问组件,根据不同的协议区分
        不同协议的区分在downloader中区分,就像一个真正的浏览器一样"""
        downloader_cls = load_object(self.settings, get('DOWNLOADER_AGENT'))
        self.downloader = downloader_cls.from_spider(spider)

    def set_pipeline(self):
        """设置数据管道,数据管道为多个时数据按顺序进入多个管道中"""

        pass

    def set_statscol(self):
        """设置统计收集器"""

        statscol_cls = load_object(self.settings, get('STATS_COLLECTION_LIST'))
        self._statscol = statscol_cls.from_spider(self)

    def set_middleware(self):
        """设置中间件管理器"""

        mw_manager_cls = load_object(self.settings, get('MIDDLEWARE_MANAGER'))
        self._mw_manager = mw_manager_cls.from_spider(self)

    def restart(self):
        pass

    def start(self):
        assert self.status == EngineStatus.stop, 'Spider engine already running'
        self.status = EngineStatus.start
        self.start_time = time.time()
        self.push_first_urls_to_scheduler()
        self.turbine()

    def stop(self):
        assert self.status != EngineStatus.stop, 'Spider engine already stop'
        self.status = EngineStatus.stop

    def pause(self):
        assert self.status != EngineStatus.stop, 'Spider engine already stop'
        self._process.suspend()
        self.status = EngineStatus.pause

    def resume(self):
        assert self.status == EngineStatus.pause, 'Spider engine arent paused'
        self._process.resume()
        self.status = EngineStatus.running

    def turbine(self):
        """引擎为running状态,循环从调度器中取出待爬取得request,并将request,protocol,response送入callback中"""

        while self.status == EngineStatus.running:
            request, protocol = self.pull_request_from_scheduler()
            response = self.send_request(request)
            response and request.callback(protocol, response)

    def push_first_urls_to_scheduler(self):
        """将first_urls送入调度器中统一处理"""

        first_urls = self.settings['FIRST_URLS']
        fisrt_requests = urls_to_requests(frist_urls,
                                          callback=self.spider_callback)
        self.push_requests_to_scheduler(first_requests)

    def pull_request_from_scheduler(self):
        try:
            protocol_string = self._scheduler.pull()
        except SchedulerEmpty:
            self.spider_logger.info('Scheduler already emtpy,stop engine soon')
            self.stop()

        protocol = protocol_string_to_object(protocol_string)
        request = protocol.request
        return (request, protocol)

    def push_request_to_scheduler(self, req):
        if isinstance(reqs, Request):
            self._scheduler.push(req)

    def push_requests_to_scheduler(self, reqs):
        if isinstance(reqs, (list, GeneratorType)):
            for req in reqs:
                self.push_request_to_scheudler(req)

    def push_data_to_pipeline(self):
        pass

    def request_backout(self, priority=None):
        pass

    def send_request(self, req):
        """发送请求"""

        return self._downloader.send(req)