Springboot项目——网页版本五子棋

网页五子棋:项目简单实现了网页版本的五子棋对战功能,同时会根据用户的天梯分数来匹配,可供多位用户同时提供对战功能。大致可分为三个模块,用户模块,匹配模块,对战模块,下面重点介绍以下三个模块。

101.42.44.62:10010/login.html

一、全局处理

1. 拦截器

1)自定义拦截器类实现 HandlerInterceptor 接口,重写 preHandle 方法,用于拦截未登录用户的请求。

2)自定义配置类实现 WebSocketConfigurer 接口,重写 registerWebSocketHandlers 方法,用于注册后续重写 TextWebSocketHandler 的类,使得后续客户端和服务端的通信能正确进行,同时为该配置类加上 @EnableWebSocket 注解,使得 Spring 知道该类是 WebSocket 的配置类;实现 WebMvcConfigurer  接口,重写 addInterceptors 方法,表示对哪些请求进行拦截。

2. 统一数据格式返回

1)自定义结果实体类 Result,其属性包括 code(业务码),errMsg(错误信息),data(接口响应的数据,泛型);其静态方法有 success(T data),在请求成功时可调用,参数 data,表示给前端返回的数据内容;fail(String errMsg),在请求非法时可调用,参数 errMsg,表示非法请求的错误信息。其中,Result 类中的属性 code 通过自定义枚举类来实现,保证 code 值的正确性。        

2)自定义响应通知类,实现 ResponseBodyAdvice 接口,并重写其 supports 方法和 beforeBodyWrite 方法,supports 方法:判断是否要执行 beforeBodyWrite 方法,true 为执行,false不执行;beforeBodyWrite 方法:对response方法进行的具体操作处理,如果返回的结果已经是Result类时,需直接返回,另外,如果返回的结果时 String类型,需通过 ObjectMapper 进行特殊处理。

 二、用户模块

通过 UserController 类实现

1. 登录

1)前端页面

2)后端实现 

使用 JWT 令牌(Json Web Token)存储用户登录信息,同时将 user 对象存储在 Session 中,方便后续使用WebSocketSession 获取到用户信息;后端首先校验用户信息的合法性,当校验成功时,为该用户生成令牌,服务器将用户的用户名存储在 token 中(方便后续获取用户信息),并设置过期时间,客户端将令牌存储在浏览器的 Local Storage 中,并将 token 设置在请求头 Header 中,后续客户端的请求都会带着 token,服务器会校验令牌,来决定是否拦截用户的请求。

2. 注册

1)前端页面

2)后端实现 

使用 MD5 算法加密、UUID 加盐共同加密用户的密码,保证用户密码的安全性。服务器首先判断用户注册信息的合法性,用户名不能重复,两次密码需输入一致等;判断合法后,将用户的密码和UUID 生成的随机盐值,使用 MD5 算法进行加密后,存储在数据库中。

3. 获取用户信息

服务器从请求头 Header 中获取到 token,并获取到存储在 token 中的用户名,根据用户名在数据库中查询相应的用户信息并返回。

三、model类

1. 用户类 User

其属性包括 int userId 用户 id,String username 用户名,String password 密码,int score 天梯分数,int totalCount 总场数,int winCount 获胜场数;

2. 自定义匹配请求实体类 MatchRequest

其属性包括 String message,表示请求内容;

3. 自定义匹配响应实体类 MatchResponse

其属性包括 boolean ok,表示响应的状态,String reason,表示响应状态错误时的错误原因,String message,表示响应内容;

4. 自定义游戏就绪响应类 GameReadyResponse

因为在服务器确认游戏匹配成功后,此时直接就向客户端发送游戏就绪响应,不需要游戏就去请求;其属性包括:String message 响应信息,boolean ok 响应状态,String reason 失败时的原因,String roomId 游戏房间 id,int thisUserId 玩家 1 的 id,int thatUserId 玩家 2 的 id,int blackUser 执黑子的玩家 id;

5. 自定义游戏请求类 GameRequest

用来表示一次游戏请求(即落子请求),其属性包括 String message 请求信息,int userId 落子玩家,int row 落子的行数,int col 落子的列数;

6. 自定义游戏响应类 GameResponse

