Files
game-cards/docs/superpowers/specs/2026-05-28-guandan-card-game-design.md
2026-05-28 23:29:43 +08:00

50 KiB
Raw Permalink Blame History

掼蛋卡牌游戏 — 设计规格

日期: 2026-05-28 引擎: Godot 4.x 语言: GDScript


一、分层架构

UI 层 (Godot) → 业务逻辑层 (纯 GDScript) → 数据层 (纯 GDScript)
                     ↓
                 网络层 (Godot ENet后期)
职责 技术选型 说明
UI 层 场景渲染、手牌布局、拖拽出牌、按钮交互、动画、音效 Godot Control 节点 / Tween 唯一依赖 Godot API 的层,纯"表现"
业务逻辑层 牌型识别、比较大小、出牌合法性、回合流转、得分计算、AI 决策 纯 GDScript零 Godot 依赖) 核心层,不引用任何 Godot 场景/节点类
数据层 牌、牌堆、手牌、回合状态、玩家数据、配置表 纯 GDScript 数据结构 不可变数据结构优先,便于状态回滚
网络层 联机消息格式、同步协议、断线重连 Godot ENet 后期开发,前置预留接口

交互规则:

  • UI 层通过信号/回调调用逻辑层(如 logic.play_cards(player, cards)
  • 逻辑层返回结果后UI 层负责渲染
  • 逻辑层完全不感知 UI、网络或 AI 的存在(依赖倒置)

异常码定义:

  • 逻辑层所有操作统一返回 Result 类型:{ok: bool, error_code: int, data: Variant}
  • 错误码在 constants.gd 中定义枚举:ERR_INVALID_CARDSERR_NOT_YOUR_TURNERR_CANNOT_PASSERR_CARD_NOT_FOUNDERR_HAND_EMPTY
  • UI 层根据 error_code 做对应提示文案,支持多语言

线程安全约束:

  • core/ 和数据层禁止写全局变量,所有状态通过参数传入、返回值传出
  • 避免联机/异步场景下的数据竞争,逻辑层保持无状态函数风格
  • 若需缓存计算结果(如 AI 评分),使用局部闭包而非全局状态

逻辑纯函数约束:

  • 核心判定函数(牌型识别、比较大小、合法性校验)必须为纯函数:相同输入必须返回相同输出,无副作用
  • 禁止在核心函数中操作文件、网络、定时器等外部资源
  • 便于单元测试、并行 AI 计算、状态回滚验证

逻辑层的异步执行方案:

  • AI 计算(尤其 L2/L3 枚举所有合法出牌组合)是 CPU 密集型操作,同步调用会卡死 UI 线程
  • 利用 Godot 4.x WorkerThreadPool 将 AI 决策放入后台线程计算,通过信号或 await 将结果回传给 UI 层
  • move_generator 的枚举计算同样异步化,game_controller 负责协调异步调用链

多线程 AI 的数据竞争防护:

  • Godot 4 的 WorkerThreadPool 执行 GDScript 时,严禁子线程直接读取或修改主线程的 GameStateGDScript 的 Dictionary/Array 非线程安全,会导致崩溃或脏数据)
  • AI 线程只能接收序列化后的纯数据快照,在子线程完成计算后,仅将生成的 Action 序列传回主线程
  • 快照格式不可使用 JSON 字符串(频繁编解码导致内存抖动和 CPU 耗时反向超标),应使用原生包装数组PackedInt32Array / PackedByteArray利用位掩码Bitmask传递手牌和已出牌状态
  • 主线程的 game_controller 统一调用 Action.apply() 修改状态,确保同一时刻只有主线程操作数据

状态回滚策略 — 命令模式 + Action 重放:

  • GDScript 中深拷贝(duplicate(true))大型 GameState 性能极差,不可行
  • 采用 命令模式Command Pattern:每次操作生成 Action,执行 Action.apply(GameState) 修改状态
  • 回滚时从初始状态重放 Action 日志到目标点,而非存储完整快照
  • 若需中间快照(如 AI 推演分支仅对变化的局部数据Hand、PlayedZone做浅拷贝不全局深拷贝

全局事件总线EventBus

  • autoload/ 中增加 event_bus.gd(全局信号中心),用于广播游戏事件
  • 事件类型:PLAYER_PLAYED_CARDSBOMB_DETONATEDTRIBUTE_TRIGGEREDROUND_ENDGAME_OVER
  • UI 组件之间通过订阅 EventBus 信号解耦,避免网状硬编码依赖
  • core/ 层不依赖 EventBusgame/ 胶水层和 ui/ 层使用

EventBus 内存泄漏防护:

  • UI 节点(如动态生成的 card_node.tscn)若连接全局 EventBus 信号但在销毁时未断开,会导致严重内存泄漏和野指针崩溃
  • 在 UI 基类中重写 _exit_tree(),自动遍历并断开所有与 EventBus 的连接
  • 或利用 Godot 4 的 Callable 弱引用特性(bind_weak())管理信号订阅,节点销毁时自动失效

配置数据的 Resource 化:

  • 静态配置卡牌属性、音效映射、AI 权重参数、牌型分值表)优先定义为继承自 Resource 的自定义类
  • 示例:class_name CardConfig extends Resourceclass_name AIParams extends Resource
  • 优势:可直接利用 Godot 编辑器 Inspector 面板可视化调参;底层 C++ 序列化的性能和安全性远超手写 JSON 解析
  • config_loader.gd 保留用于运行时动态配置(如服务器下发的活动参数),静态配置一律用 .tres Resource 文件

状态不可变性统一规范:

  • GDScript 天生偏可变,需明确不可变性边界:
    • 绝对不可变对象: CardPlayedCardsAction(创建后其字段不可修改)
    • 允许局部修改: Round(回合内状态可变)、GameState(通过 Action.apply 修改)
    • 禁止共享引用: 所有 getter 返回副本或只读包装(duplicate(false)),禁止返回内部 Array/Dictionary 的原始引用
    • ArrayDictionary 类型的成员变量外部访问时一律浅拷贝

资源生命周期管理:

  • 场景退出时必须执行清理,防止残留线程、孤儿节点和隐性内存增长:
    • 线程取消: _exit_tree() 中取消所有 WorkerThreadPool 未完成任务,await 超时后强制丢弃
    • Replay 中止: 场景切换时中断回放录制/播放,保存当前进度
    • 节点对象池上限: ObjectPool 设置 MAX_POOL_SIZE,超出部分直接 queue_free()
    • Tween 清理: _exit_tree()stop_all()kill() 所有活跃 Tween
    • 信号断开: 调用 disconnect_all() + 从 EventBus 取消订阅

