项目-双人五子棋对战:匹配模块的实现(3)

news2025/1/17 21:18:45

完整代码见: 邹锦辉个人所有代码: 测试仓库 - Gitee.com

模块详细讲解

功能需求

匹配就类似于大家平常玩的王者荣耀这样的匹配功能, 当玩家点击匹配之后, 就会进入到一个匹配队列, 当匹配到足够数量的玩家后, 就会进入确认页. 

在这里, 我们主要实现的是1 - 1匹配功能, 首先先有一个玩家点击匹配, 进入匹配队列, 然后如果有段位差不多的(就是根据我们之前讲到的天梯分数, 按分数来分段位, 后面也会实现)进入到匹配队列, 就匹配成功, 创建一个游戏房间, 双方进入游戏房间.

接下来我们来详细介绍一下具体的匹配实现原理.

具体原理

匹配这样的功能, 也需要依赖到我们之前讲到的消息推送.

 

1. 玩家1点击匹配按钮, 就会告诉服务器, 我要进行匹配, 同时进入匹配队列.

2. 玩家2(跟玩家1同一个段位)点击匹配按钮, 这时就完成了匹配.

3. 此时服务器需要告诉玩家1匹配结果. (正是因为服务器自己也不确定, 啥时候告诉玩家匹配的结果, 因此就需要依赖消息推送机制, 当服务器匹配成功之后, 就主动告诉排到的玩家, 你排到了).

具体实现

前后端接口的约定

前后端的交互接口, 也是基于websocket来展开的, websocket可以传输文本数据, 也可以传输二进制数据, 此处就直接设计成让websocket传输json格式的文本数据即可.

匹配请求:

客户端通过websocket给服务器发送一个json格式的文本数据.

ws://127.0.0.1:8080/findMatch

