Крестики-нолики на js


#1

я пытаюсь написать крестики-нолики на веб-странице.

вот js код:

  var game = document.getElementById("game");
    var goo = 0;
    for (let i = 0; i < 9; i++) {
    game.innerHTML += '<div class="block"></div>';
    }        
    var allblock = document.getElementsByClassName("block");
    game.onclick = function (e) {
    function check () {
        var combo = [[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 i = 0; i < combo.length; i++) {
            if (allblock[combo[i][0]].innerHTML == "X" && allblock[combo[i][1]].innerHTML == "X" && allblock[combo[i][2]].innerHTML == "X") return 1;
            if (allblock[combo[i][0]].innerHTML == "O" && allblock[combo[i][1]].innerHTML == "O" && allblock[combo[i][2]].innerHTML == "O") return 2;
        }
    }
    if (goo == 0) {
            if (e.target.innerHTML == "") {
                e.target.innerHTML = "X";
                goo = 1;
                var index = [];
                var j = 0;
                for (let i = 0; i < allblock.length; i++) {
                    if (allblock[i].innerHTML == "") {
                        index[j] = i;
                        j++;
                }
            }
                var random = Math.floor(Math.random() * index.length);
                var randblock = index[random];
                function rand () {
                    allblock[randblock].innerHTML = "O";
                }
                setTimeout(rand, 1000);
                goo = 0;
            }
    }
    function restart () {
        count = 0;
        goo = 0;
        game.innerHTML = "";
        for (let i = 0; i < 9; i++) {
            game.innerHTML += '<div class="block"></div>';
        }
    }
    var rez = check();
    if (!!rez) {
        switch (rez) {
            case 1:
            goo = 1;
            game.innerHTML = "Победили крестики!";
            break;
            case 2:
            goo = 1;
            game.innerHTML = "Победили нолики!";
            break;
        }
    } else {
        if (index.length == 0) {
            goo = 1;
            game.innerHTML = "Ничья!";
        }
    }
    if (!!rez || index.length == 0) {
        setTimeout(restart, 3000);
    }
    }

со стороны html все верно. так вот. у меня две проблемы
первая: по идее, после клика на блок, присвоив значение “X” его innerHTML, дальнейшее присвоение другим блокам должно быть недоступно до того, как “ИИ сделает свой ход”. но этого не происходит. почему?
вторая: когда ИИ побеждает, объявления победителя не происходит до клика кнопкой. я понимаю, что я как-то должен вынести проверку победителя за пределы клика. как это правильно сделать?

буду благодарен, если подскажете, как решить эти две проблемы


#2

Дело тут. goo становится равной 0 до того как походит компьютер. Ты устанавливаешь таймаут и сразу же обнуляешь goo. При следующем быстром клике проверка if (goo == 0) { снова дает true и блок внутри условия выполняется.

if (goo == 0) {
//...
	function rand() {
		allblock[randblock].innerHTML = "O";
	}
	setTimeout(rand, 1000);
	goo = 0;
//...

Решение - обнулять goo после хода компа (т.е в функции-коллбеке таймаута)

function rand() {
     allblock[randblock].innerHTML = "O";
     goo = 0;
 }
 setTimeout(rand, 1000);

Без рефакторинга не обойтись. Мое решение не самое лучшее с точки зрения конченого продукта. Его ценность в том что оно пытается применить “правильные” техники и при этом использовать уже знакомый тебе код.

Что изменилось:

getPossibleMovesInvexes сделано отдельной функции. Раньше index использовался в нескольких местах, которые логически должны быть разделены (проверка конца игры и шаг компьютера). Этот факт усложнял работу с кодом.

computerMove ход компа. Внутри него есть проверка isGameOver. Проверку на завершенность игры нужно делать после каждого хода. Сейчас это 2 вызова в разных функциях. Но это может быть и 1 вызов если описать ход компа и человека одной функцией. А кто ходит сейчас - закодировать переменной. В примерах решений что я кину конце поста есть использование такого подхода.

isGameOver проверка на то что состояние игры “заверешена”.

Полный код:

 var game = document.getElementById("game");
 var goo = 0;
 for (let i = 0; i < 9; i++) {
     game.innerHTML += '<div class="block"></div>';
 }
 var allblock = document.getElementsByClassName("block");


 function getPossibleMovesInvexes() {
    var index = [];
     var j = 0;
     for (let i = 0; i < allblock.length; i++) {
         if (allblock[i].innerHTML == "") {
             index[j] = i;
             j++;
         }
     }
     return index
 }


 function computerMove() {
    var index = getPossibleMovesInvexes()
     var random = Math.floor(Math.random() * index.length);
     var randblock = index[random];
     allblock[randblock].innerHTML = "O";
     goo = 0;
     isGameOver()
 }

 function restart() {
     count = 0;
     goo = 0;
     game.innerHTML = "";
     for (let i = 0; i < 9; i++) {
         game.innerHTML += '<div class="block"></div>';
     }
 }

// false - game running
// true - game over
 function isGameOver() {
     var rez = check();
     var index = getPossibleMovesInvexes()
     if (!!rez) {
         switch (rez) {
             case 1:
                 goo = 1;
                 game.innerHTML = "Победили крестики!";
                 break;
             case 2:
                 goo = 1;
                 game.innerHTML = "Победили нолики!";
                 break;
         }
     } else {
         if (index.length == 0) {
             goo = 1;
             game.innerHTML = "Ничья!";
         }
     }
     if (!!rez || index.length == 0) {
         setTimeout(restart, 3000);
         return true
     }
     return false
 }


 function check() {
     var combo = [
         [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 i = 0; i < combo.length; i++) {
         if (allblock[combo[i][0]].innerHTML == "X" && allblock[combo[i][1]].innerHTML == "X" && allblock[combo[i][2]].innerHTML == "X") return 1;
         if (allblock[combo[i][0]].innerHTML == "O" && allblock[combo[i][1]].innerHTML == "O" && allblock[combo[i][2]].innerHTML == "O") return 2;
     }
 }

 game.onclick = function(e) {
     if (goo == 0) {
         if (e.target.innerHTML == "") {
             e.target.innerHTML = "X";
             goo = 1;
             if (!isGameOver()) {
                setTimeout(computerMove, 1000);
             }
         }
     }
 }

Посмотри примеры того как другие подошли к решению этой же проблемы



И от себя - когда пишешь задачу, удобно разбивать ее на функции-шаги, и из них уже “собирать” все поведение решения. Таким образом ты решаешь одну изолированную задачу в рамках каждой функции. По сути я этим и занимался - придумывал такие функции-кусочки из которых без глобальных переделок твоего кода можно достичь ожидаемого результата. Этот подход называется “декомпозиция”.


#3

да, спасибо за помощь. про декомпозицию я знаю. вторую проблему уже сам решил. просто добавил событие onmouseup после того как поставил “X” и сначала пускал ход компа, а потом делал проверку