В нашей версии программы для компьютерной игры "Крестики-нолики" выполняются прорисовка сетки игрового поля и отслеживание щелчков мыши.

Мы продолжаем (см. PC Magazine/RE, Спецвыпуск 2/97, с. ) рассказывать о томw, как средствами Microsoft Visual C++ создать одну несложную Windows-программу, а именно нашу версию известной игры "Крестики-нолики" (англ. название - tic-tac-toe). Имя программы - Tic. в игре фигурирует поле размером 5х5, а ее цель - выстроить по горизонтали, вертикали или диагонали последовательную линию из четырех "крестиков" или "ноликов". В данной статье мы начнем наращивать "мясо" на сформированный "мастером" AppWizard скелет программы (описанный в Части 1), для чего подготовим блоки, отвечающие за прорисовку игрового поля и за отслеживание нажатий левой клавиши мыши.

Давайте загрузим Visual C++, откроем созданный в прошлой статье проект и сделаем ниже следующее.

Задание игрового поля

Для начала зададим клетки, образующие игровое поле. В библиотеке MFC имеется подходящий для подобных целей класс под именем CRect, позволяющий воспроизводить отдельные клетки. Добавим в класс CTicView матрицу CRect-элементов размерностью 5х5. Попутно подготовим конструктор класса CTicView, который будет осуществлять инициализацию CRect-элементов. Делается это следующим образом.

Найдите в окне Project Workspace элемент CTicView и щелкните на нем правой клавишей мыши; выберите в контекстном меню команду Add Variable (Добавить переменную). Введите в появившемся диалоговом окне в поле Variable Type (Тип переменной) слово "CRect" (разумеется, без кавычек), а в поле Variable Declaration (Объявление переменной) - подстроку "m_rect[5][5]". Префикс m_ означает, что данна переменная является членом класса; подобное соглашение повсеместно используется в MFC. Щелкните на кнопке с зависимой фиксацией Protected, чтобы ограничить доступ к новой переменной члена класса; щелкните OK.

Раскройте в окне Project Workspace класс CTicView и выведите на экран список его функций и элементов данных для чего щелкните на маленьком значке "плюс", расположенном рядом с именем этого класса. Если дважды щелкнуть на имени класса CTicView, то появится исходный текст для конструктора класса CTicView. Удалите строку, начинающуюся со слова "TODO" ("Что делать"), и наберите в том же месте фрагмент текста, приведенный на лист. 1.

Для отображения игры на экране мы будем пользоватьс системой координат с единицей измерения, равной 0,01 дюйм. Точка (0,0) - начало координат - находится в верхнем левом углу окна; ось X направлена вправо, ось Y - вверх. Именно по этой причине Y-координаты клеток, назначаемые конструктором, имеют отрицательные значения, а не положительные. Если бы мы указали положительные значения Y, клетки оказались бы за пределами видимой части окна.

У вас, вероятно, уже возник вопрос: зачем при задании каждой клетки конструктор CTicView вызывает функцию CRect::NormalizeRect. Это объясняетс особенностями работы PtInRect - функции класса CRect, позволяющей выяснить, был ли щелчок мыши произведен в пределах границ конкретной клетки. Дело в том, что функция PtInRect может давать неправильные результаты, если использовать отрицательные координаты и предварительно не нормализовать их с помощью CRect::NormalizeRect.

Прорисовка игрового поля

Теперь, когда игровое поле задано, давайте займемс блоком его прорисовки. Windows представляет собой среду, управление в которой организуется через сообщения о событиях; сказанное означает, что нельз напрямую в любой момент времени начертить что-то внутри окна. Если нужно перерисовать окно, Windows направляет ему сообщение WM_PAINT. Сообщения лежат в основе практически любых событий, происходящих в Windows. При создании окна оно получает сообщение WM_CREATE; при удалении - сообщение WM_DESTROY. Механизм MFC преобразует сообщения WM_PAINT в вызовы функции OnDraw, которая отвечает за облик окна; она являетс виртуальной функцией класса CView. Для того чтобы создать нужный нам облик, переопределим функцию OnDraw, содержащуюся в классе CTicView, и запишем в ней, что необходимо нарисовать в окне.

