На этом уроке мы добавим к нашей игре уровни сложности, а также настроим необходимые обработчики событий.
Уровни сложности
Сейчас мы будем фокусироваться на меню «Уровни сложности» и тех параметрах, которые мы определили. После того, как мы добавили параметры меню, можно скомпилировать и запустить игру, чтобы посмотреть, как это всё будет выглядеть. Пока никаких действий для этих пунктов у нас не предусмотрено.
При нажатии на пункт меню программа инициирует событие, указывающее на то, какой пункт меню был выбран.
Нам же просто нужно поймать это событие с помощью обработчика. Для этого в окне "Представление классов"
выберите CSameGameView
, а затем нажмите на кнопку "События"
(иконка молнии). После чего вы должны увидеть следующую картину:
Обратите внимание на часть "Команды меню"
. Нажмите на +
напротив ID_LEVEL_3COLORS
(он соответствует пункту меню "Сложность" > "3 Цвета"
). Параметр COMMAND
— это обработчик события для выбора пункта меню. Параметр UPDATE_COMMAND_UI
— это обработчик события, который позволяет изменить состояние опции меню. Под состоянием подразумевается активно/неактивно или установить/снять флаг выбора пункта меню. В нашем случае мы собираемся поставить галочку напротив выбранного уровня сложности.
Нажмите на стрелочку, которая раскрывает список, соответствующий пункту COMMAND
и выберите Add
. Повторите то же самое и для UPDATE_COMMAND_UI
. Проделайте эти действия для всех ID_LEVEL_3COLORS
, ID_LEVEL_4COLORS
и вплоть до ID_LEVEL_7COLORS
. После того, как вы с этим закончите, мы будем добавлять код.
Начнем с редактирования игровой доски, затем перейдем к Document и закончим на View. В заголовочном файле SameGameBoard.h после m_nRemaining
создайте новую переменную-член для хранения количества цветов:
1 2 |
// Количество цветов int m_nColors; |
Также нам потребуется добавить в public-раздел 2 функции. С помощью первой функции мы сможем устанавливать значение количества цветов, а с помощью второй — сможем это значение считывать.
Файл SameGameBoard.h:
1 2 3 4 |
// Гетеры и cеттеры для количества цветов int GetNumColors(void) { return m_nColors; } void SetNumColors(int nColors) { m_nColors = (nColors >= 3 && nColors <= 7) ? nColors : m_nColors; } |
Функция SetNumColors() ограничивает набор значений числом от 3 до 7 в соответствии с параметрами нашего меню. Так как мы добавляем дополнительные цвета, то нам нужно увеличить массив m_arrColors
.
Файл SameGameBoard.h:
1 2 |
// Список цветов, 0 – это цвет фона, а 1-7 – это цвета блоков static COLORREF m_arrColors[8]; |
Теперь в исходном файле для игрового поля нам нужно обновить несколько дополнительных функций и массив цветов. Конечно, нам также нужно не забыть обновить конструктор.
Файл SameGameBoard.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
COLORREF CSameGameBoard::m_arrColors[8]; CSameGameBoard::CSameGameBoard(void) : m_arrBoard(NULL), m_nColumns(15), m_nRows(15), m_nHeight(35), m_nWidth(35), m_nRemaining(0), m_nColors(3) { m_arrColors[0] = RGB(0,0,0); m_arrColors[1] = RGB(255,0,0); m_arrColors[2] = RGB(255,255,64); m_arrColors[3] = RGB(0,0,255); m_arrColors[4] = RGB(0,255,0); m_arrColors[5] = RGB(0,255,255); m_arrColors[6] = RGB(255,0,128); m_arrColors[7] = RGB(0,64,0); // Создаем и настраиваем игровую доску SetupBoard(); } |
Теперь рассмотрим функцию SetupBoard(). В предыдущие разы мы зафиксировали количество цветов числом 3
. Теперь же нам нужно изменить это значение с 3
на m_nColors
, чтобы потом, при помощи функции rand(), получать случайное количество цветов для каждой отдельной партии в игре.
Файл SameGameBoard.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void CSameGameBoard::SetupBoard(void) { // При необходимости создаем доску if(m_arrBoard == NULL) CreateBoard(); // Устанавливаем каждому блоку случайный цвет for(int row = 0; row < m_nRows; row++) for(int col = 0; col < m_nColumns; col++) m_arrBoard[row][col] = (rand() % m_nColors) + 1; // Устанавливаем количество оставшегося пространства m_nRemaining = m_nRows * m_nColumns; } |
Теперь перейдем к Document. Нам нужно добавить функции, чтобы View мог изменить количество цветов. Функцию GetNumColors() следует поместить в public-раздел Document заголовочного файла SameGameDoc.h. Также нам необходимо добавить реализацию функции SetNumColors().
Файл SameGameDoc.cpp:
1 2 3 4 5 6 7 8 |
void CSameGameDoc::SetNumColors(int nColors) { // Сначала задаем количество цветов... m_board.SetNumColors(nColors); // ...затем устанавливаем параметры игровой доски m_board.SetupBoard(); } |
На данный момент, это все изменения, которые нам потребовалось внести в Document. Вы наверняка уже заметили, что мы не внесли соответствующие правки во View и поэтому пока не можем пользоваться данными функциями. Последний шаг — это отредактировать View. При добавлении обработчиков событий в заголовочный файл SameGameView.h, автоматически должны были прописаться следующие прототипы функций.
Файл SameGameView.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Функции для изменения уровня сложности afx_msg void OnLevel3colors(); afx_msg void OnLevel4colors(); afx_msg void OnLevel5colors(); afx_msg void OnLevel6colors(); afx_msg void OnLevel7colors(); // Функции для обновления меню afx_msg void OnUpdateLevel3colors(CCmdUI *pCmdUI); afx_msg void OnUpdateLevel4colors(CCmdUI *pCmdUI); afx_msg void OnUpdateLevel5colors(CCmdUI *pCmdUI); afx_msg void OnUpdateLevel6colors(CCmdUI *pCmdUI); afx_msg void OnUpdateLevel7colors(CCmdUI *pCmdUI); |
При этом вы могли заметить пару вещей в новых прототипах, которые в коде раньше не встречались. Обозначение afx_msg
указывает на то, что функция является обработчиком событий. Функции, начинающиеся с OnUpdateLevel…(), используют указатель на объект CCmdUI
. Мы поговорим об этом чуть позже, но это именно тот способ, с помощью которого мы будем воздействовать на меню, изменяя его состояние на активное/неактивное и устанавливая метку выбрано/не выбрано для подпунктов меню. В исходный файл SameGameView.cpp нужно добавить некоторое количество дополнительного кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
BEGIN_MESSAGE_MAP(CSameGameView, CView) ON_WM_LBUTTONDOWN() ON_WM_ERASEBKGND() ON_COMMAND(ID_LEVEL_3COLORS, &CSameGameView::OnLevel3colors) ON_COMMAND(ID_LEVEL_4COLORS, &CSameGameView::OnLevel4colors) ON_COMMAND(ID_LEVEL_5COLORS, &CSameGameView::OnLevel5colors) ON_COMMAND(ID_LEVEL_6COLORS, &CSameGameView::OnLevel6colors) ON_COMMAND(ID_LEVEL_7COLORS, &CSameGameView::OnLevel7colors) ON_UPDATE_COMMAND_UI(ID_LEVEL_3COLORS, &CSameGameView::OnUpdateLevel3colors) ON_UPDATE_COMMAND_UI(ID_LEVEL_4COLORS, &CSameGameView::OnUpdateLevel4colors) ON_UPDATE_COMMAND_UI(ID_LEVEL_5COLORS, &CSameGameView::OnUpdateLevel5colors) ON_UPDATE_COMMAND_UI(ID_LEVEL_6COLORS, &CSameGameView::OnUpdateLevel6colors) ON_UPDATE_COMMAND_UI(ID_LEVEL_7COLORS, &CSameGameView::OnUpdateLevel7colors) END_MESSAGE_MAP() |
Карта сообщений (MESSAGE_MAP
) — это список макросов в языке C++, которые связывают событие с соответствующим обработчиком. Данный список генерируется автоматически, вам не нужно будет его редактировать. Функции OnLevel*colors()
(вместо *
пишется номер от 3 до 7) практически одинаковые и имеют следующий вид.
Файл SameGameView.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void CSameGameView::OnLevel3colors() { // Получаем указатель на Document CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Устанавливаем количество цветов pDoc->SetNumColors(3); // Перерисовываем View Invalidate(); UpdateWindow(); } |
Во всех функциях представления View нам нужно сначала получить указатель на Document. Затем мы устанавливаем количество используемых цветов, которое указано в имени соответствующей функции, т.е. OnLevel3colors() вызывает SetNumColors(3)
и так далее. В конце мы перерисовываем View. Эти действия необходимо повторить для всех обработчиков событий пунктов меню. После того, как все эти шаги будут выполнены, можно скомпилировать и запустить наш проект. Вы увидите, что количество цветов изменяется от 3 до 4 и так далее. Как вариант, можно попробовать рассмотреть возможность создания вспомогательной функции, которая выполнит за вас всю эту работу, принимая количество цветов в качестве аргумента. Например, в заголовочном файле SameGameView.h добавляем следующий прототип функции:
1 |
void setColorCount(int numColors); |
А в SameGameView.cpp добавим следующий код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void CSameGameView::setColorCount(int numColors) { // Сначала получаем указатель на документ CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Устанавливаем количество цветов pDoc->SetNumColors(numColors); // Перерисовываем View Invalidate(); UpdateWindow(); } void CSameGameView::OnLevel3colors() { setColorCount(3); } |
Теперь вернемся к последней группе обработчиков. Обработчики событий ON_UPDATE_COMMAND_UI
вызываются при раскрытии списка меню по команде пользователя (по одной функции для каждого пункта меню). Затем используется функция SetCheck() объекта CCmdUI
для установки/снятия флажка выбора уровня сложности. Как всегда, начинаем с получения указателя на Document, а затем проверяем количество выставленных на доске цветов.
Файл SameGameView.cpp:
1 2 3 4 5 6 7 8 9 10 11 |
void CSameGameView::OnUpdateLevel3colors(CCmdUI *pCmdUI) { // Сначала получаем указатель на Document CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Проверка установленного уровня сложности pCmdUI->SetCheck(pDoc->GetNumColors() == 3); } |
И снова вы можете заметить, что все функции OnUpdateLevel*colors()
практически идентичны друг другу, за исключением числа, с которым мы сравниваем GetNumColors(). Все эти пять функций вызываются при отображении меню уровня сложности для установки или снятия флажка выбора пользователя. Теперь ваша игра должна выглядеть примерно следующим образом:
Заключение
На этом уроке мы сделали нашу игру интереснее, добавив 4 новых уровня сложности. Мы также узнали о редакторе меню и о том, как это меню настроить. Научились устанавливать обработчики событий меню для типов событий ON_COMMAND
и ON_COMMAND_UPDATE_UI
. На следующем уроке мы добавим новые опции, позволяющие задать количество и размер блоков на игровом поле.
GitHub / Исходный код — Урок №7: Добавление уровней сложности в игре «SameGame» на C++/MFC
В файле SameGameBoard.cpp в функции:
Вызов SetupBoard(); не нужен, так как он уже есть в CSameGameDoc.cpp:
Добрый день.
Посмотрел я плотнее код игры и не нашел какого-то сакрального смысла в вызове SetupBoard() в конструкторе. Автор оригинала, на основе которого был выполнен перевод, далее в своих статьях мельком упоминает такую фразу "мы исходим из предположения, что у нас всегда есть указатель на доску"… Метод SetupBoard(), если доски нет, создаёт её и определяет соответствующий указатель. А самый ранний момент, когда можно создать указатель на доску, чтобы он "всегда был", это непосредственно в конструкторе объекта доски. Видимо поэтому он и запихнул в него лишний вызов SetupBoard(). Как-то так… 🙂
В последней (на момент написания комментария) версии VS столкнулся с тем, что вместо понятных буквенных литералов новым кнопкам присваиваются безымянные цифровые ID (ID_32772 и так далее в моем случае). Подозреваю, что это нормальная ситуация в таком случае. Решением проблемы является переход в режим редактирования меню из прошлого урока, затем щелчок ПКМ на любом пункте меню, проставить галочку на "Изменить идентификаторы", затем, при двойном нажатии на любой пункт меню вылезет менюшка свойств этого меню (у меня справа), где, в поле ИД можно выбрать из уже имеющихся или прописать свой пользовательский. После этого отображение в свойствах SameGameView будет идентичным.
Спасибо!