背景

象棋是一款益智类游戏,玩家可以通过操控棋子排兵布阵,最终获得游戏的胜利。在作者小时候,该游戏一直是风靡在村里的大街小巷,本着对童年的回味和对象棋的热爱,本主会尽自己的全力完成此款游戏的设计开发,欢迎点赞和收藏!

  • 在线地址:https://www.aixt.vip (点我直达)

功能列表

  • 游客登录
  • 账号密码/绑定邮箱登录
  • QQ授权登录
  • 账号注册,支持绑定邮箱,绑定时需要发送验证码
  • 忘记密码找回
  • 版本历史记录展示
  • 登录后展示个人信息(头像、昵称、积分、胜负和、排名)
  • 加入房间
  • 房间换桌
  • 房间踢人
  • 对局准备
  • 棋盘、棋子展示
  • 棋子移动动画
  • 游戏音效
  • 对局聊天、悔棋、认输、求和
  • 换肤功能
  • 对局观战信息展示
  • 观战全员禁言
  • 观战针对个人禁言
  • 观战针对个人踢出
  • 房间邀请功能
  • 大厅用户可观战已建立的对局
  • 大厅用户观战聊天功能
  • 大厅用户观战交换观战视角(红方与黑方互相切换)
  • 大厅用户观战可保持屏幕常亮
  • 复盘功能
  • 复盘分享功能
  • 棋谱功能
  • 数据断连(游戏断开再登录后,可恢复对局)

技术要点

  • React 前端主要语言,配合umijs编写功能
  • Ant-Mobile UI框架,有很多现成的组件供使用
  • umijs 脚本架,快速构建项目
  • socket.io 消息通信
  • node.js javascript引擎,配合ts编写服务端功能
  • ES6 学习ES6的语法,用于编写代码
  • Redis 缓存工具,用于缓存对战信息
  • MySQL 数据库,用户保存游戏产生的数据
  • typescript

项目结构(重点文件/文件夹)

├── src
│   ├── app.js                     应用配置(全局)
│   ├── config.js                  项目配置(全局)
│   ├── global.less                样式配置(全局)
│   ├── assets                     资源文件
│   │   ├── audio                  音效
│   │   ├── font                   字体
│   │   ├── images                 图片
│   │   └── skins                  皮肤
│   ├── button                     自定义按钮
│   ├── circle                     自定义弹窗
│   ├── components                 自定义加载动画
│   ├── header                     自定义标题
│   ├── layouts                    父页面
│   ├── pages                      子页面
│   │   ├── document.ejs           启动加载页
│   │   ├── board                  棋盘界面
│   │   ├── user                   登录/注册/忘记密码
│   │   ├── platform               游戏平台界面
│   │   ├── review                 复盘界面
│   │   ├── rooms                  房间界面
│   │   ├── sahre                  分享页面
│   │   ├── version                版本界面
│   │   └── watch                  观战界面
│   ├── service                    Socket封装
│   │   ├── event.js               socket事件管理(API)
│   │   └── socket.js              socket连接管理
│   └── utils                      工具包
│       ├── board-canvas-utils.js  棋盘绘制工具
│       ├── board-utils.js         棋盘工具
│       ├── cache-key-utils.js     缓存Key管理
│       ├── check-win.js           检测胜利(绝杀/困毙)
│       ├── const-utils.js         常量管理
│       ├── cryptor-utils.js       数据加解密
│       ├── head-canvas-utils.js   头像绘制工具
│       ├── images-res.js          图片资源管理
│       ├── keep-fighting.js       长将检测
│       ├── log-utils.js           日志管理
│       ├── map-res.js             地图静态数据
│       ├── rule-check.js          规则检测
│       ├── rules                  具体规则实现
│       │   ├── basic-rule.js      基本规则检测(车、马、象等)
│       │   └── boss-rule.js       Boss规则检测(碰面、被将军)
│       ├── skin-utils.js          皮肤管理
│       ├── sounds-res.js          音效管理
│       └── storage-utils.js       缓存管理
├── package.json

功能细节

1. 通信

socket.io有两个常用方法,发送emit,监听on,当服务器启动后,首先会通过on监听已预设的API,当socket与服务端成功连接后,客户端可以通过emit向相应的API发送消息,此时服务端就能收到该消息并做一系列操作,拿聊天来说,客户端调用sendChatApi发送消息到服务端后,服务端将消息进行敏感词处理,处理完成后将消息通过emit发送到指定的房间,而客户端则会通过on监听此房间的消息,从而实现消息转发。若有疑问,可查看socket.io相关文档

