Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Costas-Tyros_rus_MS

.pdf
Скачиваний:
31
Добавлен:
14.04.2015
Размер:
5 Мб
Скачать
☆

Глава 14: L­системы

Аристид Линденмайер (Aristid Lindenmayer) – датский ботаник, который изобрёл умный способ описания форм растений. Как вы знаете, ботаники тратят большое количество времени, описывая

форму растений. Их описания обычно делаются на упрощённой версии латыни, изобретённой Кар лом Линнеем (Carl Linaeus).

Линденмайеровский метод описания формы растений основывается на рисующей черепахе, ко торую вы изучили в разделе 13.1. В интернете вы найдёте огромное количество материала по этой теме. Таким образом, я ограничу себя примером, который вы можете использовать как стартовую точку для ваших собственных проектов.

•Создайте GUI проект: Lsys.

•Добавьте пакет к корню дерева проекта: aristid.

•Добавьте форму к пакету aristid: canvas.

•Постройте приложение

•Добавьте класс к aristid: draw. Снимите галочку Create Objects. Постройте проект, что бы вставить класс в дерево проекта.

•Включите пункт File/New панели задач. Добавьте

clauses

onFileNew(S, _MenuTag) :

X= canvas::new(S), X:show().

кTaskWindow.win/CodeExpert/Menu/TaskMenu/id_file/id_file_new.

14.1.Класс draw

Затем, отредактируйте класс draw как показано ниже. Постройте приложение.

%Файл draw.cl class draw

open core, vpiDomains predicates

classInfo : core::classInfo. tree:(windowHandle).

end class draw

%Файл draw.pro

implement draw

open core, vpiDomains, vpi, math clauses

classInfo("plotter/draw", "1.0"). domains

command = t(commandList); f(integer); r(integer); m. commandList= command*.

class facts

pos:(real Delta, real Turn) single. grammar:(commandList) single.

class predicates

move:(windowHandle, pnt, real, commandList) procedure (i, i, i, i).

111

derive:(commandList, commandList) procedure (i, o).

mv:(windowHandle, pnt, real, command, pnt, real) procedure (i, i, i, i, o, o).

iter:(integer, commandList, commandList) procedure (i, i, o).

clauses

pos(4.0, 0.1745329). grammar([f(1)]).

iter(0, S, S) : !. iter(I, InTree, OutTree) :

derive(InTree, S), iter(I 1, S, OutTree).

derive([], []) : !. derive([f(0)|Rest], [f(0),f(0)|S]) : !,

derive(Rest, S). derive([f(_)|Rest],

[f(0), t([r(1),f(1)]), f(0), t([r( 1),f(1)]), r(1), f(1)|S]) : !,

derive(Rest, S). derive([t(Branches)|Rest], [t(B)|S]) : !,

derive(Branches, B), derive(Rest, S).

derive([X|Rest], [X|S]) : derive(Rest, S).

mv(Win, P1, Facing, f(_), P2, Facing) : !, pos(Displacement, _Turn),

P1= pnt(X1, Y1),

X2= round(X1+ Displacement*cos(Facing)), Y2= round(Y1+Displacement*sin(Facing)), P2= pnt(X2, Y2),

drawline(Win, P1, P2).

mv(_Win, P1, Facing, m, P2, Facing) : !, pos(Displacement, _Turn),

P1= pnt(X1, Y1),

X2= round(X1+ Displacement*cos(Facing)), Y2= round(Y1+Displacement*sin(Facing)), P2= pnt(X2, Y2).

mv(_Win, P1, Facing, r(Direction), P1, F) : !, pos(_Displacement, Turn),

F= Facing+ Direction*Turn.

mv(Win, P1, Facing, t(B), P1, Facing) : move(Win, P1, Facing, B).

move(_Win, _P1, _Facing, []) : !. move(Win, P1, Facing, [X|Rest]) : mv(Win, P1, Facing, X, P, F),

move(Win, P, F, Rest).

tree(Win) :

112

grammar(Commands), iter(5, Commands, C), Facing= pi/2, Point= pnt(100, 250),

move(Win, Point, Facing, C). end implement draw

Наконец, добавьте фрагмент

clauses

