11.Prednaska

Z Pascal
Prejsť na: navigácia, hľadanie
11. Prednáška

úlohy | cvičenie


Stiahnite si súbor RobotUnit.pas.

Grafický robot

Všetky naše doterajšie grafické úlohy pracovali so štandarnými nástrojmi Canvasu v grafickej ploche Image1, ako napr. Rectangle, Brush.Color, Pen.Width, FillRect a pod. Kresliť do grafickej plochy môžeme aj inak - pomocou predpripravenej knižnice grafických robotov RobotUnit. Takéto špeciálne "vycvičené" roboty sa vedia v grafickej ploche rôzne pohybovať a pritom môžu za sebou zanechávať čiaru. Aby ale náš program mohol s robotmi pracovať, musíme kompilátoru Pascal oznámiť, že plánujeme pracovať s touto špeciálnou knižnicou. Za riadok implementation pridáme tieto riadky:

uses
  RobotUnit;

Tento zápis predpokladá, že na disku už máme súbor s definíciou správania robotov RobotUnit.pas. Je veľmi dôležité, aby sa nachádzal presne v tom istom priečinku, ako aj váš program (ak budete mať viac programov, ktoré pracujú s týmito robotmi, vo viacerých priečinkoch, do všetkých treba tento súbor nakopírovať). Teraz už môžeme zadefinovať nové roboty a s nimi aj pracovať. Formulár musí obsahovať grafickú plochu (napr. Image1) a pridáme aj jedno tlačidlo. Začneme týmto príkladom, na ktorom vysvetlíme základné správanie robota:

procedure TForm1.Button1Click(Sender: TObject);
var
  Robot: TRobot;
  I: Integer;
begin
  CS;
  Robot := TRobot.Create;
  for I := 1 to 4 do
  begin
    Robot.Fd(100);
    Robot.Lt(90);
  end;
  Robot.Free;
end;

robot kreslí štvorec so stranou 100

Postupne vysvetlíme všetko, čo sme zapísali:

  • zadeklarovali sme premennú Robot, pomocou ktorej budeme ovládať robot - je to premenná typu TRobot (meno premennej by mohlo byť napr. aj Karel, Emil, Zofka, Turtle a pod.)
  • CS - príkaz na zmazanie obrazovky - skratka z anglického "Clear Screen" (hoci na tomto mieste je to teraz zbytočné)
  • vytvorenie nového robota: TRobot.Create označuje, že chceme, aby sa vytvoril (narodil) nový robot a tento sa priradí do premennej Robot - robot sa vytvoril v strede grafickej plochy
  • vo for-cykle sa štyrikrát vykonajú dva príkazy robota:
    • Robot.Fd - skratka z anglického "forward" - robot prejde požadovaný počet krokov, teda 100
    • Robot.Lt - skratka z anglického "left" - robot sa otočí vľavo o zadaný uhol, teda 90 stupňov
  • Robot.Free - na záver sa grafický robot zruší, podobne ako keď sme končili pracovať s bitmapou (napr. Bitmapa.Free) alebo zatvárali súbor (napr. CloseFile(Subor)) - keď budeme hovoriť, že konštrukciou TRobot.Create sa robot narodí, tak potom Free označuje, že robot "umrel" - prestal existovať

Robot vo všeobecnosti pracuje na takomto princípe:

  • nachádza sa niekde v grafickej ploche a je natočený nejakým smerom - nie je ho ale vidieť - nezobrazuje sa
  • ak sa mu povie, aby prešiel nejakú vzdialenosť, tak v momentálnom smere natočenia sa presunie dopredu
  • ak sa mu povie, aby sa o nejaký uhol otočil, tak nemení svoju pozíciu, len sa na svojom mieste otočí o zadaný uhol
  • robot má k dispozícii pero, pomocou ktorého vie za sebou zanechávať čiaru - toto pero môže byť spustené (vtedy kreslí), alebo zdvihnuté (vtedy sa len presúva)
  • tomuto peru môžeme zmeniť farbu aj hrúbku

V prvom príklade má robot spustené pero, a teda nakreslí štvorec so stranou 100. Keďže sa narodil v strede plochy otočený na sever, štvorec bude mať strany rovnobežné s osami.

Aby sme si lepšie vedeli predstaviť, kde sa pri kreslení robot nachádza, môžeme toto kreslenie trochu spomaliť volaním pomocnej procedúry Wait (tá je definovaná tiež v knižnici RobotUnit):

procedure TForm1.Button1Click(Sender: TObject);
var
  Robot: TRobot;
  I: Integer;
begin
  Robot := TRobot.Create;
  for I := 1 to 5 do
  begin
    Robot.Fd(100);
    Robot.Rt(144);
    Wait(300);
  end;
end;

robot nakreslil päťcípu hviezdu so stranou 100

Procedúra Wait robí to isté, ako nám známa dvojica príkazov Repaint a Sleep(). V serióznych programoch sa takto výpočet nespomaľuje, ale v našich jednoduchých ukážkových programoch to väčšinou nevadí. V tomto príklade sme ešte použili príkaz robota Robot.Rt - skratka z anglického slova "right" - robot sa otočí o zadaný uhol vpravo, t.j. v smere otáčania hodinových ručičiek.

V ďalšom programe ukážeme, ako spomaliť samotné kreslenie čiary:

procedure TForm1.Button1Click(Sender: TObject);
var
  R: TRobot;
  I: Integer;
begin
  R := TRobot.Create(20, 250, 60);
  R.PW := 3;
  R.PC := clBlue;
  for I := 1 to 80 do
  begin
    R.Fd(5);
    Wait(50);
  end;
  Robot.Free;
end;

Aby robot nakreslil úsečku dĺžky 400, v cykle prešiel dĺžku 5 80-krát. Všimnite si, že sme použili iný spôsob vytvorenia nového robota - do konštrukcie TRobot.Create sme pridali 3 parametre - tieto označujú miesto v grafickej ploche, kde sa má robot "narodiť" (v našom prípade na súradniciach (20, 250)). Tretie číslo v tejto trojici označuje počiatočné natočenie robota: 0 označuje hore, 90 vpravo atď.

Ďalšou novinkou v tomto programe je ukážka nastavovania parametrov pera robota:

  • R.PW := 3; znamená, že nastavujeme hrúbku pera (z anglického "Pen Width") - princíp je rovnaký ako nastavenie hrúbky pera v Canvase grafickej plochy - teda niečo ako Image1.Canvas.Pen.Width := 3;
  • R.PC := clBlue; nastavuje farbu pera robota (z anglického "Pen Color") teda niečo ako Image1.Canvas.Pen.Color := clBlue;

Nasledujúci príklad ilustruje ďalšie príkazy robota:

procedure TForm1.Button1Click(Sender: TObject);
var
  Robot: TRobot;
  I: Integer;
begin
  Robot := TRobot.Create(20, 300, 80);
  Robot.PW := 3;
  Robot.PC := clGreen;
  for I := 1 to 28 do
  begin
    Robot.PD;
    Robot.Fd(5);
    Robot.PU;
    Robot.Fd(5);
    Wait(20);
  end;
  Robot.Lt(120);
  for I := 1 to 14 do
  begin
    Robot.Fd(20);
    Robot.Point(15);
    Wait(50);
  end;
  Robot.Lt(120);
  for I := 1 to 14 do
  begin
    Robot.PD;
    Robot.Fd(10);
    Robot.PU;
    Robot.Fd(5);
    Robot.Point;
    Robot.Fd(5);
    Wait(50);
  end;
  Robot.Free;
end;

robot nakreslil trojuholník s rôzne vybodkovanými stranami

