Problemy` z klasą vector - indeksy

Problemy` z klasą vector - indeksy
GO
  • Rejestracja:prawie 11 lat
  • Ostatnio:5 minut
  • Postów:148
0

Witam!
Piszę snake'a i znów mam problem. Otóż mam klasę snake, która reprezentuje gracza na ekranie. Niezbędna do jej działania jest klasa punkt - tak naprawdę to jej obiekty są wyświetlane na ekranie. W klasie snake mam zatem dynamiczną tablicę vector, w której mieszczę owe punkty. Jest w niej także funkcja "skrec", która przesuwa snake'a w którymś z kierunków (prawo, lewo, góra, dół - są reprezentowane przez enum) i korzysta z funkcji "przesun" w klasie punkt (mówię to tylko na marginesie). Oto ona:

Kopiuj
void snake::skrec(kierunki gdzie)
{
    if(kierunek_ruchu != gdzie)
    {
        for(int i = 0; i < tablica_punktow.size(); i++)
        {
            tablica_punktow[i].kierunek = gdzie;
            for(int m = 0; m < tablica_punktow.size(); m++) tablica_punktow[m].przesun(tablica_punktow[m].kierunek);

           wyswietl(); // Wyświetla snake'a na ekranie
           Sleep(50);
        }

        kierunek_ruchu = gdzie; // Koñcowe poprawki
    }
    else for(int i = 0; i < tablica_punktow.size(); i++) tablica_punktow[i].przesun(tablica_punktow[i].kierunek);
}
 

Jak widać, jej działanie jest całkiem proste. Sprawdza, czy kierunek, który sobie zażyczyliśmy (argument) jest inny niż aktualny i wykonuje skręt. Jeśli nie, to nie ma przecież potrzeby skręcania. Wtedy po prostu przesuwamy snake w "starym" kierunku.
W sumie wszystko działa, poza dwoma usterkami, nad którymi już długo się zastanawiałem, i nie mogłem znaleźć przyczyny. Pierwszą (i ważniejszą) z nich jest to, że skręt w górę jest wykonywany nie tak jak trzeba. Powód właściwie znam - po instrukcji " tablica_punktow[i].kierunek = gdzie;" indeksy w vectorze się jakby odwracają. Tzn. głowa zamiast indeksu zerowego ma ostatni. Tak mi się wydaje. I w dodatku dzieje się tak tylko dla skrętu w górę. Oprócz tego pozostaje jeszcze drobnostka - kiedy snake'a podąża w kierunku innym niż prawo (domyślny) poszczególne punkty są oddalone od siebie jedną spacją. Nie mam pojęcia, o co chodzi z tymi dwoma sprawami, dlatego zwracam się tu o pomoc.

[EDIT]: klasa punkt:

Kopiuj
class punkt // Klasa, która bêdzie reprezentowaæ punkty na ekranie, na przyk³ad fragmenty wê¿a
{
public:
    int pozycja; // Pozycja punktu na ekranie
    char znak; // Znak reprezentuj¹cy punkt na ekranie
    kierunki kierunek; // Soecjalnie dla wê¿a. Reprezentuje kierunek poruszania siê konkretnego fragmentu

    punkt() : pozycja(0), znak('*'), kierunek(prawo) {} // Konstruktor domniemany

    void wyswietl() // Funkcja "wrzucaj¹ca" punkt na ekran
    {
        string wypis;
        wypis += znak;
        ekran.insert(pozycja, wypis);
    }

    void przesun(kierunki gdzie); // Funkcja pozwala na przemieszczanie punktu po ekranie
}; 
  • jeszcze jej klasy (osobny plik cpp)
Kopiuj
void punkt::przesun(kierunki gdzie)
{
    switch(gdzie)
    {
        case prawo:
            pozycja++;
        break;

        case lewo:
            pozycja--;
        break;

        case gora:
            pozycja -= 80;
        break;

        case dol:
            pozycja += 80;
        break;
    }
} 

