В этой статье я собираюсь объяснить, как создать игру «Сапер» с CPP. Это будет консольная игра

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

Я создал игру connect4. Читать здесь

Я не буду объяснять, как настроить проект CPP. Я предполагаю, что вы уже знаете, что если вы этого не сделаете, проверьте этот документ Microsoft для установка CPP в Visual Studio. Вы также можете использовать онлайн-компиляторы, я чаще всего использую replit. Сказав это, давайте НАЧНЕМ!

Визуализация результата

Это ВАЖНАЯ часть. Вам нужно визуализировать то, что вы хотите получить на выходе. Это поможет вам улучшить Архитектуру кода. Здесь мы будем делать консольную игру. Для этого нам нужно понять игру.

ПРИМЕЧАНИЕ. Если вы хотите, существуют визуальные библиотеки, которые помогут вам использовать графику с CPP. Я сделал эту же игру, используя SFML. Вы можете ознакомиться с ними на моем Github ссылка здесь.

Сапёр – это видеоигра-головоломка для одного игрока. Цель игры состоит в том, чтобы очистить прямоугольную доску со скрытыми «минами» или бомбами, не взорвав ни одну из них, с помощью подсказок о количестве соседних мин на каждом поле.

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

Самая важная визуализация, которую должен сделать программист, — визуализировать игру как части или объекты. Вы можете представить себе большую картину, а затем разрезать ее на более мелкие части. Или представьте себе маленькое, а затем постройте из него картинку.

В качестве упражнения сделайте паузу в чтении «Здесь», а затем посмотрите на изображения выше, а затем попробуйте разбить игру на части. Это не ограничивается программированием. Так решаются самые сложные проблемы.

Мне нравится начинать с малого. Затем, визуализируя, я могу настроить некоторые функции.
Поэтому, когда я смотрю на игру, я вижу квадраты, составляющие сетку. Я назову квадрат «Плитка».

Плитка

  • Плитка может быть скрыта, открыта или отмечена. (состояние)
  • Плитка может содержать значение, такое как бомба, число или пустое значение. (тип)

Далее идет сетка из плиток. Я назову это «доска».

Доска

  • Он имеет размер.
  • Бомбы генерируются на случайных плитках.
  • Плиткам присваиваются номера в соответствии с количеством соседних плиток с бомбами.
  • Если плитка открыта и она пуста, то открываются все соседние скрытые плитки. (рекурсивный)

Далее идет сама игра.

Игра

  • Обрабатывает ввод: игра ведется с вводом x и y при использовании. и хочет ли пользователь показать или отметить его.
  • Игра выиграна, когда количество отмеченных плиток равно количеству бомб. Затем откройте все.
  • Игра считается проигранной, если открывается тайл с бомбой или если все бомбы помечены и любая из них неправильная.

Начало работы с кодированием

Начинается ВЕСЕЛЬЕ. Кодирование отличается от визуализации. Когда я кодирую, мне нравится начинать с больших. В основном потому, что я могу отлаживать и, возможно, добавлять несколько функций во время кодирования. Если я начну с малого, мне придется написать весь код, прежде чем я запущу его один раз.

Я не буду делиться всем в каждом фрагменте кода. Меняются только те части. Или это будет очень долго.

Эта программа будет довольно большой. Поэтому я разделю код на файлы заголовков и файлы CPP. Это обеспечивает лучшую читаемость. например,Game.h,который будет иметь только один класс с его объявлениями, а также необходимый заголовочный файл. И все мои определения будут в файле CPP с тем же именем, Game.cpp.

Для начала CPP нужна основная функция. Мне нравится, чтобы моя основная функция была как можно меньше. и я создам класс Game, который будет обрабатывать игровую логику, как мы упоминали выше. и вызовите Run() в main.

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

Вы могли заметить, что я использую #pragma once. Это поможет избежать дублирования объявлений классов и включений. но это доступно только на нескольких Compliers. Альтернативой, которую вы можете использовать, являются #ifndef и #endif. Подробнее об этом можно прочитать здесь.

