Greedy snake - double buffer Debug version

Posted by gavin101 on Tue, 01 Feb 2022 15:20:56 +0100

The console can have multiple screen buffers, but only one active screen buffer. This is called ActiveScreen.
The inactive screen buffer can be accessed for reading and writing, but only the active screen buffer is displayed. To make the new screen buffer the active screen buffer, use the SetConsoleActiveScreenBuffer function. (turns into active display area)
Functions to be used:
1)CreateConsoleScreenBuffer: Creates a console screen buffer.
2)WriteConsoleOutputCharacterA: specify a buffer to output the content to be output (the specified type is character array) to the console, and copy a group of characters into the continuous cells of the console screen buffer
3)SetConsoleActiveScreenBuffer: set the generated screen buffer to the active screen buffer and switch between the two caches.

Some Win32 console functions use predefined data structures, including cool and SMALL_RECT. The COORD structure contains the coordinates of the character cells in the console screen buffer. The coordinate origin (0, 0) is located in the upper left cell
HANDLE: HANDLE, which is used by WINDOWS to represent objects. It is a general HANDLE representation.
In the WINDOWS program, there are various resources (WINDOWS, icons, cursors, etc.). When creating these resources, the system allocates memory for them and returns the identification number identifying these resources, that is, the handle.
But what if the location of these resources changes?
HANDLE is fixed and will not change, but the address of the object will change. When the position of the object in memory changes, we can't find the object through the previous object pointer. HANDLE can be used to record the latest address of an object.

Parameter hConsoleOutput handle to the console screen buffer. dwSize use COORD to specify the width (X) and height (Y) of the console screen buffer. The specified width and height cannot be less than the character width and height of the current window.
Flickering snake from the previous chapter:

#include<iostream>;
#include<windows.h>;
#include<conio.h>;
using namespace std;

bool gameOver;
const int width = 20;
const int height = 20;
int x, y, fruitX, fruitY, score;
int tailX[100], tailY[100];
int nTail = 1;
int i, j;
enum eDirection { STOP = 0, LEFT, RIGHT, UP, DOWN };
eDirection dir;


void Initial()
{
	gameOver = false;
	dir = STOP;
	x = width / 2;
	y = height / 2;
	fruitX = rand() % width;
	fruitY = rand() % height;
}
void Draw()
{
	system("cls");
	HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
	int textColor = 0x06;
	SetConsoleTextAttribute(h, textColor);
	for (int i = 0; i < width + 2; i++)
		cout << "1";
	cout << endl;
	for (int j = 0; j < height; j++)
	{
		for (int i = 0; i < width; i++)
		{
			if (i == 0)
				cout << "2";
			if (i == x && j == y) {
				textColor = 0X0a;
				SetConsoleTextAttribute(h, textColor);
				cout << "O";
			}

			else if (i == fruitX && j == fruitY)
			{
				textColor = 0x084;
				SetConsoleTextAttribute(h, textColor);
				cout << "F";
			}
			else
			{
				bool FlagPrint = false;
				for (int k = 1; k < nTail; k++)
				{
					//todo: draw the tail
					if (tailX[k] == i && tailY[k] == j)
					{
						cout << "o";
						FlagPrint = true;
					}
				}
				textColor = 0x06;
				SetConsoleTextAttribute(h, textColor);
				if (!FlagPrint)
					cout << " ";
			}
			if (i == width - 1)
				cout << "3";

		}
		cout << endl;

	}
	for (i = 0; i < width + 2; i++)
		cout << "4";
	cout << endl;
}

