够级五回合大乱斗:从跟牌改成对决,以及机器人怎么出牌
够级五回合大乱斗:从跟牌改成对决,以及机器人怎么出牌

传统够级是顺序跟牌。这次把 Game_gj 整条局流程拆掉重写,做成 6 人红蓝对决、5 回合比牌。房间状态机、发牌、牌型、结算、机器人出牌都是新的,堆在 melee/ 下面。
局流程变成:
Init → (FaPai → Playing → DuelReveal → RoundSettle) × 5 → End
定庄、翻牌换座、cgjopcard 跟牌都不再驱动状态机。客户端按新协议走:发牌确认 gj.cgjrounddealconfirm,出牌 gj.cgjplaycard。
时序先抽出来
对决时刻动画很多:明牌、大类公告、胜家、收分、金钟罩。一开始这些间隔散在 GJRoomSet 里,改一次节奏要翻好几处。后来统一收到 GJMeleeStateMachineConfig,房间只读配置执行步进。
public static final int DEAL_TIMEOUT_MS = 1800;
public static final int PLAY_TIMEOUT_MS = 20000;
public static final int REVEAL_FIRST_STEP_WAIT_MS = 3600;
public static final int REVEAL_NEXT_STEP_WAIT_MS = 1000;
public static final int SETTLE_TO_BOMB_PLAYERS_WAIT_MS = 5000;
public static final int ROBOT_PLAY_DELAY_MIN_MS = 1000;
public static final int ROBOT_PLAY_DELAY_MAX_MS = 6000;
DuelReveal / RoundSettle 不走固定超时,由内部步骤往前推。getSettleStepWaitMs 看上一步是什么:炸弹收分完等 5 秒再发金钟罩,无助力时胜家到收分会短 500ms。改动画只动这个类。

发牌:108 张共用库,按回合凑牌
手牌张数写死在 GJMeleeSimpleDeal:
public static final int[] HAND_COUNTS = {6, 8, 10, 12, 14};
public static final int TOTAL_ROUND = 5;
public static final int BRIGHT_PER_ROUND = 2;
每回合从共用牌库补到目标张数,末尾最多两张明牌。上回合没打出的明牌继续占名额,下一回合只按打出的明牌张数补发。打出的牌和认输剩余手牌回收入库尾部,不洗。
补什么牌不走顺序 pop。GJMeleeDealEngine 按回合切 DealMode:R1 偏散、R2 看命中率、R3 够级牌锚点、R4 大型结构、R5 反推出牌升级。权重在 GJMeleeRoundDealProfile。
R1 炸弹空发只有 2%。R2~R5 先看留牌种子:假顺窗口里不同点 ≥3,80% 补成真顺;有连对苗头就补连对。没种子不空发长顺/长连对。炸弹空发也压过一轮,避免每把满天炸。
第 5 回合发牌直接问出牌器「补这张之后最优出牌变强没有」:
private static Integer findBestUpgradeCard(DeckPool pool, List<Integer> workingHand) {
GJMeleeCardTypeResult baseResult = GJMeleeCardTypeAnalyzer.analyze(
GJMeleeRobotPlayPicker.pickBestStructureOnly(workingHand));
long baseKey = baseResult == null ? 0 : baseResult.compareKey;
// 遍历牌库,trial 后 compareKey 更大的留下
}
发牌和出牌共用同一套牌力,不然补牌目标和机器人实际打出去的对不上。

出牌不校验牌型,结算时再识别
playCard 只要求是手牌子集、本回合还没出过。主牌型在结算侧由 GJMeleeCardTypeAnalyzer.analyze 取。能组成炸弹就走炸弹瀑布,不跟下面比倍数;没有炸弹才按现网倍数取最大,平倍走连刻 → 连对 → 顺子 → … → 单牌。
public static GJMeleeCardTypeResult analyze(List<Integer> cardList) {
GJMeleeCardTypeResult bomb = tryBombCategory(parse, cards, pointSum);
if (bomb != null) {
return bomb;
}
GJMeleeCardTypeResult best = null;
best = keepMaxMultiplier(best, tryConsecTriplet(parse, cards, pointSum));
best = keepMaxMultiplier(best, tryConsecPair(parse, cards, pointSum));
best = keepMaxMultiplier(best, tryStraight(parse, cards, pointSum));
// …
}
21 种牌型在 GJ_MELEE_CARD_TYPE,value 对协议,category() 切四大类(炸弹 4 / 刻子 3 / 对子 2 / 单牌 1)。炸弹内部 compareRank() 和 value 不完全一致,三王炸比六星炸大,对照表写在对决规则里。
选牌预览走 gj.cgjquerycardtype,返回 previewScore = pointSum × typeMultiplier,不落库。点数:王 5,10~A/2 为 3,3~9 为 1。
结算按大类,不是 9 场 1v1
每大类取全局牌力最强的人当胜者,向敌方非跳过座位收分。先 detectBombMode:
- 没炸弹:跳过炸弹大类
- 红蓝都有炸弹且会暴击:本回合只结炸弹
- 有炸弹但不暴击:
BOMB_THEN_OTHERS,打出炸弹的人后续大类不竞争、不助力、不被收分(金钟罩)
Set<Integer> skipAfterBomb = bombMode == GJMeleeRoundBombMode.BOMB_THEN_OTHERS
? new HashSet<>(bombPlayerPos) : Collections.emptySet();
for (int category : categoryOrder) {
Set<Integer> skipPos = category == CATEGORY_BOMB ? Collections.emptySet() : skipAfterBomb;
GJMeleeCategorySettlePlan plan = buildCategoryPlan(category, typeResultMap, posTeamMap,
skipPos, baseScore);
}
暴击 10 倍,同大类分出胜负就算,不要求 cardType 枚举相同。助力按大类,弱者也算,倍数 5 × 人数,多赢部分的 10% 分给助力者。广播拆成 Category → Winner → DuelSettle,间隔走上面的 config。
同阵营三人携带归零认输,截断剩余结算步进,直接 SGJ_SetEnd。认输座本回合后面也不再进 chargeList。
机器人出牌:桌上的和外面的是两套
陪玩有两条路径。房内机器人/托管走服务端 GJMeleeRobotPlayPicker;外部进程 wangmaoqh_robot 的 GjAi 听协议自己出。

