Можно ли в Delphi создать что-нибудь непохожее на базы данных? «Нет! — ехидно
скажут программисты на Си, — Все непохожее на базы данных пишеться на СиСи+».
Хотя это еще как сказать. Мне несколько раз подряд попадались исходные тексты
некоторых игр, «написанные на Си», в которых самого Си было максимум процентов
5-10, а все остальное — чистой воды Ассемблер! Случайность это или все-таки
закономерность? На мой взгляд, с таким же успехом можно использовать связку
Delphi-Ассемблер. Тем более, что в Delphi есть все для создания крупномасштабных
проектов, в том числе и игр (например, поддержка OpenGL — для работы с
3D-графикой; OpenGL, кстати, использовался при создании Quake III). Я, конечно,
не собираюсь в этой статье рассказывать, как создать в Delphi Quake III (не по
мне такие задачи). Речь пойдет о более приземленных вещах. А именно, о маленькой
простой игре, которой можно дополнить набор мелкомягких игр, устанавливаемых
вместе с Windows. Эта игра в народе называется «пятнашки» — очень популярная
раньше настольная (вернее даже, наручная) игра, которая продавалась в квадратных
коробочках с большой цифрой 15 на крышке, в которой нужно было расставить
квадратики с числами в порядке от 1 до 15. Ну что, вспомнили? Нет!?… Да, трудное
у вас было детство… Ну да ладно. Итак, значит, будем писать «пятнашки» в Delphi.
Для начала приступим к созданию интерфейса. Здесь все полностью зависит от
вашей фантазии. Но я остановлюсь на праздном сером оформлении (см. Рис. 1).
Теперь о том, как получить такой образец серости и примитивизма. Сначала на
форме располагается компонент TPanel со свойствами BevelInner и BevelOuter,
равными bvLowered, для создания эффекта бордюра по краям формы. Затем на
полученную панель ставится еще одна панель меньшего размера со свойством
BevelOuter равным bvRaised, BevelInner —bvLowered, а цвет Color —clBlack. Эта
вторая панель будет фоном для кнопок с цифрами. Затем добавляются кнопки
(компоненты TButton или TSpeedButton) с названиями about, game, exit и кнопка
начать игру (компонент TSpeedButton). Расположение их показано на рисунке.
Теперь надо создать те самые квадратики с цифрами. Эту роль играют компоненты
TButton. Расположите их на второй панели именно так, как показано на рисунке, то
есть, кнопка с цифрой (Caption) 1 должна иметь имя (Name) Button1, кнопка 2
—Button2 и т.д. Это важно. Объясняю, почему. При добавлении компонента на форму
он автоматически заносится в список (массив) компонентов формы и получает индекс
начиная с 0. В дальнейшем взаимодействие с кнопками программой будет
осуществляться через их индексы. Поэтому если у вас кнопка с именем Button1
будет иметь Caption 2, вам просто будет сложнее работать с ней. Что касается
размеров кнопок, то я установил параметры Heigth и Width каждой по 50. Да, еще.
Чтобы посмотреть индекс кнопки, размещенной на форме, в ее процедуре-обработчике
события (Event), например, OnClick, наберите: form1.caption:=inttostr((sender as tbutton).componentindex);
Это приведет к тому, что при нажатии на кнопку ее индекс будет выводиться в
заголовке формы. Потом эту строчку можете удалить.
Теперь, когда интерфейс программы готов, можно перейти непосредственно к
программированию. Опишем глобальные переменные модуля. В разделе var (там, где
написано Form1: TForm1) напишите: a:array[1..16]of byte; i,k,fl,rn,p,m:byte; x,y,x1,y1,num,pos,lr,td,lr1,td1:integer; flag:boolean;
(назначение переменных я буду объяснять далее). После этого нужно написать
процедуру, генерирующую массив случайных чисел от 1 до 16 так, чтобы они не
повторялись. Потом по этому массиву будут расставляться кнопки с числами.
Случайные числа будем заносить в массив a. Цифра 16 будет означать пустую
область, на которой нет кнопки. Процедура заполнения массива случайными числами
выглядит следующим образом: procedure rndarr; begin for k:=1 to 16 do a[k]:=0; randomize; i:=1; repeat rn:=random(16)+1; fl:=0; k:=1; while (a[k]<>rn) and (k<>17) do inc(k); if k=17 then begin a[i]:=rn; Inc(i); end; until i=17; end;
Обращаю ваше внимание на то, что приведенная выше процедура не является
обработчиком какого-либо события, поэтому не нужно ее объявлять в интерфейсной
части модуля. Просто наберите ее как есть после Implementation {$R *.DFM}. Такие
процедуры называются пользовательскими. (Для того чтобы узнать, какие числа
появляются в массиве после выполнения этой процедуры, можно использовать окно
Watch).
Визуализация массива
На этом принципе работают
многие игры. Например, тот же тетрис: имеется некий двумерный массив (стакан), в
котором нули — пустые позиции, единицы — квадратики, из которых строятся фигуры
тетриса. Далее в массиве эти единицы сдвигаются, и массив выводится на экран с
заменой 0 и 1 на графические элементы. Это повторяется несколько раз, что
создает эффект падения фигур. В данном случае все должно происходить аналогичным
образом: все перемещения производятся в массиве, а в соответствии числам из
массива располагаются сами «пятнашки». Для того чтобы это осуществить, нужно
написать процедуру, в которой читаются элементы массива, соответствующие
индексам компонентов-кнопок с цифрами. Затем происходит обращение к этим кнопкам
по их индексу и размещение их соответственно значениям в массиве. То есть, если
первым элементом массива является число 15, то первой кнопкой в левом верхнем
углу будет кнопка с цифрой 15 и т.д. Вот эта процедура (наберите ее после
первой): procedure drawarr; begin p:=0; for i:=0 to 3 do for k:=0 to 3 do begin p:=p+1; if a[p]<>16 then begin with TButton(form1.components[a[p]+5]) do begin left:=k*50+2; top:=i*50+2; end; end; end; end;
Так как у меня первая кнопка в массиве компонентов формы имеет индекс 6
(:-)), то я при обращении к кнопкам прибавляю к значению из массива число 5,
чтобы получить их индексы: TButton(form1.components[a[p]+5]));
Кнопки имеют размер 5050, первая из них расположена правее на 2 пикселя от
левого края черной (второй) панели и на 2 пикселя ниже от верхнего ее края,
поэтому, чтобы правильно их расположить, будем умножать переменные i и k на 50 и
прибавлять 2. Таким образом, если, например, i и k равны 0, то координаты первой
кнопки в левом верхнем углу, по отношению к черной панели, равны (2,2), если
i=0, k=1, то координаты —(2,52), и т.д.
Начать игру
Начнем
работу с процедурами-обработчиками событий. Первая из них — обработчик события
OnClick кнопки (TSpeedButton) «Начать игру». Дважды щелкните по этой кнопке. При
этом откроется редактор кода с таким заголовком процедуры: procedure TForm1.SpeedButton2Click(Sender: TObject); begin rndarr; drawarr; form1.speedbutton2.caption:='Начать игру заново'; end;
Таким образом, при нажатии на кнопку «Начать игру» происходит генерация
массива случайных чисел процедурой rndarr, затем — размещение кнопок с цифрами
соответственно числам в массиве процедурой drawarr и, наконец, изменение
названия кнопки Начать игру на Начать игру заново. Теперь можете запустить
программу и поклацать кнопкой «Начать игру».
О кнопках с
цифрами
Предпоследний этап. Нужно сделать так, чтобы при нажатии на
кнопку с цифрой определялось направление ее движения, т.е. то место, где нет
другой кнопки. Для этого клацните дважды, например, по кнопке 15 и в появившейся
процедуре-обработчике события OnClick (как и в предыдущий раз) между Begin и End
наберите: if flag then exit; {если flag=true — выход из процедуры} pos:=0;m:=0;num:=0; num:=(sender as tbutton).componentindex-5; {num — номер нажатой кнопки} for i:=1 to 16 do if a[i]=num then pos:=i; {определение ее позиции в массиве} {определение направления движения} if (pos-1>0)and(pos-1<>4)and(pos-1<>8)and(pos-1<>12)and(a[pos-1]=16)then m:=1; if (pos+1<17)and(pos+1<>5)and(pos+1<>9)and(pos+1<>13)and(a[pos+1]=16)then m:=2; if (pos-4>0)and(a[pos-4]=16)then m:=3; if (pos+4<17)and(a[pos+4]=16)then m:=4; if m=0 then exit; {если вокруг кнопки пустой позиции нет — выход} flag:=true; {установливаем флаг, означающий, что кнопка в движении} lr1:=(sender as tbutton).left; {сохраняем в lr1 и td1 начальные координаты} td1:=(sender as tbutton).top; lr:=0;td:=0; form1.move(sender); {вызов процедуры перемещения кнопки}
В Object Inspector в разделе Events для остальных кнопок с цифрами напротив
события OnClick укажите эту процедуру. Таким образом, она будет выполняться при
нажатии любой кнопки с цифрой. В этой процедуре определяется, есть ли рядом с
нажатой кнопкой пустая позиция. Если таковая слева, то m:=1, справа —m:=2,
сверху —m:=3, снизу —m:=4. Определение «слева или справа» происходит путем
вычета или прибавления к позиции нажатой кнопки единицы, определение «сверху или
снизу» — через вычет или прибавление 4. Переменная Flag служит для того чтобы
определить, движется ли какая-либо кнопка или нет. Если движется — процедура не
должна выполняться. Процедура перемещения кнопки form1.move описана далее.
Двигай кнопкой
Чтобы как-то «оживить» игру, требуется
движение. В данном случае — движение кнопок. Для реализации заметного
человеческому глазу и более-менее плавного движения потребуется компонент
TTimer. Выберите его из списка компонентов и расположите в любом месте формы.
Установите его свойство Interval равным 1. Затем в его единственном событии
OnTimer в Object Inspector напишите move и нажмите Enter. На экране появится
тело процедуры-обработчика этого события: procedure TForm1.move(Sender: TObject); begin timer1.enabled:=true; {включение таймера} case m of {исходя из направления движения} 1:dec(lr,5); {уменьшаем на 5 lr} 2:inc(lr,5); {увеличиваем на 5 lr} 3:dec(td,5); 4:inc(td,5); end; with TButton(components[num+5])do begin {перемещаем компонент} left:=lr1+lr;top:=td1+td;end; if (abs(lr)=50) or (abs(td)=50) then {когда пройдено 50 шагов} begin timer1.enabled:=false; {выключаем таймер} lr:=0; td:=0; flag:=false; case m of {перестановка чисел в массиве} 1:begin a[pos-1]:=a[pos];a[pos]:=16;end; 2:begin a[pos+1]:=a[pos];a[pos]:=16;end; 3:begin a[pos-4]:=a[pos];a[pos]:=16;end; 4:begin a[pos+4]:=a[pos];a[pos]:=16;end; end; fl:=0; for i:=1 to 16 do if a[i]<>i then fl:=1; {определяем, расставлены ли кнопки по порядку от 1 до 15} if fl=0 then showmessage('Вы выиграли!'); {если кнопки расставлены как надо — сообщение «Вы выиграли!»} end;
end;
Основная часть программы уже написана. Осталось внести последние штрихи.
Создайте форму AboutBox и в процедуре-обработчике события OnClick кнопки About
напишите: AboutBox.Show. Аналогично, для кнопки Exit напишите Close — для выхода
из программы. Да, еще. В Object Inspector в параметре формы BorderStyle выберите
bsDialog — форму с таким стилем невозможно развернуть или изменить ее размер.
Также, по желанию, можно сделать меню (компонент TPopUpMenu) для кнопки Game.
|