From 3d46465c0c4b3bdac3085c9e534276f5133579f6 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Sat, 17 Feb 2024 15:30:31 +0800 Subject: [PATCH 01/30] Make sure mitm is ready --- client.py | 6 +++++- mitm.py | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/client.py b/client.py index 91d1064..056240c 100644 --- a/client.py +++ b/client.py @@ -484,7 +484,11 @@ def on_button_pressed(self, event: Button.Pressed) -> None: pass def mitm_connected(self): - self.mitm_started = True + try: + self.rpc_server.ping() + self.mitm_started = True + except: + self.set_timer(2, self.mitm_connected) def my_sink(self, message) -> None: record = message.record diff --git a/mitm.py b/mitm.py index b2e57a4..0dd060a 100644 --- a/mitm.py +++ b/mitm.py @@ -79,7 +79,7 @@ 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'] + _rpc_methods_ = ['get_activated_flows', 'get_messages', 'reset_message_idx', 'page_clicker', 'do_autohu', 'ping'] def __init__(self, host, port): self.host = host self.port = port @@ -117,6 +117,9 @@ def do_autohu(self): do_autohu = True return True + def ping(self): + return True + def serve_forever(self): print(f"XMLRPC Server is running on {self.host}:{self.port}") self.server.serve_forever() From 170e33f51e01b8da7aa9b2d9f91bf0ca66d650e0 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Mon, 19 Feb 2024 11:34:41 +0800 Subject: [PATCH 02/30] Add a simple client --- simple_client.py | 207 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 simple_client.py diff --git a/simple_client.py b/simple_client.py new file mode 100644 index 0000000..55d0ca5 --- /dev/null +++ b/simple_client.py @@ -0,0 +1,207 @@ +import json +import threading +import asyncio +import signal +import time +import re +import mitmproxy.addonmanager +import mitmproxy.http +import mitmproxy.log +import mitmproxy.tcp +import mitmproxy.websocket +import mhm +from pathlib import Path +from optparse import OptionParser +from mitmproxy import proxy, options, ctx +from mitmproxy.tools.dump import DumpMaster +from xmlrpc.server import SimpleXMLRPCServer +from playwright.sync_api import sync_playwright, WebSocket +from playwright.sync_api._generated import Page + +from liqi import LiqiProto +from majsoul2mjai import MajsoulBridge + +activated_flows = [] # store all flow.id ([-1] is the recently opened) +activated_flows_instance = [] +messages_dict = dict() # flow.id -> Queue[flow_msg] +stop = False +SHOW_LIQI = False + +mhm.logger.setLevel("WARNING") + +class ClientWebSocket: + def __init__(self): + self.liqi: dict[str, LiqiProto]={} + self.bridge: dict[str, MajsoulBridge]={} + pass + + # Websocket lifecycle + def websocket_start(self, flow: mitmproxy.http.HTTPFlow): + """ + + A websocket connection has commenced. + + """ + # print('[new websocket]:',flow,flow.__dict__,dir(flow)) + assert isinstance(flow.websocket, mitmproxy.websocket.WebSocketData) + global activated_flows,messages_dict,activated_flows_instance + + activated_flows.append(flow.id) + activated_flows_instance.append(flow) + + messages_dict[flow.id]=flow.websocket.messages + + self.liqi[flow.id] = LiqiProto() + self.bridge[flow.id] = MajsoulBridge() + + def websocket_message(self, flow: mitmproxy.http.HTTPFlow): + """ + + Called when a WebSocket message is received from the client or + + server. The most recent message will be flow.messages[-1]. The + + message is user-modifiable. Currently there are two types of + + messages, corresponding to the BINARY and TEXT frame types. + + """ + assert isinstance(flow.websocket, mitmproxy.websocket.WebSocketData) + flow_msg = flow.websocket.messages[-1] + + parse_msg = self.liqi[flow.id].parse(flow_msg) + mjai_msg = self.bridge[flow.id].input(parse_msg) + if mjai_msg is not None: + print('-'*65) + print(mjai_msg) + # composed_msg = self.bridge[flow.id].action(mjai_msg, self.liqi[flow.id]) + # if composed_msg is not None and AUTOPLAY: + # ws_composed_msg = mitmproxy.websocket.WebSocketMessage(2, True, composed_msg) + # flow.messages.append(ws_composed_msg) + # flow.inject_message(flow.server_conn, composed_msg) + # print('='*65) + if SHOW_LIQI: + print(flow_msg.content) + print(parse_msg) + print('='*65) + # if parse_msg['data']['name'] == 'ActionDiscardTile': + # print("Action is DiscardTile") + # if len(parse_msg['data']['data']['operation']['operationList'])>0: + # print(parse_msg['data']['data']['operation']['operationList']) + # print("OperationList is not empty") + # parse_msg['data']['data']['operation']['operationList'] = [ + # { + # 'type': 3, + # 'combination': [ + # ['3m|4m', '4m|6m', '6m|7m'] + # ] + # }, + # { + # 'type': 3, + # 'combination': [ + # ['0m|5m', '5m|5m'] + # ] + # }, + # { + # 'type': 5, + # 'combination': [ + # ['0m|5m|5m'] + # ] + # }, + # { + # 'type': 9 + # } + # ] + # print("Composing message...") + # composed_msg = self.liqi[flow.id].compose(parse_msg) + # flow.messages[-1].kill() + # flow.messages.append(composed_msg) + # flow.inject_message(flow.client_conn, composed_msg) + # flow.messages[-1] = composed_msg + # print('='*65) + # print(parse_msg) + # print('='*65) + # print('='*65) + # if not AUTOPLAY: + # print(mjai_msg) + # print('='*65) + + # packet = flow_msg.content + # from_client = flow_msg.from_client + # print("[" + ("Sended" if from_client else "Reveived") + + # "] from '"+flow.id+"': decode the packet here: %r…" % packet) + + def websocket_end(self, flow: mitmproxy.http.HTTPFlow): + """ + + A websocket connection has ended. + + """ + # print('[end websocket]:',flow,flow.__dict__,dir(flow)) + global activated_flows,messages_dict,activated_flows_instance + activated_flows.remove(flow.id) + activated_flows_instance.remove(flow) + messages_dict.pop(flow.id) + self.liqi.pop(flow.id) + self.bridge.pop(flow.id) + +class ClientHTTP: + def __init__(self): + pass + + def request(self, flow: mitmproxy.http.HTTPFlow): + if flow.request.method == "GET": + if re.search(r'^https://game\.maj\-soul\.(com|net)/[0-9]+/v[0-9\.]+\.w/code\.js$', flow.request.url): + print("====== GET code.js ======"*3) + print("====== GET code.js ======"*3) + print("====== GET code.js ======"*3) + flow.request.url = "http://cdn.jsdelivr.net/gh/Avenshy/majsoul_mod_plus/safe_code.js" + elif re.search(r'^https://game\.mahjongsoul\.com/v[0-9\.]+\.w/code\.js$', flow.request.url): + flow.request.url = "http://cdn.jsdelivr.net/gh/Avenshy/majsoul_mod_plus/safe_code.js" + elif re.search(r'^https://mahjongsoul\.game\.yo-star\.com/v[0-9\.]+\.w/code\.js$', flow.request.url): + flow.request.url = "http://cdn.jsdelivr.net/gh/Avenshy/majsoul_mod_plus/safe_code.js" + +async def start_proxy(host, port, enable_unlocker): + opts = options.Options(listen_host=host, listen_port=port) + + master = DumpMaster( + opts, + with_termlog=False, + with_dumper=False, + ) + master.addons.add(ClientWebSocket()) + master.addons.add(ClientHTTP()) + if enable_unlocker: + from mhm.addons import WebSocketAddon as Unlocker + master.addons.add(Unlocker()) + await master.run() + return master + +if __name__ == '__main__': + with open("settings.json", "r") as f: + settings = json.load(f) + mitm_port = settings["Port"]["MITM"] + enable_unlocker = settings["Unlocker"] + enable_helper = settings["Helper"] + + mitm_host="127.0.0.1" + + print("fetching resver...") + mhm.fetch_resver() + + with open("mhmp.json", "r") as f: + mhmp = json.load(f) + mhmp["mitmdump"]["mode"] = [f"regular@{mitm_port}"] + mhmp["hook"]["enable_aider"] = enable_helper + with open("mhmp.json", "w") as f: + json.dump(mhmp, f, indent=4) + # Create and start the proxy server thread + proxy_thread = threading.Thread(target=lambda: asyncio.run(start_proxy(mitm_host, mitm_port, enable_unlocker))) + proxy_thread.start() + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + ctx.master.shutdown() + exit(0) From 0aa81a4e045001e4736b299c600a122c12de1282 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Mon, 19 Feb 2024 12:28:26 +0800 Subject: [PATCH 03/30] Able to start aider without unlocker --- mitm.py | 14 +++++++------- simple_client.py | 17 ++++++++++------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/mitm.py b/mitm.py index 0dd060a..1dbaccb 100644 --- a/mitm.py +++ b/mitm.py @@ -9,7 +9,6 @@ import mitmproxy.log import mitmproxy.tcp import mitmproxy.websocket -import mhm from pathlib import Path from optparse import OptionParser from mitmproxy import proxy, options, ctx @@ -71,9 +70,9 @@ async def start_proxy(host, port, enable_unlocker): ) master.addons.add(ClientWebSocket()) master.addons.add(ClientHTTP()) - if enable_unlocker: - from mhm.addons import WebSocketAddon as Unlocker - master.addons.add(Unlocker()) + # if enable_unlocker: + from mhm.addons import WebSocketAddon as Unlocker + master.addons.add(Unlocker()) await master.run() return master @@ -158,15 +157,16 @@ def serve_forever(self): if opts.unlocker is not None: enable_unlocker = bool(opts.unlocker) - print("fetching resver...") - mhm.fetch_resver() - with open("mhmp.json", "r") as f: mhmp = json.load(f) mhmp["mitmdump"]["mode"] = [f"regular@{mitm_port}"] + mhmp["hook"]["enable_skins"] = enable_unlocker mhmp["hook"]["enable_aider"] = enable_helper with open("mhmp.json", "w") as f: json.dump(mhmp, f, indent=4) + import mhm + print("fetching resver...") + mhm.fetch_resver() # Create and start the proxy server thread proxy_thread = threading.Thread(target=lambda: asyncio.run(start_proxy(mitm_host, mitm_port, enable_unlocker))) proxy_thread.start() diff --git a/simple_client.py b/simple_client.py index 55d0ca5..9945205 100644 --- a/simple_client.py +++ b/simple_client.py @@ -9,7 +9,6 @@ import mitmproxy.log import mitmproxy.tcp import mitmproxy.websocket -import mhm from pathlib import Path from optparse import OptionParser from mitmproxy import proxy, options, ctx @@ -27,8 +26,6 @@ stop = False SHOW_LIQI = False -mhm.logger.setLevel("WARNING") - class ClientWebSocket: def __init__(self): self.liqi: dict[str, LiqiProto]={} @@ -171,9 +168,9 @@ async def start_proxy(host, port, enable_unlocker): ) master.addons.add(ClientWebSocket()) master.addons.add(ClientHTTP()) - if enable_unlocker: - from mhm.addons import WebSocketAddon as Unlocker - master.addons.add(Unlocker()) + # if enable_unlocker: + from mhm.addons import WebSocketAddon as Unlocker + master.addons.add(Unlocker()) await master.run() return master @@ -187,18 +184,24 @@ async def start_proxy(host, port, enable_unlocker): mitm_host="127.0.0.1" print("fetching resver...") - mhm.fetch_resver() with open("mhmp.json", "r") as f: mhmp = json.load(f) mhmp["mitmdump"]["mode"] = [f"regular@{mitm_port}"] + mhmp["hook"]["enable_skins"] = enable_unlocker mhmp["hook"]["enable_aider"] = enable_helper with open("mhmp.json", "w") as f: json.dump(mhmp, f, indent=4) + import mhm + mhm.fetch_resver() + mhm.logger.setLevel("WARNING") + # Create and start the proxy server thread proxy_thread = threading.Thread(target=lambda: asyncio.run(start_proxy(mitm_host, mitm_port, enable_unlocker))) proxy_thread.start() + + try: while True: time.sleep(1) From 39121f0b71e525a7a4fbfa84bbe9242b7d892415 Mon Sep 17 00:00:00 2001 From: jch12138 Date: Sun, 25 Feb 2024 13:58:12 +0800 Subject: [PATCH 04/30] Modify the mitm startup method to support Mac OS. --- .gitignore | 3 +- client.py | 291 +++++++++++++++++++++++++++++++++-------------------- 2 files changed, 185 insertions(+), 109 deletions(-) diff --git a/.gitignore b/.gitignore index 7e2a35c..f0f0aaf 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,5 @@ /config.json /mjai/bot/mortal.pth /mjai/bot/libriichi.* -/mjai/bot_3p \ No newline at end of file +/mjai/bot_3p +.DS_Store \ No newline at end of file diff --git a/client.py b/client.py index 91d1064..06dab58 100644 --- a/client.py +++ b/client.py @@ -1,35 +1,38 @@ import atexit +import json import os -from pathlib import Path -import time -os.environ["LOGURU_AUTOINIT"] = "False" - import pathlib +import subprocess +import sys +import time import webbrowser +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path from sys import executable -from subprocess import Popen, CREATE_NEW_CONSOLE - +from threading import Thread from typing import Any, Coroutine from xmlrpc.client import ServerProxy -import json -from loguru import logger -from textual import on +from loguru import logger +from playwright.sync_api import Playwright, sync_playwright +from textual import on from textual.app import App, ComposeResult -from textual.containers import ScrollableContainer, Horizontal, Vertical -from textual.events import Event, ScreenResume -from textual.widgets import Button, Footer, Header, Static, Log, Pretty, Label, Rule, LoadingIndicator, Checkbox, Input, Markdown +from textual.containers import Horizontal, ScrollableContainer, Vertical from textual.css.query import NoMatches +from textual.events import Event, ScreenResume from textual.screen import Screen +from textual.widgets import (Button, Checkbox, Footer, Header, Input, Label, + LoadingIndicator, Log, Markdown, Pretty, Rule, + Static) +from action import Action from liqi import LiqiProto, MsgType -from mjai.player import MjaiPlayerClient from majsoul2mjai import MajsoulBridge -from tileUnicode import TILE_2_UNICODE_ART_RICH, TILE_2_UNICODE, VERTICLE_RULE -from action import Action -from concurrent.futures import ThreadPoolExecutor -from threading import Thread -from playwright.sync_api import Playwright, sync_playwright +from mjai.player import MjaiPlayerClient +from tileUnicode import TILE_2_UNICODE, TILE_2_UNICODE_ART_RICH, VERTICLE_RULE + +os.environ["LOGURU_AUTOINIT"] = "False" + submission = 'players/bot.zip' PORT_NUM = 28680 @@ -60,12 +63,16 @@ def __init__(self, flow_id, *args, **kwargs) -> None: def compose(self) -> ComposeResult: """Called to add widgets to the app.""" - liqi_log_container = ScrollableContainer(Pretty(self.app.liqi_msg_dict[self.flow_id], id="liqi_log"), id="liqi_log_container") - mjai_log_container = ScrollableContainer(Pretty(self.app.mjai_msg_dict[self.flow_id], id="mjai_log"), id="mjai_log_container") - log_container = Horizontal(liqi_log_container, mjai_log_container, id="log_container") + liqi_log_container = ScrollableContainer(Pretty( + self.app.liqi_msg_dict[self.flow_id], id="liqi_log"), id="liqi_log_container") + mjai_log_container = ScrollableContainer(Pretty( + self.app.mjai_msg_dict[self.flow_id], id="mjai_log"), id="mjai_log_container") + log_container = Horizontal( + liqi_log_container, mjai_log_container, id="log_container") liqi_log_container.border_title = "LiqiProto" mjai_log_container.border_title = "Mjai" - tehai_labels = [Label(TILE_2_UNICODE_ART_RICH["?"], id="tehai_"+str(i)) for i in range(13)] + tehai_labels = [Label(TILE_2_UNICODE_ART_RICH["?"], + id="tehai_"+str(i)) for i in range(13)] tehai_rule = Label(VERTICLE_RULE, id="tehai_rule") tsumohai_label = Label(TILE_2_UNICODE_ART_RICH["?"], id="tsumohai") tehai_container = Horizontal(id="tehai_container") @@ -75,18 +82,25 @@ def compose(self) -> ComposeResult: tehai_container.mount(tsumohai_label) tehai_container.border_title = "Tehai" akagi_action = Button("Akagi", id="akagi_action", variant="default") - akagi_pai = Button("Pai", id="akagi_pai", variant="default") - pai_unicode_art = Label(TILE_2_UNICODE_ART_RICH["?"], id="pai_unicode_art") - akagi_container = Horizontal(akagi_action, akagi_pai, pai_unicode_art, id="akagi_container") + akagi_pai = Button("Pai", id="akagi_pai", variant="default") + pai_unicode_art = Label( + TILE_2_UNICODE_ART_RICH["?"], id="pai_unicode_art") + akagi_container = Horizontal( + akagi_action, akagi_pai, pai_unicode_art, id="akagi_container") akagi_container.border_title = "Akagi" loading_indicator = LoadingIndicator(id="loading_indicator") loading_indicator.styles.height = "3" - checkbox_autoplay = Checkbox("Autoplay", id="checkbox_autoplay", classes="short", value=AUTOPLAY) - 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_autoplay = Checkbox( + "Autoplay", id="checkbox_autoplay", classes="short", value=AUTOPLAY) + 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.border_title = "Options" - bottom_container = Horizontal(checkbox_container, akagi_container, id="bottom_container") + bottom_container = Horizontal( + checkbox_container, akagi_container, id="bottom_container") yield Header() yield Footer() yield loading_indicator @@ -105,7 +119,8 @@ def on_mount(self) -> None: self.mjai_log.update(self.app.mjai_msg_dict[self.flow_id]) self.liqi_log_container = self.query_one("#liqi_log_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_labels = [self.query_one( + "#tehai_"+str(i)) for i in range(13)] self.tehai_rule = self.query_one("#tehai_rule") self.tsumohai_label = self.query_one("#tsumohai") self.tehai_container = self.query_one("#tehai_container") @@ -118,10 +133,12 @@ def on_mount(self) -> None: self.akagi_action.label = self.app.mjai_msg_dict[self.flow_id][-1]["type"] for akagi_action_class in self.akagi_action.classes: self.akagi_action.remove_class(akagi_action_class) - self.akagi_action.add_class("action_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) + self.akagi_action.add_class( + "action_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) for akagi_pai_class in self.akagi_pai.classes: self.akagi_pai.remove_class(akagi_pai_class) - self.akagi_pai.add_class("pai_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) + self.akagi_pai.add_class( + "pai_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) except IndexError: self.akagi_action.label = "Akagi" @@ -133,14 +150,17 @@ def refresh_log(self) -> None: self.liqi_log_container.scroll_end() self.liqi_msg_idx += 1 for idx, tehai_label in enumerate(self.tehai_labels): - tehai_label.update(TILE_2_UNICODE_ART_RICH[self.app.bridge[self.flow_id].my_tehais[idx]]) - self.tsumohai_label.update(TILE_2_UNICODE_ART_RICH[self.app.bridge[self.flow_id].my_tsumohai]) + tehai_label.update( + TILE_2_UNICODE_ART_RICH[self.app.bridge[self.flow_id].my_tehais[idx]]) + self.tsumohai_label.update( + TILE_2_UNICODE_ART_RICH[self.app.bridge[self.flow_id].my_tsumohai]) liqi_msg = self.app.liqi_msg_dict[self.flow_id][-1] if liqi_msg['type'] == MsgType.Notify: if liqi_msg['method'] == '.lq.ActionPrototype': if 'operation' in liqi_msg['data']['data']: if 'operationList' in liqi_msg['data']['data']['operation']: - self.action.latest_operation_list = liqi_msg['data']['data']['operation']['operationList'] + self.action.latest_operation_list = liqi_msg[ + 'data']['data']['operation']['operationList'] if liqi_msg['data']['name'] == 'ActionDiscardTile': self.action.isNewRound = False pass @@ -149,12 +169,13 @@ def refresh_log(self) -> None: self.action.reached = False if liqi_msg['method'] == '.lq.NotifyGameEndResult' or liqi_msg['method'] == '.lq.NotifyGameTerminate': self.action_quit() - + elif self.syncing: self.query_one("#loading_indicator").remove() self.syncing = False if AUTOPLAY: - logger.log("CLICK", self.app.mjai_msg_dict[self.flow_id][-1]) + logger.log( + "CLICK", self.app.mjai_msg_dict[self.flow_id][-1]) self.app.set_timer(2, self.autoplay) if self.mjai_msg_idx < len(self.app.mjai_msg_dict[self.flow_id]): self.mjai_log.update(self.app.mjai_msg_dict[self.flow_id]) @@ -163,37 +184,48 @@ def refresh_log(self) -> None: self.akagi_action.label = self.app.mjai_msg_dict[self.flow_id][-1]["type"] for akagi_action_class in self.akagi_action.classes: self.akagi_action.remove_class(akagi_action_class) - self.akagi_action.add_class("action_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) + self.akagi_action.add_class( + "action_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) for akagi_pai_class in self.akagi_pai.classes: self.akagi_pai.remove_class(akagi_pai_class) - self.akagi_pai.add_class("pai_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) + self.akagi_pai.add_class( + "pai_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) if "consumed" in self.app.mjai_msg_dict[self.flow_id][-1]: - self.akagi_pai.label = str(self.app.mjai_msg_dict[self.flow_id][-1]["consumed"]) + self.akagi_pai.label = str( + self.app.mjai_msg_dict[self.flow_id][-1]["consumed"]) if "pai" in self.app.mjai_msg_dict[self.flow_id][-1]: - self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH[self.app.mjai_msg_dict[self.flow_id][-1]["pai"]]) - self.akagi_container.mount(Label(VERTICLE_RULE, id="consumed_rule")) + self.pai_unicode_art.update( + TILE_2_UNICODE_ART_RICH[self.app.mjai_msg_dict[self.flow_id][-1]["pai"]]) + self.akagi_container.mount( + Label(VERTICLE_RULE, id="consumed_rule")) self.consume_ids.append("#"+"consumed_rule") - i=0 + i = 0 for c in self.app.mjai_msg_dict[self.flow_id][-1]["consumed"]: - self.akagi_container.mount(Label(TILE_2_UNICODE_ART_RICH[c], id="consumed_"+c+str(i))) + self.akagi_container.mount( + Label(TILE_2_UNICODE_ART_RICH[c], id="consumed_"+c+str(i))) self.consume_ids.append("#"+"consumed_"+c+str(i)) - i+=1 + i += 1 elif "pai" in self.app.mjai_msg_dict[self.flow_id][-1]: for consume_id in self.consume_ids: self.query_one(consume_id).remove() self.consume_ids = [] - self.akagi_pai.label = str(self.app.mjai_msg_dict[self.flow_id][-1]["pai"]) - self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH[self.app.mjai_msg_dict[self.flow_id][-1]["pai"]]) + self.akagi_pai.label = str( + self.app.mjai_msg_dict[self.flow_id][-1]["pai"]) + self.pai_unicode_art.update( + TILE_2_UNICODE_ART_RICH[self.app.mjai_msg_dict[self.flow_id][-1]["pai"]]) else: self.akagi_pai.label = "None" self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH["?"]) # Action - logger.info(f"Current tehai: {self.app.bridge[self.flow_id].my_tehais}") - logger.info(f"Current tsumohai: {self.app.bridge[self.flow_id].my_tsumohai}") + logger.info( + f"Current tehai: {self.app.bridge[self.flow_id].my_tehais}") + logger.info( + f"Current tsumohai: {self.app.bridge[self.flow_id].my_tsumohai}") if not self.syncing and ENABLE_PLAYWRIGHT and AUTOPLAY: - logger.log("CLICK", self.app.mjai_msg_dict[self.flow_id][-1]) + logger.log( + "CLICK", self.app.mjai_msg_dict[self.flow_id][-1]) self.app.set_timer(0.05, self.autoplay) - + except Exception as e: logger.error(e) pass @@ -203,9 +235,10 @@ def checkbox_autoplay_changed(self, event: Checkbox.Changed) -> None: global AUTOPLAY AUTOPLAY = event.value pass - + def autoplay(self) -> None: - self.action.mjai2action(self.app.mjai_msg_dict[self.flow_id][-1], self.app.bridge[self.flow_id].my_tehais, self.app.bridge[self.flow_id].my_tsumohai) + self.action.mjai2action( + self.app.mjai_msg_dict[self.flow_id][-1], self.app.bridge[self.flow_id].my_tehais, self.app.bridge[self.flow_id].my_tsumohai) pass def action_quit(self) -> None: @@ -227,6 +260,7 @@ def on_button_pressed(self, event: Button.Pressed) -> None: self.app.push_screen(FlowScreen(self.flow_id)) self.app.update_flow.pause() + class HoverLink(Static): def __init__(self, text, url, *args, **kwargs) -> None: super().__init__(*args, **kwargs) @@ -240,6 +274,7 @@ def on_click(self, event): webbrowser.open_new_tab(self.url) pass + class SettingsScreen(Static): def __init__(self, *args, **kwargs) -> None: @@ -259,60 +294,91 @@ def __init__(self, *args, **kwargs) -> None: self.value_playwright_setting_height_input = settings["Playwright"]["height"] def compose(self) -> ComposeResult: - self.port_setting_mitm_label = Label("MITM Port", id="port_setting_mitm_label") - self.port_setting_mitm_input = Input(placeholder="Port", type="integer", id="port_setting_mitm_input", value=str(self.value_port_setting_mitm_input)) - self.port_setting_mitm_container = Horizontal(self.port_setting_mitm_label, self.port_setting_mitm_input, id="port_setting_mitm_container") - self.port_setting_xmlrpc_label = Label("XMLRPC Port", id="port_setting_xmlrpc_label") - self.port_setting_xmlrpc_input = Input(placeholder="Port", type="integer", id="port_setting_xmlrpc_input", value=str(self.value_port_setting_xmlrpc_input)) - self.port_setting_xmlrpc_container = Horizontal(self.port_setting_xmlrpc_label, self.port_setting_xmlrpc_input, id="port_setting_xmlrpc_container") - self.port_setting_container = Vertical(self.port_setting_mitm_container, self.port_setting_xmlrpc_container, id="port_setting_container") + self.port_setting_mitm_label = Label( + "MITM Port", id="port_setting_mitm_label") + self.port_setting_mitm_input = Input( + placeholder="Port", type="integer", id="port_setting_mitm_input", value=str(self.value_port_setting_mitm_input)) + self.port_setting_mitm_container = Horizontal( + self.port_setting_mitm_label, self.port_setting_mitm_input, id="port_setting_mitm_container") + self.port_setting_xmlrpc_label = Label( + "XMLRPC Port", id="port_setting_xmlrpc_label") + self.port_setting_xmlrpc_input = Input( + placeholder="Port", type="integer", id="port_setting_xmlrpc_input", value=str(self.value_port_setting_xmlrpc_input)) + self.port_setting_xmlrpc_container = Horizontal( + self.port_setting_xmlrpc_label, self.port_setting_xmlrpc_input, id="port_setting_xmlrpc_container") + self.port_setting_container = Vertical( + self.port_setting_mitm_container, self.port_setting_xmlrpc_container, id="port_setting_container") self.port_setting_container.border_title = "Port" - self.unlocker_setting_label = Label("Unlocker", id="unlocker_setting_label") - self.unlocker_setting_enable_checkbox = Checkbox("Enable", id="unlocker_setting_enable_checkbox", classes="short", value=self.value_unlocker_setting_enable_checkbox) - self.unlocker_setting_v10_checkbox = Checkbox("v10", id="unlocker_setting_v10_checkbox", classes="short", value=self.value_unlocker_setting_v10_checkbox) - self.unlocker_setting_container = Horizontal(self.unlocker_setting_label, self.unlocker_setting_enable_checkbox, self.unlocker_setting_v10_checkbox, id="unlocker_setting_container") + self.unlocker_setting_label = Label( + "Unlocker", id="unlocker_setting_label") + self.unlocker_setting_enable_checkbox = Checkbox( + "Enable", id="unlocker_setting_enable_checkbox", classes="short", value=self.value_unlocker_setting_enable_checkbox) + self.unlocker_setting_v10_checkbox = Checkbox( + "v10", id="unlocker_setting_v10_checkbox", classes="short", value=self.value_unlocker_setting_v10_checkbox) + self.unlocker_setting_container = Horizontal( + self.unlocker_setting_label, self.unlocker_setting_enable_checkbox, self.unlocker_setting_v10_checkbox, id="unlocker_setting_container") self.unlocker_setting_container.border_title = "Unlocker" self.helper_setting_label = Label("Helper", id="helper_setting_label") - self.helper_setting_checkbox = Checkbox("Enable", id="helper_setting_checkbox", classes="short", value=self.value_helper_setting_checkbox) - self.helper_setting_container = Horizontal(self.helper_setting_label, self.helper_setting_checkbox, id="helper_setting_container") + self.helper_setting_checkbox = Checkbox( + "Enable", id="helper_setting_checkbox", classes="short", value=self.value_helper_setting_checkbox) + self.helper_setting_container = Horizontal( + self.helper_setting_label, self.helper_setting_checkbox, id="helper_setting_container") self.helper_setting_container.border_title = "Helper" - self.autoplay_setting_enable_label = Label("Enable", id="autoplay_setting_enable_label") - self.autoplay_setting_enable_checkbox = Checkbox("Enable", id="autoplay_setting_enable_checkbox", classes="short", value=self.value_autoplay_setting_enable_checkbox) - self.autoplay_setting_enable_container = Horizontal(self.autoplay_setting_enable_label, self.autoplay_setting_enable_checkbox, id="autoplay_setting_enable_container") - self.autoplay_setting_random_time_label = Label("Random Time", id="autoplay_setting_random_time_label") - self.autoplay_setting_random_time_min_input = Input(placeholder="Min", type="number", id="autoplay_setting_random_time_min_input") - self.autoplay_setting_random_time_max_input = Input(placeholder="Max", type="number", id="autoplay_setting_random_time_max_input") - self.autoplay_setting_random_time_container = Horizontal(self.autoplay_setting_random_time_label, self.autoplay_setting_random_time_min_input, self.autoplay_setting_random_time_max_input, id="autoplay_setting_random_time_container") - self.autoplay_setting_container = Vertical(self.autoplay_setting_enable_container, self.autoplay_setting_random_time_container, id="autoplay_setting_container") + self.autoplay_setting_enable_label = Label( + "Enable", id="autoplay_setting_enable_label") + self.autoplay_setting_enable_checkbox = Checkbox( + "Enable", id="autoplay_setting_enable_checkbox", classes="short", value=self.value_autoplay_setting_enable_checkbox) + self.autoplay_setting_enable_container = Horizontal( + self.autoplay_setting_enable_label, self.autoplay_setting_enable_checkbox, id="autoplay_setting_enable_container") + self.autoplay_setting_random_time_label = Label( + "Random Time", id="autoplay_setting_random_time_label") + self.autoplay_setting_random_time_min_input = Input( + placeholder="Min", type="number", id="autoplay_setting_random_time_min_input") + self.autoplay_setting_random_time_max_input = Input( + placeholder="Max", type="number", id="autoplay_setting_random_time_max_input") + self.autoplay_setting_random_time_container = Horizontal( + self.autoplay_setting_random_time_label, self.autoplay_setting_random_time_min_input, self.autoplay_setting_random_time_max_input, id="autoplay_setting_random_time_container") + self.autoplay_setting_container = Vertical( + self.autoplay_setting_enable_container, self.autoplay_setting_random_time_container, id="autoplay_setting_container") self.autoplay_setting_container.border_title = "Autoplay" - self.playwright_setting_enable_label = Label("Enable", id="playwright_setting_enable_label") - self.playwright_setting_enable_checkbox = Checkbox("Enable", id="playwright_setting_enable_checkbox", classes="short", value=self.value_playwright_setting_enable_checkbox) - self.playwright_setting_enable_container = Horizontal(self.playwright_setting_enable_label, self.playwright_setting_enable_checkbox, id="playwright_setting_enable_container") - self.playwright_setting_resolution_label = Label("Resolution", id="playwright_setting_resolution_label") - self.playwright_setting_width_input = Input(placeholder="Width", type="integer", id="playwright_setting_width_input", value=str(self.value_playwright_setting_width_input)) - self.playwright_setting_height_input = Input(placeholder="Height", type="integer", id="playwright_setting_height_input", value=str(self.value_playwright_setting_height_input)) - self.playwright_setting_resolution_container = Horizontal(self.playwright_setting_resolution_label, self.playwright_setting_width_input, self.playwright_setting_height_input, id="playwright_setting_resolution_container") - self.playwright_setting_container = Vertical(self.playwright_setting_enable_container, self.playwright_setting_resolution_container, id="playwright_setting_container") + self.playwright_setting_enable_label = Label( + "Enable", id="playwright_setting_enable_label") + self.playwright_setting_enable_checkbox = Checkbox( + "Enable", id="playwright_setting_enable_checkbox", classes="short", value=self.value_playwright_setting_enable_checkbox) + self.playwright_setting_enable_container = Horizontal( + self.playwright_setting_enable_label, self.playwright_setting_enable_checkbox, id="playwright_setting_enable_container") + self.playwright_setting_resolution_label = Label( + "Resolution", id="playwright_setting_resolution_label") + self.playwright_setting_width_input = Input( + placeholder="Width", type="integer", id="playwright_setting_width_input", value=str(self.value_playwright_setting_width_input)) + self.playwright_setting_height_input = Input( + placeholder="Height", type="integer", id="playwright_setting_height_input", value=str(self.value_playwright_setting_height_input)) + self.playwright_setting_resolution_container = Horizontal( + self.playwright_setting_resolution_label, self.playwright_setting_width_input, self.playwright_setting_height_input, id="playwright_setting_resolution_container") + self.playwright_setting_container = Vertical( + self.playwright_setting_enable_container, self.playwright_setting_resolution_container, id="playwright_setting_container") self.playwright_setting_container.border_title = "Playwright" - self.setting_save_button = Button("Save", variant="warning", id="setting_save_button") + self.setting_save_button = Button( + "Save", variant="warning", id="setting_save_button") - self.remove_this_then_you_badluck_for_100years_and_get_hit_by_a_car_then_die = HoverLink("Akagi is Free and Open Sourced on GitHub.\n本程式Akagi在GitHub上完全開源且免費。如果你是付費取得的,你已經被賣家欺騙,請立即舉報、差評、退款。", "https://github.com/shinkuan/Akagi", id="remove_this_you_die") + self.remove_this_then_you_badluck_for_100years_and_get_hit_by_a_car_then_die = HoverLink( + "Akagi is Free and Open Sourced on GitHub.\n本程式Akagi在GitHub上完全開源且免費。如果你是付費取得的,你已經被賣家欺騙,請立即舉報、差評、退款。", "https://github.com/shinkuan/Akagi", id="remove_this_you_die") self.setting_container = ScrollableContainer( - self.port_setting_container, - self.unlocker_setting_container, - self.helper_setting_container, - self.autoplay_setting_container, - self.playwright_setting_container, - self.setting_save_button, - self.remove_this_then_you_badluck_for_100years_and_get_hit_by_a_car_then_die, - id="setting_container" - ) + self.port_setting_container, + self.unlocker_setting_container, + self.helper_setting_container, + self.autoplay_setting_container, + self.playwright_setting_container, + self.setting_save_button, + self.remove_this_then_you_badluck_for_100years_and_get_hit_by_a_car_then_die, + id="setting_container" + ) yield self.setting_container @@ -393,14 +459,14 @@ class Akagi(App): def __init__(self, rpc_server, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.rpc_server = rpc_server - self.liqi: dict[str, LiqiProto]={} - self.bridge: dict[str, MajsoulBridge]={} + self.liqi: dict[str, LiqiProto] = {} + self.bridge: dict[str, MajsoulBridge] = {} self.active_flows = [] - self.messages_dict = dict() # flow.id -> List[flow_msg] - self.liqi_msg_dict = dict() # flow.id -> List[liqi_msg] - self.mjai_msg_dict = dict() # flow.id -> List[mjai_msg] - self.akagi_log_dict= dict() # flow.id -> List[akagi_log] - self.loguru_log = [] # List[loguru_log] + self.messages_dict = dict() # flow.id -> List[flow_msg] + self.liqi_msg_dict = dict() # flow.id -> List[liqi_msg] + self.mjai_msg_dict = dict() # flow.id -> List[mjai_msg] + self.akagi_log_dict = dict() # flow.id -> List[akagi_log] + self.loguru_log = [] # List[loguru_log] self.mitm_started = False def on_mount(self) -> None: @@ -430,7 +496,8 @@ def refresh_flow(self) -> None: try: self.query_one(f"#flow_{flow_id}") except NoMatches: - self.query_one("#FlowContainer").mount(FlowDisplay(flow_id, id=f"flow_{flow_id}")) + self.query_one("#FlowContainer").mount( + FlowDisplay(flow_id, id=f"flow_{flow_id}")) self.active_flows.append(flow_id) self.messages_dict[flow_id] = [] self.liqi_msg_dict[flow_id] = [] @@ -488,7 +555,8 @@ def mitm_connected(self): def my_sink(self, message) -> None: record = message.record - self.loguru_log.append(f"{record['time'].strftime('%H:%M:%S')} | {record['level'].name}\t | {record['message']}") + self.loguru_log.append( + f"{record['time'].strftime('%H:%M:%S')} | {record['level'].name}\t | {record['message']}") def action_quit(self) -> None: self.update_flow.stop() @@ -504,15 +572,23 @@ def exit_handler(): pass pass + def start_mitm(): - global mitm_exec - mitm_exec = Popen([executable, pathlib.Path(__file__).parent / "mitm.py"], creationflags=CREATE_NEW_CONSOLE) - pass + command = [sys.executable, pathlib.Path(__file__).parent / "mitm.py"] + + if sys.platform == "win32": + # Windows特定代码 + mitm_exec = subprocess.Popen( + command, creationflags=subprocess.CREATE_NEW_CONSOLE) + else: + # macOS和其他Unix-like系统 + mitm_exec = subprocess.Popen(command, preexec_fn=os.setsid) + if __name__ == '__main__': with open("settings.json", "r") as f: settings = json.load(f) - rpc_port = settings["Port"]["XMLRPC"] + rpc_port = settings["Port"]["XMLRPC"] rpc_host = "127.0.0.1" s = ServerProxy(f"http://{rpc_host}:{rpc_port}", allow_none=True) logger.level("CLICK", no=10, icon="CLICK") @@ -526,4 +602,3 @@ def start_mitm(): except Exception as e: exit_handler() raise e - From dd0543c5e88c0ce18cd67306a2e3c699975eb2e4 Mon Sep 17 00:00:00 2001 From: jch12138 Date: Sun, 25 Feb 2024 15:02:29 +0800 Subject: [PATCH 05/30] Add startup script under mac --- client.py | 3 +++ run_akagi.sh | 2 ++ 2 files changed, 5 insertions(+) create mode 100755 run_akagi.sh diff --git a/client.py b/client.py index 06dab58..4043ae0 100644 --- a/client.py +++ b/client.py @@ -568,12 +568,15 @@ def exit_handler(): global mitm_exec try: mitm_exec.kill() + logger.info("Stop Akagi") except: pass pass def start_mitm(): + global mitm_exec + command = [sys.executable, pathlib.Path(__file__).parent / "mitm.py"] if sys.platform == "win32": diff --git a/run_akagi.sh b/run_akagi.sh new file mode 100755 index 0000000..61a46e3 --- /dev/null +++ b/run_akagi.sh @@ -0,0 +1,2 @@ +source venv/bin/activate +python client.py From 33f6b68f9dcc636038dcf4b52171d12134d69ff7 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Sun, 25 Feb 2024 15:28:13 +0800 Subject: [PATCH 06/30] Show value of each choice. New way of getting tehai. --- .gitignore | 1 + client.py | 52 ++++++++++--------- libriichi_helper.py | 121 ++++++++++++++++++++++++++++++++++++++++++++ majsoul2mjai.py | 51 ------------------- mjai/bot/bot.py | 4 +- requirement.txt | 2 +- 6 files changed, 152 insertions(+), 79 deletions(-) create mode 100644 libriichi_helper.py diff --git a/.gitignore b/.gitignore index 7e2a35c..136085e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ /mitm_playwright.bat /playwright_test.py /unlocker_test.py +/unlocker_passthrough.py /objects /players/bot /players/docker diff --git a/client.py b/client.py index 056240c..76c3dfa 100644 --- a/client.py +++ b/client.py @@ -23,13 +23,10 @@ from textual.screen import Screen from liqi import LiqiProto, MsgType -from mjai.player import MjaiPlayerClient from majsoul2mjai import MajsoulBridge +from libriichi_helper import meta_to_recommend, state_to_tehai from tileUnicode import TILE_2_UNICODE_ART_RICH, TILE_2_UNICODE, VERTICLE_RULE from action import Action -from concurrent.futures import ThreadPoolExecutor -from threading import Thread -from playwright.sync_api import Playwright, sync_playwright submission = 'players/bot.zip' PORT_NUM = 28680 @@ -132,9 +129,6 @@ def refresh_log(self) -> None: self.liqi_log.update(self.app.liqi_msg_dict[self.flow_id][-1]) self.liqi_log_container.scroll_end() self.liqi_msg_idx += 1 - for idx, tehai_label in enumerate(self.tehai_labels): - tehai_label.update(TILE_2_UNICODE_ART_RICH[self.app.bridge[self.flow_id].my_tehais[idx]]) - self.tsumohai_label.update(TILE_2_UNICODE_ART_RICH[self.app.bridge[self.flow_id].my_tsumohai]) liqi_msg = self.app.liqi_msg_dict[self.flow_id][-1] if liqi_msg['type'] == MsgType.Notify: if liqi_msg['method'] == '.lq.ActionPrototype': @@ -153,46 +147,54 @@ def refresh_log(self) -> None: elif self.syncing: self.query_one("#loading_indicator").remove() self.syncing = False - if AUTOPLAY: + if AUTOPLAY and len(self.app.mjai_msg_dict[self.flow_id]) > 0: logger.log("CLICK", self.app.mjai_msg_dict[self.flow_id][-1]) self.app.set_timer(2, self.autoplay) if self.mjai_msg_idx < len(self.app.mjai_msg_dict[self.flow_id]): + player_state = self.app.bridge[self.flow_id].mjai_client.bot.state() + 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]]) + self.tsumohai_label.update(TILE_2_UNICODE_ART_RICH[tsumohai]) + latest_mjai_msg = self.app.mjai_msg_dict[self.flow_id][-1] + self.app.mjai_msg_dict[self.flow_id][-1]['meta'] = meta_to_recommend(latest_mjai_msg['meta']) self.mjai_log.update(self.app.mjai_msg_dict[self.flow_id]) self.mjai_log_container.scroll_end() self.mjai_msg_idx += 1 - self.akagi_action.label = self.app.mjai_msg_dict[self.flow_id][-1]["type"] + self.akagi_action.label = latest_mjai_msg["type"] for akagi_action_class in self.akagi_action.classes: self.akagi_action.remove_class(akagi_action_class) - self.akagi_action.add_class("action_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) + self.akagi_action.add_class("action_"+latest_mjai_msg["type"]) for akagi_pai_class in self.akagi_pai.classes: self.akagi_pai.remove_class(akagi_pai_class) - self.akagi_pai.add_class("pai_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) - if "consumed" in self.app.mjai_msg_dict[self.flow_id][-1]: - self.akagi_pai.label = str(self.app.mjai_msg_dict[self.flow_id][-1]["consumed"]) - if "pai" in self.app.mjai_msg_dict[self.flow_id][-1]: - self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH[self.app.mjai_msg_dict[self.flow_id][-1]["pai"]]) + self.akagi_pai.add_class("pai_"+latest_mjai_msg["type"]) + if "consumed" in latest_mjai_msg: + self.akagi_pai.label = str(latest_mjai_msg["consumed"]) + if "pai" in latest_mjai_msg: + self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH[latest_mjai_msg["pai"]]) self.akagi_container.mount(Label(VERTICLE_RULE, id="consumed_rule")) self.consume_ids.append("#"+"consumed_rule") i=0 - for c in self.app.mjai_msg_dict[self.flow_id][-1]["consumed"]: + for c in latest_mjai_msg["consumed"]: self.akagi_container.mount(Label(TILE_2_UNICODE_ART_RICH[c], id="consumed_"+c+str(i))) self.consume_ids.append("#"+"consumed_"+c+str(i)) i+=1 - elif "pai" in self.app.mjai_msg_dict[self.flow_id][-1]: + elif "pai" in latest_mjai_msg: for consume_id in self.consume_ids: self.query_one(consume_id).remove() self.consume_ids = [] - self.akagi_pai.label = str(self.app.mjai_msg_dict[self.flow_id][-1]["pai"]) - self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH[self.app.mjai_msg_dict[self.flow_id][-1]["pai"]]) + self.akagi_pai.label = str(latest_mjai_msg["pai"]) + self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH[latest_mjai_msg["pai"]]) else: self.akagi_pai.label = "None" self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH["?"]) # Action - logger.info(f"Current tehai: {self.app.bridge[self.flow_id].my_tehais}") - logger.info(f"Current tsumohai: {self.app.bridge[self.flow_id].my_tsumohai}") + logger.info(f"Current tehai: {tehai}") + logger.info(f"Current tsumohai: {tsumohai}") if not self.syncing and ENABLE_PLAYWRIGHT and AUTOPLAY: - logger.log("CLICK", self.app.mjai_msg_dict[self.flow_id][-1]) - self.app.set_timer(0.05, self.autoplay) + logger.log("CLICK", latest_mjai_msg) + # self.app.set_timer(0.05, self.autoplay) + self.autoplay(tehai, tsumohai) except Exception as e: logger.error(e) @@ -204,8 +206,8 @@ def checkbox_autoplay_changed(self, event: Checkbox.Changed) -> None: AUTOPLAY = event.value pass - def autoplay(self) -> None: - self.action.mjai2action(self.app.mjai_msg_dict[self.flow_id][-1], self.app.bridge[self.flow_id].my_tehais, self.app.bridge[self.flow_id].my_tsumohai) + def autoplay(self, tehai, tsumohai) -> None: + self.action.mjai2action(self.app.mjai_msg_dict[self.flow_id][-1], tehai, tsumohai) pass def action_quit(self) -> None: diff --git a/libriichi_helper.py b/libriichi_helper.py new file mode 100644 index 0000000..5dc25d3 --- /dev/null +++ b/libriichi_helper.py @@ -0,0 +1,121 @@ +import numpy as np + +def meta_to_recommend(meta: dict) -> dict: + # """ + # { + # "q_values":[ + # -9.09196, + # -9.46696, + # -8.365397, + # -8.849772, + # -9.43571, + # -10.06071, + # -9.295085, + # -0.73649096, + # -9.27946, + # -9.357585, + # 0.3221028, + # -2.7794597 + # ], + # "mask_bits":2697207348, + # "is_greedy":true, + # "eval_time_ns":357088300 + # } + # """ + + recommend = [] + + mask_unicode = ['🀇', '🀈', '🀉', '🀊', '🀋', '🀌', '🀍', '🀎', '🀏', '🀙', '🀚', '🀛', '🀜', '🀝', '🀞', '🀟', '🀠', '🀡', '🀐', '🀑', '🀒', '🀓', '🀔', '🀕', '🀖', '🀗', '🀘', '🀀', '🀁', '🀂', '🀃', '🀆', '🀅', '🀄', '🀋', '🀝', '🀔', '立直', '吃(下)', '吃(中)', '吃(上)', '碰', '槓(選擇)', '和', '流局', '跳過'] + + def mask_bits_to_binary_string(mask_bits): + binary_string = bin(mask_bits)[2:] + binary_string = binary_string.zfill(46) + return binary_string + + def mask_bits_to_bool_list(mask_bits): + binary_string = mask_bits_to_binary_string(mask_bits) + bool_list = [] + for bit in binary_string[::-1]: + bool_list.append(bit == '1') + return bool_list + + def eq(l, r): + # Check for approximate equality using numpy's floating-point epsilon + return np.abs(l - r) <= np.finfo(float).eps + + def softmax(arr, temperature=1.0): + arr = np.array(arr, dtype=float) # Ensure the input is a numpy array of floats + + if arr.size == 0: + return arr # Return the empty array if input is empty + + if not eq(temperature, 1.0): + arr /= temperature # Scale by temperature if temperature is not approximately 1 + + # Shift values by max for numerical stability + max_val = np.max(arr) + arr = arr - max_val + + # Apply the softmax transformation + exp_arr = np.exp(arr) + sum_exp = np.sum(exp_arr) + + softmax_arr = exp_arr / sum_exp + + return softmax_arr + + def scale_list(list): + scaled_list = softmax(list) + return scaled_list + q_values = meta['q_values'] + mask_bits = meta['mask_bits'] + mask = mask_bits_to_bool_list(mask_bits) + scaled_q_values = scale_list(q_values) + q_value_idx = 0 + + true_count = 0 + for i in range(46): + if mask[i]: + true_count += 1 + + for i in range(46): + if mask[i]: + recommend.append((mask_unicode[i], scaled_q_values[q_value_idx])) + q_value_idx += 1 + + recommend = sorted(recommend, key=lambda x: x[1], reverse=True) + return recommend + +def state_to_tehai(state) -> tuple[list[str], str]: + tehai34 = state.tehai # with tsumohai, no aka marked + akas = state.akas_in_hand + tsumohai = state.last_self_tsumo() + return _state_to_tehai(tehai34, akas, tsumohai) + +def _state_to_tehai(tile34: int, aka: list[bool], tsumohai: str|None) -> tuple[list[str], str]: + pai_str = [ + "1m", "2m", "3m", "4m", "5m", "6m", "7m", "8m", "9m", + "1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p", + "1s", "2s", "3s", "4s", "5s", "6s", "7s", "8s", "9s", + "E", "S", "W", "N", "P", "F", "C", "?" + ] + aka_str = [ + "5mr", "5pr", "5sr" + ] + tile_list = [] + for tile_id, tile_count in enumerate(tile34): + for _ in range(tile_count): + tile_list.append(pai_str[tile_id]) + for idx, aka in enumerate(aka): + if aka: + tile_list[tile_list.index("5" + ["m", "p", "s"][idx])] = aka_str[idx] + if len(tile_list)%3 == 2 and tsumohai is not None: + tile_list.remove(tsumohai) + else: + tsumohai = "?" + len_tile_list = len(tile_list) + if len_tile_list < 13: + tile_list += ["?"]*(13-len_tile_list) + + return (tile_list, tsumohai) + \ No newline at end of file diff --git a/majsoul2mjai.py b/majsoul2mjai.py index b1e7c8f..25020fb 100644 --- a/majsoul2mjai.py +++ b/majsoul2mjai.py @@ -106,9 +106,6 @@ def input(self, parse_msg: dict) -> dict | None: my_tehais = ['?']*13 for hai in range(13): my_tehais[hai] = MS_TILE_2_MJAI_TILE[parse_msg['data']['data']['tiles'][hai]] - self.my_tehais = my_tehais - self.my_tsumohai = "?" - self.my_tehais = sorted(self.my_tehais, key=cmp_to_key(compare_pai)) if len(parse_msg['data']['data']['tiles']) == 13: tehais[self.seat] = my_tehais self.mjai_message.append( @@ -210,14 +207,6 @@ def input(self, parse_msg: dict) -> dict | None: 'type': 'reach_accepted', 'actor': actor } - if actor == self.seat: - if self.my_tsumohai != "?": - self.my_tehais.append(self.my_tsumohai) - self.my_tsumohai = "?" - else: - self.my_tehais.append("?") - self.my_tehais.remove(pai) - self.my_tehais = sorted(self.my_tehais, key=cmp_to_key(compare_pai)) # Reach if parse_msg['data']['name'] == 'ActionReach': # TODO @@ -275,11 +264,6 @@ def input(self, parse_msg: dict) -> dict | None: pass case _: raise - if actor == self.seat: - for pai in consumed: - self.my_tehais.remove(pai) - self.my_tehais.append("?") - self.my_tehais = sorted(self.my_tehais, key=cmp_to_key(compare_pai)) # AnkanKakan if parse_msg['data']['name'] == 'ActionAnGangAddGang': actor = parse_msg['data']['data']['seat'] @@ -296,17 +280,6 @@ def input(self, parse_msg: dict) -> dict | None: 'consumed': consumed } ) - if actor == self.seat: - if self.my_tsumohai != "?": - self.my_tehais.append(self.my_tsumohai) - self.my_tsumohai = "?" - else: - self.my_tehais.append("?") - for pai in consumed: - self.my_tehais.remove(pai) - self.my_tehais.append("?") - self.my_tehais.remove("?") - self.my_tehais = sorted(self.my_tehais, key=cmp_to_key(compare_pai)) case OperationAnGangAddGang.AddGang: pai = MS_TILE_2_MJAI_TILE[parse_msg['data']['data']['tiles']] consumed = [pai.replace("r", "")] * 3 @@ -320,14 +293,6 @@ def input(self, parse_msg: dict) -> dict | None: 'consumed': consumed } ) - if actor == self.seat: - if self.my_tsumohai != "?": - self.my_tehais.append(self.my_tsumohai) - self.my_tsumohai = "?" - else: - self.my_tehais.append("?") - self.my_tehais.remove(pai) - self.my_tehais = sorted(self.my_tehais, key=cmp_to_key(compare_pai)) if parse_msg['data']['name'] == 'ActionBaBei': actor = parse_msg['data']['data']['seat'] @@ -338,14 +303,6 @@ def input(self, parse_msg: dict) -> dict | None: 'pai': 'N' } ) - if actor == self.seat: - if self.my_tsumohai != "?": - self.my_tehais.append(self.my_tsumohai) - self.my_tsumohai = "?" - else: - self.my_tehais.append("?") - self.my_tehais.remove("N") - self.my_tehais = sorted(self.my_tehais, key=cmp_to_key(compare_pai)) # hora if parse_msg['data']['name'] == 'ActionHule': @@ -368,8 +325,6 @@ def input(self, parse_msg: dict) -> dict | None: 'type': 'end_kyoku' } ) - self.my_tehais = ["?"]*13 - self.my_tsumohai = "?" self.react(self.mjai_client) return None # notile @@ -380,8 +335,6 @@ def input(self, parse_msg: dict) -> dict | None: 'type': 'end_kyoku' } ) - self.my_tehais = ["?"]*13 - self.my_tsumohai = "?" self.react(self.mjai_client) return None # ryukyoku @@ -397,8 +350,6 @@ def input(self, parse_msg: dict) -> dict | None: 'type': 'end_kyoku' } ) - self.my_tehais = ["?"]*13 - self.my_tsumohai = "?" self.react(self.mjai_client) return None @@ -413,8 +364,6 @@ def input(self, parse_msg: dict) -> dict | None: 'type': 'end_game' } ) - self.my_tehais = ["?"]*13 - self.my_tsumohai = "?" self.react(self.mjai_client) self.mjai_client.restart_bot(self.seat) return None diff --git a/mjai/bot/bot.py b/mjai/bot/bot.py index faaa9a9..5a5a25c 100644 --- a/mjai/bot/bot.py +++ b/mjai/bot/bot.py @@ -23,9 +23,10 @@ def react(self, events: str) -> str: return json.dumps({"type":"none"}, separators=(",", ":")) else: raw_data = json.loads(return_action) - del raw_data["meta"] return json.dumps(raw_data, separators=(",", ":")) + def state(self): + return self.model.state def main(): player_id = int(sys.argv[1]) @@ -40,5 +41,4 @@ def main(): if __name__ == "__main__": - # debug() main() \ No newline at end of file diff --git a/requirement.txt b/requirement.txt index 282a6b2..af636d5 100644 --- a/requirement.txt +++ b/requirement.txt @@ -9,4 +9,4 @@ textual==0.46.0 playwright==1.41.0 torch>=2.2.0 --find-links https://github.com/shinkuan/Akagi/releases/expanded_assets/v0.1.0-libriichi -riichi \ No newline at end of file +riichi>=0.1.1 \ No newline at end of file From e9871c53726a12e6233f4a7acb61d39f48d78a14 Mon Sep 17 00:00:00 2001 From: jch12138 Date: Sun, 25 Feb 2024 16:26:36 +0800 Subject: [PATCH 07/30] Add installation instructions for Mac. --- README.md | 14 +++++- README_CH.md | 128 +++++++++++++++++++++++++++++---------------------- 2 files changed, 86 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index f48b143..e3d70fc 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ __Get mortal.pth at [Discord](https://discord.gg/Z2wjXUK8bN)__ 5. And mortal.pth is there. ### Akagi: - +#### Windows Download `install_akagi.ps1` at [Release](https://github.com/shinkuan/Akagi/releases/latest) 1. Put `install_akagi.ps1` at the location you want to install Akagi. @@ -66,6 +66,18 @@ Download `install_akagi.ps1` at [Release](https://github.com/shinkuan/Akagi/rele 9. Install the certificate. 10. Put `mortal.pth` into `./Akagi/mjai/bot` +#### Mac +Download `install_akagi.command` from [Release](https://github.com/shinkuan/Akagi/releases/latest) + +1. Put `install_akagi.command` in the location where you want to install Akagi. +2. Download the latest Python installation package from the [Python official website](https://www.python.org/downloads/) and install it (skip this step if you already have a compatible version of Python installed). +3. Double-click `install_akagi.command` to automatically install the required dependencies. +4. If this is your first time using mitmproxy, open it. +5. Close it. +6. Go to your user home directory `~/.mitmproxy` +7. Install the certificate. +8. Put `mortal.pth` into `./Akagi/mjai/bot` + ### settings.json - `Unlocker`: Decide to use [MajsoulUnlocker](https://github.com/shinkuan/MajsoulUnlocker) or not. diff --git a/README_CH.md b/README_CH.md index 555ea45..1699f1b 100644 --- a/README_CH.md +++ b/README_CH.md @@ -35,50 +35,67 @@ https://github.com/shinkuan/RandomStuff/assets/35415788/ce1b598d-b1d7-49fe-a175- ### 安裝 -[點我到Youtube觀看安裝影片](https://youtu.be/V7NMNsZ3Ut8) +[點我到 Youtube 觀看安裝影片](https://youtu.be/V7NMNsZ3Ut8) 在開始前,你需要以下東西: -1. `mortal.pth` [(如果你沒有的話,到Discord去下載)](https://discord.gg/Z2wjXUK8bN) -2. (Optional) 推薦使用Windows Terminal,以獲得預期中的UI效果。 -3. (Optional) 如果你要使用Steam或Majsoul Plus之類的,請使用類似Proxifier的軟體將連線導向至MITM -__到[Discord](https://discord.gg/Z2wjXUK8bN)下載我提供的mortal.pth__ +1. `mortal.pth` [(如果你沒有的話,到 Discord 去下載)](https://discord.gg/Z2wjXUK8bN) +2. (Optional) 推薦使用 Windows Terminal,以獲得預期中的 UI 效果。 +3. (Optional) 如果你要使用 Steam 或 Majsoul Plus 之類的,請使用類似 Proxifier 的軟體將連線導向至 MITM + +**到[Discord](https://discord.gg/Z2wjXUK8bN)下載我提供的 mortal.pth** + 1. 到 #verify 頻道點擊 ✅ 驗證身分. 2. 到 #bot-zip -3. 選一個你喜歡的bot下載 +3. 選一個你喜歡的 bot 下載 4. 解壓縮 ### Akagi: -到[Release](https://github.com/shinkuan/Akagi/releases/latest)下載`install_akagi.ps1` +#### Windows -1. 將 `install_akagi.ps1` 放在您想安裝Akagi的位置。 +到[Release](https://github.com/shinkuan/Akagi/releases/latest)下載`install_akagi.ps1` + +1. 將 `install_akagi.ps1` 放在您想安裝 Akagi 的位置。 2. 以**管理員**身份打開 **Powershell** 3. cd 到該目錄 4. 執行: `Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass` 5. 執行: `install_akagi.ps1` -6. 如果這是您第一次使用mitmproxy,請打開它。 +6. 如果這是您第一次使用 mitmproxy,請打開它。 7. 關閉它。 8. 到使用者主目錄 `~/.mitmproxy` 9. 安裝證書。 10. 將 `mortal.pth` 放入 `./Akagi/mjai/bot` +#### Mac + +到[Release](https://github.com/shinkuan/Akagi/releases/latest)下載`install_akagi.command` + +1. 将 `install_akagi.command` 放在您想安裝 Akagi 的位置。 +2. 从[Python 官方网站](https://www.python.org/downloads/)下载最新的 Python 安装包并安装(如已安装其他兼容版本的 Python 可以跳过这一步)。 +3. 双击`install_akagi.command`自动安装所需的依赖项。 +4. 如果这是您第一次使用 mitmproxy,请打开它。 +5. 关闭它。 +6. 到使用者主目录 `~/.mitmproxy` +7. 安装证书。 +8. 将 `mortal.pth` 放入 `./Akagi/mjai/bot` + ### settings.json - - `Unlocker`: 使用 [MajsoulUnlocker](https://github.com/shinkuan/MajsoulUnlocker) - - `v10`: 如果你的客戶端還在v0.10.x版本而且你想要使用MajsoulUnlocker,設為true - - `Autoplay`: 自動打牌. - - `Helper`: [mahjong-helper](https://github.com/EndlessCheng/mahjong-helper) - - `Autohu`: Auto Ron. - - `Port`: - - `MITM`: MITM Port, 你應該將雀魂連線導向到這個Port. - - `XMLRPC`: The XMLRPC Port. - - `MJAI`: The port bind to MJAI bot container. - - `Playwright`: - - `enable`: Enable the playwright - - `width`: width of the viewport of playwright - - `height`: height of the viewport of playwright - - The rest are the setting for MajsoulUnlocker. +- `Unlocker`: 使用 [MajsoulUnlocker](https://github.com/shinkuan/MajsoulUnlocker) +- `v10`: 如果你的客戶端還在 v0.10.x 版本而且你想要使用 MajsoulUnlocker,設為 true +- `Autoplay`: 自動打牌. +- `Helper`: [mahjong-helper](https://github.com/EndlessCheng/mahjong-helper) +- `Autohu`: Auto Ron. +- `Port`: + - `MITM`: MITM Port, 你應該將雀魂連線導向到這個 Port. + - `XMLRPC`: The XMLRPC Port. + - `MJAI`: The port bind to MJAI bot container. +- `Playwright`: + - `enable`: Enable the playwright + - `width`: width of the viewport of playwright + - `height`: height of the viewport of playwright +- The rest are the setting for MajsoulUnlocker. ## 如何使用 @@ -86,7 +103,7 @@ __到[Discord](https://discord.gg/Z2wjXUK8bN)下載我提供的mortal.pth__ ![image](https://github.com/shinkuan/RandomStuff/assets/35415788/6b66a48b-48fe-4e12-b3cc-18b582410f9a) -可以看到這裡有兩個流程,通常上面的是「大廳」的Websocket流程,而下面的是「遊戲」的Websocket流程,這個會在加入對局後出現。 +可以看到這裡有兩個流程,通常上面的是「大廳」的 Websocket 流程,而下面的是「遊戲」的 Websocket 流程,這個會在加入對局後出現。 點擊下方的流程以開始。(這可能需要一些時間,點擊一次並等待,不要多次點擊) @@ -95,12 +112,12 @@ __到[Discord](https://discord.gg/Z2wjXUK8bN)下載我提供的mortal.pth__ ![image](https://github.com/shinkuan/RandomStuff/assets/35415788/17ae8275-4499-4788-a91b-ecafbac33512) 在進入遊戲流程畫面後,應該會看到這些內容。 -左上角是我們使用MITM捕獲的LiqiProto訊息。 -LiqiProto訊息隨後被轉錄為mjai格式並發送給機器人。 +左上角是我們使用 MITM 捕獲的 LiqiProto 訊息。 +LiqiProto 訊息隨後被轉錄為 mjai 格式並發送給機器人。 -右上角是MJAI訊息,這是我們的機器人發回給我們的訊息,指示我們應該採取的動作。 +右上角是 MJAI 訊息,這是我們的機器人發回給我們的訊息,指示我們應該採取的動作。 -然後下方是我們的手牌,它是使用Unicode字符組成的。 +然後下方是我們的手牌,它是使用 Unicode 字符組成的。 左下角是設置。 @@ -112,46 +129,47 @@ LiqiProto訊息隨後被轉錄為mjai格式並發送給機器人。 以下是一些你可以採取的措施。 -1. 不要使用Steam版,因為它可能會監測你電腦上正在執行的程式。改用web版。 +1. 不要使用 Steam 版,因為它可能會監測你電腦上正在執行的程式。改用 web 版。 2. 使用[Majsoul Mod Plus](https://github.com/Avenshy/majsoul_mod_plus)的`safe_code.js`。 -3. 不要開MajsoulUnlocker,因為它會竄改websocket數據。 -4. 乖乖手打,不要使用Autoplay +3. 不要開 MajsoulUnlocker,因為它會竄改 websocket 數據。 +4. 乖乖手打,不要使用 Autoplay 5. 使用貼圖與你的對手交流。 6. 不要完全照著機器人的指示打牌 -7. 不要使用Autoplay功能掛機24h打牌。 +7. 不要使用 Autoplay 功能掛機 24h 打牌。 ### 目前沒有任何辦法保證完全不封號。 # TODO - - [x] 三麻模式 - - 已完成,但尚未決定公布。 - - [x] 在應用程式內更改Setting。 - - [x] 自動打牌 - - [ ] 自動使用貼圖,讓對手認為我們不是機器人。 - - [ ] 在settings.json中添加隨機時間,讓用戶選擇他們想要的時間。 - - [ ] 混合多個AI的決策,讓我們看起來更像人類,而不是完美的機器人。 - - [x] 縮短機器人的啟動時間。(也許在遊戲開始前就啟動?) - - [x] 與[MajsoulUnlocker](https://github.com/shinkuan/MajsoulUnlocker)整合 - - [ ] 完全不使用MITM進行遊戲,使用圖像識別。 - - [ ] 決定使用哪種模型 - - [ ] 訓練數據生成 - - [ ] 進行訓練 - - [ ] 得分差異識別。 - - [ ] 流局識別。 - - [ ] 實施 - - [x] 更簡單的安裝流程 + +- [x] 三麻模式 + - 已完成,但尚未決定公布。 +- [x] 在應用程式內更改 Setting。 +- [x] 自動打牌 + - [ ] 自動使用貼圖,讓對手認為我們不是機器人。 + - [ ] 在 settings.json 中添加隨機時間,讓用戶選擇他們想要的時間。 +- [ ] 混合多個 AI 的決策,讓我們看起來更像人類,而不是完美的機器人。 +- [x] 縮短機器人的啟動時間。(也許在遊戲開始前就啟動?) +- [x] 與[MajsoulUnlocker](https://github.com/shinkuan/MajsoulUnlocker)整合 +- [ ] 完全不使用 MITM 進行遊戲,使用圖像識別。 + - [ ] 決定使用哪種模型 + - [ ] 訓練數據生成 + - [ ] 進行訓練 + - [ ] 得分差異識別。 + - [ ] 流局識別。 + - [ ] 實施 +- [x] 更簡單的安裝流程 ## Need Help! -1. 歡迎任何人提交PR(Pull Request)。 -2. 如果你有使用MajsoulUnlocker,請告訴我它運行得是否順暢,有沒有關於我們修改訊息的痕跡洩露給Majsoul伺服器? +1. 歡迎任何人提交 PR(Pull Request)。 +2. 如果你有使用 MajsoulUnlocker,請告訴我它運行得是否順暢,有沒有關於我們修改訊息的痕跡洩露給 Majsoul 伺服器? 3. 尋找一種穩定且安全的自動打牌方式。 4. 如果遇到任何錯誤,請回報。 -5. 如果你的機器人很好用,可以考慮分享你的bot.zip檔案。 +5. 如果你的機器人很好用,可以考慮分享你的 bot.zip 檔案。 # Authors -* **Shinkuan** - [Shinkuan](https://github.com/shinkuan/) +- **Shinkuan** - [Shinkuan](https://github.com/shinkuan/) ## 支持作者 @@ -159,7 +177,7 @@ LiqiProto訊息隨後被轉錄為mjai格式並發送給機器人。 ETH Mainnet: 0x83095C4355E43bDFe9cEf2e439F371900664D41F -Paypal或其他: 到Discord找我 +Paypal 或其他: 到 Discord 找我 You can find me at [Discord](https://discord.gg/Z2wjXUK8bN). @@ -205,4 +223,4 @@ Software: Akagi License: GNU Affero General Public License version 3 with Commons Clause Licensor: shinkuan -``` \ No newline at end of file +``` From 984f2728528e6ceb9ee4046de80dc357232fcba6 Mon Sep 17 00:00:00 2001 From: jch12138 Date: Sun, 25 Feb 2024 16:37:16 +0800 Subject: [PATCH 08/30] Improve the documentation, improve the startup script. --- README.md | 91 +++++++++++++++++-------------- README_CH.md | 3 +- run_akagi.sh => run_akagi.command | 2 + 3 files changed, 53 insertions(+), 43 deletions(-) rename run_akagi.sh => run_akagi.command (56%) diff --git a/README.md b/README.md index e3d70fc..8b9eafe 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,13 @@ https://github.com/shinkuan/RandomStuff/assets/35415788/ce1b598d-b1d7-49fe-a175- [YouTube Video for you to follow.](https://youtu.be/V7NMNsZ3Ut8) ### You will need: + 1. A `mortal.pth`. [(Get one from Discord server if you don't have one.)](https://discord.gg/Z2wjXUK8bN) 2. (Optional, Recommend) Use Windows Terminal to open client.py for a nice looking TUI. 3. (Optional) If you want to use Steam, Majsoul Plus, or anything other client, proxy the client using tools like proxifier. -__Get mortal.pth at [Discord](https://discord.gg/Z2wjXUK8bN)__ +**Get mortal.pth at [Discord](https://discord.gg/Z2wjXUK8bN)** + 1. Go to #verify and click the ✅ reaction. 2. Go to #bot-zip 3. Download a bot you like. @@ -52,7 +54,9 @@ __Get mortal.pth at [Discord](https://discord.gg/Z2wjXUK8bN)__ 5. And mortal.pth is there. ### Akagi: + #### Windows + Download `install_akagi.ps1` at [Release](https://github.com/shinkuan/Akagi/releases/latest) 1. Put `install_akagi.ps1` at the location you want to install Akagi. @@ -67,33 +71,35 @@ Download `install_akagi.ps1` at [Release](https://github.com/shinkuan/Akagi/rele 10. Put `mortal.pth` into `./Akagi/mjai/bot` #### Mac + Download `install_akagi.command` from [Release](https://github.com/shinkuan/Akagi/releases/latest) -1. Put `install_akagi.command` in the location where you want to install Akagi. +1. Place `install_akagi.command` in the location where you want to install Akagi. 2. Download the latest Python installation package from the [Python official website](https://www.python.org/downloads/) and install it (skip this step if you already have a compatible version of Python installed). 3. Double-click `install_akagi.command` to automatically install the required dependencies. -4. If this is your first time using mitmproxy, open it. -5. Close it. -6. Go to your user home directory `~/.mitmproxy` -7. Install the certificate. -8. Put `mortal.pth` into `./Akagi/mjai/bot` +4. Double-click `run_agaki.command` to start Akagi. +5. If you are using mitmproxy for the first time, click on "start mitm". +6. Close it. +7. Go to your user home directory `~/.mitmproxy`. +8. Install the certificate. +9. Put `mortal.pth` into `./Akagi/mjai/bot`. ### settings.json - - `Unlocker`: Decide to use [MajsoulUnlocker](https://github.com/shinkuan/MajsoulUnlocker) or not. - - `v10`: If your Majsoul client in still in v0.10.x and you want to use MajsoulUnlocker, set it to true. - - `Autoplay`: Autoplay. - - `Helper`: To use [mahjong-helper](https://github.com/EndlessCheng/mahjong-helper) or not - - `Autohu`: Auto Ron. - - `Port`: - - `MITM`: The MITM Port, you should redirect Majsoul connection to this port. - - `XMLRPC`: The XMLRPC Port. - - `MJAI`: The port bind to MJAI bot container. - - `Playwright`: - - `enable`: Enable the playwright - - `width`: width of the viewport of playwright - - `height`: height of the viewport of playwright - - The rest are the setting for MajsoulUnlocker. +- `Unlocker`: Decide to use [MajsoulUnlocker](https://github.com/shinkuan/MajsoulUnlocker) or not. +- `v10`: If your Majsoul client in still in v0.10.x and you want to use MajsoulUnlocker, set it to true. +- `Autoplay`: Autoplay. +- `Helper`: To use [mahjong-helper](https://github.com/EndlessCheng/mahjong-helper) or not +- `Autohu`: Auto Ron. +- `Port`: + - `MITM`: The MITM Port, you should redirect Majsoul connection to this port. + - `XMLRPC`: The XMLRPC Port. + - `MJAI`: The port bind to MJAI bot container. +- `Playwright`: + - `enable`: Enable the playwright + - `width`: width of the viewport of playwright + - `height`: height of the viewport of playwright +- The rest are the setting for MajsoulUnlocker. ## Instructions @@ -117,7 +123,7 @@ On top right is the MJAI Messages, this is the message our bot sent back to us, Then below is our tehai, it is composed using unicode characters. -Bottom left is the settings. +Bottom left is the settings. Bottom right is the bot's action. @@ -136,23 +142,24 @@ Following guide can minimum the probility of account suspension. ### There is no way to guarantee 100% no account suspension currently. # TODO - - [x] 3 Player Mahjong - - Already done, but not planned to release yet. - - [x] Change Setting inside application. - - [x] Autoplay - - [ ] Auto use stickers to make opponent think we are not a bot. - - [ ] Add random time in settings.json to let user choose time they want. - - [ ] Mix multiple AI's decision to make we more like a human but not a perfect bot. - - [x] Reduce Startup time of the bot. (Maybe start it before match begin?) - - [x] Intergrade with [MajsoulUnlocker](https://github.com/shinkuan/MajsoulUnlocker) - - [ ] Don't use MITM at all for the gameplay, use image recognition. - - [ ] Decide use what model - - [ ] Training data generation - - [ ] Train it - - [ ] Delta Score Recognition. - - [ ] Ryukyoku Recognition. - - [ ] Implement - - [x] Easier installation. + +- [x] 3 Player Mahjong + - Already done, but not planned to release yet. +- [x] Change Setting inside application. +- [x] Autoplay + - [ ] Auto use stickers to make opponent think we are not a bot. + - [ ] Add random time in settings.json to let user choose time they want. +- [ ] Mix multiple AI's decision to make we more like a human but not a perfect bot. +- [x] Reduce Startup time of the bot. (Maybe start it before match begin?) +- [x] Intergrade with [MajsoulUnlocker](https://github.com/shinkuan/MajsoulUnlocker) +- [ ] Don't use MITM at all for the gameplay, use image recognition. + - [ ] Decide use what model + - [ ] Training data generation + - [ ] Train it + - [ ] Delta Score Recognition. + - [ ] Ryukyoku Recognition. + - [ ] Implement +- [x] Easier installation. ## Need Help! @@ -164,7 +171,7 @@ Following guide can minimum the probility of account suspension. # Authors -* **Shinkuan** - [Shinkuan](https://github.com/shinkuan/) +- **Shinkuan** - [Shinkuan](https://github.com/shinkuan/) ## Support me @@ -178,7 +185,7 @@ You can find me at [Discord](https://discord.gg/Z2wjXUK8bN). ### What can I get after donating? -Firstly, thank you very much for your willingness to support the author. +Firstly, thank you very much for your willingness to support the author. I will prioritize the opinions of donors, such as feature requests and bug fixes. @@ -223,4 +230,4 @@ Software: Akagi License: GNU Affero General Public License version 3 with Commons Clause Licensor: shinkuan -``` \ No newline at end of file +``` diff --git a/README_CH.md b/README_CH.md index 1699f1b..112d7ac 100644 --- a/README_CH.md +++ b/README_CH.md @@ -74,7 +74,8 @@ https://github.com/shinkuan/RandomStuff/assets/35415788/ce1b598d-b1d7-49fe-a175- 1. 将 `install_akagi.command` 放在您想安裝 Akagi 的位置。 2. 从[Python 官方网站](https://www.python.org/downloads/)下载最新的 Python 安装包并安装(如已安装其他兼容版本的 Python 可以跳过这一步)。 3. 双击`install_akagi.command`自动安装所需的依赖项。 -4. 如果这是您第一次使用 mitmproxy,请打开它。 +4. 双击run_agaki.command启动agaki。 +5. 如果您是第一次使用mitmproxy,请点击start mitm。 5. 关闭它。 6. 到使用者主目录 `~/.mitmproxy` 7. 安装证书。 diff --git a/run_akagi.sh b/run_akagi.command similarity index 56% rename from run_akagi.sh rename to run_akagi.command index 61a46e3..ceb7194 100755 --- a/run_akagi.sh +++ b/run_akagi.command @@ -1,2 +1,4 @@ +#!/bin/bash +cd "$(dirname "$0")" source venv/bin/activate python client.py From 3dfb6b1ed74c6c1cabe80cd459c8aee55c7ab955 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Sun, 25 Feb 2024 20:52:36 +0800 Subject: [PATCH 09/30] code style --- client.py | 220 +++++++++++++++++++----------------------------------- 1 file changed, 78 insertions(+), 142 deletions(-) diff --git a/client.py b/client.py index 4043ae0..b7ee62a 100644 --- a/client.py +++ b/client.py @@ -63,16 +63,12 @@ def __init__(self, flow_id, *args, **kwargs) -> None: def compose(self) -> ComposeResult: """Called to add widgets to the app.""" - liqi_log_container = ScrollableContainer(Pretty( - self.app.liqi_msg_dict[self.flow_id], id="liqi_log"), id="liqi_log_container") - mjai_log_container = ScrollableContainer(Pretty( - self.app.mjai_msg_dict[self.flow_id], id="mjai_log"), id="mjai_log_container") - log_container = Horizontal( - liqi_log_container, mjai_log_container, id="log_container") + liqi_log_container = ScrollableContainer(Pretty(self.app.liqi_msg_dict[self.flow_id], id="liqi_log"), id="liqi_log_container") + mjai_log_container = ScrollableContainer(Pretty(self.app.mjai_msg_dict[self.flow_id], id="mjai_log"), id="mjai_log_container") + log_container = Horizontal(liqi_log_container, mjai_log_container, id="log_container") liqi_log_container.border_title = "LiqiProto" mjai_log_container.border_title = "Mjai" - tehai_labels = [Label(TILE_2_UNICODE_ART_RICH["?"], - id="tehai_"+str(i)) for i in range(13)] + tehai_labels = [Label(TILE_2_UNICODE_ART_RICH["?"], id="tehai_"+str(i)) for i in range(13)] tehai_rule = Label(VERTICLE_RULE, id="tehai_rule") tsumohai_label = Label(TILE_2_UNICODE_ART_RICH["?"], id="tsumohai") tehai_container = Horizontal(id="tehai_container") @@ -83,24 +79,17 @@ def compose(self) -> ComposeResult: tehai_container.border_title = "Tehai" akagi_action = Button("Akagi", id="akagi_action", variant="default") akagi_pai = Button("Pai", id="akagi_pai", variant="default") - pai_unicode_art = Label( - TILE_2_UNICODE_ART_RICH["?"], id="pai_unicode_art") - akagi_container = Horizontal( - akagi_action, akagi_pai, pai_unicode_art, id="akagi_container") + pai_unicode_art = Label(TILE_2_UNICODE_ART_RICH["?"], id="pai_unicode_art") + akagi_container = Horizontal(akagi_action, akagi_pai, pai_unicode_art, id="akagi_container") akagi_container.border_title = "Akagi" loading_indicator = LoadingIndicator(id="loading_indicator") loading_indicator.styles.height = "3" - checkbox_autoplay = Checkbox( - "Autoplay", id="checkbox_autoplay", classes="short", value=AUTOPLAY) - 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_autoplay = Checkbox("Autoplay", id="checkbox_autoplay", classes="short", value=AUTOPLAY) + 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.border_title = "Options" - bottom_container = Horizontal( - checkbox_container, akagi_container, id="bottom_container") + bottom_container = Horizontal(checkbox_container, akagi_container, id="bottom_container") yield Header() yield Footer() yield loading_indicator @@ -119,8 +108,7 @@ def on_mount(self) -> None: self.mjai_log.update(self.app.mjai_msg_dict[self.flow_id]) self.liqi_log_container = self.query_one("#liqi_log_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_labels = [self.query_one("#tehai_"+str(i)) for i in range(13)] self.tehai_rule = self.query_one("#tehai_rule") self.tsumohai_label = self.query_one("#tsumohai") self.tehai_container = self.query_one("#tehai_container") @@ -133,12 +121,10 @@ def on_mount(self) -> None: self.akagi_action.label = self.app.mjai_msg_dict[self.flow_id][-1]["type"] for akagi_action_class in self.akagi_action.classes: self.akagi_action.remove_class(akagi_action_class) - self.akagi_action.add_class( - "action_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) + self.akagi_action.add_class("action_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) for akagi_pai_class in self.akagi_pai.classes: self.akagi_pai.remove_class(akagi_pai_class) - self.akagi_pai.add_class( - "pai_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) + self.akagi_pai.add_class("pai_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) except IndexError: self.akagi_action.label = "Akagi" @@ -150,17 +136,14 @@ def refresh_log(self) -> None: self.liqi_log_container.scroll_end() self.liqi_msg_idx += 1 for idx, tehai_label in enumerate(self.tehai_labels): - tehai_label.update( - TILE_2_UNICODE_ART_RICH[self.app.bridge[self.flow_id].my_tehais[idx]]) - self.tsumohai_label.update( - TILE_2_UNICODE_ART_RICH[self.app.bridge[self.flow_id].my_tsumohai]) + tehai_label.update(TILE_2_UNICODE_ART_RICH[self.app.bridge[self.flow_id].my_tehais[idx]]) + self.tsumohai_label.update(TILE_2_UNICODE_ART_RICH[self.app.bridge[self.flow_id].my_tsumohai]) liqi_msg = self.app.liqi_msg_dict[self.flow_id][-1] if liqi_msg['type'] == MsgType.Notify: if liqi_msg['method'] == '.lq.ActionPrototype': if 'operation' in liqi_msg['data']['data']: if 'operationList' in liqi_msg['data']['data']['operation']: - self.action.latest_operation_list = liqi_msg[ - 'data']['data']['operation']['operationList'] + self.action.latest_operation_list = liqi_msg['data']['data']['operation']['operationList'] if liqi_msg['data']['name'] == 'ActionDiscardTile': self.action.isNewRound = False pass @@ -174,8 +157,7 @@ def refresh_log(self) -> None: self.query_one("#loading_indicator").remove() self.syncing = False if AUTOPLAY: - logger.log( - "CLICK", self.app.mjai_msg_dict[self.flow_id][-1]) + logger.log("CLICK", self.app.mjai_msg_dict[self.flow_id][-1]) self.app.set_timer(2, self.autoplay) if self.mjai_msg_idx < len(self.app.mjai_msg_dict[self.flow_id]): self.mjai_log.update(self.app.mjai_msg_dict[self.flow_id]) @@ -184,46 +166,35 @@ def refresh_log(self) -> None: self.akagi_action.label = self.app.mjai_msg_dict[self.flow_id][-1]["type"] for akagi_action_class in self.akagi_action.classes: self.akagi_action.remove_class(akagi_action_class) - self.akagi_action.add_class( - "action_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) + self.akagi_action.add_class("action_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) for akagi_pai_class in self.akagi_pai.classes: self.akagi_pai.remove_class(akagi_pai_class) - self.akagi_pai.add_class( - "pai_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) + self.akagi_pai.add_class("pai_"+self.app.mjai_msg_dict[self.flow_id][-1]["type"]) if "consumed" in self.app.mjai_msg_dict[self.flow_id][-1]: - self.akagi_pai.label = str( - self.app.mjai_msg_dict[self.flow_id][-1]["consumed"]) + self.akagi_pai.label = str(self.app.mjai_msg_dict[self.flow_id][-1]["consumed"]) if "pai" in self.app.mjai_msg_dict[self.flow_id][-1]: - self.pai_unicode_art.update( - TILE_2_UNICODE_ART_RICH[self.app.mjai_msg_dict[self.flow_id][-1]["pai"]]) - self.akagi_container.mount( - Label(VERTICLE_RULE, id="consumed_rule")) + self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH[self.app.mjai_msg_dict[self.flow_id][-1]["pai"]]) + self.akagi_container.mount(Label(VERTICLE_RULE, id="consumed_rule")) self.consume_ids.append("#"+"consumed_rule") i = 0 for c in self.app.mjai_msg_dict[self.flow_id][-1]["consumed"]: - self.akagi_container.mount( - Label(TILE_2_UNICODE_ART_RICH[c], id="consumed_"+c+str(i))) + self.akagi_container.mount(Label(TILE_2_UNICODE_ART_RICH[c], id="consumed_"+c+str(i))) self.consume_ids.append("#"+"consumed_"+c+str(i)) i += 1 elif "pai" in self.app.mjai_msg_dict[self.flow_id][-1]: for consume_id in self.consume_ids: self.query_one(consume_id).remove() self.consume_ids = [] - self.akagi_pai.label = str( - self.app.mjai_msg_dict[self.flow_id][-1]["pai"]) - self.pai_unicode_art.update( - TILE_2_UNICODE_ART_RICH[self.app.mjai_msg_dict[self.flow_id][-1]["pai"]]) + self.akagi_pai.label = str(self.app.mjai_msg_dict[self.flow_id][-1]["pai"]) + self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH[self.app.mjai_msg_dict[self.flow_id][-1]["pai"]]) else: self.akagi_pai.label = "None" self.pai_unicode_art.update(TILE_2_UNICODE_ART_RICH["?"]) # Action - logger.info( - f"Current tehai: {self.app.bridge[self.flow_id].my_tehais}") - logger.info( - f"Current tsumohai: {self.app.bridge[self.flow_id].my_tsumohai}") + logger.info(f"Current tehai: {self.app.bridge[self.flow_id].my_tehais}") + logger.info(f"Current tsumohai: {self.app.bridge[self.flow_id].my_tsumohai}") if not self.syncing and ENABLE_PLAYWRIGHT and AUTOPLAY: - logger.log( - "CLICK", self.app.mjai_msg_dict[self.flow_id][-1]) + logger.log("CLICK", self.app.mjai_msg_dict[self.flow_id][-1]) self.app.set_timer(0.05, self.autoplay) except Exception as e: @@ -237,8 +208,7 @@ def checkbox_autoplay_changed(self, event: Checkbox.Changed) -> None: pass def autoplay(self) -> None: - self.action.mjai2action( - self.app.mjai_msg_dict[self.flow_id][-1], self.app.bridge[self.flow_id].my_tehais, self.app.bridge[self.flow_id].my_tsumohai) + self.action.mjai2action(self.app.mjai_msg_dict[self.flow_id][-1], self.app.bridge[self.flow_id].my_tehais, self.app.bridge[self.flow_id].my_tsumohai) pass def action_quit(self) -> None: @@ -294,91 +264,60 @@ def __init__(self, *args, **kwargs) -> None: self.value_playwright_setting_height_input = settings["Playwright"]["height"] def compose(self) -> ComposeResult: - self.port_setting_mitm_label = Label( - "MITM Port", id="port_setting_mitm_label") - self.port_setting_mitm_input = Input( - placeholder="Port", type="integer", id="port_setting_mitm_input", value=str(self.value_port_setting_mitm_input)) - self.port_setting_mitm_container = Horizontal( - self.port_setting_mitm_label, self.port_setting_mitm_input, id="port_setting_mitm_container") - self.port_setting_xmlrpc_label = Label( - "XMLRPC Port", id="port_setting_xmlrpc_label") - self.port_setting_xmlrpc_input = Input( - placeholder="Port", type="integer", id="port_setting_xmlrpc_input", value=str(self.value_port_setting_xmlrpc_input)) - self.port_setting_xmlrpc_container = Horizontal( - self.port_setting_xmlrpc_label, self.port_setting_xmlrpc_input, id="port_setting_xmlrpc_container") - self.port_setting_container = Vertical( - self.port_setting_mitm_container, self.port_setting_xmlrpc_container, id="port_setting_container") + self.port_setting_mitm_label = Label("MITM Port", id="port_setting_mitm_label") + self.port_setting_mitm_input = Input(placeholder="Port", type="integer", id="port_setting_mitm_input", value=str(self.value_port_setting_mitm_input)) + self.port_setting_mitm_container = Horizontal(self.port_setting_mitm_label, self.port_setting_mitm_input, id="port_setting_mitm_container") + self.port_setting_xmlrpc_label = Label("XMLRPC Port", id="port_setting_xmlrpc_label") + self.port_setting_xmlrpc_input = Input(placeholder="Port", type="integer", id="port_setting_xmlrpc_input", value=str(self.value_port_setting_xmlrpc_input)) + self.port_setting_xmlrpc_container = Horizontal(self.port_setting_xmlrpc_label, self.port_setting_xmlrpc_input, id="port_setting_xmlrpc_container") + self.port_setting_container = Vertical(self.port_setting_mitm_container, self.port_setting_xmlrpc_container, id="port_setting_container") self.port_setting_container.border_title = "Port" - self.unlocker_setting_label = Label( - "Unlocker", id="unlocker_setting_label") - self.unlocker_setting_enable_checkbox = Checkbox( - "Enable", id="unlocker_setting_enable_checkbox", classes="short", value=self.value_unlocker_setting_enable_checkbox) - self.unlocker_setting_v10_checkbox = Checkbox( - "v10", id="unlocker_setting_v10_checkbox", classes="short", value=self.value_unlocker_setting_v10_checkbox) - self.unlocker_setting_container = Horizontal( - self.unlocker_setting_label, self.unlocker_setting_enable_checkbox, self.unlocker_setting_v10_checkbox, id="unlocker_setting_container") + self.unlocker_setting_label = Label("Unlocker", id="unlocker_setting_label") + self.unlocker_setting_enable_checkbox = Checkbox("Enable", id="unlocker_setting_enable_checkbox", classes="short", value=self.value_unlocker_setting_enable_checkbox) + self.unlocker_setting_v10_checkbox = Checkbox("v10", id="unlocker_setting_v10_checkbox", classes="short", value=self.value_unlocker_setting_v10_checkbox) + self.unlocker_setting_container = Horizontal(self.unlocker_setting_label, self.unlocker_setting_enable_checkbox, self.unlocker_setting_v10_checkbox, id="unlocker_setting_container") self.unlocker_setting_container.border_title = "Unlocker" self.helper_setting_label = Label("Helper", id="helper_setting_label") - self.helper_setting_checkbox = Checkbox( - "Enable", id="helper_setting_checkbox", classes="short", value=self.value_helper_setting_checkbox) - self.helper_setting_container = Horizontal( - self.helper_setting_label, self.helper_setting_checkbox, id="helper_setting_container") + self.helper_setting_checkbox = Checkbox("Enable", id="helper_setting_checkbox", classes="short", value=self.value_helper_setting_checkbox) + self.helper_setting_container = Horizontal(self.helper_setting_label, self.helper_setting_checkbox, id="helper_setting_container") self.helper_setting_container.border_title = "Helper" - self.autoplay_setting_enable_label = Label( - "Enable", id="autoplay_setting_enable_label") - self.autoplay_setting_enable_checkbox = Checkbox( - "Enable", id="autoplay_setting_enable_checkbox", classes="short", value=self.value_autoplay_setting_enable_checkbox) - self.autoplay_setting_enable_container = Horizontal( - self.autoplay_setting_enable_label, self.autoplay_setting_enable_checkbox, id="autoplay_setting_enable_container") - self.autoplay_setting_random_time_label = Label( - "Random Time", id="autoplay_setting_random_time_label") - self.autoplay_setting_random_time_min_input = Input( - placeholder="Min", type="number", id="autoplay_setting_random_time_min_input") - self.autoplay_setting_random_time_max_input = Input( - placeholder="Max", type="number", id="autoplay_setting_random_time_max_input") - self.autoplay_setting_random_time_container = Horizontal( - self.autoplay_setting_random_time_label, self.autoplay_setting_random_time_min_input, self.autoplay_setting_random_time_max_input, id="autoplay_setting_random_time_container") - self.autoplay_setting_container = Vertical( - self.autoplay_setting_enable_container, self.autoplay_setting_random_time_container, id="autoplay_setting_container") + self.autoplay_setting_enable_label = Label("Enable", id="autoplay_setting_enable_label") + self.autoplay_setting_enable_checkbox = Checkbox("Enable", id="autoplay_setting_enable_checkbox", classes="short", value=self.value_autoplay_setting_enable_checkbox) + self.autoplay_setting_enable_container = Horizontal(self.autoplay_setting_enable_label, self.autoplay_setting_enable_checkbox, id="autoplay_setting_enable_container") + self.autoplay_setting_random_time_label = Label("Random Time", id="autoplay_setting_random_time_label") + self.autoplay_setting_random_time_min_input = Input(placeholder="Min", type="number", id="autoplay_setting_random_time_min_input") + self.autoplay_setting_random_time_max_input = Input(placeholder="Max", type="number", id="autoplay_setting_random_time_max_input") + self.autoplay_setting_random_time_container = Horizontal(self.autoplay_setting_random_time_label, self.autoplay_setting_random_time_min_input, self.autoplay_setting_random_time_max_input, id="autoplay_setting_random_time_container") + self.autoplay_setting_container = Vertical(self.autoplay_setting_enable_container, self.autoplay_setting_random_time_container, id="autoplay_setting_container") self.autoplay_setting_container.border_title = "Autoplay" - self.playwright_setting_enable_label = Label( - "Enable", id="playwright_setting_enable_label") - self.playwright_setting_enable_checkbox = Checkbox( - "Enable", id="playwright_setting_enable_checkbox", classes="short", value=self.value_playwright_setting_enable_checkbox) - self.playwright_setting_enable_container = Horizontal( - self.playwright_setting_enable_label, self.playwright_setting_enable_checkbox, id="playwright_setting_enable_container") - self.playwright_setting_resolution_label = Label( - "Resolution", id="playwright_setting_resolution_label") - self.playwright_setting_width_input = Input( - placeholder="Width", type="integer", id="playwright_setting_width_input", value=str(self.value_playwright_setting_width_input)) - self.playwright_setting_height_input = Input( - placeholder="Height", type="integer", id="playwright_setting_height_input", value=str(self.value_playwright_setting_height_input)) - self.playwright_setting_resolution_container = Horizontal( - self.playwright_setting_resolution_label, self.playwright_setting_width_input, self.playwright_setting_height_input, id="playwright_setting_resolution_container") - self.playwright_setting_container = Vertical( - self.playwright_setting_enable_container, self.playwright_setting_resolution_container, id="playwright_setting_container") + self.playwright_setting_enable_label = Label("Enable", id="playwright_setting_enable_label") + self.playwright_setting_enable_checkbox = Checkbox("Enable", id="playwright_setting_enable_checkbox", classes="short", value=self.value_playwright_setting_enable_checkbox) + self.playwright_setting_enable_container = Horizontal(self.playwright_setting_enable_label, self.playwright_setting_enable_checkbox, id="playwright_setting_enable_container") + self.playwright_setting_resolution_label = Label("Resolution", id="playwright_setting_resolution_label") + self.playwright_setting_width_input = Input(placeholder="Width", type="integer", id="playwright_setting_width_input", value=str(self.value_playwright_setting_width_input)) + self.playwright_setting_height_input = Input(placeholder="Height", type="integer", id="playwright_setting_height_input", value=str(self.value_playwright_setting_height_input)) + self.playwright_setting_resolution_container = Horizontal(self.playwright_setting_resolution_label, self.playwright_setting_width_input, self.playwright_setting_height_input, id="playwright_setting_resolution_container") + self.playwright_setting_container = Vertical(self.playwright_setting_enable_container, self.playwright_setting_resolution_container, id="playwright_setting_container") self.playwright_setting_container.border_title = "Playwright" - self.setting_save_button = Button( - "Save", variant="warning", id="setting_save_button") + self.setting_save_button = Button("Save", variant="warning", id="setting_save_button") - self.remove_this_then_you_badluck_for_100years_and_get_hit_by_a_car_then_die = HoverLink( - "Akagi is Free and Open Sourced on GitHub.\n本程式Akagi在GitHub上完全開源且免費。如果你是付費取得的,你已經被賣家欺騙,請立即舉報、差評、退款。", "https://github.com/shinkuan/Akagi", id="remove_this_you_die") + self.remove_this_then_you_badluck_for_100years_and_get_hit_by_a_car_then_die = HoverLink("Akagi is Free and Open Sourced on GitHub.\n本程式Akagi在GitHub上完全開源且免費。如果你是付費取得的,你已經被賣家欺騙,請立即舉報、差評、退款。", "https://github.com/shinkuan/Akagi", id="remove_this_you_die") self.setting_container = ScrollableContainer( - self.port_setting_container, - self.unlocker_setting_container, - self.helper_setting_container, - self.autoplay_setting_container, - self.playwright_setting_container, - self.setting_save_button, - self.remove_this_then_you_badluck_for_100years_and_get_hit_by_a_car_then_die, - id="setting_container" - ) + self.port_setting_container, + self.unlocker_setting_container, + self.helper_setting_container, + self.autoplay_setting_container, + self.playwright_setting_container, + self.setting_save_button, + self.remove_this_then_you_badluck_for_100years_and_get_hit_by_a_car_then_die, + id="setting_container" + ) yield self.setting_container @@ -462,11 +401,11 @@ def __init__(self, rpc_server, *args, **kwargs) -> None: self.liqi: dict[str, LiqiProto] = {} self.bridge: dict[str, MajsoulBridge] = {} self.active_flows = [] - self.messages_dict = dict() # flow.id -> List[flow_msg] - self.liqi_msg_dict = dict() # flow.id -> List[liqi_msg] - self.mjai_msg_dict = dict() # flow.id -> List[mjai_msg] - self.akagi_log_dict = dict() # flow.id -> List[akagi_log] - self.loguru_log = [] # List[loguru_log] + self.messages_dict = dict() # flow.id -> List[flow_msg] + self.liqi_msg_dict = dict() # flow.id -> List[liqi_msg] + self.mjai_msg_dict = dict() # flow.id -> List[mjai_msg] + self.akagi_log_dict = dict() # flow.id -> List[akagi_log] + self.loguru_log = [] # List[loguru_log] self.mitm_started = False def on_mount(self) -> None: @@ -496,8 +435,7 @@ def refresh_flow(self) -> None: try: self.query_one(f"#flow_{flow_id}") except NoMatches: - self.query_one("#FlowContainer").mount( - FlowDisplay(flow_id, id=f"flow_{flow_id}")) + self.query_one("#FlowContainer").mount(FlowDisplay(flow_id, id=f"flow_{flow_id}")) self.active_flows.append(flow_id) self.messages_dict[flow_id] = [] self.liqi_msg_dict[flow_id] = [] @@ -555,8 +493,7 @@ def mitm_connected(self): def my_sink(self, message) -> None: record = message.record - self.loguru_log.append( - f"{record['time'].strftime('%H:%M:%S')} | {record['level'].name}\t | {record['message']}") + self.loguru_log.append(f"{record['time'].strftime('%H:%M:%S')} | {record['level'].name}\t | {record['message']}") def action_quit(self) -> None: self.update_flow.stop() @@ -581,8 +518,7 @@ def start_mitm(): if sys.platform == "win32": # Windows特定代码 - mitm_exec = subprocess.Popen( - command, creationflags=subprocess.CREATE_NEW_CONSOLE) + mitm_exec = subprocess.Popen(command, creationflags=subprocess.CREATE_NEW_CONSOLE) else: # macOS和其他Unix-like系统 mitm_exec = subprocess.Popen(command, preexec_fn=os.setsid) From 1cfde1d213eb37b0613f02fecf343b7c62030a63 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Sun, 25 Feb 2024 21:09:42 +0800 Subject: [PATCH 10/30] space --- client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.py b/client.py index aa21c6b..098c502 100644 --- a/client.py +++ b/client.py @@ -77,7 +77,7 @@ def compose(self) -> ComposeResult: tehai_container.mount(tsumohai_label) tehai_container.border_title = "Tehai" akagi_action = Button("Akagi", id="akagi_action", variant="default") - akagi_pai = Button("Pai", id="akagi_pai", variant="default") + akagi_pai = Button("Pai", id="akagi_pai", variant="default") pai_unicode_art = Label(TILE_2_UNICODE_ART_RICH["?"], id="pai_unicode_art") akagi_container = Horizontal(akagi_action, akagi_pai, pai_unicode_art, id="akagi_container") akagi_container.border_title = "Akagi" From 146747bd75af2e4b82969bd15cdf7d488320ec1a Mon Sep 17 00:00:00 2001 From: shinkuan Date: Mon, 26 Feb 2024 22:06:19 +0800 Subject: [PATCH 11/30] mask_code --- libriichi_helper.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libriichi_helper.py b/libriichi_helper.py index 5dc25d3..8e91d63 100644 --- a/libriichi_helper.py +++ b/libriichi_helper.py @@ -25,7 +25,14 @@ def meta_to_recommend(meta: dict) -> dict: recommend = [] - mask_unicode = ['🀇', '🀈', '🀉', '🀊', '🀋', '🀌', '🀍', '🀎', '🀏', '🀙', '🀚', '🀛', '🀜', '🀝', '🀞', '🀟', '🀠', '🀡', '🀐', '🀑', '🀒', '🀓', '🀔', '🀕', '🀖', '🀗', '🀘', '🀀', '🀁', '🀂', '🀃', '🀆', '🀅', '🀄', '🀋', '🀝', '🀔', '立直', '吃(下)', '吃(中)', '吃(上)', '碰', '槓(選擇)', '和', '流局', '跳過'] + mask_unicode = [ + "1m", "2m", "3m", "4m", "5m", "6m", "7m", "8m", "9m", + "1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p", + "1s", "2s", "3s", "4s", "5s", "6s", "7s", "8s", "9s", + "E", "S", "W", "N", "P", "F", "C", + '5mr', '5pr', '5sr', + 'reach', 'chi_low', 'chi_mid', 'chi_high', 'pon', 'kan_select', 'hora', 'ryukyoku', 'none' + ] def mask_bits_to_binary_string(mask_bits): binary_string = bin(mask_bits)[2:] From b4648c2a2532ff922b6adf8b5849b3c3f1d5bc98 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Mon, 26 Feb 2024 22:07:03 +0800 Subject: [PATCH 12/30] tehai value --- client.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/client.py b/client.py index 76c3dfa..6cdc25f 100644 --- a/client.py +++ b/client.py @@ -25,7 +25,7 @@ from liqi import LiqiProto, MsgType from majsoul2mjai import MajsoulBridge from libriichi_helper import meta_to_recommend, state_to_tehai -from tileUnicode import TILE_2_UNICODE_ART_RICH, TILE_2_UNICODE, VERTICLE_RULE +from tileUnicode import TILE_2_UNICODE_ART_RICH, TILE_2_UNICODE, VERTICLE_RULE, HAI_VALUE from action import Action submission = 'players/bot.zip' @@ -63,13 +63,17 @@ def compose(self) -> ComposeResult: liqi_log_container.border_title = "LiqiProto" 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_rule = Label(VERTICLE_RULE, id="tehai_rule") tsumohai_label = Label(TILE_2_UNICODE_ART_RICH["?"], id="tsumohai") + tsumohai_value_label = Label(HAI_VALUE[40], id="tsumohai_value") tehai_container = Horizontal(id="tehai_container") - for tehai_label in tehai_labels: - tehai_container.mount(tehai_label) + for i in range(13): + tehai_container.mount(tehai_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.border_title = "Tehai" akagi_action = Button("Akagi", id="akagi_action", variant="default") akagi_pai = Button("Pai", id="akagi_pai", variant="default") @@ -103,8 +107,10 @@ def on_mount(self) -> None: self.liqi_log_container = self.query_one("#liqi_log_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_rule = self.query_one("#tehai_rule") self.tsumohai_label = self.query_one("#tsumohai") + self.tsumohai_value_label = self.query_one("#tsumohai_value") self.tehai_container = self.query_one("#tehai_container") self.liqi_log_container.scroll_end() self.mjai_log_container.scroll_end() @@ -151,13 +157,33 @@ def refresh_log(self) -> None: logger.log("CLICK", self.app.mjai_msg_dict[self.flow_id][-1]) self.app.set_timer(2, self.autoplay) if self.mjai_msg_idx < len(self.app.mjai_msg_dict[self.flow_id]): + self.app.mjai_msg_dict[self.flow_id][-1]['meta'] = meta_to_recommend(self.app.mjai_msg_dict[self.flow_id][-1]['meta']) + latest_mjai_msg = self.app.mjai_msg_dict[self.flow_id][-1] + # Update tehai player_state = self.app.bridge[self.flow_id].mjai_client.bot.state() 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]) self.tsumohai_label.update(TILE_2_UNICODE_ART_RICH[tsumohai]) - latest_mjai_msg = self.app.mjai_msg_dict[self.flow_id][-1] - self.app.mjai_msg_dict[self.flow_id][-1]['meta'] = meta_to_recommend(latest_mjai_msg['meta']) + 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]) + # mjai log self.mjai_log.update(self.app.mjai_msg_dict[self.flow_id]) self.mjai_log_container.scroll_end() self.mjai_msg_idx += 1 From d44da29a068eed0e1a5922413ea47a1696636c6a Mon Sep 17 00:00:00 2001 From: shinkuan Date: Mon, 26 Feb 2024 22:07:21 +0800 Subject: [PATCH 13/30] Move liqi --- liqi.py | 7 ++----- {proto => liqi_proto}/liqi.json | 0 {proto => liqi_proto}/liqi.proto | 0 {proto => liqi_proto}/liqi_pb2.py | 0 4 files changed, 2 insertions(+), 5 deletions(-) rename {proto => liqi_proto}/liqi.json (100%) rename {proto => liqi_proto}/liqi.proto (100%) rename {proto => liqi_proto}/liqi_pb2.py (100%) diff --git a/liqi.py b/liqi.py index 5694e44..8447c78 100644 --- a/liqi.py +++ b/liqi.py @@ -7,10 +7,7 @@ from google.protobuf.json_format import MessageToDict, ParseDict -try: - from .proto import liqi_pb2 as pb -except: - from proto import liqi_pb2 as pb +from liqi_proto import liqi_pb2 as pb from rich.console import Console console = Console() @@ -48,7 +45,7 @@ def __init__(self): self.tot = 0 self.res_type = dict() self.jsonProto = json.load( - open(os.path.join(os.path.dirname(__file__), 'proto/liqi.json'), 'r')) + open(os.path.join(os.path.dirname(__file__), 'liqi_proto/liqi.json'), 'r')) def init(self): self.msg_id = 1 diff --git a/proto/liqi.json b/liqi_proto/liqi.json similarity index 100% rename from proto/liqi.json rename to liqi_proto/liqi.json diff --git a/proto/liqi.proto b/liqi_proto/liqi.proto similarity index 100% rename from proto/liqi.proto rename to liqi_proto/liqi.proto diff --git a/proto/liqi_pb2.py b/liqi_proto/liqi_pb2.py similarity index 100% rename from proto/liqi_pb2.py rename to liqi_proto/liqi_pb2.py From 0b8714b4c785d90b259f34cbce6808f0f8abb55f Mon Sep 17 00:00:00 2001 From: shinkuan Date: Mon, 26 Feb 2024 22:07:54 +0800 Subject: [PATCH 14/30] hai_value --- tileUnicode.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/tileUnicode.py b/tileUnicode.py index 212d048..696395f 100644 --- a/tileUnicode.py +++ b/tileUnicode.py @@ -487,7 +487,7 @@ │ │ ╰──────╯""", '?': - """[bold gray]╭─────╮ + """[bold]╭─────╮ │ ┏━┓ │ │ ┏┛ │ │ ● │ @@ -498,4 +498,49 @@ ║ ║ ║ -║""" \ No newline at end of file +║""" + +#▁▂▃▄▅▆▇█ +HAI_VALUE = [ +'[red]\n \n \n \n▁', +'[red]\n \n \n \n▂', +'[red]\n \n \n \n▃', +'[red]\n \n \n \n▄', # ~ 10% +'[yellow]\n \n \n \n▅', +'[yellow]\n \n \n \n▆', +'[yellow]\n \n \n \n▇', +'[yellow]\n \n \n \n█', # ~ 20% +'[yellow]\n \n \n▁\n█', +'[yellow]\n \n \n▂\n█', +'[yellow]\n \n \n▃\n█', +'[yellow]\n \n \n▄\n█', # ~ 30% +'[yellow]\n \n \n▅\n█', +'[yellow]\n \n \n▆\n█', +'[yellow]\n \n \n▇\n█', +'[yellow]\n \n \n█\n█', # ~ 40% +'[green]\n \n▁\n█\n█', +'[green]\n \n▂\n█\n█', +'[green]\n \n▃\n█\n█', +'[green]\n \n▄\n█\n█', # ~ 50% +'[green]\n \n▅\n█\n█', +'[green]\n \n▆\n█\n█', +'[green]\n \n▇\n█\n█', +'[green]\n \n█\n█\n█', # ~ 60% +'[green]\n▁\n█\n█\n█', +'[green]\n▂\n█\n█\n█', +'[green]\n▃\n█\n█\n█', +'[green]\n▄\n█\n█\n█', # ~ 70% +'[green]\n▅\n█\n█\n█', +'[green]\n▆\n█\n█\n█', +'[green]\n▇\n█\n█\n█', +'[green]\n█\n█\n█\n█', # ~ 80% +'[blue]▁\n█\n█\n█\n█', +'[blue]▂\n█\n█\n█\n█', +'[blue]▃\n█\n█\n█\n█', +'[blue]▄\n█\n█\n█\n█', # ~ 90% +'[blue]▅\n█\n█\n█\n█', +'[blue]▆\n█\n█\n█\n█', +'[blue]▇\n█\n█\n█\n█', +'[blue]█\n█\n█\n█\n█', # ~ 100% +'' # none +] \ No newline at end of file From e8d491fb8f0628adfc5ba10ceaf0ff71c7b352a8 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Tue, 27 Feb 2024 14:25:25 +0800 Subject: [PATCH 15/30] Add my_logger module, log game result. --- client.py | 15 +++------------ majsoul2mjai.py | 18 +++++++++++++++++- mjai/bot/bot.py | 6 +++++- my_logger.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 my_logger.py diff --git a/client.py b/client.py index c4ce83a..a367321 100644 --- a/client.py +++ b/client.py @@ -6,14 +6,13 @@ import sys import time import webbrowser -from concurrent.futures import ThreadPoolExecutor from pathlib import Path from sys import executable from threading import Thread from typing import Any, Coroutine from xmlrpc.client import ServerProxy -from loguru import logger +from my_logger import logger, game_result_log from textual import on from textual.app import App, ComposeResult from textual.containers import Horizontal, ScrollableContainer, Vertical @@ -29,9 +28,7 @@ from majsoul2mjai import MajsoulBridge from libriichi_helper import meta_to_recommend, state_to_tehai from tileUnicode import TILE_2_UNICODE_ART_RICH, TILE_2_UNICODE, VERTICLE_RULE, HAI_VALUE -from action import Action -os.environ["LOGURU_AUTOINIT"] = "False" submission = 'players/bot.zip' PORT_NUM = 28680 @@ -435,7 +432,6 @@ def __init__(self, rpc_server, *args, **kwargs) -> None: self.liqi_msg_dict = dict() # flow.id -> List[liqi_msg] self.mjai_msg_dict = dict() # flow.id -> List[mjai_msg] self.akagi_log_dict = dict() # flow.id -> List[akagi_log] - self.loguru_log = [] # List[loguru_log] self.mitm_started = False def on_mount(self) -> None: @@ -457,6 +453,8 @@ def refresh_flow(self) -> None: self.liqi_msg_dict.pop(flow_id) self.mjai_msg_dict.pop(flow_id) self.akagi_log_dict.pop(flow_id) + self.liqi.pop(flow_id) + self.bridge.pop(flow_id) for flow_id in flows: try: self.query_one("#FlowContainer") @@ -525,10 +523,6 @@ def mitm_connected(self): except: self.set_timer(2, self.mitm_connected) - def my_sink(self, message) -> None: - record = message.record - self.loguru_log.append(f"{record['time'].strftime('%H:%M:%S')} | {record['level'].name}\t | {record['message']}") - def action_quit(self) -> None: self.update_flow.stop() self.get_messages_flow.stop() @@ -564,10 +558,7 @@ def start_mitm(): rpc_port = settings["Port"]["XMLRPC"] rpc_host = "127.0.0.1" s = ServerProxy(f"http://{rpc_host}:{rpc_port}", allow_none=True) - logger.level("CLICK", no=10, icon="CLICK") - logger.add("akagi.log") app = Akagi(rpc_server=s) - logger.add(app.my_sink) atexit.register(exit_handler) try: logger.info("Start Akagi") diff --git a/majsoul2mjai.py b/majsoul2mjai.py index 25020fb..9166063 100644 --- a/majsoul2mjai.py +++ b/majsoul2mjai.py @@ -6,7 +6,7 @@ from convert import MS_TILE_2_MJAI_TILE, MJAI_TILE_2_MS_TILE from liqi import LiqiProto from functools import cmp_to_key -from loguru import logger +from my_logger import logger, game_result_log class Operation: NoEffect = 0 @@ -47,6 +47,10 @@ def __init__(self) -> None: self.my_tsumohai = "?" self.syncing = False + self.mode_id = -1 + self.rank = -1 + self.score = -1 + self.mjai_client = MjaiPlayerClient() self.is_3p = False pass @@ -76,6 +80,10 @@ def input(self, parse_msg: dict) -> dict | None: self.accountId = parse_msg['data']['accountId'] if parse_msg['method'] == '.lq.FastTest.authGame' and parse_msg['type'] == MsgType.Res: self.is_3p = len(parse_msg['data']['seatList']) == 3 + try: + self.mode_id = parse_msg['data']['gameConfig']['meta']['modeId'] + except: + self.mode_id = -1 seatList = parse_msg['data']['seatList'] self.seat = seatList.index(self.accountId) @@ -359,6 +367,14 @@ def input(self, parse_msg: dict) -> dict | None: return self.react(self.mjai_client) # end_game if parse_msg['method'] == '.lq.NotifyGameEndResult' or parse_msg['method'] == '.lq.NotifyGameTerminate': + try: + for idx, player in enumerate(parse_msg['data']['result']['players']): + if player['seat'] == self.seat: + self.rank = idx + 1 + self.score = player['partPoint1'] + game_result_log(self.mode_id, self.rank, self.score, self.mjai_client.bot.model_hash) + except: + pass self.mjai_message.append( { 'type': 'end_game' diff --git a/mjai/bot/bot.py b/mjai/bot/bot.py index 5a5a25c..a1408a8 100644 --- a/mjai/bot/bot.py +++ b/mjai/bot/bot.py @@ -1,5 +1,6 @@ import json import sys +import hashlib from loguru import logger @@ -9,12 +10,14 @@ class Bot: def __init__(self, player_id: int): self.player_id = player_id + model_path = "./mortal.pth" self.model = model.load_model(player_id) + with open(model_path, "rb") as f: + self.model_hash = hashlib.sha256(f.read()).hexdigest() def react(self, events: str) -> str: events = json.loads(events) - # logger.info("hi") return_action = None for e in events: return_action = self.model.react(json.dumps(e, separators=(",", ":"))) @@ -27,6 +30,7 @@ def react(self, events: str) -> str: def state(self): return self.model.state + def main(): player_id = int(sys.argv[1]) diff --git a/my_logger.py b/my_logger.py new file mode 100644 index 0000000..076699b --- /dev/null +++ b/my_logger.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import os +os.environ["LOGURU_AUTOINIT"] = "False" + +# import google.cloud.logging +# from google.auth._default import load_credentials_from_dict +# from google.cloud.logging_v2.handlers import CloudLoggingHandler + +import json +import requests +import logging +import loguru +from loguru import logger + +def my_sink(message: loguru.Message) -> None: + record = message.record + +logger.level("CLICK", no=10, icon="CLICK") +logger.add("akagi.log") +# logger.add(my_sink) + + +# def cloud_sink(message: loguru.Message) -> None: +# record = message.record +# _cloud_logger.log(message, resource={"type":"global", "labels":{"project_id":"akagi-327709"}}) + +# # Do not abuse this. +# res = requests.get("logging-key.json", allow_redirects=True) +# json_data = json.loads(res.content) +# credentials, project = load_credentials_from_dict(json_data) +# client = google.cloud.logging.Client(credentials=credentials) +# _cloud_logger = client.logger("akagi") + +# logger.level("CLOUD", no=10, icon="CLOUD") +# cloud_logger_id = logger.add(cloud_sink, level="CLOUD", format="{message}", filter=lambda record: "cloud" in record["extra"]) +# cloud_logger = logger.bind(cloud="google") + +def game_result_log(mode_id: int, rank: int, score: int, model_hash: str) -> None: + game_result = { + "mode_id": mode_id, + "rank": rank, + "score": score, + "model_hash": model_hash + } + with open("game_result.log", "a") as game_result_logger: + game_result_logger.write(json.dumps(game_result) + "\n") From de3df0058f05aa22fbeb714b0724d054fbe272a6 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Wed, 28 Feb 2024 14:56:33 +0800 Subject: [PATCH 16/30] resver update --- resver.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resver.json b/resver.json index d1b0d92..cc21344 100644 --- a/resver.json +++ b/resver.json @@ -1 +1 @@ -{"version": "0.11.24.w", "emotes": {"200001": [10, 11, 12, 14, 15, 16, 17, 18, 888, 966, 983, 984, 996, 997], "200002": [10, 11, 12, 14, 15, 16, 17, 18, 888, 995, 998], "200003": [10, 11, 12, 14, 15, 16, 17, 18, 888, 959, 979, 999], "200004": [10, 11, 12, 14, 15, 16, 17, 18, 888, 957, 986, 994, 999], "200005": [10, 11, 12, 14, 15, 16, 17, 18, 888, 968, 989, 997], "200006": [10, 11, 12, 14, 15, 16, 17, 18, 888, 961, 977, 997], "200007": [10, 11, 12, 14, 15, 16, 17, 18, 888, 961, 970, 976, 993, 999], "200008": [10, 11, 12, 14, 15, 16, 17, 18, 888, 972, 991, 997], "200009": [10, 11, 12, 14, 15, 16, 17, 18, 888, 966, 994, 998], "200010": [10, 11, 12, 14, 15, 16, 17, 18, 888, 965, 982], "200011": [10, 11, 12, 14, 15, 16, 17, 18, 888, 970, 977, 998], "200012": [10, 11, 12, 14, 15, 16, 17, 18, 888, 959, 998], "200013": [10, 11, 12, 14, 15, 16, 17, 18, 888, 966, 988, 999], "200014": [10, 11, 12, 14, 15, 16, 17, 18, 888, 969, 982, 993], "200015": [10, 11, 12, 14, 15, 16, 17, 18, 888, 994], "200016": [10, 11, 12, 14, 15, 16, 17, 18, 888, 961, 970, 988, 996], "200017": [10, 11, 12, 14, 15, 16, 17, 18, 888, 962, 982, 995], "200018": [9, 10, 11, 12, 14, 15, 16, 17, 18, 888, 962, 994], "200019": [9, 10, 11, 12, 14, 15, 16, 17, 18, 888, 972, 981, 986], "200020": [10, 11, 12, 14, 888, 967, 988, 994], "200021": [10, 11, 12, 14, 888, 954, 981, 985], "200022": [10, 11, 12, 14, 888, 991], "200023": [10, 11, 12, 14, 888, 955, 981], "200024": [10, 11, 12, 14, 888, 960, 976, 992], "200025": [10, 11, 12, 14, 888, 957, 982], "200026": [10, 11, 12, 14, 888, 973, 989], "200027": [10, 11, 12, 14, 888, 968, 988], "200028": [10, 11, 12, 14, 888, 971, 991], "200029": [10, 11, 12, 14, 888, 964, 976, 992], "200030": [10, 11, 12, 14, 888, 955, 979], "200031": [10, 11, 12, 14, 888, 959, 978], "200032": [10, 11, 12, 14, 888, 957, 976, 985], "200033": [10, 11, 12, 14, 888, 969], "200034": [10, 11, 12, 14, 969, 990], "200035": [10, 11, 12, 14, 969, 990], "200036": [10, 11, 12, 14, 969, 990], "200037": [10, 11, 12, 14, 969, 990], "200038": [10, 11, 12, 14, 888, 959, 967, 974], "200039": [10, 11, 12, 14, 888, 961, 970], "200040": [10, 11, 12, 14, 987], "200041": [10, 11, 12, 14, 987], "200042": [10, 11, 12, 14, 987], "200043": [10, 11, 12, 14, 987], "200044": [10, 11, 12, 14, 888, 960, 978], "200045": [10, 11, 12, 14, 888, 973, 981], "200046": [10, 11, 12, 14, 888, 967], "200047": [10, 11, 12, 14, 888, 957, 967], "200048": [10, 11, 12, 14, 888, 965, 971], "200049": [10, 11, 12, 14, 888, 954, 964], "200050": [10, 11, 12, 14, 980], "200051": [10, 11, 12, 14, 980], "200052": [10, 11, 12, 14, 888], "200053": [10, 11, 12, 14, 888, 958], "200054": [10, 11, 12, 14, 888, 974], "200055": [10, 11, 12, 14, 975], "200056": [10, 11, 12, 14, 975], "200057": [10, 11, 12, 14, 975], "200058": [10, 11, 12, 14, 975], "200059": [10, 11, 12, 14, 888, 955, 960], "200060": [10, 11, 12, 14, 888, 958], "200061": [10, 11, 12, 14, 888], "200062": [10, 11, 12, 14, 969], "200063": [10, 11, 12, 14, 969], "200064": [10, 11, 12, 14, 969], "200065": [10, 11, 12, 14, 969], "200066": [10, 11, 12, 14, 888, 955, 962], "200067": [10, 11, 12, 14, 888, 954], "200068": [10, 11, 12, 14, 888, 955], "200069": [10, 11, 12, 14, 888], "200070": [10, 11, 12, 14, 963], "200071": [10, 11, 12, 14, 963], "200072": [10, 11, 12, 14, 963], "200073": [10, 11, 12, 14, 963], "200074": [10, 11, 12, 14, 888], "200075": [10, 11, 12, 14, 888], "200076": [10, 11, 12, 14, 888], "200077": [10, 11, 12, 14, 888], "200078": [10, 11, 12, 14, 888], "200079": [10, 11, 12, 956], "200080": [10, 11, 12, 956], "200081": [10, 11, 12, 956], "200082": [10, 11, 12, 956], "200083": [10, 11, 12, 888], "200084": [10, 11, 12, 888], "200085": [10, 11, 12, 888], "200090": [10, 11, 12, 888]}} \ No newline at end of file +{"version": "0.11.27.w", "emotes": {"200001": [10, 11, 12, 14, 15, 16, 17, 18, 888, 966, 983, 984, 996, 997], "200002": [10, 11, 12, 14, 15, 16, 17, 18, 888, 995, 998], "200003": [10, 11, 12, 14, 15, 16, 17, 18, 888, 959, 979, 999], "200004": [10, 11, 12, 14, 15, 16, 17, 18, 888, 957, 986, 994, 999], "200005": [10, 11, 12, 14, 15, 16, 17, 18, 888, 968, 989, 997], "200006": [10, 11, 12, 14, 15, 16, 17, 18, 888, 961, 977, 997], "200007": [10, 11, 12, 14, 15, 16, 17, 18, 888, 961, 970, 976, 993, 999], "200008": [10, 11, 12, 14, 15, 16, 17, 18, 888, 972, 991, 997], "200009": [10, 11, 12, 14, 15, 16, 17, 18, 888, 966, 994, 998], "200010": [10, 11, 12, 14, 15, 16, 17, 18, 888, 965, 982], "200011": [10, 11, 12, 14, 15, 16, 17, 18, 888, 970, 977, 998], "200012": [10, 11, 12, 14, 15, 16, 17, 18, 888, 959, 998], "200013": [10, 11, 12, 14, 15, 16, 17, 18, 888, 966, 988, 999], "200014": [10, 11, 12, 14, 15, 16, 17, 18, 888, 969, 982, 993], "200015": [10, 11, 12, 14, 15, 16, 17, 18, 888, 994], "200016": [10, 11, 12, 14, 15, 16, 17, 18, 888, 961, 970, 988, 996], "200017": [10, 11, 12, 14, 15, 16, 17, 18, 888, 962, 982, 995], "200018": [9, 10, 11, 12, 14, 15, 16, 17, 18, 888, 962, 994], "200019": [9, 10, 11, 12, 14, 15, 16, 17, 18, 888, 972, 981, 986], "200020": [10, 11, 12, 14, 888, 967, 988, 994], "200021": [10, 11, 12, 14, 888, 954, 981, 985], "200022": [10, 11, 12, 14, 888, 991], "200023": [10, 11, 12, 14, 888, 955, 981], "200024": [10, 11, 12, 14, 888, 960, 976, 992], "200025": [10, 11, 12, 14, 888, 957, 982], "200026": [10, 11, 12, 14, 888, 973, 989], "200027": [10, 11, 12, 14, 888, 968, 988], "200028": [10, 11, 12, 14, 888, 971, 991], "200029": [10, 11, 12, 14, 888, 964, 976, 992], "200030": [10, 11, 12, 14, 888, 955, 979], "200031": [10, 11, 12, 14, 888, 959, 978], "200032": [10, 11, 12, 14, 888, 957, 976, 985], "200033": [10, 11, 12, 14, 888, 969], "200034": [10, 11, 12, 14, 969, 990], "200035": [10, 11, 12, 14, 969, 990], "200036": [10, 11, 12, 14, 969, 990], "200037": [10, 11, 12, 14, 969, 990], "200038": [10, 11, 12, 14, 888, 959, 967, 974], "200039": [10, 11, 12, 14, 888, 961, 970], "200040": [10, 11, 12, 14, 987], "200041": [10, 11, 12, 14, 987], "200042": [10, 11, 12, 14, 987], "200043": [10, 11, 12, 14, 987], "200044": [10, 11, 12, 14, 888, 960, 978], "200045": [10, 11, 12, 14, 888, 973, 981], "200046": [10, 11, 12, 14, 888, 967], "200047": [10, 11, 12, 14, 888, 957, 967], "200048": [10, 11, 12, 14, 888, 965, 971], "200049": [10, 11, 12, 14, 888, 954, 964], "200050": [10, 11, 12, 14, 980], "200051": [10, 11, 12, 14, 980], "200052": [10, 11, 12, 14, 888], "200053": [10, 11, 12, 14, 888, 958], "200054": [10, 11, 12, 14, 888, 953, 974], "200055": [10, 11, 12, 14, 975], "200056": [10, 11, 12, 14, 975], "200057": [10, 11, 12, 14, 975], "200058": [10, 11, 12, 14, 975], "200059": [10, 11, 12, 14, 888, 955, 960], "200060": [10, 11, 12, 14, 888, 958], "200061": [10, 11, 12, 14, 888], "200062": [10, 11, 12, 14, 969], "200063": [10, 11, 12, 14, 969], "200064": [10, 11, 12, 14, 969], "200065": [10, 11, 12, 14, 969], "200066": [10, 11, 12, 14, 888, 955, 962], "200067": [10, 11, 12, 14, 888, 954], "200068": [10, 11, 12, 14, 888, 955], "200069": [10, 11, 12, 14, 888, 953], "200070": [10, 11, 12, 14, 963], "200071": [10, 11, 12, 14, 963], "200072": [10, 11, 12, 14, 963], "200073": [10, 11, 12, 14, 963], "200074": [10, 11, 12, 14, 888], "200075": [10, 11, 12, 14, 888], "200076": [10, 11, 12, 14, 888], "200077": [10, 11, 12, 14, 888, 953], "200078": [10, 11, 12, 14, 888], "200079": [10, 11, 12, 956], "200080": [10, 11, 12, 956], "200081": [10, 11, 12, 956], "200082": [10, 11, 12, 956], "200083": [10, 11, 12, 888], "200084": [10, 11, 12, 888], "200085": [10, 11, 12, 888], "200090": [10, 11, 12, 888]}} \ No newline at end of file From 96c21a99d1fb1e4e994ca68049274ab4d0caa2c0 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Wed, 28 Feb 2024 14:56:59 +0800 Subject: [PATCH 17/30] Disabe safe_code.js when unlocker is false --- mitm.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mitm.py b/mitm.py index 1dbaccb..05b2a46 100644 --- a/mitm.py +++ b/mitm.py @@ -69,10 +69,10 @@ async def start_proxy(host, port, enable_unlocker): with_dumper=False, ) master.addons.add(ClientWebSocket()) - master.addons.add(ClientHTTP()) - # if enable_unlocker: - from mhm.addons import WebSocketAddon as Unlocker - master.addons.add(Unlocker()) + if enable_unlocker: + master.addons.add(ClientHTTP()) + from mhm.addons import WebSocketAddon as Unlocker + master.addons.add(Unlocker()) await master.run() return master From bdabd2df95533ec2844c66eb348669100b5d517b Mon Sep 17 00:00:00 2001 From: shinkuan Date: Wed, 28 Feb 2024 14:57:22 +0800 Subject: [PATCH 18/30] aliyun logger --- mjai/bot/bot.py | 3 ++- my_logger.py | 35 +++++++++++++++-------------------- requirement.txt | 1 + 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/mjai/bot/bot.py b/mjai/bot/bot.py index a1408a8..fac8953 100644 --- a/mjai/bot/bot.py +++ b/mjai/bot/bot.py @@ -1,6 +1,7 @@ import json import sys import hashlib +import pathlib from loguru import logger @@ -10,7 +11,7 @@ class Bot: def __init__(self, player_id: int): self.player_id = player_id - model_path = "./mortal.pth" + model_path = pathlib.Path(__file__).parent / f"mortal.pth" self.model = model.load_model(player_id) with open(model_path, "rb") as f: self.model_hash = hashlib.sha256(f.read()).hexdigest() diff --git a/my_logger.py b/my_logger.py index 076699b..140a5ce 100644 --- a/my_logger.py +++ b/my_logger.py @@ -3,15 +3,12 @@ import os os.environ["LOGURU_AUTOINIT"] = "False" -# import google.cloud.logging -# from google.auth._default import load_credentials_from_dict -# from google.cloud.logging_v2.handlers import CloudLoggingHandler - import json import requests import logging import loguru from loguru import logger +from aliyun.log.logger_hanlder import QueuedLogHandler, LogFields def my_sink(message: loguru.Message) -> None: record = message.record @@ -20,28 +17,26 @@ def my_sink(message: loguru.Message) -> None: logger.add("akagi.log") # logger.add(my_sink) +RECORD_LOG_FIELDS = set((LogFields.record_name, LogFields.level)) +res = requests.get("https://cdn.jsdelivr.net/gh/shinkuan/RandomStuff/aliyun_log_handler_arg.json", allow_redirects=True) +json_data = json.loads(res.content) -# def cloud_sink(message: loguru.Message) -> None: -# record = message.record -# _cloud_logger.log(message, resource={"type":"global", "labels":{"project_id":"akagi-327709"}}) - -# # Do not abuse this. -# res = requests.get("logging-key.json", allow_redirects=True) -# json_data = json.loads(res.content) -# credentials, project = load_credentials_from_dict(json_data) -# client = google.cloud.logging.Client(credentials=credentials) -# _cloud_logger = client.logger("akagi") - -# logger.level("CLOUD", no=10, icon="CLOUD") -# cloud_logger_id = logger.add(cloud_sink, level="CLOUD", format="{message}", filter=lambda record: "cloud" in record["extra"]) -# cloud_logger = logger.bind(cloud="google") +handler = QueuedLogHandler( + **json_data, + fields=RECORD_LOG_FIELDS, +) +game_result_logger = logging.getLogger("game_result_log") +game_result_logger.setLevel(logging.INFO) +game_result_logger.addHandler(handler) def game_result_log(mode_id: int, rank: int, score: int, model_hash: str) -> None: + if any((mode_id is None, rank is None, score is None, model_hash is None)): + logger.error("Invalid game result") + return game_result = { "mode_id": mode_id, "rank": rank, "score": score, "model_hash": model_hash } - with open("game_result.log", "a") as game_result_logger: - game_result_logger.write(json.dumps(game_result) + "\n") + game_result_logger.info(game_result) diff --git a/requirement.txt b/requirement.txt index af636d5..9efe5f9 100644 --- a/requirement.txt +++ b/requirement.txt @@ -8,5 +8,6 @@ rich==13.7.0 textual==0.46.0 playwright==1.41.0 torch>=2.2.0 +aliyun-log-python-sdk==0.8.15 --find-links https://github.com/shinkuan/Akagi/releases/expanded_assets/v0.1.0-libriichi riichi>=0.1.1 \ No newline at end of file From 3287c82017b804b97618146fce6ba89a2c6e7359 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Wed, 28 Feb 2024 15:01:00 +0800 Subject: [PATCH 19/30] mistake --- mitm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mitm.py b/mitm.py index 05b2a46..25dae5e 100644 --- a/mitm.py +++ b/mitm.py @@ -71,8 +71,8 @@ async def start_proxy(host, port, enable_unlocker): master.addons.add(ClientWebSocket()) if enable_unlocker: master.addons.add(ClientHTTP()) - from mhm.addons import WebSocketAddon as Unlocker - master.addons.add(Unlocker()) + from mhm.addons import WebSocketAddon as Unlocker + master.addons.add(Unlocker()) await master.run() return master From a5f8e9bf31ecad76c4688a551b298d8f08ed28b4 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Wed, 28 Feb 2024 19:49:19 +0800 Subject: [PATCH 20/30] custom aliyun-log-python-sdk for protobuf v4 --- requirement.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirement.txt b/requirement.txt index 9efe5f9..4706d75 100644 --- a/requirement.txt +++ b/requirement.txt @@ -8,6 +8,6 @@ rich==13.7.0 textual==0.46.0 playwright==1.41.0 torch>=2.2.0 -aliyun-log-python-sdk==0.8.15 +git+https://github.com/shinkuan/aliyun-log-python-sdk --find-links https://github.com/shinkuan/Akagi/releases/expanded_assets/v0.1.0-libriichi riichi>=0.1.1 \ No newline at end of file From 05f122b46b494180ce9c399195a12d8cdb0cd71f Mon Sep 17 00:00:00 2001 From: shinkuan Date: Mon, 4 Mar 2024 19:26:39 +0800 Subject: [PATCH 21/30] settings --- mhmp.json | 4 ++-- resver.json | 2 +- settings.json | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mhmp.json b/mhmp.json index 3c3fe78..ae4a2ed 100644 --- a/mhmp.json +++ b/mhmp.json @@ -4,8 +4,8 @@ "pure_python_protobuf": false }, "hook": { - "enable_skins": true, - "enable_aider": true, + "enable_skins": false, + "enable_aider": false, "enable_chest": false, "random_star_char": false, "no_cheering_emotes": false diff --git a/resver.json b/resver.json index cc21344..4ba492b 100644 --- a/resver.json +++ b/resver.json @@ -1 +1 @@ -{"version": "0.11.27.w", "emotes": {"200001": [10, 11, 12, 14, 15, 16, 17, 18, 888, 966, 983, 984, 996, 997], "200002": [10, 11, 12, 14, 15, 16, 17, 18, 888, 995, 998], "200003": [10, 11, 12, 14, 15, 16, 17, 18, 888, 959, 979, 999], "200004": [10, 11, 12, 14, 15, 16, 17, 18, 888, 957, 986, 994, 999], "200005": [10, 11, 12, 14, 15, 16, 17, 18, 888, 968, 989, 997], "200006": [10, 11, 12, 14, 15, 16, 17, 18, 888, 961, 977, 997], "200007": [10, 11, 12, 14, 15, 16, 17, 18, 888, 961, 970, 976, 993, 999], "200008": [10, 11, 12, 14, 15, 16, 17, 18, 888, 972, 991, 997], "200009": [10, 11, 12, 14, 15, 16, 17, 18, 888, 966, 994, 998], "200010": [10, 11, 12, 14, 15, 16, 17, 18, 888, 965, 982], "200011": [10, 11, 12, 14, 15, 16, 17, 18, 888, 970, 977, 998], "200012": [10, 11, 12, 14, 15, 16, 17, 18, 888, 959, 998], "200013": [10, 11, 12, 14, 15, 16, 17, 18, 888, 966, 988, 999], "200014": [10, 11, 12, 14, 15, 16, 17, 18, 888, 969, 982, 993], "200015": [10, 11, 12, 14, 15, 16, 17, 18, 888, 994], "200016": [10, 11, 12, 14, 15, 16, 17, 18, 888, 961, 970, 988, 996], "200017": [10, 11, 12, 14, 15, 16, 17, 18, 888, 962, 982, 995], "200018": [9, 10, 11, 12, 14, 15, 16, 17, 18, 888, 962, 994], "200019": [9, 10, 11, 12, 14, 15, 16, 17, 18, 888, 972, 981, 986], "200020": [10, 11, 12, 14, 888, 967, 988, 994], "200021": [10, 11, 12, 14, 888, 954, 981, 985], "200022": [10, 11, 12, 14, 888, 991], "200023": [10, 11, 12, 14, 888, 955, 981], "200024": [10, 11, 12, 14, 888, 960, 976, 992], "200025": [10, 11, 12, 14, 888, 957, 982], "200026": [10, 11, 12, 14, 888, 973, 989], "200027": [10, 11, 12, 14, 888, 968, 988], "200028": [10, 11, 12, 14, 888, 971, 991], "200029": [10, 11, 12, 14, 888, 964, 976, 992], "200030": [10, 11, 12, 14, 888, 955, 979], "200031": [10, 11, 12, 14, 888, 959, 978], "200032": [10, 11, 12, 14, 888, 957, 976, 985], "200033": [10, 11, 12, 14, 888, 969], "200034": [10, 11, 12, 14, 969, 990], "200035": [10, 11, 12, 14, 969, 990], "200036": [10, 11, 12, 14, 969, 990], "200037": [10, 11, 12, 14, 969, 990], "200038": [10, 11, 12, 14, 888, 959, 967, 974], "200039": [10, 11, 12, 14, 888, 961, 970], "200040": [10, 11, 12, 14, 987], "200041": [10, 11, 12, 14, 987], "200042": [10, 11, 12, 14, 987], "200043": [10, 11, 12, 14, 987], "200044": [10, 11, 12, 14, 888, 960, 978], "200045": [10, 11, 12, 14, 888, 973, 981], "200046": [10, 11, 12, 14, 888, 967], "200047": [10, 11, 12, 14, 888, 957, 967], "200048": [10, 11, 12, 14, 888, 965, 971], "200049": [10, 11, 12, 14, 888, 954, 964], "200050": [10, 11, 12, 14, 980], "200051": [10, 11, 12, 14, 980], "200052": [10, 11, 12, 14, 888], "200053": [10, 11, 12, 14, 888, 958], "200054": [10, 11, 12, 14, 888, 953, 974], "200055": [10, 11, 12, 14, 975], "200056": [10, 11, 12, 14, 975], "200057": [10, 11, 12, 14, 975], "200058": [10, 11, 12, 14, 975], "200059": [10, 11, 12, 14, 888, 955, 960], "200060": [10, 11, 12, 14, 888, 958], "200061": [10, 11, 12, 14, 888], "200062": [10, 11, 12, 14, 969], "200063": [10, 11, 12, 14, 969], "200064": [10, 11, 12, 14, 969], "200065": [10, 11, 12, 14, 969], "200066": [10, 11, 12, 14, 888, 955, 962], "200067": [10, 11, 12, 14, 888, 954], "200068": [10, 11, 12, 14, 888, 955], "200069": [10, 11, 12, 14, 888, 953], "200070": [10, 11, 12, 14, 963], "200071": [10, 11, 12, 14, 963], "200072": [10, 11, 12, 14, 963], "200073": [10, 11, 12, 14, 963], "200074": [10, 11, 12, 14, 888], "200075": [10, 11, 12, 14, 888], "200076": [10, 11, 12, 14, 888], "200077": [10, 11, 12, 14, 888, 953], "200078": [10, 11, 12, 14, 888], "200079": [10, 11, 12, 956], "200080": [10, 11, 12, 956], "200081": [10, 11, 12, 956], "200082": [10, 11, 12, 956], "200083": [10, 11, 12, 888], "200084": [10, 11, 12, 888], "200085": [10, 11, 12, 888], "200090": [10, 11, 12, 888]}} \ No newline at end of file +{"version": "0.11.28.w", "emotes": {"200001": [10, 11, 12, 14, 15, 16, 17, 18, 888, 966, 983, 984, 996, 997], "200002": [10, 11, 12, 14, 15, 16, 17, 18, 888, 995, 998], "200003": [10, 11, 12, 14, 15, 16, 17, 18, 888, 959, 979, 999], "200004": [10, 11, 12, 14, 15, 16, 17, 18, 888, 957, 986, 994, 999], "200005": [10, 11, 12, 14, 15, 16, 17, 18, 888, 968, 989, 997], "200006": [10, 11, 12, 14, 15, 16, 17, 18, 888, 961, 977, 997], "200007": [10, 11, 12, 14, 15, 16, 17, 18, 888, 961, 970, 976, 993, 999], "200008": [10, 11, 12, 14, 15, 16, 17, 18, 888, 972, 991, 997], "200009": [10, 11, 12, 14, 15, 16, 17, 18, 888, 966, 994, 998], "200010": [10, 11, 12, 14, 15, 16, 17, 18, 888, 965, 982], "200011": [10, 11, 12, 14, 15, 16, 17, 18, 888, 970, 977, 998], "200012": [10, 11, 12, 14, 15, 16, 17, 18, 888, 959, 998], "200013": [10, 11, 12, 14, 15, 16, 17, 18, 888, 966, 988, 999], "200014": [10, 11, 12, 14, 15, 16, 17, 18, 888, 969, 982, 993], "200015": [10, 11, 12, 14, 15, 16, 17, 18, 888, 994], "200016": [10, 11, 12, 14, 15, 16, 17, 18, 888, 961, 970, 988, 996], "200017": [10, 11, 12, 14, 15, 16, 17, 18, 888, 962, 982, 995], "200018": [9, 10, 11, 12, 14, 15, 16, 17, 18, 888, 962, 994], "200019": [9, 10, 11, 12, 14, 15, 16, 17, 18, 888, 972, 981, 986], "200020": [10, 11, 12, 14, 888, 967, 988, 994], "200021": [10, 11, 12, 14, 888, 954, 981, 985], "200022": [10, 11, 12, 14, 888, 991], "200023": [10, 11, 12, 14, 888, 955, 981], "200024": [10, 11, 12, 14, 888, 960, 976, 992], "200025": [10, 11, 12, 14, 888, 957, 982], "200026": [10, 11, 12, 14, 888, 973, 989], "200027": [10, 11, 12, 14, 888, 968, 988], "200028": [10, 11, 12, 14, 888, 971, 991], "200029": [10, 11, 12, 14, 888, 964, 976, 992], "200030": [10, 11, 12, 14, 888, 955, 979], "200031": [10, 11, 12, 14, 888, 959, 978], "200032": [10, 11, 12, 14, 888, 957, 976, 985], "200033": [10, 11, 12, 14, 888, 969], "200034": [10, 11, 12, 14, 969, 990], "200035": [10, 11, 12, 14, 969, 990], "200036": [10, 11, 12, 14, 969, 990], "200037": [10, 11, 12, 14, 969, 990], "200038": [10, 11, 12, 14, 888, 959, 967, 974], "200039": [10, 11, 12, 14, 888, 961, 970], "200040": [10, 11, 12, 14, 987], "200041": [10, 11, 12, 14, 987], "200042": [10, 11, 12, 14, 987], "200043": [10, 11, 12, 14, 987], "200044": [10, 11, 12, 14, 888, 960, 978], "200045": [10, 11, 12, 14, 888, 973, 981], "200046": [10, 11, 12, 14, 888, 967], "200047": [10, 11, 12, 14, 888, 957, 967], "200048": [10, 11, 12, 14, 888, 965, 971], "200049": [10, 11, 12, 14, 888, 954, 964], "200050": [10, 11, 12, 14, 980], "200051": [10, 11, 12, 14, 980], "200052": [10, 11, 12, 14, 888], "200053": [10, 11, 12, 14, 888, 958], "200054": [10, 11, 12, 14, 888, 953, 974], "200055": [10, 11, 12, 14, 975], "200056": [10, 11, 12, 14, 975], "200057": [10, 11, 12, 14, 975], "200058": [10, 11, 12, 14, 975], "200059": [10, 11, 12, 14, 888, 955, 960], "200060": [10, 11, 12, 14, 888, 958], "200061": [10, 11, 12, 14, 888], "200062": [10, 11, 12, 14, 969], "200063": [10, 11, 12, 14, 969], "200064": [10, 11, 12, 14, 969], "200065": [10, 11, 12, 14, 969], "200066": [10, 11, 12, 14, 888, 955, 962], "200067": [10, 11, 12, 14, 888, 954], "200068": [10, 11, 12, 14, 888, 955], "200069": [10, 11, 12, 14, 888, 953], "200070": [10, 11, 12, 14, 963], "200071": [10, 11, 12, 14, 963], "200072": [10, 11, 12, 14, 963], "200073": [10, 11, 12, 14, 963], "200074": [10, 11, 12, 14, 888], "200075": [10, 11, 12, 14, 888], "200076": [10, 11, 12, 14, 888], "200077": [10, 11, 12, 14, 888, 953], "200078": [10, 11, 12, 14, 888], "200079": [10, 11, 12, 956], "200080": [10, 11, 12, 956], "200081": [10, 11, 12, 956], "200082": [10, 11, 12, 956], "200083": [10, 11, 12, 888], "200084": [10, 11, 12, 888], "200085": [10, 11, 12, 888], "200090": [10, 11, 12, 888]}} \ No newline at end of file diff --git a/settings.json b/settings.json index 5f7df7b..ea0e9ff 100644 --- a/settings.json +++ b/settings.json @@ -1,8 +1,8 @@ { - "Unlocker": true, + "Unlocker": false, "v10": false, - "Autoplay": true, - "Helper": true, + "Autoplay": false, + "Helper": false, "Autohu": true, "Port": { "MITM": 7878, From 35519b3a1255d3fb2f95c48f4048a45b809ca62f Mon Sep 17 00:00:00 2001 From: shinkuan Date: Mon, 4 Mar 2024 19:26:48 +0800 Subject: [PATCH 22/30] key placeholder --- mjai/bot/key.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 mjai/bot/key.txt diff --git a/mjai/bot/key.txt b/mjai/bot/key.txt new file mode 100644 index 0000000..454f6b3 --- /dev/null +++ b/mjai/bot/key.txt @@ -0,0 +1 @@ +0123456789abcdef \ No newline at end of file From 2d4669d0ad93128f886df8e3b41225c6ffee6e90 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Mon, 4 Mar 2024 19:27:44 +0800 Subject: [PATCH 23/30] R Model --- mjai/bot/model.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/mjai/bot/model.py b/mjai/bot/model.py index f99e46a..214227b 100644 --- a/mjai/bot/model.py +++ b/mjai/bot/model.py @@ -250,6 +250,8 @@ def __init__( boltzmann_epsilon = 0, boltzmann_temp = 1, top_p = 1, + r = False, + key = "", ): self.engine_type = 'mortal' self.device = device or torch.device('cpu') @@ -268,6 +270,8 @@ def __init__( self.boltzmann_epsilon = boltzmann_epsilon self.boltzmann_temp = boltzmann_temp self.top_p = top_p + self.r = r + self.key = key def react_batch(self, obs, masks, invisible_obs): with ( @@ -304,7 +308,6 @@ def _react_batch(self, obs, masks, invisible_obs): else: is_greedy = torch.ones(batch_size, dtype=torch.bool, device=self.device) actions = q_out.argmax(-1) - return actions.tolist(), q_out.tolist(), masks.tolist(), is_greedy.tolist() def sample_top_p(logits, p): @@ -335,9 +338,17 @@ def load_model(seat: int) -> riichi.mjai.Bot: # Get the path of control_state_file = current directory / control_state_file control_state_file = pathlib.Path(__file__).parent / control_state_file + state = torch.load(control_state_file, map_location=device) + key = "" + if 'r' in state['config']: + del state + from . import obfuscated_model + engine = obfuscated_model.IIIIIllllll() + return riichi.mjai.Bot(engine, seat) + else: + r = False - state = torch.load(control_state_file, map_location=device) mortal = Brain(version=state['config']['control']['version'], conv_channels=state['config']['resnet']['conv_channels'], num_blocks=state['config']['resnet']['num_blocks']).eval() dqn = DQN(version=state['config']['control']['version']).eval() mortal.load_state_dict(state['mortal']) @@ -350,9 +361,11 @@ def load_model(seat: int) -> riichi.mjai.Bot: device = device, enable_amp = False, enable_quick_eval = False, - enable_rule_based_agari_guard = True, + enable_rule_based_agari_guard = False, name = 'mortal', - version= state['config']['control']['version'] + version = state['config']['control']['version'], + r = r, + key = key, ) bot = riichi.mjai.Bot(engine, seat) From e65e4df61ca44b72c4e393879032716d64ea69cc Mon Sep 17 00:00:00 2001 From: shinkuan Date: Mon, 4 Mar 2024 21:57:23 +0800 Subject: [PATCH 24/30] requirement.txt --- requirement.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirement.txt b/requirement.txt index 4706d75..faf5229 100644 --- a/requirement.txt +++ b/requirement.txt @@ -7,6 +7,7 @@ protobuf==4.25.1 rich==13.7.0 textual==0.46.0 playwright==1.41.0 +pycryptodome==3.20.0 torch>=2.2.0 git+https://github.com/shinkuan/aliyun-log-python-sdk --find-links https://github.com/shinkuan/Akagi/releases/expanded_assets/v0.1.0-libriichi From c4a2654339f2500f3f30200fd367e645089342aa Mon Sep 17 00:00:00 2001 From: shinkuan Date: Tue, 5 Mar 2024 13:28:42 +0800 Subject: [PATCH 25/30] Autoplay delay --- client.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/client.py b/client.py index a367321..c92bbf1 100644 --- a/client.py +++ b/client.py @@ -114,8 +114,8 @@ def on_mount(self) -> None: self.tsumohai_label = self.query_one("#tsumohai") self.tsumohai_value_label = self.query_one("#tsumohai_value") self.tehai_container = self.query_one("#tehai_container") - self.liqi_log_container.scroll_end() - self.mjai_log_container.scroll_end() + self.liqi_log_container.scroll_end(animate=False) + self.mjai_log_container.scroll_end(animate=False) self.liqi_msg_idx = len(self.app.liqi_msg_dict[self.flow_id]) self.mjai_msg_idx = len(self.app.mjai_msg_dict[self.flow_id]) self.update_log = self.set_interval(0.10, self.refresh_log) @@ -135,7 +135,7 @@ def refresh_log(self) -> None: try: 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() + self.liqi_log_container.scroll_end(animate=False) self.liqi_msg_idx += 1 liqi_msg = self.app.liqi_msg_dict[self.flow_id][-1] if liqi_msg['type'] == MsgType.Notify: @@ -187,7 +187,7 @@ def refresh_log(self) -> None: self.tsumohai_value_label.update(HAI_VALUE[pai_value]) # mjai log self.mjai_log.update(self.app.mjai_msg_dict[self.flow_id]) - self.mjai_log_container.scroll_end() + self.mjai_log_container.scroll_end(animate=False) self.mjai_msg_idx += 1 self.akagi_action.label = latest_mjai_msg["type"] for akagi_action_class in self.akagi_action.classes: @@ -219,10 +219,12 @@ def refresh_log(self) -> None: # Action logger.info(f"Current tehai: {tehai}") logger.info(f"Current tsumohai: {tsumohai}") + self.tehai = tehai + self.tsumohai = tsumohai if not self.syncing and ENABLE_PLAYWRIGHT and AUTOPLAY: logger.log("CLICK", latest_mjai_msg) - # self.app.set_timer(0.05, self.autoplay) - self.autoplay(tehai, tsumohai) + self.app.set_timer(0.15, self.autoplay) + # self.autoplay(tehai, tsumohai) except Exception as e: logger.error(e) @@ -234,8 +236,8 @@ def checkbox_autoplay_changed(self, event: Checkbox.Changed) -> None: AUTOPLAY = event.value pass - def autoplay(self, tehai, tsumohai) -> None: - self.action.mjai2action(self.app.mjai_msg_dict[self.flow_id][-1], tehai, tsumohai) + def autoplay(self) -> None: + self.action.mjai2action(self.app.mjai_msg_dict[self.flow_id][-1], self.tehai, self.tsumohai) pass def action_quit(self) -> None: From 61561fe47a4b69412042719edbde9759f4cfaf58 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Tue, 5 Mar 2024 13:44:50 +0800 Subject: [PATCH 26/30] 0.1.1 riichi --- requirement.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirement.txt b/requirement.txt index faf5229..ad3421a 100644 --- a/requirement.txt +++ b/requirement.txt @@ -10,5 +10,5 @@ playwright==1.41.0 pycryptodome==3.20.0 torch>=2.2.0 git+https://github.com/shinkuan/aliyun-log-python-sdk ---find-links https://github.com/shinkuan/Akagi/releases/expanded_assets/v0.1.0-libriichi +--find-links https://github.com/shinkuan/Akagi/releases/expanded_assets/v0.1.1-libriichi riichi>=0.1.1 \ No newline at end of file From 40ba8c44e1a1555d5d82fd4dae80c7c8f29d11af Mon Sep 17 00:00:00 2001 From: shinkuan Date: Thu, 7 Mar 2024 18:55:44 +0800 Subject: [PATCH 27/30] Only show last 3 mjai log --- client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.py b/client.py index c92bbf1..2e37328 100644 --- a/client.py +++ b/client.py @@ -186,7 +186,7 @@ def refresh_log(self) -> None: pai_value = 40 self.tsumohai_value_label.update(HAI_VALUE[pai_value]) # mjai log - self.mjai_log.update(self.app.mjai_msg_dict[self.flow_id]) + self.mjai_log.update(self.app.mjai_msg_dict[self.flow_id][-3:]) self.mjai_log_container.scroll_end(animate=False) self.mjai_msg_idx += 1 self.akagi_action.label = latest_mjai_msg["type"] From 2fa681964d9a416b602452d5f0399874ea3b43d1 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Thu, 7 Mar 2024 18:56:37 +0800 Subject: [PATCH 28/30] kill the re_err log to protect user. --- mhm/addons.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mhm/addons.py b/mhm/addons.py index 4fcfee8..258878c 100644 --- a/mhm/addons.py +++ b/mhm/addons.py @@ -1,4 +1,6 @@ +import json from mitmproxy import http +from urllib.parse import urlparse, parse_qs from . import logger @@ -47,5 +49,19 @@ def websocket_message(self, flow: http.HTTPFlow): log(self.manager) + def request(self, flow: http.HTTPFlow): + parsed_url = urlparse(flow.request.url) + if parsed_url.hostname == "majsoul-hk-client.cn-hongkong.log.aliyuncs.com": + qs = parse_qs(parsed_url.query) + try: + content = json.loads(qs["content"][0]) + if content["type"] == "re_err": + logger.warning(" ".join(["[i][red]Error", str(qs)])) + flow.kill() + else: + logger.debug(" ".join(["[i][green]Log", str(qs)])) + except: + return + addons = [WebSocketAddon()] From b6f5347a0df5c423ee9a65564361d5ea7199b299 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Thu, 7 Mar 2024 22:30:50 +0800 Subject: [PATCH 29/30] Remove R model --- mjai/bot/key.txt | 1 - mjai/bot/model.py | 15 --------------- 2 files changed, 16 deletions(-) delete mode 100644 mjai/bot/key.txt diff --git a/mjai/bot/key.txt b/mjai/bot/key.txt deleted file mode 100644 index 454f6b3..0000000 --- a/mjai/bot/key.txt +++ /dev/null @@ -1 +0,0 @@ -0123456789abcdef \ No newline at end of file diff --git a/mjai/bot/model.py b/mjai/bot/model.py index 214227b..1d65f24 100644 --- a/mjai/bot/model.py +++ b/mjai/bot/model.py @@ -250,8 +250,6 @@ def __init__( boltzmann_epsilon = 0, boltzmann_temp = 1, top_p = 1, - r = False, - key = "", ): self.engine_type = 'mortal' self.device = device or torch.device('cpu') @@ -270,8 +268,6 @@ def __init__( self.boltzmann_epsilon = boltzmann_epsilon self.boltzmann_temp = boltzmann_temp self.top_p = top_p - self.r = r - self.key = key def react_batch(self, obs, masks, invisible_obs): with ( @@ -340,15 +336,6 @@ def load_model(seat: int) -> riichi.mjai.Bot: control_state_file = pathlib.Path(__file__).parent / control_state_file state = torch.load(control_state_file, map_location=device) - key = "" - if 'r' in state['config']: - del state - from . import obfuscated_model - engine = obfuscated_model.IIIIIllllll() - return riichi.mjai.Bot(engine, seat) - else: - r = False - mortal = Brain(version=state['config']['control']['version'], conv_channels=state['config']['resnet']['conv_channels'], num_blocks=state['config']['resnet']['num_blocks']).eval() dqn = DQN(version=state['config']['control']['version']).eval() mortal.load_state_dict(state['mortal']) @@ -364,8 +351,6 @@ def load_model(seat: int) -> riichi.mjai.Bot: enable_rule_based_agari_guard = False, name = 'mortal', version = state['config']['control']['version'], - r = r, - key = key, ) bot = riichi.mjai.Bot(engine, seat) From 539dd67cd7f094ec5a407b0b90a8fbf6b18001d0 Mon Sep 17 00:00:00 2001 From: shinkuan Date: Thu, 7 Mar 2024 23:13:23 +0800 Subject: [PATCH 30/30] Fix: Cannot find command 'git' --- requirement.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirement.txt b/requirement.txt index ad3421a..8f884d5 100644 --- a/requirement.txt +++ b/requirement.txt @@ -9,6 +9,6 @@ textual==0.46.0 playwright==1.41.0 pycryptodome==3.20.0 torch>=2.2.0 -git+https://github.com/shinkuan/aliyun-log-python-sdk +https://github.com/shinkuan/aliyun-log-python-sdk/archive/refs/heads/master.zip --find-links https://github.com/shinkuan/Akagi/releases/expanded_assets/v0.1.1-libriichi riichi>=0.1.1 \ No newline at end of file