fengyeserver/libs/robot_common/src/main/java/taurus/util/ChangShaSuanFaTest.java

3734 lines
129 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package taurus.util;
import com.taurus.core.util.Logger;
import hunan.HandAnalysis;
import hunan.HuNanChangSha;
import java.util.*;
import java.util.stream.Collectors;
public class ChangShaSuanFaTest {
public int drawnCards; //摸牌
public static List<Integer> tinCards = new ArrayList<>();
public static boolean isTin = false;
public static boolean isChi =false;
public static boolean isPeng=false;
public static List<Integer> chuguodepai = new ArrayList<>();
// 长沙麻将特殊规则:二五八将
private static final Set<Integer> JIANG_PAIS;
static {
Set<Integer> jiangSet = new HashSet<>();
jiangSet.add(2);
jiangSet.add(5);
jiangSet.add(8);
JIANG_PAIS = Collections.unmodifiableSet(jiangSet);
}
/**
* 统一日志输出方法
*/
private void logInfo(String message) {
// 注释掉日志输出,减少测试时的信息干扰
// System.out.println(message);
}
private final static Logger log;
static {
log = Logger.getLogger(ChangShaSuanFaTest.class);
}
/**
* 主算法入口:在摸牌后、打牌前进行全面分析,确定最优出牌策略
* 核心流程:摸牌分析 -> 听牌检测 -> 策略制定 -> 出牌选择
*/
public String outCardSuanFa(List<Integer> cardInhand, List<Integer> pengCard, List<Integer> chowGroup, List<Integer> resultList) {
List<Integer> handCards = new ArrayList<>(cardInhand);
List<Integer> pinghuhandCards = new ArrayList<>(cardInhand);
chuguodepai.addAll(resultList);
handCards.addAll(chowGroup);
handCards.addAll(pengCard);
handCards.sort(Integer::compareTo);
logInfo("排序后机器人手牌: " + handCards);
int i = countPengGroups(handCards, pengCard); //刻子的数量
int pisCardsCount = countPairs(handCards);//分析七小对
//将将胡
boolean jiangHu = isJiangHu(handCards);
boolean isPengPengHu = hasThreeKeziAndTwoPairs(handCards, pengCard);
//清一色碰碰胡
boolean hasBigSuit = isAllSameSuit(handCards, pengCard); // 分析是否有可能的清一色花色
System.out.println("resultList +++++++++++++++++++++++================" + resultList);
//六对
if (pisCardsCount >= 6 && pengCard.size() == 0 && chowGroup.size() == 0) {
System.out.println("七小对数量大于等于6+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
//单张牌
List<Integer> danzhang = danzhang(handCards);
isTin = true;
Map<Integer, Integer> danzhangCountMap = new HashMap<>();
for (Integer card2 : danzhang) {
danzhangCountMap.put(card2, 0);
tinCards.add(card2);
}
for (Integer card3 : resultList) {
if (danzhangCountMap.containsKey(card3)) {
danzhangCountMap.put(card3, danzhangCountMap.get(card3) + 1);
}
}
// 找到最大的出现次数
Integer maxCount = danzhangCountMap.values().stream()
.max(Integer::compareTo)
.orElse(0);
// 找出出现次数等于最大次数的牌
List<Integer> maxCards = danzhangCountMap.entrySet().stream()
.filter(entry -> entry.getValue().equals(maxCount))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
//102/106
// 找出maxCards中那些在handCards中出现3次的牌
List<Integer> threeCardsFromMax = maxCards.stream()
.filter(card3 -> Collections.frequency(handCards, card3) == 3)
.collect(Collectors.toList());
if (threeCardsFromMax.size() > 0) {
List<Integer> zeroCountCards = threeCardsFromMax.stream()
.filter(card3 -> danzhangCountMap.getOrDefault(card3, 0) == 0)
.collect(Collectors.toList());
System.out.println("出现次数为0的牌: " + zeroCountCards);
// 从maxCards中移除
if (!zeroCountCards.isEmpty()) {
maxCards.removeAll(zeroCountCards);
System.out.println("移除后maxCards: " + maxCards);
}
}
System.out.println("maxCards中出现3次的牌: " + threeCardsFromMax);
if (threeCardsFromMax.size() > 0) {
return String.valueOf(threeCardsFromMax.get(0));
}
Integer maxCard = maxCards.isEmpty() ? null : maxCards.get(0);
System.out.println("maxCard++++++++++++++++++++++++++++++" + maxCard);
return String.valueOf(maxCard);
}
// if (isTin) {
// System.out.println(" ");
// System.out.println("==============听牌牌组-=-==========" + tinCards);
// System.out.println("=========================drawnCards +++++++++++++++++++++++================" + drawnCards);
// List<Integer> tin = new ArrayList<>(tinCards);
// System.out.println(" ");
// System.out.println("---------------tin--------------"+tin);
// ai ai1 = new ai();
//// ChangshaMahjongAI ai = new ChangshaMahjongAI();
// PlayerState playerState = new PlayerState();
// playerState.handCards = pinghuhandCards;
//
//
// for (int j = 0; j < chowGroup.size(); j += 3) {
// List<Integer> chigroup = new ArrayList<>(chowGroup.subList(j, Math.min(j + 3, chowGroup.size())));
// if (chigroup.size() == 3) {
// playerState.addChiGroup(chigroup);
// }
// }
//
//
// for (int h = 0; h < pengCard.size(); h += 3) {
// List<Integer> penggroup = new ArrayList<>(pengCard.subList(h, Math.min(h + 3, pengCard.size())));
// if (penggroup.size() == 3) {
// playerState.addPongGroup(penggroup);
// }
// }
//
//
// int bestDiscard = ai1.findBestDiscard(playerState);
//
//
// if (bestDiscard != 0) {
// if (ai1.isTinAi) {
// isTin = true;
// }
// if (tin.size()<tinCards.size()){
// System.out.println("----------听牌走ai出牌了-----");
// return String.valueOf(bestDiscard);
// }else if (!tinCards.equals(tin)){
// tinCards.addAll(tin);
// }
// }
//
//
//
// String tinOutCard = isTinOutCard(tinCards, resultList);
// System.out.println(" ");
// System.out.println("tinCards-----------" + tinCards);
// System.out.println("tinOutCard-----------" + tinOutCard);
// if (tinOutCard.equals("1")) {
// // 1. 手牌分析 - 识别刻子、顺子、对子等牌型
// HandAnalysis analysis = analyzeHand(handCards);
//
// //最终出的牌
// int outcard = selectCardToDiscard(pinghuhandCards, analysis);
// return String.valueOf(outcard);
// } else {
// return tinOutCard;
// }
//
//
// }
//
// if (isChi){
// System.out.println("-----------吃牌听牌中-------");
// ai ai1 = new ai();
//// ChangshaMahjongAI ai = new ChangshaMahjongAI();
// PlayerState playerState = new PlayerState();
// playerState.handCards = handCards;
// System.out.println("吃牌听牌chowGroup +++++++++++++++++++++++" + chowGroup);
// System.out.println("吃牌听牌pengCard +++++++++++++++++++++++" + pengCard);
//
// for (int j = 0; j < chowGroup.size(); j += 3) {
// List<Integer> chigroup = new ArrayList<>(chowGroup.subList(j, Math.min(j + 3, chowGroup.size())));
// if (chigroup.size() == 3) {
// playerState.addChiGroup(chigroup);
// }
// }
//
// System.out.println("吃牌听牌playerState.chiGroups +++++++++++++++++++ " + playerState.chiGroups);
// System.out.println("吃牌听牌最新 ai出牌--------------------================");
// for (int h = 0; h < pengCard.size(); h += 3) {
// List<Integer> penggroup = new ArrayList<>(pengCard.subList(h, Math.min(h + 3, pengCard.size())));
// if (penggroup.size() == 3) {
// playerState.addPongGroup(penggroup);
// }
// }
//
//
// int bestDiscard = ai1.findBestDiscard(playerState);
//
//
// if (bestDiscard != 0) {
// if (ai1.isTinAi) {
// isTin = true;
// isChi =false;
// }
// System.out.println("吃牌听牌出牌长麻 ++++++++++++++++++++++++++++++++++++++");
// log.info("吃牌听牌出牌长麻==============================="+bestDiscard);
// return String.valueOf(bestDiscard);
// }
// }
//
// if (isPeng){
// System.out.println("-----------碰牌听牌中-------");
// ai ai1 = new ai();
//// ChangshaMahjongAI ai = new ChangshaMahjongAI();
// PlayerState playerState = new PlayerState();
// playerState.handCards = handCards;
// System.out.println("碰牌听牌中chowGroup +++++++++++++++++++++++" + chowGroup);
// System.out.println("碰牌听牌中pengCard +++++++++++++++++++++++" + pengCard);
//
// for (int j = 0; j < chowGroup.size(); j += 3) {
// List<Integer> chigroup = new ArrayList<>(chowGroup.subList(j, Math.min(j + 3, chowGroup.size())));
// if (chigroup.size() == 3) {
// playerState.addChiGroup(chigroup);
// }
// }
//
// System.out.println("碰牌听牌中playerState.chiGroups +++++++++++++++++++ " + playerState.chiGroups);
// System.out.println("碰牌听牌中最新 ai出牌--------------------================");
// for (int h = 0; h < pengCard.size(); h += 3) {
// List<Integer> penggroup = new ArrayList<>(pengCard.subList(h, Math.min(h + 3, pengCard.size())));
// if (penggroup.size() == 3) {
// playerState.addPongGroup(penggroup);
// }
// }
//
//
// int bestDiscard = ai1.findBestDiscard(playerState);
//
//
// if (bestDiscard != 0) {
// if (ai1.isTinAi) {
// isTin = true;
// isPeng =false;
// }
// System.out.println("碰牌听牌中出牌长麻 ++++++++++++++++++++++++++++++++++++++");
// log.info("碰牌听牌中出牌长麻==============================="+bestDiscard);
// return String.valueOf(bestDiscard);
// }
// }
//将将胡
if (jiangHu && chowGroup.size() == 0) {
logInfo("将将胡");
int outcard = selectCardToDiscardJiangHu(pinghuhandCards);
return String.valueOf(outcard);
}
//打出牌可以听牌了就不要走七对换牌了
List<Integer> checktingpai = TinHuChi.checktingpai(cardInhand);
if (pisCardsCount >= 5 && pengCard.size() == 0 && chowGroup.size() == 0 && checktingpai.size() == 0) {
// ai ai1 = new ai();
// PlayerState playerState = new PlayerState();
// playerState.handCards = pinghuhandCards;
//
//
// for (int j = 0; j < chowGroup.size(); j += 3) {
// List<Integer> chigroup = new ArrayList<>(chowGroup.subList(j, Math.min(j + 3, chowGroup.size())));
// if (chigroup.size() == 3) {
// playerState.addChiGroup(chigroup);
// }
// }
//
//
// for (int h = 0; h < pengCard.size(); h += 3) {
// List<Integer> penggroup = new ArrayList<>(pengCard.subList(h, Math.min(h + 3, pengCard.size())));
// if (penggroup.size() == 3) {
// playerState.addPongGroup(penggroup);
// }
// }
//
//
// int bestDiscard = ai1.findBestDiscard(playerState);
//
// if (ai1.isTingpai) {
// System.out.println("七小对五对但是出别的牌可以优先听牌就先走ai出牌---------");
// //七小对五对但是出别的牌可以优先听牌就先走ai出牌
// return String.valueOf(bestDiscard);
// }
//大胡出牌逻辑 - 七小对
int outcard = selectCardToDiscardBig(handCards, pisCardsCount);
return String.valueOf(outcard);
}
//碰碰胡
List<Integer> checktingpai1 = TinHuChi.checktingpai(cardInhand);
if (i >= 3 && chowGroup.size() == 0 && checktingpai1.size() == 0) {
if (i == 4) {
Map<String, List<?>> stringListMap = separateKeziAndRemaining(pinghuhandCards);
List<?> remaining = stringListMap.get("remaining");
Map<Integer, Integer> danzhangCountMap = new HashMap<>();
//循环获取剩余的牌
for (Object remaining1 : remaining) {
danzhangCountMap.put((Integer) remaining1, 0);
}
for (Integer card3 : resultList) {
if (danzhangCountMap.containsKey(card3)) {
danzhangCountMap.put(card3, danzhangCountMap.get(card3) + 1);
}
}
Integer maxCount = danzhangCountMap.values().stream()
.max(Integer::compareTo)
.orElse(0);
List<Integer> maxCards = danzhangCountMap.entrySet().stream()
.filter(entry -> entry.getValue().equals(maxCount))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
Integer maxCard = maxCards.isEmpty() ? null : maxCards.get(0);
return String.valueOf(maxCard);
}
// if (isPengPengHu) {
// return selectCardToDiscardPengPengHu(pinghuhandCards);
//// System.out.println("--------------------------------碰碰胡 3刻子 2对子----==========================");
//// Map<String, List<?>> stringListMap = separateKeziAndRemaining(handCards);
//// List<?> remaining = stringListMap.get("remaining");
//// Map<Integer, Integer> danzhangCountMap = new HashMap<>();
//// //循环获取剩余的牌
//// for (Object remaining1 : remaining) {
//// danzhangCountMap.put((Integer) remaining1, 0);
//// }
////
//// for (Integer card3 : resultList) {
//// if (danzhangCountMap.containsKey(card3)) {
//// danzhangCountMap.put(card3, danzhangCountMap.get(card3) + 1);
//// }
//// }
////
//// Integer maxCount = danzhangCountMap.values().stream()
//// .max(Integer::compareTo)
//// .orElse(0);
////
////
//// List<Integer> maxCards = danzhangCountMap.entrySet().stream()
//// .filter(entry -> entry.getValue().equals(maxCount))
//// .map(Map.Entry::getKey)
//// .collect(Collectors.toList());
////
//// Integer maxCard = maxCards.isEmpty() ? null : maxCards.get(0);
//// System.out.println("==========碰碰胡----====maxCard++++++++++++++++++++++++++++++" + maxCard);
//// return String.valueOf(maxCard);
// }
}
//七小对清一色
List<Integer> qixiaoduiqingyise = qixiaoduiqingyise(handCards);
boolean isqingyiseqixiaodui = hasFourOrMorePairs(qixiaoduiqingyise);
if (isqingyiseqixiaodui && pengCard.size() == 0 && chowGroup.size() == 0) {
logInfo("执行清一色七小对策略,尝试优化七小对分布");
// 调用花色分析的清一色七小对出牌策略
String discardCard = selectCardToDiscardAllSameSuitQiXiaoDuiBySuit(handCards);
if (discardCard != null) {
return discardCard;
}
}
if (hasBigSuit && i >= 3 && chowGroup.size() == 0) {
logInfo("清一色碰碰胡");
String discardCard = selectCardToDiscardPengPeng(pinghuhandCards);
if (discardCard != null) {
return discardCard;
}
}
if (hasBigSuit) {
logInfo("执行清一色策略,尝试优化花色分布");
//满足清一色,但是平胡可以快速听牌,就走平胡 ,两手听牌
//如果可以快速听牌,就走平胡
//首先分析是否听牌,如果为听牌就去分析差几手牌听牌,差两手就走平胡
List<Integer> integers = ChangshaWinSplitCard.analyzeBestDiscard(pinghuhandCards);
Integer integer = 0;
integer = selectBestCardByPriority(integers);
if (integers.size()>1){
integer = selectBestCardRemove258(integers);
}
if (integers.size() > 0) {
System.out.println("清一色 平胡最新出牌策略=============================================== 666 " + integer);
return String.valueOf(integer);
}
ai ai2 = new ai();
PlayerState playerState1 = new PlayerState();
playerState1.handCards = pinghuhandCards;
for (int j = 0; j < chowGroup.size(); j += 3) {
List<Integer> chigroup = new ArrayList<>(chowGroup.subList(j, Math.min(j + 3, chowGroup.size())));
if (chigroup.size() == 3) {
playerState1.addChiGroup(chigroup);
}
}
for (int h = 0; h < pengCard.size(); h += 3) {
List<Integer> penggroup = new ArrayList<>(pengCard.subList(h, Math.min(h + 3, pengCard.size())));
if (penggroup.size() == 3) {
playerState1.addPongGroup(penggroup);
}
}
int bestDiscard1 = ai2.findBestDiscard(playerState1);
if (ai2.isTingpai) {
return String.valueOf(bestDiscard1);
}
TingPaiChecker.TingResult tingResult = TingPaiChecker.checkTingPai(handCards);
if (tingResult.isTingPai()){
isTin = true;
return String.valueOf(drawnCards);
}
int num =analyzeHandCards(handCards);
System.out.println("0000000000000000000000000000000 ----"+num);
if (num>=3){
System.out.println("----------num 3333 ---------------"+num);
ai ai1 = new ai();
// ChangshaMahjongAI ai = new ChangshaMahjongAI();
PlayerState playerState = new PlayerState();
playerState.handCards = pinghuhandCards;
for (int j = 0; j < chowGroup.size(); j += 3) {
List<Integer> chigroup = new ArrayList<>(chowGroup.subList(j, Math.min(j + 3, chowGroup.size())));
if (chigroup.size() == 3) {
playerState.addChiGroup(chigroup);
}
}
for (int h = 0; h < pengCard.size(); h += 3) {
List<Integer> penggroup = new ArrayList<>(pengCard.subList(h, Math.min(h + 3, pengCard.size())));
if (penggroup.size() == 3) {
playerState.addPongGroup(penggroup);
}
}
int bestDiscard = ai1.findBestDiscard(playerState);
if (bestDiscard != 0) {
if (ai1.isTinAi) {
isTin = true;
}
return String.valueOf(bestDiscard);
}
}
// 调用清一色特定出牌策略
int outcard = selectCardToDiscardForAllSameSuit(pinghuhandCards, chowGroup, pengCard);
if (outcard != -1) {
return String.valueOf(outcard);
}
}
System.out.println("00000000000000000000000000000000000000000000000000000000000000000000000");
// List<Integer> hands = new ArrayList<>();
// hands.addAll(pinghuhandCards);
// if (chowGroup.size() > 0){
// hands.addAll(chowGroup);
// }
//
// if (pengCard.size() > 0){
// hands.addAll(pengCard);
//
// }
// System.out.println("平胡最新hands=================================" +hands);
List<Integer> integers = ChangshaWinSplitCard.analyzeBestDiscard(pinghuhandCards);
Integer integer = 0;
integer = selectBestCardByPriority(integers);
if (integers.size()>1){
integer = selectBestCardRemove258(integers);
}
if (integers.size() > 0) {
System.out.println("平胡最新出牌策略=============================================== 666 " + integer);
return String.valueOf(integer);
}
ai ai1 = new ai();
// ChangshaMahjongAI ai = new ChangshaMahjongAI();
PlayerState playerState = new PlayerState();
playerState.handCards = pinghuhandCards;
System.out.println("chowGroup +++++++++++++++++++++++" + chowGroup);
System.out.println("pengCard +++++++++++++++++++++++" + pengCard);
for (int j = 0; j < chowGroup.size(); j += 3) {
List<Integer> chigroup = new ArrayList<>(chowGroup.subList(j, Math.min(j + 3, chowGroup.size())));
if (chigroup.size() == 3) {
playerState.addChiGroup(chigroup);
}
}
System.out.println("playerState.chiGroups +++++++++++++++++++ " + playerState.chiGroups);
System.out.println("最新 ai出牌--------------------================");
for (int h = 0; h < pengCard.size(); h += 3) {
List<Integer> penggroup = new ArrayList<>(pengCard.subList(h, Math.min(h + 3, pengCard.size())));
if (penggroup.size() == 3) {
playerState.addPongGroup(penggroup);
}
}
int bestDiscard = ai1.findBestDiscard(playerState);
if (bestDiscard != 0) {
if (ai1.isTinAi) {
isTin = true;
}
System.out.println("Ai出牌长麻 ++++++++++++++++++++++++++++++++++++++");
log.info("Ai出牌长麻===============================");
return String.valueOf(bestDiscard);
}
// 1. 手牌分析 - 识别刻子、顺子、对子等牌型
HandAnalysis analysis = analyzeHand(handCards);
//最终出的牌
int outcard = selectCardToDiscard(handCards, analysis);
logInfo("\n===== 手牌分析结果 =====");
logInfo("按花色分组的牌: " + analysis.cardsBySuit);
logInfo("已完成的刻子/顺子: " + analysis.completedMelds);
logInfo("对子: " + analysis.pairs);
logInfo("孤张牌: " + analysis.isolatedCards);
logInfo("面子数量: " + analysis.meldCount);
logInfo("对子数量: " + analysis.pairCount);
logInfo("听牌状态: " + analysis.isTingPai);
if (analysis.isTingPai) {
logInfo("可胡牌: " + analysis.tingCards);
}
logInfo("向听数: " + analysis.shantenCount);
logInfo("有龙七对潜力: " + analysis.hasLongQiDuiPotential);
logInfo("有碰碰胡潜力: " + analysis.hasPengPengHu);
logInfo("剩余需要分析的牌: " + analysis.remainingCards);
//如果已经是听牌状态的情况
if (analysis.isTingPai) {
System.out.println("已经是听牌状态----" + drawnCards);
return String.valueOf(drawnCards);
}
System.out.println("最终出的牌------" + outcard);
return String.valueOf(outcard);
}
// 从候选牌中选出最优的一张牌优先去除258将牌
public static Integer selectBestCardRemove258(List<Integer> cards) {
if (cards == null || cards.isEmpty()) {
return null;
}
// 如果只有一张牌,直接返回
if (cards.size() == 1) {
return cards.get(0);
}
// 创建非258牌的列表
List<Integer> non258Cards = new ArrayList<>();
List<Integer> is258Cards = new ArrayList<>();
// 分类258牌和非258牌
for (Integer card : cards) {
if (is258Jiang(card)) {
is258Cards.add(card);
} else {
non258Cards.add(card);
}
}
System.out.println("258将牌: " + is258Cards);
System.out.println("非258牌: " + non258Cards);
// 优先从非258牌中选
if (!non258Cards.isEmpty()) {
return selectBestSingleCard(non258Cards);
}
// 如果没有非258牌从258牌中选
if (!is258Cards.isEmpty()) {
return selectBestSingleCard(is258Cards);
}
return null;
}
// 检测是否满足258做将的条件
private static boolean is258Jiang(int card) {
// 提取牌的类型和值
int type = card / 100; // 百位数1-万2-筒3-条4-风5-箭
int value = card % 100; // 个位数:牌面值
// 只有万、筒、条有258做将的限制
if (type >= 1 && type <= 3) {
// 258做将二万(102)、五万(105)、八万(108)
// 二筒(202)、五筒(205)、八筒(208)
// 二条(302)、五条(305)、八条(308)
return value == 2 || value == 5 || value == 8;
}
return false;
}
// 从候选牌中选出最优的一张牌优先边张1或9
public static Integer selectBestSingleCard(List<Integer> cards) {
if (cards == null || cards.isEmpty()) {
return null;
}
// 优先找边张1或9
for (Integer card : cards) {
int value = card % 100;
if (value == 1 || value == 9) {
return card;
}
}
// 没有边张找靠近边张的牌2或8- 注意这里2是258将牌但我们已经去除了258
for (Integer card : cards) {
int value = card % 100;
if (value == 2 || value == 8) {
return card;
}
}
// 都没有,返回第一张
return cards.get(0);
}
public static Integer selectBestCardByPriority(List<Integer> cards) {
if (cards == null || cards.isEmpty()) {
return null;
}
Integer bestCard = null;
int bestPriority = Integer.MAX_VALUE;
for (Integer card : cards) {
int value = card % 100;
int priority = getCardPriority(value);
if (priority < bestPriority) {
bestPriority = priority;
bestCard = card;
} else if (priority == bestPriority) {
// 优先级相同,比较牌值(小的优先)
if (bestCard == null || value < (bestCard % 100)) {
bestCard = card;
}
}
}
return bestCard;
}
// 牌的优先级(数字越小优先级越高)
private static int getCardPriority(int value) {
if (value == 9) return 1;
if (value == 1) return 2;
if (value == 2 || value == 8) return 3; // 靠近边张
if (value >= 3 && value <= 7) return 4; // 中间牌
return 5; // 其他
}
//听牌之后去分析出牌
private String isTinOutCard(List<Integer> tinCards, List<Integer> resultList) {
// 统计听牌组中每张牌在牌桌上的出现次数
Map<Integer, Integer> tinCardCountMap = new HashMap<>();
// 初始化听牌组中所有牌的出现次数为0
for (Integer card : tinCards) {
tinCardCountMap.put(card, 0);
}
// 统计听牌组中每张牌在牌桌上的实际出现次数
for (Integer card : resultList) {
if (tinCardCountMap.containsKey(card)) {
tinCardCountMap.put(card, tinCardCountMap.get(card) + 1);
}
}
// 找到最大的出现次数
Integer maxCount = tinCardCountMap.values().stream()
.max(Integer::compareTo)
.orElse(0);
// 判断所有听牌是否都大于等于3次
boolean allGreaterOrEqualThree = !tinCardCountMap.isEmpty() &&
tinCardCountMap.values().stream().allMatch(count -> count >= 3);
if (allGreaterOrEqualThree) {
isTin = false;
// 如果所有听牌都出现大于等于3次则随机出一张听牌
Integer randomCard = tinCards.get(new Random().nextInt(tinCards.size()));
System.out.println("所有听牌出现次数都>=3随机出牌: " + randomCard);
tinCards.remove(randomCard);
return "1";
}
if (maxCount > 0 && maxCount < 3 && tinCards.size() > 1) {
// 出现次数大于0但小于3也直接出次数最多的牌
List<Integer> maxCards = tinCardCountMap.entrySet().stream()
.filter(entry -> entry.getValue().equals(maxCount))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
if (!maxCards.isEmpty()) {
isTin = false;
Integer maxCard = Collections.max(maxCards);
System.out.println("出现次数大于0直接出牌: " + maxCard);
tinCards.remove(maxCard);
return "1";
}
} else {
return String.valueOf(drawnCards);
}
// 如果所有听牌在牌桌上都没出现过maxCount为0或者需要调整听牌状态
if (maxCount == 0) {
//将tinCards转成map格式
return String.valueOf(drawnCards);
}
// 返回一个默认值或处理逻辑(根据您的实际需求调整)
return "0";
}
private List<Integer> danzhang(List<Integer> handCards) {
// 统计每张牌出现的次数
Map<Integer, Integer> cardCountMap = new HashMap<>();
for (Integer card : handCards) {
cardCountMap.put(card, cardCountMap.getOrDefault(card, 0) + 1);
}
System.out.println("统计结果: " + cardCountMap);
List<Integer> singleCards = new ArrayList<>();
// 遍历手牌,计算哪些是单张
for (Map.Entry<Integer, Integer> entry : cardCountMap.entrySet()) {
int card = entry.getKey();
int count = entry.getValue();
// 计算剩余单张的数量
int remainder = count % 2; // 取模2奇数就有一个单张
if (remainder == 1) {
singleCards.add(card);
}
}
return singleCards;
}
private int selectCardToDiscardJiangHu(List<Integer> handCards) {
List<Integer> tempHand = new ArrayList<>(handCards);
// 1. 统计牌型
Map<Integer, Integer> countMap = new HashMap<>();
for (int c : tempHand) {
countMap.put(c, countMap.getOrDefault(c, 0) + 1);
}
// 2. 找出所有258对子候选将牌
List<Integer> jiangCandidates = new ArrayList<>();
for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
if (entry.getValue() >= 2 && isCard(entry.getKey())) {
jiangCandidates.add(entry.getKey());
}
}
// 3. 选择最优将牌优先选择数量多的258对子
Integer bestJiang = selectBestJiang(jiangCandidates, countMap);
// 4. 构建需要保留的牌(将牌 + 所有258牌
Set<Integer> keepCards = new HashSet<>();
// 保留所有258牌包括将牌
for (int c : tempHand) {
if (is258Card(c)) {
keepCards.add(c);
}
}
// 5. 找出需要打出的牌非258牌
List<Integer> discardCandidates = new ArrayList<>();
for (int c : tempHand) {
if (!isCard(c)) {
discardCandidates.add(c);
}
}
// 6. 如果有非258牌优先打出
if (!discardCandidates.isEmpty()) {
return discardCandidates.get(0); // 可以优化选择策略
}
// 7. 如果全是258牌但还没有将牌需要拆牌做将
if (bestJiang == null) {
return selectCardToMakeJiang(tempHand, countMap);
}
// 8. 如果全是258牌且有将牌打出多余的258牌
return selectRedundant258Card(tempHand, countMap, bestJiang);
}
/**
* 选择最优的将牌
*/
private Integer selectBestJiang(List<Integer> jiangCandidates, Map<Integer, Integer> countMap) {
if (jiangCandidates.isEmpty()) {
return null;
}
// 策略优先选择数量多的258对子更容易碰成刻子
Integer bestJiang = null;
int maxCount = 0;
for (int candidate : jiangCandidates) {
int count = countMap.get(candidate);
if (count > maxCount) {
maxCount = count;
bestJiang = candidate;
}
}
return bestJiang;
}
/**
* 当没有将牌时,选择一张牌来促成将牌
*/
private int selectCardToMakeJiang(List<Integer> handCards, Map<Integer, Integer> countMap) {
// 策略打出手牌中数量最少的258孤张
for (int c : handCards) {
if (countMap.get(c) == 1) {
return c;
}
}
// 如果没有孤张打出手牌中数量最少的258
int minCount = Integer.MAX_VALUE;
int worstCard = handCards.get(0);
for (int c : handCards) {
int count = countMap.get(c);
if (count < minCount) {
minCount = count;
worstCard = c;
}
}
return worstCard;
}
/**
* 选择多余的258牌打出
*/
private int selectRedundant258Card(List<Integer> handCards, Map<Integer, Integer> countMap, int bestJiang) {
// 策略保留将牌打出非将牌的258孤张
// 1. 先找非将牌的孤张
for (int c : handCards) {
if (c != bestJiang && countMap.get(c) == 1) {
return c;
}
}
// 2. 找数量最少的非将牌
int minCount = Integer.MAX_VALUE;
int worstCard = handCards.get(0);
for (int c : handCards) {
if (c != bestJiang) {
int count = countMap.get(c);
if (count < minCount) {
minCount = count;
worstCard = c;
}
}
}
// 3. 如果所有牌都是将牌,只能拆将牌(这种情况很少)
if (worstCard == bestJiang) {
// 选择一张将牌打出(保留另一张)
return bestJiang;
}
return worstCard;
}
/**
* 判断是否是258牌
*/
private boolean isCard(int card) {
int cardValue = card % 10;
int cardType = card / 100;
if (cardValue == 2 || cardValue == 5 || cardValue == 8) {
return true;
}
return false;
}
/**
* 判断手牌中是否有碰牌(刻子)
* 刻子定义:三张花色相同且点数相同的牌
* 长沙麻将中碰牌需要满足至少三组刻子
*/
/**
* 统计手牌中的刻子数量
* 刻子定义:三张花色相同且点数相同的牌
*
* @param handCards 手牌列表
* @return 刻子数量
*/
private int countPengGroups(List<Integer> handCards, List<Integer> pengCard) {
List<Integer> handCards2 = new ArrayList<>();
handCards2.addAll(handCards);
// handCards2.addAll(pengCard);
System.out.println("碰碰胡 handCards2 ++++++++++++++++" + handCards2);
// 按花色分组
Map<Integer, List<Integer>> suitGroupMap = new HashMap<>();
for (Integer card : handCards2) {
int suit = card / 100;
suitGroupMap.computeIfAbsent(suit, k -> new ArrayList<>()).add(card);
}
logInfo("手牌花色分组: " + suitGroupMap.keySet().size() + " 种花色");
int keziCount = 0;
// 对每个花色分别统计刻子
for (Map.Entry<Integer, List<Integer>> entry : suitGroupMap.entrySet()) {
int suit = entry.getKey();
List<Integer> suitCards = entry.getValue();
// 统计该花色下每张牌的数量
Map<Integer, Integer> valueCountMap = new HashMap<>();
for (Integer card : suitCards) {
int value = card % 100;
valueCountMap.put(value, valueCountMap.getOrDefault(value, 0) + 1);
}
// 找出该花色的刻子
for (Map.Entry<Integer, Integer> valueEntry : valueCountMap.entrySet()) {
if (valueEntry.getValue() >= 3) {
keziCount++;
int cardValue = suit * 100 + valueEntry.getKey();
logInfo("找到刻子: " + getCardName(cardValue) + " (花色:" + getSuitName(suit) +
", 点数:" + valueEntry.getKey() + ", 数量:" + valueEntry.getValue() + ")");
}
}
}
logInfo("手牌中共找到 " + keziCount + " 组刻子");
return keziCount;
}
//分析碰碰胡是否听牌
public boolean hasThreeKeziAndTwoPairs(List<Integer> handCards, List<Integer> pengCard) {
List<Integer> handCards2 = new ArrayList<>();
handCards2.addAll(handCards);
// handCards2.addAll(pengCard);
// 统计每张牌出现的次数
Map<Integer, Integer> cardCountMap = new HashMap<>();
for (Integer card : handCards2) {
cardCountMap.put(card, cardCountMap.getOrDefault(card, 0) + 1);
}
int keziCount = 0; // 刻子数(三张相同)
int pairCount = 0; // 对子数(两张相同)
// 统计刻子和对子
for (int count : cardCountMap.values()) {
if (count >= 3) {
// 如果有3张或以上可以算作一个刻子
keziCount++;
// 如果有4张剩余的1张不能算对子但可能算单张
if (count >= 4) {
// 4张可以看作1个刻子+1张单牌或者如果有需要可以调整
// 这里暂时不额外处理
}
} else if (count == 2) {
pairCount++;
}
}
// 检查是否有至少3个刻子和至少2个对子
return keziCount >= 3 && pairCount >= 2;
}
//拿出剩余牌
private static Map<String, List<?>> separateKeziAndRemaining(List<Integer> handCards) {
List<List<Integer>> keziList = new ArrayList<>();
Set<Integer> remainingCardsSet = new HashSet<>(); // 使用Set去重
if (handCards == null || handCards.isEmpty()) {
Map<String, List<?>> result = new HashMap<>();
result.put("kezi", keziList);
result.put("remaining", new ArrayList<>());
return result;
}
// 统计每张牌的数量
Map<Integer, Integer> countMap = new HashMap<>();
for (Integer card : handCards) {
countMap.put(card, countMap.getOrDefault(card, 0) + 1);
}
// 分离刻子
for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
int card = entry.getKey();
int count = entry.getValue();
if (count >= 3) {
// 添加刻子
List<Integer> kezi = new ArrayList<>();
for (int i = 0; i < 3; i++) {
kezi.add(card);
}
keziList.add(kezi);
// 如果还有剩余加入Set去重
if (count > 3) {
remainingCardsSet.add(card);
}
} else {
// 全部加入剩余牌Set
remainingCardsSet.add(card);
}
}
// 将Set转换为List
List<Integer> remainingCards = new ArrayList<>(remainingCardsSet);
Map<String, List<?>> result = new HashMap<>();
result.put("kezi", keziList);
result.put("remaining", remainingCards);
return result;
}
/**
* 辅助方法:获取牌的名称
*/
private String getCardName(int card) {
int suit = card / 100;
int value = card % 100;
String suitName = getSuitName(suit);
return suitName + value;
}
private boolean isJiangHu(List<Integer> handCards) {
// 统计手牌中258牌的数量
int count258 = 0;
for (Integer card : handCards) {
if (is258Card(card)) {
count258++;
}
}
// 判断258牌是否大于7张
return count258 >= 10;
}
/**
* 判断一张牌是否是258牌
*/
private boolean is258Card(int card) {
// 假设牌型编码规则:
// 101-109: 一万到九万
// 201-209: 一筒到九筒
// 301-309: 一条到九条
// 获取牌的数字(个位数)
int cardValue = card % 10;
// 258牌的数字必须是2、5、8
if (cardValue == 2 || cardValue == 5 || cardValue == 8) {
// 确保是万、筒、条(排除风牌等)
int cardType = card / 100;
return true;
}
return false;
}
/**
* 七小对大胡策略出牌
* 当有5对以上时按照七小对胡牌牌型进行出牌
*/
public int selectCardToDiscardBig(List<Integer> handCards, int pisCardsCount) {
if (pisCardsCount >= 5) {
logInfo("执行七小对大胡策略,当前对子数量: " + pisCardsCount);
// 统计每张牌的数量
Map<Integer, Integer> cardCounts = new HashMap<>();
for (int card : handCards) {
cardCounts.put(card, cardCounts.getOrDefault(card, 0) + 1);
}
// 收集单张牌数量为1的牌
List<Integer> singleCards = new ArrayList<>();
for (int card : handCards) {
if (cardCounts.get(card) == 1) {
singleCards.add(card);
}
}
// 如果有单张牌,优先打出这些牌
if (!singleCards.isEmpty()) {
// 优先打出边张1和9
for (int card : singleCards) {
int value = card % 100;
if (value == 1 || value == 9) {
logInfo("打出七小对策略边张单牌: " + card);
return card;
}
}
// 其次打出中间不相关的牌(避开容易形成对子的中间牌)
for (int card : singleCards) {
int value = card % 100;
if (value == 2 || value == 8) {
logInfo("打出七小对策略边张单牌: " + card);
return card;
}
}
// 最后随便选一张单牌
logInfo("打出七小对策略单牌: " + singleCards.get(0));
return singleCards.get(0);
}
// 如果没有单张牌(所有牌都是对子或刻子),需要拆牌
// 优先拆刻子(保留对子)
List<Integer> tripleCards = new ArrayList<>();
for (int card : handCards) {
if (cardCounts.get(card) >= 3) {
tripleCards.add(card);
// 只需要一张来代表这个刻子
break;
}
}
if (!tripleCards.isEmpty()) {
logInfo("七小对策略拆刻子: " + tripleCards.get(0));
return tripleCards.get(0);
}
// 如果所有都是对子,理论上已经是七小对听牌状态
// 但为了保险,随便选一张
logInfo("七小对策略,所有牌都是对子,随便打出一张: " + handCards.get(0));
return handCards.get(0);
}
// 不满足七小对条件时的默认返回
return handCards.get(0);
}
private String selectCardToDiscardPengPengHu(List<Integer> handCards) {
logInfo("开始执行碰碰胡出牌策略");
// 1. 复制手牌,避免修改原始数据
List<Integer> remainingCards = new ArrayList<>(handCards);
// 2. 找出所有刻子(三张相同的牌),不重复利用
List<Integer> keziList = extractAllKezi(remainingCards);
int keziCount = keziList.size() / 3;
logInfo("找到刻子: " + keziCount + "组 - " + getKeziNames(keziList));
//去除刻子后的牌
List<Integer> feiCandidates = findFeiJiangCandidates5(remainingCards);
logInfo("去除刻子后的牌: " + (feiCandidates));
int i = selectFromFeiCandidates4(feiCandidates);
return String.valueOf(i);
}
/**
* 找出非将牌候选除了258将牌和刻子之外的牌
*/
private List<Integer> findFeiJiangCandidates5(List<Integer> cards) {
Map<Integer, Integer> cardCount = new HashMap<>();
List<Integer> feiJiangCandidates = new ArrayList<>();
// 统计剩余牌的数量
for (Integer card : cards) {
cardCount.put(card, cardCount.getOrDefault(card, 0) + 1);
}
for (Map.Entry<Integer, Integer> entry : cardCount.entrySet()) {
int card = entry.getKey();
int count = entry.getValue();
int value = card % 100;
for (int i = 0; i < count; i++) {
feiJiangCandidates.add(card);
}
}
return feiJiangCandidates;
}
/**
* 从非将牌候选中选择要打出的牌
* 优先打孤张,否则再拆牌
*/
private int selectFromFeiCandidates4(List<Integer> feiCandidates) {
if (feiCandidates.isEmpty()) {
return -1; // 或者抛出异常,根据实际情况处理
}
// 1. 优先找出非对子的牌
Integer isolatedCard = findIsolatedCardInFei6(feiCandidates);
logInfo("策略: 打出孤张牌 " + getCardName(isolatedCard));
return isolatedCard;
}
private Integer findIsolatedCardInFei6(List<Integer> feiCandidates) {
// 统计每张牌的出现次数
Map<Integer, Integer> countMap = new HashMap<>();
for (int card : feiCandidates) {
countMap.put(card, countMap.getOrDefault(card, 0) + 1);
}
// 寻找出现次数为 1 的牌(非对子)
for (int card : feiCandidates) {
if (countMap.get(card) == 1) {
return card;
}
}
// 如果没有非对子的牌,返回 null 或选择第一张牌等策略
return feiCandidates.get(0);
}
private String getKeziNames(List<Integer> keziList) {
if (keziList.isEmpty()) return "无";
Set<String> keziNames = new HashSet<>();
for (int i = 0; i < keziList.size(); i += 3) {
keziNames.add(getCardName(keziList.get(i)));
}
return String.join(", ", keziNames);
}
/**
* 提取所有刻子(三张相同的牌),不重复利用
*/
private List<Integer> extractAllKezi(List<Integer> cards) {
List<Integer> keziList = new ArrayList<>();
Map<Integer, Integer> cardCount = new HashMap<>();
// 统计每张牌的数量
for (Integer card : cards) {
cardCount.put(card, cardCount.getOrDefault(card, 0) + 1);
}
// 找出所有数量>=3的牌提取刻子
List<Integer> cardsToRemove = new ArrayList<>();
for (Map.Entry<Integer, Integer> entry : cardCount.entrySet()) {
int card = entry.getKey();
int count = entry.getValue();
if (count >= 3) {
// 计算可以组成几个刻子比如4张相同的牌可以组成1个刻子+1张单牌
int keziGroups = count / 3;
for (int i = 0; i < keziGroups; i++) {
// 添加3次相同的牌表示一个刻子
keziList.add(card);
keziList.add(card);
keziList.add(card);
}
// 标记需要从剩余牌中移除的牌(只移除刻子部分)
for (int i = 0; i < keziGroups * 3; i++) {
cardsToRemove.add(card);
}
}
}
// 从剩余牌中移除刻子
for (Integer card : cardsToRemove) {
cards.remove(card);
}
return keziList;
}
/**
* 最终出的牌
* 优化后的分析逻辑:
* 1. 分析剩余牌的搭子和卡隆情况
* 2. 优先打出边张且不是将的牌
* 3. 如果没有优先打边张1、2其次2、8最后随便打
*/
public int selectCardToDiscard(List<Integer> cardInhand, HandAnalysis analysis) {
logInfo("\n===== 开始选择出牌策略 =====");
int discardCard = 0;
// 如果analysis为null自动分析手牌
if (analysis == null) {
analysis = analyzeHand(cardInhand);
}
// 获取剩余牌
List<Integer> remainingCards = new ArrayList<>(analysis.isolatedCards);
logInfo("剩余需要分析的牌: " + remainingCards);
// 识别搭子和卡隆
List<List<Integer>> daAndKa = identifydaka(remainingCards);
logInfo("识别到的搭子和卡隆: " + daAndKa);
// 将搭子和卡隆中的牌收集起来
Set<Integer> daka = new HashSet<>();
for (List<Integer> card : daAndKa) {
daka.addAll(card);
}
logInfo("搭子和卡隆中的牌: " + daka);
// 找出所有可能的出牌候选,排除搭子和卡隆中的牌
Set<Integer> candidates = new HashSet<>(remainingCards);
candidates.removeAll(daka);
logInfo("排除搭子和卡隆后的候选牌: " + candidates);
// 1. 优先打出边张且不是将的牌
List<Integer> ban = new ArrayList<>();
for (int card : candidates) {
int rank = card % 100;
// 且不是将牌
if ((rank != 5 && rank != 2 && rank != 8)) {
ban.add(card);
}
}
for (int card : candidates) {
int rank = card % 100;
// 边张是1或9且不是将牌
if ((rank == 1 || rank == 9) && !analysis.usedInPairs.contains(card)) {
ban.add(card);
}
}
if (!ban.isEmpty()) {
if (ban.get(0) != 5 && ban.get(0) != 2 && ban.get(0) != 8) {
return ban.get(0);
}
}
// 4. 如果以上情况都没有,打出剩余随便哪张
if (!candidates.isEmpty()) {
discardCard = candidates.iterator().next();
logInfo("最后选择任意剩余牌: " + discardCard);
if (discardCard != 5 && discardCard != 2 && discardCard != 8) {
return discardCard;
}
}
// 检查已完成的牌组中是否有带有将的牌
for (Integer meld : analysis.isolatedCards) {
// 检查该牌组中是否有牌是将牌
if (analysis.usedInPairs.contains(meld)) {
if (meld == 5 || meld == 2 || meld == 8) {
return meld;
}
}
}
// 如果没有带有将的牌组,选择第一个牌组的第一张牌
if (!analysis.pairs.isEmpty()) {
int pcard = 0;
for (Integer pair : analysis.pairs) {
int card = pair % 100;
if (card != 5 && card != 2 && card != 8) {
pcard = pair;
}
}
if (pcard != 0) {
return pcard;
} else {
return analysis.pairs.get(0);
}
}
if (!analysis.remainingCards.isEmpty()) {
for (Integer pair : analysis.remainingCards) {
int card = pair % 100;
if (card != 5 && card != 2 && card != 8) {
return pair;
}
}
}
if (!analysis.completedMelds.isEmpty()) {
List<Integer> validMelds = new ArrayList<>();
for (List<Integer> meld : analysis.completedMelds) {
// 检查整个组合中是否包含要排除的牌
boolean containsExcluded = false;
for (Integer card : meld) {
int cardValue = card % 100;
if (cardValue == 5 || cardValue == 2 || cardValue == 8) {
containsExcluded = true;
break;
}
}
// 如果整个组合都不包含排除的牌,记录下来
if (!containsExcluded && !meld.isEmpty()) {
validMelds.add(meld.get(0));
}
}
// 如果有有效的组合,返回第一个
if (!validMelds.isEmpty()) {
return validMelds.get(0);
}
// 如果所有组合都包含排除牌,返回第一个组合的第一张牌作为备选
return analysis.completedMelds.get(0).get(0);
}
if (!analysis.remainingCards.isEmpty()) {
for (Integer pair : analysis.remainingCards) {
int card = pair % 100;
if (card == 5 || card == 2 || card == 8) {
return pair;
}
}
}
return discardCard;
}
/**
* 识别搭子和卡隆
* 搭子两张连续的牌例如1-22-3等
* 卡隆两张中间缺一张的牌例如1-3缺22-4缺3
*/
private List<List<Integer>> identifydaka(List<Integer> cards) {
List<List<Integer>> daka = new ArrayList<>();
// 按花色分组
Map<Integer, List<Integer>> cardsBySuit = new HashMap<>();
for (int card : cards) {
int suit = card / 100;
cardsBySuit.computeIfAbsent(suit, k -> new ArrayList<>()).add(card);
}
// 记录已经使用过的牌
Set<Integer> usedCards = new HashSet<>();
// 分析每个花色
for (List<Integer> suitCards : cardsBySuit.values()) {
Collections.sort(suitCards);
// 优先找搭子(连续两张)
for (int i = 0; i < suitCards.size() - 1; i++) {
int card1 = suitCards.get(i);
int card2 = suitCards.get(i + 1);
if (usedCards.contains(card1) || usedCards.contains(card2)) {
continue; // 牌已被使用
}
int rank1 = card1 % 100;
int rank2 = card2 % 100;
// 搭子:连续两张牌
if (rank2 - rank1 == 1) {
daka.add(Arrays.asList(card1, card2));
usedCards.add(card1);
usedCards.add(card2);
i++; // 跳过下一张牌
}
}
// 再找卡隆(中间差一张)
for (int i = 0; i < suitCards.size() - 1; i++) {
int card1 = suitCards.get(i);
if (usedCards.contains(card1)) {
continue;
}
for (int j = i + 1; j < suitCards.size(); j++) {
int card2 = suitCards.get(j);
if (usedCards.contains(card2)) {
continue;
}
int rank1 = card1 % 100;
int rank2 = card2 % 100;
int rankDiff = rank2 - rank1;
// 卡隆:中间差一张
if (rankDiff == 2) {
daka.add(Arrays.asList(card1, card2));
usedCards.add(card1);
usedCards.add(card2);
break;
} else if (rankDiff > 2) {
break;
}
}
}
}
return daka;
}
/**
* 碰牌判断方法
*
* @param opcard 要碰的牌
* @param handCards 当前手牌
* @return 是否允许碰牌
*/
public boolean shouldPong(int opcard, List<Integer> handCards) {
// 1. 判断是否有至少两张相同的牌可以碰
int count = 0;
for (int card : handCards) {
if (card == opcard) {
count++;
}
}
if (count < 2) {
return false;
}
// 2. 如果是清一色花色,只能碰相同花色的牌
if (isAllSameSuit1(handCards)) {
Integer mainSuit = getMainSuit(handCards);
int opcardSuit = opcard / 100;
if (mainSuit != null && mainSuit != opcardSuit) {
logInfo("清一色模式:不能碰不同花色的牌。目标牌花色:" + getSuitName(opcardSuit) + ", 主花色:" + getSuitName(mainSuit));
return false;
}
}
// 2. 复制手牌并模拟去掉要碰的两张牌
List<Integer> tempHand = new ArrayList<>(handCards);
tempHand.remove(Integer.valueOf(opcard));
tempHand.remove(Integer.valueOf(opcard));
// 3. 检查该牌是否是听牌组中的牌
List<Integer> tingCards = calculateTingCards(tempHand);
if (tingCards.contains(opcard)) {
return false;
}
// 4. 检查该牌是否是顺子的一部分
if (isPartOfSequence(opcard, handCards)) {
return false;
}
// 5. 检查该牌是否是唯一的将牌
if (isOnlyPair(tempHand, opcard)) {
return false;
}
// 其他情况允许碰牌
return true;
}
/**
* 吃牌判断方法
*
* @param opcard 要吃的牌
* @param handCards 当前手牌
* @return 是否允许吃牌
*/
public boolean shouldChow(int opcard, List<Integer> handCards) {
logInfo("吃牌判断开始, 目标牌: " + opcard + ", 当前手牌: " + handCards);
// 1. 如果是清一色花色,只能吃相同花色的牌
if (isAllSameSuit1(handCards)) {
Integer mainSuit = getMainSuit(handCards);
int opcardSuit = opcard / 100;
if (mainSuit != null && mainSuit != opcardSuit) {
logInfo("清一色模式:不能吃不同花色的牌。目标牌花色:" + getSuitName(opcardSuit) + ", 主花色:" + getSuitName(mainSuit));
return false;
}
}
// 创建手牌的副本,避免修改原手牌
List<Integer> handCopy = new ArrayList<>(handCards);
// 分析手牌,识别刻子和顺子
HandAnalysis analysis = analyzeHand(handCopy);
// 获取剩余牌(未用于刻子或顺子的牌)
List<Integer> remainingCards = analysis.remainingCards;
// 检查剩余牌是否能与其他两张牌组成包含opcard的顺子
boolean canChow = canFormChow(opcard, remainingCards);
logInfo("吃牌判断结束, 结果: " + canChow);
return canChow;
}
/**
* 检查是否能形成包含目标牌的顺子
* @param targetCard 目标牌
* @param handCards 手牌
* @return 是否能吃牌
*/
/**
* 判断是否应该杠牌(补牌)
*
* @param proposedCard 要杠的牌
* @param currentHand 当前手牌
* @param 1:暗杠, 2:明杠, 3:加杠, 4:补杠等)
* @return 是否应该杠牌
*/
public boolean shouldGang(int proposedCard, List<Integer> currentHand) {
logInfo("判断是否应该杠牌: " + proposedCard);
// 1. 如果是清一色花色,只能杠相同花色的牌
if (isAllSameSuit1(currentHand)) {
Integer mainSuit = getMainSuit(currentHand);
int proposedCardSuit = proposedCard / 100;
if (mainSuit != null && mainSuit != proposedCardSuit) {
logInfo("清一色模式:不能杠不同花色的牌。目标牌花色:" + getSuitName(proposedCardSuit) + ", 主花色:" + getSuitName(mainSuit));
return false;
}
}
// 基础杠牌条件手牌中至少有3张相同的牌
int count = 0;
for (int card : currentHand) {
if (card == proposedCard) {
count++;
}
}
if (count < 3) {
logInfo("手牌中没有足够的相同牌来杠");
return false;
}
// 这里可以添加更多杠牌决策逻辑,目前只实现清一色优化
return true;
}
private boolean canFormChow(int targetCard, List<Integer> handCards) {
// 检查是否是字牌(风牌或箭牌),字牌不能组成顺子
int suit = targetCard / 100;
if (suit == 4 || suit == 5) { // 4是风牌5是箭牌
return false;
}
// 获取目标牌的点数
int targetPoint = targetCard % 100;
// 统计手牌中每张牌的数量
Map<Integer, Integer> cardCounts = new HashMap<>();
for (int card : handCards) {
cardCounts.put(card, cardCounts.getOrDefault(card, 0) + 1);
}
// 检查三种可能的顺子组合
// 1. targetCard-2, targetCard-1, targetCard
if (targetPoint >= 3) {
int card1 = suit * 100 + (targetPoint - 2);
int card2 = suit * 100 + (targetPoint - 1);
if (cardCounts.containsKey(card1) && cardCounts.containsKey(card2)) {
return true;
}
}
// 2. targetCard-1, targetCard, targetCard+1
if (targetPoint >= 2 && targetPoint <= 8) {
int card1 = suit * 100 + (targetPoint - 1);
int card2 = suit * 100 + (targetPoint + 1);
if (cardCounts.containsKey(card1) && cardCounts.containsKey(card2)) {
return true;
}
}
// 3. targetCard, targetCard+1, targetCard+2
if (targetPoint <= 7) {
int card1 = suit * 100 + (targetPoint + 1);
int card2 = suit * 100 + (targetPoint + 2);
if (cardCounts.containsKey(card1) && cardCounts.containsKey(card2)) {
return true;
}
}
return false;
}
/**
* 计算听牌
*
* @param handCards 手牌
* @return 听牌列表
*/
private List<Integer> calculateTingCards(List<Integer> handCards) {
List<Integer> tingCards = new ArrayList<>();
Map<Integer, Integer> cardCount = countCards(handCards);
// 检查每种花色的牌
for (int suit = 1; suit <= 3; suit++) { // 1:万, 2:筒, 3:索
List<Integer> sameSuitCards = new ArrayList<>();
for (int rank = 1; rank <= 9; rank++) {
int card = suit * 100 + rank;
int count = cardCount.getOrDefault(card, 0);
for (int i = 0; i < count; i++) {
sameSuitCards.add(rank);
}
}
// 检查是否有搭子需要补牌
if (sameSuitCards.size() > 0) {
// 简单检查:如果有单张牌,可能听它的相邻牌或相同牌
Map<Integer, Integer> rankCount = new HashMap<>();
for (int rank : sameSuitCards) {
rankCount.put(rank, rankCount.getOrDefault(rank, 0) + 1);
}
for (Map.Entry<Integer, Integer> entry : rankCount.entrySet()) {
int rank = entry.getKey();
int count = entry.getValue();
// 如果是单张或对子,可能需要补牌
if (count == 1 || count == 2) {
// 添加当前牌(可能组成刻子)
tingCards.add(suit * 100 + rank);
// 添加相邻牌(可能组成顺子)
if (rank > 1) {
tingCards.add(suit * 100 + (rank - 1));
}
if (rank < 9) {
tingCards.add(suit * 100 + (rank + 1));
}
}
}
}
}
return tingCards;
}
/**
* 判断是否是顺子的一部分
*
* @param card 要判断的牌
* @param handCards 手牌
* @return 是否是顺子的一部分
*/
private boolean isPartOfSequence(int card, List<Integer> handCards) {
int suit = card / 100;
int rank = card % 100;
// 检查是否存在顺子
// 顺子是三个连续的牌比如1-2-32-3-4等
boolean hasPrevPrev = handCards.contains(suit * 100 + rank - 2);
boolean hasPrev = handCards.contains(suit * 100 + rank - 1);
boolean hasNext = handCards.contains(suit * 100 + rank + 1);
boolean hasNextNext = handCards.contains(suit * 100 + rank + 2);
// 检查是否是顺子的中间牌或边牌
return (hasPrev && hasNext) || (hasPrevPrev && hasPrev) || (hasNext && hasNextNext);
}
/**
* 判断是否是唯一的将牌
*
* @param tempHand 去掉两张要碰的牌后的手牌
* @param opcard 要碰的牌
* @return 是否是唯一的将牌
*/
public boolean isOnlyPair(List<Integer> tempHand, int opcard) {
Map<Integer, Integer> cardCount = countCards(tempHand);
int pairCount = 0;
for (Map.Entry<Integer, Integer> entry : cardCount.entrySet()) {
if (entry.getValue() == 2) {
pairCount++;
}
}
// 如果去掉要碰的牌后没有对子,说明原来的牌是唯一的将牌
return pairCount == 0;
}
/**
* 统计每张牌的数量
*
* @param handCards 手牌
* @return 牌的数量映射
*/
private Map<Integer, Integer> countCards(List<Integer> handCards) {
Map<Integer, Integer> cardCount = new HashMap<>();
for (int card : handCards) {
cardCount.put(card, cardCount.getOrDefault(card, 0) + 1);
}
return cardCount;
}
//大胡分析七小对
public int countPairs(List<Integer> handCards) {
// 1. 先根据花色和点数排序
List<Integer> sortedCards = new ArrayList<>(handCards);
sortedCards.sort((a, b) -> {
int suitA = a / 100;
int suitB = b / 100;
int rankA = a % 100;
int rankB = b % 100;
// 先按花色排序,再按点数排序
if (suitA != suitB) {
return suitA - suitB;
}
return rankA - rankB;
});
// 2. 统计对子
int pairCount = 0;
for (int i = 0; i < sortedCards.size() - 1; i++) {
if (sortedCards.get(i).equals(sortedCards.get(i + 1))) {
pairCount++;
i++; // 跳过下一张
}
}
return pairCount;
}
//分析七小对清一色
public List<Integer> qixiaoduiqingyise(List<Integer> handCards) {
// 合并所有牌
List<Integer> allCards = new ArrayList<>();
allCards.addAll(handCards);
// 按花色分组
List<Integer> wanCards = new ArrayList<>(); // 万: 101-109
List<Integer> tongCards = new ArrayList<>(); // 筒: 201-209
List<Integer> tiaoCards = new ArrayList<>(); // 条: 301-309
for (Integer card : allCards) {
if (card >= 101 && card <= 109) {
wanCards.add(card);
} else if (card >= 201 && card <= 209) {
tongCards.add(card);
} else if (card >= 301 && card <= 309) {
tiaoCards.add(card);
}
}
// 检查各花色数量
if (wanCards.size() >= 8) {
Collections.sort(wanCards);
logInfo("检测到清一色花色: 万, 数量: " + wanCards.size());
return wanCards;
}
if (tongCards.size() >= 8) {
Collections.sort(tongCards);
logInfo("检测到清一色花色: 筒, 数量: " + tongCards.size());
return tongCards;
}
if (tiaoCards.size() >= 8) {
Collections.sort(tiaoCards);
logInfo("检测到清一色花色: 条, 数量: " + tiaoCards.size());
return tiaoCards;
}
return new ArrayList<>();
}
public boolean hasFourOrMorePairs(List<Integer> qixiaoduiqingyise) {
if (qixiaoduiqingyise == null || qixiaoduiqingyise.size() < 8) {
return false; // 至少需要8张牌才能有4对
}
// 对牌进行排序
List<Integer> sortedCards = new ArrayList<>(qixiaoduiqingyise);
Collections.sort(sortedCards);
// 统计对子数量
int pairCount = 0;
int i = 0;
int n = sortedCards.size();
while (i < n - 1) {
if (sortedCards.get(i).equals(sortedCards.get(i + 1))) {
// 找到一对对子
pairCount++;
i += 2; // 跳过这对对子
} else {
i += 1; // 继续检查下一张牌
}
}
logInfo("清一色手牌中对子数量: " + pairCount);
return pairCount >= 4;
}
//分析清一色
public boolean isAllSameSuit(List<Integer> handCards, List<Integer> pengCard) {
// 统计各花色的牌数量
Map<Integer, Integer> suitCountMap = new HashMap<>();
List<Integer> handCards2 = new ArrayList<>();
handCards2.addAll(handCards);
// handCards2.addAll(pengCard);
for (Integer card : handCards2) {
int suit = card / 100; // 获取花色100=万200=筒300=条
suitCountMap.put(suit, suitCountMap.getOrDefault(suit, 0) + 1);
}
// 检查是否有花色的牌数量超过8张
for (Map.Entry<Integer, Integer> entry : suitCountMap.entrySet()) {
int suit = entry.getKey();
int count = entry.getValue();
if (count >= 10) {
String suitName = getSuitName(suit);
logInfo("检测到可能的清一色花色: " + suitName + ", 数量: " + count);
return true;
}
}
return false;
}
//分析清一色
public boolean isAllSameSuit1(List<Integer> handCards) {
// 统计各花色的牌数量
Map<Integer, Integer> suitCountMap = new HashMap<>();
for (Integer card : handCards) {
int suit = card / 100; // 获取花色100=万200=筒300=条
suitCountMap.put(suit, suitCountMap.getOrDefault(suit, 0) + 1);
}
for (Map.Entry<Integer, Integer> entry : suitCountMap.entrySet()) {
int suit = entry.getKey();
int count = entry.getValue();
if (count >= 11) {
String suitName = getSuitName(suit);
logInfo("检测到可能的清一色花色: " + suitName + ", 数量: " + count);
return true;
}
}
return false;
}
// 获取主要花色数量最多且≥9张的花色
public Integer getMainSuit(List<Integer> handCards) {
Map<Integer, Integer> suitCountMap = new HashMap<>();
for (Integer card : handCards) {
int suit = card / 100;
suitCountMap.put(suit, suitCountMap.getOrDefault(suit, 0) + 1);
}
Integer mainSuit = null;
int maxCount = 0;
for (Map.Entry<Integer, Integer> entry : suitCountMap.entrySet()) {
int suit = entry.getKey();
int count = entry.getValue();
if (count >= 10 && count > maxCount) {
maxCount = count;
mainSuit = suit;
}
}
return mainSuit;
}
// 清一色特定出牌策略
/**
* 基于花色分析的清一色七小对出牌策略
* 优先打出花色最少的牌,以优化七小对的形成
*
* @param handCards 当前手牌
* @return 最优打出的牌
*/
private String selectCardToDiscardAllSameSuitQiXiaoDuiBySuit(List<Integer> handCards) {
// 1. 分析每个花色的牌数量
Map<Integer, Integer> suitCount = new HashMap<>();
Map<Integer, List<Integer>> cardsBySuit = new HashMap<>();
for (int card : handCards) {
int suit = card / 10; // 计算花色
suitCount.put(suit, suitCount.getOrDefault(suit, 0) + 1);
// 按花色分组存储牌
cardsBySuit.computeIfAbsent(suit, k -> new ArrayList<>()).add(card);
}
logInfo("花色分布统计: " + suitCount);
// 2. 找出花色数量最少的花色(排除主花色)
Integer mainSuit = getMainSuit(handCards);
Integer leastSuit = null;
int minCount = Integer.MAX_VALUE;
for (Map.Entry<Integer, Integer> entry : suitCount.entrySet()) {
int suit = entry.getKey();
int count = entry.getValue();
// 如果不是主花色,且数量更少
if ((mainSuit == null || suit != mainSuit) && count < minCount) {
minCount = count;
leastSuit = suit;
}
}
// 3. 从最少花色中选择要打出的牌
if (leastSuit != null && cardsBySuit.containsKey(leastSuit)) {
List<Integer> leastSuitCards = cardsBySuit.get(leastSuit);
logInfo("优先从最少花色 " + getSuitName(leastSuit) + " 中选择打出的牌: " + leastSuitCards);
// 统计最少花色中各牌的出现次数
Map<Integer, Integer> cardCountsInLeastSuit = new HashMap<>();
for (int card : leastSuitCards) {
cardCountsInLeastSuit.put(card, cardCountsInLeastSuit.getOrDefault(card, 0) + 1);
}
// 优先打出单张牌出现次数为1
for (Map.Entry<Integer, Integer> entry : cardCountsInLeastSuit.entrySet()) {
if (entry.getValue() == 1) {
logInfo("选择打出最少花色中的单张牌: " + entry.getKey());
return String.valueOf(entry.getKey());
}
}
// 如果没有单张,优先打对子的,优先保留刻子或四张一样的
for (Map.Entry<Integer, Integer> entry : cardCountsInLeastSuit.entrySet()) {
if (entry.getValue() == 2) {
return String.valueOf(entry.getKey());
}
}
}
// 4. 如果没有找到合适的牌(可能所有牌都是同一花色),使用七小对大胡策略
logInfo("使用默认七小对大胡策略");
int outcard = selectCardToDiscardBig(handCards, countPairs(handCards));
return String.valueOf(outcard);
}
private String selectCardToDiscardPengPeng(List<Integer> handCards) {
logInfo("开始执行清一色碰碰胡出牌策略");
// 1. 分析每个花色的牌数量
Map<Integer, Integer> suitCount = new HashMap<>();
Map<Integer, List<Integer>> cardsBySuit = new HashMap<>();
for (int card : handCards) {
int suit = card / 100; // 计算花色修正为除以100
suitCount.put(suit, suitCount.getOrDefault(suit, 0) + 1);
// 按花色分组存储牌
cardsBySuit.computeIfAbsent(suit, k -> new ArrayList<>()).add(card);
}
// 2. 找出主要花色(牌数量最多的花色)
int mainSuit = findMainSuit(suitCount);
logInfo("主要花色: " + getSuitName(mainSuit) + ", 数量: " + suitCount.get(mainSuit));
// 3. 如果有非主要花色的牌,优先打出
if (hasNonMainSuitCards(suitCount, mainSuit)) {
logInfo("存在非主要花色牌,优先打出");
return discardNonMainSuitCard(handCards, mainSuit);
}
// 4. 所有牌都是主要花色,使用清一色碰碰胡策略
logInfo("所有牌都是主要花色,使用清一色碰碰胡策略");
return discardFromSameSuitPengPeng(handCards, mainSuit);
}
/**
* 打出非主要花色的牌
*/
private String discardNonMainSuitCard(List<Integer> handCards, int mainSuit) {
// 优先打出数量最少的非主要花色牌
List<Integer> nonMainSuitCards = new ArrayList<>();
for (int card : handCards) {
int suit = card / 100;
if (suit != mainSuit) {
nonMainSuitCards.add(card);
}
}
// 按数量排序,优先打出手牌数量少的牌
Map<Integer, Integer> cardCount = countCardOccurrences(nonMainSuitCards);
nonMainSuitCards.sort((a, b) -> {
int countA = cardCount.get(a);
int countB = cardCount.get(b);
return Integer.compare(countA, countB);
});
int discardCard = nonMainSuitCards.get(0);
logInfo("打出非主要花色牌: " + getCardName(discardCard));
return String.valueOf(discardCard);
}
/**
* 统计每张牌的出现次数
*/
private Map<Integer, Integer> countCardOccurrences(List<Integer> cards) {
Map<Integer, Integer> countMap = new HashMap<>();
for (int card : cards) {
countMap.put(card, countMap.getOrDefault(card, 0) + 1);
}
return countMap;
}
/**
* 找出主要花色(牌数量最多的花色)
*/
private int findMainSuit(Map<Integer, Integer> suitCount) {
int mainSuit = -1;
int maxCount = 0;
for (Map.Entry<Integer, Integer> entry : suitCount.entrySet()) {
if (entry.getValue() > maxCount) {
maxCount = entry.getValue();
mainSuit = entry.getKey();
}
}
return mainSuit;
}
/**
* 判断是否有非主要花色的牌
*/
private boolean hasNonMainSuitCards(Map<Integer, Integer> suitCount, int mainSuit) {
for (Map.Entry<Integer, Integer> entry : suitCount.entrySet()) {
if (entry.getKey() != mainSuit && entry.getValue() > 0) {
return true;
}
}
return false;
}
/**
* 从同花色牌中选择要打出的牌(清一色碰碰胡策略)
*/
private String discardFromSameSuitPengPeng(List<Integer> handCards, int mainSuit) {
// 统计每张牌的数量
Map<Integer, Integer> cardCount = countCardOccurrences(handCards);
// 找出刻子、将牌候选和剩余牌
List<Integer> keziCards = new ArrayList<>();
List<Integer> jiangCandidates = new ArrayList<>(); // 258将牌候选
List<Integer> otherJiangCandidates = new ArrayList<>(); // 其他将牌候选
List<Integer> remainingCards = new ArrayList<>(handCards);
// 1. 先找出刻子(三张相同的牌)
findAndRemoveKezi(remainingCards, cardCount, keziCards);
findJiangCandidates(remainingCards, jiangCandidates, otherJiangCandidates);
// 3. 选择要打出的牌
int discardCard = selectDiscardCardPengPeng(remainingCards, otherJiangCandidates, cardCount, handCards);
logInfo("清一色碰碰胡策略打出: " + getCardName(discardCard));
return String.valueOf(discardCard);
}
/**
* 找出将牌候选
*/
private void findJiangCandidates(List<Integer> cards, List<Integer> jiangCandidates, List<Integer> otherJiangCandidates) {
Map<Integer, Integer> cardCount = countCardOccurrences(cards);
for (int card : cards) {
int count = cardCount.get(card);
if (count >= 2) {
otherJiangCandidates.add(card);
} else if (count == 1) {
otherJiangCandidates.add(card);
}
}
}
/**
* 选择要打出的牌(清一色碰碰胡策略)
*/
private int selectDiscardCardPengPeng(List<Integer> remainingCards,
List<Integer> otherJiangCandidates,
Map<Integer, Integer> cardCount
, List<Integer> handCards) {
// 如果还有剩余牌,优先从剩余牌中选择
if (!remainingCards.isEmpty()) {
// 策略1: 优先打出孤张(没有相邻牌的牌)
Integer isolatedCard = findIsolatedCard(remainingCards, cardCount);
if (isolatedCard != null) {
return isolatedCard;
}
// 策略2: 优先打出非258的牌
Integer non258Card = findNon258Card(remainingCards);
if (non258Card != null) {
return non258Card;
}
// 策略3: 打出手牌数量最少的牌
return findLeastCountCard(remainingCards, cardCount);
}
if (!otherJiangCandidates.isEmpty()) {
return otherJiangCandidates.get(0);
}
// 默认情况(理论上不会执行到这里)
logInfo("警告:无法选择出牌,使用默认策略");
return remainingCards.isEmpty() ? handCards.get(0) : remainingCards.get(0);
}
/**
* 查找手牌数量最少的牌
*/
private int findLeastCountCard(List<Integer> cards, Map<Integer, Integer> cardCount) {
int minCount = Integer.MAX_VALUE;
int resultCard = cards.get(0);
for (int card : cards) {
int count = cardCount.get(card);
if (count < minCount) {
minCount = count;
resultCard = card;
}
}
return resultCard;
}
/**
* 查找非258的牌
*/
private Integer findNon258Card(List<Integer> cards) {
for (int card : cards) {
int value = card % 100;
if (value != 2 && value != 5 && value != 8) {
return card;
}
}
return null;
}
public int selectCardToDiscardForAllSameSuit(List<Integer> handCards, List<Integer> chowGroup, List<Integer> pengGroup) {
Integer mainSuit = getMainSuit(handCards);
if (mainSuit == null) {
logInfo("未找到主要花色,使用默认策略");
return -1;
}
//如果吃和碰的牌 包含主要花色的牌,放弃走清一色
if (chowGroup != null && pengGroup != null && chowGroup.size() > 0 && pengGroup.size() > 0) {
chowGroup.addAll(pengGroup);
for (Integer integer : chowGroup) {
String str = integer.toString();
if (!str.isEmpty()) {
int firstDigit = Character.getNumericValue(str.charAt(0));
if (mainSuit.equals(firstDigit)) {
return -1;
}
}
}
}
// boolean allSameSuit = isAllSameSuit(handCards, mainSuit);
String mainSuitName = getSuitName(mainSuit);
logInfo("执行清一色策略,主要花色: " + mainSuitName);
// 统计每张牌的数量
Map<Integer, Integer> cardCountMap = new HashMap<>();
for (Integer card : handCards) {
cardCountMap.put(card, cardCountMap.getOrDefault(card, 0) + 1);
}
// 收集非主要花色的牌
List<Integer> nonMainSuitCards = new ArrayList<>();
for (Integer card : handCards) {
int suit = card / 100;
if (suit != mainSuit) {
nonMainSuitCards.add(card);
}
}
// 如果有非主要花色的牌,优先打出这些牌
if (!nonMainSuitCards.isEmpty()) {
logInfo("有非主要花色牌,优先打出");
// 按优先级打出非主要花色牌
Integer cardToDiscard = selectNonMainSuitCard(nonMainSuitCards, cardCountMap);
if (cardToDiscard != null) {
return cardToDiscard;
}
}
// 如果所有牌都是主要花色,使用清一色优化策略
logInfo("所有牌都是主要花色,使用清一色优化策略");
return selectCardFromSameSuit(handCards, cardCountMap, mainSuit);
}
public boolean isAllSameSuit(List<Integer> handCards, Integer mainSuit) {
// 提取主花色牌
List<Integer> mainSuitCards = new ArrayList<>();
for (Integer card : handCards) {
int suit = card / 100;
if (suit == mainSuit) {
mainSuitCards.add(card);
}
}
Map<String, List<List<Integer>>> stringListMap = analyzeMainSuitPatterns(mainSuitCards);
List<List<Integer>> sequences = stringListMap.get("sequences"); // 顺子列表
List<List<Integer>> triplets = stringListMap.get("triplets"); // 刻子列表
List<List<Integer>> pairs = stringListMap.get("pairs"); // 对子列表
// 获取数量
int sequenceCount = sequences.size();
int tripletCount = triplets.size();
// 判断顺子加上刻子的数量是否大于6
boolean isSequenceAndTripletGreaterThan6 = (sequenceCount + tripletCount) > 6;
return isSequenceAndTripletGreaterThan6;
}
/**
* 分析主花色牌组中的顺子、刻子、对子
*
* @param mainSuitCards 主花色牌组
* @return 包含顺子、刻子、对子信息的Map
*/
private Map<String, List<List<Integer>>> analyzeMainSuitPatterns(List<Integer> mainSuitCards) {
Map<String, List<List<Integer>>> patterns = new HashMap<>();
patterns.put("sequences", new ArrayList<>()); // 顺子
patterns.put("triplets", new ArrayList<>()); // 刻子
patterns.put("pairs", new ArrayList<>()); // 对子
if (mainSuitCards == null || mainSuitCards.isEmpty()) {
return patterns;
}
// 提取所有点数
List<Integer> points = new ArrayList<>();
for (Integer card : mainSuitCards) {
int point = card % 100;
points.add(point);
}
Collections.sort(points);
// 统计每个点数的数量
Map<Integer, Integer> pointCountMap = new HashMap<>();
for (Integer point : points) {
pointCountMap.put(point, pointCountMap.getOrDefault(point, 0) + 1);
}
// 找出刻子(三张相同的牌)
for (Map.Entry<Integer, Integer> entry : pointCountMap.entrySet()) {
if (entry.getValue() >= 3) {
List<Integer> triplet = new ArrayList<>();
for (int i = 0; i < 3; i++) {
triplet.add(entry.getKey());
}
patterns.get("triplets").add(triplet);
}
}
// 找出对子
for (Map.Entry<Integer, Integer> entry : pointCountMap.entrySet()) {
if (entry.getValue() >= 2) {
List<Integer> pair = new ArrayList<>();
for (int i = 0; i < 2; i++) {
pair.add(entry.getKey());
}
patterns.get("pairs").add(pair);
}
}
// 找出顺子(需要从剩余牌中找)
// 先复制一份牌用于顺子检测(避免刻子/对子使用的牌影响顺子检测)
List<Integer> remainingPoints = new ArrayList<>(points);
// 移除刻子使用的牌(如果有的话)
for (List<Integer> triplet : patterns.get("triplets")) {
int point = triplet.get(0);
for (int i = 0; i < 3; i++) {
remainingPoints.remove((Integer) point);
}
}
// 检查顺子
for (int start = 1; start <= 7; start++) {
boolean canFormSequence = true;
List<Integer> tempPoints = new ArrayList<>(remainingPoints);
List<Integer> sequence = new ArrayList<>();
// 检查是否有连续的3个点数
for (int i = 0; i < 3; i++) {
int currentPoint = start + i;
if (!tempPoints.remove((Integer) currentPoint)) {
canFormSequence = false;
break;
}
sequence.add(currentPoint);
}
if (canFormSequence) {
patterns.get("sequences").add(sequence);
// 从剩余牌中移除这三个点数
for (int i = 0; i < 3; i++) {
remainingPoints.remove((Integer) (start + i));
}
}
}
return patterns;
}
/**
* 选择非主要花色牌打出
*/
private Integer selectNonMainSuitCard(List<Integer> nonMainSuitCards, Map<Integer, Integer> cardCountMap) {
// TODO: 2026/1/1 待测试 如果走清一色 ,打出非同花色的牌也要优先打出孤张,如果没有再走之前的其他逻辑
// // 新增:找出真正的孤张(不能组成顺子或刻子的单张)
// List<Integer> realSingleCards = new ArrayList<>();
//
// // 按牌的类型分组
// Map<Integer, List<Integer>> cardsByType = new HashMap<>();
// for (Integer card : nonMainSuitCards) {
// int type = card / 100; // 牌的类型
// cardsByType.putIfAbsent(type, new ArrayList<>());
// cardsByType.get(type).add(card % 100); // 只存牌值
// }
//
// // 分析每种类型的牌,找出真正的孤张
// for (Map.Entry<Integer, List<Integer>> entry : cardsByType.entrySet()) {
// int type = entry.getKey();
// List<Integer> values = entry.getValue();
// Collections.sort(values);
//
// // 标记哪些牌是顺子或刻子的一部分
// boolean[] usedInMeld = new boolean[values.size()];
//
// // 检查刻子(三张相同)
// for (int i = 0; i <= values.size() - 3; i++) {
// if (values.get(i).equals(values.get(i + 1)) &&
// values.get(i).equals(values.get(i + 2))) {
// usedInMeld[i] = usedInMeld[i + 1] = usedInMeld[i + 2] = true;
// i += 2; // 跳过这三张
// }
// }
//
// // 检查顺子(三张连续)
// for (int i = 0; i <= values.size() - 3; i++) {
// if (!usedInMeld[i] && !usedInMeld[i + 1] && !usedInMeld[i + 2]) {
// int v1 = values.get(i);
// int v2 = values.get(i + 1);
// int v3 = values.get(i + 2);
//
// // 检查是否连续注意麻将的1和9不连续
// if (v2 - v1 == 1 && v3 - v2 == 1) {
// // 排除1-9这样的不连续情况
// if (!(v1 == 1 && v3 == 9)) { // 1和9不连续
// usedInMeld[i] = usedInMeld[i + 1] = usedInMeld[i + 2] = true;
// i += 2; // 跳过这三张
// }
// }
// }
// }
//
// // 找出真正的孤张(没有被顺子或刻子使用的牌)
// for (int i = 0; i < values.size(); i++) {
// if (!usedInMeld[i] && cardCountMap.get(type * 100 + values.get(i)) == 1) {
// realSingleCards.add(type * 100 + values.get(i));
// }
// }
// }
//
// // 1. 优先打出手牌中真正的孤张牌
// if (!realSingleCards.isEmpty()) {
// // 在真正的孤张中优先打边张(1和9)
// Integer edgeCard = findEdgeCard(realSingleCards);
// if (edgeCard != null) {
// logInfo("打出非主要花色的边张孤牌: " + edgeCard);
// return edgeCard;
// }
// // 没有边张就打任意孤张
// logInfo("打出非主要花色的孤张牌: " + realSingleCards.get(0));
// return realSingleCards.get(0);
// }
// 1. 优先打出手牌中数量最少的单张牌
List<Integer> singleCards = new ArrayList<>();
for (Integer card : nonMainSuitCards) {
if (cardCountMap.get(card) == 1) {
singleCards.add(card);
}
}
if (!singleCards.isEmpty()) {
// 在单张牌中优先打边张(1和9)
Integer edgeCard = findEdgeCard(singleCards);
if (edgeCard != null) {
logInfo("打出非主要花色的边张单牌: " + edgeCard);
return edgeCard;
}
// 没有边张就打任意单张
logInfo("打出非主要花色的单张牌: " + singleCards.get(0));
return singleCards.get(0);
}
// 2. 拆数量最少的对子
for (Integer card : nonMainSuitCards) {
if (cardCountMap.get(card) == 2) {
logInfo("拆非主要花色的对子: " + card);
return card;
}
}
// 3. 最后随便选一张非主要花色的牌
logInfo("打出非主要花色的牌: " + nonMainSuitCards.get(0));
return nonMainSuitCards.get(0);
}
/**
* 从同花色牌中选择要打出的牌
*/
private int selectCardFromSameSuit(List<Integer> handCards, Map<Integer, Integer> cardCountMap, int mainSuit) {
// 分离出刻子、顺子、将牌和剩余牌
List<Integer> kezi = new ArrayList<>();
List<Integer> shunzi = new ArrayList<>();
List<Integer> jiang = new ArrayList<>();
List<Integer> remainingCards = new ArrayList<>(handCards);
// 先找出刻子(三张相同的牌)
findAndRemoveKezi(remainingCards, cardCountMap, kezi);
// 再找出顺子
findAndRemoveShunzi(remainingCards, mainSuit, shunzi);
// 找出将牌(258对子)
// findAndRemoveJiang(remainingCards, jiang);
// 如果还有剩余牌,从剩余牌中选择要打出的牌
if (!remainingCards.isEmpty()) {
return selectFromRemainingCards(remainingCards, cardCountMap);
}
// 如果没有剩余牌,说明牌型很好,考虑拆散一组来优化
return selectCardFromFormedGroups(handCards, cardCountMap, mainSuit);
}
/**
* 找出并移除刻子
*/
private void findAndRemoveKezi(List<Integer> cards, Map<Integer, Integer> cardCountMap, List<Integer> kezi) {
// 创建临时副本避免修改遍历中的列表
List<Integer> tempCards = new ArrayList<>(cards);
Set<Integer> processedCards = new HashSet<>(); // 记录已处理的牌
for (Integer card : tempCards) {
// 如果这张牌已经处理过或者数量不足3张跳过
if (processedCards.contains(card) || cardCountMap.get(card) < 3) {
continue;
}
// 找到刻子从原始cards列表中移除3张相同的牌
int removeCount = 0;
Iterator<Integer> iterator = cards.iterator();
while (iterator.hasNext() && removeCount < 3) {
Integer currentCard = iterator.next();
if (currentCard.equals(card)) {
iterator.remove();
removeCount++;
}
}
// 将刻子添加到结果列表3次
kezi.add(card);
kezi.add(card);
kezi.add(card);
// 标记这张牌已处理
processedCards.add(card);
logInfo("找到刻子: " + getCardName(card) + " (数量: " + cardCountMap.get(card) + ")");
}
}
/**
* 找出并移除顺子
*/
private void findAndRemoveShunzi(List<Integer> cards, int mainSuit, List<Integer> shunzi) {
List<Integer> sortedCards = new ArrayList<>(cards);
Collections.sort(sortedCards);
for (int i = 0; i <= sortedCards.size() - 3; i++) {
int card1 = sortedCards.get(i);
int card2 = sortedCards.get(i + 1);
int card3 = sortedCards.get(i + 2);
// 检查是否构成顺子
if (isShunzi(card1, card2, card3, mainSuit)) {
// 移除顺子
cards.remove((Integer) card1);
cards.remove((Integer) card2);
cards.remove((Integer) card3);
shunzi.add(card1);
shunzi.add(card2);
shunzi.add(card3);
i += 2; // 跳过已处理的牌
}
}
}
/**
* 判断三张牌是否构成顺子
*/
private boolean isShunzi(int card1, int card2, int card3, int mainSuit) {
int value1 = card1 % 100;
int value2 = card2 % 100;
int value3 = card3 % 100;
return value2 == value1 + 1 && value3 == value2 + 1;
}
/**
* 找出并移除将牌(258对子)
*/
private void findAndRemoveJiang(List<Integer> cards, List<Integer> jiang) {
Map<Integer, Integer> tempCount = new HashMap<>();
for (Integer card : cards) {
tempCount.put(card, tempCount.getOrDefault(card, 0) + 1);
}
// 优先找258对子作为将牌
for (Integer card : cards) {
int value = card % 100;
if ((value == 2 || value == 5 || value == 8) && tempCount.get(card) >= 2) {
// 找到将牌
jiang.add(card);
jiang.add(card);
cards.remove(card);
cards.remove(card);
return;
}
}
// 如果没有258对子找任意对子作为将牌
for (Integer card : cards) {
if (tempCount.get(card) >= 2) {
jiang.add(card);
jiang.add(card);
cards.remove(card);
cards.remove(card);
return;
}
}
}
/**
* 从剩余牌中选择要打出的牌
*/
private int selectFromRemainingCards(List<Integer> remainingCards, Map<Integer, Integer> cardCountMap) {
// 优先打出孤张(没有相邻牌的牌)
Integer isolatedCard = findIsolatedCard(remainingCards, cardCountMap);
if (isolatedCard != null) {
logInfo("打出孤张: " + isolatedCard);
return isolatedCard;
}
// 其次打出手牌数量最多的牌(保留单张和刻子胚子)
Integer mostCountCard = findMostCountCard(remainingCards, cardCountMap);
if (mostCountCard != null) {
logInfo("打出手牌数量多的牌: " + mostCountCard);
return mostCountCard;
}
// 最后打边张(1和9)
Integer edgeCard = findEdgeCard(remainingCards);
if (edgeCard != null) {
logInfo("打出边张: " + edgeCard);
return edgeCard;
}
// 默认打出第一张
logInfo("打出默认牌: " + remainingCards.get(0));
return remainingCards.get(0);
}
/**
* 从已组成的牌组中选择要拆打的牌
*/
private int selectCardFromFormedGroups(List<Integer> handCards, Map<Integer, Integer> cardCountMap, int mainSuit) {
// 这里可以实现更复杂的策略,比如拆散效率最低的顺子或刻子
// 简单实现:打出手牌数量最少的牌
int minCount = Integer.MAX_VALUE;
Integer cardToDiscard = null;
for (Integer card : handCards) {
int count = cardCountMap.get(card);
if (count < minCount) {
minCount = count;
cardToDiscard = card;
}
}
logInfo("从已组成牌组中打出: " + cardToDiscard);
return cardToDiscard;
}
/**
* 查找孤张(没有相邻牌的牌)
*/
private Integer findIsolatedCard(List<Integer> cards, Map<Integer, Integer> cardCountMap) {
// 统计每张牌的出现次数
Map<Integer, Integer> countMap = new HashMap<>();
for (int card : cards) {
countMap.put(card, countMap.getOrDefault(card, 0) + 1);
}
// 寻找出现次数为 1 的牌(非对子)
for (int card : cards) {
if (countMap.get(card) == 1) {
return card;
}
}
// 如果没有非对子的牌,返回 null 或选择第一张牌等策略
return cards.get(0);
}
/**
* 查找手牌数量最多的牌
*/
private Integer findMostCountCard(List<Integer> cards, Map<Integer, Integer> cardCountMap) {
int maxCount = 0;
Integer result = null;
for (Integer card : cards) {
int count = cardCountMap.get(card);
if (count > maxCount) {
maxCount = count;
result = card;
}
}
return result;
}
/**
* 查找边张(1和9)
*/
private Integer findEdgeCard(List<Integer> cards) {
for (Integer card : cards) {
int value = card % 100;
if (value == 1 || value == 9) {
return card;
}
}
return null;
}
/**
* handCards :摸牌后的手牌
* 分析手牌,将顺子,刻子,将 另存
*/
public HandAnalysis analyzeHand(List<Integer> handCards) {
logInfo("\n===== 开始手牌分析 =====");
logInfo("待分析手牌: " + handCards);
// 创建分析结果对象
HandAnalysis analysis = new HandAnalysis();
// 1. 统计每张牌的数量并按花色分组
countCardsAndGroupBySuit(handCards, analysis);
// 2. 识别刻子(三张相同)
identifyTriplets(analysis);
// 4. 识别顺子(三张连续,仅分析剩余的牌)
identifySequences(analysis);
// 3. 优先识别对子(两张相同)
identifyPairs(analysis);
// 5. 识别孤张牌
identifyIsolatedCards(analysis);
// 6. 检测听牌状态
detectTingPai(analysis);
return analysis;
}
/**
* 统计每张牌的数量并按花色分组
*/
private void countCardsAndGroupBySuit(List<Integer> handCards, HandAnalysis analysis) {
Map<Integer, Integer> counts = new HashMap<>();
Map<Integer, List<Integer>> bySuit = new HashMap<>();
// 初始化花色分组1:万, 2:筒, 3:条)
bySuit.put(1, new ArrayList<>());
bySuit.put(2, new ArrayList<>());
bySuit.put(3, new ArrayList<>());
// 统计数量并按花色分组
for (int card : handCards) {
counts.put(card, counts.getOrDefault(card, 0) + 1);
int suit = card / 100;
if (bySuit.containsKey(suit)) {
bySuit.get(suit).add(card);
}
}
// 对每个花色的牌进行排序
for (List<Integer> suitCards : bySuit.values()) {
suitCards.sort(Integer::compareTo);
}
analysis.cardCounts = counts;
analysis.cardsBySuit = bySuit;
analysis.remainingCards = new ArrayList<>(handCards);
analysis.remainingCards.sort(Integer::compareTo);
}
/**
* 识别刻子(三张相同的牌)
*/
private void identifyTriplets(HandAnalysis analysis) {
List<Integer> cardsToRemove = new ArrayList<>();
for (Map.Entry<Integer, Integer> entry : analysis.cardCounts.entrySet()) {
int card = entry.getKey();
int count = entry.getValue();
// 如果某张牌有3张或以上识别为刻子
if (count >= 3) {
// 添加刻子
List<Integer> triplet = Arrays.asList(card, card, card);
analysis.completedMelds.add(triplet);
analysis.meldCount++;
// 标记为已使用
for (int i = 0; i < 3; i++) {
analysis.usedInMelds.add(card);
}
// 记录需要从剩余牌中移除的牌
for (int i = 0; i < 3; i++) {
cardsToRemove.add(card);
}
}
}
// 从剩余牌中移除已识别为刻子的牌
for (int card : cardsToRemove) {
analysis.remainingCards.remove(Integer.valueOf(card));
}
}
/**
* 识别顺子(三张连续的牌,同一花色)
*/
private void identifySequences(HandAnalysis analysis) {
// 按花色分析顺子,只使用剩余的牌
Map<Integer, List<Integer>> remainingCardsBySuit = new HashMap<>();
for (int card : analysis.remainingCards) {
if (!analysis.usedInMelds.contains(card)) {
int suit = card / 100;
remainingCardsBySuit.computeIfAbsent(suit, k -> new ArrayList<>()).add(card);
}
}
for (Map.Entry<Integer, List<Integer>> entry : remainingCardsBySuit.entrySet()) {
int suit = entry.getKey();
List<Integer> suitCards = entry.getValue();
if (suitCards.size() < 3) continue;
suitCards.sort(Integer::compareTo);
// 创建牌的计数映射,用于跟踪每张牌的数量
Map<Integer, Integer> cardCount = new HashMap<>();
for (int card : suitCards) {
cardCount.put(card, cardCount.getOrDefault(card, 0) + 1);
}
// 遍历所有可能的顺子起始点
for (int startCard : suitCards) {
int secondCard = startCard + 1;
int thirdCard = startCard + 2;
// 检查是否同一花色且连续
if (secondCard / 100 == suit && thirdCard / 100 == suit &&
cardCount.getOrDefault(startCard, 0) > 0 &&
cardCount.getOrDefault(secondCard, 0) > 0 &&
cardCount.getOrDefault(thirdCard, 0) > 0) {
// 添加顺子
List<Integer> sequence = Arrays.asList(startCard, secondCard, thirdCard);
analysis.completedMelds.add(sequence);
analysis.meldCount++;
// 从计数中移除(每张牌只用一次)
cardCount.put(startCard, cardCount.get(startCard) - 1);
cardCount.put(secondCard, cardCount.get(secondCard) - 1);
cardCount.put(thirdCard, cardCount.get(thirdCard) - 1);
// 更新剩余牌和已用牌
analysis.remainingCards.remove(Integer.valueOf(startCard));
analysis.remainingCards.remove(Integer.valueOf(secondCard));
analysis.remainingCards.remove(Integer.valueOf(thirdCard));
analysis.usedInMelds.add(startCard);
analysis.usedInMelds.add(secondCard);
analysis.usedInMelds.add(thirdCard);
}
}
}
}
/**
* 识别对子(两张相同的牌)
*/
private void identifyPairs(HandAnalysis analysis) {
// 统计剩余牌中每张牌的数量
Map<Integer, Integer> remainingCounts = new HashMap<>();
for (int card : analysis.remainingCards) {
if (!analysis.usedInMelds.contains(card)) {
remainingCounts.put(card, remainingCounts.getOrDefault(card, 0) + 1);
}
}
// 识别对子
for (Map.Entry<Integer, Integer> entry : remainingCounts.entrySet()) {
int card = entry.getKey();
int count = entry.getValue();
if (count >= 2) {
analysis.pairs.add(card);
analysis.pairCount++;
// 标记为已使用
analysis.usedInPairs.add(card);
// 将对子的牌从剩余牌中移除
for (int i = 0; i < 2; i++) {
analysis.remainingCards.remove(Integer.valueOf(card));
}
// 检查是否为特殊对子最后一位数字为2、5、8
int lastDigit = card % 10;
if (lastDigit == 2 || lastDigit == 5 || lastDigit == 8) {
int suit = card / 100;
String suitName = getSuitName(suit);
logInfo("识别到特殊对子: " + card + " (" + suitName + lastDigit + ")");
}
}
}
// 检测是否有碰碰胡潜力(如果大部分牌都是刻子或对子)
if (analysis.meldCount >= 3 && analysis.pairCount >= 1) {
analysis.hasPengPengHu = true;
}
}
/**
* 获取花色名称
*/
private String getSuitName(int suit) {
switch (suit) {
case 1:
return "万";
case 2:
return "筒";
case 3:
return "条";
default:
return "未知";
}
}
/**
* 识别孤张牌(未组成任何牌型的单张牌)
*/
private void identifyIsolatedCards(HandAnalysis analysis) {
// 统计剩余牌中每张牌的数量
Map<Integer, Integer> remainingCounts = new HashMap<>();
for (int card : analysis.remainingCards) {
remainingCounts.put(card, remainingCounts.getOrDefault(card, 0) + 1);
}
// 识别孤张牌
for (Map.Entry<Integer, Integer> entry : remainingCounts.entrySet()) {
int card = entry.getKey();
int count = entry.getValue();
// 如果牌未用于刻子或顺子,且不是对子,则为孤张
if (!analysis.usedInPairs.contains(card)) {
for (int i = 0; i < count; i++) {
analysis.isolatedCards.add(card);
}
}
}
// 检测是否有龙七对潜力(如果有多个对子和孤张)
if (analysis.pairCount >= 4) {
analysis.hasLongQiDuiPotential = true;
}
}
/**
* 检测听牌状态
*/
private void detectTingPai(HandAnalysis analysis) {
// 创建完整手牌列表
List<Integer> fullHand = new ArrayList<>(analysis.remainingCards);
for (int card : analysis.usedInMelds) {
fullHand.add(card);
}
for (int card : analysis.usedInPairs) {
fullHand.add(card);
fullHand.add(card);
}
int totalCards = fullHand.size();
// 对于14张牌的情况需要去掉一张牌后检查是否为和牌牌型
if (totalCards == 14) {
// 尝试去掉每张牌,检查是否可以和牌
for (int card : new HashSet<>(fullHand)) {
List<Integer> tempHand = new ArrayList<>(fullHand);
tempHand.remove(Integer.valueOf(card));
if (canHu(tempHand, analysis)) {
analysis.tingCards.add(card);
}
}
}
// 对于13张牌的情况检查摸到哪些牌可以和牌
else if (totalCards == 13) {
// 尝试添加每张可能的牌,检查是否可以和牌
for (int suit = 1; suit <= 3; suit++) { // 1:万, 2:筒, 3:条
for (int rank = 1; rank <= 9; rank++) {
int card = suit * 100 + rank;
// 检查这张牌是否已经在手中有4张麻将中每种牌最多4张
int count = 0;
for (int c : fullHand) {
if (c == card) {
count++;
}
}
if (count >= 4) continue;
List<Integer> tempHand = new ArrayList<>(fullHand);
tempHand.add(card);
if (canHu(tempHand, analysis)) {
analysis.tingCards.add(card);
}
}
}
}
// 如果有可胡的牌,则为听牌状态
analysis.isTingPai = !analysis.tingCards.isEmpty();
// 计算向听数
calculateShanten(analysis);
}
/**
* 判断手牌是否符合和牌条件(基本牌型:四组合一对将)
*/
private boolean canHu(List<Integer> hand, HandAnalysis analysis) {
// 统计牌的数量
Map<Integer, Integer> tempCounts = new HashMap<>();
for (int card : hand) {
tempCounts.put(card, tempCounts.getOrDefault(card, 0) + 1);
}
// 尝试找出一对将牌
for (Map.Entry<Integer, Integer> entry : tempCounts.entrySet()) {
int card = entry.getKey();
int count = entry.getValue();
if (count >= 2) {
// 假设这对牌作为将牌
Map<Integer, Integer> copyCounts = new HashMap<>(tempCounts);
copyCounts.put(card, count - 2);
// 检查剩余的牌是否可以组成四组合(刻子或顺子)
if (checkAllMelds(copyCounts)) {
return true;
}
}
}
return false;
}
/**
* 检查剩余的牌是否可以全部组成刻子或顺子
*/
private boolean checkAllMelds(Map<Integer, Integer> counts) {
// 创建剩余牌的列表排除数量为0的牌
List<Integer> remainingCards = new ArrayList<>();
for (Map.Entry<Integer, Integer> entry : counts.entrySet()) {
int card = entry.getKey();
int count = entry.getValue();
for (int i = 0; i < count; i++) {
remainingCards.add(card);
}
}
// 排序以便分析
remainingCards.sort(Integer::compareTo);
// 递归检查是否可以组成全部的刻子或顺子
return checkMeldsRecursive(remainingCards);
}
/**
* 递归检查是否可以组成刻子或顺子
*/
private boolean checkMeldsRecursive(List<Integer> cards) {
if (cards.isEmpty()) {
return true; // 所有牌都已组成合法牌型
}
int firstCard = cards.get(0);
// 尝试组成刻子(三张相同)
if (cards.size() >= 3 && firstCard == cards.get(1) && firstCard == cards.get(2)) {
List<Integer> newCards = new ArrayList<>(cards.subList(3, cards.size()));
if (checkMeldsRecursive(newCards)) {
return true;
}
}
// 尝试组成顺子(三张连续)
if (cards.size() >= 3) {
int secondCard = firstCard + 1;
int thirdCard = firstCard + 2;
// 确保是同一花色
int suit = firstCard / 100;
if (suit == secondCard / 100 && suit == thirdCard / 100) {
List<Integer> tempCards = new ArrayList<>(cards);
tempCards.remove(Integer.valueOf(firstCard));
// 查找第二张牌
boolean foundSecond = false;
for (int i = 0; i < tempCards.size(); i++) {
if (tempCards.get(i) == secondCard) {
tempCards.remove(i);
foundSecond = true;
break;
}
}
if (foundSecond) {
// 查找第三张牌
boolean foundThird = false;
for (int i = 0; i < tempCards.size(); i++) {
if (tempCards.get(i) == thirdCard) {
tempCards.remove(i);
foundThird = true;
break;
}
}
if (foundThird && checkMeldsRecursive(tempCards)) {
return true;
}
}
}
}
return false;
}
/**
* 计算向听数(距离听牌还有几张牌)
* 优化的向听数计算算法,考虑了面子、对子和搭子的组合
*/
private void calculateShanten(HandAnalysis analysis) {
// 如果已经听牌向听数为0
if (analysis.isTingPai) {
analysis.shantenCount = 0;
return;
}
// 创建完整手牌列表
List<Integer> fullHand = new ArrayList<>(analysis.remainingCards);
for (int card : analysis.usedInMelds) {
fullHand.add(card);
}
for (int card : analysis.usedInPairs) {
fullHand.add(card);
fullHand.add(card);
}
int totalCards = fullHand.size();
if (totalCards != 13 && totalCards != 14) {
analysis.shantenCount = Integer.MAX_VALUE;
return;
}
// 统计每种牌的数量
Map<Integer, Integer> counts = new HashMap<>();
for (int card : fullHand) {
counts.put(card, counts.getOrDefault(card, 0) + 1);
}
// 计算最小向听数
int minShanten = Integer.MAX_VALUE;
// 尝试每种可能的将牌(对子)
Set<Integer> possiblePairs = new HashSet<>(counts.keySet());
// 添加没有对子的情况(需要摸一张牌形成对子)
possiblePairs.add(-1);
for (int pairCard : possiblePairs) {
Map<Integer, Integer> tempCounts = new HashMap<>(counts);
int pairShanten = 0;
// 如果选择了具体的将牌
if (pairCard != -1) {
int pairCount = tempCounts.get(pairCard);
if (pairCount >= 2) {
tempCounts.put(pairCard, pairCount - 2);
} else {
// 需要摸一张牌形成对子
pairShanten = 1;
tempCounts.put(pairCard, tempCounts.getOrDefault(pairCard, 0) + 1 - 2);
}
} else {
// 需要摸一张牌形成对子
pairShanten = 1;
}
// 计算剩余牌的向听数
int meldShanten = calculateMeldShanten(tempCounts);
int totalShanten = pairShanten + meldShanten;
// 更新最小向听数
if (totalShanten < minShanten) {
minShanten = totalShanten;
}
}
// 确保向听数不为负数
analysis.shantenCount = Math.max(0, minShanten);
}
/**
* 计算剩余牌(排除将牌后)的面子向听数
*/
private int calculateMeldShanten(Map<Integer, Integer> counts) {
// 创建剩余牌的列表
List<Integer> cards = new ArrayList<>();
for (Map.Entry<Integer, Integer> entry : counts.entrySet()) {
int card = entry.getKey();
int count = entry.getValue();
for (int i = 0; i < count; i++) {
cards.add(card);
}
}
if (cards.isEmpty()) {
return 0;
}
// 按花色和数值排序
cards.sort(Comparator.comparingInt(card -> {
int suit = card / 100;
int rank = card % 100;
return suit * 10 + rank;
}));
// 使用动态规划计算最小向听数
return calculateMeldShantenRecursive(cards, 0);
}
/**
* 递归计算面子向听数
*/
private int calculateMeldShantenRecursive(List<Integer> cards, int index) {
if (index >= cards.size()) {
return 0;
}
int minShanten = Integer.MAX_VALUE;
int currentCard = cards.get(index);
// 尝试作为刻子
if (index + 2 < cards.size() && currentCard == cards.get(index + 1) && currentCard == cards.get(index + 2)) {
int shanten = calculateMeldShantenRecursive(cards, index + 3);
if (shanten < minShanten) {
minShanten = shanten;
}
} else if (index + 1 < cards.size() && currentCard == cards.get(index + 1)) {
// 有对子,可以考虑刻子(需要摸一张)
int shanten = 1 + calculateMeldShantenRecursive(cards, index + 2);
if (shanten < minShanten) {
minShanten = shanten;
}
} else {
// 单张,可以考虑刻子(需要摸两张)
int shanten = 2 + calculateMeldShantenRecursive(cards, index + 1);
if (shanten < minShanten) {
minShanten = shanten;
}
}
// 尝试作为顺子
if (index + 2 < cards.size()) {
int secondCard = currentCard + 1;
int thirdCard = currentCard + 2;
// 检查是否同一花色
int suit = currentCard / 100;
if (secondCard / 100 == suit && thirdCard / 100 == suit) {
// 检查后两张牌是否存在
boolean hasSecond = false;
boolean hasThird = false;
int secondIndex = -1;
int thirdIndex = -1;
for (int i = index + 1; i < cards.size(); i++) {
if (cards.get(i) == secondCard && !hasSecond) {
hasSecond = true;
secondIndex = i;
} else if (cards.get(i) == thirdCard && !hasThird) {
hasThird = true;
thirdIndex = i;
}
}
if (hasSecond && hasThird) {
// 创建新的牌列表,移除组成顺子的三张牌
List<Integer> newCards = new ArrayList<>();
for (int i = 0; i < cards.size(); i++) {
if (i != index && i != secondIndex && i != thirdIndex) {
newCards.add(cards.get(i));
}
}
int shanten = calculateMeldShantenRecursive(newCards, 0);
if (shanten < minShanten) {
minShanten = shanten;
}
} else {
// 缺少牌,需要计算所需的牌数
int needed = 0;
if (!hasSecond) needed++;
if (!hasThird) needed++;
// 创建新的牌列表,移除已有的牌
List<Integer> newCards = new ArrayList<>();
for (int i = 0; i < cards.size(); i++) {
if (i != index) {
if (hasSecond && i == secondIndex) continue;
if (hasThird && i == thirdIndex) continue;
newCards.add(cards.get(i));
}
}
int shanten = needed + calculateMeldShantenRecursive(newCards, 0);
if (shanten < minShanten) {
minShanten = shanten;
}
}
}
} else if (index + 1 < cards.size()) {
// 只有两张连续牌,需要一张牌组成顺子
int nextCard = cards.get(index + 1);
if (nextCard - currentCard == 1 && (currentCard / 100) == (nextCard / 100)) {
List<Integer> newCards = new ArrayList<>(cards);
newCards.remove(index);
newCards.remove(index); // 移除第二张牌
int shanten = 1 + calculateMeldShantenRecursive(newCards, 0);
if (shanten < minShanten) {
minShanten = shanten;
}
}
}
return minShanten;
}
public static int analyzeHandCards(List<Integer> handCards) {
if (handCards == null || handCards.size() != 14) {
System.out.println("错误手牌必须是14张");
return 0;
}
System.out.println("当前手牌(14张): " + convertToReadable(handCards));
System.out.println("======================================");
// 2. 分析手牌结构
List<Integer> sortedHand = new ArrayList<>(handCards);
Collections.sort(sortedHand);
System.out.println("\n=== 手牌结构分析 ===");
// 找出所有刻子
List<List<Integer>> kezis = findKezis(sortedHand);
System.out.println("刻子(" + kezis.size() + "组):");
for (List<Integer> kezi : kezis) {
System.out.println(" " + convertToReadable(kezi));
}
// 找出所有顺子
List<List<Integer>> shunzis = findShunzis(sortedHand);
System.out.println("\n顺子(" + shunzis.size() + "组):");
for (List<Integer> shunzi : shunzis) {
System.out.println(" " + convertToReadable(shunzi));
}
// 找出所有258将牌
List<Integer> jiangCandidates = findJiangCandidates(sortedHand);
System.out.println("\n258将牌候选(" + jiangCandidates.size() + "张):");
System.out.println(" " + convertToReadable(jiangCandidates));
if (kezis.size()>0 || shunzis.size()>0 || jiangCandidates.size()>0){
return kezis.size()+shunzis.size();
}
return 0;
}
/**
* 找出所有刻子
*/
private static List<List<Integer>> findKezis(List<Integer> handCards) {
List<List<Integer>> kezis = new ArrayList<>();
List<Integer> temp = new ArrayList<>(handCards);
while (!temp.isEmpty()) {
int card = temp.get(0);
int count = Collections.frequency(temp, card);
if (count >= 3) {
List<Integer> kezi = new ArrayList<>();
kezi.add(card);
kezi.add(card);
kezi.add(card);
kezis.add(kezi);
// 移除这3张牌
for (int i = 0; i < 3; i++) {
temp.remove(Integer.valueOf(card));
}
} else {
temp.remove(Integer.valueOf(card));
}
}
return kezis;
}
/**
* 找出所有顺子
*/
private static List<List<Integer>> findShunzis(List<Integer> handCards) {
List<List<Integer>> shunzis = new ArrayList<>();
List<Integer> temp = new ArrayList<>(handCards);
// 先移除已找到的刻子
List<List<Integer>> kezis = findKezis(handCards);
for (List<Integer> kezi : kezis) {
for (int i = 0; i < 3; i++) {
temp.remove(Integer.valueOf(kezi.get(0)));
}
}
// 找顺子
Collections.sort(temp);
for (int i = 0; i < temp.size(); i++) {
int card1 = temp.get(i);
int type = getCardType(card1);
int value = getCardValue(card1);
if (type <= 3 && value <= 7) {
int card2 = card1 + 1;
int card3 = card1 + 2;
if (temp.contains(card2) && temp.contains(card3)) {
List<Integer> shunzi = new ArrayList<>();
shunzi.add(card1);
shunzi.add(card2);
shunzi.add(card3);
shunzis.add(shunzi);
// 移除这3张牌
temp.remove(Integer.valueOf(card1));
temp.remove(Integer.valueOf(card2));
temp.remove(Integer.valueOf(card3));
i--; // 因为移除了元素
}
}
}
return shunzis;
}
/**
* 找出258将牌候选
*/
private static List<Integer> findJiangCandidates(List<Integer> handCards) {
List<Integer> jiangs = new ArrayList<>();
for (int card : handCards) {
int value = getCardValue(card);
if (JIANG_PAIS.contains(value)) {
jiangs.add(card);
}
}
return jiangs;
}
/**
* 获取牌值
*/
private static int getCardValue(int card) {
return card % 10;
}
/**
* 获取牌类型
*/
private static int getCardType(int card) {
return card / 100;
}
/**
* 格式化单张牌
*/
private static String formatCard(int card) {
int type = getCardType(card);
int value = getCardValue(card);
switch (type) {
case 1:
return value + "万";
case 2:
return value + "筒";
case 3:
return value + "条";
default:
return "字牌" + value;
}
}
/**
* 转换为可读格式
*/
private static String convertToReadable(List<Integer> cards) {
if (cards == null || cards.isEmpty()) {
return "空";
}
List<String> cardStrs = new ArrayList<>();
for (int card : cards) {
cardStrs.add(formatCard(card));
}
return String.join(" ", cardStrs);
}
public List<Integer> chuguodepai() {
return chuguodepai;
}
}