===========================================================================================
Na prośbę @twonek udostępniam cały kod:
Plik main.cpp : http://pastebin.com/R0SC2sDp
Plik Menu.cpp : http://pastebin.com/mxLDQLeg
Plik Gra.cpp : http://pastebin.com/kabWR7SH
Plik Punkt.h : http://pastebin.com/QDpKWsUr
Plik Punkt.cpp : http://pastebin.com/MKijDNhA
Plik Snake.h : http://pastebin.com/QHVd14Zh
Plik Snake.cpp : http://pastebin.com/gXSMDUPF
Plik Enum.h : http://pastebin.com/0DPvzvn9

Są jeszcze pliki odpowiadające za "jedzenie" snake'a, ale jeszcze niedokończone, więc nie wstawiam.

edytowany 2x, ostatnio: gogolon
mwl4
Może zacznij oddzielać logikę od wyświetlania?
twonek
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:2500
0

Przyda się kod klasy punkt.

twonek
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:2500
0

Po kodzie przypuszczam, że ekran ma szerokość 80, a pozycje idą od lewego górnego rogu jako 1, 2, 3, 4, 5...

Kopiuj
for(int i = 0; i < tablica_punktow.size(); i++)            // dla kazdego punktu ciala
{
    tablica_punktow[i].kierunek = gdzie;                          // zmien kierunek
    for(int m = 0; m < tablica_punktow.size(); m++)          // przesun CALEGO weza
    { 
        tablica_punktow[m].przesun(tablica_punktow[m].kierunek);
    }
    ....
}

Czyli jeśli wąż ma ciało długości 5, to 1 skręt powoduje że głowa znajdzie się 5 wierszy wyżej, szyja 1 pozycja w prawo/lewo i 4 wiersze wyżej itd.

edytowany 4x, ostatnio: twonek
GO
Ekran ma 80 lini, każda po 25 znaków. Wiem, co się stanie, bo widziałem w grze. Nie uzyskałem jednak odpowiedzi na moje pytania - jak to naprawić i dlaczego "nie działa" tylko dla skrętu w górę.
twonek
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:2500
0

Czemu tak się dzieje tylko dla skrętu w górę? Jak to naprawić?

Powinno być źle dla każdego skrętu, ale być może inne bugi niwelują efekt.

Prostą i nadal niepoprawną modyfikacją jest "zmienić tylko kierunek głowy i przesuwać węża tylko raz":

Kopiuj
if(kierunek_ruchu != gdzie)
{
    tablica_punktow[0].kierunek = gdzie;               // zmien kierunek tylko glowy
    for(int i = 0; i < tablica_punktow.size(); ++i)    // przesun calego weza
        tablica_punktow[i].przesun(tablica_punktow[i].kierunek);
   ....
}

Problem jest taki, że w następnym ruchu głowa będzie leciała w górę, a reszta ciała ciała w prawo. Sensowniejsza jest implementacja ruchu w taki sposób:

  1. Głowa leci w kierunku, w którym ma lecieć
  2. Szyja zajmuje poprzednią pozycję głowy, tułów poprzednią pozycję szyi itd.
Kopiuj
for (unsigned i = tablica_punktow.size()-1; i > 0; --i)
    tablica_punktow[i].pozycja = tablica_punktow[i-1].pozycja;

tablica_punktow[0].kierunek = gdzie;             // to jest tak naprawde zbedne, skoro trzymasz ogolny kierunek
tablica_punktow[0].przesun(gdzie);
GO
twonek
Co to znaczy nie działa? Co dokładnie się dzieje? Wklej kod po poprawce.
GO
  • Rejestracja:prawie 11 lat
  • Ostatnio:5 minut
  • Postów:148
