В разных компаниях эта игра называется по-разному: «Мемори», «Найди пару» или «Мемо», но суть одна:
- Есть стопка карточек, где у каждой картинки есть такая же пара.
- Эти карточки раскладываются картинками вниз на игровом поле.
- Игроки по очереди переворачивают любые две карточки.
- Если картинки совпали — игрок забирает их себе и повторяет ход.
- Если не совпали — переворачивает их назад на то же место и ход переходит к другому игроку.
- Побеждает тот, кто соберёт больше пар.
Этим мы сегодня и будем заниматься.
Что делаем
Чтобы проект не превратился в огромный лонгрид, сделаем всё поэтапно. Сегодня будет самое начало:
- мы сделаем игровое поле;
- добавим карточки;
- и настроим механизм самой игры, чтобы их можно было переворачивать и угадывать пары.
Остальное сделаем в другой раз, этого уже будет достаточно для полноценной игры. Для реализации нам понадобятся стандартные для веба инструменты: 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;
}
Теперь оформим модальное окно:
- Используем блок
modal-overlay
, чтобы затемнить всю страницу с её содержимым. - Поверх него в блоке
modal
покажем наше окно с сообщением о победе. - Отдельно в стилях настроим внешний вид текста и кнопки.
Добавим этот код в файл со стилями:
/* настройки затемнения при выводе модального окна */
.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
},
];
})();
На экране пока ничего не появится — мы просто запомнили ссылки на картинки и сложили их в массив. Чтобы сформировать сами карточки, нам понадобится объект — переменная, внутри которой можно объявлять и вызывать разные методы.
Вот что будет происходить в этом объекте:
- Получаем доступ ко всем элементам на странице.
- Виртуально перемешиваем карточки.
- Для каждой карточки динамически создаём HTML-код — с его помощью мы увидим эту карточку на игровом поле.
- Получаем доступ ко всем карточкам.
- Устанавливаем стартовые значения разных свойств и переменных.
- Добавляем всем элементам свою реакцию на нажатие.
- Прописываем логику сравнения выбранной пары.
- Если пары закончились — показываем победное сообщение.
- Если нажали на перезапуск — запускаем игру сначала.
Получается, что за всю логику игры у нас отвечает один объект — мы настраиваем методы и обработку событий, а он дальше сам разбирается, что с этим делать и как реагировать в каждом случае. В этом сила объектно-ориентированного программирования: мы задаём общее поведение, а дальше компьютер решает всё сам.
Для «рубашки» наших карточек возьмём бесплатную фотографию какого-то кода из сервиса 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%;
}
}
У нас появилась сетка, но это всё ещё не похоже на нормальные карточки — всё налезает друг на друга и непонятно, как с этим работать. Чтобы это исправить, разделим карточки на лицевую и обратную стороны:
/* обратная сторона карточки */
.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;
}
<!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);
})();
Что дальше
Сейчас у нас всё работает, но как-то неопрятно:
- картинки не по центру;
- непонятно, как играть нескольким игрокам;
- нет подсчёта пар.
Исправим это в следующей версии. Подпишитесь, чтобы не пропустить продолжени.