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

項目地址,已上傳github ——>

項目地址 -> 碼雲

項目演示地址 -> 歡迎你們來這裏一塊兒玩遊戲哦~~

client端使用簡單的h5+js實現了棋局的整體佈局。 server端使用node的socket.io模塊與客戶端進行數據交互,棋子的落點和輸贏校驗均是在server端完成。
五子棋ui界面請見..css

client端的界面這裏就不作過多解釋了,只要稍微懂點h5就能夠自行去 這裏下載源代碼觀看,由於今天的主題主要是socket.io這一塊,因此本章只概述client和server是如何經過tcp鏈接進行交互的。html

首先先帶你們看一下目錄結構node

| server.js    (socket服務器)  
| gobang-ui.html   (是玩家下棋頁面)  
| index.html  (是用戶登錄界面)
| home.html   (是用戶大廳界面, 用來匹配等待的 若是在線人數少於2人, 則匹配失敗, 並會返回錯誤信息)
| game.html   (client端程序的入口,內嵌iframe來顯示各個頁面,經過改變iframe的src屬性,來達成僞頁面跳轉)
| img   (圖片資源文件夾)
    | tou.jpg   (棋盤界面用戶的頭像,由於登陸界面只要輸入用戶名就能夠開始遊戲了,因此全部用戶的頭像都是同樣的)
複製代碼

game.html主界面git

<!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請求同樣,頁面每次因此咱們應儘可能避免頁面跳轉這個操做。github

// 這行代碼表示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事件less

function login(username) {
    socket.emit('login', username)
}
複製代碼

server.jsdom

// 監聽鏈接
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

// 玩家登錄成功
  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上下載個人源碼,謝謝你們支持!

相關文章
相關標籤/搜索