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