日志系统分级:

  • 除 Action 游戏日志外,需另建调试日志系统(log_manager.gdautoload
    • DEBUG详细调试信息Release 默认关闭
    • INFO:关键流程节点(发牌、进贡、出牌、升级)
    • WARN异常但可恢复AI 超时降级、心跳超时、状态不一致)
    • ERROR致命错误Action 失败、状态机非法转移)
    • NETWORK:联机消息日志
    • AI_TRACEAI 决策明细日志
    • PERF:性能基准日志
  • 安全约束: INFO 及以上禁止打印完整手牌内容;联机模式 DEBUG 默认关闭;日志支持写入文件 + 按大小轮转

多人异步事件顺序约束:

  • 常见冲突场景:动画未播完时收到下一 Action、Pass 与清场同时发生、接风与结算同时触发
  • 解决方案:
    • Event Queue事件队列 game_controller 维护优先级事件队列,按时间戳串行消费
    • UI Transition Lock 动画/过渡期间锁住输入,动画结束回调释放锁
    • 强制事件串行化: 同一帧内只处理一个状态变更,防止 UI 状态错乱
    • 事件优先级:FATAL_ERROR > GAME_OVER > PLAYER_ACTION > ANIMATION > IDLE

配置热更新边界:

  • 配置分为三层,每层有不同的更新约束:
层级 说明 更新时机 示例
Immutable Match Config 对局开始后不可变 仅在房间创建时 牌数、逢人配规则、炸弹规则
Runtime Tweak Config 可运行时调整 对局中或局间 AI 难度、音效音量、UI 动画速度
Client Cosmetic Config 纯本地配置 随时 卡牌皮肤、背景主题、语言
  • RuleConfig(不可变)在联机房间中由房主下发,参与状态哈希;对局中修改需重开房间

二、核心数据模型

卡牌

掼蛋使用两副牌108张含大小王。

Card:       整数 ID 0-107两副牌位运算高效比较大小
            card_id: 0-107 全局唯一 ID用于日志追踪、网络同步和 UI 渲染
            card_value: 花色 + 点数编码,用于规则比对(忽略唯一 ID
            Suit: 4 花色 (♠ ♥ ♣ ♦) + Joker
            Rank: 2-14 (2=2, ..., 10=10, J=11, Q=12, K=13, A=14) + 小王=15 + 大王=16
            original_id: 0-53标记该牌来自第几副牌的第几张区分两副牌中相同点数花色的牌
Deck:       108 张 Card 列表 → 洗牌、发牌
Hand:       玩家手牌,按 Rank→Suit 排序

card_id 与 card_value 区分:

  • card_id0-107全局唯一日志/同步/渲染时使用
  • card_valueSuit + Rank 编码):规则比对时使用,两副牌中相同花色点数的牌共享同一 card_value
  • 牌型识别、比较大小等核心函数统一按 card_value 比较,忽略 card_id

出牌

PlayedCards: 带类型的联合
    {type: TYPE_SINGLE | TYPE_PAIR | TYPE_TRIPLE | ... | TYPE_ROCKET,
     cards: [...],
     primary_rank: 主牌点数,
     is_pure_bomb: bool}  # 炸弹是否为纯炸(无逢人配参与)

牌型识别是纯函数:输入 [Card] → 输出 PlayedCards | null

多合一牌型歧义与推荐优先级:

当手牌同时拥有多张逢人配时,同一组牌可能对应多种牌型。例如手牌 4、5、6 + 两张逢人配:

  • 可构成顺子:4、5、6、逢(7)、逢(8)
  • 可构成连对:4、4、5、5、6(逢人配转写为 4 和 5 的对子)
  • 可构成三带二:4、4、4、5、6(逢人配转写为三同张)

hand_evaluator.gd 需定义牌型推荐优先级枚举,当同一组牌存在多种合法解释时,按优先级返回默认牌型:

优先级 牌型 说明
1 火箭(四王) 最高
2 炸弹(含纯炸 > 混炸) 优先组成更大炸弹
3 同花顺
4 钢板(三连对)
5 连对
6 顺子
7 三带二
8 三不带
9 对子
10 单张 最低

UI 层在检测到歧义牌型时,允许玩家通过按钮切换可选分支("以顺子出" / "以连对出")。

同花顺配牌花色转写: 逢人配(红桃级牌)参与拼凑非红桃同花顺时,其自身花色强制转写为目标花色,结算时仅作为目标花色的点数存在,丧失原本的红桃属性。

游戏状态

Round:      当前回合状态(谁出牌、出的什么、本轮出牌历史)
GameState:  完整一局状态4个玩家、牌堆、得分、升级、贡牌信息
            round_seq: 局次编号,支持多局连胜/积分统计
            current_rank: 当前打的级牌等级2-A决定逢人配红桃级牌为万能牌

2v2 队伍模型Team

  • 掼蛋为 2v2 对战1、3 号位为一队Team A2、4 号位为一队Team B
  • 算分、升级、进贡、还贡、AI 配合逻辑全部强依赖队伍关系
  • GameState 引入 Team 结构:{team_id, players: [Player], score, current_level}
  • 每局胜利条件:一队中两人都先出完即为"双下"(大胜),一人先出完为"头游"(小胜)

局间流转状态机:

掼蛋完整循环不仅是出牌,还包含复杂的局间阶段。GameState 的状态机:

INIT → DEAL → TRIBUTE_PHASE → PLAY_PHASE → LEVEL_UP_PHASE → DEAL下一局/ GAME_OVER
阶段 说明 关键校验
INIT 初始化,确定级牌、洗牌
DEAL 发牌 27 张/人 两副牌 108 张全部分发
TRIBUTE_PHASE 进贡 / 还贡 / 抗贡 双下进贡分配逻辑、抗贡条件(对家也是进贡方则互抵)
PLAY_PHASE 出牌循环,直到三人出完 牌型合法性、回合轮转、过牌规则、三人连续 Pass 触发清场CLEARED
LEVEL_UP_PHASE 根据名次判定升级 头游队升 1 级,双下游队向胜队进贡;双下则胜队升 3 级

级牌与逢人配动态判定:

  • GameState.current_rank 维护当前打的级牌等级
  • 红桃花色 + 当前级牌点数 = 逢人配(万能牌),在牌型评估时动态赋予百搭属性
  • rule_engine / hand_evaluator 在识别牌型时,必须结合 current_rank 判定是否为逢人配