V príklade vidíme tieto nové príkazy:

  • Robot.PD; - robot dá pero dolu (z anglického "Pen Down") - odteraz budú všetky pohyby robota za ním zanechávať čiaru - robot pri "narodení" má nastavené pero dolu
  • Robot.PU; - robot dá pero hore (z anglického "Pen Up") - odteraz všetky jeho pohyby (napr. Fd nebudú kresliť žiadne čiary)
  • Robot.Point(15); - robot na mieste, kde sa práve nachádza nakreslí bodku zadanej veľkosti, napr. 15
  • Robot.Point; - keď nezadáme veľkosť bodky, kreslí sa bodka veľkosti podľa hrúbky pera, t.j. v našom prípade 3 - všimnite si, že bodka sa nakreslí, aj keď je zdvihnuté pero a teda čiary sa nekreslia.



Geometrické útvary



Nasledujúci príklad využije vnorené cykly a nakreslí 8 otočených rovnostranných trojuholníkov:

var
  Robot: TRobot;
  I, J: Integer;
begin
  Robot := TRobot.Create;
  for J := 1 to 8 do
  begin
    for I := 1 to 3 do
    begin
      Robot.Fd(100);
      Robot.Lt(120);
    end;
    Robot.Rt(45)
  end;
  Robot.Free;
end;

robot nakreslil osem trojuholníkov

V predchádzajúcich príkladoch sme videli, ako sa kreslí štvorec, päťcípa hviezda a rovnostranný trojuholník. Pri kreslení pravidelného N-uholníka si treba uvedomiť, že robot sa vo vrchole nemá otočiť o vnútorný uhol (napr. 60 pre rovnostranný trojuholník) ale o vonkajší (teda 120). Jednoduchou matematickou úvahou sa dá ukázať, že vo všeobecnosti sa pri kreslení pravidelného N-uholníka treba otáčať o 360/N stupňov - je zrejmé, že tento aritmetický výraz nie je celočíselný ale desatinný. Aj všetky príkazy robota akceptujú ako svoje parametre desatinné čísla a teda sa dá dosť presne nakresliť napr. aj pravidelný sedemuholník:

var
  Robot: TRobot;
  I, N: Integer;
begin
  Randomize;
  Robot := TRobot.Create(Random(Image1.Width), Random(Image1.Height));
  N := Random(6) + 3;     // náhodné číslo z intervalu <3, 8>
  for I := 1 to N do
  begin
    Robot.Fd(100);
    Robot.Rt(360 / N);
  end;
  Robot.Free;
end;

robot nakreslí pravidelné N-uholníky

Zaujímavý je aj pravidelný 360-uholník s nejakou veľmi malou dĺžkou strany. Tento 360-uholník môžeme na ploche považovať za kružnicu.

var
  Robot: TRobot;
  I: Integer;
begin
  CS;
  Robot := TRobot.Create(50, 100);
  for I := 1 to 360 do
  begin
    Robot.Fd(1.5);
    Robot.Rt(1);
  end;
  Robot.Free;
end;

robot nakreslil kružnicu

Ak by for-cyklus nebežal 360-krát, ale napr. 180 alebo 90, robot by nakreslil pol-kružnicu alebo štvrť-kružnicu. Podobne, ak by robot nezatáčal vpravo ale vľavo, vytváral by rôzne otočené časti kružníc - z nich by sa elegantne dal nakresliť kvet s rôznym počtom lupeňov:

var
  Robot: TRobot;
  I, J, K, N: Integer;
begin
  Robot := TRobot.Create(Random(Image1.Width), Random(Image1.Height));
  N := Random(7) + 3;
  for K := 1 to N do
  begin
    for J := 1 to 2 do
    begin
      for I := 1 to 30 do
      begin
        Robot.Fd(1);
        Robot.Rt(3);
      end;
      Robot.Rt(90);
    end;
    Robot.Rt(360 / N);
  end;
  Robot.Free;
end;

robot nakreslil kvet so siedmimi lupeňmi


Špirály


Špirály vznikajú, keď pri kreslení v cykle budeme meniť dĺžku čiary, napr.:

var
  Robot: TRobot;
  I: Integer;
begin
  Robot := TRobot.Create;
  Robot.Lt(30);
  for I := 1 to 100 do
  begin
    Robot.Fd(2 * I);
    Robot.Rt(90);
    Wait(5);
  end;
  Robot.Free;
end;

robot nakreslil špirálu

Alebo aj obyčajná špirála:

var
  Robot: TRobot;
  I: Integer;
begin
  Robot := TRobot.Create;
  for I := 1 to 1000 do
  begin
    Robot.Fd(I / 100);
    Robot.Rt(2);
    Wait(5);
  end;
  Robot.Free;
end;

robot kreslí špirálu

Iný typ veľmi zaujímavých špirál vzniká, keď nezvyšujeme dĺžku strán pri konštantnom uhle, ale naopak, pri konštantnej dĺžke zvyšujeme uhol:

var
  Robot: TRobot;
  I: Integer;
begin
  Robot := TRobot.Create;
  for I := 1 to 2000 do
  begin
    Robot.Fd(8);
    Robot.Rt(I);      // Robot.Rt(I + 0.1);
    Wait(1);
  end;
  Robot.Free;
end;

robot nakreslil ešte inú zaujímavú špirálu

robot nakreslil ešte inú zaujímavú špirálu


Aby sme mohli experimentovať so špirálami, vylepšíme program tak, že sa robot bude otáčať o náhodný uhol. Každé zatlačenie tlačidla nakreslí špirálu s nejakým iným uhlom:

procedure TForm1.Button1Click(Sender: TObject);
var
  Robot: TRobot;
  Dlzka, Uhol: Integer;
begin
  CS;
  Robot := TRobot.Create;
  Dlzka := 5;
  Uhol := Random(180);      // náhodný uhol od 0 do 179
  while Dlzka < 200 do
  begin
    Robot.Fd(Dlzka);
    Robot.Rt(Uhol);
    Dlzka := Dlzka + 2;
    Wait(1);
  end;
  Robot.Free;
  Image1.Canvas.TextOut(0, 0, Format('uhol = %d', [Uhol]));
end;

Niektoré z nakreslených špirál má nakreslený zbytočne malý počet čiar, pričom do plochy by sa zmestil aj jej väčší obrázok. Iné zo špirál sa zväčšujú prirýchlo a ukončiť ich kreslenie by sme mohli oveľa skôr. Ak by sme ale chceli cyklus ukončiť vtedy, keď sa robot vzdiali od svojho štartového bodu napr. (200, 130) aspoň o 120 (Robot.X a Robot.Y sú momentálne súradnice robota), môžeme to zapísať aj takto:

while Sqrt(Sqr(Robot.X - 200) + Sqr(Robot.Y - 130)) < 120 do ...

alebo využijeme funkciu robota Dist, pomocou ktorej robot vypočíta vzdialenosť od zadaného bodu v rovine:

while Robot.Dist(200, 130) < 120 do ...

Namiesto tlačidla Button1 môžeme do formulára vložiť pousvnú lištu TrackBar1. Nastavíme jej Min = 0 a Max = 179 a namiesto náhodného generovania uhla, uhol zistíme z pozície bežca na posuvnej lište (teda TrackBar1.Position):

procedure TForm1.TrackBar1Change(Sender: TObject);
var
  Robot: TRobot;
  Dlzka, Uhol: Integer;
  StredX, StredY: Integer;                   // súradnice stredu plochy
begin
  CS;
  StredX := Image1.Width div 2;
  StredY := Image1.Height div 2;
  Robot := TRobot.Create(StredX, StredY);
  Dlzka := 5;
  Uhol := TrackBar1.Position;
  while Robot.Dist(StredX, StredY) < StredY do
  begin
    Robot.Fd(Dlzka);
    Robot.Lt(Uhol);
    Dlzka := Dlzka + 2;
  end;
  Robot.Free;
  Image1.Canvas.TextOut(0, 0, Format('uhol = %d', [Uhol]));
end;

robot nakreslil špirálu

robot nakreslil špirálu


Náhodné prechádzky

Necháme, aby sa robot do "nekonečna" prechádzal po ploche: náhodne sa otočí o nejaký uhol a vykročí o nejakú konštantnú vzdialenosť:

