я пытаюсь написать крестики-нолики на веб-странице.
вот 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, дальнейшее присвоение другим блокам должно быть недоступно до того, как “ИИ сделает свой ход”. но этого не происходит. почему?
вторая: когда ИИ побеждает, объявления победителя не происходит до клика кнопкой. я понимаю, что я как-то должен вынести проверку победителя за пределы клика. как это правильно сделать?
буду благодарен, если подскажете, как решить эти две проблемы
Дело тут. goo становится равной 0 до того как походит компьютер. Ты устанавливаешь таймаут и сразу же обнуляешь goo. При следующем быстром клике проверка if (goo == 0) { снова дает true и блок внутри условия выполняется.
if (goo == 0) {
//...
function rand() {
allblock[randblock].innerHTML = "O";
}
setTimeout(rand, 1000);
goo = 0;
//...
Решение - обнулять goo после хода компа (т.е в функции-коллбеке таймаута)
Без рефакторинга не обойтись. Мое решение не самое лучшее с точки зрения конченого продукта. Его ценность в том что оно пытается применить “правильные” техники и при этом использовать уже знакомый тебе код.
Что изменилось:
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);
}
}
}
}
И от себя - когда пишешь задачу, удобно разбивать ее на функции-шаги, и из них уже “собирать” все поведение решения. Таким образом ты решаешь одну изолированную задачу в рамках каждой функции. По сути я этим и занимался - придумывал такие функции-кусочки из которых без глобальных переделок твоего кода можно достичь ожидаемого результата. Этот подход называется “декомпозиция”.
да, спасибо за помощь. про декомпозицию я знаю. вторую проблему уже сам решил. просто добавил событие onmouseup после того как поставил “X” и сначала пускал ход компа, а потом делал проверку