Szybkie odnajdywanie drogi

    -- Sebastian Pawlak, 1997, PCkurier 3/1999.


Gry to obecnie olbrzymia część rynku komputerowego. Potrzeba tworzenia coraz doskonalszych produkcji powoduje szybki rozwój technik ich programowania. Lecz ten, kto chce zaszokować jakimś rewelacyjnym pomysłem powinien najpierw opanować podstawowe algorytmy. Celem tego artykułu jest właśnie uzupełnienie fundamentalnej wiedzy programisty. Algorytm poszukiwania najkrótszej drogi z punktu A do B jest ważnym elementem wielu gier. W grach strategicznych (np. WarCraft) służy do automatycznego omijania przeszkód terenowych przez poruszającą się postać. Zastosowanie tego algorytmu można znaleźć w przygodówkach i strzelankach 3D. Przedstawiony tu algorytm, w niektórych przypadkach nie wyszukuje najkrótszej ścieżki, ale jest bardzo szybki i dlatego nadaje się do zastosowania w grze komputerowej. Oto pobieżny opis algorytmu szukania drogi z punktu A do B:


1. Wpisujemy:
   - pozycję startową (X,Y) do tablicy WezlyPoz [0],
   - wyliczoną odległość (odcinek między A i B) do
     LenWezly [0],
   - "zajmujemy" aktualną komórkę poprzez wpisanie 0 do
     LenMapa [X,Y].
2. Przeszukujemy tablicę LenWezly pod kątem ustalenia
   komórki K leżącej najbliżej punktu B.
3. Badamy sąsiadujące komórki dla aktualnej pozycji K
   (góra, prawo, dół, lewo, góra-lewo, góra-prawo,
    dół-prawo, dół-lewo).
4. Jeśli znajdziemy pustą komórkę to wpisujemy:
   - do LenMapa [x,y] (pusta komórka) dane z tablicy
     LenMapa [X,Y]+1 (komórka K),
   - do tablicy WezlyPoz pozycję pustej komórki,
   - do LenWezly odległość od pustej komórki do punktu B
5. Powtażamy 3-4 dla wszystkich wolnych komórek wokół
   podstawowej K.
6. Powtażamy 2-5 aż dojdziemy do punktu B.
7. Teraz według uzyskanej mapy (LenMapa) z drogami do
   przejścia od A do B szukamy najkrótszej drogi.

Na rysunku przedstawiam przykładowy efekt działania programu. Cyfry w komórkach pochodzą z tablicy LenMap.




Oto wydruk programu gotowego do uruchomienia:


{Poszukiwanie ścieżki z punktu A do B}
{autor: Sebastian Pawlak}

Uses crt,graph;

Type PozInfo=Record
 X,Y :word;
End;

Const komorka=20; {rozmiar pojedynczej komórki w pikselach}
      szer=640 div komorka-1; {ilość komórek w poziomie}
      wys=480 div komorka-1;  {ilość komórek w pionie}

Var Mapa :array [0..szer,0..wys] of byte;
    LenMapa :array [0..szer,0..wys] of word;

    i,j :word;
    PozycjaStart :PozInfo;
    PozycjaKoniec :PozInfo;
    Pos :PozInfo;
    NrWezla :word;
    NajkrotszaDroga :word;
    NajkrKrok :word;
    NajkrPos :PozInfo;
    Step :byte;