0
Kopiuj
void snake::skrec(kierunki gdzie)
{
    if(kierunek_ruchu != gdzie)
    {
        for(int i = 0; i < tablica_punktow.size(); i++)
        {
            for (unsigned i = tablica_punktow.size()-1; i > 0; --i) tablica_punktow[i].pozycja = tablica_punktow[i-1].pozycja;
            tablica_punktow[0].kierunek = gdzie;
            tablica_punktow[0].przesun(gdzie);

            wyswietl();

            Sleep(50);
        }

        kierunek_ruchu = gdzie; // Koñcowe poprawki
    }
    else for(int i = 0; i < tablica_punktow.size(); i++) tablica_punktow[i].przesun(tablica_punktow[i].kierunek);
} 

Przy tym kodzie części węża dostają "szajby" (nie wiem jak to nazwać) i latają we wszystkich kierunkach.

edytowany 1x, ostatnio: gogolon
Zobacz pozostałe 7 komentarzy
GO
ekran - string, w którym trzymam całą treść, którą co klatkę zmieniam i wypluwam na ekran. Daje ten efekt co chcę. Wcześniejsze punkty są wymazywane w innym miejscu.
twonek
No właśnie masz logikę rozrzuconą po wielu miejscach, przez co potem trudno znaleźć buga. Może buga masz w funkcji wymazującej, nie wiem. Może wrzuć na ideone.com cały kod, bo tak to trochę wróżymy.
GO
@up. Przeanalizowałem sam, ale nadal nie widzę przyczyny problemu. Dlatego wrzuciłem (prawie) wszystko na pastebina, linki w pytaniu. Liczę na pomoc.
twonek
This is a private paste. If you created this paste, please login to view it.
GO
SE
  • Rejestracja:ponad 10 lat
  • Ostatnio:ponad 4 lata
  • Postów:21
1

Jak dla mnie to najprościej było by ci węża zrobić na liście, gdzie klasa snake trzymałaby tylko wskaznik na głowę, a każdy element( czyli twój punkt ) miałby wskaźnik na poprzenik element(punkt), no i wtedy możesz przesuwać węża rekurencyjnie:

Kopiuj
 

