Идеи реализации

Применим технологию ООП для программирования игры.

Используем уже известную нам по программе «Жизнь» консольную графику, основанную на управляющих escape-последовательностях. Но, в отличие от программы «Жизнь», откажемся от непосредственного управления терминалом (команды print, печатающие на терминал трудно запоминаемые escape-последовательности), и положимся на библиотеку, которая делает всю эту работу за нас, предоставляя нам удобный набор процедур.

Раз уж мы решили применить ООП для решения нашей задачи, продумаем объектную модель игры.

Полезно будет отделить в программе ту часть, которая отвечает за пользовательский интерфейс (графика, работа с клавиатурой и звук). Для этой цели предусмотрим пакет процедур Tetris::UI (User Interface). Такое отделение будет полезно в случае, если мы в дальнейшем захотим усовершенствовать пользовательский интерфейс, например, использовать вместо скромной, консольной, полноценную графику, X Window System. Тогда нам придётся внести изменения лишь в текст модуля Tetris::UI вместо того, чтобы отыскивать по всему тексту программы вызовы графических процедур или процедур для работы с клавиатурой.

Можно пойти ещё дальше — предусмотреть в программе несколько вариантов пользовательского интерфейса, на выбор, в зависимости от ключей в командной строке. Для каждого из вариантов пользовательского интерфейса можно создать свой пакет процедур, например, Tetris::UI::Term::Slangy для консольной графики и Tetris::UI::X11 для полноценной. Оба пакета будет содержать один и тот же набор процедур (ядро), так что остальная часть программы будет «общаться» с пакетом только через это ядро одним и тем же способом. Кроме того, если это понадобится, каждый из этих пакетов можно дополнить другими, вспомогательными процедурами и переменными, специфическими для данного типа интерфейса. Остальная часть программы будет обращаться к ядру пакета, отвечающего за интерфейс, даже не зная, какой тип интерфейса он реализует.

Но пока не будем браться за всё сразу, а реализуем простой пользовательский интерфейс на основе консольной графики в пакете Tetris::UI. Содержимое пакета обсудим позднее.

Есть несколько библиотек, которые упрощают работу с консольной графикой. Наиболее популярными из них являются libncurses и libslang. Их возможности примерно одинаковы, однако в настоящее время libncurses испытывает определённые трудности, если терминал работает с кодировкой UTF-8, которая даёт возможность одновременно вводить и выводить символы из всех национальных алфавитов, используя всю таблицу символов UCS (большой файл!). Различные символы в кодировке UTF-8 имеют различную длину (в байтах), что пока сбивает с толку библиотеку libncurses. Не дожидаясь, когда ошибки в этой библиотеке будут исправлены, воспользуемся библиотекой libslang, которая не испытывает подобных трудностей.

Обе эти библиотеки написаны на языке C, и предназначены для использования в программах, написанных на C. Чтобы применить возможности таких библиотек в программах, написанных на других алгоритмических языках, требуются дополнительные ухищрения. Мы приспособили библиотеку libslang для использования в Perl, создав пакет процедур Term::Slangy. Этот пакет должен быть установлен на компьютере.

Пользователи операционной системы Mandriva Linux версий 2010.0 и 2010.1 могут скачать пакет RPM и установить его при помощи команд:

% su Password: *пароль суперпользователя* # rpm -vhi perl-Term-Slangy-323.0.0-1tz2010.0.i586.rpm Подготовка... ########################################### [100%] 1:perl-Term-Slangy ########################################### [100%]

Для его использования в программе нужно включить в текст команду

Perl
use Term::Slangy;

После этого станут доступны процедуры из этого пакета. Обсудим их подробнее.

initTT()

Эта процедура обязательно должна вызываться перед началом использования консольной графики. Она определяет размер терминала (количество строк и столбцов), узнаёт тип терминала и отыскивает в базе данных terminfo, какие управляющие escape-последовательности отвечают каким управляющим действиям.

termTT()

Эта процедура обязательно должна вызываться после использования консольной графики, иначе терминал останется в изменённом, испорченном состоянии, и возвращать его в исходное придётся командой reset.

