2026-01-01 07:54:30 +08:00
|
|
|
|
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-2,2-3等
|
|
|
|
|
|
* 卡隆:两张中间缺一张的牌,例如1-3(缺2),2-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-3,2-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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-02 02:19:59 +08:00
|
|
|
|
|
2026-01-01 07:54:30 +08:00
|
|
|
|
for (Map.Entry<Integer, Integer> entry : suitCountMap.entrySet()) {
|
|
|
|
|
|
int suit = entry.getKey();
|
|
|
|
|
|
|
|
|
|
|
|
int count = entry.getValue();
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-02 02:19:59 +08:00
|
|
|
|
if (count >= 11) {
|
2026-01-01 07:54:30 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|