Node.js/Socket.io黑杰克游戏:实现多客户端共享同一牌组
黑杰克游戏多客户端牌组不一致问题解决
我正在用Socket.io、Node.js和React开发一款简易黑杰克游戏,当前遇到的问题是:当房间内凑齐2名玩家与1名庄家后,游戏开始阶段每个客户端获取的牌组各不相同。需要实现仅构建一次牌组,并将同一牌组发送给所有客户端。
原服务端代码:
const express = require('express'); const app = express(); const http = require('http'); const cors = require('cors'); const { Server } = require('socket.io'); const buildDeck = require('./helpers/buildDeck'); require('dotenv').config(); const server = http.createServer(app); const PORT = process.env.PORT || 3001; let deck; let size; let users = ['dealer']; let playCards = []; app.use(cors()); const io = new Server(server, { cors: { origin: 'http://localhost:3000', }, }); io.on('connection', (socket) => { console.log(`${socket.id} has been connected`); socket.on('joinRoom', (data) => { size = io.sockets.adapter.rooms.get(data.room)?.size; if (size > 1) { io.to(socket.id).emit('reject'); socket.disconnect(); } else { socket.join(data.room); users.push(data.user); console.log(users); } }); socket.on('checkPlayers', (data) => { size = io.sockets.adapter.rooms.get(data.room)?.size; if (size === 2) { io.to(socket.id).emit('joinedRoom', size); deck = buildDeck(); } socket.to(data.room).emit('joinedRoom', size); }); socket.on('initialize', (room) => { for (let i = 0; i < users.length; i++) { playCards[i] = { user: users[i], cards: [deck.pop(), deck.pop()], }; } console.log(playCards); socket.to(room).emit('initialize', { deck, playCards }); }); }); server.listen(PORT, () => { console.log('Server is running on port ' + PORT); });
问题根源
- 全局变量跨房间干扰:
deck、users、playCards是全局变量,多个房间同时运行时数据会互相覆盖,导致不同房间客户端拿到错误牌组。 - 初始化消息发送不完整:
initialize事件仅给当前socket之外的房间成员发数据,发起初始化的客户端无法收到正确牌组信息。 - 用户列表未按房间隔离:全局
users数组混合所有房间玩家,导致发牌逻辑混乱。
修改方案
- 按房间存储游戏状态:创建
roomGames对象,每个房间ID对应独立的deck、users、playCards,避免跨房间污染。 - 统一发送初始化消息:使用
io.to(room).emit给房间内所有客户端发送初始化数据,包括发起请求的socket。 - 隔离房间用户数据:每个房间维护自己的用户列表,不再依赖全局变量。
修改后的完整代码
const express = require('express'); const app = express(); const http = require('http'); const cors = require('cors'); const { Server } = require('socket.io'); const buildDeck = require('./helpers/buildDeck'); require('dotenv').config(); const server = http.createServer(app); const PORT = process.env.PORT || 3001; // 按房间存储游戏状态,key为房间ID,value为该房间的游戏数据 const roomGames = {}; app.use(cors()); const io = new Server(server, { cors: { origin: 'http://localhost:3000', }, }); io.on('connection', (socket) => { console.log(`${socket.id} has been connected`); socket.on('joinRoom', (data) => { const room = data.room; const roomSize = io.sockets.adapter.rooms.get(room)?.size || 0; if (roomSize > 1) { io.to(socket.id).emit('reject'); socket.disconnect(); return; } socket.join(room); // 初始化房间游戏状态(如果不存在) if (!roomGames[room]) { roomGames[room] = { users: ['dealer'], // 每个房间独立维护用户列表 deck: null, playCards: [] }; } roomGames[room].users.push(data.user); console.log(`房间${room}用户列表:`, roomGames[room].users); }); socket.on('checkPlayers', (data) => { const room = data.room; const roomSize = io.sockets.adapter.rooms.get(room)?.size || 0; if (roomSize === 2) { // 仅在房间人数满足时构建一次牌组 roomGames[room].deck = buildDeck(); // 给当前socket发送消息 io.to(socket.id).emit('joinedRoom', roomSize); } // 给房间内其他成员发送消息 socket.to(room).emit('joinedRoom', roomSize); }); socket.on('initialize', (room) => { const gameData = roomGames[room]; if (!gameData?.deck) return; // 确保牌组已构建 // 重置当前房间的发牌数据 gameData.playCards = []; for (let i = 0; i < gameData.users.length; i++) { gameData.playCards.push({ user: gameData.users[i], cards: [gameData.deck.pop(), gameData.deck.pop()], }); } console.log(`房间${room}发牌数据:`, gameData.playCards); // 给房间内所有客户端发送初始化数据,包括当前socket io.to(room).emit('initialize', { deck: gameData.deck, playCards: gameData.playCards }); }); // 客户端断开连接时清理房间数据(可选,根据需求调整) socket.on('disconnecting', () => { const rooms = Array.from(socket.rooms); rooms.forEach(room => { if (room !== socket.id && roomGames[room]) { // 移除断开用户,或直接销毁房间状态 roomGames[room].users = roomGames[room].users.filter(user => user !== socket.handshake.query.user // 假设join时把user存在handshake里,或根据实际存储方式调整 ); // 如果房间无玩家,删除房间数据 if (roomGames[room].users.length <= 1) { // 只剩庄家 delete roomGames[room]; } } }); }); }); server.listen(PORT, () => { console.log('Server is running on port ' + PORT); });
关键修改点说明
- 房间状态隔离:
roomGames对象为每个房间保存独立游戏数据,彻底解决全局变量的跨房间干扰问题。 - 全房间消息推送:
initialize事件使用io.to(room).emit,确保房间内所有客户端收到同一套牌组和发牌数据。 - 断开连接清理:添加
disconnecting事件处理,及时清理空房间的游戏数据,避免内存浪费。
内容的提问来源于stack exchange,提问作者Bogoe David
相关产品推荐
相关产品推荐

