From ebd3f3c6107d97c1dda0c17b3e4fe7a3ed9b2b40 Mon Sep 17 00:00:00 2001 From: flykhan Date: Thu, 9 Mar 2023 10:46:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0bot=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E7=9A=84=E5=BE=AE=E6=9C=8D=E5=8A=A1=20botrun?= =?UTF-8?q?ningsystem,=20=E4=B8=BB=E6=9C=8D=E5=8A=A1=E5=89=8D=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E5=81=9A=E5=87=BA=E5=AF=B9=E5=BA=94=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kob/backend/config/SecurityConfig.java | 2 +- .../kob/backend/consumer/WebSocketServer.java | 55 +++++++----- .../com/kob/backend/consumer/utils/Game.java | 85 ++++++++++++++++--- .../kob/backend/consumer/utils/Player.java | 2 + .../pk/ReceiveBotMoveController.java | 24 ++++++ .../controller/pk/StartGameController.java | 4 +- .../impl/pk/ReceiveBotMoveServiceImpl.java | 34 ++++++++ .../service/impl/pk/StartGameServiceImpl.java | 4 +- .../service/pk/ReceiveBotMoveService.java | 6 ++ .../backend/service/pk/StartGameService.java | 2 +- backendcloud/botrunningsystem/pom.xml | 48 +++++++++++ .../BotRunningSystemApplication.java | 16 ++++ .../config/RestTemplateConfig.java | 13 +++ .../config/SecurityConfig.java | 24 ++++++ .../controller/BotRunningController.java | 25 ++++++ .../service/BotRunningService.java | 6 ++ .../service/impl/BotRunningServiceImpl.java | 18 ++++ .../service/impl/utils/Bot.java | 14 +++ .../service/impl/utils/BotPool.java | 65 ++++++++++++++ .../service/impl/utils/Consumer.java | 75 ++++++++++++++++ .../com/kob/botrunningsystem/utils/Bot.java | 73 ++++++++++++++++ .../botrunningsystem/utils/BotInterface.java | 6 ++ .../src/main/resources/application.properties | 1 + .../controller/MatchingController.java | 3 +- .../service/MatchingService.java | 2 +- .../service/impl/MatchingServiceImpl.java | 4 +- .../service/impl/utils/MatchingPool.java | 7 +- .../service/impl/utils/Player.java | 1 + backendcloud/pom.xml | 1 + web/src/components/MatchGround.vue | 50 +++++++++-- web/src/views/pk/PkIndexView.vue | 2 + 31 files changed, 627 insertions(+), 45 deletions(-) create mode 100644 backendcloud/backend/src/main/java/com/kob/backend/controller/pk/ReceiveBotMoveController.java create mode 100644 backendcloud/backend/src/main/java/com/kob/backend/service/impl/pk/ReceiveBotMoveServiceImpl.java create mode 100644 backendcloud/backend/src/main/java/com/kob/backend/service/pk/ReceiveBotMoveService.java create mode 100644 backendcloud/botrunningsystem/pom.xml create mode 100644 backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/BotRunningSystemApplication.java create mode 100644 backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/config/RestTemplateConfig.java create mode 100644 backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/config/SecurityConfig.java create mode 100644 backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/controller/BotRunningController.java create mode 100644 backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/BotRunningService.java create mode 100644 backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/BotRunningServiceImpl.java create mode 100644 backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/utils/Bot.java create mode 100644 backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/utils/BotPool.java create mode 100644 backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/utils/Consumer.java create mode 100644 backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/utils/Bot.java create mode 100644 backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/utils/BotInterface.java create mode 100644 backendcloud/botrunningsystem/src/main/resources/application.properties diff --git a/backendcloud/backend/src/main/java/com/kob/backend/config/SecurityConfig.java b/backendcloud/backend/src/main/java/com/kob/backend/config/SecurityConfig.java index 6ca863f..7f5e582 100644 --- a/backendcloud/backend/src/main/java/com/kob/backend/config/SecurityConfig.java +++ b/backendcloud/backend/src/main/java/com/kob/backend/config/SecurityConfig.java @@ -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(); diff --git a/backendcloud/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java b/backendcloud/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java index 69043ca..7ecf455 100644 --- a/backendcloud/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java +++ b/backendcloud/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java @@ -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 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 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 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)) { diff --git a/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Game.java b/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Game.java index 86a83a9..1b3ff9a 100644 --- a/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Game.java +++ b/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Game.java @@ -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 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"; } } diff --git a/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Player.java b/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Player.java index 66be0ff..add352b 100644 --- a/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Player.java +++ b/backendcloud/backend/src/main/java/com/kob/backend/consumer/utils/Player.java @@ -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; // 玩家走过的路径中每一步的方向 diff --git a/backendcloud/backend/src/main/java/com/kob/backend/controller/pk/ReceiveBotMoveController.java b/backendcloud/backend/src/main/java/com/kob/backend/controller/pk/ReceiveBotMoveController.java new file mode 100644 index 0000000..49d416a --- /dev/null +++ b/backendcloud/backend/src/main/java/com/kob/backend/controller/pk/ReceiveBotMoveController.java @@ -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 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); + } + +} diff --git a/backendcloud/backend/src/main/java/com/kob/backend/controller/pk/StartGameController.java b/backendcloud/backend/src/main/java/com/kob/backend/controller/pk/StartGameController.java index c49ad2d..1263a2a 100644 --- a/backendcloud/backend/src/main/java/com/kob/backend/controller/pk/StartGameController.java +++ b/backendcloud/backend/src/main/java/com/kob/backend/controller/pk/StartGameController.java @@ -19,7 +19,9 @@ public class StartGameController { @PostMapping("/pk/start/") public String startGame(@RequestParam MultiValueMap 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); } } diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/pk/ReceiveBotMoveServiceImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/pk/ReceiveBotMoveServiceImpl.java new file mode 100644 index 0000000..261de92 --- /dev/null +++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/pk/ReceiveBotMoveServiceImpl.java @@ -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"; + } +} diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/pk/StartGameServiceImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/pk/StartGameServiceImpl.java index 387e8bf..2e05eb1 100644 --- a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/pk/StartGameServiceImpl.java +++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/pk/StartGameServiceImpl.java @@ -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"; } } diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/pk/ReceiveBotMoveService.java b/backendcloud/backend/src/main/java/com/kob/backend/service/pk/ReceiveBotMoveService.java new file mode 100644 index 0000000..cb3926f --- /dev/null +++ b/backendcloud/backend/src/main/java/com/kob/backend/service/pk/ReceiveBotMoveService.java @@ -0,0 +1,6 @@ +// 接收 botrunningsystem 传回的 bot -> move 信息 +package com.kob.backend.service.pk; + +public interface ReceiveBotMoveService { + String receiveBotMove(Integer userId,Integer direction); // 接收传入的用户 id 和运动方向信息 +} diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/pk/StartGameService.java b/backendcloud/backend/src/main/java/com/kob/backend/service/pk/StartGameService.java index 3b06180..e65780d 100644 --- a/backendcloud/backend/src/main/java/com/kob/backend/service/pk/StartGameService.java +++ b/backendcloud/backend/src/main/java/com/kob/backend/service/pk/StartGameService.java @@ -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); } diff --git a/backendcloud/botrunningsystem/pom.xml b/backendcloud/botrunningsystem/pom.xml new file mode 100644 index 0000000..17e0917 --- /dev/null +++ b/backendcloud/botrunningsystem/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + com.kob + backendcloud + 0.0.1-SNAPSHOT + + + com.kob.botrunningsystem + botrunningsystem + + + 8 + 8 + UTF-8 + + + + + + + org.jooq + joor-java-8 + 0.9.14 + + + + + + org.springframework.boot + spring-boot-starter-security + 3.0.2 + + + + + + org.projectlombok + lombok + 1.18.26 + provided + + + + \ No newline at end of file diff --git a/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/BotRunningSystemApplication.java b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/BotRunningSystemApplication.java new file mode 100644 index 0000000..75de9df --- /dev/null +++ b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/BotRunningSystemApplication.java @@ -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); + } +} \ No newline at end of file diff --git a/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/config/RestTemplateConfig.java b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/config/RestTemplateConfig.java new file mode 100644 index 0000000..ac6a9b6 --- /dev/null +++ b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/config/RestTemplateConfig.java @@ -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(); + } +} diff --git a/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/config/SecurityConfig.java b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/config/SecurityConfig.java new file mode 100644 index 0000000..8232962 --- /dev/null +++ b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/config/SecurityConfig.java @@ -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(); + } +} diff --git a/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/controller/BotRunningController.java b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/controller/BotRunningController.java new file mode 100644 index 0000000..84ff06a --- /dev/null +++ b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/controller/BotRunningController.java @@ -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 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); + } +} diff --git a/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/BotRunningService.java b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/BotRunningService.java new file mode 100644 index 0000000..5f6dd4b --- /dev/null +++ b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/BotRunningService.java @@ -0,0 +1,6 @@ +package com.kob.botrunningsystem.service; + +public interface BotRunningService { + // 参数: userId 用户, botCode bot代码, input 输入的地图信息(障碍物,两条蛇的位置,走过的路径等信息) + String addBot(Integer userId, String botCode, String input); +} diff --git a/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/BotRunningServiceImpl.java b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/BotRunningServiceImpl.java new file mode 100644 index 0000000..1fc215e --- /dev/null +++ b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/BotRunningServiceImpl.java @@ -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"; + } +} diff --git a/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/utils/Bot.java b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/utils/Bot.java new file mode 100644 index 0000000..2809831 --- /dev/null +++ b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/utils/Bot.java @@ -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; +} diff --git a/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/utils/BotPool.java b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/utils/BotPool.java new file mode 100644 index 0000000..5167419 --- /dev/null +++ b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/utils/BotPool.java @@ -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 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); + } + } + } +} diff --git a/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/utils/Consumer.java b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/utils/Consumer.java new file mode 100644 index 0000000..f5f88af --- /dev/null +++ b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/service/impl/utils/Consumer.java @@ -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 data = new LinkedMultiValueMap<>(); + data.add("user_id", bot.getUserId().toString()); + data.add("direction", direction.toString()); + // 返回结果信息给 backend 服务端 + restTemplate.postForObject(receiveBotMoveUrl, data, String.class); + } +} diff --git a/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/utils/Bot.java b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/utils/Bot.java new file mode 100644 index 0000000..2819a6b --- /dev/null +++ b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/utils/Bot.java @@ -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 getCells(int sx, int sy, String steps) { + steps = steps.substring(1, steps.length() - 1); + List 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 aCells = getCells(aSx, aSy, strs[3]); + List 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; + } +} diff --git a/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/utils/BotInterface.java b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/utils/BotInterface.java new file mode 100644 index 0000000..70499db --- /dev/null +++ b/backendcloud/botrunningsystem/src/main/java/com/kob/botrunningsystem/utils/BotInterface.java @@ -0,0 +1,6 @@ +// 实现前端用户编写的 AI 的接口(api) +package com.kob.botrunningsystem.utils; + +public interface BotInterface { + Integer nextMove(String input); // 下一步要走的方向 +} diff --git a/backendcloud/botrunningsystem/src/main/resources/application.properties b/backendcloud/botrunningsystem/src/main/resources/application.properties new file mode 100644 index 0000000..36f374e --- /dev/null +++ b/backendcloud/botrunningsystem/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=3002 \ No newline at end of file diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/controller/MatchingController.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/controller/MatchingController.java index d022657..cc312f4 100644 --- a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/controller/MatchingController.java +++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/controller/MatchingController.java @@ -21,7 +21,8 @@ public class MatchingController { public String addPlayer(@RequestParam MultiValueMap data) { Integer userId = Integer.parseInt(Objects.requireNonNull(data.getFirst("user_id"))); 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/") diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/MatchingService.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/MatchingService.java index 39df470..aad5a9a 100644 --- a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/MatchingService.java +++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/MatchingService.java @@ -3,7 +3,7 @@ package com.kob.matchingsystem.service; public interface MatchingService { // 给匹配池添加一名玩家 - String addPlayer(Integer userId, Integer rating); + String addPlayer(Integer userId, Integer rating,Integer botId); // 从匹配池删除一名玩家 String removePlayer(Integer userId); diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/MatchingServiceImpl.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/MatchingServiceImpl.java index c4e4018..c2a6c38 100644 --- a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/MatchingServiceImpl.java +++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/MatchingServiceImpl.java @@ -11,10 +11,10 @@ public class MatchingServiceImpl implements MatchingService { public final static MatchingPool matchingPool = new MatchingPool(); @Override - public String addPlayer(Integer userId, Integer rating) { + public String addPlayer(Integer userId, Integer rating, Integer botId) { System.out.println("add player: " + userId + " " + rating); // 向匹配池添加一名玩家 - matchingPool.addPlayer(userId, rating); + matchingPool.addPlayer(userId, rating, botId); return "add player success"; } diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/MatchingPool.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/MatchingPool.java index 9e00040..a8b284b 100644 --- a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/MatchingPool.java +++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/MatchingPool.java @@ -27,11 +27,11 @@ public class MatchingPool extends Thread { MatchingPool.restTemplate = restTemplate; } - public void addPlayer(Integer userId, Integer rating) { + public void addPlayer(Integer userId, Integer rating,Integer botId) { lock.lock(); try { // 一开始匹配等待时间是 0 - playerList.add(new Player(userId, rating, 0)); + playerList.add(new Player(userId, rating,botId, 0)); } finally { lock.unlock(); } @@ -77,8 +77,11 @@ public class MatchingPool extends Thread { // 返回 a 和 b 的匹配结果 private void sendResult(Player a, Player b) { MultiValueMap data = new LinkedMultiValueMap<>(); + // 返回两名玩家的 userId 和 botId 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_bot_id",b.getBotId().toString()); restTemplate.postForObject(startGameUrl, data, String.class); } diff --git a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/Player.java b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/Player.java index a6576af..f5f2222 100644 --- a/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/Player.java +++ b/backendcloud/matchingsystem/src/main/java/com/kob/matchingsystem/service/impl/utils/Player.java @@ -11,5 +11,6 @@ import lombok.NoArgsConstructor; public class Player { private Integer userId; private Integer rating; + private Integer botId; private Integer waitingTime; // 等待时间 } diff --git a/backendcloud/pom.xml b/backendcloud/pom.xml index 90ea8c6..3bacfff 100644 --- a/backendcloud/pom.xml +++ b/backendcloud/pom.xml @@ -17,6 +17,7 @@ matchingsystem backend + botrunningsystem pom diff --git a/web/src/components/MatchGround.vue b/web/src/components/MatchGround.vue index 7d45b4f..2dbfde6 100644 --- a/web/src/components/MatchGround.vue +++ b/web/src/components/MatchGround.vue @@ -2,15 +2,27 @@