"Мастер" AppWizard уже переопределил OnDraw, однако пока эта функция ничего не делает. Вызовем ее текст, для чего выведем в окне Project Workspace список функций класса CTicView и щелкнем дважды на элементе OnDraw. Обратите внимание: среди аргументов функции OnDraw имеется лишь единственный параметр - CDC-указатель с именем pDC.

Когда Windows-программа производит вывод на экран, принтер или любое другое устройство вывода, она это делает с использованием так называемого контекста устройства (device context - DC) - некоторой структуры данных, содержащей важные сведения о характеристиках конкретного выводного устройства и о параметрах, применяемых для вывода в данной программе. Контексты устройств предоставлены в классе CDC библиотеки MFC, который содержит целый ряд принадлежащих функций, осуществляющих вывод. Например, с помощью функции CDC::Ellipse можно нарисовать эллипс. Следующий пример показывает, как нарисовать эллипс размерами 100 единиц в ширину и 50 единиц в высоту, используя CDC-указатель с именем pDC:

   pDC->Ellipse (0, 0, 100, 50);

Поскольку спецификация Graphics Device Interface (интерфейс графических устройств - GDI) системы Windows позволяет получить аппаратно-независимую модель вывода, одна и та же функция будет работать с любым выводным устройством, для которого имеется соответствующий Windows-драйвер.

Для того чтобы отобразить на экране наше игровое поле, мы будем использовать функцию CDC::Rectangle. Вернемся к функции CTicView::OnDraw и удалим строку, содержащую "TODO". Теперь отредактируем текст этой функции, чтобы она выглядела так, как показано на лист. 2. Вызов функции CDC::SetMapMode присваивает схеме соответствия для контекста устройства значение MM_LOENGLISH, предписывающее, что единицей измерени служит 0,01 дюйм (имеется в виду логический дюйм). Размер логического дюйма определяется некоторым принятым количеством пикселов, необходимым дл отображения реального дюйма на конкретном выводном устройстве. Например, при выводе на принтер один логический дюйм равняется одному физическому (реальному). При выводе на экран размер логического дюйма, как правило, колеблется от 1 до 1,5 физических. (MM_LOENGLISH - это лишь одна из нескольких схем соответствия, используемых в Windows. Для более подробной информации обращайтесь к описанию функции CDC::SetMapMode в документации).

Скорректировав текст функции OnDraw в соответствии с лист. 2, заново выполните сборку (build) и запустите программу на исполнение.

Отслеживание щелчков мыши

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

Узнать о щелчке мыши достаточно просто: когда игрок щелкает левой клавишей и указатель мыши находится в пределах клиентской области окна, оно получает сообщение WM_LBUTTONDOWN. Для отслеживания подобных событий воспользуемся "мастером" ClassWizard, предоставляемого в Visual C++; он подготовит для класса view нашей программы функцию, которая будет обрабатывать сообщения WM_LBUTTONDOWN. Создание скелета для этого обработчика производится в следующей последовательности:

При нажатии кнопки Add Function "мастер" ClassWizard добавляет в состав класса view программы Tic принадлежащую функцию c именем OnLButtonDown. Кроме того, он создает в этом view-модуле программный блок для подготовки карты сообщений (message map) - особой таблицы, в которой устанавливаются соответствия между сообщениями и их обработчиками); с ее помощью будет обеспечиваться вызов функции OnLButtonDown при приеме View-модулем сообщения WM_LBUTTONDOWN. Карту сообщений для CView можно отыскать в начале модуля TicView.cpp - этот блок начинается с макроса BEGIN_MESSAGE_MAP и заканчивается макросом END_MESSAGE_MAP.

