Snake - podział na klasy

2

Prosty tutorial do testNG masz tutaj http://www.mkyong.com/tutorials/testng-tutorials/.
Ale JUnit też się nada. Powiem szczerze że one nie różnią się specjalnie w podstawach, a TestNG ma kilka dodatkowych opcji. A Intelij IDEA ma wsparcie do obu frameworków testowych, więc spoko.

0

Nic nie wychodzi :/.

Próbowałem "pomieszać" http://www.mkyong.com/unittest/testng-parameter-testing-example/ z tym co napisałeś wcześniej:

 @DataProvider("invalidInitialData")
    @Test(expectedExceptions = SnakeCreationException .class)
    public void testFailedCreation(List<Point> initialBody, Direction initialHeadDirection) {
        new Snake(initialBody, initialHeadDirection);
    }
    
    @DataProvider("validInitialData")
    @Test
    public void testSucceedCreation (List<Point> initialBody, Direction initialHeadDirection) {
        new Snake(initialBody, initialHeadDirection);
    }

I myślę, że powinienem jakoś przykładowe zestawy danych przekazać:

@DataProvider
    public Object[][] ValidDataProvider() {
        return new Object[][]{
                
        };

Nie wiem nawet czy dobrze kombinuje. Przecież nie mogę przekazać nawet nowego Punktu do Snake typu add(0,0) bo mam osobna klasę Point.. więc muszę dane punktów dodawać przez punkt.setX, punkt.setY a potem lista.add(punkt). Więc tym bardziej nie wiem jak miałoby to być jakoś sparametryzowane..

0

Dobrze kombinujesz. Masz literówkę w postaci metody z dużej litery, a nazwa providera musi być identyczna ale jesteś na dobrym tropie.

Dla klasy Point:

class Point {
    private int x;
    private int y;

    public Point(int x, int y){
        this.x = x;
        this.y = y;
    }

   public int getX(){ return x;}
   public int getY(){ return y;}
}

Oraz konstruktora:

public Snake(List<Point> startState, Direction startDirection){
...
}

Będziesz miał przykładowy provider z testowymi danymi.

@DataProvider
public Object[][] validDataProvider() {
    return new Object[][]{
        {Arrays.asList(new Point (1,1), new Point (1,2), new Point(1,3)), Direction.DOWN}, // Pierwszy zestaw danych testowych
        {Arrays.asList(new Point (1,1), new Point (2,1), new Point(3,1)), Direction.LEFT}, // Drugi zestaw danych testowych
    };

Provider danych nieprawidłowych wygląda identycznie, tylko zestaw powinien być nieprawidłowy. Każdy provider niech posiada 5 zestawów testowych. Jeżeli odpalisz testy i oba przejdą dla wszystkich przypadków testowych to znaczy że możesz przejść dalej. Oczywiście dane wejściowe powinny przewidzieć różne błędne przypadki, np. wąż w 2 częściach, wąż skierowany głową w stronę ciała, wąż z 2 punktami w tym samym miejscu etc.

0

@mat90 Odpowiadaj w postach, a nie komentarzach. Te drugie służą jak nazwa sugeruje, do komentowania konkretnego postu.

Nie mogę napisać tego za Ciebie, bo przecież chodzi o to żebyś się sam nauczył. Jeżeli przez parę dni nic nie wymyślisz to rzucę podpowiedź, ale na gotowca nie licz ;).

0

Próbuje zrobić logikę pod wychwytywanie rozdzielonego węża. Oto efekt:

public Snake(List<Point> startState, Direction startDirection) {

        for (int i = (startState.size() - 1); i >= 0; i--) {
            int stepY, stepX;
            stepY = startState.get(i).getY() - startState.get(i - 1).getY();
            stepX = startState.get(i).getX() - startState.get(i - 1).getX();
            if (stepY > 1 || stepX > 1 || stepY <-1 || stepX <-1) {
                throw new SnakeCreationException("waz rozdzielony");
            }
        }
    }

Po pierwsze wydaje mi się ona dosyć "toporna" ale to chyba mniejszy mój problem, drugi polega na tym, że teraz przechodzą testy tylko te gdzie są dane węża "rozdzielonego".
Trzecia sprawa czy idę chociaż dobry tropem póki co..

Ps. już widzę, że na pewno można byłoby użyć liczby bezwzględnej.

1

Wrzuć ten kod na bitbucketa/githuba i udostępnij. Wtedy inni będą mogli go odpalić i zobaczyć co jest nie tak.

Każdy problem walidacji możesz rozbić na 2 podejścia.

  • Sprawdzić czy dane są poprawne
  • Sprawdzić czy dane są niepoprawne.

Każdy przypadek rozpatrujesz osobno i wybierasz jedno z podejść.
Walidacje które musisz tutaj zrobić:

  • Sprawdzenie danych podstawowych. Czy lista nie jest nullem, i czy zawiera przynajmniej jeden element/ nie jest pusta.
  • Sprawdzenie czy na liście nie występują powtarzające się punkty. Czyli czy wąż nie przechodzi w pewnym momencie przez samego siebie.
  • Sprawdzenie czy ciało jest spójne (mniej więcej to co robi Twoja metoda).

Musisz dodać resztę sprawdzań, bo np. bez 2 punktu Twoja walidacja nie zadziała prawidłwo. Jak wrzucisz to na jakieś repo, to rzucę okiem.

0

https://github.com/m4t90/Java

mam nadzieje, że dobrze udostępniłem. Testy dla nieprawidłowych danych przechodzi ale dla prawidłowych już nie. Czyli albo jedne będę przechodzić albo drugie czy jak? Zakładając, że logika jest ok to należy ją opakować jeszcze w jakieś metody? Przyznam, że konstruktor wygląda kosmicznie :P.

0

Żeby można było stwierdzić że konstruktor jest ok, wszystkie testy muszą przechodzić :). Takie jest założenie.

