0711
parent
aa005007d6
commit
a54afc33eb
|
|
@ -412,7 +412,7 @@ public class RobotConnectionManager {
|
|||
robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_WORKING);
|
||||
//初始化收手牌
|
||||
String key = robotId+"";
|
||||
log.info("key+++++++++++++++++++++++++++++++++++" + key);
|
||||
log.info("新的一局key+++++++++++++++++++++++++++++++++++" + key);
|
||||
if (jedis2.hget("{robortInfo}:" + key, "circleId") != null && jedis2.hget("{robortInfo}:" + key, "pid") != null) {
|
||||
String circleId = jedis2.hget("{robortInfo}:" + key, "circleId");
|
||||
log.info("circleId +++++++++++++++++++++++++"+circleId);
|
||||
|
|
@ -431,6 +431,7 @@ public class RobotConnectionManager {
|
|||
}
|
||||
//出牌广播
|
||||
else if ("812".equalsIgnoreCase(command)) {
|
||||
log.info("[协议812] 出牌广播 param:{}", param);
|
||||
ITArray outcard_map = param.getTArray("outcard_map");
|
||||
ITArray opchicards = param.getTArray("opchicards");
|
||||
ITArray oppengcards = param.getTArray("oppengcards");
|
||||
|
|
@ -549,22 +550,40 @@ public class RobotConnectionManager {
|
|||
Map<Integer, List<Integer>> currentPlayerzisMap = getPlayerzisMap(connecId);
|
||||
|
||||
huNanChangSha.outCard(client, currentPlayerOutcardsMap, currentPlayerchisMap, currentPlayerpengsMap, currentPlayermingsMap, currentPlayerzisMap);
|
||||
//处理完协议后保存到Redis
|
||||
HeChi currentInstance = huNanChangShaInstances.get(connecId);
|
||||
currentInstance.saveToRedis(connecId);
|
||||
//处理完协议后保存到Redis(加try-catch避免Redis异常导致机器人卡死)
|
||||
try {
|
||||
HeChi currentInstance = huNanChangShaInstances.get(connecId);
|
||||
if (currentInstance != null) {
|
||||
currentInstance.saveToRedis(connecId);
|
||||
}
|
||||
} catch (Exception redisEx) {
|
||||
log.warn("813处理完成后保存Redis失败(不影响出牌): {}", redisEx.getMessage());
|
||||
}
|
||||
//收到补杠协议
|
||||
}else if("838".equalsIgnoreCase(command)) {
|
||||
huNanChangSha.bugang(param,robotUser,client);
|
||||
//处理完协议后保存到Redis
|
||||
HeChi currentInstance = huNanChangShaInstances.get(connecId);
|
||||
currentInstance.saveToRedis(connecId);
|
||||
//处理完协议后保存到Redis(加try-catch避免Redis异常导致机器人卡死)
|
||||
try {
|
||||
HeChi currentInstance = huNanChangShaInstances.get(connecId);
|
||||
if (currentInstance != null) {
|
||||
currentInstance.saveToRedis(connecId);
|
||||
}
|
||||
} catch (Exception redisEx) {
|
||||
log.warn("838处理完成后保存Redis失败(不影响出牌): {}", redisEx.getMessage());
|
||||
}
|
||||
}
|
||||
//放招提示
|
||||
else if ("814".equalsIgnoreCase(command)) {
|
||||
huNanChangSha.actionCard(param, client);
|
||||
//处理完协议后保存到Redis
|
||||
HeChi currentInstance = huNanChangShaInstances.get(connecId);
|
||||
currentInstance.saveToRedis(connecId);
|
||||
//处理完协议后保存到Redis(加try-catch避免Redis异常导致机器人卡死)
|
||||
try {
|
||||
HeChi currentInstance = huNanChangShaInstances.get(connecId);
|
||||
if (currentInstance != null) {
|
||||
currentInstance.saveToRedis(connecId);
|
||||
}
|
||||
} catch (Exception redisEx) {
|
||||
log.warn("814处理完成后保存Redis失败(不影响出牌): {}", redisEx.getMessage());
|
||||
}
|
||||
}
|
||||
//2026.02.03修改 玩家加入房间
|
||||
else if ("2001".equalsIgnoreCase(command)) {
|
||||
|
|
@ -748,6 +767,7 @@ public class RobotConnectionManager {
|
|||
//服务器通知客户端有玩家执行了操作
|
||||
else if ("815".equalsIgnoreCase(command)) {
|
||||
huNanChangSha.shanchuchuguopai(param);
|
||||
log.info("[协议815] 玩家操作通知 - param:{}", param);
|
||||
//处理完协议后保存到Redis
|
||||
HeChi currentInstance = huNanChangShaInstances.get(connecId);
|
||||
currentInstance.saveToRedis(connecId);
|
||||
|
|
@ -783,8 +803,15 @@ public class RobotConnectionManager {
|
|||
|
||||
});
|
||||
}
|
||||
//服务器返回的错误码(如3005)
|
||||
else if ("3005".equalsIgnoreCase(command)) {
|
||||
System.out.println("[3005错误] 服务器返回错误码3005! param:" + param);
|
||||
log.error("[协议3005] 服务器返回错误! param:{}", param);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理接收到的游戏协议");
|
||||
System.out.println("【ERROR】处理游戏协议异常! 类型=" + e.getClass().getName()
|
||||
+ ", 信息=" + e.getMessage() + ", 协议号=" + command);
|
||||
e.printStackTrace(System.out);
|
||||
} finally {
|
||||
jedis0.close();
|
||||
jedis2.close();
|
||||
|
|
|
|||
|
|
@ -28,6 +28,30 @@ public class HeChi {
|
|||
public static boolean isTinPeng = false;
|
||||
private final List<Integer> changShaCardInhandgang = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 牌号转可读字符串(河池麻将专用)
|
||||
* 101-109=一万~九万, 201-209=一条~九条, 301-309=一筒~九筒
|
||||
* 401=东风,402=南风,403=西风,404=北风, 501=红中,502=发财,503=白板
|
||||
*/
|
||||
private static String cardToString(int card) {
|
||||
int suit = card / 100;
|
||||
int value = card % 100;
|
||||
String[] suitNames = {"", "万", "筒", "条", "风", "箭"};
|
||||
String[] valueNames = {"", "一", "二", "三", "四", "五", "六", "七", "八", "九"};
|
||||
switch (suit) {
|
||||
case 1: case 2: case 3:
|
||||
return valueNames[value] + suitNames[suit] + "(" + card + ")";
|
||||
case 4:
|
||||
String[] windNames = {"", "东风", "南风", "西风", "北风"};
|
||||
return windNames[value] + "(" + card + ")";
|
||||
case 5:
|
||||
String[] arrowNames = {"", "红中", "发财", "白板"};
|
||||
return arrowNames[value] + "(" + card + ")";
|
||||
default:
|
||||
return "未知(" + card + ")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//河池麻将出过的牌
|
||||
private final List<Integer> changShachuguopai = new ArrayList<>();
|
||||
|
|
@ -55,6 +79,25 @@ public class HeChi {
|
|||
private Integer lastChowCard2 = null;
|
||||
private Integer lastChowCard3 = null;
|
||||
|
||||
/**
|
||||
* 追踪最近一次612(吃碰杠)协议的实际发送时间戳
|
||||
* 用于outCard发送611前判断是否需要等待,避免611先于612到达服务器导致3005错误
|
||||
*/
|
||||
private volatile long last612SendTime = 0;
|
||||
|
||||
/**
|
||||
* 检查出牌是否受河池麻将吃牌门子限制
|
||||
* 规则:如果吃的牌属于147/258/369组,同个门子(同花色)同组的牌不能打出
|
||||
* 147组: value%3==1 (1,4,7)
|
||||
* 258组: value%3==2 (2,5,8)
|
||||
* 369组: value%3==0 (3,6,9)
|
||||
* 需要检查吃的三张牌,因为三张牌都可能触发限制
|
||||
*/
|
||||
private boolean isChowMenziRestricted(int discardCard) {
|
||||
// 【已禁用】河池麻将无147/258/369吃牌门子出牌限制规则
|
||||
return false;
|
||||
}
|
||||
|
||||
//会话标识
|
||||
public String session = "";
|
||||
//访问令牌
|
||||
|
|
@ -264,6 +307,9 @@ public class HeChi {
|
|||
if (param == null) {
|
||||
return null;
|
||||
}
|
||||
int card = param.getInt("card");
|
||||
int seat = param.getInt("seat");
|
||||
log.info("[812出牌广播] 座位{}打出牌:{}", seat, cardToString(card));
|
||||
changShaCard = param.getInt("card");
|
||||
}
|
||||
return null;
|
||||
|
|
@ -281,22 +327,37 @@ public class HeChi {
|
|||
if (command.equalsIgnoreCase("819")) {
|
||||
ITObject param = message.param;
|
||||
if (param == null) {
|
||||
System.out.println("[819摸牌-诊断] WARNING: param为空,无法获取摸牌数据!changSaCardInhand当前:" + changShaCardInhand);
|
||||
return null;
|
||||
}
|
||||
|
||||
Jedis jedis222 = Redis.use("group1_db2").getJedis();
|
||||
Integer seat1 = param.getInt("seat");
|
||||
Integer seat2 = robotUser.getSeat();
|
||||
System.out.println("819机器人座位号 " + seat2);
|
||||
System.out.println("819真人座位号 " + seat1);
|
||||
int drawnCard = param.getInt("card");
|
||||
|
||||
// ====== 诊断日志(使用System.out确保可见) ======
|
||||
System.out.println("[819摸牌-诊断] 协议原始数据 - seat(协议):" + seat1 + ", seat(机器人):" + seat2 +
|
||||
", card(摸到的牌):" + drawnCard + "(" + cardToString(drawnCard) + ")");
|
||||
System.out.println("[819摸牌-诊断] 摸牌前手牌(" + changShaCardInhand.size() + "张):" + changShaCardInhand);
|
||||
// ====== 结束诊断 ======
|
||||
|
||||
if (Objects.equals(seat1, seat2)) {
|
||||
System.out.println("819能不能进来");
|
||||
int drawnCard = param.getInt("card");
|
||||
// 是机器人自己摸牌
|
||||
log.info("[819摸牌] 机器人座位{}摸到牌:{}", seat2, cardToString(drawnCard));
|
||||
changShaSuanFaTest.drawnCards = drawnCard;//存储摸到的牌
|
||||
changShaCardInhand.add(drawnCard);
|
||||
log.info("param.getInt(player): {}", param.getInt("player"));
|
||||
log.info("摸到的牌 +++++++++ : {}", drawnCard);
|
||||
|
||||
// ====== 诊断日志 ======
|
||||
System.out.println("[819摸牌-诊断] ✓ 确认是机器人自己摸牌!添加后手牌(" + changShaCardInhand.size() + "张):" + changShaCardInhand);
|
||||
// ====== 结束诊断 ======
|
||||
|
||||
log.info("[819摸牌] 摸牌后手牌:{} (含红中{}张)", changShaCardInhand,
|
||||
changShaCardInhand.stream().filter(c -> c == 501).count());
|
||||
} else {
|
||||
// 是其他玩家摸牌
|
||||
log.info("[819摸牌] 座位{}玩家摸牌(非机器人,机器人座位{})", seat1, seat2);
|
||||
System.out.println("[819摸牌-诊断] ✗ 不是机器人摸牌,跳过。seat1=" + seat1 + ", seat2=" + seat2);
|
||||
}
|
||||
|
||||
jedis222.close();
|
||||
|
|
@ -357,9 +418,11 @@ public class HeChi {
|
|||
|
||||
/**
|
||||
* 延迟执行吃碰胡动作,增加自然的停顿效果
|
||||
* 修复:将本地手牌状态的修改延迟到发送成功后(避免3005手牌错误)
|
||||
*
|
||||
* @param client 客户端连接
|
||||
* @param params 动作参数
|
||||
* @param params 动作参数(包含_pendingType等内部字段用于状态修改)
|
||||
* @param actionName 动作名称
|
||||
*/
|
||||
private void delayedActionCard(TaurusClient client, ITObject params, String actionName) {
|
||||
//使用线程池执行延迟动作
|
||||
|
|
@ -370,7 +433,10 @@ public class HeChi {
|
|||
Thread.sleep(delaySeconds * 1000);
|
||||
|
||||
client.send("612", params, response -> {
|
||||
log.info("{}动作发送完成", actionName);
|
||||
// 记录612实际发送时间,供outCard发送611时参考
|
||||
last612SendTime = System.currentTimeMillis();
|
||||
log.info("{}动作发送完成, 时间戳:{}", actionName, last612SendTime);
|
||||
// 注意:状态修改已在actionCard()中立即执行,此处不再重复修改
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("执行{}动作时发生异常: {}", actionName, e.getMessage(), e);
|
||||
|
|
@ -432,27 +498,71 @@ public class HeChi {
|
|||
* {"tip_list":[{"type":3,"id":1,"opcard":[206],"weight":3,"card":206}]}
|
||||
*/
|
||||
public String actionCard(ITObject param, TaurusClient client) {
|
||||
System.out.println("814所有参数" + param);
|
||||
// System.out.println("[814吃碰杠胡] 收到操作选项:" + param); //已精简
|
||||
try {
|
||||
ITArray tipList = param.getTArray("tip_list");
|
||||
|
||||
// 诊断:tipList是否为null
|
||||
if (tipList == null) {
|
||||
System.out.println("[814] ⚠️ tipList为null! param=" + param);
|
||||
tipList = TArray.newInstance();//防止后续NPE
|
||||
}
|
||||
System.out.println("[814] tipList.size=" + tipList.size());
|
||||
|
||||
ITObject params = TObject.newInstance();
|
||||
int card = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
//循环
|
||||
List<Integer> yupanhandcard = new ArrayList<>();
|
||||
yupanhandcard.addAll(changShaCardInhand);
|
||||
//进行操作之前能否下听
|
||||
|
||||
// 统计手牌中红中数量
|
||||
int actionHongZhongCount = 0;
|
||||
List<Integer> handWithoutHZ = new ArrayList<>();
|
||||
for (Integer c : changShaCardInhand) {
|
||||
if (c == 501) {
|
||||
actionHongZhongCount++;
|
||||
} else {
|
||||
handWithoutHZ.add(c);
|
||||
}
|
||||
}
|
||||
// 修复日志:SLF4J无binding导致{}不被替换,改用System.out.println确保可看到值
|
||||
// System.out.println("[814操作前] 手牌:" + changShaCardInhand + ", 红中数量:" + actionHongZhongCount); //已精简
|
||||
|
||||
List<Integer> shifoutingpai = changShaSuanFaTest.handscardshifoutingpai(changShaCardInhand, chowGroup, pongGroup, gangdepai);
|
||||
boolean beforelisten = false;//记录操作之前的下听状态
|
||||
log.info("shifoutingpai{}", shifoutingpai);
|
||||
// System.out.println("[814操作前] 听牌结果:" + shifoutingpai + ", 是否已听牌:" + (shifoutingpai.size() > 0)); //已精简
|
||||
if (shifoutingpai.size() > 0) {
|
||||
beforelisten = true; //操作之前是听牌的
|
||||
}
|
||||
|
||||
log.info("beforelisten{}", beforelisten);
|
||||
// 兜底检测:如果handscardshifoutingpai没检测到听牌,再用WinCard直接检测手牌能否胡牌
|
||||
// 只要手牌加任何一张牌就能胡,说明已经在听牌状态
|
||||
if (!beforelisten && changShaCardInhand.size() == 13) {
|
||||
boolean canWin = false;
|
||||
List<Integer> candidateCards = new ArrayList<>();
|
||||
for (int j = 101; j <= 109; j++) candidateCards.add(j);
|
||||
for (int j = 201; j <= 209; j++) candidateCards.add(j);
|
||||
for (int j = 301; j <= 309; j++) candidateCards.add(j);
|
||||
for (int j = 401; j <= 404; j++) candidateCards.add(j);
|
||||
for (int j = 502; j <= 503; j++) candidateCards.add(j);
|
||||
for (Integer candidate : candidateCards) {
|
||||
WinCard win = new WinCard(handWithoutHZ, candidate, actionHongZhongCount, 501);
|
||||
if (win.tryWin()) {
|
||||
canWin = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (canWin) {
|
||||
beforelisten = true;
|
||||
// System.out.println("[814兜底检测] handscardshifoutingpai未检测到听牌,但WinCard检测到手牌可胡 → 已听牌"); //已精简
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("beforelisten:" + beforelisten); //保留:关键状态标记
|
||||
|
||||
//如果杠了之后还能继续听牌则杠
|
||||
|
||||
//优先处理胡牌
|
||||
|
|
@ -501,16 +611,22 @@ public class HeChi {
|
|||
params.putInt("qi", 0);
|
||||
params.putInt("id", 0);
|
||||
delayedActionCard(client, params, "不开杠");
|
||||
System.out.println("不开杠111");
|
||||
return "不开杠";
|
||||
}
|
||||
|
||||
// 明杠:立即修改本地状态确保后续出牌使用正确手牌
|
||||
Util.removeCard(changShaCardInhand, card, 3);
|
||||
|
||||
gangdepai.add(card);
|
||||
gangdepai.add(card);
|
||||
gangdepai.add(card);
|
||||
gangdepai.add(card);
|
||||
log.info("[状态同步] 明杠{}立即更新本地手牌:{}", cardToString(card), changShaCardInhand);
|
||||
|
||||
params.putString("session", session + "," + token);
|
||||
params.putInt("qi", 0);
|
||||
params.putInt("id", id);
|
||||
delayedActionCard(client, params, "开杠");
|
||||
System.out.println("开杠222");
|
||||
return "开杠";
|
||||
} else {
|
||||
params.putString("session", session + "," + token);
|
||||
|
|
@ -542,7 +658,15 @@ public class HeChi {
|
|||
return "不开杠";
|
||||
}
|
||||
|
||||
// 暗杠:立即修改本地状态确保后续出牌使用正确手牌
|
||||
Util.removeCard(changShaCardInhand, card, 4);
|
||||
|
||||
gangdepai.add(card);
|
||||
gangdepai.add(card);
|
||||
gangdepai.add(card);
|
||||
gangdepai.add(card);
|
||||
log.info("[状态同步] 暗杠{}立即更新本地手牌:{}", cardToString(card), changShaCardInhand);
|
||||
|
||||
params.putString("session", session + "," + token);
|
||||
params.putInt("qi", 0);
|
||||
params.putInt("id", id);
|
||||
|
|
@ -577,7 +701,15 @@ public class HeChi {
|
|||
|
||||
}
|
||||
|
||||
// 补杠:立即修改本地状态确保后续出牌使用正确手牌
|
||||
Util.removeCard(changShaCardInhand, card, 1);
|
||||
|
||||
gangdepai.add(card);
|
||||
gangdepai.add(card);
|
||||
gangdepai.add(card);
|
||||
gangdepai.add(card);
|
||||
log.info("[状态同步] 补杠{}立即更新本地手牌:{}", cardToString(card), changShaCardInhand);
|
||||
|
||||
params.putString("session", session + "," + token);
|
||||
params.putInt("qi", 0);
|
||||
params.putInt("id", id);
|
||||
|
|
@ -652,6 +784,7 @@ public class HeChi {
|
|||
sj.putInt("weight", weight);
|
||||
sj.putInt("type", type);
|
||||
sj.putTArray("opcard", opcard);
|
||||
sj.putInt("card", card); // 必须存入card字段,否则后续getInt("card")会NPE
|
||||
idObject.put(id, sj);
|
||||
}
|
||||
//计算分数
|
||||
|
|
@ -673,37 +806,84 @@ public class HeChi {
|
|||
if (entry.getKey() == changeid) {
|
||||
tmp = entry.getValue();
|
||||
log.debug("tmp ++++++++++= {}", tmp);
|
||||
if (tmp.getInt("type") == 2) {
|
||||
//碰
|
||||
ITArray outcards = tmp.getTArray("opcard");
|
||||
for (int i = 0; i < outcards.size(); i++) {
|
||||
Util.removeCard(changShaCardInhand, outcards.getInt(0), 2);
|
||||
}
|
||||
pongGroup.add(outcards.getInt(0));
|
||||
pongGroup.add(outcards.getInt(0));
|
||||
pongGroup.add(outcards.getInt(0));
|
||||
|
||||
} else if (tmp.getInt("type") == 1) {
|
||||
//吃
|
||||
ITArray outcards = tmp.getTArray("opcard");
|
||||
for (int i = 0; i < outcards.size(); i++) {
|
||||
Util.removeCard(changShaCardInhand, outcards.getInt(i), 1);
|
||||
}
|
||||
log.debug("判断吃 +++++++++{}", card);
|
||||
log.debug("判断吃 ========== {}", outcards);
|
||||
chowGroup.add(card);
|
||||
chowGroup.add(outcards.getInt(0));
|
||||
chowGroup.add(outcards.getInt(1));
|
||||
|
||||
|
||||
// 记录最后吃的三张牌(用于门子规则判断)
|
||||
lastChowCard1 = card;
|
||||
lastChowCard2 = outcards.getInt(0);
|
||||
lastChowCard3 = outcards.getInt(1);
|
||||
System.out.println("记录最后吃的三张牌(用于门子规则判断" + lastChowCard1 + lastChowCard2 + lastChowCard3);
|
||||
}
|
||||
|
||||
break; // 找到就跳出,避免不必要的遍历
|
||||
}
|
||||
}
|
||||
|
||||
// 防御性检查:如果changeid在idObject中找不到对应条目
|
||||
if (tmp == null) {
|
||||
System.out.println("[814] ⚠️ changeid=" + changeid + " 在idObject中未找到! keys=" + idObject.keySet() + ", 兜底执行'过'");
|
||||
params.putString("session", session + "," + token);
|
||||
params.putInt("qi", 0);
|
||||
params.putInt("id", 0);
|
||||
delayedActionCard(client, params, "changeid未找到-过");
|
||||
return null;
|
||||
}
|
||||
|
||||
// ===== 🚀 碰杠破坏性评估 =====
|
||||
int opType = tmp.getInt("type");
|
||||
int opCard = tmp.getInt("card");
|
||||
List<Integer> beforeOpHand = new ArrayList<>(yupanhandcard);
|
||||
|
||||
if (opType == 2 || opType == 3 || opType == 5) {
|
||||
try {
|
||||
double damage = ChangshaWinSplitCard.assessOperationDamage(
|
||||
opType,
|
||||
opCard,
|
||||
beforeOpHand,
|
||||
new ArrayList<>(), // 操作后的手牌稍后计算
|
||||
beforelisten,
|
||||
false // 是否听牌稍后检测
|
||||
);
|
||||
|
||||
System.out.println("【碰杠破坏性评估】操作类型=" + opType +
|
||||
", 牌=" + cardToString(opCard) +
|
||||
", 破坏性评分=" + String.format("%.1f", damage) +
|
||||
(damage > 10 ? " ⚠️ 破坏性较高!" : ""));
|
||||
|
||||
if (damage > 20) {
|
||||
// System.out.println("⚠️ 警告:此操作破坏性极高(" + damage + "),建议重新考虑!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("破坏性评估异常: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
// ===== 结束破坏性评估 =====
|
||||
|
||||
if (tmp.getInt("type") == 2) {
|
||||
//碰 - 立即修改本地状态确保后续出牌(813/611)使用正确的手牌
|
||||
ITArray outcards = tmp.getTArray("opcard");
|
||||
|
||||
// 立即执行状态修改
|
||||
int pengCard = outcards.getInt(0);
|
||||
Util.removeCard(changShaCardInhand, pengCard, 3);
|
||||
pongGroup.add(pengCard);
|
||||
pongGroup.add(pengCard);
|
||||
pongGroup.add(pengCard);
|
||||
log.info("[状态同步] 碰牌{}立即更新本地手牌:{}", cardToString(pengCard), changShaCardInhand);
|
||||
|
||||
} else if (tmp.getInt("type") == 1) {
|
||||
//吃 - 立即修改本地状态确保后续出牌(813/611)使用正确的手牌
|
||||
ITArray outcards = tmp.getTArray("opcard");
|
||||
|
||||
// 立即执行状态修改
|
||||
Util.removeCard(changShaCardInhand, outcards.getInt(0), 1);
|
||||
Util.removeCard(changShaCardInhand, outcards.getInt(1), 1);
|
||||
|
||||
chowGroup.add(card);
|
||||
chowGroup.add(outcards.getInt(0));
|
||||
chowGroup.add(outcards.getInt(1));
|
||||
log.info("[状态同步] 吃牌{}{}{}立即更新本地手牌:{}",
|
||||
cardToString(card), cardToString(outcards.getInt(0)), cardToString(outcards.getInt(1)),
|
||||
changShaCardInhand);
|
||||
|
||||
// 记录最后吃的三张牌(用于门子规则判断)
|
||||
lastChowCard1 = card;
|
||||
lastChowCard2 = outcards.getInt(0);
|
||||
lastChowCard3 = outcards.getInt(1);
|
||||
}
|
||||
|
||||
params.putString("session", session + "," + token);
|
||||
params.putInt("qi", 0);
|
||||
params.putInt("id", changeid);
|
||||
|
|
@ -800,6 +980,7 @@ public class HeChi {
|
|||
params.putInt("qi", 0);
|
||||
params.putInt("id", id);
|
||||
|
||||
// 补杠/开杠:立即修改本地状态确保后续出牌使用正确手牌
|
||||
if (type == 3) {
|
||||
Util.removeCard(changShaCardInhand, card, 3);
|
||||
gangdepai.add(card);
|
||||
|
|
@ -821,6 +1002,7 @@ public class HeChi {
|
|||
gangdepai.add(card);
|
||||
gangdepai.add(card);
|
||||
}
|
||||
|
||||
delayedActionCard(client, params, "");
|
||||
return null;
|
||||
}
|
||||
|
|
@ -830,6 +1012,7 @@ public class HeChi {
|
|||
params.putInt("qi", 0);
|
||||
params.putInt("id", id);
|
||||
|
||||
// 补杠/开杠:立即修改本地状态确保后续出牌使用正确手牌
|
||||
if (type == 3) {
|
||||
Util.removeCard(changShaCardInhand, card, 3);
|
||||
gangdepai.add(card);
|
||||
|
|
@ -863,6 +1046,22 @@ public class HeChi {
|
|||
delayedActionCard(client, params, "默认动作");
|
||||
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
// SLF4J NOP logger无法输出参数,用System.out.println确保异常可见
|
||||
System.out.println("[814] ⚠️ actionCard内部异常! 类型=" + e.getClass().getName() + ", 信息=" + e.getMessage());
|
||||
e.printStackTrace(System.out);
|
||||
// 兜底:发送"过"/空操作避免机器人卡死
|
||||
try {
|
||||
ITObject fallbackParams = TObject.newInstance();
|
||||
fallbackParams.putString("session", session + "," + token);
|
||||
fallbackParams.putInt("qi", 0);
|
||||
fallbackParams.putInt("id", 0);
|
||||
delayedActionCard(client, fallbackParams, "异常兜底-过");
|
||||
} catch (Exception ex) {
|
||||
System.out.println("[814] ⚠️ 兜底操作也失败: " + ex.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -917,81 +1116,71 @@ public class HeChi {
|
|||
}
|
||||
|
||||
|
||||
// 河池麻将出牌 - 先过滤掉红中(501),不让算法处理
|
||||
// 河池麻将出牌 - 先过滤掉红中(501),不让算法处理(红中通过hongZhongCount传入算法)
|
||||
List<Integer> handCardsWithoutHongZhong = new ArrayList<>();
|
||||
for (Integer card : changShaCardInhand) {
|
||||
if (card == 501) {
|
||||
System.out.println("红中先过滤掉" + changShaCardInhand);
|
||||
// 红中不参与算法处理,通过hongZhongCount传入
|
||||
} else {
|
||||
handCardsWithoutHongZhong.add(card);
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("过滤掉红中的手牌" + handCardsWithoutHongZhong);
|
||||
log.info("[出牌分析] 手牌(含红中):{}, 红中数量:{}, 过滤后手牌:{}", changShaCardInhand, hongZhongCount, handCardsWithoutHongZhong);
|
||||
|
||||
log.info("河池麻将出牌{}", changShaCardInhand);
|
||||
// 河池麻将出牌
|
||||
String changShaOutCard = changShaSuanFaTest.outCardSuanFa(handCardsWithoutHongZhong, pongGroup, chowGroup, gangdepai, resultList,hongZhongCount);
|
||||
//如果最后吃的牌是147,258,369,同个门子的牌不能打出 需要换张牌打
|
||||
//如果最后吃了牌,147/258/369同个门子(同花色)同组的牌不能打出,需要换张牌打
|
||||
if (lastChowCard1 != null && lastChowCard2 != null && lastChowCard3 != null) {
|
||||
boolean canOut = false;
|
||||
int outcard = Integer.parseInt(changShaOutCard);
|
||||
if (lastChowCard1 - outcard == 3 && lastChowCard2 - outcard < 3
|
||||
&& lastChowCard3 - outcard < 3) {
|
||||
canOut = true;
|
||||
} else if (outcard - lastChowCard1 == 3 && outcard - lastChowCard2 < 3
|
||||
&& outcard - lastChowCard3 < 3) {
|
||||
canOut = true;
|
||||
}
|
||||
if (canOut) {
|
||||
System.out.println("如果最后吃的牌是147,258,369,同个门子的牌不能打出 需要换张牌打" + outcard);
|
||||
System.out.println("lastChowCard1 " + lastChowCard1);
|
||||
System.out.println("lastChowCard2 " + lastChowCard2);
|
||||
System.out.println("lastChowCard3 " + lastChowCard3);
|
||||
System.out.println("[门子规则检查] 原选牌:" + outcard + "(" + cardToString(outcard) + "), 吃的牌:[" +
|
||||
lastChowCard1 + "," + lastChowCard2 + "," + lastChowCard3 + "]");
|
||||
if (isChowMenziRestricted(outcard)) {
|
||||
System.out.println("门子规则限制:吃的牌[" + lastChowCard1 + "," + lastChowCard2 + "," + lastChowCard3 + "]同组同门子牌不能打出,原选牌:" + outcard);
|
||||
List<Integer> tempHandCards = new ArrayList<>();
|
||||
tempHandCards.addAll(handCardsWithoutHongZhong);
|
||||
tempHandCards.remove(Integer.valueOf(outcard));
|
||||
|
||||
System.out.println("完整手牌 changShaCardInhand" + changShaCardInhand);
|
||||
System.out.println("从手牌中删除禁止打的牌后的手牌" + tempHandCards);
|
||||
if (!tempHandCards.isEmpty()) {
|
||||
System.out.println("进入重新计算");
|
||||
// 逐步移除被门子规则禁止的牌,重新选牌(增加最大重试次数防止死循环)
|
||||
int maxRetries = tempHandCards.size(); // 最多尝试手牌张数次
|
||||
int retryCount = 0;
|
||||
while (tempHandCards.size() > 1 && retryCount < maxRetries) {
|
||||
retryCount++;
|
||||
tempHandCards.remove(Integer.valueOf(outcard));
|
||||
System.out.println("移除被门子限制的牌:" + outcard + "(" + cardToString(outcard) + "),第" + retryCount + "次重试,剩余手牌:" + tempHandCards);
|
||||
changShaOutCard = changShaSuanFaTest.outCardSuanFa(tempHandCards, pongGroup, chowGroup, gangdepai, resultList,hongZhongCount);
|
||||
|
||||
// 安全检查:outCardSuanFa返回空或无效
|
||||
if (changShaOutCard == null || changShaOutCard.isEmpty()) {
|
||||
System.out.println("[门子规则] ⚠️ outCardSuanFa返回空! 强制使用剩余手牌第一张");
|
||||
if (!tempHandCards.isEmpty()) {
|
||||
changShaOutCard = String.valueOf(tempHandCards.get(0));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int newOutcard = Integer.parseInt(changShaOutCard);
|
||||
boolean newcanOut = false;
|
||||
while (tempHandCards.size() > 1) {
|
||||
if (lastChowCard1 - newOutcard == 3 && lastChowCard2 - newOutcard < 3
|
||||
&& lastChowCard3 - newOutcard < 3) {
|
||||
newcanOut = true;
|
||||
} else if (newOutcard - lastChowCard1 == 3 && newOutcard - lastChowCard2 < 3
|
||||
&& newOutcard - lastChowCard3 < 3) {
|
||||
newcanOut = true;
|
||||
}
|
||||
if (newcanOut){
|
||||
System.out.println("新选的牌也被门子规则禁止,继续尝试" + newOutcard);
|
||||
tempHandCards.remove(Integer.valueOf(newOutcard));
|
||||
changShaOutCard = changShaSuanFaTest.outCardSuanFa(tempHandCards, pongGroup, chowGroup, gangdepai, resultList,hongZhongCount);
|
||||
newOutcard = Integer.parseInt(changShaOutCard);
|
||||
} else {
|
||||
changShaOutCard = String.valueOf(newOutcard);
|
||||
break;
|
||||
}
|
||||
if (!isChowMenziRestricted(newOutcard)) {
|
||||
System.out.println("找到不受门子限制的出牌:" + newOutcard + "(" + cardToString(newOutcard) + "),共重试" + retryCount + "次");
|
||||
break;
|
||||
} else {
|
||||
System.out.println("新选的牌" + newOutcard + "(" + cardToString(newOutcard) + ")也被门子规则禁止,继续尝试");
|
||||
outcard = newOutcard;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (retryCount >= maxRetries) {
|
||||
System.out.println("[门子规则] ⚠️ 达到最大重试次数" + maxRetries + "次! 强制使用当前候选牌:" + changShaOutCard);
|
||||
}
|
||||
} else {
|
||||
System.out.println("[门子规则检查] 选牌" + outcard + "(" + cardToString(outcard) + ")不受门子限制,正常出牌");
|
||||
}
|
||||
|
||||
// if (canOut && owner.cardInhand.size() == 2) {
|
||||
// owner.isFenghu = true;
|
||||
// } else if (canOut) {
|
||||
// ITObject reconParam = new TObject();
|
||||
// owner.sendEvent(Router.GAME_EVT__UPDATE_RECONECT, reconParam);
|
||||
// return;
|
||||
//
|
||||
// }
|
||||
|
||||
// 清理门子数据(无论是否触发限制都要清理)
|
||||
lastChowCard1 = null;
|
||||
lastChowCard2 = null;
|
||||
lastChowCard3 = null;
|
||||
}
|
||||
|
||||
|
||||
log.info("河池麻将出牌{}", changShaCardInhand);
|
||||
|
||||
ITObject params = TObject.newInstance();
|
||||
|
|
@ -1020,19 +1209,39 @@ public class HeChi {
|
|||
changShaCardInhand.remove(Integer.valueOf(cardToOut));
|
||||
log.info("打过后的手牌 +++ {}", changShaCardInhand);
|
||||
params.putString("session", session + "," + token);
|
||||
System.out.println("河池麻将出牌参数 +" + params);
|
||||
log.info("[出牌决定] 机器人打出:{} (手牌:{}, 已出牌:{})",
|
||||
cardToString(cardToOut), changShaCardInhand, changShachuguopai);
|
||||
//清理最后一次吃的数据
|
||||
lastChowCard1 = null;
|
||||
lastChowCard2 = null;
|
||||
lastChowCard3 = null;
|
||||
//使用线程池替代CompletableFuture.runAsync + Thread.sleep
|
||||
|
||||
//使用线程池发送出牌协议611
|
||||
getBusinessThreadPool().execute(() -> {
|
||||
try {
|
||||
int ot = new Random().nextInt(4);
|
||||
Thread.sleep(ot * 1000);
|
||||
client.send("611", params, response -> {
|
||||
int ot = new Random().nextInt(4); // 基础随机延迟0~3秒
|
||||
|
||||
// ===== 关键修复:检测是否有待确认的612(吃碰杠)协议 =====
|
||||
// 问题根因:actionCard()立即修改本地状态后,outCard基于新状态构建card_list
|
||||
// 但如果612还没到达服务器,服务器用旧状态校验 → 3005错误
|
||||
long now = System.currentTimeMillis();
|
||||
long elapsedSince612 = now - last612SendTime;
|
||||
// 如果5秒内有612发送记录,需要额外等待确保612先到达服务器
|
||||
int extraDelayMs = 0;
|
||||
if (last612SendTime > 0 && elapsedSince612 < 5000) {
|
||||
// 612可能在路上,额外等待1~2秒让网络传输完成
|
||||
extraDelayMs = 1000 + new Random().nextInt(1000);
|
||||
log.info("[611顺序保护] 检测到{}ms前有612发送,额外延迟{}ms确保到达顺序",
|
||||
elapsedSince612, extraDelayMs);
|
||||
}
|
||||
// ===== 结束修复 =====
|
||||
|
||||
Thread.sleep(ot * 1000 + extraDelayMs);
|
||||
client.send("611", params, response -> {
|
||||
log.info("[611出牌] 发送完成");
|
||||
});
|
||||
log.info("[611出牌] 总延迟={}ms(基础{}ms+保护{}ms)",
|
||||
ot * 1000 + extraDelayMs, ot * 1000, extraDelayMs);
|
||||
} catch (Exception e) {
|
||||
log.error("线程执行错误", e);
|
||||
}
|
||||
|
|
@ -1055,17 +1264,24 @@ public class HeChi {
|
|||
* @return
|
||||
*/
|
||||
public String shanchuchuguopai(ITObject param) {
|
||||
log.info("对面吃碰删除出过的牌组");
|
||||
if (param == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer card = param.getInt("card"); // 操作牌值
|
||||
Integer type = param.getInt("type"); // 操作类型
|
||||
|
||||
|
||||
Integer playerid = param.getInt("playerid");
|
||||
|
||||
String typeName = "";
|
||||
switch (type) {
|
||||
case 1: typeName = "吃"; break;
|
||||
case 2: typeName = "碰"; break;
|
||||
case 3: typeName = "明杠"; break;
|
||||
case 5: typeName = "暗杠/补杠"; break;
|
||||
default: typeName = "未知(type=" + type + ")"; break;
|
||||
}
|
||||
log.info("[815玩家操作] 玩家{}对牌{}执行{}操作", playerid, cardToString(card), typeName);
|
||||
|
||||
|
||||
//Redis中获取机器人ID列表
|
||||
List<Integer> robotIdsList = new ArrayList<>();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -7,130 +7,80 @@ import java.util.*;
|
|||
public class TinHuChi {
|
||||
|
||||
/**
|
||||
* 测试方法
|
||||
* 测试方法 - 验证核心Bug修复
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
// System.out.println("=== 测试开始 ===\n");
|
||||
//
|
||||
// // 测试1:你的例子
|
||||
List<Integer> hand1 = new ArrayList<>();
|
||||
System.out.println("=== 核心Bug修复验证测试 ===\n");
|
||||
|
||||
hand1.add(209);
|
||||
hand1.add(209);
|
||||
hand1.add(206);
|
||||
hand1.add(206);
|
||||
hand1.add(104);
|
||||
hand1.add(104);
|
||||
hand1.add(105);
|
||||
hand1.add(105);
|
||||
hand1.add(101);
|
||||
hand1.add(202);
|
||||
hand1.add(201);
|
||||
hand1.add(109);
|
||||
hand1.add(203);
|
||||
|
||||
|
||||
|
||||
ChangShaSuanFaTest changShaSuanFaTest = new ChangShaSuanFaTest();
|
||||
String s = changShaSuanFaTest.outCardSuanFa(hand1, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), 1);
|
||||
|
||||
// int addcard =203;
|
||||
|
||||
// Map<String, Object> map = new HashMap<>();
|
||||
// //碰之后的map
|
||||
// Map<String, Object> map2 = new HashMap<>();
|
||||
//
|
||||
// //先判断碰之前还需要几手牌,以及孤章
|
||||
//// int jiangnum = checkduijiang(changShaCardInhand);
|
||||
// List<Integer> tmpChangSch = new ArrayList<>();
|
||||
// tmpChangSch.addAll(hand1);
|
||||
//// tmpChangSch.add(addcard);
|
||||
// int i = ChangshaWinSplitCard.checkNormalHu(tmpChangSch, map);
|
||||
// System.out.println("checkNormalHu 孤章数量 " + ((List<Integer>) map.get("cardResiue")).size());
|
||||
// System.out.println("checkNormalHu 手数 " + map.get("remainingMelds"));
|
||||
//
|
||||
//
|
||||
// //碰之后
|
||||
// List<Integer> pengtemphand = new ArrayList<>();
|
||||
// pengtemphand.addAll(tmpChangSch);
|
||||
// Util.removeCard(pengtemphand, addcard, 3);
|
||||
//
|
||||
// ChangshaWinSplitCard.checkNormalHu(pengtemphand, map2);
|
||||
// //判断两个map是否找到更优
|
||||
// System.out.println("碰之前 map1: 手数" + Integer.parseInt(map.get("remainingMelds").toString()));
|
||||
// System.out.println("碰之后 map2 手数:" + Integer.parseInt(map2.get("remainingMelds").toString()));
|
||||
// //碰之后的手数小于碰之前的手数,可以碰
|
||||
// if (Integer.parseInt(map2.get("remainingMelds").toString()) < Integer.parseInt(map.get("remainingMelds").toString())) {
|
||||
// System.out.println("===============碰之后的手数小于碰之前的手数,可以碰 决定碰牌================== ++++ ");
|
||||
// } else if (Integer.parseInt(map2.get("remainingMelds").toString()) == Integer.parseInt(map.get("remainingMelds").toString())) { //碰完后和碰之前手数相等,需要判断孤章数量
|
||||
// //碰之后的数量
|
||||
// int size2 = ((List<Integer>) map2.get("cardResiue")).size();
|
||||
// System.out.println("碰之后的孤章数量 " + size2);
|
||||
//
|
||||
// int size1 = ((List<Integer>) map.get("cardResiue")).size();
|
||||
// System.out.println("碰之前的孤章数量 " + size1);
|
||||
// if (size2 < size1) {
|
||||
// System.out.println("===============碰之后的孤章数量 小于 碰之前的孤章数量可以碰 ================== ++++ ");
|
||||
// }
|
||||
// //碰之后的手数大于碰之前的手数,或者碰之后的孤章数量大于碰之前的孤章数量不能碰
|
||||
// } else if (Integer.parseInt(map2.get("remainingMelds").toString()) > Integer.parseInt(map.get("remainingMelds").toString()) || ((List<Integer>) map2.get("cardResiue")).size() > ((List<Integer>) map.get("cardResiue")).size()) {
|
||||
// System.out.println("碰之后的手数大于碰之前的手数,或者碰之后的孤章数量大于碰之前的孤章数量不能碰");
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
public static List<Integer> checktingpai(List<Integer> cardhand) {
|
||||
List<Integer> tpcards = new ArrayList<>();
|
||||
List<Integer> tmphc = cardhand;
|
||||
for (int i = 0; i < cardhand.size(); i++) {
|
||||
|
||||
int tmpcard = tmphc.get(0);
|
||||
tmphc.remove(0);
|
||||
for (int j = 101; j <= 109; j++) {
|
||||
WinCard win = new WinCard(tmphc, j);
|
||||
if (win.tryWin()) {
|
||||
if (!tpcards.contains(tmpcard)) {
|
||||
tpcards.add(tmpcard);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int j = 201; j <= 209; j++) {
|
||||
WinCard win = new WinCard(tmphc, j);
|
||||
if (win.tryWin()) {
|
||||
if (!tpcards.contains(tmpcard)) {
|
||||
tpcards.add(tmpcard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 301; j <= 309; j++) {
|
||||
WinCard win = new WinCard(tmphc, j);
|
||||
if (win.tryWin()) {
|
||||
if (!tpcards.contains(tmpcard)) {
|
||||
tpcards.add(tmpcard);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int j = 401; j <= 404; j++) {
|
||||
WinCard win = new WinCard(tmphc, j);
|
||||
if (win.tryWin()) {
|
||||
if (!tpcards.contains(tmpcard)) {
|
||||
tpcards.add(tmpcard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 502; j <= 503; j++) {
|
||||
WinCard win = new WinCard(tmphc, j);
|
||||
if (win.tryWin()) {
|
||||
if (!tpcards.contains(tmpcard)) {
|
||||
tpcards.add(tmpcard);
|
||||
}
|
||||
}
|
||||
}
|
||||
tmphc.add(tmpcard);
|
||||
// ===== 测试场景:杠后11张手牌,应该检测到打八万(108)能听牌 =====
|
||||
// 用户原始问题:机器人杠六万后抓到七万,手牌为:
|
||||
// [107, 108×2, 204, 205, 206, 302×2, 305, 306, 307]
|
||||
// 预期结果:打108(拆八万对子)后,能听106(六万)或109(九万)
|
||||
|
||||
System.out.println("【测试场景1】杠后11张 - 多对子听牌检测");
|
||||
List<Integer> testHand1 = new ArrayList<>();
|
||||
testHand1.add(107); // 七万
|
||||
testHand1.add(108); // 八万
|
||||
testHand1.add(108); // 八万(对子)
|
||||
testHand1.add(204); // 四饼
|
||||
testHand1.add(205); // 五饼
|
||||
testHand1.add(206); // 六饼
|
||||
testHand1.add(302); // 二条
|
||||
testHand1.add(302); // 二条(对子)
|
||||
testHand1.add(305); // 五条
|
||||
testHand1.add(306); // 六条
|
||||
testHand1.add(307); // 七条
|
||||
|
||||
System.out.println("手牌:" + testHand1);
|
||||
System.out.println("牌数:" + testHand1.size() + "张");
|
||||
|
||||
// 测试1:checktingpaiWithWildcard 能否识别出打108后能听牌
|
||||
List<Integer> tingResult = ChangshaWinSplitCard.checktingpaiWithWildcard(testHand1, 0, 0);
|
||||
System.out.println("\n checktingpaiWithWildcard结果(打出这些牌可听): " + tingResult);
|
||||
|
||||
if (tingResult.contains(108)) {
|
||||
System.out.println(" 【成功】正确识别出打108(八万)可以听牌!");
|
||||
} else if (!tingResult.isEmpty()) {
|
||||
System.out.println(" 部分成功:检测到可听牌" + tingResult + ",但未包含108");
|
||||
} else {
|
||||
System.out.println(" 失败:未检测到任何听牌选项(原bug状态)");
|
||||
}
|
||||
return tpcards;
|
||||
|
||||
// 测试2:analyzeBestDiscardWithWildcard 能否生成包含108的候选列表
|
||||
List<Integer> discardCandidates = ChangshaWinSplitCard.analyzeBestDiscardWithWildcard(testHand1, 0, 0);
|
||||
System.out.println("\n analyzeBestDiscardWithWildcard候选列表: " + discardCandidates);
|
||||
|
||||
if (discardCandidates.contains(108)) {
|
||||
System.out.println(" 【成功】候选列表包含108(允许拆对子换取听牌)!");
|
||||
} else {
|
||||
System.out.println(" 候选列表不包含108: " + discardCandidates);
|
||||
}
|
||||
|
||||
// 测试3:完整出牌决策流程
|
||||
System.out.println("\n===== 完整出牌决策测试 =====");
|
||||
ChangShaSuanFaTest changShaSuanFaTest = new ChangShaSuanFaTest();
|
||||
String finalDecision = changShaSuanFaTest.outCardSuanFa(
|
||||
testHand1,
|
||||
new ArrayList<>(), // chowGroup
|
||||
new ArrayList<>(), // pengCard
|
||||
new ArrayList<>(), // gangdepai
|
||||
new ArrayList<>(), // 已出牌列表
|
||||
1 // 游戏阶段
|
||||
);
|
||||
|
||||
System.out.println("最终决策 → 打出: " + finalDecision);
|
||||
|
||||
if (finalDecision.equals("108")) {
|
||||
System.out.println(" 决策正确!选择打八万(108)进入听牌状态!");
|
||||
} else if (finalDecision.equals("206")) {
|
||||
System.out.println(" 决策错误!打了六饼(206)而不是八万(108)");
|
||||
} else if (finalDecision.equals("302")) {
|
||||
System.out.println(" 决策保守:打二条(302),未追求最优听牌");
|
||||
} else {
|
||||
System.out.println(" 决策:打出" + finalDecision);
|
||||
}
|
||||
|
||||
System.out.println("\n=== 测试完成 ===\n");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -150,4 +100,4 @@ public class TinHuChi {
|
|||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,44 +151,131 @@ public class WinCard {
|
|||
return true;
|
||||
}
|
||||
|
||||
public boolean tryWin() {
|
||||
if (this.cardList.size() == 0 && this.pair_count == 1) {
|
||||
/**
|
||||
* 2个万能牌+1张相同牌组成刻子
|
||||
* 例如:1张五万 + 2个红中 = 五万刻子
|
||||
*/
|
||||
private boolean tryKezi2Zhong(int card) {
|
||||
if (this.zhong_count >= 2 && Util.checkCardAndRomve(card, this.cardList, 1)) {
|
||||
List<Integer> cardGroup = Arrays.asList(card, this.zhongid, this.zhongid);
|
||||
this.zhong_count -= 2;
|
||||
this.push(cardGroup);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 2个万能牌+1张牌组成顺子
|
||||
* 尝试3种位置:牌在首位/中间/末位
|
||||
* 例如:1张五万 + 2个红中 = 四万五万六万 或 五万六万七万 或 三万四万五万
|
||||
*/
|
||||
private boolean tryShunzi2Zhong(int card) {
|
||||
int suit = card / 100;
|
||||
if (suit < 1 || suit > 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int cardValue = card % 100;
|
||||
|
||||
if (this.zhong_count < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 配置1:牌在首位(V, W, W),V需<=7
|
||||
if (cardValue <= 7 && Util.checkCardAndRomve(card, this.cardList, 1)) {
|
||||
this.zhong_count -= 2;
|
||||
List<Integer> cardGroup = Arrays.asList(card, this.zhongid, this.zhongid);
|
||||
this.push(cardGroup);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.cardList.size() == 0) {
|
||||
// 配置2:牌在末位(W, W, V),V需>=3
|
||||
if (cardValue >= 3 && this.zhong_count >= 2 && Util.checkCardAndRomve(card, this.cardList, 1)) {
|
||||
this.zhong_count -= 2;
|
||||
List<Integer> cardGroup = Arrays.asList(this.zhongid, this.zhongid, card);
|
||||
this.push(cardGroup);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 配置3:牌在中间(W, V, W),V需>=2且<=8
|
||||
if (cardValue >= 2 && cardValue <= 8 && this.zhong_count >= 2 && Util.checkCardAndRomve(card, this.cardList, 1)) {
|
||||
this.zhong_count -= 2;
|
||||
List<Integer> cardGroup = Arrays.asList(this.zhongid, card, this.zhongid);
|
||||
this.push(cardGroup);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 红中自身组成刻子(3个红中=红中刻子)
|
||||
* 不需要普通牌参与,仅消耗3个红中
|
||||
*/
|
||||
private boolean tryZhongKezi() {
|
||||
if (this.zhong_count >= 3) {
|
||||
this.zhong_count -= 3;
|
||||
List<Integer> cardGroup = Arrays.asList(this.zhongid, this.zhongid, this.zhongid);
|
||||
this.push(cardGroup);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 红中自身组成对子/将牌(2个红中=红中对子)
|
||||
*/
|
||||
private boolean tryZhongPair() {
|
||||
if (this.pair_count > 0) {
|
||||
return false;
|
||||
}
|
||||
if (this.zhong_count >= 2) {
|
||||
this.zhong_count -= 2;
|
||||
List<Integer> cardGroup = Arrays.asList(this.zhongid, this.zhongid);
|
||||
this.push(cardGroup);
|
||||
this.pair_count = 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean tryWin() {
|
||||
// 终止条件:所有牌(普通牌+红中)都分配完毕,且有将牌 → 胡牌
|
||||
if (this.cardList.size() == 0 && this.zhong_count == 0 && this.pair_count == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// cardList为空但还有红中剩余 → 红中自身组牌(兜底:只有普通牌全拆完了才让红中自己组)
|
||||
if (this.cardList.size() == 0) {
|
||||
// 3个红中=红中刻子
|
||||
if (tryZhongKezi()) {
|
||||
if (tryWin()) {
|
||||
return true;
|
||||
}
|
||||
this.rollBack();
|
||||
}
|
||||
// 2个红中=对子(将牌)
|
||||
if (tryZhongPair()) {
|
||||
if (tryWin()) {
|
||||
return true;
|
||||
}
|
||||
this.pair_count = 0;
|
||||
this.rollBack();
|
||||
}
|
||||
// 红中剩余1个无法组任何牌 → 不可能胡牌
|
||||
return false;
|
||||
}
|
||||
|
||||
int activeCard = this.cardList.get(0);
|
||||
|
||||
if (tryPair(activeCard)) {
|
||||
if (tryWin()) {
|
||||
return true;
|
||||
}
|
||||
this.pair_count = 0;
|
||||
this.rollBack();
|
||||
}
|
||||
|
||||
if (tryKezi(activeCard)) {
|
||||
if (tryWin()) {
|
||||
return true;
|
||||
}
|
||||
this.rollBack();
|
||||
|
||||
}
|
||||
if (tryShunzi(activeCard)) {
|
||||
if (tryWin()) {
|
||||
return true;
|
||||
}
|
||||
this.rollBack();
|
||||
}
|
||||
|
||||
// 红中作为万能牌替代其他牌(优先级高于红中自身组牌)
|
||||
// 1个万能牌组合(1红中替代1张缺失牌)
|
||||
if (tryKezi1Zhong(activeCard)) {
|
||||
if (tryWin()) {
|
||||
return true;
|
||||
}
|
||||
this.rollBack();
|
||||
|
||||
}
|
||||
if (tryShunzi1Zhong(activeCard)) {
|
||||
if (tryWin()) {
|
||||
|
|
@ -204,6 +291,41 @@ public class WinCard {
|
|||
this.rollBack();
|
||||
}
|
||||
|
||||
// 2个万能牌组合(2红中替代2张缺失牌)
|
||||
if (tryKezi2Zhong(activeCard)) {
|
||||
if (tryWin()) {
|
||||
return true;
|
||||
}
|
||||
this.rollBack();
|
||||
}
|
||||
if (tryShunzi2Zhong(activeCard)) {
|
||||
if (tryWin()) {
|
||||
return true;
|
||||
}
|
||||
this.rollBack();
|
||||
}
|
||||
|
||||
// 普通牌组合(不含红中,红中不够替代时走这条路)
|
||||
if (tryPair(activeCard)) {
|
||||
if (tryWin()) {
|
||||
return true;
|
||||
}
|
||||
this.pair_count = 0;
|
||||
this.rollBack();
|
||||
}
|
||||
if (tryKezi(activeCard)) {
|
||||
if (tryWin()) {
|
||||
return true;
|
||||
}
|
||||
this.rollBack();
|
||||
}
|
||||
if (tryShunzi(activeCard)) {
|
||||
if (tryWin()) {
|
||||
return true;
|
||||
}
|
||||
this.rollBack();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -221,6 +343,27 @@ public class WinCard {
|
|||
Collections.sort(this.cardList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 河池麻将专用构造器:支持指定万能牌(红中501)数量
|
||||
* cardInhand中不应包含红中(已过滤),红中数量通过wildCardCount传入
|
||||
* @param cardInhand 不含红中的手牌
|
||||
* @param addCard 待添加的牌(0表示不添加)
|
||||
* @param wildCardCount 万能牌(红中)数量
|
||||
* @param wildCardId 万能牌ID(河池麻将红中=501)
|
||||
*/
|
||||
public WinCard(List<Integer> cardInhand, int addCard, int wildCardCount, int wildCardId) {
|
||||
this.stack = new Stack<List<Integer>>();
|
||||
this.cardList = new ArrayList<Integer>();
|
||||
this.cardList.addAll(cardInhand);
|
||||
this.zhongid = wildCardId;
|
||||
this.zhong_count = wildCardCount;
|
||||
if (addCard != 0) {
|
||||
this.cardList.add(addCard);
|
||||
}
|
||||
|
||||
Collections.sort(this.cardList);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
|
|
|
|||
Loading…
Reference in New Issue