CLEARED 状态(清场规则):

  • 掼蛋规则中若连续三人弃牌Pass最后出牌者获得自由出牌权即该轮"清场"
  • Round 状态机需增加 CLEARED 子状态标识
  • UI 层配合:播放"桌面旧牌收起"动画 + 音效,明确提示玩家当前可出任意合法牌型
  • 清场后当前玩家开始新一轮出牌,本轮历史出牌记录归档至出牌历史侧边栏

接风规则Catch-Up

  • 掼蛋核心规则:当某玩家打出最后一手牌,且全场 Pass该玩家成为头游下一轮的自由出牌权接风权转移给其 对家(队友),而非下家
  • round.gd 在检测到玩家手牌清空(HAND_EMPTY)且其余人 Pass 时,强制执行接风拦截:下轮 active_player 直接指定为当前出完牌玩家的 teammate
  • 接风后队友获得自由出牌权(等同于 CLEARED 状态),可出任意牌型
  • 若接风者同时也是最后一名有空牌的玩家,则本局结束

逢人配"纯度"降级规则:

  • 含逢人配的炸弹(如 3 张真牌 + 1 张逢人配)在比较时,必须 小于 同等点数的纯炸4 张真牌)
  • 逢人配 不能 参与组成天王炸(四王 / 火箭)
  • PlayedCards.is_pure_bomb 标记记录炸弹纯度;rule_engine 在比较炸弹时,纯度作为高于点数的第一优先级:
    1. 先比类型(火箭 > 普通炸弹)
    2. 再比纯度(纯炸 > 混炸)
    3. 再比点数Rank 大的 > Rank 小的)
    4. 最后比张数(炸弹张数多的 > 少的)

进贡/还贡具体边界校验:

  • 进贡阶段合法性校验:
    • 进贡必须出手牌中 最大的牌(受当前级牌/逢人配排除规则约束,依具体规则变体而定)
    • 不能进贡逢人配(红桃级牌),不能进贡当前级牌
    • 双下进贡:末游向头游进贡、次末游向头游队友进贡
  • 还贡阶段合法性校验:
    • 还贡必须是 单张
    • 还贡不能是逢人配
    • 还贡点数不能大于 10依规则变体可配置
  • 抗贡条件:双方均为进贡方(互为进贡对象)时互抵,既不进也不还

大王抗贡的唯一性校验:

  • 掼蛋规定"双大王抗贡",两副牌共 2 张大王
  • 抗贡必须由 同一个人 同时抓到 2 张大王(Rank.16),才触发全场抗贡
  • 若队友各执一张大王,不触发抗贡
  • tribute_phase 校验逻辑:遍历单人手牌,检查单人 handRank.16 数量 ≥ 2

进贡最大牌的"同点数次级判定"

  • 当玩家需要进贡最大牌,但手牌中最大点数有两张(如两张不同花色的 K且都不属于级牌
  • 次级判定规则:
    1. 优先进贡非红桃的牌(避免将逢人配进贡出去)
    2. 若点数花色均相同(两副牌中同一张牌),系统随机选择或提供 UI 让玩家勾选
    3. 逢人配(红桃级牌)绝对不可进贡,已在主规则中排除

Action 日志

Action:     {player, action_type, cards, timestamp}
action_type: PLAY | PASS | TRIBUTE_GIVE | TRIBUTE_RETURN | RESHUFFLE | GAME_END

状态变更生成 Action 记录,构成不可变日志。这是联机同步和观战回放的基础。

贡牌/还贡专用 Action 使用独立类型 TRIBUTE_GIVETRIBUTE_RETURN,避免与普通出牌混同导致逻辑混乱和校验冲突。

逢人配来源标记: 每张用于配牌的逢人配 Card 附带 wild_source: GRADE_CARD | SYSTEM_ALLOC 标记,支持规则变体(是否允许级牌做配牌、系统自动配牌上限等)。

RuleConfig规则变体配置化

掼蛋在不同地区存在规则差异,所有核心规则必须数据驱动,不得硬编码在逻辑中。

RuleConfig: {
    same_suit_straight_beats_bomb: bool,  # 同花顺是否大于普通炸弹
    double_down_levels: int,              # 双下升几级(默认 3
    can_tribute_wild: bool,               # 是否允许红桃级牌进贡
    tribute_return_max_rank: int,         # 还贡点数上限(默认 100=不限)
    straight_extends_to_ace: bool,        # 顺子是否可延伸到 A
    bomb_compare_priority: enum,          # 炸弹比较优先级BOMB_BY_RANK 点数优先 / BOMB_BY_COUNT 张数优先
    wild_count: int,                      # 逢人配数量(默认 2
    team_formation: enum,                  # 队伍构成FIXED_1_3 固定 1/3 号位 / RANDOM 随机
}

使用约束:

  • RuleConfig 是 Immutable Match Config对局开始后不可修改
  • 联机时由房主下发统一 RuleConfig,参与 GameState 状态哈希校验
  • 核心规则函数(rule_enginehand_evaluatorgame_state)的参数中必须包含 RuleConfig 引用
  • 单机模式下可通过菜单选择预设规则("标准" / "淮安" / "安徽" 等)

三、AI 架构

层级 名称 适用场景 逻辑
L1 基础 AI 训练模式"简单"难度 能出就出最少的牌,规则合规即可
L2 规则 AI 训练模式"中等"、单机普通 启发式评分:保留炸弹、拆牌优先短顺、配牌合理使用
L3 策略 AI 训练模式"困难"、单机困难 记忆出牌、推理队友意图、控制炸弹节奏

技术细节:

  • AI 与玩家调用同一个 logic.play_cards() 接口
  • L2/L3 使用启发式评分:每个合法出牌组合 → 计算策略评分 → 选最优
  • L3 加入对战历史分析,通过复盘 Action 日志做状态推演
  • 训练模式的"牌型提示"展示 L2 AI 的最优解

AI 安全与性能约束:

  • 防死循环机制: AI 决策若超过 3 秒未返回,自动降级到 L1 基础 AI确保对局不卡死
  • 可禁用提示开关: 训练模式的牌型提示可通过配置关闭,避免干扰正式对局或高阶玩家
  • 统一决策耗时上限: 所有 AI 层级共用 MAX_AI_DECISION_MS 配置项,防止卡顿主线程;复杂计算使用分帧/线程池处理

队伍配合意识Teammate Awareness

  • L2/L3 AI 的启发式评分函数必须引入队友和对手状态权重:
    • 队友剩余牌数:队友剩 1-2 张时优先出小单张/对子"送牌"助攻
    • 队友头游判定:队友已出完时切换到保级策略
    • 对手压制权重:对手剩 1-2 张时提升炸弹/大牌的出牌优先级,避免对手跑掉
  • 评分公式示例:score = hand_quality * 0.4 + teammate_context * 0.3 + opponent_threat * 0.3

宏观局势与目标管理:

  • AI 需根据当前积分差和升级规则,动态切换策略模式:
    • 保守策略:本局只需保一人头游即可升级时,优先保护已有优势,减少激进出牌
    • 激进策略:落后方必须抓双下才能反超时,主动拆炸弹、保留大牌组合冲击双下
  • 策略模式切换由 GameState 的积分差和 current_rank 自动计算

记牌与算牌维度L3 专用):

  • L3 维护"场外信息池"Hidden Information Pool追踪三类信息
    1. 记大牌王、级牌、A 的已出数量,推算剩余威胁
    2. 记断门:某花色已出完 13 张时,推断对手不可能持有该花色的同花顺/炸弹
    3. 记剩余牌数:结合已出牌总数,推算对手手中最多几张炸弹,判断压制可能性
  • 信息池在每轮出牌后增量更新,不依赖回放全量重算

