node.js利用socket.io實現多人線上匹配聯機五子棋

會Coding的猴子發表於2018-05-31

專案地址,已上傳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上下載我的原始碼,謝謝大家支援!

相關文章