Игра крестики — нолики сама по себе не сложная.

Но для написания ее на JavaScript нужно более или менее понимать, как работает map например, так же можно использовать reduce для проверки на победу.

Сама логика простая, есть некоторое кол-во вариантов побед

const winnerLines = [ [0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6] ];

Который надо проверять при каждом ходе игрока. Вроде просто, но не совсем. Я искал в сети, как кто подходит к этому вопросу, решение в основном было — проверка «в лоб», через switch или if else if.

Я воспользовался немного иным способом, в функциях checkWinner
и winLine.

checkWinner проверяет кто ходит в данный момент, далее собирает массив с ходами конкретного символа

Например ходим «х», массив соответствует номерам ячеек где находится «х».

Далее используя функцию some обходим массив всех победных комбинаций — winnerLines. В этом цикле some передаем в функцию winLine массив с ходами текущего символа (или «х» или «о») и конкретную комбинацию из массива winnerLines.

winLine проверяет каждую ячейку комбинации через includes и если есть совпадение то вернет true, если совпадения нет, то вернет false. Я пошел через отрицания совпадения.

/*
	проверка конкретной комбинации из winnerLines
*/
function winLine (line, movesIndex) {
	for (let i = 0; i < line.length; i++) {
		if (!movesIndex.includes(line[i])) return false;
	}
	/*
		передаем победную комбинацию для изменения цвета
	*/
	changeCellColor(line);
	return true;
}

Тоже самое только на REACT ссылка