用来表示一次游戏响应(即落子响应),其属性包括 String message 响应信息,int userId 响应返回的玩家,int row 落子的行数,int col 落子的列数(由于需要实时的将棋盘上的信息反馈给每个玩家),int winner 表示获胜方玩家 id,未分出胜负用 -1 表示,int isOut 表示是否有玩家掉线,当有玩家掉线时,则认为游戏结束,掉线玩家判负;

四、游戏处理类

1. 自定义用户在线状态管理类 OnlineUserManager

其包含两个 ConcurrentHashMap 类型的属性 gameLobby,gameRoom,将用户 id 和 WebSocketSession 会话通过键值对方式存储,gameLobby 表示用户在游戏大厅的在线状态,gameroom 表示用户在游戏房间的在线状态;并为该类添加相应的方法

enterGameLobby:进入游戏大厅,在 gameLobby 中添加用户 id 和相应的 WebSocketSession;

exitGameLobby:离开游戏大厅,从 gameLobby 中删除 id 对应的键值对;

getOnline:获取用户大厅会话,通过 id 从 gameLobby 中获取相应的 WebSocketSession; 

enterGameRoom:进入游戏房间,在 gameRoom中添加用户 id 和相应的 WebSocketSession;

exitGameRoom:离开游戏房间,从 gameRoom 中删除 id 对应的键值对;

getOnlineRoom:获取用户房间会话,通过 id 从 gameRoom 中获取相应的 WebSocketSession;

即 将 ConcurrentHashMap 的 put,get,remove 等方法进行封装,分别表示用户上线,获取用户在线状态,用户下线;

2. 自定义匹配类 Matcher

其属性包含三个队列,分别表示用户的实力等级(分为正常水平,高水平和超高水平),根据用户的实力等级来匹配实力相近的玩家;其方法包括用户

进入匹配队列 add方法;用户离开匹配队列 remove 方法;构造方法,使用三个线程分别判断三个匹配队列是否匹配成功,使用 wait notify 解决忙等问题;handlerMatch 方法判断是否匹配成功,当某个队列中有两名玩家时,即匹配成功,将这两个玩家放入同一个房间中,并为两名玩家分别做出响应;

3. 自定义的房间类 Room

表示一次游戏中两名玩家对战的游戏房间,其属性分别包括 roomId 房间号,User user1 表示玩家 1,User user2 表示玩家 2,int blackUser 表示执黑子的玩家(先手权),int[][] board 表示棋盘,由于该类不应该是唯一的,有可能有多场对局同时进行,就需要多个游戏房间,故需通过构造器使用 getBean 方法注入对象,roomId 用 UUID 随机生成;其方法有 

putChess 用来处理一次落子请求:首先将客户端传递的落子请求转换为 GameRequest 对象,根据其属性 userId 判断是哪个玩家的轮次,并将棋盘 board 中对应的 row col 位置填上对应轮次玩家的标志,例如 1 表示黑子,2 表示白子,(同时判断是否有玩家离线,如果有玩家离线,则将胜方指定为未离线的玩家),同时判断游戏是否分出胜负,并将以上信息构造为 GameResponse 对象返回给每个玩家;

printBoard 在服务器端打印棋盘落子情况,以便观察是否出现异常情况;

checkWinner 判断是否决出胜负,遍历棋盘 board,判断是否存在五子连珠情况;

4. 自定义房间管理器类 RoomManager

用来管理游戏房间,其属性包括 ConcurrentHashMap<String, Room> rooms,ConcurrentHashMap<Integer, String> userToRoom,其中 rooms 将房间 id 和房间关联起来,userToRoom 的作用是将玩家 id 和房间 id 关联起来;其方法包括

add,将对应的房间 id 和房间放入 rooms 中,将 玩家 1 和房间 id 放入 玩具 2 和房间 id 分别放入userToRoom中;

remove 方法将放假 id 从 rooms 中删除,将 玩家 1 id 和玩家 2 id 分别从 userToRoom 中删除;

getRoomByUserId,通过 userId 获取到房间 id,再通过房间 id 获取到房间信息;

五、匹配模块

通过 MatchController 类实现,处理匹配时的 WebSocket 请求;该类继承自 Spring 中的TextWebSocketHandler 类,并重写其中的一些方法

1)afterConnectionEstablished 方法,该方法用来处理前后端通信时的 WebSocket 连接建立成功之后服务器应该做的逻辑,并将响应结果返回给客户端;

