Search code examples
javascriptminimax

Minimax function TIc Tac Toe


Im trying to build tic tac toe game using javascript, so basically my version of tic tac toe is without AI so computer play in the first empty spot then I replace it with a minimax function to make computer unbeatable. Function works incorrectly. What i do wrong? AI do not wanna win and let me collect 3 in row. I know code is actually bad, but i think it will work. If i simply move in 3, 6, 9 cells AI lose. AI dont understand that him need block me at 9 cell.

let turnPlr1 = true;
let p = 0;
let gameboard = ['_','_','_','_','_','_','_','_','_'];

const Gameboard = (() => {
    let btns = document.querySelectorAll('.btn');
    const score = document.querySelector('#score');

    const pc = document.querySelector('#pc');


    let num = 0;

    const arrWin = [
        [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 players = (player, score, mark) => {
        return {player, score, mark}
    }

    let player1 = players('Player1', 0, 'x');
    let player2 = players('Player2', 0, 'o');


    document.querySelector('#restart').addEventListener('click', () => {
        document.querySelectorAll('.btn').forEach(item => {
            item.innerHTML = '';
        });
        document.querySelector('#result').innerHTML = '';
        turnPlr1 = true;
        gameboard.length = 0;
        gameboard = ['_','_','_','_','_','_','_','_','_'];
        p = 0;
    });

    btns.forEach(item => {
        item.addEventListener('click', (e) => {
            if (gameboard.length === 0) {
                gameboard = ['_','_','_','_','_','_','_','_','_'];
            }
            if (turnPlr1 && e.target.innerHTML === '' ) {
                // AI behavior
                e.target.innerHTML = player1.mark;
                gameboard[e.target.id] = player1.mark;
                p++;
                if (pc.checked === false) {
                    turnPlr1 = turn(turnPlr1);
            } else if (pc.checked === true) {
                let bestScore = -Infinity;
                let bestMove = -1;
                for (let h = 0; h < gameboard.length; h++) {
                    if (gameboard[h] === '_') {
                        gameboard[h] = player2.mark;
                        let score = minimax(gameboard, 0, false);
                        gameboard[h] = '_';
                        if (score > bestScore) {
                            bestScore = score;
                            bestMove = h;
                        }
                    }
                }
                gameboard[bestMove] = player2.mark;
                btns[bestMove].innerHTML = player2.mark;
                p++;        
            }
            } else if (e.target.innerHTML === '' && pc.checked === false) {
                e.target.innerHTML = player2.mark;
                gameboard[e.target.id] = player2.mark;
                turnPlr1 = turn(turnPlr1);
            }

            if (checkWin(gameboard, arrWin)) {
                Interface.result.innerHTML = checkWin(gameboard, arrWin);
                if (document.querySelector('#result').innerHTML === `${player1.player} Win!`) {
                    player1.score++;
                }
                else if(document.querySelector('#result').innerHTML === `${player2.player} Win!`) {
                    player2.score++;
                }
                score.innerHTML = `Score: ${player1.score} | ${player2.score}`;
                    
            }
        })
    })

    function restart() {
        gameboard = ['_','_','_','_','_','_','_','_','_'];
    }

    return {
        num,
        player1,
        player2,
        gameboard,
        turnPlr1,
        p,
        arrWin,
        btns,
        restart
    }

})();

const Interface = (() => {
    const restart = document.querySelector('#restart');

    const startInp = document.querySelector('#start');
    const player1Inp = document.querySelector('#player1Inp');
    const player2Inp = document.querySelector('#player2Inp');

    startInp.addEventListener('click', (e) => {
        e.preventDefault();
        console.log('Start');
        document.querySelector('.gameboard').classList.add('active');
        document.querySelector('#startMenu').classList.add('not_active');

        Gameboard.player1.player = player1Inp.value;
        Gameboard.player2.player = player2Inp.value;

        console.log(player1Inp.value);
        console.log(player2Inp.value);
    });
    



    return {
        result,
        player1Inp,
        player2Inp,
        score
    }
    
})();


function turn(turnPlr1) {
    if(turnPlr1) {
        turnPlr1 = false;
    } else {
        turnPlr1 = true;
    }
    return turnPlr1;
}

function checkWin(gameboard, arrWin) {
    let win1 = 0;
    let win2 = 0;
    for (let i = 0; i < arrWin.length; i++) {
        for(let x = 0; x < 3; x++) {
            if (gameboard[arrWin[i][x]] === Gameboard.player1.mark) {
                win1++;
            } else if(gameboard[arrWin[i][x]] === Gameboard.player2.mark) {
                win2++;
            }
        }
        if (win1 === 3) {
            Interface.score.innerHTML = `Score ${Gameboard.player1.score} | ${Gameboard.player2.score}`;
            return `${Gameboard.player1.player} Win!`;
        } else if (win2 === 3) {
            Interface.score.innerHTML = `Score ${Gameboard.player1.score} | ${Gameboard.player2.score}`;
            return `${Gameboard.player2.player} Win!`;
        } 
        win1 = 0;
        win2 = 0;
    }
    if (Gameboard.num === 9) return 'We are even';
    win1 = 0;
    win2 = 0;

    return null;
}

function minimax(board, depth, is_maximazing) {
    let result = checkWin(gameboard, Gameboard.arrWin);
    let bestScore;
    if (result !== null) {
        if (checkWin(gameboard, Gameboard.arrWin) === `${Gameboard.player1.player} Win!`) {
            return -1;
        }
        else if (checkWin(gameboard, Gameboard.arrWin) === `${Gameboard.player2.player} Win!`) {
            return 1;
        } else if (checkWin(gameboard, Gameboard.arrWin) === 'We are even') {
            return 0;
        }

    }

    if (is_maximazing) {
        bestScore = -Infinity;
        for (let h = 0; h < gameboard.length; h++) {
            if (gameboard[h] === '_') {
                gameboard[h] = Gameboard.player2.mark;
                let score = minimax(gameboard, depth + 1, false);
                gameboard[h] = '_';
                if (score > bestScore) {
                    bestScore = score;
                }
            }
        }
        return bestScore;
    } else {
        bestScore = Infinity;
        for (let h = 0; h < gameboard.length; h++) {
            if (gameboard[h] === '_') {
                gameboard[h] = Gameboard.player1.mark;
                let score = minimax(gameboard, depth + 1, true);
                gameboard[h] = '_';
                if (score < bestScore) {
                    bestScore = score;
                }
            }
        }
        return bestScore;
    }

}

Solution

  • The problem is that your code does not detect a draw. Although you have a num property, it is never changed from its initial value.

    Actually, you don't really need it to detect a draw. You can identify a draw when there are no more underscores in your game board.

    So in the checkWin function change this:

    if (Gameboard.num === 9) return 'We are even';
    

    to this:

    if (!gameboard.includes('_')) return 'We are even';
    

    There is much to improve in your code, too much to list here. But in this context of the checkWin function, consider not returning a string that could serve for output purposes, but a number. The caller of checkWin should not have to compare human readable strings.