void przesun(int x, int y, Punkt* p)
{
       if(p == NULL)
              return;

//kopiujemy stare pozycje

       int kopia_x = p->x;
       int kopia_y = p->y;

//ustawiamy punktowi nowe pozycje dla punktu

     p->x = x;
     p->y = y;


     przesun( kopia_x, kopia_y, p->next();

}

edytowany 1x, ostatnio: setsudanhana
GO
  • Rejestracja:prawie 11 lat
  • Ostatnio:5 minut
  • Postów:148
0

Funkcję skrec wywołuje inna funkcja - ruch. Chodzi w niej tylko o to, żeby uniemożliwiła np. skręcanie w prawo kiedy ruszamy się w lewo. To ona jest wywoływana "z zewnątrz". Oto kod, który wywołuje funkcję ruch().

Kopiuj
       gracz.ruch(zamierzany); // Przesuwamy snake'a w kierunku, w którym ma się przemieszczać
       gracz.wyswietl(); // Wrzucamy snake'a na ekran
 

Te dwie instrukcje są umieszczone w głównej, nieskończonej pętli gry. Oprócz tego sprawdza ona tylko stan klawiatury. Obiekt "zamierzany" to kolejny obiekt typu kierunki, który określa gdzie gracz chciałby się ruszyć. Zmieniam go za pomocą wspomnianych instrukcji sprawdzania buforu klawiatury.

twonek
Za dużo miejsc dla potencjalnych bugów. Pokaż cały kod, najlepiej na jakimś ideone.
GO
Nie wiem, jak mam tam wrzucić kilka plików... Chyba po prostu jeszcze sam nad tym pogrzebię.
twonek
Do celów debugowania polecam nadanie każdej części ciała innej literki. No i oczywiście zachęcam do odpalania debuggera.
mwl4
  • Rejestracja:około 12 lat
  • Ostatnio:25 dni
  • Lokalizacja:Wrocław
  • Postów:399
1

Widziałem, że się męczysz troszkę ze zrobieniem tego prostego snake, a więc mała pomoc:

Kopiuj
 
#include <cstdio>
#include <cstdlib>
#include <conio.h>
#include <ctype.h>
#include <list>

struct TPoint;
struct EDirection;
class CSnakeShow;
class CSnake;

struct TPoint
{
	unsigned x;
	unsigned y;

	TPoint() : x(0), y(0) { }
	TPoint(unsigned _x, unsigned _y) : x(_x), y(_y) { }
};

struct EDirection
{
	enum Type
	{
		Left = 0,
		Right = 1,
		Top = 2, 
		Down = 3
	};
};

class CSnake
{
	friend class CSnakeShow;
private:
	EDirection::Type m_direction;

	std::list<TPoint> m_points; // od ogona do glowy

public:
	CSnake()
	{
		m_direction = EDirection::Right;
		m_points.push_back(TPoint(10, 10));
		m_points.push_back(TPoint(11, 10));
		m_points.push_back(TPoint(12, 10));
	}

	~CSnake()
	{
		m_points.clear();
	}

	void handle(bool isChangedDirection, EDirection::Type newDirection)
	{
		m_points.pop_front();

		TPoint newHead = getHeadPos();

		if(isChangedDirection)
			m_direction = newDirection;

		if(m_direction == EDirection::Right)
			++newHead.x;
		else if(m_direction == EDirection::Left)
			--newHead.x;
		else if(m_direction == EDirection::Top)
			--newHead.y;
		else if(m_direction == EDirection::Down)
			++newHead.y;

		m_points.push_back(newHead);
	}

	TPoint getHeadPos()
	{
		return m_points.back();
	}
};

const unsigned g_width = 40;
const unsigned g_height = 20;

class CSnakeShow
{
private:
	CSnake * m_snake;

	bool m_points[g_height][g_width];
public:
	CSnakeShow(CSnake * snake)
	{
		m_snake = snake;
		clear();
	}

	~CSnakeShow()
	{

	}

	void clear()
	{
		for(int i = 0; i < g_height; ++i)
		{
			for(int j = 0; j < g_width; ++j)
			{
				m_points[i][j] = false;
			}
		}
	}

	void calculate()
	{
		clear();

		for(std::list<TPoint>::iterator i = m_snake->m_points.begin(); i != m_snake->m_points.end(); ++i)
		{
			m_points[(*i).y][(*i).x] = true;
		}
	}

	void draw()
	{
		calculate();

		system("cls"); // czyszczenie konsoli

		for(int i = 0; i < g_height; ++i)
		{
			for(int j = 0; j < g_width; ++j)
			{
				printf(m_points[i][j] ? "O" : " ");
			}
			printf("\n");
		}
	}
};

int main(int, char**)
{
	CSnake snake;
	CSnakeShow snakeDisplay(&snake);
	EDirection::Type currentDirection = EDirection::Right;
	bool isDirectionChanged = false;

	bool isWorking = true;

	int key = 0;
	while(isWorking)
	{
		isDirectionChanged = false;
		if(_kbhit())
		{
			key = toupper(_getch());

			if(key == 'Q')
				isWorking = false;

			else if(key == 'D')
			{
				currentDirection = EDirection::Right;
				isDirectionChanged = true;
			}
			else if(key == 'A')
			{
				currentDirection = EDirection::Left;
				isDirectionChanged = true;
			}
			else if(key == 'W')
			{
				currentDirection = EDirection::Top;
				isDirectionChanged = true;
			}
			else if(key == 'S')
			{
				currentDirection = EDirection::Down;
				isDirectionChanged = true;
			}
		}
		snake.handle(isDirectionChanged, currentDirection);

		snakeDisplay.draw();

		_sleep(50);
	}

	return 0;
}

http://pastebin.com/v7D2RGAF

Resztę gry dopisz sam.


Asm/C/C++
GO
Dzięki, chociaż i tak wolę kiedy ktoś pomaga mi naprawiać mój własny kod.
twonek
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:2500
0

Masakra.

  1. Nadużywasz extern i globalnych danych. Ciężko się połapać co gdzie ma jaką wartość. Np. ekran powinien być parametrem każdej funkcji, która go potrzebuje, a nie globalną zmienną w jakimś innym pliku.
  2. Dlaczego insert w wyświetl? Przecież nadpisujesz dany znak. Przez to ekran rośnie wraz z każdym ruchem. No chyba że gdzieś jest obcięty, ale tego nie widziałem.
  3. Nadal masz błędną funkcję skrec. Przesuwasz całe ciało w tym samym kierunku.
Kopiuj
for(int i = 0; i < tablica_punktow.size(); i++)
{
    tablica_punktow[i].pozycja = poz;
    tablica_punktow[i].znak = z;
}

Cały wąż ma tę samą pozycję?

Jak dla mnie to kod nadaje się do napisania od nowa, tym razem bez zmiennych globalnych, z sensowniejszymi klasami.

GO
1) Postaram się tego unikać. A jeśli chodzi o pli Enum.H, może zostać? Nie mam pomysłu na inne udostępnianie enumów, a nie chcę definiować we wszystkich plikach tych samych typów wyliczeniowych. 2) ekran jest gdzie indziej obcinany 3) Nie wiem czemu tak mówisz... Przecież każda część jest przesuwania w swoim własnym kierunku. Spróbuję napisać część od nowa, ale nie wiem jakie inne klasy miałabym stosować. Nie mogę po prostu napisać lepiej, inaczej tych już istniejących?
twonek
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:2500
1

