diff --git a/backend/pom.xml b/backend/pom.xml index 632ec65..2338f00 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -74,6 +74,19 @@ runtime + + org.springframework.boot + spring-boot-starter-websocket + 2.7.2 + + + + com.alibaba + fastjson + 2.0.11 + + + diff --git a/backend/src/main/java/com/kob/backend/config/SecurityConfig.java b/backend/src/main/java/com/kob/backend/config/SecurityConfig.java index 1dd1bd6..e40b065 100644 --- a/backend/src/main/java/com/kob/backend/config/SecurityConfig.java +++ b/backend/src/main/java/com/kob/backend/config/SecurityConfig.java @@ -9,6 +9,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; 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; @@ -48,4 +49,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } + + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring().antMatchers("/websocket/**"); + } + } diff --git a/backend/src/main/java/com/kob/backend/config/WebSocketConfig.java b/backend/src/main/java/com/kob/backend/config/WebSocketConfig.java new file mode 100644 index 0000000..9c415ad --- /dev/null +++ b/backend/src/main/java/com/kob/backend/config/WebSocketConfig.java @@ -0,0 +1,18 @@ +package com.kob.backend.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +/** + * @author zfp + */ +@Configuration +public class WebSocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + + return new ServerEndpointExporter(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java b/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java new file mode 100644 index 0000000..b599c17 --- /dev/null +++ b/backend/src/main/java/com/kob/backend/consumer/WebSocketServer.java @@ -0,0 +1,146 @@ +package com.kob.backend.consumer; + +import com.alibaba.fastjson.JSONObject; +import com.kob.backend.consumer.utils.Game; +import com.kob.backend.consumer.utils.JwtAuthentication; +import com.kob.backend.controller.pojo.User; +import com.kob.backend.mapper.UserMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * @author zfp + */ +@Component +@ServerEndpoint("/websocket/{token}") // 注意不要以'/'结尾 +public class WebSocketServer { + + // 保存所有用户对应的请求链接 + // 线程安全的一个HashMap + final private static ConcurrentHashMap users = new ConcurrentHashMap<>(); + + // 保存当前的匹配池 + + final private static CopyOnWriteArraySet matchpool = new CopyOnWriteArraySet<>(); + // 发起请求链接对应的用户 + private User user; + private Session session = null; + + // 注入userMapper(和之前的有点不一样) + private static UserMapper userMapper; + +// // 保存地图 +// private Game game = null; + + @Autowired + public void setUserMapper(UserMapper userMapper) { + WebSocketServer.userMapper = userMapper; + } + + @OnOpen + public void onOpen(Session session, @PathParam("token") String token) throws IOException { + // 建立连接 + this.session = session; + + // 根据token查询用户id(JWT验证) + Integer userId = JwtAuthentication.getUserId(token); + // 根据userId查找对应的用户 + this.user = userMapper.selectById(userId); + // 连接成功 + if (this.user != null) { + users.put(userId,this); + System.out.println("Connected Success!"); + }else { + this.session.close(); + } + + System.out.println(users); + } + + @OnClose + public void onClose() { + // 关闭链接 + System.out.println("Disconnected!"); + if (this.user != null) { + users.remove(this.user.getId()); + matchpool.remove(this.user); + } + } + + private void startMatching() { + System.out.println("start-----matching----"); + matchpool.add(this.user); + System.out.println(matchpool.size()); + + 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 respA = new JSONObject(); + respA.put("event","start-matching"); + respA.put("opponent_username",b.getUsername()); + respA.put("opponent_photo",b.getPhoto()); + respA.put("gamemap",game.getG()); + + users.get(a.getId()).sendMessage(respA.toJSONString()); + + JSONObject respB = new JSONObject(); + respB.put("event","start-matching"); + respB.put("opponent_username",a.getUsername()); + respB.put("opponent_photo",a.getPhoto()); + respB.put("gamemap",game.getG()); + + users.get(b.getId()).sendMessage(respB.toJSONString()); + + } + } + + private void stopMatching() { + System.out.println("stop-----matching----"); + matchpool.remove(this.user); + } + + @OnMessage + public void onMessage(String message, Session session) { + // 从Client接收消息(读取前端发来的信息) + System.out.println("Received the message!"); + JSONObject data = JSONObject.parseObject(message); + String event = data.getString("event"); + + if ("start-matching".equals(event)) { + startMatching(); + } + else if ("stop-matching".equals(event)){ + stopMatching(); + } + } + + @OnError + public void onError(Session session, Throwable error) { + error.printStackTrace(); + } + + public void sendMessage(String msg) { + // 给前端发送信息 + synchronized (this.session) { + try { + this.session.getBasicRemote().sendText(msg); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..67a9ff4 --- /dev/null +++ b/backend/src/main/java/com/kob/backend/consumer/utils/Game.java @@ -0,0 +1,97 @@ +package com.kob.backend.consumer.utils; + +import java.util.Random; + +/** + * @author zfp + */ +public class Game { + final private Integer rows; + final private Integer cols; + final private Integer inner_walls_count; + final private int[][] g; + + final private static int[] dx = {-1,0,1,0}; + final private static int[] dy = {0,1,0,-1}; + + public Game(Integer rows,Integer cols,Integer inner_walls_count) { + this.rows = rows; + this.cols = cols; + this.inner_walls_count = inner_walls_count; + this.g = new int[rows][cols]; + } + + public int[][] getG() { + return g; + } + + private boolean check_connectivity(int sx,int sy,int tx,int ty) { + if (sx == tx && sy == ty) { + return true; + } + g[sx][sy] = 1; + for (int i = 0; i < 4; i++) { + int x = sx + dx[i] , y = sy + dy[i]; + 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; + } + + // 绘制地图是否成功 + private boolean draw() { + for (int i = 0; i < this.rows; i++) { + for (int j = 0; j < this.cols; j++) { + // 0表示空地,1表示障碍物 + g[i][j] = 0; + } + } + + // 四周加上障碍物 + + for (int r = 0; r < this.rows; r++) { + g[r][0] = g[r][this.cols - 1] = 1; + } + + for (int c = 0; c < this.cols; c++) { + g[0][c] = g[this.rows - 1][c] = 1; + } + + // 创建随机障碍物 + + Random random = new Random(); + + for (int i = 0; i < this.inner_walls_count / 2; i++) { + for (int j = 0; j < 1000; j++) { + int r = random.nextInt(this.rows); + int c = random.nextInt(this.cols); + // 判断对称的位置是否有障碍物 + if (g[r][c] == 1 || g[this.rows - 1 - r][this.cols - 1 - c] == 1){ + continue; + } + // 不要生成到蛇初始的位置上 + if (r == this.rows - 2 && c == 1 || r == 1 && c == this.cols - 2) { + continue; + } + // 生成的位置没有问题 + g[r][c] = g[this.rows - 1 - r][this.cols - 1 - c] = 1; + break; + } + } + return this.check_connectivity(this.rows - 2, 1,1,this.cols - 2); + } + + public void createMap() { + // 最多随机1000次,判断能够生成有效的地图 + for (int i = 0; i < 1000; i++) { + if (draw()) { + break; + } + } + } +} diff --git a/backend/src/main/java/com/kob/backend/consumer/utils/JwtAuthentication.java b/backend/src/main/java/com/kob/backend/consumer/utils/JwtAuthentication.java new file mode 100644 index 0000000..e7faca2 --- /dev/null +++ b/backend/src/main/java/com/kob/backend/consumer/utils/JwtAuthentication.java @@ -0,0 +1,21 @@ +package com.kob.backend.consumer.utils; + +import com.kob.backend.utils.JwtUtil; +import io.jsonwebtoken.Claims; + +/** + * @author zfp + */ +public class JwtAuthentication { + public static Integer getUserId(String token) { + // 返回值为-1时表示不存在 + int userId = -1; + try { + Claims claims = JwtUtil.parseJWT(token); + userId = Integer.parseInt(claims.getSubject()); + } catch (Exception e) { + throw new RuntimeException(e); + } + return userId; + } +} diff --git a/web/src/assets/scripts/GameMap.js b/web/src/assets/scripts/GameMap.js index b3bb1fd..d7d1f5c 100644 --- a/web/src/assets/scripts/GameMap.js +++ b/web/src/assets/scripts/GameMap.js @@ -3,7 +3,7 @@ import { Snake } from "./Snake"; import { Wall } from "./Wall"; export class GameMap extends AcGameObject { - constructor(ctx, parent) { + constructor(ctx, parent,store) { super(); // 继承类一直要先调用父类的构造函数 this.ctx = ctx; @@ -13,6 +13,8 @@ export class GameMap extends AcGameObject { this.rows = 13; this.cols = 14; + this.store = store; + this.inner_walls_count = 20; // 障碍物的数量(最大建议80) this.walls = []; // 所有障碍物组成的数组 @@ -24,60 +26,9 @@ export class GameMap extends AcGameObject { ]; } - check_connectivity(g, sx, sy, tx, ty) { // 判断生成的地图是否可以连通 - if (sx == tx && sy == ty) return true; - g[sx][sy] = true; - - let dx = [-1, 0, 1, 0], dy = [0, 1, 0, -1]; - for (let i = 0; i < 4; i++) { - let x = sx + dx[i], y = sy + dy[i]; - if (!g[x][y] && this.check_connectivity(g, x, y, tx, ty)) - return true; - } - - return false; - } - create_walls() { // 判断是否生成有效的地图 - const g = []; - for (let r = 0; r < this.rows; r++) { - g[r] = []; - for (let c = 0; c < this.cols; c++) { - g[r][c] = false; - } - } - - // 给四周加上障碍物 - for (let r = 0; r < this.rows; r++) { - g[r][0] = g[r][this.cols - 1] = true; - } - - for (let c = 0; c < this.cols; c++) { - g[0][c] = g[this.rows - 1][c] = true; - } - - // 创建随机障碍物 - for (let i = 0; i < this.inner_walls_count / 2; i++) { - for (let j = 0; j < 1000; j++) { - let r = parseInt(Math.random() * this.rows); - let c = parseInt(Math.random() * this.cols); - // 两个对称的位置都没有障碍物 - if (g[r][c] || g[this.rows - 1 - r][this.cols - 1 - c]) continue; - // 不能将障碍物生成到两条蛇的起始位置处(左下,右上) - if (r == this.rows - 2 && c == 1 || r == 1 && c == this.cols - 2) - continue; - // 每次将障碍物生成到对称的两个位置 - g[r][c] = g[this.rows - 1 - r][this.cols - 1 - c] = true; - break; - } - } - - const copy_g = JSON.parse(JSON.stringify(g)); - - // 如果生成的地图无法连通,则返回false,需要重新生成 - if (!this.check_connectivity(copy_g, this.rows - 2, 1, 1, this.cols - 2)) - return false; - + 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]) { @@ -86,8 +37,6 @@ export class GameMap extends AcGameObject { } } } - - return true; } add_listening_events() { // 绑定监听事件 @@ -110,10 +59,7 @@ export class GameMap extends AcGameObject { start() { - // 尝试1000次,直到找到符合条件的地图为止 - for (let i = 0; i < 1000; i++) - if (this.create_walls()) - break; + this.create_walls(); this.add_listening_events(); } diff --git a/web/src/components/GameMap.vue b/web/src/components/GameMap.vue index 418d511..fdc808b 100644 --- a/web/src/components/GameMap.vue +++ b/web/src/components/GameMap.vue @@ -8,14 +8,15 @@ + + + \ No newline at end of file diff --git a/web/src/store/index.js b/web/src/store/index.js index bf21e22..6be9b56 100644 --- a/web/src/store/index.js +++ b/web/src/store/index.js @@ -1,5 +1,6 @@ import { createStore } from 'vuex' import ModuleUser from "./user" // 导入user.js +import ModulePk from "./pk" export default createStore({ state: { @@ -12,5 +13,6 @@ export default createStore({ }, modules: { user: ModuleUser, + pk: ModulePk, } }) diff --git a/web/src/store/pk.js b/web/src/store/pk.js new file mode 100644 index 0000000..bc166a5 --- /dev/null +++ b/web/src/store/pk.js @@ -0,0 +1,32 @@ + +export default { + state: { // 全局变量 + status: "matching", // matching表示匹配界面,playing表示对战界面 + socket: null, + opponent_username: "", //对手的用户名 + opponent_photo: "", // 对手的头像 + gamemap: null, // 对战的地图 + }, + getters: { + }, + mutations: { // 用于修改全局数据 + updateSocket(state,socket) { + state.socket = socket; + }, + updateOpponent(state,opponent) { + state.opponent_username = opponent.username; + state.opponent_photo = opponent.photo; + }, + updateStatus(state,status) { + state.status = status; + }, + updateGameMap(state,gamemap) { + state.gamemap = gamemap; + }, + }, + actions: { // 在actions中调用修改全局变量的函数 + + }, + modules: { + } +} \ No newline at end of file diff --git a/web/src/views/pk/PkIndexView.vue b/web/src/views/pk/PkIndexView.vue index 680e6a1..ecd2462 100644 --- a/web/src/views/pk/PkIndexView.vue +++ b/web/src/views/pk/PkIndexView.vue @@ -1,16 +1,70 @@ - \ No newline at end of file + diff --git a/web/src/views/user/account/UserLoginView.vue b/web/src/views/user/account/UserLoginView.vue index e044b62..84d8fa3 100644 --- a/web/src/views/user/account/UserLoginView.vue +++ b/web/src/views/user/account/UserLoginView.vue @@ -73,7 +73,6 @@ export default { store.dispatch("getInfo",{ success() { router.push({name:"home"}); - console.log(store.state.user); } }); },