Учебник по программированию.

Первые шаги. Язык программирования PascalABC.

 

Предыдущий параграф Назад в содержание Следующий параграф


§24. Процедурный тип. Использование клавиатуры и мыши.

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


Процедурный тип.

Процедурный тип  это тип переменной, которая указывает на определённую процедуру, которая, в свою очередь, присвоена этой переменной. Т.е. процедуру можно вызвать через процедурную переменную. В ряде случаев это очень удобно. Далее мы рассмотрим все возможные случаи использования процедурных переменных.

Для того, что бы объявить переменную процедурного типа, необходимо вначале описать тип этой переменной. Тип этой переменной должен соответствовать тем процедурам, которые будут ей присвоены в процессе работы программы. Затем в программе мы присваиваем  процедурной переменной ту или иную процедуру, и затем вызываем её, используя уже процедурную переменную.

Пример:


uses GraphABC;


type pr=procedure(x,y:integer);


var Proc_1:pr;


procedure Krug(x,y:integer);

begin

  DrawCircle(x+20,y+20,20);

end;


procedure Kvadrat(x,y:integer);

begin

  DrawRectangle(x,y,x+40,y+40);

end;


begin

  Proc_1:=Krug;

  Proc_1(100,10);

  Proc_1:=Kvadrat;

  Proc_1(200,10);

end.


Ещё раз обращу внимание на то, что описание типа процедурной переменной должно соответствовать тем процедурам, которые могут быть присвоены этой переменной. Т.е. в данной программе следующее  описание типа процедурной переменной приведёт к ошибке:


type pr=procedure(x,y,z:integer);


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


type pr=procedure(i,j,k:integer;var l:integer);


var S:pr;


procedure Vivod(summa:pr);

var res:integer;

begin

  summa(34,532,21,res);

  writeln(res);

end;


procedure Slojenie(l,m,n:integer;var o:integer);

begin

  o:=l+m+n;

end;


begin

  S:=Slojenie;

  Vivod(S);

end.

________________________________________________

587


С помощью процедурной переменной возможно организовать перекрёстную рекурсию, при которой первая процедура вызывает вторую, а вторая вызывает первую. Пример:


program Perekr_Rekursiya;


const n=10;

type pr=procedure;

var mas:array [1..n] of char;//Массив символов

    m,b:integer;//Количество маленьких и больших символов соответственно

    S_M:pr;//Процедурная переменная

    i:integer;//Переменная счётчик


procedure S_B(SM:pr);

begin

  if i>n then exit;

  if (Ord(mas[i])>64)and(Ord(mas[i])<91) then

    begin

     Inc(b);

     Inc(i);

     S_B(S_M);

    end

   else SM;

end;


procedure SUM_M;

begin

  if i>n then exit;

  if (Ord(mas[i])>97)and(Ord(mas[i])<123) then

    begin

     Inc(m);

     Inc(i);

     SUM_M

    end

   else S_B(S_M);

end;


begin

//Заполняем массив

  for i:=1 to n do

    if Random(1,2) =1 then

        mas[i]:=Chr(Random(65,90))//Большие символы

      else

        mas[i]:=Chr(Random(97,122));//Маленькие символы

  for i:=1 to n do write(mas[i],'  ');//Выводим получившийся массив

//Считаем количество маленьких и больших символов

  i:=1;

  S_M:=SUM_M;

  S_B(S_M);

//Выводим результат

  writeln;

  Writeln('Больших букв - ',b);

  writeln('Маленьких букв - ',m);

end.

_____________________________________________________________________

q  G  l  l  d  d  q  X  U  z 

Больших букв - 3

Маленьких букв - 7


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

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

Плюсом ко всему стоит отметить, что для того, что бы отличать процедурную переменную от самой процедуры или функции, то можно перед её идентификатором ставить префикс, например p или f.

Пример:


var fSumma:function(r1,r2:real):real;

function Summa(r1,r2:real):real;

begin

  Summa:=r1+r2;

end;


begin

  fSumma:=Summa;

  writeln(fSumma(3.2,4.3));

end.

______________________________________

7.5


Вызов подпрограммы описанной в программе из уже откомпилированного модуля.

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

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

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

Далее получившийся код:


unit MDvaMyacha;

uses M1,M2,GraphABC;


var pStolknoveniye:procedure;//Процедурная переменная для вызова

                            //процедуры основной программы в теле модуля


//Процедура определяет, столкнулись мячи или нет

//и меняет их координаты, если столкновение произошло

procedure IzmeneniyeKoordinatPriStolknovenii;

begin

//Определяем произошло ли столкновение

  if .... then exit;

  pStolknoveniye;

//Меняем координаты мячей

  .....................................

end;


