Ah, that brings back the good old days. I did similar things in high school :-)
You will run into performance issues. Console I / O, especially on Windows, is slow. Very, very slow (sometimes slower than writing to disk, even). In fact, you will quickly be surprised how much more work you can do without affecting the latency of your game cycle, as I / O will dominate the rest. Thus, the golden rule simply minimizes the number of I / O operations that you do, above all.
First, I suggest getting rid of system("cls") and replacing it with calls to the actual Win32 console console functions that cls wraps ( docs ):
#define NOMINMAX
In fact, instead of redrawing the entire “frame” each time, you are much better off drawing (or erasing, overwriting them with a space) individual characters at a time:
// x is the column, y is the row. The origin (0,0) is top-left. void setCursorPosition(int x, int y) { static const HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); std::cout.flush(); COORD coord = { (SHORT)x, (SHORT)y }; SetConsoleCursorPosition(hOut, coord); } // Step through with a debugger, or insert sleeps, to see the effect. setCursorPosition(10, 5); std::cout << "CHEESE"; setCursorPosition(10, 5); std::cout 'W'; setCursorPosition(10, 9); std::cout << 'Z'; setCursorPosition(10, 5); std::cout << " "; // Overwrite characters with spaces to "erase" them std::cout.flush(); // Voilà, 'CHEESE' converted to 'WHEEZE', then all but the last 'E' erased
Please note that this also eliminates flickering, since there is no longer any need to completely clear the screen before redrawing - you can just change what you need to change without intermediate clearing, so the previous frame is gradually updated, remaining until it is completely updated .
I suggest using the double buffering method: to have one buffer in memory that represents the "current" state of the console screen, initially filled with spaces. Then add another buffer that represents the “next” state of the screen. The logic of updating your game will change the “next” state (just like with your battleField array). When the time comes to draw a frame, do not delete everything first. Instead, go through both buffers in parallel and write only the changes from the previous state (the “current” buffer at this point contains the previous state). Then copy the “next” buffer to the “current” buffer to configure for the next frame.
char prevBattleField[MAX_X][MAX_Y]; std::memset((char*)prevBattleField, 0, MAX_X * MAX_Y); // ... for (int y = 0; y != MAX_Y; ++y) { for (int x = 0; x != MAX_X; ++x) { if (battleField[x][y] == prevBattleField[x][y]) { continue; } setCursorPosition(x, y); std::cout << battleField[x][y]; } } std::cout.flush(); std::memcpy((char*)prevBattleField, (char const*)battleField, MAX_X * MAX_Y);
You can even take one more step and batch run the changes together into one I / O call (which is much cheaper than many calls for writing individual characters, but still proportionally more expensive, the more characters are written).
// Note: This requires you to invert the dimensions of `battleField` (and // `prevBattleField`) in order for rows of characters to be contiguous in memory. for (int y = 0; y != MAX_Y; ++y) { int runStart = -1; for (int x = 0; x != MAX_X; ++x) { if (battleField[y][x] == prevBattleField[y][x]) { if (runStart != -1) { setCursorPosition(runStart, y); std::cout.write(&battleField[y][runStart], x - runStart); runStart = -1; } } else if (runStart == -1) { runStart = x; } } if (runStart != -1) { setCursorPosition(runStart, y); std::cout.write(&battleField[y][runStart], MAX_X - runStart); } } std::cout.flush(); std::memcpy((char*)prevBattleField, (char const*)battleField, MAX_X * MAX_Y);
Theoretically, this will work much faster than the first cycle; however, in practice, this probably will not make any difference, since std::cout already buffers the record anyway. But this is a good example (and a generic pattern that shows a lot when there is no buffer in the base system), so I turned it on anyway.
Finally, note that you can reduce your sleep to 1 millisecond. In any case, Windows cannot sleep normally for less than 10-15 ms, but this will not allow your processor to achieve 100% utilization with minimal additional delay.
Note that this is not at all what the "real" games do; they almost always clear the buffer and redraw all frames. They do not flicker because they use the equivalent of a double buffer on the GPU, where the previous frame remains visible until the new frame is completely completed.
Bonus You can change the color to any of 8 different system colors , as well as the background:
void setConsoleColour(unsigned short colour) { static const HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); std::cout.flush(); SetConsoleTextAttribute(hOut, colour); }