From c9eb0078d93f1f8b86fec5d282437e322a9906e9 Mon Sep 17 00:00:00 2001 From: zhouwei <849588297@qq.com> Date: Tue, 10 Feb 2026 16:37:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B7=91=E5=BE=97=E5=BF=AB?= =?UTF-8?q?=E6=9C=BA=E5=99=A8=E4=BA=BA=E5=87=BA=E7=89=8C=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/robot/mj/handler/HuNanPaoDeKuai.java | 11 +- .../src/main/java/taurus/util/test_smart.java | 2380 +++++++++++++---- 2 files changed, 1801 insertions(+), 590 deletions(-) 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 d6c44fc..d59f1bb 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 @@ -255,16 +255,10 @@ public class HuNanPaoDeKuai { */ public String outCard(TaurusClient client) { try { - System.out.println("=== 开始执行outCard方法 ==="); - System.out.println("当前seat: " + seat); - System.out.println("当前guangboseat: " + guangboseat); - System.out.println("当前手牌数量: " + paoDekuaiCardInhand.size()); - System.out.println("当前card_list: " + card_list); - + ITArray itArray = null; itArray = test_smart.intelligentPaoDeKuaiOutCard(this, paoDekuaiCardInhand, card_list, seatRemainHistory); - System.out.println("itArray-----" + itArray); - + //无法跟牌且不是下家只剩一张牌的情况 则pass if (itArray == null && remain != 1) { System.out.println("无法跟牌,选择pass"); @@ -378,7 +372,6 @@ public class HuNanPaoDeKuai { // 修正长度字段 if (reportedLen != actualLen) { - System.out.println("[警告] 长度字段不匹配! 报告:" + reportedLen + ", 实际:" + actualLen); card_list.putInt("len", actualLen); } } 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 index 9849648..b421661 100644 --- 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 @@ -172,6 +172,13 @@ public class test_smart { return handleEndgameSituation(handCards); } + // 检查自身残局状态(手中牌较少时也要谨慎) + boolean isSelfEndgame = checkIfSelfInEndgame(handCards); + if (isSelfEndgame) { + System.out.println("[自我残局] 检测到自身处于残局状态"); + return handleSelfEndgameSituation(handCards); + } + // 正常情况:智能分析后出牌 HandAnalysis analysis = analyzeHandComposition(handCards); GameSituation situation = evaluateCurrentSituation(seatRemainHistory); @@ -201,28 +208,33 @@ public class test_smart { ", 最小牌: " + getCardName(minCard) + ", 牌数: " + len); + // 对手剩余两张牌试探策略 + if (checkIfAnyOpponentHasTwoCards(seatRemainHistory, huNanPaoDeKuai != null ? huNanPaoDeKuai.seat : 0)) { + System.out.println("【试探策略】对手剩2张牌,谨慎处理对子出牌"); + return handleOpponentTwoCardsResponse(handCards, type, minCard, opponentCards); + } + // 残局特殊处理 if (isOpponentEndgame(huNanPaoDeKuai != null ? huNanPaoDeKuai.seat : 0, seatRemainHistory)) { System.out.println("【残局策略】对手剩1张牌,智能响应"); - return handleOpponentEndgameResponse(handCards, type, minCard); + return handleOpponentEndgameResponse(handCards, type, minCard, opponentCards); } // 正常响应 - List responseCards = findMatchingResponse(handCards, type, minCard, len); + List responseCards = findMatchingResponse(handCards, type, minCard, len, opponentCards); if (responseCards.isEmpty()) { - // 保守策略:只有在特定条件下才使用炸弹 - if (shouldUseBombConservatively(handCards, type, minCard, seatRemainHistory, huNanPaoDeKuai)) { - List bomb = findSmallestBomb(handCards); - if (!bomb.isEmpty()) { - System.out.println("[保守策略] 满足炸弹使用条件,出炸弹"); - return CardUtil.toTArray(bomb); - } + // 跑得快规则:要得起必须出,但优先使用对应牌型 + // 只有在没有对应牌型且没有炸弹时才能PASS + List bomb = findSmallestBomb(handCards); + if (!bomb.isEmpty()) { + System.out.println("[强制规则] 无对应牌型但有炸弹,必须出炸弹"); + return CardUtil.toTArray(bomb); } - // 无合适牌型且不满足炸弹使用条件时的兜底策略 - System.out.println("[策略] 无匹配牌型且不满足炸弹使用条件"); - return emergencyOutCard(handCards); + // 无对应牌型且无炸弹,要不起,PASS + System.out.println("[规则] 无对应牌型且无炸弹,要不起,PASS"); + return new TArray(); // 返回空数组表示PASS } return CardUtil.toTArray(responseCards); @@ -256,25 +268,93 @@ public class test_smart { public String getDescription() { return description; } } - /** 策略倾向枚举 */ - private enum StrategyTendency { - AGGRESSIVE("激进型", "主动出击,快速出牌"), - CONSERVATIVE("保守型", "稳扎稳打,保存实力"), - BALANCED("均衡型", "攻守兼备,灵活应对"), - OPPORTUNISTIC("机会型", "等待时机,见机行事"), - PROGRESSIVE("渐进型", "先小后大,循序渐进"), // 新增:解决先打大牌问题 - PRESERVATIVE("保存型", "保留关键牌,关键时刻发力"); + /** 顺子结构类 */ + private static class StraightStructure { + List cards; + int length; + int minValue; + int maxValue; + double qualityScore; // 质量评分 + double protectionValue; // 保护价值 + boolean isNested; // 是否为嵌套结构 + List nestedChildren; // 嵌套子结构 - private final String name; - private final String description; - - StrategyTendency(String name, String description) { - this.name = name; - this.description = description; + StraightStructure(List cards) { + this.cards = cards; + this.length = cards.size(); + this.minValue = cards.get(0).cardMod; + this.maxValue = cards.get(cards.size()-1).cardMod; + this.qualityScore = calculateQualityScore(); + this.protectionValue = calculateProtectionValue(); + this.isNested = false; + this.nestedChildren = new ArrayList<>(); } - public String getName() { return name; } - public String getDescription() { return description; } + private double calculateQualityScore() { + double score = 0.0; + // 长度加成 + score += Math.min(length * 0.1, 0.8); + // 连续性加成 + score += 0.2; + // 牌值适中加成 + double avgValue = cards.stream().mapToInt(c -> c.cardMod).average().orElse(0); + if (avgValue >= 6 && avgValue <= 10) score += 0.3; // 中等牌值更优 + return Math.min(score, 1.0); + } + + private double calculateProtectionValue() { + double value = qualityScore; + // 嵌套结构额外价值 + if (isNested) value *= 1.5; + // 长顺子更高保护价值 + if (length >= 8) value *= 1.3; + return value; + } + + @Override + public String toString() { + return String.format("顺子[%d-%d](%d张,质量%.2f,保护%.2f)", + minValue, maxValue, length, qualityScore, protectionValue); + } + } + + /** 连对结构类 */ + private static class ConsecutivePairStructure { + List cards; + int pairCount; + int startValue; + int endValue; + double qualityScore; + double protectionValue; + + ConsecutivePairStructure(List cards) { + this.cards = cards; + this.pairCount = cards.size() / 2; + List values = cards.stream().map(c -> c.cardMod).distinct().sorted().collect(Collectors.toList()); + this.startValue = values.get(0); + this.endValue = values.get(values.size()-1); + this.qualityScore = calculateQualityScore(); + this.protectionValue = calculateProtectionValue(); + } + + private double calculateQualityScore() { + double score = 0.0; + // 对数加成 + score += Math.min(pairCount * 0.2, 0.8); + // 连续性加成 + score += 0.2; + return Math.min(score, 1.0); + } + + private double calculateProtectionValue() { + return qualityScore * 1.1; // 连对略高于普通顺子 + } + + @Override + public String toString() { + return String.format("连对[%d-%d](%d对,质量%.2f,保护%.2f)", + startValue, endValue, pairCount, qualityScore, protectionValue); + } } /** 牌优先级指导类 */ @@ -298,7 +378,26 @@ public class test_smart { } } - /** 游戏局势分析类 */ + /** 策略倾向枚举 */ + 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 GameSituation { int opponentCount; List opponentSeats; @@ -316,7 +415,83 @@ public class test_smart { } } - /** 手牌分析结果类 */ + /** 潜在结构类 */ + private static class PotentialStructure { + String type; // "straight", "consecutive_pair" + int missingCards; // 缺少的牌数 + int potentialLength; // 潜在长度 + double formationProbability; // 形成概率 + double valueScore; // 价值评分 + + PotentialStructure(String type, int missingCards, int potentialLength) { + this.type = type; + this.missingCards = missingCards; + this.potentialLength = potentialLength; + this.formationProbability = calculateFormationProbability(); + this.valueScore = calculateValueScore(); + } + + private double calculateFormationProbability() { + // 缺牌越少,形成概率越高 + return Math.max(0.1, 1.0 - missingCards * 0.2); + } + + private double calculateValueScore() { + double score = 0.0; + score += potentialLength * 0.1; // 长度价值 + score += formationProbability * 0.5; // 形成概率价值 + score -= missingCards * 0.1; // 缺牌惩罚 + return Math.max(0.0, score); + } + } + + /** 结构保护计划类 */ + private static class StructureProtectionPlan { + List protectedStructures; // 需要保护的结构 + Map protectionPriorities; // 保护优先级 + List alternativeMoves; // 替代出牌方案 + + StructureProtectionPlan() { + this.protectedStructures = new ArrayList<>(); + this.protectionPriorities = new HashMap<>(); + this.alternativeMoves = new ArrayList<>(); + } + + void addProtectedStructure(String type, Object structure, double priority) { + protectedStructures.add(new ProtectedStructure(type, structure)); + protectionPriorities.put(type, priority); + } + + void addAlternativeMove(String moveType, List cards, double benefitScore, String reason) { + alternativeMoves.add(new AlternativeMove(moveType, cards, benefitScore, reason)); + } + } + + /** 受保护结构类 */ + private static class ProtectedStructure { + String type; + Object structure; + + ProtectedStructure(String type, Object structure) { + this.type = type; + this.structure = structure; + } + } + + /** 替代出牌方案类 */ + private static class AlternativeMove { + String moveType; + List cards; + double benefitScore; + String reason; + + AlternativeMove(String moveType, List cards, double benefitScore, String reason) { + this.moveType = moveType; + this.cards = cards; + this.benefitScore = benefitScore; + this.reason = reason; + } + } private static class HandAnalysis { int totalCards; int singleCount; @@ -336,6 +511,13 @@ public class test_smart { int maxConsecutivePairs; boolean hasContinuousStructure; + // 专业级新增字段 + List allStraights; // 所有顺子结构 + List allConsecutivePairs; // 所有连对结构 + Map> cardGroups; // 牌值分组 + List potentialStructures; // 潜在可形成的结构 + StructureProtectionPlan protectionPlan; // 结构保护计划 + double comboQualityScore; double flexibilityScore; double controlScore; @@ -348,6 +530,12 @@ public class test_smart { double balanceScore; // 牌力平衡度 double progressionScore; // 出牌流畅度 + // 专业级评估指标 + double structureIntegrityScore; // 结构完整性评分 + double disruptionCostScore; // 拆分代价评分 + double futurePotentialScore; // 未来发展潜力 + double strategicValueScore; // 战略价值评分 + String recommendedStrategy; List alternativeStrategies; @@ -356,9 +544,9 @@ public class test_smart { @Override public String toString() { - return String.format("手牌分析: %d张牌 | 单%d 对%d 三%d 炸%d | 小%d 中%d 大%d 关键%d | 策略:%s | 平衡度:%.2f", + return String.format("手牌分析: %d张牌 | 单%d 对%d 三%d 炸%d | 小%d 中%d 大%d 关键%d | 策略:%s | 平衡度:%.2f | 完整性:%.2f", totalCards, singleCount, pairCount, trioCount, bombCount, - lowCards, mediumCards, highCards, bigCards, recommendedStrategy, balanceScore); + lowCards, mediumCards, highCards, bigCards, recommendedStrategy, balanceScore, structureIntegrityScore); } } @@ -590,43 +778,616 @@ public class test_smart { /** 均衡型决策 */ 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("[重要提醒] 优先考虑组合牌型,避免拆分有价值的牌组"); + System.out.println("[智能决策] 启动基于手牌构成的自适应决策系统"); - List outCards; + // 1. 首先进行全面的手牌价值评估 + HandEvaluation evaluation = evaluateHandComprehensively(handCards, analysis); - // 首轮出牌优先级调整:组合牌型 > 连续结构 > 单牌 - outCards = findBestComboMove(handCards, analysis); - if (!outCards.isEmpty()) { - System.out.println("[组合优先] 出组合牌型: " + outCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.toList())); - return CardUtil.toTArray(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); + // 2. 生成多种可行的出牌方案 + List options = generatePlayOptions(handCards, evaluation); + + // 3. 智能评估每个选项的预期收益 + PlayOption bestOption = selectOptimalOption(options, evaluation); + + // 4. 执行最优选择 + if (bestOption != null && !bestOption.cards.isEmpty()) { + System.out.println("[智能选择] 选择策略: " + bestOption.strategyName + + " | 预期收益: " + String.format("%.2f", bestOption.expectedValue) + + " | 出牌: " + bestOption.cards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); + return CardUtil.toTArray(bestOption.cards); } + // 5. 兜底策略 return fallbackDecision(handCards); } + + /** 全面手牌评估 */ + private static HandEvaluation evaluateHandComprehensively(List handCards, HandAnalysis analysis) { + HandEvaluation eval = new HandEvaluation(); + eval.handCards = handCards; + eval.analysis = analysis; + + // 评估各种维度 + eval.comboPotential = evaluateComboPotential(handCards); + eval.sequencePotential = evaluateSequencePotential(handCards); + eval.flexibility = evaluateHandFlexibility(handCards); + eval.controlStrength = evaluateControlStrength(handCards); + eval.riskLevel = evaluateRiskLevel(handCards); + + // 综合评分 + eval.overallScore = (eval.comboPotential * 0.3 + + eval.sequencePotential * 0.25 + + eval.flexibility * 0.2 + + eval.controlStrength * 0.15 + + (1 - eval.riskLevel) * 0.1); + + System.out.println("[手牌评估] 组合潜力:" + String.format("%.2f", eval.comboPotential) + + " | 序列潜力:" + String.format("%.2f", eval.sequencePotential) + + " | 灵活性:" + String.format("%.2f", eval.flexibility) + + " | 控制力:" + String.format("%.2f", eval.controlStrength) + + " | 风险:" + String.format("%.2f", eval.riskLevel) + + " | 综合分:" + String.format("%.2f", eval.overallScore)); + + return eval; + } + + /** 生成出牌选项 */ + private static List generatePlayOptions(List handCards, HandEvaluation evaluation) { + List options = new ArrayList<>(); + + // 1. 组合牌型选项 + addComboOptions(options, handCards, evaluation); + + // 2. 序列牌型选项 + addSequenceOptions(options, handCards, evaluation); + + // 3. 单牌选项 + addSingleCardOptions(options, handCards, evaluation); + + // 4. 保守选项 + addConservativeOptions(options, handCards, evaluation); + + System.out.println("[选项生成] 共生成" + options.size() + "个出牌选项"); + return options; + } + + /** 选择最优选项 */ + private static PlayOption selectOptimalOption(List options, HandEvaluation evaluation) { + if (options.isEmpty()) return null; + + // 根据预期价值排序 + return options.stream() + .max(Comparator.comparingDouble(option -> option.expectedValue)) + .orElse(null); + } + + /** 手牌评估类 */ + private static class HandEvaluation { + List handCards; + HandAnalysis analysis; + double comboPotential; // 组合潜力 + double sequencePotential; // 序列潜力 + double flexibility; // 灵活性 + double controlStrength; // 控制力 + double riskLevel; // 风险等级 + double overallScore; // 综合评分 + } + + /** 出牌选项类 */ + private static class PlayOption { + String strategyName; // 策略名称 + List cards; // 出牌 + double expectedValue; // 预期价值 + double riskCost; // 风险成本 + String rationale; // 选择理由 + + PlayOption(String name, List cards, double value, double risk, String rationale) { + this.strategyName = name; + this.cards = cards; + this.expectedValue = value; + this.riskCost = risk; + this.rationale = rationale; + } + } + + // ====================== 评估函数 ====================== + + /** 评估组合潜力 */ + private static double evaluateComboPotential(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + + double potential = 0.0; + + // 三张潜力 + long trios = groups.values().stream().filter(list -> list.size() >= 3).count(); + potential += trios * 0.3; + + // 对子潜力 + long pairs = groups.values().stream().filter(list -> list.size() == 2).count(); + potential += pairs * 0.2; + + // 炸弹潜力 + long bombs = groups.values().stream().filter(list -> list.size() >= 4).count(); + potential += bombs * 0.4; + + return Math.min(potential, 1.0); + } + + /** 评估序列潜力 */ + private static double evaluateSequencePotential(List handCards) { + List values = handCards.stream() + .map(c -> c.cardMod) + .distinct() + .sorted() + .collect(Collectors.toList()); + + int maxLength = 0; + int currentLength = 1; + + for (int i = 1; i < values.size(); i++) { + if (values.get(i) == values.get(i-1) + 1) { + currentLength++; + maxLength = Math.max(maxLength, currentLength); + } else { + currentLength = 1; + } + } + + // 顺子潜力 + double straightPotential = maxLength >= 5 ? Math.min((maxLength - 4) * 0.2, 0.6) : 0; + + // 连对潜力 + Map> groups = CardUtil.getCardListMap(handCards); + int consecutivePairs = 0; + int currentPairs = 0; + + for (int i = 3; i <= 14; i++) { + if (groups.getOrDefault(i, new ArrayList<>()).size() >= 2) { + currentPairs++; + consecutivePairs = Math.max(consecutivePairs, currentPairs); + } else { + currentPairs = 0; + } + } + + double pairPotential = consecutivePairs >= 2 ? Math.min((consecutivePairs - 1) * 0.15, 0.4) : 0; + + return Math.min(straightPotential + pairPotential, 1.0); + } + + /** 评估手牌灵活性 */ + private static double evaluateHandFlexibility(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + + // 牌型多样性 + long comboTypes = groups.values().stream() + .collect(Collectors.groupingBy(List::size)) + .size(); + + double diversityScore = Math.min(comboTypes * 0.2, 0.6); + + // 中等牌比例 + long mediumCards = handCards.stream() + .mapToLong(c -> c.cardMod >= 6 && c.cardMod <= 10 ? 1 : 0) + .sum(); + double mediumRatio = (double) mediumCards / handCards.size(); + + return Math.min(diversityScore + mediumRatio * 0.4, 1.0); + } + + /** 评估控制强度 */ + private static double evaluateControlStrength(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + + // 大牌控制力 + long bigCards = handCards.stream() + .mapToLong(c -> c.cardMod >= CARD_J ? 1 : 0) + .sum(); + double bigCardRatio = (double) bigCards / handCards.size(); + + // 炸弹威慑力 + long bombs = groups.values().stream().filter(list -> list.size() >= 4).count(); + double bombPower = bombs * 0.3; + + // 三张控制力 + long trios = groups.values().stream().filter(list -> list.size() >= 3).count(); + double trioPower = trios * 0.2; + + return Math.min(bigCardRatio * 0.5 + bombPower + trioPower, 1.0); + } + + /** 评估风险等级 */ + private static double evaluateRiskLevel(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + + // 孤立单牌风险 + long singleCards = groups.values().stream().filter(list -> list.size() == 1).count(); + double singleRisk = (double) singleCards / handCards.size(); + + // 大牌风险 + long bigSingles = handCards.stream() + .filter(c -> groups.get(c.cardMod).size() == 1) + .mapToLong(c -> c.cardMod >= CARD_Q ? 1 : 0) + .sum(); + double bigRisk = (double) bigSingles / handCards.size(); + + return Math.min(singleRisk * 0.7 + bigRisk * 0.3, 1.0); + } + + // ====================== 选项生成函数 ====================== + + /** 添加组合牌型选项 */ + private static void addComboOptions(List options, List handCards, HandEvaluation evaluation) { + // 飞机 + List airplane = findSmallestAirplaneWithCards(handCards); + if (!airplane.isEmpty()) { + double value = 0.8 + evaluation.comboPotential * 0.2; + double risk = 0.2; + options.add(new PlayOption("飞机出牌", airplane, value, risk, "高价值组合牌型")); + } + + // 三带二 + List trioWithTwo = findSmallestTrioWithTwo(handCards); + if (!trioWithTwo.isEmpty()) { + double value = 0.7 + evaluation.comboPotential * 0.15; + double risk = 0.15; + options.add(new PlayOption("三带二", trioWithTwo, value, risk, "中等价值组合")); + } + + // 炸弹 + List bomb = findSmallestBomb(handCards); + if (!bomb.isEmpty()) { + double value = 0.9; + double risk = 0.1; + options.add(new PlayOption("炸弹", bomb, value, risk, "最强威慑力")); + } + + // 对子 + List pair = findSmallestPair(handCards); + if (!pair.isEmpty()) { + double value = 0.4 + evaluation.comboPotential * 0.1; + double risk = 0.1; + options.add(new PlayOption("对子", pair, value, risk, "基础组合牌型")); + } + } + + /** 添加序列牌型选项 */ + private static void addSequenceOptions(List options, List handCards, HandEvaluation evaluation) { + // 顺子 + List straight = findBestStraightAdaptive(handCards); + if (!straight.isEmpty()) { + double value = 0.6 + evaluation.sequencePotential * 0.2; + double risk = 0.25; + options.add(new PlayOption("顺子", straight, value, risk, "流畅性好的序列")); + } + + // 连对 + List consecutivePair = findBestConsecutivePairAdaptive(handCards); + if (!consecutivePair.isEmpty()) { + double value = 0.5 + evaluation.sequencePotential * 0.15; + double risk = 0.2; + options.add(new PlayOption("连对", consecutivePair, value, risk, "连续对子结构")); + } + } + + /** 添加单牌选项 */ + private static void addSingleCardOptions(List options, List handCards, HandEvaluation evaluation) { + List singleCards = handCards.stream() + .filter(c -> CardUtil.getCardNumMap(handCards).get(c.cardMod) == 1) + .sorted(Comparator.comparingInt(CardObj::getCardMod)) + .collect(Collectors.toList()); + + if (!singleCards.isEmpty()) { + CardObj smallest = singleCards.get(0); + double value = 0.2; + double risk = 0.4 + evaluation.riskLevel * 0.3; + options.add(new PlayOption("最小单牌", Arrays.asList(smallest), value, risk, "消耗孤立小牌")); + + if (singleCards.size() > 1) { + CardObj largest = singleCards.get(singleCards.size() - 1); + if (largest.cardMod != smallest.cardMod) { + double largeValue = 0.3 + evaluation.controlStrength * 0.2; + double largeRisk = 0.3; + options.add(new PlayOption("最大单牌", Arrays.asList(largest), largeValue, largeRisk, "展示控制力")); + } + } + } + } + + /** 添加保守选项 */ + private static void addConservativeOptions(List options, List handCards, HandEvaluation evaluation) { + // 保守的小牌组合 + List smallCombo = findSmallConservativeCombo(handCards); + if (!smallCombo.isEmpty()) { + double value = 0.4; + double risk = 0.1; + options.add(new PlayOption("保守组合", smallCombo, value, risk, "稳健的出牌选择")); + } + } + + /** 自适应寻找最佳顺子 */ + private static List findBestStraightAdaptive(List handCards) { + List> allStraights = findAllStraights(handCards, 5); + if (allStraights.isEmpty()) return new ArrayList<>(); + + return allStraights.stream() + .max(Comparator.comparingInt(straight -> { + int length = straight.size(); + int minValue = straight.stream().mapToInt(c -> c.cardMod).min().orElse(0); + // 优先长顺子,但避免过大 + return length >= 7 ? 100 - minValue : length * 10 - minValue; + })) + .orElse(new ArrayList<>()); + } + + /** 自适应寻找最佳连对 */ + private static List findBestConsecutivePairAdaptive(List handCards) { + List> allPairs = findAllConsecutivePairs(handCards, 2); + if (allPairs.isEmpty()) return new ArrayList<>(); + + return allPairs.stream() + .max(Comparator.comparingInt(pair -> { + int pairCount = pair.size() / 2; + int minValue = pair.stream().mapToInt(c -> c.cardMod).min().orElse(0); + return pairCount * 5 - minValue; + })) + .orElse(new ArrayList<>()); + } + + /** 寻找保守的小牌组合 */ + private static List findSmallConservativeCombo(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + + // 寻找小对子 + return groups.entrySet().stream() + .filter(e -> e.getValue().size() == 2 && e.getKey() <= CARD_8) + .min(Map.Entry.comparingByKey()) + .map(e -> e.getValue()) + .orElse(new ArrayList<>()); + } + + /** 判断出某张牌是否会破坏组合结构 - 智能化版本(优化版) */ + private static boolean wouldBreakComboStructure(CardObj card, List handCards) { + int cardValue = card.cardMod; + + // 获取出手牌后剩余的手牌 + List remainingCards = new ArrayList<>(handCards); + remainingCards.remove(card); + + // 分析剩余手牌的组合潜力 + ComboPotential potential = analyzeComboPotential(remainingCards); + + // 如果剩余手牌仍有良好的组合结构,则可以安全出这张牌 + if (potential.hasGoodStructure()) { + System.out.println("[组合安全] 出" + getCardName(cardValue) + "后剩余手牌仍保持良好结构"); + return false; + } + + // 检查具体的破坏情况 + boolean affectsStraight = wouldAffectStraightFormation(cardValue, handCards); + boolean affectsConsecutivePair = wouldAffectConsecutivePairFormation(cardValue, handCards); + + if (affectsStraight || affectsConsecutivePair) { + System.out.println("[组合风险] 出" + getCardName(cardValue) + "会影响组合结构"); + return true; + } + + return false; + } + + /** 检查是否会影响顺子形成(改进版) */ + private static boolean wouldAffectStraightFormation(int cardValue, List handCards) { + // 检查该牌是否是有效顺子形成的关键牌 + boolean hasPrev = handCards.stream().anyMatch(c -> c.cardMod == cardValue - 1); + boolean hasNext = handCards.stream().anyMatch(c -> c.cardMod == cardValue + 1); + boolean hasPrev2 = handCards.stream().anyMatch(c -> c.cardMod == cardValue - 2); + boolean hasNext2 = handCards.stream().anyMatch(c -> c.cardMod == cardValue + 2); + + // 新增:检查是否真的能形成有效顺子(至少5张连续牌) + if (isValidStraightPossible(handCards, cardValue)) { + // 只有在真正能形成有效顺子时才认为是关键牌 + if ((hasPrev && hasNext) || (hasPrev && hasPrev2) || (hasNext && hasNext2)) { + System.out.println("[顺子保护] 牌" + getCardName(cardValue) + "是有效顺子的关键牌"); + return true; + } + } else { + System.out.println("[顺子判断] 牌" + getCardName(cardValue) + "不在有效顺子中,可安全出"); + } + + return false; + } + + /** 检查是否会影响连对形成(改进版) */ + private static boolean wouldAffectConsecutivePairFormation(int cardValue, List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + + // 如果该牌值有对子,且相邻牌值也有对子的可能性 + if (groups.get(cardValue) != null && groups.get(cardValue).size() >= 2) { + boolean prevHasPair = groups.getOrDefault(cardValue - 1, new ArrayList<>()).size() >= 2; + boolean nextHasPair = groups.getOrDefault(cardValue + 1, new ArrayList<>()).size() >= 2; + + // 新增:检查是否真的能形成长连对 + if (isValidConsecutivePairPossible(handCards, cardValue)) { + if (prevHasPair || nextHasPair) { + System.out.println("[连对保护] 牌" + getCardName(cardValue) + "是连对的关键牌"); + return true; + } + } else { + System.out.println("[连对判断] 牌" + getCardName(cardValue) + "不在有效连对中,可安全出"); + } + } + + return false; + } + + /** 组合潜力分析类 */ + private static class ComboPotential { + public int straightPotential = 0; // 顺子潜力 + public int pairPotential = 0; // 对子潜力 + public int trioPotential = 0; // 三张潜力 + public int consecutivePairPotential = 0; // 连对潜力 + public int airplanePotential = 0; // 飞机潜力 + public boolean hasGoodStructure = false; // 是否有良好结构 + + public boolean hasGoodStructure() { + // 判断是否有良好的组合结构 + return straightPotential >= 5 || + consecutivePairPotential >= 3 || + airplanePotential >= 1 || + (trioPotential >= 2 && pairPotential >= 2); + } + + @Override + public String toString() { + return String.format("组合潜力[顺子:%d, 连对:%d, 飞机:%d, 三张:%d, 对子:%d]", + straightPotential, consecutivePairPotential, + airplanePotential, trioPotential, pairPotential); + } + } + + /** 分析手牌的组合潜力 */ + private static ComboPotential analyzeComboPotential(List cards) { + ComboPotential potential = new ComboPotential(); + Map> groups = CardUtil.getCardListMap(cards); + + // 计算各种牌型的潜力 + potential.straightPotential = calculateStraightPotential(groups); + potential.pairPotential = calculatePairPotential(groups); + potential.trioPotential = calculateTrioPotential(groups); + potential.consecutivePairPotential = calculateConsecutivePairPotential(groups); + potential.airplanePotential = calculateAirplanePotential(groups); + + return potential; + } + + /** 判断移除某张牌后是否仍能形成有效顺子 */ + private static boolean isValidStraightPossible(List handCards, int removedCardValue) { + // 获取去除指定牌值后的手牌 + List filteredCards = handCards.stream() + .filter(c -> c.cardMod != removedCardValue) + .collect(Collectors.toList()); + + // 检查是否还能形成至少5张的有效顺子 + List values = filteredCards.stream() + .map(c -> c.cardMod) + .distinct() + .sorted() + .collect(Collectors.toList()); + + int maxLength = 0; + int currentLength = 1; + + for (int i = 1; i < values.size(); i++) { + if (values.get(i) == values.get(i-1) + 1) { + currentLength++; + maxLength = Math.max(maxLength, currentLength); + } else { + currentLength = 1; + } + } + + boolean result = maxLength >= 5; + System.out.println("[顺子验证] 移除" + getCardName(removedCardValue) + "后最长顺子:" + maxLength + "张, 有效:" + result); + return result; + } + + /** 计算顺子潜力 */ + private static int calculateStraightPotential(Map> groups) { + int maxLength = 0; + int currentLength = 0; + + for (int i = 3; i <= 14; i++) { // 3到A + if (groups.containsKey(i) && groups.get(i).size() >= 1) { + currentLength++; + maxLength = Math.max(maxLength, currentLength); + } else { + currentLength = 0; + } + } + + return maxLength; + } + + /** 计算对子潜力 */ + private static int calculatePairPotential(Map> groups) { + return (int) groups.values().stream() + .filter(list -> list.size() >= 2) + .count(); + } + + /** 计算三张潜力 */ + private static int calculateTrioPotential(Map> groups) { + return (int) groups.values().stream() + .filter(list -> list.size() >= 3) + .count(); + } + + /** 判断移除某张牌后是否仍能形成有效连对 */ + private static boolean isValidConsecutivePairPossible(List handCards, int removedCardValue) { + // 获取去除指定牌值后的手牌 + List filteredCards = handCards.stream() + .filter(c -> c.cardMod != removedCardValue) + .collect(Collectors.toList()); + + Map> groups = CardUtil.getCardListMap(filteredCards); + + // 检查是否还能形成至少2连对(4张牌) + int maxConsecutive = 0; + int currentConsecutive = 0; + + for (int i = 3; i <= 14; i++) { + if (groups.containsKey(i) && groups.get(i).size() >= 2) { + currentConsecutive++; + maxConsecutive = Math.max(maxConsecutive, currentConsecutive); + } else { + currentConsecutive = 0; + } + } + + boolean result = maxConsecutive >= 2; + System.out.println("[连对验证] 移除" + getCardName(removedCardValue) + "后最长连对:" + maxConsecutive + "对, 有效:" + result); + return result; + } + + /** 计算连对潜力 */ + private static int calculateConsecutivePairPotential(Map> groups) { + int maxConsecutive = 0; + int currentConsecutive = 0; + + for (int i = 3; i <= 14; i++) { + if (groups.containsKey(i) && groups.get(i).size() >= 2) { + currentConsecutive++; + maxConsecutive = Math.max(maxConsecutive, currentConsecutive); + } else { + currentConsecutive = 0; + } + } + + return maxConsecutive; + } + + /** 计算飞机潜力 */ + private static int calculateAirplanePotential(Map> groups) { + int maxConsecutive = 0; + int currentConsecutive = 0; + + for (int i = 3; i <= 14; i++) { + if (groups.containsKey(i) && groups.get(i).size() >= 3) { + currentConsecutive++; + maxConsecutive = Math.max(maxConsecutive, currentConsecutive); + } else { + currentConsecutive = 0; + } + } + + return maxConsecutive >= 2 ? maxConsecutive : 0; // 至少需要2个连续的三张才算飞机 + } // ====================== 辅助方法 ====================== @@ -651,7 +1412,7 @@ public class test_smart { // 3. 兜底 return emergencyOutCard(handCards); } - + /** 残局最佳组合牌选择 */ private static List findBestEndgameCombo(List handCards) { System.out.println("[残局分析] 寻找可用的组合牌型"); @@ -659,7 +1420,7 @@ public class test_smart { List bestCombo = new ArrayList<>(); int bestScore = 0; - // 评估各种组合牌型的价值(炸弹不再是最高优先级) + // 评估各种组合牌型的价值(炸弹和四带二优先级降低) // 1. 飞机带牌 List airplane = findSmallestAirplaneWithCards(handCards); @@ -669,15 +1430,7 @@ public class test_smart { System.out.println("[残局评估] 发现飞机,价值: " + airplane.size()); } - // 2. 四带二 - List fourWithTwo = findSmallestFourWithTwo(handCards); - if (!fourWithTwo.isEmpty() && fourWithTwo.size() > bestScore) { - bestCombo = fourWithTwo; - bestScore = fourWithTwo.size(); - System.out.println("[残局评估] 发现四带二,价值: " + fourWithTwo.size()); - } - - // 3. 三带二 + // 2. 三带二 List trioWithTwo = findSmallestTrioWithTwo(handCards); if (!trioWithTwo.isEmpty() && trioWithTwo.size() > bestScore) { bestCombo = trioWithTwo; @@ -685,7 +1438,7 @@ public class test_smart { System.out.println("[残局评估] 发现三带二,价值: " + trioWithTwo.size()); } - // 4. 连对 + // 3. 连对 List consecutivePair = findSmallestConsecutivePair(handCards); if (!consecutivePair.isEmpty() && consecutivePair.size() > bestScore) { bestCombo = consecutivePair; @@ -693,7 +1446,7 @@ public class test_smart { System.out.println("[残局评估] 发现连对,价值: " + consecutivePair.size()); } - // 5. 顺子 + // 4. 顺子 List straight = findSmallestStraight(handCards); if (!straight.isEmpty() && straight.size() > bestScore) { bestCombo = straight; @@ -701,7 +1454,7 @@ public class test_smart { System.out.println("[残局评估] 发现顺子,价值: " + straight.size()); } - // 6. 三张 + // 5. 三张 List trio = findSmallestTrio(handCards); if (!trio.isEmpty() && trio.size() > bestScore) { bestCombo = trio; @@ -709,7 +1462,7 @@ public class test_smart { System.out.println("[残局评估] 发现三张,价值: " + trio.size()); } - // 7. 对子 + // 6. 对子 List pair = findSmallestPair(handCards); if (!pair.isEmpty() && pair.size() > bestScore) { bestCombo = pair; @@ -717,13 +1470,19 @@ public class test_smart { System.out.println("[残局评估] 发现对子,价值: " + pair.size()); } + // 7. 四带二(仅在完全没有其他组合时考虑) + List fourWithTwo = findSmallestFourWithTwo(handCards); + if (!fourWithTwo.isEmpty() && bestScore == 0) { // 仅在无其他组合时才考虑 + System.out.println("[残局评估] 无其他组合牌型,考虑使用四带二"); + return fourWithTwo; + } + // 8. 炸弹(仅在完全没有其他组合时考虑) List bomb = findSmallestBomb(handCards); if (!bomb.isEmpty() && bestScore == 0) { // 仅在无其他组合时才考虑 System.out.println("[残局评估] 无其他组合牌型,考虑使用炸弹"); return bomb; } - if (bestScore > 0) { System.out.println("[残局决策] 选择最佳组合: " + bestCombo.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); @@ -734,87 +1493,6 @@ public class test_smart { return bestCombo; } - /** 首轮组合牌型优先决策 */ - private static List findBestComboMove(List handCards, HandAnalysis analysis) { - System.out.println("[组合优先] 分析可出的组合牌型"); - - List bestCombo = new ArrayList<>(); - int bestScore = 0; - - // 按价值优先级评估组合牌型(炸弹优先级降低) - - // 1. 飞机带牌(最高优先级) - List airplane = findSmallestAirplaneWithCards(handCards); - if (!airplane.isEmpty()) { - System.out.println("[组合优先] 发现飞机: " + airplane.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); - return airplane; - } - - // 2. 四带二 - List fourWithTwo = findSmallestFourWithTwo(handCards); - if (!fourWithTwo.isEmpty() && fourWithTwo.size() > bestScore) { - bestCombo = fourWithTwo; - bestScore = fourWithTwo.size(); - System.out.println("[组合优先] 发现四带二: " + fourWithTwo.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); - } - - // 3. 三带二 - List trioWithTwo = findSmallestTrioWithTwo(handCards); - if (!trioWithTwo.isEmpty() && trioWithTwo.size() > bestScore) { - bestCombo = trioWithTwo; - bestScore = trioWithTwo.size(); - System.out.println("[组合优先] 发现三带二: " + trioWithTwo.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); - } - - // 4. 连对 - List consecutivePair = findSmallestConsecutivePair(handCards); - if (!consecutivePair.isEmpty() && consecutivePair.size() > bestScore) { - bestCombo = consecutivePair; - bestScore = consecutivePair.size(); - System.out.println("[组合优先] 发现连对: " + consecutivePair.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); - } - - // 5. 顺子 - List straight = findSmallestStraight(handCards); - if (!straight.isEmpty() && straight.size() > bestScore) { - bestCombo = straight; - bestScore = straight.size(); - System.out.println("[组合优先] 发现顺子: " + straight.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); - } - - // 6. 三张 - List trio = findSmallestTrio(handCards); - if (!trio.isEmpty() && trio.size() > bestScore) { - bestCombo = trio; - bestScore = trio.size(); - System.out.println("[组合优先] 发现三张: " + trio.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); - } - - // 7. 对子 - List pair = findSmallestPair(handCards); - if (!pair.isEmpty() && pair.size() > bestScore) { - bestCombo = pair; - bestScore = pair.size(); - System.out.println("[组合优先] 发现对子: " + pair.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); - } - - // 8. 炸弹(最低优先级,仅在没有其他组合时考虑) - List bomb = findSmallestBomb(handCards); - if (!bomb.isEmpty() && bestScore == 0) { // 只有在没有任何其他组合时才考虑炸弹 - System.out.println("[组合优先] 无其他组合牌型,考虑使用炸弹"); - return bomb; - } - - - 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("[兜底策略] 无合适组合,智能选择最小损失"); @@ -1081,65 +1759,6 @@ public class test_smart { 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) { @@ -1218,110 +1837,7 @@ public class test_smart { 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) { @@ -1340,69 +1856,6 @@ public class test_smart { 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; - } // ====================== 基础工具方法 ====================== @@ -1423,7 +1876,7 @@ public class test_smart { .anyMatch(e -> !e.getValue().isEmpty() && e.getValue().get(e.getValue().size() - 1) == 1); } - private static ITArray handleOpponentEndgameResponse(List handCards, int type, int minCard) { + private static ITArray handleOpponentEndgameResponse(List handCards, int type, int minCard, List opponentCards) { System.out.println("[残局响应] 分析是否可以用组合牌压制"); // 1. 首先尝试用组合牌型压制 @@ -1443,7 +1896,7 @@ public class test_smart { } // 3. 其他牌型按正常逻辑处理 - List response = findMatchingResponse(handCards, type, minCard, 0); + List response = findMatchingResponse(handCards, type, minCard, 0, opponentCards); if (!response.isEmpty()) { return CardUtil.toTArray(response); } @@ -1467,6 +1920,101 @@ public class test_smart { return new TArray(); } + /** 对手剩余两张牌的试探响应策略 */ + private static ITArray handleOpponentTwoCardsResponse(List handCards, int type, int minCard, List opponentCards) { + System.out.println("[试探策略] 分析是否需要试探对手手牌构成"); + + // 检查机器人是否持有大单牌(Q及以上) + boolean hasBigSingleCards = hasBigSingleCards(handCards); + System.out.println("[试探策略] 是否持有大单牌(Q及以上): " + hasBigSingleCards); + + // 如果机器人准备出小对子(小于J),则采用试探策略 + if (type == TYPE_PAIR && minCard < CARD_J) { + List smallPair = findSmallPair(handCards, CARD_J - 1); + if (!smallPair.isEmpty() && hasBigSingleCards) { + System.out.println("[试探策略] 检测到小对子响应且持有大单牌,启动试探模式"); + return handleProbeStrategy(handCards, smallPair); + } + } + + // 其他情况按正常逻辑处理 + List response = findMatchingResponse(handCards, type, minCard, 0, opponentCards); + if (!response.isEmpty()) { + return CardUtil.toTArray(response); + } + + // 最后使用炸弹 + List bomb = findSmallestBomb(handCards); + if (!bomb.isEmpty()) { + System.out.println("[试探策略] 使用炸弹"); + return CardUtil.toTArray(bomb); + } + + return new TArray(); + } + + /** 检查是否持有大单牌(Q及以上)*/ + private static boolean hasBigSingleCards(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + + // 查找Q及以上且是单牌的牌 + for (Map.Entry> entry : groups.entrySet()) { + int value = entry.getKey(); + List cards = entry.getValue(); + + if (value >= CARD_Q && cards.size() == 1) { + return true; + } + } + return false; + } + + /** 试探策略核心逻辑 */ + private static ITArray handleProbeStrategy(List handCards, List smallPair) { + System.out.println("[试探策略] 执行试探性出牌策略"); + + // 方案1:先出一张中等单牌进行试探(8-K范围内) + CardObj probeCard = findProbeSingleCard(handCards); + if (probeCard != null) { + System.out.println("[试探策略] 出试探单牌: " + getCardName(probeCard.cardMod) + ",观察对手反应"); + return CardUtil.toTArray1(probeCard); + } + + // 方案2:如果没有合适的试探单牌,则直接出小对子(风险较高) + System.out.println("[试探策略] 无合适试探单牌,直接出小对子: " + + smallPair.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); + return CardUtil.toTArray(smallPair); + } + + /** 寻找用于试探的单牌(8-K范围)*/ + private static CardObj findProbeSingleCard(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + + // 寻找8-K范围内的单牌 + List candidates = new ArrayList<>(); + + for (Map.Entry> entry : groups.entrySet()) { + int value = entry.getKey(); + List cards = entry.getValue(); + + // 必须是单牌且在8-K范围内 + if (cards.size() == 1 && value >= CARD_8 && value <= CARD_K) { + candidates.add(cards.get(0)); + } + } + + if (!candidates.isEmpty()) { + // 优先选择较小的试探牌 + candidates.sort(Comparator.comparingInt(CardObj::getCardMod)); + CardObj selected = candidates.get(0); + System.out.println("[试探牌选择] 在" + candidates.size() + "个候选手牌中选择: " + getCardName(selected.cardMod)); + return selected; + } + + System.out.println("[试探牌选择] 未找到8-K范围内的单牌"); + return null; + } + /** 残局组合牌压制 */ private static List findEndgameComboResponse(List handCards, int targetType, int minCard) { System.out.println("[残局压制] 寻找可以压制的组合牌型"); @@ -1531,27 +2079,6 @@ public class test_smart { 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); @@ -1574,7 +2101,7 @@ public class test_smart { return CardUtil.toTArray1(minCard); } - private static List findMatchingResponse(List handCards, int type, int minCard, int len) { + private static List findMatchingResponse(List handCards, int type, int minCard, int len, List opponentCards) { System.out.println("[智能响应] 收到出牌请求 - 报告类型:" + getCardTypeName(type) + ", 最小牌:" + getCardName(minCard) + ", 长度:" + len); // 智能纠错:根据牌数和常见模式推断真实牌型 @@ -1585,9 +2112,8 @@ public class test_smart { } // 直接分析实际牌面构成,这才是真正的智能! - List actualCards = getLastPlayedCards(); - if (actualCards != null && !actualCards.isEmpty()) { - int actualType = analyzeActualCardType(actualCards); + if (opponentCards != null && !opponentCards.isEmpty()) { + int actualType = analyzeActualCardType(opponentCards); System.out.println("[智能分析] 实际牌面分析结果 - 真实类型:" + getCardTypeName(actualType)); // 优先信任我们自己的分析结果 @@ -1595,6 +2121,8 @@ public class test_smart { System.out.println("[智能纠错] 检测到服务器报告类型与实际不符,采用智能分析结果"); type = actualType; } + } else { + System.out.println("[智能分析] 无法获取实际出牌信息,依赖模式纠错"); } switch (type) { @@ -1624,12 +2152,38 @@ public class test_smart { } private static List findSingleResponse(List handCards, int minCard) { - CardObj playable = CardUtil.findMinBiggerSingleCard(handCards, minCard); + System.out.println("[单牌响应] 寻找大于" + getCardName(minCard) + "的单牌"); - if (playable != null) { - return Collections.singletonList(playable); + // 首先查找真正的单牌(手中只有一张的牌) + List trueSingleCards = handCards.stream() + .filter(c -> CardUtil.getCardNumMap(handCards).get(c.cardMod) == 1) // 确保是单牌 + .filter(c -> c.cardMod > minCard) // 必须大于对手的牌 + .sorted(Comparator.comparingInt(CardObj::getCardMod)) + .collect(Collectors.toList()); + + if (!trueSingleCards.isEmpty()) { + // 有真正的单牌,使用智能选择策略 + CardObj smartChoice = findSmartSingleCard(handCards, minCard); + if (smartChoice != null) { + System.out.println("[单牌响应] 智能选择单牌: " + getCardName(smartChoice.cardMod)); + return Collections.singletonList(smartChoice); + } + // 兜底选择最小单牌 + CardObj minCardObj = trueSingleCards.get(0); + System.out.println("[单牌响应] 兜底选择最小单牌: " + getCardName(minCardObj.cardMod)); + return Collections.singletonList(minCardObj); } + // 没有真正的单牌,必须拆组合牌 + System.out.println("[单牌响应] 无真正单牌,需要拆组合牌"); + CardObj breakChoice = findBestCardToBreak(handCards, minCard); + + if (breakChoice != null) { + System.out.println("[单牌响应] 拆组合牌选择: " + getCardName(breakChoice.cardMod)); + return Collections.singletonList(breakChoice); + } + + System.out.println("[单牌响应] 无合适单牌可出"); return new ArrayList<>(); } @@ -1658,13 +2212,24 @@ public class test_smart { // 修正:len表示连对的数量,不是总牌数 // 但是要考虑服务器可能传递的是总牌数的情况 int pairCount; - if (len >= 4 && len % 2 == 0) { + + // 智能推断连对数量 + if (len == 0) { + // 服务器传0,说明是2连对(最常见的连对) + pairCount = 2; + System.out.println("[连对响应] 服务器传len=0,推断为2连对"); + } else if (len >= 4 && len % 2 == 0) { // 如果len是偶数且>=4,可能是总牌数,需要转换为连对数 pairCount = len / 2; // 4张牌=2连对,6张牌=3连对 System.out.println("[连对响应] 检测到总牌数" + len + ",转换为" + pairCount + "连对"); + } else if (len >= MIN_CONSECUTIVE_PAIR_COUNT && len <= 6) { + // 标准的连对数(2-6连对) + pairCount = len; + System.out.println("[连对响应] 直接使用连对数" + len); } else { - // 否则是标准的连对数 - pairCount = len; // len=2表示2连对 + // 默认推断为2连对 + pairCount = 2; + System.out.println("[连对响应] 无法识别len=" + len + ",默认推断为2连对"); } System.out.println("[连对响应] 寻找" + pairCount + "连对(共" + (pairCount*2) + "张牌),最小起始牌:" + getCardName(minCard)); @@ -1770,37 +2335,81 @@ public class test_smart { Map> groups = CardUtil.getCardListMap(handCards); List carryCandidates = new ArrayList<>(); - // 1. 优先选择单牌(除了三张本身) - List singleCards = handCards.stream() - .filter(c -> c.cardMod != trioValue) // 排除三张牌 + // 获取可用于携带的牌(排除三张牌) + List availableCards = handCards.stream() + .filter(c -> c.cardMod != trioValue) + .collect(Collectors.toList()); + + // 1. 优先选择不会破坏组合结构的安全单牌 + List safeSingleCards = availableCards.stream() .filter(c -> groups.get(c.cardMod).size() == 1) // 只要单牌 + .filter(c -> !wouldBreakComboStructure(c, availableCards)) // 避免破坏组合 .sorted(Comparator.comparingInt(CardObj::getCardMod)) .collect(Collectors.toList()); - System.out.println("[智能携带] 可选单牌: " + singleCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); + System.out.println("[智能携带] 安全单牌: " + safeSingleCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); - if (singleCards.size() >= 2) { - // 选择两张单牌带走 - carryCandidates.addAll(singleCards.subList(0, 2)); - System.out.println("[智能携带] 选择带走2张单牌,优化手牌结构"); + if (safeSingleCards.size() >= 2) { + // 选择两张安全单牌带走 + carryCandidates.addAll(safeSingleCards.subList(0, 2)); + System.out.println("[智能携带] 选择带走2张安全单牌,保护组合结构"); return carryCandidates; } - // 2. 如果单牌不够,选择最小的牌 - List otherCards = handCards.stream() - .filter(c -> c.cardMod != trioValue) + // 2. 考虑特殊情况:如果剩余牌型结构良好,可以考虑带2 + List twoCards = availableCards.stream() + .filter(c -> c.cardMod == CARD_2) + .collect(Collectors.toList()); + + if (!twoCards.isEmpty() && wouldAllowCarryingTwoSpecial(availableCards)) { + System.out.println("[智能携带] 特殊情况允许带2"); + carryCandidates.addAll(twoCards); + // 补充另一张安全牌 + List otherSafeCards = availableCards.stream() + .filter(c -> c.cardMod != CARD_2) + .filter(c -> !wouldBreakComboStructure(c, availableCards)) + .sorted(Comparator.comparingInt(CardObj::getCardMod)) + .collect(Collectors.toList()); + + if (!otherSafeCards.isEmpty()) { + carryCandidates.add(otherSafeCards.get(0)); + System.out.println("[智能携带] 带2方案成功: " + + carryCandidates.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); + return carryCandidates; + } + } + + // 3. 最后的备选方案:选择最小的牌 + List otherCards = availableCards.stream() .sorted(Comparator.comparingInt(CardObj::getCardMod)) .collect(Collectors.toList()); if (otherCards.size() >= 2) { carryCandidates.addAll(otherCards.subList(0, 2)); - System.out.println("[智能携带] 选择带走2张最小牌"); + System.out.println("[智能携带] 备选方案-选择带走2张最小牌"); return carryCandidates; } System.out.println("[智能携带] 无可选携带牌"); return new ArrayList<>(); } + + /** 特殊情况判断是否允许带2 */ + private static boolean wouldAllowCarryingTwoSpecial(List availableCards) { + // 移除2后分析剩余牌的组合潜力 + List withoutTwos = availableCards.stream() + .filter(card -> card.cardMod != CARD_2) + .collect(Collectors.toList()); + + ComboPotential potential = analyzeComboPotential(withoutTwos); + + // 如果移除2后仍有良好的组合结构,则允许带2 + boolean hasGoodStructure = potential.hasGoodStructure(); + + System.out.println("[带2特殊判断] 移除2后剩余牌潜力: " + potential + ", 结构良好: " + hasGoodStructure); + + return hasGoodStructure; + } private static List findBombResponse(List handCards, int minCard) { System.out.println("[炸弹响应] 寻找大于" + getCardName(minCard) + "的炸弹"); @@ -1831,16 +2440,101 @@ public class test_smart { return result; } + /** + * 智能响应飞机带牌 + * 支持多种带牌方式的飞机,根据实际牌型灵活应对 + */ private static List findAirplaneWithCardsResponse(List handCards, int minCard, int len) { - int trioCount = len / 6; - if (trioCount < MIN_AIRPLANE_TRIO_COUNT) return new ArrayList<>(); - + System.out.println("[飞机响应] 寻找能压制" + getCardName(minCard) + "的飞机,总牌数:" + len); + + // 计算需要的三张组数 + int trioCount = calculateRequiredTrioCount(len); + if (trioCount < MIN_AIRPLANE_TRIO_COUNT) { + System.out.println("[飞机响应] 三张组数" + trioCount + "小于最小要求" + MIN_AIRPLANE_TRIO_COUNT); + return new ArrayList<>(); + } + + System.out.println("[飞机响应] 需要" + trioCount + "组连续三张"); + + // 查找所有可能的飞机 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<>()); + System.out.println("[飞机响应] 找到" + allAirplanes.size() + "个可行的飞机"); + + // 筛选能压制的飞机 + List> validAirplanes = allAirplanes.stream() + .filter(airplane -> { + // 提取三张部分的最小值 + Map countMap = airplane.stream() + .collect(Collectors.groupingBy(CardObj::getCardMod, Collectors.counting())); + int minTrioValue = countMap.entrySet().stream() + .filter(e -> e.getValue() >= 3) + .mapToInt(Map.Entry::getKey) + .min() + .orElse(0); + return minTrioValue > minCard; + }) + .sorted((a1, a2) -> { + // 优先选择三张部分较小的飞机(更节约) + Map countMap1 = a1.stream() + .collect(Collectors.groupingBy(CardObj::getCardMod, Collectors.counting())); + Map countMap2 = a2.stream() + .collect(Collectors.groupingBy(CardObj::getCardMod, Collectors.counting())); + + int min1 = countMap1.entrySet().stream() + .filter(e -> e.getValue() >= 3) + .mapToInt(Map.Entry::getKey) + .min() + .orElse(0); + int min2 = countMap2.entrySet().stream() + .filter(e -> e.getValue() >= 3) + .mapToInt(Map.Entry::getKey) + .min() + .orElse(0); + + return Integer.compare(min1, min2); + }) + .collect(Collectors.toList()); + + if (!validAirplanes.isEmpty()) { + List chosenAirplane = validAirplanes.get(0); + System.out.println("[飞机响应] 选择出牌: " + + chosenAirplane.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); + return chosenAirplane; + } + + System.out.println("[飞机响应] 无合适飞机可出"); + return new ArrayList<>(); + } + + /** + * 根据总牌数计算需要的三张组数 + * 跑得快规则:飞机带牌 = 三张组数 × (3 + 带牌系数) + * 带牌系数通常为2(带两对)或1(带两张单牌) + */ + private static int calculateRequiredTrioCount(int totalLen) { + // 常见的飞机带牌格式: + // 2连飞机带2对:2×(3+2) = 10张 + // 2连飞机带2单:2×(3+1) = 8张 + // 3连飞机带3对:3×(3+2) = 15张 + // 3连飞机带3单:3×(3+1) = 12张 + + if (totalLen == 10) return 2; // 2连飞机带2对 + if (totalLen == 8) return 2; // 2连飞机带2单 + if (totalLen == 15) return 3; // 3连飞机带3对 + if (totalLen == 12) return 3; // 3连飞机带3单 + if (totalLen == 20) return 4; // 4连飞机 + + // 通用计算:假设带牌系数为2(带对子) + if (totalLen % 5 == 0) { + return totalLen / 5; // 3+2格式 + } + + // 假设带牌系数为1(带单牌) + if (totalLen % 4 == 0) { + return totalLen / 4; // 3+1格式 + } + + return 0; // 无法识别的格式 } private static List findFourWithTwoResponse(List handCards, int minCard) { @@ -1920,17 +2614,22 @@ public class test_smart { List> straights = new ArrayList<>(); if (len < MIN_STRAIGHT_LENGTH) return straights; + // 湖南跑得快规则:2(CARD_2)不参与顺子主体构成,但可以作为带牌 List distinctValues = handCards.stream() .map(CardObj::getCardMod) + .filter(value -> value != CARD_2) // 顺子主体不能包含2 .distinct() .sorted() .collect(Collectors.toList()); + System.out.println("[顺子查找] 顺子主体可用牌值(已排除2): " + distinctValues.stream().map(v -> getCardName(v)).collect(Collectors.joining(","))); + 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; + System.out.println("[顺子查找] 序列中断: " + getCardName(distinctValues.get(i + j)) + " -> " + getCardName(distinctValues.get(i + j + 1))); break; } } @@ -1941,8 +2640,11 @@ public class test_smart { straight.add(CardUtil.getCard(value, handCards)); } straights.add(straight); + System.out.println("[顺子查找] 找到顺子: " + straight.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); } } + + System.out.println("[顺子查找] 总共找到" + straights.size() + "个顺子"); return straights; } @@ -1971,11 +2673,12 @@ public class test_smart { Map> groups = CardUtil.getCardListMap(handCards); List pairValues = groups.entrySet().stream() .filter(e -> e.getValue().size() >= 2) + .filter(e -> e.getKey() != CARD_2) // 连对主体不能包含2 .map(Map.Entry::getKey) .sorted() .collect(Collectors.toList()); - System.out.println("[连对查找] 可用对子牌值: " + pairValues.stream().map(v -> getCardName(v)).collect(Collectors.joining(","))); + System.out.println("[连对查找] 连对主体可用牌值(已排除2): " + pairValues.stream().map(v -> getCardName(v)).collect(Collectors.joining(","))); System.out.println("[连对查找] 寻找" + pairCount + "连对"); for (int i = 0; i <= pairValues.size() - pairCount; i++) { @@ -2003,6 +2706,7 @@ public class test_smart { } } + System.out.println("[连对查找] 总共找到" + consecutivePairs.size() + "个连对"); return consecutivePairs; } @@ -2084,6 +2788,11 @@ public class test_smart { return new ArrayList<>(); } + /** + * 智能查找所有可能的飞机带牌组合 + * 支持多种带牌方式:对子、单牌、混合带牌 + * 优化目标:最大化剩余手牌的价值和灵活性 + */ private static List> findAllAirplanesWithCards(List handCards, int trioCount) { List> airplanes = new ArrayList<>(); if (trioCount < MIN_AIRPLANE_TRIO_COUNT) return airplanes; @@ -2091,10 +2800,14 @@ public class test_smart { Map> groups = CardUtil.getCardListMap(handCards); List trioValues = groups.entrySet().stream() .filter(e -> e.getValue().size() >= 3) + .filter(e -> e.getKey() != CARD_2) // 飞机主体(三张序列)不能包含2 .map(Map.Entry::getKey) .sorted() .collect(Collectors.toList()); + + System.out.println("[飞机查找] 飞机主体可用三张牌值(已排除2): " + trioValues.stream().map(v -> getCardName(v)).collect(Collectors.joining(","))); + // 查找所有连续的三张序列 for (int i = 0; i <= trioValues.size() - trioCount; i++) { boolean isConsecutive = true; for (int j = 0; j < trioCount - 1; j++) { @@ -2103,37 +2816,202 @@ public class test_smart { break; } } + if (isConsecutive) { - List airplane = new ArrayList<>(); + // 构建基础飞机(三张部分) + List baseAirplane = 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)); + baseAirplane.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); + + // 智能选择带牌策略 + List> carryOptions = findOptimalCarryCards(handCards, usedValues, trioCount * 2); + + // 为每种带牌方案生成完整的飞机 + for (List carryCards : carryOptions) { + List completeAirplane = new ArrayList<>(baseAirplane); + completeAirplane.addAll(carryCards); + airplanes.add(completeAirplane); } } } return airplanes; } + + /** + * 智能选择最优的带牌组合 + * 支持:对子、单牌、混合带牌 + * 策略:优先保持剩余手牌的完整性 + */ + private static List> findOptimalCarryCards(List handCards, Set usedValues, int requiredCarryCount) { + List> options = new ArrayList<>(); + Map> groups = CardUtil.getCardListMap(handCards); + + // 获取可用的牌(排除已用于飞机的三张) + List availableCards = handCards.stream() + .filter(card -> !usedValues.contains(card.cardMod)) + .collect(Collectors.toList()); + + System.out.println("[飞机带牌] 可用于带牌的牌: " + + availableCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); + + // 策略1:优先带对子(保持牌型完整性) + List pairCarry = findBestPairCarry(groups, usedValues, requiredCarryCount); + if (!pairCarry.isEmpty()) { + options.add(pairCarry); + System.out.println("[飞机带牌] 策略1-对子带牌: " + + pairCarry.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); + } + + // 策略2:带单牌(消耗孤立单牌) + List singleCarry = findBestSingleCarry(availableCards, requiredCarryCount); + if (!singleCarry.isEmpty()) { + options.add(singleCarry); + System.out.println("[飞机带牌] 策略2-单牌带牌: " + + singleCarry.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); + } + + // 策略3:混合带牌(对子+单牌) + List mixedCarry = findBestMixedCarry(groups, availableCards, usedValues, requiredCarryCount); + if (!mixedCarry.isEmpty()) { + options.add(mixedCarry); + System.out.println("[飞机带牌] 策略3-混合带牌: " + + mixedCarry.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); + } + + return options; + } + + /** 查找最优对子带牌方案 */ + private static List findBestPairCarry(Map> groups, Set usedValues, int requiredCount) { + List carryCards = new ArrayList<>(); + List>> pairCandidates = groups.entrySet().stream() + .filter(e -> !usedValues.contains(e.getKey()) && e.getValue().size() >= 2) + .sorted(Map.Entry.comparingByKey()) + .collect(Collectors.toList()); + + int pairsNeeded = requiredCount / 2; + if (pairCandidates.size() >= pairsNeeded) { + for (int i = 0; i < pairsNeeded; i++) { + carryCards.addAll(pairCandidates.get(i).getValue().subList(0, 2)); + } + } + return carryCards; + } + + /** 查找最优单牌带牌方案 - 智能保护组合结构 */ + private static List findBestSingleCarry(List availableCards, int requiredCount) { + System.out.println("[飞机单牌带牌] 分析安全单牌选择"); + + // 优先选择不会破坏组合结构的安全单牌 + List safeSingleCards = availableCards.stream() + .filter(card -> { + Map> tempGroups = CardUtil.getCardListMap(availableCards); + return tempGroups.get(card.cardMod).size() == 1; // 确保是单牌 + }) + .filter(card -> !wouldBreakComboStructure(card, availableCards)) // 避免破坏组合 + .sorted(Comparator.comparingInt(CardObj::getCardMod)) + .collect(Collectors.toList()); + + System.out.println("[飞机单牌带牌] 安全单牌: " + + safeSingleCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); + + if (safeSingleCards.size() >= requiredCount) { + List selected = safeSingleCards.subList(0, requiredCount); + System.out.println("[飞机单牌带牌] 选择安全单牌: " + + selected.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); + return selected; + } + + // 如果没有足够的安全单牌,考虑带2(但要非常谨慎) + List twoCards = availableCards.stream() + .filter(card -> card.cardMod == CARD_2) + .collect(Collectors.toList()); + + if (!twoCards.isEmpty() && wouldAllowCarryingTwo(availableCards, requiredCount)) { + System.out.println("[飞机单牌带牌] 特殊情况:允许带2"); + List result = new ArrayList<>(); + result.addAll(twoCards); + // 补充其他安全单牌 + int remainingNeeded = requiredCount - twoCards.size(); + if (remainingNeeded > 0) { + List otherSingles = safeSingleCards.stream() + .filter(card -> card.cardMod != CARD_2) + .limit(remainingNeeded) + .collect(Collectors.toList()); + result.addAll(otherSingles); + } + if (result.size() == requiredCount) { + System.out.println("[飞机单牌带牌] 带2方案成功: " + + result.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(""))); + return result; + } + } + + System.out.println("[飞机单牌带牌] 无足够安全单牌"); + return new ArrayList<>(); + } + + /** 判断是否允许带2 - 更加灵活的判断 */ + private static boolean wouldAllowCarryingTwo(List availableCards, int requiredCount) { + // 移除2后分析剩余牌的组合潜力 + List withoutTwos = availableCards.stream() + .filter(card -> card.cardMod != CARD_2) + .collect(Collectors.toList()); + + ComboPotential potential = analyzeComboPotential(withoutTwos); + + // 如果移除2后仍有良好的组合结构,则允许带2 + boolean hasGoodStructure = potential.hasGoodStructure(); + + System.out.println("[带2判断] 移除2后剩余牌潜力: " + potential + ", 结构良好: " + hasGoodStructure); + + return hasGoodStructure; + } - 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 List findBestMixedCarry(Map> groups, + List availableCards, Set usedValues, int requiredCount) { + List carryCards = new ArrayList<>(); + + // 先拿对子 + List>> pairCandidates = groups.entrySet().stream() + .filter(e -> !usedValues.contains(e.getKey()) && e.getValue().size() >= 2) + .sorted(Map.Entry.comparingByKey()) + .collect(Collectors.toList()); + + // 拿尽可能多的对子 + int pairsTaken = 0; + for (Map.Entry> entry : pairCandidates) { + if (pairsTaken * 2 + 2 <= requiredCount) { + carryCards.addAll(entry.getValue().subList(0, 2)); + pairsTaken++; + } else { + break; + } + } + + // 补充单牌 + int remainingNeeded = requiredCount - carryCards.size(); + if (remainingNeeded > 0) { + List singleCards = availableCards.stream() + .filter(card -> !carryCards.contains(card)) + .filter(card -> { + Map> tempGroups = CardUtil.getCardListMap(availableCards); + return tempGroups.get(card.cardMod).size() == 1; + }) + .sorted(Comparator.comparingInt(CardObj::getCardMod)) + .collect(Collectors.toList()); + + if (singleCards.size() >= remainingNeeded) { + carryCards.addAll(singleCards.subList(0, remainingNeeded)); + } + } + + return carryCards.size() == requiredCount ? carryCards : new ArrayList<>(); } private static int findMaxStraightLength(List handCards) { @@ -2219,6 +3097,31 @@ public class test_smart { } } + /** 检查是否有对手只剩两张牌 */ + private static boolean checkIfAnyOpponentHasTwoCards(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 == 2) { + System.out.println("[对手检测] 检测到对手(座位" + seat + ")只剩两张牌,启动试探策略"); + return true; + } + } + } + return false; + } + private static boolean checkIfAnyOpponentHasOneCard(Map> seatRemainHistory, int currentSeat) { if (seatRemainHistory == null || seatRemainHistory.isEmpty()) { return false; @@ -2241,6 +3144,125 @@ public class test_smart { } return false; } + + /** 检查自身是否处于残局状态(手中牌较少)*/ + private static boolean checkIfSelfInEndgame(List handCards) { + if (handCards == null || handCards.isEmpty()) return false; + + // 当手中牌数少于等于4张时认为进入残局 + if (handCards.size() <= 4) { + System.out.println("[自我残局检测] 手中剩余牌数: " + handCards.size() + ",进入残局模式"); + return true; + } + + // 或者当手中大部分都是单牌时也认为是残局 + Map> groups = CardUtil.getCardListMap(handCards); + long singleCardCount = groups.values().stream().filter(list -> list.size() == 1).count(); + double singleCardRatio = (double) singleCardCount / handCards.size(); + + if (singleCardRatio >= 0.7) { // 70%以上都是单牌 + System.out.println("[自我残局检测] 单牌比例: " + String.format("%.1f", singleCardRatio*100) + "%,进入残局模式"); + return true; + } + + return false; + } + + /** 自身残局处理策略 - 关键改进!*/ + private static ITArray handleSelfEndgameSituation(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. 如果没有组合牌型,寻找最小的有效出牌 + List minimalMove = findMinimalEffectiveMove(handCards); + if (!minimalMove.isEmpty()) { + System.out.println("[自我残局] 出最小有效牌组: " + minimalMove.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); + return CardUtil.toTArray(minimalMove); + } + + // 3. 最后兜底 + System.out.println("[自我残局] 无有效出牌选择,紧急处理"); + return emergencyOutCard(handCards); + } + + /** 寻找最小有效出牌(避免拆分有价值组合)*/ + private static List findMinimalEffectiveMove(List handCards) { + System.out.println("[最小有效出牌] 寻找不会破坏牌型结构的最小出牌"); + + // 1. 寻找最小的对子 + List smallestPair = findSmallestPair(handCards); + if (!smallestPair.isEmpty()) { + System.out.println("[最小出牌] 发现最小对子"); + return smallestPair; + } + + // 2. 寻找最小的三张 + List smallestTrio = findSmallestTrio(handCards); + if (!smallestTrio.isEmpty()) { + System.out.println("[最小出牌] 发现最小三张"); + return smallestTrio; + } + + // 3. 寻找最小的单牌(但要避免拆分即将成型的组合) + CardObj smallestSingle = findSafeSingleCard(handCards); + if (smallestSingle != null) { + System.out.println("[最小出牌] 出最小安全单牌: " + getCardName(smallestSingle.cardMod)); + return Collections.singletonList(smallestSingle); + } + + System.out.println("[最小出牌] 无安全出牌选择"); + return new ArrayList<>(); + } + + /** 寻找安全的单牌(避免拆分有价值的组合)*/ + private static CardObj findSafeSingleCard(List handCards) { + // 先统计牌型分布 + Map> groups = CardUtil.getCardListMap(handCards); + List singleCards = new ArrayList<>(); + + // 收集所有单牌 + for (List group : groups.values()) { + if (group.size() == 1) { + singleCards.add(group.get(0)); + } + } + + if (singleCards.isEmpty()) { + return null; + } + + // 优先选择最小的单牌 + singleCards.sort(Comparator.comparingInt(CardObj::getCardMod)); + + // 检查这张牌是否可能参与形成更好的组合 + CardObj candidate = singleCards.get(0); + + // 如果这张牌太小(3-5),且手牌较多,可以安全出 + if (candidate.cardMod <= CARD_5 && handCards.size() > 3) { + return candidate; + } + + // 如果手牌很少(<=3张),优先出最小的 + if (handCards.size() <= 3) { + return candidate; + } + + // 其他情况下,选择相对较小但不太影响后续组合的牌 + for (CardObj card : singleCards) { + if (card.cardMod <= CARD_8) { // 限制在8以下 + return card; + } + } + + // 如果都没有合适的,就出最小的 + return candidate; + } private static String getCardName(int cardMod) { switch (cardMod) { @@ -2277,14 +3299,10 @@ public class test_smart { } } - /** 获取上家实际出牌(需要从游戏状态中获取)*/ - private static List getLastPlayedCards() { - // TODO: 从游戏状态中获取实际出牌信息 - // 这里暂时返回null,实际应用中应该从card_list中解析 - return null; - } - - /** 智能牌型纠错 - 根据常见模式和牌数推断正确牌型 */ + /** + * 智能牌型纠错 - 根据常见模式和牌数推断正确牌型 + * 修复跑得快游戏中常见的牌型识别错误 + */ private static int correctCardTypeByPattern(int reportedType, int len, int minCard) { System.out.println("[模式纠错] 分析牌型 - 报告类型:" + getCardTypeName(reportedType) + ", 牌数:" + len + ", 最小牌:" + getCardName(minCard)); @@ -2311,93 +3329,46 @@ public class test_smart { } } - // 典型错误情况4:顺子被错误报告 + // 典型错误情况4:5-12张牌优先判断为顺子而非三带二 if (len >= 5 && len <= 12) { - // 5-12张连续牌很可能是顺子 + // 关键修复:5张顺子经常被错误识别为三带二 + if (reportedType == TYPE_TRIO_WITH_TWO) { + System.out.println("[模式纠错] ⚠️ " + len + "张牌被报告为三带二,更可能是顺子"); + System.out.println("[模式纠错] 建议:将类型从三带二纠正为顺子"); + return TYPE_STRAIGHT; + } + + // 其他连续牌情况 if (reportedType == TYPE_SINGLE || reportedType == TYPE_PAIR) { System.out.println("[模式纠错] " + len + "张连续牌更可能是顺子"); return TYPE_STRAIGHT; } } + // 典型错误情况5:连对与炸弹混淆(特别是3344这种情况) + if (len == 4) { + // 4张牌时,强烈倾向于连对而非炸弹 + if (reportedType == TYPE_BOMB) { + System.out.println("[模式纠错] ⚠️ 4张牌被报告为炸弹,极可能是连对错误识别"); + System.out.println("[模式纠错] 建议:将类型从炸弹纠正为连对"); + return TYPE_CONSECUTIVE_PAIR; + } + // 如果已经是连对,保持不变 + if (reportedType == TYPE_CONSECUTIVE_PAIR) { + System.out.println("[模式纠错] 4张牌正确识别为连对"); + return reportedType; + } + } + // 如果没有发现明显错误,返回原始类型 System.out.println("[模式纠错] 未发现明显错误,保持原始类型"); return reportedType; } - + /** - * 保守策略判断是否应该使用炸弹 - * 炸弹使用条件(严格按照跑得快规则): - * 1. 对手只剩一张牌 - * 2. 对手出2 - * 3. 自身手牌≤3张 - * 4. 对手出A且手牌≤6张 - * 5. 其他极端情况下的最后手段 + * 智能分析实际牌型 - 真正的AI核心! + * 修复各种牌型识别错误,包括连对vs炸弹、顺子vs三带二等 */ - private static boolean shouldUseBombConservatively( - List handCards, - int opponentType, - int opponentMinCard, - Map> seatRemainHistory, - HuNanPaoDeKuai huNanPaoDeKuai) { - - System.out.println("[炸弹策略] 分析是否满足炸弹使用条件"); - - // 条件1:对手只剩一张牌 - if (checkIfAnyOpponentHasOneCard(seatRemainHistory, huNanPaoDeKuai != null ? huNanPaoDeKuai.seat : 0)) { - System.out.println("[炸弹策略] ✓ 对手只剩一张牌,使用炸弹"); - return true; - } - - // 条件2:对手出2 - if (opponentMinCard == CARD_2) { - System.out.println("[炸弹策略] ✓ 对手出2,使用炸弹"); - return true; - } - - // 条件3:自身手牌≤3张 - if (handCards.size() <= 3) { - System.out.println("[炸弹策略] ✓ 手牌很少(" + handCards.size() + "张),使用炸弹"); - return true; - } - - // 条件4:对手出A且手牌≤6张 - if (opponentMinCard == CARD_A && handCards.size() <= 6) { - System.out.println("[炸弹策略] ✓ 对手出A且手牌较少(" + handCards.size() + "张),使用炸弹"); - return true; - } - - // 条件5:其他极端情况(最后手段) - // 比如对手出大牌且自己即将输掉 - if (isExtremeLosingSituation(handCards, opponentType, opponentMinCard)) { - System.out.println("[炸弹策略] ✓ 极端劣势局面,使用炸弹"); - return true; - } - - System.out.println("[炸弹策略] ✗ 不满足炸弹使用条件,保留炸弹"); - return false; - } - - /** 判断是否处于极端劣势局面 */ - private static boolean isExtremeLosingSituation(List handCards, int opponentType, int opponentMinCard) { - // 如果对手出的是大牌(Q及以上)且我们没有更好的应对方式 - if (opponentMinCard >= CARD_Q) { - // 检查是否还有其他组合牌型 - HandAnalysis analysis = analyzeHandComposition(handCards); - int comboTypes = (analysis.pairCount > 0 ? 1 : 0) + - (analysis.trioCount > 0 ? 1 : 0) + - (analysis.straightCount > 0 ? 1 : 0) + - (analysis.consecutivePairCount > 0 ? 1 : 0); - - // 如果几乎没有组合牌型,且对手出大牌,可以考虑使用炸弹 - if (comboTypes <= 1 && handCards.size() <= 8) { - return true; - } - } - return false; - } - - /** 智能分析实际牌型 - 真正的AI核心! */ private static int analyzeActualCardType(List cards) { if (cards == null || cards.isEmpty()) return TYPE_SINGLE; @@ -2432,14 +3403,28 @@ public class test_smart { return TYPE_TRIO; } - // 炸弹 - if (cardCount == 4 && uniqueValues.size() == 1 && countMap.get(uniqueValues.get(0)) == 4) { - System.out.println("[牌型分析] 结论: 炸弹"); - return TYPE_BOMB; + // 炸弹 vs 连对的关键判断(4张牌) + if (cardCount == 4) { + if (uniqueValues.size() == 1 && countMap.get(uniqueValues.get(0)) == 4) { + // 真正的炸弹:四张相同 + System.out.println("[牌型分析] 结论: 炸弹 (四张" + getCardName(uniqueValues.get(0)) + ")"); + return TYPE_BOMB; + } else if (uniqueValues.size() == 2 && + countMap.get(uniqueValues.get(0)) == 2 && + countMap.get(uniqueValues.get(1)) == 2 && + uniqueValues.get(1) - uniqueValues.get(0) == 1) { + // 连对:两个连续的对子 + System.out.println("[牌型分析] 结论: 连对 (" + getCardName(uniqueValues.get(0)) + getCardName(uniqueValues.get(0)) + + getCardName(uniqueValues.get(1)) + getCardName(uniqueValues.get(1)) + ")"); + return TYPE_CONSECUTIVE_PAIR; + } else { + System.out.println("[牌型分析] 结论: 4张牌无法识别,按单牌处理"); + return TYPE_SINGLE; + } } - // 连对:检查是否全是对子且数值连续 - if (cardCount >= 4 && cardCount % 2 == 0) { + // 连对:检查是否全是对子且数值连续(6张及以上) + if (cardCount >= 6 && cardCount % 2 == 0) { boolean allPairs = uniqueValues.stream().allMatch(v -> countMap.get(v) == 2); boolean consecutive = isConsecutive(uniqueValues); @@ -2450,13 +3435,17 @@ public class test_smart { } } - // 顺子:检查是否无重复且连续 + // 顺子:检查是否无重复且连续(5张及以上) if (cardCount >= 5) { boolean noDuplicates = uniqueValues.size() == cardCount; boolean consecutive = isConsecutive(uniqueValues); if (noDuplicates && consecutive) { - System.out.println("[牌型分析] 结论: 顺子 (" + cardCount + "张)"); + StringBuilder sb = new StringBuilder(); + for (Integer val : uniqueValues) { + sb.append(getCardName(val)); + } + System.out.println("[牌型分析] 结论: 顺子 (" + cardCount + "张: " + sb.toString() + ")"); return TYPE_STRAIGHT; } } @@ -2464,16 +3453,43 @@ public class test_smart { // 三带二 if (cardCount == 5) { boolean hasThree = uniqueValues.stream().anyMatch(v -> countMap.get(v) == 3); - boolean hasTwo = uniqueValues.stream().anyMatch(v -> countMap.get(v) == 2); + // 三带二有两种情况:三带一对,或者三带两张单牌 + boolean hasValidCarry = uniqueValues.size() == 3; // 总共3个不同的牌值 - if (hasThree && hasTwo) { + if (hasThree && hasValidCarry) { System.out.println("[牌型分析] 结论: 三带二"); return TYPE_TRIO_WITH_TWO; } + + // 特殊情况:5张连续牌也可能是顺子 + boolean noDuplicates = uniqueValues.size() == cardCount; + boolean consecutive = isConsecutive(uniqueValues); + if (noDuplicates && consecutive) { + System.out.println("[牌型分析] 结论: 顺子 (5张连续牌)"); + return TYPE_STRAIGHT; + } + } + + // 四带二系列 + if (cardCount == 6) { + boolean hasFour = uniqueValues.stream().anyMatch(v -> countMap.get(v) == 4); + if (hasFour) { + System.out.println("[牌型分析] 结论: 四带二"); + return TYPE_FOUR_WITH_TWO; + } + } + + if (cardCount == 8) { + boolean hasFour = uniqueValues.stream().anyMatch(v -> countMap.get(v) == 4); + long pairCount = uniqueValues.stream().filter(v -> countMap.get(v) == 2).count(); + if (hasFour && pairCount >= 2) { + System.out.println("[牌型分析] 结论: 四带二对"); + return TYPE_FOUR_WITH_TWO_PAIR; + } } // 默认返回单牌 - System.out.println("[牌型分析] 结论: 无法识别,按单牌处理"); + System.out.println("[牌型分析] 结论: 无法精确识别,按单牌处理"); return TYPE_SINGLE; } @@ -2489,6 +3505,241 @@ public class test_smart { return true; } + /** 潜在结构分析 - 评估拆分某张牌对顺子形成的影响 */ + private static PotentialImpact analyzePotentialImpact(List handCards, CardObj targetCard) { + // 1. 移除目标牌后重新分析手牌 + List remainingCards = new ArrayList<>(handCards); + remainingCards.remove(targetCard); + + // 2. 全面评估移除前后的牌型结构变化 + ComprehensiveImpact impact = evaluateComprehensiveStructuralImpact(handCards, remainingCards); + + // 3. 综合评分:破坏顺子数×100 + 破坏连对数×50 + 牌面值 + int impactScore = impact.destroyedStraights * 100 + impact.destroyedPairs * 50 + targetCard.cardMod; + + System.out.println("[影响分析] 出" + getCardName(targetCard.cardMod) + + "会破坏" + impact.destroyedStraights + "个顺子, " + + impact.destroyedPairs + "个连对"); + + return new PotentialImpact(targetCard, impactScore, impact.destroyedStraights > 0, impact.destroyedPairs > 0); + } + + /** 全面评估结构性影响 */ + private static ComprehensiveImpact evaluateComprehensiveStructuralImpact(List originalCards, List remainingCards) { + // 分析原始手牌的组合结构 + List> originalStraights = findAllStraights(originalCards, 5); + List> originalPairs = findAllConsecutivePairs(originalCards, 2); + + // 分析剩余手牌的组合结构 + List> remainingStraights = findAllStraights(remainingCards, 5); + List> remainingPairs = findAllConsecutivePairs(remainingCards, 2); + + // 计算被破坏的结构数量 + int destroyedStraights = Math.max(0, originalStraights.size() - remainingStraights.size()); + int destroyedPairs = Math.max(0, originalPairs.size() - remainingPairs.size()); + + return new ComprehensiveImpact(destroyedStraights, destroyedPairs); + } + + /** 综合影响评估类 */ + private static class ComprehensiveImpact { + int destroyedStraights; + int destroyedPairs; + + ComprehensiveImpact(int destroyedStraights, int destroyedPairs) { + this.destroyedStraights = destroyedStraights; + this.destroyedPairs = destroyedPairs; + } + } + + /** 潜在影响评估类 */ + private static class PotentialImpact { + CardObj card; + int impactScore; // 影响分数,正值表示负面影响 + boolean breaksStraight; + boolean breaksPairs; + + PotentialImpact(CardObj card, int impactScore, boolean breaksStraight, boolean breaksPairs) { + this.card = card; + this.impactScore = impactScore; + this.breaksStraight = breaksStraight; + this.breaksPairs = breaksPairs; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(getCardName(card.cardMod)).append("(影响度:").append(impactScore); + if (breaksStraight) sb.append("[破坏顺子]"); + if (breaksPairs) sb.append("[破坏连对]"); + sb.append(")"); + return sb.toString(); + } + } + + /** 智能单牌选择 - 考虑组合结构保护 */ + private static CardObj findSmartSingleCard(List handCards, int minCard) { + List singleCards = handCards.stream() + .filter(c -> CardUtil.getCardNumMap(handCards).get(c.cardMod) == 1) + .filter(c -> c.cardMod > minCard) + .sorted(Comparator.comparingInt(CardObj::getCardMod)) + .collect(Collectors.toList()); + + if (singleCards.isEmpty()) return null; + + System.out.println("[智能单牌] 可选单牌: " + singleCards.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); + + // 分析每个单牌的潜在影响 + List impacts = new ArrayList<>(); + for (CardObj card : singleCards) { + PotentialImpact impact = analyzePotentialImpact(handCards, card); + impacts.add(impact); + System.out.println("[智能分析] " + impact.toString()); + } + + // 简化排序:优先选择影响最小的单牌 + PotentialImpact bestChoice = impacts.stream() + .min(Comparator.comparingInt(i -> i.impactScore)) + .orElse(null); + + if (bestChoice != null) { + System.out.println("[智能决策] 选择影响最小的单牌: " + getCardName(bestChoice.card.cardMod) + + " (影响度:" + bestChoice.impactScore + ")"); + return bestChoice.card; + } + + // 兜底选择 + CardObj minCardObj = singleCards.get(0); + System.out.println("[智能决策] 兜底选择最小单牌: " + getCardName(minCardObj.cardMod)); + return minCardObj; + } + + /** 智能拆牌选择 - 当必须拆组合牌时的最优选择 */ + private static CardObj findBestCardToBreak(List handCards, int minCard) { + System.out.println("[拆牌策略] 分析可拆组合牌的选择"); + + // 收集所有大于minCard的牌(包括组合中的牌) + List candidates = handCards.stream() + .filter(c -> c.cardMod > minCard) + .sorted(Comparator.comparingInt(CardObj::getCardMod)) + .collect(Collectors.toList()); + + if (candidates.isEmpty()) { + System.out.println("[拆牌策略] 无可拆之牌"); + return null; + } + + System.out.println("[拆牌策略] 可选拆牌: " + candidates.stream().map(c -> getCardName(c.cardMod)).collect(Collectors.joining(","))); + + // 分析每张牌拆掉后的手牌质量 + List breakImpacts = new ArrayList<>(); + for (CardObj card : candidates) { + BreakImpact impact = analyzeBreakImpact(handCards, card); + breakImpacts.add(impact); + System.out.println("[拆牌分析] " + impact.toString()); + } + + // 选择拆牌代价最小的牌 + BreakImpact bestBreak = breakImpacts.stream() + .min(Comparator.comparingDouble(b -> b.costScore)) + .orElse(null); + + if (bestBreak != null) { + System.out.println("[拆牌决策] 选择最优拆牌: " + getCardName(bestBreak.card.cardMod) + + " (代价分:" + String.format("%.2f", bestBreak.costScore) + ")"); + return bestBreak.card; + } + + // 兜底选择最小的可拆牌 + CardObj minBreakCard = candidates.get(0); + System.out.println("[拆牌决策] 兜底选择最小可拆牌: " + getCardName(minBreakCard.cardMod)); + return minBreakCard; + } + + /** 拆牌影响分析类 */ + private static class BreakImpact { + CardObj card; + double costScore; // 拆牌代价分(越小越好) + int remainingCombos; // 拆牌后剩余组合数 + int remainingSingles; // 拆牌后剩余单牌数 + boolean preservesBestCombo; // 是否保留了最好的组合 + + BreakImpact(CardObj card, double costScore, int remainingCombos, int remainingSingles, boolean preservesBestCombo) { + this.card = card; + this.costScore = costScore; + this.remainingCombos = remainingCombos; + this.remainingSingles = remainingSingles; + this.preservesBestCombo = preservesBestCombo; + } + + @Override + public String toString() { + return getCardName(card.cardMod) + "(代价:" + String.format("%.2f", costScore) + + ", 组合:" + remainingCombos + ", 单牌:" + remainingSingles + ")"; + } + } + + /** 分析拆某张牌的代价 */ + private static BreakImpact analyzeBreakImpact(List handCards, CardObj targetCard) { + // 模拟拆牌后的情况 + List remainingCards = new ArrayList<>(handCards); + remainingCards.remove(targetCard); + + // 分析剩余手牌结构 + HandAnalysis analysis = analyzeHandComposition(remainingCards); + int originalComboCount = countTotalCombos(handCards); + int remainingComboCount = countTotalCombos(remainingCards); + + // 计算代价分 + double costScore = 0.0; + + // 1. 组合损失惩罚(主要因素) + int comboLoss = originalComboCount - remainingComboCount; + costScore += comboLoss * 10.0; + + // 2. 牌值惩罚(避免拆大牌) + costScore += targetCard.cardMod * 0.5; + + // 3. 单牌增加惩罚 + long originalSingles = CardUtil.getCardNumMap(handCards).entrySet().stream() + .filter(e -> e.getValue() == 1).count(); + int singleIncrease = analysis.singleCount - (int)originalSingles; + costScore += singleIncrease * 2.0; + + // 4. 保留最佳组合奖励 + boolean preservesBest = preservesBestCombination(handCards, remainingCards); + if (preservesBest) costScore -= 3.0; + + return new BreakImpact(targetCard, costScore, remainingComboCount, + analysis.singleCount, preservesBest); + } + + /** 计算手牌中总的组合数 */ + private static int countTotalCombos(List handCards) { + Map> groups = CardUtil.getCardListMap(handCards); + int combos = 0; + + // 对子 + combos += groups.values().stream().filter(list -> list.size() == 2).count(); + // 三张 + combos += groups.values().stream().filter(list -> list.size() == 3).count(); + // 炸弹 + combos += groups.values().stream().filter(list -> list.size() >= 4).count(); + + // 顺子和连对 + combos += findAllStraights(handCards, 5).size(); + combos += findAllConsecutivePairs(handCards, 2).size(); + + return (int) combos; + } + + /** 判断是否保留了最好的组合 */ + private static boolean preservesBestCombination(List original, List remaining) { + // 简化判断:如果剩余手牌中仍有高质量组合则认为保留了最好组合 + HandAnalysis remainingAnalysis = analyzeHandComposition(remaining); + return remainingAnalysis.comboQualityScore > 0.5; + } + /** 调试用手牌分析 */ private static void debugHandAnalysis(List handCards) { System.out.println("\n---------- 手牌详细分析 ----------"); @@ -2550,37 +3801,4 @@ public class test_smart { return typeName + "[" + cardNames + "]"; } - // ====================== 测试方法 ====================== - - /** 测试炸弹使用保守策略 */ - public static void testBombConservativeStrategy() { - System.out.println("\n========== 炸弹使用保守策略测试 =========="); - - // 模拟不同场景 - testBombScenario("对手剩1张牌", 15, TYPE_SINGLE, CARD_7, true); - testBombScenario("对手出2", 10, TYPE_SINGLE, CARD_2, true); - testBombScenario("手牌≤3张", 3, TYPE_PAIR, CARD_8, true); - testBombScenario("对手出A且手牌≤6张", 5, TYPE_SINGLE, CARD_A, true); - testBombScenario("正常情况-不应使用炸弹", 12, TYPE_PAIR, CARD_9, false); - } - - private static void testBombScenario(String scenario, int handSize, int opponentType, int opponentMinCard, boolean shouldUseBomb) { - System.out.println("\n测试场景: " + scenario); - System.out.println("手牌数: " + handSize + ", 对手类型: " + getCardTypeName(opponentType) + ", 最小牌: " + getCardName(opponentMinCard)); - - // 创建模拟手牌 - List mockHand = new ArrayList<>(); - for (int i = 0; i < handSize; i++) { - mockHand.add(new CardObj(CARD_3 + (i % 13))); // 简单模拟 - } - - boolean result = shouldUseBombConservatively(mockHand, opponentType, opponentMinCard, null, null); - System.out.println("策略判断: " + (result ? "使用炸弹" : "保留炸弹")); - - if (result == shouldUseBomb) { - System.out.println("✅ 策略判断正确"); - } else { - System.out.println("❌ 策略判断错误,期望: " + (shouldUseBomb ? "使用" : "保留") + "炸弹"); - } - } } \ No newline at end of file