Собираем игру «Найди пару» на HTML и JS
medium

Собираем игру «Найди пару» на HTML и JS

Прокачай свой мозг

В разных компаниях эта игра называется по-разному: «Мемори», «Найди пару» или «Мемо», но суть одна:

  1. Есть стопка карточек, где у каждой картинки есть такая же пара.
  2. Эти карточки раскладываются картинками вниз на игровом поле.
  3. Игроки по очереди переворачивают любые две карточки.
  4. Если картинки совпали — игрок забирает их себе и повторяет ход. 
  5. Если не совпали — переворачивает их назад на то же место и ход переходит к другому игроку.
  6. Побеждает тот, кто соберёт больше пар.

Этим мы сегодня и будем заниматься.

Собираем игру «Найди пару» на HTML и JS

Что делаем

Чтобы проект не превратился в огромный лонгрид, сделаем всё поэтапно. Сегодня будет самое начало:

  • мы сделаем игровое поле; 
  • добавим карточки;
  • и настроим механизм самой игры, чтобы их можно было переворачивать и угадывать пары.

Остальное сделаем в другой раз, этого уже будет достаточно для полноценной игры. Для реализации нам понадобятся стандартные для веба инструменты: HTML, CSS и JavaScript. Игра будет работать в браузере, на мобильниках тоже пойдёт, если высота экрана будет 500 пикселей или больше.

Готовим HTML-файл

Чтобы мы могли управлять всеми карточками, будем создавать их в скрипте — так мы сможем сразу добавить их в память и получить над ними полный контроль. Поэтому всё, что у нас будет на странице, — размеченные блоки для будущего контента. 

Ещё мы сразу добавим модальное окно с поздравлениями — то, что увидит игрок после победы. Там будет два элемента — поздравительный текст и кнопка с предложением сыграть ещё. Мы всё это скроем с помощью стилей, а пока разместим всё на странице.

Также нам понадобится:

  • Нормализатор CSS — он сделает так, чтобы страница выглядела одинаково во всех браузерах.
  • Библиотека jQuery — с её помощью мы получим доступ ко всем элементам на странице. Можно и без неё, но с ней проще.
  • Своя таблица стилей — её мы сделаем позже, пока просто подключим.

Создаём новый файл index.html и добавляем туда сразу весь код страницы:

<!DOCTYPE html>
<html lang="ru" >
<head>
  <meta charset="UTF-8">
  <title>Найди пару</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- подключаем нормализатор CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
	<!-- подключаем свои стили -->
	<link rel="stylesheet" href="./style.css">

</head>
<body>
	<!-- общий блок для всего -->
	<div class="wrap">
		<!-- блок с игрой -->
		<div class="game"></div>
		<!-- модальное окно, которое появится после сбора всех пар -->
		<div class="modal-overlay">
			<div class="modal">
				<!-- поздравительная надпись -->
				<h2 class="winner">Победа!</h2>
				<!-- кнопка перезапуска игры -->
				<button class="restart">Сыграем ещё?</button>
			</div>
		</div>
  </div>

	  <!-- подключаем jQuery -->
	<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
	<!-- и наш скрипт -->
	<script  src="./script.js"></script>

</body>
</html>

У нас пока ничего нет из оформления, поэтому пока на странице мы увидим только текст и кнопку:

Настраиваем модальное окно

Раз у нас уже есть окно, его можно сразу настроить — добавить на страницу нужные стили и посмотреть, как будет выглядеть сообщение о победе.Создаём файл style.css и добавляем туда сначала общие настройки для всей страницы:

/* для всех элементов ограничиваем их размеры размерами блока */
* {
  box-sizing: border-box;
}

/* общие настройки страницы */
html, body {
  height: 100%;
}

/* ставим тёмный фон и растягиваем на всю высоту */
body {
  background: black;
  min-height: 100%;
  font-family: "Arial", sans-serif;
}

Теперь оформим модальное окно:

  1. Используем блок modal-overlay, чтобы затемнить всю страницу с её содержимым.
  2. Поверх него в блоке modal покажем наше окно с сообщением о победе.
  3. Отдельно в стилях настроим внешний вид текста и кнопки.