2. 棋盘棋子绘制

游戏素材都来源于网络,棋盘和棋子也就是一张贴图,在一个html中绘制一张贴图,一般用img标签,如何将一张img绘制到不同的位置,可以通过对其进行position: absolute,再根据棋盘的间距和棋子的坐标,设置css的transform: translate(${x}rem,${y}rem)

棋盘绘制示例:

gameMap.map(chess => {// gridSpan: 网格大小const x = gridSpan * chess.y - (gridSpan / 2);const y = gridSpan * chess.x - (gridSpan / 2);return (<imgkey={chess.id}alt={chess.id}// 获取棋盘的图片src={resMap.get(chess.id)}style={{// 浮动,并利用translate对棋子进行偏移(left/top是同样的效果)position: 'absolute',width: `${gridSpan}rem`,height: `${gridSpan}rem`,// 移动时的动画transition: 'transform 0.5s, opacity 0.5s',transform: `translate(${x}rem,${y}rem)`,zIndex: 3,}}/>);
})
3. 棋子规则

车走直、马走日、象走田,要实现这些棋子的规则,首先要将棋盘抽象理解成一个二维数组,x, y 相交的点为棋子所在的点,假设(左上角)车0,0的位置,那么当要走到的位置时,实际上就是坐标0,0 -> 0,1,明白这个道理后,如果你要对车进行规则判定,就非常的简单,车只能直着走,那么必然x -> x1,它两的值是相等的,如果x不相等,则再判断一下y -> y1是不是相等,如果两者都不相等,那必然不可能是横或竖着走了,另外还要考虑其它的一些情况,比如说推算到0,1时这个位置有我方的,那后面的位置,车也是走不了的,如果遇到的是对方的棋,这个位置就能走(对方的棋子可以吃)

  • 的走法判断逻辑(可以考虑一下马、象这种有蹩脚的是如何判定的)
const handleMoveC = (gameMap, from, to) => {const board = listToArray(gameMap);// 必须有一个点是在同一条线上if (from.x === to.x) {const tempChessList = [];let start = Math.min(from.y, to.y);let end = Math.max(from.y, to.y);// 判断其中间有没有棋子while (start <= end) {// 排除掉自身位置且中间不能有其它棋子if (from.y !== start && board[from.x][start]) {tempChessList.push({ x: from.x, y: start });}++start;}// 循环结束后进行判断,空棋盘直接返回(要走的位置中间没有其它棋子)if (tempChessList.length === 0) {return true;} else if (tempChessList.length === 1) {// 如果有一个棋子,则要处理一下是否是要到达的位置const chess = tempChessList.pop();if (chess.x === to.x && chess.y === to.y) {return true;}}return false;} else if (from.y === to.y) {const tempChessList = [];let start = Math.min(from.x, to.x);let end = Math.max(from.x, to.x);while (start <= end) {if (from.x !== start && board[start][from.y]) {tempChessList.push({ x: start, y: from.y });}++start;}// 循环结束后进行判断,空棋盘直接返回(要走的位置中间没有其它棋子)if (tempChessList.length === 0) {return true;} else if (tempChessList.length === 1) {// 如果有一个棋子,则要处理一下是否是要到达的位置const chess = tempChessList.pop();if (chess.x === to.x && chess.y === to.y) {return true;}}}return false;
};
4. 如何判断游戏结束
  • 当boss没有位置可走时(只有boss自己)
  • 当boss没有位置可走时(有其它棋子,但被堵死,如:象被卡住象眼了)
  • 被将军无路可走(包括没有其它棋子能抵挡boss被击杀)
  • 被将军且boss走棋就会与对方boss碰面
  • 双方都没有可进攻棋子判和
  • 对局中有一方直接认输
  • 对局中有一方发起求和且被求和方也同意和棋
5. 匹配机制

当用户进入房间后,后台有一张用户游离表会记录该用户在整个游戏中的状态,包括但不限于当前页面当前房间号房间状态,当用户首次进入房间,该房间无其他人时,房间状态等待中,这个动作,服务端只需要将该用户游离数据表房间号变更为当前分配的房间号并更改房间状态等待中,当另一位玩家也进入房间时,此时服务器会从游离数据表中根据房间状态进行数据筛选,目前匹配规则是房间状态等待中的会优先分配,当房间内人数达到2个时,房间状态会变更为多人等待,此时该房间不会参与匹配。

  • 目前已设计的房间状态