procedure PustayaProcedura;

begin 

end;


procedure CiklDvijeniyaMyachey;//Цикл в котором двигаются мячи

begin

  while true do

    begin

      ......................

      IzmeneniyeKoordinatPriStolknovenii;

    end;

end;


begin

  pStolknoveniye:=PustayaProcedura;

//Инициализация параметров

  ..............................

end.


======================================================================

program DvaMyacha;

uses MDvaMyacha,GraphABC;

var KolichestvoStolknoveniy:integer;//Количество столкновений


procedure St;

begin

  Inc(KolichestvoStolknoveniy);

  TextOut(240,1,'Количество столкновений - '

                              +IntToStr(KolicheStvostolknoveniy));

end;

 

begin

  pStolknoveniye:=St;

  CiklDvijeniyaMyachey;

end.


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

События.

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

Далее по порядку приведу все возможные события и соответствующие им процедурные переменные, а так же пример их использования.

События, связанные с мышью.

OnMouseDown: procedure (x,y,mousebutton: integer);   событие нажатия на кнопку мыши. mousebutton = 1, если нажата левая кнопка мыши, и 2, если нажата правая кнопка мыши. Здесь и в последующих случаях (x,y) координаты курсора мыши в момент наступления события.

OnMouseUp: procedure (x,y,mousebutton: integer); событие отжатия (отпускания) кнопки мыши. mousebutton = 1, если отжата левая кнопка мыши, и 2, если отжата правая кнопка мыши .

Пример:

program Knopka;

uses GraphABC;


procedure KnopkaOtjataya(x,y,mb:integer);

begin

  if (x>=10) and (x<=100) and (y>=10) and (y<=40) and (mb=1) then

    begin

      SetBrushColor(clLightGray);

      Rectangle(10,10,100,40);

      TextOut(23,17,'Не нажата')

    end

end;


procedure KnopkaNajataya(x,y,mb:integer);

begin

  if (x>=10) and (x<=100) and (y>=10) and (y<=40) and (mb=1) then

    begin

      SetBrushColor(clGray);

      Rectangle(10,10,100,40);

      TextOut(30,17,'Нажата')

    end;

end;


begin

  SetBrushColor(clLightGray);

  Rectangle(10,10,100,40);

  TextOut(23,17,'Не нажата');

 

  OnMouseDown:=KnopkaNajataya;

  OnMouseUp:=KnopkaOtjataya;

end.


На левом рисунке показана кнопка после отпускания левой кнопки мыши, на правом после нажатия этой же кнопки.        

OnMouseMove: procedure (x,y,mousebutton: integer); событие перемещения мыши. mousebutton = 0, если кнопка мыши не нажата, 1, если нажата левая кнопка мыши, и 2, если нажата правая кнопка мыши.

Хороший пример использования данного события приведён в справке, в разделе «Стандартные модули -> Модуль GraphABC -> GraphABC:  События», «Пример 1. Рисование мышью в окне». Я предлагаю немного более сложный пример, в котором с помощью мыши можно перемещать квадрат по экрану:


program kvadrat_1;

uses GraphABC;


var xk,yk:integer;//Координаты квадрата

    dx,dy:integer;//Координаты стрелки при нажатии левой кнопки мыши

                  //Внутри квадрата

    b:boolean;//True если нажали кнопку на квадрате


procedure DrawKvadrat;

begin

  SetBrushColor(clRed);

  FillRect(xk,yk,xk+20,yk+20);

end;


procedure ClearKvadrat;

begin

  SetBrushColor(clWhite);

  FillRect(xk,yk,xk+20,yk+20);

end;


procedure MouseDown(x,y,mb:integer);

begin

  if (mb=1)and(x>xk)and(x<xk+20)and(y>yk)and(y<yk+20) then

    begin

      b:=true;

      dx:=x-xk; dy:=y-yk;

    end;

end;


procedure MouseUp(x,y,mb:integer);

begin

  b:=false;

end;


procedure MouseMove(x,y,mb:integer);

begin

  if b then

    begin

      ClearKvadrat;

      xk:=x-dx; yk:=y-dy;

      DrawKvadrat; 

    end

end;


begin

  xk:=100;

  yk:=20;

  DrawKvadrat;

  OnMouseDown:=MouseDown;

  OnMouseUp:=MouseUp;

  OnMouseMove:=MouseMove;

end.


События, связанные с клавиатурой.

OnKeyDown: procedure (key: integer); событие нажатия клавиши. key - виртуальный код нажатой клавиши.

OnKeyUp: procedure (key: integer); событие отжатия (отпускания) клавиши. key - виртуальный код отжатой клавиши.

