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])
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)