Не получается сделать простого бота в крестиках-ноликах
Пытаюсь реализовать простого соперника в крестиках-ноликах, который ставит свой нолик в место, которое не занято, но никак не могу понять, как его правильно сделать. Вот исходный код JS скрипта:
const statusText = document.querySelector("#statusText");
const restartBtn = document.querySelector("#restartBtn");
const winConditions = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
let options = ["", "", "", "", "", "", "", "", ""];
let currentPlayer = "X";
let running = false;
function intializeGame() {
cells.forEach(cell => cell.addEventListener("click", cellChecked));
restartBtn.addEventListener("click", restartGame);
statusText.textContent = `${currentPlayer}'s Turn'`;
running = true;
}
function cellChecked() {
const cellIndex = this.getAttribute("cellIndex");
if (options[cellIndex] != "" || !running) {
return;
}
updateCell(this, cellIndex);
checkWinner();
}
function updateCell(cell, index) {
options[index] = currentPlayer;
cell.textContent = currentPlayer;
}
function changePlayer() {
currentPlayer = (currentPlayer == "X") ? "0" : "X";
statusText.textContent = `${currentPlayer}'s Turn'`;
}
function checkWinner() {
let roundWon = false;
for (let i = 0; i < winConditions.length; i++) {
const condition = winConditions[i];
const cellA = options[condition[0]];
const cellB = options[condition[1]];
const cellC = options[condition[2]];
if (cellA == "" || cellB == "" || cellC == "") {
continue;
}
if (cellA == cellB && cellB == cellC) {
roundWon = true;
break;
}
}
if (roundWon) {
statusText.textContent = `${currentPlayer} win!`;
running = false;
} else if (!options.includes("")) {
statusText.textContent = `Draw!`;
running = false;
} else {
changePlayer();
}
}
function restartGame() {
currentPlayer = "X";
options = ["", "", "", "", "", "", "", "", ""];
statusText.textContent = `${currentPlayer}'s turn`;
cells.forEach(cell => cell.textContent = "");
running = true;
}
intializeGame();
Как я пытался сделать на моменте смене хода
currentPlayer = (currentPlayer == "X") ? "0" : "X";
statusText.textContent = `${currentPlayer}'s Turn'`;
if (currentPlayer === "0") {
for (let i = 0; i < options.length; i++) {
if (options[i] === "") {
console.log(options[i]);
updateCell(options[i], i)
checkWinner();
}
}
}
}
Ответы (1 шт):
Автор решения: UModeL
→ Ссылка
Пробовал делать через Math.floor(Math.random() * options.length), но не вышло.
Ход мысли правильный - получаем пустые индексы и выбираем случайный их них.
Ниже приведён код, где это реализовано именно так:
const WINS = [
/* горизонтали */ [0, 1, 2], [3, 4, 5], [6, 7, 8],
/* вертикали */ [0, 3, 6], [1, 4, 7], [2, 5, 8],
/* диагонали */ [0, 4, 8], [2, 4, 6]
];
const USER = [], BOT = [];
const BOARD = document.querySelector('.board');
const CELLS = [...BOARD.querySelectorAll('.cell')];
BOARD.addEventListener('click', fUser);
const RESET = document.querySelector('.reset');
RESET.addEventListener('click', fReset);
RESET.style.display = 'none';
/* Ход игрока */
function fUser(ev) {
let target = ev.target;
if (target.className != 'cell') return;
BOARD.style.pointerEvents = 'none';
USER.push(CELLS.indexOf(target));
fCheck(USER, true, target, 'cross');
}
/* Ход бота */
function fBot() {
let blanks = [...BOARD.querySelectorAll('[class="cell"]')];
if (blanks.length <= 1) return;
let target = blanks[Math.floor(Math.random() * (blanks.length - 1))];
BOT.push(CELLS.indexOf(target));
fCheck(BOT, false, target, 'zero');
}
/* Проверка совпадений */
function fCheck(arr, owner, cell, type) {
arr.sort();
let matching = WINS.filter((m) => {
return (m.filter((x) => { return arr.includes(x) }).length == 3);
});
let fAction;
if (matching.length) { fAction = fWin.bind(null, matching, owner); }
else { fAction = owner ? fBot : fNext; }
cell.addEventListener('animationend', fAction, { once: true });
cell.classList.add(type);
}
/* Следующий ход */
function fNext() { BOARD.style.pointerEvents = '' }
/* Победа */
function fWin(winline, winner) {
BOARD.style.pointerEvents = 'none';
winline.flat().forEach((el) => { CELLS[el].classList.add('win') });
RESET.style.display = '';
}
/* Новая игра */
function fReset() {
CELLS.forEach((el) => { el.classList.remove('cross', 'zero', 'win') });
USER.length = BOT.length = 0;
BOARD.style.pointerEvents = '';
RESET.style.display = 'none';
}
.board {
display: grid; grid-template: repeat(3, 60px) / repeat(3, 60px);
width: max-content;
box-shadow: 0 0 0 1px #000;
}
.cell {
display: grid; place-items: center;
box-shadow: inset 0 0 0 1px #000;
}
.cross::before,
.zero::before {
font: bold 60px/1em sans-serif;
transform: scale(0);
animation: move 1s ease-in-out forwards;
}
.cross::before { content: '×'; color: #b22; }
.zero::before { content: '⚪'; color: #080; }
.win::before { animation: win 0.6s ease-in-out 6 forwards; }
.reset {
display: grid; place-items: center;
width: max-content; padding: 1em;
margin: 10px; float: right;
box-shadow: inset 0 0 1em #00f;
cursor: pointer;
}
@keyframes move { 50% { transform: scale(1.5) } 70%, 100% { transform: scale(1) }}
@keyframes win { 0%, 100% { transform: scale(1, 1) } 50% { transform: scale(0, 1) }}
<div class="reset">Новая игра</div>
<div class="board">
<div class="cell"></div><div class="cell"></div><div class="cell"></div>
<div class="cell"></div><div class="cell"></div><div class="cell"></div>
<div class="cell"></div><div class="cell"></div><div class="cell"></div>
</div>