Первое знакомство с объектно-ориентированным программированием

А.А.Семенов , А.Г.Юдина

Окончание. Начало см. в № 14, 15/99

Занятие № 5

Попробуем создать графический объект произвольной формы SimSprite, своего рода спрайт, только примитивный. Он может передвигаться только на черном фоне, т.к. наследует алгоритм движения предка (стирает свое изображение, перерисовывая себя черным цветом). Попробуем создать синего жучка. Его изображение вполне можно разместить в матрице 9x9 точек.

Для задания спрайта определим тип ImageArr — массив 9ґ9 байт.

{$R-,Q-}
uses Graph13h,MyObj,crt;
type
ImageArr=array[0..8,0..8] of byte;
SimSprite=object(GOb)
image: ImageArr;
constructor Init(var aimage:ImageArr;ax,ay:integer;
acolor:byte);
procedure Draw(acolor:byte); virtual;
end;

constructor SimSprite.Init(var image:ImageArr;ax,ay:integer;
acolor:byte);
begin
  inherited Init(ax,ay,acolor);
  Image:=aImage;
end;

procedure SimSprite.Draw(acolor:byte);
var
i,j: byte;
begin
  for i:=0 to 8 do
  for j:=0 to 8 do
   if acolor<>0 then PutPixel(x+j,y+i,image[i,j])
   else PutPixel(x+i,y+j,0)
  end;
const max=50;
var a:array[1..max] of SimSprite;
i:byte;
const bug:ImageArr=((0,0,0,0,9,0,0,0,0),
(0,0,1,1,1,1,1,0,0),
(1,0,1,1,9,1,1,0,1),
(0,1,1,6,9,6,1,1,0),
(0,1,1,1,9,1,1,1,0),
(0,1,1,1,1,1,1,1,0),
(1,0,1,1,1,1,1,0,1),
(0,0,0,1,1,1,0,0,0),
(0,0,0,0,14,0,0,0,0));
begin
Screen13h;
Randomize;
  for i:=1 to max do
   a[i].Init(bug,20+random(280),20+random(160),1);
  for i:=1 to max do a[i].Show;
  readln;
  while not keypressed do
   for i:=1 to max do
  a[i].Move(random (3)-2,random(3)-2);
readln
end.

Получилось! Жучки целеустремленно бегут к левому верхнему углу экрана, где и пропадают с тем, чтобы вновь появиться в правом нижнем углу.

Примечание

Вопрос: Как сделать, чтобы “жучок” бегал по травке?

Ответ: Для этого придется усовершенствовать метод Draw. Перед выводом спрайта необходимо запомнить изображение под ним в массиве 9ґ9 (придется добавить еще одно поле в объект SimSprite, его можно назвать, например, Background). Для того чтобы сохранить картинку под спрайтом, ее можно просканировать по точкам с помощью процедуры GetPixel, возвращающей цвет точки экрана с координатами x, y.

Function GetPixel(x,y: integer): byte;
  begin
  GetPixel:=Mem[$A000:(y*320 + x)];
  end;

Для того чтобы стереть спрайт, достаточно вывести по точкам массив Background.

Если нужно нарисовать спрайт, мы выведем только те точки спрайта, цвет которых отличается от “прозрачного”. Номер прозрачного цвета принимают, как правило, за нуль. Можно написать так:

...

if image[i,j]<>0 then PutPixel(x+j,y+i,image[i,j])

Домашнее задание

Разработать методы LT90 и RT90, поворачивающие спрайт налево и направо на 90 градусов. Как вы уже догадались, мы хотим научить жучков поворачиваться в процессе движения и бежать все время головой вперед, как поступают все нормальные насекомые. В следующий раз наш объект получит новые методы и будет моделировать поведение живых существ!

Занятие 6

На этом занятии наша задача — моделирование движения живого существа (жучка), меняющего направление своего движения, но при этом передвигающегося всегда головой вперед.

Для этого прежде всего нужно научиться “поворачивать” матрицу, задающую изображение.

Для поворота спрайта достаточно поменять местами строки и столбцы матрицы, изменив их последовательность на противоположную (иначе получится просто зеркальное отражение). Поэкспериментируйте на бумаге с простым спрайтом 4ґ4 точки. Для формирования повернутой картинки используем временный массив TempArr.

procedure Bug.RT90;
var
i,j:byte;
TempArr:ImageArr;
begin
  for i:=0 to 8 do
  for j:=0 to 8 do TempArr[i,j]:=image[8-j,i];
  for i:=0 to 8 do
  for j:=0 to 8 do image[i,j]:=TempArr[i,j];
end;

