node.js利用socket.io實現(xiàn)多人在線匹配聯(lián)機五子棋
項目地址,已上傳github ——>
client端使用簡單的h5+js實現(xiàn)了棋局的總體布局。 server端使用node的socket.io模塊與客戶端進行數(shù)據(jù)交互,棋子的落點和輸贏校驗均是在server端完成。
client端的界面這里就不做過多解釋了,只要稍微懂點h5就可以自行去這里 下載源代碼觀看,因為今天的主題主要是socket.io這一塊,所以本章只概述client和server是如何通過tcp連接進行交互的。
首先先帶大家看一下目錄結(jié)構(gòu)
| server.js (socket服務(wù)器) | gobang-ui.html (是玩家下棋頁面) | index.html (是用戶登陸界面) | home.html (是用戶大廳界面, 用來匹配等待的 如果在線人數(shù)少于2人, 則匹配失敗, 并會返回錯誤信息) | game.html (client端程序的入口,內(nèi)嵌iframe來顯示各個頁面,通過改變iframe的src屬性,來達成偽頁面跳轉(zhuǎn)) | 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屬性的方式來制造頁面跳轉(zhuǎn)的現(xiàn)象?因為頁面的每一次跳轉(zhuǎn)或刷新都會使socket連接斷開。就好像http中的request請求一樣,頁面每次所以我們應(yīng)盡量避免頁面跳轉(zhuǎn)這個操作。
// 這行代碼表示client端對server端進行第一次連接
var socket = io('ws://localhost:3000')
在index.html也就是用戶的登錄界面
<!-- 這是用戶登錄的按鈕 --> <div onclick="login()">開始游戲</div>
當(dāng)點擊了這個按鈕之后,它會觸發(fā)js中的login方法,但這個方法并不會直接去連接server,因為socket連接在game.html中,所以目前來看,這個頁面只是game.html的子頁面,這個方法在判斷input中的value是否為空后,立即通過全局對象parent調(diào)用的父頁面(game.html)中的login方法
// index.html中的login方法
function login() {
if (username.value === undefined || username.value === '') {
return
}
// 調(diào)用父窗口的login方法
parent.login(username.value)
}
game.html中的login方法,這個方法通過socket向server觸發(fā)了login事件
function login(username) {
socket.emit('login', username)
}
server.js
// 監(jiān)聽連接
io.on('connection', function (socket) {
// 玩家登陸, socket.emit('login', username)就是觸發(fā)了這個事件
// 監(jiān)聽了login事件
socket.on('login', function (name) {
// players是一個全局數(shù)組,里面存放了所有的玩家對象,如果players中
var flag = players.some(function (value) {
return value.name === name
})
if (flag) {
socket.emit('home', {'flag': true})
} else {
console.log(name + '已登陸')
// 創(chuàng)建玩家
new Player(socket, name)
// 將玩家放進數(shù)組中
// players.push(player)
// 如果用戶名沒有重名,那么觸發(fā)client端的home事件
socket.emit('home', {'playerCount': playerCount, 'name': name})
}
})
})
玩家client對home事件的監(jiān)聽
// 玩家登陸成功
socket.on('home', function (data) {
if (data.flag) {
game.contentWindow.flag.hidden = false
} else {
game.contentWindow.flag.hidden = true
// 保存用戶名和玩家在線人數(shù)到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長得基本一致,所以它也有一個按鈕,匹配按鈕,通過它來觸發(fā)play事件
// 玩家開始匹配
this.socket.on('play', function () {
// 如果空閑玩家總數(shù)大于或等于2,那么開始游戲
if (playerCount >= 2) {
self.pipei = true
// 如果已經(jīng)有人在開始匹配了,那么這個玩家就不需要走下面函數(shù)了,因為繼續(xù)執(zhí)行的話相當(dāng)于再開一個棋局
if (isExistFZ(self) > 0) {
// 保持不動就好,房主會自動找到你的
return
}
// 如果沒有房主,那么這個玩家將成為房主
self.fz = true
// 可用的玩家數(shù)
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棋局類,一個棋局對應(yīng)兩個玩家。
Player類的屬性
this.socket = socket // socket對象,玩家通過它來監(jiān)聽數(shù)據(jù) this.name = name // 玩家的名稱 this.color = null // 玩家棋子的顏色 this.state = 0 // 0代表空閑, 1在游戲中 this.pipei = false // 是否在匹配 this.gamePlay = null // 棋局對象 this.flag = true // 是否輪到這個玩家出棋 this.fz = false // 是否是房主
Player類對象監(jiān)聽的事件
// 監(jiān)聽玩家是否退出游戲
this.socket.on('disconnect', function () {
// 刪除數(shù)組中的玩家
// 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 () {
// 如果空閑玩家總數(shù)大于或等于2,那么開始游戲
if (playerCount >= 2) {
self.pipei = true
// 如果已經(jīng)有人在開始匹配了,那么這個玩家就不需要走下面函數(shù)了,因為繼續(xù)執(zhí)行的話相當(dāng)于再開一個棋局
if (isExistFZ(self) > 0) {
// 保持不動就好,房主會自動找到你的
return
}
// 如果沒有房主,那么這個玩家將成為房主
self.fz = true
// 可用的玩家數(shù)
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)
})
// 監(jiān)聽數(shù)據(jù),玩家下棋的時候觸發(fā)
this.socket.on('data', function (data) {
if (self.flag) {
add_pieces(self.gamePlay, data, self.color)
}
})
// 最后將當(dāng)前玩家實例放到players全局玩家數(shù)組中去
players.push(this)
Game(棋局類)
// 棋盤的格子數(shù) this.column = 21 this.arr = init_arr() // 存儲棋盤坐標(biāo)的二位數(shù)組 // 一局棋局上的兩個玩家 this.play1 = play1 this.play2 = play2 // 修改游戲狀態(tài) 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)
}
}
// 初始化數(shù)組
function init_arr() {
var arr = []
for (var i = 0; i < 21; i++) {
arr.push(new Array(21))
}
return arr
}
如果大家喜歡的話,請在github上下載我的源碼,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
Node.js利用js-xlsx處理Excel文件的方法詳解
這篇文章主要給大家介紹了關(guān)于Node.js利用js-xlsx處理Excel文件的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。2017-07-07
詳解nodejs中exports和module.exports的區(qū)別
本文主要介紹了exports 和 module.exports 的區(qū)別。具有很好的參考價值,下面跟著小編一起來看下吧2017-02-02
在Node.js中使用Express實現(xiàn)視頻評論的列表展示和刪除功能
在現(xiàn)代Web應(yīng)用中,視頻內(nèi)容和互動功能(如評論)的結(jié)合極大地增加了用戶的參與度,本文將通過一個具體的例子,展示如何在Node.js環(huán)境中使用Express框架來實現(xiàn)視頻評論的列表展示和刪除功能,需要的朋友可以參考下2024-04-04
Node.js中的require.resolve方法使用簡介
在Node.js中,可以使用require.resolve函數(shù)來查詢某個模塊文件的帶有完整絕對路徑的文件名,下面這篇文章主要介紹了Node.js中require.resolve方法使用的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-04-04
Node.JS使用Sequelize操作MySQL的示例代碼
Node.JS提供了操作數(shù)據(jù)庫的基礎(chǔ)接口,本篇文章主要介紹了Node.JS使用Sequelize操作MySQL的示例代碼,具有一定的參考價值,有興趣的可以了解一下2017-10-10
Node.js利用Express實現(xiàn)用戶注冊登陸功能(推薦)
這篇文章主要介紹了Node.js利用Express實現(xiàn)用戶注冊登陸功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10
使用Fetch API執(zhí)行GraphQL查詢和變體問題
這篇文章主要介紹了使用Fetch API執(zhí)行GraphQL查詢和變體問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04