Зачем мы разместили функцию OnLButtonDown внутри view-класса? Дело в том, что все сообщения об операциях с мышью передаются в окно, находящееся под курсором. Когда игрок щелкает на некоторой клетке игрового поля, сообщение WM_LBUTTONDOWN поступает в наш view-модуль; если же обработчик сообщения поместить в другом классе, к нему просто не будет обращений.

"Мастер" ClassWizard создал для нас функцию OnLButtonDown, но пока она ничего не делает. Задача по составлению программного фрагмента, сообщающего ей, что следует делать - например, нарисовать крестик или нолик, - возлагается именно на нас. Однако прежде чем что-либо выводить, функция OnLButtonDown должна проверить, что курсор в момент щелчка мыши располагается внутри одной из клеток игрового поля. Далее мы рассмотрим, что следует добавить в текст функции CTicView::OnLButtonDown для реализации проверки позиции курсора. Давайте временно примем, что при нахождении курсора в момент щелчка внутри клетки будет просто выводиться на экран некоторое сообщение.

Находясь в окне ClassWizard, выберите в списке принадлежащих функций класса CTicView, выведенных в окне Member Functions (Принадлежащие функции), элемент OnLButtonDown. Щелкните на кнопке Edit Code (Править текст), и перед вами откроется исходный текст функции CTicView::OnLButtonDown. Скорректируйте его так, как показано на лист. 3 и повторите сборку программы. Теперь после каждого щелчка на любой клетке должно появляться сообщение "You clicked it!" (Вы здесь щелкнули!).

Это сообщение выводится с помощью команды:

   MessageBox ("You clicked it!");

она вызывает очень удобную MFC-функцию CWnd::MessageBox. (CView - базовый класс для CTicView - является производным от класса CWnd; поэтому из принадлежащих функций класса CTicView можно спокойно обращаться к принадлежащим функциям класса CWnd.) Вызов функции CRect::PtInRect производится по одному разу дл каждой клетки - до тех пор, пока не будут проверены все клетки либо получено при возврате управления ненулевое значение. Оно свидетельствует, что в момент щелчка курсор находится внутри некоторого CRect-элемента. Параметр CPoint, передаваемый функции OnLButtonDown, содержит X- и Y-координаты положения курсора. CPoint - отдельный класс MFC, описывающий положение точек на плоскости. Если компоненты X и Y элемента CPoint равняются 10 и 20, значит, в момент щелчка курсор находился в позиции 10 пиксел вправо и 20 пиксел вниз от верхнего левого угла окна. Но координаты клеток нашего игрового поля выражены в размерности MM_LOENGLISH. Поэтому до начала какой-либо проверки необходимо конвертировать CPoint-компоненты в MM_LOENGLISH. Это преобразование реализуется следующим образом:

   CClientDC dc (this);
   dc.SetMapMode (MM_LOENGLISH);
   dc.DPtoLP (&point);

т. е. берется контекст устройства для клиентской области окна, в качестве схемы соответствия дл контекста задается MM_LOENGLISH, и вызывается функци CDC::DPtoLP. Если после преобразования функция PtInRect передает ненулевое RETURN-значение, следовательно курсор находится внутри клетки. В этом случае переменные i и j содержит индексы, указывающие положение данной клетки. Если координаты, переданные аргументом CPoint, лежат за пределами всех имеющихс клеток, то вложенный цикл for заканчтвается, и блок вывода на экран сообщения выполняться не будет.

Мы задали игровое поле и составили программный блок для отслеживания щелчков мыши при нахождении курсора внутри некоторой клетки. Теперь нам предстоит занятьс рисованием крестика или нолика. Однако предварительно нужно подготовить средства для выяснения того, не содержит ли выбранная клетка поставленного ранее крестика или нолика - в этом случае щелчок мыши должен быть проигнорирован; также необходимо вести очередность ходов, чтобы знать, какой значок следует отображать в пустой клетке.


Страница 1 -> Страница 2 -> Страница 3 -> Страница 4 -> Страница 5 ->