From a344924a16f7c25cc864e4a70eb1506778a39f75 Mon Sep 17 00:00:00 2001
From: Lator <65785354+latorc@users.noreply.github.com>
Date: Thu, 2 May 2024 11:26:41 +0800
Subject: [PATCH 01/21] Update readme.md
---
readme.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/readme.md b/readme.md
index 590164c..027d776 100644
--- a/readme.md
+++ b/readme.md
@@ -1,7 +1,8 @@
# 麻将 Copilot / Mahjong Copilot
麻将 AI 助手,基于 mjai (Mortal模型) 实现的机器人。会对游戏对局的每一步进行指导。现支持雀魂三人、四人麻将。
-QQ群:834105526
+QQ群:834105526
+Join Discord
Mahjong AI Assistant for Majsoul, based on mjai (Mortal model) bot impelementaion. When you are in a Majsoul game, AI will give you step-by-step guidance. Now supports Majsoul 3-person and 4-person game modes.
From c48dd7f6f008fce39642272d7a5dab5b739426f9 Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Mon, 27 May 2024 03:48:04 +0800
Subject: [PATCH 02/21] =?UTF-8?q?=E5=8A=A0=E8=BD=BD=20AkagiOT2=E6=A8=A1?=
=?UTF-8?q?=E5=9E=8B=E7=9A=84=E5=BB=BA=E8=AE=AE=E5=AE=9E=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bot/bot.py | 10 +++++++--
common/lan_str.py | 8 +++++++
common/settings.py | 13 ++++++++++++
gui/settings_window.py | 38 +++++++++++++++++++++++++++++++---
mjai/bot_3p/Put_model.pth_here | 1 +
readme.md | 3 +++
6 files changed, 68 insertions(+), 5 deletions(-)
create mode 100644 mjai/bot_3p/Put_model.pth_here
diff --git a/bot/bot.py b/bot/bot.py
index 9a922a4..cb88759 100644
--- a/bot/bot.py
+++ b/bot/bot.py
@@ -7,6 +7,7 @@
from common.log_helper import LOGGER
from common.mj_helper import meta_to_options, MjaiType
from common.utils import GameMode, BotNotSupportingMode
+from common.settings import Settings
def reaction_convert_meta(reaction:dict, is_3p:bool=False):
@@ -105,8 +106,13 @@ def _init_bot_impl(self, mode:GameMode=GameMode.MJ4P):
import riichi as libriichi
self.mjai_bot = libriichi.mjai.Bot(engine, self.seat)
elif mode == GameMode.MJ3P:
- import libriichi3p
- self.mjai_bot = libriichi3p.mjai.Bot(engine, self.seat)
+ settings = Settings()
+ if settings.enable_ot2_for_3p:
+ import riichi3p
+ self.mjai_bot = riichi3p.online.Bot(self.seat)
+ else:
+ import libriichi3p
+ self.mjai_bot = libriichi3p.mjai.Bot(engine, self.seat)
else:
raise BotNotSupportingMode(mode)
diff --git a/common/lan_str.py b/common/lan_str.py
index 565f627..698d2c2 100644
--- a/common/lan_str.py
+++ b/common/lan_str.py
@@ -50,6 +50,10 @@ class LanStr:
AI_MODEL_FILE_3P = "Local Model File (3P)"
AKAGI_OT_URL = "AkagiOT Server URL"
AKAGI_OT_APIKEY = "AkagiOT API Key"
+ AKAGI_OT2 = "AKAGI OT2"
+ ENABLE_AKAGI_OT2_FOR_3P = "Enable AkagiOT2 for 3P(Only Effective when model type is Local)"
+ AKAGI_OT2_URL = "AkagiOT2 Server URL"
+ AKAGI_OT2_APIKEY = "AkagiOT2 API Key"
MJAPI_URL = "MJAPI Server URL"
MJAPI_USER = "MJAPI User"
MJAPI_USAGE = "API Usage"
@@ -183,6 +187,10 @@ class LanStrZHS(LanStr):
AI_MODEL_FILE_3P = "本地模型文件(三麻)"
AKAGI_OT_URL = "AkagiOT 服务器地址"
AKAGI_OT_APIKEY = "AkagiOT API Key"
+ AKAGI_OT2 = "AKAGI OT2"
+ ENABLE_AKAGI_OT2_FOR_3P = "三麻中启用 AkagiOT2(仅在选择 Local 模型时生效)"
+ AKAGI_OT2_URL = "AkagiOT2 服务器地址"
+ AKAGI_OT2_APIKEY = "AkagiOT2 API Key"
MJAPI_URL = "MJAPI 服务器地址"
MJAPI_USER = "MJAPI 用户名"
MJAPI_USAGE = "API 用量"
diff --git a/common/settings.py b/common/settings.py
index 397a277..770b82c 100644
--- a/common/settings.py
+++ b/common/settings.py
@@ -7,6 +7,7 @@
from . import utils
DEFAULT_SETTING_FILE = 'settings.json'
+DEFAULT_OT2_JSON = 'settings_ot2.json'
class Settings:
""" Settings class to load and save settings to json file"""
@@ -40,6 +41,10 @@ def __init__(self, json_file:str=DEFAULT_SETTING_FILE) -> None:
# akagi ot model
self.akagi_ot_url:str = self._get_value("akagi_ot_url", "")
self.akagi_ot_apikey:str = self._get_value("akagi_ot_apikey", "")
+ # akagi ot2 3p model
+ self.enable_ot2_for_3p:bool = self._get_value("enable_ot2_for_3p", False, self.valid_bool)
+ self.akagi_ot2_url:str = self._get_value("akagi_ot2_url", "")
+ self.akagi_ot2_apikey: str = self._get_value("akagi_ot2_apikey", "")
# for mjapi
self.mjapi_url:str = self._get_value("mjapi_url", "https://mjai.7xcnnw11phu.eu.org", self.valid_url)
self.mjapi_user:str = self._get_value("mjapi_user", "")
@@ -86,6 +91,14 @@ def save_json(self):
if not key.startswith('_') and not callable(value)}
with open(self._json_file, 'w', encoding='utf-8') as file:
json.dump(settings_to_save, file, indent=4, separators=(', ', ': '))
+
+ # Save ot2 related settings to settings_ot2.json
+ ot2_settings = {
+ "ip": self.akagi_ot2_url,
+ "key": self.akagi_ot2_apikey
+ }
+ with open(DEFAULT_OT2_JSON, 'w', encoding='utf-8') as ot2_file:
+ json.dump(ot2_settings, ot2_file, indent=4, separators=(', ', ': '))
def _get_value(self, key:str, default_value:any, validator:Callable[[any],bool]=None) -> any:
""" Get value from settings dictionary, or return default_value if error"""
diff --git a/gui/settings_window.py b/gui/settings_window.py
index d63a81a..e891399 100644
--- a/gui/settings_window.py
+++ b/gui/settings_window.py
@@ -16,8 +16,8 @@ def __init__(self, parent:tk.Frame, setting:Settings):
super().__init__(parent)
self.st = setting
- self.geometry('700x675')
- self.minsize(700,675)
+ self.geometry('800x675')
+ self.minsize(800,800)
# self.resizable(False, False)
# set position: within main window
parent_x = parent.winfo_x()
@@ -151,7 +151,30 @@ def create_widgets(self):
_label.grid(row=cur_row, column=0, **args_label)
self.akagiot_apikey_var = tk.StringVar(value=self.st.akagi_ot_apikey)
string_entry = ttk.Entry(main_frame, textvariable=self.akagiot_apikey_var, width=std_wid*4)
- string_entry.grid(row=cur_row, column=1,columnspan=3, **args_entry)
+ string_entry.grid(row=cur_row, column=1,columnspan=3, **args_entry)
+
+ # Enable Akagi OT2
+ cur_row += 1
+ _label = ttk.Label(main_frame, text=self.st.lan().AKAGI_OT2)
+ _label.grid(row=cur_row, column=0, **args_label)
+ self.enable_ot2_for_3p_var = tk.BooleanVar(value=self.st.enable_ot2_for_3p)
+ enable_akagi_ot2_entry = ttk.Checkbutton(
+ main_frame, variable=self.enable_ot2_for_3p_var, text=self.st.lan().ENABLE_AKAGI_OT2_FOR_3P, width=std_wid*3)
+ enable_akagi_ot2_entry.grid(row=cur_row, column=1, **args_entry)
+ # Akagi OT2 url
+ cur_row += 1
+ _label = ttk.Label(main_frame, text=self.st.lan().AKAGI_OT2_URL)
+ _label.grid(row=cur_row, column=0, **args_label)
+ self.akagiot2_url_var = tk.StringVar(value=self.st.akagi_ot2_url)
+ string_entry = ttk.Entry(main_frame, textvariable=self.akagiot2_url_var, width=std_wid*4)
+ string_entry.grid(row=cur_row, column=1,columnspan=3, **args_entry)
+ # Akagi OT2 API Key
+ cur_row += 1
+ _label = ttk.Label(main_frame, text=self.st.lan().AKAGI_OT2_APIKEY)
+ _label.grid(row=cur_row, column=0, **args_label)
+ self.akagiot2_apikey_var = tk.StringVar(value=self.st.akagi_ot2_apikey)
+ string_entry = ttk.Entry(main_frame, textvariable=self.akagiot2_apikey_var, width=std_wid*4)
+ string_entry.grid(row=cur_row, column=1,columnspan=3, **args_entry)
# MJAPI url
cur_row += 1
@@ -292,6 +315,9 @@ def _on_save(self):
mode_file_3p_new = self.model_file_3p_var.get()
akagi_url_new = self.akagiot_url_var.get()
akagi_apikey_new = self.akagiot_apikey_var.get()
+ enable_ot2_for_3p_new = self.enable_ot2_for_3p_var.get()
+ akagi_url2_new = self.akagiot2_url_var.get()
+ akagi_apikey2_new = self.akagiot2_apikey_var.get()
mjapi_url_new = self.mjapi_url_var.get()
mjapi_user_new = self.mjapi_user_var.get()
mjapi_secret_new = self.mjapi_secret_var.get()
@@ -302,6 +328,9 @@ def _on_save(self):
self.st.model_file_3p != mode_file_3p_new or
self.st.akagi_ot_url != akagi_url_new or
self.st.akagi_ot_apikey != akagi_apikey_new or
+ self.st.enable_ot2_for_3p != enable_ot2_for_3p_new or
+ self.st.akagi_ot2_url != akagi_url2_new or
+ self.st.akagi_ot2_apikey != akagi_apikey2_new or
self.st.mjapi_url != mjapi_url_new or
self.st.mjapi_user != mjapi_user_new or
self.st.mjapi_secret != mjapi_secret_new or
@@ -337,6 +366,9 @@ def _on_save(self):
self.st.model_file_3p = mode_file_3p_new
self.st.akagi_ot_url = akagi_url_new
self.st.akagi_ot_apikey = akagi_apikey_new
+ self.st.enable_ot2_for_3p = enable_ot2_for_3p_new
+ self.st.akagi_ot2_url = akagi_url2_new
+ self.st.akagi_ot2_apikey = akagi_apikey2_new
self.st.mjapi_url = mjapi_url_new
self.st.mjapi_user = mjapi_user_new
self.st.mjapi_secret = mjapi_secret_new
diff --git a/mjai/bot_3p/Put_model.pth_here b/mjai/bot_3p/Put_model.pth_here
new file mode 100644
index 0000000..c65a16a
--- /dev/null
+++ b/mjai/bot_3p/Put_model.pth_here
@@ -0,0 +1 @@
+Get model.pth from the official Akagi discord channel.
\ No newline at end of file
diff --git a/readme.md b/readme.md
index 027d776..9157101 100644
--- a/readme.md
+++ b/readme.md
@@ -60,8 +60,11 @@ python main.py
```
### 配置模型
本程序支持几种模型来源。其中,本地模型(Local)是基于 Akagi 兼容的 Mortal 模型。要获取 Akagi 的模型,请参见 Akagi Github 的说明。
+如需使用 AkagiOT2 模型,请前往 Akagi 的官方 Discord 频道获取对应的 model.pth 和 riichi3p 包并安装。
### Model Configuration
This program supports different types of AI models. The 'Local' Model type uses Mortal models compatible with Akagi. To acquire Akagi's models, please refer to Akagi Github .
+To use the AkagiOT2 model, please visit Akagi's official Discord channel to obtain the corresponding model.pth and riichi3p package, and then install them.
+
## 截图 / Screenshots
From 743d9e46d2a0dd66c712c01559ee167e60cef85b Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Mon, 27 May 2024 03:54:08 +0800
Subject: [PATCH 03/21] Fix typo in setting_windows
---
gui/settings_window.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gui/settings_window.py b/gui/settings_window.py
index e891399..af64ba5 100644
--- a/gui/settings_window.py
+++ b/gui/settings_window.py
@@ -16,7 +16,7 @@ def __init__(self, parent:tk.Frame, setting:Settings):
super().__init__(parent)
self.st = setting
- self.geometry('800x675')
+ self.geometry('800x800')
self.minsize(800,800)
# self.resizable(False, False)
# set position: within main window
From e28579405a6767e3ee34c227ff160daf5925c317 Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Mon, 27 May 2024 10:59:57 +0800
Subject: [PATCH 04/21] Revert "Update readme.md"
This reverts commit a344924a16f7c25cc864e4a70eb1506778a39f75.
---
readme.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/readme.md b/readme.md
index 9157101..6d92c06 100644
--- a/readme.md
+++ b/readme.md
@@ -1,8 +1,7 @@
# 麻将 Copilot / Mahjong Copilot
麻将 AI 助手,基于 mjai (Mortal模型) 实现的机器人。会对游戏对局的每一步进行指导。现支持雀魂三人、四人麻将。
-QQ群:834105526
-Join Discord
+QQ群:834105526
Mahjong AI Assistant for Majsoul, based on mjai (Mortal model) bot impelementaion. When you are in a Majsoul game, AI will give you step-by-step guidance. Now supports Majsoul 3-person and 4-person game modes.
From 51be7b8fc343cbf0e065e9f89af72e2b32dc31fb Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Mon, 27 May 2024 14:20:19 +0800
Subject: [PATCH 05/21] =?UTF-8?q?=E9=85=8D=E7=BD=AE=20OT2=20=E6=A8=A1?=
=?UTF-8?q?=E5=9E=8B=E6=97=B6=E6=A3=80=E6=B5=8B=E5=8F=AF=E7=94=A8=E6=80=A7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bot/factory.py | 17 ++++++++++++-----
bot/local/bot_local.py | 27 ++++++++++++++++++++-------
2 files changed, 32 insertions(+), 12 deletions(-)
diff --git a/bot/factory.py b/bot/factory.py
index 47d8b2f..46856b4 100644
--- a/bot/factory.py
+++ b/bot/factory.py
@@ -8,17 +8,24 @@
MODEL_TYPE_STRINGS = ["Local", "AkagiOT", "MJAPI"]
+OT2_MODEL_PATH = "./mjai/bot_3p/model.pth"
def get_bot(settings:Settings) -> Bot:
""" create the Bot instance based on settings"""
match settings.model_type:
- case "Local":
- model_files:dict = {
- GameMode.MJ4P: sub_file(Folder.MODEL, settings.model_file),
- GameMode.MJ3P: sub_file(Folder.MODEL, settings.model_file_3p)
- }
+ case "Local":
+ if settings.enable_ot2_for_3p:
+ model_files:dict = {
+ GameMode.MJ4P: sub_file(Folder.MODEL, settings.model_file),
+ GameMode.MJ3P: OT2_MODEL_PATH
+ }
+ else:
+ model_files:dict = {
+ GameMode.MJ4P: sub_file(Folder.MODEL, settings.model_file),
+ GameMode.MJ3P: sub_file(Folder.MODEL, settings.model_file_3p)
+ }
bot = BotMortalLocal(model_files)
case "AkagiOT":
bot = BotAkagiOt(settings.akagi_ot_url, settings.akagi_ot_apikey)
diff --git a/bot/local/bot_local.py b/bot/local/bot_local.py
index d522b54..820aeaa 100644
--- a/bot/local/bot_local.py
+++ b/bot/local/bot_local.py
@@ -7,6 +7,7 @@
from common.log_helper import LOGGER
from bot.local.engine import get_engine
from bot.bot import BotMjai, GameMode
+from common.settings import Settings
class BotMortalLocal(BotMjai):
@@ -30,13 +31,25 @@ def __init__(self, model_files:dict[GameMode, str]) -> None:
except Exception as e:
LOGGER.warning("Cannot create engine for mode %s: %s", k, e, exc_info=True)
elif k == GameMode.MJ3P:
- # test import libraries for 3p
- try:
- import libriichi3p
- from bot.local.engine3p import get_engine as get_engine_3p
- self._engines[k] = get_engine_3p(self.model_files[k])
- except Exception as e: # pylint: disable=broad-except
- LOGGER.warning("Cannot create engine for mode %s: %s", k, e, exc_info=True)
+ settings = Settings()
+ if settings.enable_ot2_for_3p:
+ import riichi3p
+ try :
+ # 尝试创建一个mjai.bot实例
+ riichi3p.online.Bot(1)
+ self._engines[k] = "./mjai/bot_3p/model.pth"
+ except Exception as e:
+ LOGGER.warning("Cannot create bot for OT2 model %s: %s", k, e, exc_info=True)
+ LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
+ pass
+ else:
+ # test import libraries for 3p
+ try:
+ import libriichi3p
+ from bot.local.engine3p import get_engine as get_engine_3p
+ self._engines[k] = get_engine_3p(self.model_files[k])
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.warning("Cannot create engine for mode %s: %s", k, e, exc_info=True)
self._supported_modes = list(self._engines.keys())
if not self._supported_modes:
raise LocalModelException("No valid model files found")
From 91f535b988eafb6d8b6039c1ec78a39714792f4a Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Mon, 27 May 2024 15:04:52 +0800
Subject: [PATCH 06/21] =?UTF-8?q?=E7=94=A8=E5=88=86=E7=A6=BB=E8=BF=9B?=
=?UTF-8?q?=E7=A8=8B=E8=A7=A3=E5=86=B3=20rust=20panick=E5=AF=BC=E8=87=B4?=
=?UTF-8?q?=E5=B4=A9=E6=BA=83=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bot/local/bot_local.py | 42 +++++++++++++++++++++++++++++++++++++++---
1 file changed, 39 insertions(+), 3 deletions(-)
diff --git a/bot/local/bot_local.py b/bot/local/bot_local.py
index 820aeaa..6a92bb3 100644
--- a/bot/local/bot_local.py
+++ b/bot/local/bot_local.py
@@ -8,6 +8,39 @@
from bot.local.engine import get_engine
from bot.bot import BotMjai, GameMode
from common.settings import Settings
+import multiprocessing
+
+# 尝试获取mjai.bot实例,该方法可能会导致 panick,需要在分离进程中使用
+def create_bot_instance(queue):
+ import riichi3p
+ try:
+ # 尝试创建一个mjai.bot实例
+ riichi3p.online.Bot(1)
+ queue.put(True) # 将成功的标志放入队列
+ except Exception as e:
+ LOGGER.warning("Cannot create bot: %s", e, exc_info=True)
+ LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
+ queue.put(False) # 将失败的标志放入队列
+
+# 使用分离进程尝试创建bot实例
+def try_create_ot2_bot():
+ queue = multiprocessing.Queue()
+ process = multiprocessing.Process(target=create_bot_instance, args=(queue,))
+ process.start()
+
+ # 尝试从队列中获取结果,设置超时时间防止无限等待
+ try:
+ success = queue.get(timeout=10) # 设置适当的超时时间,例如10秒
+ except Exception as e:
+ LOGGER.error("Failed to retrieve the result from the subprocess: %s", e)
+ success = False
+
+ process.join()
+
+ if not success or process.exitcode != 0:
+ LOGGER.error("Failed to create bot or detected a crash in the subprocess with exit code %s", process.exitcode)
+ return False
+ return True
class BotMortalLocal(BotMjai):
@@ -35,9 +68,12 @@ def __init__(self, model_files:dict[GameMode, str]) -> None:
if settings.enable_ot2_for_3p:
import riichi3p
try :
- # 尝试创建一个mjai.bot实例
- riichi3p.online.Bot(1)
- self._engines[k] = "./mjai/bot_3p/model.pth"
+ # 用分离进程尝试创建一个mjai.bot实例
+ if try_create_ot2_bot():
+ self._engines[k] = "./mjai/bot_3p/model.pth"
+ else:
+ LOGGER.warning("Cannot create bot for OT2 model %s.", k, exc_info=True)
+ LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
except Exception as e:
LOGGER.warning("Cannot create bot for OT2 model %s: %s", k, e, exc_info=True)
LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
From ae11582db5aae79187a9aaee2478ee468721666c Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Mon, 27 May 2024 15:06:07 +0800
Subject: [PATCH 07/21] =?UTF-8?q?=E9=99=8D=E4=BD=8E=E8=B6=85=E6=97=B6?=
=?UTF-8?q?=E6=97=B6=E9=97=B4=E5=88=B0=203=20=E7=A7=92?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bot/local/bot_local.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bot/local/bot_local.py b/bot/local/bot_local.py
index 6a92bb3..cd29631 100644
--- a/bot/local/bot_local.py
+++ b/bot/local/bot_local.py
@@ -30,7 +30,7 @@ def try_create_ot2_bot():
# 尝试从队列中获取结果,设置超时时间防止无限等待
try:
- success = queue.get(timeout=10) # 设置适当的超时时间,例如10秒
+ success = queue.get(timeout=3) # 设置适当的超时时间,例如10秒
except Exception as e:
LOGGER.error("Failed to retrieve the result from the subprocess: %s", e)
success = False
From 1a869187256134d0fb7ef7d8e765f55c45b23200 Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Mon, 27 May 2024 15:30:03 +0800
Subject: [PATCH 08/21] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?=
=?UTF-8?q?=E6=B3=A8=E9=87=8A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bot/local/bot_local.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bot/local/bot_local.py b/bot/local/bot_local.py
index cd29631..430c367 100644
--- a/bot/local/bot_local.py
+++ b/bot/local/bot_local.py
@@ -30,7 +30,7 @@ def try_create_ot2_bot():
# 尝试从队列中获取结果,设置超时时间防止无限等待
try:
- success = queue.get(timeout=3) # 设置适当的超时时间,例如10秒
+ success = queue.get(timeout=3)
except Exception as e:
LOGGER.error("Failed to retrieve the result from the subprocess: %s", e)
success = False
From b37235d39b8646aba15acfd49008b605ddecb140 Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Tue, 28 May 2024 16:05:27 +0800
Subject: [PATCH 09/21] =?UTF-8?q?=E6=B7=BB=E5=8A=A0BotAkagiOt2=E7=B1=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bot/akagiot2/bot_akagiot2.py | 82 ++++++++++++++++++++++++++++++++++++
bot/factory.py | 5 ++-
2 files changed, 86 insertions(+), 1 deletion(-)
create mode 100644 bot/akagiot2/bot_akagiot2.py
diff --git a/bot/akagiot2/bot_akagiot2.py b/bot/akagiot2/bot_akagiot2.py
new file mode 100644
index 0000000..dfeccfb
--- /dev/null
+++ b/bot/akagiot2/bot_akagiot2.py
@@ -0,0 +1,82 @@
+import multiprocessing
+
+from bot.bot import BotMjai, GameMode
+from common.log_helper import LOGGER
+from pathlib import Path
+
+from common.utils import BotNotSupportingMode, Ot2BotCreationError
+
+model_file_path = "mjai/bot_3p/model.pth"
+
+
+class BotAkagiOt2(BotMjai):
+ """ Bot implementation for Akagi OT2 model """
+
+ def __init__(self) -> None:
+ super().__init__("Akagi OT2 Bot")
+ self._supported_modes: list[GameMode] = []
+ self._check()
+
+ def _check(self):
+ # check model file
+ if not Path(model_file_path).exists() or not Path(model_file_path).is_file():
+ LOGGER.warning("Cannot find model file for Akagi OT2 model:%s", model_file_path)
+ if try_create_ot2_bot():
+ self._supported_modes.append(GameMode.MJ3P)
+ else:
+ LOGGER.warning("Cannot create bot for OT2 model.", exc_info=True)
+ LOGGER.warning("Could be missing file: %s", model_file_path)
+ raise Ot2BotCreationError("Failed to create bot instance for Akagi OT2 model.")
+ pass
+
+ @property
+ def supported_modes(self) -> list[GameMode]:
+ """ return supported game modes"""
+ return self._supported_modes
+
+ # 覆写父类方法
+ def _init_bot_impl(self, mode: GameMode = GameMode.MJ3P):
+ if mode == GameMode.MJ3P:
+ try:
+ import riichi3p
+ self.mjai_bot = riichi3p.online.Bot(self.seat)
+ except Exception as e:
+ LOGGER.warning("Cannot create bot for Akagi OT2 model: %s", e, exc_info=True)
+ LOGGER.warning("Could be missing model.pth file in path mjai/bot_3p")
+ raise Ot2BotCreationError("Failed to create bot instance for Akagi OT2 model.")
+ else:
+ raise BotNotSupportingMode(mode)
+
+
+# 尝试获取mjai.bot实例,该方法可能会导致 panick,需要在分离进程中使用
+def create_bot_instance(queue):
+ import riichi3p
+ try:
+ # 尝试创建一个mjai.bot实例
+ riichi3p.online.Bot(1)
+ queue.put(True) # 将成功的标志放入队列
+ except Exception as e:
+ LOGGER.warning("Cannot create bot: %s", e, exc_info=True)
+ LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
+ queue.put(False) # 将失败的标志放入队列
+
+
+# 使用分离进程尝试创建bot实例
+def try_create_ot2_bot():
+ queue = multiprocessing.Queue()
+ process = multiprocessing.Process(target=create_bot_instance, args=(queue,))
+ process.start()
+
+ # 尝试从队列中获取结果,设置超时时间防止无限等待
+ try:
+ success = queue.get(timeout=3)
+ except Exception as e:
+ LOGGER.error("Failed to retrieve the result from the subprocess: %s", e)
+ success = False
+
+ process.join()
+
+ if not success or process.exitcode != 0:
+ LOGGER.error("Failed to create bot or detected a crash in the subprocess with exit code %s", process.exitcode)
+ return False
+ return True
diff --git a/bot/factory.py b/bot/factory.py
index 46856b4..5c89e0c 100644
--- a/bot/factory.py
+++ b/bot/factory.py
@@ -5,9 +5,10 @@
from .local.bot_local import BotMortalLocal
from .mjapi.bot_mjapi import BotMjapi
from .akagiot.bot_akagiot import BotAkagiOt
+from .akagiot2.bot_akagiot2 import BotAkagiOt2
-MODEL_TYPE_STRINGS = ["Local", "AkagiOT", "MJAPI"]
+MODEL_TYPE_STRINGS = ["Local", "AkagiOT", "MJAPI", "AkagiOT2"]
OT2_MODEL_PATH = "./mjai/bot_3p/model.pth"
@@ -29,6 +30,8 @@ def get_bot(settings:Settings) -> Bot:
bot = BotMortalLocal(model_files)
case "AkagiOT":
bot = BotAkagiOt(settings.akagi_ot_url, settings.akagi_ot_apikey)
+ case "AkagiOT2":
+ bot = BotAkagiOt2()
case "MJAPI":
bot = BotMjapi(settings)
case _:
From 829e53c52d79a7fc7d8c51ec3537544ff283032c Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Tue, 28 May 2024 16:05:56 +0800
Subject: [PATCH 10/21] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20OT2=20=E5=8A=A0?=
=?UTF-8?q?=E8=BD=BD=E5=BC=82=E5=B8=B8=E7=B1=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
common/utils.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/common/utils.py b/common/utils.py
index a01fdef..f180b53 100644
--- a/common/utils.py
+++ b/common/utils.py
@@ -64,10 +64,13 @@ class UiState(Enum):
GAME_ENDING = 20
-# === Exceptions ===
+# === Exceptions ===
class LocalModelException(Exception):
""" Exception for model file error"""
+class Ot2BotCreationError(Exception):
+ """ Exception for OT2 bot creation error"""
+
class MITMException(Exception):
""" Exception for MITM error"""
@@ -84,6 +87,8 @@ def error_to_str(error:Exception, lan:LanStr) -> str:
""" Convert error to language specific string"""
if isinstance(error, LocalModelException):
return lan.LOCAL_MODEL_ERROR
+ elif isinstance(error, Ot2BotCreationError):
+ return lan.OT2_MODEL_ERROR
elif isinstance(error, MitmCertNotInstalled):
return lan.MITM_CERT_NOT_INSTALLED + f"{error.args}"
elif isinstance(error, MITMException):
From dbf62423134df226222cf9df7ee51bf3d056f812 Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Tue, 28 May 2024 16:21:43 +0800
Subject: [PATCH 11/21] =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=A1=B5=E5=8E=BB?=
=?UTF-8?q?=E9=99=A4=20OT2=20enable=20=E9=80=89=E9=A1=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
common/lan_str.py | 4 ----
common/settings.py | 3 +--
gui/settings_window.py | 11 -----------
3 files changed, 1 insertion(+), 17 deletions(-)
diff --git a/common/lan_str.py b/common/lan_str.py
index 698d2c2..c4f1f2c 100644
--- a/common/lan_str.py
+++ b/common/lan_str.py
@@ -50,8 +50,6 @@ class LanStr:
AI_MODEL_FILE_3P = "Local Model File (3P)"
AKAGI_OT_URL = "AkagiOT Server URL"
AKAGI_OT_APIKEY = "AkagiOT API Key"
- AKAGI_OT2 = "AKAGI OT2"
- ENABLE_AKAGI_OT2_FOR_3P = "Enable AkagiOT2 for 3P(Only Effective when model type is Local)"
AKAGI_OT2_URL = "AkagiOT2 Server URL"
AKAGI_OT2_APIKEY = "AkagiOT2 API Key"
MJAPI_URL = "MJAPI Server URL"
@@ -187,8 +185,6 @@ class LanStrZHS(LanStr):
AI_MODEL_FILE_3P = "本地模型文件(三麻)"
AKAGI_OT_URL = "AkagiOT 服务器地址"
AKAGI_OT_APIKEY = "AkagiOT API Key"
- AKAGI_OT2 = "AKAGI OT2"
- ENABLE_AKAGI_OT2_FOR_3P = "三麻中启用 AkagiOT2(仅在选择 Local 模型时生效)"
AKAGI_OT2_URL = "AkagiOT2 服务器地址"
AKAGI_OT2_APIKEY = "AkagiOT2 API Key"
MJAPI_URL = "MJAPI 服务器地址"
diff --git a/common/settings.py b/common/settings.py
index 770b82c..25644d8 100644
--- a/common/settings.py
+++ b/common/settings.py
@@ -34,7 +34,7 @@ def __init__(self, json_file:str=DEFAULT_SETTING_FILE) -> None:
# AI Model settings
self.model_type:str = self._get_value("model_type", "Local")
- """ model type: local, mjapi"""
+ """ model type: local, mjapi, AkagiOT, AkagiOT2"""
# for local model
self.model_file:str = self._get_value("model_file", "mortal.pth")
self.model_file_3p:str = self._get_value("model_file_3p", "mortal_3p.pth")
@@ -42,7 +42,6 @@ def __init__(self, json_file:str=DEFAULT_SETTING_FILE) -> None:
self.akagi_ot_url:str = self._get_value("akagi_ot_url", "")
self.akagi_ot_apikey:str = self._get_value("akagi_ot_apikey", "")
# akagi ot2 3p model
- self.enable_ot2_for_3p:bool = self._get_value("enable_ot2_for_3p", False, self.valid_bool)
self.akagi_ot2_url:str = self._get_value("akagi_ot2_url", "")
self.akagi_ot2_apikey: str = self._get_value("akagi_ot2_apikey", "")
# for mjapi
diff --git a/gui/settings_window.py b/gui/settings_window.py
index af64ba5..190cdc4 100644
--- a/gui/settings_window.py
+++ b/gui/settings_window.py
@@ -153,14 +153,6 @@ def create_widgets(self):
string_entry = ttk.Entry(main_frame, textvariable=self.akagiot_apikey_var, width=std_wid*4)
string_entry.grid(row=cur_row, column=1,columnspan=3, **args_entry)
- # Enable Akagi OT2
- cur_row += 1
- _label = ttk.Label(main_frame, text=self.st.lan().AKAGI_OT2)
- _label.grid(row=cur_row, column=0, **args_label)
- self.enable_ot2_for_3p_var = tk.BooleanVar(value=self.st.enable_ot2_for_3p)
- enable_akagi_ot2_entry = ttk.Checkbutton(
- main_frame, variable=self.enable_ot2_for_3p_var, text=self.st.lan().ENABLE_AKAGI_OT2_FOR_3P, width=std_wid*3)
- enable_akagi_ot2_entry.grid(row=cur_row, column=1, **args_entry)
# Akagi OT2 url
cur_row += 1
_label = ttk.Label(main_frame, text=self.st.lan().AKAGI_OT2_URL)
@@ -315,7 +307,6 @@ def _on_save(self):
mode_file_3p_new = self.model_file_3p_var.get()
akagi_url_new = self.akagiot_url_var.get()
akagi_apikey_new = self.akagiot_apikey_var.get()
- enable_ot2_for_3p_new = self.enable_ot2_for_3p_var.get()
akagi_url2_new = self.akagiot2_url_var.get()
akagi_apikey2_new = self.akagiot2_apikey_var.get()
mjapi_url_new = self.mjapi_url_var.get()
@@ -328,7 +319,6 @@ def _on_save(self):
self.st.model_file_3p != mode_file_3p_new or
self.st.akagi_ot_url != akagi_url_new or
self.st.akagi_ot_apikey != akagi_apikey_new or
- self.st.enable_ot2_for_3p != enable_ot2_for_3p_new or
self.st.akagi_ot2_url != akagi_url2_new or
self.st.akagi_ot2_apikey != akagi_apikey2_new or
self.st.mjapi_url != mjapi_url_new or
@@ -366,7 +356,6 @@ def _on_save(self):
self.st.model_file_3p = mode_file_3p_new
self.st.akagi_ot_url = akagi_url_new
self.st.akagi_ot_apikey = akagi_apikey_new
- self.st.enable_ot2_for_3p = enable_ot2_for_3p_new
self.st.akagi_ot2_url = akagi_url2_new
self.st.akagi_ot2_apikey = akagi_apikey2_new
self.st.mjapi_url = mjapi_url_new
From 604cbc18cafb95b8b6c700760161167850d0ecb8 Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Tue, 28 May 2024 16:22:01 +0800
Subject: [PATCH 12/21] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20OT2=20=E5=8A=A0?=
=?UTF-8?q?=E8=BD=BD=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
common/lan_str.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/common/lan_str.py b/common/lan_str.py
index c4f1f2c..c12a4fd 100644
--- a/common/lan_str.py
+++ b/common/lan_str.py
@@ -91,6 +91,7 @@ class LanStr:
GAME_NOT_RUNNING = "Not Launched"
# errors
LOCAL_MODEL_ERROR = "Local Model Loading Error!"
+ OT2_MODEL_ERROR = "OT2 Model Loading Error!"
MITM_SERVER_ERROR = "MITM Service Error!"
MITM_CERT_NOT_INSTALLED = "Run as admin or manually install MITM cert."
MAIN_THREAD_ERROR = "Main Thread Error!"
@@ -228,6 +229,7 @@ class LanStrZHS(LanStr):
GAME_NOT_RUNNING = "未启动"
#error
LOCAL_MODEL_ERROR = "本地模型加载错误!"
+ OT2_MODEL_ERROR = "OT2 模型加载错误!"
MITM_CERT_NOT_INSTALLED = "以管理员运行或手动安装 MITM 证书"
MITM_SERVER_ERROR = "MITM 服务错误!"
MAIN_THREAD_ERROR = "主进程发生错误!"
From 70ad864f32ac7cab8e25c7f43ec418989bcc6126 Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Tue, 28 May 2024 16:28:41 +0800
Subject: [PATCH 13/21] =?UTF-8?q?Revert=20"=E5=88=A0=E9=99=A4=E5=A4=9A?=
=?UTF-8?q?=E4=BD=99=E6=B3=A8=E9=87=8A"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This reverts commit 1a869187256134d0fb7ef7d8e765f55c45b23200.
---
bot/local/bot_local.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bot/local/bot_local.py b/bot/local/bot_local.py
index 430c367..cd29631 100644
--- a/bot/local/bot_local.py
+++ b/bot/local/bot_local.py
@@ -30,7 +30,7 @@ def try_create_ot2_bot():
# 尝试从队列中获取结果,设置超时时间防止无限等待
try:
- success = queue.get(timeout=3)
+ success = queue.get(timeout=3) # 设置适当的超时时间,例如10秒
except Exception as e:
LOGGER.error("Failed to retrieve the result from the subprocess: %s", e)
success = False
From 51e000fdff45cc76160ddbc32273d9a115b95309 Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Tue, 28 May 2024 16:28:54 +0800
Subject: [PATCH 14/21] =?UTF-8?q?Revert=20"=E9=99=8D=E4=BD=8E=E8=B6=85?=
=?UTF-8?q?=E6=97=B6=E6=97=B6=E9=97=B4=E5=88=B0=203=20=E7=A7=92"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This reverts commit ae11582db5aae79187a9aaee2478ee468721666c.
---
bot/local/bot_local.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bot/local/bot_local.py b/bot/local/bot_local.py
index cd29631..6a92bb3 100644
--- a/bot/local/bot_local.py
+++ b/bot/local/bot_local.py
@@ -30,7 +30,7 @@ def try_create_ot2_bot():
# 尝试从队列中获取结果,设置超时时间防止无限等待
try:
- success = queue.get(timeout=3) # 设置适当的超时时间,例如10秒
+ success = queue.get(timeout=10) # 设置适当的超时时间,例如10秒
except Exception as e:
LOGGER.error("Failed to retrieve the result from the subprocess: %s", e)
success = False
From c00392f4bfa9f64d2d036d49fe55124af93070cd Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Tue, 28 May 2024 16:29:05 +0800
Subject: [PATCH 15/21] =?UTF-8?q?Revert=20"=E7=94=A8=E5=88=86=E7=A6=BB?=
=?UTF-8?q?=E8=BF=9B=E7=A8=8B=E8=A7=A3=E5=86=B3=20rust=20panick=E5=AF=BC?=
=?UTF-8?q?=E8=87=B4=E5=B4=A9=E6=BA=83=E7=9A=84=E9=97=AE=E9=A2=98"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This reverts commit 91f535b988eafb6d8b6039c1ec78a39714792f4a.
---
bot/local/bot_local.py | 42 +++---------------------------------------
1 file changed, 3 insertions(+), 39 deletions(-)
diff --git a/bot/local/bot_local.py b/bot/local/bot_local.py
index 6a92bb3..820aeaa 100644
--- a/bot/local/bot_local.py
+++ b/bot/local/bot_local.py
@@ -8,39 +8,6 @@
from bot.local.engine import get_engine
from bot.bot import BotMjai, GameMode
from common.settings import Settings
-import multiprocessing
-
-# 尝试获取mjai.bot实例,该方法可能会导致 panick,需要在分离进程中使用
-def create_bot_instance(queue):
- import riichi3p
- try:
- # 尝试创建一个mjai.bot实例
- riichi3p.online.Bot(1)
- queue.put(True) # 将成功的标志放入队列
- except Exception as e:
- LOGGER.warning("Cannot create bot: %s", e, exc_info=True)
- LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
- queue.put(False) # 将失败的标志放入队列
-
-# 使用分离进程尝试创建bot实例
-def try_create_ot2_bot():
- queue = multiprocessing.Queue()
- process = multiprocessing.Process(target=create_bot_instance, args=(queue,))
- process.start()
-
- # 尝试从队列中获取结果,设置超时时间防止无限等待
- try:
- success = queue.get(timeout=10) # 设置适当的超时时间,例如10秒
- except Exception as e:
- LOGGER.error("Failed to retrieve the result from the subprocess: %s", e)
- success = False
-
- process.join()
-
- if not success or process.exitcode != 0:
- LOGGER.error("Failed to create bot or detected a crash in the subprocess with exit code %s", process.exitcode)
- return False
- return True
class BotMortalLocal(BotMjai):
@@ -68,12 +35,9 @@ def __init__(self, model_files:dict[GameMode, str]) -> None:
if settings.enable_ot2_for_3p:
import riichi3p
try :
- # 用分离进程尝试创建一个mjai.bot实例
- if try_create_ot2_bot():
- self._engines[k] = "./mjai/bot_3p/model.pth"
- else:
- LOGGER.warning("Cannot create bot for OT2 model %s.", k, exc_info=True)
- LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
+ # 尝试创建一个mjai.bot实例
+ riichi3p.online.Bot(1)
+ self._engines[k] = "./mjai/bot_3p/model.pth"
except Exception as e:
LOGGER.warning("Cannot create bot for OT2 model %s: %s", k, e, exc_info=True)
LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
From b2ca49bb1bbbdd4392c5a206703940ed87eb155d Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Tue, 28 May 2024 16:31:20 +0800
Subject: [PATCH 16/21] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=86=B2=E7=AA=81?=
=?UTF-8?q?=EF=BC=8C=E5=9B=9E=E9=80=80=20localbot=E7=9A=84=E4=BF=AE?=
=?UTF-8?q?=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bot/factory.py | 16 +++++-----------
bot/local/bot_local.py | 27 +++++++--------------------
2 files changed, 12 insertions(+), 31 deletions(-)
diff --git a/bot/factory.py b/bot/factory.py
index 5c89e0c..024d356 100644
--- a/bot/factory.py
+++ b/bot/factory.py
@@ -16,17 +16,11 @@ def get_bot(settings:Settings) -> Bot:
""" create the Bot instance based on settings"""
match settings.model_type:
- case "Local":
- if settings.enable_ot2_for_3p:
- model_files:dict = {
- GameMode.MJ4P: sub_file(Folder.MODEL, settings.model_file),
- GameMode.MJ3P: OT2_MODEL_PATH
- }
- else:
- model_files:dict = {
- GameMode.MJ4P: sub_file(Folder.MODEL, settings.model_file),
- GameMode.MJ3P: sub_file(Folder.MODEL, settings.model_file_3p)
- }
+ case "Local":
+ model_files:dict = {
+ GameMode.MJ4P: sub_file(Folder.MODEL, settings.model_file),
+ GameMode.MJ3P: sub_file(Folder.MODEL, settings.model_file_3p)
+ }
bot = BotMortalLocal(model_files)
case "AkagiOT":
bot = BotAkagiOt(settings.akagi_ot_url, settings.akagi_ot_apikey)
diff --git a/bot/local/bot_local.py b/bot/local/bot_local.py
index 820aeaa..d522b54 100644
--- a/bot/local/bot_local.py
+++ b/bot/local/bot_local.py
@@ -7,7 +7,6 @@
from common.log_helper import LOGGER
from bot.local.engine import get_engine
from bot.bot import BotMjai, GameMode
-from common.settings import Settings
class BotMortalLocal(BotMjai):
@@ -31,25 +30,13 @@ def __init__(self, model_files:dict[GameMode, str]) -> None:
except Exception as e:
LOGGER.warning("Cannot create engine for mode %s: %s", k, e, exc_info=True)
elif k == GameMode.MJ3P:
- settings = Settings()
- if settings.enable_ot2_for_3p:
- import riichi3p
- try :
- # 尝试创建一个mjai.bot实例
- riichi3p.online.Bot(1)
- self._engines[k] = "./mjai/bot_3p/model.pth"
- except Exception as e:
- LOGGER.warning("Cannot create bot for OT2 model %s: %s", k, e, exc_info=True)
- LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
- pass
- else:
- # test import libraries for 3p
- try:
- import libriichi3p
- from bot.local.engine3p import get_engine as get_engine_3p
- self._engines[k] = get_engine_3p(self.model_files[k])
- except Exception as e: # pylint: disable=broad-except
- LOGGER.warning("Cannot create engine for mode %s: %s", k, e, exc_info=True)
+ # test import libraries for 3p
+ try:
+ import libriichi3p
+ from bot.local.engine3p import get_engine as get_engine_3p
+ self._engines[k] = get_engine_3p(self.model_files[k])
+ except Exception as e: # pylint: disable=broad-except
+ LOGGER.warning("Cannot create engine for mode %s: %s", k, e, exc_info=True)
self._supported_modes = list(self._engines.keys())
if not self._supported_modes:
raise LocalModelException("No valid model files found")
From b20c6a0272e69d6637d8307dcb0eba5b82d32a33 Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Tue, 28 May 2024 17:02:03 +0800
Subject: [PATCH 17/21] =?UTF-8?q?=E5=BF=BD=E7=95=A5=20model.pth?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index d51c984..5eaea24 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,4 @@ MahjongCopilot.spec
libriichi3p/*.pyd
libriichi3p/*.so
chrome_ext/*/
+mjai/bot_3p/model.pth
From e6d4afdc6e491b42a19807603d9a97b69a0c34d3 Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Tue, 28 May 2024 17:16:46 +0800
Subject: [PATCH 18/21] =?UTF-8?q?=E8=BF=98=E5=8E=9F=20bot.py=20=E9=80=BB?=
=?UTF-8?q?=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bot/bot.py | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/bot/bot.py b/bot/bot.py
index 96615d8..f4e8d17 100644
--- a/bot/bot.py
+++ b/bot/bot.py
@@ -7,7 +7,6 @@
from common.log_helper import LOGGER
from common.mj_helper import meta_to_options, MjaiType
from common.utils import GameMode, BotNotSupportingMode
-from common.settings import Settings
def reaction_convert_meta(reaction:dict, is_3p:bool=False):
@@ -110,13 +109,8 @@ def _init_bot_impl(self, mode:GameMode=GameMode.MJ4P):
import riichi as libriichi
self.mjai_bot = libriichi.mjai.Bot(engine, self.seat)
elif mode == GameMode.MJ3P:
- settings = Settings()
- if settings.enable_ot2_for_3p:
- import riichi3p
- self.mjai_bot = riichi3p.online.Bot(self.seat)
- else:
- import libriichi3p
- self.mjai_bot = libriichi3p.mjai.Bot(engine, self.seat)
+ import libriichi3p
+ self.mjai_bot = libriichi3p.mjai.Bot(engine, self.seat)
else:
raise BotNotSupportingMode(mode)
From 3ad14decfb71a7e1fb27a18e361b608c61e46369 Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Wed, 29 May 2024 14:59:29 +0800
Subject: [PATCH 19/21] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20OT2=20=E6=A8=A1?=
=?UTF-8?q?=E5=9E=8B=E5=9C=A8=E7=BA=BF=E7=8A=B6=E6=80=81=E5=B1=95=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bot/akagiot/bot_akagiot.py | 3 ++-
bot/akagiot2/bot_akagiot2.py | 22 ++++++++++++++++++----
bot/bot.py | 5 +++++
bot/local/bot_local.py | 1 +
bot/mjapi/bot_mjapi.py | 1 +
bot_manager.py | 9 +++++++++
6 files changed, 36 insertions(+), 5 deletions(-)
diff --git a/bot/akagiot/bot_akagiot.py b/bot/akagiot/bot_akagiot.py
index 1601c49..2a1a9f2 100644
--- a/bot/akagiot/bot_akagiot.py
+++ b/bot/akagiot/bot_akagiot.py
@@ -15,7 +15,8 @@ class BotAkagiOt(BotMjai):
def __init__(self, url:str, apikey:str) -> None:
super().__init__("Akagi Online Bot")
self.url = url
- self.apikey = apikey
+ self.apikey = apikey
+ self.model_type = "AkagiOT"
self._check()
diff --git a/bot/akagiot2/bot_akagiot2.py b/bot/akagiot2/bot_akagiot2.py
index dfeccfb..dba689b 100644
--- a/bot/akagiot2/bot_akagiot2.py
+++ b/bot/akagiot2/bot_akagiot2.py
@@ -1,9 +1,8 @@
import multiprocessing
+from pathlib import Path
from bot.bot import BotMjai, GameMode
from common.log_helper import LOGGER
-from pathlib import Path
-
from common.utils import BotNotSupportingMode, Ot2BotCreationError
model_file_path = "mjai/bot_3p/model.pth"
@@ -15,7 +14,9 @@ class BotAkagiOt2(BotMjai):
def __init__(self) -> None:
super().__init__("Akagi OT2 Bot")
self._supported_modes: list[GameMode] = []
+ self._is_online = "Waiting"
self._check()
+ self.model_type = "AkagiOT2"
def _check(self):
# check model file
@@ -34,7 +35,7 @@ def supported_modes(self) -> list[GameMode]:
""" return supported game modes"""
return self._supported_modes
- # 覆写父类方法
+ # 覆写父类 impl 方法
def _init_bot_impl(self, mode: GameMode = GameMode.MJ3P):
if mode == GameMode.MJ3P:
try:
@@ -47,13 +48,26 @@ def _init_bot_impl(self, mode: GameMode = GameMode.MJ3P):
else:
raise BotNotSupportingMode(mode)
+ # 覆写父类 react 方法
+ def react(self, input_msg: dict) -> dict | None:
+ reaction = super().react(input_msg)
+ if reaction is not None:
+ if self.mjai_bot.is_online():
+ self._is_online = "Online"
+ else:
+ self._is_online = "Offline"
+ return reaction
+
+ @property
+ def is_online(self):
+ return self._is_online
+
# 尝试获取mjai.bot实例,该方法可能会导致 panick,需要在分离进程中使用
def create_bot_instance(queue):
import riichi3p
try:
# 尝试创建一个mjai.bot实例
- riichi3p.online.Bot(1)
queue.put(True) # 将成功的标志放入队列
except Exception as e:
LOGGER.warning("Cannot create bot: %s", e, exc_info=True)
diff --git a/bot/bot.py b/bot/bot.py
index f4e8d17..f4b73d2 100644
--- a/bot/bot.py
+++ b/bot/bot.py
@@ -27,6 +27,11 @@ def __init__(self, name:str="Bot") -> None:
self.name = name
self._initialized:bool = False
self.seat:int = None
+ self.model_type:str = None
+
+ @property
+ def get_model_type(self) -> str:
+ return self.model_type
@property
def supported_modes(self) -> list[GameMode]:
diff --git a/bot/local/bot_local.py b/bot/local/bot_local.py
index d522b54..a0ccaed 100644
--- a/bot/local/bot_local.py
+++ b/bot/local/bot_local.py
@@ -18,6 +18,7 @@ def __init__(self, model_files:dict[GameMode, str]) -> None:
super().__init__("Local Mortal Bot")
self._supported_modes: list[GameMode] = []
self.model_files = model_files
+ self.model_type = "Local"
self._engines:dict[GameMode, any] = {}
for k,v in model_files.items():
if not Path(v).exists() or not Path(v).is_file():
diff --git a/bot/mjapi/bot_mjapi.py b/bot/mjapi/bot_mjapi.py
index feaafb4..fe95cf6 100644
--- a/bot/mjapi/bot_mjapi.py
+++ b/bot/mjapi/bot_mjapi.py
@@ -26,6 +26,7 @@ def __init__(self, setting:Settings) -> None:
self._login_or_reg()
self.id = -1
self.ignore_next_turn_self_reach:bool = False
+ self.model_type = "mjapi"
@property
def info_str(self):
diff --git a/bot_manager.py b/bot_manager.py
index 19821ee..8cf6175 100644
--- a/bot_manager.py
+++ b/bot_manager.py
@@ -463,6 +463,15 @@ def _update_overlay_botleft(self):
model_text = '🤖'
if self.is_bot_created():
model_text += self.st.lan().MODEL + ": " + self.st.model_type
+ if self.bot.get_model_type == "AkagiOT2":
+ if self.bot.is_online == "Online":
+ model_text += "(🌐)"
+ elif self.bot.is_online == "Offline":
+ model_text += "(🔌)"
+ elif self.bot.is_online == "Waiting":
+ model_text += "(⏳)"
+ else:
+ model_text += "(❓)"
else:
model_text += self.st.lan().MODEL_NOT_LOADED
From 40199c38442c3ebba98a7c25501a7a6430282c5e Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Wed, 29 May 2024 23:25:27 +0800
Subject: [PATCH 20/21] Update readme.md
---
readme.md | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/readme.md b/readme.md
index 6d92c06..62a5ac0 100644
--- a/readme.md
+++ b/readme.md
@@ -59,12 +59,48 @@ python main.py
```
### 配置模型
本程序支持几种模型来源。其中,本地模型(Local)是基于 Akagi 兼容的 Mortal 模型。要获取 Akagi 的模型,请参见 Akagi Github 的说明。
+
+#### 使用 Akagi OT2模型
如需使用 AkagiOT2 模型,请前往 Akagi 的官方 Discord 频道获取对应的 model.pth 和 riichi3p 包并安装。
+
+第一步:下载模型文件 model.pth 和 riichi3p 包
+
+第二步:将 model.pth 放置到目录 `mjai/bot_3p/` 下
+
+第三步:安装 riichi3p 包
+```bash
+pip install riichi3p-${version}.whl
+```
+此处安装对应 python 版本和操作系统的 riichi3p 包。
+
+第四步:运行 main.py,打开设置,在模型类型中选择 AkagiOT2 模型。
+
+第五步:设置 OT2 的 url地址 和 api_key,并保存。
+
+第六步:等待 OT2 模型加载完成,即可开始游戏。
+
### Model Configuration
This program supports different types of AI models. The 'Local' Model type uses Mortal models compatible with Akagi. To acquire Akagi's models, please refer to Akagi Github .
To use the AkagiOT2 model, please visit Akagi's official Discord channel to obtain the corresponding model.pth and riichi3p package, and then install them.
+#### Using the Akagi OT2 Model
+To use the AkagiOT2 model, please visit the official Akagi Discord channel to obtain the corresponding `model.pth` file and `riichi3p` package, and install them.
+
+Step 1: Download the model file `model.pth` and the `riichi3p` package.
+
+Step 2: Place the `model.pth` file in the directory `mjai/bot_3p/`.
+
+Step 3: Install the `riichi3p` package:
+```bash
+pip install riichi3p-${version}.whl
+```
+Install the `riichi3p` package appropriate for your Python version and operating system here.
+
+Step 4: Run `main.py`, open the settings, and select the AkagiOT2 model under model type.
+
+Step 5: Set the URL and API key for OT2 and save.
+Step 6: Wait for the OT2 model to load completely, then you can start the game.
## 截图 / Screenshots
From 1154c6823c6817401f9361068325b6aa231a325f Mon Sep 17 00:00:00 2001
From: Calvin Div
Date: Sat, 1 Jun 2024 22:01:09 +0800
Subject: [PATCH 21/21] =?UTF-8?q?bot=20=E5=8A=A0=E8=BD=BD=E9=AA=8C?=
=?UTF-8?q?=E8=AF=81=E7=9A=84=E8=B6=85=E6=97=B6=E6=97=B6=E9=97=B4=E5=AE=BD?=
=?UTF-8?q?=E9=99=90=E8=87=B3=2010=20=E7=A7=92=EF=BC=8C=E6=89=93=E5=8D=B0?=
=?UTF-8?q?=E8=B6=85=E6=97=B6=E9=94=99=E8=AF=AF=E3=80=82=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E6=9C=AA=E5=B0=9D=E8=AF=95=E5=8A=A0=E8=BD=BD=E7=9A=84=20bug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bot/akagiot2/bot_akagiot2.py | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/bot/akagiot2/bot_akagiot2.py b/bot/akagiot2/bot_akagiot2.py
index dba689b..97fc3b8 100644
--- a/bot/akagiot2/bot_akagiot2.py
+++ b/bot/akagiot2/bot_akagiot2.py
@@ -4,6 +4,7 @@
from bot.bot import BotMjai, GameMode
from common.log_helper import LOGGER
from common.utils import BotNotSupportingMode, Ot2BotCreationError
+import time
model_file_path = "mjai/bot_3p/model.pth"
@@ -68,6 +69,7 @@ def create_bot_instance(queue):
import riichi3p
try:
# 尝试创建一个mjai.bot实例
+ riichi3p.online.Bot(1)
queue.put(True) # 将成功的标志放入队列
except Exception as e:
LOGGER.warning("Cannot create bot: %s", e, exc_info=True)
@@ -82,10 +84,17 @@ def try_create_ot2_bot():
process.start()
# 尝试从队列中获取结果,设置超时时间防止无限等待
+ start_time = time.time()
+ timeout = 10
try:
- success = queue.get(timeout=3)
+ timeout = 10
+ success = queue.get(timeout=timeout)
except Exception as e:
+ end_time = time.time()
LOGGER.error("Failed to retrieve the result from the subprocess: %s", e)
+ if end_time - start_time >= timeout:
+ LOGGER.error("Timeout when waiting for the result from the subprocess")
+ process.terminate()
success = False
process.join()