从零开始完整开发基于websocket的在线对弈游戏【五子棋】,只用几十行代码完成全部逻辑。
五子棋是规则简单明了的策略型游戏,先形成五子连线者获胜。
本课程习作采用两人在线对弈的方式进行比赛,拿着手机在上下班路上玩特别合适。
整个过程在众触低代码应用平台进行,使用表达式描述游戏逻辑(高度简化版JS)。
本课程重点学习websocket实时消息的发送与接收处理。
两人在线下棋演示
先动手玩一玩:https://gobang.zc-app.cn
因为是在线游戏,需要登录,可以用手机和邮箱分别注册,用电脑和手机自己跟自己玩。
URL后面加/z
就进入开发模式:https://gobang.zc-app.cn/z
详尽的的教学请移步哔哩哔哩视频:https://www.bilibili.com/video/BV1QX4y1A7FW
棋盘结构
$v.棋盘 = array(14, array(14, ""))
用嵌套数据组件使用14 * 14的二维数组渲染而成,即图中画黑线的格子。
方阵结构
$v.方阵 = array(15, array(15, ""))
用嵌套数据组件使用15 * 15的二维数组渲染而成,即图中黑线交叉的点,鼠标hover在组件上时高亮的圆圈。
棋手状态
undefined、邀请中、对方出棋、己方出棋
默认就是没有值,表示还没开始下棋或者结束了(胜负已分)。对方出棋的时候己方不能落子。
账号登录
既然是在线玩的,就要求登录。可以用手机或邮箱注册。
登录后马上打开连接,有玩家上线了,有玩家邀请了,对方落子了都是通过此连接socket即时通知的。
打开连接socket:onLogin
$socket.open($c.exp, { channels: ["比赛"], onOnline: true, onOffline: true, allowMultiLogin: true })
第一个参数$c.exp是个对象,可以包含onConnect, onData, onReconnect, onError,前两个是必须的。
第二个参数是option选项,channels数组里放你需关注的频道,onOnline表示有人上线时是否要通知到你,onOffline则是有人下线是要否通知,allowMultiLogin表示同一个账号能否能在多个地方登录而不强制前面登录的账号下线。
连上后:onConnect
$socket.onlines(["比赛"])
$v.onlines = $r.比赛.filter('$x !== $c.me._id')
$v.onlines.forEach('$user.get($x)')
render()
连上后查询一下关注"比赛"频道的在线玩家,排除自己后放到$v.onlines列表后依次获取用户信息。
消息格式
下面有多个on开头的表达式都是只收到socket消息。它们都有共同的格式:type
是消息类型,x
是消息体,from
是消息发送人。消息接受人to
和发送时间d
在此案例中未使用到。
有人上线了:onOnline
$v.onlines.push(x)
$v.onlines = $v.onlines.unique()
$user.get(x)
把上线的人放入上面的$v.onlines中,并去重。
有人断线了:onOffline
$v.对手 === x ? alert("对方断线了") : ""
$v.onlines.splice($v.onlines.indexOf(x), 1)
断线了就把他/她从$v.onlines移除。如果刚好是正在跟你对弈的棋手则抛出一个警告通知。
收到数据后:onData
stopIf($c.me._id == from)
$c.exp[type].exc()
render()
先要排除是自己发出的数据,因为socket是广播消息的,自己也能收到。
然后再根据消息类型执行对应的表达式,可能的类型有:on被邀、on拒邀、on受邀、on落子。
当其他人登录时,【对手】右边的问号圆圈就会闪烁,点击它会弹出在线玩家列表,从中选择一个可发出对弈邀请。
发出对弈邀请
$socket.send($x, "on被邀", "邀请")
$v.状态 = "邀请中"
info("邀请已发出,请等待对方接受邀请")
收到消息:on被邀
stopIf($v.状态, '$socket.send(from, "on拒邀", "对方正在下棋")')
$user.get(from)
$v.对手 = from
$v.pop = "选棋子"
如果自己正在下棋就直接发出"on拒邀"消息,拒绝邀请。
获取对方用户信息,弹出模态窗口提示接受要是拒绝邀请。
收到消息:on拒邀
$v.状态 = undefined
warn(x || "对方拒绝你的邀请")
把前面的”邀请中“的状态置空,弹出对方发来的拒邀消息
选子
$v.己方 = "白" // "黑"
$c.exp.受邀.exc()
接受邀请:受邀
$socket.send($v.对手, "on受邀", $v.己方)
$v.方阵 = array(15, array(15, ""))
$v.pop = undefined
$v.对方 = ($v.己方 === "黑" ? "白" : "黑")
$v.状态 = "己方出棋"
info("请出棋")
给对方发送“on被邀“消息,捎上自己选的子。
清空方阵,准备出棋。
收到消息:on被邀
$v.方阵 = array(15, array(15, ""))
$v.对手 = from
$v.对方 = x
$v.己方 = (x === "黑" ? "白" : "黑")
$v.状态 = "对方出棋"
info("对方已接受邀请,请等待对方先出棋")
from
是对手用户ID,x
是对方选的子,自己就只能选另一种子了。
落子
stopIf($v.状态 !== "己方出棋" || $v.方阵[$parent.$index][$index] || $v.连续棋子.length > 4)
$v.落子点 = [$parent.$index, $index]
$socket.send($v.对手, "on落子", $v.落子点)
$("." + $v.己方 + "子声音").play()
$v.方阵[$parent.$index][$index] = $v.己方
$v.检查方向.forEach($c.exp.落_是否胜出)
$v.状态 = "对方出棋"
如果不是己方出棋的状态,或者落子位置不在方阵内,或者已经组成4个以上连续棋子都不可落子。
发出"on落子"消息,捎上刚才的落子点坐标轴。
播放落子声音,并把己方棋子放在方阵的落子点上,并通过动态类名发出光晕。
$v.落子点[0] === $parent.$index && $v.落子点[1] === $index ? "光晕" : ""
检查刚才的落子能否胜出。
检查胜出(形成五子连线)
要判断胜负只需落子时从落子点 [y, x] 以四种连线的正反方向分别查看,累计4个以上连续同色棋子为声。
$v.检查方向
[
[
[-1, 0],
[1, 0]
],
[
[0, -1],
[0, 1]
],
[
[1, -1],
[-1, 1]
],
[
[-1, -1],
[1, 1]
]
]
-1表示往后检查,0表示不动,1表示往前检查。比如[-1, 0]是是X轴上往负值方向检查,即正西方向;[1, -1]表示先往X轴正方向检查再往Y轴负方向检查,即东北方向。
即
落子是否胜出
$v.连续棋子 = [$v.落子点]
$l.方向 = $x[0]
$l.非连续 = false
$v.循环4次.forEach($c.exp.落_相邻同色)
$l.方向 = $x[1]
$l.非连续 = false
$v.循环4次.forEach($c.exp.落_相邻同色)
stopIf($v.连续棋子.length > 4, 'info(($v.状态 === "己方出棋" ? $v.己方 : $v.对方) + "子赢了"); $v.状态 = undefined;')
先把当前落子位置作为第一个连续棋子,先往$v.检查方向
提供的一对方向的第一个方向试探移动4次(即循环4遍)看是否有相邻同色子,再往另一个方向也试探4次。
如果试探得到的$v.连续棋子
大于4个,那当前落子方胜出。
检查与落子相邻的同色子
$l.y = $v.落子点[0] + $l.方向[0] * $x
$l.x = $v.落子点[1] + $l.方向[1] * $x
!$l.非连续 && $v.方阵[$l.y][$l.x] === ($v.状态 === "己方出棋" ? $v.己方 : $v.对方) ? $v.连续棋子.push([$l.y, $l.x]) : $l.非连续 = true
一个试探方向包括X轴方向和Y轴方向,有-1、0、1三种移法,分别移动一下坐标,检查新坐标在方阵中的棋子,如果坐标上有子,并且现在是己方出棋而且这个子正好是己方颜色,那这个子就是连续棋子的一部分。其它情况都不能算连续同色子,比如坐标上没有子,或者是对方的子,再或者是以前就已经非连续了,这次就没必要继续检查了。
胜出的连续5个棋子也要发出光晕。前面新落的子已经通过动态类名发出光晕,现在要找出连续棋子的其它棋子。
$v.连续棋子.length > 4 && $v.连续棋子.find('$x[0] === $ext.$parent.$index && $x[1] === $ext.$index') ? "光晕" : ""
我们从连续棋子里面找,看看里面是否有一个棋子的坐标跟当前检查的坐标位置相同。$x[0]是连续棋子X坐标,$x[1]是Y坐标。注意,这里是嵌套数据组件里作为动态类名的,$index是当前数据组件的下标,$parent.$index是上一层数据组件的下标。但由于它们是放在find()函数里面的,需要在前面添加$ext.
表示它们函数外面上下文提供的数据,如果没有$ext.
,那就成了find()函数提供给的上下文数据了。
准备深入研究的同学请到https://www.zcappp.cn/course/gobang页面后,点击右侧的【克隆】按钮,把整个游戏复制一份随意玩弄更改。
更多教学视频请移步哔哩哔哩空间:https://space.bilibili.com/475645807,里面不仅有各种前端可视化案例演示和讲解,还有多个完整功能的网站应用案例的开发过程演示和讲解。
从零开始完整开发基于websocket的在线对弈游戏【五子棋】,只用几十行代码完成全部逻辑。的更多相关文章
- SSM开发基于Java EE在线图书销售系统
SSM(Spring+Spring MVC+MyBatis)开发基于Java EE在线图书销售系统 网站成功建立和运行很大部分取决于网站开发前的规划,因此为了在网站建立过程中避免一些不 ...
- 高效简易开发基于websocket 的通讯应用
websocket的主要是为了解决在web上应用长连接进行灵活的通讯应用而产生,但websocket本身只是一个基础协议,对于消息上还不算灵活,毕竟websocket只提供文本和二进制流这种基础数据格 ...
- .NET Core 基于Websocket的在线聊天室
什么是Websocket 我们在传统的客户端程序要实现实时双工通讯第一想到的技术就是socket通讯,但是在web体系是用不了socket通讯技术的,因为http被设计成无状态,每次跟服务器通讯完成后 ...
- Golang+Protobuf+PixieJS 开发 Web 多人在线射击游戏(原创翻译)
简介 Superstellar 是一款开源的多人 Web 太空游戏,非常适合入门 Golang 游戏服务器开发. 规则很简单:摧毁移动的物体,不要被其他玩家和小行星杀死.你拥有两种资源 - 生命值(h ...
- 如何使用irealtime.js实现一个基于websocket的同步画板
同步画板演示 同时打开2个tab,分别在画布上写下任意内容,观察演示结果,同时可设置画笔颜色及线条宽度.演示地址 初始化画布 <canvas id="drawBoard" w ...
- .NET6: 开发基于WPF的摩登三维工业软件 (7)
做为一个摩登的工业软件,提供可编程的脚本能力是必不可少的能力.脚本既可以方便用户进行二次开发,也对方便对程序进行自动化测试.本文将结合AnyCAD对Python脚本支持的能力和WPF快速开发带脚本编辑 ...
- 基于React的贪吃蛇游戏的设计与实现
代码地址如下:http://www.demodashi.com/demo/11818.html 贪吃蛇小游戏(第二版) 一年半前层用react写过贪吃蛇小游戏https://github.com/ca ...
- 一款基于Netty开发的WebSocket服务器
代码地址如下:http://www.demodashi.com/demo/13577.html 一款基于Netty开发的WebSocket服务器 这是一款基于Netty框架开发的服务端,通信协议为We ...
- workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的)
workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的) 一.总结 1.下面链接里面还有一个来聊的php聊天室源码可以学习 2. ...
随机推荐
- 【CSAPP】Attack Lab实验笔记
attacklab这节玩的是利用一个字符串进行缓冲区溢出漏洞攻击,就小时候想象中黑客干的事儿. 做题的时候好几次感叹这些人的脑洞,"这都可以攻击?还能这么注入?这还可能借力打力?" ...
- 1 Mybatis动态SQL
Mybatis动态SQL 1. 注解开发 我们也可以使用注解的形式来进行开发,用注解来替换掉xml. 使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从 ...
- css自定义省略实例2
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- html单页面通过cdn引入element-ui组件样式不显示问题
html单页面通过cdn引入element-ui组件样式不显示问题 必须先引入vue,再通过cdn引入element,否则element-ui组件与样式无效. <!DOCTYPE html> ...
- ElasticSearch7.3学习(二十四)----相关度评分机制详解
1.算法介绍 relevance score(相关性分数) 算法,简单来说,就是计算出,一个索引中的文本,与搜索文本,他们之间的关联匹配程度.Elasticsearch使用的是 term freque ...
- 4┃音视频直播系统之浏览器中通过 WebRTC 进行桌面共享
一.共享桌面原理 共享桌面在直播系统中是一个必备功能 共享者:每秒钟抓取多次屏幕,每次抓取的屏幕都与上一次抓取的屏幕做比较,取它们的差值,然后对差值进行压缩:如果是第一次抓屏或切幕的情况,即本次抓取的 ...
- python入门基础知识三(列表和元组)
列表(list)的操作 1. 形式 var = ['char1','char2','char3',...] var = [value1,value2,value3,...] 2. 列表的增删改查 查: ...
- 百万数据 mysql count(*)优化
一.故事背景有一张 500w 左右的表做 select count(*) 速度特别慢. 二.原 SQL 分析Server version: 5.7.24-log MySQL Community Ser ...
- 基础路径规划算法(Dijikstra、A*、D*)总结
引言 在一张固定地图上选择一条路径,当存在多条可选的路径之时,需要选择代价最小的那条路径.我们称这类问题为最短路径的选择问题.解决这个问题最经典的算法为Dijikstra算法,其通过贪心选择的步骤从源 ...
- 我怀疑这是IDEA的BUG,但是我翻遍全网没找到证据!
你好呀,我是歪歪. 前几天有朋友给我发来这样的一个截图: 他说他不理解,为什么这样不报错. 我说我也不理解,把一个 boolean 类型赋值给 int 类型,怎么会不报错呢,并接着追问他:这个代码截图 ...