Console: Clear screen function "flickers"

Started by
6 comments, last by MerlinCoder 15 years, 11 months ago
Hi, I'm working on a MUD like game which I may one day convert to an online game but for now is just single player. One of the main features about this game is I wanted to do the input in real time so you could have events happening when your typing commands in (unlike cin which stops the program). The problem with this however is that I need a clear screen function. I find that the system("CLS") is the worst method and the most flickery so I tried writing my own function but end up with pretty much the same problem. Can anyone suggest a better way? The code:
#include <iostream>
#include <conio.h>
#include <windows.h>
#include <string>
#include <vector>
#include <time.h>

const char BAD_CHAR = '`';
const int BACKSPACE = 8;
const int RETURN_KEY = 13;

using namespace std;

char getInput();
void setCursor (int x, int y);
void printEvents();
void cls();
void getUserInput();


vector<string> g_events;
string g_playerInput;
int timeSinceCreation;
int thisTime, lastTime;

int main()
{
	thisTime = clock();
	lastTime = clock();

	int delayThisTime, delayLastTime;

	delayThisTime = clock();
	delayLastTime = clock();


	while(true)
	{
		thisTime = clock();
		delayThisTime = clock();

		//delay the app a bit 20fps should do
		if((delayThisTime - delayLastTime) > 50)
		{
			cls();

			if((thisTime - lastTime) > 999) //if 1 second has passed
			{	
				if(g_events.size() == 10)
				{
					g_events.erase(g_events.begin());
				}
				
				g_events.push_back("New Player");
				lastTime = thisTime;
			}
			
			//display everything that has been worked out
			getUserInput();
			printEvents();

			delayLastTime = delayThisTime;
		}
	}

	return 0;
}

char getInput()
{
	char c = BAD_CHAR;

	if(_kbhit())
	{	
		c = _getch();

		cout << c;

		return c;
	}

	return c;

}

void setCursor (int x, int y)
{
	COORD coord = {x, y};
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

void printEvents()
{
	int n;

	for(n = 0; n < g_events.size(); n++)
	{
		setCursor(0, n);
		cout << g_events[n];
	}

	setCursor(0, n+2);
	cout << g_playerInput;
}

void cls()
{
	setCursor(0, 0);

	//clear the last 20 lines of text
	for(unsigned int i = 0; i < 20 * 80; i++)
		cout << ' ';

	//set the cursor back to 0,0 so we can use it again
	setCursor(0, 0);
}

void getUserInput()
{
	//get the input
	char a = BAD_CHAR;
	a = getInput();

	if(a == BACKSPACE)
	{
		string::iterator myIter;
		myIter = g_playerInput.end() - 1;
		g_playerInput.erase(myIter);
	}
	else if(a == RETURN_KEY)
	{
		g_playerInput.clear();
		cout << "Processing command...";
	}
	else if(a != BAD_CHAR)
	{	
		g_playerInput += a;
	}

}
This project is still in early development and I'm still planning it, but I would like to get some working prototypes ASAP.
Advertisement
What if instead of printing spaces, you printed line breaks and pushed everything off screen? Maybe not what you're looking for.

The problem is that the console output stream (cout) writes directly to the console. When you're working with an API, you write to a buffer, and when you're done you can swap the screen buffer with that one and start on your next frame.

I don't really know if there's a solution to your problem without resorting to using a graphics library, which brings up more complications.
Thanks for a quick response. My main issue with line breaks is that they are quite slow, this improves the flicker a little but not really enough :(. Also I need quite a lot of breaks because of my repositioning cursor code. Maybe i should look into swapping a buffer like you mentioned. Can it be done simply with the windows api?
Look into ncurses It'll make a lot of things easier in the long run.
Quote:Original post by MerlinCoder
Can it be done simply with the windows api?

You can write directly to the console screen buffer by filling a buffer of CHAR_INFO objects and passing it to the WriteConsoleOutput() function. Here's a quick sample to demonstrate real-time input and output:

#include <windows.h>#include <cstdlib>#include <ctime>const int WIDTH = 50;const int HEIGHT = 20;int main(){	srand(static_cast<unsigned int>(time(0)));	HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);	COORD bufferSize = { WIDTH, HEIGHT };	COORD bufferCoord = { 0, 0 };	// Specifies the portion of the console screen buffer to overwrite in character coordinates	SMALL_RECT rect = { 1, 1, WIDTH, HEIGHT };	// The buffer is a one-dimensional array that represents a rectangular area	CHAR_INFO buffer[HEIGHT * WIDTH];	bool done = false;	while (!done)	{		// Fill buffer with random ASCII characters and colors		for (int y = 0; y < HEIGHT; ++y)		{			for (int x = 0; x < WIDTH; ++x)			{				buffer[y * WIDTH + x].Char.AsciiChar = rand() % 256;				buffer[y * WIDTH + x].Attributes = rand() % 256;			}		}		WriteConsoleOutput(hOut, buffer, bufferSize, bufferCoord, &rect);		// Wait for the Space key to be pressed		while (!(GetAsyncKeyState(VK_SPACE) & 0x8000))		{			// Exit if the Escape key is pressed			if (GetAsyncKeyState(VK_ESCAPE) & 0x8000)			{				done = true;				break;			}			else Sleep(20); // Give the CPU a break		}	}}

Similarly, you can draw single characters by specifying a buffer of size 1x1. Be sure to change your character set to Multi-Byte if you're using ASCII characters (like in the above example). Otherwise, you can specify Unicode characters through the Char.UnicodeChar field of the CHAR_INFO structure.

[Edited by - Darklighter on May 26, 2008 11:59:30 PM]
Thank you very much darklighter, HUGE improvement :D. Using your random ascii characters and colours with a for loop of spaces still flickers a little bit but can at least be read.

Is there a way to quickly pass a string to the buffer rather than doing it a character at a time? I will definately look into studying the windows API a little more. Do you have any links you could recomend? I want to focus mainly on console though.

Thanks again.
This page on MSDN is great, you'll probably be looking most into the Console Functions page from there, but I got the more general page for you in case you needed the other two. I've been working on a text game lately too, except with ASCII graphics, so I've been referring to the same exact stuff.

http://msdn.microsoft.com/en-us/library/ms682087(VS.85).aspx

Hope that helps!
I got quite a good solution now, I'm only using the windows code to clear the screen and using cout as normal to draw the text. This works pretty well and keeps the code nice an neat (tbh I know very very little of the windows api). Thanks for the link splinter, I'll be sure to check it out.

Thanks again guys!

This topic is closed to new replies.

Advertisement