procedure Bug.LT90;
var
i,j:byte;
TempArr:ImageArr;
begin
  for i:=0 to 8 do
  for j:=0 to 8 do TempArr[i,j]:=image[j,8-i];
  for i:=0 to 8 do
  for j:=0 to 8 do image[i,j]:=TempArr[i,j];
end;

Теперь попробуем создать специализированного потомка GOb по имени Bug.

Данный объект умеет перемещаться в направлении Direction (0ЈDirectionЈ3) со скоростью velocity.

Характеристика Direction определяет направление согласно рисунку.

{$R-,Q-}
uses Graph13h,MyObj,crt;
type
ImageArr=array[0..8,0..8] of byte;
Bug=object(GOb)
image: ImageArr;
direction,
velocity:integer;
constructor Init(var aimage:ImageArr;ax,ay:integer;
acolor,avelocity:byte);
procedure Draw(acolor:byte); virtual;
procedure RT90;
procedure LT90;
procedure Move;
end;

constructor Bug.Init(var aimage: ImageArr;ax,ay:integer;acolor,avelocity:byte);
begin
inherited Init(ax,ay,acolor);
Image:=aImage;
direction:=0; {все жучки в момент рождения повернуты головой на север}
velocity:=avelocity;
end;

procedure Bug.Draw(acolor:byte);
var
i,j: byte;
begin
   for i:=0 to 8 do
   for j:=0 to 8 do
   if acolor<>0 then PutPixel(x+j,y+i,image[i,j])
   else PutPixel(x+j,y+i,0)
end;

procedure Bug.RT90;
var
i,j:byte;
TempArr:ImageArr;
begin
  for i:=0 to 8 do
  for j:=0 to 8 do TempArr[i,j]:=image[8-j,i];
  for i:=0 to 8 do
  for j:=0 to 8 do image[i,j]:=TempArr[i,j];
  direction:=direction+1;
  if direction>3 then direction:=0;
  {наши методы RT90 и LT90 не только поворачивают спрайт,
  но и меняют направление движения}
end;

procedure Bug.LT90;
var
i,j:byte;
TempArr:ImageArr;
begin
  for i:=0 to 8 do
  for j:=0 to 8 do TempArr[i,j]:=image[j,8-i];
  for i:=0 to 8 do
  for j:=0 to 8 do image[i,j]:=TempArr[i,j];
  direction:=direction-1;
  if direction<0 then direction:=3;
end;

procedure Bug.Move;
begin
  case direction of
   0: inherited move(0,-velocity);
   1: inherited move(velocity,0);
   2: inherited move(0,velocity);
   3: inherited move(-velocity,0);
  end;
end;

const max=30;
var a:array[1..max] of Bug;
i:integer;
const {спрайт-матрица 9x9 точек:}
BugSprite:ImageArr=((0,0,0,0,9,0,0,0,0),
(0,0,6,1,1,1,6,0,0),
(1,0,1,1,1,1,1,0,1),
(0,1,1,1,9,1,1,1,0),
(0,1,1,9,9,9,1,1,0),
(0,1,1,1,9,1,1,1,0),
(1,0,1,1,1,1,1,0,1),
(0,0,0,1,1,1,0,0,0),
(0,0,0,0,14,0,0,0,0));

begin
Screen13h;
Randomize;
for i:=1 to max do
   a[i].Init(BugSprite,20+random(280),20+random(160),1,random(3)+1);
for i:=1 to max do a[i].Show;
readln;
while not keypressed do
  begin
   a[1+random(max)].RT90;a[1+random(max)].LT90;
   for i:=1 to max do
     begin
      a[i].Move;
      delay(2);
     end;
  end;
readln
end.

Теперь вы наконец можете отдохнуть. Ваши ученики будут долго с увлечением создавать аквариумы с рыбками или танкодромы. А может быть, кто-то из них замахнется на Тетрис.

Заключение

Ну, вот и все. Цель достигнута. Теперь для нас прояснился смысл эпиграфа статьи. Действительно, при разработке стандартных элементов сложной программы было бы нерационально писать весь код “с нуля”. Достаточно взять за основу ряд объектов, разработанных профессиональными программистами, и дополнить их своими полями данных и методами, чтобы они удовлетворяли вашим требованиям. Некоторые стандартные методы можно переписать заново (перекрыть), если они вас не устраивают. Так проявляются два фундаментальных свойства объектов:

· наследование (способность использовать методы и поля предка, причем доступ к его исходному тексту необязателен);

· инкапсуляция (способность объекта включать в себя новые поля, методы и даже объекты). В последнем случае объект называют группой.

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

Если вас заинтересовала статья, пишите в редакцию. Послать вопросы и пожелания авторам вы можете по адресу: andrew_semenov@mtu-net.ru   или sasha@udina.mccme.ru .

TopList