Problemy` z klasą vector - indeksy

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:

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:

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)
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.

0

Przyda się kod klasy punkt.

0

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

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.

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":

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.
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);
0
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.

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:

 

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();

}

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().

       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.

1

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

 
#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.

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.
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.

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:

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ę?

for(int i = 0; i < tablica_punktow.size(); i++)
{
    tablica_punktow[i].pozycja = poz;
    tablica_punktow[i].znak = z;
}
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...

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ć:

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
}
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...

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.

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