Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Бадд Т. - ООП в действии - 1997.pdf
Скачиваний:
206
Добавлен:
13.08.2013
Размер:
3.11 Mб
Скачать

1.Измените программы так, чтобы они выдавали все возможные решения, а не только одно. Сколько существует решений задачи о восьми ферзях? Сколько из них являются поворотами других? Как можно отбросить повороты?

2.Как вы можете объяснить, что караульный класс в языках Objective–C и Smalltalk не предоставляет свою версию метода findSolution, несмотря на то что сообщение findSolution посылается соседу в методе advance?

3.Предположим, мы обобщим задачу о восьми ферзях как проблему N ферзей. Задача: как расположить N ферзей на шахматной доске N ґ N? Как изменятся программы? Ясно, что существуют N, для которых нет решений (например, N=2 или N=3). Что будет в этом случае с нашими программами? Как можно организовать более осмысленный вывод ответа?

4.Используя графические возможности вашей системы, измените одну из программ так, чтобы она динамически изображала на шахматной доске позиции каждого ферзя по мере своей работы. Какие части кода должны знать об устройстве вывода?

Глава 6: Учебный пример: игра «Бильярд»

Во втором примере мы построим простую имитацию бильярдного стола. Программа написана на языке Object Pascal для Macintosh 1 . Как и в случае с восемью ферзями, разработка делает упор на создание автономных агентов, взаимодействующих между собой для достижения желаемого результата.

6.1. Элементы бильярда

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

1 Программа «Бильярд» была разработана на компьютере PowerPC Macintosh с использованием компилятора CodeWarrior Pascal версии 1.1. В версии для PowerPC движение столь быстро, что я решил вставить в процедуру Ball.update замедляющий цикл, несколько раз вызывающий метод draw. Игра, реализованная программой из этой главы, не соответствует никакой настоящей игре. Это не пул, это не бильярд, это просто движение шаров по столу со стенками и лузами.

Когда один шар сталкивается с другим, часть энергии первого передается второму и в результате направление движения обоих шаров меняется.

PDF created with pdfFactory Pro trial version www.pdffactory.com

6.2. Графические объекты

Основу имитации составляют три списка графических объектов, представляющих стенки, лузы и шары. Каждый графический объект включает в себя поле ссылки и поле, описывающее местоположение объекта на экране 1 .

Мы ввели упрощающее предположение, что все графические объекты занимают прямоугольную область. Это, конечно, совершенно неверно для круглых объектов наподобие шара. Более реалистичной альтернативой было бы написать процедуру, определяющую, пересеклись ли два шара, на основе их действительной геометрии. Но сложность процедуры только отвлекла бы нас от того главного, чего мы хотим добиться, приводя этот пример, а именно понять способ наделения объектов ответственностью за их поведение. Каждый графический объект знает не только как изображать себя, но и как взаимодействовать с другими объектами нашей модели мира.

6.2.1. Графический объект Wall (стенка)

Первым из наших трех графических объектов является стенка Wall. Она определяется следующим описанием класса:

1 Неясно, куда поместить изучение этого примера. С одной стороны, читателю важно как можно быстрее увидеть применение объектных принципов; поэтому желательно, чтобы этот пример встретился в книге пораньше. С другой стороны, эта программа только выиграла бы от более изощренной техники, которая обсуждается ниже. В частности, графические объекты было бы лучше представлять в виде иерархии наследования, как описано в главе 7. Кроме того, считается плохим стилем программирования размещение ссылочных полей в области данных объектов, объединенных в список; лучше отделить контейнер и элементы списка. Решение этих проблем нетривиально и содержит сложности. Мы обсудим классы контейнеров в главе 15.

Wall = object

(* поля данных *) link : Wall; region : Rect;

PDF created with pdfFactory Pro trial version www.pdffactory.com

(* угол отскока шаров *) convertFactor : real;

(* инициализирующая функция *) procedure initialize

(left, top, right, bottom : integer; cf : real); (* изображение стенки *)

