视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
node.js利用socket.io实现多人在线匹配联机五子棋
2020-11-27 22:13:35 责编:小采
文档


项目地址,已上传github ——>

 client端使用简单的h5+js实现了棋局的总体布局。 server端使用node的socket.io模块与客户端进行数据交互,棋子的落点和输赢校验均是在server端完成。

五子棋ui界面请见..

client端的界面这里就不做过多解释了,只要稍微懂点h5就可以自行去这里 下载源代码观看,因为今天的主题主要是socket.io这一块,所以本章只概述client和server是如何通过tcp连接进行交互的。

首先先带大家看一下目录结构

| server.js (socket服务器) 
| gobang-ui.html (是玩家下棋页面) 
| index.html (是用户登陆界面)
| home.html (是用户大厅界面, 用来匹配等待的 如果在线人数少于2人, 则匹配失败, 并会返回错误信息)
| game.html (client端程序的入口,内嵌iframe来显示各个页面,通过改变iframe的src属性,来达成伪页面跳转)
| img (图片资源文件夹)
| tou.jpg (棋盘界面用户的头像,因为登录界面只要输入用户名就可以开始游戏了,所以所有用户的头像都是一样的)

game.html主界面

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Title</title>
 <style>
 * {
 margin: 0;
 padding: 0;
 width: 100%;
 height: 100%;
 }
 </style>
 <!-- 引入cdn上的socket.io库 -->
 <script src="https://cdn.bootcss.com/socket.io/2.1.0/socket.io.js"></script>
</head>
<body>
<!-- 这里是程序的入口,通过js改变src属性,来切换页面 -->
<iframe id="game" src="index.html" width="100%" height="100%" scrolling="no"></iframe>
</body>
</html>

为什么我们要用嵌入iframe改变src属性的方式来制造页面跳转的现象?因为页面的每一次跳转或刷新都会使socket连接断开。就好像http中的request请求一样,页面每次所以我们应尽量避免页面跳转这个操作。

// 这行代码表示client端对server端进行第一次连接
var socket = io('ws://localhost:3000')

在index.html也就是用户的登录界面

<!-- 这是用户登录的按钮 -->
<div onclick="login()">开始游戏</div>

当点击了这个按钮之后,它会触发js中的login方法,但这个方法并不会直接去连接server,因为socket连接在game.html中,所以目前来看,这个页面只是game.html的子页面,这个方法在判断input中的value是否为空后,立即通过全局对象parent调用的父页面(game.html)中的login方法

// index.html中的login方法
function login() {
 if (username.value === undefined || username.value === '') {
 return
 }
 // 调用父窗口的login方法
 parent.login(username.value)
}

game.html中的login方法,这个方法通过socket向server触发了login事件

function login(username) {
 socket.emit('login', username)
}

server.js

// 监听连接
io.on('connection', function (socket) {
 // 玩家登陆, socket.emit('login', username)就是触发了这个事件
 // 监听了login事件
 socket.on('login', function (name) {
 // players是一个全局数组,里面存放了所有的玩家对象,如果players中 
 var flag = players.some(function (value) {
 return value.name === name
 })
 if (flag) {
 socket.emit('home', {'flag': true})
 } else {
 console.log(name + '已登陆')
 // 创建玩家
 new Player(socket, name)
 // 将玩家放进数组中
 // players.push(player)
 // 如果用户名没有重名,那么触发client端的home事件
 socket.emit('home', {'playerCount': playerCount, 'name': name})
 }
 })
})

玩家client对home事件的监听

// 玩家登陆成功
 socket.on('home', function (data) {
 if (data.flag) {
 game.contentWindow.flag.hidden = false
 } else {
 game.contentWindow.flag.hidden = true
 // 保存用户名和玩家在线人数到localStorage中
 localStorage.setItem('name', data.name)
 localStorage.setItem('playerCount', data.playerCount)
 // location.href = './home.html'
 game.src = 'home.html'
 }
 })

home.html玩家等待大厅, home.html和index.html长得基本一致,所以它也有一个按钮,匹配按钮,通过它来触发play事件

