Skip to content

Commit b8c7181

Browse files
authored
Merge pull request #1 from diverpet/dev
v0.1.0
2 parents 1a86918 + b2ca49b commit b8c7181

File tree

7 files changed

+107
-86
lines changed

7 files changed

+107
-86
lines changed

bot/akagiot2/bot_akagiot2.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import multiprocessing
2+
3+
from bot.bot import BotMjai, GameMode
4+
from common.log_helper import LOGGER
5+
from pathlib import Path
6+
7+
from common.utils import BotNotSupportingMode, Ot2BotCreationError
8+
9+
model_file_path = "mjai/bot_3p/model.pth"
10+
11+
12+
class BotAkagiOt2(BotMjai):
13+
""" Bot implementation for Akagi OT2 model """
14+
15+
def __init__(self) -> None:
16+
super().__init__("Akagi OT2 Bot")
17+
self._supported_modes: list[GameMode] = []
18+
self._check()
19+
20+
def _check(self):
21+
# check model file
22+
if not Path(model_file_path).exists() or not Path(model_file_path).is_file():
23+
LOGGER.warning("Cannot find model file for Akagi OT2 model:%s", model_file_path)
24+
if try_create_ot2_bot():
25+
self._supported_modes.append(GameMode.MJ3P)
26+
else:
27+
LOGGER.warning("Cannot create bot for OT2 model.", exc_info=True)
28+
LOGGER.warning("Could be missing file: %s", model_file_path)
29+
raise Ot2BotCreationError("Failed to create bot instance for Akagi OT2 model.")
30+
pass
31+
32+
@property
33+
def supported_modes(self) -> list[GameMode]:
34+
""" return supported game modes"""
35+
return self._supported_modes
36+
37+
# 覆写父类方法
38+
def _init_bot_impl(self, mode: GameMode = GameMode.MJ3P):
39+
if mode == GameMode.MJ3P:
40+
try:
41+
import riichi3p
42+
self.mjai_bot = riichi3p.online.Bot(self.seat)
43+
except Exception as e:
44+
LOGGER.warning("Cannot create bot for Akagi OT2 model: %s", e, exc_info=True)
45+
LOGGER.warning("Could be missing model.pth file in path mjai/bot_3p")
46+
raise Ot2BotCreationError("Failed to create bot instance for Akagi OT2 model.")
47+
else:
48+
raise BotNotSupportingMode(mode)
49+
50+
51+
# 尝试获取mjai.bot实例,该方法可能会导致 panick,需要在分离进程中使用
52+
def create_bot_instance(queue):
53+
import riichi3p
54+
try:
55+
# 尝试创建一个mjai.bot实例
56+
riichi3p.online.Bot(1)
57+
queue.put(True) # 将成功的标志放入队列
58+
except Exception as e:
59+
LOGGER.warning("Cannot create bot: %s", e, exc_info=True)
60+
LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
61+
queue.put(False) # 将失败的标志放入队列
62+
63+
64+
# 使用分离进程尝试创建bot实例
65+
def try_create_ot2_bot():
66+
queue = multiprocessing.Queue()
67+
process = multiprocessing.Process(target=create_bot_instance, args=(queue,))
68+
process.start()
69+
70+
# 尝试从队列中获取结果,设置超时时间防止无限等待
71+
try:
72+
success = queue.get(timeout=3)
73+
except Exception as e:
74+
LOGGER.error("Failed to retrieve the result from the subprocess: %s", e)
75+
success = False
76+
77+
process.join()
78+
79+
if not success or process.exitcode != 0:
80+
LOGGER.error("Failed to create bot or detected a crash in the subprocess with exit code %s", process.exitcode)
81+
return False
82+
return True

bot/factory.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,27 @@
55
from .local.bot_local import BotMortalLocal
66
from .mjapi.bot_mjapi import BotMjapi
77
from .akagiot.bot_akagiot import BotAkagiOt
8+
from .akagiot2.bot_akagiot2 import BotAkagiOt2
89

910

10-
MODEL_TYPE_STRINGS = ["Local", "AkagiOT", "MJAPI"]
11+
MODEL_TYPE_STRINGS = ["Local", "AkagiOT", "MJAPI", "AkagiOT2"]
1112
OT2_MODEL_PATH = "./mjai/bot_3p/model.pth"
1213

