Snake - problem z losowaniem współrzędnych owoca

Snake - problem z losowaniem współrzędnych owoca
Tenonymous
  • Rejestracja:prawie 8 lat
  • Ostatnio:18 dni
  • Postów:425
0

Hej,
z powodu tematu na forum, który dotyczył umiejętności zrobienia Snake'a, zaklepałem sobie na szybko prostą, konsolową implementacje. Zachciało mi się jednak trochę rozbudować projekt i dodałem parę rzeczy.
Potrzebowałem między innymi metody, która mi losowo ustawi miejsce owocu, więc napisałem coś takiego.

Kopiuj
void setFruit()
     {
         using randoms = std::uniform_int_distribution<std::mt19937::result_type>;
         std::random_device rd;
         std::mt19937 rng(rd());
         randoms dist(1, width - 1);
         randoms dist2(1, height - 1);
         auto x1 = dist(rng);
         auto x2 = dist2(rng);
         fruitX =  static_cast<int>(x1);
         fruitY = static_cast<int>(x2);
     }

Niestety nie rozwiązało to problemu do końca, ponieważ czasem owoc pojawiał się w miejscu, gdzie aktualnie znajdował się wąż(dajmy na to wąż to - - - - -, a owoc &, czasem po losowaniu pojawiało się coś takiego --&----.

Skorzystałem z biblioteki standardowej i pojawiło się coś takiego:

Kopiuj
void setFruit()
     {
         using randoms = std::uniform_int_distribution<std::mt19937::result_type>;
         std::random_device rd;
         std::mt19937 rng(rd());
         randoms dist(1, width - 1);
         randoms dist2(1, height - 1);
         auto x1 = dist(rng);
         auto x2 = dist2(rng);
        
         while (std::find(tailsX.begin(), tailsX.end(), x1) != tailsX.end())
             x1 = dist(rng);

         while (std::find(tailsY.begin(), tailsY.end(), x2) != tailsY.end())
             x2 = dist2(rng);
             
         fruitX =  static_cast<int>(x1);
         fruitY = static_cast<int>(x2);
     }

powyższego bugu już nie ma i losowanie odbywa się prawidłowo - natomiast teraz w pewnym momencie program się wysypuje i gra stoi w miejscu, wciskanie dowolnego klawisza sprawia, że jest on wypisany na konsoli, a wąż pozostaje bez ruchu. Zastanawiam się gdzie może tkwić problem?

Oczywiśćie tailsX i tailsY to std::vector.

edytowany 2x, ostatnio: Tenonymous
BU
  • Rejestracja:około 10 lat
  • Ostatnio:około miesiąc
  • Postów:422
1

Czy Twój program zawiesza się w momencie, gdy wąż zajmuje całą wysokość lub szerokość planszy i zjesz owoc?

Tenonymous
  • Rejestracja:prawie 8 lat
  • Ostatnio:18 dni
  • Postów:425
0

No właśnie nie - nie ma reguły. Raz jest to przy długości 5(startujemy od 1, z każdym zjedzeniem owocu dlugosc zwiększa się o 1), a raz przy 20.

Przykładowy screen:

snake.png

edytowany 1x, ostatnio: flowCRANE
Tenonymous
sprawdziłem teraz i jednak faktycznie jest tak jak pytasz. dzięki!
BU
Więc sprawdzaj kolizję dla X i dla Y (u Ciebie x2) w tej samej pętli. Nie znam C++, ale to od razu rzuca się w oczy.
Tenonymous
  • Rejestracja:prawie 8 lat
  • Ostatnio:18 dni
  • Postów:425
0

Problem rozwiązany. Dziękuję @Burmistrz

zmiana pętli na

Kopiuj
while (std::find(tailsX.begin(), tailsX.end(), x1) != tailsX.end() &&
                std::find(tailsY.begin(), tailsY.end(), x2) != tailsY.end()) {
                
             x1 = dist(rng);
             x2 = dist2(rng);
         }

przyniosła efekt. :)

E: jednak problem dalej jest obecny:

snake.png

edytowany 2x, ostatnio: flowCRANE
Tenonymous
owoc nie pojawia się tylko w liniach ####. Swoją drogą, dalej jest problem. Chociaż pojawia się później :/ Jakieś sugestie jeszcze?
BU
Poczekaj, aż odpisze ktoś znający C++. Ja mogę się tylko domyślać działania różnych funkcji z Twojego programu, za niecałe 4 godziny muszę wstać, więc nie mam teraz czasu na ich poznawanie...
Tenonymous
std::find przyjmuje argument wskaźnik do pierwszego elementu, wskaźnik do 1 elementu poza zakresem i szukaną wartość. Jeżeli znajduje tę wartość, to zwraca wskaźnik do niej, natomiast jeżeli nie, to zwraca wskaźnik do 1 elementu poza zakresem. Czyli generalnie jeżeli nie element znajduje się w vectorze[tablica, bez stalego rozmiaru] to funkcja std::find zwraca nam właśnie tailsX.end()/tailsY.end()w zależności od tego, którą tablicę przeszukuje.
flowCRANE
@Tenonymous: dorzucaj obrazki do załączników postów – nigdy nie przepadną, szybciej będą się ładować i nie trzeba będzie wędrować po serwisach.
Tenonymous
jasna sprawa, będę o tym pamiętał. :)
BU
  • Rejestracja:około 10 lat
  • Ostatnio:około miesiąc
  • Postów:422
1

Powinieneś porównywać obie współrzędne owocu po kolei z każdym fragmentem węża. Wydaje mi się, że teraz po prostu wykluczasz cały prostokąt, w którym znajduje się wąż. Na obrazku ze zrzutem ekranu prostokąt, który zajmuje wąż, zajmuje całą planszę, więc owoc nie może się nigdzie pojawić.