rows()
cols()

Процедуры возвращают размер терминала — количество строк и столбцов соответственно.

utf8Mode($mode)

Включает поддержку кодировки UTF-8. Если $mode равен -1, этот режим будет установлен, если терминал настроен на использование этой кодировки. Как правило, именно это и требуется. Вызов данной процедуры должен предшествовать вызову initTT().

clear()

Процедура очищает экран. Видимые изменения наступят лишь после вызова процедуры refresh().

cursorVisibility($mode)

Изменяет вид курсора в зависимости от $mode: при 0 курсор становится невидимым, при 1 — видимым. Изменения проявятся после вызова refresh().

move($row, $column)

Перемещает курсор в точку экрана с координатами ($column, $row). Изменения проявятся после вызова refresh().

writeString($string)

Выводит строку $string в текущем положении курсора. Курсор сдвигается. Изменения проявятся после вызова refresh().

writeChar($char)

Выводит символ $char в текущем положении курсора. $char — целочисленный номер символа из таблицы символов UCS. Курсор сдвигается. Изменения проявятся после вызова refresh().

fillChar($row, $col, $height, $width, $char)

Заполняет символом $char прямоугольную область экрана. Левый верхний угол прямоугольника в точке экрана с координатами ($row, $col), высота и ширина — $height и $width. Изменения проявятся после вызова refresh().

hLine($row, $col, $width, $char)
vLine($row, $col, $height, $char)

Заполняет символом $char горизонтальную и вертикальную линию, начиная от точки с координатами ($row, $col). Соответственно ширина и высота линии — $width и $height. Изменения проявятся после вызова refresh().

colorPair($color, $foreground, $background)

Определяет цветовую пару (передний план, фон) и присваивает ей номер $color. Передний план — $foreground, фон — $background. Чтобы не запоминать значения параметров $foreground и $background, в классе определены константы, приведённые в таблице:

название цветацветзначение параметра
чёрный COLOR_BLACK
красный COLOR_RED
зелёный COLOR_GREEN
коричневый COLOR_BROWN
синий COLOR_BLUE
фиолетовый COLOR_MAGENTA
циан COLOR_CYAN
светло-серый COLOR_LGRAY
серый COLOR_GRAY
ярко-красный COLOR_BRIGHT_RED
ярко-зелёный COLOR_BRIGHT_GREEN
ярко-жёлтый COLOR_BRIGHT_BROWN
ярко-синий COLOR_BRIGHT_BLUE
ярко-фиолетовый COLOR_BRIGHT_MAGENTA
яркий циан COLOR_BRIGHT_CYAN
ярко-белый COLOR_BRIGHT_WHITE

Процедуры, использующие цвета, должны ссылаться на них по номеру $color.

fillColor($row, $col, $height, $width, $color)

Заполняет цветом $color прямоугольную область экрана. Левый верхний угол прямоугольника в точке экрана с координатами ($row, $col), высота и ширина — $height и $width. Изменения проявятся после вызова refresh().

setColor($color)

Делает цвет $color текущим. Дальнейшее рисование осуществляется этим цветом.

initKeypad()

Эта процедура должна вызываться перед вызовами процедур, работающих с клавиатурой.

refresh()

Эту процедуру следует вызвать каждый раз, когда нужно сделать видимыми результаты работы «рисующих» процедур.

getKey()

Останавливает программу в ожидании нажатия клавиши. Как только клавиша нажата, возвращает номер введённого символа из таблицы символов UCS.

inputPending($msec)

Процедура ожидает нажатия клавиши в течение $msec миллисекунд. Если за этот период клавиши не нажимались, возвращает 0. Иначе завершается немедленно после нажатия, возвращая 1.

Нам потребуются три системы декартовых координат (40.3. «Системы координат в программе tetris.pl»). Одна (красная) связана с экраном, её начало будет находиться в левом верхнем углу экрана. Другая (жёлтая) — со стаканом, с началом в его левом верхнем углу. И, наконец, третья, зелёная, будет связана с текущей фигурой. Начало последней системы координат разместим в левом верхнем углу прямоугольника с вертикальными и горизонтальными сторонами, описанного около фигуры.

