diff --git a/robots/puke/robot_pk_pdk/src/main/java/robot/mj/handler/HuNanPaoDeKuai.java b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/handler/HuNanPaoDeKuai.java index 0c1ce6e..d6c44fc 100644 --- a/robots/puke/robot_pk_pdk/src/main/java/robot/mj/handler/HuNanPaoDeKuai.java +++ b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/handler/HuNanPaoDeKuai.java @@ -13,6 +13,7 @@ import taurus.client.TaurusClient; import taurus.util.CardObj; import taurus.util.CardUtil; import taurus.util.test; +import taurus.util.test_smart; import java.util.*; @@ -261,7 +262,7 @@ public class HuNanPaoDeKuai { System.out.println("当前card_list: " + card_list); ITArray itArray = null; - itArray = test.intelligentPaoDeKuaiOutCard(this, paoDekuaiCardInhand, card_list, seatRemainHistory); + itArray = test_smart.intelligentPaoDeKuaiOutCard(this, paoDekuaiCardInhand, card_list, seatRemainHistory); System.out.println("itArray-----" + itArray); //无法跟牌且不是下家只剩一张牌的情况 则pass @@ -329,35 +330,72 @@ public class HuNanPaoDeKuai { * @return */ public String paoDekuaiChupaiGuangBo(ITObject param) { - System.out.println("=== 出牌广播调试信息 ==="); - System.out.println("原始param: " + param); - System.out.println("seat字段: " + param.getInt("seat")); - System.out.println("player字段: " + param.getInt("player")); - //正确获取座位号 Integer broadcastSeat = param.getInt("seat");//从seat字段获取座位号 if (broadcastSeat != null) { guangboseat = broadcastSeat; - System.out.println("使用seat字段设置guangboseat: " + guangboseat); } else { //从player字段获取座位号 guangboseat = param.getInt("player"); - System.out.println("使用player字段设置guangboseat: " + guangboseat); } //更新出牌信息 ITObject cardObj = param.getTObject("card_obj"); if (cardObj != null) { - card_list = cardObj; - System.out.println("更新card_list为: " + card_list); + + // 创建新的card_list对象,确保包含所有必要字段 + card_list = TObject.newInstance(); + + // 复制card_obj中的所有字段 + if (cardObj.containsKey("card_list")) { + card_list.putTArray("card_list", cardObj.getTArray("card_list")); + } + + // 确保必要的字段存在 + if (cardObj.containsKey("min_card")) { + card_list.putInt("min_card", cardObj.getInt("min_card")); + } + if (cardObj.containsKey("type")) { + card_list.putInt("type", cardObj.getInt("type")); + } + if (cardObj.containsKey("len")) { + card_list.putInt("len", cardObj.getInt("len")); + } + + // 关键修复:根据实际牌数校验和修正type字段 + if (card_list.containsKey("card_list") && card_list.containsKey("type")) { + int actualLen = card_list.getTArray("card_list").size(); + int reportedType = card_list.getInt("type"); + int reportedLen = card_list.getInt("len"); + + + // 根据实际牌数推断正确类型 + int inferredType = inferCorrectType(actualLen); + if (inferredType != reportedType) { + System.out.println("[警告] 检测到类型不匹配! 报告类型:" + getCardTypeName(reportedType) + ", 推断类型:" + getCardTypeName(inferredType)); + card_list.putInt("type", inferredType); + } + + // 修正长度字段 + if (reportedLen != actualLen) { + System.out.println("[警告] 长度字段不匹配! 报告:" + reportedLen + ", 实际:" + actualLen); + card_list.putInt("len", actualLen); + } + } + // 如果len字段缺失或为0,根据实际牌数计算 + else if (card_list.containsKey("card_list")) { + int actualLen = card_list.getTArray("card_list").size(); + card_list.putInt("len", actualLen); + + // 同时推断类型 + int inferredType = inferCorrectType(actualLen); + card_list.putInt("type", inferredType); + } + } remain = param.getInt("remain"); //剩余牌数量 saveRemainHistory(guangboseat, remain); - System.out.println("座位号和手牌数量记录 " + seatRemainHistory); - System.out.println("广播手牌数量" + "座位号 " + guangboseat + "手牌数量 " + remain); - System.out.println("座位号:" + guangboseat + "的用户出牌广播:" + card_list); - System.out.println("======================="); return null; } @@ -368,5 +406,47 @@ public class HuNanPaoDeKuai { seatRemainHistory.get(guangboseat).add(remain); } + /** 根据牌数推断正确的牌型 */ + private int inferCorrectType(int len) { + switch (len) { + case 1: return 1; // 单牌 + case 2: return 2; // 对子 + case 3: return 5; // 三张 + case 4: return 8; // 炸弹 + case 5: return 7; // 三带二 + case 6: return 4; // 连对(2连对=4张,3连对=6张) + case 8: return 4; // 连对(4连对=8张) + case 10: return 4; // 连对(5连对=10张) + case 12: return 10; // 飞机带牌(333444+55+66) + default: + // 连对(偶数张牌) + if (len >= 4 && len % 2 == 0) return 4; + // 飞机带牌(5的倍数) + if (len >= 10 && len % 5 == 0) return 10; + // 顺子(5张及以上连续单牌) + if (len >= 5) return 3; + // 默认返回单牌 + return 1; + } + } + + /** 牌型数字转名称 */ + private String getCardTypeName(int type) { + switch (type) { + case 1: return "单牌"; + case 2: return "对子"; + case 3: return "顺子"; + case 4: return "连对"; + case 5: return "三张"; + case 7: return "三带二"; + case 8: return "炸弹"; + case 9: return "飞机"; + case 10: return "飞机带牌"; + case 11: return "四带二"; + case 12: return "四带两对"; + default: return "未知(" + type + ")"; + } + } + } \ No newline at end of file diff --git a/robots/puke/robot_pk_pdk/src/main/java/taurus/util/test.java b/robots/puke/robot_pk_pdk/src/main/java/taurus/util/test.java index 0da813f..1b6648c 100644 --- a/robots/puke/robot_pk_pdk/src/main/java/taurus/util/test.java +++ b/robots/puke/robot_pk_pdk/src/main/java/taurus/util/test.java @@ -36,12 +36,12 @@ public class test { int len = card_list.getInt("len"); int type = card_list.getInt("type"); - + if (huNanPaoDeKuai.seat == huNanPaoDeKuai.guangboseat) { System.out.println("=== 座位号匹配错误检测 ==="); System.out.println("seat=" + huNanPaoDeKuai.seat + ", guangboseat=" + huNanPaoDeKuai.guangboseat); System.out.println("检测到seat与guangboseat相同,可能存在状态同步问题"); - + Map otherSeatsLastRemain = getOtherSeatsLastRemain(seat, seatRemainHistory); boolean hasAnyEqualOne = otherSeatsLastRemain.values().stream().anyMatch(value -> value == 1); if (hasAnyEqualOne) { @@ -2336,14 +2336,13 @@ public class test { /** * 选择最优选项(考虑出牌后手牌) */ - private static PlayOptionWithRemain selectBestOptionWithRemain(List options, - int handSize, boolean isFirstPlay) { + private static PlayOptionWithRemain selectBestOptionWithRemain(List options, int handSize, boolean isFirstPlay) { if (options == null || options.isEmpty()) { return null; } return options.stream() .max(Comparator.comparingInt(option -> option.totalScore)) - .orElse(null); + .orElse(options.isEmpty() ? null : options.get(0)); } /** @@ -3346,4 +3345,4 @@ public class test { return analysis.estimatedTurns; } } -} \ No newline at end of file +} diff --git a/robots/puke/robot_pk_pdk/src/main/java/taurus/util/test_smart.java b/robots/puke/robot_pk_pdk/src/main/java/taurus/util/test_smart.java new file mode 100644 index 0000000..92f4bbc --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/taurus/util/test_smart.java @@ -0,0 +1,2213 @@ +package taurus.util; + +import com.taurus.core.entity.ITArray; +import com.taurus.core.entity.ITObject; +import com.taurus.core.entity.TArray; +import robot.mj.handler.HuNanPaoDeKuai; + +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.Collectors; + +/** + * 湖南跑得快智能出牌算法(全新智能化版本) + * 特色:动态分析 + 策略自适应 + 全面局势感知 + * 解决原版死板出牌问题,实现真正的智能决策 + */ +public class test_smart { + // ====================== 牌型常量 ====================== + public static final int TYPE_SINGLE = 1; + public static final int TYPE_PAIR = 2; + public static final int TYPE_STRAIGHT = 3; + public static final int TYPE_CONSECUTIVE_PAIR = 4; + public static final int TYPE_TRIO = 5; + public static final int TYPE_TRIO_WITH_TWO = 7; + public static final int TYPE_BOMB = 8; + public static final int TYPE_AIRPLANE = 9; + public static final int TYPE_AIRPLANE_WITH_CARDS = 10; + public static final int TYPE_FOUR_WITH_TWO = 11; + public static final int TYPE_FOUR_WITH_TWO_PAIR = 12; + + // 牌值定义 + private static final int CARD_3 = 3; + private static final int CARD_4 = 4; + private static final int CARD_5 = 5; + private static final int CARD_6 = 6; + private static final int CARD_7 = 7; + private static final int CARD_8 = 8; + private static final int CARD_9 = 9; + private static final int CARD_10 = 10; + private static final int CARD_J = 11; + private static final int CARD_Q = 12; + private static final int CARD_K = 13; + private static final int CARD_A = 14; + private static final int CARD_2 = 15; + + // 策略常量 + private static final int MIN_STRAIGHT_LENGTH = 5; + private static final int MIN_CONSECUTIVE_PAIR_COUNT = 2; + private static final int MIN_AIRPLANE_TRIO_COUNT = 2; + private static final int THINK_TIMEOUT_SECONDS = 3; + + // ====================== 主入口方法 ====================== + public static ITArray intelligentPaoDeKuaiOutCard( + HuNanPaoDeKuai huNanPaoDeKuai, + List cardInhand, + ITObject card_list, + Map> seatRemainHistory) { + + // 空值校验 + if (cardInhand == null || cardInhand.isEmpty()) { + System.err.println("[跑得快AI] 手牌为空,返回空出牌指令"); + return new TArray(); + } + + // 超时保护 + ExecutorService executor = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r); + t.setName("PaoDeKuai-AI-Thread"); + t.setDaemon(true); + return t; + }); + + Future future = executor.submit(() -> { + try { + ITArray outCards = coreOutCardLogic(huNanPaoDeKuai, cardInhand, card_list, seatRemainHistory); + if (outCards != null) { + if (isIllegalTrioWithOne(outCards)) { + System.err.println("[跑得快AI] 检测到非法三带一牌型,重新选择出牌"); + return reselectLegalCards(cardInhand); + } + } + return outCards == null ? new TArray() : outCards; + } catch (Exception e) { + System.err.println("[跑得快AI] 核心逻辑异常: " + e.getMessage()); + e.printStackTrace(); + return emergencyOutCard(cardInhand); + } + }); + + try { + ITArray result = future.get(THINK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + executor.shutdownNow(); + return result; + } catch (TimeoutException e) { + System.err.println("[跑得快AI] 思考超时,执行紧急合法出牌"); + future.cancel(true); + executor.shutdownNow(); + return emergencyOutCard(cardInhand); + } catch (Exception e) { + System.err.println("[跑得快AI] 执行流程异常: " + e.getMessage()); + e.printStackTrace(); + executor.shutdownNow(); + return emergencyOutCard(cardInhand); + } + } + + // ====================== 核心出牌逻辑 ====================== + private static ITArray coreOutCardLogic( + HuNanPaoDeKuai huNanPaoDeKuai, + List cardInhand, + ITObject card_list, + Map> seatRemainHistory) { + + // 预处理 + List sortedHand = new ArrayList<>(cardInhand); + Collections.sort(sortedHand); + int currentSeat = huNanPaoDeKuai != null ? huNanPaoDeKuai.seat : 0; + + System.out.println("\n========== 湖南跑得快智能出牌(全新版) =========="); + System.out.println("当前座位: " + currentSeat); + System.out.println("手牌数量: " + sortedHand.size()); + System.out.println("手牌详情: " + sortedHand.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.toList())); + + // 添加详细的手牌分析 + debugHandAnalysis(sortedHand); + + // 判断出牌模式 + boolean isFirstTurn = isFirstTurn(card_list); + boolean isReacquireTurn = isReacquirePlayTurn(huNanPaoDeKuai, seatRemainHistory, card_list); + + ITArray outCards; + if (isFirstTurn) { + System.out.println("【模式】首轮出牌 - 智能分析决策"); + outCards = handleFirstTurn(sortedHand, seatRemainHistory); + } else if (isReacquireTurn) { + System.out.println("【模式】重新获得出牌权 - 智能响应"); + outCards = handleReacquireTurn(sortedHand, seatRemainHistory, currentSeat); + } else { + System.out.println("【模式】响应出牌 - 精准匹配"); + outCards = handleResponseTurn(huNanPaoDeKuai, sortedHand, card_list, seatRemainHistory); + } + + return outCards == null ? new TArray() : outCards; + } + + // ====================== 模式1:首轮出牌策略 ====================== + private static ITArray handleFirstTurn(List handCards, Map> seatRemainHistory) { + int cardCount = handCards.size(); + System.out.println("\n========== 智能首轮出牌分析 =========="); + + // 全面分析 + HandAnalysis analysis = analyzeHandComposition(handCards); + GameSituation situation = evaluateCurrentSituation(seatRemainHistory); + + printHandAnalysis(analysis); + printSituationAnalysis(situation); + + // 综合智能决策 + ITArray decision = makeComprehensiveFirstTurnDecision(handCards, analysis, situation); + + System.out.println("[智能决策] 最终出牌: " + getDecisionDescription(decision, handCards)); + return decision; + } + + // ====================== 模式2:重新获得出牌权 ====================== + private static ITArray handleReacquireTurn(List handCards, Map> seatRemainHistory, int currentSeat) { + + // 检查紧急情况 + boolean isOpponentEndgame = checkIfAnyOpponentHasOneCard(seatRemainHistory, currentSeat); + if (isOpponentEndgame) { + System.out.println("[残局策略] 检测到对手只剩一张牌"); + return handleEndgameSituation(handCards); + } + + // 正常情况:智能分析后出牌 + HandAnalysis analysis = analyzeHandComposition(handCards); + GameSituation situation = evaluateCurrentSituation(seatRemainHistory); + return makeUniversalDecision(handCards, analysis); + } + + // ====================== 模式3:响应出牌策略 ====================== + private static ITArray handleResponseTurn( + HuNanPaoDeKuai huNanPaoDeKuai, + List handCards, + ITObject card_list, + Map> seatRemainHistory) { + + try { + if (card_list == null || !card_list.containsKey("card_list")) { + System.err.println("[响应出牌] 对手出牌信息为空,返回Pass"); + return new TArray(); + } + + ITArray opponentCardsArray = card_list.getTArray("card_list"); + List opponentCards = CardUtil.toList(opponentCardsArray); + int minCard = card_list.getInt("min_card"); + int type = card_list.getInt("type"); + int len = card_list.getInt("len"); + + System.out.println("对手出牌 - 类型: " + getCardTypeName(type) + + ", 最小牌: " + getCardName(minCard) + + ", 牌数: " + len); + + // 残局特殊处理 + if (isOpponentEndgame(huNanPaoDeKuai != null ? huNanPaoDeKuai.seat : 0, seatRemainHistory)) { + System.out.println("【残局策略】对手剩1张牌,智能响应"); + return handleOpponentEndgameResponse(handCards, type, minCard); + } + + // 正常响应 + List responseCards = findMatchingResponse(handCards, type, minCard, len); + + if (responseCards.isEmpty()) { + // 核心规则:要得起必须出!没有对应牌型时必须出炸弹 + List bomb = findSmallestBomb(handCards); + if (!bomb.isEmpty()) { + System.out.println("[强制规则] 无法用对应牌型响应,必须出炸弹"); + return CardUtil.toTArray(bomb); + } + + // 理论上不应该到达这里(有炸弹就必须出) + System.out.println("[警告] 无匹配牌型且无炸弹,违反游戏规则"); + return emergencyOutCard(handCards); + } + + return CardUtil.toTArray(responseCards); + + } catch (Exception e) { + System.err.println("[响应出牌] 解析异常: " + e.getMessage()); + e.printStackTrace(); + return emergencyOutCard(handCards); + } + } + + // ====================== 智能分析系统 ====================== + + /** 局势类型枚举 */ + private enum SituationType { + DOMINANT("主导局面", "我方牌力明显优势"), + COMPETITIVE("竞争局面", "双方势均力敌"), + DEFENSIVE("防守局面", "对方牌力较强"), + ENDGAME("残局阶段", "接近游戏结束"), + UNCERTAIN("不确定", "信息不足难以判断"); + + private final String name; + private final String description; + + SituationType(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { return name; } + public String getDescription() { return description; } + } + + /** 策略倾向枚举 */ + private enum StrategyTendency { + AGGRESSIVE("激进型", "主动出击,快速出牌"), + CONSERVATIVE("保守型", "稳扎稳打,保存实力"), + BALANCED("均衡型", "攻守兼备,灵活应对"), + OPPORTUNISTIC("机会型", "等待时机,见机行事"), + PROGRESSIVE("渐进型", "先小后大,循序渐进"), // 新增:解决先打大牌问题 + PRESERVATIVE("保存型", "保留关键牌,关键时刻发力"); + + private final String name; + private final String description; + + StrategyTendency(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { return name; } + public String getDescription() { return description; } + } + + /** 牌优先级指导类 */ + private static class CardPriority { + String cardType; // 牌型描述 + int priorityLevel; // 优先级(1-5, 1最高) + String reason; // 推荐理由 + boolean preserveBig; // 是否建议保留大牌 + + CardPriority(String cardType, int priorityLevel, String reason, boolean preserveBig) { + this.cardType = cardType; + this.priorityLevel = priorityLevel; + this.reason = reason; + this.preserveBig = preserveBig; + } + + @Override + public String toString() { + return String.format("%s(优先级%d): %s %s", + cardType, priorityLevel, reason, preserveBig ? "[保留大牌]" : ""); + } + } + + /** 游戏局势分析类 */ + private static class GameSituation { + int opponentCount; + List opponentSeats; + Map opponentRemainCards; + SituationType situationType; + double pressureLevel; + double opportunityLevel; + StrategyTendency tendency; + + @Override + public String toString() { + return String.format("局势分析: %s | 压力%.2f 机会%.2f | 倾向:%s | 对手剩余:%s", + situationType.getDescription(), pressureLevel, opportunityLevel, + tendency.getDescription(), opponentRemainCards); + } + } + + /** 手牌分析结果类 */ + private static class HandAnalysis { + int totalCards; + int singleCount; + int pairCount; + int trioCount; + int bombCount; + int straightCount; + int consecutivePairCount; + int airplaneCount; + + int lowCards; // 3-7 + int mediumCards; // 8-J + int highCards; // Q-A,2 + int bigCards; // K,A,2 (关键大牌) + + int maxStraightLength; + int maxConsecutivePairs; + boolean hasContinuousStructure; + + double comboQualityScore; + double flexibilityScore; + double controlScore; + double riskScore; + double vulnerabilityScore; + + // 新增的智能评估指标 + double bigCardRatio; // 大牌比例 + double smallCardRatio; // 小牌比例 + double balanceScore; // 牌力平衡度 + double progressionScore; // 出牌流畅度 + + String recommendedStrategy; + List alternativeStrategies; + + // 出牌优先级指导 + List priorityGuidance; + + @Override + public String toString() { + return String.format("手牌分析: %d张牌 | 单%d 对%d 三%d 炸%d | 小%d 中%d 大%d 关键%d | 策略:%s | 平衡度:%.2f", + totalCards, singleCount, pairCount, trioCount, bombCount, + lowCards, mediumCards, highCards, bigCards, recommendedStrategy, balanceScore); + } + } + + /** 分析手牌构成 */ + private static HandAnalysis analyzeHandComposition(List handCards) { + HandAnalysis analysis = new HandAnalysis(); + analysis.totalCards = handCards.size(); + analysis.alternativeStrategies = new ArrayList<>(); + + // 基础统计 + Map> groups = CardUtil.getCardListMap(handCards); + + for (List group : groups.values()) { + int size = group.size(); + switch (size) { + case 1: analysis.singleCount++; break; + case 2: analysis.pairCount++; break; + case 3: analysis.trioCount++; break; + case 4: analysis.bombCount++; break; + } + } + + // 牌力分布(细化分类) + for (CardObj card : handCards) { + int value = card.cardMod; + if (value <= CARD_7) { + analysis.lowCards++; + } else if (value <= CARD_J) { + analysis.mediumCards++; + } else if (value <= CARD_Q) { + analysis.highCards++; + } else { + analysis.highCards++; + analysis.bigCards++; // K,A,2为关键大牌 + } + } + + // 计算新增指标 + analysis.smallCardRatio = (double)(analysis.lowCards + analysis.mediumCards) / analysis.totalCards; + analysis.bigCardRatio = (double)analysis.bigCards / analysis.totalCards; + analysis.balanceScore = calculateBalanceScore(analysis); + analysis.progressionScore = calculateProgressionScore(analysis); + + // 连续结构 + analysis.maxStraightLength = findMaxStraightLength(handCards); + analysis.maxConsecutivePairs = findMaxConsecutivePairs(handCards); + analysis.hasContinuousStructure = (analysis.maxStraightLength >= 5 || analysis.maxConsecutivePairs >= 2); + + // 复合牌型 + analysis.straightCount = findAllStraights(handCards, 5).size(); + analysis.consecutivePairCount = findAllConsecutivePairs(handCards, 2).size(); + analysis.airplaneCount = findAllAirplanesWithCards(handCards, 2).size(); + + // 质量评估 + analysis.comboQualityScore = calculateComboQuality(analysis); + analysis.flexibilityScore = calculateFlexibilityScore(analysis); + analysis.controlScore = calculateControlScore(analysis); + + // 风险评估 + analysis.riskScore = calculateRiskScore(analysis); + analysis.vulnerabilityScore = calculateVulnerabilityScore(analysis); + + // 策略推荐 + analysis.recommendedStrategy = recommendAdvancedStrategy(analysis); + analysis.alternativeStrategies = generateAlternativeStrategies(analysis); + + // 生成出牌优先级指导 + analysis.priorityGuidance = generatePriorityGuidance(analysis); + + return analysis; + } + + /** 分析游戏局势 */ + private static GameSituation evaluateCurrentSituation(Map> seatRemainHistory) { + GameSituation situation = new GameSituation(); + situation.opponentRemainCards = new HashMap<>(); + situation.opponentSeats = new ArrayList<>(); + + if (seatRemainHistory == null || seatRemainHistory.isEmpty()) { + situation.situationType = SituationType.UNCERTAIN; + situation.pressureLevel = 0.5; + situation.opportunityLevel = 0.5; + situation.tendency = StrategyTendency.BALANCED; + situation.opponentCount = 2; + return situation; + } + + // 分析对手信息 + int totalOpponents = 0; + int opponentsWithFewCards = 0; + int minRemainCards = Integer.MAX_VALUE; + + for (Map.Entry> entry : seatRemainHistory.entrySet()) { + List remainList = entry.getValue(); + if (remainList != null && !remainList.isEmpty()) { + int currentRemain = remainList.get(remainList.size() - 1); + situation.opponentRemainCards.put(entry.getKey(), currentRemain); + situation.opponentSeats.add(entry.getKey()); + + totalOpponents++; + minRemainCards = Math.min(minRemainCards, currentRemain); + + if (currentRemain <= 3) { + opponentsWithFewCards++; + } + } + } + + situation.opponentCount = totalOpponents; + + // 局势判断 + if (opponentsWithFewCards > 0) { + situation.situationType = SituationType.ENDGAME; + situation.pressureLevel = 0.8; + situation.opportunityLevel = 0.7; + } else if (minRemainCards <= 5) { + situation.situationType = SituationType.COMPETITIVE; + situation.pressureLevel = 0.6; + situation.opportunityLevel = 0.5; + } else { + situation.situationType = SituationType.COMPETITIVE; + situation.pressureLevel = 0.5; + situation.opportunityLevel = 0.5; + } + + situation.tendency = determineStrategyTendency(situation); + return situation; + } + + // ====================== 智能决策系统 ====================== + + /** 综合首轮决策 */ + private static ITArray makeComprehensiveFirstTurnDecision(List handCards, HandAnalysis analysis, GameSituation situation) { + System.out.println("\n[综合决策] 手牌策略:" + analysis.recommendedStrategy + " | 局势倾向:" + situation.tendency.getName()); + + ITArray decision = null; + + switch (situation.tendency) { + case AGGRESSIVE: + decision = makeAggressiveDecision(handCards, analysis, situation); + break; + case CONSERVATIVE: + decision = makeConservativeDecision(handCards, analysis, situation); + break; + case OPPORTUNISTIC: + decision = makeOpportunisticDecision(handCards, analysis, situation); + break; + case BALANCED: + default: + decision = makeBalancedDecision(handCards, analysis, situation); + break; + } + + if (decision == null || decision.size() == 0) { + System.out.println("[备用策略] 特定策略无效,启用通用策略"); + decision = makeUniversalDecision(handCards, analysis); + } + + return decision; + } + + /** 激进型决策 */ + private static ITArray makeAggressiveDecision(List handCards, HandAnalysis analysis, GameSituation situation) { + System.out.println("[激进策略] 主动出击,优先出大牌组合"); + + List outCards; + + outCards = findBestQualityCombo(handCards, analysis); + if (!outCards.isEmpty()) { + System.out.println("[激进] 出高质量组合: " + outCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.toList())); + return CardUtil.toTArray(outCards); + } + + outCards = findLargestStraight(handCards); + if (!outCards.isEmpty()) { + System.out.println("[激进] 出最大顺子: " + outCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.toList())); + return CardUtil.toTArray(outCards); + } + + outCards = findLargePair(handCards, CARD_10); + if (!outCards.isEmpty()) { + System.out.println("[激进] 出大对子: " + outCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.toList())); + return CardUtil.toTArray(outCards); + } + + return null; + } + + /** 保守型决策 */ + private static ITArray makeConservativeDecision(List handCards, HandAnalysis analysis, GameSituation situation) { + System.out.println("[保守策略] 稳扎稳打,优先出小牌"); + + List outCards; + + outCards = findSmallComboConservative(handCards); + if (!outCards.isEmpty()) { + System.out.println("[保守] 出小牌组合: " + outCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.toList())); + return CardUtil.toTArray(outCards); + } + + outCards = findSmallPair(handCards, CARD_8); + if (!outCards.isEmpty()) { + System.out.println("[保守] 出小对子: " + outCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.toList())); + return CardUtil.toTArray(outCards); + } + + CardObj smallCard = findSmallCard(handCards, CARD_7); + if (smallCard != null) { + System.out.println("[保守] 出小单牌: " + getCardName(smallCard.cardMod)); + return CardUtil.toTArray1(smallCard); + } + + return null; + } + + /** 机会型决策 */ + private static ITArray makeOpportunisticDecision(List handCards, HandAnalysis analysis, GameSituation situation) { + System.out.println("[机会策略] 等待最佳时机,灵活选择"); + + List outCards = findOpportunisticBest(handCards, analysis, situation); + if (!outCards.isEmpty()) { + System.out.println("[机会] 出最优选择: " + outCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.toList())); + return CardUtil.toTArray(outCards); + } + + return null; + } + + /** 均衡型决策 */ + private static ITArray makeBalancedDecision(List handCards, HandAnalysis analysis, GameSituation situation) { + System.out.println("[均衡策略] 综合考虑各方面因素"); + return makeUniversalDecision(handCards, analysis); + } + + /** 通用决策 - 改进版:解决先打大牌问题 */ + private static ITArray makeUniversalDecision(List handCards, HandAnalysis analysis) { + System.out.println("[通用策略] 基于手牌分析的智能选择"); + System.out.println("[重要提醒] 当前采用渐进式出牌策略,优先出小牌,保留大牌"); + + List outCards; + + // 根据平衡度和大牌比例决定策略 + if (analysis.bigCardRatio > 0.4) { + // 大牌过多,优先出小牌 + System.out.println("[策略调整] 检测到大牌比例高(" + String.format("%.1f", analysis.bigCardRatio*100) + "%), 采用渐进式策略"); + outCards = findProgressiveMove(handCards, analysis); + } else if (analysis.balanceScore > 0.7) { + // 牌力平衡,可以正常出牌 + outCards = findBalancedMove(handCards, analysis); + } else { + // 牌力不平衡,需要调整 + outCards = findAdjustmentMove(handCards, analysis); + } + + if (!outCards.isEmpty()) { + System.out.println("[通用] 基于策略出牌: " + outCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.toList())); + return CardUtil.toTArray(outCards); + } + + return fallbackDecision(handCards); + } + + // ====================== 辅助方法 ====================== + + /** 残局智能决策 - 核心改进! */ + private static ITArray handleEndgameSituation(List handCards) { + System.out.println("[残局智能] 分析手牌构成,优先保留组合牌型"); + + // 1. 首先检查是否有组合牌型可以出 + List comboMove = findBestEndgameCombo(handCards); + if (!comboMove.isEmpty()) { + System.out.println("[残局策略] 优先出组合牌型: " + comboMove.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); + return CardUtil.toTArray(comboMove); + } + + // 2. 如果没有组合牌型,才考虑出单牌 + CardObj maxSingleCard = CardUtil.findMaxSingleCard(handCards); + if (maxSingleCard != null) { + System.out.println("[残局策略] 无组合牌型可用,出最大单牌: " + getCardName(maxSingleCard.cardMod)); + return CardUtil.toTArray1(maxSingleCard); + } + + // 3. 兜底 + return emergencyOutCard(handCards); + } + + /** 残局最佳组合牌选择 */ + private static List findBestEndgameCombo(List handCards) { + System.out.println("[残局分析] 寻找可用的组合牌型"); + + List bestCombo = new ArrayList<>(); + int bestScore = 0; + + // 评估各种组合牌型的价值 + + // 1. 炸弹(最高价值) + List bomb = findSmallestBomb(handCards); + if (!bomb.isEmpty()) { + System.out.println("[残局评估] 发现炸弹,价值: 100"); + return bomb; // 炸弹直接使用 + } + + // 2. 飞机带牌 + List airplane = findSmallestAirplaneWithCards(handCards); + if (!airplane.isEmpty() && airplane.size() > bestScore) { + bestCombo = airplane; + bestScore = airplane.size(); + System.out.println("[残局评估] 发现飞机,价值: " + airplane.size()); + } + + // 3. 四带二 + List fourWithTwo = findSmallestFourWithTwo(handCards); + if (!fourWithTwo.isEmpty() && fourWithTwo.size() > bestScore) { + bestCombo = fourWithTwo; + bestScore = fourWithTwo.size(); + System.out.println("[残局评估] 发现四带二,价值: " + fourWithTwo.size()); + } + + // 4. 三带二 + List trioWithTwo = findSmallestTrioWithTwo(handCards); + if (!trioWithTwo.isEmpty() && trioWithTwo.size() > bestScore) { + bestCombo = trioWithTwo; + bestScore = trioWithTwo.size(); + System.out.println("[残局评估] 发现三带二,价值: " + trioWithTwo.size()); + } + + // 5. 连对 + List consecutivePair = findSmallestConsecutivePair(handCards); + if (!consecutivePair.isEmpty() && consecutivePair.size() > bestScore) { + bestCombo = consecutivePair; + bestScore = consecutivePair.size(); + System.out.println("[残局评估] 发现连对,价值: " + consecutivePair.size()); + } + + // 6. 对子 + List pair = findSmallestPair(handCards); + if (!pair.isEmpty() && pair.size() > bestScore) { + bestCombo = pair; + bestScore = pair.size(); + System.out.println("[残局评估] 发现对子,价值: " + pair.size()); + } + + // 7. 顺子 + List straight = findSmallestStraight(handCards); + if (!straight.isEmpty() && straight.size() > bestScore) { + bestCombo = straight; + bestScore = straight.size(); + System.out.println("[残局评估] 发现顺子,价值: " + straight.size()); + } + + // 8. 三张 + List trio = findSmallestTrio(handCards); + if (!trio.isEmpty() && trio.size() > bestScore) { + bestCombo = trio; + bestScore = trio.size(); + System.out.println("[残局评估] 发现三张,价值: " + trio.size()); + } + + if (bestScore > 0) { + System.out.println("[残局决策] 选择最佳组合: " + bestCombo.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); + } else { + System.out.println("[残局决策] 无可用组合牌型"); + } + + return bestCombo; + } + + /** 兜底决策 */ + private static ITArray fallbackDecision(List handCards) { + System.out.println("[兜底策略] 无合适组合,智能选择最小损失"); + + List pair = findSmallestPair(handCards); + if (!pair.isEmpty()) { + System.out.println("[兜底] 出最小对子: " + pair.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.toList())); + return CardUtil.toTArray(pair); + } + + CardObj minCard = CardUtil.findMinSingleCard(handCards); + if (minCard != null) { + System.out.println("[兜底] 出最小单牌: " + getCardName(minCard.cardMod)); + return CardUtil.toTArray1(minCard); + } + + System.out.println("[警告] 无法找到任何可出牌型"); + return new TArray(); + } + + // ====================== 新增评估函数 ====================== + + /** 计算牌力平衡度 */ + private static double calculateBalanceScore(HandAnalysis analysis) { + double score = 0.0; + + // 小牌比例贡献 + double smallRatio = (double)(analysis.lowCards + analysis.mediumCards) / analysis.totalCards; + score += Math.min(smallRatio * 0.5, 0.4); + + // 大牌不过多 + double bigRatio = (double)analysis.bigCards / analysis.totalCards; + if (bigRatio <= 0.3) { + score += 0.3; + } else if (bigRatio <= 0.5) { + score += 0.2; + } + + // 中等牌适中 + double mediumRatio = (double)analysis.mediumCards / analysis.totalCards; + if (mediumRatio >= 0.3 && mediumRatio <= 0.6) { + score += 0.3; + } + + return Math.min(score, 1.0); + } + + /** 计算出牌流畅度 */ + private static double calculateProgressionScore(HandAnalysis analysis) { + double score = 0.0; + + // 小牌充足 + double smallRatio = (double)(analysis.lowCards + analysis.mediumCards) / analysis.totalCards; + score += Math.min(smallRatio * 0.6, 0.5); + + // 连续结构加分 + if (analysis.hasContinuousStructure) score += 0.3; + + // 组合牌型丰富 + int comboTypes = (analysis.pairCount > 0 ? 1 : 0) + + (analysis.trioCount > 0 ? 1 : 0) + + (analysis.straightCount > 0 ? 1 : 0); + score += Math.min(comboTypes * 0.2, 0.4); + + return Math.min(score, 1.0); + } + + /** 生成出牌优先级指导 */ + private static List generatePriorityGuidance(HandAnalysis analysis) { + List guidance = new ArrayList<>(); + + // 根据大牌比例制定策略 + if (analysis.bigCardRatio > 0.4) { + // 大牌过多,建议保留 + guidance.add(new CardPriority("小牌组合", 1, "大牌过多需先清理小牌", true)); + guidance.add(new CardPriority("中等单牌", 2, "保持出牌流畅性", true)); + guidance.add(new CardPriority("大牌单张", 4, "关键时刻使用", true)); + guidance.add(new CardPriority("大牌组合", 5, "最后阶段使用", true)); + } else if (analysis.balanceScore > 0.6) { + // 平衡状态 + guidance.add(new CardPriority("最优组合", 1, "按牌力自然出牌", false)); + guidance.add(new CardPriority("连续结构", 2, "发挥连牌优势", false)); + } else { + // 需要调整 + guidance.add(new CardPriority("改善手牌", 1, "优先调整不利牌型", false)); + guidance.add(new CardPriority("建立优势", 2, "创造更好的出牌机会", false)); + } + + return guidance; + } + + // ====================== 评估函数 ====================== + + private static double calculateComboQuality(HandAnalysis analysis) { + double quality = 0.0; + if (analysis.maxStraightLength >= 8) quality += 0.4; + else if (analysis.maxStraightLength >= 6) quality += 0.3; + else if (analysis.maxStraightLength >= 5) quality += 0.2; + + if (analysis.maxConsecutivePairs >= 4) quality += 0.3; + else if (analysis.maxConsecutivePairs >= 3) quality += 0.2; + else if (analysis.maxConsecutivePairs >= 2) quality += 0.1; + + quality += Math.min(analysis.airplaneCount * 0.2, 0.3); + quality += Math.min(analysis.bombCount * 0.25, 0.5); + + return Math.min(quality, 1.0); + } + + private static double calculateFlexibilityScore(HandAnalysis analysis) { + double flexibility = 0.0; + + int comboTypes = (analysis.pairCount > 0 ? 1 : 0) + + (analysis.trioCount > 0 ? 1 : 0) + + (analysis.straightCount > 0 ? 1 : 0) + + (analysis.consecutivePairCount > 0 ? 1 : 0) + + (analysis.airplaneCount > 0 ? 1 : 0); + flexibility += Math.min(comboTypes * 0.2, 0.8); + + double mediumRatio = (double) analysis.mediumCards / analysis.totalCards; + flexibility += mediumRatio * 0.4; + + if (analysis.hasContinuousStructure) flexibility += 0.3; + + return Math.min(flexibility, 1.0); + } + + private static double calculateControlScore(HandAnalysis analysis) { + double control = 0.0; + + double highRatio = (double) analysis.highCards / analysis.totalCards; + control += highRatio * 0.4; + + control += Math.min(analysis.bombCount * 0.3, 0.6); + control += Math.min(analysis.trioCount * 0.15, 0.3); + + if (analysis.hasContinuousStructure) control += 0.2; + + return Math.min(control, 1.0); + } + + private static double calculateRiskScore(HandAnalysis analysis) { + double score = 0.0; + + double singleRatio = (double) analysis.singleCount / analysis.totalCards; + score += singleRatio * 0.4; + + double highCardRisk = (double) analysis.highCards / analysis.totalCards; + if (analysis.pairCount + analysis.trioCount + analysis.bombCount == 0) { + score += highCardRisk * 0.3; + } + + int comboTypes = (analysis.pairCount > 0 ? 1 : 0) + + (analysis.trioCount > 0 ? 1 : 0) + + (analysis.straightCount > 0 ? 1 : 0); + if (comboTypes <= 1) score += 0.3; + + return Math.min(score, 1.0); + } + + private static double calculateVulnerabilityScore(HandAnalysis analysis) { + double vulnerability = 0.0; + + double highAloneRatio = (double) analysis.highCards / analysis.totalCards; + if (analysis.pairCount + analysis.trioCount == 0) { + vulnerability += highAloneRatio * 0.5; + } + + double singleRatio = (double) analysis.singleCount / analysis.totalCards; + vulnerability += singleRatio * 0.4; + + if (analysis.bombCount == 0 && analysis.trioCount == 0) { + vulnerability += 0.3; + } + + return Math.min(vulnerability, 1.0); + } + + private static StrategyTendency determineStrategyTendency(GameSituation situation) { + if (situation.pressureLevel > 0.7) { + if (situation.opportunityLevel > 0.6) { + return StrategyTendency.OPPORTUNISTIC; + } else { + return StrategyTendency.AGGRESSIVE; + } + } else if (situation.pressureLevel < 0.3) { + return StrategyTendency.CONSERVATIVE; + } else { + return StrategyTendency.BALANCED; + } + } + + private static String recommendAdvancedStrategy(HandAnalysis analysis) { + int comboTypes = (analysis.pairCount > 0 ? 1 : 0) + + (analysis.trioCount > 0 ? 1 : 0) + + (analysis.straightCount > 0 ? 1 : 0) + + (analysis.consecutivePairCount > 0 ? 1 : 0) + + (analysis.airplaneCount > 0 ? 1 : 0) + + (analysis.bombCount > 0 ? 1 : 0); + + double lowRatio = (double) analysis.lowCards / analysis.totalCards; + double highRatio = (double) analysis.highCards / analysis.totalCards; + + double straightWeight = analysis.maxStraightLength >= 6 ? 0.9 : + analysis.maxStraightLength >= 5 ? 0.7 : 0.3; + double comboWeight = Math.min(comboTypes * 0.2, 0.8); + double controlWeight = analysis.controlScore; + + if (straightWeight >= 0.8 && analysis.straightCount > 0) { + return "顺子主导"; + } else if (comboWeight >= 0.6 && comboTypes >= 3) { + return "复合牌型"; + } else if (analysis.pairCount >= 3 && analysis.flexibilityScore > 0.6) { + return "对子推进"; + } else if (analysis.trioCount >= 2 && controlWeight > 0.5) { + return "三张控制"; + } else if (lowRatio > 0.7) { + return "快速清理"; + } else if (highRatio > 0.6 && analysis.bombCount > 0) { + return "蓄力待发"; + } else { + return "灵活应变"; + } + } + + private static List generateAlternativeStrategies(HandAnalysis analysis) { + List alternatives = new ArrayList<>(); + + if (analysis.straightCount > 0) alternatives.add("顺子流"); + if (analysis.pairCount > 0) alternatives.add("对子流"); + if (analysis.trioCount > 0) alternatives.add("三张流"); + if (analysis.bombCount > 0) alternatives.add("炸弹流"); + if (analysis.lowCards > analysis.totalCards * 0.6) alternatives.add("清小牌"); + if (analysis.highCards > analysis.totalCards * 0.5) alternatives.add("控大牌"); + + return alternatives; + } + + // ====================== 打印分析结果 ====================== + + private static void printHandAnalysis(HandAnalysis analysis) { + System.out.println("\n========== 手牌深度分析报告 =========="); + System.out.println("📊 基础构成: " + analysis.toString()); + System.out.println("🔗 连续结构: 最长顺子" + analysis.maxStraightLength + "张, 最大连对" + analysis.maxConsecutivePairs + "对"); + System.out.println("📈 质量评估: 组合质量" + String.format("%.2f", analysis.comboQualityScore) + + ", 灵活性" + String.format("%.2f", analysis.flexibilityScore) + + ", 控制力" + String.format("%.2f", analysis.controlScore)); + System.out.println("⚠️ 风险评估: 风险评分" + String.format("%.2f", analysis.riskScore) + + ", 脆弱性" + String.format("%.2f", analysis.vulnerabilityScore)); + System.out.println("🎯 AI建议: " + analysis.recommendedStrategy); + if (analysis.alternativeStrategies != null && !analysis.alternativeStrategies.isEmpty()) { + System.out.println("🔄 备选策略: " + String.join(", ", analysis.alternativeStrategies)); + } + System.out.println("====================================="); + } + + private static void printSituationAnalysis(GameSituation situation) { + System.out.println("\n========== 游戏局势分析报告 =========="); + System.out.println("🌍 局势类型: " + situation.situationType.getDescription()); + System.out.println("💪 压力等级: " + String.format("%.2f", situation.pressureLevel) + "/1.0"); + System.out.println("⭐ 机会等级: " + String.format("%.2f", situation.opportunityLevel) + "/1.0"); + System.out.println("🧭 策略倾向: " + situation.tendency.getDescription()); + System.out.println("👥 对手信息: " + situation.opponentCount + "个对手, 剩余牌数" + situation.opponentRemainCards); + System.out.println("====================================="); + } + + // ====================== 新增核心智能查找方法 ====================== + + /** 渐进式出牌 - 解决先打大牌问题的核心方法 */ + private static List findProgressiveMove(List handCards, HandAnalysis analysis) { + System.out.println("[渐进策略] 采用先小后大的智能出牌方式"); + + List result; + + // 1. 优先找小牌组合 + result = findSmallComboProgressive(handCards); + if (!result.isEmpty()) { + System.out.println("[渐进] 出小牌组合: " + result.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.toList())); + return result; + } + + // 2. 找小对子 + result = findSmallPairProgressive(handCards); + if (!result.isEmpty()) { + System.out.println("[渐进] 出小对子: " + result.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.toList())); + return result; + } + + // 3. 找小单牌 + CardObj smallCard = findProgressiveSmallCard(handCards); + if (smallCard != null) { + System.out.println("[渐进] 出小单牌: " + getCardName(smallCard.cardMod)); + return Collections.singletonList(smallCard); + } + + // 4. 只有在非常必要时才考虑中等牌 + CardObj mediumCard = findSafeMediumCard(handCards); + if (mediumCard != null) { + System.out.println("[渐进] 出中等牌(谨慎): " + getCardName(mediumCard.cardMod)); + return Collections.singletonList(mediumCard); + } + + System.out.println("[渐进] 无合适小牌可出"); + return new ArrayList<>(); + } + + /** 平衡出牌 */ + private static List findBalancedMove(List handCards, HandAnalysis analysis) { + System.out.println("[平衡策略] 正常出牌,保持牌力均衡"); + return findMostFlexibleMove(handCards); + } + + /** 调整出牌 */ + private static List findAdjustmentMove(List handCards, HandAnalysis analysis) { + System.out.println("[调整策略] 改善手牌结构"); + + // 如果大牌过多,仍然优先小牌 + if (analysis.bigCardRatio > 0.3) { + return findProgressiveMove(handCards, analysis); + } + + // 否则正常出牌 + return findMostFlexibleMove(handCards); + } + + // ====================== 核心查找方法 ====================== + + private static List findBestQualityCombo(List handCards, HandAnalysis analysis) { + List result; + + result = findSmallestBomb(handCards); + if (!result.isEmpty()) return result; + + result = findSmallestAirplaneWithCards(handCards); + if (!result.isEmpty()) return result; + + result = findSmallestFourWithTwo(handCards); + if (!result.isEmpty()) return result; + + result = findSmallestTrioWithTwo(handCards); + if (!result.isEmpty()) return result; + + result = findSmallestStraight(handCards); + if (!result.isEmpty()) return result; + + result = findSmallestConsecutivePair(handCards); + if (!result.isEmpty()) return result; + + return new ArrayList<>(); + } + + private static List findLargestStraight(List handCards) { + List> allStraights = findAllStraights(handCards, 5); + return allStraights.stream() + .max(Comparator.comparingInt(s -> s.stream().mapToInt(c -> c.cardMod).sum())) + .orElse(new ArrayList<>()); + } + + private static List findLargePair(List handCards, int minValue) { + Map> groups = CardUtil.getCardListMap(handCards); + return groups.entrySet().stream() + .filter(e -> e.getKey() >= minValue && e.getValue().size() >= 2) + .sorted(Map.Entry.comparingByKey(Comparator.reverseOrder())) + .map(e -> e.getValue().subList(0, 2)) + .findFirst() + .orElse(new ArrayList<>()); + } + + private static List findSmallComboConservative(List handCards) { + for (int len = 6; len >= 5; len--) { + List> straights = findAllStraights(handCards, len); + for (List straight : straights) { + if (straight.stream().allMatch(c -> c.cardMod <= CARD_8)) { + return straight; + } + } + } + + List> pairs = findAllConsecutivePairs(handCards, 2); + for (List pair : pairs) { + if (pair.stream().allMatch(c -> c.cardMod <= CARD_8)) { + return pair; + } + } + + return new ArrayList<>(); + } + + private static List findOpportunisticBest(List handCards, HandAnalysis analysis, GameSituation situation) { + if (situation.pressureLevel > 0.6) { + List bomb = findSmallestBomb(handCards); + if (!bomb.isEmpty()) return bomb; + + List trio = findSmallestTrioWithTwo(handCards); + if (!trio.isEmpty()) return trio; + } + + if (situation.opportunityLevel > 0.6) { + return findBestQualityCombo(handCards, analysis); + } + + return findMostFlexibleMove(handCards); + } + + private static List findBestStraight(List handCards) { + for (int len = 12; len >= 5; len--) { + List> straights = findAllStraights(handCards, len); + if (!straights.isEmpty()) { + return straights.get(0); + } + } + return new ArrayList<>(); + } + + private static List findBestCombo(List handCards) { + List bestCombo = new ArrayList<>(); + + List airplane = findSmallestAirplaneWithCards(handCards); + if (airplane.size() > bestCombo.size()) bestCombo = airplane; + + List straight = findSmallestStraight(handCards); + if (straight.size() > bestCombo.size()) bestCombo = straight; + + List fourWithTwo = findSmallestFourWithTwo(handCards); + if (fourWithTwo.size() > bestCombo.size()) bestCombo = fourWithTwo; + + List trioWithTwo = findSmallestTrioWithTwo(handCards); + if (trioWithTwo.size() > bestCombo.size()) bestCombo = trioWithTwo; + + return bestCombo; + } + + private static List findOptimalPair(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + return groups.entrySet().stream() + .filter(e -> e.getValue().size() >= 2) + .filter(e -> e.getKey() >= CARD_6 && e.getKey() <= CARD_Q) + .sorted(Map.Entry.comparingByKey()) + .map(e -> e.getValue().subList(0, 2)) + .findFirst() + .orElse(findSmallestPair(handCards)); + } + + private static List findBestTrioWithTwo(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + Optional>> trioEntry = groups.entrySet().stream() + .filter(e -> e.getValue().size() >= 3 && e.getKey() <= CARD_9) + .sorted(Map.Entry.comparingByKey()) + .findFirst(); + + if (!trioEntry.isPresent()) return findSmallestTrioWithTwo(handCards); + + Optional>> pairEntry = groups.entrySet().stream() + .filter(e -> e.getKey() != trioEntry.get().getKey() && e.getValue().size() >= 2) + .sorted(Map.Entry.comparingByKey()) + .findFirst(); + + if (!pairEntry.isPresent()) return findSmallestTrioWithTwo(handCards); + + List result = new ArrayList<>(trioEntry.get().getValue().subList(0, 3)); + result.addAll(pairEntry.get().getValue().subList(0, 2)); + return result; + } + + private static List findSmallCardsToRemove(List handCards) { + List smallCards = handCards.stream() + .filter(c -> c.cardMod <= CARD_7) + .sorted() + .collect(Collectors.toList()); + + if (smallCards.size() >= 5) { + List> straights = findAllStraights(smallCards, 5); + if (!straights.isEmpty()) return straights.get(0); + } + + if (smallCards.size() >= 4) { + List> pairs = findAllConsecutivePairs(smallCards, 2); + if (!pairs.isEmpty()) return pairs.get(0); + } + + return findSmallestPair(smallCards); + } + + private static List findConservativeMove(List handCards) { + CardObj smallCard = findSmallCard(handCards, CARD_8); + if (smallCard != null) { + return Collections.singletonList(smallCard); + } + return new ArrayList<>(); + } + + private static List findMediumValueMove(List handCards) { + List mediumCards = handCards.stream() + .filter(c -> c.cardMod >= CARD_8 && c.cardMod <= CARD_Q) + .sorted() + .collect(Collectors.toList()); + + if (!mediumCards.isEmpty()) { + List pair = findSmallestPair(mediumCards); + if (!pair.isEmpty()) return pair; + + return Collections.singletonList(mediumCards.get(0)); + } + + return new ArrayList<>(); + } + + private static List findMostFlexibleMove(List handCards) { + List pair = findSmallestPair(handCards); + if (!pair.isEmpty() && pair.get(0).cardMod <= CARD_J) { + return pair; + } + + List straight = findSmallestStraight(handCards); + if (!straight.isEmpty()) { + return straight; + } + + CardObj smallCard = findSmallNonBigCard(handCards); + if (smallCard != null) { + return Collections.singletonList(smallCard); + } + + return new ArrayList<>(); + } + + // ====================== 渐进式出牌专用方法 ====================== + + /** 查找小牌组合(渐进式) */ + private static List findSmallComboProgressive(List handCards) { + // 优先找5-8的小顺子 + for (int len = 6; len >= 5; len--) { + List> straights = findAllStraights(handCards, len); + for (List straight : straights) { + if (straight.stream().allMatch(c -> c.cardMod <= CARD_8)) { + return straight; + } + } + } + + // 找小连对 + List> pairs = findAllConsecutivePairs(handCards, 2); + for (List pair : pairs) { + if (pair.stream().allMatch(c -> c.cardMod <= CARD_7)) { + return pair; + } + } + + return new ArrayList<>(); + } + + /** 查找小对子(渐进式) */ + private static List findSmallPairProgressive(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + return groups.entrySet().stream() + .filter(e -> e.getValue().size() >= 2) + .filter(e -> e.getKey() <= CARD_8) // 严格限制在8以下 + .sorted(Map.Entry.comparingByKey()) + .map(e -> e.getValue().subList(0, 2)) + .findFirst() + .orElse(new ArrayList<>()); + } + + /** 查找渐进式小单牌 */ + private static CardObj findProgressiveSmallCard(List handCards) { + return handCards.stream() + .filter(c -> c.cardMod <= CARD_7) // 严格限制在7以下 + .min(Comparator.comparingInt(CardObj::getCardMod)) + .orElse(null); + } + + /** 查找安全的中等牌 */ + private static CardObj findSafeMediumCard(List handCards) { + List mediumCards = handCards.stream() + .filter(c -> c.cardMod >= CARD_8 && c.cardMod <= CARD_J) + .sorted() + .collect(Collectors.toList()); + + if (!mediumCards.isEmpty()) { + // 只在没有小牌时才考虑 + boolean hasSmallCards = handCards.stream().anyMatch(c -> c.cardMod <= CARD_7); + if (!hasSmallCards) { + return mediumCards.get(0); + } + } + + return null; + } + + // ====================== 基础工具方法 ====================== + + private static boolean isFirstTurn(ITObject card_list) { + return card_list == null || + !card_list.containsKey("card_list") || card_list.getTArray("card_list").size() == 0; + } + + private static boolean isReacquirePlayTurn(HuNanPaoDeKuai huNanPaoDeKuai, Map> seatRemainHistory, ITObject card_list) { + if (huNanPaoDeKuai == null || card_list == null || seatRemainHistory == null) return false; + return huNanPaoDeKuai.guangboseat == huNanPaoDeKuai.seat && !isFirstTurn(card_list); + } + + private static boolean isOpponentEndgame(int currentSeat, Map> seatRemainHistory) { + if (seatRemainHistory == null) return false; + return seatRemainHistory.entrySet().stream() + .filter(e -> e.getKey() != currentSeat) + .anyMatch(e -> !e.getValue().isEmpty() && e.getValue().get(e.getValue().size() - 1) == 1); + } + + private static ITArray handleOpponentEndgameResponse(List handCards, int type, int minCard) { + System.out.println("[残局响应] 分析是否可以用组合牌压制"); + + // 1. 首先尝试用组合牌型压制 + List comboResponse = findEndgameComboResponse(handCards, type, minCard); + if (!comboResponse.isEmpty()) { + System.out.println("[残局策略] 使用组合牌型压制: " + comboResponse.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); + return CardUtil.toTArray(comboResponse); + } + + // 2. 如果没有组合牌型,才考虑单牌压制 + if (type == TYPE_SINGLE) { + CardObj maxCard = CardUtil.findMaxSingleCard(handCards); + if (maxCard != null && maxCard.cardMod > minCard) { + System.out.println("[残局策略] 无组合牌型,出最大单牌压制: " + getCardName(maxCard.cardMod)); + return CardUtil.toTArray1(maxCard); + } + } + + // 3. 其他牌型按正常逻辑处理 + List response = findMatchingResponse(handCards, type, minCard, 0); + if (!response.isEmpty()) { + return CardUtil.toTArray(response); + } + + // 4. 最后使用炸弹 + List bomb = findSmallestBomb(handCards); + if (!bomb.isEmpty()) { + System.out.println("[残局策略] 使用炸弹"); + return CardUtil.toTArray(bomb); + } + + // 5. 单牌兜底 + if (type == TYPE_SINGLE) { + CardObj maxCard = CardUtil.findMaxSingleCard(handCards); + if (maxCard != null) { + System.out.println("[残局策略] 出最大单牌(最终兜底): " + getCardName(maxCard.cardMod)); + return CardUtil.toTArray1(maxCard); + } + } + + return new TArray(); + } + + /** 残局组合牌压制 */ + private static List findEndgameComboResponse(List handCards, int targetType, int minCard) { + System.out.println("[残局压制] 寻找可以压制的组合牌型"); + + // 只有在对手出单牌时才考虑组合牌压制 + if (targetType != TYPE_SINGLE) { + return new ArrayList<>(); + } + + List bestCombo = new ArrayList<>(); + int bestScore = 0; + + // 评估各种可以压制单牌的组合牌型 + + // 1. 炸弹(无条件压制) + List bomb = findSmallestBomb(handCards); + if (!bomb.isEmpty()) { + System.out.println("[残局压制] 发现炸弹,直接压制"); + return bomb; + } + + // 2. 对子(压制单牌) + List pair = findSmallestPair(handCards); + if (!pair.isEmpty() && pair.get(0).cardMod > minCard) { + if (2 > bestScore) { + bestCombo = pair; + bestScore = 2; + System.out.println("[残局压制] 发现可压制对子"); + } + } + + // 3. 三张(压制单牌) + List trio = findSmallestTrio(handCards); + if (!trio.isEmpty() && trio.get(0).cardMod > minCard) { + if (3 > bestScore) { + bestCombo = trio; + bestScore = 3; + System.out.println("[残局压制] 发现可压制三张"); + } + } + + // 4. 连对(压制单牌) + List consecutivePair = findSmallestConsecutivePair(handCards); + if (!consecutivePair.isEmpty() && consecutivePair.get(0).cardMod > minCard) { + if (consecutivePair.size() > bestScore) { + bestCombo = consecutivePair; + bestScore = consecutivePair.size(); + System.out.println("[残局压制] 发现可压制连对"); + } + } + + // 5. 顺子(压制单牌) + List straight = findSmallestStraight(handCards); + if (!straight.isEmpty() && straight.get(0).cardMod > minCard) { + if (straight.size() > bestScore) { + bestCombo = straight; + bestScore = straight.size(); + System.out.println("[残局压制] 发现可压制顺子"); + } + } + + return bestCombo; + } + + private static boolean isWorthUsingBomb(List handCards, List opponentCards, Map> seatRemainHistory) { + + if (seatRemainHistory != null && seatRemainHistory.values().stream().anyMatch(l -> !l.isEmpty() && l.get(l.size() - 1) == 1)) { + System.out.println("[炸弹策略] 对手只剩一张牌,使用炸弹"); + return true; + } + + if (opponentCards != null && opponentCards.stream().anyMatch(c -> c.cardMod == CARD_2)) { + System.out.println("[炸弹策略] 对手出2,使用炸弹"); + return true; + } + + if (handCards.size() <= 3) { + System.out.println("[炸弹策略] 手牌很少(" + handCards.size() + "张),使用炸弹"); + return true; + } + + System.out.println("[炸弹策略] 不满足使用条件,保留炸弹"); + return false; + } + + private static boolean isIllegalTrioWithOne(ITArray cards) { + if (cards.size() != 4) return false; + List cardList = CardUtil.toList(cards); + Map countMap = CardUtil.getCardNumMap(cardList); + return countMap.containsValue(3) && countMap.containsValue(1); + } + + private static ITArray reselectLegalCards(List handCards) { + List pair = findSmallestPair(handCards); + if (!pair.isEmpty()) { + return CardUtil.toTArray(pair); + } + return CardUtil.toTArray1(CardUtil.findMinSingleCard(handCards)); + } + + private static ITArray emergencyOutCard(List handCards) { + if (handCards.isEmpty()) return new TArray(); + CardObj minCard = CardUtil.findMinSingleCard(handCards); + System.out.println("[紧急策略] 出最小合规单牌: " + getCardName(minCard.cardMod)); + return CardUtil.toTArray1(minCard); + } + + private static List findMatchingResponse(List handCards, int type, int minCard, int len) { + System.out.println("[智能响应] 收到出牌请求 - 报告类型:" + getCardTypeName(type) + ", 最小牌:" + getCardName(minCard) + ", 长度:" + len); + + // 直接分析实际牌面构成,这才是真正的智能! + List actualCards = getLastPlayedCards(); + if (actualCards != null && !actualCards.isEmpty()) { + int actualType = analyzeActualCardType(actualCards); + System.out.println("[智能分析] 实际牌面分析结果 - 真实类型:" + getCardTypeName(actualType)); + + // 优先信任我们自己的分析结果 + if (actualType != type) { + System.out.println("[智能纠错] 检测到服务器报告类型与实际不符,采用智能分析结果"); + type = actualType; + } + } + + switch (type) { + case TYPE_SINGLE: + return findSingleResponse(handCards, minCard); + case TYPE_PAIR: + return findPairResponse(handCards, minCard); + case TYPE_STRAIGHT: + return findStraightResponse(handCards, minCard, len); + case TYPE_CONSECUTIVE_PAIR: + return findConsecutivePairResponse(handCards, minCard, len); + case TYPE_TRIO: + return findTrioResponse(handCards, minCard); + case TYPE_TRIO_WITH_TWO: + return findTrioWithTwoResponse(handCards, minCard); + case TYPE_BOMB: + return findBombResponse(handCards, minCard); + case TYPE_AIRPLANE_WITH_CARDS: + return findAirplaneWithCardsResponse(handCards, minCard, len); + case TYPE_FOUR_WITH_TWO: + case TYPE_FOUR_WITH_TWO_PAIR: + return findFourWithTwoResponse(handCards, minCard); + default: + System.out.println("未知/非法牌型: " + type + ",无法响应"); + return new ArrayList<>(); + } + } + + private static List findSingleResponse(List handCards, int minCard) { + CardObj playable = CardUtil.findMinBiggerSingleCard(handCards, minCard); + + if (playable != null) { + return Collections.singletonList(playable); + } + + return new ArrayList<>(); + } + + private static List findPairResponse(List handCards, int minCard) { + Map> valueGroups = CardUtil.getCardListMap(handCards); + return valueGroups.entrySet().stream() + .filter(e -> e.getKey() > minCard && e.getValue().size() >= 2) + .sorted(Map.Entry.comparingByKey()) + .map(e -> e.getValue().subList(0, 2)) + .findFirst() + .orElse(new ArrayList<>()); + } + + private static List findStraightResponse(List handCards, int minCard, int len) { + if (len < MIN_STRAIGHT_LENGTH) return new ArrayList<>(); + + List> allStraights = findAllStraights(handCards, len); + return allStraights.stream() + .filter(s -> s.get(0).cardMod > minCard) + .sorted(Comparator.comparingInt(s -> s.get(0).cardMod)) + .findFirst() + .orElse(new ArrayList<>()); + } + + private static List findConsecutivePairResponse(List handCards, int minCard, int len) { + int pairCount = len / 2; + + System.out.println("[连对响应] 寻找" + pairCount + "连对,最小起始牌:" + getCardName(minCard)); + + if (pairCount < MIN_CONSECUTIVE_PAIR_COUNT) { + System.out.println("对手连对数<" + MIN_CONSECUTIVE_PAIR_COUNT + ",不符合规则,无法响应"); + return new ArrayList<>(); + } + + List> allConsecutivePairs = findAllConsecutivePairs(handCards, pairCount); + + System.out.println("[连对响应] 找到" + allConsecutivePairs.size() + "个符合条件的连对"); + + List> validPairs = allConsecutivePairs.stream() + .filter(p -> p.get(0).cardMod > minCard) + .sorted(Comparator.comparingInt(p -> p.get(0).cardMod)) + .collect(Collectors.toList()); + + if (!validPairs.isEmpty()) { + List chosenPair = validPairs.get(0); + System.out.println("[连对响应] 选择出牌:" + chosenPair.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); + return chosenPair; + } + + System.out.println("[连对响应] 无合适连对可出"); + return new ArrayList<>(); + } + + private static List findTrioResponse(List handCards, int minCard) { + Map> valueGroups = CardUtil.getCardListMap(handCards); + + List result = valueGroups.entrySet().stream() + .filter(e -> e.getKey() > minCard && e.getValue().size() >= 3) + .sorted(Map.Entry.comparingByKey()) + .map(e -> e.getValue().subList(0, 3)) + .findFirst() + .orElse(new ArrayList<>()); + + return result; + } + + private static List findTrioWithTwoResponse(List handCards, int minCard) { + Map> valueGroups = CardUtil.getCardListMap(handCards); + + Optional>> trioEntry = valueGroups.entrySet().stream() + .filter(e -> e.getKey() > minCard && e.getValue().size() >= 3) + .sorted(Map.Entry.comparingByKey()) + .findFirst(); + + if (!trioEntry.isPresent()) { + return new ArrayList<>(); + } + + Optional>> pairEntry = valueGroups.entrySet().stream() + .filter(e -> e.getKey() != trioEntry.get().getKey() && e.getValue().size() >= 2) + .sorted(Map.Entry.comparingByKey()) + .findFirst(); + + if (!pairEntry.isPresent()) { + return new ArrayList<>(); + } + + List result = new ArrayList<>(trioEntry.get().getValue().subList(0, 3)); + result.addAll(pairEntry.get().getValue().subList(0, 2)); + + return result; + } + + private static List findBombResponse(List handCards, int minCard) { + Map> valueGroups = CardUtil.getCardListMap(handCards); + return valueGroups.entrySet().stream() + .filter(e -> e.getKey() > minCard && e.getValue().size() >= 4) + .sorted(Map.Entry.comparingByKey()) + .map(e -> e.getValue().subList(0, 4)) + .findFirst() + .orElse(new ArrayList<>()); + } + + private static List findAirplaneWithCardsResponse(List handCards, int minCard, int len) { + int trioCount = len / 6; + if (trioCount < MIN_AIRPLANE_TRIO_COUNT) return new ArrayList<>(); + + List> allAirplanes = findAllAirplanesWithCards(handCards, trioCount); + return allAirplanes.stream() + .filter(a -> a.get(0).cardMod > minCard) + .sorted(Comparator.comparingInt(a -> a.get(0).cardMod)) + .findFirst() + .orElse(new ArrayList<>()); + } + + private static List findFourWithTwoResponse(List handCards, int minCard) { + Map> valueGroups = CardUtil.getCardListMap(handCards); + Optional>> fourEntry = valueGroups.entrySet().stream() + .filter(e -> e.getKey() > minCard && e.getValue().size() >= 4) + .sorted(Map.Entry.comparingByKey()) + .findFirst(); + + if (!fourEntry.isPresent()) return new ArrayList<>(); + + List four = fourEntry.get().getValue().subList(0, 4); + List carryCards = findTwoMinCardsExcept(handCards, fourEntry.get().getKey()); + if (carryCards.size() < 2) return new ArrayList<>(); + + List result = new ArrayList<>(four); + result.addAll(carryCards.subList(0, 2)); + return result; + } + + private static List findTwoMinCardsExcept(List handCards, int excludeValue) { + return handCards.stream() + .filter(c -> c.cardMod != excludeValue) + .sorted() + .limit(2) + .collect(Collectors.toList()); + } + + private static CardObj findSmallCard(List handCards, int maxValue) { + return handCards.stream() + .filter(c -> c.cardMod <= maxValue) + .min(Comparator.comparingInt(CardObj::getCardMod)) + .orElse(null); + } + + private static CardObj findSmallNonBigCard(List handCards) { + return handCards.stream() + .filter(c -> c.cardMod < CARD_K) + .min(Comparator.comparingInt(CardObj::getCardMod)) + .orElse(CardUtil.findMinSingleCard(handCards)); + } + + private static List findSmallestPair(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + return groups.entrySet().stream() + .filter(e -> e.getValue().size() >= 2) + .sorted(Map.Entry.comparingByKey()) + .map(e -> e.getValue().subList(0, 2)) + .findFirst() + .orElse(new ArrayList<>()); + } + + private static List findSmallPair(List handCards, int maxValue) { + Map> groups = CardUtil.getCardListMap(handCards); + return groups.entrySet().stream() + .filter(e -> e.getKey() <= maxValue && e.getValue().size() >= 2) + .sorted(Map.Entry.comparingByKey()) + .map(e -> e.getValue().subList(0, 2)) + .findFirst() + .orElse(new ArrayList<>()); + } + + private static List findSmallestStraight(List handCards) { + for (int len = MIN_STRAIGHT_LENGTH; len <= 12; len++) { + List> straights = findAllStraights(handCards, len); + if (!straights.isEmpty()) { + return straights.stream() + .sorted(Comparator.comparingInt(s -> s.get(0).cardMod)) + .findFirst() + .get(); + } + } + return new ArrayList<>(); + } + + private static List> findAllStraights(List handCards, int len) { + List> straights = new ArrayList<>(); + if (len < MIN_STRAIGHT_LENGTH) return straights; + + List distinctValues = handCards.stream() + .map(CardObj::getCardMod) + .distinct() + .sorted() + .collect(Collectors.toList()); + + for (int i = 0; i <= distinctValues.size() - len; i++) { + boolean isConsecutive = true; + for (int j = 0; j < len - 1; j++) { + if (distinctValues.get(i + j + 1) - distinctValues.get(i + j) != 1) { + isConsecutive = false; + break; + } + } + if (isConsecutive) { + List straight = new ArrayList<>(); + for (int j = 0; j < len; j++) { + int value = distinctValues.get(i + j); + straight.add(CardUtil.getCard(value, handCards)); + } + straights.add(straight); + } + } + return straights; + } + + private static List findSmallestConsecutivePair(List handCards) { + List> allPairs = findAllConsecutivePairs(handCards, MIN_CONSECUTIVE_PAIR_COUNT); + return allPairs.stream() + .sorted(Comparator.comparingInt(p -> p.get(0).cardMod)) + .findFirst() + .orElse(new ArrayList<>()); + } + + private static List> findAllConsecutivePairs(List handCards, int pairCount) { + List> consecutivePairs = new ArrayList<>(); + if (pairCount < MIN_CONSECUTIVE_PAIR_COUNT) return consecutivePairs; + + Map> groups = CardUtil.getCardListMap(handCards); + List pairValues = groups.entrySet().stream() + .filter(e -> e.getValue().size() >= 2) + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + + for (int i = 0; i <= pairValues.size() - pairCount; i++) { + boolean isConsecutive = true; + List currentSequence = new ArrayList<>(); + + for (int j = 0; j < pairCount; j++) { + currentSequence.add(pairValues.get(i + j)); + if (j < pairCount - 1) { + if (pairValues.get(i + j + 1) - pairValues.get(i + j) != 1) { + isConsecutive = false; + break; + } + } + } + + if (isConsecutive) { + List pairs = new ArrayList<>(); + for (int j = 0; j < pairCount; j++) { + pairs.addAll(groups.get(pairValues.get(i + j)).subList(0, 2)); + } + consecutivePairs.add(pairs); + } + } + + return consecutivePairs; + } + + private static List findSmallestTrio(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + return groups.entrySet().stream() + .filter(e -> e.getValue().size() >= 3) + .sorted(Map.Entry.comparingByKey()) + .map(e -> e.getValue().subList(0, 3)) + .findFirst() + .orElse(new ArrayList<>()); + } + + private static List findSmallestTrioWithTwo(List handCards) { + List trio = findSmallestTrio(handCards); + if (trio.isEmpty()) return new ArrayList<>(); + + Map> groups = CardUtil.getCardListMap(handCards); + Optional>> pairEntry = groups.entrySet().stream() + .filter(e -> e.getKey() != trio.get(0).cardMod && e.getValue().size() >= 2) + .sorted(Map.Entry.comparingByKey()) + .findFirst(); + + if (!pairEntry.isPresent()) return new ArrayList<>(); + + List result = new ArrayList<>(trio); + result.addAll(pairEntry.get().getValue().subList(0, 2)); + return result; + } + + private static List findSmallestBomb(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + return groups.entrySet().stream() + .filter(e -> e.getValue().size() >= 4) + .sorted(Map.Entry.comparingByKey()) + .map(e -> e.getValue().subList(0, 4)) + .findFirst() + .orElse(new ArrayList<>()); + } + + private static List findSmallestFourWithTwo(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + Optional>> fourEntry = groups.entrySet().stream() + .filter(e -> e.getValue().size() >= 4) + .sorted(Map.Entry.comparingByKey()) + .findFirst(); + + if (!fourEntry.isPresent()) return new ArrayList<>(); + + List carryCards = findTwoMinCardsExcept(handCards, fourEntry.get().getKey()); + if (carryCards.size() < 2) return new ArrayList<>(); + + List result = new ArrayList<>(fourEntry.get().getValue().subList(0, 4)); + result.addAll(carryCards.subList(0, 2)); + return result; + } + + private static List findSmallestAirplaneWithCards(List handCards) { + for (int trioCount = MIN_AIRPLANE_TRIO_COUNT; trioCount <= 3; trioCount++) { + List> airplanes = findAllAirplanesWithCards(handCards, trioCount); + if (!airplanes.isEmpty()) { + return airplanes.stream() + .sorted(Comparator.comparingInt(a -> a.get(0).cardMod)) + .findFirst() + .get(); + } + } + return new ArrayList<>(); + } + + private static List> findAllAirplanesWithCards(List handCards, int trioCount) { + List> airplanes = new ArrayList<>(); + if (trioCount < MIN_AIRPLANE_TRIO_COUNT) return airplanes; + + Map> groups = CardUtil.getCardListMap(handCards); + List trioValues = groups.entrySet().stream() + .filter(e -> e.getValue().size() >= 3) + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + + for (int i = 0; i <= trioValues.size() - trioCount; i++) { + boolean isConsecutive = true; + for (int j = 0; j < trioCount - 1; j++) { + if (trioValues.get(i + j + 1) - trioValues.get(i + j) != 1) { + isConsecutive = false; + break; + } + } + if (isConsecutive) { + List airplane = new ArrayList<>(); + Set usedValues = new HashSet<>(); + for (int j = 0; j < trioCount; j++) { + int value = trioValues.get(i + j); + airplane.addAll(groups.get(value).subList(0, 3)); + usedValues.add(value); + } + + List carryPairs = new ArrayList<>(); + List>> pairCandidates = groups.entrySet().stream() + .filter(e -> !usedValues.contains(e.getKey()) && e.getValue().size() >= 2) + .sorted(Map.Entry.comparingByKey()) + .collect(Collectors.toList()); + + if (pairCandidates.size() >= trioCount) { + for (int j = 0; j < trioCount; j++) { + carryPairs.addAll(pairCandidates.get(j).getValue().subList(0, 2)); + } + airplane.addAll(carryPairs); + airplanes.add(airplane); + } + } + } + return airplanes; + } + + private static boolean isTrio(List cards) { + if (cards.size() != 3) return false; + int value = cards.get(0).cardMod; + return cards.stream().allMatch(c -> c.cardMod == value); + } + + private static int findMaxStraightLength(List handCards) { + List distinctValues = handCards.stream() + .map(CardObj::getCardMod) + .distinct() + .sorted() + .collect(Collectors.toList()); + + int maxLength = 0; + int currentLength = 1; + + for (int i = 1; i < distinctValues.size(); i++) { + if (distinctValues.get(i) - distinctValues.get(i-1) == 1) { + currentLength++; + } else { + maxLength = Math.max(maxLength, currentLength); + currentLength = 1; + } + } + maxLength = Math.max(maxLength, currentLength); + + return maxLength; + } + + private static int findMaxConsecutivePairs(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + List pairValues = groups.entrySet().stream() + .filter(e -> e.getValue().size() >= 2) + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + + if (pairValues.isEmpty()) return 0; + + int maxPairs = 1; + int currentPairs = 1; + + for (int i = 1; i < pairValues.size(); i++) { + if (pairValues.get(i) - pairValues.get(i-1) == 1) { + currentPairs++; + } else { + maxPairs = Math.max(maxPairs, currentPairs); + currentPairs = 1; + } + } + maxPairs = Math.max(maxPairs, currentPairs); + + return maxPairs; + } + + + /** + * 改进的牌型推断方法 - 修复连对识别错误 + */ + private static int inferCorrectCardType(int len) { + // 优先处理常见的连对情况 + if (len == 4) return TYPE_CONSECUTIVE_PAIR; // 2连对 + if (len == 6) return TYPE_CONSECUTIVE_PAIR; // 3连对 + if (len == 8) return TYPE_CONSECUTIVE_PAIR; // 4连对 + if (len == 10) return TYPE_CONSECUTIVE_PAIR; // 5连对 + if (len == 12) return TYPE_AIRPLANE_WITH_CARDS; // 飞机 + + // 基础牌型 + switch (len) { + case 1: return TYPE_SINGLE; + case 2: return TYPE_PAIR; + case 3: return TYPE_TRIO; + case 4: return TYPE_BOMB; // 4张牌默认是炸弹(除非明确是连对) + case 5: return TYPE_TRIO_WITH_TWO; + case 6: return TYPE_CONSECUTIVE_PAIR; + default: + // 连对:偶数张且>=4 + if (len >= 4 && len % 2 == 0) return TYPE_CONSECUTIVE_PAIR; + // 顺子:连续5张以上 + if (len >= MIN_STRAIGHT_LENGTH) return TYPE_STRAIGHT; + // 飞机:3的倍数且>=6 + if (len >= MIN_AIRPLANE_TRIO_COUNT * 5 && len % 5 == 0) return TYPE_AIRPLANE_WITH_CARDS; + return TYPE_SINGLE; + } + } + + private static boolean checkIfAnyOpponentHasOneCard(Map> seatRemainHistory, int currentSeat) { + if (seatRemainHistory == null || seatRemainHistory.isEmpty()) { + return false; + } + + for (Map.Entry> entry : seatRemainHistory.entrySet()) { + int seat = entry.getKey(); + List remainList = entry.getValue(); + + if (seat == currentSeat) { + continue; + } + + if (remainList != null && !remainList.isEmpty()) { + int lastRemain = remainList.get(remainList.size() - 1); + if (lastRemain == 1) { + return true; + } + } + } + return false; + } + + private static String getCardName(int cardMod) { + switch (cardMod) { + case CARD_3: return "3"; + case CARD_4: return "4"; + case CARD_5: return "5"; + case CARD_6: return "6"; + case CARD_7: return "7"; + case CARD_8: return "8"; + case CARD_9: return "9"; + case CARD_10: return "10"; + case CARD_J: return "J"; + case CARD_Q: return "Q"; + case CARD_K: return "K"; + case CARD_A: return "A"; + case CARD_2: return "2"; + default: return String.valueOf(cardMod); + } + } + + private static String getCardTypeName(int type) { + switch (type) { + case TYPE_SINGLE: return "单牌"; + case TYPE_PAIR: return "对子"; + case TYPE_STRAIGHT: return "顺子"; + case TYPE_CONSECUTIVE_PAIR: return "连对"; + case TYPE_TRIO: return "三张"; + case TYPE_TRIO_WITH_TWO: return "三带二"; + case TYPE_BOMB: return "炸弹"; + case TYPE_AIRPLANE_WITH_CARDS: return "飞机带牌"; + case TYPE_FOUR_WITH_TWO: return "四带二"; + case TYPE_FOUR_WITH_TWO_PAIR: return "四带二对"; + default: return "非法/未知(" + type + ")"; + } + } + + /** 获取上家实际出牌(需要从游戏状态中获取)*/ + private static List getLastPlayedCards() { + // TODO: 从游戏状态中获取实际出牌信息 + // 这里暂时返回null,实际应用中应该从card_list中解析 + return null; + } + + /** 智能分析实际牌型 - 真正的AI核心! */ + private static int analyzeActualCardType(List cards) { + if (cards == null || cards.isEmpty()) return TYPE_SINGLE; + + System.out.println("[牌型分析] 分析牌面: " + cards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); + + // 1. 统计每种牌的数量 + Map countMap = cards.stream() + .collect(Collectors.groupingBy(CardObj::getCardMod, Collectors.counting())); + + System.out.println("[牌型分析] 牌数统计: " + countMap); + + // 2. 分析牌型特征 + int cardCount = cards.size(); + List uniqueValues = new ArrayList<>(countMap.keySet()); + Collections.sort(uniqueValues); + + // 单牌 + if (cardCount == 1) { + System.out.println("[牌型分析] 结论: 单牌"); + return TYPE_SINGLE; + } + + // 对子 + if (cardCount == 2 && uniqueValues.size() == 1 && countMap.get(uniqueValues.get(0)) == 2) { + System.out.println("[牌型分析] 结论: 对子"); + return TYPE_PAIR; + } + + // 三张 + if (cardCount == 3 && uniqueValues.size() == 1 && countMap.get(uniqueValues.get(0)) == 3) { + System.out.println("[牌型分析] 结论: 三张"); + return TYPE_TRIO; + } + + // 炸弹 + if (cardCount == 4 && uniqueValues.size() == 1 && countMap.get(uniqueValues.get(0)) == 4) { + System.out.println("[牌型分析] 结论: 炸弹"); + return TYPE_BOMB; + } + + // 连对:检查是否全是对子且数值连续 + if (cardCount >= 4 && cardCount % 2 == 0) { + boolean allPairs = uniqueValues.stream().allMatch(v -> countMap.get(v) == 2); + boolean consecutive = isConsecutive(uniqueValues); + + if (allPairs && consecutive) { + System.out.println("[牌型分析] 结论: 连对 (" + (cardCount/2) + "连)"); + return TYPE_CONSECUTIVE_PAIR; + } + } + + // 顺子:检查是否无重复且连续 + if (cardCount >= 5) { + boolean noDuplicates = uniqueValues.size() == cardCount; + boolean consecutive = isConsecutive(uniqueValues); + + if (noDuplicates && consecutive) { + System.out.println("[牌型分析] 结论: 顺子 (" + cardCount + "张)"); + return TYPE_STRAIGHT; + } + } + + // 三带二 + if (cardCount == 5) { + boolean hasThree = uniqueValues.stream().anyMatch(v -> countMap.get(v) == 3); + boolean hasTwo = uniqueValues.stream().anyMatch(v -> countMap.get(v) == 2); + + if (hasThree && hasTwo) { + System.out.println("[牌型分析] 结论: 三带二"); + return TYPE_TRIO_WITH_TWO; + } + } + + // 默认返回单牌 + System.out.println("[牌型分析] 结论: 无法识别,按单牌处理"); + return TYPE_SINGLE; + } + + /** 判断数值是否连续 */ + private static boolean isConsecutive(List values) { + if (values.size() < 2) return true; + + for (int i = 1; i < values.size(); i++) { + if (values.get(i) - values.get(i-1) != 1) { + return false; + } + } + return true; + } + + /** 调试用手牌分析 */ + private static void debugHandAnalysis(List handCards) { + System.out.println("\n---------- 手牌详细分析 ----------"); + + // 按牌值分组 + Map> groups = CardUtil.getCardListMap(handCards); + System.out.println("牌值分布:"); + groups.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(e -> { + String cardNames = e.getValue().stream() + .map(c -> getCardName(c.cardMod)) + .collect(Collectors.joining("")); + System.out.println(" " + getCardName(e.getKey()) + ": " + cardNames + " (" + e.getValue().size() + "张)"); + }); + + // 查找所有可能的牌型 + System.out.println("\n可出牌型分析:"); + + // 对子 + List pairs = findSmallestPair(handCards); + if (!pairs.isEmpty()) { + System.out.println(" 对子: " + pairs.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); + } + + // 连对 + List> consecutivePairs = findAllConsecutivePairs(handCards, 2); + if (!consecutivePairs.isEmpty()) { + System.out.println(" 连对数量: " + consecutivePairs.size()); + for (int i = 0; i < Math.min(3, consecutivePairs.size()); i++) { + List pair = consecutivePairs.get(i); + System.out.println(" 连对" + (i+1) + ": " + pair.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); + } + } + + // 顺子 + List> straights = findAllStraights(handCards, 5); + if (!straights.isEmpty()) { + System.out.println(" 顺子数量: " + straights.size()); + for (int i = 0; i < Math.min(2, straights.size()); i++) { + List straight = straights.get(i); + System.out.println(" 顺子" + (i+1) + ": " + straight.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); + } + } + + System.out.println("----------------------------------"); + } + + private static String getDecisionDescription(ITArray decision, List handCards) { + if (decision == null || decision.size() == 0) { + return "Pass"; + } + + List cards = CardUtil.toList(decision); + if (cards.isEmpty()) return "Pass"; + + String typeName = getCardTypeName(inferCorrectCardType(cards.size())); + String cardNames = cards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(",")); + return typeName + "[" + cardNames + "]"; + } +} \ No newline at end of file