{

        message:'startMatch' / 'stopMatch', //开始/结束匹配

在通过websocket传输请求信息的时候, 数据中就不用包含用户的个人信息的, 因为此时个人信息在登录之后被存储在HttpSession中了. 通过websocket也是可以拿到之前登录的HttpSession里的信息的. 

匹配响应:

ws://127.0.0.1:8080/findMatch

{

        ok: true, //匹配成功

        reason: ' ', //匹配如果失败, 失败的原因信息

        message: 'startMatch' / 'stopMatch', 

}

这个响应是客户端给服务器发送匹配请求之后, 服务器立即返回的匹配响应.

 匹配响应2: 

ws://127.0.0.1:8080/findMatch

{

        ok: true,

        reason: ' ',

        message: 'matchSuccess'

}

这个响应是真正匹配到对手之后, 服务器主动推送回来的信息. 匹配到的对手不需要在这个响应中体现, 仍然都放到服务器这边来储存即可

前端代码的实现

页面大致展示:

这里我们不难发现, 客户端的工作主要是发送两个请求, 即获取用户信息, 以及开始/结束匹配; 以及接收服务器响应, 根据响应改变按钮文本即可.(再次强调, 前端使用websocket的作用是建立连接, 发送和接收数据(请求和响应) ) 下面围绕这两个内容, 我们来实现一下前端代码.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>游戏大厅</title>
    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/game_hall.css">
</head>
<body>
    <div class="nav">五子棋对战</div>
    <!-- 整个页面的容器元素 -->
    <div class="container">
        <!-- 这个 div 在 container 中是处于垂直水平居中这样的位置的 -->
        <div>
            <!-- 展示用户信息 -->
            <div id="screen"></div>
            <!-- 匹配按钮 -->
            <div id="match-button">开始匹配</div>
        </div>
    </div>

    <script src="js/jquery.min.js"></script>
    <script>
        //获取用户信息
        $.ajax({
            type: 'get',
            url: '/userInfo',
            success: function(body) {
                let screenDiv = document.querySelector('#screen');
                screenDiv.innerHTML = '玩家: ' + body.username + " 分数: " + body.score 
                    + "<br> 比赛场次: " + body.totalCount + " 获胜场数: " + body.winCount
            },
            error: function() {
                alert("获取用户信息失败!");
            }
        });

        // 此处进行初始化 websocket, 并且实现前端的匹配逻辑. 
        let websocketUrl = 'ws://' + location.host + '/findMatch';
        let websocket = new WebSocket(websocketUrl);
        websocket.onopen = function() {
            console.log("onopen");
        }
        websocket.onclose = function() {
            console.log("onclose");
        }
        websocket.onerror = function() {
            console.log("onerror");
        }
        // 监听页面关闭事件. 当用户关闭该页面时, 就会同时关闭websocket. 
        window.onbeforeunload = function() {
            websocket.close();
        }

        // 处理服务器返回的响应数据. 这个响应就是针对 "开始匹配" / "结束匹配" 来对应的
        websocket.onmessage = function(e) {
            // 解析得到的响应对象. 返回的数据是一个 JSON 字符串, 解析成 js 对象
            let resp = JSON.parse(e.data);
            //选中按钮标签, 根据情况变换文本.
            let matchButton = document.querySelector('#match-button');
            if (!resp.ok) {
                console.log("游戏大厅中接收到了失败响应! " + resp.reason);
                return;
            }
            if (resp.message == 'startMatch') {
                // 开始匹配请求发送成功
                console.log("进入匹配队列成功!");
                matchButton.innerHTML = '匹配中...(点击停止)'
            } else if (resp.message == 'stopMatch') {
                // 结束匹配请求发送成功
                console.log("离开匹配队列成功!");
                matchButton.innerHTML = '开始匹配';
            } else if (resp.message == 'matchSuccess') {
                // 已经匹配到对手了. 
                console.log("匹配到对手! 进入游戏房间!");
                location.replace("/game_room.html");
            } else if (resp.message == 'repeatConnection') {
                alert("当前检测到多开! 请使用其他账号登录!");
                location.replace("/login.html");
            } else {
                console.log("收到了非法的响应! message=" + resp.message);
            }
        }

        // 给匹配按钮添加一个点击事件
        let matchButton = document.querySelector('#match-button');
        matchButton.onclick = function() {
            // 在触发 websocket 请求之前, 先确认下 websocket 连接是否好着呢~~ 
            if (websocket.readyState == websocket.OPEN) {
                // 如果当前 readyState 处在 OPEN 状态, 说明连接好着的~
                // 这里发送的数据有两种可能, 开始匹配/停止匹配~
                if (matchButton.innerHTML == '开始匹配') {
                    // 发送开始匹配请求
                    websocket.send(JSON.stringify({
                        message: 'startMatch',
                    }));
                } else if (matchButton.innerHTML == '匹配中...(点击停止)') {
                    // 发送停止匹配请求
                    websocket.send(JSON.stringify({
                        message: 'stopMatch',
                    }));
                }
            } else {
                // 这是说明连接当前是异常的状态(比如未登录直接打开该页面)
                alert("当前您的连接已经断开! 请重新登录!");
                location.replace('/login.html');
            }
        }
    </script>
</body>
</html>

不难发现, 核心也就是发送请求和处理响应, 这个都是通过用户点击的按钮所发起的.

这里还有一个特殊的情况就是, 用户可能会同时登录多个账号(也就是我们常说的多开), 这时候也要进行处理(前端这里仅针对响应结果进行弹窗提示, 跳转页面处理),  还有一些防止未登录直接跳转至页面的特殊情况处理, 一会在后端代码中做详细讲解.

后端代码

我们知道, 前端已经通过websocket和后端进行了连接, 因此后端就需要通过websocket会话和前端保持连接, 通过这个可以向客户端发送信息. 然后前端发来的请求会以TextMessage的形式被后端接收到. 然后服务器进行响应, 响应也就包含刚才讲的(ok/reason/message)这三个参数.

下面来看一下后端WebSocket负责接收服务器数据的方法具体实现:

@Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 实现处理开始匹配请求和停止匹配请求
        User user = (User) session.getAttributes().get("user");
        //获取到客户端给服务器的数据
        String payload = message.getPayload();
        // 当前这个数据载荷是一个JSON格式的字符串, 需要把它转换为 Java对象, MatchRequest(也就是字符串格式)
        // ObjectMapper就是完成类型转换的核心类
        MatchRequest request = objectMapper.readValue(payload, MatchRequest.class);
        MatchResponse response = new MatchResponse();
        if(request.getMessage().equals("startMatch")) {
            // 进入匹配队列
            matcher.add(user);
            // 把玩家信息放入到匹配队列之后, 就可以返回一个响应给客户端了
            response.setOk(true);
            response.setMessage("startMatch");
        } else if (request.getMessage().equals("stopMatch")) {
            // 退出匹配队列
            matcher.remove(user);
            // 移除之后了, 就可以返回一个响应给客户端了
            response.setOk(true);
            response.setMessage("stopMatch");
        } else {
            //非法情况
            response.setOk(false);
            response.setReason("非法的匹配请求");
        }
        //格式转换为JSON格式
        String jsonString = objectMapper.writeValueAsString(response);
        //返回响应
        session.sendMessage(new TextMessage(jsonString));
    }

