实现bot代码执行的微服务 botrunningsystem, 主服务前后端做出对应修改
This commit is contained in:
@@ -49,7 +49,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.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()
|
||||
.anyRequest().authenticated();
|
||||
|
||||
|
||||
@@ -3,14 +3,15 @@ package com.kob.backend.consumer;
|
||||
// WebSocket用于前后端通信
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.kob.backend.config.RestTemplateConfig;
|
||||
import com.kob.backend.consumer.utils.Game;
|
||||
import com.kob.backend.consumer.utils.JwtAuthenticationUtil;
|
||||
import com.kob.backend.mapper.BotMapper;
|
||||
import com.kob.backend.mapper.RecordMapper;
|
||||
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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@@ -19,11 +20,8 @@ import org.springframework.web.client.RestTemplate;
|
||||
import javax.websocket.*;
|
||||
import javax.websocket.server.PathParam;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import javax.xml.stream.events.StartDocument;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
@Component
|
||||
@ServerEndpoint("/websocket/{token}") // 注意不要以'/'结尾
|
||||
@@ -36,16 +34,17 @@ public class WebSocketServer {
|
||||
*/
|
||||
public static final ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>();
|
||||
// 用于和 MatchingSystem 进行通信
|
||||
private static RestTemplate restTemplate;
|
||||
public static RestTemplate restTemplate;
|
||||
// 在 WebSocketServer 中注入数据库的方法演示-> 使用 static 定义为独一份的变量
|
||||
private static UserMapper userMapper;
|
||||
private static BotMapper botMapper;
|
||||
// 注入 RecordMapper 用于调用实现游戏数据到数据库的存储
|
||||
public static RecordMapper recordMapper;
|
||||
// 后端向前端发送信息,首先需要创建一个 session
|
||||
private Session session = null;
|
||||
// 用户信息:定义一个成员变量
|
||||
private User user;
|
||||
private Game game = null;
|
||||
public Game game = null;
|
||||
// addPlayer 添加用户到匹配池的 URL; removePlayer 从匹配池移除用户的 URL
|
||||
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/";
|
||||
@@ -63,12 +62,16 @@ public class WebSocketServer {
|
||||
WebSocketServer.userMapper = userMapper;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setBotMapper(BotMapper botMapper) {
|
||||
WebSocketServer.botMapper = botMapper;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setRecordMapper(RecordMapper recordMapper) {
|
||||
WebSocketServer.recordMapper = recordMapper;
|
||||
}
|
||||
|
||||
|
||||
// @OnOpen 创建链接时自动触发这个函数
|
||||
@OnOpen
|
||||
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 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(); // 初始化地图
|
||||
// 当用户不为空时,才能执行下面的操作(防止有人退出游戏后用户已经成为空指针)
|
||||
if (users.get(a.getId()) != null)
|
||||
@@ -124,6 +137,7 @@ public class WebSocketServer {
|
||||
|
||||
game.start(); // 开启新线程,执行函数
|
||||
|
||||
// 打包地图数据信息
|
||||
JSONObject respGameData = new JSONObject();
|
||||
respGameData.put("game_map", game.getG());
|
||||
respGameData.put("rows", game.getRows());
|
||||
@@ -157,15 +171,15 @@ public class WebSocketServer {
|
||||
}
|
||||
|
||||
// 开始匹配的逻辑部分:开始匹配时向 MatchingSystem 服务端发一个请求
|
||||
private void startMatching() {
|
||||
private void startMatching(Integer botId) {
|
||||
System.out.println("start matching");
|
||||
// 这里的参数列表需要与 MatchingSystem(微服务)->MatchingController->addPlayer 的参数对应
|
||||
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
|
||||
data.add("user_id", this.user.getId().toString());
|
||||
data.add("rating", this.user.getRating().toString());
|
||||
data.add("bot_id", botId.toString()); // 将选中的 botId 传给匹配系统
|
||||
// postForObject(请求的URL,传出的数据,返回值的类型.class) -> Java 反射机制
|
||||
String res = restTemplate.postForObject(addPlayerUrl, data, String.class);
|
||||
System.out.println(this.user.getId().toString() + " " + this.user.getUsername() + " " + res);
|
||||
restTemplate.postForObject(addPlayerUrl, data, String.class);
|
||||
}
|
||||
|
||||
// 取消匹配的逻辑部分:向 MatchingSystem 发送请求:从匹配池移除一个用户
|
||||
@@ -173,18 +187,20 @@ public class WebSocketServer {
|
||||
System.out.println("stop matching");
|
||||
MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
|
||||
data.add("user_id", this.user.getId().toString());
|
||||
String res = restTemplate.postForObject(removePlayerUrl, data, String.class);
|
||||
System.out.println(this.user.getId().toString() + " " + this.user.getUsername() + " " + res);
|
||||
restTemplate.postForObject(removePlayerUrl, data, String.class);
|
||||
}
|
||||
|
||||
// direction 传入 move(移动) 方向参数
|
||||
// direction 传入 move(移动) 方向参数(将人的操作传给Gama)
|
||||
private void move(int direction) {
|
||||
// 判断角色:如果是 A 角色,则将获取到的方向设置为 A 的 nextStep 方向
|
||||
// user.getId() 是获取当前链接的用户 id
|
||||
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())) {
|
||||
game.setNextStepB(direction);
|
||||
if (game.getPlayerB().getBotId() == -1)
|
||||
game.setNextStepB(direction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +216,8 @@ public class WebSocketServer {
|
||||
|
||||
// 匹配状态函数调用
|
||||
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)) {
|
||||
stopMatching();
|
||||
} else if ("move".equals(event)) {
|
||||
|
||||
@@ -2,10 +2,11 @@ package com.kob.backend.consumer.utils;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.kob.backend.consumer.WebSocketServer;
|
||||
import com.kob.backend.pojo.Bot;
|
||||
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.concurrent.locks.ReentrantLock;
|
||||
|
||||
@@ -24,21 +25,40 @@ public class Game extends Thread {
|
||||
private Integer nextStepA = null;
|
||||
private Integer nextStepB = null;
|
||||
// 加锁:解决读写冲突用
|
||||
private ReentrantLock lock = new ReentrantLock();
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
// 整个游戏的当前状态: playing(正在进行) --> finished(游戏结束)
|
||||
private String status = "playing";
|
||||
// 定义失败者: all(平局), A(A输), B(B输)
|
||||
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.cols = cols;
|
||||
this.inner_walls_count = inner_walls_count;
|
||||
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() {
|
||||
@@ -61,14 +81,17 @@ public class Game extends Thread {
|
||||
return inner_walls_count;
|
||||
}
|
||||
|
||||
// 将地图信息转换成字符串
|
||||
private String getGameMapString() {
|
||||
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++) {
|
||||
res.append(g[i][j]);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
*/
|
||||
for (int[] row : g) {
|
||||
for (int col : row) {
|
||||
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() {
|
||||
try {
|
||||
@@ -195,6 +256,10 @@ public class Game extends Thread {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
sendBotCode(playerA);
|
||||
sendBotCode(playerB);
|
||||
|
||||
|
||||
// 循环 50 秒钟,每次 100 毫秒,判断用户输入(50 * 100 = 5000 ms后如果没有接收到输入,则判断输赢)
|
||||
for (int i = 0; i < 50; i++) {
|
||||
try {
|
||||
@@ -258,9 +323,9 @@ public class Game extends Thread {
|
||||
// 游戏结束时,判断输赢
|
||||
if (!validA && !validB) {
|
||||
loser = "all";
|
||||
} else if (!validA && validB) {
|
||||
} else if (!validA) {
|
||||
loser = "A";
|
||||
} else if (validA && !validB) {
|
||||
} else {
|
||||
loser = "B";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import java.util.List;
|
||||
@NoArgsConstructor
|
||||
public class Player {
|
||||
private Integer id;
|
||||
private Integer botId; // botId 等于 -1 时表示玩家"亲自出战",非负则表示由对应 id 的 bot 操作(Ai)
|
||||
private String botCode; // bot 的代码
|
||||
private Integer sx;
|
||||
private Integer sy;
|
||||
// 玩家走过的路径中每一步的方向
|
||||
|
||||
+24
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+3
-1
@@ -19,7 +19,9 @@ public class StartGameController {
|
||||
@PostMapping("/pk/start/")
|
||||
public String startGame(@RequestParam MultiValueMap<String, String> data) {
|
||||
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")));
|
||||
return startGameService.startGame(aId, bId);
|
||||
Integer bBotId = Integer.parseInt(Objects.requireNonNull(data.getFirst("b_bot_id")));
|
||||
return startGameService.startGame(aId, aBotId, bId, bBotId);
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -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";
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -7,9 +7,9 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class StartGameServiceImpl implements StartGameService {
|
||||
@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);
|
||||
WebSocketServer.startGame(aId, bId); // 调用 WebSocketServer->startGame 函数
|
||||
WebSocketServer.startGame(aId, aBotId, bId, bBotId); // 调用 WebSocketServer->startGame 函数
|
||||
return "start game success";
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -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 {
|
||||
// 参数是匹配的两位玩家的 id
|
||||
String startGame(Integer aId, Integer bId);
|
||||
String startGame(Integer aId, Integer aBotId, Integer bId, Integer bBotId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user