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);
}
});
},