// 玩家开始匹配
 this.socket.on('play', function () {
 // 如果空闲玩家总数大于或等于2,那么开始游戏
 if (playerCount >= 2) {
 self.pipei = true
 // 如果已经有人在开始匹配了,那么这个玩家就不需要走下面函数了,因为继续执行的话相当于再开一个棋局
 if (isExistFZ(self) > 0) {
 // 保持不动就好,房主会自动找到你的
 return
 }
 // 如果没有房主,那么这个玩家将成为房主
 self.fz = true
 // 可用的玩家数
 var player2 = null
 self.timer = setInterval(function () {
 console.log('正在匹配...')
 if (player2 = findPlayer(self)) {
 console.log('匹配成功')
 self.gamePlay = new Game(self, player2)
 player2.gamePlay = self.gamePlay
 clearInterval(self.timer)
 }
 }, 1000)
 } else {
 socket.emit('player less')
 }
 })

server.js中有两个类,一个是Player玩家类,另一个是Game棋局类,一个棋局对应两个玩家。

Player类的属性

this.socket = socket // socket对象,玩家通过它来监听数据
 this.name = name // 玩家的名称
 this.color = null // 玩家棋子的颜色
 this.state = 0 // 0代表空闲, 1在游戏中
 this.pipei = false // 是否在匹配
 this.gamePlay = null // 棋局对象
 this.flag = true // 是否轮到这个玩家出棋
 this.fz = false // 是否是房主

Player类对象监听的事件

// 监听玩家是否退出游戏
 this.socket.on('disconnect', function () {
 // 删除数组中的玩家
 // players.splice(players.indexOf(self), 1) // 删不掉
 // delete players[players.indexOf(self)]
 // 新的删除方式
 players = players.filter(function (value) {
 return value.name !== self.name
 })
 playerCount--
 // 如果退出游戏的玩家正在进行游戏,那么这局游戏也该退出
 if (self.state === 0) {
 gameCount--
 }
 console.log(self.name + '已退出游戏')
 })
 // 玩家开始匹配
 this.socket.on('play', function () {
 // 如果空闲玩家总数大于或等于2,那么开始游戏
 if (playerCount >= 2) {
 self.pipei = true
 // 如果已经有人在开始匹配了,那么这个玩家就不需要走下面函数了,因为继续执行的话相当于再开一个棋局
 if (isExistFZ(self) > 0) {
 // 保持不动就好,房主会自动找到你的
 return
 }
 // 如果没有房主,那么这个玩家将成为房主
 self.fz = true
 // 可用的玩家数
 var player2 = null
 self.timer = setInterval(function () {
 console.log('正在匹配...')
 if (player2 = findPlayer(self)) {
 console.log('匹配成功')
 self.gamePlay = new Game(self, player2)
 player2.gamePlay = self.gamePlay
 clearInterval(self.timer)
 }
 }, 1000)
 } else {
 socket.emit('player less')
 }
 })
 // 玩家取消匹配按钮
 this.socket.on('clearPlay', function () {
 clearInterval(self.timer)
 })
 // 监听数据,玩家下棋的时候触发
 this.socket.on('data', function (data) {
 if (self.flag) {
 add_pieces(self.gamePlay, data, self.color)
 }
 })
 // 最后将当前玩家实例放到players全局玩家数组中去
 players.push(this)

Game(棋局类)

// 棋盘的格子数
 this.column = 21
 this.arr = init_arr() // 存储棋盘坐标的二位数组
 // 一局棋局上的两个玩家
 this.play1 = play1
 this.play2 = play2
 // 修改游戏状态
 this.play1.state = 1
 this.play2.state = 1
 // 在游戏中,是否匹配为false
 this.play1.pipei = false
 this.play2.pipei = false
 this.play1.fz = false
 this.play1.fz = false
 // 随机给两个玩家分配棋子颜色
 this.play1.color = ~~(Math.random() * 2) === 0 ? 'white' : 'black'
 this.play2.color = this.play1.color === 'white' ? 'black' : 'white'
 // 谁是白棋谁先走
 this.play1.flag = this.play1.color === 'white'? true: false
 this.play2.flag = this.play2.color === 'white'? true: false

添加棋子方法

// 添加棋子
function add_pieces(self, position, color) {
 if (self.arr[position.x][position.y] === undefined) {
 self.arr[position.x][position.y] = color
 if (color === self.play1.color) {
 self.play1.flag = false
 self.play2.flag = true
 } else if (color === self.play2.color) {
 self.play1.flag = true
 self.play2.flag = false
 }
 check_result(self, self.arr, position, color)
 }
}
// 初始化数组
function init_arr() {
 var arr = []
 for (var i = 0; i < 21; i++) {
 arr.push(new Array(21))
 }
 return arr
}

如果大家喜欢的话,请在github上下载我的源码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

下载本文
显示全文
专题