var
  Robot: TRobot;
begin
  Robot := TRobot.Create;
  while True do
  begin
    Robot.Rt(Random(360));
    Robot.Fd(5);
    Wait(1);
  end;
end;

náhodná prechádzka

Robot veľmi rýchlo ujde z plochy preč. Pokúsime sa preto strážiť robot tak, aby nevyšiel z nejakej oblasti, t.j. ak aj vyjde, tak ho ihneď vrátime späť. Upravíme náhodnú prechádzku tak, že robot, keď sa priveľmi vzdiali od štartového bodu, urobí krok späť:

var
  Robot: TRobot;
  I, X0, Y0: Integer;
begin
  X0 := Random(Image1.Width);
  Y0 := Random(Image1.Height);
  Robot := TRobot.Create(X0, Y0);
  Robot.PC := Random($1000000);
  for I := 1 to 10000 do
  begin
    Robot.Rt(Random(360));
    Robot.Fd(5);
    if Robot.Dist(X0, Y0) > 50 then
      Robot.Fd(-5);
    // Wait(1);
  end;
  Robot.Free;
end;

náhodná prechádzka

náhodná prechádzka

Vyskúšajte niekoľko variantov podmienky a trénujte aj kombinácie podmienok pomocou logických operátorov and a or (treba si uvedomiť, že podmienka v týchto príkladoch vyjadruje, že robot opustil stráženú oblasť, t.j. že je zle a bude sa treba vrátiť).

  • kosoštvorec:
if Abs(Robot.X - 400) + Abs(Robot.Y - 300) > 100 then ...
  • obdĺžnik so stranami 200 a 80 a stredom v (400,300):
if (Abs(Robot.X - 400) > 100) or (Abs(Robot.Y - 300) > 40) then ...
  • zjednotenie troch kružníc (snehuliak):
var
  Robot: TRobot;
begin
  CS(clLtGray);           // šedé pozadie plochy
  Robot := TRobot.Create(150, 180);
  Robot.PC := clWhite;
  while True do
  begin
    Robot.Rt(Random(360));
    Robot.Fd(5);
    if (Robot.Dist(150, 340) > 90) and
       (Robot.Dist(150, 180) > 70) and
       (Robot.Dist(150, 60) > 50) then
      Robot.Fd(-5);
    if Random(1000) = 0 then
      Wait(1);
  end;
end;

snehuliak ako náhodná prechádzka

Všimnite si, že aby sme urýchlili náhodné prechádzanie sa robota, príkaz Wait(1); vykonávame približne len po každom 1000-om kroku robota. Mohli by sme na to zaviesť špeciálne počítadlo krokov robota a presne po jeho 1000 krokoch urobiť jeden Wait(1), ale výsledok by bol prakticky rovnaký ako s if Random(1000) = 0 then ...

Robot sa môže pohybovať vo vnútri jednej kružnice ale tak, aby zároveň bol mimo druhej kružnice. Ak týmto dvom kružniciam dobre nastavíme stredy tak, aby sa prekrývali, môže vzniknúť mesiac (poexperimentujte s rôznymi polomermi kružníc):

var
  Robot: TRobot;
begin
  CS(clNavy);
  Robot := TRobot.Create(100, 120);
  Robot.PC := clYellow;
  while True do
  begin
    Robot.Rt(Random(360));
    Robot.Fd(5);
    if (Robot.Dist(120, 120) > 100) or (Robot.Dist(220, 120) < 100) then
      Robot.Fd(-5);
    if Random(1000) = 0 then
      Wait(1);
  end;
end;

mesiac ako náhodná prechádzka

Posledná ukážka náhodnej prechádzky bude pre tri roboty: všetky sa pohybujú v tom istom kruhu, ale majú nastavenú inú farbu pera. Tiež sme im nastavili väčšiu hrúbku pera. Všimnite si, že roboty robia presne to isté, ale s inými náhodnými uhlami:

var
  Robot1, Robot2, Robot3: TRobot;
begin
  CS;
  Robot1 := TRobot.Create(150, 120);
  Robot1.PC := clGreen;
  Robot1.PW := 10;
  Robot2 := TRobot.Create(150, 120);
  Robot2.PC := clBlue;
  Robot2.PW := 10;
  Robot3 := TRobot.Create(150, 120);
  Robot3.PC := clYellow;
  Robot3.PW := 10;
  while True do
  begin
    Robot1.Rt(Random(360));
    Robot1.Fd(5);
    if Robot1.Dist(150, 120) > 150 then
      Robot1.Fd(-5);
 
    Robot2.Rt(Random(360));
    Robot2.Fd(5);
    if Robot2.Dist(150, 120) > 150 then
      Robot2.Fd(-5);
 
    Robot3.Rt(Random(360));
    Robot3.Fd(5);
    if Robot3.Dist(150, 120) > 150 then
      Robot3.Fd(-5);
 
    if Random(100) = 0 then
      Wait(1);
  end;
end;

náhodná prechádzka pre tri roboty
náhodná prechádzka pre tri roboty


Príklad s var-parametrami

Začneme takouto úlohou: napíšeme program, v ktorom sa 2 roboty budú neustále pohybovať po svojich kruhových dráhach. Súčasne s nimi sa tretí robot bude neustále snažiť byť presne v strede medzi nimi (ako keby bol v strede gumenej nite, ktorá je pripevnená na tie dva roboty) Vytvoríme si pomocnú procedúru PocitajStred, ktorá z dvoch robotov Robot1 a Robot2 vypočíta súradnice stredu (SX, SY) pre tretí robot:

procedure TForm1.Button1Click(Sender: TObject);
var
  Robot1, Robot2, Robot3: TRobot;
  StredX, StredY: Real;
 
  procedure PocitajStred;
  begin
    StredX := (Robot1.X + Robot2.X) / 2;
    StredY := (Robot1.Y + Robot2.Y) / 2;
  end;
 
begin
  Robot1 := TRobot.Create(80, 10, 89);
  Robot2 := TRobot.Create(380, 200, 0);
  PocitajStred;
  Robot3 := TRobot.Create(StredX, StredY, 0);
  Robot3.PC := clRed;
  while True do
  begin
    Robot1.Fd(4);
    Robot1.Rt(3);
    Robot2.Fd(3);
    Robot2.Lt(2);
    PocitajStred;
    Robot3.SetXY(StredX, StredY);
    Wait(10);
  end;
end;

tretí robot na gumenej niti


V tomto príklade sme vytvorili procedúru PocitajStred bez parametrov, lebo táto "vidí" aj na roboty Robot1, Robot2 aj na premenné StredX a StredY. Tieto premenné sú pre ňu globálne (nie sú to jej lokálne premenné).

Úlohu preprogramujeme tak, aby sa nepoužili "globálne" premenné, ale parametre. Je vám jasné, že ak by sme použili iba hodnotové parametre, nefungovalo by to:

procedure TForm1.Button1Click(Sender: TObject);
 
  procedure PocitajStred(Robot1, Robot2: TRobot; SX, SY: Real);
  begin
    SX := (Robot1.X + Robot2.X) / 2;
    SY := (Robot1.Y + Robot2.Y) / 2;
  end;
 
var
  Robot1, Robot2, Robot3: TRobot;
  StredX, StredY: Real;
begin
  Robot1 := TRobot.Create(80, 10, 89);
  Robot2 := TRobot.Create(380, 200, 0);
  PocitajStred(Robot1, Robot2, StredX, StredY);
  Robot3 := TRobot.Create(StredX, StredY, 0);
  Robot3.PC := clRed;
  while True do
  begin
    Robot1.Fd(4);
    Robot1.Rt(3);
    Robot2.Fd(3);
    Robot2.Lt(2);
    PocitajStred(Robot1, Robot2, StredX, StredY);
    Robot3.SetXY(StredX, StredY);
    Wait(10);
  end;
end;

Takto program zrejme nefunguje, lebo parametre SX a SYlokálne premenné, ktoré sú inicializované vstupnými hodnotami parametrov a po skončení procedúry sa ich hodnoty "zabúdajú". Zrejme korektné riešenie tejto úlohy je:

  procedure PocitajStred(Robot1, Robot2: TRobot; var SX, SY: Real);
  begin
    SX := (Robot1.X + Robot2.X) / 2;
    SY := (Robot1.Y + Robot2.Y) / 2;
  end;

Ukážme inú verziu tejto úlohy, kde jeden z robotov chodí po obvode štvorca a druhý po kružnici. Všimnite si, že procedúru PocitajStred sme vytiahli ešte pred Button1Click, takže sa stala globálnou:

procedure PocitajStred(Robot1, Robot2: TRobot; var SX, SY: Real);
begin
  SX := (Robot1.X + Robot2.X) / 2;
  SY := (Robot1.Y + Robot2.Y) / 2;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  Robot1, Robot2, Robot3: TRobot;
  StredX, StredY: Real;
  I: Integer;
begin
  Robot1 := TRobot.Create(240, 150, 27);
  Robot2 := TRobot.Create(180, 100, 0);
  PocitajStred(Robot1, Robot2, StredX, StredY);
  Robot3 := TRobot.Create(StredX, StredY, 0);
  Robot3.PC := clRed;
  while True do
  begin
    for I := 1 to 30 do    // skúste iný počet opakovaní, napr. 20
    begin
      Robot1.Fd(4);
      Robot2.Fd(3);
      Robot2.Lt(2);
      PocitajStred(Robot1, Robot2, StredX, StredY);
      Robot3.SetXY(StredX, StredY);
      Wait(10);
    end;
    Robot1.Rt(90);   // skúste iné uhly: 120, 144 a pod.
  end;
end;

robot na gumenej niti
robot na gumenej niti


Roboty a textové súbory

Prácu s textovými súbormi ilustrujme na niekoľkých úlohách. Predpokladajme, že máme daný textový súbor, v ktorom sú zapísané príkazy pre robota: f (pre Fd), l (pre Lt), r (pre Rt) pričom za každým písmenom je jedno celé číslo, napr.

f 100 r 120 f 100 r 120 f 100

pričom písmená a čísla sú navzájom oddelené aspoň jednou medzerou alebo novým riadkom. V nasledujúcom programe robot interpretuje takýto súbor

var
  Subor: TextFile;
  Robot: TRobot;
  Znak: Char;
  Param: Integer;
begin
  AssignFile(Subor, 'robot1.txt');
  Reset(Subor);
  Robot := TRobot.Create;
  while not Eof(Subor) do
  begin
    if Eoln(Subor) then
    begin
      Readln(Subor);
      Znak := ' ';
    end
    else
      Read(Subor, Znak);
    if Znak <> ' ' then
      Read(Subor, Param);
    case Znak of
      'f': Robot.Fd(Param);
      'r': Robot.Rt(Param);
      'l': Robot.Lt(Param);
    end;
  end;
  CloseFile(Subor);
  Robot.Free;
end;

alebo vylepšená verzia s použitím SeekEof(Subor):

var
  Subor: TextFile;
  Robot: TRobot;
  Znak: Char;
  Param: Integer;
begin
  AssignFile(Subor, 'robot1.txt');
  Reset(Subor);
  Robot := TRobot.Create;
  while not SeekEof(Subor) do
    if SeekEoln(Subor) then
      ReadLn(Subor)
    else
    begin
      Read(Subor, Znak, Param);
      case Znak of
        'f': Robot.Fd(Param);
        'r': Robot.Rt(Param);
        'l': Robot.Lt(Param);
      end;
    end;
  CloseFile(Subor);
  Robot.Free;
end;

robot interpretuje príkazy so súboru

Program sme spúšťali s textovým súborom:

f -100 f 150 r 60
f 100 r 120 f 100 r 120
f 200 l 120 f 100 l 120 f 100

V ďalšom programe predpokladáme, že v súbore sú slová forward, right, left, pu, pd (z každého aspoň prvé dve písmená) – za niektorými nasleduje číslo (parameter príkazu pre robot):

var
  Subor: TextFile;
  Robot: TRobot;
  Znak, Znak1, Znak2: Char;
  Param: Integer;
begin
  AssignFile(Subor, 'robot1.txt');
  Reset(Subor);
  Robot := TRobot.Create;
  while not SeekEof(Subor) do
    if SeekEoln(Subor) then
      ReadLn(Subor)
    else
    begin
      Read(Subor, Znak, Znak1, Znak2);
      while (Znak2 <>  ' ') and not Eoln(Subor) do
        Read(Subor, Znak2);
      if (Znak = 'f') and (Znak1 = 'o') then
      begin
        Read(Subor, Param);
        Robot.Fd(Param);
      end
      else if (Znak = 'r') and (Znak1 = 'i') then
      begin
        Read(Subor, Param);
        Robot.Rt(Param);
      end
      else if (Znak = 'l') and (Znak1 = 'e') then
      begin
        Read(Subor, Param);
        Robot.Lt(Param);
      end
      else if (Znak = 'p') and (Znak1 = 'u') then
        Robot.PU
      else if (Znak = 'p') and (Znak1 = 'd') then
        Robot.PD;
    end;
  CloseFile(Subor);
  Robot.Free;
end;

Pozn.

  • riešenie tejto úlohy môžeme zjednodušiť, ak si budeme všímať len 2. písmeno slov - v tomto prípade by sa dal použiť príkaz case

Ďalší príklad

  • Počas kreslenia nejakého obrázka pomocou grafického robota (napr. kreslenie kvetinky) si budeme do textového súboru zapamätávať momentálne súradnice robota. Súradnice robota sú v stavových premenných Robot.X a Robot.Y a zrejme ich stačí ukladať len po príkaze Fd
  • Najprv vytvoríme súbor s postupnosťou súradníc:
procedure TForm1.Button1Click(Sender: TObject);
var
  Subor: TextFile;
  Robot: TRobot;
 
  procedure Poly(Pocet, Dlzka, Uhol: Integer);
  begin
    while Pocet > 0 do
    begin
      Robot.Fd(Dlzka);
      WriteLn(Subor, Robot.X:0:2, ' ' , Robot.Y:0:2);
      Robot.Rt(Uhol);
      Dec(Pocet);
    end;
  end;
 
var
  I: Integer;
begin
  AssignFile(Subor, 'robot2.txt');
  Rewrite(Subor);
  Robot := TRobot.Create;
  Poly(1, 70, 0);
  for I := 1 to 7 do
  begin
    Poly(9, 5, 10);
    Robot.Rt(90);
    Poly(9, 5, 10);
    Robot.Rt(90 + 360 / 7);
  end;
  CloseFile(Subor);
  Robot.Free;
end;

A teraz robot interpretuje súbor, ktorý sa vytvoril v predchádzajúcom príklade:

procedure TForm1.Button2Click(Sender: TObject);
var
  Subor: TextFile;
  Robot: TRobot;
  Nx, Ny: Real;
begin
  CS;
  AssignFile(Subor, 'robot2.txt');
  Reset(Subor);
  Robot := TRobot.Create;
  while not Eof(Subor) do
  begin
    Readln(Subor, Nx, Ny);
    Robot.SetXY(Nx, Ny);
  end;
  CloseFile(Subor);
  Robot.Free;
end;

Túto dvojicu programov prepíšeme tak, že do súboru sa budú ukladať relatívne posuny robota, t.j. postupnosť vektorov:

procedure TForm1.Button1Click(Sender: TObject);
var
  Subor: TextFile;
  Robot: TRobot;
 
  procedure Poly(Pocet, Dlzka, Uhol: Integer);
  var
    X0, Y0: Real;     // v X0 a Y0 sa zapamätá pozícia robota pred príkazom Fd
  begin
    while Pocet > 0 do
    begin
      X0 := Robot.X;
      Y0 := Robot.Y;
      Robot.Fd(Dlzka);
      Writeln(Subor, Robot.X - X0:0:2, ' ' , Robot.Y - Y0:0:2);
      Robot.Rt(Uhol);
      Dec(Pocet);
    end;
  end;
 
