【小程序】websocket实现“谁是卧底”在线随机发牌 您所在的位置:网站首页 卧底游戏怎么写 【小程序】websocket实现“谁是卧底”在线随机发牌

【小程序】websocket实现“谁是卧底”在线随机发牌

2023-08-24 23:11| 来源: 网络整理| 查看: 265

目录

前言

1. 逻辑分析

2. websocket

3. 小程序端代码实现

4.服务端代码实现

后记

前言

有兴趣的同学先扫码体验一下小程序

继我的个人小程序(“你划我猜出题器”)上线第二版本(自建词库)后,又有新的想法涌现出来,做一个“谁是卧底”在线随机发牌吧(有时间再写一下第一个版本跟第二个版本的博文)。既然如此,就要思考一下“谁是卧底”的技术实现点。

词的来源,可利用现有词库管理系统。接下来的难点就是如何实现在线随机发牌。很明显,http请求无法实现“谁是卧底”的发牌,应该是服务端主动推送词语给各个客户端。

websocket能够很好的实现这个功能。

小程序websocket传送门:https://developers.weixin.qq.com/miniprogram/dev/api/network/websocket/wx.sendSocketMessage.html

 

1. 逻辑分析

 开发惯例,整理基本逻辑。“谁是卧底”的发牌逻辑相对简单。参考微信的“面对面建群”概念,我化用成“面对面建房”。

(1)房的概念用来隔离发牌的环境,每一个房就是一个单独的游戏环境,用过的词不再出现。

(2)房内的所有用户,第一个进入房的称为房主,拥有控制发牌权,其他用户无。

(3)客户端与服务端的交互组成:①建立websocket连接;②用户进入房间;③发牌;④用户离开房间;⑤断开websocket连接。

 

2. websocket

 客户端需要处理三个触发事件,三个监听事件。

(1)用户进入房间,通知服务端;

(2)用户离开房间,通知服务端;

(3)房主点击“开始发牌”,通知服务端。

对应的,客户端同样需要监听三个事件。

(1)服务端通知其他用户进入房间;

(2)服务端通知其他用户离开房间;

(3)服务端发牌。

看了websocket的API,它不似socket.io那般可以拆分事件去监听,去触发,而是统一接收服务端数据,统一发送数据给服务端,而且也不能传送对象数据,websocket只能传送字符串和二进制数据。

所以,监听和触发用onSocketMessage和sendSocketMessage统一处理的话,需要自定义对象参数来区分每个事件。这里用到JSON.stringify和JSON.parse,将对象转化为字符串,将字符串转化为对象。

wx.sendSocketMessage({ // 发送消息给服务端 data: JSON.stringify(obj), // 特别注意!!!websocket只接收string或ArrayBuffer success(res) { console.log('sendSocketMessage发送消息至服务器成功', res) }, fail(err) { console.error('sendSocketMessage发送消息至服务器报错', err) } }) wx.onSocketMessage(function (res) { // 接收服务端下发的消息 let obj = JSON.parse(res.data) // 特别注意!!res只能是string/ArrayBuffer console.log('这是来自服务器的消息') })

“谁是卧底”websocket传送数据的结构

let obj = { event: eventName, // 事件 roomKey: roomKey, // 房间号 data: data, // 数据,用户信息或者词牌信息 } /** @event * 'createRoom' 进入房间-客户端 * 'leaveRoom' 离开房间-客户端 * 'deliverPocker' 开始发牌-客户端 * 'intoRoom' 进入房间-服务端 * 'leaveRoom' 离开房间-服务端 * 'revivePocker' 发牌-服务端 */

 

3. 小程序端代码实现

3.1 准备roomKey和用户信息

点击"面对面建房"按钮申请用户授权,获取用户头像和昵称,将信息保存在客户端storage中。输入四位数字房号,将房号保存在客户端storage中,跳转到游戏页面,websocket处理均在游戏页面中处理。

3.2 建立websocket链接

进入游戏页面,在onReady生命周期函数中建立websocket连接,并且通知服务端“用户进入房间”

onReady() { console.log('onReady---------') let that = this that.setData({ roomKey: wx.getStorageSync('roomKey'), user: wx.getStorageSync('userInfo') }) that.connect() // 建立websocket连接 wx.setNavigationBarTitle({ title: '发牌房间' + that.data.roomKey }) }, connect() { let that = this wx.connectSocket({ // 建立websocket连接 url: 'wss://www.*****.****/' // wss地址 }) wx.onSocketOpen(function (res) { // 建立连接成功 that.setData({ connectStatus: 1 }) console.log('websocket 已经连接服务器', res) that.send('createRoom', that.data.roomKey, that.data.user) // 通知服务端用户进入房间 }) },

3.3 封装send函数,其作用——发送数据给服务端

send(eventName, roomKey, data) { let obj = { event: eventName, // 事件 roomKey: roomKey, // 房间号 data: data, // 传送数据 } wx.sendSocketMessage({ data: JSON.stringify(obj), success(res) { console.log('sendSocketMessage发送消息至服务器成功', res) }, fail(err) { console.error('sendSocketMessage发送消息至服务器报错', err) } }) },

3.4 关闭websocket连接

在onUnload生命周期函数里处理“离开房间”事件,并且关闭websocket连接

onUnload() { console.log('onUnload---------') this.close() }, close() { this.send('leaveRoom', this.data.roomKey, this.data.user) let that = this wx.closeSocket() // 关闭websocket连接 wx.onSocketClose(function (res) { // 关闭成功 that.setData({ connectStatus: 0 }) console.log('websocket服务器已经断开', res) }) },

3.5 在onLoad生命周期函数里监听服务端发送的消息

onLoad() { console.log('onLoad---------') let that = this let key = wx.getStorageSync('roomKey') wx.onSocketMessage(function (res) { // 接收服务端下发的消息 let obj = JSON.parse(res.data) // 将字符串转化为对象 console.log('这是来自服务器的消息', obj.event, obj.roomKey, obj.data) if (obj.event == 'intoRoom' && key == obj.roomKey) { that.resetUsers(obj.data) // 更新当前房间里的用户列表视图 } if (obj.event == 'leaveRoom' && key == obj.roomKey) { that.resetUsers(obj.data) // 更新当前房间里的用户列表视图 } if (obj.event == 'revivePocker' && key == obj.roomKey) { that.deliverPocker(obj.data) // 更新牌面词语 } }) }, resetUsers(users) { this.setData({ users: users, isOwner: false }) for (let i = 0; i < users.length; i++) { if (users[i].isOwner && (users[i].nickName == this.data.user.nickName)) { // 是否是房主 this.setData({ isOwner: true }) } } }, deliverPocker(users) { let myuser = wx.getStorageSync('userInfo') for (let i = 0; i < users.length; i++) { if (users[i].nickName == myuser.nickName) { this.setData({ pocker: users[i].pocker }) this.showWord() break; } } },

 

4.服务端代码实现

服务端主要用node的ws实现

const ws = require('ws');

(1)每个用户建立websocket连接,需要保存该连接

// https服务 const serve = https.createServer(options, app.callback()).listen(config.port, (err) => { if (err) { console.log('服务启动出错', err); } else { db.connect(); // 数据库连接 console.log('guessWord-server运行在' + config.port + '端口'); } }); // wss服务 let clients = [] // 客户端websocket连接队列 let userIndex = 0 // 客户数 let undercoverWords = null // 谁是卧底词库 const wss = new ws.Server({ server: serve }) wss.on('connection', function (wxConnect) { clients.push({ "ws": wxConnect, "nickname": 'userIndex' + (userIndex++) }); console.log('wss connection wxConnect ------ ') WordAPI.undercover().then(res => { // 获取“谁是卧底”词库 if (res.code == 200) { let arr = res.data arr.sort(function () { return 0.5 - Math.random() }) // 打乱题库 undercoverWords = arr } else { console.error(res.message) } }) wxConnect.on('message', function (msg) { // 监听客户端发送的消息 let obj = JSON.parse(msg) // console.log('接收来至客户端的信息', obj.event, obj.roomKey, obj.data.nickName) if (obj.event == 'createRoom') { // 进入房间 createRoom(obj.roomKey, obj.data) } if (obj.event == 'leaveRoom') { // 离开房间 leaveRoom(obj.roomKey, obj.data) } if (obj.event == 'deliverPocker') { // 房主发牌 deliverPocker(obj.roomKey) } }) })

(2)封装服务端的广播函数(通知所有客户端)

// 广播所有客户端消息 function broadcastSend(event, roomKey, data) { clients.forEach(function (v, i) { if (v.ws.readyState === ws.OPEN) { v.ws.send(JSON.stringify({ event: event, roomKey: roomKey, data: data })); } }) }

(3)每个用户进入房内,服务端需要往该房的用户列表里增加数据,并通知客户端

function createRoom(roomKey, user) { if (rooms[roomKey]) { // 房间已存在,加入房间 rooms[roomKey].users.push(user) } else { // 房间不存在,创建房间 user['isOwner'] = true rooms[roomKey] = { users: [user], games: [] } } broadcastSend('intoRoom', roomKey, rooms[roomKey].users) console.log(user.nickName + '进入房间' + roomKey + ',当前房间人数' + rooms[roomKey].users.length) }

(4)房主每点击一次“开始发牌”,服务端需要进行三个随机处理:

①随机抽取一组词语作为本轮游戏词

②随机决定哪个词作为卧底词

③从房内用户随机分配卧底身份

分配完毕,每个用户拥有自己的身份词,就可以将词下发给客户端了。

function deliverPocker(roomKey) { // 发牌 // 排除已发过的牌 let g = rooms[roomKey].games let current = [] for (let i = 0; i < undercoverWords.length; i++) { if (g.indexOf(undercoverWords[i].title) == -1) { current.push(undercoverWords[i].title) } } // 打乱题库 current.sort(function () { return 0.5 - Math.random() }) if (current.length == 0) { // 牌已发完 broadcastSend('revivePocker', roomKey, '') } else { // 随机取出牌,并记录 let pocker = current[0] rooms[roomKey].games.push(pocker) // 决定哪个词是卧底牌 let arr = pocker.split(',') let random = Math.random() // 随机数 let wodi = arr[0] // 卧底牌 let pingm = arr[1] // 平民牌 if (random > 0.5) { wodi = arr[1] pingm = arr[0] } // 决定哪几个人是卧底牌 let ul = rooms[roomKey].users.length // 当前房间参与游戏的人数 // 3-5人则1个卧底,6-8则2个卧底,9-11人则3个卧底,12人以上4个卧底 let wodiIndex = [] if (ul >= 3 && ul = 6 && ul = 9 && ul = 12) { wodiIndex.push(getRandomNoRepeat(0, ul - 1, wodiIndex)) wodiIndex.push(getRandomNoRepeat(0, ul - 1, wodiIndex)) wodiIndex.push(getRandomNoRepeat(0, ul - 1, wodiIndex)) wodiIndex.push(getRandomNoRepeat(0, ul - 1, wodiIndex)) } // 分发牌面 let u = rooms[roomKey].users for (let i = 0; i < ul; i++) { if (wodiIndex.indexOf(i) != -1) { u[i]['pocker'] = wodi } else { u[i]['pocker'] = pingm } } broadcastSend('revivePocker', roomKey, u) } } // 生成从minNum到maxNum的随机数 function randomNum(minNum, maxNum) { switch (arguments.length) { case 1: return parseInt(Math.random() * minNum + 1, 10); case 2: return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10); default: return 0; } } // 生成从minNum到maxNum的随机数,但不含已有的数组 function getRandomNoRepeat(minNum, maxNum, had) { let i = randomNum(minNum, maxNum); if (had.indexOf(i) === -1) { return i; } return getRandomNoRepeat(minNum, maxNum, had); }

(5)每个用户离开房,需要删除房内该用户,并通知客户端,当房内人数为0,需要销毁房

function leaveRoom(roomKey, user) { let u = rooms[roomKey].users let index = -1 for (let i = 0; i < u.length; i++) { if (u.nickName == user.nickName) { index = i break } } rooms[roomKey].users.splice(index, 1) if (rooms[roomKey].users.length == 0) { delete rooms[roomKey] broadcastSend('leaveRoom', roomKey, rooms[roomKey].users) } else { if (index == 0) { rooms[roomKey].users[0]['isOwner'] = true } broadcastSend('leaveRoom', roomKey, rooms[roomKey].users) } if (rooms[roomKey]) { console.log(user.nickName + '离开房间' + roomKey + ',当前房间人数' + rooms[roomKey].users.length) } else { console.log(user.nickName + '离开房间' + roomKey + ',当前房间人数为0,房间已被销毁') } }

走到这里,全部流程已经走通~~~~

后记

服务端代码可以优化一波~~~~有待优化,等下波优化再更新



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有