L3 手牌推断Hand Inference

  • 仅有"记牌池"不足以支撑 L3 高级决策,还需根据对手行为概率化推测其手牌分布
  • utils/probability.gd 中增加贝叶斯推断模块:
    • 对手进贡了一张红桃A → 推断对手手中缺大牌 → 后续可大胆用中等牌型压制
    • 对手多轮不出某花色 → 推定该花色可能为断门或预留炸弹
    • 还贡牌型(小单张)→ 推断对手在清杂牌准备冲刺
  • 推断结果以概率分布形式存入 Hidden Information Pool供评分函数加权使用

伪随机数生成器PRNG种子注入

  • deck.gd 的洗牌算法Fisher-Yates必须支持外部注入随机种子Seed
  • AI 决策中涉及随机选择的部分(如多个同等评分出牌)同样使用可复现的 PRNG
  • 目的:跑 GUT 自动化测试时,可复现完全相同的发牌序列和 AI 决策路径,实现确定性测试
  • 正式对局使用系统真随机(randf()),测试用固定种子

AI 可观测性与调试工具:

  • AI 决策对玩家和开发者不透明,需提供可观测性支持:
    • AI Decision Trace 决策树日志,记录每一轮 AI 考虑的所有合法出牌组合及其评分明细
    • 评分明细展示: 调试模式下显示"为什么出这手牌"面板:手牌质量: 0.7 | 队友权重: 0.8 | 对手威胁: 0.5 = 总分 2.0
    • AI HeatMap 手牌上颜色覆盖显示关键牌/危险牌权重(红色=炸弹候选,蓝色=顺子候选,黄色=配牌候选)
    • Replay + AI Debug Overlay 回放模式下叠加 AI 决策过程,可逐帧回溯 AI 思路
  • 调试模式通过配置开关控制(enable_ai_debug),正式对局关闭

AI 与规则引擎的隔离边界:

  • AI 推演必须在沙盒环境中完成,防止隐性修改真实状态:
    • AI 禁止直接修改任何 GameState 或其子对象的字段
    • AI 不允许缓存任何可变引用(如 GameState.players 数组的引用)
    • AI 推演时必须基于 GameState 的只读快照(PackedInt32Array),在沙盒中操作临时状态
    • AI 唯一输出为 Action 对象或 null(弃牌),由 game_controller 统一 apply

四、项目文件结构

game-cards/
├── project.godot
├── addons/                   # 第三方插件(按需)
├── assets/
│   ├── cards/                # 52 张卡面图 + 2 王牌 + 牌背
│   ├── ui/                   # UI 贴图、按钮、桌布背景
│   ├── audio/                # 音效、BGM
│   └── fonts/                # 字体
│
├── localization/             # 多语言文本
│   ├── zh_cn.csv             # 简体中文
│   └── en.csv                # 英文
│
├── save/                     # 存档目录(运行时生成)
│   ├── settings.cfg          # 用户设置
│   └── records/              # 对局记录
│
├── tools/                    # 工具脚本
│   ├── card_generator.gd    # 批量生成卡牌资源
│   └── config_converter.gd  # 配置文件格式转换
│
├── src/
│   ├── core/                 # ★ 业务逻辑层(零 Godot 依赖)
│   │   ├── card.gd           # Card 类型、牌组定义、排序比较
│   │   ├── deck.gd           # 洗牌、发牌
│   │   ├── hand.gd           # 手牌管理、排序
│   │   ├── hand_evaluator.gd # 牌型识别:判断给定牌组属于何种牌型
│   │   ├── move_generator.gd # 合法出牌生成:枚举手牌所有合法出牌组合(含剪枝优化)
│   │   ├── rule_engine.gd    # 出牌合法性校验 + 牌型比较大小
│   │   ├── round.gd          # 回合流转状态机
│   │   ├── game_state.gd     # 全局游戏状态(计分、升级、贡牌、队伍)
│   │   ├── actions.gd        # Action 日志定义 + apply/rollback
│   │   ├── config_loader.gd  # 配置表解析JSON/CSVAI 参数、关卡等)
│   │   └── constants.gd      # 枚举、配置常量
│   │
│   ├── utils/                # 通用工具库
│   │   ├── combinatorics.gd  # 组合数学C(n,m)、排列枚举、剪枝辅助)
│   │   ├── shuffle.gd        # 洗牌算法Fisher-Yates
│   │   └── probability.gd    # 概率计算辅助
│   │
│   ├── ai/                   # AI 模块
│   │   ├── base_ai.gd        # AI 基类接口
│   │   ├── l1_basic_ai.gd    # 简单 AI
│   │   ├── l2_rule_ai.gd     # 规则 AI带启发式评分
│   │   └── l3_strategy_ai.gd # 策略 AI
│   │
│   ├── game/                 # 游戏控制器(胶水层)
│   │   ├── game_controller.gd    # 单机对战的流程控制
│   │   ├── training_controller.gd # 训练模式控制
│   │   └── replay_recorder.gd     # 回放录制
│   │
│   ├── network/              # 联机层(后期开发)
│   │   ├── network_interface.gd   # 抽象接口
│   │   ├── client.gd              # 客户端同步
│   │   └── server.gd              # 服务器端
│   │
│   ├── ui/                   # UI 场景和脚本
│   │   ├── scenes/
│   │   │   ├── main_menu.tscn
│   │   │   ├── game_table.tscn     # 主牌桌
│   │   │   ├── training_room.tscn  # 训练室
│   │   │   ├── lobby.tscn          # 大厅(联机)
│   │   │   └── spectator.tscn      # 观战
│   │   └── components/             # 可复用 UI 组件
│   │       ├── card_node.tscn      # 单张卡牌控件
│   │       ├── hand_area.tscn      # 手牌区域
│   │       ├── played_zone.tscn    # 出牌展示区
│   │       ├── card_type_hint.tscn # 牌型提示浮窗(训练模式)
│   │       ├── play_history_sidebar.tscn # 出牌历史侧边栏(可折叠)
│   │       └── scoreboard.tscn     # 记分板
│   │
│   └── autoload/             # Godot Autoload 单例
│       ├── config.gd          # 全局配置
│       ├── event_bus.gd       # 全局事件总线(信号中心),解耦 UI 组件通信
│       ├── audio_manager.gd   # 音频管理
│       └── scene_manager.gd   # 场景切换
│
└── tests/                    # 测试(逻辑层)
    ├── test_cards.gd
    ├── test_deck.gd
    ├── test_hand_evaluator.gd
    ├── test_move_generator.gd
    ├── test_rule_engine.gd
    ├── test_game_state.gd
    ├── test_tribute.gd        # 进贡/还贡/抗贡边界
    ├── test_state_recovery.gd  # 断线重连状态恢复
    └── test_ai.gd