这里与开始匹配/结束匹配相关的就是匹配队列, 它是在matcher类中实现的. 明天我们再来具体讲解一下这个, 以及对于一些特殊情况的处理.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1790537.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PPP-B2b精密产品使用注意事项及分析

1、因为在使用PPP-B2b进行定轨的时候&#xff0c;发的精密轨道产品是B3频点的&#xff0c;需要改正的卫星质心&#xff08;Com&#xff09;与SP3精密星历对比。 2、PPP-B2b产品吸收了电离层误差&#xff0c;因此电离层提取方面与IGS电离层完全无法对其。 3、由于PPP-B2b产品精…

【微信小程序】初识小程序

项目结构 项目基本组成结构 页面基础组成结构 JSON 配置文件 App.json app.json是当前小程序的全局配置&#xff0c;包括了小程序的所有页面路径、窗口外观、界面表现、底部tab等。 在 pages 中加入路径&#xff0c;保存后&#xff0c;开发者工具可以自动帮我们创建对应的页…

SAS:什么时候用kcompress呀?

问题&#xff1a;如何截取ECGTPT变量中的后三个字符&#xff1f; 下图展示了以k开头的以及非k开头的substr函数和length函数&#xff0c;发现在UTF-8编码下&#xff0c;仅以k开头的函数能够截取成功。 释疑&#xff08;以下内容来自SAS Help&#xff09; SAS提供的字符函数…

微软云计算[2]之微软云关系数据库SQL Azure

微软云关系数据库SQL Azure SQL Azure概述SQL Azure关键技术SQL Azure数据库SQL Azure报表服务SQL Azure数据同步 SQL Azure和SQL Server对比 SQL Azure概述 SQL Azure是微软的云中关系型数据库。 SQL Azure数据库简化了多数据库的供应和部署。 SQL Azure还为用户提供内置的高…

FPGA新起点V1开发板(九)——流水灯

