如何在Socket.io中实现回合制逻辑?含角色分配等问题
井字棋Socket.io多房间回合制解决方案
核心思路:给每个房间绑定独立游戏状态
别用全局的players数组,改用房间状态映射对象,把每个房间的游戏数据(玩家、回合、棋盘)单独存储,key用房间ID,保证多房间之间完全隔离,不会互相干扰。
1. 角色分配与房间人数管控
玩家申请加入房间时,先检查当前房间人数:
- 房间没人:分配角色
X,标记为当前回合玩家 - 房间有1人:分配角色
O,同时触发游戏开始事件,通知房间内两人 - 房间≥2人:直接返回"房间已满"提示,拒绝加入
服务端核心代码示例:
// 全局存储所有房间的独立状态 const roomStates = {}; // 玩家加入房间事件 socket.on('joinRoom', (roomId) => { // 初始化房间状态(首次进入时创建) if (!roomStates[roomId]) { roomStates[roomId] = { players: [], // 存储格式:{ socketId: string, role: 'X'|'O' } currentTurn: 'X', board: Array(9).fill(null), gameStarted: false }; } const roomState = roomStates[roomId]; if (roomState.players.length >= 2) { socket.emit('roomFull'); return; } // 分配角色 const role = roomState.players.length === 0 ? 'X' : 'O'; roomState.players.push({ socketId: socket.id, role }); // 加入Socket.io房间 socket.join(roomId); socket.emit('roleAssigned', role); // 两人就位后启动游戏 if (roomState.players.length === 2) { roomState.gameStarted = true; io.to(roomId).emit('gameStart', { players: roomState.players, currentTurn: roomState.currentTurn }); } });
2. 回合制逻辑控制
所有落子请求必须经过服务端校验,只有当前回合的玩家操作才会被处理:
- 玩家发送落子请求时,先通过socketID找到其在房间内的角色
- 对比角色是否与
currentTurn一致,不一致则返回"非你的回合"提示 - 校验通过后,更新棋盘状态,切换回合(X→O,O→X),再把新状态广播给房间内所有人
关键代码示例:
socket.on('makeMove', ({ roomId, position }) => { const roomState = roomStates[roomId]; if (!roomState || !roomState.gameStarted) { socket.emit('invalidMove', '游戏未开始'); return; } // 定位当前玩家角色 const player = roomState.players.find(p => p.socketId === socket.id); if (!player || player.role !== roomState.currentTurn) { socket.emit('invalidMove', '不是你的回合'); return; } // 检查落子位置是否为空 if (roomState.board[position] !== null) { socket.emit('invalidMove', '该位置已被占用'); return; } // 更新棋盘 roomState.board[position] = player.role; // 胜负判断(此处省略具体逻辑,可自行实现) const winner = checkWinner(roomState.board); if (winner) { io.to(roomId).emit('gameEnd', { winner, board: roomState.board }); // 可选:结束后清理房间状态 delete roomStates[roomId]; return; } // 切换回合 roomState.currentTurn = roomState.currentTurn === 'X' ? 'O' : 'X'; // 广播最新游戏状态 io.to(roomId).emit('moveMade', { board: roomState.board, currentTurn: roomState.currentTurn }); });
3. 闲置房间清理
避免内存泄漏,当玩家离开时检查房间剩余人数:
- 剩余人数为0:直接删除该房间的状态对象
- 剩余人数为1:通知剩余玩家游戏结束,再删除房间状态
socket.on('disconnect', () => { // 遍历所有房间,找到当前玩家所在房间 Object.keys(roomStates).forEach(roomId => { const roomState = roomStates[roomId]; const playerIndex = roomState.players.findIndex(p => p.socketId === socket.id); if (playerIndex !== -1) { roomState.players.splice(playerIndex, 1); if (roomState.players.length === 0) { delete roomStates[roomId]; } else { io.to(roomId).emit('playerLeft', '对手已离开,游戏结束'); delete roomStates[roomId]; } } }); });
关键优势
- 完全支持多房间同时运行,每个房间状态独立无冲突
- 严格的回合校验,杜绝玩家乱操作
- 角色分配逻辑清晰,不会出现重复或冲突
- 自动清理闲置房间,优化内存占用
内容的提问来源于stack exchange,提问作者Shashwat Gutpa
相关产品推荐
相关产品推荐

