如何使函数等待事件发生?

时间:2019-01-25 13:01:30

标签: javascript html css

我正在制作井字游戏,我的问题是游戏功能不会等待用户选择他想移动的位置,它只是在我之后立即运行(gameOver)功能按下开始游戏按钮。

谁能告诉我我的代码有什么问题并帮助我解决它?

```
const start = document.getElementById('start');
const table = document.getElementById('table');
places = ["one", "two", "three", "four", "five", "six", "seven", 'eight', "nine"];
let move = 0;
start.addEventListener('click', function(){
startGame();
});

function startGame(){
console.log("Game started");
user();
}

function gameOver(){
console.log("Game Over");
}

function computer(){
let index = places[Math.floor(Math.random() * places.length)];
let pos = document.getElementById(index);
if (/[1-9]/.test(pos.innerHTML)){
    pos.innerHTML = "O";
    move += 1;
    places.splice(places.indexOf(pos), 1 );
}
if (move > 8){
    gameOver();
} else {
    user();
}
}

function user(){
table.addEventListener('click', function(event){
    let pos = event.target;
    let Id = event.target.id;
    if (/[1-9]/.test(pos.innerHTML)){
        pos.innerHTML = "X";
        move += 1;
        places.splice(places.indexOf(Id), 1 );
    }
    if (move > 8){
        gameOver();
    } else {
        computer();
    }
});
}
```
<div class="col text-center">
            <table class="table text-center">
                <tbody id="table">
                    <tr>
                        <td id="one">1</td>
                        <td id="two">2</td>
                        <td id="three">3</td>
                    </tr>
                    <tr>
                        <td id="four">4</td>
                        <td id="five">5</td>
                        <td id="six">6</td>
                    </tr>
                    <tr>
                        <td id="seven">7</td>
                        <td id="eight">8</td>
                        <td id="nine">9</td>
                    </tr>
                </tbody>
            </table>
            <br />
            <button class="btn btn-primary" type="button" id="start">Start Game</button>
        </div>

5 个答案:

答案 0 :(得分:1)

主要问题是您做了一个while循环来运行游戏,并且有一个“在玩家单击时”事件,该事件是异步的并且不会暂停游戏。

首选方法是使游戏完全异步,而无需使用while循环,并在每次计算机或玩家移动时检查移动计数器。

1)在开始时,将移动计数设置为0

2)在玩家身上单击以增加移动次数并最终运行计算机移动,或者如果move_count> 8

则运行“游戏结束”功能

注意1:记住在计算机移动时增加移动次数并检查结束位置。

注意2:使用此解决方案,播放器将始终优先移动。

答案 1 :(得分:1)

让我们分析您的user()函数:

const table = document.getElementById('table');
...
function user(){
    table.addEventListener('click', function(event){
        let pos = event.target;
        if (/[1-9]/.test(pos.innerHTML)){
            pos.innerHTML = "X";
            player = true;
        }
    });
}

这里的要点是JavaScript是一种高度异步的语言。 addEventListener函数在执行时会添加事件侦听器,然后返回,这表示user()函数已完成。然后,此监听器将在每次单击时触发相应的功能,它不会停止代码并等待单击输入。然后,由于您的代码在一段时间内并且用户功能已完全执行(请记住,只有一个语句为addEventListener),因此代码会很快完成。

要解决此问题,请在start函数的开头调用addEventListener,然后将相应的逻辑放入相应的click函数中。这样,您的代码将在用户单击时仅执行,您可以从此处调用计算机移动或gameOver功能。

答案 2 :(得分:1)

另一种方法是将游戏创建为迭代器,可以将其暂停并等待用户输入。

var game; //variable to reference the currently running game

function* newGame() { //note the '*' which creates an iterator
    while (i <= 9){
        if (player === true) {
            computer();
        } else {
            user();
            yield i; //pause the method (must return a value)
        }
        i++;
    }
    gameOver();
}

function startGame(){
    game = newGame(); //start the game by creating new iterator
    game.next(); //perform first step
}

function user(){
    table.addEventListener('click', function(event){
        let pos = event.target;
        if (/[1-9]/.test(pos.innerHTML)){
            pos.innerHTML = "X";
            player = true;
            game.next(); //continue with the game...
        }
    });
}

请注意,现在编写方式将为您分配4个不同的点击处理程序。 您还应该调用removeEventListener(),因为在调用监听器后,它不会自动清除! 但是一旦游戏开始运行,您就会发现;)。

答案 3 :(得分:0)

感谢@Keith的指导,我正在编辑此答案。

浏览器的用户输入本质上是异步的。也许有一天,我们将有一个等待事件的API,但在那之前,我们只剩下用Promises修补猴子了。

在多个项目中有用的模式是在其范围之外解决Promise。在这种模式下,解析器类似于延迟器。这种模式允许将解析程序运送到Promise构造程序以外的工作单元中。这是您可以异步等待DOM事件的两种方式的比较:

(async () => {
  const button1 = document.createElement("button")
  button1.innerText = "Resolve it out of scope"
  document.body.appendChild(button1)
  const button2 = document.createElement("button")
  button2.innerText = "Resolve it in scope"
  document.body.appendChild(button2)
  
  const remove = button => button.parentNode.removeChild(button);
  
  const asyncResolveOutScope = async () => {
    let resolver
    button1.onclick = () => {
      remove(button1)
      resolver()
    }
    return new Promise(resolve => resolver = resolve)
  }

  const asyncResolveInScope = async () => {
    return new Promise(resolve => {
      button2.onclick = () => {
        remove(button2)
        resolve()
      }
    })
  }

  console.log("Click the buttons, I'm waiting")
  await Promise.all([asyncResolveOutScope(), asyncResolveInScope()])
  
  console.log("You did it")
})()

