1 Nazwa implementacji: Tworzenie labiryntu z - e-Swoi

Transkrypt

1 Nazwa implementacji: Tworzenie labiryntu z - e-Swoi
Nazwa implementacji: Tworzenie labiryntu z wykorzystaniem algorytmu Backtracking
Autor: Jarosław Żok Rafał Brzychcy grzegorzwilczek
Opis implementacji: Zastosowanie języka Python oraz biblioteki Pygame do wizualizacji działania algorytmu Backtracking.
Algorytm wykorzystuje jedną ze struktur danych używaną powszechnie w programowaniu - stos. Idea stosu sprowadza się do realizacji
obszaru pamięci w taki sposób, że dane odkładane na stos, są zdejmowane w odwrotnej kolejności. Ostatni element odłożony na stos jest z
niego zdejmowany pierwszy.
Taki sposób dostępu jest powszechnie wykorzystywany w realizacji funkcji w programach, gdzie przed wywołaniem funkcji, wszystkie
zmienne lokalne odkładane są na stosie a po wyjściu z niej, zdejmowane ze stosu co przywraca stan programu sprzed wywołania funkcji.
Umożliwia to realizację algorytmów rekurencyjnych ale nie tylko, maszynowa interpretacja zapisu działań matematycznych za pomocą RPN
(Reverse Polish Notation) jest algorytmem intensywnie korzystającym ze stosu.
W tej implementacji zaś wykorzystamy stos do zapamiętywania stanu odwiedzonych komórek w algorytmie generowania labirytnu. Do
wizualizacji postępów w działaniu algorytmu będziemy wykorzystywać język Python wraz z biblioteką PyGame. Stos w Pythonie nie wymaga
pisania własnej jego implementacji, gdyż listy w tym języku mogą działać jak stos.
Zacznijmy od opisu samego algorytmu.
Labirynt jest tworzony na planszy o wymiarach N na M komnat. Każda komnata jest oddzielona od sąsiadów ścianą. Zadaniem algorytmu jest
przechodzenie losowo między sąsiadującymi komnatami i burzenie ścian między nimi. Tworząc przejścia algorytm tworzy jednocześnie
korytarze labiryntu. Na początku wszystkie komanty są oznaczone jako nieodwiedzone a początek labiryntu znajduje się w dowolnej
wylosowanej komnacie. Algorytm Backtrack punkt po punkcie działa następująco:
Wylosuj komnatę początkową, zaznacz ją jako odwiedzoną i aktualną Dopóki są nieodwiedzone komnaty
• Jeżeli aktualna komnata ma jakichś nieodwiedzonych sąsiadów
1. Wybierz losowo jednego z nieodwiedzonych sąsiadów aktualnej komnaty.
2. Odłóż aktualną komnatę na stos.
3. Usuń ścianę między aktualną komnatą a wybranym losowo sąsiadem
4. Niech wybrany losowo sąsiad staje się aktualną komnatą, oznacz go także jako odwiedzonego.
• Jeżeli nie ma sąsiadów, sprawdź czy stos nie jest pusty
1. Zdejmij ze stosu komnatę.
2. Zdjętą ze stosu komantę uczyń aktualną.
• W przeciwnym wypadku
1. Spośród nieodwiedzonych jeszcze komnat wybierz losowo jedną, uczyń ją aktualną i zaznacz jako odwiedzoną.
Do realizacji algorytmu w Pythonie użyjemy stworzonej przez nas klasy Maze, która przy tworzeniu wymaga dwóch parametrów
numerycznych określających ilość komnat w wierszu oraz ilość wierszy labiryntu. Klasa Maze zajmuje się także inicjalizacją środowiska
graficznego Pygame, otwarciem okna na którym będzie się rysował labirynt oraz inicjalizacją timera, który odliczając okreslony czas będzie
wywoływał metodę relizującą kolejne kroki algorytmu.
W wierszu 296 gotowego skryptu dołączonego do implementacji, tworzona jest instancja klasy Maze o wymiarach 16 na 16 komnat. Zmienna
m przechowuje instancję klasy Maze.
if __name__ == "__main__":
m = Maze(16, 16)
Dalsza część kodu poniżej realizuje pętlę oczekiwania na zdarzenie w Pygame, którym mogą być wciśnięcia klawiszy, naciśnięcie przycisku
zamykania okna, zdarzenia związen z ruchem myszy, naciskaniem jej przycisków oraz zdarzenia wywoływane gdy ustawiony timer odliczy
określoną ilość czasu. Wykorzystamy tylko dwa z nich, zamknięcie okna oraz odliczenie timera. W przypadku gdy timer odliczy określoną ilość
czasu wywołamy metodę tick() na instancji klasy Maze, która wykona kolejny krok algorytmu. Dzięki czemu sterując czasem timera, jesteśmy
w stanie wpływać na szybkość działania algorytmu i wizualizacji.
1
Projekt “Strategia Wolnych i Otwartych Implementacji jako innowacyjny model zainteresowania kierunkami informatyczno-technicznymi oraz wspierania uczniów i uczennic w
kształtowaniu kompetencji kluczowych” współfinansowany ze środków Unii Europejskiej w ramach Europejskiego Funduszu Społecznego.
while True:
for event in pygame.event.get():
if event.type == QUIT:
del m
sys.exit(0)
elif event.type == TIMER_TICK:
m.tick()
Zajmijmy się teraz samą klasą Maze. Jej deklaracja znajduje się w linii 82 przykładowego kodu. Metoda __init__ klasy jest odpowiednikiem
konstruktora w innych językach programowania. Jest ona wywoływana automatycznie przez interpreter Pythona w momencie tworzenia
instancji klasy. W naszym przypadku przyjmuje dwa parametry "width" i "height". Jak wspomniano wyżej, są to szerokość i wysokość labiryntu
wyrażona w komnatach. Zobaczmy co dzieje się metodzie __init__ klasy Maze:
random.seed()
self.stack = []
self.unvisited = []
self.visited = []
Linia "random.seed()" inicjuje generator licz pseudolosowych. Bez wykonania tego kroku, za każdym uruchomieniem programu
otrzymywalibyśmy taki sam rezultat. Wynika to z działania generatorów liczb losowych w językach programowania.
Kolejne linie inicjują zmienne klasy, zapis zmienna = [] oznacza przypisanie do zmiennej pustej listy. Na takiej zmiennej możemy następnie
wykonać wszystkie metody listy, także te które sprawiają, że lista może zachowywać się jak stos, czyli odkładanie elementu na stos:
lista.append(element) i zdejmowanie elementu z listy: lista.pop(). W klasie będziemy przechowywać trzy listy:
• stack - stos na który będziemy odkładać, komnaty do których będziemy wracać.
• unvisited - lista nieodwiedzonych jeszcze komnat. Badając długość tej listy będziemy wiedzieć, że mamy jeszcze jakieś nieodwiedzone
komnaty oraz z tej listy będziemy losować komnaty jeszcze nieodwiedzone.
• visited - lista odwiedzonych komnat. W miarę postępów w działaniu algorytmu, lista będzie powiększać się o kolejne odwiedzone komnaty.
Kolejne dwie linie sprawdzają czy podane na wejściu argumenty "width" i "height" mieszczą się w przewidzianym zakresie:
self.width = width if width < MAX_WIDTH else MAX_WIDTH
self.height = height if height < MAX_HEIGHT else MAX_HEIGHT
Wyrażenie: zmienna = wartość_1 if <wyrażenie> else wartość_2 jest w Pythonie interpretowane następująco:
Jeżeli wartość wyrażenia po słowie "if" jest True zwróć "wartość_1", w przeciwnym wypadku zwróć "wartość_2" i przypisz zwróconą wartość do
"zmienna"
Same komnaty są zdefiniowane jako pary parametrów X i Y określające położenie komnaty na planszy labiryntu. W Pythonie istnieje
reprezentacja danych, która nazywana jest tuplą, jest to zbiór elementów do których możemy dostać się za pomocą indkesu, podobnie jak ma
to miejsce w przypadku list, jednak w odróżnieniu od nich, tuple są niezmienne. Nie można usunąć elementu tupli ani dodać nowego do
gotowej. tuple definiowane są za pomocą znaku "," (przecinka). Jednocześnie możliwe jest tworzenie nowych tupli za pomocą już istniejących.
Daje to ogromne możliwości na przykład w przypadku funkcji zwracacjących więcej niż jedną wartość. W Pythonie można napisać funkcję:
def get_xy(value):
n = value / 100
m = value % 100
return n,m #Zwracamy tuplę z wartościami x i y
x,y = get_xy(150) #Tworzymy nową tuplę x,y z wartości zwróconych przez funkcję get_xy()
print x #Wypisze na konsoli wartość 1 (150/100 - dzielone całkowicie)
print y #Wypisze na konsoli wartość 50 (150 % 100 - reszta z dzielenia 150 / 100)
2
Projekt “Strategia Wolnych i Otwartych Implementacji jako innowacyjny model zainteresowania kierunkami informatyczno-technicznymi oraz wspierania uczniów i uczennic w
kształtowaniu kompetencji kluczowych” współfinansowany ze środków Unii Europejskiej w ramach Europejskiego Funduszu Społecznego.
Wracając jednak do samego algorytmu i klasy Maze. Komnaty są opisane jako wartości X i Y i w postaci takich par zapisywane w listach
visited, unvisited oraz odkładane na stosie. Wiemy więc dokładnie, które komórki odwiedziliśmy, które jeszcze możemy odwiedzić, jakie są
odłożone na stosie i do których możemy wrócić, gdy nie mamy już możliwości pójścia dalej.
Klasa Maze posiada także kilka dodatkowych metod, które pomagają w realizacji algorytmu:
get_neighbours(self, cell) - Dla podanej w parametrze "cell" komnaty, funkcja losuje jej sąsiadów o ile są jacyś i zwraca jednego z nich albo
nie zwraca żadnego.
make_visited(self, cell) - Podaną w parametrze "cell" komnatę, metoda oznacza jako odwiedzoną, sprowadza się to do przeniesienia komnaty
podanej w parametrze z listy "unvisited" do listy "visited"
draw_cell(self, cell, color)
- Rysuje na planszy komnatę podaną w parametrze "cell", kolorem określonym w prametrze "color"
wreck_wall(self, neighbours, color) - Usuwa ściany między komnatami sąsiadami podanymi jako lista komnat w parametrze "neighbours".
Lista sąsiadów komnaty jest tworzona z aktualnie wybranej komnaty oraz wylosowanego, nieodwiedzonego sąsiada.
tick(self) - metoda realizująca właściwy algorytm. Algorytm jest realizowany krok po kroku aby umożliwić obserwację jego działania. Metoda
ta jest synchronizowana ze zdarzeniami odliczenia czasu przez timer.
Z naszego punktu widzenia metoda tick() jest najważniejszą metodą obiektu Maze. Zobaczmy jak jest ona zrealizowana w przykładzie:
def tick(self):
'''
Metoda realizuje właściwy algorytm. Każde jej wywołanie to jeden krok algorytmu.
Jest ona wywoływana za każdym razem kiedy timer odliczy liczbę milisekund zawartą w zmiennej DELAY
'''
if self.stack:
#losujemy sąsiadów do których nie mamy jeszcze przejścia
neighbours = self.get_neighbours(self.current) #Jeżeli są jacyś sąsiedzi
if neighbours: cell = neighbours[0]
self.stack.append(self.current) #Aktualną odkładamy na stos
if (cell[0], cell[1]) == self.start: #Komórkę startową rysujemy na czerwono
self.draw_cell(cell, RED)
else:
self.draw_cell(cell, GREEN)
self.wreck_wall((self.current, cell), GRAY) #Burzymy ścianę między aktualną a wylosowanym sąsiadem
self.current = cell #Sąsiad staje się aktualną komnatą
self.make_visited(cell) #Zaznaczamy sąsiada jako odwiedzonego
elif self.stack: #Jeżeli nie ma sąsiada ale stos nie jest pusty
if self.current == self.start:
self.draw_cell(self.current, RED)
else:
self.draw_cell(self.current, GRAY)
3
Projekt “Strategia Wolnych i Otwartych Implementacji jako innowacyjny model zainteresowania kierunkami informatyczno-technicznymi oraz wspierania uczniów i uczennic w
kształtowaniu kompetencji kluczowych” współfinansowany ze środków Unii Europejskiej w ramach Europejskiego Funduszu Społecznego.
cell = self.stack.pop() #Zdejmujemy komnatę ze stosu
self.current = cell #Która staje się aktualną
else: #Jeżeli stos jest pusty i nie ma żadnego sąsiada #do którego możemy przebić przejście
#Losujemy komnatę z listy jeszcze nie odwiedzonych
cell = self.unvisited[random.randint(0, len(self.unvisited) - 1)] self.current = cell #Staje się ona naszą aktualną
self.make_visited(cell) #Oznaczmy ją jako odwiedzoną
pygame.display.update() #Odrysowujemy zawartość okna
else:
print("Zrobione!") #Stos jest już pusty, koniec algorytmu
pygame.time.set_timer(TIMER_TICK, 0) #Zatrzymujemy timer
W programie, na jego początku zdefiniowano także kilka zmiennych ustalających parametry programu takie jak:
• MAX_WIDTH - maksymalna ilość komnat w poziomie
• MAX_HEIGHT - maksymalna ilość komnat w pionie
• WINDOW_WIDTH - szerokość okna z prezentacją, w pikselach
• WINDOW_HEIGHT - wysokość okna z prezentacją, w pikselach
• LINE_WIDTH - grubość linii ścian oddzielających komnaty, w pikselach
• TIMER_TICK - zdarzenie użytkownika zwracane, gdy timer odliczy określoną ilość czasu.
• DELAY - opóźnienie w milisekundach, między kolejnymi "tyknięciami" timera. Z tym opóźnieniem będzie wołana metoda "tick()"
• oraz zmienne NBR_UP, NBR_LEFT, NBR_RIGHT, NBR_DOWN, będące kolejnymi wartościami od 0 do 3 oznaczającymi, do którego sąsiada ma
zostać stworzone przejście. Na podstawie tych wartości wreck_wall() wie, którą ścianę ma zburzyć.
Nie jest to oczywiście jedyny algorytm generujący labirynty ani też najbardziej optymalny, zarówno jeżeli chodzi o czas wykonania,
wymagania pamięciowe oraz rezultat w postaci gotowego labiryntu. Jego implementacja jest jednak w miarę prosta i pokazuje zastosowanie
stosu. Ciekawostką jest fakt, że ten sam algorytm może służyć nie tylko do generowania labiryntów ale także do ich przechodzenia w sposób
automatyczny.
4
Projekt “Strategia Wolnych i Otwartych Implementacji jako innowacyjny model zainteresowania kierunkami informatyczno-technicznymi oraz wspierania uczniów i uczennic w
kształtowaniu kompetencji kluczowych” współfinansowany ze środków Unii Europejskiej w ramach Europejskiego Funduszu Społecznego.
Algorytm w trakcie tworzenia przejść w labiryncie
5
Projekt “Strategia Wolnych i Otwartych Implementacji jako innowacyjny model zainteresowania kierunkami informatyczno-technicznymi oraz wspierania uczniów i uczennic w
kształtowaniu kompetencji kluczowych” współfinansowany ze środków Unii Europejskiej w ramach Europejskiego Funduszu Społecznego.
Rezultat działania algorytmu
Filmy instruktażowe:
• Labirynt przygotowanie narzędzia
http://youtu.be/bcIVbUL-2_4
• Labirynt część 1
http://youtu.be/K6BR1n-dL5s
• Labirynt część 2
http://youtu.be/V1eIwRyi7oo
• Labirynt część 3
http://youtu.be/nXFm4-za_Ew
6
Projekt “Strategia Wolnych i Otwartych Implementacji jako innowacyjny model zainteresowania kierunkami informatyczno-technicznymi oraz wspierania uczniów i uczennic w
kształtowaniu kompetencji kluczowych” współfinansowany ze środków Unii Europejskiej w ramach Europejskiego Funduszu Społecznego.

Podobne dokumenty