现在我们将编写一些实用函数。在isValidAction函数中,我们将决定用户是否想要执行有效的操作。如果 tile 的内部文本是XorO我们返回 false 作为操作无效,否则 tile 为空所以操作有效。
const isValidAction = (tile) => { if (tile.innerText === 'X' || tile.innerText === 'O'){ return false; } return true; };
下一个效用函数将非常简单。在这个函数中,我们将接收一个索引作为参数,并将棋盘数组中的相应元素设置为我们当前玩家的符号。
const updateBoard = (index) => { board[index] = currentPlayer; }
我们将编写一个小函数来处理玩家的变化。在这个函数中,我们将首先从playerDisplay. 字符串模板文字player${currentPlayer}将成为playerX或playerO取决于当前玩家。接下来,我们将使用三元表达式来更改当前玩家的值。如果是X,它将是O否则它将是X。现在,我们改变了我们用户的价值,我们需要更新innerText的playerDisplay,并应用新的播放器类的。
const changePlayer = () => { playerDisplay.classList.remove(`player${currentPlayer}`); currentPlayer = currentPlayer === 'X' ? 'O' : 'X'; playerDisplay.innerText = currentPlayer; playerDisplay.classList.add(`player${currentPlayer}`); }
现在我们将编写宣布最终游戏结果的 announer 函数。它将接收结束游戏类型并innerText根据结果更新播音员 DOM 节点的 。在最后一行中,我们必须删除隐藏类,因为播音员默认是隐藏的,直到游戏结束。
const announce = (type) => { switch(type){ case PLAYERO_WON: announcer.innerHTML = 'Player <span class="playerO">O</span> Won'; break; case PLAYERX_WON: announcer.innerHTML = 'Player <span class="playerX">X</span> Won'; break; case TIE: announcer.innerText = 'Tie'; } announcer.classList.remove('hide'); };
接下来我们将编写这个项目中最有趣的部分之一——结果评估。首先,我们将创建一个 roundWon 变量并将其初始化为 false。然后我们将遍历winConditions数组并检查棋盘上的每个获胜条件。例如,在第二次迭代中,我们将检查这些值:board3、board4、board5。
我们还将进行一些优化,如果任何字段为空,我们将调用continue并跳到下一次迭代,因为如果获胜条件中有空图块,您将无法获胜。如果所有字段都相等,那么我们就有一个赢家,因此我们将 roundWon 设置为 true 并中断 for 循环,因为任何进一步的迭代都会浪费计算。
在循环之后,我们将检查roundWon变量的值,如果为真,我们将宣布获胜者并将游戏设置为非活动状态。如果我们没有获胜者,我们将检查棋盘上是否有空牌,如果我们没有获胜者并且没有空牌,我们将宣布平局。
function handleResultValidation() { let roundWon = false; for (let i = 0; i <= 7; i++) { const winCondition = winningConditions[i]; const a = board[winCondition[0]]; const b = board[winCondition[1]]; const c = board[winCondition[2]]; if (a === "" || b === "" || c === "") { continue; } if (a === b && b === c) { roundWon = true; break; } } if (roundWon) { announce(currentPlayer === "X" ? PLAYERX_WON : PLAYERO_WON); isGameActive = false; return; } if (!board.includes("")) announce(TIE); }
接下来我们将处理用户的操作。此函数将接收一个 tile 和一个索引作为参数。当用户单击一个图块时,将调用此函数。首先我们需要检查它是否是一个有效的动作,我们还将检查游戏当前是否处于活动状态。如果两者都为真,我们innerText用当前玩家的符号更新瓷砖的 ,添加相应的类并更新板阵列。现在一切都更新了,我们必须检查游戏是否已经结束,所以我们调用handleResultValidation(). 最后,我们必须调用该changePlayer方法将轮次传递给另一个玩家。
const userAction = (tile, index) => { if (isValidAction(tile) && isGameActive) { tile.innerText = currentPlayer; tile.classList.add(`player${currentPlayer}`); updateBoard(index); handleResultValidation(); changePlayer(); } };
为了让游戏正常运行,我们必须向磁贴添加事件侦听器。我们可以通过循环遍历图块数组并为每个图块添加一个事件侦听器来做到这一点。(为了获得更好的性能,我们只能向容器添加一个事件侦听器并使用事件冒泡来捕获父级上的磁贴点击,但我认为对于初学者来说这更容易理解。)
tiles.forEach( (tile, index) => { tile.addEventListener('click', () => userAction(tile, index)); });
我们只错过了一项功能:重置游戏。为此,我们将编写一个resetBoard函数。在此函数中,我们将棋盘设置X为由九个空字符串组成,将游戏设置为活动状态,移除播音员并将玩家更改回(根据定义X始终开始)。
我们必须做的最后一件事是遍历图块并将innerText 设置回空字符串,并从图块中删除任何特定于玩家的类。
const resetBoard = () => { board = ['', '', '', '', '', '', '', '', '']; isGameActive = true; announcer.classList.add('hide'); if (currentPlayer === 'O') { changePlayer(); } tiles.forEach(tile => { tile.innerText = ''; tile.classList.remove('playerX'); tile.classList.remove('playerO'); }); }
现在我们只需要将此函数注册为重置按钮的点击事件处理程序。
resetButton.addEventListener('click', resetBoard);
就是这样,我们有一个功能齐全的井字游戏,你可以和你的朋友一起玩,玩得开心。