Jeżeli uważasz że możesz coś wydzielić do osobnej metody, to zrób to. Kod składający się z kilku mniejszych i logicznie całych części, jest łatwiejszy w pisaniu i utrzymaniu. Do tego jak to powinno być, dojdziesz w trakcie praktyki pisania. Najważniejsze żeby element z zewnątrz działał prawidłowo.

Ja będę miał czas przysiąść do tego dopiero w poniedziałek. Ale myślę że dasz radę do tego czasu sam dojść co nie działa. Uruchom najpierw wszystkie testy. Jeżeli jakiś nie zadziała, uruchom go ponownie w trybie debug i prześledź dokładnie przypadek. Jeżeli konstruktor rzuca wyjątek, a nie powinien to ustaw breakpointa w miejscu z którego leci i zobacz dlaczego dochodzi do tego momentu. Jeżeli nie rzuca, a powinien, to prześledź kod po kolei dlaczego nie dochodzi do momentu w którym kod znajduje błędy input.

0

Ok. Testy przechodzą wszystkie. Co dalej? Metody, które wcześniej mi działały teraz niekoniecznie..

0

Nie masz nigdzie zadeklarowanej "head", i nigdzie nie wywołujesz metody update. Więc to raczej nie będzie działać.

0

Kurde, nie zaktualizowałem kodu na githubie. Zauważyłem, że nie mam zdefiniowanego head i postanowiłem to przekształcić na zasadzie:

 public void update() {

        Point head = new Point(body.getFirst().getX(),body.getFirst().getY());
        int dx=0, dy=0;

        if (headDirection == Direction.DOWN) {
            dy=-1;
        }
        if (headDirection == Direction.UP) {
            dy=1;
        }
        if (headDirection == Direction.LEFT) {
            dx=-1;
        }
        if (headDirection == Direction.RIGHT) {
            dx=1;
        }
        body.addFirst(new Point(head.getX()+dx,head.getY()+dy));


    }

Pytanie czy to jest lepsze rozwiązanie i jak zrobić by działało.

0

