从零开始完整开发基于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. ...
随机推荐
- victoriaMetrics中的一些Sao操作
victoriaMetrics中的一些Sao操作 快速获取当前时间 victoriaMetrics中有一个fasttime库,用于快速获取当前的Unix时间,实现其实挺简单,就是在后台使用一个goro ...
- Java 语言实现简易版扫码登录
基本介绍 相信大家对二维码都不陌生,生活中到处充斥着扫码登录的场景,如登录网页版微信.支付宝等.最近学习了一下扫码登录的原理,感觉蛮有趣的,于是自己实现了一个简易版扫码登录的 Demo,以此记录一下学 ...
- [题解] [AGC024F] Simple Subsequence Problem
题目大意 有一个 01 串集合 \(S\),其中每个串的长度都不超过 \(N\),你要求出 \(S\) 中至少是 \(K\) 个串的子序列的最长串,如果有多解,输出字典序最小的那组解. 由于 \(S\ ...
- latex中显示代码
如何在latex中添加代码模块 首先在开头导入以下的包 \usepackage{listings} \usepackage{ctex} % 用来设置附录中代码的样式 \lstset{ basicsty ...
- wlile、 for循环和基本数据类型及内置方法
while + else 1.while与else连用 当while没有被关键字break主动结束的情况下 正常结束循环体代码之后执行else的子代码 """ while ...
- Spring Cloud Feign+Hystrix自定义异常处理
开启Hystrix spring-cloud-dependencies Dalston版本之后,默认Feign对Hystrix的支持默认是关闭的,需要手动开启. feign.hystrix.enabl ...
- 亿信BI——维度转换组件使用
功能模块: 用户点击"维度转换"模块进行维度转换操作,维度转换页面的顶部导航栏包括基本属性和转换设置两部分. 基础属性: 在基本属性模块部分,编号.标题和类型是必填项且系统已经默认 ...
- Python实现将csv文件转化为html文件
核心技术: Pandas 需要转化的csv文件(business.csv): 源代码: import pandas as pd f=pd.read_csv("business.csv&quo ...
- TKE qGPU 通过 CRD 管理集群 GPU 卡资源
作者 刘旭,腾讯云高级工程师,专注容器云原生领域,有多年大规模 Kubernetes 集群管理经验,现负责腾讯云 GPU 容器的研发工作. 背景 目前 TKE 已提供基于 qGPU 的算力/显存强隔离 ...
- golang 方法接收者
[定义]: golang的方法(Method)是一个带有receiver的函数Function,Receiver是一个特定的struct类型,当你将函数Function附加到该receiver, 这个 ...