Урок №2: Архитектура и хранение данных в игре «SameGame» на C++/MFC

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

  |

  Обновл. 20 Июл 2021  | 

 28912

 ǀ   10 

На этом уроке мы поговорим об архитектуре «Document/View» и о том, как и зачем она используется, а также начнем уже писать код нашей игры.

Архитектура «Document/View»

Архитектура «Document/View» — это очень интересная парадигма в программировании, в которой мы отделяем фактические данные нашего приложения от отображения их пользователю. Document содержит все данные, в то время как View получает эти данные из Document и отображает их пользователю в том или ином виде. Здесь наши данные — это игровая доска, время, необходимое для завершения игры, и другая соответствующая информация. Наш View отображает игровое поле в виде цветных блоков и позволяет пользователю кликать по блокам. View обрабатывает взаимодействие с пользователем и изменяет игровые данные в документе соответствующим образом, а затем обновляется, чтобы отразить изменения. Затем цикл повторяется.

При выборе архитектуры «Document/View» в мастере приложений MFC, автоматически создается вся необходимая кодовая база вместе со всеми нужными механизмами, относящимися к использованию данной технологии.

The Document: Хранение данных


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

Сначала мы создаем класс, представляющий нашу игровую доску — CSameGameBoard. Создайте новый класс, щелкнув правой кнопкой мыши по проекту SameGame в "Обозреватель решений" и выбрав "Добавить" > "Класс...". Укажите CSameGameBoard в качестве имени класса.

Теперь давайте заполним класс игровой доски. Вот код для заголовочного файла SameGameBoard.h:

Этот класс сам по себе довольно простой. Он содержит указатель m_arrBoard на двумерный массив целых чисел, которые представляют один из трех цветов (элементы с индексами 1-3), либо цвет фона (элемент с индексом 0). Затем мы добавляем переменные-члены, чтобы отслеживать строки (m_nRows), столбцы (m_nColumns), высоту (m_nHeight) и ширину (m_nWidth) в пикселях. Здесь также присутствуют функции для создания, настройки и удаления доски.

Метод CreateBoard() нужен для выделения памяти под двумерный массив, в котором будет храниться игровое поле, и для инициализации всех блоков значением фонового цвета. Метод SetupBoard() сбрасывает игровое поле путем случайного выбора цвета для каждого элемента поля на доске. Наконец, метод DeleteBoard() освобождает память, которую мы используем для игровой доски (в противном случае, это может привести к утечкам памяти).

В классе, представляющем игровую доску, также присутствует массив m_arrColors[] элементов типа COLORREF. Тип COLORREF — это 32-битный целочисленный тип unsigned, который содержит значение цвета блока в формате RGBA. Элемент массива m_arrColors[0] хранит фоновый цвет игровой доски, а элементы m_arrColors[1], m_arrColors[2] и m_arrColors[3] — цвет блоков. В конструкторе, приведенном ниже, мы будем использовать макрос RGB для создания значения COLORREF из трех целых чисел, представляющих собой числовое обозначение красного, зеленого и синего цветов.

Ниже приведена реализация класса CSameGameBoard в SameGameBoard.cpp:

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

Вот заголовочный файл SameGameDoc.h:

Большая часть этого кода покажется вам уже знакомой, за исключением нескольких вещей, специфичных для MFC. Пока мы не будем заострять внимание на строках с DECLARE_DYNCREATE и DECLARE_MESSAGE_MAP, так как они являются типичными директивами MFC.

Сейчас Document, по сути, является простой обёрткой для нашего класса. Мы добавили экземпляр нашего класса и семь функций, которые вызывают аналогичные функции класса SameGameBoard. Благодаря этому View сможет получить доступ к данным игровой доски, хранящимся в Document. Исходный файл для Document (SameGameDoc.cpp) также очень прост, так как все функции, которые мы добавили, имеют свою реализацию:

На самом деле всё, что мы добавили, — это вызов функции SetupBoard() в обработчике OnNewDocument. Всё это позволяет пользователю начать новую игру с помощью нажатия Ctrl+N или из меню File > New.

На следующих уроках мы добавим новые функции к игровому полю и к Document.

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

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

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

  1. Анатолий:

    Потребовалось в указанных выше файлах добавить #include «pch.h» иначе вываливались ошибки.

  2. Finchi:

    Заменил указатель на двумерный массив вектором, так безопаснее, ИМХО.

    Соответственно скорректировав файл SameGameBoard.cpp под вектор

  3. Виталий:

    Доброго времени суток. При создании класса CSameGameBoard у меня появился заголовочный файл CSameGameBoard.h, а не SameGameBoard.h. Также вместо SameGameBoard.cpp — CSameGameBoard.cpp. Это вообще критично? И почему так получилось.

    1. Фото аватара Дмитрий Бушуев:

      Доброго.
      >И почему так получилось.
      Так работает Visual Studio :)/ Если мне не изменяет память, то приставка "C" в данном случае является сокращением от "Class". Показывая тем самым, что ваш заголовочный файл относится к описанию нового класса.

      >Это вообще критично?
      Нет, не критично. Просто учитывайте во время изучения уроков и вносите (если нужно) соответствующие коррективы. Например, если в статье идет подключение #include "SameGameBoard", то у вас должно быть #include "CSameGameBoard", ну и т.д.

  4. Арбузик❤❤❤:

    В файле SameGameBoard.h visual studio ругался COLORREF — мол идентификатор не определен. Заinclude-ил "Windows.h" — все заработало
    Может кому помог 🙂

    1. Фото аватара Дмитрий Бушуев:

      Спасибо за информацию, чуть позже добавим её в статью.

      P.S.: На момент написания статьи (да и сейчас, только что проверил) всё собирает и без явного инклуда именно "Windows.h". Видимо, что-то изменилось 🙂

    2. Анастасия:

      Мне помогло, спасибо. Без этого ругается на COLORREF.

  5. Pecka:

    Почему в функции CSameGameBoard::DeleteBoard(void) необходимо удалять поочередно строки, а уже потом весь массив? Его нельзя сразу весь удалить

    1. Фото аватара Дмитрий Бушуев:

      К сожалению — нельзя, так устроен C++. Сами выделяем память — значит сами за собой и прибираемся. Т.е., сколько раз использовали вызовов оператора new/new[] , то и столько же должно быть вызовов delete/delete []. Иначе получите утечки памяти.

      1. Pecka:

        Спасибо за ответ!

Добавить комментарий для Pecka Отменить ответ

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