Snake in c implementieren

Neue Frage »

Auf diesen Beitrag antworten »
deppensido Snake in c implementieren

Hallo,

meine letzte Aufgabe für meinen C-Kurs ist es, ein Snake-Spiel in C zu implementieren.
Dabei darf die Ausgabe über der Konsole erfolgen. Konkret soll sich eine Schlange begrenzter Länge (es ist nicht erforderlich, dass sie länger wird) in eine Richtung bewegen und per Tastendruck, soll sie ihre Richtung ändern.
Meine Idee dabei ist es, den Kopf der Schlange per "o" und den Schwanz per "x" darzustellen.
Mein Problem ist nun: ich weise Kopf und Schwanz der Schlange einem field[][] Array zu und will dann jeweils über eine For-Schleife die Position bestimmen an die, die Schlange ausgegeben werden soll.
Dies funktionert allerdings überhaupt nicht. Sie bewegt sich nur ein bisschen nach links und rechts. Wobei es mir erstmal reicht, wenn sich die Schlange per Tastendruck bewegt. Im folgenden meinen Code, den ich bisher habe:
code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#define BLANK 0
#define HEAD 1
#define TAIL 2
#define KEY_MOVE_UP 119
#define KEY_MOVE_LEFT 97
#define KEY_MOVE_DOWN 115
#define KEY_MOVE_RIGHT 100
#define WIDTH 60
#define HEIGHT 30

struct position {
  int x;
  int y;
} pos;

int field[WIDTH][HEIGHT];
void setItem(int, int, int);
void move(int);
int collision(int, int, int);
void printGame();

int main() {
	int key;
	support_clear();
	int i,j;
	for(i=0;i<WIDTH;i++)
	  for(j=0;j<HEIGHT;j++)
	    field[i][j] = 0;
	// Startposition
	pos.x=WIDTH/2;
	pos.y=HEIGHT/2;
	setItem(pos.x, pos.y, HEAD);
	support_init();
	do {
		key = support_readkey(1000);
		move(key);
		printGame();
	} while(key!='q');
	
	printf("\n\n");
	printf("Spiel vorbei!");
	return 0; 
}

void move(int key) {
	setItem(pos.x, pos.y, TAIL);
	switch(key) {
		case KEY_MOVE_UP: pos.y--; break;
		case KEY_MOVE_LEFT: pos.x--; break;
		case KEY_MOVE_DOWN: pos.y++; break;
		case KEY_MOVE_RIGHT: pos.x++; break;
	}

	if(pos.y >= HEIGHT) pos.y=0;
	if(pos.y < 0) pos.y=HEIGHT-1;
	if(pos.x >= WIDTH) pos.x=0;
	if(pos.x < 0) pos.x=WIDTH-1;

	setItem(pos.x, pos.y, HEAD);
}

void printGame() {
 support_clear();
 int i,j;
	for(i=0;i<WIDTH;i++) {
	    for(j=0;j<HEIGHT;j++) {
		switch(field[i][j]) {
		  case HEAD: printf("o");break;
		  case TAIL: printf("x");break;
		  default:break;
		}
	    }
	} 
}

void setItem(int x, int y, int item) {
	field[x][y] = item;
}

int collision(int x, int y, int item) {
	return 0;
}


Als Hilfestellung haben wir das Modul support.c bekommen. support_init() = Ein-Ausgabe vorbereiten, support_clear() = Löschen der Terminalausgabe, support_readkey(int) = zeitbeschränktes Warten auf Tastenanschlag.

Ich hoffe mir kann jemand Tipps, geben. Vielen Dank im voraus.
 
Auf diesen Beitrag antworten »
eulerscheZahl

Für eine qualifizierte Antwort ohne "wahrscheinlich" und "vermutlich" brauche ich die angesprochene support Datei, damit ich das Programm ausführen kann.

So, wie ich deinen Code lese, gibt es immer ein 'o' und dazu eine wachsende Anzahl 'x', richtig?
Zum einen setzt du nämlich den Schwanz in field[][] nicht zurück, wenn die Schlange sich weiterbewegt hat, zum anderen werden in printGame keine Leerzeichen oder Zeilenumbrüche ausgegeben.
Die Schlange könnte man übrigens wunderbar über eine verkettete Liste darstellen, dann weißt du auch noch, wo das Ende ist, das du in field[][] zurücksetzen musst.
Auf diesen Beitrag antworten »
deppensido