1314

1415
def get_bot(settings:Settings) -> Bot:
1516
""" create the Bot instance based on settings"""
1617

1718
match settings.model_type:
18-
case "Local":
19-
if settings.enable_ot2_for_3p:
20-
model_files:dict = {
21-
GameMode.MJ4P: sub_file(Folder.MODEL, settings.model_file),
22-
GameMode.MJ3P: OT2_MODEL_PATH
23-
}
24-
else:
25-
model_files:dict = {
26-
GameMode.MJ4P: sub_file(Folder.MODEL, settings.model_file),
27-
GameMode.MJ3P: sub_file(Folder.MODEL, settings.model_file_3p)
28-
}
19+
case "Local":
20+
model_files:dict = {
21+
GameMode.MJ4P: sub_file(Folder.MODEL, settings.model_file),
22+
GameMode.MJ3P: sub_file(Folder.MODEL, settings.model_file_3p)
23+
}
2924
bot = BotMortalLocal(model_files)
3025
case "AkagiOT":
3126
bot = BotAkagiOt(settings.akagi_ot_url, settings.akagi_ot_apikey)
27+
case "AkagiOT2":
28+
bot = BotAkagiOt2()
3229
case "MJAPI":
3330
bot = BotMjapi(settings)
3431
case _:

bot/local/bot_local.py

Lines changed: 7 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,6 @@
77
from common.log_helper import LOGGER
88
from bot.local.engine import get_engine
99
from bot.bot import BotMjai, GameMode
10-
from common.settings import Settings
11-
import multiprocessing
12-
13-
# 尝试获取mjai.bot实例,该方法可能会导致 panick,需要在分离进程中使用
14-
def create_bot_instance(queue):
15-
import riichi3p
16-
try:
17-
# 尝试创建一个mjai.bot实例
18-
riichi3p.online.Bot(1)
19-
queue.put(True) # 将成功的标志放入队列
20-
except Exception as e:
21-
LOGGER.warning("Cannot create bot: %s", e, exc_info=True)
22-
LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
23-
queue.put(False) # 将失败的标志放入队列
24-
25-
# 使用分离进程尝试创建bot实例
26-
def try_create_ot2_bot():
27-
queue = multiprocessing.Queue()
28-
process = multiprocessing.Process(target=create_bot_instance, args=(queue,))
29-
process.start()
30-
31-
# 尝试从队列中获取结果,设置超时时间防止无限等待
32-
try:
33-
success = queue.get(timeout=3)
34-
except Exception as e:
35-
LOGGER.error("Failed to retrieve the result from the subprocess: %s", e)
36-
success = False
37-
38-
process.join()
39-
40-
if not success or process.exitcode != 0:
41-
LOGGER.error("Failed to create bot or detected a crash in the subprocess with exit code %s", process.exitcode)
42-
return False
43-
return True
4410

4511

4612
class BotMortalLocal(BotMjai):
@@ -64,28 +30,13 @@ def __init__(self, model_files:dict[GameMode, str]) -> None:
6430
except Exception as e:
6531
LOGGER.warning("Cannot create engine for mode %s: %s", k, e, exc_info=True)
6632
elif k == GameMode.MJ3P:
67-
settings = Settings()
68-
if settings.enable_ot2_for_3p:
69-
import riichi3p
70-
try :
71-
# 用分离进程尝试创建一个mjai.bot实例
72-
if try_create_ot2_bot():
73-
self._engines[k] = "./mjai/bot_3p/model.pth"
74-
else:
75-
LOGGER.warning("Cannot create bot for OT2 model %s.", k, exc_info=True)
76-
LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
77-
except Exception as e:
78-
LOGGER.warning("Cannot create bot for OT2 model %s: %s", k, e, exc_info=True)
79-
LOGGER.warning("Could be missing model.pth file in path ./mjai/bot_3p")
80-
pass
81-
else:
82-
# test import libraries for 3p
83-
try:
84-
import libriichi3p
85-
from bot.local.engine3p import get_engine as get_engine_3p
86-
self._engines[k] = get_engine_3p(self.model_files[k])
87-
except Exception as e: # pylint: disable=broad-except
88-
LOGGER.warning("Cannot create engine for mode %s: %s", k, e, exc_info=True)
33+
# test import libraries for 3p
34+
try:
35+
import libriichi3p
36+
from bot.local.engine3p import get_engine as get_engine_3p
37+
self._engines[k] = get_engine_3p(self.model_files[k])
38+
except Exception as e: # pylint: disable=broad-except
39+
LOGGER.warning("Cannot create engine for mode %s: %s", k, e, exc_info=True)
8940
self._supported_modes = list(self._engines.keys())
9041
if not self._supported_modes:
9142
raise LocalModelException("No valid model files found")