Widzę że wypchnąłeś sformatowany kod. Czad! Ale ciągle masz pustego maina, nic nie robisz z tym wężem. Ani go nie tworzysz, ani nim nie ruszasz.

0

Wrzuciłem coś do maina. Teraz się program nie kompiluje.

2

Nie dodałes genericsów do listy. Zamień

List body = new LinkedList();

na

List<Point> body = new LinkedList<>();

Poza tym wywołujesz metodę "changeDirection" ktora sprawdza czy nowy kierunek nie jest przeciwny staremu. Problem polega na tym że nie masz zdefiniowanego starego (headDirection) więc dostajesz NullPointerException. Musisz gdzieś zdefiniować najpierw headDirection.

PS. I jeszcze przekazujesz liste do konstruktora jako parametr, ale nigdzie jej nie przypisujesz.

0

Pytanie, metody odpowiadające za sterowanie i "akcje" lepiej wrzucić do osobnej klasy czy tworzyć metody w ramach klasy Snake?

0

Tylko zdajesz sobie sprawę z tego że interfejsy ActionListener i KeyListener będą działać tylko w Swingu (tzn jeżeli utworzysz sobie okienko korzystając ze standardowych klas javy?). Nie będzie to działało w konsoli.

0

@mat90
Dobrze napisane metody łatwo jest przetestować. Natomiast te skopane bardzo ciężko. To dobry wyznacznik jakości kodu. Jeżeli napiszesz metodę która robi 1 rzecz, to test też sprawdzi jedną rzecz. Jeżeli napiszesz metodę która robi 10 rzeczy, to masz masę możliwych wariantów do obsłużenia i w praktyce nie da się napisać dobrych testów. A po co nam właściwie testy? Jeżeli kiedyś przyjdzie Ci pisać duży projekt, to nic nigdy nie będzie stałe od początku do końca. W pewnym momencie klient stwierdzi że chce żeby jedna rzecz działa całkowicie inaczej. Jeżeli każda klasa ma testy, to po zmianach po prostu uruchamiasz wszystkie i sprawdzasz czy każdy działa. Jeżeli tak, to znaczy że przy okazji zmian nic innego nie zepsułeś. Bez testów nie miałbyś takiej pewności. To że nie działa wyszłoby po jakimś czasie. Być może byłoby bardzo dużo rzeczy do naprawienia.

Podsumujmy zalety pisania testów:

  • Klasy i metody są podzielone na odpowiednio małe, elementarne części (do dużych naprawdę ciężko pisze się testy)
  • Wiesz że pojedyncze metody działają tak jak chciałeś, jeszcze przed złożeniem wszystkiego do całości
  • Gdy przyjdzie czas na zmiany, masz możliwość sprawdzenia czy nic przy okazji nie zepsułeś.

To w jaki sposób napisałeś konstuktor klasy Snake, to technika TDD: https://pl.wikipedia.org/wiki/Test-driven_development. Nie jest ona obowiązkowa (w przeciwieństwie do testów), ale nauczy Cię jak projektować, a nie tylko pisać. Dlatego sugeruję żebyś pozostał przy tej technice do końca i każdą funkcjonalność zaczął od testów.

I uprzedzając pytanie, czy na pewno testy są pisane zawsze i wszędzie. Oczywiście że nie. Brak testów w projekcie jest pierwszym symptomem że pracujesz w JanuszSofcie, czyli firmie z której trzeba jak najprędzej uciekać i nie oglądać się za siebie.

Istnieje zbiór 5 reguł projektowania klas w programowaniu obiektowym które każdy programista powinien nie tylko znać, ale też stosować. https://pl.wikipedia.org/wiki/SOLID_(programowanie_obiektowe). Pierwsza odpowiada jasno na Twoje pytanie, gdzie powinien znaleźć się kontroler. Jedna klasa == jedna funkcjonalność.