void Input()
{
	if (_kbhit())
	{
		switch (_getch())
		{
		case'a':
			dir = LEFT;
			x--;

			break;
		case'd':
			dir = RIGHT;
			x++;

			break;
		case'w':
			dir = UP;
			y--;

			break;
		case's':
			dir = DOWN;
			y++;

			break;
		case'x':
			gameOver = true;
			break;
		default:
			break;
		}
	}
}
void Logic()
{
	for (i = nTail; i >= 1; i--)
	{
		tailX[i] = tailX[i - 1];
		tailY[i] = tailY[i - 1];

	}
	tailX[0] = x;
	tailY[0] = y;

	cout << tailY[1];
	cout << tailX[1];

	for (int i = 0; i < nTail; i++)
		if (tailX[i] == x && tailY[i] == y)
			gameOver == true;

	if (x > width || x<0 || y>height || y < 0)
		gameOver = true;
	/*if (x >= width) x = 0;
	else if (x < 0)
		x = width - 1;
	if (y >= height)y = 0;
	else if (y < 0)
		y = height - 1;*/
	if (x == fruitX && y == fruitY)
	{
		score += 10;
		fruitX = rand() % width;
		fruitY = rand() % height;
		nTail++;

	}

}

int main()
{


	Initial();
	while (!gameOver)
	{
		Draw();
		Input();

		Logic();
	}

	system("pause");
	return 0;
}



of

The non buffered version of the flash screen is as above.
After adding some double buffering settings,
Let's take our time to see what went wrong.

We write Draw2() to

void Draw2()
{
	cout << "get into draw2()loop";
	WORD textColor = 0X06;///Color of wall
	short j=0;
	COORD coord_wall;
	int currentLine = 0;
	WORD textColor_Head = 0X0a;
	WORD textColor_Fruit = 0x084;
	WORD textColor_Tail = 0x084;
	WORD textColor_blank = 0x06;

	for (int i = 0; i < width - 2; i++)
	{
		coord_wall.X = i;
		coord_wall.Y = currentLine;
		WriteConsoleOutputAttribute(hOutBuf, &textColor, 1, coord_wall, &bytes);
		WriteConsoleOutputAttribute(hOutput, &textColor, 1, coord_wall, &bytes);
		ScreenData[currentLine][i] = '1';
	}
	currentLine++;
	cout << "Print 1 wall";
	for ( j = 0; j < height; j++)
	{
		for (int i = 0; i < width; i++)
		{
			if ( i == 0)
			{
				coord_wall.X = i;
				coord_wall.Y = currentLine + j;
				WriteConsoleOutputAttribute(hOutBuf, &textColor, 1, coord_wall, &bytes);
				WriteConsoleOutputAttribute(hOutput, &textColor, 1, coord_wall, &bytes);
				ScreenData[currentLine+j][i] = '2';
				
			}
			cout << "Print 2 walls";
			if (i == x && j == y) {
				coord_wall.X = i;
				coord_wall.Y = currentLine + j;
				WriteConsoleOutputAttribute(hOutBuf, &textColor_Head, 1,coord_wall, &bytes);
				WriteConsoleOutputAttribute(hOutput, &textColor_Head, 1, coord_wall, &bytes);
				ScreenData[currentLine + j][i] = 'O';
			}
			else if (i == fruitX && j == fruitY)
			{
				coord_wall.X = i;
				coord_wall.Y= currentLine + j;
				WriteConsoleOutputAttribute(hOutBuf, &textColor_Fruit, 1, coord_wall, &bytes);
				WriteConsoleOutputAttribute(hOutput, &textColor_Fruit, 1, coord_wall, &bytes);
				ScreenData[currentLine + j][i] = 'F';

			}
			else
			{
				bool FlagPrint = false;
				for (int k = 1; k < nTail; k++)
				{
					//todo: draw the tail
					if (tailX[k] == i && tailY[k] == j)
					{
						coord_wall.X = i;
						coord_wall.Y = currentLine + j;
						WriteConsoleOutputAttribute(hOutBuf, &textColor_Tail, 1, coord_wall, &bytes);
						WriteConsoleOutputAttribute(hOutput, &textColor_Tail, 1, coord_wall,&bytes);
						ScreenData[currentLine + j][i] = 'o';
						FlagPrint = true;
				
					}
				}
				
				if (!FlagPrint)
				{
					coord_wall.X = i;
					coord_wall.Y = currentLine + j;
					WriteConsoleOutputAttribute(hOutBuf, &textColor_blank, 1, coord_wall, &bytes);
					WriteConsoleOutputAttribute(hOutput, &textColor_blank, 1, coord_wall, &bytes);
					ScreenData[currentLine + j][i] = ' ';
	
				}
					
			}
			if (i == width - 1)
			{
				coord_wall.X = i;
				coord_wall.Y = currentLine + j;
				WriteConsoleOutputAttribute(hOutBuf, &textColor, 1, coord_wall, &bytes);
				WriteConsoleOutputAttribute(hOutput, &textColor, 1, coord_wall, &bytes);
				ScreenData[currentLine + j][i] = '3';
			}

		}
		cout << endl;
		
	}
	cout << "End of cycle";
	///short j = height;
	for (int i = 0; i < width + 2; i++)
	{
		coord_wall.X = i;
		coord_wall.Y = currentLine + j;
		WriteConsoleOutputAttribute(hOutBuf, &textColor, 1, coord_wall, &bytes);
		WriteConsoleOutputAttribute(hOutput, &textColor, 1, coord_wall, &bytes);
		ScreenData[currentLine + j][i] = '4';

	}
	currentLine++;
	sprintf_s(ScreenData[currentLine], "Game score:%4d", score);

}

