Treści zawarte w tym e‑materiale wykraczają poza poziom edukacyjny. Jeśli jednak jesteś zainteresowany tematyką rekurencji oraz wykorzystaniem jej do obliczania największego wspólnego dzielnika (NWD), to zachęcamy Cię do zapoznania się z poniższym e‑materiałem.
Ten materiał wymaga znajomości środowiska Scratch. Potrzebne informacje na ten temat znajdziesz w materiale Wprowadzenie do środowiska ScratchP9AYGQXHJWprowadzenie do środowiska Scratch.
Pewnie mieliście okazję zagrać w grę, w której bohater musiał pokonać labirynt. Być może nawet sami tworzyliście grę, w której gracze sterowali bohaterem, prowadząc go przez labirynt. Teraz zadanie dla duszka będzie trudniejsze. Po kliknięciu zielonej flagi, duszek będzie musiał samodzielnie znaleźć drogę na wyjście z labiryntu. Będzie realizował algorytm, który poprowadzi go do wyjścia przez labirynt. Przykładowe działanie aplikacji można zobaczyć na poniższym filmie.
RcYgjpKWcXFJN
Na planszy w powyższej aplikacji są pola w trzech kolorach – zielonym, czerwonym i jedno niebieskie. Zielony oznacza korytarz, czyli pola po których duszek może się poruszać, a czerwony - ściany, czyli pola, niedostępne dla duszka. Wyjście oznaczone jest kolorem niebieskim.
Wiedząc, że scena w Scratchu ma 480 pikseli szerokości i 360 pikseli wysokości możesz łatwo obliczyć długość boku pojedynczego pola. Dzieląc, np. wysokość (360) przez liczbę pól, które znajdują się w jednej kolumnie (9) dostaniesz wyniki równy 40. Tyle wynosi długość boku pojedynczego pola.
Przykładowy plik z planszą możesz pobrać poniżej.
R1217LNuF5eNb
Wykorzystaj poniższy dziennik do zapisywania notatek lub przemyśleń.
RmmJjmSvVzQ5i
1
Ćwiczenie 1
Wczytaj planszę z labiryntem jako nowe tło sceny. Pamiętaj o usunięciu domyślnego, białego tła. Zmień rozmiary duszka, tak aby mieścił się w pojedynczym polu.
W prawym dolnym rogu programu znajduje się przycisk do dodawania nowego tła. Po jego kliknięciu pojawi się opcja, która pozwoli ci wczytać własne tło. Jeśli chcesz dostosować rozmiar duszka, możesz to zrobić edytując odpowiednie pole, które znajduje się po prawej stronie w sekcji zarządzania duszkami.
W celu wczytania pobranego tła, zlokalizuj i naciśnij przycisk Wczytaj tło. Poniższy zrzut ekranu pomoże ci go zlokalizować. Następnie wybierz wcześniej pobraną grafikę i naciśnij Otwórz.
R1b8YzqCK6unh
Aby podstawowy duszek zmieścił się w pojedynczym kwadracie, ustaw jego rozmiar na 35. W tym celu wpisz odpowiednią liczbę w polu tekstowym obok napisu Rozmiar.
R19CQxXQhxOK3
Algorytm znajdowania wyjścia z labiryntu
1
Ćwiczenie 2
Opracuj plan działania dla duszka, określając, jak powinien się poruszać, aby znaleźć drogę. Przyjmij założenie, że duszek „patrzy” tylko o jedno pole wprzód oraz pamięta (oznacza) pola, które już odwiedził. Może więc stwierdzić, czy sąsiednie pole jest ścianą, wolnym polem albo polem, przez które już przeszedł. Może też cofać się, jak trafi w ślepą uliczkę.
Do poprawnego stworzenia algorytmu rozwiązującego to zadanie niezbędne będzie zrozumienie pojęcia rekurencjiPXyPxbpbPrekurencji. Rekurencja jest ważnym konceptem programistycznym, który polega na tym, że funkcja może wywołać samą siebie w celu rozwiązania problemu.
Duszek, stojąc na danym polu, powinien najpierw je oznaczyć. Następnie może próbować podjąć próbę przejścia na jedno z czterech sąsiednich pól. Ponieważ nic więcej nie wie, więc jest obojętne, które pole wybierze. Może na przykład rozpocząć od pola w kierunku, w którym aktualnie patrzy. Przy próbie przejścia na sąsiednie pole mogą wystąpić następujące sytuacje:
Sąsiednie pole jest wyjściem. Sukces i koniec algorytmu.
Sąsiednie pole jest wolne i nie było odwiedzane. Należy na nie przejść i kontynuować działanie, tak jakby to było pole startowe. Jeżeli nie przyniesie rezultatu (nie doprowadzi do wyjścia) należy się wycofać z ruchu.
Sąsiednie pole jest ścianą lub było już odwiedzone. Należy zmienić kierunek (np. w prawo o 90 stopni).
Ponieważ duszek ma do sprawdzenia cztery kierunki, powyższe czynności powinien powtórzyć cztery razy.
Funkcja szukaj_drogi:
Oznacz pole, na którym stoisz.
Powtórz 4 razy: 2.1. Jeżeli pole, na które patrzy duszek jest wyjściem to: ⠀ 2.1.1. Przejdź na pole na wprost (przesuń o 40 kroków). ⠀ 2.1.2. Powiedz: „Znalazłem wyjście!”. ⠀ 2.1.3. Zatrzymaj wszystko. 2.2. Jeżeli pole na wprost jest wolne i nie było odwiedzane, to: ⠀ 2.2.1. Przejdź na pole na wprost (przesuń o 40 kroków). ⠀ 2.2.2. Wywołaj funkcję szukaj_drogi. ⠀ 2.2.3. Wycofaj się z ostatniego ruchu (przesuń o -40 kroków). 2.3. Jeżeli pole na wprost jest ścianą lub było odwiedzone to: ⠀ 2.3.1. Obróć się w prawo o 90 stopni.
Zatrzymaj ten skrypt.
Zwróć uwagę na bardzo ważny punkt 2.2.2. Duszek rozpoczyna ponownie wykonywanie tego samego algorytmu od początku, ale z innego pola. Jeżeli z tego pola jest wyjście, nie wróci już do punktu 2.2.3. (ponieważ zakończy wykonywanie algorytmu w punkcie 2.1.2.). Natomiast jeżeli poszukiwanie drogi z tego pola zakończy się niepowodzeniem, wróci do punktu 2.2.3. i wycofa się z ostatniego ruchu. Zwróć też uwagę, że sprawdzenie warunku w punkcie 2.3. jest niepotrzebne. Jeżeli sąsiednie pole nie było wyjściem (punkt 2.1.) i nie dało się przez nie dojść do wyjścia (punkt 2.2.), to jest ścianą lub zostało oznaczone jako odwiedzone. Powyższy przepis można więc nieznacznie uprościć.
szukaj_drogi:
Oznacz pole, na którym stoisz.
Powtórz 4 razy: 2.1. Jeżeli pole na wprost jest wyjściem to: ⠀ 2.1.1. Przejdź na pole na wprost (przesuń o 40 kroków). ⠀ 2.1.2. Powiedz: „Znalazłem wyjście!”. ⠀ 2.1.3. Zatrzymaj wszystko. 2.2. Jeżeli pole na wprost jest wolne i nie było odwiedzane to: ⠀ 2.2.1. Przejdź na pole na wprost (przesuń o 40 kroków). ⠀ 2.2.2. Szukaj drogi. ⠀ 2.2.3. Wycofaj się z ostatniego ruchu (przesuń o -40 kroków). 2.3. Obróć się w prawo o 90 stopni.
Zatrzymaj ten skrypt.
2
Ćwiczenie 3
Wykonaj poniższe kroki, aby ustalić aktualną pozycję i kierunek duszka. Zakładamy, że duszek zaczyna na polu B2 i spogląda w dół. Kierunki jakie może wykonać to: dół, góra, lewo, prawo.
Zapisz początkową pozycję duszka jako B2,
Zapisz początkowy kierunek duszka jako dół,
Przejdź do następnego kroku algorytmu.
R5K5MZejQRBWr
R1GEJkQ7V7r1g
Poniższy film pokazuje działanie algorytmu dla planszy powyżej. Wypełniona tabela powinna odpowiadać ruchom duszka.
RXsPxc44cOT6r
Kolejne wywołania algorytmu szukaj_drogi:
RiniYdpwHEFaQ
Rq6bB3Zg4dDVx
Ważne!
W naszym rozwiązaniu, gdy napotkaliśmy pewien problem, zastosowaliśmy podejście, w którym odwołujemy się do tego samego problemu, ale dla innych danych. Może to oznaczać, że mamy podobną sytuację do rozwiązania, ale z innymi wartościami lub innymi warunkami. Na przykład, jeśli mamy do wykonania pewne zadanie, które można podzielić na mniejsze zadania tego samego typu, możemy użyć rekurencji (rekursji). To znaczy, że w celu rozwiązania całego zadania, wykonujemy te same kroki dla każdego z tych mniejszych zadań. Rekurencja polega na tworzeniu zbioru poleceń, które wywołują same siebie wewnątrz siebie. W Scratchu takie zbiory stanowią utworzone przez nas bloki w kategorii Moje bloki. Działa to podobnie jak rosyjskie lalki Matrioszka, gdzie jedna lalka jest schowana wewnątrz drugiej. W przypadku rekurencji, funkcja wykonuje się, a następnie wywołuje samą siebie, aby rozwiązać podzadanie, a to podzadanie może z kolei wywołać kolejne podzadania, aż do momentu osiągnięcia warunku zakończenia.
Więcej na temat rekurencji dowiesz się w dalszej części materiału.
Tworzymy skrypty dla duszka
Przed przystąpieniem do tworzenia skryptów należy jeszcze rozstrzygnąć dwa problemy:
Jak duszek ma oznaczać pole?
W jaki sposób duszek ma rozpoznawać kolor sąsiedniego pola?
Zwróć uwagę, że nie ma znaczenia, czy pole jest ścianą, czy było już odwiedzone. Najwygodniej będzie oznaczać pola odwiedzone tym samym kolorem co ściana. Jak można zauważyć na filmach, duszek stawia na nowo odwiedzanym polu kropkę w kolorze ściany.
Żeby sprawdzić w jakim kolorze jest sąsiednie pole, duszek musi niestety na nim stanąć. Przejdzie więc najpierw na sąsiednie pole. Jeżeli pod duszkiem nie ma koloru czerwonego, kontynuuje działania. Jeżeli jest kolor czerwony, wycofuje się. W związku z tym dostosujemy nasz algorytm w postaci listy kroków do możliwości duszka w środowisku Scratch.
szukaj_drogi:
Oznacz pole, na którym stoisz.
Powtórz 4 razy: 2.1. Przesuń o 40 kroków. 2.2. Jeżeli dotyka koloru niebieskiego to: ⠀ 2.2.1. Powiedz „Znalazłem wyjście przez 2 sekundy”. ⠀ 2.2.2. Zatrzymaj wszystko. 2.3. Jeżeli nie dotyka koloru czerwonego to: ⠀ 2.3.1. Szukaj drogi. 2.4. Przesuń o -40 kroków. 2.5. Obróć się w prawo o 90 stopni.
Zatrzymaj ten skrypt.
2
Ćwiczenie 4
Stwórz swój własny skrypt, który będzie wykonywał opisany wcześniej algorytm. Zacznij od utworzenia nowego bloku o nazwie oznacz_pole. Możesz tego dokonać klikając przycisk Utwórz blok w kategorii Moje bloki. Dane wejściowe pozostaw puste.
Po utworzeniu nowego bloku jest on od razu dostępny w kategorii Więcej bloków. Możesz go wykorzystać w definicji tego bloku. W ten sposób wykonasz wywołanie tej samej funkcji dla nowych parametrów.
R16RMCVP9IfSd
2
Ćwiczenie 5
Uzupełnij blok oznacz_pole. Zastanów się, jak narysować czerwony ślad. Pamiętaj, że duszek po narysowaniu musi być na tej samej pozycji, co przed rysowaniem.
Wystarczy jak czerwony ślad będzie pojedynczą kropką. Jego zadanie to pokazanie przebytej przez duszka drogi.
RsjShxG8BNq6s
2
Ćwiczenie 6
Przygotuj skrypt, który po uruchomieniu spowoduje, że duszek dotrze do niebieskiego pola na planszy. Aby tego dokonać, umieść duszka na polu startowym i ustal, w którym kierunku ma się poruszać. Dodatkowo, użyj pisaka do oznaczenia odwiedzonych miejsc. Do znalezienia trasy wykorzystaj wcześniej przygotowany blok o nazwie szukaj_drogi.
Zastanów się, jakie czynności początkowe, oprócz ustawienia atrybutów pisaka (rozmiaru, koloru), należy wykonać po kliknięciu zielonej flagi. Weź też pod uwagę, że jeżeli duszek będzie zbyt dużych rozmiarów, Scratch będzie błędnie interpretował, że duszek zawsze dotyka koloru czerwonego.
Duszka należy ustawić w polu startowym, obróconego w wybranym kierunku (jeden z czterech: lewo, prawo, góra, dół) oraz wyczyścić scenę po poprzednim uruchomieniu.
R1W1iItrO4XsF
Uruchom i przetestuj działanie aplikacji. Prawdopodobnie duszek bardzo szybko biega po labiryncie. Możesz go spowolnić, wstawiając klocek czekaj … si0E2ogPrm8_d479e210czekaj … s z odpowiednio dobranym czasem opóźnienia przed wywołaniem bloku szukaj_drogi. Możesz także przygotować drugi skrypt uruchamiany po kliknięciu zielonej flagi animujący duszka podczas chodzenia po labiryncie.
i0E2ogPrm8_d479e187
R2TTlVzOyuwRw
Czujnik sprawdza, czy duszek dotyka wybranego koloru na scenie. Aby wybrać kolor, należy kliknąć myszką w kwadracik z kolorem w klocku, następnie wskazać kursorem myszy kolor na ekranie.
Przykład:
R1YAHxtaDdCzy
i0E2ogPrm8_d479e210
R1Z6YginlxEqt
Bloczek powoduje wstrzymanie działania skryptu na określony czas.
Przykład:
RZze1Pxa3TEq4
Zadania uzupełniające
2
Ćwiczenie 7
Popraw planszę tak, aby nie było przejścia z pola startowego do wyjścia lub usuń wyjście. Możesz w tym celu skopiować tło sceny i je nieznacznie poprawić w edytorze graficznym. Na którym polu duszek zakończy poszukiwanie drogi? Jaki blok za to odpowiada? Popraw skrypt tak, aby duszek poinformował w dymku komiksowym, że nie ma przejścia do wyjścia.
Zablokowanie drogi do wyjścia możesz osiągnąć zmieniając kolor jednego z pól, np. D4 z zielonego na czerwony. Możesz także usunąć wyjście zamalowując niebieski kwadrat na czerwono.
Przyjrzyj się skryptowi odpowiedzialnemu za szukanie drogi i znajdź polecenie, które odpowiada za poruszanie się duszka po planszy.
RR9wp5uthXmLl
Duszek powróci do pola startowego po odwiedzeniu wszystkich możliwych pól, do których może dojść. Wywołanie wcześniej wykonanych skryptów zakończy się po wykonaniu wszystkich bloków (ostatni blok zatrzymaj ten skrypt), więc sterowanie wróci do skryptu zielonej flagi. Tam, po wykonaniu bloku szukaj_drogi, można dodać wyświetlenie informacji w dymku komiksowym o nieznalezieniu wyjścia.
2
Ćwiczenie 8
Zmodyfikuj tak aplikację, aby duszek próbę wykonania kolejnego ruchu rozpoczynał zawsze od tego samego kierunku (np. w prawo), a nie kierunku, w którym patrzy.
Zadbaj o to, aby duszek wycofując się z ruchu, patrzył w tym samym kierunku, w którym wykonał ruch.
Wstaw dodatkowy blok ustawiający duszka w wybraną stronę tuż przed rekurencyjnym wywołaniem funkcji szukaj_drogi.
R1bTNH5HlV4yg
Ciekawostka
Do realizacji zadania została zastosowana technika projektowania algorytmów – rekurencja. Budując algorytm rekurencyjny należy pamiętać o podstawowych zasadach: wywołanie rekurencyjne zazwyczaj występuje dla zmienionych danych przy spełnieniu pewnego warunku. Ciąg wywołań rekurencyjnych powinien się zakończyć w momencie spełnienia warunku stopu. Warunek stopu oznacza, że program dla dowolnych danych musi ostatecznie zakończyć działanie. Ustalenie warunku stopu jest ważne w przypadku funkcji rekurencyjnych, bowiem definiując funkcje rekurencyjne nietrudno o warunku stopu zapomnieć. Najczęściej budujemy blok rekurencyjny z danymi wejściowymi, a warunek, kiedy następuje wywołanie rekurencyjne zależy od tych danych. W omawianym algorytmie rekurencyjnym (wychodzenia z labiryntu) rolę danych wejściowych pełni pole, na którym stoi duszek, choć sam blok (w skryptach) jest zdefiniowany bez parametrów. Wywołanie rekurencyjne zależy od koloru pola, na które próbuje przejść duszek. Porównaj działanie opracowanej przez siebie aplikacji z filmem, który znajduje się na początku tego materiału. Czy obie aplikacje stosują ten sam algorytm znajdowania drogi wyjścia z labiryntu? Łatwo zauważysz, że w twojej aplikacji duszek znajduje drogę do wyjścia, ale wcale nie najkrótszą, często wracając. Takie algorytmy nazywane są algorytmami z powrotami i często są zapisywane rekurencyjnie. Natomiast w przedstawionej na filmie aplikacji duszek znajduje najkrótszą drogę do wyjścia. Rozwiązuje tak naprawdę bardziej ogólny problem znalezienia najlepszej drogi (w tym przypadku najkrótszej) pomiędzy dwoma miejscami (polami). Z problemem znalezienia najlepszej drogi (najkrótszej, najszybszej) możesz się zetknąć choćby korzystając z nawigacji satelitarnej lub map internetowych. W przyszłości, jeśli będziesz kontynuować swoją przygodę z algorytmami, poznasz zapewne algorytm znajdowania najkrótszej drogi przejścia, a także inne problemy rozwiązywane algorytmami z powrotami.
2
Ćwiczenie 9
Zajrzyj do projektu Ułamki zwykłeD1AyvQpb3Ułamki zwykłe dotyczącego algorytmu Euklidesa. Własność NWD została zdefiniowana rekurencyjnie:
przy założeniu, że a>b.
Spróbuj utworzyć własny blok obliczający NWD rekurencyjnie i stwórz prostą aplikację sprawdzającą, czy blok działa poprawnie.
Dodatkowe informacje znajdziesz w materiale Klasyczny Algorytm Euklidesa z resztą z dzieleniaPs7XRBNbCKlasyczny Algorytm Euklidesa z resztą z dzielenia. Odpowiednie zrozumienie algorytmu Euklidesa jest niezbędne, aby stworzyć poprawne rozwiązanie.
Zapoznaj się z przykładem skryptu, który znajdziesz w materiale Tworzenie gry interaktywnej - ułamki zwykłeD1AyvQpb3Tworzenie gry interaktywnej - ułamki zwykłe. Skrypt ma za zadanie znaleźć największy wspólny dzielnik (NWD). Jedyną różnicą jest to, że funkcja zostanie wywołana ponownie, ale z innymi liczbami jako parametrami. Dzięki temu możemy wielokrotnie używać tej samej funkcji do rozwiązywania problemów z różnymi liczbami.
Przykładowe skrypty:
R1LiLNHW7KbPa
RMTEFzwiToH6L
Ważne!
Dla wielu problemów można podać rozwiązanie zarówno rekurencyjne, jak i iteracyjne (czyli z wykorzystaniem pętli). W szczególności, jeśli w algorytmie rekurencyjnym następuje dokładnie jedno wywołanie rekurencyjne, zawsze można podać rozwiązanie z jedną pętlą powtarzaj ażi0E2ogPrm8_d479e272powtarzaj aż. W takiej sytuacji lepiej jest stosować rozwiązanie iteracyjne.
i0E2ogPrm8_d479e272
RgPsBWPPBDAOE
Klocek powtarza zestaw poleceń aż do spełnienia warunku logicznego. Warunki budujemy z klocków kategorii Czujniki i Wyrażenia.