# Konsole Flackert bei Snake



## whatever87 (22. Mai 2006)

Hallo alle zusammen,

ich hab es endlich geschafft Snake zu programmieren.Es läuft auch ganz gut Kollisionen usw. funktionieren glaub ich auch alle. Ich habe Snake als Konsolenprogramm geschrieben, nun habe ich aber ein Problem. Die Konsole Flackert so dermaßen, dass man da schon fast nen Epilleptischen anfall bekommt. Hab auch schon bei google und hier im Forum gesucht woran das liegen könnte, aber nix gefunden.
Ich hoffe der Code ist gut lesbar, sind hoffentlich genug Kommentare drin. Habe mir noch nicht überlegt wie ich einige Sachen eleganter und schneller machen könnte, will erstmal das das geflackere aufhört. Für anregungen und Kritik jeglicher Art bin ich aber offen.


```
using namespace std;

/* Includes */
#include "stdafx.h"

/* Tasten definieren */
const int HOCH = 119;
const int LINKS = 97;
const int RUNTER = 115;
const int RECHTS = 100;
const int PAUSE = 112;
const int ESC = 27;

/* Globale Variablen */
const int MAX_SNAKE_LENGTH = 441; //23 * 23
const char SnakeBody = 219;
int SnakeLength = 5;
int Snake[MAX_SNAKE_LENGTH][2];
char Field[23][23];
int Food[1][2] = {1,1};
int RANGE_MIN = 1;	//Startwert des Zufallsgenerators
int RANGE_MAX = 22;//Endwert des Zufallsgenerators
int CollisionCount = 0;
char KeyPress = RECHTS;
char KeyPressOld = RECHTS;
bool FoodEaten = true;
unsigned long int Score = 0;
int Speed = 100;
int GameState = 0;
int PointsCount = 10;


/* Funktionsprototypen */
void FieldInit(void);
void PrintField(void);
void SnakeInit(void);
void SetFood(void);
void WriteSnakeToField(void);
void WriteFoodToField(void);
int MoveSnake(void);
char abbruch = 'j';
int temp;

int main()
{
	/* Initialisierung des zufallsgenerators mit der aktuellen zeit */
	srand( (unsigned) time(NULL));
	FieldInit();
		SnakeInit();
		SetFood();
	
	do
	{
		
		FieldInit();
		PrintField();
		GameState = MoveSnake();
		Sleep(20);
		if(GameState == 2)
		{
			cout << "Verloren! Nochmal spieln? (j/n): ";
			cin >> abbruch;
			SnakeInit();
			SetFood();
		}
	}while(abbruch == 'j');
	
}

/* Spielfeld mit Leerzeichen füllen */
void FieldInit(void)
{
	for(int y = 0; y < 23; y++)
	{
		for(int x = 0; x < 23; x++)
		{	
			/* Inneres Spielfeld */
			Field[x][y] = ' ';
			/* Spielfeldwand */
			if(x == 0)
			{
				if(y == 0)
				{
					/* Linkes oberes eckstück */
					Field[x][y] = 201;
				}
				else if(y == 22)
				{
					/* Linkes unteres eckstück */
					Field[x][y] = 200;
				}
				else if(y < 23)
				{
					/* vertikaler strich */
					Field[x][y] = 186;
				}
			}
			else if(x == 22)
			{
				if(y == 0)
				{
					/* rechtes oberes eckstück */
					Field[x][y] = 187;
				}
				else if(y == 22)
				{
					/* rechtes unteres eckstück */
					Field[x][y] = 188;
				}
				else if(y < 23)
				{
					/* vertikaler strich */
					Field[x][y] = 186;
				}
			}
			else if(y == 0)
			{
				if(x < 23)
				{
					/* horizontaler strich*/
					Field[x][y] = 205;
				}
			}
			else if(y == 22)
			{
					if(x < 23)
					{
						/* horizontaler strich */
						Field[x][y] = 205;
					}
			}
		}
	}
}

/* Spielfeld ausgeben */
void PrintField(void)
{
	WriteSnakeToField();
	WriteFoodToField();
	/* Sendet den DOS-Befehl "cls" an die Konsole
	   ist das selbe wie clrscr() in borland */	
	system("cls");
	for(int y = 0; y < 23; y++)
	{
		for(int x = 0; x < 24; x++)
		{
			
			if(x == 23)
			{
				if(y == 0)
				{
					cout << Score;
				}
			}
			else
			{
				cout << Field[x][y];
			}
		}
		cout << "\n";
	}
}

/* Startposition der Schlange festlegen, restliches array mit Leerzeichen füllen */
void SnakeInit(void)
{
	for(int iPosY = 0; iPosY < 2; iPosY++)
	{
		for(int iPosX = 0; iPosX < MAX_SNAKE_LENGTH; iPosX++)
		{
			Snake[iPosX][iPosY] = -1;
			/* -1 damit der Bug der bei Pos0,0
			   für kurze Zeit ein Körperteil erscheinen
			   lässt behoben ist 
			*/	
		}
	}
	/* Startpos der schlange, schlange hat 5LE am anfang */
	Snake[0][0] = 5;
	Snake[0][1] = 21;
	Snake[1][0] = 4;
	Snake[1][1] = 21;
	Snake[2][0] = 3;
	Snake[2][1] = 21;
	Snake[3][0] = 2;
	Snake[3][1] = 21;
	Snake[4][0] = 1;
	Snake[4][1] = 21;

}

/* Futterposition nach zufallsprinzip ermitteln */
void SetFood(void)
{
	Field[Food[0][0]][Food[0][1]] = ' '; //alte Futterposition löschen
	
	/* Mit dieser rechnung gibt der zufallsgen. nur zwischen min und max aus */
	Food[0][0] = ( ((double)rand() / (double)RAND_MAX) * RANGE_MAX + RANGE_MIN );
	Food[0][1] = ( ((double)rand() / (double)RAND_MAX) * RANGE_MAX + RANGE_MIN );
	
	/* Falls ein Poswert 0/23 sein sollte 
	 kommt trotz abfrage manchmal vor... 
	 dies ist die 1. von 2 abfragen OMG */
	if(Food[0][0] == 0)//für x
	{
		Food[0][0] = 1;
	}
	else if(Food[0][0] == 23)
	{
		Food[0][0] = 22;
	}
	else if(Food[0][1] == 0)//für y
	{
		Food[0][1] = 1;
	}
	else if(Food[0][1] == 23)
	{
		Food[0][1] = 22;
	}
}

/* Schlangenposition ins Field-Array schreiben */
void WriteSnakeToField(void)
{
	int TmpSnakePosX;
	int TmpSnakePosY;
	/* Schlangenpositionen ins Feld schreiben */
	for(int PosX = 0; PosX < SnakeLength; PosX++)
	{
		for(int PosY = 0; PosY < 2; PosY++)
		{
			if(PosY == 0)
			{
				TmpSnakePosX = Snake[PosX][PosY];
			}
			else if(PosY == 1)
			{
				TmpSnakePosY = Snake[PosX][PosY];
			}
		}
		/* an der pos. der schlange ein Körperteil ins Feld schreiben */
		Field[TmpSnakePosX][TmpSnakePosY] = SnakeBody;
	}
}

/* die Pos. des Futters ins Spielfeld schreiben */
void WriteFoodToField(void)
{
	if(FoodEaten == true)
	{
		SetFood();

		/* Falls das Feld schon belegt ist */
		while(Field[Food[0][0]][Food[0][1]] != ' ')
		{
			SetFood();
		}
	}
	/* An der Position des Futters ein 'O' schreiben */
	Field[Food[0][0]][Food[0][1]] = 'O';
	FoodEaten = false;
}

/* tasten abfragen und richtung bestimmen */
int MoveSnake(void)
{
	if(_kbhit() != 0)
	{
		KeyPress = _getch();
	}
	
	/* umdrehen ist nicht erlaubt! */
	if( (KeyPressOld == HOCH) && (KeyPress == RUNTER) )
	{
		KeyPress = HOCH;
	}
	else if( (KeyPressOld == LINKS) && (KeyPress == RECHTS) )
	{
		KeyPress = LINKS;
	}
	else if( (KeyPressOld == RUNTER) && (KeyPress == HOCH) )
	{
		KeyPress = RUNTER;
	}
	else if( (KeyPressOld == RECHTS) && (KeyPress == LINKS) )
	{
		KeyPress = RECHTS;
	}
	
	/* Bewegen der Schlange und Kollisionen Abfragen */
	switch(KeyPress)
	{
		case HOCH:
			{
				{
				int TempX;
				int TempY;
				/* Kollision oben */
				if(Snake[0][1] - 1 == 0)
				{
					CollisionCount += 1;
					if(CollisionCount == 3)
					{
						CollisionCount = 0;
						return 2;
					}
				}
				/* Kollision mit Schlange selbst */
				else if(Field[ Snake[0][0] ][ Snake[0][1] - 1 ] == SnakeBody)
				{
					CollisionCount += 1;
					if(CollisionCount == 3)
					{
						CollisionCount = 0;
						return 2;
					}
				}
				else
				{
					/* Kollision mit Futter */
					if(Field[ Snake[0][0] ][ Snake[0][1] - 1 ] == 'O')
					{
						SnakeLength += 1;
						FoodEaten = true;
						Score += PointsCount;
					}
					/* Schlange Bewegen */
					for(int i = SnakeLength - 1; i > 0; i--)
					{
						TempX = Snake[i-1][0];
						TempY = Snake[i-1][1];
						Snake[i][0] = TempX;
						Snake[i][1] = TempY;
					}
					/* Neue Position für den Kopf */
					Snake[0][1] = Snake[0][1] - 1;
					CollisionCount = 0;
					KeyPressOld = HOCH;
					return 1;
				}
			}
			break;
		case LINKS:
			{
				int TempX;
				int TempY;
				/* kollision links */
				if(Snake[0][0] - 1 == 0)
				{
					CollisionCount += 1;
					if(CollisionCount == 3)
					{
						CollisionCount = 0;
						return 2;
					}
				}
				/* Kollision mit Schlange selbst */
				else if(Field[ Snake[0][0] - 1 ][ Snake[0][1] ] == SnakeBody)
				{
					CollisionCount += 1;
					if(CollisionCount == 3)
					{
						CollisionCount = 0;
						return 2;
					}
				}
				else
				{
					/* Kollision mit Futter */
					if(Field[ Snake[0][0] - 1 ][ Snake[0][1] ] == 'O')
					{
						SnakeLength += 1;
						FoodEaten = true;
						Score += PointsCount;
					}
					/* Schlange Bewegen */
					for(int i = SnakeLength - 1; i > 0; i--)
					{
						TempX = Snake[i-1][0];
						TempY = Snake[i-1][1];
						Snake[i][0] = TempX;
						Snake[i][1] = TempY;
					}
					/* Neue Position für den Kopf */
					Snake[0][0] = Snake[0][0] - 1;
					CollisionCount = 0;
					KeyPressOld = LINKS;
					return 1;
				}
			}
			break;
		case RUNTER:
			{
				int TempX;
				int TempY;
				/* Kollision unten */
				if(Snake[0][1] + 1 == 22)
				{
					CollisionCount += 1;
					if(CollisionCount == 3)
					{
						CollisionCount = 0;
						return 2;
					}
				}
				/* Kollision mit Schlange selbst */
				else if(Field[ Snake[0][0] ][ Snake[0][1] + 1 ] == SnakeBody)
				{
					CollisionCount += 1;
					if(CollisionCount == 3)
					{
						CollisionCount = 0;
						return 2;
					}
				}
				else
				{
					/* Kollision mit Futter */
					if(Field[ Snake[0][0] ][ Snake[0][1] + 1 ] == 'O')
					{
						SnakeLength += 1;
						FoodEaten = true;
						Score += PointsCount;
					}
					/* Schlange Bewegen */
					for(int i = SnakeLength - 1; i > 0; i--)
					{
						TempX = Snake[i-1][0];
						TempY = Snake[i-1][1];
						Snake[i][0] = TempX;
						Snake[i][1] = TempY;
					}
					/* Neue Position für den Kopf */
					Snake[0][1] = Snake[0][1] + 1;
					CollisionCount = 0;
					KeyPressOld = RUNTER;
					return 1;
				}
			}
			break;
		case RECHTS:
			{
				int TempX;
				int TempY;
				/* Kollision rechts */
				if(Snake[0][0] + 1 == 22)
				{
					CollisionCount += 1;
					if(CollisionCount == 3)
					{
						CollisionCount = 0;
						return 2;
					}
				}
				/* Kollision mit Schlange selbst */
				else if(Field[ Snake[0][0] + 1 ][ Snake[0][1] ] == SnakeBody)
				{
					CollisionCount += 1;
					if(CollisionCount == 3)
					{
						CollisionCount = 0;
						return 2;
					}
				}
				else
				{
					/* Kollision mit Futter */
					if(Field[ Snake[0][0] + 1 ][ Snake[0][1] ] == 'O')
					{
						SnakeLength += 1;
						FoodEaten = true;
						Score += PointsCount;
					}
					/* Schlange Bewegen */
					for(int i = SnakeLength - 1; i > 0; i--)
					{
						TempX = Snake[i-1][0];
						TempY = Snake[i-1][1];
						Snake[i][0] = TempX;
						Snake[i][1] = TempY;
					}
					/* Neue Position für den Kopf */
					Snake[0][0] = Snake[0][0] + 1;
					CollisionCount = 0;
					KeyPressOld = RECHTS;
					return 1;
				}
			}
			break;
			}
		case ESC:
			return 0;
	}
	return 1;
}
```

danke im vorraus.
habe das Projekt als Zip angehängt, ist MS Visual Studio 2005 .NET

greetz whatever


----------



## Flegmon (22. Mai 2006)

hi

system("cls"); 

Da liegt das Problem. Ich hab kA wie man das macht, dass das nicht flackert. Aber ich kenns von der Textausgabe. Is nervig.


----------



## whatever87 (22. Mai 2006)

danke für die schnelle antwort.
des mit dem sytem("cls"); hab ich mir auch schon überlegt. habs auch mit dem borland builder probiert, da kann man den befehl clrscr(); benutzen der is um einiges schneller aber der scheiss flackert trotzdem.
würs was bringen wenn ich des mit nem buffer mach? also erst die positionsdaten in einen zwischenpuffer kopieren und dann erst in das eigentlich Field kopieren und gleich ausgeben?
hab mir des schon überlegt aber obs was bringt is halt die frage...


----------



## Endurion (23. Mai 2006)

Das ist leider eine Schwäche der Windows-Konsole. Da helfen auch systemnahe Konsolenfunktionen nicht, das flackern bleibt.

Es hilft da eigentlich nur A) Fullscreen oder B) eine GDI/DirectDraw/Direct3d/OpenGL-Emulation der ASCII-Darstellung.


----------



## jokey2 (23. Mai 2006)

Müßte es nicht eigentlich auch ohne 'cls' gehen? Du überschreibst doch sowieso alle Positionen in der Konsole entweder mit einer Leerstelle, oder mit einem Zeichen für Schlange bzw. Futter.


----------



## Matthias Reitinger (23. Mai 2006)

Endurion hat gesagt.:
			
		

> Das ist leider eine Schwäche der Windows-Konsole. Da helfen auch systemnahe Konsolenfunktionen nicht, das flackern bleibt.
> 
> Es hilft da eigentlich nur A) Fullscreen oder B) eine GDI/DirectDraw/Direct3d/OpenGL-Emulation der ASCII-Darstellung.


…oder C) Verwendung eines Konsolepuffers:


```
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>

#define CONSOLE_WIDTH	80
#define CONSOLE_HEIGHT	25
#define CONSOLE_CHARS (CONSOLE_WIDTH * CONSOLE_HEIGHT)

HANDLE g_hScreenBuffer;
short g_aFrameBuffer[CONSOLE_CHARS];

void consolePresent()
{
	CHAR_INFO aCharInfo[CONSOLE_CHARS];
	int i;
	
	for (i = 0; i < CONSOLE_CHARS; ++i) {
		aCharInfo[i].Char.AsciiChar = g_aFrameBuffer[i] & 0xff;
		aCharInfo[i].Attributes = (g_aFrameBuffer[i] >> 8) & 0xff;
	}
	
	COORD max, src;
	SMALL_RECT out;
	
	max.X = CONSOLE_WIDTH;
	max.Y = CONSOLE_HEIGHT;
	
	src.X = src.Y = 0;
	
	out.Top = out.Left = 0;
	out.Right = CONSOLE_WIDTH - 1;
	out.Bottom = CONSOLE_HEIGHT - 1;
	
	WriteConsoleOutput(g_hScreenBuffer, aCharInfo, max, src, &out);
}

void consoleSetup()
{
	g_hScreenBuffer = CreateConsoleScreenBuffer(
		GENERIC_WRITE, 0, NULL,
		CONSOLE_TEXTMODE_BUFFER, NULL);
	SetConsoleActiveScreenBuffer(g_hScreenBuffer);
	
	COORD size;
	size.X = CONSOLE_WIDTH;
	size.Y = CONSOLE_HEIGHT;
	SetConsoleScreenBufferSize(g_hScreenBuffer, size);
	
	SetConsoleMode(g_hScreenBuffer, 0);
	
	CONSOLE_CURSOR_INFO cci;
	cci.bVisible = FALSE;
	cci.dwSize = 1;
	SetConsoleCursorInfo(g_hScreenBuffer, &cci);
}

int main(int argc, char** argv)
{
	consoleSetup();
	
	char *szMessage = "tutorials.de - User helfen Usern   ***   ";
	int nLen = strlen(szMessage);
	int aOffsets[CONSOLE_HEIGHT];
	short *aMessage = malloc(sizeof(short) * nLen);
	int i;

	for (i = 0; i < CONSOLE_HEIGHT; ++i) aOffsets[i] = rand() % nLen;
	for (i = 0; i < nLen; ++i) aMessage[i] = 0x0700 | szMessage[i];

	while (_kbhit() == 0) {				
		for (i = 0; i < CONSOLE_HEIGHT; ++i) {
			aOffsets[i] = (aOffsets[i] + (i%2)*2-1) % nLen;
			int nOffset = (nLen + aOffsets[i]) % nLen;
			int j;
			short *dest = g_aFrameBuffer + CONSOLE_WIDTH * i;
			for (j = 0; j < CONSOLE_WIDTH; ++j) {
				nOffset = (nOffset + 1) % nLen;
				*dest = aMessage[nOffset];
				dest++;
			}
		}

		consolePresent();

		Sleep(80);
	}
	
	free(aMessage);
	
	return 0;
}
```
Ja, Kommentare waren grad aus  