hallo,

hier die support.c
code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
#include "support.h"

#ifdef _WIN32

#include <conio.h>

void support_init() {
	// not needed
}

void support_clear() {
	system("CLS");
}

int support_readkey(int timeout_ms) {
	Sleep(timeout_ms);
	if (!kbhit()) return 0;
	return getch();
}

#else

#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <sys/select.h>

void support_init() {
	struct termios tio;
	tcgetattr(STDIN_FILENO, &tio);
	tio.c_lflag &= (~ICANON & ~ECHO);
	tcsetattr(STDIN_FILENO, TCSANOW, &tio);
}

void support_clear() {
	printf("\x1B[2J\x1B[0;0f");
}

int support_readkey(int timeout_ms) {
	struct timeval tv = { 0L, timeout_ms * 1000L };
	fd_set fds;
	FD_ZERO(&fds);
	FD_SET(0, &fds);
	int r = select(1, &fds, NULL, NULL, &tv);
	if (!r) return 0;

	return getchar();
}

#endif



und hier die support.h

code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
//   ___     __ ___                                       _
//  |_ _|   / // _ \     ___ _   _ _ __  _ __   ___  _ __| |_
//   | |   / /| | | |   / __| | | | '_ \| '_ \ / _ \| '__| __|
//   | |  / / | |_| |   \__ \ |_| | |_) | |_) | (_) | |  | |_
//  |___|/_/   \___/    |___/\__,_| .__/| .__/ \___/|_|   \__|
//                                |_|   |_|

/**
 * Prepares support library for operation.
 *
 * Call this once at (or near) program start.
 */
void support_init();


/**
 * Clears the screen.
 */
void support_clear();


/**
 * Waits up to "timeout_ms" milliseconds for key press.
 *
 * Returns ASCII value or 0 (if no key was pressed).
 */
int support_readkey(int timeout_ms);



zum ausführen haben wir noch eine Makefile bekommen. Kompilierbar dann mit: make all

code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
CFLAGS=-std=c11 -Wall -g
CC=clang

all: snake

.PHONY: all clean

snake: snake.o support.o

snake.o: snake.c support.h

clean:
	rm -f snake
	rm -f snake.o support.o


hier wird der clang Compiler vorausgesetzt. Ich hoffe, dass das kein Problem ist, sonst müsste man die makeFile anpassen. Zum einbinden der support.c reicht es dann #include "support.h" zu schreiben.

"
So, wie ich deinen Code lese, gibt es immer ein 'o' und dazu eine wachsende Anzahl 'x', richtig?"
Absolut richtig.

"Zum einen setzt du nämlich den Schwanz in field[][] nicht zurück, wenn die Schlange sich weiterbewegt hat" Ja, das muss ich noch anpassen, ich hatte mir vorgestellt die Schlange auf eine vorgegebene Länge wachsen zu lassen und diese dann nicht weiter wachsen lassen. Fürs erste würde es mir aber reichen, wenn ich die wachsende Schlange schon mal per Tastensteuerung kontrollieren kann.

In PrintGame hab ich mir überlegt, dass man mit Zeilenumbrüchen die Schlange nach unten wandern kann, aber wie komme ich dann wieder hoch? Gibt es da auch einen Befehl für? Also runter wäre ja mit printf("\n"); Aber umgekehrt weiß ich nicht, ob es das gibt verwirrt .

Mit verketteten Listen habe ich noch so meine Probleme, daher dachte ich es wäre einfacher das über ein Array zu machen. Das Ende müsste dann ja im letzten Feld von field[][] gespeichert sein.

Ich hoffe du kannst mir darauf basierend weiterhelfen. Vielen Dank im voraus.
Auf diesen Beitrag antworten »
eulerscheZahl

Das makefile brauche ich nicht, ein gcc *.c tut es genauso. clang ist nicht installiert, wäre aber dank Paketverwaltung schnell geschehen smile

Zitat:
In PrintGame hab ich mir überlegt, dass man mit Zeilenumbrüchen die Schlange nach unten wandern kann, aber wie komme ich dann wieder hoch? Gibt es da auch einen Befehl für? Also runter wäre ja mit printf("\n"); Aber umgekehrt weiß ich nicht, ob es das gibt verwirrt .

