From 2c6fd5e0a0a20105ad4e8e2c4732eab50d669710 Mon Sep 17 00:00:00 2001 From: zhouwei <849588297@qq.com> Date: Fri, 6 Feb 2026 21:57:36 +0800 Subject: [PATCH] =?UTF-8?q?init=20=20=E8=B7=91=E5=BE=97=E5=BF=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../puke/robot_pk_pdk/config/game-config.xml | 10 + .../puke/robot_pk_pdk/config/log4j.properties | 20 + .../puke/robot_pk_pdk/config/taurus-core.xml | 62 + .../robot_pk_pdk/config/taurus-permanent.xml | 75 + robots/puke/robot_pk_pdk/pom.xml | 47 + .../src/main/java/robot/mj/Config.java | 138 + .../src/main/java/robot/mj/EXActionEvent.java | 9 + .../main/java/robot/mj/EXGameController.java | 413 ++ .../src/main/java/robot/mj/EXMainServer.java | 164 + .../src/main/java/robot/mj/EXPlayer.java | 31 + .../src/main/java/robot/mj/EXRoom.java | 53 + .../java/robot/mj/RobotConnectionManager.java | 519 +++ .../src/main/java/robot/mj/RoomCreator.java | 326 ++ .../robot/mj/business/AccountBusiness.java | 380 ++ .../java/robot/mj/handler/HuNanPaoDeKuai.java | 307 ++ .../main/java/robot/mj/info/RobotUser.java | 164 + .../src/main/java/taurus/util/CardObj.java | 37 + .../src/main/java/taurus/util/CardUtil.java | 409 ++ .../main/java/taurus/util/ROBOTEventType.java | 13 + .../src/main/java/taurus/util/test.java | 3336 +++++++++++++++++ .../test/java/robot_mj_hongzhong/Main.java | 15 + 21 files changed, 6528 insertions(+) create mode 100644 robots/puke/robot_pk_pdk/config/game-config.xml create mode 100644 robots/puke/robot_pk_pdk/config/log4j.properties create mode 100644 robots/puke/robot_pk_pdk/config/taurus-core.xml create mode 100644 robots/puke/robot_pk_pdk/config/taurus-permanent.xml create mode 100644 robots/puke/robot_pk_pdk/pom.xml create mode 100644 robots/puke/robot_pk_pdk/src/main/java/robot/mj/Config.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXActionEvent.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXGameController.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXMainServer.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXPlayer.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXRoom.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/robot/mj/RobotConnectionManager.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/robot/mj/RoomCreator.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/robot/mj/business/AccountBusiness.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/robot/mj/handler/HuNanPaoDeKuai.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/robot/mj/info/RobotUser.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/taurus/util/CardObj.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/taurus/util/CardUtil.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/taurus/util/ROBOTEventType.java create mode 100644 robots/puke/robot_pk_pdk/src/main/java/taurus/util/test.java create mode 100644 robots/puke/robot_pk_pdk/src/test/java/robot_mj_hongzhong/Main.java diff --git a/robots/puke/robot_pk_pdk/config/game-config.xml b/robots/puke/robot_pk_pdk/config/game-config.xml new file mode 100644 index 0000000..2111373 --- /dev/null +++ b/robots/puke/robot_pk_pdk/config/game-config.xml @@ -0,0 +1,10 @@ + + + + 8.134.76.43 + 8.134.76.43 + 8766 + 8766 + 66 + true + \ No newline at end of file diff --git a/robots/puke/robot_pk_pdk/config/log4j.properties b/robots/puke/robot_pk_pdk/config/log4j.properties new file mode 100644 index 0000000..6786dba --- /dev/null +++ b/robots/puke/robot_pk_pdk/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/puke/robot_pk_pdk/config/taurus-core.xml b/robots/puke/robot_pk_pdk/config/taurus-core.xml new file mode 100644 index 0000000..d7b7811 --- /dev/null +++ b/robots/puke/robot_pk_pdk/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/puke/robot_pk_pdk/config/taurus-permanent.xml b/robots/puke/robot_pk_pdk/config/taurus-permanent.xml new file mode 100644 index 0000000..3728386 --- /dev/null +++ b/robots/puke/robot_pk_pdk/config/taurus-permanent.xml @@ -0,0 +1,75 @@ + + + 1 + + 512 + + Heap + + Heap + + 524288 + + 1024 + + 32768 + + 160 + + + 1 + 2 + 1 + + + true + + 300 + + + + + + + + + + 1.2.3.4 + + + 127.0.0.1 + + 10000 + + + + false +
0.0.0.0
+ 80 +
+ + + + robot - test + robot.mj.EXMainServer + + + + + Sys + 2 + 8 + 60000 + 20000 + + + + + Ext + 2 + 8 + 60000 + 20000 + + +
\ No newline at end of file diff --git a/robots/puke/robot_pk_pdk/pom.xml b/robots/puke/robot_pk_pdk/pom.xml new file mode 100644 index 0000000..5821a88 --- /dev/null +++ b/robots/puke/robot_pk_pdk/pom.xml @@ -0,0 +1,47 @@ + + 4.0.0 + + com.robot + robot_pk_paodekuai + 1.0.0 + jar + + robot_mj_changsha + http://maven.apache.org + + + UTF-8 + + + + + com.robot + robot_common + 1.0.0 + + + + + + + + + robot + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + + 1.8 + 1.8 + UTF-8 + + + + + + + diff --git a/robots/puke/robot_pk_pdk/src/main/java/robot/mj/Config.java b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/Config.java new file mode 100644 index 0000000..1b30f78 --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/Config.java @@ -0,0 +1,138 @@ +package robot.mj; + +public class Config { + public static final int FENGDING_SCORE = 28; + public static final int XIPAI_SCORE = 10; + + public static final int ANCHOU_SCORE = 10; + + public static final String ROOM_CONFIG_ZIMO = "zimo"; + public static final String ROOM_CONFIG_ZHUANGXIAN = "zhuangxian"; + public static final String ROOM_CONFIG_NIAO = "niao"; + public static final String ROOM_CONFIG_NIAO_TYPE = "niao_type"; + public static final String ROOM_CONFIG_PIAO_NIAO = "piao_niao"; //0:不嫖 1:自由票 2:固定票 + public static final String ROOM_CONFIG_PIAO_NIAO_AUTO = "auto_piao"; //piao fen + public static final String ROOM_CONFIG_PIAO_NIAO_OPT = "piao_niao_opt"; //piao fen + public static final String ROOM_CONFIG_ZT_LIULIUSHUN = "zhongtuliuliushun"; + public static final String ROOM_CONFIG_ZT_DASIXI = "zhongtusixi"; + public static final String ROOM_CONFIG_QS_JIEJIEGAO = "jiejiegao"; + public static final String ROOM_CONFIG_QS_SANTONG = "santong"; + public static final String ROOM_CONFIG_QS_YIZHIHUA = "yizhihua"; + public static final String ROOM_CONFIG_QUEYIMEN = "queyimen"; + public static final String ROOM_CONFIG_FENGDING = "fengding"; + public static final String ROOM_CONFIG_FENGDING_SCORE = "fengding_score"; + public static final String ROOM_CONFIG_XIPAI = "xi_pai"; + public static final String ROOM_CONFIG_XIPAI_SCORE = "xi_pai_score"; + + public static final String ROOM_CONFIG_ANCHOU_SCORE = "an_chou_score"; + + public static final String ROOM_CONFIG_DIFEN_SCORE = "difen_score"; + public static final String ROOM_CONFIG_NIAOFEN_SCORE = "niaofen_score"; + public static final String ROOM_CONFIG_NIAOFEN_OPT = "niaofen_opt"; //0中鸟加分,//1中鸟加倍 + public static final String ROOM_CONFIG_KAI_GONG = "kai_gong"; //0:开杠2张,1:开杠四张 + + + public static final int NIAO_TYPE_ADD = 0; + + public static final int NIAO_TYPE_DOUBLE = 1; + + public static final int NIAO_TYPE_CS2NIAO = 2; + + + public static final String ROOM_CONFIG_QS_JTYN = "two_pair"; + + public static final String ROOM_CONFIG_NO_JIANG = "no_jiang"; + + public static final String ROOM_CONFIG_NATIVE_HU = "native_hu"; + + + public static final String ROOM_CONFIG_FOUR_WIN = "four_win"; + + public static final String SETTLE_XIAO_DIAN_PAO = "xiao_dian_pao"; + public static final String SETTLE_XIAO_JIE_PAO = "xiao_jie_pao"; + public static final String SETTLE_XIAO_ZIMO = "xiao_zimo"; + public static final String SETTLE_DA_DIAN_PAO = "da_dian_pao"; + public static final String SETTLE_DA_JIE_PAO = "da_jie_pao"; + public static final String SETTLE_DA_ZIMO = "da_zimo"; + + + public static final String GAME_EVT_PLAYER_DEAL = "811"; + + public static final String GAME_DIS_CARD = "611"; + + public static final String GAME_EVT_DISCARD = "812"; + + public static final String GAME_EVT_DISCARD_TIP = "813"; + + public static final String GAME_EVT_FZTIPS = "814"; + + public static final String GAME_ACTION = "612"; + + public static final String GAME_EVT_ACTION = "815"; + + public static final String GAME_EVT_HU = "816"; + + public static final String GAME_EVT_RESULT1 = "817"; + + public static final String GAME_EVT_RESULT2 = "818"; + + public static final String GAME_EVT_DRAW = "819"; + + public static final String GAME_EVT_CHANGE_ACTIVE_PLAYER = "820"; + + public static final String GAME_EVT_NIAO = "821"; + + public static final String GAME_EVT_QSTIP = "822"; + + public static final String GAME_EVT_QSWIN = "823"; + + public static final String GAME_EVT_OPENKONG = "824"; + + public static final String GAME_EVT_HAIDITIP = "825"; + + public static final String GAME_EVT_PIAONIAO_TIP = "833"; + + public static final String GAME_EVT_PIAONIAO = "834"; + + public static final String GAME_EVT_TING_TIP = "835"; + + public static final String GAME_EVT_TING = "836"; + + public static final String CREATE_ROOM_ROBOT = "create_room_for_robot"; + public static final String INIT_CONNECTION = "init_connection"; + + /** + * 加入房间 - robot_mgr to robot_mj_cs 的内部协议号 + */ + public static final String JOIN_ROOM = "2002"; + + /** + * 发送准备 - robot_mgr to robot_mj_cs 的内部协议号 + */ + public static final String GAME_READY = "2003"; + + /** + * 退出房间 - robot_mgr to robot_mj_cs 的内部协议号 + */ + public static final String EXIT_ROOM = "2005"; + + /** + * 手动重连 - robot_mgr to robot_mj_cs 的内部协议号 + */ + public static final String RECONNECT = "2006"; + + /** + * 发送准备 - robot_mj_cs to game_mj_cs 的协议号 + */ + public static final String GAME_READY_CS = "1003"; + + /** + * 加入房间 - robot_mgr to game_mj_cs 的内部协议号 + */ + public static final String JOIN_ROOM_CS = "1002"; + + /** + * 退出房间 - robot_mgr to game_mj_cs 的内部协议号 + */ + public static final String EXIT_ROOM_CS = "1005"; +} \ No newline at end of file diff --git a/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXActionEvent.java b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXActionEvent.java new file mode 100644 index 0000000..556d1b3 --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXActionEvent.java @@ -0,0 +1,9 @@ +package robot.mj; + +public class EXActionEvent { + + public static final String EVENT_ACTION = "action"; + + public static final String EVENT_DISCARD = "discard"; + +} diff --git a/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXGameController.java b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXGameController.java new file mode 100644 index 0000000..8fb3187 --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXGameController.java @@ -0,0 +1,413 @@ +package robot.mj; + +import com.robot.GameController; +import com.robot.GameInterceptor; +import com.robot.MainServer; +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.mj.info.RobotUser; +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.ConcurrentHashMap; + +/** + * 跑得快游戏控制器 - 处理游戏协议 + */ +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<>(); + + public EXGameController() { + super(); + log.info("跑得快游戏控制器已初始化"); + } + + /** + * 处理客户端初始化/认证协议 + */ + @ActionKey(value = Config.INIT_CONNECTION, validate = GameInterceptor.NOT_PLAYER) + public void handleInitialization(Session session, ITObject params, int gid) { + try { + log.info("收到客户端初始化请求,Session: {}, GID: {}, 参数: {}", session, gid, params); + + String type = params.getString("type"); + String client = params.getString("client"); + log.info("客户端类型: {}, 客户端标识: {}", type, client); + + //验证参数 + if ("robot_mj_cs".equals(type) && "robot_mgr".equals(client)) { + //返回成功响应 + ITObject response = TObject.newInstance(); + response.putString("status", "success"); + response.putString("message", "初始化成功"); + + log.info("客户端初始化成功,发送响应"); + MainServer.instance.sendResponse(gid, 0, response, session); + } else { + log.warn("客户端初始化失败,参数验证不通过: type={}, client={}", type, client); + + ITObject response = TObject.newInstance(); + response.putString("status", "failed"); + response.putString("message", "参数验证失败"); + MainServer.instance.sendResponse(gid, 1, response, session); + } + } catch (Exception e) { + log.error("处理初始化请求时发生错误", e); + + ITObject response = TObject.newInstance(); + response.putString("status", "error"); + response.putString("message", "服务器内部错误: " + e.getMessage()); + + MainServer.instance.sendResponse(gid, 1, response, session); + } + } + + /** + * 接收来自robot_mgr的创建房间协议 + */ + @ActionKey(value = Config.CREATE_ROOM_ROBOT, validate = GameInterceptor.NOT_PLAYER) + public void createRoomForRobot(Session session, ITObject params, int gid) { + try { + int reqGroupId = params.getInt("groupId"); + int wanfaId = params.getInt("wanfaId"); + int robotId = params.getInt("robotId"); + + //使用统一的房间创建器在Redis中创建房间 + RoomCreator.RoomInfo roomInfo = RoomCreator.createRoomInRedis(reqGroupId, wanfaId, robotId); + + if (roomInfo != null) { + //返回房间信息给robot_mgr + ITObject response = TObject.newInstance(); + response.putString("roomKey", roomInfo.getRoomId()); + response.putInt("groupId", roomInfo.getGroupId()); + response.putInt("wanfaId", roomInfo.getWanfaId()); + response.putInt("robotId", robotId); + response.putString("server_ip", roomInfo.getServerIp()); + response.putInt("server_port", roomInfo.getServerPort()); + + log.info("成功创建房间,房间ID: {}", roomInfo.getRoomId()); + //使用Taurus框架方法发送响应 + MainServer.instance.sendResponse(gid, 0, response, session); + } else { + log.error("创建房间失败,群组ID: {}, 玩法ID: {}", reqGroupId, wanfaId); + } + } catch (Exception e) { + log.error("处理创建房间请求时发生错误", e); + } + } + + /** + * 接收来自robot_mgr的加入房间协议 + */ + @ActionKey(value = Config.JOIN_ROOM, validate = GameInterceptor.NOT_PLAYER) + public void joinRoom(Session session, ITObject params, int gid) { + try { + String connecId = params.getString("connecId"); + TaurusClient client = getCsMjGameServerConnection(connecId); + System.out.println("接收来自robot_mgr的加入房间协议 connecId: = "+connecId); + System.out.println("接收来自robot_mgr的加入房间协议 client: = "+client); + if (client == null) { + ITObject errorResponse = TObject.newInstance(); + errorResponse.putString("status", "failed"); + errorResponse.putString("message", "无法获取游戏服务器连接"); + MainServer.instance.sendResponse(gid, 1, errorResponse, session); + return; + } + + //设置session和token + String sessionToken = params.getString("session"); + String robotSession = null; + String token = null; + if (sessionToken != null && sessionToken.contains(",")) { + String[] sessionParts = sessionToken.split(","); + if (sessionParts.length >= 2) { + robotSession = sessionParts[0]; + token = sessionParts[1]; + robotConnectionManager.setSessionAndToken(robotSession, token, connecId); + } + } + + Integer groupId = params.getInt("groupId"); + String roomId = params.getString("roomId"); + String robotId = params.getString("robotId"); + + GroupRoomBusiness.joinRoom(groupId, roomId, robotSession, null); + + Thread.sleep(5000); + + params.del("groupId"); + params.del("roomId"); + params.del("connecId"); + //发送加入房间请求到game_mj_cs + client.send(Config.JOIN_ROOM_CS, params, response -> { + System.out.println("joinRoomController: " + response); + RobotUser roomInfo = new RobotUser(); + roomInfo.setCurrentRoomId(Integer.parseInt(roomId)); + roomInfo.setConnecId(connecId); + roomInfo.setUserId(0); + //机器人房间映射关系 + robotRoomMapping.put(robotId, roomInfo); + }); + + ITObject paramsReq = TObject.newInstance(); + paramsReq.putString("status", "success"); + MainServer.instance.sendResponse(gid, 0, paramsReq, session); + + } catch (Exception e) { + log.error("处理加入房间请求时发生错误", e); + ITObject errorResponse = TObject.newInstance(); + errorResponse.putString("status", "error"); + errorResponse.putString("message", "处理加入房间请求时发生错误: " + e.getMessage()); + MainServer.instance.sendResponse(gid, 1, errorResponse, session); + } + } + + /** + * 接收来自robot_mgr的机器人准备协议 + */ + @ActionKey(value = Config.GAME_READY, validate = GameInterceptor.NOT_PLAYER) + public void robotReadyRoom(Session session, ITObject params, int gid) { + try { + String connecId = params.getString("connecId"); + TaurusClient client = getCsMjGameServerConnection(connecId); + System.out.println("接收来自robot_mgr的机器人准备协议 connecId: = "+connecId); + System.out.println("接收来自robot_mgr的机器人准备协议 client: = "+client); + if (client == null) { + ITObject errorResponse = TObject.newInstance(); + errorResponse.putString("status", "failed"); + errorResponse.putString("message", "无法获取游戏服务器连接"); + MainServer.instance.sendResponse(gid, 1, errorResponse, session); + return; + } + + params.del("connecId"); + Thread.sleep(1000); + //发送准备请求到game_mj_cs + client.send(Config.GAME_READY_CS, params, response -> { + System.out.println("robotReadyRoom: " + response); + }); + + ITObject paramsReq = TObject.newInstance(); + paramsReq.putString("status", "success"); + MainServer.instance.sendResponse(gid, 0, paramsReq, session); + + } catch (Exception e) { + log.error("处理机器人准备请求时发生错误", e); + ITObject errorResponse = TObject.newInstance(); + errorResponse.putString("status", "error"); + errorResponse.putString("message", "服务器内部错误: " + e.getMessage()); + MainServer.instance.sendResponse(gid, 1, errorResponse, session); + } + } + + /** + * 接收来自web_group的加入房间协议 + */ + @ActionKey(value = "225", 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"); + + //防止玩家操作同一房间 导致其他机器人加入 + List robotUsers = getRobotUsersByRoomId(Integer.parseInt(roomId)); + if (!robotUsers.isEmpty()) { + synchronized (robotUsers) { + RobotUser robotUser = robotUsers.get(0); + if (robotId != Integer.parseInt(robotUser.getRobotId())) { + System.err.println("房间{"+ roomId +"}中已有机器人{"+robotUser.getRobotId()+"},当前机器人{"+robotId+"}不执行加入逻辑"); + return; + } + } + } + System.err.println("225开始进房间: "+"room:"+ roomId +"robot:"+robotId); + //加入房间 + joinRoomCommon(robotId, roomId, groupId, params); + System.err.println("225已进入房间准备成功: "+"room:"+ roomId +"robot:"+robotId); + } + + /** + * 接收来自web_group的主动重连协议 + */ + @ActionKey(value = "226", validate = GameInterceptor.NOT_PLAYER) + public void webGroupActive(Session session, ITObject params, int gid) { + int robotId = params.getInt("robotid"); + String roomId = params.getString("roomid"); + System.err.println("226开始进房间: " + "room:" + roomId + "robot:" + robotId); + //加入房间 + joinRoomCommon(params.getInt("robotid"), params.getString("roomid"), params.getInt("groupid"), params); + System.err.println("226已进入房间准备成功: " + "room:" + roomId + "robot:" + 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; + } + + System.err.println("重启后开始进房间: " + "room:" + robotUser.getCurrentRoomId() + "robot:" + 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); + System.err.println("重启后已进入房间准备成功: " + "room:" + robotUser.getCurrentRoomId() + "robot:" + robotUser.getRobotId()); + + } catch (Exception e) { + throw new RuntimeException(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; + } + } + + System.err.println("开始进房间: room:" + roomId); + System.err.println("开始进房间: {user}:" + robotId); + + TaurusClient client = getCsMjGameServerConnection(roomId + "_" + robotId); + GroupRoomBusiness.joinRoom(groupId, "room:" + roomId, "{user}:" + robotId, null); + + //机器人房间映射关系 + 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(2000); + params.putString("session", "{user}:" + robotId + "," + robotSession); + + //发送加入房间请求到game_mj_cs + client.send(Config.JOIN_ROOM_CS, params, response -> { + robotConnectionManager.reconnectToGameServer(response, robotUser, client); + }); + System.err.println("已进入房间成功: " + robotUser.getConnecId()); + Thread.sleep(1000); + if (client.isConnected()) { + client.send(Config.GAME_READY_CS, params, response -> { + System.out.println("1003:" + response); + }); + jedis2.hset("gallrobot", String.valueOf(robotUser.getRobotId()), "1"); + + robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_READY); + robotConnectionManager.setSessionAndToken("{user}:" + robotId, robotSession, robotUser.getConnecId()); + } + robotUser.setIntoRoomTime(robotConnectionManager.getTime()); + System.err.println("已进入房间准备成功: " + robotUser.getConnecId()); + } catch (Exception e) { + throw new RuntimeException(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("123456"); + robotUserCopy.setGameHost("8.134.76.43"); + robotUserCopy.setGamePort("6841"); + robotUserCopy.setRobotGroupid("330800"); + robotUserCopy.setRobotPid("66"); + 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) { + robotRoomMapping.remove(robotId); + } + + /** + * 根据机器人ID和连接ID获取跑得快游戏服务器连接 + * 基于robotId和connectionId的组合复用连接 + */ + public static TaurusClient getCsMjGameServerConnection(String connecId) { + TaurusClient taurusClient = robotConnectionManager.getGameClient(connecId); + System.out.println("根据机器人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/puke/robot_pk_pdk/src/main/java/robot/mj/EXMainServer.java b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXMainServer.java new file mode 100644 index 0000000..5301bd6 --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXMainServer.java @@ -0,0 +1,164 @@ +package robot.mj; + +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.entity.ITObject; +import com.taurus.core.entity.TObject; +import com.taurus.core.plugin.redis.Redis; +import com.taurus.permanent.TPServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; +import robot.mj.info.RobotUser; +import taurus.client.NetManager; +import taurus.client.TaurusClient; + +import java.util.concurrent.TimeUnit; + +import static robot.mj.EXGameController.robotRoomMapping; + +/** + * 跑得快机器人主服务器 + * TCP服务端接收robot_mgr的协议 同时作为客户端连接game_mj_cs处理AI逻辑 + */ +public class EXMainServer extends MainServer{ + private static final Logger log = LoggerFactory.getLogger(EXMainServer.class); + + private static final RobotConnectionManager robotConnectionManager = new RobotConnectionManager(); + private static final EXGameController exGameController = new EXGameController(); + private volatile boolean connectionCheckRunning = true; + + @Override + public void onStart() { + super.onStart(); + + // 1. 先启动独立的事件处理线程(只启动一次) + startNetEventThread(); + + // 2. 启动连接检查定时任务 + //startConnectionCheckScheduler(); + //测试 + Jedis jedis2 = Redis.use("group1_db2").getJedis(); + String robotskey = "g{"+762479+"}:play:"+66; + Map maprobot = jedis2.hgetAll(robotskey); + for(Map.Entry entry : maprobot.entrySet()) { + System.out.println(entry.getKey() + ":" + entry.getValue()); + //是否创建 + RobotUser robotUser = new RobotUser(); + robotUser.setRobotId(entry.getKey()); + robotUser.setPassword("123456"); + robotUser.setGameHost("8.134.76.43"); + robotUser.setGamePort("6841"); + robotUser.setRobotGroupid("762479"); + robotUser.setRobotPid("66"); + + robotRoomMapping.put(entry.getKey(), robotUser); + } + + for(Map.Entry entry : robotRoomMapping.entrySet()) { + RobotUser robotUser = entry.getValue(); + //1、登录 + //判断是否登录 + if(!robotUser.isLogin){ + robotConnectionManager.login(robotUser); + } + } + +// TPServer.me().getTimerPool().scheduleAtFixedRate(new Runnable() { +// @Override +// public void run() { +// +// for(Map.Entry entry : robotRoomMapping.entrySet()) { +// RobotUser robotUser = entry.getValue(); +// //1、登录 +// //判断是否登录 +// if(!robotUser.isLogin){ +// robotConnectionManager.login(robotUser); +// } +// /*//2、链接 +// //判断是否链接 +// System.out.println("robotUser.isconnect"+robotUser.getIsconnect()); +// if(!robotUser.getIsconnect()){ +// robotConnectionManager.connectGame(robotUser); +// }else{ +// robotConnectionManager.renconnect(robotUser); +// } +// +// +// //4、加入房间 +// if(robotUser.getStatus()==0){ +// //没状态时候进入加入房间 +// ITObject params = new TObject(); +// params.putString("connecId", robotUser.getRoomId()+"_"+robotUser.getRobotId()); +// params.putString("roomId", robotUser.getRoomId()); +// params.putString("groupId", robotUser.getRobotGroupid()); +// params.putString("session", "{user}:"+robotUser.getRobotId()); +// exGameController.joinRoom(null, params, robotUser.getClient().getId()); +// } +// System.out.println("robotUser.getIntoRoomTime()"+robotUser.getIntoRoomTime()); +// System.out.println("robGetTiem"+robotConnectionManager.getTime()); +// if(robotUser.getIntoRoomTime()+6<=robotConnectionManager.getTime()){ +// //5、退出房间 +// robotConnectionManager.outoRoom(robotUser); +// }*/ +// +// } +// } +// }, 0, 5 ,TimeUnit.SECONDS); + //5、干活 + log.info("跑得快机器人服务器已启动"); + log.info("服务器将监听端口 {} 用于接收robot_mgr管理协议", gameSetting.port); + + jedis2.close(); + } + + /** + * 独立的事件处理线程 + */ + private void startNetEventThread() { + Thread eventThread = new Thread(() -> { + while (true) { + NetManager.processEvents(); + try { + Thread.sleep(2); + } catch (InterruptedException e) { + break; + } catch (Exception e) { + } + } + }, "NetEvent-Thread"); + + eventThread.setDaemon(true); // 设置为守护线程 + eventThread.start(); + } + + + + @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(); + + // 停止连接检查线程 + connectionCheckRunning = false; + log.info("跑得快机器人服务器已停止"); + } +} \ No newline at end of file diff --git a/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXPlayer.java b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXPlayer.java new file mode 100644 index 0000000..54b8039 --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXPlayer.java @@ -0,0 +1,31 @@ +package robot.mj; + +import com.robot.Util; +import com.robot.data.Player; +import com.robot.data.Room; +import com.robot.data.Score; +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 java.util.*; + +/** + * + * + */ +public class EXPlayer extends Player { + + + + public EXPlayer(int playerid, Room table, String session_id) { + super(playerid, table, session_id); + System.out.println("new robot "); + } + + public EXRoom getRoom() { + return (EXRoom) room; + } + +} diff --git a/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXRoom.java b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXRoom.java new file mode 100644 index 0000000..84ea302 --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/EXRoom.java @@ -0,0 +1,53 @@ +package robot.mj; + +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); + + + } + + + + + public void checkAction() { + + } + + + + + + + + @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/puke/robot_pk_pdk/src/main/java/robot/mj/RobotConnectionManager.java b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/RobotConnectionManager.java new file mode 100644 index 0000000..7ac4235 --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/RobotConnectionManager.java @@ -0,0 +1,519 @@ +package robot.mj; + +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.database.DataBase; +import com.taurus.core.plugin.redis.Redis; +import com.taurus.core.util.ICallback; +import com.taurus.core.util.StringUtil; +import robot.mj.business.AccountBusiness; +import robot.mj.handler.HuNanPaoDeKuai; +import robot.mj.info.RobotUser; +import taurus.client.Message; +import taurus.client.MessageResponse; +import taurus.client.TaurusClient; +import taurus.client.SocketCode; +import redis.clients.jedis.Jedis; + +import java.sql.SQLException; +import java.util.*; +import java.util.concurrent.*; + +import static robot.mj.EXGameController.robotRoomMapping; + +/** + * 机器人连接管理器 - 管理与游戏服务器的连接 + */ +public class RobotConnectionManager { + + private static final Map huNanPaoDeKuaiInstances = new ConcurrentHashMap<>(); + private final EXGameController exGameController; + + private final String host="8.134.76.43"; + private final int port=6841; + + + public RobotConnectionManager() { + exGameController = new EXGameController(); + } + + /** + * 获取跑得快处理器实例 + */ + private HuNanPaoDeKuai getHuNanPaoDeKuaiInstance(String connecId) { + HuNanPaoDeKuai existingInstance = huNanPaoDeKuaiInstances.get(connecId); + if (existingInstance != null) { + return existingInstance; + } + + HuNanPaoDeKuai newInstance = new HuNanPaoDeKuai(); + + //从Redis恢复状态 + boolean restored = newInstance.restoreFromRedis(connecId); + if (restored) { + System.out.println("从Redis恢复HuNanPaoDeKuai实例: " + connecId); + } else { + System.out.println("创建新的HuNanPaoDeKuai实例: " + connecId); + } + + huNanPaoDeKuaiInstances.put(connecId, newInstance); + System.out.println("当前HuNanPaoDeKuai实例总数: " + huNanPaoDeKuaiInstances.size()); + return newInstance; + } + + /** + * 设置会话和令牌 + */ + public void setSessionAndToken(String session, String token, String connecId) { + HuNanPaoDeKuai instance = getHuNanPaoDeKuaiInstance(connecId); + instance.session = session; + instance.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) { + return null; + } + } + + /** + * 断开与游戏服务器的连接(主动断开) + */ + public void disconnectFromGameServer(String connecId) { + System.out.println("开始主动断开连接: {"+connecId+"}"); + RobotUser robotUser = robotRoomMapping.remove(connecId); + + //清理连接数据 + if (connecId != null) { + HuNanPaoDeKuai.removeFromRedis(connecId); + + HuNanPaoDeKuai instance = huNanPaoDeKuaiInstances.get(connecId); + if (instance != null) { + instance.getPaoDekuaiCardInhand().clear(); + instance.getSeatRemainHistory().clear(); + System.out.println("清空HuNanPaoDeKuai集合数据: " + connecId); + } + + huNanPaoDeKuaiInstances.remove(connecId); + } + + if (robotUser != null) { + TaurusClient client = robotUser.getClient(); + if (client != null) { + try { + if (client.isConnected()) { + client.killConnection(); + } + System.out.println("客户端主动断开连接完成: {"+connecId+"}"); + } catch (Exception e) { + System.out.println("断开客户端连接时发生异常: " + connecId + ", 错误: " + e.getMessage()); + } + } else { + System.out.println("客户端连接不存在: {"+connecId+"}"); + } + } + } + + /** + * 设置事件监听器 + */ + 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; + System.out.println("csmj OnEvent msg: " + 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()){ + 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); + } + } + System.out.println("playerData:"+playerData); + + System.out.println("obj:"+obj); + System.out.println("reloadInfo:"+reloadInfo); + if(reloadInfo!=null) { + //重连回来的 + //同步手牌 + 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"); + } + } + } + + System.out.println("hcard>0"+hcard); + if(hcard.size()>0){ + //同步手牌 + HuNanPaoDeKuai currentInstance = getHuNanPaoDeKuaiInstance(connecId); + + //同步逻辑 + if (hcard.size() > 0) { + //直接同步服务器手牌数据 + currentInstance.updateHandCard(hcard); + System.out.println("断线重连:同步手牌数据,服务器手牌:" + hcard); + } + + //同步最后出牌信息 + /*if(info_list!=null) { + for (int i = 0; i < info_list.size(); i++) { + ITObject playerInfo = info_list.getTObject(i); + Integer playerid = playerInfo.getInt("playerid"); + + //找到最后出牌的玩家信息 + if(playerid != null && playerid == Integer.parseInt(robotUser.getRobotId())) { + ITObject lastOutCard = playerInfo.getTObject("last_outcard"); + if(lastOutCard != null) { + //提取最后出牌的关键信息 + int minCard = lastOutCard.getInt("min_card"); + int len = lastOutCard.getInt("len"); + int type = lastOutCard.getInt("type"); + ITArray cardList = lastOutCard.getTArray("card_list"); + + ITObject syncedCardList = TObject.newInstance(); + syncedCardList.putTArray("card_list", cardList); + syncedCardList.putInt("min_card", minCard); + syncedCardList.putInt("len", len); + syncedCardList.putInt("type", type); + + currentInstance.setCard_list(syncedCardList); + System.out.println("断线重连:同步最后出牌数据 - min_card:" + minCard + ", len:" + len + ", type:" + type + ", cards:" + cardList); + } + break; + } + } + }*/ + + sleepTime(2000); + currentInstance.outCard(client); + } else { + System.err.println("警告:重连时未获取到手牌数据"); + } + } + } + }else { + renconnect(robotUser); + } + } + + /** + * 处理接收到的游戏协议 + */ + private void handleProtocol(String command, Message message, TaurusClient client, String connecId) { + RobotUser robotUser = robotRoomMapping.get(connecId); + int robotId = Integer.parseInt(robotUser.getRobotId()); + ITObject param = message.param; + HuNanPaoDeKuai huNanPaoDeKuai = getHuNanPaoDeKuaiInstance(connecId); + Jedis jedis0 = Redis.use().getJedis(); + Jedis jedis2 = Redis.use("group1_db2").getJedis(); + try { + //跑得快 机器人处理事件 + //跑的快 初始化手牌 + if ("2011".equalsIgnoreCase(command)) { + huNanPaoDeKuai.paoDeKuaiCardInHead(param, client); + //处理完协议后保存到Redis + HuNanPaoDeKuai currentInstance = huNanPaoDeKuaiInstances.get(connecId); + currentInstance.saveToRedis(connecId); + } + //出牌广播 + else if ("2021".equalsIgnoreCase(command)) { + huNanPaoDeKuai.paoDekuaiChupaiGuangBo(param); + //处理完协议后保存到Redis + HuNanPaoDeKuai currentInstance = huNanPaoDeKuaiInstances.get(connecId); + currentInstance.saveToRedis(connecId); + } + //出牌提示事件,牌权 + else if ("2004".equalsIgnoreCase(command)) { + Integer player = param.getInt("player"); + Integer seat1 = param.getInt("seat"); + System.out.println("跑的快seat1 77777777777777777777777777" + seat1); + System.out.println("跑的快palyer6666666666666666666666666" + player); + if (seat1 != null) { + huNanPaoDeKuai.seat = seat1; + } + + //出牌 + huNanPaoDeKuai.outCard(client); + //处理完协议后保存到Redis + HuNanPaoDeKuai currentInstance = huNanPaoDeKuaiInstances.get(connecId); + currentInstance.saveToRedis(connecId); + } + //结算准备 + else if ("2007".equalsIgnoreCase(command)) { + //type为1 为大结算 type为0为小结算 + Integer type = param.getInt("type"); + System.out.println("type ++++++++++++++++++++++++" + type); + if (type == 0) { + huNanPaoDeKuai.getSeatRemainHistory().clear(); + huNanPaoDeKuai.getPaoDekuaiCardInhand().clear(); + ITArray card_list = huNanPaoDeKuai.getCard_list().getTArray("card_list"); + card_list.clear(); +// ready(); + } + + if (type == 1) { + updateLeftoverRobot(Integer.parseInt(robotUser.getRobotId())); + } + + } + else if ("2009".equalsIgnoreCase(command)) { + CompletableFuture.runAsync(() -> { + Integer paramRobotId = param.getInt("aid"); + sleepTime(6000); + + if (robotUser != null) { + String roomKey = String.valueOf(robotUser.getCurrentRoomId()); + + //查询该房间的玩家信息 + String playersStr = jedis0.hget(roomKey, "players"); + if (!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 = jedis0.hget(roomKey, "gpid"); + String gpId = jedis0.hget(roomKey, "group"); + + //发送退出房间协议 + ITObject params = TObject.newInstance(); + client.send("1005", params, response -> { + EXGameController.removeRobotRoomInfo(String.valueOf(paramRobotId)); + //断开连接 + disconnectFromGameServer(connecId); + //更新机器人剩余数量 + updateLeftoverRobot(paramRobotId); + System.out.println("2009发送退出房间协议1005,robotId: {"+paramRobotId+"}"); + }); + } + } + } + } + }); + } + //解散房间时候恢复机器人账号可以使用 + else if ("2008".equalsIgnoreCase(command)) { + updateLeftoverRobot(Integer.parseInt(robotUser.getRobotId())); + disconnectFromGameServer(connecId); + } + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + jedis0.close(); + 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"); + + System.out.println("机器人 {"+robotId+"} 退出房间,修改gallrobot为0"); + } finally { + jedis2.close(); + } + } + + /** + * 机器人登录 + */ + public void login(RobotUser robotUser){ + System.out.println("login:"+robotUser.getRobotId()); + ITObject object = null; + AccountBusiness accountBusiness = null; + accountBusiness = new AccountBusiness(); + try { + //先快速登录 + object = accountBusiness.fastLogin(Integer.parseInt(robotUser.getRobotId())); + System.out.println("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()); + System.err.println("重启获取的机器人还有当前房间,准备加入: "+robotUser.getConnecId()); + exGameController.webGroupJoinRoom(robotUser); + } + } + } + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void connectGame(RobotUser robotUser){ + if(robotUser.isLogin){ + if(robotUser.getClient()==null){ + TaurusClient client = new TaurusClient(robotUser.getGameHost()+":"+robotUser.getGamePort(), "game", TaurusClient.ConnectionProtocol.Tcp); + client.setSession(robotUser.getLoginsession()); + client.connect(); + setupEventListeners(client, robotUser.getCurrentRoomId()+"_"+robotUser.getRobotId()); + robotUser.setIsconnect(client.isConnected()); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + robotUser.setClient(client); + EXGameController.robotRoomMapping.put(robotUser.getCurrentRoomId()+"_"+robotUser.getRobotId(), robotUser); + }else{ + System.out.println("reconnect"); + System.out.println("client.isConnected()"+robotUser.getClient().isConnected()); + if(robotUser.getClient().isConnected()){ + robotUser.setIsconnect(true); + }else{ + System.out.println("reconnect"+robotUser.getClient().getGameID()); + TaurusClient client = new TaurusClient(robotUser.getGameHost()+":"+robotUser.getGamePort(), "game", TaurusClient.ConnectionProtocol.Tcp); + client.setSession(robotUser.getLoginsession()); + client.connect(); + robotUser.setIsconnect(client.isConnected()); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(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) { + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/robots/puke/robot_pk_pdk/src/main/java/robot/mj/RoomCreator.java b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/RoomCreator.java new file mode 100644 index 0000000..c3f0ecc --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/RoomCreator.java @@ -0,0 +1,326 @@ +package robot.mj; + +import com.data.cache.BaseCache; +import com.data.cache.GroupCache; +import com.taurus.core.entity.ITObject; +import com.taurus.core.entity.TObject; +import com.taurus.core.plugin.redis.Redis; +import com.taurus.core.util.StringUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; + +import java.util.HashMap; +import java.util.Map; + +/** + * 房间创建器 - 统一处理房间创建逻辑 + */ +public class RoomCreator { + private static final Logger log = LoggerFactory.getLogger(RoomCreator.class); + + /** + * 在Redis中创建房间 + * + * @param groupId 群组ID + * @param wanfaId 玩法ID + */ + public static RoomInfo createRoomInRedis(int groupId, int wanfaId, int robotId) { + try { + Jedis jedis0 = Redis.use("group1_db0").getJedis(); + Jedis jedis11 = Redis.use("group1_db11").getJedis(); + + try { + //从free_room队列获取房间ID + String newRoomId = Redis.use("group1_db1").rpop("free_room"); + if (StringUtil.isEmpty(newRoomId)) { + log.error("没有可用的房间ID,free_room队列为空"); + return null; + } + //Redis.use("group1_db1").lpush("free_room", newRoomId); + + String roomKey = "room:" + newRoomId; + + //获取群组信息 + String groupKey = "group:" + groupId; + Map groupInfo = jedis11.hgetAll(groupKey); + //不存在则创建默认群组信息 + if (groupInfo.isEmpty()) { + log.warn("群组信息不存在,群组ID: {},正在创建默认群组信息", groupId); + createDefaultGroupInfo(jedis11, groupId); + groupInfo = jedis11.hgetAll(groupKey); + } + + //确保游戏配置存在 + String gameConfigKey = "game:" + wanfaId; + Map gameInfo = jedis0.hgetAll(gameConfigKey); + //不存在则创建跑得快的默认游戏配置 + if (gameInfo.isEmpty()) { + log.warn("游戏配置不存在,玩法ID: {},正在创建跑得快默认游戏配置", wanfaId); + createDefaultGameConfig(jedis0, wanfaId); + gameInfo = jedis0.hgetAll(gameConfigKey); + } + + //获取玩法配置 + String playKey = "g{" + groupId + "}:play:" + wanfaId; + Map playConfig = jedis11.hgetAll(playKey); + //不存在则创建跑得快的默认玩法配置 + if (playConfig.isEmpty()) { + log.warn("玩法配置不存在,群组ID: {}, 玩法ID: {},正在创建跑得快默认配置", groupId, wanfaId); + createDefaultChangshaMajiangConfig(jedis11, groupId, wanfaId); + playConfig = jedis11.hgetAll(playKey); + } + + //构建房间信息 + Map roomMap = new HashMap<>(); + + //基础房间信息 + roomMap.put("AA", "0"); //AA支付标志 + roomMap.put("agent", "1"); //代理标志 + roomMap.put("dismiss_time", "30"); //解散时间 + roomMap.put("gpid", String.valueOf(wanfaId)); //玩法ID + roomMap.put("group", String.valueOf(groupId)); //群组ID + roomMap.put("hpOnOff", "1"); //体力开关 + roomMap.put("hp_times", "1000"); //体力次数 + roomMap.put("id", newRoomId); //房间ID + roomMap.put("kick_time", "30"); //踢出时间 + roomMap.put("owner", "{user}:" + robotId); //房间所有者 + roomMap.put("maxPlayers", "2"); //最大玩家数 + roomMap.put("opt", "2"); //选项 + roomMap.put("pay", "0"); //支付金额 + roomMap.put("payer", String.valueOf(robotId)); //支付者ID + roomMap.put("rewardType", "0"); //奖励类型 + roomMap.put("rewardValueType", "0"); //奖励值类型 + roomMap.put("seats", "[1]"); //房间状态 + roomMap.put("status", "0"); //房间状态 + roomMap.put("times", "4"); //局数 + roomMap.put("xipai_rewardType", "3"); //洗牌奖励类型 + roomMap.put("xipai_rewardValueType", "1"); //洗牌奖励值类型 + + //如果有体力配置 + String hpConfig = playConfig.get("hpConfig"); + if (hpConfig != null) { + roomMap.put("limitInRoom", "1"); //房间内限制 + } + + //处理玩法选项配置 + String playConfigJson = playConfig.getOrDefault("config", "{}"); + ITObject configData = TObject.newFromJsonData(playConfigJson); + + //原逻辑删除了字段 + configData.del("opt"); + configData.del("AA"); + + //将处理后的配置数据存储到options字段 + roomMap.put("options", configData.toJson()); //选项配置 + roomMap.put("game", String.valueOf(wanfaId)); //游戏ID + roomMap.put("open", "1"); //开放状态 + roomMap.put("round", "0"); //回合数 + roomMap.put("create_time", String.valueOf(System.currentTimeMillis() / 1000)); //创建时间 + roomMap.put("cache_ver", "1"); //缓存版本 + roomMap.put("players", "[]"); //玩家列表 + roomMap.put("fake_existTime", "30"); //fake存在时间 + + //设置房间信息 + jedis0.hmset(roomKey, roomMap); + + //更新剩余机器人数量 + String leftoverRobotStr = jedis11.hget(playKey, "leftover_robot"); + int leftoverRobot = leftoverRobotStr != null ? Integer.parseInt(leftoverRobotStr) : 0; + if (leftoverRobot > 0) { + jedis11.hincrBy(playKey, "leftover_robot", -1); + } + + //将房间添加到群组房间列表 + String groomsKey = "g{" + groupId + "}:rooms"; + jedis11.zadd(groomsKey, wanfaId * 10000 + 1101, roomKey); + + //更新群组的缓存版本 确保客户端能够获取到最新的群组信息 + String groupKeyForUpdate = GroupCache.genKey(groupId); + BaseCache.updateCacheVer(jedis11, groupKeyForUpdate); + + //创建并返回房间信息 + RoomInfo roomInfo = new RoomInfo(); + roomInfo.setRoomId(roomKey); + roomInfo.setGroupId(groupId); + roomInfo.setWanfaId(wanfaId); + roomInfo.setServerIp("127.0.0.1"); + roomInfo.setServerPort(getGamePortForWanfa(wanfaId)); + + log.info("成功创建机器人房间: {}, 群组: {}, 玩法: {}", roomKey, groupId, wanfaId); + return roomInfo; + } finally { + jedis0.close(); + jedis11.close(); + } + } catch (Exception e) { + log.error("创建房间时发生错误", e); + return null; + } + } + + /** + * 根据玩法ID获取游戏服务器端口 + */ + private static int getGamePortForWanfa(int wanfaId) { + switch (wanfaId) { + case 10: //跑得快 + return 6841; + default: + return 6841; + } + } + + /** + * 为跑得快创建默认玩法配置 + */ + private static void createDefaultChangshaMajiangConfig(Jedis jedis11, int groupId, int wanfaId) { + try { + log.info("为群组 {} 和跑得快玩法 {} 创建默认玩法配置", groupId, wanfaId); + + String playKey = "g{" + groupId + "}:play:" + wanfaId; + + Map defaultConfig = new HashMap<>(); + defaultConfig.put("opt", "1"); + defaultConfig.put("groupId", String.valueOf(groupId)); + defaultConfig.put("id", String.valueOf(wanfaId)); + defaultConfig.put("gameId", String.valueOf(wanfaId)); + defaultConfig.put("name", "跑得快-机器人专用"); + defaultConfig.put("deskId", "0"); + defaultConfig.put("maxPlayers", "4"); + defaultConfig.put("hpOnOff", "0"); + defaultConfig.put("rewardType", "0"); + defaultConfig.put("rewardValueType", "0"); + defaultConfig.put("xipai_rewardType", "0"); + defaultConfig.put("xipai_rewardValueType", "0"); + defaultConfig.put("hp_times", "0"); + defaultConfig.put("ban", "0"); + defaultConfig.put("robot_room", "1"); + defaultConfig.put("shangxian_robot", "10"); //限制机器人数量 + defaultConfig.put("leftover_robot", "10"); //设置剩余机器人数量 + defaultConfig.put("reward", "0"); + defaultConfig.put("xipai_reward", "100"); + defaultConfig.put("anchou_reward", "1"); + defaultConfig.put("mark", "0"); + defaultConfig.put("cache_ver", "1"); + + //跑得快配置选项 + defaultConfig.put("config", "{\"maxPlayers\":4,\"pid\":22,\"opt\":1,\"AA\":0,\"maxRound\":8,\"dianpao\":1,\"zimo\":1,\"haidi\":1,\"ting\":1,\"piao\":0,\"chongfeng\":1,\"queyimen\":0,\"hujia\":0,\"hongzhong\":1,\"qgh\":0,\"qgh_opt\":0,\"qgh_type\":0,\"qgh_score\":0,\"qgh_double\":0,\"qgh_double_opt\":0,\"qgh_double_type\":0,\"qgh_double_score\":0,\"qgh_double_score_opt\":0,\"qgh_double_score_type\":0,\"qgh_double_score_type_opt\":0}"); + defaultConfig.put("hpConfig", "{\"times\":1,\"limitInRoom\":0,\"maxRound\":8}"); + + jedis11.hmset(playKey, defaultConfig); + + //更新缓存版本 + jedis11.hincrBy(playKey, "cache_ver", 1); + + //更新群组玩法列表 将此玩法添加进去 + String gpidsKey = "g{" + groupId + "}:pids"; + jedis11.zadd(gpidsKey, 1 * 10 + 0, String.valueOf(wanfaId)); + + log.info("成功创建跑得快默认玩法配置 键名: {}", playKey); + } catch (Exception e) { + log.error("创建跑得快默认玩法配置时发生错误 群组ID: {}, 玩法ID: {}", groupId, wanfaId, e); + } + } + + /** + * 创建默认群组信息 + */ + private static void createDefaultGroupInfo(Jedis jedis11, int groupId) { + try { + log.info("为群组 {} 创建默认群组信息", groupId); + + String groupKey = "group:" + groupId; + + Map defaultGroupInfo = new HashMap<>(); + defaultGroupInfo.put("ban", "0"); //未禁用 + defaultGroupInfo.put("ban_apply", "0"); //不禁止申请 + defaultGroupInfo.put("ban_chat1", "false"); + defaultGroupInfo.put("ban_chat2", "false"); + defaultGroupInfo.put("create_time", String.valueOf(System.currentTimeMillis() / 1000)); + defaultGroupInfo.put("dissolve_opt", "1"); //解散选项 + defaultGroupInfo.put("exit_opt", "0"); + defaultGroupInfo.put("gms", "19"); + defaultGroupInfo.put("id", String.valueOf(groupId)); + defaultGroupInfo.put("kick_opt", "1"); //踢人选项 + defaultGroupInfo.put("name", "机器人专用群组-" + groupId); + defaultGroupInfo.put("notice", ""); + defaultGroupInfo.put("opt", "1"); //开启状态 + defaultGroupInfo.put("option", "0"); //选项 + defaultGroupInfo.put("owner", "999999"); //使用机器人系统账户ID + defaultGroupInfo.put("pay_type", "1"); //房主支付 + defaultGroupInfo.put("type", "2"); //普通群组类型 + + jedis11.hmset(groupKey, defaultGroupInfo); + + log.info("成功创建默认群组信息,键名: {}", groupKey); + } catch (Exception e) { + log.error("创建默认群组信息时发生错误,群组ID: {}", groupId, e); + } + } + + /** + * 创建默认游戏配置 + */ + private static void createDefaultGameConfig(Jedis jedis0, int wanfaId) { + try { + log.info("为玩法 {} 创建默认游戏配置", wanfaId); + + String gameConfigKey = "game:" + wanfaId; + + Map defaultGameInfo = new HashMap<>(); + defaultGameInfo.put("id", String.valueOf(wanfaId)); + defaultGameInfo.put("name", "跑得快"); + defaultGameInfo.put("maxPlayers", "4"); //跑得快为4人游戏 + defaultGameInfo.put("minPlayers", "2"); //最少2人 + defaultGameInfo.put("type", "1"); //游戏类型 + defaultGameInfo.put("status", "1"); //状态开启 + defaultGameInfo.put("icon", ""); //图标 + defaultGameInfo.put("desc", "跑得快游戏"); //描述 + defaultGameInfo.put("version", "1.0"); //版本 + defaultGameInfo.put("pay", "0"); //支付金额 + defaultGameInfo.put("times", "8"); //局数 + defaultGameInfo.put("hpType", "0"); // 体力值类型,必需字段 + defaultGameInfo.put("gameType", "1"); // 游戏类型,必需字段 + defaultGameInfo.put("isNonnegative", "0"); // 不可负分,必需字段 + + defaultGameInfo.put("opt1", "8"); + defaultGameInfo.put("opt2", "16"); + defaultGameInfo.put("opt3", "24"); + defaultGameInfo.put("opt4", "32"); + defaultGameInfo.put("opt5", "40"); + //设置支付选项 + defaultGameInfo.put("pay1_2", "10"); + defaultGameInfo.put("pay1_3", "15"); + defaultGameInfo.put("pay1_4", "20"); + + jedis0.hmset(gameConfigKey, defaultGameInfo); + + log.info("成功创建默认游戏配置,键名: {}", gameConfigKey); + } catch (Exception e) { + log.error("创建默认游戏配置时发生错误,玩法ID: {}", wanfaId, e); + } + } + + /** + * 房间信息类 + */ + public static class RoomInfo { + private String roomId; + private int groupId; + private int wanfaId; + private String serverIp; + private int serverPort; + + public String getRoomId() { return roomId; } + public void setRoomId(String roomId) { this.roomId = roomId; } + public int getGroupId() { return groupId; } + public void setGroupId(int groupId) { this.groupId = groupId; } + public int getWanfaId() { return wanfaId; } + public void setWanfaId(int wanfaId) { this.wanfaId = wanfaId; } + public String getServerIp() { return serverIp; } + public void setServerIp(String serverIp) { this.serverIp = serverIp; } + public int getServerPort() { return serverPort; } + public void setServerPort(int serverPort) { this.serverPort = serverPort; } + } +} \ No newline at end of file diff --git a/robots/puke/robot_pk_pdk/src/main/java/robot/mj/business/AccountBusiness.java b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/business/AccountBusiness.java new file mode 100644 index 0000000..bdf1bfa --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/business/AccountBusiness.java @@ -0,0 +1,380 @@ +package robot.mj.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.ErrorCode; +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 com.taurus.web.WebException; +import redis.clients.jedis.Jedis; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class AccountBusiness extends Controller { + private static Logger logger = Logger.getLogger(AccountBusiness.class); + + public static String request(String httpUrl, String httpArg) { + BufferedReader reader = null; + String result = null; + StringBuffer sbf = new StringBuffer(); + httpUrl = httpUrl + "?" + httpArg; + + try { + URL url = new URL(httpUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + InputStream is = connection.getInputStream(); + reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); + String strRead = reader.readLine(); + if (strRead != null) { + sbf.append(strRead); + while ((strRead = reader.readLine()) != null) { + sbf.append("\n"); + sbf.append(strRead); + } + } + reader.close(); + result = sbf.toString(); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + public static String md5(String plainText) { + StringBuffer buf = null; + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(plainText.getBytes()); + byte b[] = md.digest(); + int i; + buf = new StringBuffer(""); + for (int offset = 0; offset < b.length; offset++) { + i = b[offset]; + if (i < 0) + i += 256; + if (i < 16) + buf.append("0"); + buf.append(Integer.toHexString(i)); + } + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return buf.toString(); + } + + public static String encodeUrlString(String str, String charset) { + String strret = null; + if (str == null) + return str; + try { + strret = java.net.URLEncoder.encode(str, charset); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + return strret; + } + + private final 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) throws Exception { + 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) throws Exception { + + 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)) { + System.out.println("进入了77777777777777777777"); + logger.error("id:" + id + " ban login"); + throw new WebException(ErrorCode.BAN_LOGIN); + } + System.out.println("进入了9999999999999"); + + ITArray resultArray = DataBase.use().executeQueryByTArray(sql); + 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"); + System.out.println("进入了00000000000"); + + throw new WebException(ErrorCode._NO_SESSION); + + } + } + System.out.println("进入了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); + String old_nick = acc_bean.nick; + String old_portrait = acc_bean.portrait; +// String new_nick = reqData.getUtfString("nick"); +// String new_portrait = reqData.getUtfString("portrait"); +// if (!old_nick.equals(new_nick) || !old_portrait.equals(new_portrait)) { +// ITObject userData1 = TObject.newInstance(); +// userData1.putUtfString("nick", userData.getUtfString("nick")); +// userData1.putUtfString("portrait", userData.getUtfString("portrait")); +// userData1.putInt("sex", userData.getInt("sex")); +// updateSession(userData, accountid); +// } + } + + 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); + +// Set allToken = Redis.use("group1_db0").smembers(session + "_token"); +// for (String temp : allToken) { +// if (!Redis.use("group1_db0").exists(temp)) { +// Redis.use("group1_db0").srem(session + "_token", temp); +// logger.info("delte timeout token:" + temp); +// } +// } + System.out.println("进入了2222222222222"); + + long tokenNum = Redis.use("group1_db0").scard(session + "_token"); + if (tokenNum >= 10) { + logger.warn("id:" + accountid + " repeat login, token count:" + tokenNum); + } + System.out.println("进入了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 + * @throws Exception + */ + private final int UpdateUserData(ITObject reqData, long id) throws Exception { + 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/puke/robot_pk_pdk/src/main/java/robot/mj/handler/HuNanPaoDeKuai.java b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/handler/HuNanPaoDeKuai.java new file mode 100644 index 0000000..1a43cda --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/handler/HuNanPaoDeKuai.java @@ -0,0 +1,307 @@ +package robot.mj.handler; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.taurus.core.entity.ITArray; +import com.taurus.core.entity.ITObject; +import com.taurus.core.entity.TObject; +import com.taurus.core.plugin.redis.Redis; +import redis.clients.jedis.Jedis; +import taurus.client.TaurusClient; +import taurus.util.CardObj; +import taurus.util.CardUtil; +import taurus.util.test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HuNanPaoDeKuai { + + public int seat = 0; + + //座位号 剩余牌数量 + private final Map> seatRemainHistory = new HashMap<>(); + + // 跑的快玩家手牌 + private final List paoDekuaiCardInhand = new ArrayList<>(); + + // 会话标识 + public String session = ""; + // 访问令牌 + public String token = ""; + + //跑的快别人出的牌 + private ITObject card_list = TObject.newInstance(); + + public int guangboseat = 0; + + public int remain = 0; + + public Map> getSeatRemainHistory() { + return seatRemainHistory; + } + + public List getPaoDekuaiCardInhand() { + return paoDekuaiCardInhand; + } + + public ITObject getCard_list() { + return card_list; + } + + public void setCard_list(ITObject cardList) { + this.card_list = cardList; + } + + private static final Gson gson = new Gson(); + + + /** + * 将当前实例状态序列化为JSON字符串并保存到Redis + * @param connecId 连接ID + */ + public void saveToRedis(String connecId) { + Jedis jedis = Redis.use("group1_db2").getJedis(); + try { + Map stateMap = new HashMap<>(); + + stateMap.put("seatRemainHistory", gson.toJson(seatRemainHistory)); + stateMap.put("paoDekuaiCardInhand", gson.toJson(paoDekuaiCardInhand)); + stateMap.put("card_list", gson.toJson(card_list)); + + stateMap.put("seat", String.valueOf(seat)); + stateMap.put("guangboseat", String.valueOf(guangboseat)); + stateMap.put("remain", String.valueOf(remain)); + + + stateMap.put("session", session); + stateMap.put("token", token); + + String redisKey = "{pdk}:" + connecId; + jedis.hmset(redisKey, stateMap); + //1小时过期时间 + jedis.expire(redisKey, 3600); + + System.out.println("保存HuNanPaoDeKuai状态到Redis: " + connecId); + } catch (Exception e) { + System.err.println("保存HuNanPaoDeKuai状态到Redis失败: " + e.getMessage()); + } finally { + jedis.close(); + } + } + + /** + * 从Redis恢复实例状态 + * @param connecId 连接ID + * @return 是否成功恢复 + */ + public boolean restoreFromRedis(String connecId) { + Jedis jedis = Redis.use("group1_db2").getJedis(); + try { + String redisKey = "{pdk}:" + connecId; + Map stateMap = jedis.hgetAll(redisKey); + + if (stateMap == null || stateMap.isEmpty()) { + System.out.println("Redis中没有找到HuNanPaoDeKuai状态数据: " + connecId); + return false; + } + + //反序列化集合 + if (stateMap.containsKey("seatRemainHistory")) { + seatRemainHistory.clear(); + Map> remainHistory = gson.fromJson(stateMap.get("seatRemainHistory"), + new TypeToken>>(){}.getType()); + if (remainHistory != null) seatRemainHistory.putAll(remainHistory); + } + + if (stateMap.containsKey("paoDekuaiCardInhand")) { + paoDekuaiCardInhand.clear(); + List handCards = gson.fromJson(stateMap.get("paoDekuaiCardInhand"), + new TypeToken>(){}.getType()); + if (handCards != null) paoDekuaiCardInhand.addAll(handCards); + } + + if (stateMap.containsKey("card_list")) { + String cardListJson = stateMap.get("card_list"); + if (cardListJson != null && !cardListJson.isEmpty()) { + card_list = TObject.newInstance(); + } + } + + session = stateMap.getOrDefault("session", ""); + token = stateMap.getOrDefault("token", ""); + + // 恢复关键状态字段 + if (stateMap.containsKey("seat")) { + try { + seat = Integer.parseInt(stateMap.get("seat")); + } catch (NumberFormatException e) { + seat = 0; + } + } + + if (stateMap.containsKey("guangboseat")) { + try { + guangboseat = Integer.parseInt(stateMap.get("guangboseat")); + } catch (NumberFormatException e) { + guangboseat = 0; + } + } + + if (stateMap.containsKey("remain")) { + try { + remain = Integer.parseInt(stateMap.get("remain")); + } catch (NumberFormatException e) { + remain = 0; + } + } + + System.out.println("从Redis恢复HuNanPaoDeKuai状态成功: " + connecId + ", 手牌数量: " + paoDekuaiCardInhand.size()); + return true; + } catch (Exception e) { + System.err.println("从Redis恢复HuNanPaoDeKuai状态失败: " + e.getMessage()); + return false; + } finally { + jedis.close(); + } + } + + /** + * 从Redis清除实例状态 + * @param connecId 连接ID + */ + public static void removeFromRedis(String connecId) { + Jedis jedis = Redis.use("group1_db2").getJedis(); + try { + String redisKey = "{pdk}:" + connecId; + jedis.del(redisKey); + System.out.println("从Redis删除HuNanPaoDeKuai状态: " + connecId); + } catch (Exception e) { + System.err.println("从Redis删除HuNanPaoDeKuai状态失败: " + e.getMessage()); + } finally { + jedis.close(); + } + } + + /** + * 同步手牌 + * @param handCard + */ + public void updateHandCard(List handCard) { + System.out.println("updateHandCard同步手牌:"+ handCard); + paoDekuaiCardInhand.clear(); + System.out.println("updateHandCard同步手牌paoDekuaiCardInhand:"+ paoDekuaiCardInhand); + for(Integer card : handCard) { + paoDekuaiCardInhand.add(new CardObj(card)); + } + System.out.println("updateHandCard同步手牌add:"+ paoDekuaiCardInhand); + } + + public void updateOutCard(List outCard) { + System.out.println("updateOutCard - 出牌记录:" + outCard); + + ITArray cardArray = CardUtil.maJiangToTArray(outCard); + + ITObject newCardList = TObject.newInstance(); + newCardList.putTArray("card_list", cardArray); + card_list = newCardList; + + System.out.println("updateOutCard - 已更新card_list:" + card_list); + } + + + /** + * 出牌方法 + */ + public String outCard(TaurusClient client) { + ITArray itArray = null; + itArray = test.intelligentPaoDeKuaiOutCard(this,paoDekuaiCardInhand, card_list, seatRemainHistory); + System.out.println("itArray-----" + itArray); + if (remain == 1 && itArray == null) { //如果玩家的下家只有一张牌,玩家出单张必须是最大的 + CardObj maxSingleCard = CardUtil.findMaxSingleCard(paoDekuaiCardInhand); + System.out.println("出的最大牌 " + maxSingleCard); + itArray = CardUtil.toTArray1(maxSingleCard); + } + + //跑得快出牌 + System.out.println("机器人出牌 " + seat + CardUtil.toList(itArray)); + ITObject params = TObject.newInstance(); + params.putString("session", session + "," + token); + params.putTArray("card", itArray); + params.putTArray("all_card", CardUtil.toTArray(paoDekuaiCardInhand)); + CardUtil.removeCard(paoDekuaiCardInhand, CardUtil.toList(itArray)); //删除手牌里打过的牌 + System.out.println("目前机器人剩余手牌:" + paoDekuaiCardInhand.toString()); + client.send("1013", params, response -> + + { + System.out.println("出牌成功: " + response.returnCode); + }); + return null; + } + + /** + * 跑得快初始化手牌 + * + * @param param 消息参数 + * @return + */ + public String paoDeKuaiCardInHead(ITObject param, TaurusClient client) { + ITArray cardList = param.getTArray("cards"); + List cardObjs = new ArrayList<>(); + if (cardList.size() != 0) { + cardObjs = CardUtil.toList(cardList); + } + paoDekuaiCardInhand.addAll(cardObjs); +// if (paoDekuaiCardInhand.size() > 14) { +// outCard(client); +// System.out.println("机器人:" + param.getInt("seat") + "为庄家,需要出牌" + ",牌为:" + paoDekuaiCardInhand.get(0)); +// } + System.out.println("机器人:" + param.getInt("seat") + "初始化手牌" + ",牌为:" + paoDekuaiCardInhand.toString()); + return null; + } + + /** + * 跑的快出牌广播 + * + * @param param 消息参数 + * @return + */ + public String paoDekuaiChupaiGuangBo(ITObject param) { + System.out.println("=== 出牌广播调试信息 ==="); + System.out.println("原始param: " + param); + System.out.println("seat字段: " + param.getInt("seat")); + System.out.println("player字段: " + param.getInt("player")); + + card_list = param.getTObject("card_obj"); + + // 修复:正确获取座位号 + Integer seat = param.getInt("seat"); // 从seat字段获取座位号 + if (seat != null) { + guangboseat = seat; + System.out.println("使用seat字段设置guangboseat: " + guangboseat); + } else { + // 兼容旧版本:尝试从player字段获取座位号 + guangboseat = param.getInt("player"); + System.out.println("使用player字段设置guangboseat: " + guangboseat); + } + + remain = param.getInt("remain"); //剩余牌数量 + saveRemainHistory(guangboseat, remain); + System.out.println("座位号和手牌数量记录 " + seatRemainHistory); + System.out.println("广播手牌数量" + "座位号 " + guangboseat + "手牌数量 " + remain); + System.out.println("座位号:" + guangboseat + "的用户出牌广播:" + param.getTObject("card_obj")); + System.out.println("======================="); + return null; + } + + public void saveRemainHistory(int guangboseat, int remain) { + if (!seatRemainHistory.containsKey(guangboseat)) { + seatRemainHistory.put(guangboseat, new ArrayList<>()); + } + seatRemainHistory.get(guangboseat).add(remain); + } + + +} diff --git a/robots/puke/robot_pk_pdk/src/main/java/robot/mj/info/RobotUser.java b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/info/RobotUser.java new file mode 100644 index 0000000..1471c59 --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/robot/mj/info/RobotUser.java @@ -0,0 +1,164 @@ +package robot.mj.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/puke/robot_pk_pdk/src/main/java/taurus/util/CardObj.java b/robots/puke/robot_pk_pdk/src/main/java/taurus/util/CardObj.java new file mode 100644 index 0000000..ba0507e --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/taurus/util/CardObj.java @@ -0,0 +1,37 @@ +package taurus.util; + +public class CardObj implements Comparable{ + public int card; + public int cardMod; + + public CardObj(int card) { + this.card = card; + this.cardMod = card % 100; + } + + public int getCard() { + return card; + } + + public void setCard(int card) { + this.card = card; + } + + public int getCardMod() { + return cardMod; + } + + public void setCardMod(int cardMod) { + this.cardMod = cardMod; + } + + @Override + public int compareTo(CardObj paramT) { + return cardMod == paramT.cardMod ? 0 : cardMod < paramT.cardMod ? -1 : 1; + } + + @Override + public String toString() { + return card + ""; + } +} diff --git a/robots/puke/robot_pk_pdk/src/main/java/taurus/util/CardUtil.java b/robots/puke/robot_pk_pdk/src/main/java/taurus/util/CardUtil.java new file mode 100644 index 0000000..52a4018 --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/taurus/util/CardUtil.java @@ -0,0 +1,409 @@ +package taurus.util; + +import com.taurus.core.entity.ITArray; +import com.taurus.core.entity.TArray; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CardUtil { + + //用于找比指定牌大的最小单牌 + public static CardObj findMinBiggerSingleCard(List cardList, int minCard) { + if (cardList == null || cardList.isEmpty() || minCard <= 0) { + return null; + } + CardObj result = null; + int minBiggerValue = Integer.MAX_VALUE; + for (CardObj card : cardList) { + int cardMod = card.getCardMod(); + if (cardMod > minCard && cardMod < minBiggerValue) { + minBiggerValue = cardMod; + result = card; + } + } + return result; + } + + // 补充:找最小单牌(如未实现) + public static CardObj findMinSingleCard(List cardList) { + if (cardList == null || cardList.isEmpty()) { + return null; + } + CardObj minCard = cardList.get(0); + for (CardObj card : cardList) { + if (card.getCardMod() < minCard.getCardMod()) { + minCard = card; + } + } + return minCard; + } + + /** + * 从手牌中找出最大的单牌 + * + * @param handCards 手牌列表 + * @return 最大的单牌,如果没有单牌则返回null + */ + public static CardObj findMaxSingleCard(List handCards) { + if (handCards == null || handCards.isEmpty()) { + return null; + } + handCards.sort((c1, c2) -> c2.cardMod - c1.cardMod); + return handCards.get(0); + } + + + /** + * list to TArray + * + * @param list + * @return + */ + public static final ITArray toTArray(List list) { + ITArray result = new TArray(); + for (CardObj card : list) { + result.addInt(card.card); + } + return result; + } + + public static final ITArray toTArray1(CardObj list) { + ITArray result = new TArray(); + result.addInt(list.card); + return result; + } + + public static final ITArray maJiangToTArray(List list) { + ITArray result = new TArray(); + for (Integer integer : list) { + result.addInt(integer); + } + return result; + } + + public static final CardObj getCard(int eventCard, List cardList) { + for (CardObj card : cardList) { + if (card.cardMod == eventCard) { + return card; + } + } + return null; + } + + public static final CardObj getCard1(int eventCard, List cardList) { + for (CardObj card : cardList) { + if (card.card == eventCard) { + return card; + } + } + return null; + } + + /** + * 检测牌数量 + * + * @param eventCard + * @param cardList + * @param num + * @return + */ + public static final boolean checkCard(int eventCard, List cardList, int num) { + int result = 0; + for (CardObj card : cardList) { + if (card.cardMod == eventCard) { + result++; + if (result == num) + return true; + } + } + return false; + } + + public static final boolean checkGoodCard(int eventCard, List cardList, int num) { + int result = 0; + for (CardObj card : cardList) { + if (card.cardMod == eventCard) { + result++; + if (result >= num) + return true; + } + } + return false; + } + + public static final boolean checkShunZi(int eventCard, List cardList) { + int result = 0; + if (checkGoodCard(eventCard + 1, cardList, 1)) { + result++; + if (checkGoodCard(eventCard + 2, cardList, 1)) { + result++; + if (checkGoodCard(eventCard + 3, cardList, 1)) { + result++; + if (checkGoodCard(eventCard + 4, cardList, 1)) { + result++; + if (checkGoodCard(eventCard + 5, cardList, 1)) { + result++; + } + } + } + } + } + if (checkGoodCard(eventCard - 1, cardList, 1)) { + result++; + if (checkGoodCard(eventCard - 2, cardList, 1)) { + result++; + if (checkGoodCard(eventCard - 3, cardList, 1)) { + result++; + if (checkGoodCard(eventCard - 4, cardList, 1)) { + result++; + if (checkGoodCard(eventCard - 5, cardList, 1)) { + result++; + } + } + } + } + } + + if (result >= 4) { + return true; + } + return false; + } + + public static final boolean checkSevenShunzi(int eventCard, List cardList) { + int result = 0; + if (checkGoodCard(eventCard + 1, cardList, 1)) { + result++; + if (checkGoodCard(eventCard + 2, cardList, 1)) { + result++; + if (checkGoodCard(eventCard + 3, cardList, 1)) { + result++; + if (checkGoodCard(eventCard + 4, cardList, 1)) { + result++; + if (checkGoodCard(eventCard + 5, cardList, 1)) { + result++; + if (checkGoodCard(eventCard + 6, cardList, 1)) { + result++; + if (checkGoodCard(eventCard + 7, cardList, 1)) { + result++; + } + } + } + } + } + } + } + if (checkGoodCard(eventCard - 1, cardList, 1)) { + result++; + if (checkGoodCard(eventCard - 2, cardList, 1)) { + result++; + if (checkGoodCard(eventCard - 3, cardList, 1)) { + result++; + if (checkGoodCard(eventCard - 4, cardList, 1)) { + result++; + if (checkGoodCard(eventCard - 5, cardList, 1)) { + result++; + if (checkGoodCard(eventCard - 6, cardList, 1)) { + result++; + if (checkGoodCard(eventCard - 7, cardList, 1)) { + result++; + } + } + } + } + } + } + } + + if (result >= 6) { + return true; + } + return false; + } + + public static final boolean checkFenJi(int eventCard, List cardList) { + int result = 0; + if (checkGoodCard(eventCard, cardList, 2)) { + result++; + if (checkGoodCard(eventCard + 1, cardList, 3)) { + result++; + } + if (checkGoodCard(eventCard - 1, cardList, 3)) { + result++; + } + } + + if (result >= 2) { + return true; + } + return false; + } + + public static final boolean checkFourDui(int eventCard, List cardList) { + int result = 0; + if (checkGoodCard(eventCard, cardList, 1)) { + result++; + } + + if (checkGoodCard(eventCard + 1, cardList, 2)) { + result++; + if (checkGoodCard(eventCard + 2, cardList, 2)) { + result++; + if (checkGoodCard(eventCard + 3, cardList, 2)) { + result++; + if (checkGoodCard(eventCard + 4, cardList, 2)) { + result++; + } + } + } + } + if (checkGoodCard(eventCard - 1, cardList, 2)) { + result++; + if (checkGoodCard(eventCard - 2, cardList, 2)) { + result++; + if (checkGoodCard(eventCard - 3, cardList, 2)) { + result++; + if (checkGoodCard(eventCard - 4, cardList, 2)) { + result++; + } + } + } + } + + if (result >= 4) { + return true; + } + return false; + } + + public static final boolean checkQPai(int eventCard, List cardList) { + int result = 0; + if (eventCard >= 12) { + result++; + for (CardObj card : cardList) { + if (card.cardMod >= 12) { + result++; + } + } + } + + if (result >= 5) { + return true; + } + return false; + } + + /** + * TArray to list + * + * @param list + * @return + */ + public static final List toList(ITArray list) { + List tem = new ArrayList(); + for (int i = 0; i < list.size(); ++i) { + tem.add(new CardObj(list.getInt(i))); + } + return tem; + } + + /** + * 获取每张牌的数量MAP + * + * @param cardList + * @return + */ + public static final Map getCardNumMap(List cardList) { + Map result = new HashMap(); + for (CardObj card : cardList) { + if (!result.containsKey(card.cardMod)) { + result.put(card.cardMod, 1); + } else { + int num = result.get(card.cardMod); + result.put(card.cardMod, (num + 1)); + } + } + return result; + } + + /** + * 获取每张牌的数量MAP + * + * @param cardList + * @return + */ + public static final void getCardNumMap(Map result, List cardList) { + result.clear(); + for (CardObj card : cardList) { + if (!result.containsKey(card.cardMod)) { + result.put(card.cardMod, 1); + } else { + int num = result.get(card.cardMod); + result.put(card.cardMod, (num + 1)); + } + } + } + + public static final void getCardNumMap(Map result, List cardList, CardObj tempCard) { + result.clear(); + result.put(tempCard.cardMod, 1); + for (CardObj card : cardList) { + if (!result.containsKey(card.cardMod)) { + result.put(card.cardMod, 1); + } else { + int num = result.get(card.cardMod); + result.put(card.cardMod, (num + 1)); + } + } + } + + /** + * 获取每张牌的数量MAP + * + * @param cardList + * @return + */ + public static final Map> getCardListMap(List cardList) { + Map> result = new HashMap>(); + for (CardObj card : cardList) { + if (!result.containsKey(card.cardMod)) { + List list = new ArrayList(); + list.add(card); + result.put(card.cardMod, list); + } else { + List list = result.get(card.cardMod); + list.add(card); + } + } + return result; + } + + static public void removeCard(List cardList, List cards) { + for (int i = 0; i < cards.size(); i++) { + for (int j = 0; j < cardList.size(); j++) { + if (cardList.get(j).card == cards.get(i).card) { + cardList.remove(j); + break; + } + } + } + } + + static public void removeCard1(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++; + } + } + } +} diff --git a/robots/puke/robot_pk_pdk/src/main/java/taurus/util/ROBOTEventType.java b/robots/puke/robot_pk_pdk/src/main/java/taurus/util/ROBOTEventType.java new file mode 100644 index 0000000..d242292 --- /dev/null +++ b/robots/puke/robot_pk_pdk/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/puke/robot_pk_pdk/src/main/java/taurus/util/test.java b/robots/puke/robot_pk_pdk/src/main/java/taurus/util/test.java new file mode 100644 index 0000000..f058930 --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/main/java/taurus/util/test.java @@ -0,0 +1,3336 @@ +package taurus.util; + +import com.taurus.core.entity.ITArray; +import com.taurus.core.entity.ITObject; +import robot.mj.handler.HuNanPaoDeKuai; + +import java.util.*; +import java.util.stream.Collectors; + +public class test { + + /** + * 跑得快出牌策略 + */ + public static ITArray intelligentPaoDeKuaiOutCard(HuNanPaoDeKuai huNanPaoDeKuai, List cardInhand, ITObject card_list, Map> seatRemainHistory) { + System.out.println("机器人手牌 cardInhand: " + cardInhand); + System.out.println("card_list: " + card_list); + System.out.println("机器人座位号 8888888888888888888888888888 " + huNanPaoDeKuai.seat); + int seat = huNanPaoDeKuai.seat; + + List sortedHand = new ArrayList<>(cardInhand); + Collections.sort(sortedHand); + List playCards; + + boolean isFirstTurn = card_list == null || !card_list.containsKey("card_list") || + card_list.getTArray("card_list").size() == 0; + + if (isFirstTurn) { + System.out.println("进入自己先出牌-----------"); + playCards = intelligentFirstPlayWithRemainConsideration(huNanPaoDeKuai,sortedHand, seatRemainHistory); + } else { + ITArray opponentCardsArray = card_list.getTArray("card_list"); + List opponentCards = CardUtil.toList(opponentCardsArray); + + int minCard = card_list.getInt("min_card"); + int len = card_list.getInt("len"); + int type = card_list.getInt("type"); + + + if (huNanPaoDeKuai.seat == huNanPaoDeKuai.guangboseat) { + Map otherSeatsLastRemain = getOtherSeatsLastRemain(seat, seatRemainHistory); + boolean hasAnyEqualOne = otherSeatsLastRemain.values().stream().anyMatch(value -> value == 1); + if (hasAnyEqualOne) { + System.out.println("进入对面只剩下一张牌"); + // 优先出对子 + List pair = findAnyPair(cardInhand); + System.out.println("检测到对手只剩一张牌,优先出对子压制111" + pair); + if (pair.size() == 2) { + System.out.println("推荐出牌: " + pair + " (对子压制)"); + return CardUtil.toTArray(pair); + } else { + // 没有对子,出最大单牌 + System.out.println("检测到对手只剩一张牌 没有对子,出最大单牌"); + CardObj maxSingleCard = findMaxSingleCard(cardInhand); + return CardUtil.toTArray1(maxSingleCard); + } + } else { + System.out.println("机器人跑得快座位号 88888888888888 " + huNanPaoDeKuai.seat); + System.out.println("广播座位号 9999999999 " + huNanPaoDeKuai.guangboseat); + + //座位号出问题了 + playCards = intelligentFirstPlayWithRemainConsideration(huNanPaoDeKuai, sortedHand, seatRemainHistory); + return CardUtil.toTArray(playCards); + } + } + + Map otherSeatsLastRemain = getOtherSeatsLastRemain(seat, seatRemainHistory); + boolean hasAnyEqualOne = otherSeatsLastRemain.values().stream().anyMatch(value -> value == 1); + + if (hasAnyEqualOne && type == 1) { + System.out.println("进入对面只剩下一张牌,出最大单牌"); + CardObj maxSingleCard = findMaxSingleCard(cardInhand); + return CardUtil.toTArray1(maxSingleCard); + } else { + playCards = intelligentResponsePlayWithRemainConsideration(sortedHand, minCard, len, opponentCards, type); + return CardUtil.toTArray(playCards); + } + } + return CardUtil.toTArray(playCards); + } + + /** + * 深度分析手牌结构 + */ + private static HandAnalysis analyzeHandStructure(List handCards) { + HandAnalysis analysis = new HandAnalysis(); + analysis.valueCount = getValueCount(handCards); + + // 基础统计 + analysis.smallSingleCount = countSmallSingles(handCards, analysis.valueCount); + analysis.bigCardCount = countBigCards(analysis.valueCount); + analysis.pairCount = countPairs(analysis.valueCount); + analysis.trioCount = countTrios(analysis.valueCount); + analysis.bombCount = countBombs(analysis.valueCount); + + // 顺子分析 + analysis.maxStraightLength = findMaxStraightLength(handCards); + + // 连对分析 + analysis.maxConsecutivePairs = findMaxConsecutivePairs(handCards); + + // 控制牌分析 + analysis.hasControlCards = analysis.bigCardCount > 0; + + // 收集所有可能的出牌 + analysis.allPossiblePlays = collectAllPossibleFirstPlays(handCards); + + // 设置特征标签 + setAnalysisFeatures(analysis); + + return analysis; + } + + /** + * 首轮出牌 + */ + private static List intelligentFirstPlayWithRemainConsideration(HuNanPaoDeKuai huNanPaoDeKuai,List handCards, Map> seatRemainHistory) { + if (handCards.isEmpty()) return new ArrayList<>(); + + // === 飞机优先出牌 === + System.out.println("=== 检测飞机牌型 ==="); + List airplanePlay = findAndPlayAirplane(handCards); + if (!airplanePlay.isEmpty()) { + System.out.println("推荐飞机出牌: " + airplanePlay + " (类型:飞机带四张)"); + return airplanePlay; + } + + + // 4-8张手牌特殊处理 + if (handCards.size() >= 4 && handCards.size() <= 8) { + System.out.println("=== 4-8张手牌快速出完策略 ==="); + List quickWinPlay = findQuickWinPlay(handCards); + if (!quickWinPlay.isEmpty()) { + System.out.println("推荐快速出牌: " + quickWinPlay + " (类型:" + getPlayType(quickWinPlay) + ")"); + return quickWinPlay; + } + } + + // 特殊处理:手牌很少时的策略 + if (handCards.size() <= 3) { + //如果对面只剩下一张牌,并且自己剩下的牌里有对子优先打对子 + List bestCard = findBestCardToPlay(huNanPaoDeKuai,handCards, seatRemainHistory); + //如果没有对子出最大的单牌 + if (bestCard.isEmpty()) { + System.out.println("没有对子出最大的单牌"); + List pair = findAnyPair(handCards); + if (pair.isEmpty()) { + CardObj maxCard = findMaxCard(handCards); + return Collections.singletonList(maxCard); + } + return pair; + } + return bestCard; + } + + if (handCards.size() == 15) { + Map valueCount = getValueCount(handCards); + // 找出所有的三张 + List trioValues = valueCount.entrySet().stream() + .filter(entry -> entry.getValue() == 3) // 严格等于3,确保是完整的三张 + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + + // 需要正好3个三张才能形成三个三带结构 + if (trioValues.size() != 3) { + System.out.println("三张数量不是3个,不构成完美三个三带结构"); + + // 修复:分析大牌分布,避免首轮全出大牌 + HandAnalysis analysis = analyzeHandStructure(handCards); + List bestFirstPlay = selectBestFirstPlayWithBigCardProtection(handCards, analysis); + + System.out.println("=== 分析结果 ==="); + System.out.println("牌型特征: " + analysis.features.get("feature")); + System.out.println("小单牌: " + analysis.smallSingleCount + ", 大牌: " + analysis.bigCardCount + + ", 对子: " + analysis.pairCount + ", 顺子长度: " + analysis.maxStraightLength); + System.out.println("推荐首出: " + bestFirstPlay + " (类型:" + getPlayType(bestFirstPlay) + ")"); + return bestFirstPlay; + } + + return tryPlayTrioWithOne(handCards, valueCount); + } + + // 修复:分析大牌分布,避免首轮全出大牌 + HandAnalysis analysis = analyzeHandStructure(handCards); + List bestFirstPlay = selectBestFirstPlayWithBigCardProtection(handCards, analysis); + + System.out.println("=== 分析结果 ==="); + System.out.println("牌型特征: " + analysis.features.get("feature")); + System.out.println("小单牌: " + analysis.smallSingleCount + ", 大牌: " + analysis.bigCardCount + + ", 对子: " + analysis.pairCount + ", 顺子长度: " + analysis.maxStraightLength); + System.out.println("推荐首出: " + bestFirstPlay + " (类型:" + getPlayType(bestFirstPlay) + ")"); + + return bestFirstPlay; + } + + + /** + * 计算单牌的孤立程度分数(越高越孤立) + */ + private static int calculateIsolationScore(Map valueCount, int value) { + int score = 0; + + // 基础分:自身是单张 + if (valueCount.getOrDefault(value, 0) == 1) { + score += 10; + } + + // 前后都没有牌加分 + if (valueCount.getOrDefault(value - 1, 0) == 0) score += 5; + if (valueCount.getOrDefault(value + 1, 0) == 0) score += 5; + + // 前后2格都没有牌额外加分 + if (valueCount.getOrDefault(value - 2, 0) == 0) score += 2; + if (valueCount.getOrDefault(value + 2, 0) == 0) score += 2; + + return score; + } + + + /** + * 检测并构建飞机出牌(优先带小单牌或小对子,尽量不拆牌) + */ + private static List findAndPlayAirplane(List handCards) { + Map valueCount = getValueCount(handCards); + + // 找出所有三张的牌值 + List trioValues = valueCount.entrySet().stream() + .filter(entry -> entry.getValue() >= 3) // 至少3张 + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + + if (trioValues.size() < 2) { + return new ArrayList<>(); // 不够两个三张,无法形成飞机 + } + + // 寻找连续的三张(飞机) + List> airplaneChains = new ArrayList<>(); + List currentChain = new ArrayList<>(); + + for (int i = 0; i < trioValues.size(); i++) { + if (currentChain.isEmpty()) { + currentChain.add(trioValues.get(i)); + } else { + int lastValue = currentChain.get(currentChain.size() - 1); + int currentValue = trioValues.get(i); + + if (currentValue == lastValue + 1) { + currentChain.add(currentValue); + } else { + if (currentChain.size() >= 2) { + airplaneChains.add(new ArrayList<>(currentChain)); + } + currentChain.clear(); + currentChain.add(currentValue); + } + } + } + + if (currentChain.size() >= 2) { + airplaneChains.add(new ArrayList<>(currentChain)); + } + + if (airplaneChains.isEmpty()) { + return new ArrayList<>(); // 没有连续的飞机 + } + + // 选择最长的飞机链 + List bestAirplane = airplaneChains.stream() + .max(Comparator.comparingInt(List::size)) + .orElse(airplaneChains.get(0)); + + System.out.println("找到飞机链: " + bestAirplane + " (长度:" + bestAirplane.size() + ")"); + + // 构建飞机主体(所有三张) + List airplane = new ArrayList<>(); + for (int value : bestAirplane) { + List trio = handCards.stream() + .filter(card -> card.cardMod == value) + .limit(3) + .collect(Collectors.toList()); + airplane.addAll(trio); + } + + // 使用智能策略寻找带牌(考虑出完后的手牌结构) + List carryCards = findBestCarryCardsForAirplane(handCards, bestAirplane, valueCount); + + int requiredCarryCount = bestAirplane.size() * 2; + + if (carryCards.size() >= requiredCarryCount) { + airplane.addAll(carryCards.subList(0, requiredCarryCount)); + System.out.println("形成飞机带" + requiredCarryCount + "张: " + + airplane.stream().map(card -> card.cardMod).collect(Collectors.toList())); + return airplane; + } else { + System.out.println("飞机带牌不足,需要" + requiredCarryCount + "张,实际只有" + carryCards.size() + "张"); + return new ArrayList<>(); + } + } + + /** + * 为飞机寻找最佳带牌(优先带小单牌或小对子,尽量不拆牌) + */ + private static List findBestCarryCardsForAirplane(List handCards, List airplaneValues, + Map valueCount) { + List carryCards = new ArrayList<>(); + int requiredCarryCount = airplaneValues.size() * 2; + + // 复制手牌并移除飞机主体 + List availableCards = new ArrayList<>(handCards); + for (int value : airplaneValues) { + availableCards.removeIf(card -> card.cardMod == value); + } + + System.out.println("可用带牌: " + availableCards.stream() + .map(card -> card.cardMod) + .sorted() + .collect(Collectors.toList())); + + // 分析各种带牌方案对剩余手牌的影响 + List options = analyzeAllCarryOptions(availableCards, requiredCarryCount); + + // 选择最优方案 + CarryOption bestOption = selectBestCarryOption(options); + + if (bestOption != null) { + System.out.println("最优带牌方案: " + bestOption.carryCards.stream() + .map(card -> card.cardMod) + .collect(Collectors.toList()) + + ", 剩余手数: " + bestOption.remainingTurns + + ", 剩余牌型: " + bestOption.remainingPattern); + return bestOption.carryCards; + } + + // 如果没有找到合适方案,使用保守策略 + return findConservativeCarryCards(availableCards, requiredCarryCount); + } + + + /** + * 分析所有可能的带牌方案 + */ + private static List analyzeAllCarryOptions(List availableCards, int requiredCount) { + List options = new ArrayList<>(); + // 生成所有可能的带牌组合 + List> allCombinations = generateCarryCombinations(availableCards, requiredCount); + + for (List combination : allCombinations) { + // 计算剩余手牌 + List remainingCards = new ArrayList<>(availableCards); + remainingCards.removeAll(combination); + + // 分析剩余手牌 + RemainAnalysis analysis = analyzeRemainingHand(remainingCards); + + CarryOption option = new CarryOption(); + option.carryCards = combination; + option.remainingCards = remainingCards; + option.remainingTurns = analysis.estimatedTurns; + option.remainingPattern = analysis.bestPattern; + option.score = calculateCarryOptionScore(analysis, combination); + + options.add(option); + } + + return options; + } + + /** + * 生成所有可能的带牌组合 + */ + private static List> generateCarryCombinations(List availableCards, int requiredCount) { + List> combinations = new ArrayList<>(); + + if (availableCards.size() > 8) { + return generateHeuristicCarryCombinations(availableCards, requiredCount); + } + + // 生成所有组合 + generateCombinationsRecursive(availableCards, requiredCount, 0, new ArrayList<>(), combinations); + return combinations; + } + + /** + * 递归生成组合 + */ + private static void generateCombinationsRecursive(List availableCards, int requiredCount, + int startIndex, List current, + List> combinations) { + if (current.size() == requiredCount) { + combinations.add(new ArrayList<>(current)); + return; + } + + for (int i = startIndex; i < availableCards.size(); i++) { + current.add(availableCards.get(i)); + generateCombinationsRecursive(availableCards, requiredCount, i + 1, current, combinations); + current.remove(current.size() - 1); + } + } + + /** + * 启发式方法生成带牌组合(避免组合爆炸) + */ + private static List> generateHeuristicCarryCombinations(List availableCards, int requiredCount) { + List> combinations = new ArrayList<>(); + Map valueCount = getValueCount(availableCards); + + // 策略1:优先使用完整对子的组合 + List allPairs = valueCount.entrySet().stream() + .filter(entry -> entry.getValue() >= 2) + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + + // 生成以对子为主的组合 + for (int i = 0; i < Math.min(3, allPairs.size()); i++) { + int pairValue = allPairs.get(i); + List pair = availableCards.stream() + .filter(card -> card.cardMod == pairValue) + .limit(2) + .collect(Collectors.toList()); + + if (pair.size() == 2) { + // 对子 + 单牌组合 + List remainingCards = new ArrayList<>(availableCards); + remainingCards.removeAll(pair); + + if (requiredCount == 4) { + // 需要再找2张单牌 + List singles = getAvailableSingles(remainingCards).stream() + .filter(card -> card.cardMod <= 10) + .limit(2) + .collect(Collectors.toList()); + + if (singles.size() == 2) { + List combination = new ArrayList<>(pair); + combination.addAll(singles); + combinations.add(combination); + } + } + } + } + + // 策略2:单牌组合(优先小单牌) + if (combinations.isEmpty()) { + List smallSingles = availableCards.stream() + .filter(card -> valueCount.getOrDefault(card.cardMod, 0) == 1) // 真单牌 + .filter(card -> card.cardMod <= 10) + .sorted(Comparator.comparingInt(card -> card.cardMod)) + .limit(requiredCount) + .collect(Collectors.toList()); + + if (smallSingles.size() == requiredCount) { + combinations.add(smallSingles); + } + } + + // 策略3:混合组合(对子+单牌) + if (combinations.isEmpty()) { + // 找一个对子 + Optional pairOpt = allPairs.stream().findFirst(); + if (pairOpt.isPresent()) { + int pairValue = pairOpt.get(); + List pair = availableCards.stream() + .filter(card -> card.cardMod == pairValue) + .limit(2) + .collect(Collectors.toList()); + + if (pair.size() == 2) { + List remainingCards = new ArrayList<>(availableCards); + remainingCards.removeAll(pair); + + // 找需要的单牌 + List neededSingles = remainingCards.stream() + .filter(card -> card.cardMod != pairValue) + .sorted(Comparator.comparingInt(card -> card.cardMod)) + .limit(requiredCount - 2) + .collect(Collectors.toList()); + + if (neededSingles.size() == requiredCount - 2) { + List combination = new ArrayList<>(pair); + combination.addAll(neededSingles); + combinations.add(combination); + } + } + } + } + + // 策略4:纯单牌组合(任意单牌) + if (combinations.isEmpty()) { + List anySingles = availableCards.stream() + .sorted(Comparator.comparingInt(card -> card.cardMod)) + .limit(requiredCount) + .collect(Collectors.toList()); + combinations.add(anySingles); + } + + // 策略5:考虑保留顺子的特殊组合 + List> straightPreservingCombinations = generateStraightPreservingCombinations(availableCards, requiredCount); + combinations.addAll(straightPreservingCombinations); + + return combinations; + } + + /** + * 生成保留顺子的带牌组合 + */ + private static List> generateStraightPreservingCombinations(List availableCards, int requiredCount) { + List> combinations = new ArrayList<>(); + List values = availableCards.stream() + .map(card -> card.cardMod) + .distinct() + .sorted() + .collect(Collectors.toList()); + + System.out.println("分析顺子保留策略,可用牌值: " + values); + + // 找出所有可能的顺子 + for (int length = 5; length <= values.size(); length++) { + for (int start = 0; start <= values.size() - length; start++) { + List potentialStraight = values.subList(start, start + length); + if (isConsecutive(potentialStraight, 0, potentialStraight.size())) { + System.out.println("发现顺子: " + potentialStraight); + + // 尝试从顺子外取带牌,保留这个顺子 + Set straightSet = new HashSet<>(potentialStraight); + List outsideCards = availableCards.stream() + .filter(card -> !straightSet.contains(card.cardMod)) + .collect(Collectors.toList()); + + System.out.println("顺子外牌: " + outsideCards.stream() + .map(card -> card.cardMod) + .collect(Collectors.toList())); + + if (outsideCards.size() >= requiredCount) { + // 顺子外有足够的牌,完美! + List combination = outsideCards.stream() + .sorted(Comparator.comparingInt(card -> card.cardMod)) + .limit(requiredCount) + .collect(Collectors.toList()); + combinations.add(combination); + System.out.println("完美方案: 从顺子外取牌 " + combination.stream() + .map(card -> card.cardMod) + .collect(Collectors.toList())); + } else if (outsideCards.size() > 0) { + // 顺子外不够,需要从顺子中取少量牌,但要尽量保持顺子完整 + int neededFromStraight = requiredCount - outsideCards.size(); + + // 从顺子的"间隙"位置取牌,尽量不破坏顺子连续性 + List bestStraightCards = getBestCardsFromStraight(availableCards, potentialStraight, neededFromStraight); + + if (bestStraightCards.size() == neededFromStraight) { + List combination = new ArrayList<>(outsideCards); + combination.addAll(bestStraightCards); + combinations.add(combination); + System.out.println("较好方案: 顺子外" + outsideCards.size() + "张 + 顺子内" + neededFromStraight + "张 = " + + combination.stream().map(card -> card.cardMod).collect(Collectors.toList())); + } + } + } + } + } + + return combinations; + } + + /** + * 从顺子中取最合适的牌(尽量保持顺子完整性) + */ + private static List getBestCardsFromStraight(List availableCards, List straight, int neededCount) { + List selected = new ArrayList<>(); + + // 策略:优先取顺子两端的牌,或者取有重复值的牌 + Map valueCount = getValueCount(availableCards); + + // 先找有重复值的牌(对子、三张等) + for (int value : straight) { + if (valueCount.getOrDefault(value, 0) > 1 && selected.size() < neededCount) { + CardObj card = availableCards.stream() + .filter(c -> c.cardMod == value) + .findFirst() + .orElse(null); + if (card != null) { + selected.add(card); + } + } + } + + // 如果还不够,从顺子两端取 + if (selected.size() < neededCount) { + // 从左端取 + for (int i = 0; i < straight.size() && selected.size() < neededCount; i++) { + int value = straight.get(i); + if (!selected.stream().anyMatch(card -> card.cardMod == value)) { + CardObj card = availableCards.stream() + .filter(c -> c.cardMod == value) + .findFirst() + .orElse(null); + if (card != null) { + selected.add(card); + } + } + } + } + + // 如果还不够,从右端取 + if (selected.size() < neededCount) { + for (int i = straight.size() - 1; i >= 0 && selected.size() < neededCount; i--) { + int value = straight.get(i); + if (!selected.stream().anyMatch(card -> card.cardMod == value)) { + CardObj card = availableCards.stream() + .filter(c -> c.cardMod == value) + .findFirst() + .orElse(null); + if (card != null) { + selected.add(card); + } + } + } + } + + return selected; + } + + /** + * 计算带牌方案分数(优化版) + */ + private static int calculateCarryOptionScore(RemainAnalysis analysis, List carryCards) { + int score = 0; + + // 剩余手数越少分数越高(最重要) + score += (10 - analysis.estimatedTurns) * 100; + + // 如果剩余牌能一次性出完,大幅加分 + if (analysis.estimatedTurns == 1) { + score += 500; + } + + // 如果剩余牌是顺子,大幅加分 + if (analysis.bestPattern.contains("顺子")) { + score += 300; + } + + // 如果剩余牌是连对,加分 + if (analysis.bestPattern.contains("连对")) { + score += 200; + } + + // 带牌中单牌数量越少越好(避免拆对子) + Map carryCount = getValueCount(carryCards); + long singleCarryCount = carryCount.values().stream().filter(c -> c == 1).count(); + score -= singleCarryCount * 20; + + // 带牌中对子数量越多越好 + long pairCarryCount = carryCount.values().stream().filter(c -> c == 2).count(); + score += pairCarryCount * 25; + + // 优先带小牌 + int maxCarryValue = carryCards.stream().mapToInt(c -> c.cardMod).max().orElse(0); + if (maxCarryValue <= 10) { + score += 15; + } + + System.out.println("方案评分: 带牌=" + carryCards.stream().map(c -> c.cardMod).collect(Collectors.toList()) + + ", 剩余手数=" + analysis.estimatedTurns + ", 牌型=" + analysis.bestPattern + ", 分数=" + score); + + return score; + } + + /** + * 分析剩余手牌(优化版) + */ + private static RemainAnalysis analyzeRemainingHand(List remainingCards) { + RemainAnalysis analysis = new RemainAnalysis(); + + if (remainingCards.isEmpty()) { + analysis.estimatedTurns = 0; + analysis.bestPattern = "出完"; + return analysis; + } + + // 检查是否能形成顺子 + List> straights = findStraightsAbove(remainingCards, -1, remainingCards.size()); + if (!straights.isEmpty()) { + analysis.estimatedTurns = 1; + analysis.bestPattern = "顺子出完"; + return analysis; + } + + // 检查是否能形成连对 + if (remainingCards.size() % 2 == 0) { + List> consecutivePairs = findConsecutivePairsAbove(remainingCards, -1, remainingCards.size() / 2); + if (!consecutivePairs.isEmpty()) { + analysis.estimatedTurns = 1; + analysis.bestPattern = "连对出完"; + return analysis; + } + } + + // 检查单次出完 + if (canPlayInOneTurn(remainingCards)) { + analysis.estimatedTurns = 1; + analysis.bestPattern = "一次性出完"; + return analysis; + } + + // 估算手数 + analysis.estimatedTurns = estimateTurnsForRemaining(remainingCards, getValueCount(remainingCards)); + analysis.bestPattern = getBestPattern(remainingCards); + + return analysis; + } + + + /** + * 检查是否能一次性出完 + */ + private static boolean canPlayInOneTurn(List cards) { + int type = getPlayType(cards); + return type != -1; // 如果能识别出牌型,就能一次性出完 + } + + /** + * 估算剩余手牌需要的手数 + */ + private static int estimateTurnsForRemaining(List cards, Map count) { + int turns = 0; + + // 统计各种牌型 + int singles = 0; + int pairs = 0; + int trios = 0; + int bombs = 0; + + for (int cardCount : count.values()) { + switch (cardCount) { + case 1: + singles++; + break; + case 2: + pairs++; + break; + case 3: + trios++; + break; + case 4: + bombs++; + break; + } + } + + // 炸弹 + turns += bombs; + + // 三带 + int trioTurns = trios; + if (trios > 0 && singles + pairs >= trios) { + // 三带可以带牌 + trioTurns = trios; + } + turns += trioTurns; + + // 对子 + int pairTurns = pairs; + turns += pairTurns; + + // 单牌(减去被三带带走的) + int remainingSingles = Math.max(0, singles - trios); + turns += remainingSingles; + + return Math.max(1, turns); + } + + /** + * 获取最佳剩余牌型 + */ + private static String getBestPattern(List cards) { + if (cards.size() >= 5) { + List> straights = findStraightsAbove(cards, -1, 5); + if (!straights.isEmpty()) return "有顺子"; + + List> consecutivePairs = findConsecutivePairsAbove(cards, -1, 2); + if (!consecutivePairs.isEmpty()) return "有连对"; + } + + Map count = getValueCount(cards); + if (count.values().stream().anyMatch(c -> c >= 2)) { + return "有对子"; + } + + return "多单牌"; + } + + + /** + * 选择最优带牌方案 + */ + private static CarryOption selectBestCarryOption(List options) { + return options.stream() + .max(Comparator.comparingInt(option -> option.score)) + .orElse(null); + } + + /** + * 保守策略(当智能分析失败时使用) + */ + private static List findConservativeCarryCards(List availableCards, int requiredCount) { + List carryCards = new ArrayList<>(); + + // 优先使用小对子 + List smallPairs = getAvailableSmallPairs(availableCards); + for (int pairValue : smallPairs) { + if (carryCards.size() + 2 <= requiredCount) { + List pair = availableCards.stream() + .filter(card -> card.cardMod == pairValue) + .limit(2) + .collect(Collectors.toList()); + if (pair.size() == 2) { + carryCards.addAll(pair); + availableCards.removeAll(pair); + } + } + } + + // 然后使用小单牌 + if (carryCards.size() < requiredCount) { + List singles = getAvailableSingles(availableCards).stream() + .filter(card -> card.cardMod <= 10) + .collect(Collectors.toList()); + for (CardObj single : singles) { + if (carryCards.size() < requiredCount) { + carryCards.add(single); + availableCards.remove(single); + } + } + } + + // 最后使用任意牌 + if (carryCards.size() < requiredCount) { + List allCards = new ArrayList<>(availableCards); + allCards.sort(Comparator.comparingInt(card -> card.cardMod)); + for (CardObj card : allCards) { + if (carryCards.size() < requiredCount) { + carryCards.add(card); + } + } + } + + return carryCards; + } + + // 辅助类 + static class CarryOption { + List carryCards; + List remainingCards; + int remainingTurns; + String remainingPattern; + int score; + } + + static class RemainAnalysis { + int estimatedTurns; + String bestPattern; + } + + + // 辅助方法:获取当前可用的单牌 + private static List getAvailableSingles(List cards) { + Map count = getValueCount(cards); + return cards.stream() + .filter(card -> count.getOrDefault(card.cardMod, 0) == 1) + .sorted(Comparator.comparingInt(card -> card.cardMod)) + .collect(Collectors.toList()); + } + + // 辅助方法:获取当前可用的小对子 + private static List getAvailableSmallPairs(List cards) { + Map count = getValueCount(cards); + return count.entrySet().stream() + .filter(entry -> entry.getValue() == 2) + .filter(entry -> entry.getKey() <= 10) + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + } + + // 辅助方法:获取当前所有对子 + private static List getAllAvailablePairs(List cards) { + Map count = getValueCount(cards); + return count.entrySet().stream() + .filter(entry -> entry.getValue() >= 2) + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + } + + /** + * 寻找能一次性或接近一次性出完的牌型 (7-9张专用) + */ + private static List findQuickWinPlay(List handCards) { + List sortedHand = new ArrayList<>(handCards); + Collections.sort(sortedHand); + Map valueCount = getValueCount(handCards); + + List cardObjs = tryPlayTrioWithOne(handCards, valueCount); + if (!cardObjs.isEmpty()) { + System.out.println("7-9张专用 ++ 先出三带"); + return cardObjs; + } + + List chainPair = findChainPair(handCards); + if (!chainPair.isEmpty()) { + System.out.println("7-9张专用 ++ 先出连对"); + return chainPair; + } + + List play = tryPlayStraight(handCards); + if (!play.isEmpty()) { + System.out.println("7-9张专用 ++ 先出顺子"); + return play; + } + return findControlPlayForQuickWin(handCards); + } + + private static List findChainPair(List handCards) { + if (handCards.size() < 4) { + return new ArrayList<>(); + } + + Map valueCount = getValueCount(handCards); + List pairValues = valueCount.entrySet().stream() + .filter(entry -> entry.getValue() >= 2) + .map(Map.Entry::getKey) + .filter(value -> value < 15) + .sorted() + .collect(Collectors.toList()); + + if (pairValues.size() < 2) { + return new ArrayList<>(); + } + + // 寻找所有连对 + List> allChains = new ArrayList<>(); + List currentChain = new ArrayList<>(); + + for (int i = 0; i < pairValues.size(); i++) { + if (currentChain.isEmpty()) { + currentChain.add(pairValues.get(i)); + } else { + int lastValue = currentChain.get(currentChain.size() - 1); + int currentValue = pairValues.get(i); + + if (currentValue == lastValue + 1) { + currentChain.add(currentValue); + } else { + if (currentChain.size() >= 2) { + allChains.add(new ArrayList<>(currentChain)); + } + currentChain.clear(); + currentChain.add(currentValue); + } + } + } + + if (currentChain.size() >= 2) { + allChains.add(new ArrayList<>(currentChain)); + } + + if (allChains.isEmpty()) { + return new ArrayList<>(); + } + + // 优先选择最长的连对 + List bestChain = allChains.stream() + .max(Comparator.>comparingInt(List::size)) + .orElse(allChains.get(0)); // 如果没有最长的,就取第一个找到的 + + // 构建牌列表 + List result = new ArrayList<>(); + for (int value : bestChain) { + result.addAll(getPairByValue(handCards, value)); + } + + return result; + } + + + private static List tryPlayStraight(List handCards) { + final int MIN_STRAIGHT_VALUE = 3; + final int MAX_STRAIGHT_VALUE = 14; + + List distinctValues = handCards.stream() + .map(c -> c.cardMod) + .filter(value -> value >= MIN_STRAIGHT_VALUE && value <= MAX_STRAIGHT_VALUE) + .distinct() + .sorted() + .collect(Collectors.toList()); + + System.out.println("可用的顺子值: " + distinctValues); + + // 如果可用牌值少于5张,直接返回 + if (distinctValues.size() < 5) { + return new ArrayList<>(); + } + int maxPossibleLength = Math.min(distinctValues.size(), 12); // 最多12张 + + for (int length = maxPossibleLength; length >= 5; length--) { + for (int i = 0; i <= distinctValues.size() - length; i++) { + if (isConsecutive(distinctValues, i, length)) { + List straightValues = distinctValues.subList(i, i + length); + System.out.println("找到" + length + "张顺子: " + straightValues); + return buildStraight(handCards, straightValues); + } + } + } + + return new ArrayList<>(); + } + + /** + * 检查是否能一次性出完 + */ + private static List findOneShotPlay(List handCards) { + int handSize = handCards.size(); + + // 检查顺子 + if (handSize >= 5) { + for (int len = handSize; len >= 5; len--) { + List> straights = findStraightsAbove(handCards, -1, len); + if (!straights.isEmpty()) { + // 正好等于手牌数量的顺子 + for (List straight : straights) { + if (straight.size() == handSize) { + return straight; + } + } + } + } + } + + // 检查连对 + if (handSize >= 4 && handSize % 2 == 0) { + int pairCount = handSize / 2; + List> consecutivePairs = findConsecutivePairsAbove(handCards, -1, pairCount); + if (!consecutivePairs.isEmpty()) { + return consecutivePairs.get(0); + } + } + + // 检查炸弹+单牌组合 + Map valueCount = getValueCount(handCards); + for (Map.Entry entry : valueCount.entrySet()) { + if (entry.getValue() == 4 && handSize == 6) { + // 四带二 + List bomb = handCards.stream() + .filter(card -> card.cardMod == entry.getKey()) + .limit(4) + .collect(Collectors.toList()); + + List otherCards = handCards.stream() + .filter(card -> card.cardMod != entry.getKey()) + .collect(Collectors.toList()); + + if (otherCards.size() >= 2) { + List result = new ArrayList<>(bomb); + result.addAll(otherCards.subList(0, 2)); + return result; + } + } + } + + return new ArrayList<>(); + } + + /** + * 寻找顺子组合出牌 + */ + private static List findStraightComboPlay(List handCards) { + int handSize = handCards.size(); + + // 尝试各种顺子长度 + for (int straightLen = 5; straightLen <= Math.min(8, handSize - 1); straightLen++) { + List> straights = findStraightsAbove(handCards, -1, straightLen); + + for (List straight : straights) { + List remainHand = removeCards(handCards, straight); + int remainSize = remainHand.size(); + + // 剩余牌很少,容易处理 + if (remainSize <= 3) { + // 检查剩余牌是否都是大牌或者能形成控制 + boolean remainIsGood = isRemainHandGood(remainHand); + if (remainIsGood) { + System.out.println("顺子" + straightLen + "张 + 剩余" + remainSize + "张好牌"); + return straight; + } + } + + // 剩余牌能形成对子或三张 + if (remainSize == 2 || remainSize == 3) { + Map remainCount = getValueCount(remainHand); + boolean isPairOrTrio = remainCount.values().stream().allMatch(count -> count == remainSize); + if (isPairOrTrio) { + System.out.println("顺子" + straightLen + "张 + 对子/三张"); + return straight; + } + } + } + } + + return new ArrayList<>(); + } + + /** + * 寻找连对组合出牌 + */ + private static List findConsecutivePairComboPlay(List handCards) { + int handSize = handCards.size(); + + for (int pairCount = 2; pairCount <= 4; pairCount++) { + int requiredCards = pairCount * 2; + if (requiredCards > handSize) continue; + + List> consecutivePairs = findConsecutivePairsAbove(handCards, -1, pairCount); + + for (List pairs : consecutivePairs) { + List remainHand = removeCards(handCards, pairs); + int remainSize = remainHand.size(); + + // 剩余牌处理逻辑 + if (remainSize <= 3 && isRemainHandGood(remainHand)) { + System.out.println(pairCount + "连对 + 剩余" + remainSize + "张好牌"); + return pairs; + } + + // 剩余牌能形成顺子 + if (remainSize >= 5) { + List> remainStraights = findStraightsAbove(remainHand, -1, remainSize); + if (!remainStraights.isEmpty()) { + System.out.println(pairCount + "连对 + " + remainSize + "顺子"); + return pairs; + } + } + } + } + + return new ArrayList<>(); + } + + /** + * 寻找三带类组合出牌 + */ + private static List findTrioComboPlay(List handCards) { + Map valueCount = getValueCount(handCards); + int handSize = handCards.size(); + + // 查找三张 + List trioValues = valueCount.entrySet().stream() + .filter(entry -> entry.getValue() == 3) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + for (int trioValue : trioValues) { + List trio = handCards.stream() + .filter(card -> card.cardMod == trioValue) + .limit(3) + .collect(Collectors.toList()); + + List remainAfterTrio = removeCards(handCards, trio); + + + // 三带二 + if (handSize == 5 && remainAfterTrio.size() == 2) { + Map remainCount = getValueCount(remainAfterTrio); + if (remainCount.size() == 1) { // 是对子 + List result = new ArrayList<>(trio); + result.addAll(remainAfterTrio); + return result; + } + } + } + + return new ArrayList<>(); + } + + /** + * 为快速出完寻找控制性出牌 + */ + private static List findControlPlayForQuickWin(List handCards) { + return Collections.singletonList(findMediumCardForQuickWin(handCards)); + } + + /** + * 检查剩余手牌质量 + */ + private static boolean isRemainHandGood(List remainHand) { + if (remainHand.isEmpty()) return true; + + // 剩余都是大牌 + boolean allBigCards = remainHand.stream().allMatch(card -> card.cardMod >= 13); + if (allBigCards) return true; + + // 剩余牌能形成对子或三张 + Map remainCount = getValueCount(remainHand); + if (remainCount.size() == 1) return true; // 全是同值牌 + + // 剩余牌值接近,容易组合 + int maxValue = remainHand.stream().mapToInt(c -> c.cardMod).max().orElse(0); + int minValue = remainHand.stream().mapToInt(c -> c.cardMod).min().orElse(0); + if (maxValue - minValue <= 2) return true; + + return false; + } + + + private static List findControlPair(List handCards) { + Map valueCount = getValueCount(handCards); + + // 1. 先找出所有对子,按牌值分组 + List allPairs = valueCount.entrySet().stream() + .filter(entry -> entry.getValue() >= 2) + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + + if (allPairs.isEmpty()) { + return new ArrayList<>(); + } + + return getPairByValue(handCards, allPairs.get(0)); + } + + private static boolean hasControlAfterPlay(List handCards, int playPairValue, Map valueCount) { + // 模拟出掉这个对子后,是否还有控制牌 + Map tempCount = new HashMap<>(valueCount); + tempCount.put(playPairValue, tempCount.get(playPairValue) - 2); + + // 检查是否还有: + // a) 更大的对子 + return tempCount.entrySet().stream() + .anyMatch(entry -> entry.getValue() >= 2 && entry.getKey() > playPairValue); + } + + + private static List getPairByValue(List handCards, int value) { + return handCards.stream() + .filter(card -> card.cardMod == value) + .limit(2) + .collect(Collectors.toList()); + } + + /** + * 为快速出完寻找中等牌值 + */ + private static CardObj findMediumCardForQuickWin(List handCards) { + List sortedHand = new ArrayList<>(handCards); + Collections.sort(sortedHand); + + +// // 优先选择9-J之间的牌 +// List optimalCards = sortedHand.stream() +// .filter(card -> card.cardMod >= 9 && card.cardMod <= 11) +// .collect(Collectors.toList()); +// +// if (!optimalCards.isEmpty()) { +// return optimalCards.get(optimalCards.size() / 2); // 取中间值 +// } +// +// // 次选7-Q之间的牌 +// List fallbackCards = sortedHand.stream() +// .filter(card -> card.cardMod >= 7 && card.cardMod <= 12) +// .collect(Collectors.toList()); +// +// if (!fallbackCards.isEmpty()) { +// return fallbackCards.get(fallbackCards.size() / 2); +// } +// + // 默认取中间位置的牌 + int middleIndex = sortedHand.size() / 2; + return sortedHand.get(middleIndex); + } + + + private static List tryPlayTrioWithOne(List handCards, Map valueCount) { + System.out.println("三张牌型"); + // 先按牌值排序(从小到大) + handCards.sort(Comparator.comparingInt(c -> c.cardMod)); + + // 查找最小的三张 + for (CardObj card : handCards) { + if (valueCount.getOrDefault(card.cardMod, 0) >= 3) { + List trio = handCards.stream() + .filter(c -> c.cardMod == card.cardMod) + .limit(3) + .collect(Collectors.toList()); + + // 寻找两张带牌,优先不破坏对子和顺子牌型 + List sideCards = findBestSideCardsForTrio(handCards, card.cardMod, valueCount); + + if (sideCards.size() >= 2) { + List result = new ArrayList<>(trio); + result.addAll(sideCards.subList(0, 2)); + System.out.println("全是三张牌型" + result); + + return result; + } + } + } + return new ArrayList<>(); + } + + private static List findBestSideCardsForTrio(List handCards, int trioValue, + Map valueCount) { + List candidateCards = new ArrayList<>(); + + + // 收集所有不是三张牌本身的牌,并且不破坏其他三张牌 + for (CardObj card : handCards) { + if (card.cardMod != trioValue) { + // 检查这张牌是否属于其他三张牌(不拆散其他三张) + if (valueCount.getOrDefault(card.cardMod, 0) < 3) { + candidateCards.add(card); + } + } + } + + + // 按优先级选择带牌策略 + List bestChoice = new ArrayList<>(); + + + // 策略1:优先选择单张牌(不破坏对子和三张) + List singleCards = findSingleCards(candidateCards, valueCount); + if (singleCards.size() >= 2) { + return singleCards.subList(0, Math.min(2, singleCards.size())); + } + + // 策略2:选择最小的对子(如果只剩一个对子,拆开带) + List pairCards = findSmallestPair(candidateCards, valueCount); + if (!pairCards.isEmpty()) { + if (pairCards.size() >= 2) { + return pairCards.subList(0, 2); // 带整个对子 + } else { + bestChoice.add(pairCards.get(0)); // 先带一张 + } + } + + // 策略3:如果还不够,选择其他单张(仍然不破坏三张) + for (CardObj card : candidateCards) { + if (bestChoice.size() < 2 && !bestChoice.contains(card)) { + // 再次确认不破坏三张牌 + if (valueCount.getOrDefault(card.cardMod, 0) < 3) { + bestChoice.add(card); + } + } + } + + return bestChoice; + } + + /** + * 查找最小的对子(不破坏三张牌) + */ + private static List findSmallestPair(List cards, Map valueCount) { + List pairCards = new ArrayList<>(); + + // 找出最小的对子(且不属于三张) + for (CardObj card : cards) { + if (valueCount.getOrDefault(card.cardMod, 0) == 2) { // 严格等于2,不是三张 + List pair = cards.stream() + .filter(c -> c.cardMod == card.cardMod) + .limit(2) + .collect(Collectors.toList()); + + // 如果这个对子还没被加入,且比当前选择更小 + if (pairCards.isEmpty() || + (pair.size() >= 2 && pair.get(0).cardMod < pairCards.get(0).cardMod)) { + pairCards = pair; + } + } + } + + return pairCards; + } + + /** + * 查找单张牌(不破坏三张牌) + */ + private static List findSingleCards(List cards, Map valueCount) { + List singleCards = new ArrayList<>(); + + // 找出真正的单张牌(没有其他同值的牌,且不属于三张) + for (CardObj card : cards) { + if (valueCount.getOrDefault(card.cardMod, 0) == 1) { + singleCards.add(card); + } + } + + // 按牌值排序 + singleCards.sort(Comparator.comparingInt(c -> c.cardMod)); + return singleCards; + } + + /** + * 找最大单牌 + */ + private static CardObj findMaxCard(List handCards) { + return handCards.stream() + .max(Comparator.comparingInt(c -> c.cardMod)) + .orElse(handCards.get(0)); + } + + + /** + * 选择最佳首出牌(保护大牌) + */ + private static List selectBestFirstPlayWithBigCardProtection(List handCards, HandAnalysis analysis) { + List candidates = new ArrayList<>(); + + // 为每个可能的首出牌分析后续影响 + for (List play : analysis.allPossiblePlays) { + int playType = getPlayType(play); + + // 首轮禁止出炸弹 + if (playType == Config.TYPE_ZHA) { + System.out.println("首轮跳过炸弹: " + play); + continue; + } + + // 首轮禁止出包含过多大牌的牌型 + if (containsTooManyBigCardsInFirstRound(play)) { + System.out.println("首轮跳过多大牌组合: " + play); + continue; + } + + List remainHand = removeCards(handCards, play); + RemainHandAnalysis remainAnalysis = analyzeRemainHand(remainHand); + + PlayOption option = new PlayOption(play, playType); + PlayOptionWithRemain optionWithRemain = new PlayOptionWithRemain(option, remainHand, remainAnalysis); + + // 基于整体牌型特征评分(修复炸弹评分) + calculateFirstRoundScore(optionWithRemain, analysis, handCards); + candidates.add(optionWithRemain); + } + + // 选择最优候选 + return selectBestCandidate(candidates, analysis, handCards); + } + + /** + * 首轮专用评分(禁止炸弹和过大牌型) + */ + private static void calculateFirstRoundScore(PlayOptionWithRemain option, HandAnalysis analysis, List originalHand) { + int score = 0; + int playType = option.type; + + // 1. 首轮绝对禁止炸弹 + if (playType == Config.TYPE_ZHA) { + score -= 10000; + option.totalScore = score; + return; + } + + // 2. 禁止拆炸弹 + if (isBreakingBomb(option.cards, originalHand)) { + score -= 1000; + option.totalScore = score; + return; + } + + // 3. 首轮限制大牌使用 + if (containsTooManyBigCardsInFirstRound(option.cards)) { + score -= 500; + } + + // 4. 根据牌型特征动态评分 + score += evaluateFirstRoundByHandStructure(option, analysis, originalHand); + + // 5. 剩余手牌质量 + score += option.analysis.remainScore; + + // 6. 出牌数量考虑 + score += Math.min(option.cards.size() * 5, 20); // 限制最大加分 + + option.totalScore = score; + } + + /** + * 首轮大牌限制检查 + */ + private static boolean containsTooManyBigCardsInFirstRound(List cards) { + long bigCardCount = cards.stream() + .filter(card -> card.cardMod >= 10) + .count(); + + // 首轮限制:不能超过总牌数的1/3是大牌 + return bigCardCount >= (cards.size() + 2) / 3; + } + + /** + * 首轮专用结构评估 + */ + private static int evaluateFirstRoundByHandStructure(PlayOptionWithRemain option, HandAnalysis analysis, List originalHand) { + int score = 0; + int playType = option.type; + int handSize = originalHand.size(); + + // === 首轮核心策略:出小牌保留控制力 === + + // 特征1: 多小单牌 → 优先出能带走小牌的牌型 + if (analysis.smallSingleCount >= 3) { + if (playType == Config.TYPE_3_1 || playType == Config.TYPE_SHUNZI) { + score += 25; + } + if (playType == Config.TYPE_DANPAI && isSmallCard(option.cards.get(0))) { + score += 20; // 首轮出小单牌积极处理 + } + } + + // 特征2: 长顺子/连对 → 优先出中等长度的组合 + if (analysis.maxStraightLength >= 6 && playType == Config.TYPE_SHUNZI) { + int length = option.cards.size(); + if (length >= 5 && length <= 7) { + score += 14; // 中等长度顺子优先 + } + } + + if (analysis.maxConsecutivePairs >= 3 && playType == Config.TYPE_LIANDUI) { + int pairCount = option.cards.size() / 2; + if (pairCount >= 2 && pairCount <= 3) { + score += 18; // 中等连对优先 + } + } + + // 特征3: 首轮避免使用大牌(除非手牌很少) + if (handSize > 6) { + int maxPlayValue = option.cards.stream().mapToInt(c -> c.cardMod).max().orElse(0); + if (maxPlayValue >= 14) { // A, 2 + score -= 15; + } else if (maxPlayValue >= 13) { // K + score -= 8; + } + } + + // 特征4: 对子多 → 优先出小对子 + if (analysis.pairCount >= 4 && playType == Config.TYPE_DUIZI) { + int maxValue = option.cards.stream().mapToInt(c -> c.cardMod).max().orElse(0); + if (maxValue <= 10) { // 小对子加分 + score += 15; + } + } + + + // 策略1: 优先处理难以组合的牌 + if (playType == Config.TYPE_DANPAI && isSmallCard(option.cards.get(0))) { + score += 10; // 首轮积极出小单牌 + } + + + if (playType == Config.TYPE_3_1) { + // 检查三带一是否带的是小牌 + CardObj singleCard = findSingleCardInTrioWithOne(option.cards); + if (singleCard != null && isSmallCard(singleCard)) { + score += 15; // 三带小牌优先 + } + } + + return score; + } + + /** + * 在三带一中找到单牌 + */ + private static CardObj findSingleCardInTrioWithOne(List cards) { + if (cards.size() != 4) return null; + + Map> groups = groupByValue(cards); + for (Map.Entry> entry : groups.entrySet()) { + if (entry.getValue().size() == 1) { + return entry.getValue().get(0); + } + } + return null; + } + + /** + * 选择最佳候选(首轮专用) + */ + private static List selectBestCandidate(List candidates, HandAnalysis analysis, List originalHand) { + // 过滤掉无效选项 + List validCandidates = candidates.stream() + .filter(opt -> opt.totalScore > -500 && !isBomb(opt.type)) + .collect(Collectors.toList()); + + if (validCandidates.isEmpty()) { + // 没有合适选择时,出最小单牌 + return Arrays.asList(findMediumCardForFirstRound(originalHand)); + } + + // 按分数排序 + validCandidates.sort((a, b) -> Integer.compare(b.totalScore, a.totalScore)); + + // 输出前3个候选用于调试 + System.out.println("=== 首轮候选分析 ==="); + for (int i = 0; i < Math.min(3, validCandidates.size()); i++) { + PlayOptionWithRemain opt = validCandidates.get(i); + System.out.println("候选" + (i + 1) + ": " + opt.cards + " 类型:" + opt.type + " 分数:" + opt.totalScore); + } + + return validCandidates.get(0).cards; + } + + private static CardObj findMediumCardForFirstRound(List handCards) { + List sortedHand = new ArrayList<>(handCards); + Collections.sort(sortedHand); + + // 优先选择8-Q之间的牌(中等牌值) + List mediumCards = sortedHand.stream() + .filter(card -> card.cardMod >= 8 && card.cardMod <= 12) // 8到Q + .collect(Collectors.toList()); + + if (!mediumCards.isEmpty()) { + // 取中等牌中最小的 + return mediumCards.get(0); + } + + // 如果没有8-Q的牌,选择7-K之间的牌 + List fallbackCards = sortedHand.stream() + .filter(card -> card.cardMod >= 7 && card.cardMod <= 13) // 7到K + .collect(Collectors.toList()); + + if (!fallbackCards.isEmpty()) { + return fallbackCards.get(0); + } + + // 实在没有合适的中等牌,选择中间位置的牌 + int middleIndex = sortedHand.size() / 2; + return sortedHand.get(middleIndex); + } + + private static CardObj findSmallestCard(List handCards) { + return handCards.stream() + .min(Comparator.comparingInt(c -> c.cardMod)) + .orElse(handCards.get(0)); + } + + /** + * 检查是否为炸弹 + */ + private static boolean isBomb(int playType) { + return playType == Config.TYPE_ZHA; + } + + /** + * 收集所有可能的首出牌(排除炸弹) + */ + private static List> collectAllPossibleFirstPlays(List handCards) { + List> allPlays = new ArrayList<>(); + + // 单牌(限制大牌) + allPlays.addAll(findSinglesAbove(handCards, -1).stream() + .filter(play -> play.get(0).cardMod <= 12) // 首轮不出K以上的单牌 + .collect(Collectors.toList())); + + // 对子 + allPlays.addAll(findPairsAbove(handCards, -1)); + + // 顺子 (5-8张) + for (int len = 5; len <= 8; len++) { + allPlays.addAll(findStraightsAbove(handCards, -1, len)); + } + + // 连对 (2-4连对) + for (int pairCount = 2; pairCount <= 4; pairCount++) { + allPlays.addAll(findConsecutivePairsAbove(handCards, -1, pairCount)); + } + + // 三带一 + allPlays.addAll(findTrioWithOneAbove(handCards, -1)); + + + return allPlays; + } + + + /** + * 基于整体牌型特征评分(保护大牌) + */ + private static void calculateStructuralScoreWithBigCardProtection(PlayOptionWithRemain option, + HandAnalysis analysis, List originalHand) { + int score = 0; + + // 1. 禁止拆炸弹 + if (isBreakingBomb(option.cards, originalHand)) { + score -= 1000; + option.totalScore = score; + return; + } + + // 2. 修复:保护大牌扣分机制 + score -= calculateBigCardPenalty(option.cards, originalHand); + + // 3. 根据牌型特征动态评分 + score += evaluateByHandStructureWithBigCardProtection(option, analysis, originalHand); + + // 4. 剩余手牌质量 + score += option.analysis.remainScore; + + // 5. 出牌数量考虑 + score += option.cards.size() * 8; + + option.totalScore = score; + } + + /** + * 计算大牌惩罚分数(出大牌扣分) + */ + private static int calculateBigCardPenalty(List playCards, List originalHand) { + int penalty = 0; + + // 统计手牌中的大牌数量 + long originalBigCards = originalHand.stream() + .filter(card -> card.cardMod >= 13) // K, A, 2 + .count(); + + // 统计出牌中的大牌数量 + long playBigCards = playCards.stream() + .filter(card -> card.cardMod >= 13) + .count(); + + // 如果出牌中包含大牌,根据比例扣分 + if (playBigCards > 0) { + double bigCardRatio = (double) playBigCards / playCards.size(); + + // 大牌比例越高,扣分越多 + penalty += (int) (bigCardRatio * 50); + + // 如果手牌中大牌不多,扣分更重 + if (originalBigCards <= 3) { + penalty += 30; + } + + // 特别保护2(A)和A(K) + for (CardObj card : playCards) { + if (card.cardMod == 15) { // 2 + penalty += 25; + } else if (card.cardMod == 14) { // A + penalty += 15; + } else if (card.cardMod == 13) { // K + penalty += 10; + } + } + } + + return penalty; + } + + /** + * 根据手牌结构特征评估(保护大牌) + */ + private static int evaluateByHandStructureWithBigCardProtection(PlayOptionWithRemain option, + HandAnalysis analysis, List originalHand) { + int score = 0; + int playType = option.type; + int handSize = originalHand.size(); + + // === 关键修复:手牌很多时的保守策略 === + if (handSize > 10) { + // 手牌多时,优先出小牌,保护大牌 + if (playType == Config.TYPE_DANPAI) { + CardObj card = option.cards.get(0); + if (card.cardMod <= 10) { // 10及以下的小牌 + score += 20; + } else if (card.cardMod >= 13) { // K以上的大牌 + score -= 25; // 大牌扣分 + } + } + + // 手牌多时优先出顺子、连对等组合牌 + if (playType == Config.TYPE_SHUNZI || playType == Config.TYPE_LIANDUI) { + // 检查顺子/连对中是否包含大牌 + boolean containsBigCard = option.cards.stream().anyMatch(card -> card.cardMod >= 13); + if (!containsBigCard) { + score += 35; // 不含大牌的顺子/连对大幅加分 + } else { + score += 15; // 含大牌的顺子/连对适当加分 + } + } + + // 手牌多时避免首出对子(容易被压) + if (playType == Config.TYPE_DUIZI) { + int maxValue = option.cards.stream().mapToInt(c -> c.cardMod).max().orElse(0); + if (maxValue >= 12) { // Q以上的对子 + score -= 20; + } + } + } + + // === 手牌较少时的激进策略 === + if (handSize <= 6) { + if (handSize <= 3) { + // 手牌很少时,优先出最大的牌夺取出牌权 + if (playType == Config.TYPE_DANPAI) { + CardObj card = option.cards.get(0); + if (card.cardMod >= 13) { // K, A, 2 + score += 50; // 大单牌大幅加分 + } + } + // 避免出小对子 + if (playType == Config.TYPE_DUIZI) { + int maxValue = option.cards.stream().mapToInt(c -> c.cardMod).max().orElse(0); + if (maxValue < 10) { // 小对子扣分 + score -= 30; + } + } + } + + // 手牌较少时,优先出能一次出完或接近出完的牌型 + if (option.cards.size() == handSize) { + score += 80; // 能一次出完大幅加分 + } else if (option.cards.size() >= handSize - 2) { + score += 40; // 接近出完加分 + } + } + + // 特征1: 小单牌多 → 优先出能带走小牌的牌型 + if (analysis.smallSingleCount >= 3) { + if (playType == Config.TYPE_3_1 || playType == Config.TYPE_SHUNZI) { + score += 30; + } + if (playType == Config.TYPE_DANPAI && isSmallCard(option.cards.get(0))) { + score += 15; + } + } + + // 特征2: 连对长 → 优先出连对 + if (analysis.maxConsecutivePairs >= 3) { + if (playType == Config.TYPE_LIANDUI) { + score += 25; + } + } + + // 特征3: 大牌多 → 可以出控制力强的牌型 + if (analysis.bigCardCount >= 2) { + if (playType == Config.TYPE_LIANDUI || playType == Config.TYPE_SHUNZI) { + score += 15; + } + } + + // 特征4: 对子多 → 优先出对子或连对 + if (analysis.pairCount >= 4) { + if (playType == Config.TYPE_DUIZI || playType == Config.TYPE_LIANDUI) { + score += 20; + } + } + + // 特征5: 避免首出带大牌的牌型(除非手牌很少) + if (containsBigCards(option.cards) && analysis.smallSingleCount > 0 && handSize > 6) { + score -= 25; + } + + // === 新增:出牌控制力评估 === + score += evaluatePlayControl(option, originalHand); + + return score; + } + + + /** + * 手牌分析类 + */ + static class HandAnalysis { + Map features = new HashMap<>(); + List> allPossiblePlays = new ArrayList<>(); + Map valueCount; + + // 关键特征 + int smallSingleCount; // 小单牌数量(3-8) + int bigCardCount; // 大牌数量(2,A,K) + int pairCount; // 对子数量 + int trioCount; // 三张数量 + int bombCount; // 炸弹数量 + int maxStraightLength; // 最长顺子 + int maxConsecutivePairs; // 最长连对 + boolean hasControlCards; // 是否有控制牌 + } + + /** + * 评估出牌控制力 + */ + private static int evaluatePlayControl(PlayOptionWithRemain option, List originalHand) { + int score = 0; + int playType = option.type; + + // 计算出牌中的最大牌值 + int maxPlayValue = option.cards.stream().mapToInt(c -> c.cardMod).max().orElse(0); + + // 单牌:大牌有控制力 + if (playType == Config.TYPE_DANPAI) { + if (maxPlayValue >= 15) score += 40; // 2 + else if (maxPlayValue >= 14) score += 30; // A + else if (maxPlayValue >= 13) score += 20; // K + else if (maxPlayValue <= 8) score -= 10; // 小单牌控制力差 + } + + // 对子:大对子有控制力 + if (playType == Config.TYPE_DUIZI) { + if (maxPlayValue >= 13) score += 25; // K对以上 + else if (maxPlayValue <= 9) score -= 15; // 小对子容易被压 + } + + // 顺子、连对:长度越长越难被压 + if (playType == Config.TYPE_SHUNZI || playType == Config.TYPE_LIANDUI) { + score += option.cards.size() * 2; + } + + // 炸弹:绝对控制力 + if (playType == Config.TYPE_ZHA) { + score += 60; + } + + return score; + } + + /** + * 找到最好的单牌出牌 + */ + private static List findBestCardToPlay(HuNanPaoDeKuai huNanPaoDeKuai,List handCards, Map> seatRemainHistory) { + Map otherSeatsLastRemain = getOtherSeatsLastRemain(huNanPaoDeKuai.seat, seatRemainHistory); + boolean hasAnyEqualOne = otherSeatsLastRemain.values().stream().anyMatch(value -> value == 1); + if (hasAnyEqualOne) { + // 优先考虑能出完的牌型:三带、连对、顺子 + System.out.println("检测到对手只剩一张牌,优先查找能出完的牌型"); + + // 检查三带(三带一或三带对) + List trioPlay = findQuickWinPlay(handCards); + if (!trioPlay.isEmpty()) { + System.out.println("找到能出完的三带牌型: " + trioPlay); + return trioPlay; + } + + // 检查连对 + List consecutivePairs = findChainPair(handCards); + if (!consecutivePairs.isEmpty()) { + System.out.println("找到能出完的连对牌型: " + consecutivePairs); + return consecutivePairs; + } + + // 如果没有能出完的牌型,再出对子压制 + System.out.println("未找到能出完的牌型,优先出对子压制"); + List pair = findAnyPair(handCards); + if (pair != null && pair.size() == 2) { + System.out.println("推荐出牌: " + pair + " (对子压制)"); + return pair; + } + } + return new ArrayList<>(); + } + + /** + * 在手牌中找任意对子 + */ + private static List findAnyPair(List handCards) { + Map> valueGroups = groupByValue(handCards); + return valueGroups.entrySet().stream() + .filter(entry -> entry.getValue().size() >= 2) + .min(Comparator.comparingInt(Map.Entry::getKey)) + .map(entry -> entry.getValue().subList(0, 2)) + .orElse(new ArrayList<>()); + } + + + // 辅助方法 + private static int countSmallSingles(List handCards, Map valueCount) { + return (int) valueCount.entrySet().stream() + .filter(entry -> entry.getValue() == 1 && entry.getKey() >= 3 && entry.getKey() <= 8) + .count(); + } + + private static int countBigCards(Map valueCount) { + return (int) valueCount.keySet().stream() + .filter(value -> value >= 13) // K, A, 2 + .count(); + } + + private static boolean isSmallCard(CardObj card) { + return card.cardMod >= 3 && card.cardMod <= 8; + } + + private static boolean containsBigCards(List cards) { + return cards.stream().anyMatch(card -> card.cardMod >= 13); + } + + /** + * 检查是否拆炸弹 + */ + private static boolean isBreakingBomb(List playCards, List originalHand) { + Map originalCount = getValueCount(originalHand); + Map playCount = getValueCount(playCards); + + for (Map.Entry entry : playCount.entrySet()) { + int cardValue = entry.getKey(); + int playCountForValue = entry.getValue(); + int originalCountForValue = originalCount.getOrDefault(cardValue, 0); + + // 如果原始有4张,但只出了3张或更少,就是拆炸弹 + if (originalCountForValue >= 4 && playCountForValue < 4) { + return true; + } + } + return false; + } + + /** + * 智能响应出牌(考虑出牌后手牌) + */ + private static List intelligentResponsePlayWithRemainConsideration(List handCards, int minCard, + int len, List opponentCards, int type) { + if (handCards.isEmpty()) return new ArrayList<>(); + + // 查找能压住对手的牌,并评估出牌后的手牌 + List validOptions = findValidResponseOptionsWithRemain(handCards, minCard, len, type); + + // 选择最优出牌 + PlayOptionWithRemain bestOption = selectBestOptionWithRemain(validOptions, handCards.size(), false); + + System.out.println("响应出牌选择: " + bestOption.cards + ", 类型: " + bestOption.type + + ", 综合分数: " + bestOption.totalScore + ", 预计手数: " + bestOption.getEstimatedTurns()); + return bestOption.cards; + } + + /** + * 分析所有出牌选项(考虑出牌后手牌) + */ + private static List analyzeAllPlayOptionsWithRemain(List handCards) { + List options = new ArrayList<>(); + + // 分析各种牌型 + addOptionsWithRemainAnalysis(options, analyzeSingles(handCards), handCards); + addOptionsWithRemainAnalysis(options, analyzePairs(handCards), handCards); + addOptionsWithRemainAnalysis(options, analyzeStraights(handCards), handCards); + addOptionsWithRemainAnalysis(options, analyzeConsecutivePairs(handCards), handCards); + addOptionsWithRemainAnalysis(options, analyzeBombs(handCards), handCards); + addOptionsWithRemainAnalysis(options, analyzeTrioWithOnes(handCards), handCards); + addOptionsWithRemainAnalysis(options, analyzeTrioWithPairs(handCards), handCards); + addOptionsWithRemainAnalysis(options, analyzeFourWithTwos(handCards), handCards); + + return options; + } + + /** + * 添加选项并进行出牌后手牌分析 + */ + private static void addOptionsWithRemainAnalysis(List target, + List source, + List originalHand) { + for (PlayOption option : source) { + // 计算出牌后的手牌 + List remainHand = calculateRemainHand(originalHand, option.cards); + + // 分析出牌后手牌的质量 + RemainHandAnalysis analysis = analyzeRemainHand(remainHand); + + // 创建包含出牌后分析的选项 + PlayOptionWithRemain optionWithRemain = new PlayOptionWithRemain(option, remainHand, analysis); + + // 计算综合分数 + calculateTotalScore(optionWithRemain, originalHand.size()); + + target.add(optionWithRemain); + } + } + + /** + * 计算出牌后的手牌 + */ + private static List calculateRemainHand(List originalHand, List playCards) { + List remainHand = new ArrayList<>(originalHand); + + // 从手牌中移除打出的牌 + for (CardObj playCard : playCards) { + Iterator iterator = remainHand.iterator(); + while (iterator.hasNext()) { + CardObj card = iterator.next(); + if (card.cardMod == playCard.cardMod && card.card == playCard.card) { + iterator.remove(); + break; + } + } + } + + return remainHand; + } + + /** + * 分析出牌后手牌质量 + */ + private static RemainHandAnalysis analyzeRemainHand(List remainHand) { + RemainHandAnalysis analysis = new RemainHandAnalysis(); + + if (remainHand.isEmpty()) { + analysis.isWin = true; + analysis.estimatedTurns = 0; + analysis.remainScore = 1000; // 出完牌的最高分 + return analysis; + } + + // 分析剩余手牌的各种特征 + analysis.cardCount = remainHand.size(); + analysis.maxCardValue = remainHand.stream().mapToInt(c -> c.cardMod).max().orElse(0); + analysis.minCardValue = remainHand.stream().mapToInt(c -> c.cardMod).min().orElse(0); + analysis.avgCardValue = remainHand.stream().mapToInt(c -> c.cardMod).average().orElse(0); + + // 统计剩余牌型组合 + Map valueCount = getValueCount(remainHand); + analysis.singleCount = (int) valueCount.values().stream().filter(count -> count == 1).count(); + analysis.pairCount = (int) valueCount.values().stream().filter(count -> count == 2).count(); + analysis.trioCount = (int) valueCount.values().stream().filter(count -> count >= 3).count(); + + // 新增:统计剩余大牌数量 + analysis.bigCardRemain = (int) remainHand.stream() + .filter(card -> card.cardMod >= 13) + .count(); + + // 分析顺子潜力 + analysis.straightPotential = analyzeStraightPotential(remainHand); + + // 分析连对潜力 + analysis.consecutivePairPotential = analyzeConsecutivePairPotential(remainHand); + + // 估算出完剩余牌需要的手数 + analysis.estimatedTurns = estimateRemainingTurns(remainHand, valueCount); + + // 计算剩余手牌分数 + analysis.remainScore = calculateRemainHandScore(analysis); + + return analysis; + } + + /** + * 估算剩余手牌需要的手数 + */ + private static int estimateRemainingTurns(List remainHand, Map valueCount) { + if (remainHand.isEmpty()) return 0; + + int estimatedTurns = 0; + + // 统计各种牌型组合 + int singleCount = 0; + int pairCount = 0; + int trioCount = 0; + int bombCount = 0; + + for (int count : valueCount.values()) { + switch (count) { + case 1: + singleCount++; + break; + case 2: + pairCount++; + break; + case 3: + trioCount++; + break; + case 4: + bombCount++; + break; + } + } + + // 估算手数(简化算法) + // 炸弹可以一次出完 + estimatedTurns += bombCount; + + // 三张可以带牌或单独出 + estimatedTurns += Math.max(trioCount, singleCount + pairCount - trioCount); + + // 对子和单牌 + estimatedTurns += Math.max(pairCount, singleCount - trioCount); + + // 确保至少1手 + estimatedTurns = Math.max(1, estimatedTurns); + + return estimatedTurns; + } + + /** + * 分析顺子潜力 + */ + private static int analyzeStraightPotential(List remainHand) { + List distinctValues = remainHand.stream() + .map(c -> c.cardMod) + .filter(value -> value != 15) // 排除2 + .distinct() + .sorted() + .collect(Collectors.toList()); + + if (distinctValues.size() < 5) return 0; + + int maxStraightLength = 1; + int currentLength = 1; + + for (int i = 1; i < distinctValues.size(); i++) { + if (distinctValues.get(i) - distinctValues.get(i - 1) == 1) { + currentLength++; + maxStraightLength = Math.max(maxStraightLength, currentLength); + } else { + currentLength = 1; + } + } + + return maxStraightLength >= 5 ? maxStraightLength : 0; + } + + /** + * 分析连对潜力 + */ + private static int analyzeConsecutivePairPotential(List remainHand) { + Map valueCount = getValueCount(remainHand); + List pairValues = valueCount.entrySet().stream() + .filter(entry -> entry.getValue() >= 2) + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + + if (pairValues.size() < 2) return 0; + + int maxConsecutivePairs = 1; + int currentConsecutive = 1; + + for (int i = 1; i < pairValues.size(); i++) { + if (pairValues.get(i) - pairValues.get(i - 1) == 1) { + currentConsecutive++; + maxConsecutivePairs = Math.max(maxConsecutivePairs, currentConsecutive); + } else { + currentConsecutive = 1; + } + } + + return maxConsecutivePairs >= 2 ? maxConsecutivePairs : 0; + } + + /** + * 计算剩余手牌分数 + */ + private static int calculateRemainHandScore(RemainHandAnalysis analysis) { + int score = 0; + + // 牌数越少分数越高 + score += (20 - analysis.cardCount) * 5; + + // 预计手数越少分数越高 + score += (10 - analysis.estimatedTurns) * 8; + + // 剩余大牌数量加分(保护大牌策略) + score += analysis.bigCardRemain * 12; + + // 有顺子潜力加分 + if (analysis.straightPotential >= 5) { + score += analysis.straightPotential * 3; + } + + // 有连对潜力加分 + if (analysis.consecutivePairPotential >= 2) { + score += analysis.consecutivePairPotential * 4; + } + + // 单牌少加分 + score -= analysis.singleCount * 2; + + // 对子多加分(容易组合) + score += analysis.pairCount * 3; + + // 三张多加分 + score += analysis.trioCount * 5; + + return Math.max(0, score); + } + + /** + * 计算综合分数 + */ + private static void calculateTotalScore(PlayOptionWithRemain option, int originalHandSize) { + int totalScore = 0; + + // 当前出牌的基础分数 + totalScore += getBaseTypeScore(option.type); + + // 出牌后手牌质量分数(权重较高) + totalScore += option.analysis.remainScore * 2; + + // 预计手数考虑 + totalScore -= option.analysis.estimatedTurns * 10; + + // 出牌大小考虑(优先出小牌) + int maxPlayValue = option.cards.stream().mapToInt(c -> c.cardMod).max().orElse(0); + totalScore -= maxPlayValue; + + // 首轮出牌特殊考虑 + if (originalHandSize == option.analysis.cardCount + option.cards.size()) { // 首轮 + // 首轮避免出太大的牌 + if (maxPlayValue > 12) { + totalScore -= 15; // 加大扣分 + } + // 首轮优先出能形成控制的牌型 + if (option.type == Config.TYPE_SHUNZI || option.type == Config.TYPE_LIANDUI) { + totalScore += 15; + } + } + + // 炸弹特殊处理 + if (option.type == Config.TYPE_ZHA) { + if (option.analysis.cardCount > 8) { // 手牌多时慎用炸弹 + totalScore -= 30; // 加大扣分 + } else { // 手牌少时积极用炸弹 + totalScore += 25; + } + } + + option.totalScore = totalScore; + } + + /** + * 选择最优选项(考虑出牌后手牌) + */ + private static PlayOptionWithRemain selectBestOptionWithRemain(List options, + int handSize, boolean isFirstPlay) { + return options.stream() + .max(Comparator.comparingInt(option -> option.totalScore)) + .orElse(options.isEmpty() ? null : options.get(0)); + } + + /** + * 查找有效的响应选项(考虑出牌后手牌) + */ + private static List findValidResponseOptionsWithRemain(List handCards, + int minCard, int len, int type) { + List validOptions = new ArrayList<>(); + List basicOptions = new ArrayList<>(); + +// boolean isFewCards = handCards.size() <= 15; + + // 根据牌型查找基本选项 + switch (type) { + case Config.TYPE_DANPAI: +// if (isFewCards) { +// // 手牌少时,用非炸弹牌找单牌 +// basicOptions.addAll(findSinglesAboveWithoutBreakingBomb(handCards, minCard).stream() +// .map(cards -> new PlayOption(cards, Config.TYPE_DANPAI)) +// .collect(Collectors.toList())); +// } else { + basicOptions.addAll(findSinglesAbove(handCards, minCard).stream() + .map(cards -> new PlayOption(cards, Config.TYPE_DANPAI)) + .collect(Collectors.toList())); +// } + break; + case Config.TYPE_DUIZI: +// if (isFewCards) { +// basicOptions.addAll(findPairsAboveWithoutBreakingBomb(handCards, minCard).stream() +// .map(cards -> new PlayOption(cards, Config.TYPE_DUIZI)) +// .collect(Collectors.toList())); +// } else { + basicOptions.addAll(findPairsAbove(handCards, minCard).stream() + .map(cards -> new PlayOption(cards, Config.TYPE_DUIZI)) + .collect(Collectors.toList())); +// } + break; + case Config.TYPE_SHUNZI: +// if (isFewCards) { +// basicOptions.addAll(findStraightsAboveWithoutBreakingBomb(handCards, minCard, len).stream() +// .map(cards -> new PlayOption(cards, Config.TYPE_SHUNZI)) +// .collect(Collectors.toList())); +// } else { + basicOptions.addAll(findStraightsAbove(handCards, minCard, len).stream() + .map(cards -> new PlayOption(cards, Config.TYPE_SHUNZI)) + .collect(Collectors.toList())); +// } + break; + case Config.TYPE_LIANDUI: +// if (isFewCards) { +// basicOptions.addAll(findConsecutivePairsAboveWithoutBreakingBomb(handCards, minCard, len).stream() +// .map(cards -> new PlayOption(cards, Config.TYPE_LIANDUI)) +// .collect(Collectors.toList())); +// } else { + basicOptions.addAll(findConsecutivePairsAbove(handCards, minCard, len).stream() + .map(cards -> new PlayOption(cards, Config.TYPE_LIANDUI)) + .collect(Collectors.toList())); +// } + break; + case Config.TYPE_3_1: +// if (isFewCards) { +// basicOptions.addAll(findTrioWithOneAboveWithoutBreakingBomb(handCards, minCard).stream() +// .map(cards -> new PlayOption(cards, Config.TYPE_3_1)) +// .collect(Collectors.toList())); +// } else { + basicOptions.addAll(findTrioWithOneAbove(handCards, minCard).stream() + .map(cards -> new PlayOption(cards, Config.TYPE_3_1)) + .collect(Collectors.toList())); +// } + break; + + case Config.TYPE_FEIJI: + + + break; + + case Config.TYPE_ZHA: + // 炸弹本身不需要特殊处理,因为不会拆炸弹 + basicOptions.addAll(findBombsAbove(handCards, minCard).stream() + .map(cards -> new PlayOption(cards, Config.TYPE_ZHA)) + .collect(Collectors.toList())); + break; + } + + // 如果正常牌型找不到,尝试用炸弹压 + if (basicOptions.isEmpty() && type != Config.TYPE_ZHA) { + List bombOptions = findBombsAbove(handCards, -1).stream() + .map(cards -> new PlayOption(cards, Config.TYPE_ZHA)) + .collect(Collectors.toList()); + +// if (isFewCards) { + // 手牌少时,只有在确实没有其他选择时才用炸弹 +// if (bombOptions.isEmpty()) { +// } else { +// // 有炸弹,但手牌少时要特别标记 +// basicOptions.addAll(bombOptions); +// } +//// } else { + // 手牌多时正常使用炸弹 + basicOptions.addAll(bombOptions); + } + // 为每个基本选项添加出牌后分析 + addOptionsWithRemainAnalysis(validOptions, basicOptions, handCards); + + return validOptions; + } + + + private static List> findTrioWithOneAboveWithoutBreakingBomb(List handCards, int minCard) { + Map valueCount = getValueCount(handCards); + Set bombs = findBombValues(valueCount); + + List> result = new ArrayList<>(); + + // 从手牌中完全移除炸弹牌 + List cardsWithoutBombs = handCards.stream() + .filter(card -> !bombs.contains(card.cardMod)) + .collect(Collectors.toList()); + + // 用非炸弹牌重新计算牌值数量 + Map valueCountWithoutBombs = getValueCount(cardsWithoutBombs); + + // 找三张(在非炸弹牌中找) + List trioCandidates = valueCountWithoutBombs.entrySet().stream() + .filter(entry -> entry.getValue() >= 3 && entry.getKey() > minCard) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + for (int trioValue : trioCandidates) { + // 在非炸弹牌中找两张带牌 + List carryCards = findTwoCarryCardsWithoutBombs(cardsWithoutBombs, trioValue); + + if (carryCards.size() == 2) { + List play = cardsWithoutBombs.stream() + .filter(card -> card.cardMod == trioValue) + .limit(3).collect(Collectors.toList()); + play.addAll(carryCards); + result.add(play); + } + } + + return result; + } + + /** + * 在非炸弹牌中找两张带牌 + */ + private static List findTwoCarryCardsWithoutBombs(List cardsWithoutBombs, int excludeValue) { + // 移除三张的主牌 + List availableCards = cardsWithoutBombs.stream() + .filter(card -> card.cardMod != excludeValue) + .collect(Collectors.toList()); + + Map valueCount = getValueCount(availableCards); + + // 优先找对子作为带牌 + Optional pairCandidate = valueCount.entrySet().stream() + .filter(entry -> entry.getValue() >= 2) + .map(Map.Entry::getKey) + .findFirst(); + + if (pairCandidate.isPresent()) { + int pairValue = pairCandidate.get(); + return availableCards.stream() + .filter(card -> card.cardMod == pairValue) + .limit(2) + .collect(Collectors.toList()); + } + + // 没有对子,找两张单牌 + List sortedCards = availableCards.stream() + .sorted(Comparator.comparingInt(card -> card.cardMod)) + .collect(Collectors.toList()); + + if (sortedCards.size() >= 2) { + Set usedValues = new HashSet<>(); + List result = new ArrayList<>(); + + for (CardObj card : sortedCards) { + if (!usedValues.contains(card.cardMod)) { + result.add(card); + usedValues.add(card.cardMod); + if (result.size() == 2) { + return result; + } + } + } + } + + return new ArrayList<>(); + } + + /** + * 不拆炸弹的单牌查找 + */ + private static List> findSinglesAboveWithoutBreakingBomb(List handCards, int minCard) { + Map valueCount = getValueCount(handCards); + Set bombs = findBombValues(valueCount); + + return handCards.stream() + .filter(card -> card.cardMod > minCard && !bombs.contains(card.cardMod)) + .map(Collections::singletonList) + .collect(Collectors.toList()); + } + + /** + * 不拆炸弹的对子查找 + */ + private static List> findPairsAboveWithoutBreakingBomb(List handCards, int minCard) { + Map valueCount = getValueCount(handCards); + Set bombs = findBombValues(valueCount); + + return valueCount.entrySet().stream() + .filter(entry -> entry.getValue() >= 2 && entry.getKey() > minCard) + .filter(entry -> !bombs.contains(entry.getKey())) // 排除炸弹牌值 + .map(entry -> handCards.stream() + .filter(card -> card.cardMod == entry.getKey()) + .limit(2) + .collect(Collectors.toList())) + .collect(Collectors.toList()); + } + + /** + * 不拆炸弹的顺子查找 + */ + private static List> findStraightsAboveWithoutBreakingBomb(List handCards, int minCard, int len) { + Map valueCount = getValueCount(handCards); + Set bombs = findBombValues(valueCount); + + // 从原牌中移除炸弹牌 + List cardsWithoutBombs = handCards.stream() + .filter(card -> !bombs.contains(card.cardMod)) + .collect(Collectors.toList()); + + // 用非炸弹牌找顺子 + return findStraightsAbove(cardsWithoutBombs, minCard, len); + } + + /** + * 不拆炸弹的连对查找 + */ + private static List> findConsecutivePairsAboveWithoutBreakingBomb(List handCards, int minCard, int len) { + Map valueCount = getValueCount(handCards); + Set bombs = findBombValues(valueCount); + + // 从原牌中移除炸弹牌 + List cardsWithoutBombs = handCards.stream() + .filter(card -> !bombs.contains(card.cardMod)) + .collect(Collectors.toList()); + + return findConsecutivePairsAbove(cardsWithoutBombs, minCard, len); + } + + + /** + * 找出所有的炸弹牌值 + */ + private static Set findBombValues(Map valueCount) { + return valueCount.entrySet().stream() + .filter(entry -> entry.getValue() == 4) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } + + /** + * 获取基础牌型分数 + */ + private static int getBaseTypeScore(int type) { + switch (type) { + case Config.TYPE_SHUNZI: + return 25; + case Config.TYPE_LIANDUI: + return 20; + case Config.TYPE_FEIJI: + return 18; + case Config.TYPE_3_1: + return 15; + case Config.TYPE_4_2_dui: + return 12; + case Config.TYPE_DUIZI: + return 8; + case Config.TYPE_DANPAI: + return 5; + case Config.TYPE_ZHA: + return 30; + default: + return 10; + } + } + + // ============ 牌型分析方法 ============ + + /** + * 分析单牌选项 + */ + private static List analyzeSingles(List handCards) { + List options = new ArrayList<>(); + Map> valueGroups = groupByValue(handCards); + + // 真单牌 + for (Map.Entry> entry : valueGroups.entrySet()) { + if (entry.getValue().size() == 1) { + options.add(new PlayOption(Arrays.asList(entry.getValue().get(0)), Config.TYPE_DANPAI)); + } + } + + // 从对子中拆出的单牌 + for (Map.Entry> entry : valueGroups.entrySet()) { + if (entry.getValue().size() >= 2) { + options.add(new PlayOption(Arrays.asList(entry.getValue().get(0)), Config.TYPE_DANPAI)); + } + } + + return options; + } + + /** + * 分析对子选项 + */ + private static List analyzePairs(List handCards) { + List options = new ArrayList<>(); + Map> valueGroups = groupByValue(handCards); + + for (Map.Entry> entry : valueGroups.entrySet()) { + if (entry.getValue().size() >= 2) { + options.add(new PlayOption(entry.getValue().subList(0, 2), Config.TYPE_DUIZI)); + } + } + + return options; + } + + /** + * 分析顺子选项 + */ + private static List analyzeStraights(List handCards) { + List options = new ArrayList<>(); + + for (int length = 5; length <= 12; length++) { + List> straights = findStraightsAbove(handCards, -1, length); + for (List straight : straights) { + options.add(new PlayOption(straight, Config.TYPE_SHUNZI)); + } + } + + return options; + } + + /** + * 分析连对选项 + */ + private static List analyzeConsecutivePairs(List handCards) { + List options = new ArrayList<>(); + + for (int pairCount = 2; pairCount <= 6; pairCount++) { + List> consecutivePairs = findConsecutivePairsAbove(handCards, -1, pairCount); + for (List pairs : consecutivePairs) { + options.add(new PlayOption(pairs, Config.TYPE_LIANDUI)); + } + } + + return options; + } + + /** + * 分析炸弹选项 + */ + private static List analyzeBombs(List handCards) { + List options = new ArrayList<>(); + List> bombs = findBombsAbove(handCards, -1); + + for (List bomb : bombs) { + options.add(new PlayOption(bomb, Config.TYPE_ZHA)); + } + + return options; + } + + /** + * 分析三带一选项 + */ + private static List analyzeTrioWithOnes(List handCards) { + List options = new ArrayList<>(); + List> trioWithOnes = findTrioWithOneAbove(handCards, -1); + + for (List cards : trioWithOnes) { + options.add(new PlayOption(cards, Config.TYPE_3_1)); + } + + return options; + } + + /** + * 分析三带对选项 + */ + private static List analyzeTrioWithPairs(List handCards) { + List options = new ArrayList<>(); + List> trioWithPairs = findTrioWithPairAbove(handCards, -1); + + for (List cards : trioWithPairs) { + options.add(new PlayOption(cards, Config.TYPE_FEIJI)); + } + + return options; + } + + /** + * 分析四带二选项 + */ + private static List analyzeFourWithTwos(List handCards) { + List options = new ArrayList<>(); + List> fourWithTwos = findFourWithTwoPairsAbove(handCards, -1); + + for (List cards : fourWithTwos) { + options.add(new PlayOption(cards, Config.TYPE_4_2_dui)); + } + + return options; + } + + // ============ 辅助方法 ============ + + /** + * 按牌值分组 + */ + private static Map> groupByValue(List handCards) { + Map> groups = new HashMap<>(); + for (CardObj card : handCards) { + groups.computeIfAbsent(card.cardMod, k -> new ArrayList<>()).add(card); + } + return groups; + } + + /** + * 查找最大单牌 + */ + private static CardObj findMaxSingleCard(List handCards) { + return handCards.stream() + .max(Comparator.comparingInt(c -> c.cardMod)) + .orElse(handCards.get(0)); + } + + // ============ 保留原有的查找方法 ============ + + private static List> findSinglesAbove(List handCards, int minValue) { + List> result = new ArrayList<>(); + for (CardObj card : handCards) { + if (card.cardMod > minValue) { + result.add(Arrays.asList(card)); + } + } + return result; + } + + private static List> findPairsAbove(List handCards, int minValue) { + List> result = new ArrayList<>(); + Map> valueGroups = groupByValue(handCards); + + for (List cards : valueGroups.values()) { + if (cards.size() >= 2 && cards.get(0).cardMod > minValue) { + result.add(Arrays.asList(cards.get(0), cards.get(1))); + } + } + return result; + } + + private static List> findStraightsAbove(List handCards, int minValue, int length) { + List> result = new ArrayList<>(); + + // 只过滤掉2(15),A(14)可以参与顺子 + List distinctValues = handCards.stream() + .map(c -> c.cardMod) + .filter(value -> value != 15) // 只排除2 + .distinct() + .sorted() + .collect(Collectors.toList()); + + for (int i = 0; i <= distinctValues.size() - length; i++) { + if (isConsecutive(distinctValues, i, length) && distinctValues.get(i) > minValue) { + result.add(buildStraight(handCards, distinctValues.subList(i, i + length))); + } + } + + return result; + } + + private static List> findConsecutivePairsAbove(List handCards, int minValue, int pairCount) { + List> result = new ArrayList<>(); + if (handCards.isEmpty() || pairCount < 2) return result; + + handCards.sort(Comparator.comparingInt(c -> c.cardMod)); + Map> valueGroups = new HashMap<>(); + for (CardObj card : handCards) { + valueGroups.computeIfAbsent(card.cardMod, k -> new ArrayList<>()).add(card); + } + + List pairValues = new ArrayList<>(); + for (Map.Entry> entry : valueGroups.entrySet()) { + if (entry.getValue().size() >= 2) { + pairValues.add(entry.getKey()); + } + } + + if (pairValues.size() < pairCount) return result; + Collections.sort(pairValues); + + for (int i = 0; i <= pairValues.size() - pairCount; i++) { + boolean isConsecutive = true; + for (int j = i; j < i + pairCount - 1; j++) { + if (pairValues.get(j + 1) - pairValues.get(j) != 1) { + isConsecutive = false; + break; + } + } + + if (isConsecutive && pairValues.get(i) > minValue) { + List consecutivePairs = new ArrayList<>(); + for (int k = i; k < i + pairCount; k++) { + int value = pairValues.get(k); + List pair = valueGroups.get(value).subList(0, 2); + consecutivePairs.addAll(pair); + } + result.add(consecutivePairs); + } + } + return result; + } + + private static List> findBombsAbove(List handCards, int minValue) { + List> result = new ArrayList<>(); + handCards.sort(Comparator.comparingInt(c -> c.cardMod)); + Map> valueGroups = new HashMap<>(); + for (CardObj card : handCards) { + valueGroups.computeIfAbsent(card.cardMod, k -> new ArrayList<>()).add(card); + } + + for (Map.Entry> entry : valueGroups.entrySet()) { + int cardValue = entry.getKey(); + List cards = entry.getValue(); + if (cards.size() >= 4 && cardValue > minValue) { + result.add(cards); + } + } + return result; + } + + private static List> findTrioWithOneAbove(List handCards, int minValue) { + List> result = new ArrayList<>(); + Map> valueGroups = groupByValue(handCards); + + // 找出所有三张的牌 + List trioValues = new ArrayList<>(); + for (Map.Entry> entry : valueGroups.entrySet()) { + if (entry.getValue().size() >= 3 && entry.getKey() > minValue) { + trioValues.add(entry.getKey()); + } + } + + Collections.sort(trioValues); + + for (int trioValue : trioValues) { + List trio = valueGroups.get(trioValue).subList(0, 3); + + // 找出所有可带的牌(排除三张的牌值) + List availableCards = new ArrayList<>(); + for (Map.Entry> entry : valueGroups.entrySet()) { + if (entry.getKey() != trioValue) { + availableCards.addAll(entry.getValue()); + } + } + + if (availableCards.size() < 2) continue; + + // 找出所有可能的两个牌组合(可以是一对或两个单牌) + findTrioCombinations(result, trio, availableCards); + } + + return result; + } + + + private static void findTrioCombinations(List> result, List trio, List availableCards) { + availableCards.sort(Comparator.comparingInt(c -> c.cardMod)); + Set combinationSet = new HashSet<>(); + + for (int i = 0; i < availableCards.size() - 1; i++) { + CardObj card1 = availableCards.get(i); + for (int j = i + 1; j < availableCards.size(); j++) { + CardObj card2 = availableCards.get(j); + if (card1.card != card2.card) { + String comboKey = card1.cardMod <= card2.cardMod ? + card1.cardMod + "-" + card2.cardMod : + card2.cardMod + "-" + card1.cardMod; + if (!combinationSet.contains(comboKey)) { + combinationSet.add(comboKey); + List play = new ArrayList<>(trio); + play.add(card1); + play.add(card2); + result.add(play); + } + } + } + } + } + + private static List> findTrioWithPairAbove(List handCards, int minValue) { + // 实现飞机逻辑 + return new ArrayList<>(); + } + + private static List> findFourWithTwoPairsAbove(List handCards, int minValue) { + // 实现四带二对子逻辑 + return new ArrayList<>(); + } + + private static boolean isConsecutive(List values, int start, int length) { + for (int i = start; i < start + length - 1; i++) { + if (values.get(i + 1) - values.get(i) != 1) { + return false; + } + } + return true; + } + + private static List buildStraight(List handCards, List values) { + List straight = new ArrayList<>(); + for (int value : values) { + // 取第一张该值的牌(确保不取2,但可以取A) + for (CardObj card : handCards) { + if (card.cardMod == value && value != 15) { // 只排除2 + straight.add(card); + break; + } + } + } + return straight; + } + + /** + * 统计对子数量 + */ + private static int countPairs(Map valueCount) { + return (int) valueCount.values().stream() + .filter(count -> count == 2) + .count(); + } + + /** + * 统计三张数量 + */ + private static int countTrios(Map valueCount) { + return (int) valueCount.values().stream() + .filter(count -> count == 3) + .count(); + } + + /** + * 统计炸弹数量 + */ + private static int countBombs(Map valueCount) { + return (int) valueCount.values().stream() + .filter(count -> count >= 4) + .count(); + } + + /** + * 查找最长顺子长度 + */ + private static int findMaxStraightLength(List handCards) { + // 只过滤掉2(15),A(14)可以参与顺子 + List distinctValues = handCards.stream() + .map(c -> c.cardMod) + .filter(value -> value != 15) // 只排除2 + .distinct() + .sorted() + .collect(Collectors.toList()); + + if (distinctValues.size() < 5) return 0; + + int maxLength = 1; + int currentLength = 1; + + for (int i = 1; i < distinctValues.size(); i++) { + if (distinctValues.get(i) - distinctValues.get(i - 1) == 1) { + currentLength++; + maxLength = Math.max(maxLength, currentLength); + } else { + currentLength = 1; + } + } + + return maxLength >= 5 ? maxLength : 0; + } + + /** + * 查找最长连对长度 + */ + private static int findMaxConsecutivePairs(List handCards) { + Map valueCount = getValueCount(handCards); + List pairValues = valueCount.entrySet().stream() + .filter(entry -> entry.getValue() >= 2) + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + + if (pairValues.size() < 2) return 0; + + int maxLength = 1; + int currentLength = 1; + + for (int i = 1; i < pairValues.size(); i++) { + if (pairValues.get(i) - pairValues.get(i - 1) == 1) { + currentLength++; + maxLength = Math.max(maxLength, currentLength); + } else { + currentLength = 1; + } + } + + return maxLength; + } + + /** + * 设置分析特征标签 + */ + private static void setAnalysisFeatures(HandAnalysis analysis) { + // 基础特征 + analysis.features.put("smallSingleCount", analysis.smallSingleCount); + analysis.features.put("bigCardCount", analysis.bigCardCount); + analysis.features.put("pairCount", analysis.pairCount); + analysis.features.put("trioCount", analysis.trioCount); + analysis.features.put("bombCount", analysis.bombCount); + analysis.features.put("maxStraightLength", analysis.maxStraightLength); + analysis.features.put("maxConsecutivePairs", analysis.maxConsecutivePairs); + + // 特征标签 + if (analysis.smallSingleCount >= 3) { + analysis.features.put("feature", "多小单牌"); + } else if (analysis.maxStraightLength >= 6) { + analysis.features.put("feature", "长顺子"); + } else if (analysis.maxConsecutivePairs >= 3) { + analysis.features.put("feature", "长连对"); + } else if (analysis.pairCount >= 4) { + analysis.features.put("feature", "多对子"); + } else if (analysis.bigCardCount >= 3) { + analysis.features.put("feature", "多大牌"); + } else { + analysis.features.put("feature", "均衡牌型"); + } + } + + /** + * 从手牌中移除指定的牌 + */ + private static List removeCards(List originalHand, List cardsToRemove) { + List remainHand = new ArrayList<>(originalHand); + + for (CardObj cardToRemove : cardsToRemove) { + Iterator iterator = remainHand.iterator(); + while (iterator.hasNext()) { + CardObj card = iterator.next(); + if (card.cardMod == cardToRemove.cardMod && card.card == cardToRemove.card) { + iterator.remove(); + break; + } + } + } + + return remainHand; + } + + /** + * 获取出牌类型 + */ + private static int getPlayType(List cards) { + if (cards == null || cards.isEmpty()) return -1; + + int size = cards.size(); + Map valueCount = getValueCount(cards); + + // 单牌 + if (size == 1) { + return Config.TYPE_DANPAI; + } + + // 对子 + if (size == 2 && valueCount.size() == 1) { + return Config.TYPE_DUIZI; + } + + // 顺子 + if (size >= 5 && isStraight(cards)) { + return Config.TYPE_SHUNZI; + } + + // 连对 + if (size >= 4 && size % 2 == 0 && isConsecutivePairs(cards)) { + return Config.TYPE_LIANDUI; + } + + // 三带一 + if (size == 4 && hasTrioWithSingle(valueCount)) { + return Config.TYPE_3_1; + } + + // 三带二 + if (size == 5 && hasTrioWithPair(valueCount)) { + return Config.TYPE_FEIJI; + } + + // 炸弹 + if (size >= 4 && valueCount.size() == 1) { + return Config.TYPE_ZHA; + } + + // 四带二 + if (size == 6 && hasFourWithTwo(valueCount)) { + return Config.TYPE_4_2_dui; + } + + return -1; // 未知类型 + } + + /** + * 判断是否为顺子 + */ + private static boolean isStraight(List cards) { + if (cards.size() < 5) return false; + + List values = cards.stream() + .map(c -> c.cardMod) + .distinct() + .sorted() + .collect(Collectors.toList()); + + // 只检查是否有2(15),2不能参与顺子,A(14)可以参与 + if (values.contains(15)) { + return false; + } + + if (values.size() != cards.size()) return false; // 有重复牌值 + + for (int i = 1; i < values.size(); i++) { + if (values.get(i) - values.get(i - 1) != 1) { + return false; + } + } + + return true; + } + + /** + * 判断是否为连对 + */ + private static boolean isConsecutivePairs(List cards) { + if (cards.size() < 4 || cards.size() % 2 != 0) return false; + + Map valueCount = getValueCount(cards); + + // 检查每个牌值都是2张 + for (int count : valueCount.values()) { + if (count != 2) return false; + } + + // 检查是否连续 + List values = valueCount.keySet().stream() + .sorted() + .collect(Collectors.toList()); + + for (int i = 1; i < values.size(); i++) { + if (values.get(i) - values.get(i - 1) != 1) { + return false; + } + } + + return true; + } + + /** + * 判断是否为三带一 + */ + private static boolean hasTrioWithSingle(Map valueCount) { + if (valueCount.size() != 2) return false; + + boolean hasTrio = valueCount.values().stream().anyMatch(count -> count == 3); + boolean hasSingle = valueCount.values().stream().anyMatch(count -> count == 1); + + return hasTrio && hasSingle; + } + + /** + * 判断是否为三带二 + */ + private static boolean hasTrioWithPair(Map valueCount) { + if (valueCount.size() != 2) return false; + + boolean hasTrio = valueCount.values().stream().anyMatch(count -> count == 3); + boolean hasPair = valueCount.values().stream().anyMatch(count -> count == 2); + + return hasTrio && hasPair; + } + + /** + * 判断是否为四带二 + */ + private static boolean hasFourWithTwo(Map valueCount) { + if (valueCount.size() != 3) return false; + + boolean hasFour = valueCount.values().stream().anyMatch(count -> count == 4); + boolean hasTwoSingles = valueCount.values().stream().filter(count -> count == 1).count() == 2; + boolean hasOnePair = valueCount.values().stream().anyMatch(count -> count == 2); + + return hasFour && (hasTwoSingles || hasOnePair); + } + + + /** + * 统计牌值数量 + */ + private static Map getValueCount(List cards) { + Map countMap = new HashMap<>(); + for (CardObj card : cards) { + countMap.put(card.cardMod, countMap.getOrDefault(card.cardMod, 0) + 1); + } + return countMap; + } + + // 获取除当前座位外所有其他座位的最后记录 + public static Map getOtherSeatsLastRemain(int currentSeat, Map> seatRemainHistory) { + Map result = new HashMap<>(); + + for (Map.Entry> entry : seatRemainHistory.entrySet()) { + int seat = entry.getKey(); + List remainList = entry.getValue(); + + // 排除当前座位 + if (seat != currentSeat && !remainList.isEmpty()) { + // 取list最后一个记录 + int lastRemain = remainList.get(remainList.size() - 1); + result.put(seat, lastRemain); + } + } + return result; + } + + + /** + * 基础出牌选项类 + */ + static class PlayOption { + List cards; + int type; + int score; + + public PlayOption(List cards, int type) { + this.cards = cards; + this.type = type; + this.score = 0; + } + } + + /** + * 出牌后手牌分析类 + */ + static class RemainHandAnalysis { + boolean isWin = false; + int cardCount = 0; + int maxCardValue = 0; + int minCardValue = 0; + double avgCardValue = 0; + int singleCount = 0; + int pairCount = 0; + int trioCount = 0; + int straightPotential = 0; + int consecutivePairPotential = 0; + int estimatedTurns = 0; + int remainScore = 0; + int bigCardRemain = 0; // 新增:剩余大牌数量 + } + + /** + * 出牌选项类(包含出牌后手牌分析) + */ + static class PlayOptionWithRemain { + List cards; + int type; + int totalScore; + List remainHand; + RemainHandAnalysis analysis; + + public PlayOptionWithRemain(PlayOption option, List remainHand, RemainHandAnalysis analysis) { + this.cards = option.cards; + this.type = option.type; + this.remainHand = remainHand; + this.analysis = analysis; + this.totalScore = 0; + } + + public int getEstimatedTurns() { + return analysis.estimatedTurns; + } + } +} \ No newline at end of file diff --git a/robots/puke/robot_pk_pdk/src/test/java/robot_mj_hongzhong/Main.java b/robots/puke/robot_pk_pdk/src/test/java/robot_mj_hongzhong/Main.java new file mode 100644 index 0000000..b355eea --- /dev/null +++ b/robots/puke/robot_pk_pdk/src/test/java/robot_mj_hongzhong/Main.java @@ -0,0 +1,15 @@ +package robot_mj_hongzhong; + +import com.taurus.permanent.TPServer; + +public class Main { + public static void main(String[] args) { + System.out.println("启动跑得快机器人服务器..."); + System.out.println("服务器将监听端口8766用于游戏协议"); + + //启动机器人服务 + TPServer.me().start(); + + System.out.println("跑得快机器人服务器已启动"); + } +} \ No newline at end of file