Версия для печати


Игра «Ищем пары»
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1339

Петр Смирнов
дата публикации 27-02-2008 08:07

Игра "Ищем пары"

Многие начинающие программисты начинают свои программы даже не с написания программы Hello, world, а начинают с уже довольно серьезных программных продуктов - компьютерных игр. Но, не имея достаточного опыта программирования сразу же написать хорошую игру практически невозможно. Давать советы вроде "Попробуй сперва написать что-то попроще", как правило, не достигают цели, особенно если программировать человек начинает в юном возрасте со свойственным этому возрасту максимализмом. Столкнувшись с неодолимыми трудностями, такой человек нередко бросает программировать. Но что посоветовать таким программистам? Читать теорию правильного построения программ, их архитектуры? Как правило, это только усугубляет ситуацию - почти все мои знакомые предпочитают больше практических экспериментов, нежели теоретических разглагольствований. Может быть, попробовать таким программистам дать пример простой игры, такой, с каких начинают программировать - игра на клеточном поле?

Таких игр можно назвать очень много - от шашек и шахмат до сапера и морского боя. Все они объединены единой идеей - клеточное поле (или два - для морского боя), на котором разворачиваются события. Такие поля по своей сути плоские, но я встречал и трехмерные реализации с превосходной анимацией и детально проработанной графикой, но смысл от этого не изменяется - игрок кликает по определенным клеткам, а доска преобразуется в соответствии с правилами игры. Если вспомнить тот же морской бой, то мы поймем, что правила игры абсолютно не зависят от того, на чем мы играем - в шахматы на реальной доске, в плоскую компьютерную версию, где фигуры "перескакивают" с клетки на клетку без отрисовки промежуточного положения, или где трехмерные фигуры наносят удары по противнику и величественно перемещаются на новую позицию. Поэтому вполне очевидным является отдельная реализация правил игры и, возможно, алгоритма ответа со стороны компьютера и собственно отображения. Этот подход является стандартным в программировании и называется паттерн Модель - Вид - Контроллер.

Подробнее про то, что же представляет собой этот подход, Вы можете прочитать в статье Wikipedia.

Здесь и далее я буду использовать следующие условные обозначение: жирным шрифтом я буду выделять новые термины, или важные имена файлов, курсивом я буду выделять термины далее по тексту, чтобы Вы могли легко ориентироваться по тексту. По моему опыту, не имеющий никаких "опорных" точек текст плохо читается, а эта статья все-таки первый мой опыт, так что не судите строго, если что не так. Мелким шрифтом будут выделены участки, которые можно пропустить - они не несут информации, необходимой для понимания текста, являются, по сути, комментариями.
Я постараюсь объяснить в меру своего понимания. Модель - это нечто, что реализует правила игры и алгоритм обсчета ответного хода, Вид - то, что показывает пользователю содержимое Модели в удобном для пользователя виде, а Контроллер - нечто, что принимает команды пользователя и влияет на Модель и Вид. Данный подход очень важен при разработке Web приложений, в этом практически в один голос уверяют авторы многочисленных статей по Web программированию. Для Windows данная технология не обязательно, но также возможна (как инструмент для облегчения жизни программиста административными мерами). Нередко я экспериментирую с правилами и прихожу к выводу, что им можно следовать не совсем в том виде, как описано в книжках, а другим - более удобным в конкретном случае способом. Если взглянуть на Delphi, а точнее, на средства, предоставляемые средой программирования и языком Object Pascal, то можно прийти к выводу, что наиболее удобным для реализации такого паттерна является сокращенный паттерн Модель - Вид.
Кстати, разработчики языка Oberon в статье Oberon: перспективы эволюции даже ставят концепцию Модель/Вид вперед Модель/Вид/Контроллер, тогда как разработчики и приверженцы языка Java с пеной у рта доказывают, что Модель/Вид/Контроллер всегда было первично, а Модель/Вид это упрощенный, а потому неполноценный подход. Мне лично ближе подход оберонщиков.
Обычно, я реализую Модель как отдельный объект, а Вид - как отдельную форму (в ней будет совмещаться как Вид, так и Контроллер). В дальнейшем в качестве сокращения для паттерна Модель/Вид/Контроллер и Модель/Вид я буду использовать одно обозначение MVC, что означает Model/View/Controller, не делая на данном этапе разницы между этими паттернами. А теперь давайте рассмотрим применение MVC на примере игры "Ищем пары".

