实现bot代码执行的微服务 botrunningsystem, 主服务前后端做出对应修改
This commit is contained in:
parent
fffb1d5a60
commit
ebd3f3c610
|
@ -49,7 +49,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
.and()
|
.and()
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers("/user/account/token/", "/user/account/register/").permitAll()
|
.antMatchers("/user/account/token/", "/user/account/register/").permitAll()
|
||||||
.antMatchers("/pk/start/").hasIpAddress("127.0.0.1")
|
.antMatchers("/pk/start/","/pk/receive/bot/move/").hasIpAddress("127.0.0.1")
|
||||||
.antMatchers(HttpMethod.OPTIONS).permitAll()
|
.antMatchers(HttpMethod.OPTIONS).permitAll()
|
||||||
.anyRequest().authenticated();
|
.anyRequest().authenticated();
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,15 @@ package com.kob.backend.consumer;
|
||||||
// WebSocket用于前后端通信
|
// WebSocket用于前后端通信
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.kob.backend.config.RestTemplateConfig;
|
|
||||||
import com.kob.backend.consumer.utils.Game;
|
import com.kob.backend.consumer.utils.Game;
|
||||||
import com.kob.backend.consumer.utils.JwtAuthenticationUtil;
|
import com.kob.backend.consumer.utils.JwtAuthenticationUtil;
|
||||||
|
import com.kob.backend.mapper.BotMapper;
|
||||||
import com.kob.backend.mapper.RecordMapper;
|
import com.kob.backend.mapper.RecordMapper;
|
||||||
import com.kob.backend.mapper.UserMapper;
|
import com.kob.backend.mapper.UserMapper;
|
||||||
|
import com.kob.backend.pojo.Bot;
|
||||||
|
import com.kob.backend.pojo.Record;
|
||||||
import com.kob.backend.pojo.User;
|
import com.kob.backend.pojo.User;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.web.WebProperties;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
@ -19,11 +20,8 @@ import org.springframework.web.client.RestTemplate;
|
||||||
import javax.websocket.*;
|
import javax.websocket.*;
|
||||||
import javax.websocket.server.PathParam;
|
import javax.websocket.server.PathParam;
|
||||||
import javax.websocket.server.ServerEndpoint;
|
import javax.websocket.server.ServerEndpoint;
|
||||||
import javax.xml.stream.events.StartDocument;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@ServerEndpoint("/websocket/{token}") // 注意不要以'/'结尾
|
@ServerEndpoint("/websocket/{token}") // 注意不要以'/'结尾
|
||||||
|
@ -36,16 +34,17 @@ public class WebSocketServer {
|
||||||
*/
|
*/
|
||||||
public static final ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>();
|
public static final ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>();
|
||||||
// 用于和 MatchingSystem 进行通信
|
// 用于和 MatchingSystem 进行通信
|
||||||
private static RestTemplate restTemplate;
|
public static RestTemplate restTemplate;
|
||||||
// 在 WebSocketServer 中注入数据库的方法演示-> 使用 static 定义为独一份的变量
|
// 在 WebSocketServer 中注入数据库的方法演示-> 使用 static 定义为独一份的变量
|
||||||
private static UserMapper userMapper;
|
private static UserMapper userMapper;
|
||||||
|
private static BotMapper botMapper;
|
||||||
// 注入 RecordMapper 用于调用实现游戏数据到数据库的存储
|
// 注入 RecordMapper 用于调用实现游戏数据到数据库的存储
|
||||||
public static RecordMapper recordMapper;
|
public static RecordMapper recordMapper;
|
||||||
// 后端向前端发送信息,首先需要创建一个 session
|
// 后端向前端发送信息,首先需要创建一个 session
|
||||||
private Session session = null;
|
private Session session = null;
|
||||||
// 用户信息:定义一个成员变量
|
// 用户信息:定义一个成员变量
|
||||||
private User user;
|
private User user;
|
||||||
private Game game = null;
|
public Game game = null;
|
||||||
// addPlayer 添加用户到匹配池的 URL; removePlayer 从匹配池移除用户的 URL
|
// addPlayer 添加用户到匹配池的 URL; removePlayer 从匹配池移除用户的 URL
|
||||||
private final static String addPlayerUrl = "http://127.0.0.1:3001/player/add/";
|
private final static String addPlayerUrl = "http://127.0.0.1:3001/player/add/";
|
||||||
private final static String removePlayerUrl = "http://127.0.0.1:3001/player/remove/";
|
private final static String removePlayerUrl = "http://127.0.0.1:3001/player/remove/";
|
||||||
|
@ -63,12 +62,16 @@ public class WebSocketServer {
|
||||||
WebSocketServer.userMapper = userMapper;
|
WebSocketServer.userMapper = userMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setBotMapper(BotMapper botMapper) {
|
||||||
|
WebSocketServer.botMapper = botMapper;
|
||||||
|
}
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public void setRecordMapper(RecordMapper recordMapper) {
|
public void setRecordMapper(RecordMapper recordMapper) {
|
||||||
WebSocketServer.recordMapper = recordMapper;
|
WebSocketServer.recordMapper = recordMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// @OnOpen 创建链接时自动触发这个函数
|
// @OnOpen 创建链接时自动触发这个函数
|
||||||
@OnOpen
|
@OnOpen
|
||||||
public void onOpen(Session session, @PathParam("token") String token) throws IOException {
|
public void onOpen(Session session, @PathParam("token") String token) throws IOException {
|
||||||
|
@ -108,12 +111,22 @@ public class WebSocketServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 匹配逻辑函数
|
// 匹配逻辑函数
|
||||||
public static void startGame(Integer aId, Integer bId) {
|
public static void startGame(Integer aId, Integer aBotId, Integer bId, Integer bBotId) {
|
||||||
User a = userMapper.selectById(aId);
|
User a = userMapper.selectById(aId);
|
||||||
User b = userMapper.selectById(bId);
|
User b = userMapper.selectById(bId);
|
||||||
|
Bot botA = botMapper.selectById(aBotId);
|
||||||
|
Bot botB = botMapper.selectById(bBotId);
|
||||||
|
|
||||||
// 匹配成功时,创建联机地图
|
// 匹配成功时,创建联机地图
|
||||||
Game game = new Game(13, 14, 20, a.getId(), b.getId());
|
Game game = new Game(
|
||||||
|
13,
|
||||||
|
14,
|
||||||
|
20,
|
||||||
|
a.getId(),
|
||||||
|
botA,
|
||||||
|
b.getId(),
|
||||||
|
botB
|
||||||
|
);
|
||||||
game.createMap(); // 初始化地图
|
game.createMap(); // 初始化地图
|
||||||
// 当用户不为空时,才能执行下面的操作(防止有人退出游戏后用户已经成为空指针)
|
// 当用户不为空时,才能执行下面的操作(防止有人退出游戏后用户已经成为空指针)
|
||||||
if (users.get(a.getId()) != null)
|
if (users.get(a.getId()) != null)
|
||||||
|
@ -124,6 +137,7 @@ public class WebSocketServer {
|
||||||
|
|
||||||
game.start(); // 开启新线程,执行函数
|
game.start(); // 开启新线程,执行函数
|
||||||
|
|
||||||
|
// 打包地图数据信息
|
||||||
JSONObject respGameData = new JSONObject();
|
JSONObject respGameData = new JSONObject();
|
||||||
respGameData.put("game_map", game.getG());
|
respGameData.put("game_map", game.getG());
|
||||||
respGameData.put("rows", game.getRows());
|
respGameData.put("rows", game.getRows());
|
||||||
|
@ -157,15 +171,15 @@ public class WebSocketServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始匹配的逻辑部分:开始匹配时向 MatchingSystem 服务端发一个请求
|
// 开始匹配的逻辑部分:开始匹配时向 MatchingSystem 服务端发一个请求
|
||||||
private void startMatching() {
|
private void startMatching(Integer botId) {
|
||||||
System.out.println("start matching");
|
System.out.println("start matching");
|
||||||
// 这里的参数列表需要与 MatchingSystem(微服务)->MatchingController->addPlayer 的参数对应
|
// 这里的参数列表需要与 MatchingSystem(微服务)->MatchingController->addPlayer 的参数对应
|
||||||
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
|
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
|
||||||
data.add("user_id", this.user.getId().toString());
|
data.add("user_id", this.user.getId().toString());
|
||||||
data.add("rating", this.user.getRating().toString());
|
data.add("rating", this.user.getRating().toString());
|
||||||
|
data.add("bot_id", botId.toString()); // 将选中的 botId 传给匹配系统
|
||||||
// postForObject(请求的URL,传出的数据,返回值的类型.class) -> Java 反射机制
|
// postForObject(请求的URL,传出的数据,返回值的类型.class) -> Java 反射机制
|
||||||
String res = restTemplate.postForObject(addPlayerUrl, data, String.class);
|
restTemplate.postForObject(addPlayerUrl, data, String.class);
|
||||||
System.out.println(this.user.getId().toString() + " " + this.user.getUsername() + " " + res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消匹配的逻辑部分:向 MatchingSystem 发送请求:从匹配池移除一个用户
|
// 取消匹配的逻辑部分:向 MatchingSystem 发送请求:从匹配池移除一个用户
|
||||||
|
@ -173,18 +187,20 @@ public class WebSocketServer {
|
||||||
System.out.println("stop matching");
|
System.out.println("stop matching");
|
||||||
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
|
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
|
||||||
data.add("user_id", this.user.getId().toString());
|
data.add("user_id", this.user.getId().toString());
|
||||||
String res = restTemplate.postForObject(removePlayerUrl, data, String.class);
|
restTemplate.postForObject(removePlayerUrl, data, String.class);
|
||||||
System.out.println(this.user.getId().toString() + " " + this.user.getUsername() + " " + res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// direction 传入 move(移动) 方向参数
|
// direction 传入 move(移动) 方向参数(将人的操作传给Gama)
|
||||||
private void move(int direction) {
|
private void move(int direction) {
|
||||||
// 判断角色:如果是 A 角色,则将获取到的方向设置为 A 的 nextStep 方向
|
// 判断角色:如果是 A 角色,则将获取到的方向设置为 A 的 nextStep 方向
|
||||||
// user.getId() 是获取当前链接的用户 id
|
// user.getId() 是获取当前链接的用户 id
|
||||||
if (game.getPlayerA().getId().equals(user.getId())) {
|
if (game.getPlayerA().getId().equals(user.getId())) {
|
||||||
game.setNextStepA(direction);
|
// 判断当前是否是人工操作(botId == -1),是人工操作时,返回人工操作结果
|
||||||
|
if (game.getPlayerA().getBotId() == -1)
|
||||||
|
game.setNextStepA(direction); // 亲自出马时,才接受这个用户键盘输入
|
||||||
} else if (game.getPlayerB().getId().equals(user.getId())) {
|
} else if (game.getPlayerB().getId().equals(user.getId())) {
|
||||||
game.setNextStepB(direction);
|
if (game.getPlayerB().getBotId() == -1)
|
||||||
|
game.setNextStepB(direction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +216,8 @@ public class WebSocketServer {
|
||||||
|
|
||||||
// 匹配状态函数调用
|
// 匹配状态函数调用
|
||||||
if ("start-matching".equals(event)) {
|
if ("start-matching".equals(event)) {
|
||||||
startMatching();
|
// 传入前端 MatchGround.vue->click_match_btn->JSON.stringify() 封装传过来的 bot_id
|
||||||
|
startMatching(data.getInteger("bot_id"));
|
||||||
} else if ("stop-matching".equals(event)) {
|
} else if ("stop-matching".equals(event)) {
|
||||||
stopMatching();
|
stopMatching();
|
||||||
} else if ("move".equals(event)) {
|
} else if ("move".equals(event)) {
|
||||||
|
|
|
@ -2,10 +2,11 @@ package com.kob.backend.consumer.utils;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.kob.backend.consumer.WebSocketServer;
|
import com.kob.backend.consumer.WebSocketServer;
|
||||||
|
import com.kob.backend.pojo.Bot;
|
||||||
import com.kob.backend.pojo.Record;
|
import com.kob.backend.pojo.Record;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
import javax.swing.event.InternalFrameEvent;
|
|
||||||
import java.sql.Time;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
@ -24,21 +25,40 @@ public class Game extends Thread {
|
||||||
private Integer nextStepA = null;
|
private Integer nextStepA = null;
|
||||||
private Integer nextStepB = null;
|
private Integer nextStepB = null;
|
||||||
// 加锁:解决读写冲突用
|
// 加锁:解决读写冲突用
|
||||||
private ReentrantLock lock = new ReentrantLock();
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
// 整个游戏的当前状态: playing(正在进行) --> finished(游戏结束)
|
// 整个游戏的当前状态: playing(正在进行) --> finished(游戏结束)
|
||||||
private String status = "playing";
|
private String status = "playing";
|
||||||
// 定义失败者: all(平局), A(A输), B(B输)
|
// 定义失败者: all(平局), A(A输), B(B输)
|
||||||
private String loser = "";
|
private String loser = "";
|
||||||
|
// 传送消息给 BotRunningSystem 的链接
|
||||||
|
private static final String addBotUrl = "http://127.0.0.1:3002/bot/add/";
|
||||||
|
|
||||||
|
|
||||||
// 初始化(有参)构造函数
|
// 初始化(有参)构造函数
|
||||||
public Game(Integer rows, Integer cols, Integer inner_walls_count, Integer idA, Integer idB) {
|
public Game(Integer rows, Integer cols, Integer inner_walls_count, Integer idA, Bot botA, Integer idB, Bot botB) {
|
||||||
this.rows = rows;
|
this.rows = rows;
|
||||||
this.cols = cols;
|
this.cols = cols;
|
||||||
this.inner_walls_count = inner_walls_count;
|
this.inner_walls_count = inner_walls_count;
|
||||||
this.g = new int[rows][cols];
|
this.g = new int[rows][cols];
|
||||||
playerA = new Player(idA, rows - 2, 1, new ArrayList<>());
|
|
||||||
playerB = new Player(idB, 1, cols - 2, new ArrayList<>());
|
// 定义默认 botId 值: botId 等于 -1 时表示玩家"亲自出战",非负则表示由对应 id 的 bot 操作(Ai)
|
||||||
|
Integer botIdA = -1, botIdB = -1;
|
||||||
|
// 定义默认 bot 代码值
|
||||||
|
String botCodeA = "", botCodeB = "";
|
||||||
|
// 判断 botA 不等于 null 时,对 botA 的 id 和 code 重新赋值;否则取默认值
|
||||||
|
if (botA != null) {
|
||||||
|
botIdA = botA.getId();
|
||||||
|
// botA.getContent() 是 bot 的代码区域
|
||||||
|
botCodeA = botA.getContent();
|
||||||
|
}
|
||||||
|
// 判断 botB 不等于 null 时,对 botB 的 id 和 code 重新赋值
|
||||||
|
if (botB != null) {
|
||||||
|
botIdB = botB.getId();
|
||||||
|
botCodeB = botB.getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
playerA = new Player(idA, botIdA, botCodeA, rows - 2, 1, new ArrayList<>());
|
||||||
|
playerB = new Player(idB, botIdB, botCodeB, 1, cols - 2, new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Player getPlayerA() {
|
public Player getPlayerA() {
|
||||||
|
@ -61,14 +81,17 @@ public class Game extends Thread {
|
||||||
return inner_walls_count;
|
return inner_walls_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将地图信息转换成字符串
|
||||||
private String getGameMapString() {
|
private String getGameMapString() {
|
||||||
StringBuilder res = new StringBuilder();
|
StringBuilder res = new StringBuilder();
|
||||||
// 将地图数据展开成一维
|
// 将地图数据展开成一维
|
||||||
/* for (int i = 0; i < rows; i++) {
|
/*
|
||||||
|
for (int i = 0; i < rows; i++) {
|
||||||
for (int j = 0; j < cols; j++) {
|
for (int j = 0; j < cols; j++) {
|
||||||
res.append(g[i][j]);
|
res.append(g[i][j]);
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
*/
|
||||||
for (int[] row : g) {
|
for (int[] row : g) {
|
||||||
for (int col : row) {
|
for (int col : row) {
|
||||||
res.append(col);
|
res.append(col);
|
||||||
|
@ -185,6 +208,44 @@ public class Game extends Thread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将当前的局面信息编码成字符串
|
||||||
|
private String getInput(Player player) {
|
||||||
|
Player me, you;
|
||||||
|
if (playerA.getId().equals(player.getId())) {
|
||||||
|
me = playerA;
|
||||||
|
you = playerB;
|
||||||
|
} else {
|
||||||
|
me = playerB;
|
||||||
|
you = playerA;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地图#me.sx#me.sy#我的操作#you.sx#you.sy#对手操作
|
||||||
|
// getStepsString() 操作序列可能是空的,为了防止分割出错,在这里加上()
|
||||||
|
return getGameMapString() + "#"
|
||||||
|
+ me.getSx() + "#"
|
||||||
|
+ me.getSy() + "#("
|
||||||
|
+ me.getStepsString() + ")#"
|
||||||
|
+ you.getSx() + "#"
|
||||||
|
+ you.getSy() + "#("
|
||||||
|
+ you.getStepsString() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送 bot 代码让服务器自动执行
|
||||||
|
private void sendBotCode(Player player) {
|
||||||
|
//// botId 等于 -1 时表示玩家"亲自出战",非负则表示由对应 id 的 bot 操作(Ai)
|
||||||
|
if (player.getBotId() == -1) return; // 当人来亲自操作时,直接 return, 不需要执行代码
|
||||||
|
|
||||||
|
// 不是人来控制时(改为 Bot 按照代码逻辑自动执行)
|
||||||
|
// 封装数据包
|
||||||
|
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
|
||||||
|
data.add("user_id", player.getId().toString());
|
||||||
|
data.add("bot_code", player.getBotCode());
|
||||||
|
data.add("input", getInput(player));
|
||||||
|
|
||||||
|
// 向 BotRunningSystem 发送消息
|
||||||
|
WebSocketServer.restTemplate.postForObject(addBotUrl, data, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
// 获取两名玩家的下一步操作
|
// 获取两名玩家的下一步操作
|
||||||
private boolean nextStep() {
|
private boolean nextStep() {
|
||||||
try {
|
try {
|
||||||
|
@ -195,6 +256,10 @@ public class Game extends Thread {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendBotCode(playerA);
|
||||||
|
sendBotCode(playerB);
|
||||||
|
|
||||||
|
|
||||||
// 循环 50 秒钟,每次 100 毫秒,判断用户输入(50 * 100 = 5000 ms后如果没有接收到输入,则判断输赢)
|
// 循环 50 秒钟,每次 100 毫秒,判断用户输入(50 * 100 = 5000 ms后如果没有接收到输入,则判断输赢)
|
||||||
for (int i = 0; i < 50; i++) {
|
for (int i = 0; i < 50; i++) {
|
||||||
try {
|
try {
|
||||||
|
@ -258,9 +323,9 @@ public class Game extends Thread {
|
||||||
// 游戏结束时,判断输赢
|
// 游戏结束时,判断输赢
|
||||||
if (!validA && !validB) {
|
if (!validA && !validB) {
|
||||||
loser = "all";
|
loser = "all";
|
||||||
} else if (!validA && validB) {
|
} else if (!validA) {
|
||||||
loser = "A";
|
loser = "A";
|
||||||
} else if (validA && !validB) {
|
} else {
|
||||||
loser = "B";
|
loser = "B";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ import java.util.List;
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class Player {
|
public class Player {
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
private Integer botId; // botId 等于 -1 时表示玩家"亲自出战",非负则表示由对应 id 的 bot 操作(Ai)
|
||||||
|
private String botCode; // bot 的代码
|
||||||
private Integer sx;
|
private Integer sx;
|
||||||
private Integer sy;
|
private Integer sy;
|
||||||
// 玩家走过的路径中每一步的方向
|
// 玩家走过的路径中每一步的方向
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.kob.backend.controller.pk;
|
||||||
|
|
||||||
|
import com.kob.backend.service.pk.ReceiveBotMoveService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class ReceiveBotMoveController {
|
||||||
|
@Autowired
|
||||||
|
private ReceiveBotMoveService receiveBotMoveService;
|
||||||
|
|
||||||
|
@PostMapping("/pk/receive/bot/move/")
|
||||||
|
public String receiveBotMove(@RequestParam MultiValueMap<String, String> data) {
|
||||||
|
Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));
|
||||||
|
Integer direction = Integer.parseInt(Objects.requireNonNull(data.getFirst("direction")));
|
||||||
|
return receiveBotMoveService.receiveBotMove(userId, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,7 +19,9 @@ public class StartGameController {
|
||||||
@PostMapping("/pk/start/")
|
@PostMapping("/pk/start/")
|
||||||
public String startGame(@RequestParam MultiValueMap<String, String> data) {
|
public String startGame(@RequestParam MultiValueMap<String, String> data) {
|
||||||
Integer aId = Integer.parseInt(Objects.requireNonNull(data.getFirst("a_id")));
|
Integer aId = Integer.parseInt(Objects.requireNonNull(data.getFirst("a_id")));
|
||||||
|
Integer aBotId = Integer.parseInt(Objects.requireNonNull(data.getFirst("a_bot_id")));
|
||||||
Integer bId = Integer.parseInt(Objects.requireNonNull(data.getFirst("b_id")));
|
Integer bId = Integer.parseInt(Objects.requireNonNull(data.getFirst("b_id")));
|
||||||
return startGameService.startGame(aId, bId);
|
Integer bBotId = Integer.parseInt(Objects.requireNonNull(data.getFirst("b_bot_id")));
|
||||||
|
return startGameService.startGame(aId, aBotId, bId, bBotId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.kob.backend.service.impl.pk;
|
||||||
|
|
||||||
|
import com.kob.backend.consumer.WebSocketServer;
|
||||||
|
import com.kob.backend.consumer.utils.Game;
|
||||||
|
import com.kob.backend.service.pk.ReceiveBotMoveService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ReceiveBotMoveServiceImpl implements ReceiveBotMoveService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String receiveBotMove(Integer userId, Integer direction) {
|
||||||
|
System.out.println("receive bot move: -> " + "userid: " + userId + "direction: " + direction);
|
||||||
|
|
||||||
|
// 看看操作的 user 是否还存在
|
||||||
|
if (WebSocketServer.users.get(userId) != null) {
|
||||||
|
// 取出 game
|
||||||
|
Game game = WebSocketServer.users.get(userId).game;
|
||||||
|
// 判断 game 是否存在
|
||||||
|
if (game != null) {
|
||||||
|
// 将 Bot 的操作传给 game
|
||||||
|
// 判断角色:如果是 A 角色,则将获取到的方向设置为 botA 的 nextStep 方向
|
||||||
|
if (game.getPlayerA().getId().equals(userId)) {
|
||||||
|
game.setNextStepA(direction);
|
||||||
|
} else if (game.getPlayerB().getId().equals(userId)) {
|
||||||
|
game.setNextStepB(direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return "receive bot move success";
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,9 +7,9 @@ import org.springframework.stereotype.Service;
|
||||||
@Service
|
@Service
|
||||||
public class StartGameServiceImpl implements StartGameService {
|
public class StartGameServiceImpl implements StartGameService {
|
||||||
@Override
|
@Override
|
||||||
public String startGame(Integer aId, Integer bId) {
|
public String startGame(Integer aId, Integer aBotId, Integer bId, Integer bBotId) {
|
||||||
System.out.println("start game " + aId + " " + bId);
|
System.out.println("start game " + aId + " " + bId);
|
||||||
WebSocketServer.startGame(aId, bId); // 调用 WebSocketServer->startGame 函数
|
WebSocketServer.startGame(aId, aBotId, bId, bBotId); // 调用 WebSocketServer->startGame 函数
|
||||||
return "start game success";
|
return "start game success";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
// 接收 botrunningsystem 传回的 bot -> move 信息
|
||||||
|
package com.kob.backend.service.pk;
|
||||||
|
|
||||||
|
public interface ReceiveBotMoveService {
|
||||||
|
String receiveBotMove(Integer userId,Integer direction); // 接收传入的用户 id 和运动方向信息
|
||||||
|
}
|
|
@ -3,5 +3,5 @@ package com.kob.backend.service.pk;
|
||||||
|
|
||||||
public interface StartGameService {
|
public interface StartGameService {
|
||||||
// 参数是匹配的两位玩家的 id
|
// 参数是匹配的两位玩家的 id
|
||||||
String startGame(Integer aId, Integer bId);
|
String startGame(Integer aId, Integer aBotId, Integer bId, Integer bBotId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<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>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.kob</groupId>
|
||||||
|
<artifactId>backendcloud</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.kob.botrunningsystem</groupId>
|
||||||
|
<artifactId>botrunningsystem</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- 用于动态编译 java-8 代码 -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.jooq/joor-java-8 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jooq</groupId>
|
||||||
|
<artifactId>joor-java-8</artifactId>
|
||||||
|
<version>0.9.14</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 用于安全验证-->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 简化代码,可以帮助写一些构造函数, set() , get() 函数等-->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.26</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.kob.botrunningsystem;
|
||||||
|
|
||||||
|
import com.kob.botrunningsystem.service.BotRunningService;
|
||||||
|
import com.kob.botrunningsystem.service.impl.BotRunningServiceImpl;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class BotRunningSystemApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 启动 BotRunningSystem 服务之前启动 BotPool 线程
|
||||||
|
BotRunningServiceImpl.botPool.start(); // 启动 Bot 执行线程
|
||||||
|
|
||||||
|
SpringApplication.run(BotRunningSystemApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.kob.botrunningsystem.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class RestTemplateConfig {
|
||||||
|
@Bean
|
||||||
|
public RestTemplate getRestTemplate() {
|
||||||
|
return new RestTemplate();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.kob.botrunningsystem.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
@Override
|
||||||
|
public void configure(HttpSecurity http) throws Exception {
|
||||||
|
http.csrf().disable()
|
||||||
|
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||||
|
.and()
|
||||||
|
.authorizeRequests()
|
||||||
|
.antMatchers("/bot/add/").hasIpAddress("127.0.0.1")
|
||||||
|
.antMatchers(HttpMethod.OPTIONS).permitAll()
|
||||||
|
.anyRequest().authenticated();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.kob.botrunningsystem.controller;
|
||||||
|
|
||||||
|
import com.kob.botrunningsystem.service.BotRunningService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class BotRunningController {
|
||||||
|
@Autowired
|
||||||
|
private BotRunningService botRunningService;
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/bot/add/")
|
||||||
|
public String addBot(@RequestParam MultiValueMap<String, String> data) {
|
||||||
|
Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));
|
||||||
|
String botCode = data.getFirst("bot_code");
|
||||||
|
String input = data.getFirst("input");
|
||||||
|
return botRunningService.addBot(userId, botCode, input);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.kob.botrunningsystem.service;
|
||||||
|
|
||||||
|
public interface BotRunningService {
|
||||||
|
// 参数: userId 用户, botCode bot代码, input 输入的地图信息(障碍物,两条蛇的位置,走过的路径等信息)
|
||||||
|
String addBot(Integer userId, String botCode, String input);
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.kob.botrunningsystem.service.impl;
|
||||||
|
|
||||||
|
import com.kob.botrunningsystem.service.BotRunningService;
|
||||||
|
import com.kob.botrunningsystem.service.impl.utils.BotPool;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class BotRunningServiceImpl implements BotRunningService {
|
||||||
|
// BotPool 全局只有一个线程,所以这里定义成静态
|
||||||
|
public static final BotPool botPool = new BotPool();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String addBot(Integer userId, String botCode, String input) {
|
||||||
|
System.out.println(userId + " add a bot: " + botCode + " " + input);
|
||||||
|
botPool.addBot(userId, botCode, input);
|
||||||
|
return "add bot success";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.kob.botrunningsystem.service.impl.utils;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Bot {
|
||||||
|
Integer userId;
|
||||||
|
String botCode;
|
||||||
|
String input;
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.kob.botrunningsystem.service.impl.utils;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
public class BotPool extends Thread {
|
||||||
|
// 定义可重入锁
|
||||||
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
// 定义条件变量:如果队列空,线程会阻塞;一旦有新的任务输入,则唤醒线程
|
||||||
|
private final Condition condition = lock.newCondition();
|
||||||
|
// 定义队列: Queue 会在两个线程中操作 ==> 生产者会生产任务 ; 消费者会消费任务
|
||||||
|
private final Queue<Bot> bots = new LinkedList<>();
|
||||||
|
|
||||||
|
// 往队列中插入一个 bot
|
||||||
|
public void addBot(Integer userId, String botCode, String input) {
|
||||||
|
// 这里涉及对 Queue 的操作,需要枷锁
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
bots.add(new Bot(userId, botCode, input)); // 给队列添加新的 bot
|
||||||
|
condition.signalAll(); // 唤醒另外一个线程
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消费任务:编译执行代码(取出来的任务) ==> 使用 JOOR
|
||||||
|
// 每次执行一个代码都开一个线程,用来操控执行时间
|
||||||
|
private void consume(Bot bot) {
|
||||||
|
// 将代码发送给 Consumer
|
||||||
|
Consumer consumer = new Consumer(); // 定义一个 consumer
|
||||||
|
consumer.startTimeout(2000,bot); // 每个 bot 每回合最多执行 2 秒(每个玩家每回合设定等待 5 秒输入)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 如果队列空,线程会阻塞;一旦有新的任务输入,则唤醒线程
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// 循环消费任务
|
||||||
|
while (true) {
|
||||||
|
lock.lock();
|
||||||
|
// 如果 bots 队列为空,则阻塞当前线程
|
||||||
|
if (bots.isEmpty()) {
|
||||||
|
try {
|
||||||
|
condition.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
lock.unlock(); // 报异常要解锁
|
||||||
|
break; // 报异常则直接 break 中断进程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 bots 不空,则
|
||||||
|
else {
|
||||||
|
// 取出队头任务
|
||||||
|
Bot bot = bots.remove(); // Queue.remove() 返回并删除对头元素
|
||||||
|
lock.unlock(); // 取完队头,即解锁
|
||||||
|
|
||||||
|
// 消费取出来的任务: consume() 比较耗时,因此需要在执行之前先解锁 lock.unlock
|
||||||
|
consume(bot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.kob.botrunningsystem.service.impl.utils;
|
||||||
|
|
||||||
|
import com.kob.botrunningsystem.utils.BotInterface;
|
||||||
|
import org.joor.Reflect;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class Consumer extends Thread {
|
||||||
|
private Bot bot;
|
||||||
|
// 定义返回信息 URL
|
||||||
|
private final static String receiveBotMoveUrl = "http://127.0.0.1:3000/pk/receive/bot/move/";
|
||||||
|
|
||||||
|
//注入 RestTemplate 用于给 backend 服务发送信息
|
||||||
|
private static RestTemplate restTemplate;
|
||||||
|
|
||||||
|
//注入 RestTemplate 用于给 backend 服务发送信息
|
||||||
|
@Autowired
|
||||||
|
public void setRestTemplate(RestTemplate restTemplate) {
|
||||||
|
Consumer.restTemplate = restTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 传入 等待时间 和 bot
|
||||||
|
public void startTimeout(long timeout, Bot bot) {
|
||||||
|
this.bot = bot;
|
||||||
|
this.start(); // 执行线程
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 最多阻塞 timeout 时间后继续执行后面的操作(上面的线程最多等待执行 timeout 秒)
|
||||||
|
// 相比 sleep 的好处是, join 最多执行 timeout 秒,如果这个早跑完任务就早点结束阻塞; sleep 则必须阻塞够 timeout 才会结束阻塞
|
||||||
|
this.join(timeout);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
this.interrupt(); // 中断当前线程:一旦等待时间超过 timeout 秒,就中断线程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在 Code 中的 Bot 类名后面," implements com.kob.botrunningsystem.utils.BotInterface" 之前加上 uid
|
||||||
|
private String addUid(String code, String uid) {
|
||||||
|
int k = code.indexOf(" implements com.kob.botrunningsystem.utils.BotInterface");
|
||||||
|
return code.substring(0, k) + uid + code.substring(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// 使用 UUID 生成随机字符串
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
String uid = uuid.toString().substring(0, 8); // 返回前 8 位 uuid 作为 uid
|
||||||
|
|
||||||
|
// Reflect 来自 JOOR 依赖: 可以动态的编译 java 代码
|
||||||
|
// 重名类只会编译一遍,为了每次新线程都重新编译,
|
||||||
|
// 在类名后加一个随机字符串 UUID(每次返回一个不一样的ID)
|
||||||
|
BotInterface botInterface = Reflect.compile(
|
||||||
|
"com.kob.botrunningsystem.utils.Bot" + uid,
|
||||||
|
addUid(bot.getBotCode(), uid)
|
||||||
|
).create().get(); // compile(编译) 完这个类之后 create(创建) 一个类,并 get(获取) 到这个类
|
||||||
|
|
||||||
|
// 存储获取到的方向
|
||||||
|
Integer direction = botInterface.nextMove(bot.getInput());
|
||||||
|
System.out.println("move-direction: " + bot.getUserId() + " " + direction); // 根据动态编译结果返回输入
|
||||||
|
|
||||||
|
// 打包信息
|
||||||
|
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
|
||||||
|
data.add("user_id", bot.getUserId().toString());
|
||||||
|
data.add("direction", direction.toString());
|
||||||
|
// 返回结果信息给 backend 服务端
|
||||||
|
restTemplate.postForObject(receiveBotMoveUrl, data, String.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Bot 自动化代码示例
|
||||||
|
package com.kob.botrunningsystem.utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Bot implements com.kob.botrunningsystem.utils.BotInterface {
|
||||||
|
static class Cell {
|
||||||
|
public int x, y;
|
||||||
|
public Cell(int x, int y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean check_tail_increasing(int step) { // 检验当前回合,蛇的长度是否增加
|
||||||
|
if (step <= 10) return true;
|
||||||
|
return step % 3 == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Cell> getCells(int sx, int sy, String steps) {
|
||||||
|
steps = steps.substring(1, steps.length() - 1);
|
||||||
|
List<Cell> res = new ArrayList<>();
|
||||||
|
|
||||||
|
int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
|
||||||
|
int x = sx, y = sy;
|
||||||
|
int step = 0;
|
||||||
|
res.add(new Cell(x, y));
|
||||||
|
for (int i = 0; i < steps.length(); i ++ ) {
|
||||||
|
int d = steps.charAt(i) - '0';
|
||||||
|
x += dx[d];
|
||||||
|
y += dy[d];
|
||||||
|
res.add(new Cell(x, y));
|
||||||
|
if (!check_tail_increasing( ++ step)) {
|
||||||
|
res.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer nextMove(String input) {
|
||||||
|
String[] strs = input.split("#");
|
||||||
|
int[][] g = new int[13][14];
|
||||||
|
for (int i = 0, k = 0; i < 13; i ++ ) {
|
||||||
|
for (int j = 0; j < 14; j ++, k ++ ) {
|
||||||
|
if (strs[0].charAt(k) == '1') {
|
||||||
|
g[i][j] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int aSx = Integer.parseInt(strs[1]), aSy = Integer.parseInt(strs[2]);
|
||||||
|
int bSx = Integer.parseInt(strs[4]), bSy = Integer.parseInt(strs[5]);
|
||||||
|
|
||||||
|
List<Cell> aCells = getCells(aSx, aSy, strs[3]);
|
||||||
|
List<Cell> bCells = getCells(bSx, bSy, strs[6]);
|
||||||
|
|
||||||
|
for (Cell c: aCells) g[c.x][c.y] = 1;
|
||||||
|
for (Cell c: bCells) g[c.x][c.y] = 1;
|
||||||
|
|
||||||
|
int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
|
||||||
|
for (int i = 0; i < 4; i ++ ) {
|
||||||
|
int x = aCells.get(aCells.size() - 1).x + dx[i];
|
||||||
|
int y = aCells.get(aCells.size() - 1).y + dy[i];
|
||||||
|
if (x >= 0 && x < 13 && y >= 0 && y < 14 && g[x][y] == 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
// 实现前端用户编写的 AI 的接口(api)
|
||||||
|
package com.kob.botrunningsystem.utils;
|
||||||
|
|
||||||
|
public interface BotInterface {
|
||||||
|
Integer nextMove(String input); // 下一步要走的方向
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
server.port=3002
|
|
@ -21,7 +21,8 @@ public class MatchingController {
|
||||||
public String addPlayer(@RequestParam MultiValueMap<String, String> data) {
|
public String addPlayer(@RequestParam MultiValueMap<String, String> data) {
|
||||||
Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));
|
Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id")));
|
||||||
Integer rating = Integer.parseInt(Objects.requireNonNull(data.getFirst("rating")));
|
Integer rating = Integer.parseInt(Objects.requireNonNull(data.getFirst("rating")));
|
||||||
return matchingService.addPlayer(userId, rating);
|
Integer botId = Integer.parseInt(Objects.requireNonNull(data.getFirst("bot_id")));
|
||||||
|
return matchingService.addPlayer(userId, rating,botId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/player/remove/")
|
@PostMapping("/player/remove/")
|
||||||
|
|
|
@ -3,7 +3,7 @@ package com.kob.matchingsystem.service;
|
||||||
|
|
||||||
public interface MatchingService {
|
public interface MatchingService {
|
||||||
// 给匹配池添加一名玩家
|
// 给匹配池添加一名玩家
|
||||||
String addPlayer(Integer userId, Integer rating);
|
String addPlayer(Integer userId, Integer rating,Integer botId);
|
||||||
|
|
||||||
// 从匹配池删除一名玩家
|
// 从匹配池删除一名玩家
|
||||||
String removePlayer(Integer userId);
|
String removePlayer(Integer userId);
|
||||||
|
|
|
@ -11,10 +11,10 @@ public class MatchingServiceImpl implements MatchingService {
|
||||||
public final static MatchingPool matchingPool = new MatchingPool();
|
public final static MatchingPool matchingPool = new MatchingPool();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String addPlayer(Integer userId, Integer rating) {
|
public String addPlayer(Integer userId, Integer rating, Integer botId) {
|
||||||
System.out.println("add player: " + userId + " " + rating);
|
System.out.println("add player: " + userId + " " + rating);
|
||||||
// 向匹配池添加一名玩家
|
// 向匹配池添加一名玩家
|
||||||
matchingPool.addPlayer(userId, rating);
|
matchingPool.addPlayer(userId, rating, botId);
|
||||||
return "add player success";
|
return "add player success";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,11 +27,11 @@ public class MatchingPool extends Thread {
|
||||||
MatchingPool.restTemplate = restTemplate;
|
MatchingPool.restTemplate = restTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPlayer(Integer userId, Integer rating) {
|
public void addPlayer(Integer userId, Integer rating,Integer botId) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
// 一开始匹配等待时间是 0
|
// 一开始匹配等待时间是 0
|
||||||
playerList.add(new Player(userId, rating, 0));
|
playerList.add(new Player(userId, rating,botId, 0));
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
|
@ -77,8 +77,11 @@ public class MatchingPool extends Thread {
|
||||||
// 返回 a 和 b 的匹配结果
|
// 返回 a 和 b 的匹配结果
|
||||||
private void sendResult(Player a, Player b) {
|
private void sendResult(Player a, Player b) {
|
||||||
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
|
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
|
||||||
|
// 返回两名玩家的 userId 和 botId
|
||||||
data.add("a_id", a.getUserId().toString());
|
data.add("a_id", a.getUserId().toString());
|
||||||
|
data.add("a_bot_id",a.getBotId().toString());
|
||||||
data.add("b_id", b.getUserId().toString());
|
data.add("b_id", b.getUserId().toString());
|
||||||
|
data.add("b_bot_id",b.getBotId().toString());
|
||||||
restTemplate.postForObject(startGameUrl, data, String.class);
|
restTemplate.postForObject(startGameUrl, data, String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,5 +11,6 @@ import lombok.NoArgsConstructor;
|
||||||
public class Player {
|
public class Player {
|
||||||
private Integer userId;
|
private Integer userId;
|
||||||
private Integer rating;
|
private Integer rating;
|
||||||
|
private Integer botId;
|
||||||
private Integer waitingTime; // 等待时间
|
private Integer waitingTime; // 等待时间
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
<modules>
|
<modules>
|
||||||
<module>matchingsystem</module>
|
<module>matchingsystem</module>
|
||||||
<module>backend</module>
|
<module>backend</module>
|
||||||
|
<module>botrunningsystem</module>
|
||||||
</modules>
|
</modules>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="matchground">
|
<div class="matchground">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- 左边 6 份,右边 6 份 -->
|
<!-- 左边 4 份,右边 4 份,中间留 4 份用于选择出战方式 -->
|
||||||
<div class="col-6">
|
<div class="col-4">
|
||||||
<!-- 用户自己的信息 -->
|
<!-- 用户自己的信息 -->
|
||||||
<div class="user-photo">
|
<div class="user-photo">
|
||||||
<img :src="$store.state.user.photo" alt="">
|
<img :src="$store.state.user.photo" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div class="user-username">{{ $store.state.user.username }}</div>
|
<div class="user-username">{{ $store.state.user.username }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-4">
|
||||||
|
<div class="user-select-bot">
|
||||||
|
<!-- v-model 绑定 selece_bot -->
|
||||||
|
<select v-model="select_bot" class="form-select" aria-label="Default select example">
|
||||||
|
<!-- 玩家亲自出战的 value 置为 -1, 如果不是亲自出战, 则是非负数 -->
|
||||||
|
<option value="-1" selected>亲自出战</option>
|
||||||
|
<option v-for="bot in bots" :key="bot.id" :value="bot.id">
|
||||||
|
{{ bot.title }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
<!-- 对手的信息 -->
|
<!-- 对手的信息 -->
|
||||||
<div class="opponent-photo">
|
<div class="opponent-photo">
|
||||||
<img :src="$store.state.pk.opponent_photo" alt="">
|
<img :src="$store.state.pk.opponent_photo" alt="">
|
||||||
|
@ -33,32 +45,60 @@ export default {
|
||||||
setup: () => {
|
setup: () => {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
let match_btn_info = ref("开始匹配")
|
let match_btn_info = ref("开始匹配")
|
||||||
|
let bots = ref([]);
|
||||||
|
let select_bot = ref("-1"); // 选中的 bot 一开始是 -1 ,即一开始是亲自出战模式
|
||||||
|
|
||||||
const click_match_btn = () => {
|
const click_match_btn = () => {
|
||||||
if (match_btn_info.value === "开始匹配") {
|
if (match_btn_info.value === "开始匹配") {
|
||||||
match_btn_info.value = "取消";
|
match_btn_info.value = "取消";
|
||||||
|
console.log(select_bot.value);
|
||||||
// 向后端发请求,使用 JSON.stringify() 将一个 JSON 封装成一字符串
|
// 向后端发请求,使用 JSON.stringify() 将一个 JSON 封装成一字符串
|
||||||
store.state.pk.socket.send(JSON.stringify({
|
store.state.pk.socket.send(JSON.stringify({
|
||||||
// 传一个 event 域
|
// 传一个 event 域
|
||||||
event:"start-matching",
|
event: "start-matching",
|
||||||
|
bot_id:select_bot.value,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
match_btn_info.value = "开始匹配";
|
match_btn_info.value = "开始匹配";
|
||||||
store.state.pk.socket.send(JSON.stringify({
|
store.state.pk.socket.send(JSON.stringify({
|
||||||
event:"stop-matching",
|
event: "stop-matching",
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const refresh_bots = () => {
|
||||||
|
// 获取 bots 列表
|
||||||
|
store.dispatch("getList", {
|
||||||
|
success(resp) {
|
||||||
|
console.log(resp);
|
||||||
|
bots.value = resp;
|
||||||
|
},
|
||||||
|
error(resp) {
|
||||||
|
console.log(resp);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_bots(); // 从云端动态获取 bots
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
match_btn_info,
|
match_btn_info,
|
||||||
click_match_btn,
|
click_match_btn,
|
||||||
|
bots,
|
||||||
|
select_bot,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
div.user-select-bot {
|
||||||
|
padding-top: 20vh;
|
||||||
|
width: 60%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.matchground {
|
.matchground {
|
||||||
/* 60% 浏览器宽度, 70% 浏览器高度 */
|
/* 60% 浏览器宽度, 70% 浏览器高度 */
|
||||||
width: 60vw;
|
width: 60vw;
|
||||||
|
|
|
@ -36,6 +36,8 @@ export default {
|
||||||
photo: "https://typoraflykhan.oss-cn-beijing.aliyuncs.com/202302251825860.png",
|
photo: "https://typoraflykhan.oss-cn-beijing.aliyuncs.com/202302251825860.png",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
store.commit("updateLoser", "none");
|
||||||
|
|
||||||
// 新建 WebSocket
|
// 新建 WebSocket
|
||||||
socket = new WebSocket(socketUrl);
|
socket = new WebSocket(socketUrl);
|
||||||
store.commit("updateSocket", socket);
|
store.commit("updateSocket", socket);
|
||||||
|
|
Loading…
Reference in New Issue