Когда-то, году в 2002-м, на мой компьютер попала интересная игрушка, так сказать, класса тетриса (подробное описание геймплея приведено ниже); она очень полюбилась моей маме, которая играла в эту игру часами. Однако был досадный недостаток: после, кажется, 10 запусков игра начинала требовать регистрацию, которая, что удивительно, была бесплатной, но через интернет, что, само собой, являлось непреодолимым препятствием, поскольку интернета-то никакого тогда в наших краях в глаза не видывали, хоть и слыхали, что есть такая штука. Приходилось постоянно переустанавливать. Через года три, когда интернет уже провели, а игра успела стать shareware и начать просить за регистрацию сколько-то денег, я попробовал её зарегистрировать, однако сайт производителя был к тому времени скорее мёртв, чем жив, и, по-видимому, остаётся таким и по сей день. В интернете легко находится shareware-версия игры, множество, не побоюсь этого слова, кейгенов, являющихся на самом деле троянами, и ни одной возможности зарегистрировать игру, чтобы мама могла в неё играть уже совсем на другом компьютере. В какой-то момент я подумал: а почему бы просто самому не сделать аналогичную игру и решить тем самым проблему на корню? Заодно из этого может получится какой-никакой hello-world по разработке простой игры для ПК в современных условиях — который я и предлагаю вниманию читателей.  Итак, что же за игру мы будем делать? Суть такова. В прямоугольном стакане 7×13 падает горизонтальная палка, состоящая из 3 цветных блоков (всего есть 5 цветов). Во время движения её можно двигать вправо-влево, а также менять блоки местами в порядке ротации справа налево (красный, зелёный, синий → зелёный, синий, красный). Как только палка коснётся пола стакана или же какого-нибудь из находящихся в стакане неподвижных блоков, ею больше управлять нельзя. Блоки, составляющие палку, продолжают падение отдельно до тех пор, пока не станут на неподвижный блок или пол стакана. После этого проверяется, не получилась ли в стакане горизонтальная, вертикальная или диагональная линия из трёх или более блоков одного цвета; такие линии уничтожаются. Если сверху уничтоженной линии были блоки, они сползают вниз на образовавшееся пустое место, после этого снова происходит уничтожение образовавшихся линий. Когда всё устаканилось, сверху начинает падать новая палка. За выстраивание уничтожаемых линий игрок получает очки. Игра заканчивается, когда стакан заполнен доверху. Технологии. Игру будем делать на C# (давно хотел посмотреть, что же это такое), OpenGL (DirectX работает только под Windows, а я больше люблю Linux), Mercurial для контроля версий (писать код без VCS — неуважение к себе). Игра будет называться Impressive Solids. Разработку под Windows будем вести в Microsoft Visual C# 2010 Express (распространяется бесплатно). Также нам понадобится TortoiseHg — Windows-клиент системы контроля версий Mercurial. Под системы на базе Linux будем использовать MonoDevelop и консольный hg. Для подключения OpenGL задействуем binding OpenTK. Нужно скачать свежий nightly build (на момент написания статьи: 2011-12-03). Создаём в Visual C# Express новый empty project под названием ImpressiveSolids. Сохраняем. Затем открываем директорию с проектом, вызываем для неё контекстное меню и выбираем TortoiseHg → Create Repository Here. Отмечаем пункты создания файла .hgignore и открытия workbench после инициализации. Открываем в Visual C# Express файл .hgignore и записываем в него следующие строки. Это нужно для того, чтобы система контроля версий не учитывала ненужные бинарные файлы. Внутри директории solution (не проекта; там, где лежит .hgignore) создаём поддиректорию OpenTK и копируем в неё файлы OpenTK*.dll и OpenTK*.dll.config из директории opentk\Binaries\OpenTK\Release\ в архиве OpenTK. В Visual C# Express контекстное меню References → Add Reference → Browse. Выбираем ../OpenTK/OpenTK.dll. Кроме того, нужно добавить reference на System.Drawing с вкладки .NET. Создаём новый класс Game. Это главный класс программы, в нём находится точка входа, а сам он — наследник OpenTK.GameWindow и отвечает за обновление состояния игры (OnUpdateFrame) и перерисовку (OnRenderFrame). Сейчас это будет просто чёрное окно. Заходим в свойства проекта (Project → ImpressiveSolids Properties) и указываем Target framework: .NET Framework 2.0; Output type: Windows Application; Startup object: ImpressiveSolids.Game. Можно сохранять и запускать, должно появиться чёрное окно размером 700×500 с заголовком «Impressive Solids». Если всё прошло гладко, идём в TortoiseHg Workbench и коммитим всё с пометкой «Initial game window». Реализуем управляемое падение палки. Для этого прежде всего нужно задать модель текущего состояния палки. Во-первых, позиция. По умолчанию — сверху по центру стакана. Будем считать, что (0; 0) соответствует верхнему левому углу стакана. Нужно, кстати, задать его размеры MapWidth, MapHeight. Цвета блоков, составляющих палку, будем хранить как массив целых чисел; зададим количество возможных цветов ColorsCount и договоримся, что цвет обозначается целочисленным значением от 0 до ColorsCount − 1. Добавим в класс Game метод New и вызовем его из OnLoad. В этом методе реализуем построение палки из блоков случайных цветов. Попробуем отобразить нашу палку на экране, пока в самом примитивном варианте (блок изображаем цветным прямоугольником, стакан начинаем прямо в верхнем левом углу окна). Внесём в код некоторые изменения. Ухищрения с Nominal/Projection Width/Height понадобились для того, чтобы изображение масштабировалось при изменении размеров окна, но в то же время пропорции не искажались. Теперь сделаем наконец, чтобы палка падала и чтобы работали клавиши ←, →, ↑ (ротация цветов). Коммитим все изменения: «The stick, falling and controllable». Как видим, пока нет проверки на выход за границы стакана. Исправим это упущение в дальнейшем. Займёмся ситуацией, когда палка опустилась на пол или на уже присутствующие в стакане блоки. Пока не будем разбираться с последующим неконтролируемым падением блоков и уничтожением линий, а сделаем просто, чтобы составляющие палку блоки застывали на месте (даже повисая в воздухе) и начинала падать следующая палка. Смоделируем состояние стакана в виде двумерного массива целых чисел. Координаты будут соответствовать клетчатой сетке стакана, значениями будет цвет блока в данной клетке — или отрицательное число, если в клетке пусто. Здесь уже будет необходимо ввести проверку на выход палки за границы стакана, иначе возникнут обращения к массиву по несуществующим индексам. Теперь можно быстро набросать блоков до самого верха, как в старом добром «Тетрисе». Для тестирования можно увеличить скорость падения, заменив 0.02f на 0.2f, а вообще надо будет сделать возможность ускорения по нажатию клавиши ↓. Не забываем коммитить изменения в репозиторий Mercurial: «Fixing blocks after the stick fell». Следующее, что нам нужно сделать — это чтобы блоки не зависали в воздухе, а продолжали падать вниз, пока не упрутся. В этот период времени палки на экране нет, управлять ничем нельзя. В связи с этим введём в игру понятие состояния. Игра в каждый момент времени находится в одном из следующих состояний: Сделаем соответствующие объявления в коде. Чтобы изобразить плавное падение блоков и не усложнять при этом модель стакана (Map), прибегнем к хитрости. Пусть блок, который падает из клетки (X; Y) в клетку (X; Y + 1) — а куда ещё ему падать? — числится в клетке (X; Y) вплоть до момента окончательного попадания в нижнюю клетку; а дополнительно будем хранить дробное смещение блока по вертикали, которое будет постепенно увеличиваться, пока не превысит единицу. Т. е. реальные координаты блока будут не (X; Y), а (X; Y + Δ), это надо будет учесть в OnRenderFrame. Чтобы падать могла сразу целая колонна из блоков, под которыми образовалась дырка, клетки карты перебираем снизу вверх (нижний блок увлекает за собой верхний) и учитываем не только пустоту клетки, но и статус ImpactFallOffset (потому что положительное число означает, что этот блок падает вниз). Коммитим с пометкой: «Blocks fall on impact until stabilized». Пришла пора наконец заняться основным элементом геймплея: уничтожением выстроенных линий одного цвета. Зададим минимальную длину линии. Достаточно пройти по всем клеткам, в которых есть блоки, и от каждой попытаться построить линию в одном из четырёх возможных направлений (горизонталь, вертикаль и две диагонали). Если обнаружена линия одного цвета достаточной длины, каждый составляющий её блок заносим в стек. После того, как проверена вся карта, уничтожаем все блоки, занесённые в стек, и помечаем положение как нестабильное. В репозитории помечаем: «Destroying lines of same color». Вы, вероятно, заметили забавное поведение игры при достижении верха стакана: вверху начинают мигать разными цветами, сменяя друг друга, бесконечно появляющиеся и тут же застывающие палки. Давайте обработаем ситуацию проигрыша: ничего не будет происходить, пока пользователь не нажмёт клавишу Enter. Здесь всё просто. Коммитим, без лишних размышлений подписав: «Game over». На этом первая часть разработки закончена. У нас на руках — вполне полнофункциональная игра, в которую уже сейчас можно играть. Во второй части мы займёмся оформлением. Наложим текстуры, будем показывать текущий счёт, рекордный счёт; палку, которая выпадет следующей. Отцентрируем наконец изображение стакана в окне игры, изобразим его границы. В общем, доведём всё до ума. Проект доступен на Google Project Hosting Bitbucket, там можно посмотреть итоговый исходный код, скачать архив с готовым к запуску исполняемым файлом. Программист