procedure draw;

(* сообщение стенке, что о нее ударился шар *) procedure hitBy (aBall : Ball);

end;

Поле link (ссылка) служит для поддержания списка объектов Wall. Инициализирующий метод просто задает местоположение (region) стенки и параметр отскока (convert factor):

procedure Wall.initialize

(left, top, right, bottom : integer; cf : real); begin

(* инициализация convertFactor *) convertFactor := cf;

(* установить область для стены *)

SetRect (region, left, top, right, bottom); end;

Стенка может быть нарисована просто как сплошной прямоугольник. Это выполняется стандартной процедурой для Macintosh:

procedure Wall.draw; begin

PaintRect (region); end;

Самое интересное происходит со стенкой, когда о нее ударяется шар. Направление его движения изменяется, основываясь на значении параметра convertFactor для стенки. (Переменная convertFactor равна или нулю, или pi, в зависимости от того, горизонтальная стенка или вертикальная.) В результате столкновения шар будет двигаться в новом направлении.

procedure Wall.hitBy (aBall : Ball); begin

(* оттолкнем шар от стенки *) aBall.setDirection(convertFactor — aBall.direction);

end;

6.2.2. Графический объект Hole (луза)

Hole (луза) определяется следующим описанием класса:

Hole = object

(* поля данных *) link : Hole; region : Rect;

(* инициализирующая функция *) procedure initialize (x, y : integer); (* изображение лунки *)

procedure draw;

(* сообщение лузе, что в нее попал шар *) procedure hitBy (aBall : Ball);

end;

PDF created with pdfFactory Pro trial version www.pdffactory.com

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

procedure Hole.initialize (x, y : integer); begin

(* определить область с центром в x, y *) SetRect(region, x-5, y-5, x+5, y+5);

end;

procedure Hole.draw; begin

PaintOval (region); end;

Больший интерес представляет происходящее при «ударе» шара о лузу. Есть два случая. Если шар оказался белым (он идентифицируется глобальной переменной CueBall), то он возвращается назад в игру на определенную позицию. В остальных случаях шар лишается энергии и убирается со стола в специальную область.

procedure Hole.hitBy (aBall : Ball); begin

(* остановить шар; убрать его со стола *) aBall.energy := 0.0;

aBall.erase;

(* передвинуть шар *) if aBall = CueBall then

aBall.setCenter(50.100); else begin

saveRack := saveRack + 1;

aBall.setCenter (10 + saveRack * 15, 250); end;

(* перерисовать шар *) aBall.draw;

end;

6.2.3. Графический объект Ball (шар)

Последним графическим объектом является шар, определяемый следующим описанием класса:

Ball = object

(* поля данных для шара *) link : Ball;

region : Rect;

direction : real; (* направление в радианах *) energy : real;

(* инициализирующая функция *) procedure initialize (x, y : integer); (* методы *)

procedure draw; procedure erase; procedure update;

procedure hitBy (aBall : Ball);

procedure setDirection (newDirection : real); (* возвращают x, y — координаты центра шара *) function x : integer;

function y : integer; end;

В дополнение к полям ссылки (link) и местоположения (region), общими с остальными объектами, шар имеет два новых поля данных: direction (направление), вычисленное в

PDF created with pdfFactory Pro trial version www.pdffactory.com

радианах, и energy (энергия), представляющее собой вещественное значение. Как и в случае лузы, шар инициализируется аргументами, описывающими координаты его центра. Первоначально шар не имеет энергии и его направление нулевое.

procedure Ball.initialize (x, y : integer); begin

SetRect (region, x-5, y-5, x+5, y+5); setDirection (0.0);

energy := 0.0; end;

Шар изображается либо окружностью, либо сплошным кругом, в зависимости от того, является ли он белым или нет.

procedure Ball.draw; begin

if self = CueBall then (* рисуем окружность *) FrameOval (region);

else

(* рисуем круг *) PaintOval (region);

end;

