寻找一种更干净的方式来获得井字游戏的赢家



我正在编写一个函数,该函数采用一个 2D 数组,该数组表示井字游戏的网格。最初它是[[0, 0, 0], [0, 0, 0], [0, 0, 0]]的,玩家 1 表示为1,玩家 2 表示为-1

如果确实有获胜者,则检查获胜者的函数应返回获胜者。

我的尝试是这样的:

const checkIfWin = (b) => {
if (b[0][0] == b[0][1] && b[0][1] == b[0][2]) return b[0][0]
if (b[1][0] == b[1][1] && b[1][2] == b[1][1]) return b[1][0]
if (b[2][0] == b[2][1] && b[2][1] == b[2][2]) return b[2][2]
if (b[0][0] == b[1][0] && b[1][0] == b[2][0]) return b[2][0]
if (b[0][1] == b[1][1] && b[1][1] == b[2][1]) return b[2][1]
if (b[0][2] == b[1][2] && b[1][2] == b[2][2]) return b[2][2]
if (b[0][0] == b[1][1] && b[1][1] == b[2][2]) return b[2][2]
if (b[0][2] == b[1][1] && b[1][1] == b[2][0]) return b[2][0]
}
checkIfWin([ [ 1, 0, 0 ], [ 0, 1, 0 ], [ -1, -1, 1 ] ]) // 1

它有效,但我认为它不是真的优雅或干净,我想知道是否有更好的方法来检查获胜者并返回获胜者?

  1. 使用两个辅助函数:checkRow(i)checkColumn(i),分别检查给定行和列中的所有值是否相同。
  2. 对于范围[0, number of rows]中的i,请使用checkRow(i)
  3. 对于范围[0, number of columns]中的i,请使用checkColumn(i)
  4. 如果仍然没有找到获胜者,请检查对角线(您可以使用另一个辅助函数)

编程起来非常简单,所以我把它留给你。但是您可以使用此方法将问题分解为更简单的部分。

您可以编写一个泛型函数来检查给定起始单元格和偏移量的情况下,一行中有多少个单元格具有相同的值。

  • 对于行,偏移量将为 1。
  • 对于列,偏移量将是电路板大小。
  • 对于对角线,偏移量将是电路板尺寸加减 1。

const boardSize = 3;
// board state in a flat array to
// simplify jumping to the next cell
// via a given offset
const state = [
1, 0, 0,
1, 1, 0,
1, 1, 0,
]
function numInARow(cellIndex, offset) {
let value = state[cellIndex];
let result = 1;
while(state[cellIndex += offset] === value) {
result++;
}
return result;
}
console.log(numInARow(0, 1)); // first row (1)
console.log(numInARow(0, 3)); // first column (3)
console.log(numInARow(0, 4)); // diagonal from top left (2)

建立后,您可以使用循环来检查每一行和每一列。

for(let i = 0; i < boardSize; i++) {
if(numInARow(state, i, 1) >= boardSize) {
// row win
}
if(numInARow(state, boardSize * i >= boardSize) {
// column win
}
if(numInARow(state, i, boardSize + 1) >= boardSize {
// diagonal pointing downhill to the right
}
if(numInARow(state, i, boardSize - 1) >= boardSize {
// diagonal pointing uphill to the right
}
}

如果你只是从一个角落检查到另一个角落,对角线并不真正需要循环,但是将它们放在那里对于这么小的板子没有任何伤害。

要考虑的另一件事是,您只需要检查最后一个播放的方块。我的演示代码仅从起始单元格(向右和向下)向前检查,但可以很容易地调整为向后检查。这将提高大尺寸电路板的性能,因为您不会每次都签入整个电路板。

有很多方法可以做到这一点。您可以依靠正则表达式并将您的板表示为包含 9 个字符的单个字符串。

以下是该想法的实现:

class TicTacToe {
constructor() {
this.board = ".........";
this.mover = "O"; // Player that made last move
this.hasWon = false;
this.isDraw = false;
}
play(i) {
if (this.hasWon || this.board[i] !== ".") return; // Invalid move
this.mover = "XO"["OX".indexOf(this.mover)]; // Toggle player
this.board = this.board.slice(0, i) + this.mover + this.board.slice(i + 1);
this.hasWon = /(w)(11(...)*$|...1...1|..1..1|.1.1..$)/.test(this.board);
this.isDraw = !this.hasWon && !this.board.includes(".");
}
}
// I/O handling
document.querySelector("table").addEventListener("click", function (e) {
const cell = e.target;
if (cell.tagName !== "TD") return;
const move = cell.parentNode.rowIndex * 3 + cell.cellIndex
game.play(move);
cell.textContent = game.mover;
const message = game.hasWon ? game.mover + " has won!" : 
game.isDraw ? "It's a draw" : "Click to play";
document.querySelector("span").textContent = message;
});
document.querySelector("button").addEventListener("click", newGame);
function newGame() {
game = new TicTacToe;
for (const td of document.querySelectorAll("td")) td.textContent = "";
document.querySelector("span").textContent = "Click to play";
};
newGame();
table { border-collapse: collapse }
td { vertical-align: middle; text-align: center; min-width: 24px; height: 24px; border: 1px solid; }
<span></span>
<table>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
</table>
<button>New game</button>

最新更新