parent
42074fb132
commit
1b4d8d808a
13 changed files with 507 additions and 71 deletions
@ -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(); |
||||
} |
||||
} |
@ -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<Integer,WebSocketServer> users = new ConcurrentHashMap<>(); |
||||
|
||||
// 保存当前的匹配池
|
||||
|
||||
final private static CopyOnWriteArraySet<User> 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<User> 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(); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -0,0 +1,100 @@ |
||||
<template> |
||||
<div class="matchground"> |
||||
<div class="row"> |
||||
<div class="col-6"> |
||||
<div class="user_photo"> |
||||
<img :src="$store.state.user.photo" alt=""> |
||||
</div> |
||||
<div class="username"> |
||||
<span class="info">用户名: </span>{{ $store.state.user.username }} |
||||
</div> |
||||
</div> |
||||
<div class="col-6"> |
||||
<div class="user_photo"> |
||||
<img :src="$store.state.pk.opponent_photo" alt=""> |
||||
</div> |
||||
<div class="username"> |
||||
<span class="info">用户名: </span>{{ $store.state.pk.opponent_username }} |
||||
</div> |
||||
</div> |
||||
<div class="col-12" style="text-align: center; padding-top: 10vh;"> |
||||
<button type="button" v-if="is_matched" class="btn btn-success btn-lg" @click="click_match_btn">{{match_btn_info}}</button> |
||||
<button type="button" v-else class="btn btn-danger btn-lg" @click="click_match_btn">{{match_btn_info}}</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { ref } from 'vue' |
||||
import {useStore} from 'vuex' |
||||
|
||||
export default { |
||||
setup() { |
||||
const store = useStore(); |
||||
|
||||
let match_btn_info = ref("开始匹配"); |
||||
let is_matched = ref(true); |
||||
|
||||
const click_match_btn = () => { |
||||
if (match_btn_info.value === "开始匹配") { |
||||
match_btn_info.value = "取消匹配"; |
||||
is_matched.value = false; |
||||
// 给后端发送一个json信息表示当前属于哪个事件 |
||||
store.state.pk.socket.send(JSON.stringify({ |
||||
event: "start-matching", |
||||
})); |
||||
|
||||
|
||||
|
||||
}else { |
||||
|
||||
match_btn_info.value = "开始匹配"; |
||||
is_matched.value = true; |
||||
store.state.pk.socket.send(JSON.stringify({ |
||||
event: "stop-matching", |
||||
})); |
||||
} |
||||
} |
||||
return { |
||||
match_btn_info, |
||||
is_matched, |
||||
click_match_btn, |
||||
} |
||||
}, |
||||
} |
||||
</script> |
||||
|
||||
|
||||
<style scoped> |
||||
.matchground { |
||||
width: 60vw; |
||||
height: 70vh; |
||||
margin: 40px auto; |
||||
background-color: rgba(123, 197, 212, 0.5); |
||||
|
||||
} |
||||
.info { |
||||
font-size: 24px; |
||||
color: black; |
||||
} |
||||
|
||||
img { |
||||
margin-top: 10vh; |
||||
border-radius: 50%; |
||||
width: 20vh; |
||||
} |
||||
|
||||
.user_photo { |
||||
text-align: center; |
||||
} |
||||
|
||||
.username { |
||||
margin-top: 40px; |
||||
font-size: 24px; |
||||
font-weight: bold; |
||||
color:crimson; |
||||
text-align: center; |
||||
} |
||||
|
||||
</style> |
@ -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: { |
||||
} |
||||
} |
@ -1,16 +1,70 @@ |
||||
<template> |
||||
<PlayGround/> |
||||
<!-- status等于playing才进入对战界面 --> |
||||
<PlayGround v-if="$store.state.pk.status === 'playing'" /> |
||||
<MatchGround v-if="$store.state.pk.status === 'matching'" /> |
||||
</template> |
||||
|
||||
<script> |
||||
import PlayGround from '../../components/PlayGround.vue' |
||||
import PlayGround from "../../components/PlayGround.vue"; |
||||
import MatchGround from "../../components/MatchGround.vue"; |
||||
import { onMounted, onUnmounted } from "vue"; // 加载组件和卸载组件时 |
||||
|
||||
import { useStore } from "vuex"; |
||||
|
||||
export default { |
||||
components: { |
||||
PlayGround |
||||
} |
||||
} |
||||
components: { |
||||
PlayGround, |
||||
MatchGround, |
||||
}, |
||||
setup() { |
||||
const store = useStore(); |
||||
const socket_url = `ws://localhost:3000/websocket/${store.state.user.token}/`; |
||||
let socket = null; |
||||
// 当前组件被加载(点击pk页面)时执行该函数 |
||||
// onMounted和setup作用相似 |
||||
onMounted(() => { |
||||
store.commit("updateOpponent", { |
||||
username: "my opponent", |
||||
photo: |
||||
"https://cdn.acwing.com/media/article/image/2022/08/09/1_1db2488f17-anonymous.png", |
||||
}); |
||||
// 建立一个socket链接 |
||||
socket = new WebSocket(socket_url); |
||||
|
||||
socket.onopen = () => { |
||||
store.commit("updateSocket", socket); |
||||
console.log("connnected!"); |
||||
}; |
||||
// 接收到后端发来的信息 |
||||
socket.onmessage = (msg) => { |
||||
const data = JSON.parse(msg.data); |
||||
// 匹配成功 |
||||
if (data.event === "start-matching") { |
||||
console.log(data) |
||||
store.commit("updateOpponent", { |
||||
username: data.opponent_username, |
||||
photo: data.opponent_photo, |
||||
}); |
||||
setTimeout(() => { |
||||
store.commit("updateStatus", "playing"); |
||||
}, 2000); |
||||
|
||||
// 更新地图 |
||||
store.commit("updateGameMap", data.gamemap); |
||||
} |
||||
}; |
||||
|
||||
socket.onclose = () => { |
||||
console.log("disconnected!"); |
||||
store.commit("updateStatus", "matching"); |
||||
}; |
||||
}); |
||||
// 切换其他页面或者关闭pk页面时调用 |
||||
onUnmounted(() => { |
||||
socket.close(); |
||||
}); |
||||
}, |
||||
}; |
||||
</script> |
||||
|
||||
<style scoped> |
||||
</style> |
||||
<style scoped></style> |
||||
|
Loading…
Reference in new issue