Snake - podział na klasy

0

plansza, waz, jedzenie dla weza

Ok. Zacznij od planszy. Jakie właściwości powinna posiadać według Ciebie? Najlepiej pomyśl nietechnicznie, tak jakbyś w życiu niczego nie programował. Z czego powinna składać się plansza do Snakea.

0

Plansza sklada sie z jakiegos ksztaltu, koloru, z granic, przynajmniej w klasycznej wersji.

0

Ok, to spróbuj napisać klasę Board, która będzie miała tylko pola. Bez żadnych metod.

0

Przychodzi mi na myśl tylko:

public class Board {
    private final int SIZE = 500; // Zakładam kwadratowe pole
}

Pisałem wcześniej też o kolorze ale wtedy raczej wykorzystałbym już jakaś bibliotekę do GUI.

0

Ok, żeby nie mylić, olejmy na razie kolor.

Pytanie podstawowe. Czy chciałbyś umieszczać jakieś przeszkody na planszy, tak jak w kolejnych wersjach snake'a, czy tak jak w nokii 3210 na planszy nic się nie znajduje a wąż może uderzyć tylko w obramowanie lub samego siebie?

0

Tylko klasyka, bez przeszkod :).

0

W takim razie rozmiar planszy wystarczy do jej narysowania i obliczenia kolizji węża. Podpowiem, że na planszy musisz umieścić jeszcze węża i coś co będzie przechowywało jedzenie. Jedzenie potrzebne jest do zwiększania trudności i nabijania punktów, ale bez niego można śmiało wyliczyć kolizję węża oraz fakt czy gra się skończyła. Dlatego na razie niech celem będzie właśnie to.

Potrzebujesz jedynie konstruktora, metody update() i isSnakeDead() która będzie zwracać true lub false w zależności czy wąż trafił w ścianę czy nie. Większa część metody update powinna odpalić się tylko jeżeli wąż żyje, więc jest ona zależna od isSnakeDead.

Napisz to teraz w kodzie i zaproponuj testy do tego. Pamiętaj że na razie piszesz tylko logikę. Oznacza to że musisz martwić się kolorami/rozmiarami kwadratów do rysowania i innymi wizualnymi rzeczami.

0

Witam ponownie,
Wybaczcie, że przez tyle czasu nie odświeżałem, nie pisałem w temacie.
Otóż postanowiłem jeszcze raz powtórzyć pewne sprawy i jeszcze raz napisać według rad tutaj owego snake'a. Oczywiście kod w wielu miejscach jest taki sam jak był ale np. pisząc jeszcze razy, starając się zrozumieć każdą linijkę, zauważyłem, że wcześniej moje testy sprawdzające obiekt snake wyłapywały tylko wyjątek w momencie kiedy dwa elementy obok siebie były takie same, teraz wyłapuje wyjątek nawet jeżeli punkty powtórzą się w dowolnym miejscu.

Gorąca prośba (przede wszystkim do @krzysiek050) aby znowu spróbować mi pomóc, zajrzeć do kodu.

Dodałem klasę Board oraz stworzyłem klasę testującą BoardTest, popisałem testy, potem tworzyłem do nich metody (TDD). Problem polega trochę na tym, że boję się, że może metody są nie funkcjonalne, a to, że przechodzą testy po prostu zasłania mi oczy.
Chciałbym, że ktoś to sprawdził i może pokierował co dalej.

Zaznaczam, że celowo nie dodałem jeszcze elementu jedzenie. Chciałbym najpierw stworzyć Snake, który będzie się poruszał sam po planszy. Jak to osiągnę, myślę, że zresztą pójdzie mi łatwiej.

Aktualny kod: https://github.com/m4t90/Snake/

1

Takich rzeczy jak Snake.iml albo folder out/ nie wstawiaj do repozytorium.

0

Problem polega trochę na tym, że boję się, że może metody są nie funkcjonalne, a to, że przechodzą testy po prostu zasłania mi oczy.

Tzn. jak odpalasz grę, to jest wszystko dobrze czy nie? Ew. mógłbyś testować na większej skali (coś jak testy integracyjne), zrobić w testach całą rozgrywkę węża.

0

Plan byl taki, napisać logike z testami, pozniej podpiąć jakieś graficzne gui.

0