onPaint(S, _Rectangle, _GDIObject) : draw::tree(S:getVPIWindow()).

к PaintResponder. Постройте и запустите приложение.

Откройте форму для рисования, выбрав пункт File/New из меню приложения. Она покажет дере во, такое, как на рисунке.

14.2.Примеры

Пример этой главы находятся в папке Lsys.

113

Глава 15: Игры

В этой главе вы научитесь реализовывать игры в Visual Prolog. Конечно же, хорошая игра это как хорошая поэма. Недостаточно выучить русский язык и правила стихосложения для того, чтобы писать как Пушкин. Поэт, кроме знания языка, на котором он будет писать, нуждается в воображении, креа тивности, и т.д. То же самое относится к хорошему разработчику игр. Вам нужно много творчества для того, чтобы сделать что то наподобие Tetris или Space Invaders. Я использовал эти примеры для того, чтобы показать, что хорошие игры не зависят от прихотливой графики или звуковых эффектов. Важными особенностями являются тревожное ожидание, длительное напряжение и ощущение на грады за хорошую работу.

Однажды, молодой человек спросил Лопе де Вега (Lope de Vega), как писать стихи. Ответ был та кой: Это просто: Заглавная буква в начале каждой строфы и рифма в конце. «Что же мне вста вить в середину?», спросил парень. В середину, hay que poner talento1, сказал Лопе де Вега.

В этом уроке рассматривается несколько техник, подобных заглавным буквам и рифме. Чтобы получить хорошую игру, вам потребуется добавить к рецепту талант.

Игра, которую вы реализуете, простая. Есть червяк, быстро ползущий по экрану. Игрок должен изменять направление его движения для того, чтобы он не врезался в стены и препятствия. Вам не обходимо реализовать следующие элементы:

1.Окно, по которому червяк будет ползать. Окно должно иметь четыре кнопки для управ ления движением червяка.

2.Класс состояния, который описывает направление движения и положение червя. Он экс портирует предикаты mov/0, для изменения положения червя, и turn/2, который может быть использован для его поворотов.

3.Класс, который рисует изображение положения червя. Он экспортирует предикат snap shot(Win). Обратите внимание на следующую деталь: предикат snapshot/1 не может нарисовать червя прямо на окне. Это бы заставило экран мерцать. Он должен построить изображение, и только затем нарисовать его на экране.

4.Таймер, который вызывает mov/0 и snapshot/1 через фиксированные интервалы, для создания иллюзии движения.

Мы знаем, что делать; давайте это делать. До этого момента я избегал создания объектов. Дей

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

•Создайте проект: game.

Project Name: game

UI Strategy: Object Oriented GUI (pfc/vpi)

TargetType: Exe

Base Directory: C\vip70

Sub Directory: game

•Создайте пакет: playground.

1 исп. «нужно поместить талант»

114

Рисунок 15.1 Форма лужайка

•Создайте форму: lawn. Вы добавите четыре кнопки к форме lawn; обращайтесь к рис.

15.1 за оформлением. Хорошей идеей будет сменить названия кнопок в диалоговом окне

Properties с pushButton_ctl на up_ctl, down_ctl, left_ctl, и right_ctl. Постройте при ложение для того, чтобы включить в него эту форму. Затем включите File/New и добавьте

clauses

onFileNew(S, _MenuTag) : X= lawn::new(S), X:show().

кProjWin/Code Expert/Menu/TaskMenu/id_file/id_file_new.

•Создайте класс objstate внутри playground. Не отключайте Create Objects для класса objstate. Внесите в него код, показанный на рис. 15.3; NB: Класс objstate должен иметь интерфейс в файле objstate.i. Постройте приложение.

•Создайте класс draw внутри playground. Отключите Create Objects. Внесите код с рис. 15.2. Постройте приложение.

•Создайте класс click для хранения часов. Снимите Creates Objects.

%File: click.cl class click

open core, vpiDomains predicates

bip:(window). kill:().

end class click

% File:click.pro implement click open core

class facts

tm:vpiDomains::timerId := null. clauses

bip(W) : tm := W:timerSet(500). kill() : vpi::timerKill(tm).

end implement click

Постройте приложение для включения click в проект.

115

•Добавьте

clauses

onDestroy(_Source) : click::kill().

