Skip to content

Artem-WebDeveloper/pair-em-up

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pair em up!

Live Demo

Deploy - https://artem-webdeveloper.github.io/pair-em-up/

pair-em-optiwebp

About

Это интерактивная игра-головоломка, где цель - очистить поле от чисел, находя пары (одинаковые цифры или дающие в сумме 10). Приложение поддерживает три режима с разной логикой генерации, пять ассист-инструментов, сохранение прогресса, система undo, таймер, таблица рекордов, фоновая музыка и звуки.

Написано на чистом Vanilla JS - без сборщиков, фреймворков, TypeScript и сторонних библиотек. Вся архитектура, DOM-рендер и управление состоянием - вручную, без абстракций фреймворка.

Проект выполнен в рамках курса RS School. Главное техническое ограничение: <body> в HTML пустой - весь интерфейс строится через JavaScript. | Техническое Задание

Game Design

Режимы игры

  • Classic - числа 1–19 (без нуля) в строгом порядке, 9 в строку
  • Random - тот же набор, позиции перемешаны
  • Chaotic - 27 случайных цифр от 1 до 9, дубли разрешены

При нажатии «Добавить числа» каждый режим ведёт себя по-своему: Classic продолжает последовательность, Random добавляет случайные из того же набора, Chaotic добавляет столько новых, сколько осталось на поле.

Правила пар и очки

Пара Очки
Одинаковые числа (7 + 7) +1
Сумма = 10 (3 + 7) +2
Двойная пятёрка (5 + 5) +3

Пара валидна, если между ячейками нет заполненных клеток - по горизонтали, вертикали или через «перелом» строки (последняя ячейка строки N → первая ячейка строки N+1).

Ассист-инструменты

Инструмент Что делает Лимит
💡 Hints Показывает кол-во доступных ходов (макс. "5+") без лимита
↩ Revert Отмена последнего хода 1 раз за ход
➕ Add Numbers Добавляет числа по правилам режима 10 раз
🔀 Shuffle Перемешивает оставшиеся числа 5 раз
🧹 Eraser Убирает одну выбранную ячейку 5 раз

Конец игры

  • Победа: набрано 100+ очков
  • Поражение: нет ходов + все ассисты исчерпаны - ИЛИ поле достигло 50 строк

Сохранение

  • gameState - автосохранение при каждом действии (защита от случайного закрытия)
  • savedGame - ручное сохранение кнопкой «Save», восстанавливается кнопкой «Continue»

Подробные правила игры


Project Structure

src/
├── js/
│   ├── app.js               # Точка входа, инициализация игры
│   ├── config.js            # Стейт, константы
│   ├── helpers.js           # Утилиты 
│   ├── settings.js          # Управление настройками и темой
│   ├── game/                # Core логика игры
│   │   ├── gameStatus.js    # Проверка условий победы и поражения
│   │   ├── logic.js         # Алгоритмы валидации пар
│   │   ├── sound.js         # Audio API менеджер
│   │   └── timer.js         # Игровой таймер
│   └── view/                # UI компоненты и рендер DOM
│
└── styles/
    └── scss/                # Стили

Challenges & Solutions

1. Алгоритм проверки пути

Задача: две ячейки можно соединить только если между ними нет заполненных клеток - по горизонтали, вертикали или через «перелом» строки (последняя ячейка строки N и первая строки N+1 считаются соседними)

Решение: два отдельных чека - checkIsEmptyCells для линейного пути и checkIsEmptyColumns для вертикального. Перелом строки — третий явный случай со своей индексной логикой

2. Event Delegation

Задача: сетка полностью динамическая - ячейки генерируются и удаляются на каждом ходу. Вешать слушатель на каждую ячейку значит постоянно добавлять и чистить обработчики было бы слишком дорого

Решение: один click на контейнере, цель определяется через e.target.closest. Один обработчик на весь жизненный цикл игры, сколько бы раз сетка не перерисовалась

3. Два уровня сохранения

Задача: автосохранение и ручной сейв должны жить независимо и не перетирать друг друга

Решение: два отдельных ключа в localStorage - gameState обновляется после каждого действия, savedGame пишется только по кнопке. Кнопка «Continue» появляется только когда ключ реально существует

4. Снапшоты состояния

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

Решение: structuredClone() для инициализации стейта и настроек. Для снапшота истории - JSON.parse(JSON.stringify(...)) с явной формой объекта, чтобы в снимок попадало только нужное

5. Рендер конца игры

Задача: синхронный вызов renderGameEndScreen() после успешной пары прерывал анимацию на середине

Решение: завернул в queueMicrotask() - рендер откладывается до завершения текущей задачи, анимация отрабатывает полностью


Setup Dev

# Клонировать репозиторий
git clone https://github.com/ArtemWebDeveloper/pair-em-up.git 
cd pair-em-up

# Установить зависимости
npm install 

# Запустить в режиме разработки
npm run dev  

License

MIT © 2026 Artem

About

Числовая браузерная головоломка на поиск пар

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors