造孽
gamemap: null,声明为对象 不要为""
status: "matching" 如果为playing会先生成gamemap导致得到空对象
此时 会报错 再匹配也没有用
报错始终加载不出来 一定要做一遍流程 先gamemap onmunted 等等new 注意执行顺序
结合控制台
云端生成地图
判断图的连通性
这里直接复制js里面的内容然后改一改关键字就可以了
private boolean check_connectivity(int sx,int sy,int tx,int ty){//判断两个点之间
if (sx == tx && sy == ty) return true;
g[sx][sy] = 1;//标记为走过了
//上下左右偏移量 算法基础课floodfill java语法课蛇形矩阵
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){
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++){
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 check_connectivity(this.rows-2,1,1,this.cols-2);
}
Game game=new Game(13,14,20);game.createMap();//应该存到A和B的连接里 这里用局部变量代替
前端接受信息
一开始就把socket存下来
socket.onopen = () => {
console.log("connected");
store.commit("updateSocket", socket);
}
socket.onmessage = msg => {
const data = JSON.parse(msg.data);//解析出来
if (data.event === "start-matching") {
store.commit("updateOpponent", {
username: data.opponent_username,
photo: data.opponent_photo,
});
setTimeout(() => {
store.commit("updateStatus", "playing");
}, 2000);//设置延迟两秒后启动 太快看不清头像 也无法取消游戏
}
}
匹配
因为是多线程可能有并发问题
并发问有线程安全问题和共享内存不可见性
所以这里使用while
while(matchpool.size()>=2){
Iterator<User> it=matchpool.iterator();//注意迭代器的使用
User a=it.next(),b=it.next();
matchpool.remove(a);
matchpool.remove(b);
JSONObject respA=new JSONObject();
respA.put("event","start-matching");
respA.put("opponent_username",b.getUsername());
respA.put("opponent_photo",b.getPhoto());
users.get(a.getId()).sendMessage(respA.toJSONString());//直接发送请求 不会中断 不像return
JSONObject respB=new JSONObject();//json对象
respB.put("event","start-matching");
respB.put("opponent_username",a.getUsername());
respB.put("opponent_photo",a.getPhoto());
users.get(b.getId()).sendMessage(respB.toJSONString());
}
事件
加入匹配池
final private static CopyOnWriteArraySet<User> matchpool=new CopyOnWriteArraySet<>();
线程安全的set 加入维护
oncolse 关闭时
final private static CopyOnWriteArraySet<User> matchpool=new CopyOnWriteArraySet<>();
两者区别
users 是用来查看有谁在Pk页面 加入连接 而matchpool用来查看有谁正在匹配
set是集合 是可以自由增加长度的数组 存取对象 而users里仅仅存放数字
域名
https协议都需要域名 secrity
仅仅用ip地址是申请不下来的
点击匹配
把用户放到匹配池里边 如果匹配池满了两个的话 就把两个人变成一组
返回给客户端
事件区分
通信json
store.state.pk.socket.send(JSON.stringify({
event:"start-matching",//将大括号里的东西转化为字符串
}));
后端
onmessage里面 //用来路由
JSONObject data=JSONObject.parseObject(message);
String event=data.getString("event");//取出来
if("start-matching".equals(event)){//如果event 在前边 可能会报 event空异常 直接使用字符串相同就可以
startMatching();
}else if("stop-matching".equals(event)){
stopMatching();
}
解析出来
前端匹配页面和pk页面
<PlayGround v-if="$store.state.pk.status==='playing'" />
全局变量 如果在templae里写就加$
script里边是加入store常量里调用
两个页面通过全局变量里的status更改
匹配页面
这是mutations函数的写法和它的调用
updateOpponent(state, opponent) {//注意state
state.opponent_username = opponent.username;
state.opponent_photo = opponent.photo;
},
store.commit("updateOpponent", {
username: "我的对手",
photo: "https://cdn.acwing.com/media/article/image/2022/08/09/1_1db2488f17-anonymous.png",
})
-
圆头像 需要选中的是Img div.user-photo>img border-radius:50%; 半径调width:20vh;
居中是上级的.photo textalign -
浅灰色 background-color: rgba(50, 50, 50, 0.5);
const click_match_btn = () => {
if (match_btn_info.value === "开始匹配") {
match_btn_info.value = "取消";
} else {
match_btn_info.value = "开始匹配";
}
}
<button type="button" class="btn btn-warning btn-lg"
@click="click_match_btn">{{ match_btn_info }}</button>
较为复杂的逻辑我们需要写一个函数作为事件
封装工具类String token转为userid
public class JwtAuthentication {
public static Integer getUserId(String token){
Integer userid=-1;
try {
Claims claims = JwtUtil.parseJWT(token);//解析出来
userid = Integer.parseInt(claims.getSubject());//如果能解析出来就是合法的
} catch (Exception e) {
throw new RuntimeException(e);
}
return userid;
//我们希望能够直接调用 而不需要实例化来调用
}
}
Integer userId= JwtAuthentication.getUserId(token);
onopen中调用
if(this.user!=null){
users.put(userId,this);
}else{
this.session.close();
}
System.out.println(users);//输出看一下
加入jwt验证的安全连接
在访问连接里传入token
let socketUrl=“ws://127.0.0.1:3000/websocket/${store.state.user.token}/”;注意取得的方式
socket = new WebSocket(socketUrl);//js自带的api
新建了一个实例对象
之前是直接传入userid建立连接
现在开始加入jwt验证
后端接收到
@ServerEndpoint("/websocket/{token}")
{
@OnOpen
public void onOpen(Session session, @PathParam("token") String token)//这里取得是已经有的
}
websocket并没有session的概念 不用加到表头 直接放进连接里就可以了
封装的工具类可以放进Utils里边
解析String类型的token jwt验证
前端创建连接
我们需要组件都加载完才建立连接
取出来周期函数
onMounted当组件被挂载完之后执行的函数
onUnmounted当组件被卸载的时候执行的函数
新建了pk.js
用来存放关于匹配的全局变量
使用module存取组件是个好习惯
let socketUrl=“ws://127.0.0.1:3000/websocket/${store.state.user.id}/”;//把http改成ws 域名不变
数据的格式是spring定义的 传过来数据的是放在了data里边
const data=JSON.parse(msg.data);
重点是onUnmounted一定要断开连接 如果不断开 进入其他页面回来就会重新创额外的一个对象 一个客户端三四个连接
所以一定要断开连接 socket的相关操作(onopen new 等等)不一定要写在onmounted里边 这样写为了对称
操作执行在setup完成之后
WebSocket实例的具体实现
webs实例包括user 和 session 静态 users HashMap和静态的usermapper
包括新建了一个static变量HashMap<Integer,websocket>
用来存放全部的连接用户 Userid映射为websocket实例
注入了userMapper 定义私有变量session
@OnOpen public void onOpen(Session session, @PathParam("token") String token)
存取了session 和token取出userid
根据userid 从usermapper里取出用户并存放在webs实例的user
包括连接的onopen
onclose
onmessage
获取信息
public void sendMessage(String message){
synchronized (this.session){
try{
this.session.getBasicRemote().sendText(message);
}catch (IOException e){
e.printStackTrace();
}
}//需要抛异常 直接输出就可以了
}
注入websocket
websocket
不是单例模式 不是单独的组件 需要额外的注入模式
不像其他的@Autowired直接注入
private static UserMapper userMapper;
@Autowired
public void setUserMapper(UserMapper userMapper){
WebSocketServer.userMapper=userMapper;//一个接口
}
单例模式是指同一个时间只能有一个实例 这里是每建立一个连接都会新建一个实例
线程安全
每个实例在线程里边所以要线程安全 userid 映射成websocket实例
就是我们A线程在进入方法后,拿到了count的值,刚把这个值读取出来还没有改变count的值的时候,
结果线程B也进来的,那么导致线程A和线程B拿到的count值是一样的
ConcurrentHashMap[HTML_REMOVED]是线程安全的
static 保证所有成员都是共享的
websockt基本原理
websockt 每一个链接都会维护起来(实例化)
每次接收到消息 就会把类实例化 不会消失
链接的私有信息存成私有变量 所有链接的公共信息存成类的静态变量
本身就是多线程 开一个线程维护这个类 (多线程并发)
集成websocket
加入pom依赖 添加配置 添加config
onopen
在连接建立的时候 自动调用下面的函数
其他注解同理
问题
两名玩家都在本地生成 生成过程放在服务器上 统一生成地图
game->create map 判断哪条蛇输那条赢 判断分析放在服务器上
很少的通信 如果是fps游戏 判断全放在服务器端上的话就会有很大的延迟(所以有很多挂)
回合制可以放在云端 生成地图后传给两个客户端 等待用户输入 另外也可以接受代码输入(微服务)
死循环一直在等 或者超时判断为输 推荐多把逻辑画一画
前端后端通信使用json方便一些
匹配机制
是一个异步的过程 用一个mathing system 另外一个进程类似mysql和springboot
人多中挑选分数最接近的匹配成一组 将结果返回给servier端
server端会返回个客户端 客户端可以检查对手是谁
会等待很长的一段事件 http请求 立即发送 不会经过太长时间
websocket服务器也可以向客户端发送请求
多去和面试官探讨逻辑问题 不用探讨知识问题 每一帧每一帧的动
贪吃是如何拐弯之类的 ta可以听懂 以前可能没有见过
交简介里的东西一定要自己写 不然大家都一样了 可以换一个游戏 不一定非要用这个游戏 换个五子棋什么的
sp-0118-6-1 匹配系统
客户端向服务器两端通信 生成游戏地图
表头
Idea操 作 指 南:https://www.acwing.com/blog/content/25456/
每次都要点击的网址:https://www.acwing.com/blog/content/28250/
G i t B a s h : https://www.acwing.com/blog/content/22768/
WindowsIdea :https://www.acwing.com/blog/content/23868/
本节课讲义https://www.acwing.com/file_system/file/content/whole/index/content/6325603/