在连接建立之后,通过 WebSocketSession 类中提供的 getAttributes().get 方法获取到用户信息,通过用户 id 得到的 WebSocket 会话,若不为 null,则说明用户已经在其他地方登录,此时不应重复登录(由于此时还没有进行任何操作,得到的 id 对应的 WebSocket 应为 null),当 id 对应的 WebSocket 会话为null 时,将此时的 WebSocket 会话和 id 对应放入 map 中,表示用户进入游戏大厅,即用户上线; 

2)handleTextMessage 方法,该方法用来处理客户端与服务器通信过程中客户端发送的请求,并将请求处理之后的响应返回给客户端;

首先通过 WebSocketSession 类中提供的 getAttributes().get 方法获取到用户信息,通过TextMessage 提供的方法 getPayload 将请求解析为字符串,再通过 ObjectMapper类 提供的 readValue 方法将字符串 转化为 MatchRequest 对象,服务器根据客户端请求中的内容(开始匹配或者结束匹配),决定用户进入匹配队列还是离开匹配队列,并构造相应的响应并返回给客户端;

3)handleTransportError 方法,该方法用来处理当通信连接出现异常时,服务器应做的逻辑;

此时通信连接异常,说明玩家已经下线,需要将 用户 id 从 map 中删除;考虑到这样一种情况:同一个账号使用两个不同的浏览器先后登录同一个账号,此时经过 afterConnectionEstablished 方法判定之后不能重复登录,此时需将后登陆的连接关闭(但先登录的连接不应该关闭),为了防止将先登录的 webSecoket会话错误删除,此处需要再做一个判定,当当前的 WebSocketSession == 用户 id 对应的 WebSocketSession 时再进行删除,同时将用户从匹配队列中移除;

4)afterConnectionClosed 方法,该方法用户处理通信连接关闭之后,服务器应做的逻辑;同上连接异常时的处理情况;

六、游戏模块

通过 GameController 类实现,处理游戏中的 WebSocket 请求;该类继承自 Spring 中的TextWebSocketHandler 类,并重写其中的一些方法

1)afterConnectionEstablished 方法

首先,通过 WebSocketSession 类中提供的 getAttributes().get 方法获取到用户信息,如果用户为null 则直接返回一个 reason 为用户未登录的 GameReadyResponse 响应;否则,根据 RoomManager 类中的 getRoomByUserId 方法获取到玩家所在房间;如果房间不存在,则说明尚未匹配到,并返回响应;再接着判断用户是否在其他地方已经登录,通过 OnlineUserManager 获得当前用户的游戏房间状态和游戏大厅状态(WebSocketSession),如果有一个不为 null 则说明用户已经登录,返回用户多开响应;否则,就通过 OnlineUserManager 中的 enterGameRoom 方法设置该用户的游戏房间状态;判断 room 中的 user1 是否为空,若为空,则将当前玩家设置为user1,并将先手方设置为当前玩家,此时设置过一个玩家之后应该直接返回,等待第二个玩家的请求到来时,此时 room 中的 user1 已经不为空了,已经被上一个玩家先占了,此时将该玩家设置为 user2,此时 room 中的两个玩家均已连接上,需分别通知两个玩家游戏准备就绪,通过 noticeGameReady 方法实现;注意判断 user1 是否为空时,有可能两个玩家的请求同时到达,同时判定 user1 为空,此时 room 中的 user1 被设置了两次,而 user2 没有被设置,为了防止这种情况的发生,需要在判断时使用 synchronized 对 room 加锁;

2)noticeGameReady 方法的实现

该方法参数列表包含 Room room,User thisUser,User thatUser,分别表示游戏房间,玩家自己和另一个玩家,这个方法需要向客户端发送一个游戏准备就绪 GameReadyResponse 的响应,就需要通过 WebSocketSession 中的 sendMessage 方法发送,故需先通过 OnlineUserManager 中的 getOnlineRoom 方法获取到 session会话,然后构造一个 GameReadyResponse 响应并设置其中的 roomId,thisUserId,thatUserId等属性,再通过 sendMessage 发送;

3)handleTextMessage

先通过 WebSocketSession 类中提供的 getAttributes().get 方法获取到用户信息,并判断用户信息是否为空,再通过 RoomManager 中的 getRoomByUserId 获取到当前房间 room 的信息,通过调用 room 中的 putChess 方法处理一次落子请求;