Wróćmy do projektu Twojej gry. Widać że się uczysz a moim zdaniem jak już się uczyć, to prawidłowo od początku do końca. Będzie trochę dłużej i trochę trudniej niż podejściem @TomRiddle, ale po napisaniu będziesz wiedział jak pisać program i nie wstydzić się za kod, a nie tylko klepać zaliczenie na studia. Więc określ czy wolisz ścieżkę trudniejszą, ale bardziej owocną czy łatwą po której będziesz musiał się oduczyć niektórych nawyków.

Jeżeli wybierzesz TDD, to sugeruję tak jak na poczatku, napisać prostą logikę która działa na testach, a później podpiąć ją pod GUI. Jeżeli tak zrobisz, to później będziesz mógł zamiast Swinga podpiać LibGDX i pograć na smartfonie i komputerze.

0

Chce pisać porządnie, stąd ogolnie ten temat. Przeciez w pierwszym poscie pisałem, że przerobilem tutka ze snakiem, ktory działał ale co z tego skoro wyglądał nawet dla mnie bardzo srednio. Wiem, że idzie mi jak idzie ale spróbować można. Chodz moze to juz dla mnie za pozno. Nie jestem też mlodzieniaszkiem, skończyłem studia inzynierskie (nie informatyczne), pracuje. I z doświadczenia wiem że podstawy sa najwazniejsze. Jeżeli je sie dobrze opanuje to ok, jeżeli nie to za jakiś czas i ta trzeba byłoby do tego wracać

1

Ok, w takim razie musisz przetestować jeszcze metody changeDirection/update/getState. Do tego będziesz potrzebował zmienić metodę getState, która zwróci nową instancję klasy SnakeState, zawierającą listę ciała oraz kierunek głowy. Klasa na wzór Point powinna mieć pola + konstuktor z wszystkimi parametrami + gettery.

Spróbuj napisać tutaj słowami co według Ciebie powinny robić te metody (changeDirection/update/getState) i co musiałbyś w nich przetestować.

Na koniec jeszcze powiem Ci o metodzie update(), w teorii, bo mam wrażenie że nie wszystko rozumiesz (mimo że kod jest ok). Jest to metoda bardzo często stosowana przy pisaniu gier, którą posiadają obiekty mogące zmienić swój stan. Wyobraź sobie grę Space Invaders. Masz tam wiele obiektów o zmiennym stanie. Twój statek/wrogie statki/Twoje pociski/Wrogie pociski. Każdy z nich przesuwa się w każdej klatce o jakiś odcinek (może poza Twoim statkiem jeżeli nie będziesz nim ruszał). Wyobraź sobie że dla każdego musiałbyś osobno implementować metodę i każdy z osobno przesuwać. Jeżeli do każdego obiektu dodasz metodę update(), to wystarczy ją odpalić dla każdego obiektu. Jeżeli zrobisz interfejs:

public interface Updateable {
    void update();
}

To wystarczy że każdy obiekt który może się poruszyć na planszy będzie go implementował, wszystkie te obiekty wrzucisz do jednej listy i uruchomisz update dla wszystkich:

List<Updatable> allUpdatableObjectsInGame;

for(Updatable updatableObject: allUpdatableObjectsInGame){
    updatableObject.update();
}
//Lub dla javy 8 (tego nie musisz znać)
allUpdatableObjectsInGame.forEach(Updatable::update);

I wszystkie obiekty na planszy się przesuną.

Oczywiście jest pewien haczyk, a mianowicie o jaki dystans mają się przesunąć :). W przypadku Snake, nie jest to problem. Plansza składa się z kwadratów, a jedno przesunięcie jest zawsze o jeden kwadrat. Update jest robiony zawsze 1 raz na klatkę. Dlatego sterując w Twojej grze prędkością update/odświeżania, sterujesz trudnością gry. Jeżeli zrobisz update 4 razy na sekundę, to wąż przesunie się o 4 kratki w tym czasie. Można założyć że będzie 4x trudniej niż gdyby odświeżanie było raz na sekundę. W większości gier to działa jednak inaczej, bo przecież ilość klatek jest zwykle determinowana przez wydajność sprzętu. Dziwnie by się grało w wiedzmina gdyby 60 kl/s było 2x trudniejsze niż 30 kl/s prawda :)? Dlatego metoda update, przyjmuje parametr który określa jaki czas minął od ostatniego odświeżenia (delta time). W zależności od tego jak długo czasu upłynęło od ostatniej aktualizacji, tak daleko przesuwasz obiekt.
To tyle teorii z gier. Mam nadzieję że trochę Ci to rozjaśniło i przyda się na przyszłość.

