From ed801cebd2aa5f979396f354c3a020666b665b8e Mon Sep 17 00:00:00 2001 From: miaoqingshuai Date: Mon, 15 Jun 2026 18:42:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=BE=E7=82=AE=E7=BD=9A=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../robot_zp_fpf/config/game-config.xml | 10 + .../robot_zp_fpf/config/log4j.properties | 20 + .../robot_zp_fpf/config/taurus-core.xml | 62 ++ .../robot_zp_fpf/config/taurus-permanent.xml | 75 ++ robots/zhipai/robot_zp_fpf/pom.xml | 70 ++ .../src/main/java/robot/zp/Config.java | 37 + .../main/java/robot/zp/EXGameController.java | 371 +++++++ .../src/main/java/robot/zp/EXMainServer.java | 170 ++++ .../src/main/java/robot/zp/EXPlayer.java | 26 + .../src/main/java/robot/zp/EXRoom.java | 32 + .../java/robot/zp/RobotConnectionManager.java | 903 +++++++++++++++++ .../robot/zp/business/AccountBusiness.java | 290 ++++++ .../robot/zp/handler/FangPaoFaHandler.java | 695 +++++++++++++ .../main/java/robot/zp/info/RobotUser.java | 164 ++++ .../robot/zp/thread/ResourceCleanupUtil.java | 72 ++ .../robot/zp/thread/ThreadPoolConfig.java | 117 +++ .../java/taurus/util/CardUtilFangpaofa.java | 104 ++ .../java/taurus/util/ChiPengDecisionV2.java | 356 +++++++ .../java/taurus/util/FangPaoFaSuanFa.java | 923 ++++++++++++++++++ .../main/java/taurus/util/ROBOTEventType.java | 13 + .../src/test/java/robot_zp_fpf/Main.java | 15 + 21 files changed, 4525 insertions(+) create mode 100644 robots/zhipai/robot_zp_fpf/config/game-config.xml create mode 100644 robots/zhipai/robot_zp_fpf/config/log4j.properties create mode 100644 robots/zhipai/robot_zp_fpf/config/taurus-core.xml create mode 100644 robots/zhipai/robot_zp_fpf/config/taurus-permanent.xml create mode 100644 robots/zhipai/robot_zp_fpf/pom.xml create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/Config.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXGameController.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXMainServer.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXPlayer.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXRoom.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/RobotConnectionManager.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/business/AccountBusiness.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/handler/FangPaoFaHandler.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/info/RobotUser.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/thread/ResourceCleanupUtil.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/thread/ThreadPoolConfig.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/CardUtilFangpaofa.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/ChiPengDecisionV2.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/FangPaoFaSuanFa.java create mode 100644 robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/ROBOTEventType.java create mode 100644 robots/zhipai/robot_zp_fpf/src/test/java/robot_zp_fpf/Main.java diff --git a/robots/zhipai/robot_zp_fpf/config/game-config.xml b/robots/zhipai/robot_zp_fpf/config/game-config.xml new file mode 100644 index 0000000..0c0ecd5 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/config/game-config.xml @@ -0,0 +1,10 @@ + + + + 8.138.162.178 + 8.138.162.178 + 8917 + 8917 + 17 + true + \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fpf/config/log4j.properties b/robots/zhipai/robot_zp_fpf/config/log4j.properties new file mode 100644 index 0000000..6786dba --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/config/log4j.properties @@ -0,0 +1,20 @@ + +log4j.rootLogger = INFO,consoleAppender,fileAppender + +# ConsoleAppender +log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender +log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.consoleAppender.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%t] %c{2} %3x - %m%n + + +# Regular FileAppender +log4j.appender.fileAppender=org.apache.log4j.DailyRollingFileAppender +log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.fileAppender.File=${WORKDIR}/logs/web_main.log +log4j.appender.fileAppender.layout.ConversionPattern=%d{dd MMM yyyy | HH:mm:ss,SSS} | %-5p | %t | %c{3} | %3x | %m%n +log4j.appender.fileAppender.Encoding=UTF-8 +log4j.appender.fileAppender.DatePattern='.'yyyy-MM-dd +log4j.appender.dailyFile.Append=true + +# The file is rolled over very day +log4j.appender.fileAppender.DatePattern ='.'yyyy-MM-dd \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fpf/config/taurus-core.xml b/robots/zhipai/robot_zp_fpf/config/taurus-core.xml new file mode 100644 index 0000000..e5ea14d --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/config/taurus-core.xml @@ -0,0 +1,62 @@ + + + log4j.properties + + + redis + com.taurus.core.plugin.redis.RedisPlugin + + + + 80 + + 20 + + 5 + + -1 + + true + + true + + true + + 100 + + 60000 + + 30000 + + 1800000 + + true + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fpf/config/taurus-permanent.xml b/robots/zhipai/robot_zp_fpf/config/taurus-permanent.xml new file mode 100644 index 0000000..b081943 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/config/taurus-permanent.xml @@ -0,0 +1,75 @@ + + + 1 + + 512 + + Heap + + Heap + + 524288 + + 16384 + + 32768 + + 512 + + + 4 + 2 + 2 + + + true + + 300 + + + + + + + + + + 1.2.3.4 + + + 127.0.0.1 + + 10000 + + + + false +
0.0.0.0
+ 80 +
+ + + + robot - test + robot.zp.EXMainServer + + + + + Sys + 16 + 32 + 60000 + 5000 + + + + + Ext + 16 + 32 + 60000 + 5000 + + +
\ No newline at end of file diff --git a/robots/zhipai/robot_zp_fpf/pom.xml b/robots/zhipai/robot_zp_fpf/pom.xml new file mode 100644 index 0000000..f4cb64a --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/pom.xml @@ -0,0 +1,70 @@ + + 4.0.0 + + com.robot + robot_zp_fpf + 1.0.0 + jar + + robot_zp_fpf + http://maven.apache.org + + + UTF-8 + + + + + com.robot + robot_common + 1.0.0 + + + + + + + + + game + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + + 1.8 + 1.8 + UTF-8 + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + robot.zp.EXMainServer + + + + + + + + + + + + + diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/Config.java b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/Config.java new file mode 100644 index 0000000..3991501 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/Config.java @@ -0,0 +1,37 @@ +package robot.zp; + +public class Config { + + /** + * 发送准备 - robot_zp_fls to game_zp_fls 的协议号 + */ + public static final String GAME_READY_FLS = "1003"; + + /** + * 加入房间 - robot_mgr to game_zp_fls 的内部协议号 + */ + public static final String JOIN_ROOM_FLS = "1002"; + + /** Web组加入房间协议 */ + public static final String WEB_GROUP_JOIN_ROOM = "225"; + + /** Web组主动重连协议 */ + public static final String WEB_GROUP_ACTIVE_RECONNECT = "226"; + + //==================== 游戏服务器配置 ==================== + /** 游戏服务器主机地址 */ + public static final String GAME_SERVER_HOST = "8.138.162.178"; + + /** 游戏服务器端口 */ + public static final String GAME_SERVER_PORT = "18017"; + + /** 默认密码 */ + public static final String DEFAULT_PASSWORD = "123456"; + + /** 默认PID */ + public static final String DEFAULT_PID = "17"; + + /** 默认群组ID */ + public static final String DEFAULT_GROUP_ID = "426149"; + +} \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXGameController.java b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXGameController.java new file mode 100644 index 0000000..08e662d --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXGameController.java @@ -0,0 +1,371 @@ +package robot.zp; + +import com.robot.GameController; +import com.robot.GameInterceptor; +import com.taurus.core.entity.ITObject; +import com.taurus.core.entity.TObject; +import com.taurus.core.plugin.redis.Redis; +import com.taurus.core.routes.ActionKey; +import com.taurus.permanent.data.Session; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; +import robot.zp.info.RobotUser; +import robot.zp.thread.ThreadPoolConfig; +import taurus.client.TaurusClient; +import taurus.client.business.GroupRoomBusiness; +import taurus.util.ROBOTEventType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import static robot.zp.thread.ThreadPoolConfig.scheduleDelay; + +/** + * 放炮罚游戏控制器 - 处理游戏协议 + */ +public class EXGameController extends GameController { + private static final Logger log = LoggerFactory.getLogger(EXGameController.class); + + private static final RobotConnectionManager robotConnectionManager = new RobotConnectionManager(); + + //机器人房间 + protected static final Map robotRoomMapping = new ConcurrentHashMap<>(); + + //记录最近访问时间 + private static final Map lastAccessTime = new ConcurrentHashMap<>(); + + //设置连接超时时间(5分钟) + private static final long CONNECTION_TIMEOUT = 5 * 60 * 1000; + + public EXGameController() { + super(); + log.info("放炮罚游戏控制器已初始化"); + } + + /** + * 接收来自web_group的加入房间协议 + */ + @ActionKey(value = Config.WEB_GROUP_JOIN_ROOM, validate = GameInterceptor.NOT_PLAYER) + public void webGroup(Session session, ITObject params, int gid) { + int robotId = params.getInt("robotid"); + String roomId = params.getString("roomid"); + int groupId = params.getInt("groupid"); + + //检查Redis中该房间是否真的包含当前机器人 + if (!checkRobotInRoomRedis(roomId, String.valueOf(robotId))) { + //Redis中不存在该机器人 清理本地可能的错误映射 + List robotUsers = getRobotUsersByRoomId(Integer.parseInt(roomId)); + if (!robotUsers.isEmpty()) { + synchronized (robotUsers) { + RobotUser robotUser = robotUsers.get(0); + log.warn("房间{}中Redis未找到机器人{},但本地映射存在{},清理本地映射", roomId, robotId, robotUser.getRobotId()); + robotRoomMapping.remove(robotUser.getConnecId()); + robotRoomMapping.remove(robotUser.getRobotId()); + } + } + } else { + //Redis中存在该机器人 检查是否是不同机器人的冲突 + List 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("房间{}中Redis已存在机器人{},当前机器人{}不执行加入逻辑", roomId, existingRobotId, robotId); + return; + } + } + } + } + log.info("225开始进房间: room:{} robot:{}", roomId, robotId); + //加入房间 + joinRoomCommon(robotId, roomId, groupId, params); + log.info("225已进入房间准备成功: room:{} robot:{}", roomId, robotId); + } + + /** + * 接收来自web_group的主动重连协议 + */ + @ActionKey(value = Config.WEB_GROUP_ACTIVE_RECONNECT, validate = GameInterceptor.NOT_PLAYER) + public void webGroupActive(Session session, ITObject params, int gid) { + int robotId = params.getInt("robotid"); + String roomId = params.getString("roomid"); + log.info("226开始进房间: room:{} robot:{}", roomId, robotId); + //加入房间 + joinRoomCommon(params.getInt("robotid"), params.getString("roomid"), params.getInt("groupid"), params); + log.info("226已进入房间准备成功: room:{} robot:{}", roomId, robotId); + } + + /** + * 重启服务断线重连 + * */ + public void webGroupJoinRoom(RobotUser robotUser) { + String connecId = robotUser.getConnecId(); + Jedis jedis0 = Redis.use("group1_db0").getJedis(); + Jedis jedis2 = Redis.use("group1_db2").getJedis(); + //重启检查 + try { + Set robotTokens = jedis0.smembers("{user}:"+robotUser.getRobotId()+"_token"); + String robotSession = null; + + for (String token : robotTokens) { + if (jedis0.exists(token)) { + robotSession = token; + break; + } + } + String gallrobot = jedis2.hget("gallrobot", robotUser.getRobotId()); + if (gallrobot.equals("0")) { + robotRoomMapping.remove(connecId); + return; + } + + log.info("重启后开始进房间: room:{} robot:{}", robotUser.getCurrentRoomId(), robotUser.getRobotId()); + ITObject params = new TObject(); + params.putString("session", "{user}:" + robotUser.getRobotId() + "," + robotSession); + //加入房间 + joinRoomCommon(Integer.parseInt(robotUser.getRobotId()), String.valueOf(robotUser.getCurrentRoomId()), Integer.parseInt(robotUser.getRobotGroupid()), params); + log.info("重启后已进入房间准备成功: room:{} robot:{}", robotUser.getCurrentRoomId(), robotUser.getRobotId()); + + } catch (Exception e) { + log.error("重启服务断线重连时发生错误", e); + } finally { + jedis0.close(); + jedis2.close(); + } + } + + /** + * 加入房间逻辑 + */ + private void joinRoomCommon(int robotId, String roomId, int groupId, ITObject params) { + Jedis jedis0 = Redis.use("group1_db0").getJedis(); + Jedis jedis2 = Redis.use("group1_db2").getJedis(); + try { + Set robotTokens = jedis0.smembers("{user}:" + robotId + "_token"); + String robotSession = null; + + for (String token : robotTokens) { + if (jedis0.exists(token)) { + robotSession = token; + break; + } + } + + log.info("开始进房间: room:{}", roomId); + log.info("开始进房间: {user}:{}", robotId); + + TaurusClient client = getFlsGameServerConnection(roomId + "_" + robotId); + ITObject joinResult = GroupRoomBusiness.joinRoom(groupId, "room:" + roomId, "{user}:" + robotId, null); + System.out.println("GroupRoomBusiness.joinRoom 结果:robotId:{"+robotId+"}, roomId:{"+roomId+"}, result:{"+joinResult+"}"); + //机器人房间映射关系 + RobotUser robotUser = getRobotRoomInfo(String.valueOf(robotId)); + String connecId = roomId + "_" + robotId; + if (robotUser.getCurrentRoomId() == 0) { + robotUser.setCurrentRoomId(Integer.parseInt(roomId)); + robotUser.setClient(client); + robotUser.setConnecId(connecId); + } + + //先不放入映射 等确认加入成功后再放入 + //robotRoomMapping.put(robotUser.getConnecId(), robotUser); + robotRoomMapping.remove(robotUser.getRobotId()); + //非阻塞延迟替代Thread.sleep + scheduleDelay(() -> { + + }, 2, TimeUnit.SECONDS); + params.putString("session", "{user}:" + robotId + "," + robotSession); + + //发送加入房间请求到game_zp_fls + client.send(Config.JOIN_ROOM_FLS, params, response -> { + //成功响应后才建立映射关系 + System.out.println("能进来吗"); + robotRoomMapping.put(robotUser.getConnecId(), robotUser); + robotConnectionManager.reconnectToGameServer(response, robotUser, client); + }); + + log.info("已进入房间成功: {}", robotUser.getConnecId()); + Thread.sleep(1000); + if (client.isConnected()) { + System.out.println("发送准备"); + 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); + robotConnectionManager.setSessionAndToken("{user}:" + robotId, robotSession, robotUser.getConnecId()); + } + //添加超时检查机制 + CompletableFuture.runAsync(() -> { + try { + //定时任务替代Thread.sleep + scheduleDelay(() -> { + //15秒后还没有建立映射关系 加入可能失败 + if (robotRoomMapping.get(robotUser.getConnecId()) == null) { + log.warn("机器人{}加入房间{}超时,清理临时状态", robotId, roomId); + robotConnectionManager.disconnectFromGameServer(connecId); + } + }, 15, TimeUnit.SECONDS); +// 15秒后还没有建立映射关系 加入可能失败 + if (robotRoomMapping.get(robotUser.getConnecId()) == null) { + log.warn("机器人{}加入房间{}超时,清理临时状态", robotId, roomId); + robotConnectionManager.disconnectFromGameServer(connecId); + } + } catch (Exception e) { + log.error("机器人加入房间超时", e); + } + }, ThreadPoolConfig.getBusinessThreadPool());//指定自定义线程池 + robotUser.setIntoRoomTime(robotConnectionManager.getTime()); + log.info("已进入房间准备成功: {}", robotUser.getConnecId()); + } catch (Exception e) { + log.error("加入房间时发生错误", e); + } finally { + jedis0.close(); + jedis2.close(); + } + } + + /** + * 根据机器人ID获取其所在的房间信息 + */ + public static RobotUser getRobotRoomInfo(String robotId) { + RobotUser robotUser = robotRoomMapping.get(robotId); + if (robotUser ==null) { + RobotUser robotUserCopy = new RobotUser(); + robotUserCopy.setRobotId(robotId); + robotUserCopy.setPassword(Config.DEFAULT_PASSWORD); + robotUserCopy.setGameHost(Config.GAME_SERVER_HOST); + robotUserCopy.setGamePort(Config.GAME_SERVER_PORT); + robotUserCopy.setRobotGroupid(Config.DEFAULT_GROUP_ID); + robotUserCopy.setRobotPid(Config.DEFAULT_PID); + return robotUserCopy; + } + return robotRoomMapping.get(robotId); + } + + /** + * 根据房间ID获取所有对应的RobotUser + */ + public List getRobotUsersByRoomId(int roomId) { + String prefix = roomId + "_"; + List result = new ArrayList<>(); + + for (Map.Entry entry : robotRoomMapping.entrySet()) { + if (entry.getKey().startsWith(prefix)) { + result.add(entry.getValue()); + } + } + return result; + } + + /** + * 根据机器人ID删除其所在的房间信息 + */ + public static void removeRobotRoomInfo(String robotId) { + RobotUser removedUser = robotRoomMapping.remove(robotId); + lastAccessTime.remove(robotId); + + // 如果有连接ID,也一并清理 + if (removedUser != null && removedUser.getConnecId() != null) { + robotRoomMapping.remove(removedUser.getConnecId()); + lastAccessTime.remove(removedUser.getConnecId()); + } + + log.info("清理机器人房间信息: {}", robotId); + } + + /** + * 更新连接的最后访问时间 + */ + public static void updateLastAccessTime(String connecId) { + lastAccessTime.put(connecId, System.currentTimeMillis()); + } + + /** + * 清理超时的连接 + */ + public static void cleanupExpiredConnections() { + long currentTime = System.currentTimeMillis(); + List expiredConnections = new ArrayList<>(); + + for (Map.Entry entry : lastAccessTime.entrySet()) { + if (currentTime - entry.getValue() > CONNECTION_TIMEOUT) { + expiredConnections.add(entry.getKey()); + } + } + + for (String connecId : expiredConnections) { + RobotUser robotUser = robotRoomMapping.get(connecId); + if (robotUser != null) { + log.info("清理超时连接: {}, 机器人ID: {}", connecId, robotUser.getRobotId()); + robotConnectionManager.disconnectFromGameServer(connecId); + } + robotRoomMapping.remove(connecId); + lastAccessTime.remove(connecId); + } + + if (!expiredConnections.isEmpty()) { + log.info("本次清理了 {} 个超时连接", expiredConnections.size()); + } + } + + /** + * 检查Redis中房间是否包含指定机器人 + * @param roomId 房间ID + * @param robotId 机器人ID + * @return 是否包含该机器人 + */ + private boolean checkRobotInRoomRedis(String roomId, String robotId) { + Jedis jedis = Redis.use().getJedis(); + try { + //查询该房间的玩家信息 + String playersStr = jedis.hget("room:" + roomId, "players"); + if (playersStr == null || playersStr.equals("[]")) { + return false; + } + + String players = playersStr.substring(1, playersStr.length() - 1); + String[] playerIds = players.split(","); + + //检查是否包含该机器人 + int targetRobotId = Integer.parseInt(robotId); + for (String playerIdStr : playerIds) { + int playerId = Integer.parseInt(playerIdStr.trim()); + if (playerId == targetRobotId) { + return true; + } + } + return false; + } catch (Exception e) { + log.error("检查Redis房间玩家信息时发生错误,roomId: {}, robotId: {}", roomId, robotId, e); + return false; + } finally { + jedis.close(); + } + } + + /** + * 根据机器人ID和连接ID获取福禄寿游戏服务器连接 + * 基于robotId和connectionId的组合复用连接 + */ + public static TaurusClient getFlsGameServerConnection(String connecId) { + TaurusClient taurusClient = robotConnectionManager.getGameClient(connecId); + log.info("根据机器人ID和连接ID获取福禄寿游戏服务器连接 client: {}", taurusClient); + if (taurusClient != null) { + log.debug("成功获取游戏服务器连接,connecId: {}", connecId); + return taurusClient; + } + taurusClient = robotConnectionManager.connectToGameServer(connecId); + return taurusClient; + } + +} \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXMainServer.java b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXMainServer.java new file mode 100644 index 0000000..f7b7228 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXMainServer.java @@ -0,0 +1,170 @@ +package robot.zp; + +import java.util.Map; + +import com.robot.GameController; +import com.robot.MainServer; +import com.robot.data.Player; +import com.robot.data.Room; +import com.taurus.core.plugin.redis.Redis; +import com.taurus.core.routes.Routes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; +import robot.zp.info.RobotUser; +import robot.zp.thread.ResourceCleanupUtil; +import robot.zp.thread.ThreadPoolConfig; +import taurus.client.NetManager; + +import static robot.zp.EXGameController.robotRoomMapping; + +/** + * 福禄寿机器人主服务器 + * TCP服务端接收robot_mgr的协议 同时作为客户端连接game_zp_fls处理AI逻辑 + */ +public class EXMainServer extends MainServer{ + private static final Logger log = LoggerFactory.getLogger(EXMainServer.class); + + private static final RobotConnectionManager robotConnectionManager = new RobotConnectionManager(); + + @Override + public void onStart() { + super.onStart(); + + //JVM关闭钩子 + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + log.info("收到JVM关闭信号,开始优雅关闭..."); + try { + //关闭所有机器人连接 + for (Map.Entry entry : robotRoomMapping.entrySet()) { + RobotUser robotUser = entry.getValue(); + if (robotUser.getClient() != null && robotUser.getClient().isConnected()) { + robotConnectionManager.disconnectFromGameServer(robotUser.getConnecId()); + } + } + + //关闭线程池 + robot.zp.thread.ThreadPoolConfig.shutdown(); + + log.info("优雅关闭完成"); + } catch (Exception e) { + log.error("关闭过程中发生异常", e); + } + })); + + // 1. 先启动独立的事件处理线程(只启动一次) + startNetEventThread(); + + // 2. 启动资源清理定时任务 + startResourceCleanupScheduler(); + + // 3. 启动系统监控 + //startConnectionCheckScheduler(); + //测试 + Jedis jedis2 = Redis.use("group1_db2").getJedis(); + String robotskey = "g{"+Config.DEFAULT_GROUP_ID+"}:play:"+Config.DEFAULT_PID; + Map maprobot = jedis2.hgetAll(robotskey); + for(Map.Entry entry : maprobot.entrySet()) { + log.info("{}:{}", entry.getKey(), entry.getValue()); + //是否创建 + RobotUser robotUser = new RobotUser(); + robotUser.setRobotId(entry.getKey()); + robotUser.setPassword(Config.DEFAULT_PASSWORD); + robotUser.setGameHost(Config.GAME_SERVER_HOST); + robotUser.setGamePort(Config.GAME_SERVER_PORT); + robotUser.setRobotGroupid(Config.DEFAULT_GROUP_ID); + robotUser.setRobotPid(Config.DEFAULT_PID); + + robotRoomMapping.put(entry.getKey(), robotUser); + } + + for(Map.Entry entry : robotRoomMapping.entrySet()) { + RobotUser robotUser = entry.getValue(); + //1、登录 + //判断是否登录 + if(!robotUser.isLogin){ + robotConnectionManager.login(robotUser); + } + } + + log.info("福禄寿机器人服务器已启动"); + log.info("服务器将监听端口 {} 用于接收robot_mgr管理协议", gameSetting.port); + log.info("当前线程池配置: {}", ThreadPoolConfig.getThreadPoolStatus()); + + jedis2.close(); + } + + /** + * 独立的事件处理线程 + */ + private void startNetEventThread() { + Thread eventThread = new Thread(() -> { + while (true) { + NetManager.processEvents(); + try { + Thread.sleep(2); + } catch (InterruptedException e) { + break; + } catch (Exception e) { + } + } + }, "Changsha_Thread"); + + eventThread.setDaemon(true); //设置为守护线程 + eventThread.start(); + } + + /** + * 启动资源清理定时任务 + */ + private void startResourceCleanupScheduler() { + Thread cleanupThread = new Thread(() -> { + while (true) { + try { + //每30秒执行一次资源清理 + Thread.sleep(30000); + ResourceCleanupUtil.performCleanup(); + log.info("线程池状态: {}", ThreadPoolConfig.getThreadPoolStatus()); + } catch (InterruptedException e) { + break; + } catch (Exception e) { + log.error("资源清理任务异常: {}", e.getMessage(), e); + // 发生异常时尝试清理 + try { + ResourceCleanupUtil.performCleanup(); + } catch (Exception cleanupEx) { + log.error("异常清理也失败: {}", cleanupEx.getMessage(), cleanupEx); + } + } + } + }, "ResourceCleanupThread"); + + cleanupThread.setDaemon(true); + cleanupThread.start(); + log.info("资源清理定时任务已启动"); + } + + + + @Override + public Room newRoom(String roomid, Map redis_room_map) { + return new EXRoom(roomid, redis_room_map); + } + + @Override + public Player newPlayer(int i, Room room, String s) { + return new EXPlayer(i, room, s); + } + + + protected GameController newController() { + return new EXGameController(); + } + + @Override + public void onStop() { + super.onStop(); + + log.info("福禄寿机器人服务器已停止"); + } +} \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXPlayer.java b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXPlayer.java new file mode 100644 index 0000000..fe596c7 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXPlayer.java @@ -0,0 +1,26 @@ +package robot.zp; + +import com.robot.data.Player; +import com.robot.data.Room; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * + */ +public class EXPlayer extends Player { + + + private static final Logger log = LoggerFactory.getLogger(EXPlayer.class); + + public EXPlayer(int playerid, Room table, String session_id) { + super(playerid, table, session_id); + log.info("new robot"); + } + + public EXRoom getRoom() { + return (EXRoom) room; + } + +} diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXRoom.java b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXRoom.java new file mode 100644 index 0000000..ee57e52 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/EXRoom.java @@ -0,0 +1,32 @@ +package robot.zp; + +import com.robot.data.Room; + + +import java.util.Map; + +public class EXRoom extends Room { + + public EXRoom(String roomid, Map redis_room_map) { + super(roomid, redis_room_map); + } + @Override + protected void roomResult() { + + } + @Override + public void endGame() { + super.endGame(); + } + + @Override + public void saveMilitaryTotal(boolean dissmiss) { + super.saveMilitaryTotal(dissmiss); + } + + @Override + public void clear() { + super.clear(); + } + +} diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/RobotConnectionManager.java b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/RobotConnectionManager.java new file mode 100644 index 0000000..118e376 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/RobotConnectionManager.java @@ -0,0 +1,903 @@ +package robot.zp; + +import com.taurus.core.entity.ITArray; +import com.taurus.core.entity.ITObject; +import com.taurus.core.entity.TArray; +import com.taurus.core.entity.TObject; +import com.taurus.core.events.Event; +import com.taurus.core.events.IEventListener; +import com.taurus.core.plugin.redis.Redis; +import com.taurus.core.util.ICallback; +import com.taurus.core.util.Logger; +import com.taurus.core.util.StringUtil; +//import robot.zp.business.AccountBusiness; +import robot.zp.business.AccountBusiness; +import robot.zp.info.RobotUser; +import robot.zp.thread.ThreadPoolConfig; +import taurus.client.Message; +import taurus.client.MessageResponse; +import taurus.client.TaurusClient; +import taurus.client.SocketCode; +import redis.clients.jedis.Jedis; +import robot.zp.handler.FangPaoFaHandler; +import taurus.util.ROBOTEventType; + +import java.util.*; +import java.util.concurrent.*; + +import static robot.zp.thread.ThreadPoolConfig.scheduleDelay; + +import static robot.zp.EXGameController.robotRoomMapping; + +/** + * 机器人连接管理器 - 管理与游戏服务器的连接 + */ +public class RobotConnectionManager { + + private final Logger log = Logger.getLogger(RobotConnectionManager.class); + private static final Map fangpaofaHandlerInstances = new ConcurrentHashMap<>(); + + //记录活跃连接 用于资源清理判断 + private static final Set activeConnections = ConcurrentHashMap.newKeySet(); + + //记录连接创建时间 + private static final Map connectionCreationTime = new ConcurrentHashMap<>(); + + //连接最大生存时间(5 分钟) + private static final long MAX_CONNECTION_LIFETIME = 5 * 60 * 1000; + private final EXGameController exGameController; + + private final String host = Config.GAME_SERVER_HOST; + private final int port = Integer.parseInt(Config.GAME_SERVER_PORT); + + /*福禄寿游戏算法相关 start*/ + private final Map>> playerOutcardsMapByConn = new ConcurrentHashMap<>(); + private final Map>> playerchisMapByConn = new ConcurrentHashMap<>(); + private final Map>> playerpengsMapByConn = new ConcurrentHashMap<>(); + private final Map>> playermingsMapByConn = new ConcurrentHashMap<>(); + private final Map>> playerzisMapByConn = new ConcurrentHashMap<>(); + + private Map> getPlayerOutcardsMap(String connecId) { + return playerOutcardsMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>()); + } + + private Map> getPlayerchisMap(String connecId) { + return playerchisMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>()); + } + + private Map> getPlayerpengsMap(String connecId) { + return playerpengsMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>()); + } + + private Map> getPlayermingsMap(String connecId) { + return playermingsMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>()); + } + + private Map> getPlayerzisMap(String connecId) { + return playerzisMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>()); + } + + private int pid = 0; + private Map count = new HashMap(); + /*福禄寿游戏算法相关 end*/ + + + public RobotConnectionManager() { + exGameController = new EXGameController(); + } + + /** + * 获取放炮罚处理器实例 + */ + private FangPaoFaHandler getFuLuShouHandlerInstance(String connecId) { + //标记连接为活跃状态 + activeConnections.add(connecId); + connectionCreationTime.put(connecId, System.currentTimeMillis()); + + //定期清理过期连接 + cleanupExpiredInstances(); + + FangPaoFaHandler existingInstance = fangpaofaHandlerInstances.get(connecId); + if (existingInstance != null) { + return existingInstance; + } + + FangPaoFaHandler newInstance = new FangPaoFaHandler(); + log.info("创建新的 FuLuShouHandler 实例:{}", connecId); + + fangpaofaHandlerInstances.put(connecId, newInstance); + log.info("当前 FuLuShouHandler 实例总数:{}", fangpaofaHandlerInstances.size()); + return newInstance; + } + + /** + * 设置会话和令牌 + */ + public void setSessionAndToken(String session, String token, String connecId) { + FangPaoFaHandler handler = getFuLuShouHandlerInstance(connecId); + handler.session = session; + handler.token = token; + } + + /** + * 连接到福禄寿游戏服务器 + */ + public TaurusClient connectToGameServer(String connecId) { + try { + //创建Taurus客户端 + TaurusClient client = new TaurusClient(host + ":" + port, "game", TaurusClient.ConnectionProtocol.Tcp); + + //设置事件监听器 + setupEventListeners(client, connecId); + + client.connect(); + + return client; + } catch (Exception e) { + log.error("连接到游戏服务器时发生异常", e); + return null; + } + } + + /** + * 断开与游戏服务器的连接(主动断开) + */ + public void disconnectFromGameServer(String connecId) { + log.info("开始主动断开连接:{}", connecId); + RobotUser robotUser = robotRoomMapping.remove(connecId); + + //标记连接为非活跃 + activeConnections.remove(connecId); + connectionCreationTime.remove(connecId); + + //清理连接数据 + if (connecId != null) { + FangPaoFaHandler handler = fangpaofaHandlerInstances.get(connecId); + if (handler != null) { + //清理所有集合数据以释放内存 + handler.clearAllData(); + log.info("清空 FuLuShouHandler 集合数据:{}", connecId); + } + + //移除实例和相关数据 + fangpaofaHandlerInstances.remove(connecId); + playerOutcardsMapByConn.remove(connecId); + playerchisMapByConn.remove(connecId); + playerpengsMapByConn.remove(connecId); + playermingsMapByConn.remove(connecId); + playerzisMapByConn.remove(connecId); + + log.info("清理完成,当前活跃连接数:{}, 实例数:{}", activeConnections.size(), fangpaofaHandlerInstances.size()); + } + + if (robotUser != null) { + TaurusClient client = robotUser.getClient(); + if (client != null) { + try { + if (client.isConnected()) { + client.killConnection(); + } + log.info("客户端主动断开连接完成:{}", connecId); + } catch (Exception e) { + log.error("断开客户端连接时发生异常:{}, 错误:{}", connecId, e.getMessage(), e); + } + } else { + log.warn("客户端连接不存在:{}", connecId); + } + + //同时清理机器人房间映射 + EXGameController.removeRobotRoomInfo(robotUser.getRobotId()); + } + } + + /** + * 清理过期的实例 + */ + private void cleanupExpiredInstances() { + long currentTime = System.currentTimeMillis(); + List expiredConnections = new ArrayList<>(); + + //检查连接生存时间 + for (Map.Entry entry : connectionCreationTime.entrySet()) { + if (currentTime - entry.getValue() > MAX_CONNECTION_LIFETIME) { + expiredConnections.add(entry.getKey()); + } + } + + //清理过期连接 + for (String connecId : expiredConnections) { + log.info("清理过期连接实例:{}", connecId); + disconnectFromGameServer(connecId); + } + + if (!expiredConnections.isEmpty()) { + log.info("本次清理了 {} 个过期连接实例", expiredConnections.size()); + } + } + + /** + * 设置事件监听器 + */ + public void setupEventListeners(TaurusClient client, String connecId) { + //添加消息事件监听器 + IEventListener messageListener = new IEventListener() { + @Override + public void handleEvent(Event event) { + //获取 msg + Message message = (Message) event.getParameter("msg"); + + ITObject param = message.param; + //回调协议号 + String command = message.command; + log.debug("fls OnEvent msg: {}", command); + System.out.println("协议号" + command); + //根据玩法ID处理不同的回调 + if (StringUtil.isNotEmpty(command)) { + //直接处理协议 + handleProtocol(command, message, client, connecId); + } + } + }; + + //添加连接状态监听器 + IEventListener connectListener = new IEventListener() { + @Override + public void handleEvent(Event event) { + Message message = (Message) event.getParameter("msg"); + SocketCode code = (SocketCode) event.getParameter("code"); + + } + }; + + //注册事件监听器 + client.addEventListener(TaurusClient.NetClientEvent.OnEvent, messageListener); + client.addEventListener(TaurusClient.NetClientEvent.Connect, connectListener); + } + + + /** + * 机器人断线重连 + */ + public void reconnectToGameServer(MessageResponse response, RobotUser robotUser, TaurusClient client) { + String connecId = robotUser.getCurrentRoomId() + "_" + robotUser.getRobotId(); + if (client.isConnected()) { + try { + ITObject obj = response.messageData.param.getTObject("tableInfo"); + ITObject reloadInfo = response.messageData.param.getTObject("reloadInfo"); + if (obj != null) { + //处理 seat + //获取机器人的seat + ITArray playerData = obj.getTArray("playerData"); + for (int i = 0; i < playerData.size(); i++) { + ITObject tms = playerData.getTObject(i); + Integer tmuserid = tms.getInt("aid"); + if (tmuserid == Integer.parseInt(robotUser.getRobotId())) { + Integer seat = tms.getInt("seat"); + robotUser.setSeat(seat); + } + } + log.info("playerData: {}", playerData); + + log.info("obj: {}", obj); + log.info("reloadInfo: {}", reloadInfo); + if (reloadInfo != null) { + //重连回来的 + int curren_outcard_seat = reloadInfo.getInt("curren_outcard_seat"); + if (curren_outcard_seat == robotUser.getSeat()) { + //同步手牌 + ITArray hand_card = reloadInfo.getTArray("hand_card"); + ITArray info_list = reloadInfo.getTArray("info_list"); + + List hcard = new ArrayList<>(); + if (hand_card != null) { + for (int i = 0; i < hand_card.size(); i++) { + hcard.add(hand_card.getInt(i)); + } + } + ITArray outcard_list = new TArray(); + if (info_list != null) { + for (int i = 0; i < info_list.size(); i++) { + ITObject tms = info_list.getTObject(i); + Integer playerid = tms.getInt("playerid"); + if (playerid == Integer.parseInt(robotUser.getRobotId())) { + outcard_list = tms.getTArray("outcard_list"); + } + } + } + + log.info("hcard>0{}", hcard); + if (hcard.size() > 0) { + //同步手牌 + FangPaoFaHandler currentInstance = getFuLuShouHandlerInstance(connecId); + + //同步逻辑比较手牌数量 + List currentHand = currentInstance.getChangShaCardInhand(); + if (currentHand.isEmpty() || hcard.size() > currentHand.size()) { + //手牌集合为空 或者 玩家出牌了 + currentInstance.updateHandCard(hcard); + log.info("断线重连:同步手牌数据,服务器手牌:{}", hcard); + } else { + log.info("断线重连:使用Redis恢复的手牌数据,数量:{}", currentHand.size()); + } + + if (outcard_list.size() > 0) { + List outcards = new ArrayList<>(); + for (int i = 0; i < outcard_list.size(); i++) { + outcards.add(outcard_list.getInt(i)); + } + + //检查出牌记录是否需要同步 + List currentOutCards = currentInstance.getChuGuoCardInhand(); + if (currentOutCards.isEmpty() || outcards.size() > currentOutCards.size()) { + currentInstance.updateOutCard(outcards); + log.info("断线重连:同步出牌数据,服务器出牌:{}", outcards); + } else { + log.info("断线重连:使用Redis恢复的出牌数据,数量:{}", currentOutCards.size()); + } + } + + //非阻塞的延迟执行,增加更完善的异常处理 + scheduleDelay(() -> { + try { + //重新获取当前实例,确保数据一致性 + FangPaoFaHandler reconnectedInstance = getFuLuShouHandlerInstance(connecId); + Map> currentPlayerOutcardsMap = getPlayerOutcardsMap(connecId); + Map> currentPlayerchisMap = getPlayerchisMap(connecId); + Map> currentPlayerpengsMap = getPlayerpengsMap(connecId); + Map> currentPlayermingsMap = getPlayermingsMap(connecId); + Map> currentPlayerzisMap = getPlayerzisMap(connecId); + + reconnectedInstance.outCard(client, currentPlayerOutcardsMap, currentPlayerchisMap, currentPlayerpengsMap, currentPlayermingsMap, currentPlayerzisMap); + log.info("断线重连后成功执行出牌操作"); + } catch (Exception e) { + log.error("断线重连后执行出牌操作时发生异常", e); + //即使出牌失败,也要确保连接状态正确 + try { + if (robotUser != null) { + robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_READY); + } + } catch (Exception statusEx) { + log.error("更新机器人状态时发生异常", statusEx); + } + } + }, 2, TimeUnit.SECONDS); + } else { + log.warn("警告:重连时未获取到手牌数据"); + } + } + } + } + } catch (Exception e) { + log.error("机器人断线重连异常"); + } + } else { + renconnect(robotUser); + } + } + + /** + * 处理接收到的游戏协议 + * 福禄寿支持的协议: + * 核心流程: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) { + RobotUser robotUser = robotRoomMapping.get(connecId); + + //更新连接的最后访问时间 + EXGameController.updateLastAccessTime(connecId); + + if (robotUser == null) { + log.error("未找到机器人用户信息,连接ID: {}", connecId); + return; + } + + int robotId = Integer.parseInt(robotUser.getRobotId()); + ITObject param = message.param; + FangPaoFaHandler handler = getFuLuShouHandlerInstance(connecId); + Jedis jedis0 = Redis.use().getJedis(); + Jedis jedis2 = Redis.use("group1_db2").getJedis(); + try { + //福禄寿 机器人处理事件 + //初始化手牌 + if ("811".equalsIgnoreCase(command)) { + robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_WORKING); + //初始化手牌 + String key = robotId + ""; + if (jedis2.hget("{robortInfo}:" + key, "circleId") != null && jedis2.hget("{robortInfo}:" + key, "pid") != null) { + 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); + //处理完协议后保存到Redis + FangPaoFaHandler currentInstance = fangpaofaHandlerInstances.get(connecId); + currentInstance.saveToRedis(connecId); + } + //出牌广播 + else if ("812".equalsIgnoreCase(command)) { + ITArray outcard_map = param.getTArray("outcard_map"); + ITArray opchicards = param.getTArray("opchicards"); + ITArray oppengcards = param.getTArray("oppengcards"); + ITArray opmingcards = param.getTArray("opmingcards"); + ITArray opzicards = param.getTArray("opzicards"); + + //获取当前连接专用的Maps + Map> currentPlayerOutcardsMap = getPlayerOutcardsMap(connecId); + Map> currentPlayerchisMap = getPlayerchisMap(connecId); + Map> currentPlayerpengsMap = getPlayerpengsMap(connecId); + Map> currentPlayermingsMap = getPlayermingsMap(connecId); + Map> 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 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 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 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 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 outziList = new ArrayList<>(); + for (int j = 0; j < outziArray.size(); j++) { + outziList.add(outziArray.getInt(j)); + } + currentPlayerzisMap.put(playerId, outziList); + } + } + + handler.onDiscardBroadcast(message); + } + //摸牌 + else if ("819".equalsIgnoreCase(command)) { + System.out.println("cliend" + client.getSession()); + System.out.println("819 +++++++" + robotId); + handler.drawCard(message,robotUser); + //处理完协议后保存到Redis + FangPaoFaHandler currentInstance = fangpaofaHandlerInstances.get(connecId); + currentInstance.saveToRedis(connecId); + } + //出牌提示 + else if ("813".equalsIgnoreCase(command)) { + //获取当前连接的 Maps + Map> currentPlayerOutcardsMap = getPlayerOutcardsMap(connecId); + Map> currentPlayerchisMap = getPlayerchisMap(connecId); + Map> currentPlayerpengsMap = getPlayerpengsMap(connecId); + Map> currentPlayermingsMap = getPlayermingsMap(connecId); + Map> currentPlayerzisMap = getPlayerzisMap(connecId); + + handler.makeDiscardDecision(client, currentPlayerOutcardsMap, currentPlayerchisMap, currentPlayerpengsMap, currentPlayermingsMap, currentPlayerzisMap, robotId); + //处理完协议后保存到Redis + FangPaoFaHandler currentInstance = fangpaofaHandlerInstances.get(connecId); + currentInstance.saveToRedis(connecId); + } else if ("814".equalsIgnoreCase(command)) { + handler.actionTip(param, client,robotUser); + //放跑提示 + } else if ("822".equalsIgnoreCase(command)) { + System.out.println("放跑提示"); + FangPaoFaHandler.fangPaoTipEvent(message, client); + //打鸟提示 + }else if ("832".equalsIgnoreCase(command)){ + handler.daniao(param,client); + System.out.println("打鸟提示" + param); + } + //飘操作 + else if ("1015".equalsIgnoreCase(command)) { + log.info("收到飘操作协议:{}", param); + } + //2026.02.03修改 玩家加入房间 + else if ("2001".equalsIgnoreCase(command)) { + //直接使用定时任务替代Thread.sleep,避免嵌套异步调用 + scheduleDelay(() -> { + Jedis jedis = Redis.use().getJedis(); + try { + String roomKey = String.valueOf(robotUser.getCurrentRoomId()); + + //查询该房间的玩家信息 + String playersStr = jedis.hget("room:" + roomKey, "players"); + if (playersStr != null && !playersStr.equals("[]")) { + String players = playersStr.substring(1, playersStr.length() - 1); + String[] playerIds = players.split(","); + + //判断只有当前机器人一个玩家 + if (playerIds.length == 1) { + int playerId = Integer.parseInt(playerIds[0].trim()); + if (playerId == robotId) { + //发送退出房间协议 + ITObject params = TObject.newInstance(); + client.send("1005", params, response -> { + EXGameController.removeRobotRoomInfo(String.valueOf(robotId)); + //更新机器人剩余数量 + updateLeftoverRobot(robotId); + disconnectFromGameServer(connecId); + log.info("2001发送退出房间协议1005,robotId: {}", robotId); + }); + } + } + } + } catch (Exception e) { + log.error("处理玩家加入房间检查时发生异常", e); + } finally { + // 确保Jedis连接关闭 + if (jedis != null) { + jedis.close(); + } + } + }, 6, TimeUnit.SECONDS); + log.info("玩家{}加入房间:{}", robotUser.getCurrentRoomId(), param); + } + //2026.02.03修改 玩家退出房间也要检查 + else if ("2002".equalsIgnoreCase(command)) { + //直接使用定时任务替代Thread.sleep,避免嵌套异步调用 + scheduleDelay(() -> { + Jedis jedis = Redis.use().getJedis(); + try { + String roomKey = String.valueOf(robotUser.getCurrentRoomId()); + + //查询该房间的玩家信息 + String playersStr = jedis.hget("room:" + roomKey, "players"); + if (playersStr != null && !playersStr.equals("[]")) { + String players = playersStr.substring(1, playersStr.length() - 1); + String[] playerIds = players.split(","); + + //判断只有当前机器人一个玩家 + if (playerIds.length == 1) { + int playerId = Integer.parseInt(playerIds[0].trim()); + if (playerId == robotId) { + //发送退出房间协议 + ITObject params = TObject.newInstance(); + client.send("1005", params, response -> { + EXGameController.removeRobotRoomInfo(String.valueOf(robotId)); + //更新机器人剩余数量 + updateLeftoverRobot(robotId); + disconnectFromGameServer(connecId); + log.info("2002发送退出房间协议1005,robotId: {}", robotId); + }); + } + } + } + } catch (Exception e) { + log.error("处理玩家退出房间检查时发生异常", e); + } finally { + if (jedis != null) { + jedis.close(); + } + } + }, 6, TimeUnit.SECONDS); + } + //2026.02.05修改 玩家解散房间 + else if ("2005".equalsIgnoreCase(command)) { + EXGameController.removeRobotRoomInfo(String.valueOf(robotId)); + //更新机器人剩余数量 + updateLeftoverRobot(robotId); + disconnectFromGameServer(connecId); + log.info("2005玩家发送解散房间协议,robotId: {}", robotId); + } + //2026.02.03修改 解散房间时候恢复机器人账号可以使用 + else if ("2008".equalsIgnoreCase(command)) { + updateLeftoverRobot(Integer.parseInt(robotUser.getRobotId())); + disconnectFromGameServer(connecId); + } + //2026.02.03修改 通过机器人房间映射直接获取房间信息 + else if ("2009".equalsIgnoreCase(command)) { + //直接使用定时任务替代Thread.sleep,避免嵌套异步调用 + scheduleDelay(() -> { + Jedis jedis = null; + try { + jedis = Redis.use().getJedis(); + Integer paramRobotId = param.getInt("aid"); + if (robotUser != null && paramRobotId != null) { + String roomKey = String.valueOf(robotUser.getCurrentRoomId()); + + //查询该房间的玩家信息 + String playersStr = jedis.hget(roomKey, "players"); + if (playersStr != null && !playersStr.equals("[]")) { + String players = playersStr.substring(1, playersStr.length() - 1); + String[] playerIds = players.split(","); + + //判断只有当前机器人一个玩家 + if (playerIds.length == 1) { + int playerId = Integer.parseInt(playerIds[0].trim()); + if (playerId == paramRobotId) { + String gpid = jedis.hget(roomKey, "gpid"); + + //更新机器人剩余数量 + if (gpid != null && count != null && count.containsKey(Integer.parseInt(gpid))) { + Integer currentValue = count.get(Integer.parseInt(gpid)); + if (currentValue != null && currentValue > 0) { + count.put(Integer.parseInt(gpid), currentValue - 1); + } + } + + //发送退出房间协议 + ITObject params = TObject.newInstance(); + client.send("1005", params, response -> { + EXGameController.removeRobotRoomInfo(String.valueOf(paramRobotId)); + //断开连接 + disconnectFromGameServer(connecId); + //更新机器人剩余数量 + updateLeftoverRobot(paramRobotId); + log.info("2009发送退出房间协议1005,robotId: {}", paramRobotId); + }); + } + } + } + } + } catch (NumberFormatException e) { + log.error("2009协议数字格式异常,robotId: {}, connecId: {}", param.get("aid"), connecId); + } catch (NullPointerException e) { + log.error("2009协议空指针异常,connecId: {}", connecId); + } catch (Exception e) { + log.error("2009协议处理异常: {}, connecId: {}", e.getMessage(), connecId, e); + } finally { + if (jedis != null) { + jedis.close(); + } + } + }, 6, TimeUnit.SECONDS); + } + //结算 + else if ("817".equalsIgnoreCase(command)) { + //清空所有 FuLuShouHandler 相关的集合数据 + handler.clearAllData(); + + Integer type = param.getInt("type"); + if (type == 1 || type == 2) { //为 1 为大结算 为 2 为解散 + if (count != null && count.containsKey(pid)) { + Integer currentValue = count.get(pid); + if (currentValue > 0) { + count.put(pid, currentValue - 1); + } + } + //更新机器人剩余数量 + updateLeftoverRobot(Integer.parseInt(robotUser.getRobotId())); + + //游戏结束后主动断开连接 + disconnectFromGameServer(connecId); + } + ITObject params = TObject.newInstance(); + params.putString("session", client.getSession()); + client.send("1003", params, new ICallback() { + @Override + public void action(MessageResponse messageResponse) { + + } + }); + } + //服务器通知客户端有玩家执行了操作 + else if ("815".equalsIgnoreCase(command)) { + handler.onPlayerAction(param, robotUser); + } else if ("821".equalsIgnoreCase(command)) { + handler.bupai(param,robotUser); + } else if ("818".equalsIgnoreCase(command)) { + handler.guopai(param, client, robotUser); + } + //飘鸟提示 + else if ("833".equalsIgnoreCase(command)) { + handler.piaoNiaoTip(); + } + //飘鸟提示 reload + else if ("2031".equalsIgnoreCase(command)) { + log.info("收到飘鸟提示 reload: {}", param); + } + //飘鸟事件 + else if ("2032".equalsIgnoreCase(command)) { + log.info("收到飘鸟事件:{}", param); + } + //2001-2009 房间相关协议保持原有逻辑 + } catch (Exception e) { + log.error("处理接收到的游戏协议异常:{}, command: {}", e.getMessage(), command); + } finally { + if (jedis0 != null) { + jedis0.close(); + } + if (jedis2 != null) { + jedis2.close(); + } + } + } + + /** + * 增加 leftover_robot 数量 机器人退出房间 + */ + private void updateLeftoverRobot(int robotId) { + Jedis jedis2 = Redis.use("group1_db2").getJedis(); + try { + + jedis2.hset("gallrobot", String.valueOf(robotId), "0"); + + jedis2.hset("{grobot}:" + robotId, "start", "0"); + + log.info("机器人 {} 退出房间,修改gallrobot为0", robotId); + } finally { + jedis2.close(); + } + } + + /** + * 机器人登录 + */ + public void login(RobotUser robotUser) { + log.info("login:{}", robotUser.getRobotId()); + ITObject object = null; + AccountBusiness accountBusiness = null; + accountBusiness = new AccountBusiness(); + try { + //先快速登录 + object = accountBusiness.fastLogin(Integer.parseInt(robotUser.getRobotId())); + log.info("object:{}", object); + if (object == null) { + object = accountBusiness.idPasswordLogin(Integer.parseInt(robotUser.getRobotId()), robotUser.getPassword()); + } + ITObject finalObject = object; + CompletableFuture.runAsync(() -> { + if (finalObject != null) { + //判断是否有房间 + if (finalObject.getTObject("account") != null) { + ITObject validate = TObject.newInstance(); + validate.putString("token", finalObject.getString("token")); + robotUser.setToken(finalObject.getString("token")); + ; + robotUser.setLoginsession("{user}:" + robotUser.getRobotId()); + if (robotUser.getLoginsession() != null) { + robotUser.setIsLogin(true); + } + if (finalObject.getTObject("account").get("roomid") != null) { + String roomid = finalObject.getTObject("account").get("roomid").toString(); + robotUser.setCurrentRoomId(Integer.parseInt(roomid)); + connectGame(robotUser); + + robotUser.setConnecId(robotUser.getCurrentRoomId() + "_" + robotUser.getRobotId()); + log.info("重启获取的机器人还有当前房间,准备加入: {}", robotUser.getConnecId()); + exGameController.webGroupJoinRoom(robotUser); + } + } + } + }, ThreadPoolConfig.getBusinessThreadPool()); //指定自定义线程池 + } catch (Exception e) { + log.error("机器人登录异常"); + } + } + + public void connectGame(RobotUser robotUser) { + if (robotUser.isLogin) { + if (robotUser.getClient() == null) { + TaurusClient client = new TaurusClient(robotUser.getGameHost() + ":" + robotUser.getGamePort(), "cm" + robotUser.getRobotId(), TaurusClient.ConnectionProtocol.Tcp); + client.setSession(robotUser.getLoginsession()); + client.connect(); + setupEventListeners(client, robotUser.getCurrentRoomId() + "_" + robotUser.getRobotId()); + robotUser.setIsconnect(client.isConnected()); + try { + Thread.sleep(1000); + } catch (Exception e) { + log.error("连接游戏服务器时发生异常", e); + } + robotUser.setClient(client); + EXGameController.robotRoomMapping.put(robotUser.getCurrentRoomId() + "_" + robotUser.getRobotId(), robotUser); + } else { + log.info("reconnect"); + log.info("client.isConnected(){}", robotUser.getClient().isConnected()); + if (robotUser.getClient().isConnected()) { + robotUser.setIsconnect(true); + } else { + log.info("reconnect{}", robotUser.getClient().getGameID()); + TaurusClient client = new TaurusClient(robotUser.getGameHost() + ":" + robotUser.getGamePort(), "cm" + robotUser.getRobotId(), TaurusClient.ConnectionProtocol.Tcp); + client.setSession(robotUser.getLoginsession()); + client.connect(); + robotUser.setIsconnect(client.isConnected()); + try { + Thread.sleep(1000); + } catch (Exception e) { + log.error("重新连接游戏服务器时发生异常", e); + } + robotUser.setClient(client); + EXGameController.robotRoomMapping.put(robotUser.getCurrentRoomId() + "_" + robotUser.getRobotId(), robotUser); + } + } + } + } + + /** + * 重连 + */ + public void renconnect(RobotUser robotUser) { + TaurusClient client = robotUser.getClient(); + if (client != null) { + if (client.isConnected()) { + client.connect(); + robotUser.setIsconnect(client.isConnected()); + } + } + } + + /** + * 根据connecId获取游戏服务器连接 + */ + public TaurusClient getGameClient(String connecId) { + return robotRoomMapping.get(connecId) != null ? robotRoomMapping.get(connecId).getClient() : null; + } + + + public int getTime() { + return Integer.parseInt((System.currentTimeMillis() + "").substring(0, 10)); + } + + public static void sleepTime(int time) { + try { + //添加延迟 + Thread.sleep(time); + } catch (InterruptedException e) { + } + } + +} \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/business/AccountBusiness.java b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/business/AccountBusiness.java new file mode 100644 index 0000000..a4d7d41 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/business/AccountBusiness.java @@ -0,0 +1,290 @@ +package robot.zp.business; + +import com.data.bean.AccountBean; +import com.data.bean.GameBean; +import com.data.cache.AccountCache; +import com.data.cache.BaseCache; +import com.data.cache.GameCache; +import com.data.util.Utility; +import com.taurus.core.entity.ITArray; +import com.taurus.core.entity.ITObject; +import com.taurus.core.entity.TArray; +import com.taurus.core.entity.TObject; +import com.taurus.core.plugin.database.DataBase; +import com.taurus.core.plugin.redis.Redis; +import com.taurus.core.plugin.redis.RedisLock; +import com.taurus.core.util.Logger; +import com.taurus.core.util.StringUtil; +import com.taurus.core.util.Utils; +import com.taurus.web.Controller; +import redis.clients.jedis.Jedis; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class AccountBusiness extends Controller { + private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(AccountBusiness.class); + private static Logger logger = Logger.getLogger(AccountBusiness.class); + + private ITObject fillLoginData(String session, int accountid) { + ITObject resData = TObject.newInstance(); + ITObject userData = TObject.newInstance(); + resData.putTObject("account", userData); + resData.putUtfString("session_id", session); + resData.putTArray("games", getOnlineGames()); + Jedis jedis0 = Redis.use("group1_db0").getJedis(); + try { + Map map = jedis0.hgetAll(session); + userData.putInt("id", accountid); + userData.putInt("diamo", Integer.parseInt(map.get("diamo"))); + userData.putUtfString("nick", map.get("nick")); + userData.putUtfString("portrait", map.get("portrait")); + userData.putInt("sex", Integer.parseInt(map.get("sex"))); + userData.putInt("type", Integer.parseInt(map.get("type"))); + int mng = Integer.parseInt(map.get("mng")); + userData.putInt("mng", mng); + + String phone = map.get("phone"); + if (StringUtil.isNotEmpty(phone)) { + userData.putUtfString("phone", phone); + } + + String address = map.get("address"); + if (StringUtil.isNotEmpty(address)) { + userData.putUtfString("address", address); + } + + String real_info = map.get("real_info"); + if (StringUtil.isNotEmpty(real_info)) { + userData.putTObject("real_info", TObject.newFromJsonData(real_info)); + } + String oldRoom = Utility.getOldRoomV2(jedis0, 0, session, accountid); + if (StringUtil.isNotEmpty(oldRoom)) { + String roomid = oldRoom.replace("room:", ""); + String group = jedis0.hget(oldRoom, "group"); + int groupId = 0; + if (StringUtil.isNotEmpty(group)) { + groupId = Integer.parseInt(group); + } + userData.putUtfString("roomid", roomid); + userData.putInt("groupId", groupId); + } + } finally { + jedis0.close(); + } + + resData.putUtfString("groupWeb", Redis.use("group1_db1").hget("web_requrl", "groupWeb_jefe")); + return resData; + } + + public final ITObject fastLogin(int userid) { + Jedis jedis = Redis.use("group1_db0").getJedis(); + ITObject resData = null; + try { + Set usertoken = jedis.smembers("{user}:" + userid + "_token"); + if (usertoken.size() <= 0) { + return null; + } + String token = ""; + for (String item : usertoken) { + token = item; + } + String session = "{user}:" + userid; + + + AccountBean acc_bean = AccountCache.getAccount(session); + resData = fillLoginData(session, acc_bean.id); + String idPwdBan = Redis.use("group1_db0").get(acc_bean.id + "_login_ban"); + if (StringUtil.isNotEmpty(idPwdBan)) { + logger.error("id:" + acc_bean.id + " ban login"); + //throw new WebException(ErrorCode.BAN_LOGIN); + } + resData.putString("token", token); + return resData; + } catch (Exception e) { + + } finally { + jedis.close(); + } + + return resData; + + } + + + public final ITObject idPasswordLogin(int id, String password) { + logger.info("id:" + id + " login"); + + Jedis jedis0 = Redis.use("group1_db0").getJedis(); + RedisLock lock = new RedisLock("wx_" + id, jedis0); + try { + + logger.info("==========> password111 = " + password); + String superPwd = Redis.use("group1_db1").get("superpwd2021"); + String sql = ""; + if (!StringUtil.isEmpty(superPwd)) { + if (!password.equals(superPwd)) { + password = Utils.getMD5Hash(password); + sql = String.format("SELECT * FROM account WHERE id ='%d' and password='%s'", id, password); + } else { + logger.info("==========> password = " + password); + + sql = String.format("SELECT * FROM account WHERE id ='%d' ", id); + } + } else { + password = Utils.getMD5Hash(password); + sql = String.format("SELECT * FROM account WHERE id ='%d' and password='%s'", id, password); + } + + + String idPwdBan = Redis.use("group1_db0").get(id + "_login_ban"); + if (StringUtil.isNotEmpty(idPwdBan)) { + logger.info("进入了77777777777777777777"); + logger.error("id:" + id + " ban login"); + //throw new WebException(ErrorCode.BAN_LOGIN); + } + logger.info("进入了9999999999999"); + + ITArray resultArray = null; + try { + resultArray = DataBase.use().executeQueryByTArray(sql); + } catch (SQLException e) { + log.error(e); + } + if (resultArray.size() == 0) { + if (Redis.use("group1_db0").exists(id + "_pwd_token")) { + Redis.use("group1_db0").incrBy(id + "_pwd_token", 1); + } else { + Redis.use("group1_db0").set(id + "_pwd_token", 1 + ""); + Redis.use("group1_db0").expire(id + "_pwd_token", 300); + } + + String idPwdToken = Redis.use("group1_db0").get(id + "_pwd_token"); + if (StringUtil.isNotEmpty(idPwdToken)) { + long count = Long.parseLong(idPwdToken); + if (count >= 10) { + Redis.use("group1_db0").set(id + "_login_ban", "1"); + Redis.use("group1_db0").expire(id + "_login_ban", 1800); + logger.error("pwd error count:" + count + " not login"); + logger.info("进入了00000000000"); + + //throw new WebException(ErrorCode._NO_SESSION); + + } + } + logger.info("进入了111111111111"); + + //throw new WebException(ErrorCode._FAILED); + } + + ITObject userData = resultArray.getTObject(0); + int accountid = userData.getInt("id"); + UpdateUserData(userData, accountid); + + AccountBean acc_bean = AccountCache.getAccount(accountid); + String session = acc_bean.redis_key; + this.setSession(session); + + if (resultArray.size() > 0) { + this.setSession(session); + } + + ITObject resData = fillLoginData(session, accountid); + String token = Utils.getMD5Hash(id + "_" + password + "_" + System.currentTimeMillis() + "e4!Fesu]]{QyUuEA" + + Math.random() * 1000000); + Redis.use("group1_db0").sadd(session + "_token", token); + + Redis.use("group1_db0").hset(token, "user", session); + Redis.use("group1_db0").hset(token, "create_time", "" + System.currentTimeMillis() / 1000); + Redis.use("group1_db0").expire(token, 172800); + + logger.info("进入了2222222222222"); + + long tokenNum = Redis.use("group1_db0").scard(session + "_token"); + if (tokenNum >= 10) { + logger.warn("id:" + accountid + " repeat login, token count:" + tokenNum); + } + logger.info("进入了33333333333333333332"); + + resData.putString("token", token); + return resData; + } finally { + lock.unlock(); + } + } + + private static String updateSession(ITObject userData, int id) { + String session = AccountCache.genKey(id); + Map map = new HashMap(); + Utils.objectToMap(userData, map); + + Jedis jedis0 = Redis.use("group1_db0").getJedis(); + try { + jedis0.hmset(session, map); + BaseCache.updateCacheVer(jedis0, session); + } finally { + jedis0.close(); + } + + return session; + } + + /** + * 获取在线游戏 + */ + public static ITArray getOnlineGames() { + ITArray games = new TArray(); + Jedis jedis1 = Redis.use("group1_db1").getJedis(); + try { + Set list = jedis1.zrevrangeByScore("online_games", 1000, 1); + for (String game : list) { + int gameId = Integer.parseInt(game); + GameBean gb = GameCache.getGame(gameId); + if (gb == null) + continue; + ITObject gameObj = gb.getTObject(); + + for (Entry entry : gb.pay.entrySet()) { + gameObj.putInt(entry.getKey(), entry.getValue()); + } + games.addTObject(gameObj); + } + } finally { + jedis1.close(); + } + return games; + } + + /** + * + * @return + */ + private int UpdateUserData(ITObject reqData, long id) { + ITObject userData = TObject.newInstance(); + userData.putInt("id", (int) id); + + userData.putUtfString("acc", reqData.getUtfString("acc")); + userData.putUtfString("portrait", reqData.getUtfString("portrait")); + userData.putUtfString("nick", reqData.getUtfString("nick")); + int sex = reqData.getInt("sex"); + if (sex == 0) { + sex = 1; + reqData.putInt("sex", sex); + } + userData.putInt("sex", sex); + + userData.putInt("mng", 0); + userData.putInt("type", 0); + if (reqData.containsKey("diamo")) { + userData.putInt("diamo", reqData.getInt("diamo")); + } + + userData.putInt("invitation", 1); + String session = updateSession(userData, (int) id); + this.setSession(session); + return (int) id; + } +} diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/handler/FangPaoFaHandler.java b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/handler/FangPaoFaHandler.java new file mode 100644 index 0000000..b0ff7b3 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/handler/FangPaoFaHandler.java @@ -0,0 +1,695 @@ +package robot.zp.handler; + +import com.google.gson.Gson; +import com.taurus.core.entity.ITArray; +import com.taurus.core.entity.ITObject; +import com.taurus.core.entity.TDataWrapper; +import com.taurus.core.entity.TObject; +import com.taurus.core.plugin.redis.Redis; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; +import robot.zp.info.RobotUser; +import taurus.client.Message; +import taurus.client.TaurusClient; +import taurus.util.*; + +import java.util.*; + +/** + * 放炮罚游戏算法处理器 + * 专门处理福禄寿麻将的游戏逻辑和算法 + */ +public class FangPaoFaHandler { + private static final Logger log = LoggerFactory.getLogger(FangPaoFaHandler.class); + private static final Gson gson = new Gson(); + + //手牌 + private final List handCards = new ArrayList<>(); + + //出过的牌 + private final List outCards = new ArrayList<>(); + + //中间区(摸牌后先放入这里,等待动作判断) + private int middleCard = 0; + + //会话标识 + public String session = ""; + // 访问令牌 + public String token = ""; + + // 当前操作牌 + private int currentCard = 0; + + private int modepai = 0; + + + // 【新增】明牌分类存储 + private final List chiCards = new ArrayList<>(); // 吃的牌 + private final List pengCards = new ArrayList<>(); // 碰的牌 + private final List weiCards = new ArrayList<>(); // 偎的牌 + private final List paoCards = new ArrayList<>(); // 跑的牌 + private final List tiCards = new ArrayList<>(); // 提的牌 + private final List kanCards = new ArrayList<>(); // 坎的牌 + + + public static void fangPaoTipEvent(Message message, TaurusClient client) { + ITObject param = message.param; + Integer card = param.getInt("card"); + ITObject params = TObject.newInstance(); + params.putInt("card", card); + client.send("823", params, response -> { + System.out.println("操作成功: " + response.returnCode); + }); + } + + + /** + * 获取手牌 + */ + public List getChangShaCardInhand() { + return handCards; + } + + /** + * 获取出过的牌 + */ + public List getChuGuoCardInhand() { + return outCards; + } + + /** + * 初始化手牌 (协议 811) + */ + public void initHandCards(Message message) { + ITObject param = message.param; + if (param == null) { + return; + } + + ITArray cardList = param.getTArray("card_list"); + handCards.clear(); + + for (int i = 0; i < cardList.size(); i++) { + handCards.add(cardList.getInt(i)); + } + System.out.println("放炮罚初始化手牌" + handCards); + log.info("放炮罚初始化手牌:{} 张", handCards.size()); + log.debug("手牌详情:{}", handCards); + } + + /** + * 摸牌处理 (协议 819) + * 放炮罚规则:摸牌后先放入中间区,等待动作判断 + */ + public void drawCard(Message message, RobotUser robotUser) { + ITObject param = message.param; + if (param == null) { + return; + } + int jiqirenseat = 0; + if (robotUser != null) { + jiqirenseat = robotUser.getSeat(); + } + int seat = param.getInt("seat"); + System.out.println("819 jiqirenseat" + jiqirenseat); + System.out.println("819 seat" + seat); + int drawnCard = param.getInt("card"); + System.out.println("819摸牌 ++++ " + drawnCard); + + if (drawnCard > 0 && seat == jiqirenseat) { + // ✅ 只记录modepai,不加入手牌 + currentCard = drawnCard; + modepai = drawnCard; + System.out.println("进入摸牌里面" + drawnCard); + System.out.println("摸牌下面" + modepai); + System.out.println("摸完后的手牌(未加入)" + handCards); + log.info("放炮罚摸牌:{}", drawnCard); + log.debug("当前手牌数量:{}", handCards.size()); + } + } + + /** + * 出牌广播处理 (协议 812) + */ + public void onDiscardBroadcast(Message message) { + ITObject param = message.param; + if (param == null) { + return; + } + +// currentCard = param.getInt("card"); +// log.debug("出牌广播:card={}", currentCard); + } + + /** + * 动作提示处理 (协议 814) + * 处理 吃碰胡 + */ + public void actionTip(ITObject param, TaurusClient client, RobotUser robotUser) { +// robotUser.getSeat() + System.out.println("获取后台发给客户端的动作处理的所有参数" + param); + Integer uid = param.getInt("uid"); + ITArray tipList = param.getTArray("tip_list"); + if (tipList == null || tipList.size() == 0) { + return; + } + log.info("收到动作提示,tip_list 数量:{}", tipList.size()); + int type = 0; + int id = 0; + // 优先处理胡牌 + for (int i = 0; i < tipList.size(); i++) { + TObject tip = (TObject) tipList.get(i).getObject(); + type = tip.getInt("type"); + id = tip.getInt("id"); + System.out.println("type+++++++" + type); + if (type == 8) { // 胡牌 + System.out.println("收到胡牌提示"); + ITObject params = TObject.newInstance(); + params.putString("session", session + "," + token); + params.putInt("qi", 0); + params.putInt("id", id); + + delayedAction(client, params, "胡牌"); + return; + } + } + + // 使用优化版吃碰决策算法(支持多选项智能选择) + ChiPengDecisionV2.DecisionResult result = ChiPengDecisionV2.decide(handCards, tipList,chiCards,pengCards,weiCards,paoCards,tiCards); + System.out.println("result +++" + result); + ITObject params = TObject.newInstance(); + params.putString("session", session + "," + token); + + if (result.shouldAct) { + System.out.println("执行type" + type); + System.out.println("执行吃或碰 id" + result.actionId); + System.out.println("执行吃或碰 具体牌和分数 " + result.reason); + + // 从手牌中删除opcard里的牌 +// removeOpCardsFromHand(tipList, result.actionId); + +// modepai = 0; + // 执行吃或碰 + params.putInt("qi", 0); + params.putInt("id", result.actionId); + + String actionName = result.reason; + delayedAction(client, params, actionName); + log.info("吃碰决策: 执行{}", result.reason); + } else { + System.out.println("跳过吃或碰"); + params.putInt("id", uid); + delayedAction(client, params, "过"); + System.out.println("吃碰决策: 跳过, 原因: {}" + result.reason); + log.info("吃碰决策: 跳过, 原因: {}", result.reason); + } + } + + + /** + * 从手牌中删除opcard里的牌(吃或碰时调用) + * 放炮罚规则:考虑中间区的牌 + * + * @param tipList 原始的tip_list + * @param actionId 选择的动作ID + */ + private void removeOpCardsFromHand(ITArray tipList, int actionId) { + if (tipList == null || tipList.size() == 0) { + return; + } + + // ✅ 关键: 执行任何动作前,先将摸的牌加入手牌 + if (modepai > 0) { + handCards.add(modepai); + System.out.println("执行动作前将摸的牌加入手牌: " + modepai); + } + + // 找到对应的tip + for (int i = 0; i < tipList.size(); i++) { + TObject tip = (TObject) tipList.get(i).getObject(); + int id = tip.getInt("id"); + + if (id == actionId) { + int type = tip.getInt("type"); + ITArray opcardArray = tip.getTArray("opcard"); + + if (opcardArray != null && opcardArray.size() > 0) { + // 删除opcard中的所有牌 + for (int j = 0; j < opcardArray.size(); j++) { + int card = opcardArray.getInt(j); + removeCardFromHand(card); + System.out.println("删除opcard中的牌: " + card); + } + } + + int card1 = tip.getInt("card"); + + // 如果是吃牌且吃的牌是自己摸的(modepai),也要删除 + if (type == 1 && card1 == modepai) { + // 吃自己摸的牌,需要删除这张牌 + removeCardFromHand(card1); + System.out.println("吃自己摸的牌,删除: " + card1); + } + + if (type == 2 && card1 == modepai) { + removeCardFromHand(card1); + System.out.println("碰自己摸的牌,删除: " + card1); + } + + // ✅ 执行完动作后,清除modepai + modepai = 0; + } + } + + // 重新排序手牌 + handCards.sort(Integer::compareTo); + System.out.println("吃碰后手牌: " + handCards); + } + + /** + * 从手牌中删除指定的一张牌 + * + * @param card 要删除的牌 + */ + private void removeCardFromHand(int card) { + Integer cardObj = Integer.valueOf(card); + + System.out.println("从手牌中删除指定的一张牌 hands " + handCards); + int index = handCards.indexOf(cardObj); + if (index != -1) { + System.out.println("从手牌中删除指定的一张牌 " + index); + handCards.remove(index); + System.out.println("从手牌中删除指定的一张牌的手牌" + handCards); + } else { + log.warn("手牌中找不到要删除的牌: {}", card); + } + } + + + /** + * 同步手牌 + */ + public void updateHandCard(List handCard) { + log.info("updateHandCard 同步手牌:{}", handCard); + handCards.clear(); + handCards.addAll(handCard); + log.info("updateHandCard 同步手牌完成,数量:{}", handCards.size()); + } + + /** + * 同步出牌 + */ + public void updateOutCard(List outCard) { + outCards.clear(); + outCards.addAll(outCard); + log.info("updateOutCard 同步出牌完成,数量:{}", outCards.size()); + } + + /** + * 出牌决策 (协议 813) - 兼容旧方法名 + */ + public void outCard(TaurusClient client, + Map> playerOutcardsMap, + Map> playerchisMap, + Map> playerpengsMap, + Map> playermingsMap, + Map> playerzisMap) { + makeDiscardDecision(client, playerOutcardsMap, playerchisMap, playerpengsMap, playermingsMap, playerzisMap, null); + } + + public void makeDiscardDecision(TaurusClient client, + Map> playerOutcardsMap, + Map> playerchisMap, + Map> playerpengsMap, + Map> playermingsMap, + Map> playerzisMap, + Integer robotId) { + if (handCards.isEmpty()) { + log.warn("手牌为空,无法出牌"); + return; + } + + // 出牌前,如果modepai有值,先加入手牌 + if (modepai > 0) { + handCards.add(modepai); + System.out.println("出牌前将摸的牌加入手牌: " + modepai); + modepai = 0; + } + + System.out.println(robotId + "放炮罚机器人手牌" + handCards); + int i = FangPaoFaSuanFa.selectBestCard(handCards, chiCards,pengCards,weiCards,paoCards,tiCards); + Integer cardToOut = handCards.get(i); + System.out.println(robotId + "放炮罚出牌 最新出牌" + cardToOut); + + ITObject params = TObject.newInstance(); + params.putInt("card", cardToOut); + + //添加历史出牌 + if (!outCards.isEmpty()) { + List cardsToSend = new ArrayList<>(outCards); + params.putTArray("outcard_list", CardUtil.maJiangToTArray(cardsToSend)); + } + + params.putTArray("card_list", CardUtil.maJiangToTArray(handCards)); + params.putString("session", session + "," + token); + + outCards.add(cardToOut); + handCards.remove(i); + + // 记录出牌 + handCards.sort(Integer::compareTo); + System.out.println(robotId + " 最新版本 放炮罚牌 删掉出的牌了" + handCards); + + log.info("放炮罚出牌:{}", cardToOut); + log.debug("放炮罚剩余手牌:{}", handCards); + + // 延迟发送,模拟思考时间 + delayedDiscard(client, params); + } + + + /** + * 飘鸟提示处理 (协议 833) + */ + public void piaoNiaoTip() { + log.info("收到飘鸟提示"); + //TODO: 实现飘鸟决策逻辑 + } + + /** + * 清理所有数据 + */ + public void clearAllData() { + chiCards.clear(); + pengCards.clear(); + weiCards.clear(); + paoCards.clear(); + tiCards.clear(); + handCards.clear(); + outCards.clear(); + middleCard = 0; + currentCard = 0; + modepai = 0; + log.info("放炮罚处理器数据已清空"); + } + + /** + * 延迟执行动作 + */ + private void delayedAction(TaurusClient client, ITObject params, String actionName) { + Thread thread = new Thread(() -> { + try { + int delaySeconds = 1 + new Random().nextInt(2); + log.info("执行{}动作,延迟{}秒", actionName, delaySeconds); + Thread.sleep(delaySeconds * 1000); + + client.send("612", params, response -> { + System.out.println("动作发送完成"); + log.info("{}动作发送完成", actionName); + }); + } catch (Exception e) { + log.error("执行{}动作时发生异常:{}", actionName, e.getMessage(), e); + } + }); + thread.start(); + } + + /** + * 延迟出牌 + */ + private void delayedDiscard(TaurusClient client, ITObject params) { + Thread thread = new Thread(() -> { + try { + int delay = new Random().nextInt(4); + Thread.sleep(delay * 1000); + + client.send("611", params, response -> { + log.debug("出牌发送完成"); + }); + } catch (Exception e) { + log.error("出牌时发生异常:{}", e.getMessage(), e); + } + }); + thread.start(); + } + + public void onPlayerAction(ITObject param, RobotUser robotUser) { + System.out.println("815所有参数" + param); + + //机器人id + System.out.println("进入815"); + System.out.println(robotUser.getRobotId() + " 815下当前机器人手牌 " + handCards); + Integer type = param.getInt("type"); + + String robotId = robotUser.getRobotId(); + Integer playerid = param.getInt("playerid"); + Integer from_seat = param.getInt("from_seat"); + System.out.println("from_seat++++++++ " + from_seat); + System.out.println("robotUser.getSeat() " + robotUser.getSeat()); + ITArray opcard = param.getTArray("opcard"); + Integer card = param.getInt("card"); + System.out.println("815 判断之前 card" + card); + System.out.println("815 type" + type); + //如果是机器人玩家进来的话,就判断删除手牌 + System.out.println("机器人id" + robotId); + System.out.println("玩家id" + playerid); + + //单独将吃碰畏提跑的牌存起来 + if (robotId.equals(playerid.toString())){ + //吃 + if (type == 1){ + List tem = new ArrayList<>(); + for (int i = 0; i < opcard.size(); ++i) { + tem.add(opcard.getInt(i)); + } + chiCards.addAll(tem); + chiCards.add(card); + System.out.println("吃的牌组" + chiCards); + } + //碰 + if (type == 2){ + pengCards.add(card); + pengCards.add(card); + pengCards.add(card); + System.out.println("碰的牌组" + pengCards); + + } + + if (type == 3){ + kanCards.add(card); + kanCards.add(card); + kanCards.add(card); + + } + //跑 + if (type == 6){ + paoCards.add(card); + paoCards.add(card); + paoCards.add(card); + System.out.println("跑的牌组" + paoCards); + + } + //提 + if (type == 7){ + tiCards.add(card); + tiCards.add(card); + tiCards.add(card); + tiCards.add(card); + } + //偎 + if (type == 4){ + weiCards.add(card); + weiCards.add(card); + weiCards.add(card); + } + //抽偎 + if (type == 5){ + weiCards.add(card); + weiCards.add(card); + weiCards.add(card); + } + } + + + //机器人 自己进行未提等操作 + if (robotId != null && robotId.equals(playerid.toString())) { + System.out.println("opcard" + opcard); + //type = 5抽偎/type = 4偎牌 + //机器人吃和碰的话 不需要在这里删除手牌 + if (type != 1 && type != 2) { + if (type == 5 || type == 4) { + List tem = new ArrayList<>(); + for (int i = 0; i < opcard.size(); ++i) { + tem.add(opcard.getInt(i)); + } + System.out.println("tem.size" + tem.size()); + CardUtilFangpaofa.removeCard(handCards, card, tem.size()); + modepai = 0; + } else if (type != 3){ + //type = 7 提牌 type = 6 跑 + List tem = new ArrayList<>(); + for (int i = 0; i < opcard.size(); ++i) { + tem.add(opcard.getInt(i)); + } + System.out.println("tem.size" + tem.size()); + CardUtilFangpaofa.removeCard(handCards, card, tem.size()); + modepai = 0; + }else if (type == 3){ + //type = 3 坎 + List tem = new ArrayList<>(); + for (int i = 0; i < opcard.size(); ++i) { + tem.add(opcard.getInt(i)); + } + if (modepai == card){ + modepai = 3; + } + System.out.println("tem.size" + tem.size()); + CardUtilFangpaofa.removeCard(handCards, card, tem.size()); + } + } + + //吃碰删除手牌 + if (type == 1 || type == 2) { + if (opcard == null) { + return; + } + + // 执行任何动作前,先将摸的牌加入手牌 + if (modepai > 0) { + handCards.add(modepai); + System.out.println("执行动作前将摸的牌加入手牌: " + modepai); + } + + if (opcard.size() > 0) { + // 删除opcard中的所有牌 + for (int j = 0; j < opcard.size(); j++) { + int card2 = opcard.getInt(j); + removeCardFromHand(card2); + System.out.println("删除opcard中的牌: " + card2); + } + } + + // 如果是吃牌且吃的牌是自己摸的(modepai),也要删除 + if (type == 1 && card == modepai) { + // 吃自己摸的牌,需要删除这张牌 + removeCardFromHand(card); + System.out.println("吃自己摸的牌,删除: " + card); + } + + if (type == 2 && card == modepai) { + removeCardFromHand(card); + System.out.println("碰自己摸的牌,删除: " + card); + } + + // 执行完动作后,清除modepai + modepai = 0; + System.out.println("吃碰后手牌: " + handCards); + } + //如果是真人进入 并且是操作的机器人摸到的牌,进行为提等操作, + } else if (from_seat == robotUser.getSeat()) { + System.out.println("进入真人玩家进入815"); + modepai = 0; + } + } + + + public void guopai(ITObject param, TaurusClient client, RobotUser robotUser) { + int seat = robotUser.getSeat(); + int card = param.getInt("card"); + + System.out.println("过牌/自动出牌"); + System.out.println("过牌删除手牌前" + handCards); + System.out.println("打印机器人座位号" + seat); + System.out.println("后台发过来的座位号" + param.getInt("seat")); + System.out.println("818中的card=" + card); + System.out.println("当前modepai=" + modepai); + + if (seat == param.getInt("seat")) { + // 自己座位的818 + + // 判断818的语义 + if (card == modepai && modepai > 0) { + // 这张牌从未加入手牌,所以不需要删除 + // 只需要清零modepai + modepai = 0; + System.out.println("清除modepai"); + } + } + System.out.println("过牌删除手牌删除后" + handCards); + System.out.println("最终modepai=" + modepai); + } +// ... ex + + public void bupai(ITObject param, RobotUser robotUser) { + Integer card = param.getInt("card"); + Integer seat = param.getInt("seat"); + System.out.println("机器人seat" + robotUser.getSeat()); + System.out.println("后台发过来的座位号" + seat); + System.out.println("放炮罚补牌" + card); + if (seat == robotUser.getSeat() && card != modepai && modepai != 3) { + handCards.add(card); + } + if (modepai == 3){ + modepai = 0; + } + } + + public void daniao(ITObject param,TaurusClient client) { + Integer niao = param.getInt("niao"); + //目前先不打鸟 + ITObject params = new TObject(); + params.putInt("niaoflag",0); + daniao(client, params, "打鸟"); + } + + + private void daniao(TaurusClient client, ITObject params, String actionName) { + Thread thread = new Thread(() -> { + try { + int delaySeconds = 1 + new Random().nextInt(2); + log.info("执行{}动作,延迟{}秒", actionName, delaySeconds); + Thread.sleep(delaySeconds * 1000); + + client.send("831", params, response -> { + System.out.println("动作发送完成"); + log.info("{}动作发送完成", actionName); + }); + } catch (Exception e) { + log.error("执行{}动作时发生异常:{}", actionName, e.getMessage(), e); + } + }); + thread.start(); + } + + public void saveToRedis(String connecId) { + Jedis jedis = Redis.use("group1_db2").getJedis(); + try { + Map stateMap = new HashMap<>(); + + stateMap.put("fangpaofaCardInhand", gson.toJson(handCards)); + stateMap.put("changShachuguopai", gson.toJson(outCards)); +// stateMap.put("chuGuoPainum", gson.toJson(chuGuoPainum)); +// stateMap.put("gangdepai", gson.toJson(gangdepai)); +// stateMap.put("pongGroup", gson.toJson(pongGroup)); +// stateMap.put("chowGroup", gson.toJson(chowGroup)); +// stateMap.put("changShaCardInhandgang", gson.toJson(changShaCardInhandgang)); + +// stateMap.put("changShaCard", String.valueOf(changShaCard)); + stateMap.put("session", session); + stateMap.put("token", token); + + String redisKey = "{fpf}:" + connecId; + jedis.hmset(redisKey, stateMap); + //1小时过期时间 + jedis.expire(redisKey, 3600); + + log.info("保存Fangpaofa状态到Redis: {}", connecId); + } catch (Exception e) { + log.error("保存Fangpaofa状态到Redis失败: {}", e.getMessage()); + } finally { + jedis.close(); + } + } + +} \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/info/RobotUser.java b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/info/RobotUser.java new file mode 100644 index 0000000..dd6de18 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/info/RobotUser.java @@ -0,0 +1,164 @@ +package robot.zp.info; + +import taurus.client.TaurusClient; + +/** + * 机器人房间信息类 + */ +public class RobotUser { + private String connecId; + private int userId; + private String robotId; + + private int seat; + public int status; //工作状态 0,1:等待,2:干活 + public boolean isconnect = false; //是否连接上 + public int intoRoomTime; //进入房间时间戳 + public String password; + public String gameHost; + public String gamePort; + public String robotGroupid; + public String robotPid; + public boolean isLogin = false; + private String token; + private String loginsession; + public int currentRoomId;//当前房间id + public TaurusClient client = null; + + public TaurusClient getClient() { + return client; + } + + public void setClient(TaurusClient client) { + this.client = client; + } + + public int getCurrentRoomId() { + return currentRoomId; + } + + public void setCurrentRoomId(int currentRoomId) { + this.currentRoomId = currentRoomId; + } + + public String getLoginsession() { + return loginsession; + } + + public void setLoginsession(String loginsession) { + this.loginsession = loginsession; + } + + public boolean getIsLogin() { + return isLogin; + } + + public void setIsLogin(boolean login) { + isLogin = login; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getGameHost() { + return gameHost; + } + + public void setGameHost(String gameHost) { + this.gameHost = gameHost; + } + + public String getGamePort() { + return gamePort; + } + + public void setGamePort(String gamePort) { + this.gamePort = gamePort; + } + + public String getRobotGroupid() { + return robotGroupid; + } + + public void setRobotGroupid(String robotGroupid) { + this.robotGroupid = robotGroupid; + } + + public String getRobotPid() { + return robotPid; + } + + public void setRobotPid(String robotPid) { + this.robotPid = robotPid; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public int getIntoRoomTime() { + return intoRoomTime; + } + + public void setIntoRoomTime(int intoRoomTime) { + this.intoRoomTime = intoRoomTime; + } + + public boolean getIsconnect() { + return isconnect; + } + + public void setIsconnect(boolean isconnect) { + this.isconnect = isconnect; + } + + public int getSeat() { + return seat; + } + + public void setSeat(int seat) { + this.seat = seat; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getConnecId() { + return connecId; + } + + public void setConnecId(String connecId) { + this.connecId = connecId; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public String getRobotId() { + return robotId; + } + + public void setRobotId(String robotId) { + this.robotId = robotId; + } + +} diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/thread/ResourceCleanupUtil.java b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/thread/ResourceCleanupUtil.java new file mode 100644 index 0000000..106891e --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/thread/ResourceCleanupUtil.java @@ -0,0 +1,72 @@ +package robot.zp.thread; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import robot.zp.EXGameController; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 资源清理工具类 + * 用于管理和清理不再使用的资源 防止内存泄漏 + */ +public class ResourceCleanupUtil { + private static final Logger log = LoggerFactory.getLogger(ResourceCleanupUtil.class); + + //需要清理的资源 + private static final Set pendingCleanupResources = ConcurrentHashMap.newKeySet(); + + /** + * 执行资源清理 + * 清理已完成对局但仍在内存中的资源 + */ + public static void performCleanup() { + if (pendingCleanupResources.isEmpty()) { + //执行常规清理 + performRegularCleanup(); + return; + } + + log.info("开始执行资源清理,待清理资源数: {}", pendingCleanupResources.size()); + int cleanedCount = 0; + + Set resourcesToClean = ConcurrentHashMap.newKeySet(); + resourcesToClean.addAll(pendingCleanupResources); + + for (String resourceId : resourcesToClean) { + try { + //从待清理列表中移除 + pendingCleanupResources.remove(resourceId); + cleanedCount++; + + log.info("已清理资源: {}", resourceId); + } catch (Exception e) { + log.error("清理资源时发生异常: {}, 错误: {}", resourceId, e.getMessage(), e); + } + } + + log.info("资源清理完成,共清理: {} 个资源", cleanedCount); + + //执行常规清理 + performRegularCleanup(); + } + + /** + * 执行常规清理 + */ + private static void performRegularCleanup() { + try { + //清理过期的机器人连接 + EXGameController.cleanupExpiredConnections(); + + //输出当前系统状态 + log.info("=== 系统资源状态 ==="); + log.info("{}", ThreadPoolConfig.getThreadPoolStatus()); + + } catch (Exception e) { + log.error("执行常规清理时发生异常: {}", e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/thread/ThreadPoolConfig.java b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/thread/ThreadPoolConfig.java new file mode 100644 index 0000000..2511401 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/robot/zp/thread/ThreadPoolConfig.java @@ -0,0 +1,117 @@ +package robot.zp.thread; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 线程池配置类 + */ +public class ThreadPoolConfig { + + private static final Logger log = LoggerFactory.getLogger(ThreadPoolConfig.class); + //线程池配置 + private static final ExecutorService BUSINESS_THREAD_POOL = + new ThreadPoolExecutor( + 5, //核心线程数 + 20, //最大线程数 + 60, //空闲线程存活时间 + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(5000), + new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(1); + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "RobotBusinessThread-" + threadNumber.getAndIncrement()); + t.setDaemon(true); + t.setPriority(Thread.NORM_PRIORITY - 1); + return t; + } + }, + new ThreadPoolExecutor.CallerRunsPolicy() + ); + + //添加定时任务线程池 + private static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = + new ScheduledThreadPoolExecutor(2, new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(1); + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "RobotScheduledThread-" + threadNumber.getAndIncrement()); + t.setDaemon(true); + t.setPriority(Thread.NORM_PRIORITY - 1); + return t; + } + }); + + public static ExecutorService getBusinessThreadPool() { + return BUSINESS_THREAD_POOL; + } + + public static ScheduledExecutorService getScheduledExecutorService() { + return SCHEDULED_EXECUTOR_SERVICE; + } + + /** + * 执行延迟任务,替代Thread.sleep + */ + public static void scheduleDelay(Runnable task, long delay, TimeUnit unit) { + log.debug("提交延迟任务: 延迟{} {}, 当前时间: {}", delay, unit, System.currentTimeMillis()); + SCHEDULED_EXECUTOR_SERVICE.schedule(() -> { + try { + log.debug("执行延迟任务开始: 当前时间: {}", System.currentTimeMillis()); + task.run(); + log.debug("执行延迟任务完成: 当前时间: {}", System.currentTimeMillis()); + } catch (Exception e) { + log.error("延迟任务执行异常: {}", e.getMessage(), e); + } + }, delay, unit); + } + + /** + * 关闭线程池 释放资源 + */ + public static void shutdown() { + log.info("开始关闭线程池..."); + + //关闭定时任务线程池 + SCHEDULED_EXECUTOR_SERVICE.shutdown(); + try { + if (!SCHEDULED_EXECUTOR_SERVICE.awaitTermination(3, TimeUnit.SECONDS)) { + log.info("定时任务线程池强制关闭"); + SCHEDULED_EXECUTOR_SERVICE.shutdownNow(); + } + } catch (InterruptedException e) { + SCHEDULED_EXECUTOR_SERVICE.shutdownNow(); + Thread.currentThread().interrupt(); + } + + //关闭业务线程池 + BUSINESS_THREAD_POOL.shutdown(); + try { + if (!BUSINESS_THREAD_POOL.awaitTermination(5, TimeUnit.SECONDS)) { + log.info("业务线程池强制关闭"); + BUSINESS_THREAD_POOL.shutdownNow(); + } + } catch (InterruptedException e) { + BUSINESS_THREAD_POOL.shutdownNow(); + Thread.currentThread().interrupt(); + } + + log.info("线程池关闭完成"); + } + + /** + * 获取线程池状态信息 + */ + public static String getThreadPoolStatus() { + ThreadPoolExecutor executor = (ThreadPoolExecutor) BUSINESS_THREAD_POOL; + return String.format("线程池状态 - 核心:%d, 活跃:%d, 完成:%d, 队列:%d", + executor.getCorePoolSize(), + executor.getActiveCount(), + executor.getCompletedTaskCount(), + executor.getQueue().size()); + } +} diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/CardUtilFangpaofa.java b/robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/CardUtilFangpaofa.java new file mode 100644 index 0000000..25c10ea --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/CardUtilFangpaofa.java @@ -0,0 +1,104 @@ +package taurus.util; + +import java.io.*; +import java.util.*; + +public class CardUtilFangpaofa { + static public int cardNum(int eventCard, List cardList) { + int result = 0; + for (Integer card : cardList) { + if (card == eventCard) { + result += 1; + } + } + return result; + } + + static public boolean checkCard(int eventCard, Map cardMap) { + if(cardMap.containsKey(eventCard)&&cardMap.get(eventCard)>=1) { + return true; + } + return false; + } + + static public void addCard(int eventCard,Map cardMap,int add) { + if(cardMap.containsKey(eventCard)) { + int num = cardMap.get(eventCard); + cardMap.put(eventCard, num+add); + }else if(add >0){ + cardMap.put(eventCard, add); + } + } + + static public int cardType(int card) { + return card / 100; + } + + static public boolean isRedCard(int card) { + return card % 100 == 2 || card % 100 == 7 || card % 100 == 10; + } + + static public boolean isBlackCard(int card) { + return card % 100 != 2 && card % 100 != 7 && card % 100 != 10; + } + + static public Map getCardNumMap(List cardList) { + Map result = new HashMap(); + for (Integer card : cardList) { + if (!result.containsKey(card)) { + result.put(card, 1); + } else { + int num = result.get(card); + result.put(card, (num + 1)); + } + } + return result; + } + + + + static public void removeCard(List cardList, int card, int count) { + int curCount = 0; + for (int i = 0; i < cardList.size(); i++) { + if (count == curCount) { + return; + } + if (cardList.get(i) == card) { + + cardList.remove(i); + i--; + curCount++; + } + } + } + + static public void removeGroup(List group, int card) { + for (int i = 0; i < group.size(); i++) { + int[] cardArray = group.get(i); + if (cardArray[0] == card) { + group.remove(cardArray); + return; + } + } + } + + + + public static List deepCopy(List src) throws IOException, ClassNotFoundException { + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(byteOut); + out.writeObject(src); + + ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); + ObjectInputStream in = new ObjectInputStream(byteIn); + @SuppressWarnings("unchecked") + List dest = (List) in.readObject(); + return dest; + } + + public static void distinctList(List cardList) { + Set set = new HashSet<>(cardList); + cardList.clear(); + cardList.addAll(set); + } +} diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/ChiPengDecisionV2.java b/robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/ChiPengDecisionV2.java new file mode 100644 index 0000000..c17a984 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/ChiPengDecisionV2.java @@ -0,0 +1,356 @@ +package taurus.util; + +import com.taurus.core.entity.ITArray; +import com.taurus.core.entity.TObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +/** + * 放炮罚 - 吃碰决策 + + */ +public class ChiPengDecisionV2 { + private static final Logger log = LoggerFactory.getLogger(ChiPengDecisionV2.class); + + public static final int TYPE_CHOW = 1; + public static final int TYPE_PONG = 2; + + public static class DecisionResult { + public boolean shouldAct; + public int actionId; + public String reason; + + public DecisionResult(boolean shouldAct, int actionId, String reason) { + this.shouldAct = shouldAct; + this.actionId = actionId; + this.reason = reason; + } + + public static DecisionResult skip(String reason) { + return new DecisionResult(false, 0, reason); + } + + public static DecisionResult act(int actionId, String reason) { + return new DecisionResult(true, actionId, reason); + } + } + + /** + * 【修正版】主决策方法 + */ + public static DecisionResult decide(List handCards, ITArray tipList, + List chiCards, List pengCards, + List weiCards, List paoCards, + List tiCards) { + if (tipList == null || tipList.size() == 0) { + return DecisionResult.skip("没有可操作的选项"); + } + + int currentHuxi = FangPaoFaSuanFa.calculateTotalHuxi( + handCards, chiCards, pengCards, weiCards, paoCards, tiCards); + int currentMenzi = FangPaoFaSuanFa.calculateTotalMenzi( + handCards, chiCards, pengCards, weiCards, paoCards, tiCards); + + int huxiDeficit = Math.max(0, 15 - currentHuxi); + int menziDeficit = Math.max(0, 7 - currentMenzi); + + // 统计手牌中对子和坎的数量 + Map cardCount = new HashMap<>(); + for (int card : handCards) { + cardCount.put(card, cardCount.getOrDefault(card, 0) + 1); + } + int pairCount = 0; // 对子数 + int kanCount = 0; // 坎数 + for (int count : cardCount.values()) { + if (count == 2) pairCount++; + if (count == 3) kanCount++; + } + + log.info("【修正决策】胡息:{}/15(缺{}), 门子:{}/7(缺{}), 对子:{}, 坎:{}", + currentHuxi, huxiDeficit, currentMenzi, menziDeficit, pairCount, kanCount); + + // 遍历所有选项,选择最优的 + DecisionResult bestResult = DecisionResult.skip("没有合适的操作"); + double bestScore = 0; + + for (int i = 0; i < tipList.size(); i++) { + TObject tip = (TObject) tipList.get(i).getObject(); + int type = tip.getInt("type"); + int id = tip.getInt("id"); + int card = tip.getInt("card"); + ITArray opcardArray = tip.getTArray("opcard"); + + List opcard = new ArrayList<>(); + if (opcardArray != null) { + for (int j = 0; j < opcardArray.size(); j++) { + opcard.add(opcardArray.getInt(j)); + } + } + + DecisionResult result; + double score; + + if (type == TYPE_PONG) { + result = evaluatePongCorrected(handCards, card, currentHuxi, currentMenzi, huxiDeficit, menziDeficit); + score = extractNumericScore(result.reason); + } else if (type == TYPE_CHOW) { + result = evaluateChowCorrected(handCards, card, opcard, currentHuxi, currentMenzi, huxiDeficit, menziDeficit, pairCount, kanCount); + score = extractNumericScore(result.reason); + } else { + continue; + } + + if (result.shouldAct && score > bestScore) { + bestScore = score; + bestResult = result; + bestResult.actionId = id; + } + } + + return bestResult; + } + + public static DecisionResult decide(List handCards, ITArray tipList) { + return decide(handCards, tipList, null, null, null, null, null); + } + + /** + * 评估碰牌 - 积极碰,保留胡息潜力 + */ + private static DecisionResult evaluatePongCorrected(List handCards, int card, + int currentHuxi, int currentMenzi, + int huxiDeficit, int menziDeficit) { + int countInHand = countCard(handCards, card); + if (countInHand < 2) { + return DecisionResult.skip("无对子"); + } + + boolean isBig = isBigCard(card); + int pengHuxi = isBig ? 3 : 1; + + // ========== 碰牌评分 ========== + double score = 60; // 基础分(碰牌积极) + + // 加分1:胡息贡献 + if (huxiDeficit > 0) { + score += pengHuxi * 20; + + if (currentHuxi + pengHuxi >= 15) { + score += 80; // 碰后达标 + } + } + + // 加分2:门子贡献 + if (menziDeficit > 0) { + score += 30; + + if (currentMenzi + 1 >= 7) { + score += 60; + } + } + + // 加分3:碰后可升级(摸第4张成跑/提) + if (countInHand == 3) { + score += 40; // 碰后还有1张在外,可能摸成提 + } + + // 【修正】碰牌不扣搭子分(因为对子本身就是搭子) + + // 碰牌阈值:>=55就碰 + if (score >= 55) { + return DecisionResult.act(-1, String.format("%.0f", score)); + } + + return DecisionResult.skip(String.format("%.0f", score)); + } + + /** + * 评估吃牌 - 严格限制,保护对子 + */ + private static DecisionResult evaluateChowCorrected(List handCards, int card, + List opcard, + int currentHuxi, int currentMenzi, + int huxiDeficit, int menziDeficit, + int pairCount, int kanCount) { + if (opcard == null || opcard.size() != 2) { + return DecisionResult.skip("数据错误"); + } + + int card1 = opcard.get(0); + int card2 = opcard.get(1); + boolean isBig = isBigCard(card); + + boolean isSpecialSequence = checkIsSpecialSequence(card1, card2, card); + int shunziHuxi = isSpecialSequence ? (isBig ? 6 : 3) : 0; + + // ========== 【核心修正1】检查吃的牌是否来自对子 ========== + int count1 = countCard(handCards, card1); + int count2 = countCard(handCards, card2); + + if (count1 >= 2 || count2 >= 2) { + // 吃的牌中有对子,坚决不吃!吃了就失去碰/偎机会 + return DecisionResult.skip("-200"); // 极高分,禁止吃 + } + + // ========== 【核心修正2】普通顺子0胡息,严格限制 ========== + if (!isSpecialSequence) { + // 只有在以下情况才吃普通顺子: + // 1. 胡息已够(>=15) + // 2. 门子严重不足(缺2个以上) + // 3. 手牌对子/坎很多(>=4个),不缺胡息来源 + + if (currentHuxi >= 15 && menziDeficit >= 2 && (pairCount + kanCount) >= 4) { + double score = 50; + + if (currentMenzi + 1 >= 7) { + score += 60; + } + + if (score >= 55) { + return DecisionResult.act(-1, String.format("%.0f", score)); + } + } + + return DecisionResult.skip("-100"); // 其他情况不吃普通顺子 + } + + // ========== 【核心修正3】二七十/一二三,可以吃 ========== + double score = 50; // 基础分 + + // 胡息贡献 + if (huxiDeficit > 0) { + score += shunziHuxi * 15; + + if (currentHuxi + shunziHuxi >= 15) { + score += 80; + } + } + + // 门子贡献 + if (menziDeficit > 0) { + score += 25; + + if (currentMenzi + 1 >= 7) { + score += 60; + } + } + + // 检查吃后手牌质量 + List tempHand = new ArrayList<>(handCards); + for (int c : opcard) { + removeCards(tempHand, c, 1); + } + int afterDazi = countGoodDazi(tempHand); + + if (afterDazi < 2) { + score -= 30; + } + + // 吃牌阈值:>=55才吃 + if (score >= 55) { + return DecisionResult.act(-1, String.format("%.0f", score)); + } + + return DecisionResult.skip(String.format("%.0f", score)); + } + + /** + * 检查是否是二七十或一二三组合 + */ + private static boolean checkIsSpecialSequence(int card1, int card2, int card3) { + if (!FangPaoFaSuanFa.sameSuit(card1, card2) || !FangPaoFaSuanFa.sameSuit(card1, card3)) { + return false; + } + + int v1 = FangPaoFaSuanFa.getCardValue(card1); + int v2 = FangPaoFaSuanFa.getCardValue(card2); + int v3 = FangPaoFaSuanFa.getCardValue(card3); + + List values = Arrays.asList(v1, v2, v3); + Collections.sort(values); + + if (values.get(0) == 1 && values.get(1) == 2 && values.get(2) == 3) { + return true; + } + + if (values.get(0) == 2 && values.get(1) == 7 && values.get(2) == 10) { + return true; + } + + return false; + } + + private static double extractNumericScore(String reason) { + try { + return Double.parseDouble(reason); + } catch (Exception e) { + return 0; + } + } + + private static int countCard(List handCards, int card) { + int count = 0; + for (int c : handCards) { + if (c == card) count++; + } + return count; + } + + private static void removeCards(List handCards, int card, int count) { + for (int i = 0; i < count; i++) { + handCards.remove(Integer.valueOf(card)); + } + } + + private static int countGoodDazi(List handCards) { + int goodDaziCount = 0; + + Map cardCount = new HashMap<>(); + for (int card : handCards) { + cardCount.put(card, cardCount.getOrDefault(card, 0) + 1); + } + for (int count : cardCount.values()) { + if (count == 2) goodDaziCount++; + } + + List sorted = new ArrayList<>(handCards); + Collections.sort(sorted); + + Set counted = new HashSet<>(); + for (int i = 0; i < sorted.size() - 1; i++) { + int card1 = sorted.get(i); + int card2 = sorted.get(i + 1); + + if (FangPaoFaSuanFa.sameSuit(card1, card2)) { + int v1 = FangPaoFaSuanFa.getCardValue(card1); + int v2 = FangPaoFaSuanFa.getCardValue(card2); + int diff = Math.abs(v1 - v2); + + String key = card1 + "_" + card2; + if (counted.contains(key)) continue; + + if (diff == 1) { + if ((v1 >= 3 && v1 <= 7) || (v2 >= 3 && v2 <= 7)) { + goodDaziCount++; + counted.add(key); + } + } else if (diff == 2) { + goodDaziCount++; + counted.add(key); + } + } + } + + return goodDaziCount; + } + + private static int getCardValue(int card) { + return FangPaoFaSuanFa.getCardValue(card); + } + + private static boolean isBigCard(int card) { + return FangPaoFaSuanFa.isBigCard(card); + } +} diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/FangPaoFaSuanFa.java b/robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/FangPaoFaSuanFa.java new file mode 100644 index 0000000..cafafde --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/FangPaoFaSuanFa.java @@ -0,0 +1,923 @@ +package taurus.util; + +import java.util.*; + +/** + * 湖南娄底放炮罚(跑胡子)智能出牌算法 - 终极优化版 + * + * 胡牌的两个必要条件(缺一不可): + * 1. 胡息达标 >= 15胡 + * 2. 牌型完整 = 正好7个门子(6组+1将) + * + * 编码规则: + * - 101-110: 小写一到十 + * - 201-210: 大写壹到拾 + * - 红牌: 个位是2、7、0 + */ +public class FangPaoFaSuanFa { + + public static final int SMALL_CARD = 1; + public static final int BIG_CARD = 2; + + // 胡息分值表 + private static final int TI_BIG_HUXI = 12; + private static final int TI_SMALL_HUXI = 9; + private static final int PAO_BIG_HUXI = 9; + private static final int PAO_SMALL_HUXI = 6; + private static final int WEI_BIG_HUXI = 6; + private static final int WEI_SMALL_HUXI = 3; + private static final int PENG_BIG_HUXI = 3; + private static final int PENG_SMALL_HUXI = 1; + private static final int KAN_BIG_HUXI = 6; + private static final int KAN_SMALL_HUXI = 3; + private static final int ERQISHI_BIG_HUXI = 6; + private static final int ERQISHI_SMALL_HUXI = 3; + private static final int SHUNZI_BIG_HUXI = 6; + private static final int SHUNZI_SMALL_HUXI = 3; + + + + // 权重系数 + private static final double WEIGHT_PATTERN = 6.0; + private static final double WEIGHT_HUXI = 5.0; + private static final double WEIGHT_MENZI = 6.0; + private static final double WEIGHT_ENTRY = 4.0; + private static final double WEIGHT_RED_BONUS = 1.5; + private static final double WEIGHT_STRATEGY = 15.0; // 战略最重要 + + private static final int MIN_HUXI_TO_WIN = 15; + private static final int MIN_MENZI_TO_WIN = 7; + + + + private static final int EARLY_GAME_HAND_COUNT = 14; + private static final int MID_GAME_HAND_COUNT = 10; + private static final int LATE_GAME_HAND_COUNT = 6; + + static boolean sameSuit(int card1, int card2) { + return isBigCard(card1) == isBigCard(card2); + } + + /** + * 主方法:选择最优出牌 + */ + public static int selectBestCard(List handCards, + List chiCards, + List pengCards, + List weiCards, + List paoCards, + List tiCards) { + if (handCards == null || handCards.isEmpty()) { + return -1; + } + + if (handCards.size() == 1) { + return 0; + } + + // 分析手牌状态(包含明牌信息) + CardAnalysis analysis = analyzeHandCardsWithMingTypes( + handCards, chiCards, pengCards, weiCards, paoCards, tiCards); + + // 检查是否已经听牌 + boolean isTing = checkIsTing(analysis, handCards); + + Map cardScores = new HashMap<>(); + for (int i = 0; i < handCards.size(); i++) { + int card = handCards.get(i); + double score = calculateCardScore(card, i, handCards, analysis, isTing); + cardScores.put(i, score); + } + + // 找出评分最低的牌(最应该打出) + int bestIndex = -1; + double minScore = Double.MAX_VALUE; + + for (Map.Entry entry : cardScores.entrySet()) { + if (entry.getValue() < minScore) { + minScore = entry.getValue(); + bestIndex = entry.getKey(); + } + } + + return bestIndex; + } + + /** + * 计算单张牌的综合评分 + */ + private static double calculateCardScore(int card, int index, + List handCards, + CardAnalysis analysis, + boolean isTing) { + double totalScore = 0; + + // 1. 牌型价值评估(基础分) + double patternScore = evaluatePatternValue(card, index, handCards, analysis); + totalScore += patternScore * WEIGHT_PATTERN; + + // 2. 胡息贡献评估 + double huxiScore = evaluateHuxiContribution(card, analysis); + totalScore += huxiScore * WEIGHT_HUXI; + + // 3. 门子数贡献评估 + double menziScore = evaluateMenziContribution(card, handCards, analysis); + totalScore += menziScore * WEIGHT_MENZI; + + // 4. 进张面评估 + double entryScore = evaluateEntryPotential(card, handCards, analysis); + totalScore += entryScore * WEIGHT_ENTRY; + + // 5. 红牌加分 + double redBonus = evaluateRedCardBonus(card, analysis); + totalScore += redBonus * WEIGHT_RED_BONUS; + + // 6. 战略调整(根据缺口动态调整) + double strategyAdjustment = evaluateStrategicPriority(card, handCards, analysis, isTing); + totalScore += strategyAdjustment * WEIGHT_STRATEGY; + + return totalScore; + } + + // ==================== 核心评估方法 ==================== + + /** + * 【终极优化】牌型价值评估 - 真人思维 + */ + private static double evaluatePatternValue(int card, int index, List handCards, + CardAnalysis analysis) { + double score = 0; + int cardValue = getCardValue(card); + int count = analysis.cardCountMap.getOrDefault(card, 0); + boolean isBig = isBigCard(card); + boolean isRed = isRedCard(card); + + // ========== 绝对优先级:四张相同(提)========== + if (count >= 4) { + score += 200; // 极高分,绝对不能拆 + return score; + } + + // ========== 极高优先级:三张相同(坎)========== + if (count == 3) { + score += 120; // 高分保护 + + // 二七十坎,价值翻倍 + if (isErQiShiComponent(card)) { + score += 40; + } + + // 红牌坎额外加分 + if (isRed) score += 15; + + return score; + } + + // ========== 高优先级:对子(可偎可碰,也可能是将牌)========== + if (count == 2) { + score += 60; // 提高对子基础分 + + // 大字对子价值更高(胡息多) + if (isBig) score += 20; else score += 12; + + // 二七十组件(极易形成胡息) + if (isErQiShiComponent(card)) { + score += 30; + if (hasCompleteErQiShi(handCards, card)) score += 50; + } + + // 绞牌潜力(大小字同值) + if (hasJiaoPartnerImproved(card, handCards, analysis)) score += 18; + + // 红牌对子 + if (isRed) score += 12; + + // 【关键】如果缺少将牌,对子价值大幅提升 + if (!analysis.hasJiang) { + score += 50; // 这可能是唯一的将牌候选 + } + + return score; + } + + // ========== 中等优先级:单张 ========== + if (count == 1) { + // 顺子潜力分析 + ShunziInfo shunziInfo = analyzeShunziPotential(card, handCards); + if (shunziInfo.hasPotential) { + score += shunziInfo.qualityScore; + + // 二七十潜力,额外高分 + if (shunziInfo.isErQiShi) score += 45; + } + + // 二七十组件单张 + if (isErQiShiComponent(card)) { + score += 25; + int partnerCount = countErQiShiPartners(handCards, cardValue); + if (partnerCount >= 2) score += 35; + else if (partnerCount == 1) score += 20; + } + + // 绞牌潜力 + if (hasJiaoPartnerImproved(card, handCards, analysis)) score += 20; + + // 搭子质量 + DaziInfo daziInfo = analyzeDaziQuality(card, handCards); + if (daziInfo.isGoodDazi) score += daziInfo.score; + + // 孤张惩罚(没有任何联系的单张) + if (!shunziInfo.hasPotential && !isErQiShiComponent(card) && + !hasJiaoPartnerImproved(card, handCards, analysis) && !daziInfo.isGoodDazi) { + score += 5; // 低分,适合打出 + } + + // 红牌单张也有价值 + if (isRed) score += 8; + } + + return score; + } + + /** + * 【终极优化】胡息贡献评估 - 精确计算 + */ + private static double evaluateHuxiContribution(int card, CardAnalysis analysis) { + double score = 0; + int count = analysis.cardCountMap.getOrDefault(card, 0); + boolean isBig = isBigCard(card); + + // 根据当前数量评估潜在胡息贡献 + if (count >= 4) { + // 提:直接获得大量胡息 + score += isBig ? TI_BIG_HUXI : TI_SMALL_HUXI; + } else if (count == 3) { + // 坎:已有胡息,摸成提还有提升空间 + double currentHuxi = isBig ? KAN_BIG_HUXI : KAN_SMALL_HUXI; + double potentialHuxi = isBig ? TI_BIG_HUXI : TI_SMALL_HUXI; + score += currentHuxi + (potentialHuxi - currentHuxi) * 0.6; + } else if (count == 2) { + // 对子:可以碰或偎 + double weiHuxi = isBig ? WEI_BIG_HUXI : WEI_SMALL_HUXI; + double pengHuxi = isBig ? PENG_BIG_HUXI : PENG_SMALL_HUXI; + score += (weiHuxi + pengHuxi) / 2.0 * 1.2; // 提高对子胡息评分 + } else if (count == 1) { + // 单张:看能否形成有胡息的组合 + + // 优先检查二七十潜力 + if (isErQiShiComponent(card)) { + int partnerCount = countErQiShiPartnersFromHand(analysis.cardCountMap, getCardValue(card)); + if (partnerCount >= 2) { + // 有两张伙伴,极有可能形成二七十 + double erqiShiHuxi = isBig ? ERQISHI_BIG_HUXI : ERQISHI_SMALL_HUXI; + score += erqiShiHuxi * 0.8; + } else if (partnerCount == 1) { + score += 8; // 有一张伙伴,仍有希望 + } + } + + // 普通顺子潜力(注意:普通顺子0胡息!) + ShunziInfo shunziInfo = analyzeShunziPotential(card, Collections.emptyList()); + if (shunziInfo.hasPotential && shunziInfo.isErQiShi) { + // 只有二七十顺子才有胡息 + double shunziHuxi = isBig ? SHUNZI_BIG_HUXI : SHUNZI_SMALL_HUXI; + score += shunziHuxi * (shunziInfo.entryCount / 6.0); + } + // 普通顺子不加分(0胡息) + } + + // 【关键策略】胡息不足时,大幅提升能增加胡息的牌的价值 + if (analysis.currentHuxi < MIN_HUXI_TO_WIN) { + int deficit = MIN_HUXI_TO_WIN - analysis.currentHuxi; + // 每缺1胡,权重增加50% + score *= (1 + deficit * 0.5); + } + + return score; + } + + /** + * 【终极优化】门子数贡献评估 - 精准计算 + */ + private static double evaluateMenziContribution(int card, List handCards, + CardAnalysis analysis) { + double score = 0; + int count = analysis.cardCountMap.getOrDefault(card, 0); + int currentMenzi = analysis.menziCount; + + // 评估该牌对门子的贡献能力 + if (count >= 3) { + // 坎:已经完成一门 + score += 35; + } else if (count == 2) { + // 对子:有潜力成门(碰/偎) + score += 18; + + // 如果有绞牌伙伴,潜力更大 + if (hasJiaoPartnerImproved(card, handCards, analysis)) { + score += 10; + } + } else if (count == 1) { + // 单张:看顺子潜力 + ShunziInfo shunziInfo = analyzeShunziPotential(card, handCards); + if (shunziInfo.hasPotential) { + score += shunziInfo.entryCount * 2; + + // 二七十顺子更可靠 + if (shunziInfo.isErQiShi) score += 15; + } + } + + // 【关键策略】门子不足时,大幅提升权重 + if (currentMenzi < MIN_MENZI_TO_WIN) { + int deficit = MIN_MENZI_TO_WIN - currentMenzi; + // 每缺1门,权重增加60% + score *= (1 + deficit * 0.6); + } + + return score; + } + + /** + * 【终极优化】进张面评估 - 计算有效进张数 + */ + private static double evaluateEntryPotential(int card, List handCards, + CardAnalysis analysis) { + double score = 0; + int cardValue = getCardValue(card); + int count = analysis.cardCountMap.getOrDefault(card, 0); + + if (count == 2) { + // 对子:可以摸成坎(还有2张在外) + score += 15; + if (isBigCard(card)) score += 6; // 大字更难摸,但胡息高 + } else if (count == 1) { + // 单张:计算所有可能的进张 + + // 顺子进张 + ShunziInfo shunziInfo = analyzeShunziPotential(card, handCards); + if (shunziInfo.hasPotential) { + score += shunziInfo.entryCount * 3; + + // 进张数多的特别好 + if (shunziInfo.entryCount >= 6) score += 12; + } + + // 搭子进张 + DaziInfo daziInfo = analyzeDaziQuality(card, handCards); + if (daziInfo.isGoodDazi) { + score += daziInfo.entryCount * 2.5; + } + + // 二七十进张(特别重要) + if (isErQiShiComponent(card)) { + int partnerCount = countErQiShiPartnersFromHand(analysis.cardCountMap, cardValue); + if (partnerCount >= 2) score += 20; + } + } else if (count == 3) { + // 坎:摸成提(还有1张在外) + score += 10; + } + + return score; + } + + /** + * 红牌加分评估 + */ + private static double evaluateRedCardBonus(int card, CardAnalysis analysis) { + if (!isRedCard(card)) return 0; + + double bonus = 8; + int redCount = analysis.redCardCount; + + // 红牌越多,每张的价值越高(胡息累积) + if (redCount >= 10) bonus += 18; + else if (redCount >= 7) bonus += 10; + + return bonus; + } + /** + * 【终极智能版】战略优先级评估 - 果断决策 + */ + private static double evaluateStrategicPriority(int card, List handCards, + CardAnalysis analysis, boolean isTing + ) { + double adjustment = 0; + + // 听牌后打安全牌 + if (isTing) { + int count = analysis.cardCountMap.getOrDefault(card, 0); + if (count == 1) adjustment -= 20; + return adjustment; + } + + // 计算缺口 + int huxiDeficit = Math.max(0, 15 - analysis.currentHuxi); + int menziDeficit = Math.max(0, 7 - analysis.menziCount); + + int count = analysis.cardCountMap.getOrDefault(card, 0); + + // ========== 【核心1】胡息够了,全力凑门子 ========== + if (huxiDeficit == 0 && menziDeficit > 0) { + if (count >= 3) { + adjustment += 150; // 坎:已完成一门,超高分 + } else if (count == 2) { + adjustment += 120; // 对子:可碰/偎成门 + + if (hasJiaoPartnerImproved(card, handCards, analysis)) { + adjustment += 20; // 有绞牌搭档 + } + } else if (count == 1) { + ShunziInfo info = analyzeShunziPotential(card, handCards); + if (info.hasPotential) { + adjustment += info.entryCount * 8 + 40; + if (info.isErQiShi) adjustment += 30; + } else { + adjustment -= 50; // 孤张,鼓励打出 + } + } + + return adjustment; + } + + // ========== 【核心2】门子够了,全力凑胡息 ========== + if (menziDeficit == 0 && huxiDeficit > 0) { + if (count >= 3) { + adjustment += 120; // 坎:有胡息 + } else if (count == 2) { + adjustment += 100; // 对子:可碰/偎得胡息 + + if (isErQiShiComponent(card)) { + adjustment += 25; + } + } else if (count == 1) { + if (isErQiShiComponent(card)) { + adjustment += 80; + int partners = countErQiShiPartnersFromHand(analysis.cardCountMap, getCardValue(card)); + if (partners >= 2) adjustment += 40; + } + if (isRedCard(card)) adjustment += 25; + } + + return adjustment; + } + + // ========== 【核心3】双都不够,哪个缺口大补哪个 ========== + if (huxiDeficit > 0 && menziDeficit > 0) { + if (huxiDeficit >= menziDeficit) { + // 胡息缺口更大,优先补胡息 + if (count >= 3) { + adjustment += 100; + } else if (count == 2) { + adjustment += 80; + + if (isErQiShiComponent(card)) { + adjustment += 20; + } + } else if (count == 1) { + if (isErQiShiComponent(card)) { + adjustment += 60; + int partners = countErQiShiPartnersFromHand(analysis.cardCountMap, getCardValue(card)); + if (partners >= 2) adjustment += 30; + } + if (isRedCard(card)) adjustment += 15; + } + } else { + // 门子缺口更大,优先补门子 + if (count >= 3) { + adjustment += 110; + } else if (count == 2) { + adjustment += 90; + } else if (count == 1) { + ShunziInfo info = analyzeShunziPotential(card, handCards); + if (info.hasPotential) { + adjustment += info.entryCount * 6 + 30; + if (info.isErQiShi) adjustment += 25; + } else { + adjustment -= 40; + } + } + } + + return adjustment; + } + + // ========== 【核心4】双都够了,保守打法 ========== + if (huxiDeficit == 0 && menziDeficit == 0) { + adjustment += 30; + } + + return adjustment; + } + + + + + + + // ==================== 辅助分析方法 ==================== + + /** + * 检查是否听牌 + */ + private static boolean checkIsTing(CardAnalysis analysis, List handCards) { + // 条件1: 胡息达标 + if (analysis.currentHuxi < MIN_HUXI_TO_WIN) return false; + + // 条件2: 门子数达到6个(再进一门就够7个) + if (analysis.menziCount < 6) return false; + + // 条件3: 有将牌或能形成将牌 + if (!analysis.hasJiang && analysis.pairs.isEmpty()) return false; + + return true; + } + + private static class ShunziInfo { + boolean hasPotential = false; + int qualityScore = 0; + boolean isErQiShi = false; + int entryCount = 0; + public ShunziInfo() {} + } + + /** + * 【优化】分析顺子潜力 + */ + private static ShunziInfo analyzeShunziPotential(int card, List handCards) { + ShunziInfo info = new ShunziInfo(); + int value = getCardValue(card); + int type = getCardType(card); + + if (value <= 0 || value > 10) return info; + + boolean hasPrev1 = hasCardOfType(type, value - 1, handCards); + boolean hasNext1 = hasCardOfType(type, value + 1, handCards); + boolean hasPrev2 = hasCardOfType(type, value - 2, handCards); + boolean hasNext2 = hasCardOfType(type, value + 2, handCards); + + // 两面搭(如45_,_56) + if (hasPrev1 && hasNext1) { + info.hasPotential = true; + info.qualityScore = 30; + info.entryCount = 6; + + // 检查是否是二七十组件 + if ((value == 2 && hasCardOfType(type, 7, handCards)) || + (value == 7 && hasCardOfType(type, 2, handCards)) || + (value == 10 && hasCardOfType(type, 7, handCards))) { + info.isErQiShi = true; + } + return info; + } + + // 卡张搭(如4_6) + if (hasPrev2 && hasNext2) { + info.hasPotential = true; + info.qualityScore = 18; + info.entryCount = 4; + return info; + } + + // 边张搭(如12_, _89) + if ((value == 1 && hasNext1 && hasNext2) || + (value == 2 && hasPrev1 && hasNext1) || + (value == 9 && hasPrev1 && hasNext1) || + (value == 10 && hasPrev1 && hasPrev2)) { + info.hasPotential = true; + info.qualityScore = 12; + info.entryCount = 4; + return info; + } + + // 单边联系 + if (hasPrev1 || hasNext1) { + info.hasPotential = true; + info.qualityScore = 10; + info.entryCount = 4; + return info; + } + + return info; + } + + private static class DaziInfo { + boolean isGoodDazi = false; + int score = 0; + int entryCount = 0; + public DaziInfo() {} + } + + /** + * 【优化】分析搭子质量 + */ + private static DaziInfo analyzeDaziQuality(int card, List handCards) { + DaziInfo info = new DaziInfo(); + int value = getCardValue(card); + int type = getCardType(card); + + for (int offset = -2; offset <= 2; offset++) { + if (offset == 0) continue; + + int targetValue = value + offset; + if (targetValue < 1 || targetValue > 10) continue; + + if (hasCardOfType(type, targetValue, handCards)) { + int diff = Math.abs(offset); + + if (diff == 1) { + // 连张搭(如45) + if ((value >= 3 && value <= 7) || (targetValue >= 3 && targetValue <= 7)) { + // 中张连张,优质搭子 + info.isGoodDazi = true; + info.score = 22; + info.entryCount = 6; + } else { + info.isGoodDazi = true; + info.score = 15; + info.entryCount = 4; + } + } else if (diff == 2) { + // 卡张搭(如46) + info.isGoodDazi = true; + info.score = 12; + info.entryCount = 4; + } + } + } + + return info; + } + + /** + * 检查是否有绞牌伙伴(大小字同值) + */ + private static boolean hasJiaoPartnerImproved(int card, List handCards, CardAnalysis analysis) { + int value = getCardValue(card); + int type = getCardType(card); + + // 检查对子中的绞牌 + for (int pairCard : analysis.pairs) { + int pairType = getCardType(pairCard); + int pairValue = getCardValue(pairCard); + if (pairType != type && pairValue == value) return true; + } + + // 检查单张中的绞牌 + int count = analysis.cardCountMap.getOrDefault(card, 0); + if (count >= 1) { + int otherTypeCard = (type == SMALL_CARD) ? + getCardCode(BIG_CARD, value) : getCardCode(SMALL_CARD, value); + if (handCards.contains(otherTypeCard)) { + return true; + } + } + + return false; + } + + /** + * 检查是否有完整的二七十组合 + */ + private static boolean hasCompleteErQiShi(List handCards, int card) { + int value = getCardValue(card); + int type = getCardType(card); + + if (value == 2) { + return hasCardOfType(type, 7, handCards) && hasCardOfType(type, 10, handCards); + } else if (value == 7) { + return hasCardOfType(type, 2, handCards) && hasCardOfType(type, 10, handCards); + } else if (value == 10) { + return hasCardOfType(type, 2, handCards) && hasCardOfType(type, 7, handCards); + } + return false; + } + + private static int countErQiShiPartners(List handCards, int currentValue) { + int count = 0; + for (int card : handCards) { + int v = getCardValue(card); + if (v == 2 || v == 7 || v == 10) { + if (v != currentValue) count++; + } + } + return count; + } + + private static int countErQiShiPartnersFromHand(Map cardCountMap, int currentValue) { + int count = 0; + for (Map.Entry entry : cardCountMap.entrySet()) { + int v = getCardValue(entry.getKey()); + if (v == 2 || v == 7 || v == 10) { + if (v != currentValue) count += entry.getValue(); + } + } + return count; + } + + /** + * 手牌分析结果类 + */ + private static class CardAnalysis { + Map cardCountMap = new HashMap<>(); + List pairs = new ArrayList<>(); + List kans = new ArrayList<>(); + List tis = new ArrayList<>(); + Set erQiShiComponents = new HashSet<>(); + int currentHuxi = 0; + int menziCount = 0; + int redCardCount = 0; + boolean hasJiang = false; // 是否有将牌 + + public CardAnalysis(List handCards, List chiCards, + List pengCards, List weiCards, + List paoCards, List tiCards) { + // 统计暗牌 + for (int card : handCards) { + cardCountMap.put(card, cardCountMap.getOrDefault(card, 0) + 1); + } + + // 识别暗牌的对子、坎、提 + for (Map.Entry entry : cardCountMap.entrySet()) { + int count = entry.getValue(); + int card = entry.getKey(); + boolean isBig = isBigCard(card); + + if (count >= 4) { + tis.add(card); + menziCount++; + currentHuxi += isBig ? TI_BIG_HUXI : TI_SMALL_HUXI; + } else if (count == 3) { + kans.add(card); + menziCount++; + currentHuxi += isBig ? KAN_BIG_HUXI : KAN_SMALL_HUXI; + } else if (count == 2) { + pairs.add(card); + } + + if (isErQiShiComponent(card)) { + erQiShiComponents.add(card); + } + + if (isRedCard(card)) { + redCardCount += count; + } + } + + // 判断是否有将牌 + hasJiang = !pairs.isEmpty(); + + // 统计明牌的胡息和门子 + if (chiCards != null && !chiCards.isEmpty()) { + int chiMenzi = chiCards.size() / 3; + menziCount += chiMenzi; + // 吃的顺子,需要检查是否是二七十 + for (int i = 0; i < chiCards.size(); i += 3) { + int c1 = chiCards.get(i); + int c2 = chiCards.get(i + 1); + int c3 = chiCards.get(i + 2); + if (checkIsSpecialSequence(c1, c2, c3)) { + boolean isBig = isBigCard(c1); + currentHuxi += isBig ? SHUNZI_BIG_HUXI : SHUNZI_SMALL_HUXI; + } + // 普通顺子0胡息,不加 + } + } + + if (pengCards != null && !pengCards.isEmpty()) { + Set pengSet = new HashSet<>(pengCards); + for (int card : pengSet) { + menziCount++; + boolean isBig = isBigCard(card); + currentHuxi += isBig ? PENG_BIG_HUXI : PENG_SMALL_HUXI; + } + } + + if (weiCards != null && !weiCards.isEmpty()) { + Set weiSet = new HashSet<>(weiCards); + for (int card : weiSet) { + menziCount++; + boolean isBig = isBigCard(card); + currentHuxi += isBig ? WEI_BIG_HUXI : WEI_SMALL_HUXI; + } + } + + if (paoCards != null && !paoCards.isEmpty()) { + Set paoSet = new HashSet<>(paoCards); + for (int card : paoSet) { + menziCount++; + boolean isBig = isBigCard(card); + currentHuxi += isBig ? PAO_BIG_HUXI : PAO_SMALL_HUXI; + } + } + + if (tiCards != null && !tiCards.isEmpty()) { + Set tiSet = new HashSet<>(tiCards); + for (int card : tiSet) { + menziCount++; + boolean isBig = isBigCard(card); + currentHuxi += isBig ? TI_BIG_HUXI : TI_SMALL_HUXI; + } + } + } + + /** + * 检查是否是二七十或一二三组合 + */ + private boolean checkIsSpecialSequence(int card1, int card2, int card3) { + if (!sameSuit(card1, card2) || !sameSuit(card1, card3)) { + return false; + } + + int v1 = getCardValue(card1); + int v2 = getCardValue(card2); + int v3 = getCardValue(card3); + + List values = Arrays.asList(v1, v2, v3); + Collections.sort(values); + + if (values.get(0) == 1 && values.get(1) == 2 && values.get(2) == 3) { + return true; + } + + if (values.get(0) == 2 && values.get(1) == 7 && values.get(2) == 10) { + return true; + } + + return false; + } + + private boolean sameSuit(int card1, int card2) { + return isBigCard(card1) == isBigCard(card2); + } + } + + private static CardAnalysis analyzeHandCardsWithMingTypes( + List handCards, List chiCards, + List pengCards, List weiCards, + List paoCards, List tiCards) { + return new CardAnalysis(handCards, chiCards, pengCards, weiCards, paoCards, tiCards); + } + + // ==================== 工具方法 ==================== + + public static int getCardValue(int card) { + return card % 10; + } + + public static int getCardType(int card) { + int hundredDigit = card / 100; + if (hundredDigit == 1) return SMALL_CARD; + else if (hundredDigit == 2) return BIG_CARD; + return 0; + } + + public static boolean isBigCard(int card) { + return card >= 200 && card <= 210; + } + + public static boolean isRedCard(int card) { + int value = getCardValue(card); + return value == 2 || value == 7 || value == 0; + } + + public static boolean isErQiShiComponent(int card) { + int value = getCardValue(card); + return value == 2 || value == 7 || value == 0; + } + + public static boolean hasCardOfType(int type, int value, List handCards) { + if (value < 1 || value > 10) return false; + int targetCard = getCardCode(type, value); + return handCards.contains(targetCard); + } + + public static int getCardCode(int type, int value) { + if (type == SMALL_CARD) return 100 + value; + else if (type == BIG_CARD) return 200 + value; + return 0; + } + + public static int calculateTotalHuxi(List handCards, + List chiCards, + List pengCards, + List weiCards, + List paoCards, + List tiCards) { + CardAnalysis analysis = new CardAnalysis(handCards, chiCards, pengCards, weiCards, paoCards, tiCards); + return analysis.currentHuxi; + } + + public static int calculateTotalMenzi(List handCards, + List chiCards, + List pengCards, + List weiCards, + List paoCards, + List tiCards) { + CardAnalysis analysis = new CardAnalysis(handCards, chiCards, pengCards, weiCards, paoCards, tiCards); + return analysis.menziCount; + } + + +} diff --git a/robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/ROBOTEventType.java b/robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/ROBOTEventType.java new file mode 100644 index 0000000..d242292 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/main/java/taurus/util/ROBOTEventType.java @@ -0,0 +1,13 @@ +package taurus.util; + +public class ROBOTEventType { + /** + * 机器人状态 + */ + public static final int ROBOT_INTOROOM_READY = 1;//等待状态 + public static final int ROBOT_INTOROOM_WORKING = 2;//工作状态 + public static final int ROBOT_UNUSE = 0;//未使用状态 + + + +} diff --git a/robots/zhipai/robot_zp_fpf/src/test/java/robot_zp_fpf/Main.java b/robots/zhipai/robot_zp_fpf/src/test/java/robot_zp_fpf/Main.java new file mode 100644 index 0000000..6368847 --- /dev/null +++ b/robots/zhipai/robot_zp_fpf/src/test/java/robot_zp_fpf/Main.java @@ -0,0 +1,15 @@ +package robot_zp_fpf; + +import com.taurus.permanent.TPServer; + +public class Main { + public static void main(String[] args) { + System.out.println("启动放炮罚机器人服务器..."); + System.out.println("服务器将监听端口8917用于游戏协议"); + + //启动机器人服务 + TPServer.me().start(); + + System.out.println("放炮罚机器人服务器已启动"); + } +} \ No newline at end of file