文章目录 一、模块框图二、代码编写三、注意点四、总结 一、模块框图 二、代码编写 endmodule下面需要敲出一个回车代码拼接是大括号 led < {led[2:0],led[3]}注意二进制和十进制 module flow_led(input sys_clk50,input rst_n,output reg [3:0] le…

探索 Adobe Illustrator 2023 (AI 2023) for Mac/Win——创意设计的强大工具

Adobe Illustrator 2023 (AI 2023) for Mac/Win 是一款在设计领域备受推崇的专业矢量图形编辑软件软件&#xff0c;为设计师们提供了无尽的创意可能性。 它具有强大而精确的绘图功能&#xff0c;让用户能够轻松绘制出各种复杂的图形、线条和形状。无论是简洁的图标设计还是精美…

STM32作业实现(八)触摸按键TPAD

目录 STM32作业设计 STM32作业实现(一)串口通信 STM32作业实现(二)串口控制led STM32作业实现(三)串口控制有源蜂鸣器 STM32作业实现(四)光敏传感器 STM32作业实现(五)温湿度传感器dht11 STM32作业实现(六)闪存保存数据 STM32作业实现(七)OLED显示数据 STM32作业实现(八)触摸按…

vscode中选择pytorch虚拟环境中库没有导入报错,但是本机命令行下载过了

这是下载成功的结果 这个时候你会发现matplotlib的库是下载过的&#xff0c;没法下载 这个的原因是你的matplotlib库是下载到本机的python上但是pytorch框架上的是没有这个库的&#xff0c;此时应该打开ananconda promopt 然后输入activate pytorch转换成pytorch环境 然后pip…

gitlabcicd-k8s部署runner

一.环境信息 存储使用nfs挂载持久化 k8s环境 helm安装 建议helm 3 二.部署gitlab-runner 1.查看gitlab版本 进入容器可通过执行&#xff1a;gitlab-rake gitlab:env:info rootgitlab-647f4bd8b4-qz2j9:/# gitlab-rake gitlab:env:info System information System: Current Us…

zimo221软件和PCtoLCD2002软件的使用

Zimo221软件和PCtoLCD2002软件的使用 在没有字库时&#xff0c;我们可能需要自建汉字库&#xff0c;这时&#xff0c;汉字取模软件就会变得很重要。 一、zimo221取模方式&#xff1a; 1、打开软件 2、点击“基本操作” 3、一定要先点击“新建图像”按钮&#xff0c;见下图…

vue3中 window绑定scroll事件滚动页面获取不到e.target.scrollTop

遇到的问题 vue3项目 onMounted(() > {window.addEventListener(scroll, (e) > {console.log(e.target.scrollTop)}) })想要监听页面中的滚动&#xff0c;然后获取滚动距离实现一些功能&#xff0c;发现event参数中获取不到e.target.scrollTop&#xff08;印象中以前使…

NSIS 安装包默认支持的参数

NSIS 安装包默认支持的参数 NSIS 制作的安装包默认支持 /NCRC、/S、/D 三个参数&#xff0c;详见下文 3.2 Installer Usage&#xff08;来自 Command Line Usage&#xff09;。 以上三个参数对应的功能分别为禁止 CRC 校验、静默安装、设置安装路径&#xff0c;这三个功能不需…

JAVA家政系统小程序源码,家政系统源码,支持店铺入驻接单,师傅入驻接单:专业团队自主研发的一套上门家政APP系统成品源码,支持商用

JAVA家政系统小程序源码&#xff0c;家政系统源码&#xff0c;支持店铺入驻接单&#xff0c;师傅入驻接单&#xff1a;专业团队自主研发的一套上门家政APP系统成品源码&#xff0c;支持商用 家政系统是一套可提供上门家政的系统&#xff0c;可在线预约开荒保洁、上门维修、美容…

使用 Node.js 和 Azure Function App 自动更新 Elasticsearch 索引

作者&#xff1a;来自 Elastic Jessica Garson 维护最新数据至关重要&#xff0c;尤其是在处理频繁变化的动态数据集时。这篇博文将指导你使用 Node.js 加载数据&#xff0c;并通过定期更新确保数据保持最新。我们将利用 Azure Function Apps 的功能来自动执行这些更新&#xf…

Vue中引入elementUI中的container组件失效

1.不用修改官网中任何css或者html 2.按需引入&#xff0c;不是只是引入官网的就可以 import Vue from vue import Router from vue-router import HelloWorld from /components/HelloWorld import First from /components/views/First import Second from /components/views/…

操作失败——后端

控制台观察&#xff0c;页面发送的保存菜品的请求 返回的response显示&#xff1a; ---------- 我开始查看明明感觉都挺正常&#xff0c;没啥错误&#xff0c;就是查不出来。结果后面电脑关机重启后&#xff0c;隔一天看&#xff0c;就突然可以了。我觉着可能是浏览器的缓存没…

基本算法——位运算

a^b 原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 题目描述 运行代码 #include<iostream> using namespace std; long long a,b,c,t1; int main() {cin>>a>>b>>c;for(;b;b/2){if(b&1)tt*a%c;aa*a%c;}cout<<t%c; } 代码思路…

HTML5+CSS3+JS小实例:网格图库

实例:网格图库 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0&…

俄罗斯ozon平台计算器,ozon定价计算器

在数字化飞速发展的今天&#xff0c;电商平台已成为商家们展示产品、吸引顾客的重要窗口。而在俄罗斯这一广阔的市场中&#xff0c;Ozon平台以其独特的优势&#xff0c;成为了众多电商卖家的首选。然而&#xff0c;想要在Ozon平台上脱颖而出&#xff0c;除了优质的产品和服务外…

C语言(结构体)

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#xff0c;在这里撰写成文一…