ekran jest gdzie indziej obcinany
Nie widziałem kodu odpowiedzialnego za to. A zresztą po co wstawiać i potem obcinać? Skoro Twoim celem jest podmiana spacji na gwiazdkę na określonej pozycji.

Przecież każda część jest przesuwania w swoim własnym kierunku
Fakt, moje niedopatrzenie. Ale 1 skręt powoduje n ruchów (gdzie n to długość węża) zamiast jednego.

ale nie wiem jakie inne klasy miałabym stosować
Gra wygląda na dobrego kandydata. Wtedy ekran nadaje się na pole tej klasy.

I trochę o konwencji: spójności brakuje. Albo angielski, albo polski (snake, punkt). Albo wielkie litery, albo małe (WYJSCIE, gora) - choć zazwyczaj wartości enumów się nazywa wielkimi. Tak samo typy (klasy, enumy) powinny być z wielkiej. I nie kierunki tylko Kierunek, bo wartość enuma oznacza 1 kierunek:

Kopiuj
enum Kierunek { PRAWO, LEWO, GORA, DOL };

tablica_punktow jest nazwą, która więcej mąci niż wyjaśnia.

I dlaczego cały wąż ma taką samą pozycję?

Kopiuj
for(int i = 0; i < tablica_punktow.size(); i++)
{
    tablica_punktow[i].pozycja = poz;
    tablica_punktow[i].znak = z;
}
GO
  • Rejestracja:prawie 11 lat
  • Ostatnio:5 minut
  • Postów:148
0

Chciałem to zamieścić w komentarzu, ale osiągnąłem limit znaków. Dlatego w odpowiedzi.

  1. Miałem problem z zastosowaniem funkcji replace, która sprawiała, że punkty szalały po mapie.
  2. Musi tak być. Załóżmy, że przesuwamy głowę w górę. Jeśli nie przesuniemy także reszty "ciała", to powstanie luka pomiędzy poszczególnymi segmentami. A potem będzie już taki chaos, że nawet nie warto opisywać ;P
  3. Dzięki. Więc problem z nadmiarem extern'ów załatwiony

Wiem, od dłuższego czasu nie mogę się zdecydować, czy w swoich programach stosować język angielski czy polski. Na razie programuję jedynie hobbystycznie, i nie jestem pewien, czy powinienem się przyzwyczajać do stosowania angielskiego, choć w niektórych przypadkach wolę nie stosować polskiego - jakby wyglądała nazwa pliku z klasą węża - Waz.h? bez polskich znaków wygląda to raczej na bliżej nieokreślone słowo... Poza tym, dzięki za rady.