Procedure Grafika;
Var a,b,c :integer;
Begin
 a:=0; b:=0;
 InitGraph (a,b,'c:\tp\bgi\'); {włącza tryb graficzny}

 SetColor (blue);
 {obramowanie planszy}
 For a:=0 to szer do Mapa [a,0]:=1;
 For a:=0 to szer do Mapa [a,wys]:=1;
 For a:=0 to wys do Mapa [0,a]:=1;
 For a:=0 to wys do Mapa [szer,a]:=1;

 For a:=2 to wys do Mapa [14,a]:=1;

 For a:=0 to szer do
  For b:=0 to wys do
  Begin
   Rectangle (a*komorka,b*komorka,a*komorka+komorka,
              b*komorka+komorka);
   If Mapa [a,b]<>0 then
   Begin
    SetColor (green);
    For c:=b*komorka+1 to b*komorka+komorka-1 do
     Line (a*komorka+1,c,a*komorka+komorka-1,c);
    SetColor (blue);
   End;
  End;

  SetColor (10);
  For a:=0 to Komorka div 3 do
   Circle (PozycjaStart.x*komorka+komorka div 2,
           PozycjaStart.y*komorka+komorka div 2,a);
  SetColor (11);
  For a:=0 to komorka div 3 do
   Circle (PozycjaKoniec.x*komorka+komorka div 2,
           PozycjaKoniec.y*komorka+komorka div 2,a);
End;

{Główna procedura odnajdywania ścieżki}
Procedure FindPath;
Var WezlyPoz :array [0..500] of PozInfo;
    LenWezly :array [0..500] of word;

{"zerowanie" tablicy LenWezly}
Procedure Czysc;
Var a,b:integer;
Begin
 For a:=0 to szer do
  For b:=0 to wys do
   LenMapa [a,b]:=$FFFF;
End;

{tworzy węzeł - punkt pretendujący do ścieżki}
Procedure TworzWezel (x,y:integer);
Var s :string;
Begin
 If ((LenMapa[Pos.X+x,Pos.Y+y]=$FFFF) AND
     (Mapa[Pos.X+x,Pos.Y+y]=0)) then
 Begin
  LenMapa [Pos.X+x,Pos.Y+y]:=LenMapa [Pos.X,Pos.Y]+1;
  WezlyPoz [NrWezla].X:=Pos.X+x;
  WezlyPoz [NrWezla].Y:=Pos.Y+y;
  {tu obliczana jest odległość z punktu A do B;}
  {zastosowane jest tu Tw. Pitagorasa, ale bez wyciągania}
  {pierwiastka z całości ; otrzymana wartość określa}
  {odległość z A do B, a komputer nie musi tracić czasu}
  {na pierwiastkowanie}
  LenWezly [NrWezla]:=((Pos.X+x)-PozycjaKoniec.X)*((Pos.X+x)
    -PozycjaKoniec.X)+((Pos.Y+y)-PozycjaKoniec.Y)*((Pos.Y+y)
    -PozycjaKoniec.Y);
  Inc (NrWezla);

  {poniższe 3 funkcje weź w nawias klamrowy, aby nie były}
  {wyświetlane na rysunku numery z tablicy LenMapa}
  Str (LenMapa [Pos.X+x,Pos.Y+y],s);
  SetColor (15);
  OutTextxy ((Pos.x+x)*komorka+(komorka div 2-length (s)
             *8 div 2)+1,(Pos.y+y)*komorka+7,s);
 End;
End;

{Sprawdza, który punkt leży bliżej celu}
Procedure Sprawdz (x,y:integer);
Begin
 If LenMapa[Pos.X+x,Pos.Y+y]<NajkrKrok then
 Begin
  NajkrKrok:=LenMapa[Pos.X+x,Pos.Y+y];
  NajkrPos.X:=Pos.X+x;  NajkrPos.Y:=Pos.Y+y;
 End;
End;

Begin
 Czysc;

 {wpisanie do listy pozycji startowej}
 WezlyPoz [0]:=PozycjaStart;
 LenWezly [0]:=(PozycjaStart.X-PozycjaKoniec.X)
                *(PozycjaStart.X-PozycjaKoniec.X)
                +(PozycjaStart.Y-PozycjaKoniec.Y)
                *(PozycjaStart.Y-PozycjaKoniec.Y);
 LenMapa [PozycjaStart.X,PozycjaStart.Y]:=0;

 NrWezla:=1;
 Repeat
  NajkrotszaDroga:=0;
  For i:=0 to NrWezla-1 do
   If (LenWezly [i]<LenWezly [NajkrotszaDroga]) then
       NajkrotszaDroga:=i;

  Pos:=WezlyPoz [NajkrotszaDroga];
  WezlyPoz [NajkrotszaDroga]:=WezlyPoz [NrWezla-1];
  LenWezly [NajkrotszaDroga]:=LenWezly [NrWezla-1];
  Dec (NrWezla);

  TworzWezel (0,-1); {góra}
  TworzWezel (1,0);  {prawo}
  TworzWezel (0,1);  {dół}
  TworzWezel (-1,0); {lewo}
  TworzWezel (1,-1); {prawy-górny}
  TworzWezel (1,1);  {prawy-dolny}
  TworzWezel (-1,1); {lewy-dolny}
  TworzWezel (-1,-1);{lewy-górny}
 Until ((Pos.X=PozycjaKoniec.X)
        AND(Pos.Y=PozycjaKoniec.Y))OR(NrWezla=0);

 If NrWezla<>0 then
 Begin
  Pos := PozycjaKoniec;
  step:=0;
  While NOT ((Pos.X=PozycjaStart.X)
             AND (Pos.Y=PozycjaStart.Y)) do
  Begin
   Inc (Step);
   NajkrKrok := $FFFF;

   Sprawdz (0,-1); {góra}
   Sprawdz (1,0);  {prawo}
   Sprawdz (0,1);  {dół}
   Sprawdz (-1,0); {lewo}
   Sprawdz (1,-1); {prawy-górny}
   Sprawdz (1,1);  {prawy-dolny}
   Sprawdz (-1,1); {lewy-dolny}
   Sprawdz (-1,-1);{lewy-górny}

   Pos:=NajkrPos;

   SetColor (6);
   Circle (NajkrPos.X*komorka+komorka div 2,
           NajkrPos.Y*komorka+komorka div 2,
           komorka div 2-1);
   Circle (NajkrPos.X*komorka+komorka div 2,
           NajkrPos.Y*komorka+komorka div 2,
           komorka div 2-2);

   End;
 End
 Else OutTextxy (3,3,'Brak przejscia !!!!');
End;

Begin
 PozycjaStart.X:=3;  PozycjaStart.Y:=10;
 PozycjaKoniec.X:=25;  PozycjaKoniec.Y:=6;

 Grafika;
 FindPath;

 ReadKey;
 CloseGraph;
End.

Są jeszcze inne metody badania ścieżki, z punktu A do B, o których informacje można uzyskać w internecie.

w3cw3c
automatyka przemysłowa