Na razie skup się na kompletnym przetestowanym Snake. Jeżeli Ci się uda, przejdziemy dalej.

0

changeDirection() powinna zmienić (bądź zostawić, zależy od aktualnego kierunku) kierunek głowy węża. Metodę tą myślę, że należałoby przetestować np. jeżeli aktualny kierunek jest góra to nie może nagle zmienić na dół (tak samo prawo - lewo). Czyli test np: jeżeli aktualny kierunek lewo, nowy kierunek prawo, zwróć false (waż nadal przesuwa się w lewo). Metoda update() powinna na podstawie stanu (czyli changeDirection) węża przesuwać w odpowiednim kierunku, jeżeli natrafił to go powiększać. A metoda getState powinna pobierać stan węża, czyli jaki jest kierunek głowy, ile punktów ma ciało.

1

Ok, widzę że rozumiesz. To teraz wystarczy że zrobisz odpowiednie testy i sprawdzisz czy przechodzą. Zauważ tylko, że np. do przetestowania update(), czy changeDirection(), musisz użyć getState(), czyli żeby testy tych pierwszych działały prawidłowo najpierw muszą przechodzić testy getState() (niezbyt trudne do napisania ;)).

Point w obecnej postaci jest klasą niezmienną (Immutable). Jeżeli raz ją skonstruujesz, to nie masz możliwości już zmiany jej parametrów. Z jednej strony jest to ograniczające, ale z drugiej posiada kilka zalet. Dla Ciebie ważne jest że nie musisz jej zbytnio testować, ani nigdy nie musisz się martwić że ktoś zmieni ja z zewnątrz, czyli możesz ją udostępniać na zewnątrz bez tworzenia kopii (tak jak musisz np. tworzyć kopię listy). Możesz nawet dla większej czytelności dodać modyfikator final do pól tej klasy. Da Ci to pewność że grzebiąc przy niej nie zepsujesz jej niezmienności.
Tak samo możesz zrobić SnakeState który udostępnisz na zewnątrz. Klasa musi zawierać 2 pola niemodyfikoalne (po oznaczeniu final dalej się kompiluje) listy oraz kierunku głowy. Kierunek głowy jest sam w sobie niemodyfikowalny jako enum, więc musisz jeszcze się upewnić że lista będzie kopią punktów. Punkty, tak jak napisałem wcześniej są niemodyfikowalne, więc nie musisz robić ich kopii. Po takim zestawieniu będziesz miał stan (czasem nazywany projekcją obiektu) który udostępnisz na zewnątrz i przy którym nie będzie dało się zrobić niczego co zepsuje węża.

0

Czyli czysto toeretycznie, chcąc zastosowac TDD do metody getState() (metody update i changeDirection sa juz napisane wiec po sprawie), musialbym stworzyc test ktory tworzylby nowy obiekt SnakeState za pomoca metody getState, ktora teraz nie istnieje i test nie przechodzi, nastepnie tworze ta metode i test przrchodzi, tak? Nastepnie jeszcze test czy obiekt zwracany przez metode getState jest taki sam jak ten Snake z klasy Snake?

0
  1. Najpierw musisz poprawić getState żeby miała prawidłową sygnaturę, tj żeby zamiast listy zwracała getState. Metoda przestanie Ci się kompilować więc możesz zamiast return body;, dać return null;//TODO
  2. Następnie piszesz test. Żeby przetestować stan, to musisz wiedzieć czego oczekiwać. Jako że konstruktor masz już przetestowany, to możesz przy jego pomocy stworzyć nowy obiekt Snake z jakimś body i jakimś head i sprawdzić czy ta metoda zwraca właśnie taki stan.

