C++ Konsolen-Snake



  • Hallo Zusammen 😉
    Ich hab mir mal Gedanken über ein simples Konsolen Snake gemacht.
    Hier mal der Code:
    Da geht es erstmal nur um die Bewegung der Schlange im Spielfeld

    #include <iostream>
    #include <conio.h>
    #include <windows.h>
    
    using namespace std;
    
    //Jedes Feld der Schlane hat X- und Y-Wert
    struct tSnake
    {
        int posX, posY;
    };
    
    int main()
    {
        //MAX X- und Y-Werte
        const int X = 40;
        const int Y = 20;
    
        int x, y;
        char Feld[X][Y];
        tSnake Snake[10];
        int FoodEaten = 0;
    
        //Anfangsposition von Snake[0] bestimmen, also von dem "Kopf"
        Snake[0].posX = (X / 2) - 1;
        Snake[0].posY = (Y / 2) - 1;
    
        bool exit = false;
    
        while (!exit)
            {
    
        char c = getch();
    
            //Position des "Kopfes" ändern
            switch(c)
                {
                    case 'w':
                        Snake[0].posY--;
                        break;
    
                    case 'a':
                        Snake[0].posX--;
                        break;
    
                    case 's':
                        Snake[0].posY++;
                        break;
    
                    case 'd':
                        Snake[0].posX++;
                        break;
    
                    default:
                        exit = true;
                        break;
                }
    
        system("cls");
    
        //Jedem Feld das Zeichen '.' zuweisen
        for(y = 0; y < Y; y++)
            {
                for(x = 0; x < X; x++)
                    {
                        Feld[x][y] =  '.';
                    }
            }
    
        for(int i = 0; i <= FoodEaten; i++)
            {
                int j = i + 1;
    
                switch(c)
                    {
                        case 'w':
                            Snake[j].posY = Snake[i].posY + 1;
                            break;
    
                        case 'a':
                            Snake[j].posX = Snake[i].posX + 1;
                            break;
    
                        case 's':
                            Snake[j].posY = Snake[i].posY - 1;
                            break;
    
                        case 'd':
                            Snake[j].posX = Snake[i].posX - 1;
                            break;
    
                        default:
                            break;
                    }
    
                //Schlange auf dem Spielfeld markieren
                Feld[Snake[j].posX][Snake[j].posY] = 'X';
                Feld[Snake[i].posX][Snake[i].posY] = 'X';
            }
    
        //Spielfeld ausgeben
        for(y = 0; y < Y; y++)
            {
                for(x = 0; x < X; x++)
                    {
                        cout << Feld[x][y];
                    }
                cout << endl;
            }
    
            }
    
        system("PAUSE");
        return 0;
    }
    

    Jedoch kommt beim kompilieren zuerst die ganz normale Konsole, aber wenn ich dann etwas Eingebe dann komnmt "Snake.exe funktioniert nciht mehr". 😕
    Ich glaube der Fehler kommt daher, dass ich bei

    for(int i = 0; i <= FoodEaten; i++)
            {
                int j = i + 1;
    
                switch(c)
                    {
                        ...
                    }
    
                //Schlange auf dem Spielfeld markieren
                Feld[Snake[j].posX][Snake[j].posY] = 'X';
                Feld[Snake[i].posX][Snake[i].posY] = 'X';
            }
    

    während der For-Schleife auf dem Spielfeld Punkte zuweisen will.
    Weis vielleicht eine von euch eine Lösung für mein Problem?
    🤡
    MFG xylophon


  • Mod

    Geh mit dem Debugger durch, dann siehst du wo das Problem auftritt und was die unmittelbare Ursache ist. Und dann ist es meistens leicht, auf die Wurzel des Übels zu schließen. Ich tippe mal spontan auf eine Bereichsüberschreitung.



  • Das liegt an der Initialisierung deines Snake-Arrays:

    tSnake Snake[10];
    

    Dadurch wird Speicherplatz für 10 Snakes auf dem Stack reserviert, aber dieser Speicher ist immernoch mit zufälligen Werten gefüllt(Es sei denn man compiliert das Projekt mit VS im DebugMode). Dadurch haben deine Snakes zufällige Werte und die Zuweisung.

    Feld[Snake[j].posX][Snake[j].posY] = 'X';
    Feld[Snake[i].posX][Snake[i].posY] = 'X';
    

    schlägt "meistens" fehl, weil die posX und posY der Snake[1] zufällige Werte enthalten und du damit aus dem Array rausfliegst und irgendwo in den Speicher schreibst...

    Initialisier dein Array doch einfach mit Nullen 😉

    tSnake Snake[10] = {0};
    


  • Erstmal Danke für die Antworten 🙂

    Habs jetzt umgeändert und jeden Wert mit 0 intialisiert. Der Fehler kommt jetzt nicht mehr.
    Dafür aber habe ich da irgendwo ein Problem mit meiner Logik in dem Programm.
    Es passiert noch immer nicht das, was ich eigentlich haben will oO
    Hab hier mal ein Bild zusammengestellt, was wann passiert, bin aber völlig ratlos wieso es nicht so ist wie ich es dachte..
    http://www.abload.de/img/picturesljj5.png

    Bei diesem Beispile ist

    int FoodEaten = 3;
    

    Denn eigentlich sollte ja jedem X- und Y- Wert der Schlange der Wert des vorherigen Teils der Schlange zugeordnet werden 😕



  • Also offensichtlich ist hier die schnelle Methode, alles in main() zu hauen, nicht der richtige Weg. Ich würde Dir raten, eine Klasse Display und eine Klasse Snake zu bauen. Display ist die Zugriffsstruktur auf deinen Bildschirm und hat z.B. auch draw(const Snake &snake). Somit hast du das modular und kannst auch noch eine KI Snake einbauen, die z.B. Äpfel wegschnappt :-D.



  • Okay werde das ganze jetzt nochmal neu mit Klassen machen. Hoffe ich schaffs 😉
    Danke für die Antworten! :p



  • Hier mal mein Display:

    #ifndef __display__
    #define __display__
    
    #include <iostream>
    #include <exception>
    
    class Display
    {
    public:
    
    	static const char EMPTY = ' ';
    	static const char SNAKE = 'o';
    	static const char SNAKE_HEAD = 'ö';
    	static const char WALL = '#';
    	static const char NL = '\n';
    
    	Display(size_t w, size_t h)
    		: m_width(w), m_height(h)
    	{
    		m_display = new char[(w+3)*(h+2)+1];
    
    		erase();
    	}
    
    	~Display()
    	{
    		if(m_display)
    		{
    			delete [] m_display;
    			m_display = 0;
    		}
    
    	}
    
    	char &element(size_t x, size_t y)
    	{
    		if(x>=0 && x < m_width && y>=0 && y<m_height)
    			return m_display[ (y+1)*(m_width+3)+(x+1) ];
    		else
    			throw std::exception();
    	}
    
    	void erase()
    	{
    		for(size_t x = 0; x < m_width + 3; ++x)
    		{
    			for(size_t y = 0; y < m_height + 2; ++y)
    			{
    				char ch;
    
    				if(x==m_width+2)
    					ch = NL;
    				else
    					if(x==0||x==m_width+1||y==0||y==m_height+1)
    						ch = WALL;
    					else
    						ch = EMPTY;
    
    				m_display[y*(m_width + 3)+x] = ch;
    			}
    		}
    
    		m_display[(m_width+3)*(m_height+2)] = 0;
    	}
    
    	void print() const
    	{
    		std::cout << m_display << std::endl << std::endl;
    	}
    
    protected:
    private:
    	size_t m_width, m_height;
    	char *m_display;
    };
    
    #endif
    


  • Wan glaubst du wird size_t kleiner 0 sein?
    Google: std::vector



  • #ifndef __display__
    nicht ganz standard konform

    char &element(size_t x, size_t y)
        {
            if(x>=0 && x < m_width && y>=0 && y<m_height)
                return m_display[ (y+1)*(m_width+3)+(x+1) ];
            else
                throw std::exception();
        }
    

    imho eher ein fall für assert als eine exception.



  • älberttoälbert schrieb:

    Wan glaubst du wird size_t kleiner 0 sein?
    Google: std::vector

    Du bist son Kandidat, der auf die Frage: "Wie kann ich in c++ 5+3 berechnen und ausgeben" mit "cout<<8;" antworten, weil ihr so krass schlau seid und es schon kürzt.
    Ein guter Programmierer schreibt auch sehr triviale Sachen hin um zu zeigen, dass er mitgedacht hat. Ein Tester würde ansonsten schauen ob x,y überhaupt unsigned ist.
    Zudem denke ich, ist der Compiler inzwischen schon schlau genug, das dürfen wir ihm schon zutrauen.

    unskilled schrieb:

    #ifndef __display__
    nicht ganz standard konform

    char &element(size_t x, size_t y)
        {
            if(x>=0 && x < m_width && y>=0 && y<m_height)
                return m_display[ (y+1)*(m_width+3)+(x+1) ];
            else
                throw std::exception();
        }
    

    imho eher ein fall für assert als eine exception.

    Außerdem hat unskilled Unrecht: assert benutzt der Programmierer für sich. Wenn ich Dir eine Klasse schreibe, dann sollte kein Aufruf in meiner Klasse zu einem Assert-Fehler führen, weil sonst der Fehler bei MIR liegt. Lies Dir den Verwendungszweck von assert noch einmal durch! Wenn meine Klasse falsch genutzt wird oder es Komplikationen gibt, dann nur mit exceptions oder über einen Status (wie es früher in C immer üblich war). Eigentlich arbeiten die ganzen Betriebssystem APIs mit einem Result-Rückgabewert.

    Also:

    tresult element(size_t x, size_t y, char &elem)
        {
            if(!(x>=0 && x < m_width && y>=0 && y<m_height))
                return DISPLAY_BAD_COORD;
    
            elem = m_display[ (y+1)*(m_width+3)+(x+1) ];
            return RESULT_OK;
        }
    
    // ...
    
    int main()
    {
         // ...
         tresult result;
         char field=0;
         if((result=display.element(x, y, field))==RESULT_OK)
         {
             // great it works
         }
         else
         {
             // this is a very bad runtime error, since I (the caller) have to check x,y before calling element()
             // another error could be display allocation failed, not enough memory
         }
    }
    

    Und so benutzt man assert!
    Assert ist sowas wie: "Ich schwöre Folgendes... {meine Unterschrift}"
    Wenn jemand anders deinen assert auf false bringt, dann solltest du dich schon fast schämen 😉

    Übertriebenes Beispiel:

    Programmierer: A, B

    // strict company
    #define scASSERT(expr, programmer)    if(!(expr)) { SEND_EMAIL(programmer, "One of your holy asserts were false. We cannot trust you, so you're kicked, Goodbye!"); abort(); }
    
    // programmer A writes
    class EvenNumberGenerator
    {
       public:
           virtual int tick() = 0;
    }
    
    class EvenNumberGeneratorImpl : public EvenNumberGenerator
    {
       public:
           virtual int tick() { int x = rand(); return x+x%2; }
    }
    
    int evenRandNumber(EvenNumberGenerator *eng)
    {
        // tick is programmed by me
        int i = eng->tick();
    
        scASSERT(i%2==0, "a@badcompany.com");
        return i;
    }
    // ---------------------
    
    // programmer B writes
    class Hehe:public EvenNumberGenerator
    {
       public:
           virtual int tick() { return 1; }
    }
    
    int main()
    {
        // lol goodbye
        evenRandNumber(new Hehe());
    }
    // ---------------------
    

    Deshalb: Traue niemandem! Überprüfe immer alles nochmal, bevor es benutzt wirde. Bei Snake wäre das aber Overkill!



  • PhilippHToner schrieb:

    älberttoälbert schrieb:

    Wan glaubst du wird size_t kleiner 0 sein?
    Google: std::vector

    Du bist son Kandidat, der auf die Frage: "Wie kann ich in c++ 5+3 berechnen und ausgeben" mit "cout<<8;" antworten, weil ihr so krass schlau seid und es schon kürzt.
    Ein guter Programmierer schreibt auch sehr triviale Sachen hin um zu zeigen, dass er mitgedacht hat. Ein Tester würde ansonsten schauen ob x,y überhaupt unsigned ist.
    Zudem denke ich, ist der Compiler inzwischen schon schlau genug, das dürfen wir ihm schon zutrauen.

    Bist du ein Troll oder nur ein total überheblicher Anfänger? In beiden fällen ist es wohl sinnlos weiter zu "diskutieren".


Anmelden zum Antworten