From f0c463e6de4e2e0de579f9cd852611dab911da26 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Mon, 8 Apr 2024 00:18:06 +0800 Subject: [PATCH] overlay show top three --- client.py | 100 +++++++++----- mitm.py | 364 +++++++++++++++++++++++++++++++++++++++++++------ tileUnicode.py | 11 +- 3 files changed, 402 insertions(+), 73 deletions(-) diff --git a/client.py b/client.py index 96f8c58..bc0cad2 100644 --- a/client.py +++ b/client.py @@ -66,12 +66,14 @@ def update(self, mjai_msg, state): for i in range(3): self.consumes[i].update(TILE_2_UNICODE_ART_RICH["?"]) self.weight.update("0.0") + self.app.rpc_server.draw_top3([self.recommand_idx, "?", "?", "?", "?", 0.0]) return recommand = mjai_msg['meta'][self.recommand_idx] for action_class in self.action.classes: if "action_" in action_class: self.action.remove_class(action_class) self.weight.update(f"{(recommand[1]*100):.2f}") + weight_text = f"{(recommand[1]*100):.2f}%" if recommand[0] in TILE_LIST: self.action.label = recommand[0] self.action.add_class("action_"+recommand[0]) @@ -79,6 +81,7 @@ def update(self, mjai_msg, state): self.vertical_rule.update(EMPTY_VERTICAL_RULE) for i in range(3): self.consumes[i].update(TILE_2_UNICODE_ART_RICH["?"]) + self.app.rpc_server.draw_top3([self.recommand_idx, recommand[0], "?", "?", "?", weight_text]) elif recommand[0] in ['chi_low', 'chi_mid', 'chi_high']: self.action.label = "chi" self.action.add_class("action_chi") @@ -88,15 +91,18 @@ def update(self, mjai_msg, state): last_kawa_tile_idx = TILE_LIST.index(last_kawa_tile) match recommand[0]: case 'chi_low': - self.consumes[0].update(TILE_2_UNICODE_ART_RICH[TILE_LIST[last_kawa_tile_idx-2]]) - self.consumes[1].update(TILE_2_UNICODE_ART_RICH[TILE_LIST[last_kawa_tile_idx-1]]) + c0 = TILE_LIST[last_kawa_tile_idx+1] + c1 = TILE_LIST[last_kawa_tile_idx+2] case 'chi_mid': - self.consumes[0].update(TILE_2_UNICODE_ART_RICH[TILE_LIST[last_kawa_tile_idx-1]]) - self.consumes[1].update(TILE_2_UNICODE_ART_RICH[TILE_LIST[last_kawa_tile_idx+1]]) + c0 = TILE_LIST[last_kawa_tile_idx-1] + c1 = TILE_LIST[last_kawa_tile_idx+1] case 'chi_high': - self.consumes[0].update(TILE_2_UNICODE_ART_RICH[TILE_LIST[last_kawa_tile_idx+1]]) - self.consumes[1].update(TILE_2_UNICODE_ART_RICH[TILE_LIST[last_kawa_tile_idx+2]]) + c0 = TILE_LIST[last_kawa_tile_idx-2] + c1 = TILE_LIST[last_kawa_tile_idx-1] + self.consumes[0].update(TILE_2_UNICODE_ART_RICH[c0]) + self.consumes[1].update(TILE_2_UNICODE_ART_RICH[c1]) self.consumes[2].update(TILE_2_UNICODE_ART_RICH["?"]) + self.app.rpc_server.draw_top3([self.recommand_idx, "chi", last_kawa_tile, c0, c1, weight_text]) elif recommand[0] in ['pon']: self.action.label = "pon" self.action.add_class("action_pon") @@ -106,6 +112,7 @@ def update(self, mjai_msg, state): for i in range(2): self.consumes[i].update(TILE_2_UNICODE_ART_RICH[last_kawa_tile]) self.consumes[2].update(TILE_2_UNICODE_ART_RICH["?"]) + self.app.rpc_server.draw_top3([self.recommand_idx, "pon", last_kawa_tile, last_kawa_tile, last_kawa_tile, weight_text]) elif recommand[0] in ['kan_select']: # The recommandation only shows kan_select, but not ['daiminkan', 'ankan', 'kakan'], # this is due to the Mortal model structure limitations. @@ -116,6 +123,7 @@ def update(self, mjai_msg, state): self.vertical_rule.update(EMPTY_VERTICAL_RULE) for i in range(3): self.consumes[i].update(TILE_2_UNICODE_ART_RICH["?"]) + self.app.rpc_server.draw_top3([self.recommand_idx, "kan", "?", "?", "?", weight_text]) elif recommand[0] in ['reach', 'hora', 'ryukyoku', 'none']: self.action.label = recommand[0] self.action.add_class("action_"+recommand[0]) @@ -123,6 +131,7 @@ def update(self, mjai_msg, state): self.vertical_rule.update(EMPTY_VERTICAL_RULE) for i in range(3): self.consumes[i].update(TILE_2_UNICODE_ART_RICH["?"]) + self.app.rpc_server.draw_top3([self.recommand_idx, recommand[0], "?", "?", "?", weight_text]) elif recommand[0] in ['nukidora']: self.action.label = "nukidora" self.action.add_class("action_nukidora") @@ -130,6 +139,7 @@ def update(self, mjai_msg, state): self.vertical_rule.update(EMPTY_VERTICAL_RULE) for i in range(3): self.consumes[i].update(TILE_2_UNICODE_ART_RICH["?"]) + self.app.rpc_server.draw_top3([self.recommand_idx, "nukidora", "N", "?", "?", weight_text]) pass @@ -161,17 +171,17 @@ def compose(self) -> ComposeResult: recommandations_container.border_title = "Recommandations" mjai_log_container.border_title = "Mjai" tehai_labels = [Label(TILE_2_UNICODE_ART_RICH["?"], id="tehai_"+str(i)) for i in range(13)] - tehai_value_labels = [Label(HAI_VALUE[40], id="tehai_value_"+str(i)) for i in range(13)] + # tehai_value_labels = [Label(HAI_VALUE[40], id="tehai_value_"+str(i)) for i in range(13)] tehai_rule = Label(VERTICAL_RULE, id="tehai_rule") tsumohai_label = Label(TILE_2_UNICODE_ART_RICH["?"], id="tsumohai") - tsumohai_value_label = Label(HAI_VALUE[40], id="tsumohai_value") + # tsumohai_value_label = Label(HAI_VALUE[40], id="tsumohai_value") tehai_container = Horizontal(id="tehai_container") for i in range(13): tehai_container.mount(tehai_labels[i]) - tehai_container.mount(tehai_value_labels[i]) + # tehai_container.mount(tehai_value_labels[i]) tehai_container.mount(tehai_rule) tehai_container.mount(tsumohai_label) - tehai_container.mount(tsumohai_value_label) + # tehai_container.mount(tsumohai_value_label) tehai_container.border_title = "Tehai" akagi_action = Button("Akagi", id="akagi_action", variant="default") akagi_pai = Button("Pai", id="akagi_pai", variant="default") @@ -184,9 +194,9 @@ def compose(self) -> ComposeResult: loading_indicator = LoadingIndicator(id="loading_indicator") loading_indicator.styles.height = "3" checkbox_autoplay = Checkbox("Autoplay", id="checkbox_autoplay", classes="short", value=AUTOPLAY) + checkbox_overlay = Checkbox("Overlay ", id="checkbox_overlay", classes="short") checkbox_test_one = Checkbox("test_one", id="checkbox_test_one", classes="short") - checkbox_test_two = Checkbox("test_two", id="checkbox_test_two", classes="short") - checkbox_container = Vertical(checkbox_autoplay, checkbox_test_one, id="checkbox_container") + checkbox_container = Vertical(checkbox_autoplay, checkbox_overlay, id="checkbox_container") checkbox_container.border_title = "Options" bottom_container = Horizontal(checkbox_container, akagi_container, id="bottom_container") yield Header() @@ -211,10 +221,10 @@ def on_mount(self) -> None: self.recommandations_container = self.query_one("#recommandations_container") self.mjai_log_container = self.query_one("#mjai_log_container") self.tehai_labels = [self.query_one("#tehai_"+str(i)) for i in range(13)] - self.tehai_value_labels = [self.query_one("#tehai_value_"+str(i)) for i in range(13)] + # self.tehai_value_labels = [self.query_one("#tehai_value_"+str(i)) for i in range(13)] self.tehai_rule = self.query_one("#tehai_rule") self.tsumohai_label = self.query_one("#tsumohai") - self.tsumohai_value_label = self.query_one("#tsumohai_value") + # self.tsumohai_value_label = self.query_one("#tsumohai_value") self.tehai_container = self.query_one("#tehai_container") # self.liqi_log_container.scroll_end(animate=False) self.mjai_log_container.scroll_end(animate=False) @@ -233,8 +243,9 @@ def on_mount(self) -> None: self.akagi_action.label = "Akagi" def refresh_log(self) -> None: - # Yes I know this is stupid try: + if self.flow_id not in self.app.liqi_msg_dict: + self.action_quit() if self.liqi_msg_idx < len(self.app.liqi_msg_dict[self.flow_id]): # self.liqi_log.update(self.app.liqi_msg_dict[self.flow_id][-1]) # self.liqi_log_container.scroll_end(animate=False) @@ -275,25 +286,38 @@ def refresh_log(self) -> None: tehai, tsumohai = state_to_tehai(player_state) for idx, tehai_label in enumerate(self.tehai_labels): tehai_label.update(TILE_2_UNICODE_ART_RICH[tehai[idx]]) - action_list = [x[0] for x in latest_mjai_msg['meta']] - for idx, tehai_value_label in enumerate(self.tehai_value_labels): - # latest_mjai_msg['meta'] is list of (pai, value) - try: - pai_value = int(latest_mjai_msg['meta'][action_list.index(tehai[idx])][1] * 40) - if pai_value == 40: - pai_value = 39 - except ValueError: - pai_value = 40 - tehai_value_label.update(HAI_VALUE[pai_value]) + # action_list = [x[0] for x in latest_mjai_msg['meta']] + # for idx, tehai_value_label in enumerate(self.tehai_value_labels): + # # latest_mjai_msg['meta'] is list of (pai, value) + # try: + # pai_value = int(latest_mjai_msg['meta'][action_list.index(tehai[idx])][1] * 40) + # if pai_value == 40: + # pai_value = 39 + # except ValueError: + # pai_value = 40 + # tehai_value_label.update(HAI_VALUE[pai_value]) self.tsumohai_label.update(TILE_2_UNICODE_ART_RICH[tsumohai]) - if tsumohai in action_list: - try: - pai_value = int(latest_mjai_msg['meta'][action_list.index(tsumohai)][1] * 40) - if pai_value == 40: - pai_value = 39 - except ValueError: - pai_value = 40 - self.tsumohai_value_label.update(HAI_VALUE[pai_value]) + # if tsumohai in action_list: + # try: + # pai_value = int(latest_mjai_msg['meta'][action_list.index(tsumohai)][1] * 40) + # if pai_value == 40: + # pai_value = 39 + # except ValueError: + # pai_value = 40 + # self.tsumohai_value_label.update(HAI_VALUE[pai_value]) + + self.app.rpc_server.clear_top3() + + # 將weight轉換為字典形式以便快速查找 + weight_dict = dict(self.app.mjai_msg_dict[self.flow_id][-1]['meta']) + + # 生成對應tile_list的權重列表 + tile_order_weight = [weight_dict[tile] if tile in weight_dict else -1.0 if tile == "?" else 0.0 for tile in tehai] + tile_order_weight.append(weight_dict[tsumohai] if tsumohai in weight_dict else -1.0 if tsumohai == "?" else 0.0) + # tile_order_weight is numpy.float64, need to convert to float + tile_order_weight = [float(weight) for weight in tile_order_weight] + self.app.rpc_server.draw_weight(tile_order_weight) + # mjai log self.mjai_log.update(self.app.mjai_msg_dict[self.flow_id][-3:]) self.mjai_log_container.scroll_end(animate=False) @@ -338,7 +362,6 @@ def refresh_log(self) -> None: logger.log("CLICK", latest_mjai_msg) self.app.set_timer(0.15, self.autoplay) # self.autoplay(tehai, tsumohai) - except Exception as e: logger.error(e) @@ -349,6 +372,14 @@ def checkbox_autoplay_changed(self, event: Checkbox.Changed) -> None: global AUTOPLAY AUTOPLAY = event.value pass + + @on(Checkbox.Changed, "#checkbox_overlay") + def checkbox_overlay_changed(self, event: Checkbox.Changed) -> None: + if event.value: + self.app.rpc_server.start_overlay_action() + else: + self.app.rpc_server.stop_overlay_action() + pass def redo_action(self) -> None: try: @@ -370,6 +401,7 @@ def autoplay(self) -> None: pass def action_quit(self) -> None: + self.app.rpc_server.stop_overlay_action() self.app.set_timer(2, self.app.update_flow.resume) self.update_log.stop() self.app.pop_screen() diff --git a/mitm.py b/mitm.py index 25dae5e..8859829 100644 --- a/mitm.py +++ b/mitm.py @@ -1,4 +1,5 @@ import json +import random import threading import asyncio import signal @@ -16,6 +17,8 @@ from xmlrpc.server import SimpleXMLRPCServer from playwright.sync_api import sync_playwright, WebSocket from playwright.sync_api._generated import Page +from action import LOCATION +from tileUnicode import TILE_2_UNICODE activated_flows = [] # store all flow.id ([-1] is the recently opened) messages_dict = dict() # flow.id -> Queue[flow_msg] @@ -78,7 +81,9 @@ async def start_proxy(host, port, enable_unlocker): # Create a XMLRPC server class LiqiServer: - _rpc_methods_ = ['get_activated_flows', 'get_messages', 'reset_message_idx', 'page_clicker', 'do_autohu', 'ping'] + _rpc_methods_ = ['get_activated_flows', 'get_messages', 'reset_message_idx', 'page_clicker', + 'do_autohu', 'evaluate', 'start_overlay_action', 'stop_overlay_action', 'draw_weight', + 'draw_top3', 'clear_top3', 'ping'] def __init__(self, host, port): self.host = host self.port = port @@ -87,6 +92,9 @@ def __init__(self, host, port): self.server.register_function(getattr(self, name)) self.message_idx = dict() # flow.id -> int + self._canvas_id = None + self.page = None + def get_activated_flows(self): return activated_flows @@ -107,13 +115,55 @@ def reset_message_idx(self): self.message_idx[flow_id] = 0 def page_clicker(self, xy): - global click_list - click_list.append(xy) + global enable_playwright, playwright_controller + if not enable_playwright: + return False + playwright_controller.click_list.append(xy) return True def do_autohu(self): - global do_autohu - do_autohu = True + return self.evaluate("() => view.DesktopMgr.Inst.setAutoHule(true)") + + def evaluate(self, script): + global enable_playwright, playwright_controller + if not enable_playwright: + return False + playwright_controller.evaluate_list.append(script) + return True + + def start_overlay_action(self): + global enable_playwright, playwright_controller + if not enable_playwright: + return False + playwright_controller.start_overlay_action() + return True + + def stop_overlay_action(self): + global enable_playwright, playwright_controller + if not enable_playwright: + return False + playwright_controller.stop_overlay_action() + return True + + def draw_weight(self, weight): + global enable_playwright, playwright_controller + if not enable_playwright: + return False + playwright_controller.draw_weight(weight) + return True + + def draw_top3(self, top3): + global enable_playwright, playwright_controller + if not enable_playwright: + return False + playwright_controller.draw_top3(top3) + return True + + def clear_top3(self): + global enable_playwright, playwright_controller + if not enable_playwright: + return False + playwright_controller.clear_top3() return True def ping(self): @@ -123,6 +173,271 @@ def serve_forever(self): print(f"XMLRPC Server is running on {self.host}:{self.port}") self.server.serve_forever() + +ACTION_2_UNICODE = { + 'chi': '吃', + 'chi_low': '吃', + 'chi_mid': '吃', + 'chi_high': '吃', + 'pon': '碰', + 'kan': '槓', + 'daiminkan': '槓', + 'ankan': '槓', + 'kakan': '槓', + 'hora': '和', + 'zimo': '和', + 'ryukyoku': '流局', + 'reach': '立直', + 'nukidora': '拔北', + 'none': '跳過', +} + + +class PlaywrightController: + def __init__(self, width, height, mitm_port): + self.playwrightContextManager = sync_playwright() + self.playwright = self.playwrightContextManager.__enter__() + self.chromium = self.playwright.chromium + self.browser = self.chromium.launch_persistent_context( + user_data_dir=Path(__file__).parent / 'data', + headless=False, + viewport={'width': width, 'height': height}, + proxy={"server": f"http://localhost:{mitm_port}"}, + ignore_default_args=['--enable-automation'] + ) + self.width = width + self.height = height + self._canvas_id = None + self._top_3_canvas_id = None + self._recommendation_canvas_id = None + self.scale = width / 16 + self.tiles_location_scaled = [[x*self.scale, y*self.scale] for x, y in LOCATION["tiles"]] + self.tsumo_space_scaled = int(LOCATION["tsumo_space"] * self.scale) + + print(f'startup browser success') + + self.page = self.browser.new_page() + + self.page.goto('https://game.maj-soul.com/1/') + print(f'go to page success, url: {self.page.url}') + + self._t3c_width = self.width * 0.16 + self._t3c_height = self.height * 0.14 + self._t3c_x = self.width - self._t3c_width + self._t3c_y = self.height - self._t3c_height + + self.click_list = [] + self.evaluate_list = [] + + def run(self): + while True: + if len(self.click_list) > 0: + xy = self.click_list.pop(0) + xy_scale = {"x":xy[0]*self.scale,"y":xy[1]*self.scale} + print(f"page_clicker: {xy_scale}") + self.page.mouse.move(x=xy_scale["x"], y=xy_scale["y"]) + time.sleep(0.1) + self.page.mouse.click(x=xy_scale["x"], y=xy_scale["y"], delay=100) + if self.evaluate_list: + for _ in range(len(self.evaluate_list)): + script = self.evaluate_list.pop(0) + print(f"evaluate: {script}") + self.page.evaluate(script) + time.sleep(0.1) # main thread will block here + + def start_overlay_action(self): + """ Display overlay on page. Will ignore if already exist, or page is None""" + # random 8-byte alpha-numeric string + + if self._canvas_id: # if exist, skip and return + return + if self._top_3_canvas_id: + return + if self.page is None: + return + # LOGGER.debug("browser Start overlay") + random_str = ''.join(random.choices('0123456789abcdef', k=8)) + self._canvas_id = "myCanvas" + random_str = ''.join(random.choices('0123456789abcdef', k=8)) + self._top3_bg_canvas_id = "top3bgCanvas" + random_str = ''.join(random.choices('0123456789abcdef', k=8)) + self._top_3_canvas_id = "top3Canvas" + js_code = f"""(async () => {{ + // Create a canvas element and add it to the document body + const canvas = document.createElement('canvas'); + canvas.id = '{self._canvas_id}'; + canvas.width = {self.width}; // Width of the canvas + canvas.height = {self.height}; // Height of the canvas + + // Set styles to ensure the canvas is on top + canvas.style.position = 'fixed'; // Use 'fixed' or 'absolute' positioning + canvas.style.left = '0'; // Position at the top-left corner of the viewport + canvas.style.top = '0'; + canvas.style.zIndex = '9999997'; // High z-index to ensure it is on top + canvas.style.pointerEvents = 'none'; // Make the canvas click-through + document.body.appendChild(canvas); + + // Create a canvas element and add it to the document body + const top3bgCanvas = document.createElement('canvas'); + top3bgCanvas.id = '{self._top3_bg_canvas_id}'; + top3bgCanvas.width = {self.width}; // Width of the canvas + top3bgCanvas.height = {self.height}; // Height of the canvas + top3bgCanvas.style.position = 'fixed'; // Use 'fixed' or 'absolute' positioning + top3bgCanvas.style.zIndex = '9999998'; // High z-index to ensure it is on top + top3bgCanvas.style.top = '0'; // Position at the top-left corner of the viewport + top3bgCanvas.style.left = '0'; + top3bgCanvas.style.pointerEvents = 'none'; // Make the canvas click-through + document.body.appendChild(top3bgCanvas); + + const top3bgCtx = top3bgCanvas.getContext('2d'); + top3bgCtx.fillStyle = 'rgba(0, 0, 0, 0.5)'; + top3bgCtx.fillRect({self._t3c_x}, {self._t3c_y}, {self._t3c_width}, {self._t3c_height}); + + // Create a canvas element and add it to the document body + const top3Canvas = document.createElement('canvas'); + top3Canvas.id = '{self._top_3_canvas_id}'; + top3Canvas.width = {self.width}; // Width of the canvas + top3Canvas.height = {self.height}; // Height of the canvas + top3Canvas.style.position = 'fixed'; // Use 'fixed' or 'absolute' positioning + top3Canvas.style.zIndex = '9999999'; // High z-index to ensure it is on top + top3Canvas.style.top = '0'; // Position at the top-left corner of the viewport + top3Canvas.style.left = '0'; + top3Canvas.style.pointerEvents = 'none'; // Make the canvas click-through + document.body.appendChild(top3Canvas); + }})();""" + + self.evaluate_list.append(js_code) + + def stop_overlay_action(self): + """ Remove overlay from page. Will ignore if page is None, or overlay not on""" + + if (self._canvas_id is None) or (self.page is None): + return + # LOGGER.debug("browser Stop overlay") + js_code = f"""(() => {{ + const canvas = document.getElementById('{self._canvas_id}'); + if (canvas) {{ + canvas.remove(); + }} + const top3_canvas = document.getElementById('{self._top_3_canvas_id}'); + if (top3_canvas) {{ + top3_canvas.remove(); + }} + const top3_bg_canvas = document.getElementById('{self._top3_bg_canvas_id}'); + if (top3_bg_canvas) {{ + top3_bg_canvas.remove(); + }} + }})()""" + self.evaluate_list.append(js_code) + self._canvas_id = None + self._top_3_canvas_id = None + self._top3_bg_canvas_id = None + self._botleft_text = None + + def draw_weight(self, weight): + if (self._canvas_id is None) or (self.page is None): + return + bar_width = int(0.2 * self.scale) + max_bar_height = int(1 * self.scale) + js_code = f"""(() => {{ + const canvas = document.getElementById('{self._canvas_id}'); + if (!canvas || !canvas.getContext) {{ + return; + }} + const ctx = canvas.getContext('2d'); + + // const TILES_LOCATION = [[x, y], ...]; // 示例坐标 + // const weight = [0.5, 0.8, ...]; // 示例权重列表 + TILES_LOCATION = {self.tiles_location_scaled} + weight = {weight} + scale = {self.scale} + + // 定义长条的宽度和最大长度 + const BAR_WIDTH = {bar_width}; + const MAX_BAR_HEIGHT = {max_bar_height}; + const TSUMO_SPACE = {self.tsumo_space_scaled}; + + ctx.clearRect(0, TILES_LOCATION[0][1] - MAX_BAR_HEIGHT - 55, canvas.width, MAX_BAR_HEIGHT); + + // 绘制每个长条 + for (let i = 0; i < weight.length; i++) {{ + const w = weight[i]; + + // Tsumohai has a special location + if (w == -1.0 || i == 13) {{ + if (weight[13] == -1.0) {{ + break; + }} + const barHeight2 = weight[13] * MAX_BAR_HEIGHT; + const [x2, y2] = TILES_LOCATION[i]; + const barX2 = x2 - BAR_WIDTH / 2 + TSUMO_SPACE; + const barY2 = y2 - barHeight2 - 55; // 55 is the half height of the tile + ctx.fillStyle = 'hsl(' + weight[13]*180 + ', 100%, 50%)'; + ctx.fillRect(barX2, barY2, BAR_WIDTH, barHeight2); + break; + }} + + // 根据weight值计算长条的实际高度 + const barHeight = w * MAX_BAR_HEIGHT; + + // 获取该长条的中心底部位置 + const [x, y] = TILES_LOCATION[i]; + + // 计算长条的左上角坐标,以便以中心底部位置为基准绘制 + const barX = x - BAR_WIDTH / 2; + const barY = y - barHeight - 55; // 55 is the half height of the tile + + // 设置绘制样式 + ctx.fillStyle = 'hsl(' + w*180 + ', 100%, 50%)'; // 设置长条颜色 + + // 绘制长条 + ctx.fillRect(barX, barY, BAR_WIDTH, barHeight); + }} + }})();""" + self.evaluate_list.append(js_code) + + def draw_top3(self, top3): + if (self._canvas_id is None) or (self.page is None): + return + top_idx, action, tile, consume1, consume2, weight = top3 + if action in ACTION_2_UNICODE.keys(): + rcmd_msg = f"{ACTION_2_UNICODE[action]}{TILE_2_UNICODE[tile]} {TILE_2_UNICODE[consume1]}{TILE_2_UNICODE[consume2]}" + else: + rcmd_msg = f"切{TILE_2_UNICODE[action]}" + + js_code = f"""(async () => {{ + const canvas = document.getElementById('{self._top_3_canvas_id}'); + if (!canvas || !canvas.getContext) {{ + return; + }} + const ctx = canvas.getContext('2d'); + + ctx.fillStyle = "#FFFFFF"; + ctx.font = 'bold {int(self._t3c_height/4)}px Arial'; + ctx.textAlign = 'left'; + ctx.fillText('{rcmd_msg}', {int(self._t3c_x)}, {int(self._t3c_height/4 + self._t3c_height*top_idx/3 + self._t3c_y)}); + ctx.textAlign = 'right'; + ctx.fillText('{weight}', {int(self._t3c_width + self._t3c_x)}, {int(self._t3c_height/4 + self._t3c_height*top_idx/3 + self._t3c_y)}); + }})()""" + self.evaluate_list.append(js_code) + + def clear_top3(self): + if (self._canvas_id is None) or (self.page is None): + return + js_code = f"""(() => {{ + const canvas = document.getElementById('{self._top_3_canvas_id}'); + if (!canvas || !canvas.getContext) {{ + return; + }} + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + }})()""" + self.evaluate_list.append(js_code) + + def exit(self): + self.playwrightContextManager.__exit__() + + if __name__ == '__main__': with open("settings.json", "r") as f: settings = json.load(f) @@ -176,49 +491,22 @@ def serve_forever(self): server_thread = threading.Thread(target=lambda: liqiServer.serve_forever()) server_thread.start() + playwright_controller = None page = None if enable_playwright: - playwrightContextManager = sync_playwright() - playwright = playwrightContextManager.__enter__() - chromium = playwright.chromium - browser = chromium.launch_persistent_context( - user_data_dir=Path(__file__).parent / 'data', - headless=False, - viewport={'width': playwright_width, 'height': playwright_height}, - proxy={"server": f"http://localhost:{mitm_port}"}, - ignore_default_args=['--enable-automation'] - ) - - print(f'startup browser success') - - page = browser.new_page() - - page.goto('https://game.maj-soul.com/1/') - print(f'go to page success, url: {page.url}') + playwright_controller = PlaywrightController(playwright_width, playwright_height, mitm_port) click_list = [] + evaluate_list = [] do_autohu = False # On Ctrl+C, stop the other threads try: - while True: - if enable_playwright: - if len(click_list) > 0: - xy = click_list.pop(0) - xy_scale = {"x":xy[0]*scale,"y":xy[1]*scale} - print(f"page_clicker: {xy_scale}") - page.mouse.move(x=xy_scale["x"], y=xy_scale["y"]) - time.sleep(0.1) - page.mouse.click(x=xy_scale["x"], y=xy_scale["y"], delay=100) - if do_autohu and autohu: - print(f"do_autohu") - page.evaluate("() => view.DesktopMgr.Inst.setAutoHule(true)") - # page.locator("#layaCanvas").click(position=xy_scale) - do_autohu = False - time.sleep(1) # main thread will block here + if enable_playwright: + playwright_controller.run() except KeyboardInterrupt: # On Ctrl+C, stop the other threads if enable_playwright: - playwrightContextManager.__exit__() + playwright_controller.__exit__() ctx.master.shutdown() liqiServer.server.shutdown() exit(0) diff --git a/tileUnicode.py b/tileUnicode.py index 2345b18..a66283f 100644 --- a/tileUnicode.py +++ b/tileUnicode.py @@ -43,7 +43,16 @@ '4z': '🀃', '5z': '🀆', '6z': '🀅', - '7z': '🀄' + '7z': '🀄', + # '?': '🀫', + '?': '', + 'E': '🀀', + 'S': '🀁', + 'W': '🀂', + 'N': '🀃', + 'P': '🀆', + 'F': '🀅', + 'C': '🀄', } TILE_2_UNICODE_ART = {