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.