node.js利用socket.io实现多人在线匹配联机五子棋


Posted in Javascript onMay 31, 2018

项目地址,已上传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上下载我的源码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
零基础学JavaScript最新动画教程+iso光盘下载
Jan 22 Javascript
JavaScript 事件属性绑定带参数的函数
Mar 13 Javascript
js遍历td tr等html元素
Dec 13 Javascript
Mac/Windows下如何安装Node.js
Nov 22 Javascript
jQuery中replaceWith()方法用法实例
Dec 25 Javascript
AngularJS解决ng界面长表达式(ui-set)的方法分析
Nov 07 Javascript
Javascript数组循环遍历之forEach详解
Nov 07 Javascript
xmlplus组件设计系列之按钮(2)
Apr 26 Javascript
浅谈Node.js ORM框架Sequlize之表间关系
Jul 24 Javascript
浅谈Koa服务限流方法实践
Oct 23 Javascript
Vue数据双向绑定原理及简单实现方法
May 18 Javascript
使用vuepress搭建静态博客的示例代码
Feb 14 Javascript
JS与jQuery实现ListBox上移,下移,左移,右移操作功能示例
May 31 #jQuery
微信小程序登录换取token的教程
May 31 #Javascript
Rollup处理并打包JS文件项目实例代码
May 31 #Javascript
手把手教你vue-cli单页到多页应用的方法
May 31 #Javascript
详解基于Node.js的HTTP/2 Server实践
May 31 #Javascript
jQuery基于Ajax实现读取XML数据功能示例
May 31 #jQuery
jQuery实现动态加载select下拉列表项功能示例
May 31 #jQuery
You might like
编译php 5.2.14+fpm+memcached(具体操作详解)
2013/06/18 PHP
php写入mysql中文乱码的实例解决方法
2019/09/17 PHP
解决Laravel5.5下的toArray问题
2019/10/15 PHP
jQuery UI-Draggable 参数集合
2010/01/10 Javascript
初试jQuery EasyUI 使用介绍
2010/04/01 Javascript
关于jQuery中的end()使用方法
2011/07/10 Javascript
extjs3 combobox取value和text案例详解
2013/02/06 Javascript
使用js修改客户端注册表的方法
2013/08/09 Javascript
将input file的选择的文件清空的两种解决方案
2013/10/21 Javascript
Javascript复制实例详解
2016/01/28 Javascript
详解JavaScript异步编程中jQuery的promise对象的作用
2016/05/03 Javascript
gulp-htmlmin压缩html的gulp插件实例代码
2016/06/06 Javascript
详解js的异步编程技术的方法
2017/02/09 Javascript
MUI 上拉刷新/下拉加载功能实例代码
2017/04/13 Javascript
利用JS判断客户端类型你应该知道的四种方法
2017/12/22 Javascript
elementui的默认样式修改方法
2018/02/23 Javascript
深入浅析Vue中的 computed 和 watch
2018/06/06 Javascript
angular2组件中定时刷新并清除定时器的实例讲解
2018/08/31 Javascript
Vue.js更改调试地址端口号的实例
2018/09/19 Javascript
JS常见面试试题总结【去重、遍历、闭包、继承等】
2019/08/27 Javascript
Vue 用Vant实现时间选择器的示例代码
2019/10/25 Javascript
从表单校验看JavaScript策略模式的使用详解
2020/10/17 Javascript
Python实现的十进制小数与二进制小数相互转换功能
2017/10/12 Python
python dict 相同key 合并value的实例
2019/01/21 Python
python 扩展print打印文件路径和当前时间信息的实例代码
2019/10/11 Python
Pytorch中Tensor与各种图像格式的相互转化详解
2019/12/26 Python
Python pysnmp使用方法及代码实例
2020/08/24 Python
某公司.Net方向面试题
2014/04/24 面试题
EJB包括(SessionBean,EntityBean)说出他们的生命周期,及如何管理事务的
2015/07/24 面试题
vue 中 get / delete 传递数组参数方法
2021/03/23 Vue.js
儿科护士实习自我鉴定
2013/10/17 职场文书
建筑专业自我鉴定
2013/10/22 职场文书
教育科研先进个人材料
2014/01/26 职场文书
老兵退伍标语
2014/10/07 职场文书
2015年复活节活动总结
2015/02/27 职场文书
部门主管竞聘书
2015/09/15 职场文书