<div class="board" id="board"></div>
<button id="reset">リセット</button>
body {
    display: flex;
    flex-direction: column;
    align-items: center;
    font-family: Arial, sans-serif;
}

.board {
    display: grid;
    grid-template-columns: repeat(3, 80px);
    grid-template-rows: repeat(3, 80px);
    gap: 1px;
}

.cell {
    width: 80px;
    height: 80px;
    display: flex;
    justify-content: center;
    align-items: center;
    border: 1px solid #000;
    cursor: pointer;
}

.svg-icon {
    width: 80%;
    height: 80%;
}

button {
  appearance: none;
  margin-top: 20px;
  padding: 7px 20px;
  font-size: 16px;
  line-height: 16px;
  border: 1px solid #999;
  border-radius: 30px;
  background-color: transparent;
}
const board = document.getElementById('board');
const resetButton = document.getElementById('reset');
let cells;
let currentPlayer = 'X';
let gameOver = false;
let winningCombination = null;

function init() {
    board.innerHTML = '';
    gameOver = false;
    currentPlayer = 'X';
    winningCombination = null;
    cells = Array(9).fill(null);
    for (let i = 0; i < 9; i++) {
        const cell = document.createElement('div');
        cell.className = 'cell';
        cell.addEventListener('click', () => handleCellClick(i));
        board.appendChild(cell);
    }
}

function handleCellClick(index) {
    if (gameOver || cells[index]) return;
    makeMove(index);
    if (!gameOver) {
        setTimeout(makeAIMove, 500);
    }
}

function makeMove(index) {
    if (cells[index]) return;
    cells[index] = currentPlayer;
    updateBoard();
    if (checkWinner()) {
        alert(`${currentPlayer}の勝ちです!`);
        gameOver = true;
    } else if (cells.every(cell => cell)) {
        alert('引き分けです!');
        gameOver = true;
    } else {
        currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
    }
}

function makeAIMove() {
    if (gameOver) return;

    // 自分が3連続で並べられる場合に勝利するパターンを探す
    let winningMove = findWinningMove();
    if (winningMove !== null) {
        makeMove(winningMove);
        return;
    }

    // 相手が2連続で並べられるのを妨害する
    let blockingMove = findBlockingMove();
    if (blockingMove !== null) {
        makeMove(blockingMove);
        return;
    }

    // 上記のいずれも該当しない場合はランダムに置く
    let availableMoves = cells.map((cell, index) => cell === null ? index : null).filter(index => index !== null);
    let move = availableMoves[Math.floor(Math.random() * availableMoves.length)];
    makeMove(move);
}

function findWinningMove() {
    const winningCombinations = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6]
    ];
    for (let combination of winningCombinations) {
        const [a, b, c] = combination;
        if (cells[a] === 'X' && cells[b] === 'X' && cells[c] === 'X') {
            if (a === 0 && cells[0] === null) return 0;
            if (a === 1 && cells[1] === null) return 1;
            if (a === 2 && cells[2] === null) return 2;
            if (a === 3 && cells[3] === null) return 3;
            if (a === 4 && cells[4] === null) return 4;
            if (a === 5 && cells[5] === null) return 5;
            if (a === 6 && cells[6] === null) return 6;
            if (a === 7 && cells[7] === null) return 7;
            if (a === 8 && cells[8] === null) return 8;
        }
    }
    return null;
}

function findBlockingMove() {
    const winningCombinations = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6]
    ];
    for (let combination of winningCombinations) {
        const [a, b, c] = combination;
        if (cells[a] === 'X' && cells[b] === 'X' && cells[c] === null) return c;
        if (cells[a] === 'X' && cells[c] === 'X' && cells[b] === null) return b;
        if (cells[b] === 'X' && cells[c] === 'X' && cells[a] === null) return a;
    }
    return null;
}

function updateBoard() {
    board.childNodes.forEach((cell, index) => {
        cell.innerHTML = ''; // リセット時に内容を消去
        if (cells[index]) {
            const svg = document.createElement('div');
            svg.innerHTML = cells[index] === 'X' ? getSVG('X') : getSVG('O');
            svg.className = 'svg-icon';
            cell.appendChild(svg);
        }
        if (winningCombination && winningCombination.includes(index)) {
            cell.querySelector('svg').style.stroke = 'red'; // 勝利セルの線の色を赤に変更
        }
    });
}

function getSVG(player) {
    if (player === 'X') {
        return `
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" stroke="black" stroke-width="10">
                <line x1="10" y1="10" x2="90" y2="90"/>
                <line x1="90" y1="10" x2="10" y2="90"/>
            </svg>
        `;
    } else {
        return `
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" stroke="black" stroke-width="10" fill="none">
                <circle cx="50" cy="50" r="40"/>
            </svg>
        `;
    }
}

function checkWinner() {
    const winningCombinations = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6]
    ];
    for (let combination of winningCombinations) {
        if (combination.every(index => cells[index] === currentPlayer)) {
            winningCombination = combination;
            return true;
        }
    }
    return false;
}

resetButton.addEventListener('click', init);

init();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.