Цикл игры

Далее мы начнем строить игровой цикл. Чтобы понять поток, я напишу псевдокод. Проще говоря, я напишу частичный код, а затем преобразую его в код.

START Loop
 Get input - rowIndex, columnIndex, reveal/mark
 Check input validity,
  if input is invalid, RESTART loop
 send input for processing
 check game status,
  if game Over, END Loop
  else, NEXT Loop

Run() будет выглядеть так.

Для тех, кто не понимает, как continue работает в цикле do-while, continue переходите к условному выражению цикла. В цикле while или for цикл идет вверх. Но в цикле do-while он идет вниз.

Вход

Поскольку у нас есть 3 входа и большую часть времени мы должны отправлять их вместе, я создам структуру. и переместите его в другой заголовочный файл input.h

Попробуйте запустить код и посмотрите, нет ли ошибок.

Для обработки и проверки достоверности ввода нам необходимо настроить доску.

Доска

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

  • Показать доску
  • Проверить правильность ввода
  • Обработать ввод
  • Проверьте, закончилась ли игра и есть ли в ней выигрыш/проигрыш

Теперь мы наконец можем отложить класс Game в сторону.

Согласно приведенной выше логике, доска представляет собой сетку из плиток. Итак, давайте сначала построим Плитку, а затем перейдем к доске.

плитка

Сначала создайте класс Tile. Согласно приведенной выше логике, у нас есть две вещи для хранения. Один из них — состояние плитки, а другой — тип. Для их обработки лучше использовать enums. И я использую класс enum. Об их преимуществах перед обычными перечислениями читайте здесь.
поэтому мы создадим перечисление TileState и перечисление TileType. Мы также поместим значения по умолчанию как скрытые и пустые.

Теперь Tile должен иметь возможность хранить количество соседних бомб и отображать их. Я собираюсь использовать следующее представление

  • Скрытый - #
  • Отмечено — О
  • Бомба — Х
  • Счет — (1–8)

На этом плитки готовы, теперь вернемся к доске.

Доска… (продолжение)

Следующим шагом на доске является создание сетки плитки. для этого нам нужна максимальная строка и максимальный столбец.

Так как мы будем часто использовать точки X и Y. Для этого лучше создать структуру. Делает код чище.

Я также меняю ввод.

Для размера доски я выберу сетку 9x9. и я инициализирую его динамически.

теперь для достоверности ввода я изменю несколько вещей. Вместо проверки всего ввода я проверю только позицию и изменю имя с IsInputValid() на IsInBounds() . аналогичным образом измените ввод в игре.

Далее нужно отобразить.

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

Геймплей

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

Я также сделал несколько рефакторингов. Я добавил «m_» к частным переменным.

Размещение бомб

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

Для этого мне пришлось создать 2 функции в тайле, а также перегрузить оператор «==» для вектора.

Номер места

После того, как бомбы размещены, нам нужно разместить соседний счетчик бомб.

Выявить все пустое

Когда мы раскрываем плитку, и это пустая плитка, все соседние плитки также открываются. это происходит рекурсивно, пока не будет достигнута непустая плитка. Я собираюсь использовать алгоритм под названием заливка, чтобы реализовать эту функцию.

Окончание

Теперь нам нужно добавить игру по условию. Проигрыш в игре прост. Когда вы обнаружите бомбу, игра окончена. Для победы в игре. Я могу проверить, равно ли количество бомб количеству нераскрытых тайлов. Это то, что я оставляю вам, ребята, чтобы понять.

На этом вы, наконец, закончили игру. Запустите игру. Рефакторинг вашего кода. Если вы знаете какие-либо оптимизации, сделайте это. Это все люди.

Если вы хотите узнать более глубокий процесс. Я создал игру connect4. Читать здесь.

СЧАСТЛИВОЕ КОДИРОВАНИЕ