文章目录
文章链接
Java多线程实现跑步比赛【比赛详解】
Java多线程实现跑步比赛【基本设计】
Java多线程实现跑步比赛【RunMap——地图映射类】
Java多线程实现跑步比赛【RunMan——运动员映射类】
Java多线程实现跑步比赛【RunManFunc,RunStart,Utils——工具类】
Java多线程实现跑步比赛【SpringBoot——调用逻辑】
RunManFunc——地图事件逻辑类
package com.sport.map;
import com.sport.entity.RunMan;
import com.sport.map.condition.model.Condition;
import com.sport.map.condition.model.Effect;
import com.sport.map.condition.model.ImmunizationCondition;
import com.sport.map.event.MapEvent;
import com.sport.utils.MapUtils;
/**
* <p>
* 处理Map Event的各种方法。
*
* <pre>
* 触发条件: 速度 名次 时间 掷骰子 > >= == < <= number
* 免疫条件: 速度 名次 时间 掷骰子 > >= == < <= number'
* 效应: 速度 负重 距离 '+ - = number'
* 效应参数: timeOfDuration 持续时间单位为秒, -1 时为永久
* </pre>
*
* @author jiajun.b.zhang
* @version V1.0.0
*/
public class RunMapFunc {
/** 记录比赛文言 */
private String gameCommentary;
/** 判断时间是否被触发 */
private boolean executeFlag = true;
/**
* <p>
* 处理事件,先进行事件的类型判断,然后进行事件的条件判断。 如果不满足触发条件,则不对事件进行处理。如果满足触发条件,则进行
* 免疫条件判断,如果不满足免疫条件,则将事件影响添加到事件寄存器。 如果满足免疫条件,则进行事件免疫。
*
* @param mapEvent 地图事件
* @param runMan 运动员
* @return 返回事件文言
*/
public String startEvents(MapEvent mapEvent, RunMan runMan) {
gameCommentary = "";
if (mapEvent.getEffectsOfEvent().getConditions().size() > 0) {
//确定地图事件是否拥有触发条件
mapEvent.getEffectsOfEvent().getConditions().forEach((conditionItem) -> {
//循环触发条件
if (checkCondition(conditionItem, runMan, mapEvent.getRunManRank(runMan)) && executeFlag) {
//检查地图事件是否被触发
//记录触发事件后文言
gameCommentary = conditionItem.getDescription() + mapEvent.getDescription();
//判断事件是否被免疫
judgmentInfluence(mapEvent, runMan);
//将执行标志改为false
executeFlag = false;
}
});
} else {
//记录事件文言
gameCommentary = mapEvent.getDescription();
//判断事件是否被免疫
judgmentInfluence(mapEvent, runMan);
}
//将执行标志改为true
executeFlag = true;
return gameCommentary;
}
/**
* <p>判断地图事件是否被免疫,如果未被免疫,首先判断影响是否为永久,如果为永久对运动员的基础属性值进行修改。然后将事件影响记录到状态寄存器
* @param mapEvent 地图事件
* @param runMan 运动员
*/
public void judgmentInfluence(MapEvent mapEvent, RunMan runMan) {
if (!checkImmunizationCondition(mapEvent.getEffectsOfEvent().getImmunizationCondition(), runMan,
mapEvent.getRunManRank(runMan))) {
//判断事件是否被免疫
mapEvent.getEffectsOfEvent().getEffects().forEach((effectItem) -> {
if (effectItem.getOptions().getTimeOfDuration() == -1) {
//判断事件影响是否为永久
//事件影响
checkEffectPermanent(effectItem, runMan);
}
//添加事件影响到状态寄存器
runMan.addStateRegister(effectItem);
gameCommentary = gameCommentary + effectItem.getDescription();
});
} else {
gameCommentary = gameCommentary + mapEvent.getEffectsOfEvent().getImmunizationCondition().getDescription();
}
}
/**
* <p>根据触发条件判断事件是否被触发
* @param condition 地图事件触发条件
* @param runMan 运动员
* @param rank 运动员排名
* @return 事件是否被触发
*
*/
public boolean checkCondition(Condition condition, RunMan runMan, double rank) {
//选择检查项目名
switch (condition.getItem()) {
case "速度":
return checkSymbol(runMan.getSpeed(), condition.getValue(), condition.getOperator());
case "名次":
return checkSymbol(rank, condition.getValue(), condition.getOperator());
case "时间":
return checkSymbol(runMan.getMileage() / runMan.getSpeed() * 1000, condition.getValue(),
condition.getOperator());
case "掷骰子":
return checkSymbol(MapUtils.getRandom(), condition.getValue(), condition.getOperator());
default:
return false;
}
}
/**
* <p>根据免疫条件判断事件是否被触发
* @param immunizationCondition 地图事件免疫条件
* @param runMan 运动员
* @param rank 运动员排名
* @return 事件是否被免疫
*/
public boolean checkImmunizationCondition(ImmunizationCondition immunizationCondition, RunMan runMan, double rank) {
//选择检查项目名
switch (immunizationCondition.getItem()) {
case "速度":
return checkSymbol(runMan.getSpeed(), immunizationCondition.getValue(),
immunizationCondition.getOperator());
case "名次":
return checkSymbol(rank, immunizationCondition.getValue(), immunizationCondition.getOperator());
case "时间":
return checkSymbol(runMan.getMileage() / runMan.getSpeed() * 1000, immunizationCondition.getValue(),
immunizationCondition.getOperator());
case "掷骰子":
return checkSymbol(MapUtils.getRandom(), immunizationCondition.getValue(),
immunizationCondition.getOperator());
default:
return false;
}
}
/**
* <p>添加事件影响
* @param effect 地图事件影响
* @param runMan 运动员
*/
public void checkEffectTime(Effect effect, RunMan runMan) {
//选择检查项目名
switch (effect.getItem()) {
case "速度":
runMan.setSpeedState(addState(runMan.getSpeed(), effect.getValue(), effect.getOperator()));
break;
case "负重":
runMan.setAddedWeightState(addState(runMan.getAddedWeight(), effect.getValue(), effect.getOperator()));
break;
case "距离":
runMan.setMileage(addState(runMan.getMileage(), effect.getValue(), effect.getOperator()));
break;
default:
break;
}
}
/**
* <p>添加事件影响
* @param effect 地图事件影响
* @param runMan 运动员
*/
public void checkEffectPermanent(Effect effect, RunMan runMan) {
switch (effect.getItem()) {
case "速度":
runMan.setSpeed(addState(runMan.getSpeed(), effect.getValue(), effect.getOperator()));
break;
case "负重":
runMan.setAddedWeight(addState(runMan.getAddedWeight(), effect.getValue(), effect.getOperator()));
break;
case "距离":
runMan.setMileage(addState(runMan.getMileage(), effect.getValue(), effect.getOperator()));
break;
default:
break;
}
}
/**
* <p> 判断检查符号
* @param runManValue 运动员属性值
* @param conditionValue 地图事件触发条件所需值
* @param symbol 判断符号
* @return 是否触发地图事件
*/
public boolean checkSymbol(double runManValue, double conditionValue, String symbol) {
switch (symbol) {
case ">":
return runManValue > conditionValue;
case ">=":
return runManValue >= conditionValue;
case "==":
return runManValue == conditionValue;
case "<":
return runManValue < conditionValue;
case "<=":
return runManValue <= conditionValue;
default:
return false;
}
}
/**
* <p> 添加事件影响
* @param runManValue 运动员属性值
* @param stateValue 状态值
* @param symbol 运算符号
* @return 状态运算值
*/
public double addState(double runManValue, double stateValue, String symbol) {
switch (symbol) {
case "+":
return runManValue + stateValue;
case "-":
return runManValue - stateValue;
case "=":
return stateValue;
default:
return 0;
}
}
}
RunStart——继承Runnable实现多线程
package com.sport.services;
import com.sport.entity.GameResult;
import com.sport.entity.RunMan;
import com.sport.map.RunMap;
import com.sport.map.RunMapFunc;
import com.sport.mapper.PlayerInfoMapper;
import com.sport.utils.MapUtils;
/**
* <p> 比赛处理
*
* @author jiajun.b.zhang
* @version V1.0.0
*
*/
public class RunStart implements Runnable {
/** 比赛地图 */
private RunMap runMap;
/** 运动员 */
private RunMan runMan;
/** 执行标志 */
private boolean startFlag = true;
/** 每次执行所跑时间 */
private int second;
/** 执行总时长 */
private int gameDuration;
/** 执行文言 */
private String gameCommentary;
/** 本次比赛Id */
private String gameId;
/** 执行储存比赛结果操作所需的PlayerInfoMapper */
private PlayerInfoMapper playerInfoService;
/** 处理事件的逻辑方法 */
private RunMapFunc runMapFunc;
/** msg信息 */
private String msgString;
public RunStart(RunMap runMap, RunMan runMan, PlayerInfoMapper playerInfoService,
String gameId) {
this.runMap = runMap;
this.runMan = runMan;
this.second = 10;
this.gameDuration = 0;
this.playerInfoService = playerInfoService;
this.runMapFunc = new RunMapFunc();
this.gameId = gameId;
this.gameCommentary="";
}
@Override
public void run() {
while (this.startFlag) {
beginPlaying();
}
}
/**
* 开始比赛
*/
private void beginPlaying() {
MapUtils.prepare(this.second);
for (int i = 0; i < this.second; i++) {
//初始化运动员状态
runMan.initState();
//当前距离
runMan.setMileage(runMan.getMileage() + (runMan.getSpeedState() / 3.6));
//遍历事件合集
runMap.getEvents().forEach((eventItem) -> {
if (runMan.getMileage() >= eventItem.getOccurredAt()) {
//进入事件
if (!eventItem.getRunManRank().contains(runMan)) {
//查看是否已触发事件
//加入排名
eventItem.addRunManRank(runMan);
//添加message
msgString = runMapFunc.startEvents(eventItem, runMan);
if (msgString != null) {
//写入文言
gameCommentary += msgString;
}
}
}
});
gameDuration++;
if (runMan.getMileage() >= runMap.getDistance()) {
// 执行标志
this.startFlag = false;
// 加入最终排名
runMap.addRunManRank(runMan);
// 将比赛结果写入数据库
playerInfoService.addGameResult(new GameResult(runMan.getId(), gameId, MapUtils.getDate(),
runMap.getName(),
runMap.getRunManRank(runMan), (int) (runMan.getMileage() / gameDuration * 3.6), gameDuration,
gameCommentary.replace("null", "")));
break;
}
runMan.regressionState();
}
}
}
Utils——工具类
MapUtils——地图工具类
package com.sport.utils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.yaml.snakeyaml.Yaml;
import com.sport.entity.RunMan;
import com.sport.map.RunMap;
/**
* 地图工具包
* @author jiajun.b.zhang
* @version V1.0.0
*
*/
public class MapUtils {
private static Logger log = LoggerFactory.getLogger(MsgUtils.class);
private static Yaml yamlTool = new Yaml();
/**
* 初始化地图
* @param url 地图名称
* @return 比赛地图
*/
public static RunMap initRunMap(String url) {
ClassPathResource classPathResource = new ClassPathResource(url + ".yaml");
try {
log.info("Init Map");
return yamlTool.loadAs(classPathResource.getInputStream(), RunMap.class);
} catch (IOException e) {
log.error("Failed to import map", e);
return null;
} finally {
log.info("Init Map End");
}
}
/**
* 模拟摇骰子
* @return 随机数
*/
public static int getRandom() {
return (int) (Math.random() * 6) + 1;
}
/**
* 等待事件
* @param second 秒
*/
public static void prepare(int second) {
try {
Thread.sleep(10 * second);
} catch (InterruptedException e) {
log.error("Failed Prepare", e);
}
}
/**
* 获取当前日期
* @return 日期
*/
public static Date getDate() {
return new Date();
}
/**
* 获取运动员排名
* @return 运动员排名
*/
public static int getRank(RunMan runMan,ArrayList<RunMan> runManRank) {
try {
for (int i = 0; i < runManRank.size(); i++) {
if (runManRank.get(i).getId()==runMan.getId()) {
return i+1;
}
}
throw new NullPointerException("The player is currently unranked, RunManId: "+runMan.getId());
} catch (NullPointerException e) {
log.error("MapEvent Error:"+e.getMessage(),e);
return 0;
}
}
}
ThreadPoolUtils——线程池工具类
package com.sport.utils;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* ThreadPoolUtils
* @author jiajun.b.zhang
* @version V1.0.0
*
*/
@Component
public class ThreadPoolUtils {
/** corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去; */
private static int corePoolSize;
/** maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量; */
private static int maximumPoolSize;
/** keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁; */
private static int keepAliveTime;
/** unit:keepAliveTime的单位 */
private static TimeUnit unit = TimeUnit.MILLISECONDS;
/** workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种; */
private static BlockingQueue<Runnable> workQueue = new SynchronousQueue<Runnable>();
/**
* 读取配置文件中的corePoolSize到静态变量
* @param corePoolSize 线程数量
*/
@Value("${ThreadPool.corePoolSize}")
public void setCorePoolSize(int corePoolSize) {
ThreadPoolUtils.corePoolSize = corePoolSize;
}
/**
* 读取配置文件中的maximumPoolSize到静态变量
* @param maximumPoolSize 最大线程数量
*/
@Value("${ThreadPool.maximumPoolSize}")
public void setMaximumPoolSize(int maximumPoolSize) {
ThreadPoolUtils.maximumPoolSize = maximumPoolSize;
}
/**
* 读取配置文件中的keepAliveTime到静态变量
* @param keepAliveTime 线程池中空闲线程等待工作的超时时间
*/
@Value("${ThreadPool.keepAliveTime}")
public void setKeepAliveTime(int keepAliveTime) {
ThreadPoolUtils.keepAliveTime = keepAliveTime;
}
/**
* 创建线程池
* @return 线程池
*/
public static ThreadPoolExecutor buildRunGameThreadPoolExecutor() {
return new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue);
}
}
application.yml——配置文件
server:
port: ####端口号
spring:
datasource:
username: ####数据库用户名
password: ####数据库密码
url: jdbc:mysql://localhost:3306/#数据库名?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.jdbc.Driver
mybatis:
mapper-locations: classpath*:mapping/*Mapper.xml
type-aliases-package: com.sport.entity
ThreadPool:
corePoolSize: 20
maximumPoolSize: 20
keepAliveTime: 0