4)handleTransportError

当游戏的某一方连接异常时,此处逻辑是通知另一方直接获胜,通过 noticeUserWin 方法;并改变玩家的游戏房间在线状态;

5)noticeUserWin 方法的实现

该方法参数为一个 User 对象(指掉线玩家),主要完成的功能是通知未掉线的玩家获胜,并返回响应给该玩家,并修改两名玩家的天梯分数,获胜场次,总场次等信息;

先通过 RoomManager 中的 getRoomByUserId 获取到相应的房间信息,根据房间信息,判断该通知哪个玩家获胜,构造一个 GameResponse 对象,并设置其Winner,isOut等相关信息,再将响应发送给该玩家,同时修改两名玩家数据库中的相应信息;

6)afterConnectionClosed 同上连接异常时的处理情况;

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

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

相关文章

腾盾科创无人机亮相第二十四届新疆农机博览会引发观展热潮

5月25日&#xff0c;第二十四届新疆农业机械博览会、2024“一带一路”智慧农业大会&#xff08;以下简称新疆农机博览会&#xff09;在新疆国际会展中心开幕。展会现场多种农牧业的新装备、新技术集中亮相&#xff0c;其中首次在新疆农机博览会上亮相的腾盾科创无人机产品引发观…

C语言——基于stm32G030的温湿度传感器项目实验

一、功能要求&#xff1a; 设备自检功能&#xff1a; 设备上电自检&#xff08;检查传感器采集是否正常&#xff0c; DHT11有存在响应&#xff0c; 可以自检使用&#xff0c; &#xff09;自检通过后&#xff0c;由串口打印设备状态信息。 自动控制功能&#xff1a; 进入自动控…

Clickhouse MergeTree 存储引擎架构总结——Clickhouse 架构篇(二)

文章目录 前言MergeTree存储引擎的三大特点MergeTree 的数据组织MergeTree的文件组织数据文件、元数据文件、索引文件和其他文件分区数据库和表 索引与事务数据库存储引擎的对比存储引擎如何影响查询速度MergeTree存储引擎的工作过程 前言 存储引擎是ClickHouse非常重要的一个…

小易大数据:大数据报告查询领域的黑马,这些优势让你无法忽视!

随着大数据技术被运用到各行各业&#xff0c;风控领域也不例外&#xff0c;形成了基于大数据技术的大数据信用&#xff0c;也就是我们常说的大数据报告或者网贷大数据&#xff0c;在众多的查询平台中&#xff0c;小易大数据平台在市面上是比较受欢迎的&#xff0c;那在小易平台…

windows内存管理

一 windows系统的内存管理涉及哪些 1.1 虚拟内存管理机制 windows操作系统使用虚拟内存技术&#xff0c;将磁盘文件&#xff0c;通过映射对象&#xff08;存储在物理内存&#xff09;关联&#xff0c;映射到虚拟内存作为文件试图。即用户操作"虚拟内存中File View Objec…

C-数据结构-树状存储基本概念

‘’’ 树状存储基本概念 深度&#xff08;层数&#xff09; 度&#xff08;子树个数&#xff09; 叶子 孩子 兄弟 堂兄弟 二叉树&#xff1a; 满二叉树&#xff1a; 完全二叉树&#xff1a; 存储&#xff1a;顺序&#xff0c;链式 树的遍历&#xff1a;按层遍历&#xff0…

Kibana(一张图片胜过千万行日志)

Kibana&#xff08;一张图片胜过千万行日志&#xff09; Kibana是一个开源的分析和可视化平台&#xff0c;设计用于和Elasticsearch一起工作。 你用Kibana来搜索&#xff0c;查看&#xff0c;并和存储在Elasticsearch索引中的数据进行交互。 你可以轻松地执行高级数据分析&a…

LangChain 0.2 - 基于 SQL 数据构建问答系统

本文翻译整理自&#xff1a;Build a Question/Answering system over SQL data https://python.langchain.com/v0.2/docs/tutorials/sql_qa/ 文章目录 一、项目说明⚠️ 安全说明⚠️架构 二、设置三、Chains1、将问题转换为 SQL查询2、执行 SQL查询3、回答问题 四、Agents1、S…

TiDB学习3:TiKV