Jako że napisałeś już provider z poprawnymi danymi, to wystarczy że użyjesz go do tworzenia Snake'a.

   import org.testng.Assert;

    @Test(List<Point> initialBody, Direction initialHeadDirection) {
    public void testGetState() {
        Snake testedSnake = new Snake(initialBody, initialHeadDirection);
        SnakeState testedSnakeState = testedSnake.getState();
        //Porównywanie czy wartości które są w naszym stanie są tymi czego oczekujemy. Najpierw wartość obecna, a później oczekiwana. 
        Assert.assertNotNull(testedSnakeState); // Czy w ogóle dostaliśmy stan? 
        Assert.assertEquals(initialHeadDirection, testedSnakeState.getHeadDirection()); //Czy głowa jest w takim kierunku jak oczekiwaliśmy? 
        Assert.assertEquals(initialBody, testedSnakeState.getBody()); // Czy ciało jest takie samo jakiego oczekiwaliśmy? 
    }

Masz tutaj 3 asercje. Jeżeli któraś się wysypie, to dostaniesz odpowiedni komunikat i będziesz wiedział co jest nie tak.

Co do metod update i changeState, to nie jest jeszcze po sprawie :). Są napisane, czyli nie skorzystasz z TDD, ale testy nadal musisz napisać. Tak jak napisałem wcześniej, TDD nie jest obligatoryjne, ale samo testowanie jednostkowe tak. Tak naprawdę, to metoda update(), jest najważniejszą ze wszystkich i ją koniecznie trzeba przetestować.

http://testng.org/javadocs/org/testng/Assert.html

Testy jednostkowe w javie piszesz zawsze jako public void test*. One nigdy nic nie zwracają. Masz tylko 2 możliwe ścieżki zakończenia testu:

  • Oczekujesz że test przejdzie wszystkie asercje (tak jak wyżej). Czyli testujesz ścieżkę w której oczekujesz że jakieś obiekty będą miały jakiś stan. Test się nie powiedzie jeżeli nie dojdzie do końca (assert nie wyjdzie i zostanie rzucony wyjątek).
  • Oczekujesz że zostanie rzucony jakiś wyjątek. Czyli tak jak w testFailedCreation. Jeżeli test dojdzie do końca bez wyrzucenia tego wyjątku, to znaczy że metoda nie działa tak jak chcesz i test nie przejdzie.
0

Napisałem test metody getState, nie przechodzi mi ostatniej asercji. Wydaje mi się, że przez to , że porównywane są ze sobą List i LinkedList i coś w tej materii nie działa. Nie mogę przypisać też w konstruktorze

body=startState

bo się klasa nie kompiluje. Jak zmienię to na: body=(LinkedList)startState;

 to wyskakuje <code class="java">java.util.Arrays$ArrayList cannot be cast to java.util.LinkedList
1

W konstruktorze dostajesz Jakąś listę, a potrzebujesz LinkedList. Tak czy siak musisz zrobić kopię tej listy, żeby uniemożliwić zmianę ciała z zewnątrz.
Wejdź na https://docs.oracle.com/javase/8/docs/api/java/util/LinkedList.html#LinkedList-java.util.Collection- i przeczytaj jak działa ten konstruktor. Potem spróbuj użyć go żeby nie rzucało Ci wyjątku.

Jak dochodzi Ci do ostatniej asercji jeżeli nie możesz zbudować obiektu węża :>? Po południu postaram się znaleźć chwilę żeby prześledzić i odpalić kod. Napiszę wtedy kilka słów więcej.

0