// 空房间
export const ROOM_STATUS_EMPTY = 'EMPTY';
// 有人在房间中等待(仅1个人)
export const ROOM_STATUS_WAIT = 'WAIT';
// 多个人在房间中等待
export const ROOM_STATUS_MULTIPLE_WAIT = 'MULTIPLE_WAIT';
// 匹配成功(双方已准备)
export const ROOM_STATUS_MATCH_SUCCESS = 'MATCH_SUCCESS';
// 对战中
export const ROOM_STATUS_BATTLE = 'BATTLE';
// 对战有一方超时了
export const ROOM_STATUS_TIMEOUT = 'TIMEOUT';
// 对战结束
export const ROOM_STATUS_BATTLE_OVER = 'BATTLE_OVER';
6. 棋子移动

如果你对如何使用emiton已经有足够的了解,那么棋子移动则会变得异常简单,简单来说,棋子移动至少有两个对象的数据传送给服务端,分别是fromto,它们都包含x, y属性,前者为棋子所在位置,后者为要到达的位置,但值得注意的是,对战时,对方看到的是我方的镜像数据,也就是说,棋子通过服务器发送给对方时,对方需要做棋子坐标翻转,下面是一个真实的棋子移动数据包

{"fromChessBox": {"color": "boxColorBlack","show": true,"x": 4,"y": 7},"battleId": "73782847853944809178100889398553","from": {"isBlackColor": true,"isAttach": true,"prefix": "P","isBoss": false,"x": 4,"y": 7,"id": "BRP"},"to": {"x": 4,"y": 4},"toChessBox": {"color": "boxColorBlack","show": true,"x": 4,"y": 4},"stepExplain": "炮2平5","userId": "tour_47347731","roomId": 7
}
7.悔棋

如果在对局中想要悔棋,首先,如果是我方落子时,我方是无法悔棋的,其次,当非我方落子时,我方发起悔棋后,对方需要同意,我方才会做悔棋操作,悔棋在技术上的实现方案是,将每一次的快照都保存在本地,当调用悔棋方法时,将倒数第一条数据删除,并还原当前棋盘数据成倒数第二条。值得注意的是,当用户在房间或对局中,用户有可能将页面刷新,所以此处跟数据断连功能也是紧密配合的,当用户做了上述操作时,数据断连会将对局的数据从服务器中查询并发送给客户端

8.观战实现

当创建对局后,对局表会产生一条对战记录,该条记录的状态为对战中,当其它用户进入观战列表时,列表会检索对战表的对战状态,将对战中的数据展示在列表中,另外还有一个重要的概念,当用户进入房间后,后台会将用户的 socket 加入到分配的房间中,而房间是可以存在多人的,消息通知是直接往这个房间推送的,如果用户观战了某一场对局,那么该用户也会加入这个房间中,并且共享这个房间的数据,此时观战的数据来源有两部分,第一部分则是这场对局已保存在库里的数据,另一部分则是对局玩家操作棋子后,将棋子操作信息发送到房间,观战用户则共享房间的数据,再通过逻辑在棋盘上呈现。

9.复盘和分享

a. 通过前面几点的介绍,您大致能了解到,对战是会对每一步保存快照的,而复盘则是对这些快照的数据进行读取展示。
b.分享使用的数据也是一样的,用户将复盘的数据分享时,后台会为盘对局创建一个唯一的分享码,通过分享链接前缀+分享码组成一个分享链接,用户通过分享的链接进入页面后,使用的也是快照数据。

10.邀请功能

为了提高匹配成功率,用户在房间时可以通过在线列表,通过对玩家进行邀请,邀请请求发送到服务器后,服务器通过被邀请的用户账号查找其对应的socket,并通过emit发送邀请信息,同悔棋一样,受邀也需要对方同意,才能加入该房间,如若房间人数已满,房间状态已进行对战,此些情况都会被视为邀请已过期

更新还在继续,如果您对本游戏感兴趣,欢迎点赞收藏,本博主会陆续揭露代码细节...

