feyerobot/libs/robot_common/src/main/java/taurus/util/TinHuGang.java

406 lines
13 KiB
Java
Raw Normal View History

2026-01-01 07:54:30 +08:00
package taurus.util;
import hunan.HuNanChangSha;
import java.util.*;
public class TinHuGang {
/**
*
*/
public static boolean canGang(List<Integer> handCards, int card, boolean isMingGang) {
int type = card / 100;
int value = card % 100;
System.out.println("\n检查" + (isMingGang ? "明" : "暗") + "杠" + value + getTypeName(type) + ":");
// 检查是否符合杠牌条件
int count = Collections.frequency(handCards, card);
if (isMingGang) {
// 明杠:手牌中需要有三张相同的牌
if (count < 3) {
System.out.println(" 手牌中没有三张相同的" + value + getTypeName(type) + ",不能明杠");
return false;
}
System.out.println(" 发现三张" + value + getTypeName(type) + ",可以明杠");
} else {
// 暗杠:手牌中需要有四张相同的牌
if (count < 4) {
System.out.println(" 手牌中没有四张相同的" + value + getTypeName(type) + ",不能暗杠");
return false;
}
System.out.println(" 发现四张" + value + getTypeName(type) + ",可以暗杠");
}
// 检查杠牌后是否立即听牌(杠牌后手牌本身就是听牌状态)
return canTingImmediatelyAfterGang(handCards, card, isMingGang);
}
/**
*
*/
private static boolean canTingImmediatelyAfterGang(List<Integer> handCards, int gangCard, boolean isMingGang) {
int type = gangCard / 100;
int value = gangCard % 100;
System.out.println("\n模拟" + (isMingGang ? "明" : "暗") + "杠" + value + getTypeName(type) + ":");
// 1. 模拟杠牌:移除手牌中的牌
List<Integer> afterGang = new ArrayList<>(handCards);
if (isMingGang) {
// 明杠:移除手牌中的三张牌
for (int i = 0; i < 3; i++) {
afterGang.remove(Integer.valueOf(gangCard));
}
// 杠牌后手牌数13张 → 10张
} else {
// 暗杠:移除手牌中的四张牌
for (int i = 0; i < 4; i++) {
afterGang.remove(Integer.valueOf(gangCard));
}
// 暗杠后需要补牌,但这里先不处理
}
Collections.sort(afterGang);
System.out.println(" 杠后手牌(" + afterGang.size() + "张): " + convertToReadable(afterGang));
// 2. 检查是否需要258将
boolean needs258 = !checkSuitCount(afterGang);
if (needs258) {
System.out.println(" 花色牌数不足10张需要258做将");
}
// 3. 检查杠牌后手牌本身是否就是听牌状态
// 注意杠牌后手牌数是10张这10张牌本身应该是听牌状态
// 也就是说,随便摸一张牌(任何牌)都能胡牌
System.out.println("\n 检查杠后手牌是否听牌:");
if (afterGang.size() != 10) {
System.out.println(" 手牌数不是10张不符合听牌条件");
return false;
}
// 检查这10张牌是否听牌
boolean canTing = checkIfHandIsTingPai(afterGang, needs258);
if (canTing) {
System.out.println(" ✓ 杠后手牌是听牌状态!");
// 显示听哪些牌
Set<Integer> tingCards = getTingCards(afterGang, needs258);
if (!tingCards.isEmpty()) {
System.out.print(" 听" + tingCards.size() + "张牌: ");
List<String> tingStrs = new ArrayList<>();
for (int tingCard : tingCards) {
tingStrs.add((tingCard%100) + getTypeName(tingCard/100));
}
System.out.println(String.join(", ", tingStrs));
}
return true;
} else {
System.out.println(" ✗ 杠后手牌不是听牌状态");
return false;
}
}
/**
*
*/
private static boolean checkIfHandIsTingPai(List<Integer> hand, boolean needs258) {
if (hand.size() != 10) {
return false;
}
// 听牌状态:再摸任何一张牌都能胡牌
// 我们需要检查是否至少有一张牌能让这手牌胡牌
Set<Integer> allCards = getAllCards();
int tingCount = 0;
for (int testCard : allCards) {
List<Integer> tempHand = new ArrayList<>(hand);
tempHand.add(testCard);
if (canHu(tempHand, needs258)) {
tingCount++;
}
}
System.out.println(" 可胡" + tingCount + "张牌");
return tingCount > 0;
}
/**
*
*/
private static Set<Integer> getTingCards(List<Integer> hand, boolean needs258) {
Set<Integer> tingCards = new HashSet<>();
if (hand.size() != 10) {
return tingCards;
}
Set<Integer> allCards = getAllCards();
for (int testCard : allCards) {
List<Integer> tempHand = new ArrayList<>(hand);
tempHand.add(testCard);
if (canHu(tempHand, needs258)) {
tingCards.add(testCard);
}
}
return tingCards;
}
/**
*
*/
private static boolean canHu(List<Integer> handCards, boolean needs258) {
// 手牌排序
List<Integer> sorted = new ArrayList<>(handCards);
Collections.sort(sorted);
// 胡牌时手牌数必须是3n+2
if (sorted.size() != 11 && sorted.size() != 14) {
return false;
}
// 检查七对子
if (checkQiDuiZi(sorted)) {
// 七对子不需要258将
return true;
}
// 如果需要258将检查普通胡牌必须有258将
if (needs258) {
return checkNormalHuWith258(sorted);
} else {
// 不需要258将检查普通胡牌
return checkNormalHu(sorted);
}
}
/**
*
*/
private static boolean checkQiDuiZi(List<Integer> handCards) {
if (handCards.size() != 14) return false;
Map<Integer, Integer> countMap = new HashMap<>();
for (int card : handCards) {
countMap.put(card, countMap.getOrDefault(card, 0) + 1);
}
// 检查是否都是对子
for (int count : countMap.values()) {
if (count != 2) {
return false;
}
}
return true;
}
/**
* 258
*/
private static boolean checkNormalHuWith258(List<Integer> handCards) {
// 尝试每种258作为将牌
for (int card : handCards) {
int value = card % 100;
// 检查是否是258
if (value == 2 || value == 5 || value == 8) {
// 检查是否有至少2张相同的牌做将
int count = Collections.frequency(handCards, card);
if (count >= 2) {
// 移除将牌
List<Integer> remaining = new ArrayList<>(handCards);
for (int i = 0; i < 2; i++) {
remaining.remove(Integer.valueOf(card));
}
// 检查剩余牌是否能组成顺子/刻子
if (canGroup(remaining)) {
return true;
}
}
}
}
return false;
}
/**
* 258
*/
private static boolean checkNormalHu(List<Integer> handCards) {
return checkNormalHuRecursive(new ArrayList<>(handCards), false);
}
private static boolean checkNormalHuRecursive(List<Integer> handCards, boolean hasJiang) {
if (handCards.isEmpty()) {
return true; // 所有牌都分组成功
}
Collections.sort(handCards);
// 统计第一张牌的数量
int firstCard = handCards.get(0);
int count = Collections.frequency(handCards, firstCard);
// 尝试作为刻子(三张相同)
if (count >= 3) {
List<Integer> remaining = new ArrayList<>(handCards);
for (int i = 0; i < 3; i++) {
remaining.remove(Integer.valueOf(firstCard));
}
if (checkNormalHuRecursive(remaining, hasJiang)) {
return true;
}
}
// 尝试作为顺子(三张连续)
int type = firstCard / 100;
int value = firstCard % 100;
if (type < 4 && value <= 7) { // 字牌和8、9不能组成顺子
int second = type * 100 + (value + 1);
int third = type * 100 + (value + 2);
if (handCards.contains(second) && handCards.contains(third)) {
List<Integer> remaining = new ArrayList<>(handCards);
remaining.remove(Integer.valueOf(firstCard));
remaining.remove(Integer.valueOf(second));
remaining.remove(Integer.valueOf(third));
if (checkNormalHuRecursive(remaining, hasJiang)) {
return true;
}
}
}
// 尝试作为将(对子)- 只能有一个将
if (!hasJiang && count >= 2) {
List<Integer> remaining = new ArrayList<>(handCards);
remaining.remove(Integer.valueOf(firstCard));
remaining.remove(Integer.valueOf(firstCard));
if (checkNormalHuRecursive(remaining, true)) {
return true;
}
}
return false;
}
/**
*
*/
private static boolean canGroup(List<Integer> cards) {
if (cards.isEmpty()) {
return true; // 所有牌都分组成功
}
List<Integer> sorted = new ArrayList<>(cards);
Collections.sort(sorted);
int firstCard = sorted.get(0);
int count = Collections.frequency(sorted, firstCard);
// 尝试作为刻子(三张相同)
if (count >= 3) {
List<Integer> remaining = new ArrayList<>(sorted);
for (int i = 0; i < 3; i++) {
remaining.remove(Integer.valueOf(firstCard));
}
if (canGroup(remaining)) {
return true;
}
}
// 尝试作为顺子(三张连续)
int type = firstCard / 100;
int value = firstCard % 100;
if (type < 4 && value <= 7) { // 字牌和8、9不能组成顺子
int second = type * 100 + (value + 1);
int third = type * 100 + (value + 2);
if (sorted.contains(second) && sorted.contains(third)) {
List<Integer> remaining = new ArrayList<>(sorted);
remaining.remove(Integer.valueOf(firstCard));
remaining.remove(Integer.valueOf(second));
remaining.remove(Integer.valueOf(third));
if (canGroup(remaining)) {
return true;
}
}
}
return false;
}
/**
* >=10
*/
private static boolean checkSuitCount(List<Integer> hand) {
Map<Integer, Integer> suitCount = new HashMap<>();
for (int card : hand) {
int type = card / 100;
if (type < 4) {
suitCount.put(type, suitCount.getOrDefault(type, 0) + 1);
}
}
for (int count : suitCount.values()) {
if (count >= 10) {
return true;
}
}
return false;
}
/**
*
*/
private static Set<Integer> getAllCards() {
Set<Integer> allCards = new HashSet<>();
for (int type = 1; type <= 3; type++) {
for (int value = 1; value <= 9; value++) {
allCards.add(type * 100 + value);
}
}
return allCards;
}
/**
*
*/
private static String convertToReadable(List<Integer> cards) {
StringBuilder sb = new StringBuilder();
for (int card : cards) {
int type = card / 100;
int value = card % 100;
sb.append(value).append(getTypeName(type)).append(" ");
}
return sb.toString();
}
/**
*
*/
private static String getTypeName(int type) {
switch(type) {
case 1: return "万";
case 2: return "筒";
case 3: return "条";
default: return "字";
}
}
}