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