Урок №4: Обработка событий в игре «Same Game»

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

  Обновл. 17 Ноя 2019  | 

 1356

В этом и следующем уроках мы попытаемся реализовать функционал удаления цветных блоков по щелчку мыши. Функционал игры будет минимальным, но, что самое главное — мы получим полностью рабочий прототип. А уже в остальных уроках мы добавим в нашу программу больше различных возможностей, например, уровни сложности и прочее.

А пока что начнём с того, что рассмотрим, что такое событийно-ориентированное программирование.

Событийно-ориентированное программирование

Контроль управления в событийно-ориентированном программировании определяется событиями (или сообщениями, посылаемыми программе). Пользователь выполняет какое-то действие, например, нажимает на клавишу (тем самым посылается сообщение “была нажата такая-то клавиша”), и программа реагирует на это событие, выполняя заранее определённый код. Основной цикл в программе, управляемой событиями, просто ожидает подобного сообщения, а затем вызывает соответствующий обработчик и возвращается к ожиданию другого события. В свою очередь, обработчик событий — это фрагмент кода, который вызывается каждый раз, когда происходит определённое событие.

Обработка событий


Библиотека MFC по своей сути основана на событиях/сообщениях и поэтому позволяет нам довольно легко создавать необходимые обработчики и реагировать на любое действие, которое мы хотим использовать в нашей программе. Чтобы настроить обработку событий в MFC, в Visual Studio есть возможность вывести и просмотреть список всех сообщений, на которые можно отреагировать. Все сообщения в Windows являются константами с приставкой WM_, за которой следует имя сообщения. Для реагирования на щелчки мыши в клиентской области нашей игры имеются сообщения для левой, правой и средней кнопок мыши.

Событие, которое мы будем при этом использовать — это WM_LBUTTONDOWN. Данное сообщение отправляется платформой MFC каждый раз, когда пользователь нажимает левую кнопку мыши. Всё, что нам нужно сделать, это настроить обработчик для этого события, а затем отреагировать на него, когда оно случится. Чтобы добавить обработчик событий, откройте окно «Свойства», выбрав класс CSameGameView и нажмите правую кнопку мыши (или Alt+Enter). Ниже приведено то, что вы увидите в окне свойств. (Если это не так, то убедитесь, что ваш курсор находится в объявлении класса внутри заголовочного файла SameGameView.h)

На скриншоте ниже мой курсор наведён на раздел «Сообщения», нажмите на него. Найдите опцию WM_LBUTTONDOWN, нажмите на неё, кликните на раскрывающийся список и выберите <Add> OnLButtonDown:

Это добавит к вашему View обработчик события OnLButtonDown() с некоторым кодом по умолчанию для вызова реализации функции CView(). В этом месте мы поместим следующий код в тело функции.

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

Файл SameGameView.cpp:

Двумя аргументами функции является целочисленный параметр-флаг, на который, пока что, можно не обращать внимания, и объект типа CPoint. Объект CPoint содержит координаты (x, y) того места вашего View, где был произведён клик мышью. Это нужно для того, чтобы выяснить, на какой именно блок кликнули. Первые несколько строк кода нам уже знакомы: мы просто получаем валидный указатель на Document. Чтобы найти строку и столбец блока, по которому щёлкнули, нужно координату x разделить на ширину блока, а координату y — на высоту:

Ну а поскольку мы используем целочисленное деление, то результатом будет именно те строка и столбец, по которым пользователь кликнул.

Как только у нас есть строка и столбец, мы будем вызывать метод DeleteBlocks() (добавим его чуть позже) в Document, чтобы удалить соседние блоки. Эта функция возвращает количество блоков, которые она удалила. Если ни один из блоков удалить не получилось, то функция просто завершает своё выполнение. Если блоки были удалены, то нам нужно заставить View «перерисовать себя», чтобы отразить изменения игрового поля. Вызов функции Invalidate() сигнализирует для View, что вся клиентская область должна быть перерисована, а вопросом перерисовки займётся функция UpdateWindow():

Теперь, когда игровое поле обновлено и перерисовано, мы проверяем, не закончилась ли игра. В разделе «Условия завершения» мы подробно рассмотрим, как мы можем это определить. А пока что просто добавим вызов соответствующей функции:

Если игра завершилась, то мы получаем количество блоков, оставшихся на доске, и сообщаем об этом пользователю. Для этого создаём объект CString, который является строковым классом MFC, и вызываем его встроенный метод format(). Метод format() ведёт себя так же, как и sprintf(). Здесь мы используем макрос MFC _T(), чтобы разрешить использовать различные типы строк (т.е. ASCII или Unicode). Наконец, мы вызываем функцию MessageBox(), которая отображает небольшое диалоговое окно с заголовком Игра Закончена и сообщением, которое мы создали с помощью метода format(). Диалоговое окно имеет кнопку OK (MB_OK) и значок информации (MB_ICONINFORMATION).

Файл SameGameDoc.h:

После того, как мы добавили эти функции в Document, пришло время изменить игровое поле, чтобы позаботиться об этих операциях. В заголовочном файле игрового поля добавьте следующие public-методы (поместите их прямо перед функцией DeleteBoard()).

Файл SameGameBoard.h:

Функция GetRemainingCount() просто возвращает количество оставшихся блоков. Мы будем хранить это значение в переменной с именем m_nRemaining. Нам нужно добавить её в private-раздел класса. А вот с двумя другими функциями придётся немного повозиться.

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

Файл SameGameBoard.cpp:

Не забудьте также обновить количество оставшихся блоков в методе SetupBoard().

Файл SameGameBoard.cpp:

Удаление блоков с доски реализуем в виде двухэтапного процесса. Сначала мы заменяем все смежные блоки одного цвета на цвет фона (таким образом, удаляя их). А затем перемещаем верхние блоки вниз, а блоки, стоящие по бокам — вправо/влево (соответственно). Назовём это сжатием доски.

Осуществим удаление блоков, используя вспомогательную рекурсивную функцию DeleteNeighborBlocks(), которую поместим в private-раздел класса. Она будет выполнять основную часть работы по удалению блоков. Для этого нам нужно в закрытом разделе класса сразу после функции CreateBoard() добавить следующее.

Файл SameGameBoard.h:

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

В следующем уроке мы рассмотрим реализацию алгоритма функции DeleteBlocks().

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

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

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

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