var
  I: Integer;
begin
  AssignFile(Subor, 'robot3.txt');
  Rewrite(Subor);
  Robot := TRobot.Create;
  Poly(1, 70, 0);
  for I := 1 to 7 do
  begin
    Poly(9, 5, 10);
    Robot.Rt(90);
    Poly(9, 5, 10);
    Robot.Rt(90 + 360 / 7);
  end;
  CloseFile(Subor);
  Robot.Free;
end;

Robot vygenerujeme na náhodnej pozícii a určíme náhodnú veľkosť obrázka (náhodný interval <0.3, 2.5>):

procedure TForm1.Button2Click(Sender: TObject);
var
  Subor: TextFile;
  Robot: TRobot;
  Nx, Ny, Koef: Real;
  Sirka, Vyska: Integer;
begin
  AssignFile(Subor, 'robot3.txt');
  Reset(Subor);
  Randomize;
  Sirka := Image1.Width;
  Vyska := Image1.Height;
  Robot := TRobot.Create(Random(Sirka), Random(Vyska - 100) + 100);
  Koef := Random(23) / 10 + 0.3;
  while not Eof(Subor) do
  begin
    Readln(Subor, Nx, Ny);
    Robot.SetXY(Robot.X + Nx * Koef, Robot.Y + Ny * Koef);
  end;
  CloseFile(Subor);
  Robot.Free;
end;

obrázok zo súboru so zmenou veľkosti

Ak je Koef = 1, tak sa obrázok nakreslí v pôvodnej veľkosti, ak je Koef < 1, tak bude zmenšený a ak Koef > 1, tak bude zväčšený.


Polia robotov

Ukážeme využitie poľa robotov. Už predtým sme pracovali naraz s viac robotmi. Ešte zaujímavejšie je, keď roboty sú v poli a pracujeme s nimi pomocou cyklov. V príklade vygenerujeme vedľa seba na vodorovnej priamke N robotov, každému nastavíme iný uhol a všetky "naraz" nakreslia kružnicu. Najprv pomalá verzia, t.j. po každom pohybe každého robota pozdržíme výpočet pomocou Wait:

const
  N = 50;
var
  Robot: array [1..N] of TRobot;
  I, J: Integer;
begin
  for I := 1 to N do
    Robot[I] := TRobot.Create(5 * I + 10, 120, I * 15);
  for J := 1 to 180 do
    for I := 1 to N do
    begin
      Robot[I].Fd(2);
      Robot[I].Rt(2);
      Wait(1);
    end;
  for I := 1 to N do
    Robot[I].Free;
end;

Program urýchlime tak, že urobíme len jeden Wait, až keď sa pohnú všetky roboty:

  ...
  for I := 1 to N do
  begin
    Robot[I] := TRobot.Create(5 * I + 10, 120, I * 15);
    Robot[I].PW := 3;
  end;
  for J := 1 to 180 do
  begin
    for I := 1 to N do
      with Robot[I] do
      begin
        Fd(2);
        Rt(2);
      end;
    Wait(1);
  end;
  ...

kreslí sa naraz 50 kružníc

Na tomto príklade zároveň ukazujeme aj nový pascalovský príkaz with: vo vnútri neho sa "prednostne" pracuje s daným robotom.



Jednoduchá animácia pomocou robotov


Predchádzajúci príklad pozmeníme takto: for-cyklus nahradíme nekonečným while-cyklom: while True do a pred samotným pohybom robotov zmažeme obrazovku. Vznikne efekt, v ktorom na obrazovke vidíme len posledne nakreslené čiary od všetkých robotov:

const
  N = 50;
var
  Robot: array [1..N] of TRobot;
  I: Integer;
begin
  for I := 1 to N do
  begin
    Robot[I] := TRobot.Create(5 * I + 10, 120, I * 15);
    Robot[I].PW := 10;
  end;
  while True do
  begin
    CS;
    for I := 1 to N do
      with Robot[I] do
      begin
        Fd(2);
        Rt(2);
      end;
    Wait(1);
  end;
end;

50 pohybujúcich sa robotov

S programom môžeme ďalej experimentovať: hoci robot pri krúžení prejde len vzdialenosť 4, nakreslí úsečku dĺžky 100; tiež si všimnite, že každý robot sa otáča o iný uhol:

const
  N = 50;
var
  Robot: array [1..N] of TRobot;
  I: Integer;
begin
  for I := 1 to N do
  begin
    Robot[I] := TRobot.Create(5 * I+10, 120, I * 15);
    Robot[I].PW := 3;
    Robot[I].PC := clYelLow;
  end;
  while True do
  begin
    CS(clNavy);
    for I := 1 to N do
      with Robot[I] do
      begin
        Fd(100);
        Fd(-98);
        Rt(1 + I / N);
      end;
    Wait(1);
  end;
end;

50 pohybujúcich sa robotov ako úsečky dĺžky 100

V ďalšom príklade nevygenerujeme roboty na jednej priamke, ale rovnomerne na kružnici: najprv ich všetky vytvoríme v strede plochy, každý natočíme iným smerom a potom so zdvihnutým perom prejdú nejakú rovnakú vzdialenosť. Použili sme stavovú premennú (vlastnosť) robota H (heading - momentálny smer otočenia), pomocou ktorej môžeme nastavovať absolútne natočenie robotov:

const
  N = 60;
var
  Robot: array [1..N] of TRobot;
  I: Integer;
begin
  for I := 1 to N do
  begin
    Robot[I] := TRobot.Create;
    with Robot[I] do
    begin
      PW := 15;
      PC := clBlue;
      PU;
      H := 360 / N * I;
      Fd(100);
      PD;
      // H := H*2;
    end;
  end;
  while True do
  begin
    CS;
    for I := 1 to N do
      with Robot[I] do
      begin
        Fd(4);
        Rt(2);
      end;
    Wait(1);
  end;
end;

60 robotov na kružnici sa pohybuje

Môžete experimentovať s rôznymi počiatočnými natočeniami robotov, napr. ak odkomentujete priradenie H := H*2; vznikajú veľmi zaujímavé efekty. Napr.

const
  N = 360;
var
  Robot: array [1..N] of TRobot;
  I: Integer;
begin
  for I := 1 to N do
  begin
    Robot[I] := TRobot.Create;
    with Robot[I] do
    begin
      // PW := 10;
      PC := clBlue;
      PU;
      H := 360 / N * I;
      Fd(100);
      PD;
      H := H * 50;
    end;
  end;
  while True do
  begin
    CS;
    for I := 1 to N do
      with Robot[I] do
      begin
        Fd(204);
        Fd(-200);
        Rt(2);
      end;
    Wait(1);
  end;
end;

360 robotov ako úsečky

Aj iné konštanty vo výraze H := H * 50; vytvárajú pekné obrazce - vyskúšajte, napr. 2, 3, 4, 9, 10, 15, 30, 45, 50, 56, 60, 90, 120, 151, 179, ...



Roboty sa naháňajú


Na náhodných pozíciách vygenerujeme N robotov. Potom budeme opakovať túto akciu: v každom kroku sa každý robot posunie o 1/100 vzdialenosti k svojmu nasledovníkovi (posledný sa posunie k prvému):

const
  N = 8;
var
  Robot: array [1..N] of TRobot;
  I: Integer;
  NasledovnyX, NasledovnyY: Real;
begin
  CS;
  for I := 1 to N do
  begin
    Robot[I] := TRobot.Create(Random(Image1.Width), Random(Image1.Height));
    Robot[I].PW := 5;
    Robot[I].PC := Random($1000000);
  end;
  while True do
  begin
    for I := 1 to N do
      with Robot[I] do
      begin
        NasledovnyX := Robot[I mod N + 1].X;
        NasledovnyY := Robot[I mod N + 1].Y;
        Towards(NasledovnyX, NasledovnyY);
        Fd(Dist(NasledovnyX, NasledovnyY) / 100);
      end;
    Wait(1);
  end;