Wird ja von der support Datei erledigt, indem einfach die ganze Ausgabe gelöscht wird.
Unter Linux kriegst du mit \x1b[1A\x1b[2K\x1b[1A die vorherige Zeile weg. Zum kompletten löschen tut es auch ein system("clear");

code:
1:
Mit verketteten Listen habe ich noch so meine Probleme, daher dachte ich es wäre einfacher das über ein Array zu machen. Das Ende müsste dann ja im letzten Feld von field[][] gespeichert sein.

dann schreibe zumindest die "Lebensdauer" in ein Feld, also wie viele Runden es noch aushält. Dann kannst du immer dekrementieren, wenn >0 und die Felder mit Werten > 0 werden als x ausgegeben.

Habe deinen Code leicht überarbeitet, hatte die Stellen schon in meinem letzten Beitrag angedeutet:
code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#define BLANK 0
#define HEAD 1
#define TAIL 2
#define KEY_MOVE_UP 119
#define KEY_MOVE_LEFT 97
#define KEY_MOVE_DOWN 115
#define KEY_MOVE_RIGHT 100
#define WIDTH 10
#define HEIGHT 5

struct position {
  int x;
  int y;
} pos;

int field[WIDTH][HEIGHT];
void setItem(int, int, int);
void move(int);
int collision(int, int, int);
void printGame();

int main() {
	int key;
	support_init(); //das sollte am Anfang stehen, noch vor clear
	support_clear();
	int i,j;
	for(i=0;i<WIDTH;i++)
	  for(j=0;j<HEIGHT;j++)
	    field[i][j] = 0;
	pos.x=WIDTH/2;
	pos.y=HEIGHT/2;
	setItem(pos.x, pos.y, HEAD);
	do {
		key = support_readkey(1000);
		move(key);
		printGame();
	} while(key!='q');
	
	printf("\n\n");
	printf("Spiel vorbei!");
	return 0; 
}

void move(int key) {
	setItem(pos.x, pos.y, TAIL);
	switch(key) {
		case KEY_MOVE_UP: pos.y--; break;
		case KEY_MOVE_LEFT: pos.x--; break;
		case KEY_MOVE_DOWN: pos.y++; break;
		case KEY_MOVE_RIGHT: pos.x++; break;
	}

	if(pos.y >= HEIGHT) pos.y=0;
	if(pos.y < 0) pos.y=HEIGHT-1;
	if(pos.x >= WIDTH) pos.x=0;
	if(pos.x < 0) pos.x=WIDTH-1;
    setItem(pos.x, pos.y, HEAD);
}

void printGame() {
 support_clear();
 int i,j;
    for(j=0;j<HEIGHT;j++) { //Schleifenreihenfolge getauscht
		for(i=0;i<WIDTH;i++) {
			switch(field[i][j]) {
			  case HEAD: printf("o");break;
			  case TAIL: printf("x");break;
			  default: printf(" "); break; //default Leerzeichen
			}
	    }
    	printf("\n"); //Zeilenumbruch
	} 
}

void setItem(int x, int y, int item) {
	field[x][y] = item;
}

int collision(int x, int y, int item) {
	return 0;
}
 
Auf diesen Beitrag antworten »
deppensido

Super, vielen Dank. Daran kann ich erst mal weiterarbeiten. Sobald ich weitergekommen bin oder irgendwo noch Schwierigkeiten bekomme, melde ich mich sofort wieder.
Auf diesen Beitrag antworten »
deppensido

hallo,

ich habe jetzt mal die move(int) Funktion erweitert, sodass sich die Schlange automatisch bewegt und der Schwanz abgetrennt wird, sodass die Schlange eine feste Länge hat. Dies funktioniert allerdings noch nicht fehlerfrei, z.B. wenn man über den Rand geht, wird der Teil vom Rand nicht entfernt. Zudem sollte es so sein, dass die Schlange nicht durch das gedrückt halten einer Taste beschleunigt. Die collision(int,int,int) Funktion habe ich auch noch fertig bekommen und funktioniert auch richtig, wobei hier nicht viel zu tun war.
Könntest du bitte nochmal über die move-Funktion schauen, damit das mit dem abschneiden des Schwanzes etc. fehlerfrei funktioniert? Mir fällt da nicht wirklich was brauchbares ein, bin da schon ziemlich verzweifelt. Vielen Dank im voraus. Hier noch der Code:

code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#define BLANK 0
#define HEAD 1
#define TAIL 2
#define KEY_MOVE_UP 119
#define KEY_MOVE_LEFT 97
#define KEY_MOVE_DOWN 115
#define KEY_MOVE_RIGHT 100
#define WIDTH 40
#define HEIGHT 40
#define LENGTH 8

struct position {
  int x;
  int y;
} pos;

int direction = KEY_MOVE_UP;
int length = 0;
int end = 0;
int field[WIDTH][HEIGHT];
void setItem(int, int, int);
void move(int);
int collision(int, int, int);
void printGame();

int main() {
	int key;
	support_init(); //das sollte am Anfang stehen, noch vor clear
	support_clear();
	int i,j;
	for(i=0;i<WIDTH;i++)
	  for(j=0;j<HEIGHT;j++)
	    field[i][j] = 0;
	pos.x=WIDTH/2;
	pos.y=HEIGHT/2;
	setItem(pos.x, pos.y, HEAD);
	direction = 119;
	do {
		key = support_readkey(200);
		move(key);
		printGame();
	} while(end==0 && key!='q');
	
	printf("\n\n");
	printf("Spiel vorbei!");
	return 0; 
}

void move(int key) {
      
	setItem(pos.x, pos.y, TAIL);
    
	if(length < LENGTH) {
	    length++;
	}
	
	switch(key) {
		case KEY_MOVE_UP: direction = KEY_MOVE_UP; break;
		case KEY_MOVE_LEFT: direction = KEY_MOVE_LEFT; break;
		case KEY_MOVE_DOWN: direction = KEY_MOVE_DOWN; break;
		case KEY_MOVE_RIGHT: direction = KEY_MOVE_RIGHT; break;
		default: break;
	}
	int i;
	switch(direction) {
	  case KEY_MOVE_UP: pos.y--;if(pos.y < 0) pos.y=HEIGHT; for(i=-length;i<length;i++)setItem(pos.x+i,pos.y+length, BLANK); break;
	  case KEY_MOVE_LEFT: pos.x--;if(pos.x < 0) pos.x=WIDTH;for(i=-length;i<length;i++) setItem(pos.x+length,pos.y+i, BLANK);break;
	  case KEY_MOVE_DOWN: pos.y++;if(pos.y >= HEIGHT) pos.y=0;for(i=-length;i<length;i++) setItem(pos.x+i,pos.y-length, BLANK);break;
	  case KEY_MOVE_RIGHT: pos.x++;if(pos.x >= WIDTH) pos.x=0; for(i=-length;i<length;i++) setItem(pos.x-length,pos.y+i, BLANK);break;
	}
	end = collision(pos.x, pos.y, HEAD);
	setItem(pos.x, pos.y, HEAD);
}

void printGame() {
 support_clear();
 int i,j;
    for(j=0;j<HEIGHT;j++) { //Schleifenreihenfolge getauscht
		for(i=0;i<WIDTH;i++) {
			switch(field[i][j]) {
			  case HEAD: printf("o");break;
			  case TAIL: printf("x");break;
			  case BLANK: printf(" "); break; 
			}
	    }
    	printf("\n"); //Zeilenumbruch
	} 
}

void setItem(int x, int y, int item) {
	if(x>=0&&y>=0) {
	  field[x][y] = item;
	}
}

int collision(int x, int y, int item) {
  if(field[x][y] == TAIL) return 1; else return 0;
}
Auf diesen Beitrag antworten »
eulerscheZahl

Das ist mir zu umständlich (den Teil mit der Schleife im switch verstehe ich gerade nicht).

Ich habe mal das mit der Lebensdauer umgesetzt (siehe meinen letzten Beitrag), ist meiner Meinung nach einfacher.
code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#define KEY_MOVE_UP 119
#define KEY_MOVE_LEFT 97
#define KEY_MOVE_DOWN 115
#define KEY_MOVE_RIGHT 100
#define WIDTH 40
#define HEIGHT 40
#define LENGTH 8

struct position {
  int x;
  int y;
} pos;

int direction = KEY_MOVE_UP;
int end = 0;
int field[WIDTH][HEIGHT];
void setItem(int, int, int);
void move(int);
int collision(int, int, int);
void printGame();

int main() {
	int key;
	support_init();
	support_clear();
	int i,j;
	for (i = 0; i < WIDTH; i++)
	  for (j = 0; j < HEIGHT; j++)
	    field[i][j] = 0;
	pos.x = WIDTH / 2;
	pos.y = HEIGHT / 2;
	printGame();
	do {
		key = support_readkey(200);
		move(key);
		printGame();
	} while(!end && key!='q');
	
	printf("\n\nSpiel vorbei!\n\n");
	return 0; 
}

void move(int key) {
	int x, y;
	for (x = 0; x < WIDTH; x++) {
		for (y = 0; y < HEIGHT; y++) {
			if (field[x][y] > 0) {
				field[x][y]--;
			}
		}
	}

	if (key == KEY_MOVE_UP || key == KEY_MOVE_LEFT || key == KEY_MOVE_DOWN || key == KEY_MOVE_RIGHT) {
		direction = key;
	}
	switch(direction) {
		case KEY_MOVE_UP: pos.y = (pos.y+HEIGHT-1) % HEIGHT; break;
		case KEY_MOVE_LEFT: pos.x = (pos.x+WIDTH-1) % WIDTH; break;
		case KEY_MOVE_DOWN: pos.y = (pos.y+1) % HEIGHT; break;
		case KEY_MOVE_RIGHT: pos.x = (pos.x+1) % WIDTH; break;
		default: return;
	}
	if (field[pos.x][pos.y] > 0) end = 1;
	field[pos.x][pos.y] = LENGTH;
}

void printGame() {
 support_clear();
 int i, j;
	for (j = 0; j < HEIGHT; j++) {
		for (i = 0; i < WIDTH; i++) {
			switch(field[i][j]) {
				case LENGTH: printf("o"); break;
				case 0: printf(" "); break; 
				default: printf("x"); break;
			}
		}
		printf("\n");
	} 
}
Auf diesen Beitrag antworten »
deppensido

erst mal vielen Dank für die Hilfe. Das funktioniert jetzt genauso, wie
ich es mir erhofft hatte.

Ich kann aber noch nicht ganz verstehen, warum die Berechnungen für pos.y, pos.x im folgenden
Codefragment funktionieren.

code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
switch(direction) {
		case KEY_MOVE_UP: pos.y = (pos.y+HEIGHT-1) % HEIGHT; break;
		case KEY_MOVE_LEFT: pos.x = (pos.x+WIDTH-1) % WIDTH; break;
		case KEY_MOVE_DOWN: pos.y = (pos.y+1) % HEIGHT; break;
		case KEY_MOVE_RIGHT: pos.x = (pos.x+1) % WIDTH; break;
		default: return;
	}


Hier hast du ja, mit Hilfe von WIDTH bzw. HEIGHT Modulo Berechnungen gemacht, aber mir ist nicht klar, warum das so funktioniert. Den Rest kann ich nachvollziehen, aber hier hätte ich Probleme das erklären zu können.
Auf diesen Beitrag antworten »
eulerscheZahl

Bei right: wir gehen 1 nach rechts mit pos.x+1. Jetzt kann es aber sein, dass wir am rechten Rand herausgelaufen sind (pos.x == WIDTH), da muss die Position dann wieder auf 0 gesetzt werden, indem man WIDTH abzieht. Allgemein kann man einfach den Rest der Division verwenden, da man dann immer im Bereich [0;WIDTH[ ist.

Wenn wir das jetzt auf left übertragen, also pos.x-1 rechnen, kann es sein, dass pos.x == 0 ist. Und modulo liefert bei negativen Zahlen ein negatives Ergebnis, das wir hier nicht gebrauchen können. Also wird noch WIDTH addiert und erst dann modulo gerechnet, somit ist die Eingabe für modulo immer positiv.

Stelle dir hier den obigen Text kopiert und substituiert mit right -> down und left -> up vor.
 
Neue Frage »
Antworten »


Verwandte Themen

Die Beliebtesten »
Die Größten »
Die Neuesten »