Grüße,
 Matthias


----------



## whatever87 (23. Mai 2006)

danke sehr matthias werd mich mal dran setzen. hab den code grad mal kurz überflogen und nix gepeilt *g*
ich denk mal ich muss mein code einfach da in die main irgendwo reinsetzen. wird schon irgendwie klappen.google wird mir bestimmt auch weiterhelfen können
ich sag dann bescheid wenns geklappt hat

greetz whatever

edit: @jokey2: nee ohne cls gehts net, weil dann sieht des aus wie wenn man nen Film band laufen lassen würde.
und die scrollbar wird dann immer immer kleiner. d.h. wenn ich nach oben scrolle sehe ich die voran gegangen "screens" kp. wie ich des erklärn soll ^^


----------



## Endurion (23. Mai 2006)

Matthias Reitinger hat gesagt.:
			
		

> …oder C) Verwendung eines Konsolepuffers:



Das meinte ich eigentlich mit systemnahen Konsolenfunktionen. Flackert zwar anders, aber prinzipiell genauso.


----------



## jokey2 (23. Mai 2006)

> edit: @jokey2: nee ohne cls gehts net, weil dann sieht des aus wie wenn man nen Film band laufen lassen würde.
> und die scrollbar wird dann immer immer kleiner. d.h. wenn ich nach oben scrolle sehe ich die voran gegangen "screens" kp. wie ich des erklärn soll


Ja, das hatte ich schon gesehen, als ich es dann mal ausprobiert habe. :-(
Aber Du könntest vor der Ausgabe mit gotoxy(0,0) (anstatt cls) den Cursor in die linke, obere Ecke setzen und dann zeichnen. Dann wird alles überschrieben.
Falls Du mit Visual Studio arbeitest, da gibt es leider kein gotoxy. Aber hier gibt es eine Headerdatei zu runterladen, in der diese Funktion für Windows implementiert ist.
Du kannst es natürlich auch selber mit SetConsoleCursorPosition(...) implementieren. Aber wieso selber machen, wenn es andere schon getan haben!? ;-)


----------



## Matthias Reitinger (23. Mai 2006)

Endurion hat gesagt.:
			
		

> Das meinte ich eigentlich mit systemnahen Konsolenfunktionen. Flackert zwar anders, aber prinzipiell genauso.


Also ich kann da kein Flackern erkennen  Was verstehst du unter „anders flackern“?


----------



## Endurion (24. Mai 2006)

Auf jeden Fall weniger als bei system( "cls" ).

Ich habe bei GameDev mal bei einem der 3-Stunden-Spiel-Programmierwettbewerb mitgemacht, da war ASCII-Darstellung Pflicht. Da habe ich mich auch vorher schlau gemacht, und das war wirklich der allgemeine Konsens, dass das flackert. Wenn es im Fenstermodus bleibt.

Hier ein Link dazu: http://benryves.com/tutorials/?t=winconsole


----------



## Matthias Reitinger (24. Mai 2006)

Konsens hin oder her – ich sehe, was ich sehe, und das ist nun mal kein erkennbares Flackern, welcher Form auch immer  Auch die Demos des Text Mode Demo Contest laufen bei mir flüssig im Konsolenfenster ab.

Wie dem auch sei: eine bessere Lösung gibt es scheinbar sowieso nicht, wenn man etwas in ein echtes Konsolenfenster „zeichnen“ will.

PS: Hab mir grad mal deinen Beitrag zu besagtem Contest angeschaut. Der flackert in der Tat. Vermutlich liegt das daran, dass du die Anzahl der Frames pro Sekunde nicht limitierst. Am besten wäre es wohl, mit einer konstanten Framerate zu arbeiten.


----------



## Endurion (25. Mai 2006)

Meinst du, das macht einen Unterschied? 

Wäre ja mal interessant, eventuell liegt das daran oder an diversen Grafiktreibern. Nuja, ich habe mir dann einen auf DirectDraw aufgebauten Ascii-Emulator gebaut, da war das dann sowieso sauber.

Danke für den Tipp!


----------



## whatever87 (28. Mai 2006)

jokey du bist der beste... hat in den letzten tagen net so viel zeit und lust gehabt mich da wieder dran zu setzen weil ich schier verzweifelt bin! des mit dem gotoxy(x,y) hat einwandfrei funktioniert *g* es flacker nix nada niente ^^ jez bekomm ich auch keine kopfschmerzen.
jez probier ich mal die bugs auszumerzen und n paar sachen eleganter zu machen wenns geht.

greetz whatever


----------