кDestroyListener в диалоговом окне Properties формы lawn.frm.

•Добавьте

class facts

stt:objstate := objstate::new(). clauses

onShow(Parent, _CreationData) : stt := objstate::new(), stt:init(), click::bip(Parent).

кShowListener в диалоговом окне Properties формы lawn.frm.

•Добавьте код для ClickResponder, соответствующему кнопкам:

clauses

onDownClick(_S) = button::defaultAction() : stt:turn(0, 10).

clauses

onLeftClick(_S) = button::defaultAction() : stt:turn( 10, 0).

clauses

onRightClick(_S) = button::defaultAction() : stt:turn(10, 0).

clauses

onUpClick(_S) = button::defaultAction() : stt:turn(0, 10).

•Добавьте

clauses

onTimer(_Source, _TimerID) : stt:mov(), R= rct(10, 10, 210, 210), invalidate(R).

кTimeListener в диалоговом окне Properties формы lawn.frm.

•Добавьте

clauses

onPaint(_Source, _Rectangle, GDIObject) : draw::snapshot(GDIObject, stt).

к PaintResponder в диалоговом окне Properties формы lawn.frm.

Постройте и запустите программу. После выбора пункта File/New, выскочит окно с червяком. Вы можете управлять червяком, используя четыре кнопки.

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

116

найдёте формулы для вычисления перспективной проекции точки. Алекс Соарес (Alex Soares) написал оригинальную игру, на которой я основал это руководство. Его игра была в 3D, в Visual Prolog 5.2, ис пользуя DirectX – библиотеку, содержащую много ресурсов для 3D игр.

%Файл draw.cl class draw

open core, vpiDomains predicates

classInfo : core::classInfo. snapshot:(windowGDI Win, objstate).

end class draw

%Файл draw.pro

implement draw

open core, vpiDomains, vpi class predicates

draw:(windowHandle, objstate) procedure. clauses

classInfo("playground/draw", "1.0"). draw(W, S) : S:segm(Rectangle),

vpi::drawEllipse(W, Rectangle), fail. draw(_W, _S).

snapshot(Win, S) : S:mov(), !, Rectangle= rct(0, 0, 200, 200), W= pictOpen(Rectangle), draw(W, S),

Pict= pictClose(W),

Win:pictDraw(Pict, pnt(10, 10), rop_SrcCopy). end implement draw

Рисунок 15.2 draw.cl и draw.pro

Давайте продолжим с нашим изучением предикатов рисования. Если вы рисуете фигуры на каж дый тик часов, вы создаёте анимацию. Однако, если вы производите рисование прямо на видимом окне, анимация будет мерцать, что не очень хорошо. Решение этой проблемы – рисование на спря танном окне, и передача его в видимое окно только после того, как оно будет готово к отображению. Предикат

W= pictOpen(Rectangle)

открывает скрытое окно для того, чтобы вы рисовали, не беспокоясь о мерцающей анимации. Предикат

Pict= pictClose(W)

создаёт изображение из содержимого скрытого окна в то же время он закрывает окно. Наконец, предикат

pictDraw(Win, Pict, pnt(10, 10), rop_SrcCopy)

передаёт изображение в видимое окно. Вот пример кода, содержащего эти три предиката:

117

snapshot(Win, S) : S:mov(), !,

Rectangle= rct(0, 0, 200, 200), W= pictOpen(Rectangle), draw(W, S),

Pict= pictClose(W),

Win:pictDraw(Pict, pnt(10, 10), rop_SrcCopy).

Аргумент rop_SrcCopy определяет режим растровых операций. Он говорит, что система должна скопировать рисунок из исходного окна в назначенное. Есть и другие режимы передачи изображе ний из одного окна в другое:

•rop_SrcAnd, производит логическое И между битами рисунка и фоном. Вы можете ис пользовать это для создания спрайтов.

•rop_PatInvert, инвертирует цвета.

•rop_SrcInvert инвертирует цвета источника.

15.1.Объектныефакты

Обратите внимание на одну специфическую деталь: когда мы имеем дело с классами, которые не создают объектов, состояние объекта устанавливается фактами класса (class facts); вот как факт класса объявляется:

class facts