Проект игры выполнен полностью в среде Delphi 6.0 с использованием ее стандартных инструментов: компилятора ресурсов brc32 и редактора ресурсов Image Editor. Пакет состоит из двух частей: редактора уровней и собственно игры. Давайте приступим к рассмотрению редактора. Его исходный текст хранится в файле Editor.zip (30 kb). Скачайте его и распакуйте в любую папку.
Поскольку уровни в игре могут иметь весьма сложную форму, я решил, что самое простое, что я могу сделать для их создания - написать простенький редактор уровней. В самой папке, куда Вы распаковали архив, вы увидите следующие файлы:

Когда вы запустите Editor.exe, то Вы увидите окно, похожее на то, что Вы видите на рисунке:

Внешний вид окна может отличаться в зависимости от используемых скинов, и расширений оболочки Windows.
Если Вы откроете исходный код редактора уровней из подпапки Source папки, куда Вы распаковали архив, то увидите как минимум следующие файлы:В папке также могут находиться другие файлы, если Вы уже открывали проект в среде Delphi или пытались его компилировать - среда создает ряд временных файлов, которые помогают в работе и хранят Ваши настройки, но они не обязательны. Как я уже говорил, основная логика игры заложена именно в модели, то есть в файле common.pas. Этот же файл находится и в проекте игры (см. ниже), скопирован он исключительно для удобства. В этом файле Вы найдете ряд служебных объявлений и объявление главного объекта - TGameField. Все методы подробно прокомментированы (даже, наверное, слишком подробно), поэтому для начинающего программиста не будет сложностей разобраться в том, для чего же предназначен тот или иной метод. Вид находится в файлах с именем MainEd.* - там находится собственно форма и ее реализация. Код также прокомментирован. Хочется остановиться немного на взаимодействии между моделью и видом, столь подробно описываемом в любом руководстве по паттерну MVS. В начале Вид создает экземпляр класса TGameField (он называется GameField) и пытается загрузить в него данные по умолчанию, даже если это не получилось - GameField просто примет все значения по умолчанию, то есть не будет содержать ни одного уровня. Далее Вид регистрирует свой обработчик FieldChange у Вида. Это является очень важным моментом. Воздействие со стороны пользователя на Вид осуществляется с помощью мыши, управление с клавиатуры не предусмотрено. Вид преобразует щелчки мыши по игровому полю в два числа - координаты ячейки, по которой был осуществлен щелчок и передает эти координаты в GameField через метод Click. GameField рассчитывает изменение состояния игрового поля (для редактора это изменение состоит в том, что состояние клетки переключается между активным и неактивным) и вызывает свое событие OnChange, то есть зарегистрированный обработчик FieldChange. Благодаря этому, Виду нет нужды поддерживать себя актуальным где-либо, кроме процедуры FieldChange - при любых изменениях модели FieldChange будет вызван и настроит Вид в соответствии с моделью. Это выражается в некоторой потере производительности, так как в случае с полным ручным контролем мы должны изменять только один параметр формы - например, заголовок, тогда как сейчас мы не знаем, что конкретно было изменено (хотя эту функциональность при желании можно предусмотреть и это несложно) и вынуждены перезагружать Вид полностью, зато это с успехом компенсируется гибкостью - при изменении ширины или высоты нам нет нужды помнить, что требуется перерисовать поле - все будет выполнено полностью автоматически без нашего ведома! Вы никогда ничего не забудете - об этом позаботится Модель! Обработка оповещения Модели в Редакторе весьма сложна - он изменяет доступность целого ряда элементов меню в зависимости от наличия уровней, изменяет заголовки элементов меню, размеры игрового поля и, наконец, перерисовывает само игровое поле.
Каждая из этих операций производится при любом изменении модели - даже если мы сменили заголовок уровня, игровое поле все-равно перерисуется. Это можно обойти, если дополнить FieldChange специальным множеством флагов, показывающим, какое конкретно событие именно произошло. Но это усложнит реализацию, тогда как Редактор вообще является внутренним продуктом "для служебного пользования" и небольшая неоптимальность и "тормоза" вполне (для меня) допустимы и "оптимизацией ради оптимизации" я заниматься не стану.

А теперь давайте рассмотрим второй проект - саму игру. Игра находится в архиве Doubles.zip (36 kb). Скачайте архив и распакуйте его в любую папку. В папке Вы найдете следующие файлы:Принцип работы игры полностью идентичен принципу работы Редактора - только интерфейс отличается. После запуска игры, Вы увидите окно, похожее на приведенное на рисунке:

Внешний вид окна может отличаться в зависимости от используемых скинов и расширений оболочки Windows.
В процессе игры, вы можете увидеть и другие окна. Все они выполнены в максимально аскетичном стиле стандартных диалогов VCL, чтобы не отвлекать Вас от основной задачи - понимания принципа работы игры. Например, для ввода имени победителя я применил стандартный InputQuery:

Также для отображения таблицы рекордов я использовал обычный TMemo с разделителями-табуляциями:

Это приводит к деформации таблицы при вводе имен слишком длинных, или слишком коротких, но решать эту проблему я (пока) не буду. Возможно, потом я дополню статью версиями программ, выполненных на основе этой: например, с красивым интерфейсом, плавно переворачивающимися картами и другими вещами, но это будет уже слишком сложно для первого обучающего проекта. Этот подход вполне работоспособен, а начинающим программистам следует навсегда запомнить правило: сперва сделай, чтобы работало, потом сделай, чтобы работало хорошо, потом сделай, чтобы это было красиво. Нарушение этого правила приводит к непонятному исходному коду, непонятным программам, которые трудно отлаживать и модернизировать. Данная программа является всего лишь первой ступенью - чтобы работало. Открыв подкаталог Source каталога, куда вы распаковали архив, вы увидите следующие файлы:

В папке также могут находиться другие файлы, созданные средой Delphi в процессе работы. При запуске программы, она создает в первую очередь Модель - объект GameField класса TGameField. Далее программа пытается загрузить карту уровней из ресурсов. Затем программа пытается загрузить таблицу рекордов в глобальный объект RecordTable класса TRecordTable - это скрытый файл, имеющий то же имя, что и программа, но с расширением Dat.

При всей моей нелюбви к глобальным объектам, здесь я не смог придумать вменяемой архитектуры, позволяющей двум формам одновременно пользоваться одним и тем же объектом, кроме как перекрытия конструктора с одним параметром - этим самым объектом.
Затем программа сравнивает, от той ли игры у нас имеются рекорды, полагаясь только на количество уровней - иначе просто возникнет ошибка при обращении к уровню, которого нет либо в таблице рекордов, либо на игровом поле! После всего, я создаю меню из заголовков уровней, что позволяет мне выбирать уровень через меню. Наконец, делаем первый уровень активным и устанавливаем оповещатель на Модель. Оповещатель сработает автоматически, поэтому Вид будет обновлен автоматически. Обработка оповещения модели очень простая, в отличие от Редактора - здесь только изменяются размеры игрового поля, размер формы, если это необходимо и перерисовывается игровое поле.

Изменение размера формы подробно прокомментировано. Суть всего кода заключается только в том, чтобы при изменении размера все ячейки имели одинаковый размер, но при этом справа не оставалось пустого места. Также там обеспечен минимальный размер ячейки - 25 пикселей.

Это ограничение можно уменьшить, но понравится ли Вам игра на скорость, где надо буквально выцеливать каждую карту мышкой, или запоминать совершенно незапоминающиеся комбинации размера 3x3 пикселя? Мне бы такая игра точно не понравилась.
Таймер обеспечивает регулярное отображение времени на Статусной строке, в том числе и статус победителя. Далее все функции носят самодокументируемые имена - рисование вписанной в клетку карты, обработка (переадресация Модели) щелчка мыши по игровому полю, изменение текущего уровня и совсем простые функции - отображения справки и таблицы рекордов. В конце работы программы игровое поле GameField и таблица рекордов RecordTable уничтожаются, что приводит к автоматическому сохранению таблицы рекордов.

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

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

Когда Вы поймете, что данная статья для Вас слишком проста, Вы сможете перейти к другим, более сложным статьям, написанным более опытными авторами. Я рекомендую прочитать также статью обзорного характера гибкость и масштабируемость компьютерных игр про правильный подход к проектированию сложных компьютерных игр. Ну и, конечно, никогда не забывайте про своих верных помощников www.rambler.ru, www.yandex.ru, www.google.ru

Ну и напоследок хочется пожелать удачного игростроения! Доставляйте удовольствие не только себе, но и другим.

Все примеры откомпилированы в среде Delphi 6.0 с использованием RunTime пакетов vcl60.bpl, rtl60.bpl.
Специально для Королевства Delphi


К материалу прилагаются файлы: