Не получается сделать простого бота в крестиках-ноликах

Пытаюсь реализовать простого соперника в крестиках-ноликах, который ставит свой нолик в место, которое не занято, но никак не могу понять, как его правильно сделать. Вот исходный код 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>

→ Ссылка