node.js利用socket.io實(shí)現(xiàn)多人在線匹配聯(lián)機(jī)五子棋
項(xiàng)目地址,已上傳github ——>
client端使用簡(jiǎn)單的h5+js實(shí)現(xiàn)了棋局的總體布局。 server端使用node的socket.io模塊與客戶(hù)端進(jìn)行數(shù)據(jù)交互,棋子的落點(diǎn)和輸贏校驗(yàn)均是在server端完成。
client端的界面這里就不做過(guò)多解釋了,只要稍微懂點(diǎn)h5就可以自行去這里 下載源代碼觀看,因?yàn)榻裉斓闹黝}主要是socket.io這一塊,所以本章只概述client和server是如何通過(guò)tcp連接進(jìn)行交互的。
首先先帶大家看一下目錄結(jié)構(gòu)
| server.js (socket服務(wù)器) | gobang-ui.html (是玩家下棋頁(yè)面) | index.html (是用戶(hù)登陸界面) | home.html (是用戶(hù)大廳界面, 用來(lái)匹配等待的 如果在線人數(shù)少于2人, 則匹配失敗, 并會(huì)返回錯(cuò)誤信息) | game.html (client端程序的入口,內(nèi)嵌iframe來(lái)顯示各個(gè)頁(yè)面,通過(guò)改變iframe的src屬性,來(lái)達(dá)成偽頁(yè)面跳轉(zhuǎn)) | img (圖片資源文件夾) | tou.jpg (棋盤(pán)界面用戶(hù)的頭像,因?yàn)榈卿浗缑嬷灰斎胗脩?hù)名就可以開(kāi)始游戲了,所以所有用戶(hù)的頭像都是一樣的)
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庫(kù) --> <script src="https://cdn.bootcss.com/socket.io/2.1.0/socket.io.js"></script> </head> <body> <!-- 這里是程序的入口,通過(guò)js改變src屬性,來(lái)切換頁(yè)面 --> <iframe id="game" src="index.html" width="100%" height="100%" scrolling="no"></iframe> </body> </html>
為什么我們要用嵌入iframe改變src屬性的方式來(lái)制造頁(yè)面跳轉(zhuǎn)的現(xiàn)象?因?yàn)轫?yè)面的每一次跳轉(zhuǎn)或刷新都會(huì)使socket連接斷開(kāi)。就好像http中的request請(qǐng)求一樣,頁(yè)面每次所以我們應(yīng)盡量避免頁(yè)面跳轉(zhuǎn)這個(gè)操作。
// 這行代碼表示client端對(duì)server端進(jìn)行第一次連接 var socket = io('ws://localhost:3000')
在index.html也就是用戶(hù)的登錄界面
<!-- 這是用戶(hù)登錄的按鈕 --> <div onclick="login()">開(kāi)始游戲</div>
當(dāng)點(diǎn)擊了這個(gè)按鈕之后,它會(huì)觸發(fā)js中的login方法,但這個(gè)方法并不會(huì)直接去連接server,因?yàn)閟ocket連接在game.html中,所以目前來(lái)看,這個(gè)頁(yè)面只是game.html的子頁(yè)面,這個(gè)方法在判斷input中的value是否為空后,立即通過(guò)全局對(duì)象parent調(diào)用的父頁(yè)面(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方法,這個(gè)方法通過(guò)socket向server觸發(fā)了login事件
function login(username) { socket.emit('login', username) }
server.js
// 監(jiān)聽(tīng)連接 io.on('connection', function (socket) { // 玩家登陸, socket.emit('login', username)就是觸發(fā)了這個(gè)事件 // 監(jiān)聽(tīng)了login事件 socket.on('login', function (name) { // players是一個(gè)全局?jǐn)?shù)組,里面存放了所有的玩家對(duì)象,如果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) // 將玩家放進(jìn)數(shù)組中 // players.push(player) // 如果用戶(hù)名沒(méi)有重名,那么觸發(fā)client端的home事件 socket.emit('home', {'playerCount': playerCount, 'name': name}) } }) })
玩家client對(duì)home事件的監(jiān)聽(tīng)
// 玩家登陸成功 socket.on('home', function (data) { if (data.flag) { game.contentWindow.flag.hidden = false } else { game.contentWindow.flag.hidden = true // 保存用戶(hù)名和玩家在線人數(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長(zhǎng)得基本一致,所以它也有一個(gè)按鈕,匹配按鈕,通過(guò)它來(lái)觸發(fā)play事件
// 玩家開(kāi)始匹配 this.socket.on('play', function () { // 如果空閑玩家總數(shù)大于或等于2,那么開(kāi)始游戲 if (playerCount >= 2) { self.pipei = true // 如果已經(jīng)有人在開(kāi)始匹配了,那么這個(gè)玩家就不需要走下面函數(shù)了,因?yàn)槔^續(xù)執(zhí)行的話(huà)相當(dāng)于再開(kāi)一個(gè)棋局 if (isExistFZ(self) > 0) { // 保持不動(dòng)就好,房主會(huì)自動(dòng)找到你的 return } // 如果沒(méi)有房主,那么這個(gè)玩家將成為房主 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中有兩個(gè)類(lèi),一個(gè)是Player玩家類(lèi),另一個(gè)是Game棋局類(lèi),一個(gè)棋局對(duì)應(yīng)兩個(gè)玩家。
Player類(lèi)的屬性
this.socket = socket // socket對(duì)象,玩家通過(guò)它來(lái)監(jiān)聽(tīng)數(shù)據(jù) this.name = name // 玩家的名稱(chēng) this.color = null // 玩家棋子的顏色 this.state = 0 // 0代表空閑, 1在游戲中 this.pipei = false // 是否在匹配 this.gamePlay = null // 棋局對(duì)象 this.flag = true // 是否輪到這個(gè)玩家出棋 this.fz = false // 是否是房主
Player類(lèi)對(duì)象監(jiān)聽(tīng)的事件
// 監(jiān)聽(tīng)玩家是否退出游戲 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-- // 如果退出游戲的玩家正在進(jìn)行游戲,那么這局游戲也該退出 if (self.state === 0) { gameCount-- } console.log(self.name + '已退出游戲') }) // 玩家開(kāi)始匹配 this.socket.on('play', function () { // 如果空閑玩家總數(shù)大于或等于2,那么開(kāi)始游戲 if (playerCount >= 2) { self.pipei = true // 如果已經(jīng)有人在開(kāi)始匹配了,那么這個(gè)玩家就不需要走下面函數(shù)了,因?yàn)槔^續(xù)執(zhí)行的話(huà)相當(dāng)于再開(kāi)一個(gè)棋局 if (isExistFZ(self) > 0) { // 保持不動(dòng)就好,房主會(huì)自動(dòng)找到你的 return } // 如果沒(méi)有房主,那么這個(gè)玩家將成為房主 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)聽(tīng)數(shù)據(jù),玩家下棋的時(shí)候觸發(fā) this.socket.on('data', function (data) { if (self.flag) { add_pieces(self.gamePlay, data, self.color) } }) // 最后將當(dāng)前玩家實(shí)例放到players全局玩家數(shù)組中去 players.push(this)
Game(棋局類(lèi))
// 棋盤(pán)的格子數(shù) this.column = 21 this.arr = init_arr() // 存儲(chǔ)棋盤(pán)坐標(biāo)的二位數(shù)組 // 一局棋局上的兩個(gè)玩家 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 // 隨機(jī)給兩個(gè)玩家分配棋子顏色 this.play1.color = ~~(Math.random() * 2) === 0 ? 'white' : 'black' this.play2.color = this.play1.color === 'white' ? 'black' : 'white' // 誰(shuí)是白棋誰(shuí)先走 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 }
如果大家喜歡的話(huà),請(qǐng)?jiān)趃ithub上下載我的源碼,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- js實(shí)現(xiàn)網(wǎng)頁(yè)五子棋進(jìn)階版
- js+html實(shí)現(xiàn)網(wǎng)頁(yè)五子棋
- JavaScript+canvas實(shí)現(xiàn)五子棋游戲
- js數(shù)組案例之五子棋游戲
- 純JS實(shí)現(xiàn)五子棋游戲兼容各瀏覽器(附源碼)
- 基于JavaScript實(shí)現(xiàn)五子棋游戲
- Javascript和HTML5利用canvas構(gòu)建Web五子棋游戲?qū)崿F(xiàn)算法
- JS canvas繪制五子棋的棋盤(pán)
- H5+C3+JS實(shí)現(xiàn)五子棋游戲(AI篇)
- Node.js+Socket.io實(shí)現(xiàn)雙人在線五子棋對(duì)戰(zhàn)
相關(guān)文章
Node.JS 循環(huán)遞歸復(fù)制文件夾目錄及其子文件夾下的所有文件
在Node.js中,要實(shí)現(xiàn)目錄文件夾的循環(huán)遞歸復(fù)制也非常簡(jiǎn)單,使用fs模塊即可,僅需幾行,而且性能也不錯(cuò),我們先來(lái)實(shí)現(xiàn)文件的復(fù)制,需要的朋友可以參考下2017-09-09node 標(biāo)準(zhǔn)輸入流和輸出流代碼實(shí)例
這篇文章主要介紹了node 標(biāo)準(zhǔn)輸入流和輸出流代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09垃圾回收器的相關(guān)知識(shí)點(diǎn)總結(jié)
本文是小編在網(wǎng)絡(luò)上整理的關(guān)于垃圾回收器的相關(guān)知識(shí)點(diǎn),很多語(yǔ)言和程序都用的到,有興趣的可以學(xué)習(xí)下。2018-05-05nodejs的http和https下載遠(yuǎn)程資源post數(shù)據(jù)實(shí)例
這篇文章主要為大家介紹了nodejs的http和https下載遠(yuǎn)程資源post數(shù)據(jù)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09node.js突破nginx防盜鏈機(jī)制,下載圖片案例分析
這篇文章主要介紹了node.js突破nginx防盜鏈機(jī)制,下載圖片的方法,結(jié)合具體案例形式分析了防盜鏈的相關(guān)原理與node.js使用axios庫(kù)下載防盜鏈圖片的相關(guān)操作技巧,需要的朋友可以參考下2023-04-04