Co do tego kodu - to bardzo dziwne, ale kiedy go poprawiłem, wąż skręca błędnie we wszystkich kierunkach... Czyli tak jak mówiłeś, dziwny przypadek, że błędnie skręcał tylko w górę wynikał tylko i wyłącznie z innego błędu. No niestety, funkcja skręcająca rzeczywiście jest błędna. A ja nie wiem jak ją napisać, żeby wreszcie działała poprawnie...

Azarien
"Chciałem to zamieścić w komentarzu, ale osiągnąłem limit znaków." - no i bardzo dobrze że jest limit znaków, bo komentarze służą głównie do krótkich offtopów. dyskusję "na temat" prowadzimy w postach.
GO
Nie mówię, że limitum powinno nie być...
twonek
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:2500
0

Jeżeli nie masz szczególnego powodu, żeby nie używać angielskiego to lepiej teraz stosuj wszędzie angielski.

Tak jak kilka(naście) postów wyżej pisałem, ruch węża wyobrażam sobie tak:

  1. Tylko głowa korzysta z kierunku
  2. Ruch powoduje, że głowa przesuwa się o 1 pole w tym zapamiętanym kierunku
  3. Reszta ciała podąża za głową

Technicznie może to mniej więcej tak wyglądać:

Kopiuj
void turn(Direction newDirection)                  // skrecanie NIE rusza weza, zmienia tylko kierunek
{
    if (acceptableDirection(head.direction, newDirection))     // sprawdzanie czy rzeczywiscie kierunek oznacza skret
    {
        head.direction = newDirection;
    }
}

void move(vector<Point>& snakeBody)
{
    // snakeBody to vector<Point> trzymajacy punkty z pozycjami (i kierunkiem, choc tylko glowa z tego korzysta)
    for (unsigned i = snakeBody.size()-1; i > 0; --i)
    {
        snakeBody[i].position = snakeBody[i-1].position;
    }

    moveHead(snakeBody[0]);
}

void moveHead(Point& head)
{
    head.position = head.computeNewPosition();      // ta metoda bierze obecna pozycje i kierunek i wylicza gdzie powinna byc nowa pozycja
}
GO
  • Rejestracja:prawie 11 lat
  • Ostatnio:5 minut
  • Postów:148
0

Dzięki za wszystkie odpowiedzi. Napiszę całość od nowa z uwzględnieniem Twoich wskazówek. Tylko jeszcze małe pytanie odnośnie ostatniej odpowiedzi w tym wątku:
Co do pętli for w funkcji move, załóżmy, że wąż ma wielkość 3. Skoro tak, to i na początku będzie miało wartość 3-1 = 2. No więc przy pierwszym obiegu pętli:
snakeBody[2].position = snakeBody[1].position;
A coś takiego nie ma sensu, skoro snakeBody[1] jeszcze nie było tknięte przez pętlę. Przecież obiega ona obiekty od wyższych do niższych indeksów.
Ta funkcja skręcająca to same problemy... Przecież 1 skręt w przypadku węża o wielkości 3 składa się z 3 kroków, czyli co każdy krok trzeba całość wyświetlać itd...

twonek
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:2500
0

Zakładamy, że przed wywołaniem move wąż ma jakąś określoną pozycję. Nawet jak zaczynasz grę to też nadajesz wężowi jakąś pozycję początkową przecież. Wtedy wszystko jest ok, bo ogonek (snakeBody[2]) idzie na miejsce tułowia (snakeBody[1]), tułów idzie na miejsce głowy, a głowa się przesunie zgodnie z nowym kierunkiem.

Skręt to 1 ruch. Wystarczy, że głowa zmienia kierunek. Potem (w następnych) ruchach głowa będzie się przesuwać w linii prostej, a reszta ciała podąża za nią. Jeśli skręt oznacza to co mówisz, to przecież wtedy wąż nie potrafi zrobić np. schodów z ciała.

edytowany 1x, ostatnio: twonek
GO
Dobra, piszę całość od nowa, zobaczymy jak wyjdzie.

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.