end;

8 robotov sa navzájom naháňa

Všimnite si použite I mod N + 1 - takto I-ty robot pristupuje ku svojmu nasledovníkovi, pričom posledný má nasledovníka prvého. Ďalej sme použili nový príkaz robota Towards, ktorý ho otočí do smeru k nejakému bodu (X, Y). Keďže sa po nejakom čase všetky roboty skoro stretnú v jednom bode, bolo by vhodné cyklus vtedy ukončiť. Premyslite si, ako zistiť, že všetky roboty sú už navzájom veľmi blízko.

Roboty môžu kresliť veľmi zaujímavé obrazce, keď okrem toho, že prejdú 1/10 vzdialenosti ku svojmu nasledovníkovi, nakreslia k nemu aj spojnicu:

var
  Robot: array [1..10] of TRobot;
  I, J, N: Integer;
  NasledovnyX, NasledovnyY, Vzdial: Real;
begin
  CS(clBlack);
  N := Random(8)+3;
  for I := 1 to N do
  begin
    Robot[I] := TRobot.Create(Random(Image1.Width), Random(Image1.Height));
    Robot[I].PW := 2;
    Robot[I].PC := Random($1000000);
  end;
  for J := 1 to 100 do
  begin
    for I := 1 to N do
      with Robot[I] do
      begin
        NasledovnyX := Robot[I mod N + 1].X;
        NasledovnyY := Robot[I mod N + 1].Y;
        Vzdial := Dist(NasledovnyX, NasledovnyY);
        Towards(NasledovnyX, NasledovnyY);
        Fd(Vzdial);
        Fd(Vzdial / 10 - Vzdial);
      end;
   Wait(1);
  end;
  for I := 1 to N do
    Robot[I].Free;
end;

roboty sa naháňajú
roboty sa naháňajú

Z programu môžete vyhodiť Wait(1). Všimnite si, že hoci je pole robotov zadeklarované ako 10-prvkové, využívame z neho len prvých N prvkov, pričom N je náhodné číslo od 3 do 10.


Príklad s robotom

Vytvoríme novú aplikáciu - do formulára položíme grafickú plochu (Image1) a pod ňu vstupný riadok (komponent Edit1 zo štandardnej palety komponentov). Naprogramujeme takéto správanie: v grafickej ploche sa bude nachádzať kresliaci robot a budeme ho riadiť príkazmi, ktoré sa zapisujú do vstupného riadka (jednoduchý interpreter). V Inšpektore objektov sa nastavíme na komponent Edit1, prepneme Udalosti (Events) a dvojklikneme OnKeyPress - vytvorí sa metóda Edit1KeyPress - táto metóda sa zavolá vždy, keď sa bude niečo zapisovať do editovacieho riadka. Nás však bude zaujímať len kláves <Enter> s kódom #13.

Program bude rozpoznávať túto množinu príkazov: cs fd lt rt pu pd setpc setpw (príkaz setpw pre zmenu hrúbky pera a setpc pre farbu pera). Parameter príkazu bude od neho oddelený aspoň jednou medzerou. Už vieme, že Edit1.Text obsahuje momentálny text v editovacom riadku. Použijeme štandardnú funkciu StrToIntDef, ktorá prevedie reťazec na číslo a ak reťazec nie je správne zadané celé číslo, tak vráti druhý parameter funkcie (tzv. náhradnú hodnotu - default).

Pomocou const môžeme definovať aj pole konštánt - za identifikátor konštanty napíšeme definíciu poľa a za znamienko rovnosti do zátvoriek postupne vymenujeme všetky konštanty.

uses
  RobotUnit;
 
var
  Robot: TRobot;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  Robot := TRobot.Create;
  Edit1.Text := '';
end;
 
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
const
  Tab: array [1..7] of string = ('fd', 'lt', 'rt', 'pu', 'pd', 'setpc', 'setpw');
  Farba: array [0..7] of TColor =
    (clBlack, clRed, clGreen, clBlue, clYellow, clGray, clNavy, clWhite);
var
  Retazec: string;
  I, Param: Integer;
begin
  if Key <> #13 then
    Exit;
  Retazec := Trim(Edit1.Text);
  I := Pos(' ', Retazec + ' ');
  Param := StrToIntDef(Copy(Retazec, I + 1, MaxInt), 0);
  Retazec := LowerCase(Copy(Retazec, 1, I - 1));
  I := 1;
  while (I <= High(Tab)) and (Retazec <> Tab[I]) do
    Inc(I);
  case I of
    1: Robot.Fd(Param);
    2: Robot.Lt(Param);
    3: Robot.Rt(Param);
    4: Robot.PU;
    5: Robot.PD;
    6: if (Param >= Low(Farba)) and (Param <= High(Farba)) then
         Robot.SetPC(Farba[Param]);
    7: Robot.SetPW(Param);
  end;
  Edit1.Text := '';
end;

riadenie robota cez vstupný riadok

Program vylepšíme tak, že zabezpečíme, aby bolo robot vidieť - nakreslíme ho malým trojuholníkom. Trojuholník bude natočený v smere natočenia robota - vždy, keď sa robot pohne alebo otočí, prekreslí sa celá grafická plocha najprv bez robota a na záver ho dokreslíme v novej polohe (pomocná procedúra Kresli). V poli Prikazy si budeme pamätať postupnosť všetkých doterajších príkazov. Príkaz cs vyprázdni túto zapamätanú postupnosť.

var
  Robot: TRobot;
  Prikazy: array [1..1000] of record
    Prikaz, Param: Integer;
  end;
  Pocet: Integer = 0;   // inicializácia globálnej premennej
 
procedure Kresli;
const
  Farba: array [0..7] of TColor =
    (clBlack, clRed, clGreen, clBlue, clYellow, clGray, clNavy, clWhite);
var
  I: Integer;
begin
  with Robot do
  begin
    PC := clBlack;
    PW := 1;
    SetXY(Form1.Image1.Width / 2, Form1.Image1.Height / 2);
    H := 0;
    PD;
    CS;
    for I := 1 to Pocet do
      with Prikazy[I] do
        case Prikaz of
          1: Fd(Param);
          2: Lt(Param);
          3: Rt(Param);
          4: PU;
          5: PD;
          6: if (Param >= Low(Farba)) and (Param <= High(Farba)) then
               PC := Farba[Param];
          7: PW := Param;
        end;
    // nakreslí rovnoramenný fialový trojuholník
    PC := clPurple;
    PW := 3;
    PD;
    Rt(90);
    Fd(8);
    Lt(105);
    Fd(30.9);
    Lt(150);
    Fd(30.9);
    Lt(105);
    Fd(8);
  end;
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  Robot := TRobot.Create;
  Edit1.Text := '';
  Kresli;
end;
 
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
const
  Tab: array [0..7] of string = ('cs', 'fd', 'lt', 'rt', 'pu', 'pd', 'setpc', 'setpw');
var
  Retazec: string;
  I, Param: Integer;
begin
  if Key <> #13 then
    Exit;
  Retazec := Trim(Edit1.Text);
  I := Pos(' ', Retazec + ' ');
  Param := StrToIntDef(Copy(Retazec, I + 1, MaxInt), 0);
  Retazec := LowerCase(Copy(Retazec, 1, I - 1));
  I := 0;
  while (I <= High(Tab)) and (Retazec <> Tab[I]) do
    Inc(I);
  if I > High(Tab) then
    Exit;
  if I = 0 then
    Pocet := 0     // príkaz cs
  else
  begin
    Inc(Pocet);
    Prikazy[Pocet].Prikaz := I;
    Prikazy[Pocet].Param := Param;
  end;
  Kresli;
  Edit1.Text := '';
end;

polohu robota vidíme ako trojuholník
polohu robota vidíme ako trojuholník