Tenonymous
  • Rejestracja:prawie 8 lat
  • Ostatnio:18 dni
  • Postów:425
0

Napisałem prostą klasę dla punktu:

Kopiuj
struct Point {
    int x,y;
    Point() : x(0), y(0){}
    Point(int _x, int _y) : x(_x), y(_y){}
    bool operator ==(const Point& p) const {
        return x == p.x && y == p.y;
    }
    void set(int _x, int _y) {
        x = _x;
        y = _y;
    }
};

a potem zmieniłem funkcję losującą tak:

Kopiuj
void setFruit()
     {

         using randoms = std::uniform_int_distribution<std::mt19937::result_type>;
         std::random_device rd;
         std::mt19937 rng(rd());
         randoms dist(0, width - 1);
         randoms dist2(0, height - 1);
         auto x1 = dist(rng);
         auto x2 = dist2(rng);
         Point p(static_cast<int>(x1),static_cast<int>(x2));
         std::vector<Point> points(tailsX.size());
         for (vSize i = 0; i != tailsX.size(); ++i) {
             points[i] = Point(tailsX[i], tailsY[i]);
         }
         while ((std::find(points.begin(), points.end(), p) != points.end())) {
             x1 = dist(rng);
             x2 = dist2(rng);
             p.set(static_cast<int>(x1),static_cast<int>(x2));
         }


         fruitX =  static_cast<int>(x1);
         fruitY = static_cast<int>(x2);
     }

i wszystko działa jak należy. :)

Edit: Oczywiście, klasa Point jest totalnie zbędna, bo to samo można uzyskać przy użyciu std::pair<int,int>. No i właśnie przy tej wersji pozostałem.

edytowany 2x, ostatnio: flowCRANE
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 13 godzin
  • Lokalizacja:Tuchów
  • Postów:12175
3

Łatwiej by Ci było, gdybyś miał dodatkowo wektor punktów z wolnymi miejscami na planszy. Wiele ich nie będzie – w końcu konsola to niewielka siatka znaków. Zamiast w pętli na pałę losować punkt i sprawdzać czy wskazuje na puste miejsce czy nie, losuj indeks punktu w wektorze, następnie pobierz te współrzędne i usuń punkt z wektora.

Aby to zadziałało, po każdym ruchu usuwaj z tego wektora nowy punkt głowy i dodawaj do niego stary punkt ogona. Wyjątkiem jest klatka, w której wąż zjada owoc – wtedy nie modyfikuj wektora w ogóle.


Tradycyjne losowanie współrzędnych na planszy w większej części pokrytej przez węża, może równie dobrze trwać w nieskończoność, zawieszając grę. Sposób opisany wyżej zapewni stałą złożoność losowania, nie dopuszczając do takich zwiech. Będziesz mógł pokryć wężem calutką planszę, bez żadnych strat czasowych.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 4x, ostatnio: flowCRANE
Tenonymous
  • Rejestracja:prawie 8 lat
  • Ostatnio:18 dni
  • Postów:425
0

To już na jutro, ciężka głowa po weselu nie sprzyja przesiadywaniu nad nowymi rozwiązaniami.

Tak swoją drogą, napomknę jeszcze, że odchudziłem kod, po przez pozbycie się dwóch vectorów dla X i Y, std::vector<std::pair<int,int>> jest dużo lepszym rozwiązaniem. :)

KR
Mały offtop: osobiście zrobiłbym klasę abstrakcyjną CescCiala z strukturą 'int x; int y;', getter/stetter do tej struktury oraz getter do 'char getSymbolCiala()'. W wektorze znajdowałyby się wskaźniki do CzescCiała. w ten sposób można w ładny i przyjemny sposób zrobić węża z głową, tułowiem i ogonem i przy okazji masz wszystko w jednym wektorze, działasz na głowie, wszystko części ciała aktualizujesz tuż przed ruchem głowy
Patryk27
@krecikondexin: overkill z tymi klasami abstrakcyjnymi IMO - enum sprawdzi się równie dobrze na przykład.
kq
Jak pisałem snejka to wolałem aktualizować dwie pozycje zamiast wszystkich, do czego idealny był deque<point>.
Tenonymous
deque<point> tu będzie zdecydowanie lepsze, dzięki po raz kolejny @kq
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:4 dni
  • Lokalizacja:Szczecin
1

Skoro problem rozwiązany to dorzucę od siebie:

klasa Point jest totalnie zbędna, bo to samo można uzyskać przy użyciu std::pair<int,int>

Dla czytelności zdecydowanie lepsza jest nazwana klasa, a nie generyczna para.

Kopiuj
using randoms = std::uniform_int_distribution<std::mt19937::result_type>;

Klasy uniform_x_distribution liczą na docelowy typ, źródłowy dedukują sobie z podanego im rng/prng.


Tenonymous
co do czytelności using point = std::pair<int, int>; zrobiłem po prostu tak, zamiast tworzyć nową strukturę.
Tenonymous
  • Rejestracja:prawie 8 lat
  • Ostatnio:18 dni
  • Postów:425
0
kq napisał(a):

Dla czytelności zdecydowanie lepsza jest nazwana klasa, a nie generyczna para.

ale więcej kodu, a w założeniu było uszczuplenie tego. :D

kq napisał(a):
Kopiuj
using randoms = std::uniform_int_distribution<std::mt19937::result_type>;

Klasy uniform_x_distribution liczą na docelowy typ, źródłowy dedukują sobie z podanego im rng/prng.

Racja. Nie wiem czemu się tak samoistnie zjadłem tym. Poprawione, dzięki. :)

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.