procedure Ball.erase; begin

EraseRect (region); end;

Метод update используется для изменения позиции шара. Если он имеет заметную энергию, то слегка сдвигается, а затем проверяет, не задел ли он другой объект. Глобальная переменная ballMoved устанавливается в true, если какой-либо шар на столе сдвинулся. Если шар задел другой объект, шар сообщает об этом объекту. Сообщения бывают трех видов; они соответствуют ударам по лузе, стенке и другим шарам. Наследование, которое мы изучаем в главе 7, предоставляет методы объединения этих трех тестов в один цикл.

procedure Ball.update; var

hptr : Hole; wptr : Wall; bptr : Ball;

dx, dy : integer; theIntersection : Rect;

begin

if energy > 0.5 then begin

ballMoved := true; (* удалить шар *) erase;

(* уменьшить энергию *) energy := energy — 0.05; (* сдвинуть шар *)

dx := trunc(5.0 * cos(direction)); dy := trunc(5.0 * sin(direction)); offsetRect(region, dx, dy);

(* перерисовать шар *) draw;

(* проверить, не попали ли в лузу *) hptr := listOfHoles;

while (hptr <> nil) do

PDF created with pdfFactory Pro trial version www.pdffactory.com

if SectRect (region, hptr.region, theIntersection) then

begin hptr.hitBy(self); hptr := nil;

end else

hptr := hptr.link;

(* проверить, не ударились ли в стенку *) wptr := listOfWalls;

while (wptr <> nil) do

if SectRect (region, wptr.region, theIntersection) then

begin wptr.hitBy(self); wptr := nil;

end else

wptr := wptr.link;

(* проверить, не ударили ли шар *) bptr := listOfBalls;

while (bptr <> nil) do

if SectRect (region, bptr.region, theIntersection) then

begin bptr.hitBy(self); bptr := nil;

end else

bptr := bptr.link;

end;

end;

Когда один шар ударяет другой, энергия первого делится пополам между ними. Также меняются направления движения обоих шаров.

procedure Ball.hitBy (aBall : Ball); var

da : real; begin

(* уменьшить энергию ударяющего шара наполовину *) aBall.energy := aBall.energy / 2;

(* и добавить ее к нашему шару *) energy := energy + aBall.energy;