目录 1. TiKV架构和作用 2. RocksDB 2.1 写入 2.2 查询 2.3 Column Families列簇 3. 分布式事务 3.1 事务流程 3.2 分布式事务流程 3.3 MVCC 4. Raft与Multi Raft 4.1 Raft日志复制 4.2 Raft Leader选举 5. TiKV- 读写 5.1 数据的写入 5.2 数据的读取ReadIndex …

夏日防晒笔记

1 防晒霜 使用方法&#xff1a;使用前上下摇晃瓶身4至5次&#xff0c;在距离肌肤10至15cm处均匀喷上。如在面部使用&#xff0c;请先喷在掌心再均匀涂抹于面部。排汗量较多时或擦拭肌肤后&#xff0c;请重复涂抹以确保防晒效果。卸除时使用普通洁肤产品洗净即可。

leetcode-主持人调度(二)-110

题目要求 思路 1.先将开始时间和结束时间拆分放到两个数组中进行排序 2.如果开始的时间小于结束时间&#xff0c;说明目前没有空闲的人&#xff0c;需要增加人&#xff0c;如果大于等于&#xff0c;说明有人刚结束了主持&#xff0c;可以进行新的主持了&#xff0c;变更到下一…

深度学习-序列模型

深度学习-序列模型 1. 定义2. 应用领域3. 典型模型4. 技术细节5. 总结 序列模型是一种处理序列数据的机器学习模型&#xff0c;其输入和/或输出通常为序列形式的数据。以下是关于序列模型的详细解释&#xff1a; 1. 定义 序列模型是输入输出均为序列数据的模型&#xff0c;它…

【Django】开发个人博客系统【1】

使用Django开发个人博客系统&#xff0c;博客系统包括用户&#xff08;博主&#xff09;注册和登录、博主资料信息、图片墙功能、留言板功能、文章列表、文章正文内容和Admin后台系统。 1. 项目架构设计 下一步将上述设置写入Django的配置文件settings.py&#xff0c;当Django…

R可视化:另类的柱状图

介绍 方格状态的柱状图 加载R包 knitr::opts_chunk$set(echo TRUE, message FALSE, warning FALSE) library(patternplot) library(png) library(ggplot2) library(gridExtra)rm(list ls()) options(stringsAsFactors F)导入数据 data <- read.csv(system.file(&qu…

【代码随想录——回溯算法——三周目】

1. 子集2 这题需要先进行排序&#xff0c;和候选人那题类似。防止出现重复的子集。 func subsetsWithDup(nums []int) [][]int {path : make([]int, 0)res : make([][]int, 0)sort.Ints(nums)var dfs func(nums []int, start int)dfs func(nums []int, start int) {res app…

保留两位小数不四舍五入,10000.55变成10000.54的坑

正解 function moneyFormat(num){ let money num "";//隐式转换为字符串和toString()效果一样//没有小数补齐这个0if(money.indexOf(".")"-1"){moneymoney".00";}else{//有小数截取前二位小数moneymoney.substring(0,money.inde…

多线程基本常识

多线程的状态 在Java中&#xff0c;一个线程的生命周期有以下几种状态&#xff1a; 新建&#xff08;New&#xff09;&#xff1a;当线程对象被创建时&#xff0c;线程处于新建状态。此时线程对象存在&#xff0c;但还没有调用start()方法启动线程。 运行&#xff08;Runnable…

逆向基础:软件手动脱壳技术入门

这里整合了一下之前自己学习软件手工脱壳的一些笔记和脱文&#xff0c;希望能给新学软件逆向和脱壳的童鞋们一点帮助。 1 一些概念 1.1 加壳 加壳的全称应该是可执行程序资源压缩&#xff0c;是保护文件的常用手段。加壳过的程序可以直接运行&#xff0c;但是不能查看源代码…

基于多源数据的微服务系统失败测试用例诊断

简介 本文介绍由南开大学、华为云及清华大学共同合作的论文:基于多源数据的微服务系统失败测试用例诊断。该论文已被FSE 2024&#xff08;The ACM International Conference on the Foundations of Software Engineering&#xff09; 会议录用&#xff0c;论文标题为: Fault D…

【MySQL】库的操作+表的操作

库的操作表的操作 1.库的操作1.1创建数据库1.2删除数据库1.3查找数据库1.4修改数据库1.5数据库备份和恢复1.6查看连接情况 2.库的操作2.1创建表2.2查看表结构2.3修改表2.4删除表 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; …