福禄寿
parent
a2f8b61c29
commit
2093f434df
|
|
@ -12,26 +12,26 @@ public class Config {
|
||||||
*/
|
*/
|
||||||
public static final String JOIN_ROOM_FLS = "1002";
|
public static final String JOIN_ROOM_FLS = "1002";
|
||||||
|
|
||||||
/** Web组加入房间协议 */
|
/** Web 组加入房间协议 */
|
||||||
public static final String WEB_GROUP_JOIN_ROOM = "225";
|
public static final String WEB_GROUP_JOIN_ROOM = "225";
|
||||||
|
|
||||||
/** Web组主动重连协议 */
|
/** Web 组主动重连协议 */
|
||||||
public static final String WEB_GROUP_ACTIVE_RECONNECT = "226";
|
public static final String WEB_GROUP_ACTIVE_RECONNECT = "226";
|
||||||
|
|
||||||
//==================== 游戏服务器配置 ====================
|
//==================== 游戏服务器配置 ====================
|
||||||
/** 游戏服务器主机地址 */
|
/** 游戏服务器主机地址 */
|
||||||
public static final String GAME_SERVER_HOST = "127.0.0.1";
|
public static final String GAME_SERVER_HOST = "8.134.76.43";
|
||||||
|
|
||||||
/** 游戏服务器端口 */
|
/** 游戏服务器端口 */
|
||||||
public static final String GAME_SERVER_PORT = "8971";
|
public static final String GAME_SERVER_PORT = "8870";
|
||||||
|
|
||||||
/** 默认密码 */
|
/** 默认密码 */
|
||||||
public static final String DEFAULT_PASSWORD = "123456";
|
public static final String DEFAULT_PASSWORD = "123456";
|
||||||
|
|
||||||
/** 默认PID */
|
/** 默认PID */
|
||||||
public static final String DEFAULT_PID = "107";
|
public static final String DEFAULT_PID = "77";
|
||||||
|
|
||||||
/** 默认群组ID */
|
/** 默认群组ID */
|
||||||
public static final String DEFAULT_GROUP_ID = "426149";
|
public static final String DEFAULT_GROUP_ID = "762479";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,16 +2,15 @@ package robot.zp;
|
||||||
|
|
||||||
import com.robot.GameController;
|
import com.robot.GameController;
|
||||||
import com.robot.GameInterceptor;
|
import com.robot.GameInterceptor;
|
||||||
|
import com.robot.MainServer;
|
||||||
import com.taurus.core.entity.ITObject;
|
import com.taurus.core.entity.ITObject;
|
||||||
import com.taurus.core.entity.TObject;
|
import com.taurus.core.entity.TObject;
|
||||||
import com.taurus.core.plugin.redis.Redis;
|
import com.taurus.core.plugin.redis.Redis;
|
||||||
import com.taurus.core.routes.ActionKey;
|
import com.taurus.core.routes.ActionKey;
|
||||||
|
import com.taurus.core.util.Logger;
|
||||||
import com.taurus.permanent.data.Session;
|
import com.taurus.permanent.data.Session;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import redis.clients.jedis.Jedis;
|
import redis.clients.jedis.Jedis;
|
||||||
import robot.zp.info.RobotUser;
|
import robot.zp.info.RobotUser;
|
||||||
import robot.zp.thread.ThreadPoolConfig;
|
|
||||||
import taurus.client.TaurusClient;
|
import taurus.client.TaurusClient;
|
||||||
import taurus.client.business.GroupRoomBusiness;
|
import taurus.client.business.GroupRoomBusiness;
|
||||||
import taurus.util.ROBOTEventType;
|
import taurus.util.ROBOTEventType;
|
||||||
|
|
@ -20,7 +19,6 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
|
@ -30,7 +28,7 @@ import static robot.zp.thread.ThreadPoolConfig.scheduleDelay;
|
||||||
* 福禄寿游戏控制器 - 处理游戏协议
|
* 福禄寿游戏控制器 - 处理游戏协议
|
||||||
*/
|
*/
|
||||||
public class EXGameController extends GameController {
|
public class EXGameController extends GameController {
|
||||||
private static final Logger log = LoggerFactory.getLogger(EXGameController.class);
|
private static final Logger log = Logger.getLogger(EXGameController.class);
|
||||||
|
|
||||||
private static final RobotConnectionManager robotConnectionManager = new RobotConnectionManager();
|
private static final RobotConnectionManager robotConnectionManager = new RobotConnectionManager();
|
||||||
|
|
||||||
|
|
@ -56,39 +54,59 @@ public class EXGameController extends GameController {
|
||||||
int robotId = params.getInt("robotid");
|
int robotId = params.getInt("robotid");
|
||||||
String roomId = params.getString("roomid");
|
String roomId = params.getString("roomid");
|
||||||
int groupId = params.getInt("groupid");
|
int groupId = params.getInt("groupid");
|
||||||
|
|
||||||
//检查Redis中该房间是否真的包含当前机器人
|
String lockKey = "room_lock:" + roomId;
|
||||||
if (!checkRobotInRoomRedis(roomId, String.valueOf(robotId))) {
|
synchronized (lockKey.intern()) {
|
||||||
//Redis中不存在该机器人 清理本地可能的错误映射
|
if (checkRobotInRoomRedis(roomId, String.valueOf(robotId))) {
|
||||||
List<RobotUser> robotUsers = getRobotUsersByRoomId(Integer.parseInt(roomId));
|
log.info("机器人{" + robotId + "}已在房间{" + roomId + "}中(Redis 中存在),直接允许加入");
|
||||||
if (!robotUsers.isEmpty()) {
|
} else {
|
||||||
synchronized (robotUsers) {
|
RobotUser existingRobotUser = getRobotRoomInfo(String.valueOf(robotId));
|
||||||
RobotUser robotUser = robotUsers.get(0);
|
if (existingRobotUser != null && existingRobotUser.getCurrentRoomId() == Integer.parseInt(roomId)) {
|
||||||
log.warn("房间{}中Redis未找到机器人{},但本地映射存在{},清理本地映射", roomId, robotId, robotUser.getRobotId());
|
log.info("机器人{" + robotId + "}已在房间{" + roomId + "}中(本地映射存在),直接允许加入");
|
||||||
robotRoomMapping.remove(robotUser.getConnecId());
|
} else {
|
||||||
robotRoomMapping.remove(robotUser.getRobotId());
|
if (isPlayerIdConflictInRoom(roomId, robotId)) {
|
||||||
}
|
log.warn("检测到机器人{" + robotId + "}与房间{" + roomId + "}中的真人玩家 ID 冲突,拒绝加入");
|
||||||
}
|
ITObject errorResponse = TObject.newInstance();
|
||||||
} else {
|
errorResponse.putString("status", "failed");
|
||||||
//Redis中存在该机器人 检查是否是不同机器人的冲突
|
errorResponse.putString("message", "机器人 ID 与房间内玩家冲突");
|
||||||
List<RobotUser> robotUsers = getRobotUsersByRoomId(Integer.parseInt(roomId));
|
MainServer.instance.sendResponse(gid, 1, errorResponse, session);
|
||||||
if (!robotUsers.isEmpty()) {
|
|
||||||
synchronized (robotUsers) {
|
|
||||||
RobotUser robotUser = robotUsers.get(0);
|
|
||||||
int existingRobotId = Integer.parseInt(robotUser.getRobotId());
|
|
||||||
|
|
||||||
if (robotId != existingRobotId) {
|
|
||||||
//不同机器人的冲突
|
|
||||||
log.warn("房间{}中Redis已存在机器人{},当前机器人{}不执行加入逻辑", roomId, existingRobotId, robotId);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//检查Redis中该房间是否真的包含当前机器人
|
||||||
|
if (!checkRobotInRoomRedis(roomId, String.valueOf(robotId))) {
|
||||||
|
//Redis中不存在该机器人 清理本地可能的错误映射
|
||||||
|
List<RobotUser> robotUsers = getRobotUsersByRoomId(Integer.parseInt(roomId));
|
||||||
|
if (!robotUsers.isEmpty()) {
|
||||||
|
synchronized (robotUsers) {
|
||||||
|
RobotUser robotUser = robotUsers.get(0);
|
||||||
|
log.warn("房间" + roomId + "中 Redis 未找到机器人" + robotId + ",但本地映射存在" + robotUser.getRobotId() + ",清理本地映射");
|
||||||
|
robotRoomMapping.remove(robotUser.getConnecId());
|
||||||
|
robotRoomMapping.remove(robotUser.getRobotId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Redis中存在该机器人 检查是否是不同机器人的冲突
|
||||||
|
List<RobotUser> robotUsers = getRobotUsersByRoomId(Integer.parseInt(roomId));
|
||||||
|
if (!robotUsers.isEmpty()) {
|
||||||
|
synchronized (robotUsers) {
|
||||||
|
RobotUser robotUser = robotUsers.get(0);
|
||||||
|
int existingRobotId = Integer.parseInt(robotUser.getRobotId());
|
||||||
|
|
||||||
|
if (robotId != existingRobotId) {
|
||||||
|
//不同机器人的冲突
|
||||||
|
log.warn("房间" + roomId + "中 Redis 已存在机器人" + existingRobotId + ",当前机器人" + robotId + "不执行加入逻辑");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.info("225开始进房间: room:{} robot:{}", roomId, robotId);
|
log.info("225 开始进房间:room:" + roomId + " robot:" + robotId);
|
||||||
//加入房间
|
//加入房间
|
||||||
joinRoomCommon(robotId, roomId, groupId, params);
|
joinRoomCommon(robotId, roomId, groupId, params);
|
||||||
log.info("225已进入房间准备成功: room:{} robot:{}", roomId, robotId);
|
log.info("225 已进入房间准备成功:room:" + roomId + " robot:" + robotId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -98,10 +116,10 @@ public class EXGameController extends GameController {
|
||||||
public void webGroupActive(Session session, ITObject params, int gid) {
|
public void webGroupActive(Session session, ITObject params, int gid) {
|
||||||
int robotId = params.getInt("robotid");
|
int robotId = params.getInt("robotid");
|
||||||
String roomId = params.getString("roomid");
|
String roomId = params.getString("roomid");
|
||||||
log.info("226开始进房间: room:{} robot:{}", roomId, robotId);
|
log.info("226 开始进房间:room:" + roomId + " robot:" + robotId);
|
||||||
//加入房间
|
//加入房间
|
||||||
joinRoomCommon(params.getInt("robotid"), params.getString("roomid"), params.getInt("groupid"), params);
|
joinRoomCommon(params.getInt("robotid"), params.getString("roomid"), params.getInt("groupid"), params);
|
||||||
log.info("226已进入房间准备成功: room:{} robot:{}", roomId, robotId);
|
log.info("226 已进入房间准备成功:room:" + roomId + " robot:" + robotId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -128,12 +146,12 @@ public class EXGameController extends GameController {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("重启后开始进房间: room:{} robot:{}", robotUser.getCurrentRoomId(), robotUser.getRobotId());
|
log.info("重启后开始进房间:room:" + robotUser.getCurrentRoomId() + " robot:" + robotUser.getRobotId());
|
||||||
ITObject params = new TObject();
|
ITObject params = new TObject();
|
||||||
params.putString("session", "{user}:" + robotUser.getRobotId() + "," + robotSession);
|
params.putString("session", "{user}:" + robotUser.getRobotId() + "," + robotSession);
|
||||||
//加入房间
|
//加入房间
|
||||||
joinRoomCommon(Integer.parseInt(robotUser.getRobotId()), String.valueOf(robotUser.getCurrentRoomId()), Integer.parseInt(robotUser.getRobotGroupid()), params);
|
joinRoomCommon(Integer.parseInt(robotUser.getRobotId()), String.valueOf(robotUser.getCurrentRoomId()), Integer.parseInt(robotUser.getRobotGroupid()), params);
|
||||||
log.info("重启后已进入房间准备成功: room:{} robot:{}", robotUser.getCurrentRoomId(), robotUser.getRobotId());
|
log.info("重启后已进入房间准备成功:room:" + robotUser.getCurrentRoomId() + " robot:" + robotUser.getRobotId());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("重启服务断线重连时发生错误", e);
|
log.error("重启服务断线重连时发生错误", e);
|
||||||
|
|
@ -151,22 +169,22 @@ public class EXGameController extends GameController {
|
||||||
Jedis jedis2 = Redis.use("group1_db2").getJedis();
|
Jedis jedis2 = Redis.use("group1_db2").getJedis();
|
||||||
try {
|
try {
|
||||||
Set<String> robotTokens = jedis0.smembers("{user}:" + robotId + "_token");
|
Set<String> robotTokens = jedis0.smembers("{user}:" + robotId + "_token");
|
||||||
String robotSession = null;
|
String robotSession = robotTokens.stream().filter(jedis0::exists).findFirst().orElse(null);
|
||||||
|
|
||||||
for (String token : robotTokens) {
|
log.info("开始进房间:room:{"+roomId+"}");
|
||||||
if (jedis0.exists(token)) {
|
log.info("开始进房间:{user}:{"+robotId+"}");
|
||||||
robotSession = token;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("开始进房间: room:{}", roomId);
|
//建立 TCP 连接
|
||||||
log.info("开始进房间: {user}:{}", robotId);
|
TaurusClient client = getFlsGameServerConnection(roomId + "_" + robotId);
|
||||||
|
if (client == null) {
|
||||||
|
log.error("机器人{"+robotId+"}连接游戏服务器失败,connecId:{"+roomId + "_" + robotId+"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TaurusClient client = getFlsGameServerConnection(roomId + "_" + robotId);
|
ITObject joinResult = GroupRoomBusiness.joinRoom(groupId, "room:" + roomId, "{user}:" + robotId, null);
|
||||||
GroupRoomBusiness.joinRoom(groupId, "room:" + roomId, "{user}:" + robotId, null);
|
log.info("GroupRoomBusiness.joinRoom 结果:robotId:{"+robotId+"}, roomId:{"+roomId+"}, result:{"+joinResult+"}");
|
||||||
|
|
||||||
//机器人房间映射关系
|
log.info("机器人{"+robotId+"}准备发送 JOIN_ROOM_CS(1002) 协议");
|
||||||
RobotUser robotUser = getRobotRoomInfo(String.valueOf(robotId));
|
RobotUser robotUser = getRobotRoomInfo(String.valueOf(robotId));
|
||||||
String connecId = roomId + "_" + robotId;
|
String connecId = roomId + "_" + robotId;
|
||||||
if (robotUser.getCurrentRoomId() == 0) {
|
if (robotUser.getCurrentRoomId() == 0) {
|
||||||
|
|
@ -174,58 +192,115 @@ public class EXGameController extends GameController {
|
||||||
robotUser.setClient(client);
|
robotUser.setClient(client);
|
||||||
robotUser.setConnecId(connecId);
|
robotUser.setConnecId(connecId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//先不放入映射 等确认加入成功后再放入
|
|
||||||
//robotRoomMapping.put(robotUser.getConnecId(), robotUser);
|
|
||||||
robotRoomMapping.remove(robotUser.getRobotId());
|
|
||||||
//非阻塞延迟替代Thread.sleep
|
|
||||||
scheduleDelay(() -> {
|
|
||||||
|
|
||||||
}, 2, TimeUnit.SECONDS);
|
//先不放入映射 等确认加入成功后再放入
|
||||||
|
robotRoomMapping.remove(robotUser.getRobotId());
|
||||||
|
|
||||||
params.putString("session", "{user}:" + robotId + "," + robotSession);
|
params.putString("session", "{user}:" + robotId + "," + robotSession);
|
||||||
|
|
||||||
//发送加入房间请求到game_zp_fls
|
//发送 JOIN_ROOM_FLS(1002)
|
||||||
client.send(Config.JOIN_ROOM_FLS, params, response -> {
|
client.send(Config.JOIN_ROOM_FLS, params, response -> {
|
||||||
//成功响应后才建立映射关系
|
try {
|
||||||
robotRoomMapping.put(robotUser.getConnecId(), robotUser);
|
log.info("JOIN_ROOM_FLS(1002) 响应:{"+response+"}");
|
||||||
robotConnectionManager.reconnectToGameServer(response, robotUser, client);
|
|
||||||
|
//检查响应是否成功
|
||||||
|
if (response == null || response.messageData == null || response.messageData.param == null) {
|
||||||
|
log.error("机器人{"+robotId+"}加入房间{"+roomId+"}失败:game_mj_cs 返回 null");
|
||||||
|
cleanupFailedRobot(robotUser, connecId, roomId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ITObject responseParam = response.messageData.param;
|
||||||
|
int responseCode = responseParam.containsKey("code") ? responseParam.getInt("code") : 0;
|
||||||
|
if (responseCode != 0) {
|
||||||
|
log.error("机器人{"+robotId+"}加入房间{"+roomId+"}失败:game_mj_cs 返回错误码{"+responseCode+"}, response:{"+response+"}");
|
||||||
|
cleanupFailedRobot(robotUser, connecId, roomId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//1002 响应成功后,添加短暂延迟等待服务器将机器人加入 Redis
|
||||||
|
scheduleDelay(() -> {
|
||||||
|
try {
|
||||||
|
//验证机器人是否真的进入了房间(检查 Redis)
|
||||||
|
if (!checkRobotInRoomRedis(roomId, String.valueOf(robotId))) {
|
||||||
|
log.error("机器人{"+robotId+"}加入房间{"+roomId+"}失败:1002 响应成功但 Redis 中未找到机器人,清理资源");
|
||||||
|
cleanupFailedRobot(robotUser, connecId, roomId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("机器人{"+robotId+"}Redis 验证成功,建立映射关系");
|
||||||
|
|
||||||
|
//成功响应后才建立映射关系
|
||||||
|
synchronized (robotRoomMapping) {
|
||||||
|
robotRoomMapping.put(robotUser.getConnecId(), robotUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("机器人{"+robotId+"}已成功加入房间{"+roomId+"},建立映射关系");
|
||||||
|
|
||||||
|
//发送准备协议
|
||||||
|
scheduleDelay(() -> {
|
||||||
|
try {
|
||||||
|
if (client != null && client.isConnected()) {
|
||||||
|
client.send(Config.GAME_READY_FLS, params, readyResponse -> {
|
||||||
|
try {
|
||||||
|
log.info("GAME_READY 响应:{"+readyResponse+"}");
|
||||||
|
|
||||||
|
//设置准备状态
|
||||||
|
robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_READY);
|
||||||
|
robotConnectionManager.setSessionAndToken("{user}:" + robotId, robotSession, robotUser.getConnecId());
|
||||||
|
|
||||||
|
//标记机器人为可用状态
|
||||||
|
jedis2.hset("gallrobot", String.valueOf(robotUser.getRobotId()), "1");
|
||||||
|
robotUser.setIntoRoomTime(robotConnectionManager.getTime());
|
||||||
|
|
||||||
|
log.info("机器人{"+robotId+"}准备成功,房间:{"+roomId+"}");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("机器人{"+robotId+"}设置准备状态失败"+ e);
|
||||||
|
cleanupFailedRobot(robotUser, connecId, roomId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("机器人{"+robotId+"}发送准备协议失败"+ e);
|
||||||
|
cleanupFailedRobot(robotUser, connecId, roomId);
|
||||||
|
}
|
||||||
|
}, 1000, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("机器人{"+robotId+"}Redis 验证失败"+ e);
|
||||||
|
cleanupFailedRobot(robotUser, connecId, roomId);
|
||||||
|
}
|
||||||
|
}, 500, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理 JOIN_ROOM_CS 响应时发生异常", e);
|
||||||
|
cleanupFailedRobot(robotUser, connecId, roomId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info("已进入房间成功: {}", robotUser.getConnecId());
|
log.info("已进入房间成功:{"+robotUser.getConnecId()+"}");
|
||||||
Thread.sleep(1000);
|
|
||||||
if (client.isConnected()) {
|
|
||||||
client.send(Config.GAME_READY_FLS, params, response -> {
|
|
||||||
log.info("1003:{}", response);
|
|
||||||
});
|
|
||||||
jedis2.hset("gallrobot", String.valueOf(robotUser.getRobotId()), "1");
|
|
||||||
|
|
||||||
robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_READY);
|
/*//添加超时检查机制(15 秒)
|
||||||
robotConnectionManager.setSessionAndToken("{user}:" + robotId, robotSession, robotUser.getConnecId());
|
|
||||||
}
|
|
||||||
//添加超时检查机制
|
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
try {
|
try {
|
||||||
//定时任务替代Thread.sleep
|
//定时任务替代 Thread.sleep
|
||||||
scheduleDelay(() -> {
|
scheduleDelay(() -> {
|
||||||
//15秒后还没有建立映射关系 加入可能失败
|
//15 秒后还没有建立映射关系或状态不是准备状态,说明加入失败
|
||||||
if (robotRoomMapping.get(robotUser.getConnecId()) == null) {
|
RobotUser currentUser = robotRoomMapping.get(connecId);
|
||||||
log.warn("机器人{}加入房间{}超时,清理临时状态", robotId, roomId);
|
if (currentUser == null || currentUser.getStatus() != ROBOTEventType.ROBOT_INTOROOM_READY) {
|
||||||
robotConnectionManager.disconnectFromGameServer(connecId);
|
log.warn("机器人" + robotId + "加入房间" + roomId + "超时(15 秒),清理临时状态");
|
||||||
}
|
cleanupFailedRobot(robotUser, connecId, roomId);
|
||||||
|
}
|
||||||
}, 15, TimeUnit.SECONDS);
|
}, 15, TimeUnit.SECONDS);
|
||||||
//15秒后还没有建立映射关系 加入可能失败
|
|
||||||
if (robotRoomMapping.get(robotUser.getConnecId()) == null) {
|
|
||||||
log.warn("机器人{}加入房间{}超时,清理临时状态", robotId, roomId);
|
|
||||||
robotConnectionManager.disconnectFromGameServer(connecId);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("机器人加入房间超时", e);
|
log.error("机器人" + robotId + "加入房间超时检查异常", e);
|
||||||
}
|
}
|
||||||
}, ThreadPoolConfig.getBusinessThreadPool());//指定自定义线程池
|
}, ThreadPoolConfig.getBusinessThreadPool());*/
|
||||||
robotUser.setIntoRoomTime(robotConnectionManager.getTime());
|
log.info("已进入房间准备成功:{"+robotUser.getConnecId()+"}");
|
||||||
log.info("已进入房间准备成功: {}", robotUser.getConnecId());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("加入房间时发生错误", e);
|
log.error("加入房间时发生错误", e);
|
||||||
|
//发生异常时清理资源
|
||||||
|
String failedConnecId = roomId + "_" + robotId;
|
||||||
|
cleanupFailedRobot(null, failedConnecId, roomId);
|
||||||
} finally {
|
} finally {
|
||||||
jedis0.close();
|
jedis0.close();
|
||||||
jedis2.close();
|
jedis2.close();
|
||||||
|
|
@ -278,7 +353,7 @@ public class EXGameController extends GameController {
|
||||||
lastAccessTime.remove(removedUser.getConnecId());
|
lastAccessTime.remove(removedUser.getConnecId());
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("清理机器人房间信息: {}", robotId);
|
log.info("清理机器人房间信息:" + robotId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -304,7 +379,7 @@ public class EXGameController extends GameController {
|
||||||
for (String connecId : expiredConnections) {
|
for (String connecId : expiredConnections) {
|
||||||
RobotUser robotUser = robotRoomMapping.get(connecId);
|
RobotUser robotUser = robotRoomMapping.get(connecId);
|
||||||
if (robotUser != null) {
|
if (robotUser != null) {
|
||||||
log.info("清理超时连接: {}, 机器人ID: {}", connecId, robotUser.getRobotId());
|
log.info("清理超时连接:" + connecId + ", 机器人 ID: " + robotUser.getRobotId());
|
||||||
robotConnectionManager.disconnectFromGameServer(connecId);
|
robotConnectionManager.disconnectFromGameServer(connecId);
|
||||||
}
|
}
|
||||||
robotRoomMapping.remove(connecId);
|
robotRoomMapping.remove(connecId);
|
||||||
|
|
@ -312,7 +387,7 @@ public class EXGameController extends GameController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!expiredConnections.isEmpty()) {
|
if (!expiredConnections.isEmpty()) {
|
||||||
log.info("本次清理了 {} 个超时连接", expiredConnections.size());
|
log.info("本次清理了 " + expiredConnections.size() + " 个超时连接");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -344,7 +419,7 @@ public class EXGameController extends GameController {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("检查Redis房间玩家信息时发生错误,roomId: {}, robotId: {}", roomId, robotId, e);
|
log.error("检查 Redis 房间玩家信息时发生错误,roomId: " + roomId + ", robotId: " + robotId, e);
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
jedis.close();
|
jedis.close();
|
||||||
|
|
@ -357,13 +432,129 @@ public class EXGameController extends GameController {
|
||||||
*/
|
*/
|
||||||
public static TaurusClient getFlsGameServerConnection(String connecId) {
|
public static TaurusClient getFlsGameServerConnection(String connecId) {
|
||||||
TaurusClient taurusClient = robotConnectionManager.getGameClient(connecId);
|
TaurusClient taurusClient = robotConnectionManager.getGameClient(connecId);
|
||||||
log.info("根据机器人ID和连接ID获取福禄寿游戏服务器连接 client: {}", taurusClient);
|
log.info("根据机器人 ID 和连接 ID 获取福禄寿游戏服务器连接 client: " + taurusClient);
|
||||||
if (taurusClient != null) {
|
if (taurusClient != null) {
|
||||||
log.debug("成功获取游戏服务器连接,connecId: {}", connecId);
|
log.debug("成功获取游戏服务器连接,connecId: " + connecId);
|
||||||
return taurusClient;
|
return taurusClient;
|
||||||
}
|
}
|
||||||
taurusClient = robotConnectionManager.connectToGameServer(connecId);
|
taurusClient = robotConnectionManager.connectToGameServer(connecId);
|
||||||
return taurusClient;
|
return taurusClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查机器人 ID 是否与房间内已有玩家冲突
|
||||||
|
*/
|
||||||
|
private boolean isPlayerIdConflictInRoom(String roomId, int robotId) {
|
||||||
|
Jedis jedis = Redis.use().getJedis();
|
||||||
|
Jedis jedis2 = Redis.use("group1_db2").getJedis();
|
||||||
|
try {
|
||||||
|
//查询该房间的玩家信息
|
||||||
|
String playersStr = jedis.hget("room:" + roomId, "players");
|
||||||
|
if (playersStr == null || playersStr.equals("[]")) {
|
||||||
|
log.info("房间{"+roomId+"}为空,机器人{"+robotId+"}可以加入");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String players = playersStr.substring(1, playersStr.length() - 1);
|
||||||
|
String[] playerIds = players.split(",");
|
||||||
|
|
||||||
|
//统计房间中的真人数量和机器人数量
|
||||||
|
int realPlayerCount = 0;
|
||||||
|
int robotCount = 0;
|
||||||
|
boolean hasSameRobot = false;
|
||||||
|
|
||||||
|
for (String playerIdStr : playerIds) {
|
||||||
|
try {
|
||||||
|
int playerId = Integer.parseInt(playerIdStr.trim());
|
||||||
|
|
||||||
|
String robotData = jedis2.hget("{robot}:" + playerId, "password");
|
||||||
|
boolean isRobot = (robotData != null);
|
||||||
|
|
||||||
|
if (isRobot) {
|
||||||
|
robotCount++;
|
||||||
|
//检查是否是相同的机器人 ID
|
||||||
|
if (playerId == robotId) {
|
||||||
|
hasSameRobot = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
realPlayerCount++;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.error("解析玩家 ID 失败:"+playerIdStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSameRobot) {
|
||||||
|
log.info("房间{"+roomId+"}中已有相同机器人{"+robotId+"},允许重复加入");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (realPlayerCount >= 2) {
|
||||||
|
log.warn("房间{"+roomId+"}中已有{"+realPlayerCount+"}个真人玩家,拒绝机器人{"+robotId+"}加入");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (realPlayerCount == 1) {
|
||||||
|
if (robotCount == 0) {
|
||||||
|
log.info("房间{"+roomId+"}中有 1 个真人玩家,允许第一个机器人{"+robotId+"}加入陪打");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
log.info("房间{"+roomId+"}中已有 1 个真人 +1 个机器人,拒绝额外机器人{"+robotId+"}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (robotCount >= 2) {
|
||||||
|
log.warn("房间{"+roomId+"}中已有{"+robotCount+"}个机器人,不再添加新机器人{"+robotId+"}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("房间{"+roomId+"}当前状态:真人{"+realPlayerCount+"}人,机器人{"+robotCount+"}个,允许机器人{"+robotId+"}加入");
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("检查房间人数时发生异常,roomId:{"+roomId+"}, robotId:{"+robotId+"}" + e);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
jedis.close();
|
||||||
|
jedis2.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理加入失败的机器人资源
|
||||||
|
*/
|
||||||
|
private void cleanupFailedRobot(RobotUser robotUser, String connecId, String roomId) {
|
||||||
|
try {
|
||||||
|
String robotId = robotUser != null ? robotUser.getRobotId() : "unknown";
|
||||||
|
log.info("开始清理失败机器人资源:robotId:{"+robotId+"}, connecId:{"+connecId+"}, roomId:{"+roomId+"}");
|
||||||
|
|
||||||
|
// 清理映射关系
|
||||||
|
if (robotUser != null) {
|
||||||
|
robotRoomMapping.remove(robotUser.getConnecId());
|
||||||
|
robotRoomMapping.remove(robotUser.getRobotId());
|
||||||
|
} else {
|
||||||
|
robotRoomMapping.remove(connecId);
|
||||||
|
if (connecId != null && connecId.contains("_")) {
|
||||||
|
String[] parts = connecId.split("_");
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
robotRoomMapping.remove(parts[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 断开 TCP 连接
|
||||||
|
TaurusClient client = robotUser != null ? robotUser.getClient() : null;
|
||||||
|
if (client != null && client.isConnected()) {
|
||||||
|
client.killConnection();
|
||||||
|
log.info("已清理失败机器人{"+robotId+"}的 TCP 连接");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知连接管理器清理资源
|
||||||
|
robotConnectionManager.disconnectFromGameServer(connecId);
|
||||||
|
|
||||||
|
log.info("完成失败机器人资源的清理:connecId:{"+connecId+"}, roomId:{"+roomId+"}");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("清理失败机器人资源时发生异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,8 +7,7 @@ import com.robot.MainServer;
|
||||||
import com.robot.data.Player;
|
import com.robot.data.Player;
|
||||||
import com.robot.data.Room;
|
import com.robot.data.Room;
|
||||||
import com.taurus.core.plugin.redis.Redis;
|
import com.taurus.core.plugin.redis.Redis;
|
||||||
import org.slf4j.Logger;
|
import com.taurus.core.util.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import redis.clients.jedis.Jedis;
|
import redis.clients.jedis.Jedis;
|
||||||
import robot.zp.info.RobotUser;
|
import robot.zp.info.RobotUser;
|
||||||
import robot.zp.thread.ResourceCleanupUtil;
|
import robot.zp.thread.ResourceCleanupUtil;
|
||||||
|
|
@ -22,7 +21,7 @@ import static robot.zp.EXGameController.robotRoomMapping;
|
||||||
* TCP服务端接收robot_mgr的协议 同时作为客户端连接game_zp_fls处理AI逻辑
|
* TCP服务端接收robot_mgr的协议 同时作为客户端连接game_zp_fls处理AI逻辑
|
||||||
*/
|
*/
|
||||||
public class EXMainServer extends MainServer{
|
public class EXMainServer extends MainServer{
|
||||||
private static final Logger log = LoggerFactory.getLogger(EXMainServer.class);
|
private static final Logger log = Logger.getLogger(EXMainServer.class);
|
||||||
|
|
||||||
private static final RobotConnectionManager robotConnectionManager = new RobotConnectionManager();
|
private static final RobotConnectionManager robotConnectionManager = new RobotConnectionManager();
|
||||||
|
|
||||||
|
|
@ -64,7 +63,7 @@ public class EXMainServer extends MainServer{
|
||||||
String robotskey = "g{"+Config.DEFAULT_GROUP_ID+"}:play:"+Config.DEFAULT_PID;
|
String robotskey = "g{"+Config.DEFAULT_GROUP_ID+"}:play:"+Config.DEFAULT_PID;
|
||||||
Map<String, String> maprobot = jedis2.hgetAll(robotskey);
|
Map<String, String> maprobot = jedis2.hgetAll(robotskey);
|
||||||
for(Map.Entry<String, String> entry : maprobot.entrySet()) {
|
for(Map.Entry<String, String> entry : maprobot.entrySet()) {
|
||||||
log.info("{}:{}", entry.getKey(), entry.getValue());
|
log.info(entry.getKey() + ":" + entry.getValue());
|
||||||
//是否创建
|
//是否创建
|
||||||
RobotUser robotUser = new RobotUser();
|
RobotUser robotUser = new RobotUser();
|
||||||
robotUser.setRobotId(entry.getKey());
|
robotUser.setRobotId(entry.getKey());
|
||||||
|
|
@ -87,8 +86,8 @@ public class EXMainServer extends MainServer{
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("福禄寿机器人服务器已启动");
|
log.info("福禄寿机器人服务器已启动");
|
||||||
log.info("服务器将监听端口 {} 用于接收robot_mgr管理协议", gameSetting.port);
|
log.info("服务器将监听端口 " + gameSetting.port + " 用于接收 robot_mgr 管理协议");
|
||||||
log.info("当前线程池配置: {}", ThreadPoolConfig.getThreadPoolStatus());
|
log.info("当前线程池配置:" + ThreadPoolConfig.getThreadPoolStatus());
|
||||||
|
|
||||||
jedis2.close();
|
jedis2.close();
|
||||||
}
|
}
|
||||||
|
|
@ -123,16 +122,16 @@ public class EXMainServer extends MainServer{
|
||||||
//每30秒执行一次资源清理
|
//每30秒执行一次资源清理
|
||||||
Thread.sleep(30000);
|
Thread.sleep(30000);
|
||||||
ResourceCleanupUtil.performCleanup();
|
ResourceCleanupUtil.performCleanup();
|
||||||
log.info("线程池状态: {}", ThreadPoolConfig.getThreadPoolStatus());
|
log.info("线程池状态:" + ThreadPoolConfig.getThreadPoolStatus());
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
break;
|
break;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("资源清理任务异常: {}", e.getMessage(), e);
|
log.error("资源清理任务异常:" + e.getMessage(), e);
|
||||||
// 发生异常时尝试清理
|
// 发生异常时尝试清理
|
||||||
try {
|
try {
|
||||||
ResourceCleanupUtil.performCleanup();
|
ResourceCleanupUtil.performCleanup();
|
||||||
} catch (Exception cleanupEx) {
|
} catch (Exception cleanupEx) {
|
||||||
log.error("异常清理也失败: {}", cleanupEx.getMessage(), cleanupEx);
|
log.error("异常清理也失败:" + cleanupEx.getMessage(), cleanupEx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import static robot.zp.EXGameController.robotRoomMapping;
|
||||||
*/
|
*/
|
||||||
public class RobotConnectionManager {
|
public class RobotConnectionManager {
|
||||||
|
|
||||||
private final Logger log = Logger.getLogger(RobotConnectionManager.class);
|
private static final Logger log = Logger.getLogger(RobotConnectionManager.class);
|
||||||
private static final Map<String, FuLuShouHandler> fuLuShouHandlerInstances = new ConcurrentHashMap<>();
|
private static final Map<String, FuLuShouHandler> fuLuShouHandlerInstances = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
//记录活跃连接 用于资源清理判断
|
//记录活跃连接 用于资源清理判断
|
||||||
|
|
@ -44,39 +44,14 @@ public class RobotConnectionManager {
|
||||||
|
|
||||||
//连接最大生存时间(5 分钟)
|
//连接最大生存时间(5 分钟)
|
||||||
private static final long MAX_CONNECTION_LIFETIME = 5 * 60 * 1000;
|
private static final long MAX_CONNECTION_LIFETIME = 5 * 60 * 1000;
|
||||||
|
|
||||||
private final EXGameController exGameController;
|
private final EXGameController exGameController;
|
||||||
|
|
||||||
private final String host= Config.GAME_SERVER_HOST;
|
private final String host= Config.GAME_SERVER_HOST;
|
||||||
private final int port= Integer.parseInt(Config.GAME_SERVER_PORT);
|
private final int port= Integer.parseInt(Config.GAME_SERVER_PORT);
|
||||||
|
|
||||||
/*福禄寿游戏算法相关 start*/
|
|
||||||
private final Map<String, Map<Integer, List<Integer>>> playerOutcardsMapByConn = new ConcurrentHashMap<>();
|
|
||||||
private final Map<String, Map<Integer, List<Integer>>> playerchisMapByConn = new ConcurrentHashMap<>();
|
|
||||||
private final Map<String, Map<Integer, List<Integer>>> playerpengsMapByConn = new ConcurrentHashMap<>();
|
|
||||||
private final Map<String, Map<Integer, List<Integer>>> playermingsMapByConn = new ConcurrentHashMap<>();
|
|
||||||
private final Map<String, Map<Integer, List<Integer>>> playerzisMapByConn = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private Map<Integer, List<Integer>> getPlayerOutcardsMap(String connecId) {
|
|
||||||
return playerOutcardsMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>());
|
|
||||||
}
|
|
||||||
private Map<Integer, List<Integer>> getPlayerchisMap(String connecId) {
|
|
||||||
return playerchisMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>());
|
|
||||||
}
|
|
||||||
private Map<Integer, List<Integer>> getPlayerpengsMap(String connecId) {
|
|
||||||
return playerpengsMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>());
|
|
||||||
}
|
|
||||||
private Map<Integer, List<Integer>> getPlayermingsMap(String connecId) {
|
|
||||||
return playermingsMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>());
|
|
||||||
}
|
|
||||||
private Map<Integer, List<Integer>> getPlayerzisMap(String connecId) {
|
|
||||||
return playerzisMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>());
|
|
||||||
}
|
|
||||||
private int pid = 0;
|
private int pid = 0;
|
||||||
private Map<Integer, Integer> count = new HashMap<Integer, Integer>();
|
private Map<Integer, Integer> count = new HashMap<Integer, Integer>();
|
||||||
/*福禄寿游戏算法相关 end*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public RobotConnectionManager() {
|
public RobotConnectionManager() {
|
||||||
exGameController = new EXGameController();
|
exGameController = new EXGameController();
|
||||||
|
|
@ -99,10 +74,17 @@ public class RobotConnectionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
FuLuShouHandler newInstance = new FuLuShouHandler();
|
FuLuShouHandler newInstance = new FuLuShouHandler();
|
||||||
log.info("创建新的 FuLuShouHandler 实例:{}", connecId);
|
|
||||||
|
//从 Redis 恢复状态
|
||||||
|
boolean restored = newInstance.restoreFromRedis(connecId);
|
||||||
|
if (restored) {
|
||||||
|
log.info("从 Redis 恢复 FuLuShouHandler 实例:" + connecId);
|
||||||
|
} else {
|
||||||
|
log.info("创建新的 FuLuShouHandler 实例:" + connecId);
|
||||||
|
}
|
||||||
|
|
||||||
fuLuShouHandlerInstances.put(connecId, newInstance);
|
fuLuShouHandlerInstances.put(connecId, newInstance);
|
||||||
log.info("当前 FuLuShouHandler 实例总数:{}", fuLuShouHandlerInstances.size());
|
log.info("当前 FuLuShouHandler 实例总数:" + fuLuShouHandlerInstances.size());
|
||||||
return newInstance;
|
return newInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +121,7 @@ public class RobotConnectionManager {
|
||||||
* 断开与游戏服务器的连接(主动断开)
|
* 断开与游戏服务器的连接(主动断开)
|
||||||
*/
|
*/
|
||||||
public void disconnectFromGameServer(String connecId) {
|
public void disconnectFromGameServer(String connecId) {
|
||||||
log.info("开始主动断开连接:{}", connecId);
|
log.info("开始主动断开连接:" + connecId);
|
||||||
RobotUser robotUser = robotRoomMapping.remove(connecId);
|
RobotUser robotUser = robotRoomMapping.remove(connecId);
|
||||||
|
|
||||||
//标记连接为非活跃
|
//标记连接为非活跃
|
||||||
|
|
@ -148,22 +130,20 @@ public class RobotConnectionManager {
|
||||||
|
|
||||||
//清理连接数据
|
//清理连接数据
|
||||||
if (connecId != null) {
|
if (connecId != null) {
|
||||||
|
//从 Redis 删除状态
|
||||||
|
FuLuShouHandler.removeFromRedis(connecId);
|
||||||
|
|
||||||
FuLuShouHandler handler = fuLuShouHandlerInstances.get(connecId);
|
FuLuShouHandler handler = fuLuShouHandlerInstances.get(connecId);
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
//清理所有集合数据以释放内存
|
//清理所有集合数据以释放内存
|
||||||
handler.clearAllData();
|
handler.clearAllData();
|
||||||
log.info("清空 FuLuShouHandler 集合数据:{}", connecId);
|
log.info("清空 FuLuShouHandler 集合数据:" + connecId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//移除实例和相关数据
|
//移除实例和相关数据
|
||||||
fuLuShouHandlerInstances.remove(connecId);
|
fuLuShouHandlerInstances.remove(connecId);
|
||||||
playerOutcardsMapByConn.remove(connecId);
|
|
||||||
playerchisMapByConn.remove(connecId);
|
log.info("清理完成,当前活跃连接数:" + activeConnections.size() + ", 实例数:" + fuLuShouHandlerInstances.size());
|
||||||
playerpengsMapByConn.remove(connecId);
|
|
||||||
playermingsMapByConn.remove(connecId);
|
|
||||||
playerzisMapByConn.remove(connecId);
|
|
||||||
|
|
||||||
log.info("清理完成,当前活跃连接数:{}, 实例数:{}", activeConnections.size(), fuLuShouHandlerInstances.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (robotUser != null) {
|
if (robotUser != null) {
|
||||||
|
|
@ -173,12 +153,12 @@ public class RobotConnectionManager {
|
||||||
if (client.isConnected()) {
|
if (client.isConnected()) {
|
||||||
client.killConnection();
|
client.killConnection();
|
||||||
}
|
}
|
||||||
log.info("客户端主动断开连接完成:{}", connecId);
|
log.info("客户端主动断开连接完成:" + connecId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("断开客户端连接时发生异常:{}, 错误:{}", connecId, e.getMessage(), e);
|
log.error("断开客户端连接时发生异常:" + connecId + ", 错误:" + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.warn("客户端连接不存在:{}", connecId);
|
log.warn("客户端连接不存在:" + connecId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//同时清理机器人房间映射
|
//同时清理机器人房间映射
|
||||||
|
|
@ -202,12 +182,12 @@ public class RobotConnectionManager {
|
||||||
|
|
||||||
//清理过期连接
|
//清理过期连接
|
||||||
for (String connecId : expiredConnections) {
|
for (String connecId : expiredConnections) {
|
||||||
log.info("清理过期连接实例:{}", connecId);
|
log.info("清理过期连接实例:" + connecId);
|
||||||
disconnectFromGameServer(connecId);
|
disconnectFromGameServer(connecId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!expiredConnections.isEmpty()) {
|
if (!expiredConnections.isEmpty()) {
|
||||||
log.info("本次清理了 {} 个过期连接实例", expiredConnections.size());
|
log.info("本次清理了 " + expiredConnections.size() + " 个过期连接实例");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,20 +200,21 @@ public class RobotConnectionManager {
|
||||||
@Override
|
@Override
|
||||||
public void handleEvent(Event event) {
|
public void handleEvent(Event event) {
|
||||||
//获取 msg
|
//获取 msg
|
||||||
Message message = (Message) event.getParameter("msg");
|
Message message = (Message) event.getParameter("msg");
|
||||||
|
|
||||||
ITObject param = message.param;
|
ITObject param = message.param;
|
||||||
//回调协议号
|
//回调协议号
|
||||||
String command = message.command;
|
String command = message.command;
|
||||||
log.debug("fls OnEvent msg: {}", command);
|
log.info("【福禄寿】收到游戏协议:command=" + command + ", connecId=" + connecId);
|
||||||
|
|
||||||
//根据玩法ID处理不同的回调
|
//根据玩法 ID 处理不同的回调
|
||||||
if (StringUtil.isNotEmpty(command)) {
|
if (StringUtil.isNotEmpty(command)) {
|
||||||
//直接处理协议
|
log.info("开始处理协议:" + command + ", connecId=" + connecId);
|
||||||
handleProtocol(command, message, client, connecId);
|
//直接处理协议
|
||||||
}
|
handleProtocol(command, message, client, connecId);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//添加连接状态监听器
|
//添加连接状态监听器
|
||||||
IEventListener connectListener = new IEventListener() {
|
IEventListener connectListener = new IEventListener() {
|
||||||
|
|
@ -258,6 +239,11 @@ public class RobotConnectionManager {
|
||||||
String connecId = robotUser.getCurrentRoomId()+"_"+robotUser.getRobotId();
|
String connecId = robotUser.getCurrentRoomId()+"_"+robotUser.getRobotId();
|
||||||
if(client.isConnected()){
|
if(client.isConnected()){
|
||||||
try {
|
try {
|
||||||
|
log.info(String.valueOf(response.messageData.param));
|
||||||
|
if (response.messageData.param==null) {
|
||||||
|
log.info("警告:reconnectToGameServer 重连时未获取到参数");
|
||||||
|
return;
|
||||||
|
}
|
||||||
ITObject obj = response.messageData.param.getTObject("tableInfo");
|
ITObject obj = response.messageData.param.getTObject("tableInfo");
|
||||||
ITObject reloadInfo = response.messageData.param.getTObject("reloadInfo");
|
ITObject reloadInfo = response.messageData.param.getTObject("reloadInfo");
|
||||||
if (obj != null) {
|
if (obj != null) {
|
||||||
|
|
@ -272,10 +258,10 @@ public class RobotConnectionManager {
|
||||||
robotUser.setSeat(seat);
|
robotUser.setSeat(seat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("playerData: {}", playerData);
|
log.info("playerData: " + playerData);
|
||||||
|
|
||||||
log.info("obj: {}", obj);
|
log.info("obj: " + obj);
|
||||||
log.info("reloadInfo: {}", reloadInfo);
|
log.info("reloadInfo: " + reloadInfo);
|
||||||
if (reloadInfo != null) {
|
if (reloadInfo != null) {
|
||||||
//重连回来的
|
//重连回来的
|
||||||
int curren_outcard_seat = reloadInfo.getInt("curren_outcard_seat");
|
int curren_outcard_seat = reloadInfo.getInt("curren_outcard_seat");
|
||||||
|
|
@ -301,7 +287,7 @@ public class RobotConnectionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("hcard>0{}", hcard);
|
log.info("hcard>0" + hcard);
|
||||||
if (hcard.size() > 0) {
|
if (hcard.size() > 0) {
|
||||||
//同步手牌
|
//同步手牌
|
||||||
FuLuShouHandler currentInstance = getFuLuShouHandlerInstance(connecId);
|
FuLuShouHandler currentInstance = getFuLuShouHandlerInstance(connecId);
|
||||||
|
|
@ -311,9 +297,9 @@ public class RobotConnectionManager {
|
||||||
if (currentHand.isEmpty() || hcard.size() > currentHand.size()) {
|
if (currentHand.isEmpty() || hcard.size() > currentHand.size()) {
|
||||||
//手牌集合为空 或者 玩家出牌了
|
//手牌集合为空 或者 玩家出牌了
|
||||||
currentInstance.updateHandCard(hcard);
|
currentInstance.updateHandCard(hcard);
|
||||||
log.info("断线重连:同步手牌数据,服务器手牌:{}", hcard);
|
log.info("断线重连:同步手牌数据,服务器手牌:" + hcard);
|
||||||
} else {
|
} else {
|
||||||
log.info("断线重连:使用Redis恢复的手牌数据,数量:{}", currentHand.size());
|
log.info("断线重连:使用 Redis 恢复的手牌数据,数量:" + currentHand.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outcard_list.size() > 0) {
|
if (outcard_list.size() > 0) {
|
||||||
|
|
@ -326,28 +312,23 @@ public class RobotConnectionManager {
|
||||||
List<Integer> currentOutCards = currentInstance.getChuGuoCardInhand();
|
List<Integer> currentOutCards = currentInstance.getChuGuoCardInhand();
|
||||||
if (currentOutCards.isEmpty() || outcards.size() > currentOutCards.size()) {
|
if (currentOutCards.isEmpty() || outcards.size() > currentOutCards.size()) {
|
||||||
currentInstance.updateOutCard(outcards);
|
currentInstance.updateOutCard(outcards);
|
||||||
log.info("断线重连:同步出牌数据,服务器出牌:{}", outcards);
|
log.info("断线重连:同步出牌数据,服务器出牌:" + outcards);
|
||||||
} else {
|
} else {
|
||||||
log.info("断线重连:使用Redis恢复的出牌数据,数量:{}", currentOutCards.size());
|
log.info("断线重连:使用 Redis 恢复的出牌数据,数量:" + currentOutCards.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//非阻塞的延迟执行,增加更完善的异常处理
|
//非阻塞的延迟执行
|
||||||
scheduleDelay(() -> {
|
scheduleDelay(() -> {
|
||||||
try {
|
try {
|
||||||
//重新获取当前实例,确保数据一致性
|
//重新获取当前实例 确保数据一致性
|
||||||
FuLuShouHandler reconnectedInstance = getFuLuShouHandlerInstance(connecId);
|
FuLuShouHandler reconnectedInstance = getFuLuShouHandlerInstance(connecId);
|
||||||
Map<Integer, List<Integer>> currentPlayerOutcardsMap = getPlayerOutcardsMap(connecId);
|
|
||||||
Map<Integer, List<Integer>> currentPlayerchisMap = getPlayerchisMap(connecId);
|
|
||||||
Map<Integer, List<Integer>> currentPlayerpengsMap = getPlayerpengsMap(connecId);
|
|
||||||
Map<Integer, List<Integer>> currentPlayermingsMap = getPlayermingsMap(connecId);
|
|
||||||
Map<Integer, List<Integer>> currentPlayerzisMap = getPlayerzisMap(connecId);
|
|
||||||
|
|
||||||
reconnectedInstance.outCard(client, currentPlayerOutcardsMap, currentPlayerchisMap, currentPlayerpengsMap, currentPlayermingsMap, currentPlayerzisMap);
|
reconnectedInstance.outCard(client);
|
||||||
log.info("断线重连后成功执行出牌操作");
|
log.info("断线重连后成功执行出牌操作");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("断线重连后执行出牌操作时发生异常", e);
|
log.error("断线重连后执行出牌操作时发生异常", e);
|
||||||
//即使出牌失败,也要确保连接状态正确
|
//即使出牌失败 也要确保连接状态正确
|
||||||
try {
|
try {
|
||||||
if (robotUser != null) {
|
if (robotUser != null) {
|
||||||
robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_READY);
|
robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_READY);
|
||||||
|
|
@ -373,10 +354,8 @@ public class RobotConnectionManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理接收到的游戏协议
|
* 处理接收到的游戏协议
|
||||||
* 福禄寿支持的协议:
|
*
|
||||||
* 核心流程:811(发牌), 819(摸牌), 812(出牌广播), 813(出牌提示), 814(放招提示), 612(动作), 611(出牌), 815(动作通知), 816(胡牌), 817(结算), 820(换玩家)
|
* 福禄寿协议
|
||||||
* 飘鸟系统:1015(飘操作), 833(飘鸟提示), 2031(飘鸟提示 reload), 2032(飘鸟事件)
|
|
||||||
* 房间相关:2001, 2002, 2005, 2008, 2009
|
|
||||||
*/
|
*/
|
||||||
private void handleProtocol(String command, Message message, TaurusClient client, String connecId) {
|
private void handleProtocol(String command, Message message, TaurusClient client, String connecId) {
|
||||||
RobotUser robotUser = robotRoomMapping.get(connecId);
|
RobotUser robotUser = robotRoomMapping.get(connecId);
|
||||||
|
|
@ -385,7 +364,7 @@ public class RobotConnectionManager {
|
||||||
EXGameController.updateLastAccessTime(connecId);
|
EXGameController.updateLastAccessTime(connecId);
|
||||||
|
|
||||||
if (robotUser == null) {
|
if (robotUser == null) {
|
||||||
log.error("未找到机器人用户信息,连接ID: {}", connecId);
|
log.error("未找到机器人用户信息,连接 ID: " + connecId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,145 +377,64 @@ public class RobotConnectionManager {
|
||||||
//福禄寿 机器人处理事件
|
//福禄寿 机器人处理事件
|
||||||
//初始化手牌
|
//初始化手牌
|
||||||
if ("811".equalsIgnoreCase(command)) {
|
if ("811".equalsIgnoreCase(command)) {
|
||||||
|
log.info("【811】初始化手牌,connecId=" + connecId);
|
||||||
robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_WORKING);
|
robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_WORKING);
|
||||||
//初始化手牌
|
FuLuShouHandler currentInstance = fuLuShouHandlerInstances.get(connecId);
|
||||||
String key = robotId+"";
|
currentInstance.initHandCards(message);
|
||||||
if (jedis2.hget("{robortInfo}:" + key, "circleId") != null && jedis2.hget("{robortInfo}:" + key, "pid") != null) {
|
currentInstance.saveToRedis(connecId);
|
||||||
String circleId = jedis2.hget("{robortInfo}:" + key, "circleId");
|
|
||||||
String pid = jedis2.hget("{robortInfo}:" + key, "pid");
|
|
||||||
String getStart = "g{" + circleId + "}:play:" + pid;
|
|
||||||
if (!pid.equals("0")){
|
|
||||||
jedis2.hset(getStart, key, "2");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handler.initHandCards(message);
|
|
||||||
}
|
}
|
||||||
//出牌广播
|
//出牌广播
|
||||||
else if ("812".equalsIgnoreCase(command)) {
|
else if ("812".equalsIgnoreCase(command)) {
|
||||||
ITArray outcard_map = param.getTArray("outcard_map");
|
log.info("【812】出牌广播,connecId=" + connecId);
|
||||||
ITArray opchicards = param.getTArray("opchicards");
|
FuLuShouHandler currentInstance = fuLuShouHandlerInstances.get(connecId);
|
||||||
ITArray oppengcards = param.getTArray("oppengcards");
|
currentInstance.onDiscardBroadcast(message);
|
||||||
ITArray opmingcards = param.getTArray("opmingcards");
|
currentInstance.saveToRedis(connecId);
|
||||||
ITArray opzicards = param.getTArray("opzicards");
|
|
||||||
|
|
||||||
//获取当前连接专用的Maps
|
|
||||||
Map<Integer, List<Integer>> currentPlayerOutcardsMap = getPlayerOutcardsMap(connecId);
|
|
||||||
Map<Integer, List<Integer>> currentPlayerchisMap = getPlayerchisMap(connecId);
|
|
||||||
Map<Integer, List<Integer>> currentPlayerpengsMap = getPlayerpengsMap(connecId);
|
|
||||||
Map<Integer, List<Integer>> currentPlayermingsMap = getPlayermingsMap(connecId);
|
|
||||||
Map<Integer, List<Integer>> currentPlayerzisMap = getPlayerzisMap(connecId);
|
|
||||||
|
|
||||||
//清空旧数据 用新数据完全覆盖
|
|
||||||
currentPlayerOutcardsMap.clear();
|
|
||||||
currentPlayerchisMap.clear();
|
|
||||||
currentPlayerpengsMap.clear();
|
|
||||||
currentPlayermingsMap.clear();
|
|
||||||
currentPlayerzisMap.clear();
|
|
||||||
//出过的牌
|
|
||||||
if (outcard_map != null) {
|
|
||||||
for (int i = 0; i < outcard_map.size(); i++) {
|
|
||||||
ITObject playerData = outcard_map.getTObject(i);
|
|
||||||
int playerId = playerData.getInt("playerId");
|
|
||||||
ITArray outcardsArray = playerData.getTArray("outcards");
|
|
||||||
|
|
||||||
List<Integer> outcardsList = new ArrayList<>();
|
|
||||||
for (int j = 0; j < outcardsArray.size(); j++) {
|
|
||||||
outcardsList.add(outcardsArray.getInt(j));
|
|
||||||
}
|
|
||||||
|
|
||||||
//存储到当前连接的Map中(覆盖旧数据)
|
|
||||||
currentPlayerOutcardsMap.put(playerId, outcardsList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//吃的牌
|
|
||||||
if (opchicards != null) {
|
|
||||||
for (int i = 0; i < opchicards.size(); i++) {
|
|
||||||
ITObject playerData = opchicards.getTObject(i);
|
|
||||||
int playerId = playerData.getInt("playerId");
|
|
||||||
ITArray outchiArray = playerData.getTArray("opchicards");
|
|
||||||
|
|
||||||
List<Integer> outchiList = new ArrayList<>();
|
|
||||||
for (int j = 0; j < outchiArray.size(); j++) {
|
|
||||||
outchiList.add(outchiArray.getInt(j));
|
|
||||||
}
|
|
||||||
currentPlayerchisMap.put(playerId, outchiList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//碰的牌
|
|
||||||
if (oppengcards != null) {
|
|
||||||
for (int i = 0; i < oppengcards.size(); i++) {
|
|
||||||
ITObject playerData = oppengcards.getTObject(i);
|
|
||||||
int playerId = playerData.getInt("playerId");
|
|
||||||
ITArray outpengArray = playerData.getTArray("oppengcards");
|
|
||||||
|
|
||||||
List<Integer> outpengList = new ArrayList<>();
|
|
||||||
for (int j = 0; j < outpengArray.size(); j++) {
|
|
||||||
outpengList.add(outpengArray.getInt(j));
|
|
||||||
}
|
|
||||||
currentPlayerpengsMap.put(playerId, outpengList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//明杠的牌
|
|
||||||
if (opmingcards != null) {
|
|
||||||
for (int i = 0; i < opmingcards.size(); i++) {
|
|
||||||
ITObject playerData = opmingcards.getTObject(i);
|
|
||||||
int playerId = playerData.getInt("playerId");
|
|
||||||
ITArray outmingArray = playerData.getTArray("opmingcards");
|
|
||||||
|
|
||||||
List<Integer> outmingList = new ArrayList<>();
|
|
||||||
for (int j = 0; j < outmingArray.size(); j++) {
|
|
||||||
outmingList.add(outmingArray.getInt(j));
|
|
||||||
}
|
|
||||||
currentPlayermingsMap.put(playerId, outmingList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//暗杠的牌
|
|
||||||
if (opzicards != null) {
|
|
||||||
for (int i = 0; i < opzicards.size(); i++) {
|
|
||||||
ITObject playerData = opzicards.getTObject(i);
|
|
||||||
int playerId = playerData.getInt("playerId");
|
|
||||||
ITArray outziArray = playerData.getTArray("opzicards");
|
|
||||||
|
|
||||||
List<Integer> outziList = new ArrayList<>();
|
|
||||||
for (int j = 0; j < outziArray.size(); j++) {
|
|
||||||
outziList.add(outziArray.getInt(j));
|
|
||||||
}
|
|
||||||
currentPlayerzisMap.put(playerId, outziList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.onDiscardBroadcast(message);
|
|
||||||
}
|
}
|
||||||
//摸牌
|
//未知协议 3005 - 可能是游戏结束或其他通知
|
||||||
|
else if ("3005".equalsIgnoreCase(command)) {
|
||||||
|
log.warn("【3005】收到未知协议 3005,connecId=" + connecId + ", param=" + param);
|
||||||
|
log.warn("这可能是游戏结束、流局或其他系统通知,需要检查服务器文档");
|
||||||
|
// TODO: 根据 3005 的实际含义添加处理逻辑
|
||||||
|
// 如果是游戏结束,可能需要清空状态或等待下一局
|
||||||
|
}
|
||||||
|
//摸牌事件
|
||||||
else if ("819".equalsIgnoreCase(command)) {
|
else if ("819".equalsIgnoreCase(command)) {
|
||||||
handler.drawCard(message);
|
log.info("【819】摸牌事件,connecId=" + connecId + ", param=" + param);
|
||||||
|
FuLuShouHandler currentInstance = fuLuShouHandlerInstances.get(connecId);
|
||||||
|
currentInstance.drawCard(message);
|
||||||
|
currentInstance.saveToRedis(connecId);
|
||||||
}
|
}
|
||||||
//出牌提示
|
//出牌提示(轮到机器人出牌)
|
||||||
else if ("813".equalsIgnoreCase(command)) {
|
else if ("813".equalsIgnoreCase(command)) {
|
||||||
//获取当前连接的 Maps
|
log.info("【813】出牌提示,connecId=" + connecId);
|
||||||
Map<Integer, List<Integer>> currentPlayerOutcardsMap = getPlayerOutcardsMap(connecId);
|
FuLuShouHandler currentInstance = fuLuShouHandlerInstances.get(connecId);
|
||||||
Map<Integer, List<Integer>> currentPlayerchisMap = getPlayerchisMap(connecId);
|
currentInstance.makeDiscardDecision(client);
|
||||||
Map<Integer, List<Integer>> currentPlayerpengsMap = getPlayerpengsMap(connecId);
|
currentInstance.saveToRedis(connecId);
|
||||||
Map<Integer, List<Integer>> currentPlayermingsMap = getPlayermingsMap(connecId);
|
|
||||||
Map<Integer, List<Integer>> currentPlayerzisMap = getPlayerzisMap(connecId);
|
|
||||||
|
|
||||||
handler.makeDiscardDecision(client, currentPlayerOutcardsMap, currentPlayerchisMap, currentPlayerpengsMap, currentPlayermingsMap, currentPlayerzisMap);
|
|
||||||
}
|
}
|
||||||
//放招提示
|
//放招提示/动作提示
|
||||||
else if ("814".equalsIgnoreCase(command)) {
|
else if ("814".equalsIgnoreCase(command)) {
|
||||||
handler.actionTip(param, client);
|
log.info("【814】动作提示,connecId=" + connecId);
|
||||||
|
log.info("【814】参数详情:" + param.toString());
|
||||||
|
ITArray tipList = param.getTArray("tip_list");
|
||||||
|
if (tipList != null) {
|
||||||
|
log.info("【814】tip_list 数量:" + tipList.size());
|
||||||
|
for (int i = 0; i < tipList.size(); i++) {
|
||||||
|
TObject tip = (TObject) tipList.get(i).getObject();
|
||||||
|
log.info("【814】tip[" + i + "]: type=" + tip.getInt("type") + ", id=" + tip.getInt("id"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FuLuShouHandler currentInstance = fuLuShouHandlerInstances.get(connecId);
|
||||||
|
currentInstance.actionTip(param, client);
|
||||||
|
currentInstance.saveToRedis(connecId);
|
||||||
|
log.info("【814】处理完成,已保存到 Redis");
|
||||||
}
|
}
|
||||||
//飘操作
|
//飘操作
|
||||||
else if ("1015".equalsIgnoreCase(command)) {
|
else if ("1015".equalsIgnoreCase(command)) {
|
||||||
log.info("收到飘操作协议:{}", param);
|
log.info("收到飘操作协议:" + param);
|
||||||
}
|
}
|
||||||
//2026.02.03修改 玩家加入房间
|
//玩家加入房间
|
||||||
else if ("2001".equalsIgnoreCase(command)) {
|
else if ("2001".equalsIgnoreCase(command)) {
|
||||||
//直接使用定时任务替代Thread.sleep,避免嵌套异步调用
|
//直接使用定时任务替代Thread.sleep
|
||||||
scheduleDelay(() -> {
|
scheduleDelay(() -> {
|
||||||
Jedis jedis = Redis.use().getJedis();
|
Jedis jedis = Redis.use().getJedis();
|
||||||
try {
|
try {
|
||||||
|
|
@ -559,7 +457,7 @@ public class RobotConnectionManager {
|
||||||
//更新机器人剩余数量
|
//更新机器人剩余数量
|
||||||
updateLeftoverRobot(robotId);
|
updateLeftoverRobot(robotId);
|
||||||
disconnectFromGameServer(connecId);
|
disconnectFromGameServer(connecId);
|
||||||
log.info("2001发送退出房间协议1005,robotId: {}", robotId);
|
log.info("2001 发送退出房间协议 1005,robotId: " + robotId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -573,11 +471,11 @@ public class RobotConnectionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 6, TimeUnit.SECONDS);
|
}, 6, TimeUnit.SECONDS);
|
||||||
log.info("玩家{}加入房间:{}", robotUser.getCurrentRoomId(), param);
|
log.info("玩家" + robotUser.getCurrentRoomId() + "加入房间:" + param);
|
||||||
}
|
}
|
||||||
//2026.02.03修改 玩家退出房间也要检查
|
//玩家退出房间
|
||||||
else if ("2002".equalsIgnoreCase(command)) {
|
else if ("2002".equalsIgnoreCase(command)) {
|
||||||
//直接使用定时任务替代Thread.sleep,避免嵌套异步调用
|
//直接使用定时任务替代Thread.sleep
|
||||||
scheduleDelay(() -> {
|
scheduleDelay(() -> {
|
||||||
Jedis jedis = Redis.use().getJedis();
|
Jedis jedis = Redis.use().getJedis();
|
||||||
try {
|
try {
|
||||||
|
|
@ -600,7 +498,7 @@ public class RobotConnectionManager {
|
||||||
//更新机器人剩余数量
|
//更新机器人剩余数量
|
||||||
updateLeftoverRobot(robotId);
|
updateLeftoverRobot(robotId);
|
||||||
disconnectFromGameServer(connecId);
|
disconnectFromGameServer(connecId);
|
||||||
log.info("2002发送退出房间协议1005,robotId: {}", robotId);
|
log.info("2002 发送退出房间协议 1005,robotId: " + robotId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -614,22 +512,22 @@ public class RobotConnectionManager {
|
||||||
}
|
}
|
||||||
}, 6, TimeUnit.SECONDS);
|
}, 6, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
//2026.02.05修改 玩家解散房间
|
//解散房间
|
||||||
else if ("2005".equalsIgnoreCase(command)) {
|
else if ("2005".equalsIgnoreCase(command)) {
|
||||||
EXGameController.removeRobotRoomInfo(String.valueOf(robotId));
|
EXGameController.removeRobotRoomInfo(String.valueOf(robotId));
|
||||||
//更新机器人剩余数量
|
//更新机器人剩余数量
|
||||||
updateLeftoverRobot(robotId);
|
updateLeftoverRobot(robotId);
|
||||||
disconnectFromGameServer(connecId);
|
disconnectFromGameServer(connecId);
|
||||||
log.info("2005玩家发送解散房间协议,robotId: {}", robotId);
|
log.info("2005 玩家发送解散房间协议,robotId: " + robotId);
|
||||||
}
|
}
|
||||||
//2026.02.03修改 解散房间时候恢复机器人账号可以使用
|
//恢复机器人账号
|
||||||
else if ("2008".equalsIgnoreCase(command)) {
|
else if ("2008".equalsIgnoreCase(command)) {
|
||||||
updateLeftoverRobot(Integer.parseInt(robotUser.getRobotId()));
|
updateLeftoverRobot(Integer.parseInt(robotUser.getRobotId()));
|
||||||
disconnectFromGameServer(connecId);
|
disconnectFromGameServer(connecId);
|
||||||
}
|
}
|
||||||
//2026.02.03修改 通过机器人房间映射直接获取房间信息
|
//获取房间信息
|
||||||
else if ("2009".equalsIgnoreCase(command)) {
|
else if ("2009".equalsIgnoreCase(command)) {
|
||||||
//直接使用定时任务替代Thread.sleep,避免嵌套异步调用
|
//直接使用定时任务替代Thread.sleep
|
||||||
scheduleDelay(() -> {
|
scheduleDelay(() -> {
|
||||||
Jedis jedis = null;
|
Jedis jedis = null;
|
||||||
try {
|
try {
|
||||||
|
|
@ -666,18 +564,18 @@ public class RobotConnectionManager {
|
||||||
disconnectFromGameServer(connecId);
|
disconnectFromGameServer(connecId);
|
||||||
//更新机器人剩余数量
|
//更新机器人剩余数量
|
||||||
updateLeftoverRobot(paramRobotId);
|
updateLeftoverRobot(paramRobotId);
|
||||||
log.info("2009发送退出房间协议1005,robotId: {}", paramRobotId);
|
log.info("2009 发送退出房间协议 1005,robotId: " + paramRobotId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
log.error("2009协议数字格式异常,robotId: {}, connecId: {}", param.get("aid"), connecId);
|
log.error("2009 协议数字格式异常,robotId: " + param.get("aid") + ", connecId: " + connecId);
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
log.error("2009协议空指针异常,connecId: {}", connecId);
|
log.error("2009 协议空指针异常,connecId: " + connecId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("2009协议处理异常: {}, connecId: {}", e.getMessage(), connecId, e);
|
log.error("2009 协议处理异常:" + e.getMessage() + ", connecId: " + connecId, e);
|
||||||
} finally {
|
} finally {
|
||||||
if (jedis != null) {
|
if (jedis != null) {
|
||||||
jedis.close();
|
jedis.close();
|
||||||
|
|
@ -685,11 +583,11 @@ public class RobotConnectionManager {
|
||||||
}
|
}
|
||||||
}, 6, TimeUnit.SECONDS);
|
}, 6, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
//结算
|
//结算事件
|
||||||
else if ("817".equalsIgnoreCase(command)) {
|
else if ("817".equalsIgnoreCase(command)) {
|
||||||
//清空所有 FuLuShouHandler 相关的集合数据
|
//清空所有 FuLuShouHandler 相关的集合数据
|
||||||
handler.clearAllData();
|
handler.clearAllData();
|
||||||
|
|
||||||
Integer type = param.getInt("type");
|
Integer type = param.getInt("type");
|
||||||
if (type == 1 || type == 2) { //为 1 为大结算 为 2 为解散
|
if (type == 1 || type == 2) { //为 1 为大结算 为 2 为解散
|
||||||
if (count != null && count.containsKey(pid)) {
|
if (count != null && count.containsKey(pid)) {
|
||||||
|
|
@ -700,7 +598,7 @@ public class RobotConnectionManager {
|
||||||
}
|
}
|
||||||
//更新机器人剩余数量
|
//更新机器人剩余数量
|
||||||
updateLeftoverRobot(Integer.parseInt(robotUser.getRobotId()));
|
updateLeftoverRobot(Integer.parseInt(robotUser.getRobotId()));
|
||||||
|
|
||||||
//游戏结束后主动断开连接
|
//游戏结束后主动断开连接
|
||||||
disconnectFromGameServer(connecId);
|
disconnectFromGameServer(connecId);
|
||||||
}
|
}
|
||||||
|
|
@ -709,29 +607,37 @@ public class RobotConnectionManager {
|
||||||
client.send("1003", params, new ICallback<MessageResponse>() {
|
client.send("1003", params, new ICallback<MessageResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void action(MessageResponse messageResponse) {
|
public void action(MessageResponse messageResponse) {
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//服务器通知客户端有玩家执行了操作
|
//动作通知(有玩家执行了操作)
|
||||||
else if ("815".equalsIgnoreCase(command)) {
|
else if ("815".equalsIgnoreCase(command)) {
|
||||||
|
log.info("【815】玩家动作通知,connecId=" + connecId + ", param=" + param);
|
||||||
handler.onPlayerAction(param);
|
handler.onPlayerAction(param);
|
||||||
|
log.info("【815】处理完成,准备保存到 Redis");
|
||||||
|
//处理完协议后保存到 Redis
|
||||||
|
FuLuShouHandler currentInstance = fuLuShouHandlerInstances.get(connecId);
|
||||||
|
currentInstance.saveToRedis(connecId);
|
||||||
|
log.info("【815】已保存到 Redis");
|
||||||
}
|
}
|
||||||
//飘鸟提示
|
//飘鸟提示
|
||||||
else if ("833".equalsIgnoreCase(command)) {
|
else if ("833".equalsIgnoreCase(command)) {
|
||||||
handler.piaoNiaoTip();
|
handler.piaoNiaoTip();
|
||||||
|
//处理完协议后保存到 Redis
|
||||||
|
FuLuShouHandler currentInstance = fuLuShouHandlerInstances.get(connecId);
|
||||||
|
currentInstance.saveToRedis(connecId);
|
||||||
}
|
}
|
||||||
//飘鸟提示 reload
|
//飘鸟提示
|
||||||
else if ("2031".equalsIgnoreCase(command)) {
|
else if ("2031".equalsIgnoreCase(command)) {
|
||||||
log.info("收到飘鸟提示 reload: {}", param);
|
log.info("收到飘鸟提示 reload: " + param);
|
||||||
}
|
}
|
||||||
//飘鸟事件
|
//飘鸟事件
|
||||||
else if ("2032".equalsIgnoreCase(command)) {
|
else if ("2032".equalsIgnoreCase(command)) {
|
||||||
log.info("收到飘鸟事件:{}", param);
|
log.info("收到飘鸟事件:" + param);
|
||||||
}
|
}
|
||||||
//2001-2009 房间相关协议保持原有逻辑
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("处理接收到的游戏协议异常:{}, command: {}", e.getMessage(), command);
|
log.error("处理接收到的游戏协议异常:" + e.getMessage() + ", command: " + command);
|
||||||
} finally {
|
} finally {
|
||||||
if (jedis0 != null) {
|
if (jedis0 != null) {
|
||||||
jedis0.close();
|
jedis0.close();
|
||||||
|
|
@ -753,7 +659,7 @@ public class RobotConnectionManager {
|
||||||
|
|
||||||
jedis2.hset("{grobot}:" + robotId, "start", "0");
|
jedis2.hset("{grobot}:" + robotId, "start", "0");
|
||||||
|
|
||||||
log.info("机器人 {} 退出房间,修改gallrobot为0", robotId);
|
log.info("机器人 " + robotId + " 退出房间,修改 gallrobot 为 0");
|
||||||
} finally {
|
} finally {
|
||||||
jedis2.close();
|
jedis2.close();
|
||||||
}
|
}
|
||||||
|
|
@ -763,14 +669,14 @@ public class RobotConnectionManager {
|
||||||
* 机器人登录
|
* 机器人登录
|
||||||
*/
|
*/
|
||||||
public void login(RobotUser robotUser){
|
public void login(RobotUser robotUser){
|
||||||
log.info("login:{}", robotUser.getRobotId());
|
log.info("login:" + robotUser.getRobotId());
|
||||||
ITObject object = null;
|
ITObject object = null;
|
||||||
AccountBusiness accountBusiness = null;
|
AccountBusiness accountBusiness = null;
|
||||||
accountBusiness = new AccountBusiness();
|
accountBusiness = new AccountBusiness();
|
||||||
try {
|
try {
|
||||||
//先快速登录
|
//先快速登录
|
||||||
object = accountBusiness.fastLogin(Integer.parseInt(robotUser.getRobotId()));
|
object = accountBusiness.fastLogin(Integer.parseInt(robotUser.getRobotId()));
|
||||||
log.info("object:{}", object);
|
log.info("object:" + object);
|
||||||
if(object==null){
|
if(object==null){
|
||||||
object = accountBusiness.idPasswordLogin(Integer.parseInt(robotUser.getRobotId()), robotUser.getPassword());
|
object = accountBusiness.idPasswordLogin(Integer.parseInt(robotUser.getRobotId()), robotUser.getPassword());
|
||||||
}
|
}
|
||||||
|
|
@ -792,7 +698,7 @@ public class RobotConnectionManager {
|
||||||
connectGame(robotUser);
|
connectGame(robotUser);
|
||||||
|
|
||||||
robotUser.setConnecId(robotUser.getCurrentRoomId()+"_"+robotUser.getRobotId());
|
robotUser.setConnecId(robotUser.getCurrentRoomId()+"_"+robotUser.getRobotId());
|
||||||
log.info("重启获取的机器人还有当前房间,准备加入: {}", robotUser.getConnecId());
|
log.info("重启获取的机器人还有当前房间,准备加入:" + robotUser.getConnecId());
|
||||||
exGameController.webGroupJoinRoom(robotUser);
|
exGameController.webGroupJoinRoom(robotUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -824,7 +730,7 @@ public class RobotConnectionManager {
|
||||||
if(robotUser.getClient().isConnected()){
|
if(robotUser.getClient().isConnected()){
|
||||||
robotUser.setIsconnect(true);
|
robotUser.setIsconnect(true);
|
||||||
}else{
|
}else{
|
||||||
log.info("reconnect{}", robotUser.getClient().getGameID());
|
log.info("reconnect" + robotUser.getClient().getGameID());
|
||||||
TaurusClient client = new TaurusClient(robotUser.getGameHost()+":"+robotUser.getGamePort(), "cm"+robotUser.getRobotId(), TaurusClient.ConnectionProtocol.Tcp);
|
TaurusClient client = new TaurusClient(robotUser.getGameHost()+":"+robotUser.getGamePort(), "cm"+robotUser.getRobotId(), TaurusClient.ConnectionProtocol.Tcp);
|
||||||
client.setSession(robotUser.getLoginsession());
|
client.setSession(robotUser.getLoginsession());
|
||||||
client.connect();
|
client.connect();
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -28,7 +28,7 @@ public class ResourceCleanupUtil {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("开始执行资源清理,待清理资源数: {}", pendingCleanupResources.size());
|
log.info("开始执行资源清理,待清理资源数:" + pendingCleanupResources.size());
|
||||||
int cleanedCount = 0;
|
int cleanedCount = 0;
|
||||||
|
|
||||||
Set<String> resourcesToClean = ConcurrentHashMap.newKeySet();
|
Set<String> resourcesToClean = ConcurrentHashMap.newKeySet();
|
||||||
|
|
@ -40,13 +40,13 @@ public class ResourceCleanupUtil {
|
||||||
pendingCleanupResources.remove(resourceId);
|
pendingCleanupResources.remove(resourceId);
|
||||||
cleanedCount++;
|
cleanedCount++;
|
||||||
|
|
||||||
log.info("已清理资源: {}", resourceId);
|
log.info("已清理资源:" + resourceId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("清理资源时发生异常: {}, 错误: {}", resourceId, e.getMessage(), e);
|
log.error("清理资源时发生异常:" + resourceId + ", 错误:" + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("资源清理完成,共清理: {} 个资源", cleanedCount);
|
log.info("资源清理完成,共清理:" + cleanedCount + " 个资源");
|
||||||
|
|
||||||
//执行常规清理
|
//执行常规清理
|
||||||
performRegularCleanup();
|
performRegularCleanup();
|
||||||
|
|
@ -62,10 +62,10 @@ public class ResourceCleanupUtil {
|
||||||
|
|
||||||
//输出当前系统状态
|
//输出当前系统状态
|
||||||
log.info("=== 系统资源状态 ===");
|
log.info("=== 系统资源状态 ===");
|
||||||
log.info("{}", ThreadPoolConfig.getThreadPoolStatus());
|
log.info(ThreadPoolConfig.getThreadPoolStatus());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("执行常规清理时发生异常: {}", e.getMessage(), e);
|
log.error("执行常规清理时发生异常:" + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,14 +58,14 @@ public class ThreadPoolConfig {
|
||||||
* 执行延迟任务,替代Thread.sleep
|
* 执行延迟任务,替代Thread.sleep
|
||||||
*/
|
*/
|
||||||
public static void scheduleDelay(Runnable task, long delay, TimeUnit unit) {
|
public static void scheduleDelay(Runnable task, long delay, TimeUnit unit) {
|
||||||
log.debug("提交延迟任务: 延迟{} {}, 当前时间: {}", delay, unit, System.currentTimeMillis());
|
log.debug("提交延迟任务:延迟" + delay + " " + unit + ", 当前时间:" + System.currentTimeMillis());
|
||||||
SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
|
SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
|
||||||
try {
|
try {
|
||||||
log.debug("执行延迟任务开始: 当前时间: {}", System.currentTimeMillis());
|
log.debug("执行延迟任务开始:当前时间:" + System.currentTimeMillis());
|
||||||
task.run();
|
task.run();
|
||||||
log.debug("执行延迟任务完成: 当前时间: {}", System.currentTimeMillis());
|
log.debug("执行延迟任务完成:当前时间:" + System.currentTimeMillis());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("延迟任务执行异常: {}", e.getMessage(), e);
|
log.error("延迟任务执行异常:" + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}, delay, unit);
|
}, delay, unit);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,4 +1,621 @@
|
||||||
package taurus.util;
|
package taurus.util;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 福禄寿字牌游戏算法
|
||||||
|
*/
|
||||||
public class FuLuShouSuanFa {
|
public class FuLuShouSuanFa {
|
||||||
|
|
||||||
|
//特殊牌标记
|
||||||
|
//福
|
||||||
|
private static final int SPECIAL_FU = 101;
|
||||||
|
//禄
|
||||||
|
private static final int SUB_SPECIAL_LU = 701;
|
||||||
|
//寿
|
||||||
|
private static final int SUB_SPECIAL_SHOU = 1013;
|
||||||
|
//上
|
||||||
|
private static final int SPECIAL_SHANG = 201;
|
||||||
|
//大
|
||||||
|
private static final int SUB_SPECIAL_DA = 301;
|
||||||
|
//人
|
||||||
|
private static final int SUB_SPECIAL_REN = 501;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是特殊字(福、上)
|
||||||
|
*/
|
||||||
|
public static boolean isSpecialChar(int card) {
|
||||||
|
return card == SPECIAL_FU || card == SPECIAL_SHANG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是次特殊字(大、人、禄、寿)
|
||||||
|
*/
|
||||||
|
public static boolean isSubSpecialChar(int card) {
|
||||||
|
return card == SUB_SPECIAL_DA || card == SUB_SPECIAL_REN || card == SUB_SPECIAL_LU || card == SUB_SPECIAL_SHOU;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查两个半搭子的息数计算
|
||||||
|
* @param halfCombos 两个半搭子(每个半搭子是 2 张牌的列表)
|
||||||
|
* @return 总息数
|
||||||
|
*/
|
||||||
|
public static int calculateHalfComboXi(List<List<Integer>> halfCombos) {
|
||||||
|
if (halfCombos.size() != 2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> combo1 = halfCombos.get(0);
|
||||||
|
List<Integer> combo2 = halfCombos.get(1);
|
||||||
|
|
||||||
|
int xi1 = getHalfComboBaseXi(combo1);
|
||||||
|
int xi2 = getHalfComboBaseXi(combo2);
|
||||||
|
|
||||||
|
//有一个属于特殊组合
|
||||||
|
if (isSpecialHalfCombo(combo1) || isSpecialHalfCombo(combo2)) {
|
||||||
|
return xi1 + xi2;
|
||||||
|
}
|
||||||
|
|
||||||
|
//都不属于特殊组合 取最大值
|
||||||
|
return Math.max(xi1, xi2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个半搭子的基础息值
|
||||||
|
*/
|
||||||
|
public static int getHalfComboBaseXi(List<Integer> combo) {
|
||||||
|
if (combo.size() != 2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int card1 = combo.get(0);
|
||||||
|
int card2 = combo.get(1);
|
||||||
|
|
||||||
|
//特殊二字组合:上大、上人、福禄、福寿
|
||||||
|
if ((card1 == SPECIAL_SHANG && (card2 == SUB_SPECIAL_DA || card2 == SUB_SPECIAL_REN)) ||
|
||||||
|
(card2 == SPECIAL_SHANG && (card1 == SUB_SPECIAL_DA || card1 == SUB_SPECIAL_REN))) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((card1 == SPECIAL_FU && isFuLuShouRelated(card2)) ||
|
||||||
|
(card2 == SPECIAL_FU && isFuLuShouRelated(card1))) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
//大人组合
|
||||||
|
if ((card1 == SUB_SPECIAL_DA && card2 == SUB_SPECIAL_REN) ||
|
||||||
|
(card2 == SUB_SPECIAL_DA && card1 == SUB_SPECIAL_REN)) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
//对子
|
||||||
|
if (card1 == card2) {
|
||||||
|
if (card1 == SPECIAL_SHANG || card1 == SPECIAL_FU) {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
return 3;//其他对子
|
||||||
|
}
|
||||||
|
|
||||||
|
//检查是否包含特殊字
|
||||||
|
if (isSpecialChar(card1) || isSpecialChar(card2)) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是特殊半搭子
|
||||||
|
*/
|
||||||
|
public static boolean isSpecialHalfCombo(List<Integer> combo) {
|
||||||
|
if (combo.size() != 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int card1 = combo.get(0);
|
||||||
|
int card2 = combo.get(1);
|
||||||
|
|
||||||
|
//上大、上人、福禄、福寿
|
||||||
|
if (card1 == SPECIAL_SHANG && (card2 == SUB_SPECIAL_DA || card2 == SUB_SPECIAL_REN)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (card2 == SPECIAL_SHANG && (card1 == SUB_SPECIAL_DA || card1 == SUB_SPECIAL_REN)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (card1 == SPECIAL_FU && isFuLuShouRelated(card2)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (card2 == SPECIAL_FU && isFuLuShouRelated(card1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否与福禄寿相关
|
||||||
|
*/
|
||||||
|
private static boolean isFuLuShouRelated(int card) {
|
||||||
|
//禄或寿
|
||||||
|
return card == SUB_SPECIAL_LU || card == SUB_SPECIAL_SHOU;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算六句加一字时单字的息
|
||||||
|
*/
|
||||||
|
public static int calculateSingleCardXi(int card) {
|
||||||
|
if (isSpecialChar(card)) {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
if (isSubSpecialChar(card)) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算碰的息数
|
||||||
|
*/
|
||||||
|
public static int getPongXi(int card) {
|
||||||
|
if (isSpecialChar(card)) {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算吃(放)的息数
|
||||||
|
* @param cards 吃的三张牌
|
||||||
|
*/
|
||||||
|
public static int getChowXi(List<Integer> cards) {
|
||||||
|
if (cards.size() != 3) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//检查是否包含福或上(福禄寿、上大人句子)
|
||||||
|
for (int card : cards) {
|
||||||
|
if (isSpecialChar(card)) {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算招(杠)的息数
|
||||||
|
*/
|
||||||
|
public static int getGangXi(int card) {
|
||||||
|
if (isSpecialChar(card)) {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查复字牌是否可以操作(吃、碰、杠、胡)
|
||||||
|
* @param card 要操作的牌
|
||||||
|
* @param isSelfDraw 是否是自摸
|
||||||
|
* @return 是否可以操作
|
||||||
|
*/
|
||||||
|
public static boolean canOperateCompoundCard(int card, boolean isSelfDraw) {
|
||||||
|
//如果是自摸 任何牌都可以操作
|
||||||
|
if (isSelfDraw) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCompoundCard(card)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//其他单字牌可以正常操作
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测是否是复字牌
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 判断是否是复字牌
|
||||||
|
* @param card 牌的 ID
|
||||||
|
* @return 是否是复字牌
|
||||||
|
*/
|
||||||
|
public static boolean isCompoundCard(int card) {
|
||||||
|
return (card >= 901 && card <= 908) || (card >= 701 && card <= 703);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测是否是坎(三张相同的单字牌)
|
||||||
|
*/
|
||||||
|
public static boolean isKan(List<Integer> cards) {
|
||||||
|
if (cards.size() != 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//不能有复字牌
|
||||||
|
for (int card : cards) {
|
||||||
|
if (isCompoundCard(card)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//三张必须相同
|
||||||
|
return cards.get(0).intValue() == cards.get(1).intValue() && cards.get(1).intValue() == cards.get(2).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测是否是招(杠)
|
||||||
|
*/
|
||||||
|
public static boolean isGang(List<Integer> cards) {
|
||||||
|
if (cards.size() != 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//统计每种牌的数量
|
||||||
|
Map<Integer, Integer> countMap = new HashMap<>();
|
||||||
|
for (int card : cards) {
|
||||||
|
countMap.put(card, countMap.getOrDefault(card, 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//四张相同的单字牌
|
||||||
|
if (countMap.size() == 1) {
|
||||||
|
int card = cards.get(0);
|
||||||
|
return !isCompoundCard(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
//三张单字牌 + 一张复字牌
|
||||||
|
if (countMap.size() == 2) {
|
||||||
|
boolean hasCompound = false;
|
||||||
|
boolean hasThreeSame = false;
|
||||||
|
|
||||||
|
for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
|
||||||
|
if (isCompoundCard(entry.getKey())) {
|
||||||
|
hasCompound = true;
|
||||||
|
}
|
||||||
|
if (entry.getValue() == 3) {
|
||||||
|
hasThreeSame = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasCompound && hasThreeSame;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测胡牌是否符合规则
|
||||||
|
* @param allCards 所有牌(19 张)
|
||||||
|
* @param chowGroups 吃的牌组
|
||||||
|
* @param pongGroups 碰的牌组
|
||||||
|
* @param gangGroups 杠的牌组
|
||||||
|
* @return 胡牌结果
|
||||||
|
*/
|
||||||
|
public HuResult checkWin(List<Integer> allCards, List<List<Integer>> chowGroups, List<List<Integer>> pongGroups, List<List<Integer>> gangGroups) {
|
||||||
|
|
||||||
|
if (allCards.size() != 19) {
|
||||||
|
return new HuResult(false, "牌数不是 19 张");
|
||||||
|
}
|
||||||
|
|
||||||
|
//五句 + 两个半搭子
|
||||||
|
HuResult result1 = checkWinType1(allCards, chowGroups, pongGroups, gangGroups);
|
||||||
|
if (result1.isWin() && result1.getTotalXi() >= 11) {
|
||||||
|
return result1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//六句 + 任意一字
|
||||||
|
HuResult result2 = checkWinType2(allCards, chowGroups, pongGroups, gangGroups);
|
||||||
|
if (result2.isWin() && result2.getTotalXi() >= 11) {
|
||||||
|
return result2;
|
||||||
|
}
|
||||||
|
|
||||||
|
//两种形式都不符合 返回失败原因
|
||||||
|
String msg = "不符合胡牌条件";
|
||||||
|
if (result1.isWin() && result1.getTotalXi() < 11) {
|
||||||
|
msg = "息数不足 11 息(当前:" + result1.getTotalXi() + "息)";
|
||||||
|
} else if (result2.isWin() && result2.getTotalXi() < 11) {
|
||||||
|
msg = "息数不足 11 息(当前:" + result2.getTotalXi() + "息)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HuResult(false, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查第一种胡牌类型:五句 + 两个半搭子
|
||||||
|
*/
|
||||||
|
private HuResult checkWinType1(List<Integer> allCards, List<List<Integer>> chowGroups, List<List<Integer>> pongGroups, List<List<Integer>> gangGroups) {
|
||||||
|
//已组成的句子数量
|
||||||
|
int sentenceCount = chowGroups.size() + pongGroups.size() + gangGroups.size();
|
||||||
|
|
||||||
|
if (sentenceCount != 5) {
|
||||||
|
return new HuResult(false, "句子数量不是 5 个");
|
||||||
|
}
|
||||||
|
|
||||||
|
//计算剩余牌是否构成两个半搭子
|
||||||
|
int usedCards = sentenceCount * 3;
|
||||||
|
int remaining = allCards.size() - usedCards;
|
||||||
|
|
||||||
|
if (remaining != 4) {
|
||||||
|
return new HuResult(false, "剩余牌数不是 4 张");
|
||||||
|
}
|
||||||
|
|
||||||
|
//提取剩余的 4 张牌
|
||||||
|
List<Integer> remainingCards = new ArrayList<>();
|
||||||
|
List<Integer> usedCardList = new ArrayList<>();
|
||||||
|
|
||||||
|
//添加所有已使用的牌
|
||||||
|
for (List<Integer> group : chowGroups) {
|
||||||
|
usedCardList.addAll(group);
|
||||||
|
}
|
||||||
|
for (List<Integer> group : pongGroups) {
|
||||||
|
usedCardList.addAll(group);
|
||||||
|
}
|
||||||
|
for (List<Integer> group : gangGroups) {
|
||||||
|
usedCardList.addAll(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
//找出未使用的牌
|
||||||
|
Map<Integer, Integer> cardCountMap = new HashMap<>();
|
||||||
|
for (int card : allCards) {
|
||||||
|
cardCountMap.put(card, cardCountMap.getOrDefault(card, 0) + 1);
|
||||||
|
}
|
||||||
|
for (int card : usedCardList) {
|
||||||
|
cardCountMap.put(card, cardCountMap.get(card) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<Integer, Integer> entry : cardCountMap.entrySet()) {
|
||||||
|
for (int i = 0; i < entry.getValue(); i++) {
|
||||||
|
remainingCards.add(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//检查4张牌是否能组成两个半搭子(每半搭 2 张)
|
||||||
|
if (remainingCards.size() != 4) {
|
||||||
|
return new HuResult(false, "无法提取 4 张剩余牌");
|
||||||
|
}
|
||||||
|
|
||||||
|
//尝试所有可能的两两分组
|
||||||
|
boolean canFormTwoHalfCombos = false;
|
||||||
|
|
||||||
|
List<Integer> combo1 = Arrays.asList(remainingCards.get(0), remainingCards.get(1));
|
||||||
|
List<Integer> combo2 = Arrays.asList(remainingCards.get(2), remainingCards.get(3));
|
||||||
|
if (isValidHalfCombo(combo1) && isValidHalfCombo(combo2)) {
|
||||||
|
canFormTwoHalfCombos = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canFormTwoHalfCombos) {
|
||||||
|
combo1 = Arrays.asList(remainingCards.get(0), remainingCards.get(2));
|
||||||
|
combo2 = Arrays.asList(remainingCards.get(1), remainingCards.get(3));
|
||||||
|
if (isValidHalfCombo(combo1) && isValidHalfCombo(combo2)) {
|
||||||
|
canFormTwoHalfCombos = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canFormTwoHalfCombos) {
|
||||||
|
combo1 = Arrays.asList(remainingCards.get(0), remainingCards.get(3));
|
||||||
|
combo2 = Arrays.asList(remainingCards.get(1), remainingCards.get(2));
|
||||||
|
if (isValidHalfCombo(combo1) && isValidHalfCombo(combo2)) {
|
||||||
|
canFormTwoHalfCombos = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canFormTwoHalfCombos) {
|
||||||
|
return new HuResult(false, "剩余 4 张牌无法组成两个有效半搭子");
|
||||||
|
}
|
||||||
|
|
||||||
|
//计算总息数
|
||||||
|
int totalXi = calculateTotalXiFromGroups(chowGroups, pongGroups, gangGroups, Arrays.asList(combo1, combo2), 0);
|
||||||
|
|
||||||
|
HuResult result = new HuResult(true, "五句 + 两半搭胡牌", totalXi);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查第二种胡牌类型:六句 + 任意一字
|
||||||
|
*/
|
||||||
|
private HuResult checkWinType2(List<Integer> allCards, List<List<Integer>> chowGroups, List<List<Integer>> pongGroups, List<List<Integer>> gangGroups) {
|
||||||
|
int sentenceCount = chowGroups.size() + pongGroups.size() + gangGroups.size();
|
||||||
|
|
||||||
|
if (sentenceCount != 6) {
|
||||||
|
return new HuResult(false, "句子数量不是 6 个");
|
||||||
|
}
|
||||||
|
|
||||||
|
int usedCards = sentenceCount * 3;
|
||||||
|
int remaining = allCards.size() - usedCards;
|
||||||
|
|
||||||
|
if (remaining != 1) {
|
||||||
|
return new HuResult(false, "剩余牌数不是 1 张");
|
||||||
|
}
|
||||||
|
|
||||||
|
//提取剩余的 1 张牌
|
||||||
|
List<Integer> usedCardList = new ArrayList<>();
|
||||||
|
for (List<Integer> group : chowGroups) {
|
||||||
|
usedCardList.addAll(group);
|
||||||
|
}
|
||||||
|
for (List<Integer> group : pongGroups) {
|
||||||
|
usedCardList.addAll(group);
|
||||||
|
}
|
||||||
|
for (List<Integer> group : gangGroups) {
|
||||||
|
usedCardList.addAll(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Integer, Integer> cardCountMap = new HashMap<>();
|
||||||
|
for (int card : allCards) {
|
||||||
|
cardCountMap.put(card, cardCountMap.getOrDefault(card, 0) + 1);
|
||||||
|
}
|
||||||
|
for (int card : usedCardList) {
|
||||||
|
cardCountMap.put(card, cardCountMap.get(card) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int singleCard = 0;
|
||||||
|
for (Map.Entry<Integer, Integer> entry : cardCountMap.entrySet()) {
|
||||||
|
if (entry.getValue() > 0) {
|
||||||
|
singleCard = entry.getKey();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (singleCard == 0) {
|
||||||
|
return new HuResult(false, "无法提取单张牌");
|
||||||
|
}
|
||||||
|
|
||||||
|
//计算总息数
|
||||||
|
int totalXi = calculateTotalXiFromGroups(chowGroups, pongGroups, gangGroups, new ArrayList<>(), singleCard);
|
||||||
|
|
||||||
|
return new HuResult(true, "六句 + 一字胡牌", totalXi);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是有效的半搭子(2 张牌)
|
||||||
|
*/
|
||||||
|
private boolean isValidHalfCombo(List<Integer> combo) {
|
||||||
|
if (combo.size() != 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int card1 = combo.get(0);
|
||||||
|
int card2 = combo.get(1);
|
||||||
|
|
||||||
|
//对子是有效的半搭子
|
||||||
|
if (card1 == card2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//特殊组合:上大、上人、福禄、福寿
|
||||||
|
if ((card1 == SPECIAL_SHANG && (card2 == SUB_SPECIAL_DA || card2 == SUB_SPECIAL_REN)) ||
|
||||||
|
(card2 == SPECIAL_SHANG && (card1 == SUB_SPECIAL_DA || card1 == SUB_SPECIAL_REN))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((card1 == SPECIAL_FU && isFuLuShouRelated(card2)) ||
|
||||||
|
(card2 == SPECIAL_FU && isFuLuShouRelated(card1))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//大人组合
|
||||||
|
if ((card1 == SUB_SPECIAL_DA && card2 == SUB_SPECIAL_REN) ||
|
||||||
|
(card2 == SUB_SPECIAL_DA && card1 == SUB_SPECIAL_REN)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从各组动作中计算总息数
|
||||||
|
*/
|
||||||
|
private int calculateTotalXiFromGroups(List<List<Integer>> chowGroups, List<List<Integer>> pongGroups, List<List<Integer>> gangGroups, List<List<Integer>> halfCombos, int singleCard) {
|
||||||
|
int totalXi = 0;
|
||||||
|
|
||||||
|
//吃
|
||||||
|
for (List<Integer> cards : chowGroups) {
|
||||||
|
totalXi += getChowXi(cards);
|
||||||
|
}
|
||||||
|
|
||||||
|
//碰
|
||||||
|
for (List<Integer> group : pongGroups) {
|
||||||
|
if (!group.isEmpty()) {
|
||||||
|
totalXi += getPongXi(group.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//杠
|
||||||
|
for (List<Integer> group : gangGroups) {
|
||||||
|
if (!group.isEmpty()) {
|
||||||
|
totalXi += getGangXi(group.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//半搭子
|
||||||
|
if (halfCombos.size() == 2) {
|
||||||
|
totalXi += calculateHalfComboXi(halfCombos);
|
||||||
|
}
|
||||||
|
|
||||||
|
//单张牌
|
||||||
|
if (singleCard != 0) {
|
||||||
|
totalXi += calculateSingleCardXi(singleCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalXi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只从吃碰杠动作中计算息数(不包含手牌中的潜在息数)
|
||||||
|
* 根据福禄寿规则:只有吃碰杠对方的牌才能获得息数,手牌中的对子/组合没有息数
|
||||||
|
* @param actionRecords 动作记录列表
|
||||||
|
* @return 总息数
|
||||||
|
*/
|
||||||
|
public int calculateXiFromActions(List<ActionRecord> actionRecords) {
|
||||||
|
int totalXi = 0;
|
||||||
|
|
||||||
|
for (ActionRecord record : actionRecords) {
|
||||||
|
switch (record.type) {
|
||||||
|
case PONG:
|
||||||
|
totalXi += getPongXi(record.card);
|
||||||
|
break;
|
||||||
|
case CHOW:
|
||||||
|
totalXi += getChowXi(record.cards);
|
||||||
|
break;
|
||||||
|
case GANG:
|
||||||
|
totalXi += getGangXi(record.card);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalXi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动作记录类
|
||||||
|
*/
|
||||||
|
public static class ActionRecord {
|
||||||
|
public ActionType type;
|
||||||
|
public int card;
|
||||||
|
public List<Integer> cards;
|
||||||
|
|
||||||
|
public ActionRecord(ActionType type, int card) {
|
||||||
|
this.type = type;
|
||||||
|
this.card = card;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionRecord(ActionType type, List<Integer> cards) {
|
||||||
|
this.type = type;
|
||||||
|
this.cards = cards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动作类型枚举
|
||||||
|
*/
|
||||||
|
public enum ActionType {
|
||||||
|
PONG, //碰
|
||||||
|
CHOW, //吃
|
||||||
|
GANG //杠
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 胡牌结果类
|
||||||
|
*/
|
||||||
|
public static class HuResult {
|
||||||
|
private boolean win;
|
||||||
|
private String message;
|
||||||
|
private int totalXi;
|
||||||
|
|
||||||
|
public HuResult(boolean win, String message) {
|
||||||
|
this.win = win;
|
||||||
|
this.message = message;
|
||||||
|
this.totalXi = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HuResult(boolean win, String message, int totalXi) {
|
||||||
|
this.win = win;
|
||||||
|
this.message = message;
|
||||||
|
this.totalXi = totalXi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWin() { return win; }
|
||||||
|
public String getMessage() { return message; }
|
||||||
|
public int getTotalXi() { return totalXi; }
|
||||||
|
public void setTotalXi(int xi) { this.totalXi = xi; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue