diff --git a/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java b/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java index 3f2cfe9..a87f4cd 100644 --- a/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java +++ b/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java @@ -21,10 +21,6 @@ import java.util.concurrent.CopyOnWriteArraySet; @Component @ServerEndpoint("/websocket/{token}") // 注意不要以'/'结尾 public class WebSocketServer { - // 后端向前端发送信息,首先需要创建一个 session - private Session session = null; - // 用户信息:定义一个成员变量 - private User user; /* 存储所有链接:对所有 websocket 可见的全局变量,存储为 static 静态变量 因为每个 websocket 实例都在一个独立的线程里,所以该公共变量应该是线程安全的 @@ -36,6 +32,10 @@ public class WebSocketServer { private static final CopyOnWriteArraySet matchPool = new CopyOnWriteArraySet<>(); // 在 WebSocketServer 中注入数据库的方法演示-> 使用 static 定义为独一份的变量 private static UserMapper userMapper; + // 后端向前端发送信息,首先需要创建一个 session + private Session session = null; + // 用户信息:定义一个成员变量 + private User user; // 注入方法 @Autowired @@ -85,6 +85,54 @@ public class WebSocketServer { } } + // 开始匹配的逻辑部分 + private void startMatching() { + System.out.println("start matching"); + matchPool.add(this.user); + + while (matchPool.size()>=2){ + // 迭代器用于枚举前两个人进行匹配 + Iterator it = matchPool.iterator(); + User a = it.next(), b = it.next(); + // 取出两个人之后,从匹配池中将他们删除 + matchPool.remove(a); + matchPool.remove(b); + + // 匹配成功时,创建联机地图 + Game game = new Game(13,14,20); + game.createMap(); // 初始化地图 + + JSONObject respGameData = new JSONObject(); + respGameData.put("game_map",game.getG()); + respGameData.put("rows",game.getRows()); + respGameData.put("cols",game.getCols()); + respGameData.put("inner_walls_count",game.getInnerWallsCount()); + + // 将 a 配对成功的消息传回客户端 + JSONObject respA = new JSONObject(); + respA.put("event","start-matching"); + respA.put("opponent_username",b.getUsername()); + respA.put("opponent_photo",b.getPhoto()); + respA.put("game_data",respGameData); + // 获取 a 的链接,并通过 sendMessage 将消息传给前端 + users.get(a.getId()).sendMessage(respA.toJSONString()); + + // 同理,将 b 的匹配成功信息传回给前端 + JSONObject respB = new JSONObject(); + respB.put("event","start-matching"); + respB.put("opponent_username",a.getUsername()); + respB.put("opponent_photo",a.getPhoto()); + respB.put("game_data",respGameData); + users.get(b.getId()).sendMessage(respB.toJSONString()); + } + } + + // 取消匹配的逻辑部分 + private void stopMatching() { + System.out.println("stop matching"); + matchPool.remove(this.user); + } + // @OnMessage 用于从前端接收请求: 一般 onMessage 当成路由使用,做为消息判断处理的中间部分 @OnMessage public void onMessage(String message, Session session) { @@ -121,47 +169,6 @@ public class WebSocketServer { } } - // 开始匹配的逻辑部分 - private void startMatching() { - System.out.println("start matching"); - matchPool.add(this.user); - - while (matchPool.size()>=2){ - // 迭代器用于枚举前两个人进行匹配 - Iterator it = matchPool.iterator(); - User a = it.next(), b = it.next(); - // 取出两个人之后,从匹配池中将他们删除 - matchPool.remove(a); - matchPool.remove(b); - - // 匹配成功时,创建联机地图 - Game game = new Game(13,14,20); - game.createMap(); // 初始化地图 - - // 将 a 配对成功的消息传回客户端 - JSONObject respA = new JSONObject(); - respA.put("event","start-matching"); - respA.put("opponent_username",b.getUsername()); - respA.put("opponent_photo",b.getPhoto()); - respA.put("game_map",game.getG()); - // 获取 a 的链接,并通过 sendMessage 将消息传给前端 - users.get(a.getId()).sendMessage(respA.toJSONString()); - - // 同理,将 b 的匹配成功信息传回给前端 - JSONObject respB = new JSONObject(); - respB.put("event","start-matching"); - respB.put("opponent_username",a.getUsername()); - respB.put("opponent_photo",a.getPhoto()); - respB.put("game_map",game.getG()); - users.get(b.getId()).sendMessage(respB.toJSONString()); - } - } - - // 取消匹配的逻辑部分 - private void stopMatching() { - System.out.println("stop matching"); - matchPool.remove(this.user); - } } diff --git a/backend/src/main/java/com/kob/backend/consumer/utils/Game.java b/backend/src/main/java/com/kob/backend/consumer/utils/Game.java index 7849eac..071e3ca 100644 --- a/backend/src/main/java/com/kob/backend/consumer/utils/Game.java +++ b/backend/src/main/java/com/kob/backend/consumer/utils/Game.java @@ -17,15 +17,51 @@ public class Game { public Game(Integer rows, Integer cols, Integer inner_walls_count) { this.rows = rows; this.cols = cols; - this.inner_walls_count = rows; + this.inner_walls_count = inner_walls_count; this.g = new int[rows][cols]; } + public int getRows(){ + return rows; + } + public int getCols(){ + return cols; + } + public int getInnerWallsCount(){ + return inner_walls_count; + } + // 返回生成的地图 public int[][] getG() { return g; } + // 联通检测方法---true(联通)---false(不通),参数: 起点坐标 sx,sy ,终点坐标 tx,ty + private boolean check_connectivity(int sx, int sy, int tx, int ty) { + // 起点就是终点时,结果联通,直接返回 true + if (sx == tx && sy == ty) return true; + g[sx][sy] = 1; + + //枚举"上右下左"四个方向,求当前点下一个相邻点的坐标 + for (int i = 0; i < 4; i++) { + int x = sx + dx[i]; + int y = sy + dy[i]; + + // 判断是否撞到障碍物( g[x][y] == 0 表示没有碰到障碍物 ),如果没有赚到障碍物,且可以找到重点的话,返回 true(联通) + if (x >= 0 && x < this.rows && y >= 0 && y < this.cols && g[x][y] == 0) { + if (check_connectivity(x, y, tx, ty)) { + // 还原状态 + g[sx][sy] = 0; + return true; + } + } + } + + // 还原状态 + g[sx][sy] = 0; + return false; + } + // 画地图 public boolean draw() { // 一开始现将所有障碍物初始化为 false @@ -74,32 +110,6 @@ public class Game { return check_connectivity(this.rows - 2, 1, 1, this.cols - 2); } - // 联通检测方法---true(联通)---false(不通),参数: 起点坐标 sx,sy ,终点坐标 tx,ty - private boolean check_connectivity(int sx, int sy, int tx, int ty) { - // 起点就是终点时,结果联通,直接返回 true - if (sx == tx && sy == ty) return true; - g[sx][sy] = 1; - - //枚举"上右下左"四个方向,求当前点下一个相邻点的坐标 - for (int i = 0; i < 4; i++) { - int x = sx + dx[i]; - int y = sy + dy[i]; - - // 判断是否撞到障碍物( g[x][y] == 0 表示没有碰到障碍物 ),如果没有赚到障碍物,且可以找到重点的话,返回 true(联通) - if (x >= 0 && x < this.rows && y >= 0 && y < this.cols && g[x][y] == 0) { - if (check_connectivity(x, y, tx, ty)) { - // 还原状态 - g[sx][sy] = 0; - return true; - } - } - } - - // 还原状态 - g[sx][sy] = 0; - return true; - } - public void createMap() { // 循环绘制:如果发现哪次循环中画地图成功了,则跳出循环,绘制结束 for (int i = 0; i < 1000; i++) { diff --git a/backend/src/main/java/com/kob/backend/consumer/utils/JwtAuthenticationUtil.java b/backend/src/main/java/com/kob/backend/consumer/utils/JwtAuthenticationUtil.java index 9e7065c..2606ae7 100644 --- a/backend/src/main/java/com/kob/backend/consumer/utils/JwtAuthenticationUtil.java +++ b/backend/src/main/java/com/kob/backend/consumer/utils/JwtAuthenticationUtil.java @@ -6,11 +6,10 @@ import io.jsonwebtoken.Claims; //Jwt 验证配置工具类 public class JwtAuthenticationUtil { public static Integer getUserId(String token) { -// 核心验证逻辑 -// 默认 userid 赋值为 -1 ,表示不存在 + // 默认 userid 赋值为 -1 ,表示不存在 int userId = -1; try { -// 将 token 解析,如果能成功解析出 userid 表示合法,否则表示不合法 + // 将 token 解析,如果能成功解析出 userid 表示合法,否则表示不合法 Claims claims = JwtUtil.parseJWT(token); userId = Integer.parseInt(claims.getSubject()); } catch (Exception e) { diff --git a/web/src/assets/scripts/GameMap.js b/web/src/assets/scripts/GameMap.js index 35c23b9..055b47f 100644 --- a/web/src/assets/scripts/GameMap.js +++ b/web/src/assets/scripts/GameMap.js @@ -15,18 +15,19 @@ export class GameMap extends AcGameObject { this.ctx = ctx; this.parent = parent; this.store = store; - // 存下每个格子的绝对距离 this.L = 0; -/* - // 定义棋盘格的行数和列数(前端生成地图时使用) + /* // 定义棋盘格的行数和列数(前端生成地图时使用) // 行数和列数不同时设置为偶数或者不同时设置为奇数,可以避免AB两蛇同时进入同一个格子,避免因此对优势者不公平 this.rows = 13; this.cols = 14; // 绘制棋盘内部区域的障碍物(墙)的数量(前端生成地图时使用) - this.inner_walls_count = 30; - */ + this.inner_walls_count = 20; */ + + this.rows = this.store.state.pk.rows; + this.cols = this.store.state.pk.cols; + this.inner_walls_count = this.store.state.pk.inner_walls_count; // 存储所有的墙 // 上面的 super() 会先将 AcGameObject 先绘制, walls 的绘制在后面执行,因此墙最后会覆盖原棋盘格进行绘制 @@ -40,7 +41,7 @@ export class GameMap extends AcGameObject { ]; } -/* + /* // 判断函数:判断角色路径是否联通。传入参数:g数组,起点和终点的横纵坐标(前端生成地图时使用) check_connectivity(g, sx, sy, tx, ty) { // 当起点坐标和中点坐标一致时,判断联通,直接返回 @@ -64,11 +65,10 @@ export class GameMap extends AcGameObject { */ // 创建障碍物(后端生成地图版本) - create_wall() { + create_walls() { // 取出后端生成后传到前端 store 中的 game_map const g = this.store.state.pk.game_map; - // 枚举数组,将 g[r][c] == true 的部分绘制出来 // 如果上一步连通性检测失败,则退出 this.create_wall() 函数,本步骤不再执行生成新对象的操作 for (let r = 0; r < this.rows; r++) { @@ -79,7 +79,6 @@ export class GameMap extends AcGameObject { } } } - // 绘制成功则 return turn return true; } @@ -188,11 +187,11 @@ export class GameMap extends AcGameObject { start() { // 开始时调用一次创建墙的函数 // 循环 1000 次,如果成功创建则 break ,否则继续循环创建(前端生成地图时使用这一条) - // for (let i = 0; i < 1000; i++) if (this.create_wall()) break; + // for (let i = 0; i < 1000; i++) if (this.create_walls()) break; // 使用后端生成地图时,这里只需要调用一次就好 - this.create_wall(); - + this.create_walls(); + // 开始时启动监听方法 this.add_listening_events(); } diff --git a/web/src/assets/scripts/temp.js b/web/src/assets/scripts/temp.js new file mode 100644 index 0000000..b505bc2 --- /dev/null +++ b/web/src/assets/scripts/temp.js @@ -0,0 +1,121 @@ +import { AcGameObject } from "./AcGameObject"; +import { Wall } from "./Wall"; +import { Snake } from './Snake'; + +export class GameMap extends AcGameObject { + constructor(ctx, parent, store) { + super(); + + this.ctx = ctx; + this.parent = parent; + this.store = store; + this.L = 0; + + this.rows = 13; + this.cols = 14; + + this.inner_walls_count = 20; + this.walls = []; + + this.snakes = [ + new Snake({id: 0, color: "#4876EC", r: this.rows - 2, c: 1}, this), + new Snake({id: 1, color: "#F94848", r: 1, c: this.cols - 2}, this), + ]; + } + + create_walls() { + const g = this.store.state.pk.gamemap; + + for (let r = 0; r < this.rows; r ++ ) { + for (let c = 0; c < this.cols; c ++ ) { + if (g[r][c]) { + this.walls.push(new Wall(r, c, this)); + } + } + } + } + + add_listening_events() { + this.ctx.canvas.focus(); + + const [snake0, snake1] = this.snakes; + this.ctx.canvas.addEventListener("keydown", e => { + if (e.key === 'w') snake0.set_direction(0); + else if (e.key === 'd') snake0.set_direction(1); + else if (e.key === 's') snake0.set_direction(2); + else if (e.key === 'a') snake0.set_direction(3); + else if (e.key === 'ArrowUp') snake1.set_direction(0); + else if (e.key === 'ArrowRight') snake1.set_direction(1); + else if (e.key === 'ArrowDown') snake1.set_direction(2); + else if (e.key === 'ArrowLeft') snake1.set_direction(3); + }); + } + + start() { + this.create_walls(); + + this.add_listening_events(); + } + + update_size() { + this.L = parseInt(Math.min(this.parent.clientWidth / this.cols, this.parent.clientHeight / this.rows)); + this.ctx.canvas.width = this.L * this.cols; + this.ctx.canvas.height = this.L * this.rows; + } + + check_ready() { // 判断两条蛇是否都准备好下一回合了 + for (const snake of this.snakes) { + if (snake.status !== "idle") return false; + if (snake.direction === -1) return false; + } + return true; + } + + next_step() { // 让两条蛇进入下一回合 + for (const snake of this.snakes) { + snake.next_step(); + } + } + + check_valid(cell) { // 检测目标位置是否合法:没有撞到两条蛇的身体和障碍物 + for (const wall of this.walls) { + if (wall.r === cell.r && wall.c === cell.c) + return false; + } + + for (const snake of this.snakes) { + let k = snake.cells.length; + if (!snake.check_tail_increasing()) { // 当蛇尾会前进的时候,蛇尾不要判断 + k -- ; + } + for (let i = 0; i < k; i ++ ) { + if (snake.cells[i].r === cell.r && snake.cells[i].c === cell.c) + return false; + } + } + + return true; + } + + update() { + this.update_size(); + if (this.check_ready()) { + this.next_step(); + } + this.render(); + } + + render() { + const color_even = "#AAD751", color_odd = "#A2D149"; + for (let r = 0; r < this.rows; r ++ ) { + for (let c = 0; c < this.cols; c ++ ) { + if ((r + c) % 2 == 0) { + this.ctx.fillStyle = color_even; + } else { + this.ctx.fillStyle = color_odd; + } + this.ctx.fillRect(c * this.L, r * this.L, this.L, this.L); + } + } + } +} diff --git a/web/src/store/pk.js b/web/src/store/pk.js index 82010b9..b6e94e1 100644 --- a/web/src/store/pk.js +++ b/web/src/store/pk.js @@ -8,6 +8,9 @@ export default { opponent_username: "", opponent_photo: "", game_map: null, + rows: 0, + cols: 0, + inner_walls_count: 0, }, getters: {}, mutations: { @@ -25,9 +28,12 @@ export default { state.status = status; }, // 更新地图 - updateGameMap(state,game_map){ - state.game_map = game_map; - } + updateGameMap(state, game_data) { + state.game_map = game_data.game_map; + state.rows = game_data.rows; + state.cols = game_data.cols; + state.inner_walls_count = game_data.inner_walls_count; + }, }, actions: {}, module: {}, diff --git a/web/src/views/pk/PkIndexView.vue b/web/src/views/pk/PkIndexView.vue index e9fbd8c..f93fd19 100644 --- a/web/src/views/pk/PkIndexView.vue +++ b/web/src/views/pk/PkIndexView.vue @@ -56,9 +56,10 @@ export default { // 匹配成功,延迟两秒后:更改匹配状态 matching -> playing setTimeout(() => { store.commit("updateStatus", "playing"); - }, 2000); + }, 200); // 匹配成功,从后端更新地图 - store.commit("updateGameMap",data.game_map) + store.commit("updateGameMap", data.game_data); + console.log(data.game_data); } }