Добавим этот код в файл со стилями:

/* настройки затемнения при выводе модального окна */
.modal-overlay {
  /* затемняем экран */
  background: rgba(0, 0, 0, 0.8);
  /* располагаем окно по центру экрана */
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

/* настройки модального окна */
.modal {
  position: relative;
  width: 500px;
  height: 300px;
  max-height: 90%;
  max-width: 90%;
  min-height: 380px;
  margin: 0 auto;
  background: white;
  top: 50%;
  transform: translateY(-50%);
  padding: 30px 10px;
}

/* настройки шрифта сообщения о победе */
.modal .winner {
  font-size: 80px;
  text-align: center;
  color: #4d4d4d;
  text-shadow: 0px 3px 0 black;
}

/* если ширина окна маленькая, делаем шрифт поменьше */
@media (max-width: 480px) {
  .modal .winner {
    font-size: 60px;
  }
}

/* настройки кнопки перезапуска игры */
.modal .restart {
  margin: 30px auto;
  padding: 20px 30px;
  display: block;
  font-size: 30px;
  border: none;
  background: #4d4d4d;
  background: linear-gradient(#4d4d4d, #222);
  border: 1px solid #222;
  border-radius: 5px;
  color: white;
  text-shadow: 0px 1px 0 black;
  cursor: pointer;
}

/* меняем фон при наведении мышки на кнопку */
.modal .restart:hover {
  background: linear-gradient(#222, black);
}

/* выравниваем надписи на модальном окне по центру */
.modal .message {
  text-align: center;
}

Окно готово, но на старте оно не нужно, поэтому скроем его: добавим display: none; в оба первых блока. Это скроет окно, но всё оформление останется — как только окно нам понадобится, мы уберём это свойство через скрипт.

Создаём карточки

Теперь нам понадобится скрипт. Создаём файл script.js и добавляем в него пустую функцию — вся работа будет происходить внутри неё:

(function(){
})();

Так как у нас журнал про технологии и программирование, для карточек мы будем использовать логотипы популярных языков, программ и технологий. Для этого сходим в Википедию, возьмём оттуда ссылки на логотипы и используем их для создания массива с карточками. Этот массив, как и всё остальное, положим внутрь нашей основной функции:

// весь скрипт — это одна большая функция
(function(){
	
	// карточки
	var cards = [
		{	
			// название
			name: "php",
			// адрес картинки
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/PHP-logo.svg/297px-PHP-logo.svg.png",
			// порядковый номер пары
			id: 1,
		},
		{
			name: "css3",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/CSS3_logo.svg/160px-CSS3_logo.svg.png",
			id: 2
		},
		{
			name: "html5",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/160px-HTML5_logo_and_wordmark.svg.png",
			id: 3
		},
		{
			name: "jquery",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fd/JQuery-Logo.svg/440px-JQuery-Logo.svg.png",
			id: 4
		}, 
		{
			name: "javascript",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/99/Unofficial_JavaScript_logo_2.svg/160px-Unofficial_JavaScript_logo_2.svg.png",
			id: 5
		},
		{
			name: "node",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Node.js_logo.svg/262px-Node.js_logo.svg.png",
			id: 6
		},
		{
			name: "photoshop",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/af/Adobe_Photoshop_CC_icon.svg/164px-Adobe_Photoshop_CC_icon.svg.png",
			id: 7
		},
		{
			name: "python",
			img: "https://www.python.org/static/img/python-logo@2x.png",
			id: 8
		},
		{
			name: "rails",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Ruby_On_Rails_Logo.svg/425px-Ruby_On_Rails_Logo.svg.png",
			id: 9
		},
		{
			name: "sass",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Sass_Logo_Color.svg/213px-Sass_Logo_Color.svg.png",
			id: 10
		},
		{
			name: "sublime",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/79/Breezeicons-apps-48-sublime-text.svg/160px-Breezeicons-apps-48-sublime-text.svg.png",
			id: 11
		},
		{
			name: "wordpress",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/20/WordPress_logo.svg/440px-WordPress_logo.svg.png",
			id: 12
		},
	];

})();

На экране пока ничего не появится — мы просто запомнили ссылки на картинки и сложили их в массив. Чтобы сформировать сами карточки, нам понадобится объект — переменная, внутри которой можно объявлять и вызывать разные методы. 

Вот что будет происходить в этом объекте:

  1. Получаем доступ ко всем элементам на странице.
  2. Виртуально перемешиваем карточки.
  3. Для каждой карточки динамически создаём HTML-код — с его помощью мы увидим эту карточку на игровом поле.
  4. Получаем доступ ко всем карточкам.
  5. Устанавливаем стартовые значения разных свойств и переменных.
  6. Добавляем всем элементам свою реакцию на нажатие.
  7. Прописываем логику сравнения выбранной пары.
  8. Если пары закончились — показываем победное сообщение.
  9. Если нажали на перезапуск — запускаем игру сначала.

Получается, что за всю логику игры у нас отвечает один объект — мы настраиваем методы и обработку событий, а он дальше сам разбирается, что с этим делать и как реагировать в каждом случае. В этом сила объектно-ориентированного программирования: мы задаём общее поведение, а дальше компьютер решает всё сам.

Для «рубашки» наших карточек возьмём бесплатную фотографию какого-то кода из сервиса Unsplash.

//  объявляем объект, внутри которого будет происходить основная механика игры
var Memory = {

	// создаём карточку
	init: function(cards){
		//  получаем доступ к классам
		this.$game = $(".game");
		this.$modal = $(".modal");
		this.$overlay = $(".modal-overlay");
		this.$restartButton = $("button.restart");
		// собираем из карточек массив — игровое поле
		this.cardsArray = $.merge(cards, cards);
		// перемешиваем карточки
		this.shuffleCards(this.cardsArray);
		// и раскладываем их
		this.setup();
	},

	// как перемешиваются карточки
	shuffleCards: function(cardsArray){
		// используем встроенный метод .shuffle
		this.$cards = $(this.shuffle(this.cardsArray));
	},

	// раскладываем карты
	setup: function(){
		// подготавливаем код с карточками на страницу
		this.html = this.buildHTML();
		// добавляем код в блок с игрой
		this.$game.html(this.html);
		// получаем доступ к сформированным карточкам
		this.$memoryCards = $(".card");
		// на старте мы не ждём переворота второй карточки
		this.paused = false;
		// на старте у нас нет перевёрнутой первой карточки
 		this.guess = null;
 		// добавляем элементам на странице реакции на нажатия
		this.binding();
	},

	// как элементы будут реагировать на нажатия
	binding: function(){
		// обрабатываем нажатие на карточку
		this.$memoryCards.on("click", this.cardClicked);
		// и нажатие на кнопку перезапуска игры
		this.$restartButton.on("click", $.proxy(this.reset, this));
	},

	// что происходит при нажатии на карточку
	cardClicked: function(){
		// получаем текущее состояние родительской переменной
		var _ = Memory;
		// и получаем доступ к карточке, на которую нажали
		var $card = $(this);
		// если карточка уже не перевёрнута и мы не нажимаем на ту же самую карточку второй раз подряд
		if(!_.paused && !$card.find(".inside").hasClass("matched") && !$card.find(".inside").hasClass("picked")){
			// переворачиваем её
			$card.find(".inside").addClass("picked");
			// если мы перевернули первую карточку
			if(!_.guess){
				// то пока просто запоминаем её
				_.guess = $(this).attr("data-id");
			// если мы перевернули вторую и она совпадает с первой
			} else if(_.guess == $(this).attr("data-id") && !$(this).hasClass("picked")){
				// оставляем обе на поле перевёрнутыми и показываем анимацию совпадения
				$(".picked").addClass("matched");
				// обнуляем первую карточку
				_.guess = null;
					// если вторая не совпадает с первой
					} else {
						// обнуляем первую карточку
						_.guess = null;
						// не ждём переворота второй карточки
						_.paused = true;
						// ждём полсекунды и переворачиваем всё обратно
						setTimeout(function(){
							$(".picked").removeClass("picked");
							Memory.paused = false;
						}, 600);
					}
			// если мы перевернули все карточки
			if($(".matched").length == $(".card").length){
				// показываем победное сообщение
				_.win();
			}
		}
	},

	// показываем победное сообщение
	win: function(){
		// не ждём переворота карточек
		this.paused = true;
		// плавно показываем модальное окно с предложением сыграть ещё
		setTimeout(function(){
			Memory.showModal();
			Memory.$game.fadeOut();
		}, 1000);
	},

	// показываем модальное окно
	showModal: function(){
		// плавно делаем блок с сообщением видимым
		this.$overlay.show();
		this.$modal.fadeIn("slow");
	},

	// прячем модальное окно
	hideModal: function(){
		this.$overlay.hide();
		this.$modal.hide();
	},

	// перезапуск игры
	reset: function(){
		// прячем модальное окно с поздравлением
		this.hideModal();
		// перемешиваем карточки
		this.shuffleCards(this.cardsArray);
		// раскладываем их на поле
		this.setup();
		// показываем игровое поле
		this.$game.show("slow");
	},

	// Тасование Фишера–Йетса - https://bost.ocks.org/mike/shuffle/
	shuffle: function(array){
		var counter = array.length, temp, index;
	   	while (counter > 0) {
        	index = Math.floor(Math.random() * counter);
        	counter--;
        	temp = array[counter];
        	array[counter] = array[index];
        	array[index] = temp;
	    	}
	    return array;
	},

	// код, как добавляются карточки на страницу
	buildHTML: function(){
		// сюда будем складывать HTML-код
		var frag = '';
		// перебираем все карточки подряд
		this.$cards.each(function(k, v){
			// добавляем HTML-код для очередной карточки
			frag += '<div class="card" data-id="'+ v.id +'"><div class="inside">\
			<div class="front"><img src="'+ v.img +'"\
			alt="'+ v.name +'" /></div>\
			<div class="back"><img src="https://images.unsplash.com/photo-1576836165612-8bc9b07e7778?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1587&q=80"\
			alt="Codepen" /></div></div>\
			</div>';
		});
		// возвращаем собранный код
		return frag;
	}
};

Чтобы объект «ожил», добавим в самый конец нашей основной функции вызов первого метода объекта:

// запускаем игру
Memory.init(cards);

У нас появилось много всякого разного на странице, но выглядит хаотично, а всё потому, что мы не настроили стили для карточек. Сейчас будем исправлять.

Настраиваем стили карточек

Наша задача сейчас — разместить картинки по карточкам, а сами карточки расположить по сетке 6 × 4. Для этого мы сначала подготовим два блока — общий и блок с карточками:

/* стили основного блока */
.wrap {
  /* устанавливаем относительное позиционирование */
  position: relative;
  /* высота элементов */
  height: 100%;
  /* минимальная высота и отступы */
  min-height: 500px;
  padding-bottom: 20px;
}

/* блок с игрой */
.game {
  /* добавляем трёхмерность для эффекта вращения */
  transform-style: preserve-3d;
  perspective: 500px;
  /* пусть элементы занимают всё доступное им пространство */
  min-height: 100%;
  height: 100%;
}

Теперь сделаем сетку — добавим стили именно для карточек, где укажем их высоту и ширину. Ещё добавим медиазапрос, чтобы на небольших экранах карточки тоже выглядели хорошо:

/* стили карточек */
.card {
  /* параметры расположения, высоты и ширины карточки */
  float: left;
  width: 16.66666%;
  height: 25%;
  /* отступы */
  padding: 5px;
  /* выравнивание по центру */
  text-align: center;
  /* подключаем блочные элементы и перспективу */
  display: block;
  perspective: 500px;
  /* добавляем относительное позиционирование */
  position: relative;
  cursor: pointer;
  z-index: 50;
}

/* настройки размера карт при максимальной ширине экрана 800 пикселей */
@media (max-width: 800px) {
  .card {
    width: 25%;
    height: 16.666%;
  }
}
Собираем игру «Найди пару» на HTML и JS

У нас появилась сетка, но это всё ещё не похоже на нормальные карточки — всё налезает друг на друга и непонятно, как с этим работать. Чтобы это исправить, разделим карточки на лицевую и обратную стороны:

/* обратная сторона карточки */
.card .inside {
/* содержимое занимает весь размер карточки */
  width: 100%;
  height: 100%;
  display: block;
  /* анимация переворачивания */
  transform-style: preserve-3d;
  transition: 0.4s ease-in-out;
  /* у лицевой стороны будет белый фон */
  background: white;
}



/* общие настройки для обеих сторон карточки */
.card .front, .card .back {
  /* рисуем границу */
  border: 1px solid black;
  /* прячем обратную сторону */
  -webkit-backface-visibility: hidden;
          backface-visibility: hidden;
  /* абсолютное позиционирование */
  position: absolute;
  top: 0;
  left: 0;
  /* размеры и отступ */
  width: 100%;
  height: 100%;
  padding: 20px;
}

/* настройки изображения на лицевой и обратной стороне */
.card .front img, .card .back img {
  /* картинка занимает всю ширину */
  max-width: 100%;
  /* отображаем как блочный элемент, без отступов */
  display: block;
  margin: 0 auto;
  max-height: 100%;
}

/* настройки лицевой стороны */
.card .front {
  /* переворачиваем карточку обложкой вверх */
  transform: rotateY(-180deg);
}

/* настройки при максимальной ширине экрана 800 пикселей */
@media (max-width: 800px) {
  .card .front {
    padding: 5px;
  }

  .card .back {
    padding: 10px;
  }
}

  /* запускаем анимацию переворачивания при клике на карточку */
.card .inside.picked, .card .inside.matched {
  transform: rotateY(180deg);
}

Карточки расположились по сетке, переворачиваются и возвращаются назад, если пара не совпала. Единственное, чего нам не хватает, — анимации совпадения. Сделаем так, чтобы при переворачивании пары фон у карточек из неё на мгновение становился зелёным:

/* задаём ключевые кадры анимации совпадения */
@keyframes matchAnim {
  0% {
  /*  зелёный фон */
    background: #bcffcc;
  }
  100% {
  /*  белый фон  */
    backgroud: white;
  }
}

/* и делаем то же самое для движка WebKit */
@-webkit-keyframes matchAnim {
  0% {
    background: #bcffcc;
  }
  100% {
    background: white;
  }
}

  /* анимация совпадения пары */
.card .inside.matched {
  -webkit-animation: 1s matchAnim ease-in-out;
          animation: 1s matchAnim ease-in-out;
  -webkit-animation-delay: 0.4s;
          animation-delay: 0.4s;
}
Собираем игру «Найди пару» на HTML и JS

Поиграть на странице проекта.

<!DOCTYPE html>
<html lang="ru" >
<head>
  <meta charset="UTF-8">
  <title>Найди пару</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- подключаем нормализатор CSS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
	<!-- подключаем свои стили -->
	<link rel="stylesheet" href="./style.css">

</head>
<body>
	<!-- общий блок для всего -->
	<div class="wrap">
		<!-- блок с игрой -->
		<div class="game"></div>
		<!-- модальное окно, которое появится после сбора всех пар -->
		<div class="modal-overlay">
			<div class="modal">
				<!-- поздравительная надпись -->
				<h2 class="winner">Победа!</h2>
				<!-- кнопка перезапуска игры -->
				<button class="restart">Сыграем ещё?</button>
			</div>
		</div>
  </div>

	  <!-- подключаем jQuery -->
	<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
	<!-- и наш скрипт -->
	<script  src="./script.js"></script>

</body>
</html>

/* для всех элементов ограничиваем их размеры размерами блока */
* {
  box-sizing: border-box;
}

/* общие настройки страницы */
html, body {
  height: 100%;
}

/* ставим тёмный фон и растягиваем на всю высоту */
body {
  background: black;
  min-height: 100%;
  font-family: "Arial", sans-serif;
}

/* стили основного блока */
.wrap {
  /* устанавливаем относительное позиционирование */
  position: relative;
  /* высота элементов */
  height: 100%;
  /* минимальная высота и отступы */
  min-height: 500px;
  padding-bottom: 20px;
}

/* блок с игрой */
.game {
  /* добавляем трёхмерность для эффекта вращения */
  transform-style: preserve-3d;
  perspective: 500px;
  /* пусть элементы занимают всё доступное им пространство */
  min-height: 100%;
  height: 100%;
}



/* стили карточек */
.card {
  /* параметры расположения, высоты и ширины карточки */
  float: left;
  width: 16.66666%;
  height: 25%;
  /* отступы */
  padding: 5px;
  /* выравнивание по центру */
  text-align: center;
  /* подключаем блочные элементы и перспективу */
  display: block;
  perspective: 500px;
  /* добавляем относительное позиционирование */
  position: relative;
  cursor: pointer;
  z-index: 50;
}

/* настройки размера карт при максимальной ширине экрана 800 пикселей */
@media (max-width: 800px) {
  .card {
    width: 25%;
    height: 16.666%;
  }
}

/* обратная сторона карточки */
.card .inside {
/* содержимое занимает весь размер карточки */
  width: 100%;
  height: 100%;
  display: block;
  /* анимация переворачивания */
  transform-style: preserve-3d;
  transition: 0.4s ease-in-out;
  /* у лицевой стороны будет белый фон */
  background: white;
}



/* общие настройки для обеих сторон карточки */
.card .front, .card .back {
  /* рисуем границу */
  border: 1px solid black;
  /* прячем обратную сторону */
  -webkit-backface-visibility: hidden;
          backface-visibility: hidden;
  /* абсолютное позиционирование */
  position: absolute;
  top: 0;
  left: 0;
  /* размеры и отступ */
  width: 100%;
  height: 100%;
  padding: 20px;
}

/* настройки изображения на лицевой и обратной стороне */
.card .front img, .card .back img {
  /* картинка занимает всю ширину */
  max-width: 100%;
  /* отображаем как блочный элемент, без отступов */
  display: block;
  margin: 0 auto;
  max-height: 100%;
}

/* настройки лицевой стороны */
.card .front {
  /* переворачиваем карточку обложкой вверх */
  transform: rotateY(-180deg);
}

/* настройки при максимальной ширине экрана 800 пикселей */
@media (max-width: 800px) {
  .card .front {
    padding: 5px;
  }

  .card .back {
    padding: 10px;
  }
}

  /* запускаем анимацию переворачивания при клике на карточку */
.card .inside.picked, .card .inside.matched {
  transform: rotateY(180deg);
}

/* задаём ключевые кадры анимации совпадения */
@keyframes matchAnim {
  0% {
  /*  зелёный фон */
    background: #bcffcc;
  }
  100% {
  /*  белый фон  */
    backgroud: white;
  }
}

/* и делаем то же самое для движка WebKit */
@-webkit-keyframes matchAnim {
  0% {
    background: #bcffcc;
  }
  100% {
    background: white;
  }
}

  /* анимация совпадения пары */
.card .inside.matched {
  -webkit-animation: 1s matchAnim ease-in-out;
          animation: 1s matchAnim ease-in-out;
  -webkit-animation-delay: 0.4s;
          animation-delay: 0.4s;
}

/* настройки затемнения при выводе модального окна */
.modal-overlay {
  /* на старте его не видно */
  display: none;
  /* затемняем экран */
  background: rgba(0, 0, 0, 0.8);
  /* располагаем окно по центру экрана */
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

/* настройки модального окна */
.modal {
  display: none;
  position: relative;
  width: 500px;
  height: 300px;
  max-height: 90%;
  max-width: 90%;
  min-height: 380px;
  margin: 0 auto;
  background: white;
  top: 50%;
  transform: translateY(-50%);
  padding: 30px 10px;
}

/* настройки шрифта сообщения о победе */
.modal .winner {
  font-size: 80px;
  text-align: center;
  color: #4d4d4d;
  text-shadow: 0px 3px 0 black;
}

/* если ширина окна маленькая, делаем шрифт поменьше */
@media (max-width: 480px) {
  .modal .winner {
    font-size: 60px;
  }
}

/* настройки кнопки перезапуска игры */
.modal .restart {
  margin: 30px auto;
  padding: 20px 30px;
  display: block;
  font-size: 30px;
  border: none;
  background: #4d4d4d;
  background: linear-gradient(#4d4d4d, #222);
  border: 1px solid #222;
  border-radius: 5px;
  color: white;
  text-shadow: 0px 1px 0 black;
  cursor: pointer;
}

/* меняем фон при наведении мышки на кнопку */
.modal .restart:hover {
  background: linear-gradient(#222, black);
}

/* выравниваем надписи на модальном окне по центру */
.modal .message {
  text-align: center;
}

// Memory Game
// © 2014 Nate Wiley
// License -- MIT

// весь скрипт — это одна большая функция
(function(){
	
	//  объявляем объект, внутри которого будет происходить основная механика игры
	var Memory = {

		// создаём карточку
		init: function(cards){
			//  получаем доступ к классам
			this.$game = $(".game");
			this.$modal = $(".modal");
			this.$overlay = $(".modal-overlay");
			this.$restartButton = $("button.restart");
			// собираем из карточек массив — игровое поле
			this.cardsArray = $.merge(cards, cards);
			// перемешиваем карточки
			this.shuffleCards(this.cardsArray);
			// и раскладываем их
			this.setup();
		},

		// как перемешиваются карточки
		shuffleCards: function(cardsArray){
			// используем встроенный метод .shuffle
			this.$cards = $(this.shuffle(this.cardsArray));
		},

		// раскладываем карты
		setup: function(){
			// подготавливаем код с карточками на страницу
			this.html = this.buildHTML();
			// добавляем код в блок с игрой
			this.$game.html(this.html);
			// получаем доступ к сформированным карточкам
			this.$memoryCards = $(".card");
			// на старте мы не ждём переворота второй карточки
			this.paused = false;
			// на старте у нас нет перевёрнутой первой карточки
     		this.guess = null;
     		// добавляем элементам на странице реакции на нажатия
			this.binding();
		},

		// как элементы будут реагировать на нажатия
		binding: function(){
			// обрабатываем нажатие на карточку
			this.$memoryCards.on("click", this.cardClicked);
			// и нажатие на кнопку перезапуска игры
			this.$restartButton.on("click", $.proxy(this.reset, this));
		},

		// что происходит при нажатии на карточку
		cardClicked: function(){
			// получаем текущее состояние родительской переменной
			var _ = Memory;
			// и получаем доступ к карточке, на которую нажали
			var $card = $(this);
			// если карточка уже не перевёрнута и мы не нажимаем на ту же самую карточку второй раз подряд
			if(!_.paused && !$card.find(".inside").hasClass("matched") && !$card.find(".inside").hasClass("picked")){
				// переворачиваем её
				$card.find(".inside").addClass("picked");
				// если мы перевернули первую карточку
				if(!_.guess){
					// то пока просто запоминаем её
					_.guess = $(this).attr("data-id");
				// если мы перевернули вторую и она совпадает с первой
				} else if(_.guess == $(this).attr("data-id") && !$(this).hasClass("picked")){
					// оставляем обе на поле перевёрнутыми и показываем анимацию совпадения
					$(".picked").addClass("matched");
					// обнуляем первую карточку
					_.guess = null;
						// если вторая не совпадает с первой
						} else {
							// обнуляем первую карточку
							_.guess = null;
							// не ждём переворота второй карточки
							_.paused = true;
							// ждём полсекунды и переворачиваем всё обратно
							setTimeout(function(){
								$(".picked").removeClass("picked");
								Memory.paused = false;
							}, 600);
						}
				// если мы перевернули все карточки
				if($(".matched").length == $(".card").length){
					// показываем победное сообщение
					_.win();
				}
			}
		},

		// показываем победное сообщение
		win: function(){
			// не ждём переворота карточек
			this.paused = true;
			// плавно показываем модальное окно с предложением сыграть ещё
			setTimeout(function(){
				Memory.showModal();
				Memory.$game.fadeOut();
			}, 1000);
		},

		// показываем модальное окно
		showModal: function(){
			// плавно делаем блок с сообщением видимым
			this.$overlay.show();
			this.$modal.fadeIn("slow");
		},

		// прячем модальное окно
		hideModal: function(){
			this.$overlay.hide();
			this.$modal.hide();
		},

		// перезапуск игры
		reset: function(){
			// прячем модальное окно с поздравлением
			this.hideModal();
			// перемешиваем карточки
			this.shuffleCards(this.cardsArray);
			// раскладываем их на поле
			this.setup();
			// показываем игровое поле
			this.$game.show("slow");
		},

		// Тасование Фишера–Йетса - https://bost.ocks.org/mike/shuffle/
		shuffle: function(array){
			var counter = array.length, temp, index;
		   	while (counter > 0) {
	        	index = Math.floor(Math.random() * counter);
	        	counter--;
	        	temp = array[counter];
	        	array[counter] = array[index];
	        	array[index] = temp;
		    	}
		    return array;
		},

		// код, как добавляются карточки на страницу
		buildHTML: function(){
			// сюда будем складывать HTML-код
			var frag = '';
			// перебираем все карточки подряд
			this.$cards.each(function(k, v){
				// добавляем HTML-код для очередной карточки
				frag += '<div class="card" data-id="'+ v.id +'"><div class="inside">\
				<div class="front"><img src="'+ v.img +'"\
				alt="'+ v.name +'" /></div>\
				<div class="back"><img src="https://images.unsplash.com/photo-1576836165612-8bc9b07e7778?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1587&q=80"\
				alt="Codepen" /></div></div>\
				</div>';
			});
			// возвращаем собранный код
			return frag;
		}
	};

	// карточки
	var cards = [
		{	
			// название
			name: "php",
			// адрес картинки
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/PHP-logo.svg/297px-PHP-logo.svg.png",
			// порядковый номер пары
			id: 1,
		},
		{
			name: "css3",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/CSS3_logo.svg/160px-CSS3_logo.svg.png",
			id: 2
		},
		{
			name: "html5",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/160px-HTML5_logo_and_wordmark.svg.png",
			id: 3
		},
		{
			name: "jquery",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fd/JQuery-Logo.svg/440px-JQuery-Logo.svg.png",
			id: 4
		}, 
		{
			name: "javascript",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/99/Unofficial_JavaScript_logo_2.svg/160px-Unofficial_JavaScript_logo_2.svg.png",
			id: 5
		},
		{
			name: "node",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Node.js_logo.svg/262px-Node.js_logo.svg.png",
			id: 6
		},
		{
			name: "photoshop",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/af/Adobe_Photoshop_CC_icon.svg/164px-Adobe_Photoshop_CC_icon.svg.png",
			id: 7
		},
		{
			name: "python",
			img: "https://www.python.org/static/img/python-logo@2x.png",
			id: 8
		},
		{
			name: "rails",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Ruby_On_Rails_Logo.svg/425px-Ruby_On_Rails_Logo.svg.png",
			id: 9
		},
		{
			name: "sass",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Sass_Logo_Color.svg/213px-Sass_Logo_Color.svg.png",
			id: 10
		},
		{
			name: "sublime",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/79/Breezeicons-apps-48-sublime-text.svg/160px-Breezeicons-apps-48-sublime-text.svg.png",
			id: 11
		},
		{
			name: "wordpress",
			img: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/20/WordPress_logo.svg/440px-WordPress_logo.svg.png",
			id: 12
		},
	];
    
	// запускаем игру
	Memory.init(cards);


})();

Что дальше

Сейчас у нас всё работает, но как-то неопрятно:

  • картинки не по центру;
  • непонятно, как играть нескольким игрокам;
  • нет подсчёта пар.

Исправим это в следующей версии. Подпишитесь, чтобы не пропустить продолжени.

Код:

Nate Wiley

Художник:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Кирилл Климентьев

Соцсети:

Виталий Вебер

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
medium