Pająk łapie muchy

Wprowadzenie


Stworzymy aplikację, w której nasz bohater – pająk - będzie łapał muchy. Sterowanie pająkiem będzie odbywać się przy pomocy klawiszy strzałek. Muchy będą się tworzyć w losowej ilości w losowych miejscach, mało tego, stworzymy prostą symulację muchy – będzie się poruszała sama, a gdy „wyczuje” pająka, spróbuje od niego uciec. Poniżej zrzut, jak aplikacja docelowo będzie wyglądać:

Pm01.PNG

Czego się nauczymy:

  • generowania większej niż 1 ilości obiektów danej klasy naraz,
  • poruszania obiektem przy pomocy klawiszy strzałek,
  • sprawdzania, czy obiekt pewnej klasy nie jest w pobliżu.

Tworzenie pola gry Potrzebujemy stworzyć podklasę Świata, a także aktorów, którzy będą po naszym świecie (a konkretniej – ścianie) chodzić. Pytanie, czy w kwestii much będziemy potrzebować stworzyć 'n' różnych aktorów, żeby ich dodać? Zobaczymy, jak można kwestię „mnożenia” obiektów rozwiązać, tworzymy więc tylko jednego aktora klasy Mucha, jednego Pająka, oraz - jako Świat – Ścianę, dodając im odpowiednie grafiki. (jeśli chodzi o ścianę, polecamy tę białą – przy czerwonej czasem pająk się może zlać z tłem). Kompilujemy, powinniśmy otrzymać następujący widok:

Pm02.PNG

Pająk

Mamy zatem stworzone pole gry, z prawej strony widać też listę aktorów, możemy się zatem zająć programowaniem naszego głównego antybohatera. Dodajmy go najpierw do Świata. Wchodzimy więc do edytora klasy Sciana, usuwamy na tę chwilę wszystkie komentarze. W metodzie konstruktora Sciany pod wywołaniem metody rodzica (dla przypomnienia – super(600,400,1)) tworzymy nowy obiekt:

   Pajak pajaczek = new Pajak();

Oprócz tego potrzebujemy też dodać go do naszego świata na sam środek planszy, poprzez znaną nam instrukcję:

   addObject(pajaczek, 300, 200);

Kompilujemy i sprawdzamy, czy mamy pająka na ścianie (tej wirtualnej, rzecz jasna). Nasz pająk może i jest już dodany do świata, ale nie rusza się...co jest dobrą oznaką, bo musimy go nauczyć chodzić! Edytujemy zatem klasę Pajak (komentarze usuwamy). Teraz zagadka: gdzie powinniśmy dodać kod odpowiedzialny za chodzenie? Mamy taką specjalną metodę act(), która wykonuje się przez cały czas trwania programu – może ją moglibyśmy wykorzystać? Nawet nie 'moglibyśmy', co 'powinniśmy'! Zagadka kolejna - jak można nauczyć pająka chodzić? Pomyślmy: mamy ograniczone pole manewru, jeśli chodzi o klawiszologię – nasz antyheros potrafi się poruszać tylko w 2 wymiarach, możemy skorzystać zatem ze starego dobrego warunku 'if' – jeżeli. Co będziemy sprawdzać w naszym warunku? Potrzebujemy metody, która jest mostem między programem komputerowym a klawiaturą, możemy zatem spróbować przejrzeć dostępne metody dla samego Greenfoota! Wpisujemy więc w metodę act() następujący kod:

if(Greenfoot.)

Ustawiamy kursor edytora po kropce i wciskamy kombinację ctrl+spacja – dostaniemy listę metod:

Pm03.PNG

