node.js использует socket.io для реализации многопользовательского онлайн-матча онлайн-гобанга

Node.js внешний интерфейс сервер JavaScript

Адрес проекта, загруженный на github ——>

Адрес проекта -> Облако кода

Демонстрационный адрес проекта ->Приглашаем всех прийти и поиграть в игру вместе~~

Клиентская сторона использует простой h5+js для реализации общего макета шахматной игры. Серверная сторона использует модуль socket.io узла для обмена данными с клиентской стороной.Расстановка фигур и проверка выигрыша и проигрыша выполняются на стороне сервера.
Пожалуйста, ознакомьтесь с интерфейсом пользовательского интерфейса игры в нарды.

Интерфейс на стороне клиента не будет здесь слишком подробно объясняться, пока вы немного понимаете h5, вы можете пройти самостоятельно.здесьЗагрузите исходный код, чтобы посмотреть, поскольку сегодняшняя тема в основном посвящена socket.io, поэтому в этой главе описывается только то, как клиент и сервер взаимодействуют через 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, чтобы создать явление перехода на страницу? Потому что каждый переход или обновление страницы отключает соединение сокета. Так же, как запрос запроса в http, страница каждый раз, поэтому мы должны попытаться избежать операции перехода страницы.

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

В index.html, который является интерфейсом входа пользователя.

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

При нажатии этой кнопки запускается метод входа в js, но этот метод не будет напрямую подключаться к серверу, поскольку сокет подключен в game.html, поэтому в настоящее время эта страница является просто подстраницей game.html. , этот метод немедленно передает метод входа в родительскую страницу (game.html), вызываемую родительским глобальным объектом, после определения того, является ли значение на входе пустым.

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

Метод входа в game.html, этот метод запускает событие входа на сервер через сокет.

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})
    }
  })
})

Клиент плеера слушает домашнее событие

  // 玩家登陆成功
  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 в основном одинаковы, поэтому он также имеет кнопку, соответствует кнопке и запускает через нее событие воспроизведения.

  // 玩家开始匹配
  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)

Игра (шахматная игра)

    // 棋盘的格子数
    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, спасибо за вашу поддержку!