You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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");
});

问题根源分析

  1. 全局状态污染:全局变量otherPlayerChoice和playerChoice会被所有游戏局共享,重置时的清空操作无法保证彻底隔离不同局的状态
  2. 重复事件绑定:在choice事件内部绑定secondPlayerMove/test事件,每次玩家选择都会新增一个监听,旧监听会保留上一局的data.choice引用,触发时会用上一局的选择参与判定
  3. 无局级状态隔离:没有为每一组玩家的每一局游戏创建独立状态容器,导致不同局的选择数据互相干扰

解决方案:局级状态隔离+事件逻辑重构

以下是修正后的完整服务端代码,核心改动是为每局游戏创建独立状态,避免全局变量和重复绑定问题:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 04:47:22