Teraz czas na testowanie metody changeDirection(). Myślę, że można to zrobić tak: przyjąć dwa zestawy danych z trzema kierunkami np:
zestaw danych dla zmiany kierunku: Direction UP (kierunek początkowy), Direction.LEFT (parametr changeDirection, czyli nowy kierunek głowy), Direction.LEFT (spodziewany kierunek głowy) i potem zrobić test z assercją czy te dwa są sobie równe.
Drugi zestaw danych np. Direction.UP, Direction.DOWN, Direction.UP i test z asercją czy pierwszy obiekt równa sie trzeciemu.
Czy to prawidłowa koncepcja? Jeżeli nie to proszę o delikatną podpowiedź.

EDIT: zrobilem test na jednym duzym zestawie danych

1

Testy mi się niestety nie kompilują. Metody z SnakeState nie zgadzają się z tymi użytymi w testach. Przed pushem postaraj się żeby Twój projekt się kompilował.

Generalnie jest dobrze. Teraz wystarczy przetestować metodę update i wąż będzie prawie gotowy.
Kilka uwag:

  • Obecnie nie powinieneś mieć żadnych odwołań do GUI. W jednym miejscu masz ;). Spróbuj znaleźć i usunąć.
  • Poczytaj o wyjątkach. Większość z nich ma 2 główne parametry które możesz im nadać. String message, oraz Throwable cause. Message jest wiadomością dla tego kto będzie czytał wystąpienie błędu. Cause jest przyczyną, czyli gdybyś np. robił zapis wyników i on nie powiódłby się bo nie można zapisać pliku, to wyrzucasz wyjątek SaveScoreException i w nim podajesz wyjątek w operacji plikowej. do zapisu tych dwóch paramteru użyj konstruktora klasy bazowej, czyli np. super(message) w tym wypadku. Wyjątek nie powinien jednak niczego wyświetlać. Na samej górze aplikacji, zrobisz try/catch który złapie każdy wyjątek lecący z dołu i go zaloguje z całym stacktracem. Dzięki temu, jeżeli coś mimo wszystko pójdzie nie tak, to będziesz wiedział gdzie szukać. Jak dojdzie GUI, to System.out już nie będzie widoczne i warto będzie wszystkie błędy przekierować do pliku.
  • SnakeState powinien być immutable. Teraz niestety nie jest. Napisałem test, który możesz dodać i spróbować poprawić kod tak, żeby test przeszedł.
    @Test(dataProvider = "validDataProvider")
    public void testStateIsImmutable(List<Point> initialBody, Direction initialHeadDirection) {
        Snake snake = new Snake(initialBody, initialHeadDirection);
        SnakeState state = snake.getState();
        List<Point> copyOfBody = new LinkedList<>(state.getList());

        state.getList().add(new Point(0,0)); // zmiana listy ze stanu
        Assert.assertEquals(snake.getState().getList(), copyOfBody); //zmiana listy state nie zmienia nam samego stanu węża.
    }
0

Ok, test do update zrobiony. Musze pomyśleć czy da się go jeszcze trochę zwięźlej napisać. SnakeState jest teraz immutable i test, który napisałeś przechodzi. Wydaje mi się, że odwołanie do GUI usunąłem. Pozostaje sprawa z wyjątkami oraz napisanie kodu związanego z jedzeniem i powiększaniem lub nie węża.

EDIT: Co robić dalej :)?

1

Ostatnio nie miałem czasu, ale o temacie pamiętam :).

Musisz zadać sobie pytanie z jakich części składa się właściwie gra snake. Omińmy jakies menu/buttony które nie są koniecznie wymagane do działania gry. Wyobraź sobie że projektujesz grę która po uruchomieniu odlicza od 3 do 0 o gra się od razu uruchamia. Jakie są jej elementy? Jakie obiekty możesz tam wydzielić? Jeden już masz (wąż), teraz spisz tutaj resztę.

0

Plansza, wąż, jedzenie dla węża :D. Jeżeli dobrze mówię to zostaje Plansza, po której porusza się wąż oraz pojawia się żarcie. Żarcie pojawia się losowo gdzieś na planszy od początku oraz na nowo kiedy głowa węża najedzie na nie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1