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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 08:57:34