common/lan_str.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ class LanStr:
5050
AI_MODEL_FILE_3P = "Local Model File (3P)"
5151
AKAGI_OT_URL = "AkagiOT Server URL"
5252
AKAGI_OT_APIKEY = "AkagiOT API Key"
53-
AKAGI_OT2 = "AKAGI OT2"
54-
ENABLE_AKAGI_OT2_FOR_3P = "Enable AkagiOT2 for 3P(Only Effective when model type is Local)"
5553
AKAGI_OT2_URL = "AkagiOT2 Server URL"
5654
AKAGI_OT2_APIKEY = "AkagiOT2 API Key"
5755
MJAPI_URL = "MJAPI Server URL"
@@ -93,6 +91,7 @@ class LanStr:
9391
GAME_NOT_RUNNING = "Not Launched"
9492
# errors
9593
LOCAL_MODEL_ERROR = "Local Model Loading Error!"
94+
OT2_MODEL_ERROR = "OT2 Model Loading Error!"
9695
MITM_SERVER_ERROR = "MITM Service Error!"
9796
MITM_CERT_NOT_INSTALLED = "Run as admin or manually install MITM cert."
9897
MAIN_THREAD_ERROR = "Main Thread Error!"
@@ -187,8 +186,6 @@ class LanStrZHS(LanStr):
187186
AI_MODEL_FILE_3P = "本地模型文件(三麻)"
188187
AKAGI_OT_URL = "AkagiOT 服务器地址"
189188
AKAGI_OT_APIKEY = "AkagiOT API Key"
190-
AKAGI_OT2 = "AKAGI OT2"
191-
ENABLE_AKAGI_OT2_FOR_3P = "三麻中启用 AkagiOT2(仅在选择 Local 模型时生效)"
192189
AKAGI_OT2_URL = "AkagiOT2 服务器地址"
193190
AKAGI_OT2_APIKEY = "AkagiOT2 API Key"
194191
MJAPI_URL = "MJAPI 服务器地址"
@@ -232,6 +229,7 @@ class LanStrZHS(LanStr):
232229
GAME_NOT_RUNNING = "未启动"
233230
#error
234231
LOCAL_MODEL_ERROR = "本地模型加载错误!"
232+
OT2_MODEL_ERROR = "OT2 模型加载错误!"
235233
MITM_CERT_NOT_INSTALLED = "以管理员运行或手动安装 MITM 证书"
236234
MITM_SERVER_ERROR = "MITM 服务错误!"
237235
MAIN_THREAD_ERROR = "主进程发生错误!"

common/settings.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,14 @@ def __init__(self, json_file:str=DEFAULT_SETTING_FILE) -> None:
3434

3535
# AI Model settings
3636
self.model_type:str = self._get_value("model_type", "Local")
37-
""" model type: local, mjapi"""
37+
""" model type: local, mjapi, AkagiOT, AkagiOT2"""
3838
# for local model
3939
self.model_file:str = self._get_value("model_file", "mortal.pth")
4040
self.model_file_3p:str = self._get_value("model_file_3p", "mortal_3p.pth")
4141
# akagi ot model
4242
self.akagi_ot_url:str = self._get_value("akagi_ot_url", "")
4343
self.akagi_ot_apikey:str = self._get_value("akagi_ot_apikey", "")
4444
# akagi ot2 3p model
45-
self.enable_ot2_for_3p:bool = self._get_value("enable_ot2_for_3p", False, self.valid_bool)
4645
self.akagi_ot2_url:str = self._get_value("akagi_ot2_url", "")
4746
self.akagi_ot2_apikey: str = self._get_value("akagi_ot2_apikey", "")
4847
# for mjapi