Mam pytanie odnośnie podpinania GUI do logiki. Weźmy pod uwagę najprostszy przykład (chyba) czyli rysowanie Board. Rozumiem, że w nowej klasie np. Paint dziedziczące po Application (jeżeli korzystamy z JavaFX) mam stworzyć jakiś kontener (kontener, gdyż w nim będą punkty snake'a itd) na podstawie pól w klasie Board, w tym wypadku rozmiaru i ewentualnie mógłbym dodać stałą kolor. Następnie tworze w klasie Paint metodę np. drawSnake , która na podstawie SnakeState rysuje każdy punkt ciała np. jako prostokąt itd. Dobrze to rozumiem?

1

Logika powinna zwracać stan. Coś powinno umieć wziąć ten stan i narysować to na odpowiedniej planszy. Najważniejsze, to żeby żadna zmiana w części graficznej nie wymagała zmiany w części logicznej (odwrotnie jest ok).

BTW. Skorzystaj z LibGDX. Jest przeznaczona do gier i nie będziesz musiał wynajdywać koła od nowa i będzie to wydajne.

0

Da się w łatwy sposób podpiąć bibliotekę libgdx do gotowego już projektu? Czy muszę stworzyć nowy projekt i tam przepisać moje klasy?

1

Nic nie przepisujesz. Logika powinna wyglądać tak, że mógłbyś podpiąć pod nią 20 różnych bibliotek graficznych i nie musiałbyś w nich robić żadnych zmian. Podepnij mavena pod logikę (jeżeli jeszcze tego nie zrobiłeś) i w nowo stworzonym projekcie LibGDX po prostu ją podepnij i użyj. Z punktu widzenia całej aplikacji, LibGDX jest frameworkiem, a logika biblioteką której użyjesz.

0

Jak mądrze rozwiązać sprawę jedzenia. Chciałem to zrobić za pomocą po prostu stworzenia obiektu klasy Point i umieszczenia go na mapie, potem jeżeli wąż zje to wywoływana jest metoda losowania nowej pozycji węża itd. Drugi problem to w której klasie powinien znaleźć się zapis dotyczący tego, że jeżeli wąż zje to nie należy usuwać ostatniego jego elementu (wąż się wydłuża).

0

w tej samej klasie która odpowiada za ruch węża.

0

Pytanie tylko co masz w tych klasach*
Bo możesz ładnie podzielić grę na klasy, a w metodach mieć syf. Wtedy to trochę mija się z celem. Możesz też ładnie podzielić grę na klasy, ale tylko w teorii bo np. te klasy będą zawierać jakieś dziwne zależności, nie będą od siebie niezależne itp.

Samo podzielenie na klasy i wrzucenie do klasy A czegoś tam, do klasy B czegoś innego nie musi wcale być gwarantem dobrego kodu obiektowego.

  • EDIT w sumie teraz patrzę, że jesteś autorem wątku i mogę sobie zobaczyć co masz w tych klasach, BTW przez rok robisz węża czy wróciłeś do robienia po przerwie?).
0

Napisałem metodę snakeEat w klasie Board, metoda ta ma zadanie sprawdzać czy wąż zjadł:

 public boolean snakeEat() {
        if (food.getX() == snakestate.getBody().getFirst().getX() && food.getY() == snakestate.getBody().getFirst()
                .getY()) {
            return true;
        } else {
            snakestate.getBody().removeLast();
            return false;
        }
    }

problem polega na tym, że metoda próbuje usunąć ostatni elementy obiektu z klasy SnakeState, która zwraca z założenia tylko stan obiektu klasy Snake, więc to nie zadziała, obiekt klasy snakeState jest niezmienny. Jak to ominąć? Najłatwiej byłoby mi dodać tą metodę w klasie Snake ale wydaje mi się, że to byłoby trochę pomieszane, gdyż musiałbym dodać do tej klasy jedzenie a przecież klasa miała za zadanie tworzyć tylko snake'a.

1

snakeEat w klasie Board

dlaczego klasa Board ma mieć w ogóle metodę snakeEat? Wąż powinien sam być odpowiedzialny za swoje jedzenie, przynajmniej tak by było ładnie, obiektowo (w realnych sytuacjach można nagiąć trochę zasady obiektówki, ale czy tutaj rzeczywiście jest do tego powód? Wątpię)

snakestate.getBody().getFirst().getX() && food.getY() == snakestate.getBody().getFirst()

łamiesz zasadę enkapsulacji (chowania danych). Jeśli obiekt snakestate jest odpowiedzialny za stan węża (jak rozumiem), to ten właśnie obiekt powinien mieć metodę getX(), która zwróci ci X. A ty rozbierasz całego węża, bierzesz jego ciało, bierzesz pierwszy element ciała, i bierzesz X z niego.