Рисунок 40.3. Системы координат в программе tetris.pl

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

Будем рассматривать фигуры тетриса как объекта класса Tetris::Figure.

В начале работы программы создаются семь фигур и размещаются в массиве. Затем происходит следующее. Из массива фигур выбирается случайным образом одна — «текущая». Она размещается в верхней части стакана и начинает своё падение, отзываясь на нажатия клавиш управления. Как только текущая фигура утратит возможность сдвигаться вниз, фигура становится набором клеточек на дне стакана. Однако она не прекращает своего существования и ждёт своего часа в массиве фигур, пока снова не выпадет случайно её номер.

Под действием управляющих клавиш фигура может сдвигаться вправо/влево, поворачиваться на 90° в обе стороны, или упасть на дно стакана. В отсутствие нажатий в течение определённого времени фигура сдвигается вниз, пока это возможно. Следует учесть, что все описанные сдвиги и повороты выполняются только в пределах стакана, причём таким образом, чтобы фигура не перекрывала клеточки, уже находящиеся на дне стакана. Если клеточки или границы стакана мешают выполнить манёвр, ничего не происходит.

Данные объекта $figure класса Tetris::Figure включают информацию о форме фигуры и её цвете. Такие разнородные сведения удобно хранить в ассоциативном массиве с ключами shapes (форма) и color (цвет).

Поскольку фигуры не обладают самостоятельной способностью нарисоваться на экране (обязанность рисовать возложена на класс Tetris::UI), просто пронумеруем цвета числами и разместим эти числа как значения, отвечающие ключу color. Класс Tetris::UI разберётся, как сопоставить эти номера реальным цветам, используемым при рисовании.

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

Вместо вычислений форм фигуры, получающихся при поворотах, мы для каждой фигуры создадим массив, содержащий всё такие её формы. Ссылку на этот массив разместим как значение, отвечающее ключу shapes. Самый первый (имеющий индекс 0) элемент массива содержит текущую форму фигуры. При повороте влево этот первый элемент удаляется из начала массива и добавляется в конец. При вращении вправо, наоборот, удаляется последний элемент и добавляется в начало. На наше счастье некоторые фигуры обладают поворотной симметрией. Фигуры «I», «Z», «S» имеют симметрию второго порядка, поэтому их формы повторяются после двукратного поворота; их массив форм будет содержать только два элемента. Фигура «Q» имеет симметрию четвёртого порядка, и её массив форм будет состоять из единственной формы. Оставшиеся фигуры «T», «J», «L» не имеют поворотной симметрии, поэтому в их массивах форм будет по четыре элемента.

Но как закодировать ту или иную форму фигуры? Мы предлагаем не совсем обычный подход. Каждую ряд фигуры будем кодировать с помощью строки, в которой заполненные клетки изображаются символом , а отсутствующие — символом (вместо этих символов можно взять любые другие, скажем, # и пробел). Строки, соответствующие рядам фигуры, соединяются вместе, а в качестве разделителя рядов используется символ | (см. 40.1. «Формы фигур тетриса и кодирующие их строки»). Информации, заключённой в такой строке, вполне достаточно, чтобы, к примеру, вычислить ширину и высоту фигуры.


new($shapes, $color)

Конструктор новых фигур. Передаваемые параметры — информация о форме ($shapes) и цвете ($color) фигуры.

width()
height()

Возвращают соответственно ширину и высоту фигуры (в клеточках).

getCell($j, $i)

Возвращает 1, если в системе координат, связанной с фигурой («зелёной», см. рисунок 40.3. «Системы координат в программе tetris.pl»), клетка с координатами ($j, $i) принадлежит фигуре, и 0 в противном случае.

shape()

Возвращает текущую форму фигуры, точнее, кодирующую эту форму строку. Напомним, что это первый элемент массива форм.

rotateLeft()
rotateRight()

Поворачивают фигуру соответственно влево и вправо.

Текста пока нет

Информатика-54© А. Н. Швец