- 桌面应用
- 人工智能
【免费下载链接】Akagi
支持雀魂、天鳳、麻雀一番街、天月麻將,能夠使用自定義的AI模型實時分析對局並給出建議,內建Mortal AI作為示例。 Supports Majsoul, Tenhou, Riichi City, Amatsuki, with the ability to use custom AI models to analyze games in real time and provide suggestions. Comes with Mortal AI as a built-in example.
导读
Akagi 的核心能力是「实时分析对局并给出建议」,而这一切都建立在一条统一的事件流之上:无论数据来自雀魂的 protobuf WebSocket、天鳳的 JSON WebSocket,还是麻雀一番街的二进制帧 WebSocket,最终都要转换成 AI 模型与前端 HUD 都能消费的 mjai JSONL 事件。承担这一转换职责的,就是本文要讲的src/bridge协议桥模块。读完本文,你将理解 Bridge trait 的抽象设计、桥的工厂创建流程、autoplay 层通过BridgeHooks与桥共享信息的机制,以及三个平台桥各自的线格式解析细节与重连处理方案,并掌握向 Akagi 添加新平台桥的完整步骤。
Bridge 模块的定位:wire 协议与 mjai 事件流之间的翻译层
在 Akagi 的架构中,src/bridge负责把各游戏平台私有的「线上协议」(wire protocol)翻译成 mjai JSONL 协议事件流。mjai 是日本麻将 AI 对局服务器的标准协议(JSONL:每行一个 JSON 事件),AI 机器人(bot)直接消费该协议,因此桥的存在让 Akagi 得以「一次解析、多处复用」:无论是下游的 AI bot、对局分析引擎,还是前端 HUD,看到的都是同一套 mjai 事件。
模块自述(src/bridge/README.md):Platform-specific protocol bridges between game wire protocols and the mjai JSONL protocol consumed by AI bots.
值得强调的是,mjai 事件类型并不归 bridge 模块所有,而是定义在 src/schema/mjai/mod.rs 的crate::schema::mjai中。这一设计在 src/schema/README.md 中有明确说明:所有跨越模块边界的数据——协议事件、前后端 IPC 载荷、持久化记录——都放在 schema 模块,避免被某个子系统独占。因此桥的输出、AI bot 的输入、前端 HUD 的展示共用同一套MjaiEvent类型。
核心抽象:Bridgetrait
桥的抽象在 src/bridge/mod.rs 中定义为一个只含两个方法的 trait:
pub trait Bridge: Send { fn parse(&mut self, direction: Direction, content: &[u8]) -> ParseResult; fn build(&mut self, command: &MjaiEvent) -> Option<Vec<u8>>; }parse— 将一条原始的入站 WS 二进制帧翻译为零个或多个MjaiEvent。它接收一个Direction参数标明帧方向:Direction::Up表示客户端→服务器的上行(如请求),Direction::Down表示服务器→客户端的下行(如响应、通知)。build— 反向转换:把一个出站 mjai 命令(同样以MjaiEvent表示)编码为可选的原始 WS 二进制帧,供 autoplay(自动打牌)使用。返回None表示该平台无法/无需编码此命令。
解析结果ParseResult
parse的返回值不是简单的事件数组,而是 ParseResult:
pub struct ParseResult { pub events: Vec<MjaiEvent>, pub parsed: Option<ParsedFrame>, }其中events是帧翻译出的 mjai 事件(可能为零个),而parsed是桥对帧的第一层结构化视图——例如 Majsoul 解码后的 protobuf 方法名与载荷、天鳳的{tag, …}JSON 字典——它被提供给 inspector(对局检查器),让开发者能看到「桥认为这个帧是什么意思」。无法解码的帧(握手、不支持的 method、畸形载荷)返回None。
mjai 事件类型:跨项目的统一契约
MjaiEvent枚举(src/schema/mjai/mod.rs)完整镜像了 mjai 协议规范的 15 种事件类型,外加 3 人麻将特有的kita(北抜き)变体:
| 事件 | 含义 | 关键字段 |
|---|---|---|
start_game | 对局开始 | names、id(bot 视角的自家座位)、num_players |
start_kyoku | 局开始 | bakaze、dora_marker、oya、scores、tehais |
tsumo | 摸牌 | actor、pai(他家摸牌为"?") |
dahai | 打牌 | actor、pai、tsumogiri(是否摸切) |
chi/pon/daiminkan/kakan/ankan | 副露 | actor、target、consumed |
dora | 宝牌指示 | dora_marker |
reach/reach_accepted | 立直声明 / 成立 | actor |
hora | 和牌 | actor、target、deltas、ura_markers |
ryukyoku | 流局 | deltas |
kita | 北抜き(仅 3 人) | actor、pai |
end_kyoku/end_game | 局 / 对局结束 | reason(Confirmed/Terminated) |
几个值得注意的实现细节(均有源码佐证):
- 序列化约定:枚举使用
#[serde(tag = "type", rename_all = "snake_case")],因此 JSON 中"type"永远是第一个键。测试 json_consistency_and_type_first 逐条断言了每个变体 round-trip 后type置顶。 - 玩家数量字段:
start_game与start_kyoku都携带num_players(3 或 4),serde 默认值为 4,保证旧的 4 人日志行仍能原样解析。 - Tile 用
String表示:如"1m"、"5mr"(赤五)、"E"、"P"、"?"。schema 刻意不引入第三方 Tile 类型,让桥与任何牌编码库解耦。 end_game的语义扩展:GameEndReason::Terminated表示服务器在无最终结算载荷的情况下终止了会话(如天鳳断线重连期间,见下文),序列化时仍为{"type":"end_game"}。
桥的实例化:一个 WebSocket flow 一个桥
「一个桥实例对应一个独立游戏会话」是模块的基本原则。对于 Majsoul,这意味着每个 WebSocket flow 一个桥实例——因为每个 flow 都有自己独立的请求 id 序列与游戏状态。这一约束直接体现在 src/bridge/majsoul/mod.rs 的模块注释中,也体现在 LiqiParser 的设计上:Response 包只携带msg_id,必须从「本 flow 专属的pending请求表」中查回对应的方法名与响应类型,因此解析器不能跨 flow 共享。
桥的创建:for_platform工厂
for_platform 根据配置中的Platform枚举创建对应的Box<dyn Bridge>:
pub fn for_platform( platform: crate::config::Platform, flow_log: Option<Arc<FlowLogger>>, session: Option<Arc<Session>>, hooks: BridgeHooks, ) -> Box<dyn Bridge>三个参数各有分工:
flow_log:每个 WS flow 的文本转储(每个解析过的消息一行 JSON),由 proxy 处理器在 WebSocket 升级时创建并传入(参见 src/logger/README.md 与 src/proxy/handler.rs)。session:透传给需要按需打开附加日志文件的桥。例如 Majsoul 桥每个新对局会轮转出一个新的*.mjai.jsonl文件(见 rotate_mjai_log)。hooks:autoplay 层的共享槽位(下节详述)。
工厂内部为每个平台组装并「builder 式」注入对应槽位:Majsoul 桥挂上time_budget与input_watch,天鳳桥挂上tenhou_state与notify,Riichi City 桥挂上riichi_inject。
调用方有两处,分别对应两种捕获路径:
- Chromium/CDP 捕获路径(src/capture/chromium/mod.rs):由于有
Page句柄,可以驱动浏览器页面点击,因此会注入time_budget、input_watch、tenhou_state等「基于页面」的槽位。 - MITM 代理路径(src/proxy/handler.rs):没有
Page句柄,只能注入 Riichi City 的帧注入门riichi_inject与notify通知通道。
BridgeHooks:autoplay 层与桥共享事实的槽位
autoplay(自动打牌)层需要一些「只有协议解析器才看得到」的事实,于是设计了BridgeHooks这个共享槽位结构(src/bridge/mod.rs)。其设计要点是:每个字段都是可选的、平台特定的;打包成一个结构体后,为某平台新增槽位就不必改动中间所有构造函数参数列表。
| 字段 | 平台 | 携带内容 |
|---|---|---|
time_budget | Majsoul | 服务器每个决策窗口的时间授权(OptionalOperationList.time_fixed/time_add,毫秒),供 autoplay 规划用(见src/autoplay/budget.rs) |
input_watch | Majsoul | 客户端自身上行输入命令的计数器,用于区分「一次真实点击」与「被 UI 吞掉的点击」(见src/autoplay/verify.rs) |
tenhou_state | Tenhou | 天鳳牌索引解析后的手牌 + 当前决策窗口——「客户端帧可编码」所需的事实(见src/autoplay/tenhou_state.rs) |
riichi_inject | Riichi City | 帧注入门,由桥在cmd_enter_room与cmd_room_end之间维护(见src/autoplay/inject.rs) |
notify | 各平台 | 前端 toast 通道,用于传输「mjai 事件无法承载、但必须告知用户」的状态(如天鳳局中断线重连会暂停分析),两条捕获路径都会接上 |
以 Majsoul 的time_budget为例:桥在解析到包含OptionalOperationList的帧时,把time_fixed/time_add写入共享槽位;autoplay 据此知道本次决策窗口还剩多少时间。再如input_watch:桥在dispatch中监听.lq.FastTest.inputOperation与.lq.FastTest.inputChiPengGang两个上行方法,只有「客户端确实接受了输入」才会发出它们,因此这些帧就是 autoplay 点击落地的证明(见 src/bridge/majsoul/mod.rs)。
三座平台桥的实现剖析
Majsoul(雀魂):protobuf + 位置相关 XOR 的五层线格式
Majsoul 桥由parser.rs(线格式解码)与mod.rs(mjai 状态机)两部分组成,线格式自述为 5 层(src/bridge/majsoul/parser.rs):
[type byte][msg_id u16 LE?][Wrapper protobuf] └─ name + data(bytes) └─ inner DynamicMessage └─ {name, data: base64(XOR(protobuf))} // action only- 类型字节:
01=Notify(通知,无msg_id)、02=Request(请求)、03=Response(响应)。Request/Response 在偏移 1..3 携带小端 u16 的msg_id。 - 方法名路由:依赖内嵌的 liqi.json(编译期
include_str!注入)——一个以全限定方法名为键的扁平 rpc-map:- 两段名(
lq.NotifyX)→ 直接在描述符池中查lq.NotifyX; - 三段名(
lq.Service.method)→ 查liqi.json[".lq.Service.method"]得到{req, resp}全限定类型名,再在池中解析。测试 all_routes_resolve 断言了池中 300+ 条路由的 req/resp 类型全部可解析,防止 proto 重新生成后与 liqi.json 失配。
- 两段名(
- Action 数据 XOR:某些内层消息携带
{name, data},其中data是对「名为name的 action」的 protobuf 字节做 base64 编码后的 XOR 密文。解码函数 wtf_decode 实现了一个位置相关的 XOR 方案:9 字节密钥表[0x84, 0x5E, 0x4E, 0x42, 0x39, 0xA2, 0x1F, 0x60, 0x1C],基数base = 23 ^ data.len(),逐字节异或base + 5*i + k。
一个重要的对称性陷阱:重连重放(GameRestore)里的 action 是不做 XOR 的。decode_restore_action(src/bridge/majsoul/parser.rs)直接 base64→protobuf 解码,若在此路径上误跑 XOR 反而会损坏字节。为此测试 gameplay_field_numbers_stable 锁定了lq.ActionPrototype等关键消息的字段编号,避免字段漂移在运行时才暴露。
状态机侧(src/bridge/majsoul/mod.rs)值得关注的状态与动作:
- 座位解析:
account_id从上行authGame请求捕获,seat在对应响应中通过seat_list索引解析;座位确定后桥发出start_game且id = seat,下游 bot 据此知道自己是哪家。 - 立直声明处理:
pending_reach_accepted记录刚通过ActionDiscardTile.is_liqi声明立直的座位,按 mjai 状态机要求,宣言牌必须先透传、随后才扣 1000 点;若下一动作是ActionHule(宣言牌被荣和),立直作废,不发出reach_accepted。 - 杠宝牌时机(
DoraTiming):mjai 区分暗杠(即乗り——摸岭上牌前翻宝牌)与加杠/大明杠(後乗り——摸岭上牌后、下次打牌前翻宝牌)。线上服务器与此吻合:暗杠的新宝牌指示随摸岭上的ActionDealTile.doras到达,而加杠/大明杠的指示被扣留到杠家下次打牌,随ActionDiscardTile增长后的doras数组一起到达。桥用PendingBeforeRinshan/PendingAfterRinshan两个时机状态调度这些指示,使dora事件在正确的节点发出。 - 荣和目标追踪:
HuleInfo本身不携带荣和目标座位,桥必须自行跟踪「最近一次翻牌揭示者的座位」(last_revealed_tile_actor),否则对杠/拔北的荣和会记错对象。该字段由ActionDiscardTile(普通荣和)、ActionAnGangAddGang(kakan)(搶槓)、ActionAnGangAddGang(ankan)(国士无双抢暗杠,雀魂特有规则)更新。 - 对局日志:桥在每次发出
start_game时于<session>/majsoul/majsoul_<ts>.mjai.jsonl轮转新文件,之后每个 mjai 事件追加为一行 JSON。
Tenhou(天鳳):纯 JSON tag 驱动的观察型桥
天鳳桥的线格式是纯 JSON:一个 WS 帧就是一个事件,由tag字段分发,心跳是字面量字节<Z/>。完整的 tag 清单与字段语义来自原 Akagi Python 天鳳桥的忠实 Rust 移植(src/bridge/tenhou/README.md):
| Tag | 触发 | mjai 输出 |
|---|---|---|
<Z/> | 心跳 | (无) |
HELO/REJOIN/SAIKAI/BYE/SHUFFLE | 会话控制 | (无) |
GO | 规则/房间(局前) | (无;暂存供MatchInfo,bit0x10= 三麻) |
UN | 名单 | (无;start_game消费,空槽 = 三麻) |
TAIKYOKU | 对局开始 | start_game(从oya解析自家座位) |
INIT | 局开始 | start_kyoku(0 分槽检测三麻) |
REINIT | 重连:进行中手牌的快照 | (无——见下文「局中重连」) |
T<n>/U<n>/V<n>/W<n> | 摸牌(相对座位 0..3) | tsumo |
D<n>等(大写) | 手切 | dahai { tsumogiri: false } |
d<n>等(小写) | 摸切 | dahai { tsumogiri: true } |
N(带m) | 副露 | chi/pon/daiminkan/kakan/ankan/kita |
REACH step=1/2 | 立直声明 / 成立 | reach/reach_accepted |
DORA | 新宝牌指示 | dora |
AGARI | 和牌 | hora+end_kyoku(带owari时 +end_game) |
RYUUKYOKU | 流局 | ryukyoku+end_kyoku(带owari时 +end_game) |
几个关键的编码规则:
- 牌编码:天鳳牌是整数索引
0..=135。index / 4给出牌型(0..=33),index % 4是变体。赤五精确位于16、52、88(序列化为5mr、5pr、5sr),见 tile.rs。 - 座位编码:天鳳消息永远使用相对座位(rel 0 是观察者),
State::rel_to_abs/abs_to_rel换算到 mjai 的绝对框架。桥从<TAIKYOKU oya="N"/>解析自家绝对座位:seat = (4 - N) % 4。三麻时线上的绝对座保持 4 个位置(缺位者是线位 3 的幽灵),seed[0]跳过其发牌。 - 副露位域:
<N m="..."/>把副露种类、目标座位、牌构成打包进一个整数。位解码在 meld.rs,严格遵循十万一的mentsu136.txt规范。北抜き((m & 0x3F) == 0x20)是特例,先于结构化解码器处理。 t属性(决策窗口):摸牌与打牌帧可能携带t位掩码,标明服务器正在向我们提供的选项,其角色与 Majsoul 的OptionalOperationList相同,桥把它记录在State::window供autoplay::tenhou_state使用。位含义取决于携带它的帧,两集合不重叠:
| bit | 摸牌帧(T<n>) | 打牌帧(D/E/F/G<n>) |
|---|---|---|
| 1 | — | pon |
| 2 | — | daiminkan |
| 4 | — | chi |
| 8 | — | ron |
| 16 | tsumo agari | — |
| 32 | riichi | — |
| 64 | 九種九牌 | — |
暗杠与加杠不在掩码中——客户端从手牌自行推导,Akagi 也通过立直引擎的合法动作推导。虽然只有 autoplay 消费该窗口,解析仍然是无条件的:成本低,且「陈旧的窗口比没有窗口更糟」。
反向编码(encode.rs)是此平台Bridge::build的唯一实现,产出 mjai → 客户端帧的映射(src/bridge/tenhou/encode.rs):
| mjai | 帧 |
|---|---|
dahai | {"tag":"D","p":<index>} |
reach | {"tag":"REACH"} |
chi | {"tag":"N","type":3,"hai0":<index>,"hai1":<index>} |
pon | {"tag":"N","type":1,"hai0":<index>,"hai1":<index>} |
daiminkan | {"tag":"N","type":2} |
ankan | {"tag":"N","type":4,"hai":<type * 4>} |
kakan | {"tag":"N","type":5,"hai":<index>} |
hora(自摸) | {"tag":"N","type":7} |
hora(荣和) | {"tag":"N","type":6} |
ryukyoku | {"tag":"N","type":9} |
none | {"tag":"N"} |
值得注意的是,autoplay 并不使用build的输出:天鳳客户端拥有自己的盘面状态,若打牌帧绕过客户端处理器直达服务器,客户端会冻结,因此autoplay::tenhou改为驱动客户端的输入路径,只从这个模块借用牌索引查询。会话控制帧(JOIN/GOK/NEXTREADY)被刻意不编码——Akagi 观察的是真实客户端,那些帧由客户端自己发送。
局中重连(REINIT)是天鳳桥最微妙的部分。当 Web 客户端重连对局时会开新 socket,桥看到的序列是:
↑ HELO ↓ GO (type, lobby) ↓ UN (full roster) ↑ GOK ↓ SAIKAI (ba, oya, sc) ↓ REINIT … then live play——没有TAIKYOKU。<REINIT/>充当<INIT/>的替代:携带seed/ten/oya/hai,外加每个有副露座位的m<rel>(逗号分隔的<N m=…/>位域)与kawa<rel>(牌索引,255标记立直宣言——下一项即立直牌)。它是快照而非日志——没有任何信息说明副露相对打牌的先后——因此无法重建进行中手牌的 mjai 事件流(相比之下,Majsoul 的GameRestore是有序 action 日志,所以 Majsoul 桥可以重放而天鳳桥不能)。
桥对此的处理策略(src/bridge/tenhou/README.md):
- 从帧中解析座位与人数,保证本 flow 后续归属正确;
- 只重建本 flow 自身需要的东西——自家手牌(
hai)、自家副露(m0)、立直(kawa0中的255)、是否手上有摸牌(牌数)——让 autoplay 与Bridge::build在本局剩余部分继续工作; - 不发出任何事件,并置
State::suspended:所有 handler 照常运行(手牌与窗口继续跟踪),但dispatch丢弃其事件直到下一个<INIT/>——否则会把实时对局套在「几巡之前」的快照上; - 通过
BridgeHooks::notify发出警告 toast,并在下一个<INIT/>补发start_game,以正确的座位与人数为 tracker、bot 与历史记录重新打开对局。
重连期间唯一仍被放行的事件是对局结束,此时end_game以terminated_game(GameEndReason::Terminated)发出:重连前的各手牌从未被本 flow 看到,历史记录会丢弃该对局,而非把它归档为数据残缺的完整对局。
Riichi City(麻雀一番街):15 字节二进制头 + JSON 的观察型桥
Riichi City 桥只通过 MITM 代理可达(原生客户端、无 Web 版,Chromium/CDP 捕获路径不适用),且默认只读(observe-only),不过其build实现了上行注入能力(见下文)。
线格式WPacket为 15 字节大端头 + 可选 JSON 体(src/bridge/riichi_city/packet.rs):
[0..4] packet_size u32 total bytes including header [4..6] header_size u16 always 15 ─┐ together the magic [6..8] version u16 always 1 ─┘ bytes 00 0f 00 01 [8..12] message_index u32 request/response correlation counter [12..14] cmd u16 binary command enum (CMDAuth = 1) [14] has_body u8 0 / 1 [15..] json_payload UTF-8 JSON (present when packet_size > 15)命令采用两级分发:二进制cmd只在CMDAuth(1)时有用——其 JSON 体携带玩家uid(客户端→服务器);所有对局消息则在 JSON 体内携带字符串"cmd"字段(cmd_enter_room、cmd_game_start、cmd_game_action_brc…),分发以该字符串为键。两个方向都会解析(uid包是上行,对局广播是下行;客户端请求帧不匹配任何服务器cmd_*,直接忽略)。
cmd→ mjai 的映射表(src/bridge/riichi_city/README.md):
Riichi Citycmd(+ action code) | mjai 事件 |
|---|---|
cmd_enter_room | —(记录玩家、桌大小) |
cmd_game_start | start_game(首局)→start_kyoku→ 开局tsumo |
cmd_in_card_brc | tsumo(他家摸牌,隐藏?) |
cmd_send_current_action | tsumo(自家摸牌,揭示) |
cmd_game_action_brcaction 2/3/4 | chi |
| … action 5 | pon |
| … action 6 | daiminkan |
| … action 8 | ankan(5 的第一槽位为红) |
| … action 9 | kakan |
| … action 11 | reach? +dahai(+ 延后的reach_accepted、杠宝牌 flush) |
| … action 13 | kita(北抜き) |
| … action 7 / 10 / 12 | —(在此标记结束;由cmd_game_end结算) |
cmd_gang_bao_brc | —(延后一个杠宝牌指示) |
cmd_game_end | hora(每名和牌者)或ryukyoku,然后end_kyoku |
cmd_room_end | end_game |
结算逻辑有几个易错的细节(src/bridge/riichi_city/mod.rs):
- 和牌 vs 流局以
all_point判定,而非win_info是否存在:win_info条目只有在all_point > 0时才是赢家;荒牌流局时win_info列出的是听牌玩家(all_point == 0,手牌揭示),必须读作流局。 deltas语义:每位玩家的point_profit(+extra_profit)——包含本场棒与已收的供托,但排除玩家自己的立直棒。立直的 −1000 由 mjai 消费者在reach_accepted时应用(与天鳳桥的拆分方式相同),若用user_point差分会重复计数。因此和牌 delta 之和 = +已收供托,流局 delta 之和为 0。- 荣和目标:
win_info[].user_id是赢家,荣和目标是最新打出者(last_dahai_actor),自摸目标即赢家本人;里宝牌 =win_info[].li_bao_card。每名赢家各发一个hora(两荣/三荣会得到多个)。 - 三麻:与 v2(用幽灵座填充到 4)不同,本桥发出原生长度 3的
scores/tehais,actor 索引保持在0..=2,与天鳳桥及 V3 下游三麻管线一致。
添加一个新平台:从枚举到工厂的完整步骤
原文档给出了四步流程(src/bridge/README.md),结合源码可以展开为可操作的清单:
- 给
config::Platform添加一个变体(src/config/platform.rs)。注意该枚举还实现了subdir()(用作日志会话下的子目录名)和From<Platform> for schema::Platform(映射到历史记录标签),新增平台时这两处也要同步补齐。 - 创建
src/bridge/<name>/mod.rs,定义实现Bridge的结构体。结构体通常持有:一个解析器(可能带请求-响应关联表)、一个 flow 级状态机(座位、手牌、dora 等)、可选的mjai_log与shared槽位。 - 在 src/bridge/mod.rs 重新导出,并在
for_platform添加 match 分支,用 builder 风格把需要的BridgeHooks字段注入实例。 - 如果平台要支持 autoplay 且其规划器需要「只有解析器看得到」的信息,就给
BridgeHooks加字段而不是加参数——这正是该结构体存在的理由。
此外,参考三个既有桥的实践,还应补上:单元测试(Majsoul 侧锁定了描述符池加载、rpc-map 路由、全部路由可解析、关键字段编号;Riichi City 侧用feed(..)辅助函数配假id 喂入真实帧形状)以及平台自身的 README(天鳳桥与 Riichi City 桥各有一份独立 README 详述 tag 表、编码规则与重连语义)。
小结
Akagi 的 bridge 模块以 15+1 种MjaiEvent为统一契约,通过仅含parse/build两个方法的Bridgetrait,把三种线格式截然不同的平台(protobuf+XOR 的雀魂、纯 JSON 的天鳳、15 字节二进制头+JSON 的麻雀一番街)收敛为同一条事件流;for_platform工厂与BridgeHooks共享槽位则保证了「一个 flow 一个桥」的状态隔离与 autoplay 层的事实注入。无论是阅读对局分析逻辑、接入新平台,还是排查自动打牌的行为,都可以从 src/bridge/README.md 出发,沿 src/bridge/mod.rs → 各平台子目录(majsoul、tenhou、riichi_city)→ src/schema/mjai/mod.rs 这条链路逐层深入。
- 桌面应用
- 人工智能
【免费下载链接】Akagi
支持雀魂、天鳳、麻雀一番街、天月麻將,能夠使用自定義的AI模型實時分析對局並給出建議,內建Mortal AI作為示例。 Supports Majsoul, Tenhou, Riichi City, Amatsuki, with the ability to use custom AI models to analyze games in real time and provide suggestions. Comes with Mortal AI as a built-in example.
相关推荐
Akagi雀魂助手:麻将AI智能决策系统深度解析
Akagi雀魂助手:麻将AI智能决策系统深度解析 在当今数字化的麻将竞技环境中,传统经验已难以应对复杂多变的牌局形势。Akagi雀魂助手作为专业级麻将AI辅助系
桌面应用人工智能Akagi雀魂助手:智能化麻将对战分析系统深度解析
Akagi雀魂助手:智能化麻将对战分析系统深度解析 在激烈的麻将对局中,如何精准把握每一个决策时机?Akagi雀魂助手作为一款专为雀魂游戏设计的智能辅助客户端,
桌面应用人工智能Akagi雀魂助手:智能麻将分析与实战指南
Akagi雀魂助手是一款专为雀魂麻将游戏设计的智能辅助客户端,通过集成先进的AI分析模型,能够实时解析牌局并提供精准的操作建议。该项目旨在为玩家提供便捷的方式实
桌面应用人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考