Minesweeper

Задача:

  • Спроектировать и реализовать популярную однопользовательскую игру Mineswepper. Пример одной из онлайн реализации можно найти по адресу http://minesweeperonline.com/#
  • Задача состоит из базовых требований – обязательных для успешного выполнения задачи, и бонусных – требующих продвинутых знаний, но дающих дополнительные баллы при успешном решении

Описание:

  • Цель игры – открыть все ячейки поля, не содержащие мины, одновременно избегая ячеек, которые их содержат. Когда все ячейки поля, не содержащие мины, будут открыты, игрок побеждает.
  • Каждая ячейка может содержать число, которое показывает количество мин, окружающих ячейку, с максимально возможным значением 8.

Технологии:

  • HTML, Sass
  • TypeScript
  • HTML5 Canvas
  • Vite

Базовые требования:

  • Перед игрой Игроку необходимо указать размерность поля в виде рядов и колонок и количества мин (RхCxM), либо выбрать один из 3х вариантов, с заранее заданным количеством: Начинающий (9x9x10), Продвинутый (16x16x40), Эксперт (16x30x99).
  • Минимальное и максимальное количство мин на поле должно удовлетворять условию 1 < mines < R*C
  • Когда закончится генерирование игрового поля, Игрок может выполнять следующие действия:
    • Левый клик по ячейке поля:
      • Если ячейка поля содержит мину, все остальные ячейки поля с минами становятся видимыми и Игрок проигрывает.
      • Если ячейка поля не содержит мину, ячейка отобразит число близлежащих мин. Если вокруг ячейки нет мин (ячейка пуста), то игра продолжит открывать прилегающие ячейки, пока не встретит ячейку с числом или не дойдет до края поля.
    • Правый клик по ячейке поля
      • Правый клик по ячейке поля установит на эту ячейку флаг. Флаг просто помечает ячейку для игрока как потенциально опасную, и игровая логика игнорирует данную ячейку исполняя логику игры.
    • Левый клик по смайлу
      • Сброс текущей игровой сессии и старт новой.
  • Дополнительно:
    • Для приятного игрового процесса, повторяющего логику исходной игры, первый клик по ячейке никогда не должен приводить к проигрышу. Это значит, что Игрок никогда не должен попасть на мину при первом открытии ячейки

Бонус требования:

  • Реализовать поддержку больших игровых полей – размерностью до 10000х10000 строк и колонок.
  • Реализовать поддержку двух видов рендера с возможность переключения во время игры.
  • Реализовать поддержку тем - темная, светлая и анимации.
  • Реализовать режим сохранения игры, чтобы игрок мог вернуться к последнему игровому состоянию после закрытия вкладки браузера.
  • Реализовать поддержку пользовательского ввода как с помощью мышки, так и с помощью тачпада (Windows, Mac)

Ограничения:

  • Для реализации графической части проекта можно использовать HTML5 Canvas или WebGL / WebGPU. Если примите решение использовать WebGL, разрешается использовать минимальную стороннюю обертку, чтобы сократить написание инициализационного кода. Логику, которую мы просим вас написать самому – это логика отрисовки поля, чтобы применяемый подход был способен отрисовать очень большие поля (поле 1000 x 1000 ячеек может занимать до 40k x 40k пикселей).
  • При использовании WebAssembly мы ждем от вас написанного кода на WAT спецификации. Кросс компиляция из других языков, как С или Rust не допускается.
  • Главная цель задания – увидеть, как вы спроектировали архитектуру приложения, которое может отображать большое количество данных на экране и способного быстро их перемещать в обоих направлениях. Оценить подход к эффективному хранению данных поля и реализации ключевой логики игры (клик по ячейкам для отображения ее состояния и состояния соседних ячеек) – игрок не должен ощущать длинных задержек по времени при клике по ячейке на больших полях.
  • Не забывайте про призводительность. Старайтесь написать настолько производительный код, на сколько возможно. Если вы понимаете, что какаято часть может быть улучшена, но не готовы ее реализовать, оставьте комментарий с описанием, как бы вы хотели это сделать.

Результат:

Реализация:

Игровое поле реализовано с помощью технологии HTML5 Canvas

Локальное состояние игры хранится в следующих переменных (game.ts):

  • GameInfo
    • difficulty - выбранная сложность
    • state - состояние игры (Playing, Won, Lost)
    • tileW - ширина одной ячейки (в пикселях)
    • tileH - высота однной ячейки (в пикселях)
    • firstTry - был совершен первый ход, или еще нет (true/false)
    • optimizedRender - включен оптимизированный рендер или нет (true/false)
  • grid - хранит в себе ячейки игрового поля
  • diffs - хранит в себе список изменний ячеек игрового поля, которые нужно отрендерить (используется при оптимизированном рендере)

Каждая ячейка хранит в себе информацию (tile.ts):

  • x - абсциссы ячейки на игровом поле
  • y - ординаты ячейки на игровом поле
  • hasMine - содержит ли ячейка мину (true/false)
  • danger - количество ячеек с минами, расположенные в области 3x3 вокруг ячейки
  • currentState - текущее состояние ячейки (Visible, Hidden, Flagged)

Есть два вида рендера:

  • Обычный рендер
    • Включен по умолчанию
    • Перерендриваются все ячейки после каждого хода игрока
  • Оптимизированный рендер
    • Включается в настройках
    • Перерендриваются только те ячейки, которые были изменены после каждого хода игрока

Есть четыре вида сложности:

  • Кастомный
    • Количество рядов выбирает сам игрок (от 2 до 1000)
    • Количество колонок выбирает сам игрок (от 2 до 1000)
    • Количество мин выбирает сам игрок (от 1 до рядов * колонок - 1)
  • Начинающий
    • 9 рядов
    • 9 колонок
    • 10 мин
  • Продвинутый
    • 16 рядов
    • 16 колонок
    • 40 мин
  • Эксперт
    • 16 рядов
    • 30 колонок
    • 99 мин

Реализованные доп. требования

  • Поддержка больших полей (до 1000 x 1000)
  • Поддержку двух видов рендера
  • Тёмная тема
  • Поддержка пользотельского ввода (лкм и пкм)
  • Все настройки, а также состояние игры сохраняются в localStorage