我不确定这对您有多大帮助,但这是一个使用tic-tac-toe-toe-toe-toe-toe-toe的游戏示例,该游戏使用出厂的(解决范围外)Promise模式:

class game {
  constructor(name, order=["Computer", "User"]) {
    this.userTurnResolver
    this.name = name
    this.players = [
      {name: "Computer", turn: async() => {
        const cells = this.unusedCells
        return cells[Math.floor(Math.random() * cells.length)]
      }},
      {name: "User", turn: async() => new Promise(resolve => this.userTurnResolver = resolve)}
    ].sort((a, b) => order.indexOf(a.name) - order.indexOf(b.name))
    this.players[0].symbol = "X"
    this.players[1].symbol = "O"
  }
  log(...args) {
    console.log(`${this.name}: `, ...args)
  }
  get cells() {
    return Array.from(this.table.querySelectorAll("td")).map(td => td.textContent)
  }
  get unusedCells() {
    return Array.from(this.table.querySelectorAll("td")).filter(td => !isNaN(td.textContent)).map(td => td.textContent)
  }
  userClick(e) {
    const cell = isNaN(e.target.textContent) ? false : parseInt(e.target.textContent)
    if (cell && this.userTurnResolver) this.userTurnResolver(cell)
  }
  render() {
    //This would usually be done with HyperHTML. No need for manual DOM manipulation or event bindings.
    const container = document.createElement("div")
    container.textContent = this.name
    document.body.appendChild(container)
    this.table = document.createElement("table")
    this.table.innerHTML = "<tbody><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>4</td><td>5</td><td>6</td></tr><tr><td>7</td><td>8</td><td>9</td></tr></tbody>"
    this.table.onclick = e => this.userClick(e)
    container.appendChild(this.table)
  }
  async start() {
    this.render()
    this.log("Game has started")

    const wins = [
      {desc: "First Row", cells: [0, 1, 2]}, {desc: "Second Row", cells: [3, 4, 5]},
      {desc: "Third Row", cells: [6, 7, 8]}, {desc: "Diagonal", cells: [0, 4, 8]},
      {desc: "Diagonal", cells: [2, 4, 6]}, {desc: "First Column", cells: [0, 3, 6]},
      {desc: "Second Column", cells: [1, 4, 7]}, {desc: "Third Column", cells: [2, 5, 8]}
    ]
    const checkWin = symbol => wins.find(win => win.cells.every(i => this.cells[i] === symbol))

    const takeTurn = async ({name, turn, symbol}, round, i) => {
      this.log(`It is ${name}'s turn (round ${round}, turn ${i})`)
      const choice = await turn()
      this.log(`${name} had their turn and chose ${choice}`)
      this.table.querySelectorAll("td")[choice-1].textContent = symbol
      return checkWin(symbol)
    }

    let win, round = 0, i = 0
    while (!win && i < 9) {
      round++
      for (let player of this.players) {
        i++
        win = await takeTurn(player, round, i)
        if (win) {
          this.log(`${player.name} is the winner with ${win.desc}`)
          break
        }
        if (i===9) {
          this.log(`We have a stalemate`)
          break
        }
      }
    }
    this.log("Game over")
  }
}

(new game("Game 1", ["User", "Computer"])).start();
(new game("Game 2")).start();
(new game("Game 3")).start();

答案 4 :(得分:0)

Javascript是单线程运行时,因此您所做的许多事情都被称为pd.cut,例如获得鼠标单击,执行ajax / fetch请求之类的事情。所有这些都不会阻塞Javascript线程,而是使用回调等。

幸运的是,现代JS引擎具有一个称为asynchronous的功能,它的基本作用是使异步代码看起来是同步的。

下面是对您的代码进行了稍微修改的地方,主要的变化是使您的async / await函数变成了Promise,以便可以成为user

顺便说一句。这样做还有其他错误,例如单击已使用的单元格,但是我将其保留在此处,因此人们很容易看到我为使脚本运行而进行了哪些更改。

awaited
const start = document.getElementById('start');
const table = document.getElementById('table');
places = ["one", "two", "three", "four", "five", "six", "seven", 'eight', "nine"];
let i = 1;
let player = true;
start.addEventListener('click', function(){
    startGame();
});

async function startGame(){
    while (i <= 9){
        if (player === true) {
            computer();
        } else {
            await user();
        }
        i++;
    }
    gameOver();
}

function gameOver(){
    console.log("Game Over");
}

function computer(){
    let index = places[Math.floor(Math.random() * places.length)];
    let pos = document.getElementById(index);
    if (/[1-9]/.test(pos.innerHTML)){
        pos.innerHTML = "O";
        player = false;
    }
}

function user(){
  return new Promise((resolve) => {
    function evClick(event) {
      table.removeEventListener('click', evClick);
      let pos = event.target;
      if (/[1-9]/.test(pos.innerHTML)){
          pos.innerHTML = "X";
          player = true;
      }
      resolve();
    }
    table.addEventListener('click', evClick)
  });
}
td {
  border: 1px solid black;
  margin: 15px;
  padding: 15px;
}