city:(string Name, pnt Position). conn:(pnt, pnt).

clauses

city("Salt Lake", pnt(30, 40)). city("Logan", pnt(100, 120)). city("Provo", pnt(100, 160)).

conn(pnt(30, 40) , pnt(100, 120)). conn(pnt(100, 120), pnt(100, 160)).

Факт класса является общим для всех объектов класса. Это означает, что если объектный преди кат (предикат, объявленный в интерфейсе) изменяет факт, он изменяется для всех объектов класса. Поэтому, если сохранять состояние червя в факте класса, игра бы имела следующую неприятную особенность:

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

Проблема в том, что факты класса, используемые для описания состояния червя, имеют память класса (class memory), которая остаётся от одной формы к другой, от одного объекта червя к другому.

Решение простое: объявляйте факты без добавления ключевого слова class к заголовку объявления; тогда у каждого объекта будет собственный набор фактов. Вот как объявить объектные факты:

facts

w:(integer, pnt) nondeterm. % червяк d:(integer, integer) single. % направление

clauses

init() : assert(w(1, pnt(110, 100))),

assert(w(2, pnt(120, 100))), assert(w(3, pnt(130, 100))), assert(w(4, pnt(140, 100))), assert(w(5, pnt(140, 100))).

118

d(10, 0).

Эта схема сохранения состояния объекта реализована в программе на рис. 15.3.

% Файл objstate.cl

class objstate : objstate end class objstate

%Файл objstate.i interface objstate

open core, vpiDomains predicates

init:().

turn:(integer, integer). mov:().

segm:(rct) nondeterm (o). end interface objstate

%Файл objstate.pro implement objstate

open core, vpiDomains facts

w:(integer, pnt) nondeterm. % worm d:(integer, integer) single. % direction

clauses

init() : assert(w(1, pnt(110, 100))), assert(w(2, pnt(120, 100))), assert(w(3, pnt(130, 100))), assert(w(4, pnt(140, 100))), assert(w(5, pnt(140, 100))).

d(10, 0).

mov() : retract(w(1, P)), P= pnt(X1, Y1),

d(DX, DY), P1= pnt(X1+DX, Y1+DY), assert(w(1, P1)), retract(w(2, P2)), assert(w(2, P)),

retract(w(3, P3)), assert(w(3, P2)), retract(w(4, P4)), assert(w(4, P3)), retract(w(5, _)), assert(w(5, P4)), !.

mov().

segm(rct(X, Y, X+10, Y+10)) : w(_, pnt(X, Y)). turn(DX, DY) : assert(d(DX, DY)).

end implement objstate

Рисунок 15.3 Класс objstate

119

Глава 16: Анимация

Пожалуйста, взгляните на главу 8, если вы не помните, как пользоваться событием painting. Вам также понадобится использовать маски для создания рисунков с прозрачным фоном; эта тема тоже была рассмотрена в главе 8. Вы также можете вспомнить использование GDIObject, которое сделано в разделе 3.2, где вы также найдёте материал об обновлении (invalidating) прямоугольника для ри сования.

•Создайте проект

Project Name: rattlesnake

UI Strategy: Object oriented GUI (pfc/gui)

В этом проекте вы анимируете знаменитую гремучую змею, которая присутствует на флаге Джо на Пол Джонса (John Paul Jones) «Не наступите на меня!» (Don’t thread on me).

•Создайте новый пакет, и пусть его имя будет snake.

•Создайте новую форму canvas. Вложите её в пакет snake.

•Создайте класс click для хранения событий часов. Отключите Creates Objects. Используй те код, данный на рис. 16.1 для определения и реализации click.

•Постройте приложение. Включите File/New. Затем, добавьте

onFileNew(S, _MenuTag) :

F= canvas::new(S), F:show(), click::bip(F).

к TaskWindow.win/CodeExpert/Menu/TaskMenu/id_file/id_file_new.

%Файл click.cl class click

open core predicates

bip:(window). kill:(window).

end class click

%Файл click.pro implement click

open core class facts

tm:vpiDomains::timerId := null. clauses

bip(W) : tm := W:timerSet(1000). kill(W) : W:timerKill(tm).

end implement click

Рисунок 16.1 Класс click разбирается с таймером

120

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]