Применим технологию ООП для программирования игры.
Используем уже известную нам по программе «Жизнь» консольную графику,
основанную на управляющих 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%]
Для его использования в программе нужно включить в текст команду
Perluse 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
—
целочисленный номер символа из таблицы символов
UCS. Курсор сдвигается. Изменения проявятся после
вызова $char
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»). Одна (красная) связана с экраном, её начало будет находиться в левом верхнем углу экрана. Другая (жёлтая) — со стаканом, с началом в его левом верхнем углу. И, наконец, третья, зелёная, будет связана с текущей фигурой. Начало последней системы координат разместим в левом верхнем углу прямоугольника с вертикальными и горизонтальными сторонами, описанного около фигуры.
Первая координатная система нужна, поскольку рисовальные процедуры из пакета
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()
Поворачивают фигуру соответственно влево и вправо.
show()
hide()
Показаться и скрыться (стать невидимой).
move($dir
)
Сдвинуться или повернуться. Параметр
указывает направление сдвига или
поворота в соответствии с таблицей:
$dir
значение
| направление сдвига/поворота |
---|---|
0 | сдвиг вправо |
1 | сдвиг вверх |
2 | сдвиг влево |
3 | сдвиг вниз |
4 | поворот влево |
5 | поворот вправо |
Данная процедура не приводит к перемещению фигуры на экране. Она всего лишь
меняет её внутренние свойства (к примеру, координаты фигуры в стакане). Чтобы
действительно переместить фигуру на экране, нужно вызвать процедуру
showMove
.
showMove($dir
)
Переместиться в соответствии с параметром
и показать перемещение на
экране.
$dir
canMove($dir
)
Возвращает 1
, если фигура может сдвинуться или повернуться
в соответствии с направлением
;
в противном случае возвращает $dir
0
.
fall()
Упасть.
init()
Подготовить пользовательский интерфейс (экран, клавиатуру) к работе. Вывести баннер с названием программы и сведениями об авторах.
term()
Завершить работу пользовательского интерфейса: очистить экран, зажечь курсор, вернуть экран в первоначальное состояние.
showFigure($fig
, $x
, $y
, $color
)
Показать фигуру
(объект класса
$fig
Tetris::Figure
) в стакане, используя «жёлтые» координаты
и $x
. Если параметр
$y
не указан, используется
собственный цвет фигуры, в противном случае используется указанный номер цвета.
$color
showNextFigure($fig
)
Показать фигуру
как следующую,
то есть вне стакана, в специально предназначенном для этого месте под надписью
«Следующая:».
$fig
showScore($score
)
Показать текущий счёт
рядом
с надписью «Счёт:».
$score
showGlass()
Показать стакан.
play($fileName
)
Проиграть аудиофайл, чьё имя указано в параметре
.
$fileName
Текста пока нет