Všimnite si, že v procedúre Kresli potrebujeme pracovať s rozmermi grafickej plochy, t.j. Width a Height. Keďže je to globálna procedúra, ktorá ale nepatrí formuláru TForm1, na to aby sme mohli pracovať s grafickou plochou, musíme uvádzať nielen Image1 ale aj Form1, t.j. Form1.Image1.Width.

Projekt teraz zmeníme tak, že namiesto editovacieho riadka použijeme textovú plochu ('Memo), do ktorej sa bude zapisovať celá postupnosť príkazov robota. Pri každej zmene v tejto textovej ploche (udalosť OnChange) sa celá táto postupnosť vyhodnotí až do konca, pričom nesprávne alebo nedokončené príkazy sa ignorujú. Formulár by teraz mohol vyzerať takto:

rozloženie komponentov vo formulári

Okrem inicializácie formulára (FormCreate) vytvoríme aj udalosť, ktorá sa vyvolá pri každej zmene textovej plochy (pri písaní do textovej plochy, t.j. Memo1Change):

uses
  RobotUnit;
 
var
  Robot: TRobot;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  Robot := TRobot.Create;
  Memo1.Lines.Clear;
  Memo1Change(Sender);
end;
 
procedure TForm1.Memo1Change(Sender: TObject);
const
  Tab: array [1..7] of string = ('fd', 'lt', 'rt', 'pu', 'pd', 'setpc', 'setpw');
  Farba: array [0..7] of TColor =
    (clBlack, clRed, clGreen, clBlue, clYellow, clGray, clNavy, clWhite);
var
  I, J: Integer;
  Retazec:  string;
  Param: integer;
begin
  with Robot do
  begin
    PC := clBlack;
    PW := 1;
    SetXY(Image1.Width / 2, Image1.Height / 2);
    H := 0;
    PD;
    CS;
    for J := 0 to Memo1.Lines.Count - 1 do
    begin
      Retazec := Trim(Memo1.Lines.Strings[J]);
      I := Pos(' ', Retazec + ' ');
      Param := StrToIntDef(Copy(Retazec, I + 1, MaxInt), 0);
      Retazec := LowerCase(Copy(Retazec, 1, I - 1));
      I := 1;
      while (I <= High(Tab)) and (Retazec <> Tab[I]) do
        Inc(I);
      case I of
        1: Fd(Param);
        2: Lt(Param);
        3: Rt(Param);
        4: PU;
        5: PD;
        6: if (Param >= Low(Farba)) and (Param <= High(Farba)) then
             PC := Farba[Param];
        7: PW := Param;
      end;
    end;
    // nakreslí rovnoramenný fialový trojuholník
    PC := clPurple;
    PW := 3;
    PD;
    Rt(90);
    Fd(8);
    Lt(105);
    Fd(30.9);
    Lt(150);
    Fd(30.9);
    Lt(105);
    Fd(8);
  end;
end;

robot vykonáva postupnosť príkazov
robot vykonáva postupnosť príkazov
robot vykonáva postupnosť príkazov


Ďalšie námety:

  • zrealizujte do tohto projektu tlačidlá, ktoré vyčistia textovú plochu, uložia, resp. načítajú do/zo súboru
  • doprogramujte to tak, aby sme mohli v jednom riadku za sebou zadať aj viac príkazov (oddelených napr. medzerou)


Myš a časovač

Robot je grafické pero, ktoré dokáže nejaké veci navyše: napr. veľmi jednoducho "vie natočiť súradnicovú sústavu", v grafickej ploche ich môžeme definovať ľubovoľný počet, a pod. Prvý príklad ilustruje použitie robota ako obyčajné grafické pero:

var
  Robot: TRobot;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  Robot := TRobot.Create;
  Robot.PU;
end;
 
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  Robot.PD;
end;
 
procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
begin
  Robot.SetXY(X, Y);
end;
 
procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  Robot.PU;
end;

V tomto programe je robot "prilepený" k myši - pri každom pohybe myši (onMouseMove) sa pohne na jej miesto. Robot má ale stále pero hore a kreslí len, keď príde udalosť onMouseDown, t.j. keď stlačíme nejaké tlačidlo myši. Do podprogramu pre udalosť onMouseMove môžeme pridať aj iné akcie, napr. sa robot najprv otočí smerom k novej pozícii (použili sme príkaz Towards(X, Y) - otočí robota k zadanému bodu v rovine) a až potom sa na ňu presunie - pritom robot môže niečo aj kresliť, napr. úsečky:

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
begin
  Robot.Towards(X, Y);
  Robot.Fd(100);
  Robot.Fd(-100);
  Robot.SetXY(X, Y);
end;

kreslenie robotom

môžeme pridať CS:

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
begin
  CS;
  Robot.Towards(X, Y);
  Robot.MoveXY(X, Y);
  Robot.Fd(100);
  Robot.Fd(-100);
end;

alebo

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var
  I: Integer;
begin
  Robot.Towards(X, Y);
  Robot.Movexy(X, Y);
  for I := 1 to 5 do
  begin
    Robot.Fd(30);
    Robot.Rt(144);
  end;
end;

kreslenie robotom s hviezdičkami

alebo farebné paličky:

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
begin
  if Robot.IsPD then    // či má robot spustené pero
  begin
    Robot.PW := 5;
    Robot.PC := Random($1000000);
    Robot.Towards(X, Y);
    Robot.MoveXY(X, Y);
    Robot.Lt(90);
    Robot.Fd(60);
    Robot.Point(15);
    Robot.Fd(-60);
  end;
end;

kreslenie robotom s farebnými paličkami

alebo rovnostranný trojuholník:

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
begin
  CS;
  Robot.Towards(X, Y);
  Robot.MoveXY(X, Y);
  Robot.PW := 5;
  Robot.PC := clGreen;
  Robot.Rt(90);
  Robot.Fd(8);
  Robot.Lt(105);
  Robot.Fd(30.9);
  Robot.Lt(150);
  Robot.Fd(30.9);
  Robot.Lt(105);
  Robot.Fd(8);
end;



Klikanie na roboty



V ďalšom príklade môžeme vidieť generovanie robotov pri klikaní myšou: kliknutie na voľné miesto vygeneruje nový robot, kliknutie na už existujúci robot ho mierne otočí:

var
  Robot: array [1..100] of TRobot;
  N: Integer = 0;     // aktuálny počet robotov
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  CS;   // aby sa grafická plocha zobrazila už pri štarte
end;
 
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  I: Integer;
begin
  I := 1;
  while (I <= N) and not Robot[I].IsNear(X, Y) do
    Inc(I);
  if I <= N then
    with Robot[I] do
    begin
      Rt(15);
      Fd(50);
      Fd(-50);
    end
  else
  begin
    Inc(N);
    Robot[N] := TRobot.Create(X, Y);
    with Robot[N] do
    begin
      PW := 5;
      PC := Random($1000000);
      Fd(50);
      Fd(-50);
    end;
  end;
end;

Použili sme preddefinovanú logickú funkciu (metódu) robotov IsNear, ktorá pre daný bod v rovine odpovie, či je robot od neho blízko

Ďalšie námety:

  • nové roboty by mohli vznikať na kliknutie so zatlačeným pravým tlačidlom myši, na ľavé tlačidlo môže robot urobiť nejakú akciu - nakresliť nejaký obrázok, alebo sa prilepí na myš a do nasledujúceho kliknutia putuje s myšou

Predchádzajúcu úlohu, v ktorej sme klikaním vytvárali nové roboty, resp. ich otáčali, trochu pozmeníme: kliknutie na robot naštartuje kreslenie kružnice - a to pomaly - použime známu procedúru Wait:

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  I, J: Integer;
begin
  I := 1;
  while (I <= N) and not Robot[I].IsNear(X, Y) do
    Inc(I);
  if I > N then
  begin
    Inc(N);
    Robot[N] := TRobot.Create(X, Y);
    with Robot[N] do
    begin
      PW := 5;
      PC := Random(256 * 256 * 256);
      Point;
    end;
  end
  else
    with Robot[I] do
      for J := 1 to 36 do
      begin
        Fd(10);
        Rt(10);
        Wait(50);       // t.j. Repaint; Sleep(50);
      end;
end;

Po rozbehnutí robota program nereaguje, až kým robot nedobehne celú kružnicu. Systémová procedúra Sleep totiž nedovolí, aby aplikácia reagovala na ľubovoľné udalosti, teda nereaguje nielen na klikanie myšou do plochy, ale ani na tlačenie tlačidiel, ani posúvanie aplikácie a ani ukončenie aplikácie.

V takýchto úlohách nám pomôže časovač.



Časovač a roboty



V prvom jednoduchom príklade predpokladáme časovač so sekundovým tikaním, t.j. Interval = 1000. Robot bude kresliť úsečku dĺžky 100 a každú sekundu sa otočí o 6 stupňov (t.j. ako sekundová ručička):

var
  Robot: TRobot;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  Robot := TRobot.Create;
  Robot.PW := 5;
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  CS;
  Robot.Rt(6);
  Robot.Fd(100);
  Robot.Fd(-100);
end;

Vidíme, že sekundová ručička sa hýbe tak ako má. Môžete dorobiť druhý robot, ktorý bude zobrazovať minútovú ručičku. Na otestovanie môžete hodinky urýchliť zmenšením doby tikania (napr. Interval = 10).


Príklad s robotmi, ktoré sa po kliknutí rozbehnú po kružnici, preprogramujeme pomocou časovača: pre každý robot si budeme pamätať v poli Kolko, koľko krokov po kružnici ešte musí prebehnúť. Program teraz vyzerá takto:

var
  Robot: array[1..100] of TRobot;
  Kolko: array[1..100] of Integer;
  N: Integer = 0;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  CS;
end;
 
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  I: Integer;
begin
  I := 1;
  while (I <= N) and not Robot[I].IsNear(X, Y) do
    Inc(I);
  if I > N then
  begin
    Inc(N);
    Robot[N] := TRobot.Create(X, Y);
    with Robot[N] do
    begin
      Point(8);
      PW := 3;
    end;
    Kolko[N] := 0;
  end
  else
  begin
    Robot[I].PC := Random($1000000);
    Kolko[I] := 36;
  end;
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
var
  I: Integer;
begin
  for I := 1 to N do
    if Kolko[I] > 0 then
      with Robot[I] do
      begin
        Fd(10);
        Rt(10);
        Dec(Kolko[I]);
      end;
end;

časovaču Timer1 sme nastavili Interval na 50 milisekúnd.


Ďalší príklad kombinuje časovač a klikanie s ťahaním myšou: ak klikneme na voľné miesto grafickej plochy, vznikne na tomto mieste robot a ten sa ihneď začne otáčať na mieste. Ak klikneme na otáčajúci sa robot, môžeme ho ťahaním myšou premiestniť na novú pozíciu. Všimnite si, že robot sa otáča aj počas jeho ťahania. Časovač je najlepšie nastaviť na Interval = 50.

var
   Robot: array [1..1000] of TRobot;
   N: Integer = 0;
   Taham: Integer = 0;
 
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  I: Integer;
begin
  I := 1;
  while (I <= N) and not Robot[I].IsNear(X, Y) do
    Inc(I);
  if I <= N then
    Taham := I
  else
  begin
    Inc(N);
    Robot[N] := TRobot.Create(X, Y);
  end;
end;
 
procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  if Taham <> 0 then
    Robot[Taham].MoveXY(X, Y);
end;
 
procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  Taham := 0;
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
var
  I: Integer;
begin
  cs;
  for I := 1 to N do
    with Robot[I] do
    begin
      Point(20);
      Rt(6);
      Fd(50);
      Fd(-50);
    end;
end;


Objekt Robot

Premenné typu TRobot (podobne aj TBitmap) sa líšia od "obyčajných" premenných (napr. typu Integer, string, array, record, a pod.) tým, že

  • si pamätajú svoj momentálny stav v svojich tzv. stavových premenných (napr. pozícia, farba, ...)
  • majú svoje súkromné príkazy, pomocou ktorých ich nejako riadime, resp. meníme ich stavové premenné - takýmto príkazom (sú to procedúry) hovoríme metódy a "rozumejú" im len roboty (premenné typu TRobot)
  • musia byť vytvorené (nie deklarované) špeciálnym spôsobom (TRobot.Create(...);) a kým sa takto nevytvoria, nesmú sa vôbec používať
  • takýmto premenným hovoríme OBJEKT a typom, z ktorých vytvárame objekty (napr. TRobot) hovoríme TRIEDA (po anglicky object a class); niekedy budeme objektu hovoriť aj inštancia triedy
  • okrem robotov sme sa už stretli aj s inými objektmi, napr. Form1, Image1, Button1
  • zatiaľ si o objektoch treba zapamätať, že
    • sú to premenné, ktoré môžu v sebe obsahovať veľa stavových premenných
    • tiež "v sebe" obsahujú nejaké svoje procedúry (metódy) => tomuto hovoríme zapuzdrenie (enkapsulácia), lebo v jednom "puzdre" sú aj údaje (stavové premenné) aj algoritmy (metódy), ktoré vedia s týmito údajmi pracovať.

Zhrňme všetky doteraz známe príkazy a nastavenia robota:

  • vytvorenie nového robota:
  • Create;
robot vznikne v strede plochy
  • Create(x, y);
robot vznikne na súradniciach (x, y)
  • Create(x, y, uhol);
robot vznikne na súradniciach (x, y) s počiatočným uhlom uhol
  • pohyby a kreslenie:
  • Fd(dĺžka);
"forward" - robot prejde vzdialenosť dĺžka
  • SetXY(x, y);
robot sa premiestni na súradnicu (x, y)
  • MoveXY(x, y);
robot sa premiestni na súradnicu (x, y) so zdvihnutým perom
  • Point;
robot nakreslí bodku veľkosti hrúbky pera
  • Point(veľkosť);
robot nakreslí bodku veľkosti veľkosť
  • otáčanie sa:
  • Rt(uhol);
"right" - robot sa namieste otočí o uhol stupňov vpravo
  • Lt(uhol);
"left" - robot sa namieste otočí o uhol stupňov vľavo
  • SetH(uhol);
"set heading" - robot sa namieste otočí do absolútneho uhla uhol stupňov
  • nastavovanie:
  • PC := farba;
"pen color" - robotu nastavíme farbu pera na farba
  • PW := hrúbka;
"pen width" - robotu nastavíme hrúbku pera na hrúbka
  • PU;
"pen up" - robotu zdvihneme pero
  • PD;
"pen down" - robotu spustíme pero

Všimnite si, že PC a PW sú stavové premenné a môžeme s nimi pracovať skoro ako s obyčajnými premennými, napr.

  • R.PW := R.PW + 1;
  • R.PC := R.PC div 256;

Ďalšími stavovými premennými robota sú:

  • X, Y - pozícia robota - uvedomte si rozdiel medzi príkazmi v týchto dvoch riadkoch:
  • R.X := 100; R.Y := 170;
  • R.SetXY(100, 170);
  • H - absolútny smer momentálneho otočenia, napr. robot môžeme otočiť vpravo o 30 stupňov aj takto:
  • R.H := R.H + 30;

Ďalšie príkazy, ktoré súvisia s robotmi, ale sú "globálne":

  • CS;
"clear screen" - zmažeme grafickú plochu bielou farbou
  • CS(farba);
zmažeme grafickú plochu farbou farba
  • Wait(ms);
program sa pozdrží na ms milisekúnd

Môžete si pozrieť RobotUnit.pas a pokúsiť sa pochopiť ďalšie metódy a stavové premenné. V niektorých nasledujúcich prednáškach sa s nimi stretneme.


späť | ďalej