Tym sposobem jeśli cokolwiek się zmieni w środku węża, to będziesz musiał zmienić klasę Board (a pewnie i 3 klasy po drodze), co będzie słabe.
Lepszym podejściem byłoby więc

   if (food.getX() == snakestate.getX() && food.getY() == snakestate.getY() {

i w metodzie snakestate.getX, byś dalej robił kombinacje.

tylko to by było lepiej tylko pod kątem enkapsulacji, bo dalej byś miał metodę snakeEat w Board, czyli dalej byś miał pomieszanie.

Obiekty powinny się zajmować sobą (enkapsulacja danych) i powinny mieć określoną odpowiedzialność, a tutaj widzę już na tym fragmencie jakieś rozmycie, co który obiekt robi. Plansza zajmuje się jedzeniem węża rozbierając go na kawałki. Pomyśl jak to brzmi (a przecież tak właśnie napisałeś).

0

Ok, działa ale jest jeden problem, oby mały :). Używam biblioteki libgdx i teraz mam taki problem, że w mojej "grze" wszystko oparte jest na współrzędnych, np.

batch.draw(apple, snake.food.getX(), snake.food.getY()) 

i rysuje w tym miejscu teksturę jabłka. Problem polega na tym, że tekstury mają np. 40x40 pixeli teraz trzeba naprawdę dobrze najechać (idealnie w punkt punkt), żeby trafić. Najłatwiej ustawić tekstury na 1x1pixeli i wtedy to działa jak należy ale trzeba używać lupy. Da się to jako przeskalować bez zmieniania całej logiki?

0

Da się to jako przeskalować bez zmieniania całej logiki?

Da się, pod warunkiem, że oddzieliłeś kod odpowiedzialny za wyświetlanie od reszty aplikacji.

Powinieneś móc po prostu albo pomnożyć x i y przez wartość skali, albo użyć odpowiedniej macierzy, czy przeskalować w inny sposób (zależny po trochę od konkretnej biblioteki, której używasz do renderowania)

Rendering powinien siedzieć gdzieś w jednym miejscu kodu i tam powinny znajdować się wywoływania funkcji libgdx czy innej biblioteki do renderowania - tak, żebyś mógł w razie czego łatwo dodać skalowanie czy coś innego. Gorzej jeśli masz po całej aplikacji rozsiane wywołania funkcji renderującej - wtedy czeka cię spory refactoring.

0

Ale jak przemnoze np. X razy 10 to otrzymam juz inny punkt, prawda?

0

Tak wygląda kod:


package com.mygdx.game;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

import java.util.LinkedList;

public class MyGdxGame implements ApplicationListener {
    private SpriteBatch batch;
    private Texture tex;
    private Texture apple;


    Snake snake;
    Board board;
    LinkedList body;
    Point head;
    Direction initDirection;
    Direction newDirection;


    @Override
    public void create() {
        tex = new Texture("head.png");
        apple = new Texture("apple.png");
        batch = new SpriteBatch();
        head = new Point(250, 250);
        body = new LinkedList();
        body.addFirst(head);
        initDirection = Direction.UP;
        snake = new Snake(body, initDirection);
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // Czyści ekran

        batch.begin();
        if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
            newDirection = Direction.LEFT;
            snake.changeDirection(newDirection);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
            newDirection = Direction.RIGHT;
            snake.changeDirection(newDirection);
        }

        if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
            newDirection = Direction.UP;
            snake.changeDirection(newDirection);
        }
        if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
            newDirection = Direction.DOWN;
            snake.changeDirection(newDirection);
        }

        do {
            board = new Board(snake.getState());
            snake.update();
            board.update();
            for (int i = (board.snakestate.getBody().size() - 1); i >= 0; i--) {
                batch.draw(tex, board.snakestate.getX(i), board.snakestate.getY(i), 10, 10);
            }
            batch.draw(apple, snake.food.getX(), snake.food.getY(), 10, 10);
            batch.end();
        } while (!!board.isSnakeDead());
    }


    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

    @Override
    public void dispose() {
    }


}
1

W jakim celu gra tworzy ciało wężowi? (czasem może mieć sens wstrzykiwanie zmiennych przez konstruktor, ale wydaje mi się, że tutaj jest to niepotrzebne i jest to pomieszanie odpowiedzialności węża i gry. Wąż niech sobie sam tworzy ciało, przyjmując co najwyżej parametr w konstruktorze new Point(250, 250); żeby wiedział, w którym miejscu ma to ciało utworzyć)

0

@mat90: tu masz zestaw cwiczeń, z gotowymi testami. Może przyda się do zrozumienia jak działają testy.
http://exercism.io/languages/java/about

0

Straszliwie mi się to wydaje przekomplikowane, wonsz to nie system bankowy.
https://github.com/trzystaosiemnasty/snake-game/blob/master/src/snake_game/core.cljs Keep it simple

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