/**
* @description Базовый массив картинок для карточек в игре
*/
const images = ['img/cat1.png', 'img/cat2.png',
'img/cat3.png', 'img/cat4.png', 'img/cat5.png',
'img/cat6.png', 'img/cat7.png', 'img/cat8.png',
'img/cat9.png', 'img/cat10.png', 'img/cat11.png',
'img/cat12.png', 'img/cat13.png', 'img/cat14.png', 'img/cat15.png'];
/**
* @description Состояние игры
*/
let game = {
cardCount: 0,
isStarted: false,
openCards: [], // Открытые карточки для проверки совпадений.
matchesNumber: 0, // Количество совпавших карточек.
result: { min: "00", sec: "00", moves: 0 },
timerHandle: undefined,
delayTime: 2000
}
/**
* @description Экземляр Vue, представляющий меню игры
*/
let menuVm = new Vue({
el: "#menu",
data: game.result
})
/**
* @description Экземляр Vue, представляющий данные об окончании игры
*/
let victoryModalVm = new Vue({
el: "#victory-modal",
data: game.result
})
/**
* @description Кнопка перезапуска игры
*/
let restartButton = document.getElementById('restart');
/**
* @description Кнопка паузы
*/
let pauseButton = document.getElementById('pause');
/**
* @description Игровая сетка с карточками
*/
let cardGrid = document.getElementById('card-grid');
/**
* @description Модальное наложение для сообщений о начале и об окончании игры
*/
let modalOverlay = document.getElementById("modal-overlay");
/**
* @description Модальное сообщение для начала игры
*/
let startGameModal = document.getElementById("start-game-modal");
/**
* @description Модальное сообщение при окончании игры
*/
let victoryModal = document.getElementById("victory-modal");
/**
* Создаёт карточку для игровой сетки
* @param {number} number - индекс в массиве картинок для карточки
* @returns {object} Сгенерированная блок карточки со специальным стилем
*/
function generateCard(number){
let card = document.createElement('div');
card.classList.add("card-wrapper");
card.innerHTML = '<div class="card close"><div class="back">' +
'</div><img class="front" src="'+images[number]+'" ' +
'alt="'+(number+1)+'" style="user-select: none; ' +
'-webkit-user-drag: none;"></div></div>';
return card;
}
/**
* @description Создаёт пары карточек и добавляет их на игровую сетку
*/
function initCards(){
// Получаем размер игровой сетки и рассчитываем общее количество карточек.
let gridSizeElements = document.getElementsByClassName("grid-size");
let row, column = 0;
for (let i = 0; i < gridSizeElements.length; i++) {
if(gridSizeElements[i].classList.contains("selected")){
row = Number.parseInt(gridSizeElements[i].innerHTML[0]);
column = Number.parseInt(gridSizeElements[i].innerHTML[2]);
game.cardCount = row * column;
}
}
// Создаём массив последовательно идущих попарных картинок.
let initialCards = [];
for (let i = 0; i < game.cardCount / 2; i++){
initialCards.push(generateCard(i));
initialCards.push(generateCard(i));
}
// Перемешиваем картинки.
let cards = [];
while (initialCards.length > 0){
let ranNum = Math.floor(Math.random() * initialCards.length);
cards.push(initialCards[ranNum]);
if(ranNum > -1){
initialCards.splice(ranNum, 1);
}
}
// Если игровая сетка была не пустая, удаляем все карточки.
while (cardGrid.firstChild) {
cardGrid.removeChild(cardGrid.firstChild);
}
// Добавляем созданные карточки на игровую сетку.
for(let i = 0; i < cards.length; i++){
cardGrid.appendChild(cards[i]);
}
// Рассчитываем высоту и длину карточек.
// Учитываем внутренний отступ у игровой сетки (card-grid.style.padding).
let padding = 15 * 2; // 15px * 2 отступа (слева/справа или сверху/снизу).
// Учитываем внешний отступ каждой карточки.
// (margin = 5px * (column - 1) расстояний между карточками)
let margin = 5 * (column - 1);
let indents = padding + margin;
let cardWrappers = document.getElementsByClassName("card-wrapper");
for (var i = 0; i < cardWrappers.length; i++) {
cardWrappers[i].style.width = "calc((100% - "+indents+"px) / "
+column+")";
cardWrappers[i].style.height = "calc((100% - "+indents+"px) / "+row+")";
}
}
/**
* @description Показывает или скрывает все карточки одновременно
*/
function changeAllCardsState(){
let cards = document.getElementsByClassName('card');
for (var i = 0; i < cards.length; i++) {
cards[i].classList.toggle("close");
}
}
/**
* @description Показывает все карточки на на время для запоминания
*/
function showAll(){
changeAllCardsState();
setTimeout(changeAllCardsState, game.delayTime);
}
/**
* @description Запускает таймер игры и делает доступными кнопку начала игры
*/
function startTimer(){
game.isStarted = true;
restartButton.disabled = false;
pauseButton.disabled = false;
pauseButton.style.backgroundImage = "url(\"img/pause.png\")";
let sec = 0;
let min = 0;
game.timerHandle = setInterval(function() {
if (game.isStarted) {
sec++;
if (sec > 60){
min++;
sec = 0;
}
game.result.min = (min < 10) ? "0" + min : min;
game.result.sec = (sec < 10) ? "0" + sec : sec;
}
}, 1000);
}
/**
* @description Изменяет состяние игры: пауза или продолжение игры.
*/
pauseButton.addEventListener("click", function(){
game.isStarted = !game.isStarted;
pauseButton.style.backgroundImage = game.isStarted ?
"url(\"img/pause.png\")" : "url(\"img/play.png\")";
});
/**
* @description Начинает новую игру
*/
function startGame(){
modalOverlay.style.display = "none";
restartButton.disabled = true;
pauseButton.disabled = true;
showAll(); // Показываем карточки на 2 секунды для запоминания.
// Запускаем таймер игры после того,
// как будут открыты и закрыты карточки (после двух секунд).
setTimeout(startTimer, game.delayTime);
}
/**
* @description Сброс настроек игры
*/
function resetGame(){
game.isStarted = false;
game.openCards = []; // Создаём новый пустой массив с открытыми карточками.
game.matchesNumber = 0; // Обнуляем количество совпадений.
game.result.moves = 0;
game.result.min = "00";
game.result.sec = "00";
pauseButton.style.backgroundImage = "url(\"img/play.png\")";
initCards();
if (game.timerHandle != null) {
clearInterval(game.timerHandle); // Обнуляем счётчик времени.
}
}
/**
* @description Проверка на совпадение карточек
*/
function checkForMatches() {
setTimeout(function(){
if (game.openCards[0].innerHTML != game.openCards[1].innerHTML){
game.openCards[0].classList.toggle("close");
game.openCards[1].classList.toggle("close");
} else {
game.matchesNumber += 2;
}
game.openCards = [];
game.result.moves++;
if (game.matchesNumber == game.cardCount){
clearInterval(game.timerHandle); // Обнуляем счётчик времени.
game.isStarted = false;
showEndGameModal();
}
}, 1000);
}
/**
* @description Отображение модального окна для начала игры
*/
function showStartGameModal(){
resetGame();
modalOverlay.style.display = "flex";
startGameModal.style.display = "block";
victoryModal.style.display = "none";
}
/**
* @description Отображение модального окна с поздравлениями по окончанию игры
*/
function showEndGameModal(){
modalOverlay.style.display = "flex";
victoryModal.style.display = "block";
startGameModal.style.display = "none";
}
/**
* @description Перезапуск игры во время продолжения игры
*/
restartButton.addEventListener("click", function(){
showStartGameModal();
});
/**
* @description Начало новой игры после окончания предыдущей
*/
document.getElementById("play-again").addEventListener("click", function(){
showStartGameModal();
});
/**
* @description Инициализация карточек, их отрисовка и запуск новой игры
*/
document.getElementById("start-game").addEventListener("click", function(){
initCards();
startGame();
});
/**
* @description Выделяет нажатую кнопку с размером игрового поля.
*/
document.getElementById("size-container").addEventListener("click",
function(e){
if(e.target.classList.contains("grid-size")){
let elements = document.getElementsByClassName("grid-size");
// Убираем подсветку ранее выделенной кнопки.
for(let i = 0; i < elements.length; i++){
if(elements[i].classList.contains("selected")){
elements[i].classList.remove("selected");
}
}
// Подсвечиваем выбранную кнопку.
e.target.classList.add("selected");
}
});
/**
* @description По клику на игровую карточку изменяет её состояние:
открывает или скрывает, выполняя проверку на совпадения.
*/
cardGrid.addEventListener("click", function(e){
if (!game.isStarted) return;
// Если карточка закрыта и количество открытых карточек меньше двух,
// открываем данную карточку.
if (e.target.parentElement.classList.contains("card") &&
e.target.parentElement.classList.contains("close") &&
game.openCards.length < 2){
const card = e.target.parentElement;
card.classList.toggle("close");
game.openCards.push(card);
}
if (game.openCards.length == 2){
checkForMatches();
}
});
/**
* @description Показывает модальное окно для начала новой игры
*/
showStartGameModal();