|
||||||||
|
§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.
|