Wśród nich jest metoda isKeyDown(String), w opisie tej metody, widzimy, że faktycznie, to może być strzał w dziesiątkę - „Sprawdza, czy konkretny klawisz jest naciśnięty”. Dodajemy po kropce isKeyDown(). Jako parametr tej metody potrzebujemy nazwę klawisza, którą będziemy sprawdzać. Nazwy są angielskie, skoro będziemy używać strzałek, to mają one następujące nazwy: górna - "up”, dolna - "down”, lewa - "left” oraz prawa - "right”. Wstawiamy "up” do miejsca parametru i powinniśmy mieć następujący kod:

     if(Greenfoot.isKeyDown("up")

Co ma się stać, jeśli użytkownik wciśnie klawisz strzałka w górę? Nasz pajęczak powinien się poruszyć w górę – musimy pamiętać, jak grafika jest rysowana w Javie (i C++ i wielu innych językach) – lewy górny róg jest początkiem naszego układu, ma więc współrzędne (0, 0). Prawy dolny – (600, 400). Można zatem zauważyć, że idąc w dół, nasze 'Y' będzie rosło, idąc w górę – malało! Wiemy więc, jak musimy zmieniać X i Y naszego aktora, teraz zastanówmy się, czego musimy użyć, żeby to położenie zmienić (słowo-klucz: ustawić). Będziemy mu ustawiać położenie, możemy zatem poszukać, jakie metody związane są z ustawianiem. Pod naszym warunkiem wpisujemy set, wciskamy ctrl+spacja i widzimy, że jest metoda setLocation(x,y), która potrzebuje dwóch liczb. Jeśli chcemy, żeby nasz pająk szedł do góry, nasza współrzędna X powinna się nie zmieniać, co innego współrzędna Y. Tę zmniejszymy o 5. Potrzebujemy jeszcze obecną lokację – od tego są metody pobierające – getX() oraz getY(). Wykorzystamy je zatem do naszych celów i koniec końców powinniśmy dostać następujący kod dla klawisza ”up”:

     if(Greenfoot.isKeyDown("up"))
     setLocation(getX(), getY()-5);

Tłumacząc na język polski: jeśli klawisz „strzałka w górę” został naciśnięty, to ustaw lokację na ObecneX oraz ObecneY, ale pomniejszone o 5. Kompilujemy i testujemy. Działa! Działanie reszty klawiszy ustawiamy analogicznie. Cały kod metody act() pająka wygląda zatem następująco:


Pm04.PNG

Kompilujemy i sprawdzamy, czy faktycznie nasz pająk goni we wszystkich kierunkach. Błędy mogą wynikać z literówek w nazwach klawiszy lub nieprawidłowych modyfikacjach współrzędnych.

  • Pytanie: Jeśli nie chcielibyśmy korzystać z metody setLocation(), to czy da się w inny sposób nauczyć pająka chodzić? Pająk w tym momencie robi „moonwalking”.
  • Odpowiedź: Da się. Potrzebowalibyśmy nieco “roztyć” nasz kod, ale kwestię wymyślenia jak to zrobić, pozostawiamy Wam, drodzy nauczyciele, oraz Waszym uczniom.
  • Podpowiedź: wykorzystamy ten drugi sposób w ruchu much.


Mucha

Ruch

Pająka zakodowaliśmy, możemy przejść do muchy. Zajmiemy się najpierw ruchem muchy, dodajemy ją do świata, w konkretnym położeniu:

    Mucha muszka = new Mucha();
    addObject(muszka, 100, 100);

Po kompilacji, nasza mucha powinna sobie siedzieć na ścianie wraz z pająkiem:

Pm05.PNG

Żeby ją poruszyć, znowu potrzebujemy wykorzystać metodą act() - jednak to mucha sama będzie się poruszała, my ją co najwyżej będziemy mogli zjeść. Zagadka: w jaki sposób muchy się poruszają? Latają cały czas? Czy może latają, przycupną gdzieś na ścianie i chodzą po niej w losowym (a przynajmniej tak się wydaje) kierunku, a następnie znowu gdzieś się zatrzymują? Mamy zatem dwa problemy do rozwiązania:

  • ograniczyć ruch much,
  • wylosować im kierunek, w jakim mają się poruszać.


Problem drugi jest do rozwiązania poprzez odpowiednią metodę Greenfoota. Pierwszy problem można rozwiązać następująco – ograniczyć chód do odpowiedniego momentu. Czym będzie „odpowiedni moment”? Nie mamy licznika czasu, ale możemy stworzyć kod, który będzie pseudo-inteligencją muchy, tj. mucha namyśli się, że chce latać, to poleci, jeśli natomiast dalej będzie wolała kontemplować na ścianie, nie poruszy się. Ten moment, kiedy się namyśli, możemy uznać jako „traf w lotto”, czyli będziemy losowali liczby z jakiegoś przedziału, ale tylko przy kilku liczbach nasza mucha się poruszy. Przechodzimy do edytora muchy. Tworzymy zmienną typu całkowitego o nazwie „czyLatac”. Jej wartość będzie losowana z przedziału od 0 do 99. Czyli:

   int czyLatac = Greenfoot.getRandomNumber(100);


Wylosowaliśmy liczbę imitującą proces myślowy muchy, teraz musimy go jakoś wykorzystać. Mówiliśmy, że tylko przy określonych liczbach mucha się poruszy. Możemy wykorzystać instrukcję if i sprawdzić, czy wylosowana liczba trafia na moment, kiedy mucha ma się poruszyć. Proponujemy, żeby przedział latania nie był duży, maksymalnie 7-8% z całego przedziału losowania zmiennej czyLatac. Co ma się stać, kiedy mucha stwierdzi, że chce sobie rozprostować nogi? Ma gdzieś przejść. Mamy tutaj do rozwiązania problem #2. Dla przykładu, stwórzmy sobie osobną metodę, odpowiadającą tylko i wyłącznie, za ruch much i ustawienie kierunku, w którym ma lecieć. Tworzymy publiczną metodę frunMucho(), która nie zwraca nic, oraz nie przyjmuje parametru {przypomnienie, własne metody tworzymy poza metodą act()}. Jej jedynym kodem będzie wylosowanie kierunku i ustawienie obrotu, oraz przejście o 1 piksel. W pająku użyliśmy setLocation, tutaj jednak sprawdzi się bardziej inna metoda – setRotation(). Jako parametr przyjmuje ona liczbę całkowitą – kąt, w którym ma się nasz aktor zwrócić. Chcemy, żeby mucha obracała się w losowym kierunku, więc znowu wykorzystamy losowanie, ale teraz w przedziale od 0 do 359. Oprócz tego mucha ma przejść 1 piksel. Cała metoda wygląda następująco:


Pm06.PNG

Wracamy do metody act(), w której mamy stworzoną zmienną oraz zalążek warunku:

Pm07.PNG

Co powinniśmy wstawić, jeśli warunek zostanie spełniony? Potrzebujemy tylko i wyłącznie wywołać metodę frunMucho(); Kompilujemy i sprawdzamy. Mucha faktycznie stoi w miejscu, ale nieśmiało drży – żeby ją nieco ożywić, dodajmy instrukcję move(3) przed zmienną czyLatac i przetestujmy. Mucha faktycznie nieco lepiej się rusza. Ruch mamy zapewniony, na tym etapie kod muchy powinien wyglądać następująco:

Pm08.PNG


Zjadanie przez pająka oraz ucieczka

Kolejnym krokiem w naszej aplikacji jest zaprogramowanie dwóch rzeczy: ucieczki, jeśli pająk znajdzie się w pobliżu muchy, oraz zjadania muchy, jeśli pająk ją doścignie. Zajmijmy się na tę chwilę pożarciem. W gruncie rzeczy chodzi o interakcję dwóch obiektów – kiedy pająk dotyka muchy. Kolejny raz z pomocą przychodzi nam warunek 'jeżeli'. Zjadanie muchy będziemy robili nie wprost – to mucha będzie cały czas (czyli w metodzie act() ) sprawdzała, czy pająk ją dopadł i to jej zadaniem jest poinformowanie świata o tym tragicznym wydarzeniu. W Grenfoocie możemy sprawdzić, czy dany obiekt dotyka innego obiektu klasy. W dokumentacji Aktora mamy metodę isTouching(nazwa klasy). Ni mniej ni więcej działa to tak, że nasza mucha za każdym razem sprawdza, czy dotyka innego obiektu klasy. Nazwa klasy jako parametr to nic innego jak Pajak.class. Nasz warunek do skleceniu tych informacji wygląda następująco:

   if(isTouching(Pajak.class))

Teraz musimy się zastanowić nad tym, co ma się stać w środku warunku. Skoro pająki i muchy chodzą po świecie, to może ma on jakąś metodę do usuwania obiektów? Ma – to w naszym świecie dodajemy i usuwamy obiekty, odwołujemy się do świata, a konkretnie do metody usuńObiekt, którego parametrem będzie...konkretna mucha – czyli słówko kluczowe to “this”:

   getWorld().removeObject(this);

Możemy przekompilować i przetestować działanie tego warunku. Na co trzeba uważać? Na kolejność instrukcji. Instrukcja poruszania się (move) musi być jako pierwsza, bo jeśli będzie po warunku „dotyku”, to pojawi się błąd, że takiego obiektu już nie ma w świecie. Innymi słowy - kazalibyśmy martwej musze poruszyć się. Kwestia uciekania – w tym momencie nasza mucha o bardzo małym rozumku jest w stanie wylosować obrót prosto w otwór gębowy naszego bohatera. Możemy jej jednak powiedzieć, że jeśli wyczuje w swoim pobliżu obiekt pewnej klasy, to ma uciekać – w tym przypadku ma raz jeszcze wywołać metodę frunMucho(). Aktorzy w Greenfoocie mają kolejną przydatną metodę – getObjectsInRange(zasięg, nazwa klasy). Metoda ta tworzy swoją własną listę obiektów danej klasy, która jest w zasięgu muchy. Zasięg jest mierzony w pikselach, nazwa klasy to w tym przypadku, podobnie jak przy sprawdzaniu interakcji, Pajak.class.

Skoro sprawdzenie, czy mucha ma gdzieś polecieć oraz sprawdzenie, czy nie ma pająka w pobliżu mają na celu wywołanie metody frunMucho, to czy nie możemy skorzystać z już stworzonego warunku if(czyLata<6) ? Możemy. Musimy się tylko zastanowić, jak będzie wyglądał warunek ucieczki. Metoda getObjectsInRange zwraca nam listę – nie możemy jej przyrównać do konkretnej liczby, ale możemy sprawdzić, czy jest pusta!

    getObjectsInRange(150, Pajak.class).isEmpty()

Jeśli mucha zobaczy pająka, to jest on do tej listy dodawany. W tym momencie sprawdzenie, czy dana lista jest pusta, zwróci nam fałsz. Musimy zatem zaprzeczyć (tu się kłania logika matematyczna): jeżeli lista obiektów w pobliżu nie jest pusta, to nasza mucha ma odlecieć. Zaprzeczenie w C++ i Javie to postawienie wykrzyknika przed sprawdzanym zdarzeniem:

      !getObjectsInRange(150, Pajak.class).isEmpty()

Musimy jeszcze rozgraniczyć warunki – na wylosowanie latania oraz na sprawdzanie radaru. Do tego służy operator || - „lub”. Cały warunek przyjmuje postać:

     if(czyLatac<6 || (!getObjectsInRange(150, Pajak.class).isEmpty()))
                           frunMucho();

Mucha pomyśli: Jeśli znudziło mi się stanie w miejscu lub moja lista zawierająca pająki w moim pobliżu nie jest pusta, mam odlecieć. Możemy skompilować i przetestować. Na tym etapie powinniśmy mieć taki oto kod muchy:

Pm09.PNG

Dodawanie wielu obiektów naraz


Została jeszcze jedna kwestia do omówienia – dodanie kilku(nastu) obiektów naraz do programu. Do tej pory dodawaliśmy jeden obiekt naraz, poprzez wywołanie konstruktora i metody addObject(). Dodanie wielu obiektów nie jest niczym strasznym, zwłaszcza, że sam program wie, który obiekt podlega zjedzeniu, usunięciu, wylosowaniu latania etc. Opuszczamy naszych aktorów, pora zająć się światem. Na tę chwilę, po uruchomieniu programu, tworzy się tylko jedna mucha – i to w jednym miejscu. Żeby stworzyć 'n' takich samych obiektów, wystarczy użyć pętli. Musimy obudować nasz konstruktor i dodanie obiektu do świata odpowiednią pętlą i gotowe. Oprócz tego musimy przy każdej nowej musze wylosować inne współrzędne, zatem nasze statyczne (100,100) przy dodawaniu muchy do świata muszą być zastąpione poprzez wynik losowania. Granicą losowania są wymiary naszego świata. Dobrze, żeby sama pętla zaczynała się od 0, a kończyła na wymyślonej przez nas liczbie:


Pm10.PNG

Czy jednak zawsze musi być stała liczba much? Nie, nie musi. W pętli, w części sprawdzającej ( i < 6), liczbę 6 można zamienić na wynik losowania:

Pm11.PNG

Za każdym zresetowaniem aplikacji pojawi się inna liczba. W linii losującej ilość muc, 3 na końcu oznacza po prostu, że minimalna ilość obiektów musi wynosić 3. Tyle – kompilujemy, testujemy!

Ostatnio zmodyfikowano 11:30, 14 kwi 2017