Snake in C Programmieren (Schlange wachsen lassen)



  • Hallo,

    ich versuche gerade in C Snake zu programmieren. Ich bin jetzt soweit, dass ich die Schlange steuern kann, Wände und Zufallswände angezeigt werden und Futter angezeigt wird. Wenn die Schlange das Futter iss, bleibt allerdings der Schlangenteil an der Stelle des Futterstücks liegen. Wie kann ich es schaffen, dass das Futterstück mit der Schlange mitzieht?

    So sieht mein bisheriger Code aus:

    #include <stdio.h>
    #include <conio.h>
    #include <stdlib.h>
    #include <windows.h>
    #include <time.h>
    
    #define Hoehe 40
    #define Breite 70
    #define Leer 0
    #define Wand 1
    #define Schlange 2
    #define Futter 3
    #define ZeichenWand '#'
    #define ZeichenSchlange 'o'
    #define ZeichenFutter '@'
    #define AnzahlFutter 5
    
    unsigned int Spielfeld[Breite][Hoehe]; //Komplettes Spielfeld Breite 40, Höhe 70
    int ZufallSchlangeb, ZufallSchlangeh;
    char bewegung;
    int ZufallFutterb, ZufallFutterh;
    int ZufallWandh, ZufallWandb;
    int Schlangenlaenge;
    int Schlangenelemente[50][2];
    
    int game_over(int temp)
    {
    
    	if (temp == Wand || temp == Schlange) // Game Over wenn die Schlange in eine Wand oder sich selber fährt
    	{
    		return 1;
    	}
    	else
    	{
    		return 0;
    	}
    }
    
    int schlange_wachsen(int temp)
    {
    
    	if (temp == !Futter)
    	{
    		Spielfeld[ZufallSchlangeb][ZufallSchlangeh] = Leer;
    
    	}
    	else if (temp == Futter)
    	{
    		return 0;
    	}
    }
    
    void initialisiere_spielfeld()
    {
    	unsigned int h;
    	unsigned int b;
    
    	//Spielfeld komplett mit 0en füllen
    	for (h = 0; h < Hoehe; h++)
    	{
    
    		for (b = 0; b < Breite; b++)
    		{
    			Spielfeld[b][h] = Leer;
    		}
    	} //end Spielfeld mit 0en füllen
    
    	for (b = 0; b < Breite; b++)
    	{
    		//Den oberen Rand als besetzt bezeichnen
    		Spielfeld[b][0] = Wand;
    
    		//Den unteren Rand als besetzt besetzt bezeichnen
    		Spielfeld[b][Hoehe - 1] = Wand;
    	}
    
    	for (h = 0; h < Hoehe; h++)
    	{
    		//Den linken Rand als besetzt bezeichnen
    		Spielfeld[0][h] = Wand;
    
    		//Den rechten Rand als besetzt bezeichnen
    		Spielfeld[Breite - 1][h] = Wand;
    	}//end den Rand als besetzt bezeichnen
    
    }
    
    void zeichne_futter(int Anzahl)
    {
    	int a;
    	for (a = 0; a < Anzahl; a++)
    	{
    		ZufallFutterb = rand() % (Breite - 2) + 1; // erzeugt eine zufällige ganzzahlige x - Koordinate aus dem Bereich 0 bis(Breite - 1)
    		ZufallFutterh = rand() % (Hoehe - 2) + 1; // erzeigt eine zufällige ganzzahlige y - Koordinate  aus dem Bereich 0 bis(Höhe - 1)
    
    		Spielfeld[ZufallFutterb][ZufallFutterh] = Futter; // Gibt die Position des Futters aus
    	}
    
    }
    
    void zeichne_zufallswaende(int Anzahl) // Bestimmt die Menge der Wände im Spielfeld
    {
    	int a;
    
    	for (a = 0; a < Anzahl; a++)
    	{
    		ZufallWandb = rand() % (Breite - 2) + 1; // erzeugt eine zufällige ganzzahlige x - Koordinate aus dem Bereich 0 bis(Breite - 1)
    		ZufallWandh = rand() % (Hoehe - 2) + 1; // erzeigt eine zufällige ganzzahlige y - Koordinate  aus dem Bereich 0 bis(Höhe - 1)
    
    		int ZufallsvariablefuerWaende = rand() % 4;
    		int ZufallfuerWaendeimFeld = (rand() % 3) + 5;
    		int c;
    		switch (ZufallsvariablefuerWaende)
    		{
    		case 0:
    			for (c = 0; c < ZufallfuerWaendeimFeld; c++)
    			{
    				Spielfeld[ZufallWandb++][ZufallWandh] = Wand; // horizontale Wandlinie zeichnen
    				if (ZufallWandb == Breite - 1)
    				{
    					break;
    				}
    			}break;
    		case 1:
    
    			for (c = 0; c < ZufallfuerWaendeimFeld; c++)
    			{
    				Spielfeld[ZufallWandb][ZufallWandh++] = Wand; // vertikale Wandlinie zeichnen
    				if (ZufallWandh == Hoehe - 1)
    				{
    					break;
    				}
    			} break;
    		case 2:
    
    			for (c = 0; c < ZufallfuerWaendeimFeld; c++)
    			{
    				Spielfeld[ZufallWandb++][ZufallWandh++] = Wand; // schräg nach oben gehende Wandlinie zeichnen
    				if (ZufallWandb == Breite - 1 || ZufallWandh == Hoehe - 1)
    				{
    					break;
    				}
    			}break;
    		case 3:
    
    			for (c = 0; c < ZufallfuerWaendeimFeld; c++)
    			{
    				Spielfeld[ZufallWandb++][ZufallWandh--] = Wand; // schräg nach unten gehende Wandlinie zeichnen
    				if (ZufallWandb == Breite - 1 || ZufallWandh == 0)
    				{
    					break;
    				}
    			}break;
    		}
    
    	}
    
    }
    
    void set_cursor_position(int x, int y) // setzt das Spielfeld immer wieder zur Koordinate 0
    {
    	//Inizialisiert die Kooardinaten
    	COORD coord = { x, y }; //Setzt die Position des Spielfelds
    	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord); // set_cursor_position
    }
    
    void erzeuge_schlange()
    {
    	ZufallSchlangeb = rand() % (Breite - 2) + 1; // erzeugt eine zufällige ganzzahlige x - Koordinate aus dem Bereich 0 bis(Breite - 1)
    	ZufallSchlangeh = rand() % (Hoehe - 2) + 1; // erzeigt eine zufällige ganzzahlige y - Koordinate  aus dem Bereich 0 bis(Höhe - 1)
    	if (Spielfeld[ZufallSchlangeb][ZufallSchlangeh] == Wand) // Wenn Schlange auf Wandstück gesetzt wird, soll die Schlang neu erzeugt werden
    	{
    		erzeuge_schlange();
    	}
    	else if (Spielfeld[ZufallSchlangeb][ZufallSchlangeh] == Futter) // Wenn Schlange auf Futter gesetzt wird, soll die Schlang neu erzeugt werden
    	{
    		erzeuge_schlange();
    	}
    	else Spielfeld[ZufallSchlangeb][ZufallSchlangeh] = Schlange; // Schlange wird erzeugt
    }
    
    int bewege_schlange()
    {
    	int temp = 0;
    
    	switch (bewegung)
    	{
    	case 'w': temp = Spielfeld[ZufallSchlangeb][ZufallSchlangeh - 1]; // Temporäre Variable für Bewegung oben
    		Spielfeld[ZufallSchlangeb][--ZufallSchlangeh] = Schlange; break; // Schlange nach oben steuern	
    
    	case 'a': temp = Spielfeld[ZufallSchlangeb - 1][ZufallSchlangeh]; // Temporäre Variable für Bewegung links
    		Spielfeld[--ZufallSchlangeb][ZufallSchlangeh] = Schlange; break; // Schlange nach links steuern
    
    	case 's': temp = Spielfeld[ZufallSchlangeb][ZufallSchlangeh + 1]; // Temporäre Variable für Bewegung unten
    		Spielfeld[ZufallSchlangeb][++ZufallSchlangeh] = Schlange; break; // Schlange nach unten steuern
    
    	case 'd': temp = Spielfeld[ZufallSchlangeb + 1][ZufallSchlangeh]; // Temporäre Variable für Bewegung rechts
    		Spielfeld[++ZufallSchlangeb][ZufallSchlangeh] = Schlange; break; // Schlange nach rechts steuern
    	}
    
    	return temp;
    }
    
    int benutzer_eingabe()
    {
    	int Schlangebewegen = 0;
    	if (_kbhit()) // Benutzereingabe bleibt solange bestehen, bis Benutzer etwas anderes eingibt
    	{
    		bewegung = _getch();
    	}
    	Schlangebewegen = bewege_schlange();
    
    	return Schlangebewegen;
    }
    
    void zeige_spielfeld()
    {
    	int h;
    	int b;
    
    	for (h = 0; h < Hoehe; h++)
    	{
    
    		for (b = 0; b < Breite; b++)
    		{
    			if (Spielfeld[b][h] == Wand) // Zeichnet die Wand an ihre Position aufgrund von initialisiere_spielfeld()
    			{
    				printf("%c", ZeichenWand);
    			}
    			else if (Spielfeld[b][h] == Futter) // Zeichnet das Futter
    			{
    				printf("%c", ZeichenFutter);
    			}
    			else if (Spielfeld[b][h] == Schlange) // Zeichnet die Schlange
    			{
    				printf("%c", ZeichenSchlange);
    			}
    			else printf(" ");
    
    		}
    		printf("\n");
    	}
    }
    
    int main()
    {
    	int x;
    	int y;
    	int temp1 = 0;
    
    	srand((unsigned int)time(NULL)); // bringt den Zufallsgenerator zum Laufen
    
    	initialisiere_spielfeld();
    	zeichne_futter(5); // Anzahl des Futters Eingeben
    	zeichne_zufallswaende(10); // Anzahl der Wände im Spielfeld
    
    	erzeuge_schlange();
    
    	bewegung = 'd'; // Schlange wird am Anfang sofort nach rechts gestartet
    	do
    	{
    		temp1 = benutzer_eingabe();
    		zeige_spielfeld();
    		schlange_wachsen(temp1);
    		set_cursor_position(0, 0);
    
    	} while (game_over(temp1) != 1);
    	x = 28;
    	y = 20;
    	set_cursor_position(x, y);
    	printf("Gamer Over");
    
    	_getch();
    	return 0;
    
    } //end
    

    Ich bin für jede Hilfe dankbar!



  • Was mir beim durchlesen/compilieren auffällt:

    Globale Variablen machen die Sache nicht übersichtlicher. zeichne_futter(): ZufallFutterb und ZufallFutterh werden nur innerhalb der Funktion genutzt, warum also global?

    main.c: In function 'schlange_wachsen': main.c:52:1: warning: control reaches end of non-void function [-Wreturn-type] Warum nicht void als Rückgabetyp?

    erzeuge_schlange(): rekursiv?? do while()!

    main(): temp1 ist ein ganz ungünstiger Name, warum nicht richtung oder ähnlich?

    Eine Funktion um das Programm zu verlassen wäre ganz praktisch... Und die Schlange sollte nicht bei Programmstart direkt loslaufen. Wenn man eine falsche Taste drückt verschwindet die Schlange.

    Wie kann ich es schaffen, dass das Futterstück mit der Schlange mitzieht?

    Äh... Das Futterstück ist doch gegessen und damit verschwunden wenn die Schlange da war, was soll da "mitziehen"? Aktuell bleibt das Futterfeld als Teil der Schlange markiert, ist das das Problem oder soll die Schlange auch wachsen?



  • Ich werde mir mal deine Punkte anschauen. Schon mal vielen Dank für die Hilfe. Ja das Problem ist, dass die Schlange nicht wächst.



  • weiter:
    zeichne_zufallswaende(): Wenn du Pech hast wird Futter mit Wand überschrieben.

    Konstanten schreibt man eigentlich per Konvention in Großbuchstaben.

    Die feste Größe ist etwas ungünstig, wenn das Fenster zu klein ist entsteht Chaos. Man kann per Windows-API die Fenstergröße abfragen oder auch setzen (Code auf Anfrage), ist aber aktuell vielleicht nicht Priorität Nr. 1...

    gut: Alles mögliche konfigurierbar per #define und diese alle an einem Ort ganz oben.

    Ich würde die Sache anders strukturieren, dann wird es imho übersichtlicher. Durch Prototypen können die diversen Funktionen nach der main() kommen: Erst das Wesentliche und dann die Details.

    Hier mal meine Version. Wie gesagt, über vieles lässt sich diskutieren, jeder hat seine Art. Wichtig ist: Erst mit Papier und Bleistift einen genauen Plan erstellen und dann erst zur Tastatur greifen (was ich aber ehrlich gesagt auch nicht immer mache...).

    Für das Schlangenwachstum: Du merkst dir die Positionen der einzelnden Schlangenteile in einem Array. Bei jeder Bewegung schiebst du die Arrayelemente eins weiter nach links und fügst ans Ende die neue Position ein.

    #include <stdio.h>
    #include <conio.h>
    #include <stdlib.h>
    #include <windows.h>
    #include <time.h>
    
    //siehe Anmerkung
    #define HOEHE 40
    #define BREITE 70
    
    //hier bietet sich ein typedef enum an
    #define LEER 0
    #define WAND 1
    #define SCHLANGE 2
    #define FUTTER 3
    
    #define ZEICHEN_WAND '#'
    #define ZEICHEN_SCHLANGE 'o'
    #define ZEICHEN_FUTTER '@'
    
    #define ANZAHL_FUTTER 5
    #define ANZAHL_ZUFALLSWAENDE 10 //fehlte
    #define MAX_SCHLANGENLAENGE 5
    
    //ja, ich schrieb globale Variablen sind doof, aber manchmal wie hier ersparen sie einem zusätzliche Parameter die in (fast?) jeder Funktion vorkommen. Kann man sicherlich drüber diskutieren...
    unsigned int Spielfeld[BREITE][HOEHE];
    unsigned int Schlangenelemente[MAX_SCHLANGENLAENGE];
    
    //Prototypen
    void set_cursor_position(const int x, const int y);
    void initialisiere_spielfeld(void);
    void erzeuge_futter(const int);
    void erzeuge_zufallswaende(const int);
    void erzeuge_schlange(void);
    void zeige_spielfeld(void);
    char benutzereingabe(void);
    void schlange_bewegen_wachsen(const char, int const * );
    
    int main()
    {
    	char taste;
    	int is_game_over=0;
    
    	srand((unsigned int)time(NULL)); //initialisiert den Zufallszahlengenerator mit einem "zufälligen" Wert (genannt seed)
    
    	initialisiere_spielfeld();
    	erzeuge_zufallswaende(ANZAHL_ZUFALLSWAENDE); //gezeichnet (auf dem Monitor) wird hier nicht
    	erzeuge_futter(ANZAHL_FUTTER);
    
    	erzeuge_schlange();
    
    	do
    	{
    		zeige_spielfeld(); //enthält cursorpos setzen
    
    		taste = benutzereingabe();
    		if(taste=='e') //vorzeitig beenden
    			return 0;
    
    		schlange_bewegen_wachsen(taste, &is_game_over);
    
    	} while (!is_game_over);
    
    	set_cursor_position(28, 20);
    	printf("Gamer Over");
    	_getch();
    
    	return 0;
    
    }
    


  • Grmbl. Meine Deklaration von Schlangenelemente ist natürlich falsch. Entweder ein [2] dahinter oder typedef struct nutzen. Jaja, wenn man schnell sein will...



  • Und wieder Mist gebaut:
    void schlange_bewegen_wachsen(const char, int * const);

    und es fehlt int Schlangenlaenge. Ich glaub ich bin erstmal ruhig. 🙄



  • Ohne mir jetzt den ganzen Code durchgelesen zu haben, wieso lässt du nicht das Futter verschwinden nachdem es die Schlange "gegessen" hat und erhöhst die Länge der Schlange um 1?



  • Bitmapper schrieb:

    erhöhst die Länge der Schlange um 1?

    Weil das nicht reichen würde. Wenn Schlangenlaenge nicht global wäre würde der Compiler meckern "unused". Es gibt bisher keine Schlangenwachsfunktion. 😉



  • 420815 schrieb:

    Bitmapper schrieb:

    erhöhst die Länge der Schlange um 1?

    Weil das nicht reichen würde. Wenn Schlangenlaenge nicht global wäre würde der Compiler meckern "unused". Es gibt bisher keine Schlangenwachsfunktion. 😉

    Ich habe mir das so vorgestellt:
    Es gibt eine Variable (von mir aus auch global) die immer die aktuelle Länge der Schlange beinhaltet.
    Diese wird um eins erhöht immer wenn die Schlange ein stück Futter "isst" und die Funktion die die Schlange zeichnet, zeichnet sie eben immer so lang wie in der Variable steht.



  • Natürlich, aber das muss halt auch erstmal implementiert werden, ich habe oben ja was dazu geschrieben (Array).



  • Schon mal vielen Dank für eure Hilfe. 👍 Besonders an dich 420815. Mein Hauptproblem ist, dass ich nicht weiß wo und wie ich das Array für die Wachsfunktion einfügen muss. Ich muss ja irgendwie erkennen können, wie lang die Schlange derzeit sein müsste und in welche Richtung die Schlange gerade fährt. Je nach dem muss ich dann entsprechend viele Teile der Schlange anzeigen lassen (natürlich auch noch an der richtigen Stelle) und entsprechend viele Teile löschen. Wo und Wie ich das aber überall reinschreiben muss ist mir ein unlösbar zu scheindendes Rätsel. 😕 Jedenfalls konnte ich dank euch schon mal ein paar kleiner Probleme (bzw. Dinge die zu Problemen werden könnten) beseitigen. Das Hauptproblem besteht aber leider immer noch. 😞



  • Wow. Ich hab das massiv unterschätzt. Hier mal so wie ich es machen würde. Der Code ist UNGETESTET, gcc spuckt aber zumindestens keine Warnungen aus. Kann fehlerhaft sein!!

    #define MAX_SCHLANGENLAENGE 5
    
    #define INITIAL_X 1
    #define INITIAL_Y 1
    
    #define TASTE_OBEN 'w'
    #define TASTE_UNTEN 's'
    
    typedef struct
    {
    	signed int x;
    	signed int y;
    } coord_t;
    
    unsigned int Spielfeld[BREITE][HOEHE];
    coord_t Schlangenelemente[MAX_SCHLANGENLAENGE]; //von Kopf=[0] bis Schwanz=[MAX_SCHLANGENLAENGE-1]
    unsigned int LaengeSchlange;
    
    void init(void)
    {
    	LaengeSchlange=1;
    	Schlangenelemente[0].x=INITIAL_X;
    	Schlangenelemente[0].y=INITIAL_Y;
    }
    
    void schlange_bewegen_wachsen(const char taste, int * const is_g_over)
    {
    	int i;
    	coord_t bewegung;
    	coord_t kopf_neu;
    	coord_t segment_neu;
    
    	*is_g_over=0;
    
    	switch(taste) //Annahme: Koordinatensystemursprung ist unten links
    	{
    		case TASTE_OBEN:
    					bewegung.x=0;
    					bewegung.y=1;
    					break;
    		case TASTE_UNTEN: 
    					bewegung.x=0;
    					bewegung.y=-1;
    					break;
    		//...
    
    		default: return; //ungültige Taste -> keine Bewegung -> nix zu tun
    	}
    
    	kopf_neu.x=Schlangenelemente[0].x+bewegung.x;
    	kopf_neu.y=Schlangenelemente[0].y+bewegung.y;
    
    	if(Spielfeld[kopf_neu.x][kopf_neu.y]==WAND || Spielfeld[kopf_neu.x][kopf_neu.y]==SCHLANGE) //gegen die Wand gefahren oder gegen sich selber?
    	{
    		*is_g_over=1;
    		return;
    	}
    	else if(Spielfeld[kopf_neu.x][kopf_neu.y]==FUTTER) //wachsen lassen?
    	{
    		if(LaengeSchlange<MAX_SCHLANGENLAENGE) //noch Platz?
    		{
    			//neues Schlangenstück da wo jetzt der Schwanz ist der wegbewegt wird, davon die Koordinaten sichern
    			segment_neu=Schlangenelemente[LaengeSchlange-1];
    
    			//bewegen
    			for(i=0;i<LaengeSchlange;i++)
    			{
    				Schlangenelemente[i].x+=bewegung.x;
    				Schlangenelemente[i].y+=bewegung.y;
    			}
    
    			//verlängern
    			LaengeSchlange++;
    
    			//und neues Segment einbauen
    			Schlangenelemente[LaengeSchlange-1]=segment_neu;
    		}
    	}
    	else //nur bewegen
    	{
    		for(i=0;i<LaengeSchlange;i++)
    		{
    			Schlangenelemente[i].x+=bewegung.x;
    			Schlangenelemente[i].y+=bewegung.y;
    		}
    	}
    }
    


  • Was ich schrieb

    Für das Schlangenwachstum: Du merkst dir die Positionen der einzelnden Schlangenteile in einem Array. Bei jeder Bewegung schiebst du die Arrayelemente eins weiter nach links und fügst ans Ende die neue Position ein.

    ist falsch. Wenn die Schlange nur bewegt wird ändern sich im Array nur die x und y Werte, da wird nichts verschoben. Das passiert nur wenn die Schlange wächst.



  • Und wieder eine Ungereimtheit: Entweder man zeichnet die Schlangenteile direkt ins Spielfeld, dann muss man in schlange_bewegen_wachsen() auch das mit einbauen. Oder man trennt das und malt bei der Ausgabe erst das komplette Feld und zeichnet dann die Schlange ein, dann fehlt in meiner Hauptschleife nach zeige_spielfeld() (oder zeichne_) noch ein zeige/zeichne_schlange().



  • Mensch 420815, registriere dich doch mal 🙂
    dann kannst du auch deine Beiträge nachträglich bearbeiten


Anmelden zum Antworten