React+Socket.io实时石头剪刀布游戏重置后选择匹配异常问题
React + Socket.io 实时石头剪刀布游戏:重置后选择匹配错误问题解决
问题描述
我开发了一款基于React和Socket.io的实时石头剪刀布游戏,当前遇到的问题是:当任意玩家重置游戏后再次做出选择时,系统会将其本次选择与对方上一局的选择进行比对,而非当前局的选择。
当前尝试的代码片段
socket.on("choice", (data) => { console.log(`Player ${data.playerNumber} chose ${data.choice}`); // Find the other player const otherPlayer = players.find((player) => player !== socket.id); // Get the other player's choice socket.to(otherPlayer).emit("opposing-player-move", data.choice); socket.on("secondPlayerMove", (move) => { // Declare otherPlayerChoice here let otherPlayerChoice = move.choice; // Determine the winner let result; if (data.choice === otherPlayerChoice) { result = "It's a tie!"; } else if ( (data.choice === 0 && otherPlayerChoice === 2) || (data.choice === 1 && otherPlayerChoice === 0) || (data.choice === 2 && otherPlayerChoice === 1) ) { result = `Player ${data.playerNumber === 0 ? 1 : 0} wins!`; } else { result = `Player ${data.playerNumber} wins!`; } // Emit the result to both players io.to(socket.id).emit("game-result", result); io.to(otherPlayer).emit("game-result", result); io.emit("disable-buttons"); // Reset otherPlayerChoice after the game ends otherPlayerChoice = null; data.choice = null; }); });
此前的完整尝试代码
const express = require("express"); const app = express(); const http = require("http"); const { Server } = require("socket.io"); const cors = require("cors"); app.use(cors()); const server = http.createServer(app); let players = []; let otherPlayerChoice = null; // initialize otherPlayerChoice to null let playerChoice = null; // initialize playerChoice to null const io = new Server(server, { cors: { origin: "http://localhost:3000", methods: ["GET", "POST"], }, }); io.on("connection", (socket) => { console.log(`User Connected: ${socket.id}`); players.push(socket.id); const playerNumber = players.indexOf(socket.id); socket.emit("playerNumber", playerNumber); socket.on("disconnect", () => { console.log(`User Disconnected: ${socket.id}`); const index = players.indexOf(socket.id); players.splice(index, 1); }); socket.on("choice", (data) => { console.log(`Player ${data.playerNumber} chose ${data.choice}`); // Find the other player const otherPlayer = players.find((player) => player !== socket.id); // Get the other player's choice socket.to(otherPlayer).emit("opposing-player-move", data.choice); socket.on("test", (move) => { otherPlayerChoice = move.choice; // update otherPlayerChoice playerChoice = data.choice; // update playerChoice // Determine the winner let result; if (playerChoice === otherPlayerChoice) { result = "It's a tie!"; } else if ( (playerChoice === 0 && otherPlayerChoice === 2) || (playerChoice === 1 && otherPlayerChoice === 0) || (playerChoice === 2 && otherPlayerChoice === 1) ) { result = `Player ${data.playerNumber === 0 ? 1 : 0} wins!`; } else { result = `Player ${data.playerNumber} wins!`; } // Emit the result to both players io.to(socket.id).emit("game-result", result); io.to(otherPlayer).emit("game-result", result); io.emit("disable-buttons"); // Reset the choices to null after the game ends otherPlayerChoice = null; playerChoice = null; }); }); }); server.listen(3001, () => { console.log("SERVER IS RUNNING"); });
问题根源分析
- 全局状态污染:全局变量
otherPlayerChoice和playerChoice会被所有游戏局共享,重置时的清空操作无法保证彻底隔离不同局的状态 - 重复事件绑定:在
choice事件内部绑定secondPlayerMove/test事件,每次玩家选择都会新增一个监听,旧监听会保留上一局的data.choice引用,触发时会用上一局的选择参与判定 - 无局级状态隔离:没有为每一组玩家的每一局游戏创建独立状态容器,导致不同局的选择数据互相干扰
解决方案:局级状态隔离+事件逻辑重构
以下是修正后的完整服务端代码,核心改动是为每局游戏创建独立状态,避免全局变量和重复绑定问题:
const express = require("express"); const app = express(); const http = require("http"); const { Server } = require("socket.io"); const cors = require("cors"); app.use(cors()); const server = http.createServer(app); const io = new Server(server, { cors: { origin: "http://localhost:3000", methods: ["GET", "POST"], }, }); // 存储所有游戏房间的状态:key为房间ID,value为房间内的选择数据 const games = {}; // 等待配对的玩家队列 const waitingPlayers = []; io.on("connection", (socket) => { console.log(`User Connected: ${socket.id}`); // 玩家加入等待队列,配对成2人房间 if (waitingPlayers.length > 0) { const opponentId = waitingPlayers.pop(); const roomId = `${socket.id}-${opponentId}`; // 为双方创建房间并分配玩家编号 socket.join(roomId); io.to(opponentId).join(roomId); socket.emit("playerNumber", 0); io.to(opponentId).emit("playerNumber", 1); // 初始化房间游戏状态 games[roomId] = { choices: [null, null], // 索引0对应玩家0,索引1对应玩家1 roomId }; } else { waitingPlayers.push(socket.id); socket.emit("waiting-for-opponent"); } // 监听玩家选择事件 socket.on("choice", (data) => { // 找到当前玩家所在的房间 const roomId = Object.keys(games).find(room => io.sockets.adapter.rooms.get(room)?.has(socket.id)); if (!roomId) return; const game = games[roomId]; // 更新当前玩家的选择 game.choices[data.playerNumber] = data.choice; // 通知对手当前玩家的选择 const opponentNumber = data.playerNumber === 0 ? 1 : 0; const opponentId = Array.from(io.sockets.adapter.rooms.get(roomId)).find(id => id !== socket.id); io.to(opponentId).emit("opposing-player-move", data.choice); // 检查双方是否都已做出选择 if (game.choices[0] !== null && game.choices[1] !== null) { // 判定胜负 let result; const p1Choice = game.choices[0]; const p2Choice = game.choices[1]; if (p1Choice === p2Choice) { result = "It's a tie!"; } else if ( (p1Choice === 0 && p2Choice === 2) || (p1Choice === 1 && p2Choice === 0) || (p1Choice === 2 && p2Choice === 1) ) { result = "Player 0 wins!"; } else { result = "Player 1 wins!"; } // 通知双方结果 io.to(roomId).emit("game-result", result); io.to(roomId).emit("disable-buttons"); } }); // 监听游戏重置事件 socket.on("reset-game", () => { const roomId = Object.keys(games).find(room => io.sockets.adapter.rooms.get(room)?.has(socket.id)); if (!roomId) return; // 清空当前房间的选择状态 games[roomId].choices = [null, null]; // 通知双方重置游戏界面 io.to(roomId).emit("reset-interface"); }); socket.on("disconnect", () => { console.log(`User Disconnected: ${socket.id}`); // 从等待队列移除 const waitIndex = waitingPlayers.indexOf(socket.id); if (waitIndex !== -1) { waitingPlayers.splice(waitIndex, 1); return; } // 清理对应房间的状态 const roomId = Object.keys(games).find(room => io.sockets.adapter.rooms.get(room)?.has(socket.id)); if (roomId) { delete games[roomId]; // 通知对手玩家断开连接 const opponentId = Array.from(io.sockets.adapter.rooms.get(roomId)).find(id => id !== socket.id); if (opponentId) { io.to(opponentId).emit("opponent-disconnected"); } } }); }); server.listen(3001, () => { console.log("SERVER IS RUNNING"); });
关键改动说明
- 房间化管理:用Socket.io的房间功能为每对玩家创建独立游戏空间,避免跨局干扰
- 局级状态隔离:每个房间的选择数据存储在
games对象的对应条目里,每局游戏状态独立 - 避免重复绑定:全局监听
choice和reset-game事件,不在事件内部嵌套绑定 - 明确重置逻辑:新增
reset-game事件,重置时清空对应房间的选择状态,通知双方刷新界面 - 完善配对逻辑:新增等待队列,自动为玩家配对,保证两人一组进行游戏
内容的提问来源于stack exchange,提问作者Stefan-Yves
相关产品推荐
相关产品推荐