Все виртуальные коды клавиш можете просмотреть в справке «Стандартные модули -> Модуль GraphABC -> GraphABC: Виртуальные коды клавиш». В книге приведу лишь некоторые: VK_Left, VK_Right, VK_Up, VK_Down стрелочке влево, вправо, вверх и вниз соответственно. Так же можете просмотреть все коды в подсказке, набрав VK. Далее пример, в котором квадрат перемещается с помощью, приведённых выше клавиш, и возвращается обратно при их отпускании:


program kvadrat_2;

uses GraphABC;


var xk,yk:integer;//Координаты квадрата


procedure DrawKvadrat;

begin

  SetBrushColor(clRed);

  FillRect(xk,yk,xk+20,yk+20);

end;


procedure ClearKvadrat;

begin

  SetBrushColor(clWhite);

  FillRect(xk,yk,xk+20,yk+20);

end;


procedure KeyDown(key:integer);

begin

  case key of

    VK_Left :begin ClearKvadrat; Dec(xk); DrawKvadrat end;

    VK_Right:begin ClearKvadrat; Inc(xk); DrawKvadrat end;

    VK_UP   :begin ClearKvadrat; Dec(yk); DrawKvadrat end;

    VK_Down :begin ClearKvadrat; Inc(yk); DrawKvadrat end;

  end;

end;


procedure KeyUp(Key:integer);

begin

  ClearKvadrat;

  xk:=100;  yk:=20;

  DrawKvadrat;

end;


begin

  xk:=100;  yk:=20;

  DrawKvadrat;

  OnKeyDown:=KeyDown;

  OnKeyUp:=KeyUp;

end.


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


program KodKlavish;

uses GraphABC;

procedure KeyUp(key:integer);

begin

  writeln(key);

end;


begin

  OnKeyUp:=KeyUp;

end.


Запустите программу и понажимайте нужные вам клавиши. С лева будут появляться их числовые константы. В примере были нажаты клавиша с цифрой 7 и клавиша Enter.


OnKeyPress: procedure (ch: char); событие нажатия символьной клавиши. ch - символ, генерируемый нажатой символьной клавишей.

Используя данное событие можно выводить, набираемый с клавиатуры, текст на экран:


program Vivod_Texta;

uses GraphABC;


var xt,yt:integer;//Координаты буквы


procedure KeyPress(ch:char);

begin

  TextOut(xt,yt,ch);

  xt:=xt+TextWidth(ch);

  if xt>=(640-TextWidth(ch)) then

    begin

      yt:=yt+TextHeight(ch);

      xt:=1;

    end

end;


begin

  xt:=1;  yt:=1;

  OnKeyPress:=KeyPress;

end.


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


Задача.

1.В данном параграфе был приведён модуль MDvaMyacha,  в котором два мяча отталкиваются друг от друга. На основе этого модуля написать свой, в котором будет три мяча. Программа, использующая модуль должна считать количество столкновений каждой пары мячей. Модуль третьего мяча М3 можно взять из последнего задания предыдущего параграфа.

2. Используя модуль M1, написать игру, в которой мяч можно поймать мышкой, затем в другом месте экрана его отпустить.


Решение.

1.

unit MTriMyacha;

uses M1,M2,M3,GraphABC;


var pStolknoveniye:procedure(Para:integer);//Процедурная переменная для

                      //вызова процедуры основной программы в теле модуля


//Процедура изменяет координаты мячей при их столкновении

procedure IzmeneniyeKoordinatPriStolknovenii(Para,r1,r2:integer;

                                  var x1,y1,x2,y2,Vx1,Vx2,Vy1,Vy2:integer;

                                       var NaprX1,NaprX2,NaprY1,NaprY2:byte);

........................................


begin

//Определяем произошло ли столкновение

.......................................

  pStolknoveniye(Para);

.......................................

end;

.................................


procedure CiklDvijeniyaMyachey;//Цикл в котором двигаются мячи

begin

  while true do

    begin

     M1.Draw;        M2.Draw;        M3.Draw;

     DrawRectangle(9,19,631,471);

     Redraw;

     Sleep(30);

     M1.clear;       M2.Clear;       M3.Clear;

     M1.koordinati;  M2.koordinati;  M3.Koordinati;

    IzmeneniyeKoordinatPriStolknovenii(1,M1.r,M2.r,M1.x,M1.y,M2.x,M2.y,

            M1.Vx,M2.Vx,M1.Vy,M2.Vy,M1.naprX,M2.naprX,M1.naprY,M2.naprY);

     IzmeneniyeKoordinatPriStolknovenii(2,M1.r,M3.r,M1.x,M1.y,M3.x,M3.y,

            M1.Vx,M3.Vx,M1.Vy,M3.Vy,M1.naprX,M3.naprX,M1.naprY,M3.naprY);

     IzmeneniyeKoordinatPriStolknovenii(3,M3.r,M2.r,M3.x,M3.y,M2.x,M2.y,

            M3.Vx,M2.Vx,M3.Vy,M2.Vy,M3.naprX,M2.naprX,M3.naprY,M2.naprY);

     end;