关键原则:

  • core/ 目录下任何文件不引用 res://src/ui/res://src/game/ 或 Godot 节点类
  • game/ 是桥接层,串联 core + UI或 core + network
  • test 只测 core 层,不依赖场景

rule_engine 拆分理由:

  • 带有逢人配(百搭)时,合法出牌组合数量呈指数级爆炸,rule_engine.gd 职责过重
  • 拆分为三个模块:
    • hand_evaluator.gd:判断给定牌组属于何种牌型(纯判定,轻量)
    • rule_engine.gd:出牌合法性校验 + 比较大小(规则比对)
    • move_generator.gd:生成当前手牌所有合法出牌组合(含剪枝优化,供 AI 专用)

move_generator 剪枝策略(防组合爆炸):

逢人配 + 双副牌导致合法出牌组合数呈指数级失控,必须硬性剪枝:

  • 等价状态去重: 相同牌型、相同点数的出牌只保留一种如两张不同的黑桃A组成的对子视为等价
  • 对称配牌剪枝: 多张逢人配可互换位置时,只保留一种枚举(如两张逢人配当 78 与当 87 等价)
  • Beam Search 上限: L2/L3 评分阶段保留前 BEAM_WIDTH(默认 50个最优候选丢弃低分分支
  • 最大枚举节点数: 单次 move_generator 枚举上限 MAX_ENUM_NODES(默认 10000超限后自动降级为启发式搜索
  • 降级策略: 枚举超限时切换为贪心出牌(按牌型优先级选择最大牌组合),保证回合不超时

新增文件

├── src/core/
│   ├── rule_config.gd       # RuleConfig 资源类(定义规则变体)
│   └── ...
├── src/
│   ├── statistics/           # 数值统计模块
│   │   └── stats_tracker.gd  # 胜率、双下率、炸弹率等统计
│   │
│   ├── debug/                # 调试与可观测性
│   │   ├── ai_trace.gd       # AI 决策日志
│   │   └── debug_overlay.gd  # 调试叠加层
│   │
│   ├── logging/              # 日志系统
│   │   └── log_manager.gd    # 分级日志管理

五、测试策略

测试类型 范围 工具 内容
单元测试 core/ 所有模块 GUT (Godot Unit Test) 牌型识别、比较大小、得分计算、洗牌发牌
集成测试 core/ + ai/ GUT AI 对不同局面的决策、完整一局流程
回归测试 core/ 规则引擎 GUT 固定测试套件,每次修改规则引擎必须全过
恶意输入测试 core/ 所有入口 GUT 空牌、重复牌、越界牌、非法牌型组合
性能基准 core/ + ai/ GUT + 计时 牌型识别 <1ms、AI决策 <3s、状态序列化 <50ms
UI 测试 ui/ 场景 手动 + 截图对比 手牌渲染、动画、交互

重点测试用例:

  • 掼蛋所有牌型识别覆盖(单张、对子、三不带、三带二、顺子、钢板、连对、同花顺、炸弹、火箭)
  • 配牌(逢人配)边界情况 — 最高/最低配、多配同时可选
  • 炸弹升级场景(四炸 → 五炸 → … → 火箭)
  • 逢人配被炸弹压制的优先级
  • CI 每次提交跑 gut 命令行,全绿才算合格

回归测试集要求: 维护一份固定的 tests/regression/ 目录,包含 50+ 覆盖各种边界的牌型场景,每次修改规则引擎必须全部通过。

恶意输入测试覆盖:

  • 传入空数组、重复卡片 ID、越界 ID<0 或 >107
  • 不符合任何牌型的非法卡牌组合
  • 同一张牌在牌组中出现超过 4 次(两副牌最多每种 4 张)

性能基准阈值:

  • 单次牌型识别 < 1ms
  • move_generator 最坏情况(手牌 15 张含 2 张逢人配):枚举所有合法组合 < 16ms保证异步处理不超时
  • AI 决策L2< 2sL3< 5s超时自动降级
  • 完整状态序列化/反序列化 < 50ms

进贡/还贡/抗贡边界测试用例:

  • 双下进贡分配逻辑:谁向谁进贡、进几张、大小牌选择
  • 抗贡条件判定:双方均为进贡方时互抵(不进不还)的精确条件
  • 还贡牌型合法性:还贡必须是单张、不能还逢人配、不能还级牌
  • 进贡阶段 Action 日志完整性

断线重连状态恢复测试:

  • 基于 Action 日志重放恢复 GameState 的集成测试
  • 确保重连后双方状态绝对一致:已出的牌、剩余牌数、当前分数、谁在回合中
  • 测试中断场景:进贡中掉线、出牌中掉线、结算中掉线

测试数据生成器:

  • 随机牌局生成器Fuzz tools/fuzz_dealer.gd,自动生成 10000+ 局随机发牌,喂入 AI 对打,自动发现崩溃/卡死/非法状态
  • 特殊边界牌局生成器: tools/edge_case_generator.gd,预设边界场景(全炸弹手、全逢人配、空手回溯等)
  • Monte Carlo 对局生成: 随机发牌 × 多 AI 难度 × 多规则配置的批量对局,输出胜率统计和性能分布
  • Fuzz 测试集成至 CI 每次提交跑 500 局随机对局,发现异常自动标记

六、开发顺序

  1. 训练模式 — 核心逻辑 + L1/L2 AI + 牌型提示 UI
  2. 单机对战 — 完整四人局 + L3 AI + 完整 UI
  3. 联机 — Action 同步协议 + 匹配 + 房间
  4. 观战 — Action 流回放 + 观战 UI

各阶段接口冻结点:

阶段 冻结接口 说明
训练模式完成 Card、Deck、Hand、PlayedCards、rule_engine 核心数据结构和牌型判定不再变更
单机对战完成 GameState、Round、Action、AI 基类接口 状态管理和 AI 接口定型
联机开始 NetworkInterface 抽象层 联机协议可独立迭代不影响上层

每个阶段冻结后,后续重构只影响当前阶段模块,不回溯修改已冻结接口。

训练模式优先完成:

  • 牌型校验全覆盖(所有牌型 + 边界 + 逢人配),全部回归测试通过
  • 再做 AI 集成与 UI 开发,避免"功能在前,正确性在后"导致返工

单机模式全流程验证:

  • 必须先跑通"发牌 → 进贡 → 还贡 → 出牌循环 → 升级判定 → 结算"完整链路
  • 验证多局连胜、级牌升级、炸弹升级等长期状态正确性

七、约束与边界

单局最大时长限制: 配置项 MAX_GAME_DURATION_SEC,超时判定为强制结束/平局,防止一方故意拖延。

防重复出牌校验: 同一套牌(相同 original_id 组合)不能在同一轮连续打出,尤其针对"过牌后再出同一套"的边界。

出牌区最大显示数量: 中央出牌区每次最多显示 4 组(各玩家最新一轮出牌),旧牌自动收起,避免 UI 溢出。

级牌、主牌、逢人配优先级判定流程:

  1. 判定当前级牌等级(从 GameState.level 读取)
  2. 主牌 = 级牌 + 两张逢人配(系统配)
  3. 逢人配可用于补齐任意牌型(顺子、钢板、连对等),但必须在炸弹压制规则下低于同点数炸弹
  4. 逢人配不参与火箭(大小王)判定

终端状态分支:

  • GAME_END_NORMAL一方升至A并获胜
  • GAME_END_SURRENDER:一方投降
  • GAME_END_TIMEOUT:超时强制结束
  • GAME_END_DRAW:平局(双方同分且均未升级)
  • GAME_END_DISCONNECT:联机中一方掉线超时

断线托管机制: 联机模式下,离线玩家自动交由 AI默认 L2托管并在 UI 显示托管标识和倒计时;重连后恢复玩家控制。

异常恢复策略:

  • 不可恢复的异常Fatal Error需分级处理
    • Action apply 失败: 日志记录 + 拒绝该 Action + 请求服务端全量状态同步
    • 非法状态进入: 自动回退到最近合法快照(每 N 轮自动保存快照),从快照重放
    • Action 日志损坏: 标记损坏段 + 请求全量同步 + 客户端重建 UI
    • 回放中断: 保存进度 + 允许从中断点续播
    • 网络包缺失: 基于 action_seq 检测空洞 + 发送补发请求 + 超时后请求全量同步
  • 崩溃日志导出: 发生 Fatal Error 时自动导出崩溃栈 + 最近 100 条 Action 日志到 save/crash/

多人托管切换边界:

  • 托管玩家重连时的冲突场景需要明确定义:
    • 所有权转移时机: 托管状态下AI 已产生但未 apply 的 Action 在玩家重连瞬间丢弃;正在执行的 AI 计算可等结果返回,但标记为"AI 建议"而非强制执行
    • AI 行为不可撤销: 已 apply 的 AI Action 不可回退,玩家重连后从当前状态继续
    • 重连保护期: RECONNECT_GRACE_PERIOD(默认 2s重连后 2 秒内禁用托管自动出牌,给玩家恢复操作时间
    • 托管模式下 UI 显示闪烁边框 + "AI Auto-Play" 标签

八、UI 交互规范

手牌布局规则:

  • 自动对齐:手牌按 Rank→Suit 排序后均匀分布,中心对齐
  • 重叠防遮挡:手牌扇形/横向排列,重叠率 50%-65%,被选中牌抬高 20px
  • 长牌适配:手牌 >20 张时自动缩小间距和卡牌尺寸,确保全部可见

27 张手牌的"智能理牌/折叠"

  • 起手 27 张牌(进贡后可达 28 张),在移动端或小屏幕上极其拥挤,拖拽框选极易误触
  • 按牌型分类视图: 手牌区分为单张区、对子区、三同张区、炸弹区、顺子区,玩家可切换视图
  • 自动折叠: 连续的顺子、钢板、三同张自动折叠显示为一组,点击展开
  • 手动标记: 玩家可手动将多张牌标记为一组,组内牌共享高亮颜色
  • 折叠/展开状态持久化,保存到用户设置

出牌历史侧边栏:

  • 中央出牌区收起旧牌后,玩家算牌极为困难
  • 在 UI 边缘增加可折叠的"出牌历史侧边栏",以紧凑文本或微缩图形式记录最近 3-5 轮各家的出牌明细
  • 显示格式:[轮次] 玩家1: 过 | 玩家2: 对2 | 玩家3: 对K| 玩家4: 过
  • 辅助玩家手动算牌、也为 L3 AI 的记牌模块提供快速回溯入口

滑动框选防误触V-Filtering 重叠度阈值):

  • 移动端 27 张牌极度紧凑,框选时极易连带选中目标牌左右两侧的边缘牌
  • 引入 滑动轨迹重叠度阈值:手指滑动的 X/Y 轴轨迹覆盖某张卡牌 中心区域超过 40% 时才判定选中
  • 仅擦过卡牌边缘(覆盖率 <10%)的不触发悬起/选中
  • 阈值可配置(SELECTION_OVERLAP_THRESHOLD),桌面端可适当降低

逢人配智能吸附:

  • 玩家手持逢人配且正在构建不完整牌型时(如已选 3、4、5、6),点击"提示"或长按空白区
  • UI 自动将手牌中的逢人配牌弹起并闪烁,提示玩家此牌可补齐当前残缺牌型(如当 7 完成顺子)
  • 发生多歧义时(既可补顺子也可补连对),弹出小型选项菜单供玩家选择

卡牌 UI 节点轻量化设计:

  • 同屏可能超过 150+ 个卡牌 Control 节点4 人 × 27 张 + 出牌区 + 历史记录),每个节点挂载独立 Tween/信号监听在低端设备极易掉帧
  • 分级渲染策略:
    • 暗牌(非玩家手牌):仅渲染微缩"数量标签Label"或单一共享九宫格动态图,不生成独立的卡牌实体节点
    • 明牌(玩家手牌 + 中央出牌区):实例化为独立的 card_node.tscn,但禁用不必要的关系检测和碰撞
  • 对局中动态销毁已出牌的节点复用节点对象池Object Pool避免频繁 instantiate / queue_free

出牌二次确认:

  • 玩家点击"出牌"按钮后出现确认提示,显示即将打出的牌型和点数
  • 训练模式可选关闭确认,单机/联机默认开启
  • 支持"自动出牌"快捷键:选中牌后双击或按 Enter 直接出

多牌选择交互:

  • 单击选中/取消单张牌
  • 长按拖拽框选连续区域
  • Ctrl+点击连选多张
  • "提示"按钮一键选中 AI 推荐的最优出牌组合

回合计时与状态屏蔽:

  • 每回合显示倒计时进度条(默认 30s超时自动过牌
  • 等待他人出牌时,手牌区灰显 + 禁用操作
  • 出牌结束到下一回合之间显示过渡动画0.5s),期间屏蔽输入

输入系统冲突裁定:

  • 同时存在点击、长按、滑动框选、双击、Ctrl 连选等多种输入方式,需定义冲突优先级:
    • 输入状态机: InputFSM 管理空闲IDLE→ 轻触TOUCH_DOWN→ 拖动DRAGGING→ 长按激活LONG_PRESS→ 释放RELEASED
    • Gesture Priority 滑动优先于长按(手指移动 >5px 进入 DRAGGING取消长按计时双击 < 300ms 且位移 <5px 判定双击Ctrl 状态与触摸并存时优先解析为 Ctrl 连选
    • PC / Mobile 分离输入策略: 桌面端优先键盘快捷键 + 鼠标操作;移动端优先触摸手势;同一事件不触发双份响应

移动端性能边界:

  • 最低目标机型: iPhone X2017 / 骁龙 665 中端 Android保持 ≥ 30 FPS
  • FPS 目标: 桌面 ≥ 60 FPS移动端 ≥ 30 FPS
  • 最大内存占用: 桌面 < 256MB移动端 < 128MB
  • 动态降级策略: FPS 持续 < 20 时自动关闭动画Tween、降低卡牌分辨率、减少出牌历史保留轮数
  • 低端机配置开关: LOW_PERF_MODE,关闭粒子特效、阴影、抗锯齿,使用预烘焙卡牌贴图

跨平台适配:

  • 分辨率适配: 支持 16:9、21:9、4:3 主流比例;采用 Control.anchor + Control.expand 响应式布局
  • 横竖屏: 强制横屏landscape卡牌游戏竖屏无法容纳 27 张手牌
  • DPI 缩放: 使用 Godot Theme 系统和 DisplayServer.screen_get_dpi() 自动调整字体/控件尺寸
  • 安全区Safe Area 适配刘海屏/挖孔屏,使用 DisplayServer.get_display_safe_area() 避让
  • 平板设备使用特殊 UI 布局(更大卡牌、更宽手牌区域)

九、数据与序列化

状态哈希校验: 每局结束计算 GameState 的 SHA-256 哈希,客户端和服务器(或双方客户端)比对,防止联机数据篡改。

日志裁剪策略:

  • Action 日志按局分割,开局清空上局日志
  • 单局日志上限 10000 条(约 500 轮出牌),超出自动截断头部
  • 完整日志仅在开启回放录制时保留,写入 save/records/

存档格式版本:

  • 所有序列化数据头部包含 version: int 字段
  • 当前版本 V1后续升级需提供 migrate_vN_to_vN+1() 迁移函数
  • 读取存档时校验版本号,不兼容版本提示用户更新

状态重放的随机数种子绑定:

  • Action 日志用于回滚/重放时,若涉及 RESHUFFLE(洗牌)或 AI 决策中的 PRNG 动作,必须强制绑定该局开局的 seed
  • 断线重连重放 Action 时若随机数种子不一致AI 会产生与掉线前完全不同的决策分支,导致前后端状态彻底跑飞
  • Action 日志起始位置记录 {seed: int, timestamp: int},重放时还原相同种子
  • deck.gd 的洗牌和 AI 的随机选择全部使用可注入种子的 PRNG

Replay 确定性保证(多平台一致性):

仅绑定 seed 不足以保证跨平台回放一致,还须满足严格条件:

  • Action 执行顺序绝对固定(按时间戳严格递增,禁止并发 apply
  • Dictionary 遍历不能参与逻辑GDScript Dictionary 迭代顺序不保证,改用 Array + 排序)
  • 所有排序必须使用稳定排序算法(sort_custom + 稳定比较器)
  • AI 随机源禁止使用系统随机(randf() / randi()),强制使用种子 PRNG
  • 时间戳不能参与规则逻辑(仅用于日志标记和 UI 动画节奏,不用于状态判定)

数值统计体系:

  • 长期统计数据用于匹配、AI 调优和平衡分析:
    • stats_tracker.gd 记录每个玩家的:总对局数、胜率、双下率、被双下率
    • AI / 实战统计:平均出牌时长、炸弹使用率、逢人配使用次数、进贡胜率
    • Replay 元数据:对局时长、回合数、局次、规则配置、参与者信息
  • 统计数据存储在 save/stats/,格式为结构化 JSON支持导出分析
  • AI 难度统计分离记录("L1 胜率" vs "L2 胜率"),辅助 AI 调优

十、网络层(预留)

同步模式选型:

模式 原理 适用
状态同步(推荐) 服务器计算权威状态,客户端接收后渲染 回合制卡牌,延迟容忍度高
帧同步 同步操作指令,各端独立计算 实时操作类游戏,此处不适用

选择状态同步模型,同步频率为每轮出牌结束时触发一次。

客户端作弊防护Server-Authoritative

所有 Action 必须在服务端做权威校验,客户端仅允许发送"意图"

  • 非法 Action 注入防范: 服务端 validate_action(game_state, action) 校验每一项 Action包括出牌合法性、回合归属、seq_id 连续性
  • 客户端手牌篡改防范: 客户端不持有完整 GameState服务端维护权威手牌客户端只收到自身手牌信息
  • 重放攻击防范: Action 增加 seq_id(严格递增)+ nonce(随机盐),服务端拒绝重复 seq_id
  • Action_seq 回退检测: 服务端记录每个连接的最后 ack_seq,拒绝 < ack_seq 的 Action
  • 客户端伪造 AI 托管结果防范: 托管由服务端发起AI 计算在服务端完成;托管期间客户端发送的 Action 一律拒绝
  • 防作弊时效: 出牌合法性只在服务端最终裁定,客户端可以做本地预判以优化 UI 响应,但服务端结果始终覆盖客户端

联机同步冲突恢复Authoritative Rollback

客户端预测状态与服务端权威状态不一致时的处理:

  • 客户端每次提交 Action 后,服务端回传 {action_seq, state_hash}
  • 客户端比对自身状态的哈希:若一致则确认;若不一致则触发回滚
  • 回滚流程: 暂停 UI 输入 → 请求从 action_seq 开始的全量状态同步 → 重建 UI手牌重排、出牌区清空重渲染→ 播放"重同步"过渡动画 → 恢复输入
  • UI 层必须支持状态全量重建:接收到新的 GameStateView 后完全重建手牌、出牌区、计分板,而非增量 patch

网络协议版本兼容:

  • 存档版本(data_version)≠ 联机协议版本(protocol_version),两者独立管理
  • protocol_version 定义在 NetworkInterface 中,起手握手阶段交换
  • 客户端最低兼容版本: MIN_COMPATIBLE_PROTOCOL,低于此版本的客户端拒绝连接,提示更新
  • 热更新后的版本协商: Lobby 阶段交换支持的 protocol 范围,若房主与客户端版本不兼容则显示提示
  • 协议变更需提供 protocol_migrate_vN_to_vN+1() 迁移处理

观战一致性:

  • 手牌可见性: 非好友房观战默认隐藏所有手牌,仅显示公共出牌区;好友房可经房主授权后显示队友手牌
  • 延迟观战: 观战者看到的画面延迟 N 回合(默认 3 回合 / 可配置),防止实时观战泄露信息给对战者
  • 中途加入: 允许中途加入观战,从加入时刻最近的 CLEARED 状态开始重放,不追溯更早回合
  • 透视作弊防护: 观战者持有独立的 spectator_view 视野过滤器,与对局玩家网络通道完全隔离
  • 观战人数上限:每局最多 50 名观战者,超出拒绝新加入

联机状态机正式定义:

完整的房间生命周期状态图,确保 network / game / ui 三层状态同步:

                    创建房间
                       ↓
    ┌───────────────── IDLE ──────────────────┐
    │                   ↓ 有人加入              │
    │               WAITING (等待满员)           │
    │                   ↓ 4人齐                │
    │               READY_CHECK                │
    │                          ╲              │
    │         ALL_READY     TIMEOUT            │
    │              ↓            ↓              │
    │         IN_GAME     返回 WAITING         │
    │              ↓                           │
    │  ┌─── PLAYING ───┐                      │
    │  │   ↓ 掉线       │                      │
    │  │  HOSTING      │   (托管中)            │
    │  │   ↓ 重连       │                      │
    │  │  RECONNECTING │                      │
    │  │   ↓           │                      │
    │  └→ PLAYING     ←┘                      │
    │      ↓ 对局结束                          │
    │   FINISHED                              │
    │      ↓                                  │
    │  VOTE_PHASE (投降投票/再开投票)          │
    │      ↓                                  │
    └── IDLE ─────────────────────────────────┘
          ↓ 房主解散房间
       DESTROYED

各状态约束:

  • PLAYINGHOSTING心跳超时触发AI 自动托管UI 标记托管状态
  • HOSTINGRECONNECTING:客户端发起重连,服务端追帧
  • RECONNECTINGPLAYING:状态同步完成,RECONNECT_GRACE_PERIOD 内禁止托管出牌
  • FINISHEDVOTE_PHASE:允许投降投票(联机)/ 下一局投票,对手否决权生效
  • VOTE_PHASEIDLE:投票通过或超时,房间解锁

房间与开局条件:

  • NET_ROOM_CREATEDNET_PLAYER_JOINED × 4 → NET_ALL_READYNET_GAME_START
  • 4 名玩家全部准备后房主点击开局
  • 等待中可设 AI 补位开关

心跳与异常处理:

  • 客户端每 5s 发送心跳包
  • 服务端 15s 未收到心跳判定断线,触发托管
  • 重连后追帧:客户端发送最后收到的 action_seq,服务端补发缺失的 Action 列表

房间锁、准备状态:

  • 开局后房间上锁,禁止新玩家加入
  • 对局中任意时间仅房主可发起投降投票(需队友同意)
  • 对局结束后房间自动解锁,返回等待状态

断线重连的视野过滤Vision Filter / Fog of War

  • 服务端序列化状态发送给特定客户端时,必须经过视野过滤器
  • 只打包该玩家有权知道的信息:自身手牌、公共出牌区、已公开的进贡/还贡牌、历史出牌记录
  • 绝对禁止将包含所有玩家手牌的完整内存对象直接下发,防止抓包窥牌
  • NetworkInterface 中实现 filter_state_for_player(game_state, player_id): GameStateView 方法,返回裁剪后的可见状态

进贡阶段的动态视野变化:

  • 进贡和还贡不是同时发生的A 进贡给 B 时B 可见此牌,但 C 和 D 不可见
  • 直到还贡结束或出牌阶段该牌被打出,其他玩家才可见该牌的去向
  • 服务端的 filter_state_for_player 必须动态感知 TRIBUTE_PHASE 内的"卡牌所有权临时移交状态"
  • 防止联机时外挂通过抓包提前得知他人的进贡牌

投降机制的对手否决权:

  • 掼蛋涉及"升 3 级(双下)"和"升 1 级"的巨大差异,一方提前投降会损害对手抓双下的收益
  • 投降不仅需要队友同意,还必须发起对手接受投票
  • 流程:发起方 → 队友同意 → 发起对手投票 → 对手两人中至少一人接受 → 投降生效
  • 若对手拒绝投降,游戏必须继续,直至产生实际的名次结算
  • 单机模式中对 AI 可配置投降阈值(如分差 > 100 分),免除投票流程