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

JavaScript战舰游戏:等待船只放置点击完成再执行下一个函数

解决连续调用船只放置函数的异步执行问题

问题核心

你当前的playerPlaceShip是同步函数,连续调用时会立刻绑定多轮事件监听,导致第二个函数在首次点击完成前就启动,同时叠加的事件监听会引发逻辑混乱。要实现按顺序等待每艘船放置完成,核心是将异步的用户交互(点击)转化为可等待的异步操作,同时保持游戏流程控制权在游戏循环模块。

解决方案

将playerPlaceShip改造为返回Promise的函数,在游戏循环中用async/await按顺序调用,确保每一轮放置完成后再启动下一轮。

1. 修改DOM模块:让playerPlaceShip返回Promise

// DOM模块中修改playerPlaceShip函数
const playerPlaceShip = (board, ship) => {
  return new Promise((resolve) => {
    const squares = document.querySelectorAll('#p1 .board .square');
    narrative.textContent = `Lead your ${ship.type} into battle. Press X to steer.`;

    // 定义各事件处理函数
    const handleClick = (e) => {
      clickToPlace(e, board, ship);
      resolve(); // 用户点击完成后,标记当前Promise完成
      removeAllListeners(); // 清理当前轮次的所有事件监听
    };

    const handleMouseOver = (e) => renderShadow(e, 'fill', ship.length);
    const handleMouseOut = (e) => renderShadow(e, 'clear', ship.length);
    const handleKeyDown = (e) => {
      if(e.key === 'x') {
        switchAxis();
        squares.forEach(square => square.classList.remove('hovered'));
        renderShadow(lastCoords, 'fill', ship.length);
      }
    };

    // 绑定事件
    squares.forEach(square => {
      square.addEventListener('mouseover', handleMouseOver);
      square.addEventListener('mouseout', handleMouseOut);
      square.addEventListener('click', handleClick);
    });
    window.addEventListener('keydown', handleKeyDown);

    // 封装监听清理逻辑
    function removeAllListeners() {
      squares.forEach(square => {
        square.removeEventListener('mouseover', handleMouseOver);
        square.removeEventListener('mouseout', handleMouseOut);
        square.removeEventListener('click', handleClick);
      });
      window.removeEventListener('keydown', handleKeyDown);
    }
  });
};

// 原clickToPlace函数保持不变
const clickToPlace = (e, board, ship) => {
  let { x, y } = e.target.dataset;
  x = parseInt(x, 10);
  y = parseInt(y, 10);
  board.place(ship, x, y, axis);
  renderShadow(e, 'place', ship.length);
};

2. 修改游戏循环模块:用async/await顺序调用

// 将放置流程封装为async函数
async function startPlayerShipPlacement() {
  // 渲染棋盘
  renderBoard(p1Board, p1Box);
  renderBoard(p2Board, p2Box);
  
  // 依次等待每艘船放置完成
  await playerPlaceShip(p1Board, p1Carrier);
  await playerPlaceShip(p1Board, p1Battleship);
  // 后续船只可继续添加await调用
}

// 启动放置流程
startPlayerShipPlacement();

方案优势

  • 流程可控:游戏循环模块通过async/await完全控制放置顺序,不会将控制权转移到DOM模块。
  • 无监听叠加:每轮放置完成后自动清理事件监听,避免多轮函数调用导致的事件冲突。
  • 异步逻辑清晰:用Promise将用户点击的异步操作标准化,符合现代JS异步编程范式。

内容的提问来源于stack exchange,提问作者Pete

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 05:27:26