(* установить наше новое направление *) setDirection(hitAngle(self.x aBall.x,

self.y — aBall.y);

(* и направление ударяющего шара *) da := aBall.direction — direction;

aBall.setDirection (aBall.direction + da); end;

function hitAngle (dx, dy : real) : real; const

PI = 3.14159; var

na : real; begin

if (abs(dx) < 0.05) then na := PI / 2;

else

na := arctan (abs(dy / dx)); if (dx < 0) then

na := PI — na;

PDF created with pdfFactory Pro trial version www.pdffactory.com

if (dy < 0) then na := -na;

hitAngle := na; end;

6.3. Основная программа

В предыдущем параграфе описывались статические характеристики программы. Динамика начинается при нажатии кнопки мыши. При этом вызывается следующая процедура:

procedure mouseButtonDown (x, y : integer); var

bptr : Ball; begin

(* присвоим белому шару некоторую энергию *) CueBall.energy := 20.0;

(* и направление *) CueBall.setDirection(hitAngle (CueBall.x — x,

CueBall.y — y));

(* изменения происходят, пока движется хотя бы один шар *) ballMoved := true;

while ballMoved do begin

ballMoved := false; bptr := listOfBalls; while bptr <> nil do begin

bptr.update;

bptr := bptr.link; end;

end;

end;

Оставшаяся часть программы относительно прямолинейна и не представлена здесь. Весь текст находится в Приложении Б. Основная часть кода связана с инициализацией новых объектов и организацией цикла ожидания события, то есть действия пользователя.

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

6.4. Использование наследования

В главе 1 мы описали наследование неформально, а в главе 7 обсудим, как оно работает в каждом из рассматриваемых нами языков. Здесь мы поясним, как наследование используется для упрощения имитации бильярда. Думается, что читателю лучше

вернуться к этому параграфу после ознакомления с общими положениями о наследовании в следующей главе.

Первым шагом в использовании наследования в нашей имитации бильярда является описание общего класса «графический объект». Он породит трех потомков: шары, стенки и лузы. Родительский класс определяется следующим образом:

GraphicalObject = object (* поля данных *)

PDF created with pdfFactory Pro trial version www.pdffactory.com

link : GraphicalObject; region : Rect;

(* инициализирующая функция *)

procedure setRegion (left, top, right, bottom : integer);

(* операции, выполняемые графическими объектами *) procedure draw;

procedure erase; procedure update;

function intersect (anObj : GraphicalObject) : boolean;

procedure hitBy (anObj : GraphicalObject); end;

Инициализирующая функция setRegion просто устанавливает область, занимаемую объектом. Методы draw и update ничего не делают, так как их фактическое поведение определено в дочерних классах. Программа erase очищает область, занимаемую объектом. intersect возвращает значение true, если объект-аргумент пересекается с рассматриваемым объектом. И наконец, метод hitBy также переопределяется в дочерних классах. Хотя двигаются только шары и, следовательно, аргументом этой функции всегда будет шар, тот факт, что класс Ball еще не определен, означает, что мы должны объявить аргумент как имеющий более общий тип GraphicalObject:

procedure GraphicalObject.setRegion (left, top, right, bottom : integer);

begin

SetRect(region, left, top, right, bottom); end;

procedure GraphicalObject.draw; begin

(* переопределяется в дочернем классе *) end;

procedure GraphicalObject.erase; begin

EraseRect (region); end;

procedure GraphicalObject.update;

begin (* переопределяется в дочернем классе *) end;

procedure GraphicalObject.hitBy(anObject : GraphicalObject);

begin (* переопределяется в дочернем классе *) end;

function GraphicalObject.intersect (anObject : GraphicalObject) : boolean;

var

theIntersection : Rect; begin

intersect := SectRect

(region, anObject.region, theIntersection); end;

Теперь Ball, Wall и Hole объявляются как подклассы общего класса GraphicalObject, и внутри них ни к чему объявлять данные или функции, если только они не переопределяются:

Hole = object (GraphicalObject)

(* инициализация местоположения лузы *) procedure initialize (x, y : integer); (* изображение лузы *)

PDF created with pdfFactory Pro trial version www.pdffactory.com

procedure draw; override;

(* сообщить лузе, что в нее попал шар *) procedure hitBy (anObject : GraphicalObject);

override;

end;

Процедура hitBy должна преобразовать тип аргумента в Ball. Благоразумно проверить тип до приведения:

procedure Wall.hitBy (anObj : GraphicalObject); var

aBall : Ball; begin

if Member (anObj, Ball) then begin

aBall := Ball(anObj); aBall.setDirection(convertFactor — aBall.direction);

end;

end;

Делая класс CueBall подклассом Ball, мы ликвидируем условный оператор в программе изображения шара.

CueBall = Object (Ball) procedure draw; override;

end;

procedure Ball.draw; begin

(* рисуем круг *) PaintOval (region);

end;

procedure CueBall.draw; begin

(* рисуем окружность *) FrameOval (region);

end;

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

procedure drawBoard; var

gptr : GraphicalObject; begin

SetPort (theWindow); gptr := listOfObjects;

while gptr <> nil do begin gptr.draw;

gptr := gptr.link; end;

end;

Наиболее важным местом этого кода является вызов функции draw внутри цикла. Несмотря на то что вызов написан один, иногда будет вызываться функция класса Ball, а в других случаях класса Wall или Hole. Тот факт, что одно обращение к функции может привести к вызовам различных функций, относится к понятию полиморфизма. Мы обсудим его в главе 14.

PDF created with pdfFactory Pro trial version www.pdffactory.com

Соседние файлы в предмете Программирование на C++