snake Spielchen



  • Hallo,
    ich habe ein kleines snake Spielchen gemacht und wollte mal fragen,
    ob das von der Programmierung her einigermaßen gelungen ist bzw. ob ihr Fehler entdeckt oder was ihr anders/besser gemacht hättet.
    Z. B. frage ich mich, ob es sinnvoll ist, jeden Durchgang der Hauptschleife abzufragen, ob es schon gameover ist.

    #include <iostream>
    #include <windows.h>
    #include <ctime>
    #include <queue>
    
    short randh; // zufällige Koordinaten fürs Essen
    short randb; //
    const short Hoehe = 16;
    const short Breite =32;
    
    char Welt[Hoehe][Breite] ={
    	{'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'o', 'O', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'X'},
    	{'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'}
    };
    
    void ClearScreen();
    void GenRandFood();
    
    int main()
    {
    	std::cout << "Spiel wird vorbereitet.";
    
    	// Initialisierung
    	short Kopfx = 16;
    	short Kopfy =  7;
    	char Richtung = 'r';
    	short snakelen = 2;
    	bool gameover = false;
    	bool essenvorhanden = false;
    	srand(time(NULL)); // Zufallsgenerator initialisieren
    	std::queue<short> snakebodyx; // Körperteile X-Positionen
    	std::queue<short> snakebodyy; // und Y-Positionen
    	snakebodyx.push(15);
    	snakebodyy.push(7);
    	bool aussetzen = false; // Löschung des letzten Gliedes setzt durchs Essen einmal aus
    
    	// Programmstart
    	ClearScreen();
    
    	// Hauptschleife
    	while(gameover == false)
    	{
    
    		// Eingabe verarbeiten
    		if(GetAsyncKeyState(VK_LEFT) & 0x8000 && Richtung != 'r'){Richtung = 'l';} // 180°-Drehungen verhindern
    		else
    		{
    			if(GetAsyncKeyState(VK_RIGHT) & 0x8000 && Richtung != 'l'){Richtung = 'r';}
    			else
    			{
    				if(GetAsyncKeyState(VK_UP) & 0x8000 && Richtung != 'u'){Richtung = 'o';}
    				else
    				{
    					if(GetAsyncKeyState(VK_DOWN) & 0x8000 && Richtung != 'o'){Richtung = 'u';}
    				}
    			}
    		}
    
    		// Spielereignisse
    		switch(Richtung)
    		{
    			case 'l':
    				if(Welt[Kopfy][Kopfx-1] == '.') // Essen am Zielort des Kopfes?
    				{
    					snakelen++;
    					essenvorhanden = false;
    					aussetzen = true;
    				}
    				if(Welt[Kopfy][Kopfx-1] == 'X' || Welt[Kopfy][Kopfx-1] == 'o')
    				{
    					gameover = true;
    				}
    				else
    				{
    					Welt[Kopfy][Kopfx-1] = 'O'; // Kopf der Schlange
    					Welt[Kopfy][Kopfx] = 'o'; // normales Körperteil
    					snakebodyx.push(Kopfx); // Wo vor der Bewegung der Kopf war, ist jetzt ein Körperteil, der in queue gepackt wird.
    					snakebodyy.push(Kopfy); //
    					Welt[snakebodyy.front()][snakebodyx.front()] = ' '; // löscht letztes Glied
    					Kopfx--;
    				}
    				break;
    
    			case 'r':
    				if(Welt[Kopfy][Kopfx+1] == '.')
    				{
    					snakelen++;
    					essenvorhanden = false;
    					aussetzen = true;
    				}
    				if(Welt[Kopfy][Kopfx+1] == 'X' || Welt[Kopfy][Kopfx+1] == 'o')
    				{
    					gameover = true;
    				}
    				else
    				{
    					Welt[Kopfy][Kopfx+1] = 'O';
    					Welt[Kopfy][Kopfx] = 'o';
    					snakebodyx.push(Kopfx);
    					snakebodyy.push(Kopfy);
    					Welt[snakebodyy.front()][snakebodyx.front()] = ' ';
    					Kopfx++;
    				}
    				break;
    
    			case 'o':
    				if(Welt[Kopfy-1][Kopfx] == '.')
    				{
    					snakelen++;
    					essenvorhanden = false;
    					aussetzen = true;
    				}
    				if(Welt[Kopfy-1][Kopfx] == 'X' || Welt[Kopfy-1][Kopfx] == 'o')
    				{
    					gameover = true;
    				}
    				else
    				{
    					Welt[Kopfy-1][Kopfx] = 'O';
    					Welt[Kopfy][Kopfx] = 'o';
    					snakebodyx.push(Kopfx);
    					snakebodyy.push(Kopfy);
    					Welt[snakebodyy.front()][snakebodyx.front()] = ' ';
    					Kopfy--;
    				}
    				break;
    
    			case 'u':
    				if(Welt[Kopfy+1][Kopfx] == '.')
    				{
    					snakelen++;
    					essenvorhanden = false;
    					aussetzen = true;
    				}
    				if(Welt[Kopfy+1][Kopfx] == 'X' || Welt[Kopfy+1][Kopfx] == 'o')
    				{
    					gameover = true;
    				}
    				else
    				{
    					Welt[Kopfy+1][Kopfx] = 'O';
    					Welt[Kopfy][Kopfx] = 'o';
    					snakebodyx.push(Kopfx);
    					snakebodyy.push(Kopfy);
    					Welt[snakebodyy.front()][snakebodyx.front()] = ' ';
    					Kopfy++;
    				}
    				break;
    
    			default: std::cout << "Error: default in switch-Anweisung!"; Sleep(3000); break;
    		}
    
    		if(aussetzen == false)
    		{
    			snakebodyx.pop(); // Körperteil aus der queue entfernen
    			snakebodyy.pop(); //
    		}
    		else
    		{
    			aussetzen = false; // es wurde bereits ausgesetzt, also nächste Runde nicht mehr aussetzen
    		}
    
    		if(essenvorhanden == false)
    		{
    			GenRandFood();
    			Welt[randh][randb] = '.';
    			essenvorhanden = true;
    		}
    
    		// Bild rendern
    		ClearScreen();
    
    		for(int h = 0; h < Hoehe; h++)
    		{
    			for(int b = 0; b < Breite; b++)
    			{
    				std::cout << Welt[h][b];
    			}
    			std::cout << "\n";
    		}
    
    		Sleep(1000/25); // ~FPS und Spielgeschwindigkeit
    
    	};
    
    	// Game over / Programmende
    	ClearScreen();
    	std::cout << "Game over!";
    	Sleep(3000);
    	ClearScreen();
    	return 0;
    }
    
    void GenRandFood()
    {
    	// int rand_nr = rand() % (max - min) + min;
    	randh = rand() % (Hoehe-2 - 1) + 1; // Array fängt bei 0 an
    	randb = rand() % (Breite-2 - 1) + 1; //
    	if(Welt[randh][randb] == 'O' || Welt[randh][randb] == 'o')
    	{
    		GenRandFood();
    	}
    }
    
    // ClearScreen-Methode von http://www.cplusplus.com/articles/4z18T05o/#Windows
    // einfach übernommen, leert die Konsole
    void ClearScreen()
    {
    	HANDLE hStdOut;
    	CONSOLE_SCREEN_BUFFER_INFO csbi;
    	DWORD count;
    	DWORD cellCount;
    	COORD homeCoords = {0, 0};
    
    	hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    	if(hStdOut == INVALID_HANDLE_VALUE) return;
    
    	/* Get the number of cells in the current buffer */
    	if(!GetConsoleScreenBufferInfo(hStdOut, &csbi)) return;
    	cellCount = csbi.dwSize.X *csbi.dwSize.Y;
    
    	/* Fill the entire buffer with spaces */
    	if(!FillConsoleOutputCharacter(hStdOut, (TCHAR) ' ', cellCount, homeCoords, &count)) return;
    
    	/* Fill the entire buffer with the current colors and attributes */
    	if(!FillConsoleOutputAttribute(hStdOut, csbi.wAttributes, cellCount, homeCoords, &count)) return;
    
    	/* Move the cursor home */
    	SetConsoleCursorPosition(hStdOut, homeCoords);
    }
    


  • Kinemeier schrieb:

    Z. B. frage ich mich, ob es sinnvoll ist, jeden Durchgang der Hauptschleife abzufragen, ob es schon gameover ist.

    Wie denn sonst? Wenn du den Kopf ein Stück weiter bewegst musst du natürlich prüfen ob man in eine Wand reingelaufen ist.

    Ansonsten zum Programmierstil:
    - Globale Variablen sollte man vermeiden. Das Welt Array meinetwegen aber die Random Positionen fürs Essen wären ganz klar ein Kandidat für einen normalen Rückgabewert.
    - Ich hätte mir eine Klasse für 2D Positionen erstellt. Dann braucht man nur eine queue für die Positionen und auch sonst weniger ...x und ...y Variablen.
    - Das switch Zeug für die verschiedenen Richtungen ist fast alles der gleiche Code. Das sollte man versuchen zusammen zu fassen. Wenn du eine Klasse für 2D Positionen hast kann man damit auch Richtungen darstellen. Statt die Richtungen als r, l, o, u zu speichern würde man dann (1,0), (-1,0), (0, 1), (0, -1) oder so speichern. Und dann vereinfachen sich auch deine ganzen Fälle im switch weil du nur die aktuellen Position + Richtung rechnen musst um die neue Position zu erhalten.
    - Schönheitsfehler: Statt

    if(...) {...}
    else
    {
        if(...) {...}
        else
        {
            if(...) {...}
        }
    }
    

    besser:

    if(...) {...}
    else if(...) {...}
    else if(...) {...}
    

    Das ist jedenfalls was mir mal auf die schnelle einfällt.



  • Das mit dem switch wurde ja schon erwähnt... Ich würde den Level aber auf jeden Fall noch aus einer Datei lesen.



  • TGGC schrieb:

    Ich würde den Level aber auf jeden Fall noch aus einer Datei lesen.

    Oder beim Programmstart generieren. Dann könnte man auch die Größe des Spielfelds dynamisch festlegen und verschiedene Varianten mit/ohne Wänden ermöglichen.



  • Okay, danke soweit.
    Allerdings ist mir beim testen aufgefallen, dass manchmal das Essen nicht platziert wird.
    Woran kann das nur liegen?


Anmelden zum Antworten