Создание игры «Сапер» на С++/SFML

  Дмитрий Бушуев  | 

  Обновл. 26 Ноя 2020  | 

 9517

 ǀ   3 

Игра «Сапер» (англ. «Minesweeper«) — это простая, но в то же время очень интересная игра-головоломка. В начале игры открывается поле, разделенное на ровные клетки, на котором спрятаны мины. Когда игрок кликает мышкой по произвольной клетке на поле, то в этой клетке появляется цифра, показывающая сколько мин спрятано по соседству. Анализируя эти цифры можно понять, где спрятаны следующие мины. Затем на клетке (в которой игрок предполагает, что спрятана мина) ставится флажок для наглядного обозначения мины.

Таким образом, задача игрока — найти все мины, спрятанные на игровом поле, и при этом постараться не подорваться на них. А наша задача — разработать игру «Сапер» средствами графической библиотеки SFML и языка программирования С++.

Модель игрового поля

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

Размеры одного квадратного элемента текстуры составляют 32×32 пикселя. Размеры игрового поля — 10×10 квадратов.

Теперь перейдем непосредственно к написанию кода. Как вы уже наверняка знаете, минимальный каркас SFML-приложения имеет примерно следующий вид:

Модель игрового поля составляют 2 двумерных массива:

   int gridLogic[12][12] — представляет логическую часть игрового поля;

   int gridView[12][12] — отображает графическую составляющую игрового поля.

В коде это выглядит следующим образом:

Далее нужно создать спрайт из заготовленной текстуры и сформировать первоначальный вид игрового поля. Для этого, мы всем элементам массива gridView[i][j] присваиваем значение (10) соответствующего порядкового номера квадратика текстуры:

Добавим функцию отрисовки игрового поля:

И в результате получим вот такую заготовку:

Примечание: Если у вас ничего не отображается, то попробуйте прописать полный путь до файла текстуры. Например, вместо:

укажите:

При этом у вас на компьютере должна быть папка C:\images, в которой должен находиться файл tiles.jpg.

Расстановка и подсчет мин


Для расстановки мин на игровом поле воспользуемся генератором случайных чисел с заданным диапазоном значений от 0 до 4. Если выпадает ноль, то ставим мину (9), в противном случае — пустая клетка (0). Индексы 9 и 0 совпадают с порядковыми номерами соответствующих изображений в текстуре:

Дальше реализуем подсчет мин окружающих каждую клетку. Для этого в двойном цикле for поочередно пройдемся по каждой клетке. Если в текущей клетке уже стоит мина, то просто переходим к новому шагу итерации. Если же в текущей клетке мины нет, то с помощью девяти условий if поочередно проверяем все примыкающие к ней клетки. Если там есть мины, то на каждом сработавшем if увеличиваем счетчик количества мин на 1:

Посмотрим, что у нас получилось. Для этого добавим строку gridView[i][j] = gridLogic[i][j], тем самым отобразив всё поле на экране:

Результат:

Примечание: Учитывайте, что расстановка мин генерируется случайным образом. Поэтому ваш результат запуска программы наверняка будет отличаться от того, что изображено на вышеприведенной картинке.

Обработка нажатий кнопок мыши

Теперь добавим функции обработки нажатия ЛКМ и установки флажка по щелчку ПКМ. Если по нажатию ЛКМ мы открыли клетку, а там находится мина, то следом открываем всё поле — игра окончена:

Настало время запустить приложение «Сапер» и посмотреть на готовый игровой процесс:

  GitHub / Создание игры «Сапер» на С++/SFML — Исходный код

Заключение


Вот такое небольшое и довольно-таки простое приложение можно написать, используя C++ и SFML. Конечно, наша игра далека от идеала и не лишена недостатков. Например, алгоритм игры не исключает ситуации, когда игрок может проиграть уже на первом ходу. Также отсутствуют такие элементы игрового процесса, как: таймер обратного отсчета, кнопки перезапуска и паузы игры, подсчет уже отмеченных игроком мин и т.д. Возможно, мы рассмотрим реализацию подобных механизмов в следующих статьях, но ничего не мешает и вам самим попробовать добавить данный функционал. Как говорится, дорога в тысячу миль начинается с первого шага. Этот первый шаг мы сделали вместе, а дальше — дело за вами.

Дерзайте 🙂

Примечание: Статья написана по мотивам одноименного видеоролика за авторством FamTrinli.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (15 оценок, среднее: 4,60 из 5)
Загрузка...

Комментариев: 3

  1. Аватар Жека:

    Спасибо, в детстве все мечтали сами переписать эти игры самому — мечта сбылась!

    У меня есть вопрос: вот эта конструкция, которая гоняет главный игровой цикл приложения

    вешает проц на 100%. Ну, 1 ядро, но будь ей доступны оба, повесила бы оба. Наверняка в реальных играх устроено иначе. Очень хотелось бы узнать как.

    P.S. Немножко не получилось у меня, буду искать причину 🙂 вот так вышло https://skr.sh/s5wvsyM3KIH

    1. Аватар Жека:

      По поводу загрузки уже примерно нашел:

      https://habr.com/ru/post/136878/
      https://habr.com/ru/post/134559/

      Вторая ссылка вроде четче. Кратко: тоже используется while (true), но код большую часть времени "спит" и просыпается только тогда, когда возникает событие. Именно так работает стандартный Сапёр из Windows: загрузка проца держится на 0 и изредка подпрыгивает до 1%.

      1. Аватар Жека:

        Используя эти идеи, сумел заварганить! Теперь проца кушает ровно 0 – event-driven подход в действии!

        Вот код: https://pastebin.com/xdxFxA0V

        Кратко – просто заменил функцию pollEvent() на waitEvent() и немного переставил часть кода. Ф-я waitEvent() блокирует поток и ждет события — то, что нам в данном случае нужно. Также добавил: при взрыве все игровое поле открывается.

        Жаль только, что при проверке, есть ли рядом мина, мы выходим за границы массива. Это не есть хорошо.

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *