Игра «Память» отлично тренирует работу с состоянием интерфейса: карточки открываются, сравниваются попарно, блокируются на время проверки и остаются открытыми при совпадении. В этом уроке разбираем финальную версию на HTML, CSS и JavaScript — ту же, что доступна в разделе «Поиграть».

Что получится в конце

Готовая браузерная игра «Память»: поле 4×4, случайная раскладка, проверка пар, счётчик ходов, таймер партии, подсчёт найденных пар и кнопка перезапуска.

Сначала попробуйте готовую игру

Сыграйте одну партию до конца и обратите внимание на поведение интерфейса: карточки не дают открыть третью карту во время проверки пары, а статистика обновляется в реальном времени.

Открыть игру отдельно

Структура проекта

Игра собрана в трёх файлах и не требует библиотек.

pamyat/
├── source.html
├── style.css
└── game.js

HTML: собираем каркас игры

Разметка содержит заголовок, блок статистики, кнопку новой партии, контейнер карточек и строку статуса. JavaScript работает через data-memory-* атрибуты, поэтому структура остаётся прозрачной.

<main class="memory" data-memory-game>
  <section class="memory__stats" aria-label="Статистика игры">
    <p data-memory-moves>Ходы: 0</p>
    <p data-memory-time>Время: 00:00</p>
    <p data-memory-pairs>Пары: 0 / 8</p>
  </section>

  <button class="memory__restart" type="button" data-memory-restart>Новая партия</button>
  <div class="memory__board" data-memory-board></div>
  <p class="memory__status" data-memory-status aria-live="polite">Найдите все пары карточек.</p>
</main>

CSS: сетка и единый стиль

Поле использует CSS Grid 4×4, карточки имеют состояния is-open и is-matched, а общая палитра и кнопки выдержаны в том же визуальном стиле, что и другие игры проекта.

.memory__board {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
}

.memory__card.is-open {
  background: linear-gradient(135deg, #355cae, #2d4f98);
}

.memory__card.is-matched {
  background: linear-gradient(135deg, #2a8f86, #216f72);
}

JavaScript: подготовка колоды

Берём восемь символов, дублируем, перемешиваем алгоритмом Фишера-Йетса и превращаем в массив карточек с флагами open и matched.

const icons = ["⚡", "🎯", "🎮", "🧩", "🚀", "💎", "🔥", "🌙"];

function shuffle(values) {
  const output = values.slice();
  for (let i = output.length - 1; i > 0; i -= 1) {
    const j = Math.floor(Math.random() * (i + 1));
    [output[i], output[j]] = [output[j], output[i]];
  }
  return output;
}

JavaScript: логика открытия карточек

Игрок может открыть максимум две карточки за ход. На время сравнения поле блокируется, чтобы нельзя было открыть третью карту и сломать состояние партии.

if (!card || locked || card.open || card.matched) return;

card.open = true;
openIds.push(id);
renderBoard();

if (openIds.length < 2) return;

moves += 1;
locked = true;

Проверка пары, таймер и завершение партии

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

if (first.icon === second.icon) {
  first.matched = true;
  second.matched = true;
  matchedPairs += 1;
} else {
  window.setTimeout(function () {
    first.open = false;
    second.open = false;
  }, 540);
}

if (matchedPairs === totalPairs) {
  stopTimer();
}

Перезапуск игры

Кнопка «Новая партия» полностью сбрасывает состояние: таймер, ходы, пары, открытые карточки и раскладку.

function startGame() {
  stopTimer();
  buildDeck();
  openIds = [];
  locked = false;
  matchedPairs = 0;
  moves = 0;
  elapsedSeconds = 0;
  renderBoard();
}

restartButton.addEventListener("click", startGame);

Как проверить сборку

Проверка Ожидаемый результат
Открыть две разные карточки Обе закрываются обратно после короткой паузы
Открыть совпадающую пару Карточки остаются открытыми и помечаются как найденные
Попробовать кликнуть третью карточку во время паузы Клик игнорируется, пока идёт проверка пары
Сделать первый ход Запускается таймер, статистика начинает обновляться
Нажать «Новая партия» Состояние полностью сбрасывается и колода перемешивается заново

FAQ

Можно ли изменить размер поля, например на 6×6?

Да. Увеличьте набор символов, количество карточек и настройте grid-template-columns под новую ширину поля.

Как сделать счёт более «игровым»?

Можно начислять бонус за серию совпадений без ошибки и штрафовать за промахи. Текущая архитектура это позволяет без больших переделок.

Где лучше хранить рекорды по времени?

Для локальной версии подойдёт localStorage. Если нужен общий рейтинг между игроками, понадобится серверная часть и API.

Почему карточки рендерятся заново после хода?

Так проще держать интерфейс синхронным с состоянием. Для маленькой сетки 4×4 это быстрый и надёжный подход.

Исходники урока

Откройте финальные файлы этой версии: HTML, CSS и JavaScript. Это тот же код, который работает в игре наверху статьи и на отдельной странице «Поиграть».

Обложка книги JavaScript с нуля: учимся программировать, создавая игры

Книга по JavaScript для начинающих

«JavaScript с нуля: учимся программировать, создавая игры»

Пошаговый формат с маленькими практическими задачами: от базового синтаксиса до первых игровых механик без перегруза теорией.

Читать книгу на ЛитРес →