michaellindahl
michaellindahl

Reputation: 2052

console cout animation - C++

I would like to animate a 40x20 block of characters that I am cout-ing. I would like to clear the console with system("cls"); and then have the next block on character instantly appear. Currently the next block is coming on typewriter style.

The most simplest answer to my question would just to have a 20 line by 40 character oss stream cout at once, instead of doing it typewriter style.

Main.cpp:

    mazeCreator.cout();
    Sleep(5000);
    system("cls");

cout()

void MazeCreator::cout() {
    char wallChar = (char) 219;
    char pavedChar = (char) 176;
    char lightChar = ' ';
    char startChar = 'S';
    char finishChar = 'F';
    char errorChar = '!';
    char removedWallChar = 'R';
    char landmarkLocationChar = 'L';

    ostringstream oss;
    for (int row = 0; row < rows; row++) {
        oss << " ";
        for (int col = 0; col < columns; col++) {
            if (mazeArray[row][col] == wall)
                oss << wallChar;
            else if (mazeArray[row][col] == paved)
                oss << pavedChar;
            else if (mazeArray[row][col] == light)
                oss << lightChar;
            else if (mazeArray[row][col] == start)
                oss << startChar;
            else if (mazeArray[row][col] == finish)
                oss << finishChar;
            else if (mazeArray[row][col] == removedWall)
                oss << removedWallChar;
            else if (mazeArray[row][col] == landmarkLocation)
                oss << landmarkLocationChar;
            else
                oss << errorChar;
        }
        oss << "\n";
    }
    oss << "\n\n";

    cout << oss.str();
}

Upvotes: 1

Views: 3676

Answers (2)

HighCommander4
HighCommander4

Reputation: 52739

You could maintain two 2D arrays in your code, one with the current block of characters on the screen (let's call it cur) and one with the next block (let's call it next).

Assume cur stores the block that's on screen right now. Set up the next block by writing into the next array. When you're ready to put it on the screen, loop through cur and next simultaneously, and only for characters where they differ, use SetConsoleCursorPosition to jump to that location and write the new character.

Once you've done that, copy the contents of next into cur and move on to the next block.

UPDATE: Here's an example:

class console_buffer
{
public:
    console_buffer(int rows, int columns) 
                   // start out with spaces
                 : cur(rows, vector<char>(columns, ' ')), 
                   next(rows, vector<char>(columns, ' '))
    {
    }

    void sync()
    {
        // Loop over all positions
        for (int row = 0; row < cur.size(); ++row)
            for (int col = 0; col < cur[row].size(); ++col)

                // If the character at this position has changed
                if (cur[row][col] != next[row][col])
                {
                    // Move cursor to position
                    COORD c = {row, col};
                    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c);

                    // Overwrite character
                    cout.put(next[row][col]);
                }

         // 'next' is the new 'cur'
         cur = next;
    }

    void put(char c, int row, int col)
    {
        next[row][col] = c;
    }
private:
    vector<vector<char> > cur;
    vector<vector<char> > next;
};

...

int main()
{
    console_buffer buf(40, 20);

    // set up first block
    ... some calls to buf.put() ...

    // make first block appear on screen
    buf.sync();

    // set up next block
    ... some calls to buf.put()

    // make next block appear on screen
    buf.sync();

    // etc.
}

Upvotes: 2

Qaz
Qaz

Reputation: 61910

You can implement double buffering using CreateConsoleScreenBuffer. Something along these lines should work. I've used this once, quite a while ago, so it might not be perfect.

HANDLE current = GetStdHandle (STD_OUTPUT_HANDLE);

HANDLE buffer = CreateConsoleScreenBuffer (
    GENERIC_WRITE,
    0,
    NULL,
    CONSOLE_TEXTMODE_BUFFER,
    NULL
);

WriteConsole (/*fill with what you're drawing*/);

system ("cls"); //clear this screen before swapping    
SetConsoleActiveScreenBuffer (buffer);

WriteConsole (/*do it to the other one now*/);

system ("cls");    
SetConsoleActiveScreenBuffer (current); //swap again

//repeat as needed

CloseHandle (buffer); //clean up

Upvotes: 0

Related Questions