common/utils.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,13 @@ class UiState(Enum):
6464
GAME_ENDING = 20
6565

6666

67-
# === Exceptions ===
67+
# === Exceptions ===
6868
class LocalModelException(Exception):
6969
""" Exception for model file error"""
7070

71+
class Ot2BotCreationError(Exception):
72+
""" Exception for OT2 bot creation error"""
73+
7174
class MITMException(Exception):
7275
""" Exception for MITM error"""
7376

@@ -84,6 +87,8 @@ def error_to_str(error:Exception, lan:LanStr) -> str:
8487
""" Convert error to language specific string"""
8588
if isinstance(error, LocalModelException):
8689
return lan.LOCAL_MODEL_ERROR
90+
elif isinstance(error, Ot2BotCreationError):
91+
return lan.OT2_MODEL_ERROR
8792
elif isinstance(error, MitmCertNotInstalled):
8893
return lan.MITM_CERT_NOT_INSTALLED + f"{error.args}"
8994
elif isinstance(error, MITMException):

gui/settings_window.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,6 @@ def create_widgets(self):
153153
string_entry = ttk.Entry(main_frame, textvariable=self.akagiot_apikey_var, width=std_wid*4)
154154
string_entry.grid(row=cur_row, column=1,columnspan=3, **args_entry)
155155

156-
# Enable Akagi OT2
157-
cur_row += 1
158-
_label = ttk.Label(main_frame, text=self.st.lan().AKAGI_OT2)
159-
_label.grid(row=cur_row, column=0, **args_label)
160-
self.enable_ot2_for_3p_var = tk.BooleanVar(value=self.st.enable_ot2_for_3p)
161-
enable_akagi_ot2_entry = ttk.Checkbutton(
162-
main_frame, variable=self.enable_ot2_for_3p_var, text=self.st.lan().ENABLE_AKAGI_OT2_FOR_3P, width=std_wid*3)
163-
enable_akagi_ot2_entry.grid(row=cur_row, column=1, **args_entry)
164156
# Akagi OT2 url
165157
cur_row += 1
166158
_label = ttk.Label(main_frame, text=self.st.lan().AKAGI_OT2_URL)
@@ -315,7 +307,6 @@ def _on_save(self):
315307
mode_file_3p_new = self.model_file_3p_var.get()
316308
akagi_url_new = self.akagiot_url_var.get()
317309
akagi_apikey_new = self.akagiot_apikey_var.get()
318-
enable_ot2_for_3p_new = self.enable_ot2_for_3p_var.get()
319310
akagi_url2_new = self.akagiot2_url_var.get()
320311
akagi_apikey2_new = self.akagiot2_apikey_var.get()
321312
mjapi_url_new = self.mjapi_url_var.get()
@@ -328,7 +319,6 @@ def _on_save(self):
328319
self.st.model_file_3p != mode_file_3p_new or
329320
self.st.akagi_ot_url != akagi_url_new or
330321
self.st.akagi_ot_apikey != akagi_apikey_new or
331-
self.st.enable_ot2_for_3p != enable_ot2_for_3p_new or
332322
self.st.akagi_ot2_url != akagi_url2_new or
333323
self.st.akagi_ot2_apikey != akagi_apikey2_new or
334324
self.st.mjapi_url != mjapi_url_new or
@@ -366,7 +356,6 @@ def _on_save(self):
366356
self.st.model_file_3p = mode_file_3p_new
367357
self.st.akagi_ot_url = akagi_url_new
368358
self.st.akagi_ot_apikey = akagi_apikey_new
369-
self.st.enable_ot2_for_3p = enable_ot2_for_3p_new
370359
self.st.akagi_ot2_url = akagi_url2_new
371360
self.st.akagi_ot2_apikey = akagi_apikey2_new
372361
self.st.mjapi_url = mjapi_url_new

0 commit comments

Comments
 (0)