房内:分回合策略,不是每把打最强
GJRoomSet 出牌阶段扫座位。机器人第一次进本回合会随机挂 1~6 秒,到点再 autoSubmit。人类超时打最小单张,机器人和托管打策略选牌。
if (setPos.getRoomPos().isRobot()) {
Long dueMillis = this.roundRobotPlayDueMillis.get(i);
if (dueMillis == null) {
this.roundRobotPlayDueMillis.put(i, now + CommMath.randomInt(
GJMeleeStateMachineConfig.ROBOT_PLAY_DELAY_MIN_MS,
GJMeleeStateMachineConfig.ROBOT_PLAY_DELAY_MAX_MS));
continue;
}
if (now < dueMillis) {
continue;
}
}
this.autoSubmitRoundPlayBestForPos(i);
真正选牌在 pickRoundPlay。入口按回合切开,带上明牌、阵营、R1 是否持王:
public static List<Integer> pickRoundPlay(List<Integer> handCards, int curRound,
Map<Integer, List<Integer>> brightCardsByPos,
int selfPos, Map<Integer, Integer> posTeamMap,
Map<Integer, Boolean> hadKingInRound1ByPos) {
PlayContext ctx = new PlayContext(curRound, brightCardsByPos, selfPos, posTeamMap,
hadKingInRound1ByPos);
if (curRound == 1) {
return pickRound1Play(handCards);
}
if (curRound == 4) {
return pickRound4Play(handCards, ctx);
}
if (curRound == 5) {
return pickRound5Play(handCards, ctx);
}
if (curRound >= 3) {
return pickRound3Play(handCards, ctx);
}
return pickBestPlay(handCards, ctx);
}
R1 故意不强出。10 以上的顺骨架、对子、炸弹和王留下,其余扔掉。绝对不出王。一手全是该留的,才打一张伤害最小的非王。这是给后面凑级炸留种子,和发牌侧「R1 有王 → 后面 80% 走级炸路线」对上。
R3 顺子不到 8 张不出。最强若是单牌,改优先连刻 / 连对 / 8 顺。
R4、R5 会看敌营级炸威胁。明牌有王 +40,R1 持过王 +30,明牌同点够级牌 ≥2 张 +20。威胁 ≥60 就避炸弹,改出非炸最优:
if (ctx.enemyBombThreat >= ENEMY_BOMB_THREAT_THRESHOLD) {
List<Integer> nonBomb = pickBestNonBomb(handCards, ctx);
if (CollectionUtils.isNotEmpty(nonBomb)) {
return nonBomb;
}
}
威胁不高时,R4 有普通炸弹 85% 打出去,否则 fallback 四连对 / 三连刻 / 7 顺。R5 能成型级炸就打级炸,否则最强结构。
候选来自 listFormableCardTypes。比的不是纯牌型大小,是预览分 点数和 × 倍数,同分再 GJMeleeCardTypeCompare。结构牌确定后还会附带安全散牌抬 pointSum,附带前试一次 analyze,主牌型变了就丢掉。王在凑级炸路上会留着,不往王炸里提前扔。手牌 ≥3 张同点也不会拆散去凑别的。
外部 GjAi:对齐状态机,别在 FaPai 就出牌
外部机器人以前用 contains 匹配事件,GoldCarryChoiceNotify 会被误当成出牌相关;发牌一到就出牌,Playing 还没到。这次改成精确匹配,RoundDeal 只确认,等 SGJ_ChangeStatus(Playing) 再出。别人 PlayCardDone 到了自己还没出,当作 ChangeStatus 丢了,补一刀。
if ("Playing".equalsIgnoreCase(state)) {
inPlayingPhase = true;
schedulePlayCardIfNeeded(RandomUtils.nextInt(1, 4));
return;
}
出牌决策在 GjOutLogic.decideRoundPlay。优先用手牌里还能对上的 cardTypeList 最强项(和 picker 目标一致);列表空了才本地回退:王组、同点最大组、最小单张。GjCardType 对齐 GJ_MELEE_CARD_TYPE 的 1~21。明牌按阵营过滤,阵营未知时按座位奇偶,和 assignTeamsBySeat 一致。
携带归零不抢着发 C2S。服务端对机器人已经 50% 补充 / 认输,GjAi 只同步停手,避免 pending 清了再报错。金币场准备 carryGold=0,桌内分由服务端按 minCarry/maxCarry 随机写,不扣账户。
melee/ 把发牌、识别、结算、出牌策略、时序拆开了。房间负责步进和协议,算法尽量无状态。发牌第 5 回合会调用出牌器,出牌器又调用识别器,三条线共用 compareKey / 倍数,不然机器人打出来的和结算对不上。
外部 AI 没有嵌服务端 picker(不在一个进程),所以用 cardTypeList 对齐牌力,本地只做兜底。cardTypeList 后来从发牌包里拿掉、改成查询预览之后,外部这条会更多走回退,这块后面还得把 cgjquerycardtype 接上,否则陪玩和房内机器人不是同一套脑。
单测主要铺在 DealEngine、RobotPlayPicker、CardTypeAnalyzer、状态机间隔上。对决节奏、留牌补顺、威胁避炸这类东西,改权重比改流程便宜,所以常量和步进都尽量集中。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)