零前端基础硬刚《象棋》- 持续更新相关推荐

  1. 前端基础知识点汇总(持续更新...)

    1. HTML5 文档类型和字符集是? <!DOCYPE>声明位于html文档的最上方,处于`html`  UTF-8字符集. 2. HTML5语义化的优势? HTML结构清晰 代码可度性 ...

  2. javaSE基础重点知识点总结 持续更新

    javaSE基础重点知识点解析 文章目录 javaSE基础重点知识点解析 前言 1.重载和重写 2.匿名对象 3.可变个数形参的方法 4.MVC设计模式 5.java.lang.Object类 6.自 ...

  3. 前端javaScript高频面试题——持续更新

    目录 1.== 和 ===区别,分别在什么情况使用 2. 判断数据类型的方法 3.说说JavaScript中的数据类型?存储上的差别? 4.JavaScript中的操作符 5.var,let,cons ...

  4. 前端综合面试题一(持续更新)

    Map和Object的区别 es6提供了一个Map类,这是新增的一个数据结构,用起来有点像Object 区别: Object本质上是哈希结构的键值对的集合,它只能用字符串.数字或者Symbol等简单数 ...

  5. 计算机基础面经积累---持续更新

    1.gcc,g++,gdb常用命令 首先了解gcc,g++的区别.要先知道我们写的源代码是如何被编译器运行的.大概有四个阶段: 预处理:处理宏定义等宏命令,删除空格等,生成后缀为".i&qu ...

  6. web前端面试常考问题——持续更新中(5.20)

    1.介绍一下你的技术栈             HTML5 + CSS3 + ES6 + Jquery + React + Webpack + git + npm .... 2.简单自我介绍 3.电商 ...

  7. 前端经典面试题(持续更新中)

    前言 写本章的初衷是因为目前网上的面试题层出不穷,各种类型的问题都有,不是很符合本公司的项目要求以及就面试者的水品也是层次不齐,所以很难有一个通用性面试题目. 本文章将根据笔者作为面试官以及面试者的一 ...

  8. 渗透学习-文件上传篇-基础知识部分(持续更新中)

    提示:仅供进行学习使用,请勿做出非法的行为.如若由任何违法行为,将依据法律法规进行严惩!!! 文章目录 前言 一.文件上传漏洞简要阐述 文件上传是什么? 为什么会产生文件上传漏洞?及其可能一哪些危害? ...

  9. 说话人识别相关基础知识整理(持续更新)

    说话人识别领域的研究所面临的挑战 背景噪声问题,跨信道问题,多说话人分割聚类,多模态识别,短语音问题,语音的长时变换问题,耳语音以及其他各种实际应用环境下的鲁棒性问题等. 说话人识别技术研究的核心是解 ...

最新文章

  1. (C#)如何利用Graphics画出一幅图表
  2. docker helowin 迁移_docker-compose 安装 oracle_11g_r2 并实现数据持久化
  3. 采用流水线技术实现8位加法器
  4. jfinal使用shiro注解大体流程
  5. CF1253F Cheap Robot
  6. LOJ.6435.[PKUSC2018]星际穿越(倍增)
  7. linux系统下:IO端口,内存,PCI总线 的 读写(I/O)操作
  8. python 嵌套类实例_使用dict访问Python中嵌套的类实例
  9. PHP开源的项目管理软件
  10. 软件工程期末考试复习(二)
  11. 医院建筑综合布线方案特点
  12. 相机去畸变软件OCamCalib的使用方法
  13. 读心神探感悟 读心神探 语录 读心神探 观后感
  14. mybatis面试题集锦
  15. Node.js中的child_process模块详解
  16. 面试题 10.11. 峰与谷-快速排序
  17. 微信8.0苹果怎么更新
  18. JetBrains 家族所有 IDE 主题配色方案 下载安装方法
  19. office2016和office visio2016同时安装包你一次成功
  20. 模拟CMOS集成电路设计入门学习(8)

热门文章

  1. android signal 6,Android x86 交叉编译后,运行出现signal 6 (SIGABRT), code -6 (SI_TKILL)
  2. 全网最简单C盘清理攻略
  3. android 解析ksc字幕文件,KSC字模发布帖(注:KSC字幕可以用KAJConvert3转成KAJ)
  4. 计算机音乐数字乐谱青芒,4弦吉他数字曲谱_《7月上》4弦吉他琴谱
  5. python爬取连续一字板股票及当时日期数据【原创分享】
  6. 计算机辅助发音,计算机辅助普通话发音评测关键技术研究
  7. 读《美国纽约摄影学院摄影教材》下册
  8. 戴明環(PDCA)高效学习,管理,辦公
  9. 设计图纸管理系统_解决图文档查找困难
  10. Java项目——物业管理系统(附源码+数据库)