Then report the error
Change the output line to:

	cout<<ScreenData[currentLine], "Game score:%4d", score;

Draw2() no longer reports an error.
(although I don't know why)
Print results:


That's nice. Yellow is the result of DRAW() and white is the result of Draw2().
We delete the Chinese characters used for detection. In int main(), draw() and draw2() are printed at the same time

It can be seen that the screen flickers very huge, but fortunately! No error is reported and the operation is normal. It's not easy.
Next, let's join show_ Double buffer() function. Demonstrate the efficacy of double buffering.

void Show_doublebuffer()
{
	int i;
	Draw2();
	if (bufferSwapFlag == false)
	{
		bufferSwapFlag = true;
		for (i = 0; i < height + 5; i++)
		{
			coord.Y = i;
			WriteConsoleOutputCharacterA(hOutBuf, ScreenData[i], width, coord, &bytes);
		}
		SetConsoleActiveScreenBuffer(hOutBuf);
	}
	else
	{
		bufferSwapFlag = false;
		for (i = 0; i < height + 5; i++)
		{
			coord.Y = i;
			WriteConsoleOutputCharacterA(hOutput, ScreenData[i], width, coord, &bytes);
		}
		SetConsoleActiveScreenBuffer(hOutput);
	}

}

Here we have another problem:
The area that should have been displayed becomes a blank. But when cout < < screendata in Draw2(), the array appears correctly. Therefore, I reasonably speculate that there should be a problem with this function, resulting in the failure to correctly display the double buffer function.
Then I was surprised to find out. I didn't write it completely in Initial().

After completion, the code running results are shown in the figure below:

The picture basically no longer flickers. Double buffering succeeded temporarily.
Question: 1 Missing wall 2
2. Game score printing position error

#include<iostream>;
#include<windows.h>;
#include<conio.h>;
#define _CRT_SECURE_NO_DEPRECATE

using namespace std;

HANDLE hOutput, hOutBuf;
COORD coord = { 0,0 };
DWORD bytes = 0;
bool BufferSwapFlag = false;
bool gameOver;
bool bufferSwapFlag;
const int width = 20;
const int height = 20;
int x, y, fruitX, fruitY, score;
int tailX[100], tailY[100];
int nTail = 1;
int i, j;
enum eDirection { STOP = 0, LEFT, RIGHT, UP, DOWN };
eDirection dir;
char ScreenData[width + 5][height + 5];

void Initial()
{
	hOutBuf = CreateConsoleScreenBuffer(
		GENERIC_WRITE,//Defines that the process can write data to the buffer
		FILE_SHARE_WRITE,//Define buffer shareable write permissions
		NULL,
		CONSOLE_TEXTMODE_BUFFER,
		NULL
	);
	hOutput = CreateConsoleScreenBuffer(
		GENERIC_WRITE,
		FILE_SHARE_WRITE,
		NULL,
		CONSOLE_TEXTMODE_BUFFER,
		NULL
	);
	//Hide the cursor of two buffers
	CONSOLE_CURSOR_INFO cci;
	cci.bVisible = 0;
	cci.dwSize = 1;
	SetConsoleCursorInfo(hOutput, &cci);
	SetConsoleCursorInfo(hOutBuf, &cci);
	gameOver = false;
	dir = STOP;
	x = width / 2;
	y = height / 2;
	fruitX = rand() % width;
	fruitY = rand() % height;
}
/*
void Draw()
{
	system("cls");
	HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
	int textColor = 0x06;
	SetConsoleTextAttribute(h, textColor);
	for (int i = 0; i < width + 2; i++)
		cout << "1";
	cout << endl;
	for (int j = 0; j < height; j++)
	{
		for (int i = 0; i < width; i++)
		{
			if (i == 0)
				cout << "2";
			if (i == x && j == y) {
				textColor = 0X0a;
				SetConsoleTextAttribute(h, textColor);
				cout << "O";
			}

			else if (i == fruitX && j == fruitY)
			{
				textColor = 0x084;
				SetConsoleTextAttribute(h, textColor);
				cout << "F";
			}
			else
			{
				bool FlagPrint = false;
				for (int k = 1; k < nTail; k++)
				{
					//todo:Draw tail
					if (tailX[k] == i && tailY[k] == j)
					{
						cout << "o";
						FlagPrint = true;
					}
				}
				textColor = 0x06;
				SetConsoleTextAttribute(h, textColor);
				if (!FlagPrint)
					cout << " ";
			}
			if (i == width - 1)
				cout << "3";

		}
		cout << endl;

	}
	for (i = 0; i < width + 2; i++)
		cout << "4";
	cout << endl;
}
*/
void Draw2()
{
	//Cout < < "enter draw2() cycle";
	WORD textColor = 0X06;///Color of wall
	short j = 0;
	COORD coord_wall;
	int currentLine = 0;
	WORD textColor_Head = 0X0a;
	WORD textColor_Fruit = 0x084;
	WORD textColor_Tail = 0x084;
	WORD textColor_blank = 0x06;

	for (int i = 0; i < width - 2; i++)
	{
		coord_wall.X = i;
		coord_wall.Y = currentLine;
		WriteConsoleOutputAttribute(hOutBuf, &textColor, 1, coord_wall, &bytes);
		WriteConsoleOutputAttribute(hOutput, &textColor, 1, coord_wall, &bytes);
		ScreenData[currentLine][i] = '1';
	}
	currentLine++;
	//Cout < < "print 1 Wall";
	for (j = 0; j < height; j++)
	{
		for (int i = 0; i < width; i++)
		{
			if (i == 0)
			{
				coord_wall.X = i;
				coord_wall.Y = currentLine + j;
				WriteConsoleOutputAttribute(hOutBuf, &textColor, 1, coord_wall, &bytes);
				WriteConsoleOutputAttribute(hOutput, &textColor, 1, coord_wall, &bytes);
				ScreenData[currentLine + j][i] = '2';

			}

			if (i == x && j == y) {
				coord_wall.X = i;
				coord_wall.Y = currentLine + j;
				WriteConsoleOutputAttribute(hOutBuf, &textColor_Head, 1, coord_wall, &bytes);
				WriteConsoleOutputAttribute(hOutput, &textColor_Head, 1, coord_wall, &bytes);
				ScreenData[currentLine + j][i] = 'O';
			}
			else if (i == fruitX && j == fruitY)
			{
				coord_wall.X = i;
				coord_wall.Y = currentLine + j;
				WriteConsoleOutputAttribute(hOutBuf, &textColor_Fruit, 1, coord_wall, &bytes);
				WriteConsoleOutputAttribute(hOutput, &textColor_Fruit, 1, coord_wall, &bytes);
				ScreenData[currentLine + j][i] = 'F';

			}
			else
			{
				bool FlagPrint = false;
				for (int k = 1; k < nTail; k++)
				{
					//todo: draw the tail
					if (tailX[k] == i && tailY[k] == j)
					{
						coord_wall.X = i;
						coord_wall.Y = currentLine + j;
						WriteConsoleOutputAttribute(hOutBuf, &textColor_Tail, 1, coord_wall, &bytes);
						WriteConsoleOutputAttribute(hOutput, &textColor_Tail, 1, coord_wall, &bytes);
						ScreenData[currentLine + j][i] = 'o';
						FlagPrint = true;

					}
				}

				if (!FlagPrint)
				{
					coord_wall.X = i;
					coord_wall.Y = currentLine + j;
					WriteConsoleOutputAttribute(hOutBuf, &textColor_blank, 1, coord_wall, &bytes);
					WriteConsoleOutputAttribute(hOutput, &textColor_blank, 1, coord_wall, &bytes);
					ScreenData[currentLine + j][i] = ' ';

				}

			}
			if (i == width - 1)
			{
				coord_wall.X = i;
				coord_wall.Y = currentLine + j;
				WriteConsoleOutputAttribute(hOutBuf, &textColor, 1, coord_wall, &bytes);
				WriteConsoleOutputAttribute(hOutput, &textColor, 1, coord_wall, &bytes);
				ScreenData[currentLine + j][i] = '3';
			}

		}
		cout << endl;

	}
	//Cout < < "end of cycle";
	///short j = height;
	for (int i = 0; i < width + 2; i++)
	{
		coord_wall.X = i;
		coord_wall.Y = currentLine + j;
		WriteConsoleOutputAttribute(hOutBuf, &textColor, 1, coord_wall, &bytes);
		WriteConsoleOutputAttribute(hOutput, &textColor, 1, coord_wall, &bytes);
		ScreenData[currentLine + j][i] = '4';
		

	}
	currentLine++;
	sprintf_s( ScreenData[currentLine],"Game score:%4d", score);

}
void Show_doublebuffer()
{
	int j;
	Draw2();
	if (bufferSwapFlag == false)
	{
		bufferSwapFlag = true;
		for (j = 0; j < height + 5; j++)
		{
			coord.Y = j;
			WriteConsoleOutputCharacterA(hOutBuf, ScreenData[j], width, coord, &bytes);
		}
		SetConsoleActiveScreenBuffer(hOutBuf);
	}
	else
	{
		bufferSwapFlag = false;
		for (j = 0; j < height + 5; j++)
		{
			coord.Y = j;
			WriteConsoleOutputCharacterA(hOutput, ScreenData[j], width, coord, &bytes);
		}
		SetConsoleActiveScreenBuffer(hOutput);
	}

}

void Input()
{
	if (_kbhit())
	{
		switch (_getch())
		{
		case'a':
			dir = LEFT;
			x--;

			break;
		case'd':
			dir = RIGHT;
			x++;

			break;
		case'w':
			dir = UP;
			y--;

			break;
		case's':
			dir = DOWN;
			y++;

			break;
		case'x':
			gameOver = true;
			break;
		default:
			break;
		}
	}
}
void Logic()
{
	for (i = nTail; i >= 1; i--)
	{
		tailX[i] = tailX[i - 1];
		tailY[i] = tailY[i - 1];

	}
	tailX[0] = x;
	tailY[0] = y;

	for (int i = 0; i < nTail; i++)
		if (tailX[i] == x && tailY[i] == y)
			gameOver == true;

	if (x > width || x<0 || y>height || y < 0)
		gameOver = true;
	if (x == fruitX && y == fruitY)
	{
		score += 10;
		fruitX = rand() % width;
		fruitY = rand() % height;
		nTail++;

	}

}

int main()
{


	Initial();
	while (!gameOver)
	{

		Show_doublebuffer();
		Input();

		Logic();

	}

//	system("pause");
	return 0;
}