end;


begin

  pStolknoveniye:=PustayaProcedura;

//Устанавливаем координаты замкнутой области

...............................................

  M3.X1:=10;M3.X2:=630;M3.Y1:=20;M3.Y2:=470;

//Устанавливаем начальные параметры движения

...............................................

  M3.y:=Random(M3.Y1+M2.R,M3.Y2-M3.R);

  M3.x:=Random(M3.X1+M3.r,M3.X2-M3.r);

  M3.naprY:=Random(3,4); M3.naprX:=Random(1,2);

  M3.Vy:=Random(4,20);   M3.Vx:=Random(2,10);

...............................................

end.

=========================================================================

program TriMyacha;

uses MTriMyacha,GraphABC;

var KolSt_1,KolSt_2,KolSt_3:integer;//Количество столкновений первой,

                                    //второй и третьей пар соответственно

procedure St(Para:integer);

begin

  TextOut(1,1,'Количество столкновений:');

  case Para of

    1:begin

        Inc(KolSt_1);

        TextOut(200,1,'зел. и кр. - '+IntToStr(KolSt_1))

      end

    2:begin

        Inc(KolSt_2);

        TextOut(320,1,'зел. и жёл. - '+IntToStr(KolSt_2))

      end

    3:begin

        Inc(KolSt_3);

        TextOut(450,1,'кр. и жёл. - '+IntToStr(KolSt_3))

      end

  end;

end;

 

begin

  pStolknoveniye:=St;

  CiklDvijeniyaMyachey;

end.


Примечание: за основу модуля MTriMychya был принят модуль MDvaMyacha. За основу программы TriMychya была принята программа DvaMyacha. В место кода, который не был изменён, вставлены многоточия. Изменённый код приведён полностью.

Основное изменение  это то, что в процедуру IzmeneniyeKoordinatPriStolknovenii добавлен параметр Para (Пара), с помощью которого в процедуру передаётся номер пары мячей. Всего в программе получается три пары (1 и 2 мячи; 1 и 3 мячи; 2 и 3 мячи). Так же в процедурную переменную pStolknovenie добавлен такой же параметр.


2.

program GameMyach;

uses M1, GraphABC;

var Najatiye:=false;//True если нажали на мяче

    dx,dy:integer;//Положение точки нажатия относительно центра мяча


procedure MouseDown(mx,my,mb:integer);


begin

  if mb=1 then

    begin

      //Определяем расстояние между точкой нажатия и центром мяча

      var b:real;//Угол бетта

      var S:real;//Расстояние между точкой нажатия и центром мяча

      if mx<>x then

         begin

           b:=ArcTan((my-y)/(mx-x));

           S:=abs(mx-x)/Cos(Abs(b));

         end

       else

         begin

           b:=Pi/2;

           S:=Abs(y-my);

         end

      if S < r then //Если нажатие на мяче произошло

        begin

          Najatiye:=true;

          dx:=x-mx;  dy:=y-my;

        end;

    end;

end;


procedure MouseUp(mx,my,mb:integer);

begin

  Najatiye:=False;

end;


procedure MouseMove(mx,my,mb:integer);

begin

  if Najatiye then

    begin

      Clear;

      x:=dx+mx;

      if x<(X1+r) then x:=X1+r;

      if x>(X2-r) then x:=X2-r;

      y:=dy+my;

      if y<(Y1+r) then y:=Y1+r;

      if y>(Y2-r) then y:=y2-r;

      Draw;

      Redraw;

    end;

end;



begin

  OnMouseDown:=MouseDown;

  OnMouseUp:=MouseUp;

  OnMouseMove:=MouseMove;

//Устанавливаем координаты замкнутой области

  X1:=10;X2:=630;Y1:=10;Y2:=470;

//Устанавливаем начальные параметры движения

  y:=Random(Y1+R,Y2-R); x:=Random(X1+R,X2-r);

  naprY:=Random(3,4); naprX:=Random(1,2); Vy:=Random(4,20);

  Vx:=Random(2,10);

//Рисуем замкнутую область

  Rectangle(X1-2,Y1-2,X2+2,Y2+2);

//Рисуем мяч

  LockDrawing;

  while true do

    begin

      Draw;

      Redraw;

      Sleep(1);

      if Najatiye then continue;

      clear;

      koordinati;

    end;

end.



Предыдущий параграф Назад в содержание Следующий параграф