cterminalconsole

What is the "cleanest" way to clear the console in C


First, I'll explain my purpose. For fun I'm wanting to create a character-based renderer in the terminal, something simple that can just draw shapes by printing "#" and " ". However, I'm also wanting to have the ability to have frames and so support animations, for instance a line rotating in the center of the screen or a square bouncing around. A piece of context is that I'm using windows.

Initially I theorized that I would need to print a frame and then clear the screen before printing the next frame. The method I found to do this is using System("cls"). However, this creates a very flickery effect and the cursor can be seen moving about sporadically. Now I'm thinking is that I instead need something that can replace the old "frame" rather than clearing it and then printing the new, however I'm not entirely sure the best method to go about this.

Thanks


Solution

  • What is the "cleanest" way to clear the console in C

    There is no universal way to do this in C, because C itself has no concept of "screen" or "console". Any mechanism you use will need to be specific to your particular console device. There are libraries that can abstract this for you. (ncurses, for example, though this particular one may be a little tricky on Windows.)

    Since you're writing for Windows, you have access to the Windows Console API for controlling the console. HOWEVER, Microsoft now recommends what it calls "virtual terminal sequences", but which most others call "ANSI escape sequences". Or you could join me in my rebellious ways, and call them "ANSI terminal control sequences". This approach involves writing special byte sequences to the console that it interprets as commands rather than as data to display. To use this mechanism to clear the console in which your program is running, you could:

    fputs("\033[2J", stdout);
    fflush(stdout);
    

    I guarantee that that is faster than calling the system() function to execute a command in the console. Note that I chose fputs() over puts(), printf(), and other alternatives intentionally. Unlike puts(), it does not append a newline, and it has less overhead than the printf() family of functions.

    HOWEVER,

    1. The flicker you observe is surely the result of clearing the screen in the first place, especially in combination with the time it takes to draw / print the next frame afterward. Clearing the screen faster will not be nearly as helpful as drawing the next frame faster will be.

    2. I suspect that you're conflating two distinct effects: clearing the console and moving the cursor to the top left position. These are not necessarily associated with each other. Indeed, the code fragment above does only the clearing, not the cursor repositioning.

    3. Once you understand the separation between cursor positioning and the content displayed on the console, it should become evident that you don't necessarily have to clear the screen (separately) at all, if you're prepared instead to simply overwrite its full contents with the next frame.

    4. The console is pretty slow overall. Maybe not too slow for what you want to do if you are careful about how you do it, but do be prepared for the possibility that it just isn't fast enough for you no matter what you do.

    If you want to proceed with rolling your own console-based renderer, then here are some suggestions:

    1. Draw each new frame directly on top of the previous one, without clearing the screen between. To facilitate that, you can move the cursor to the top, left of the console window with a different terminal-control sequence:

      fputs("\033[1;1H", stdout);
      
    2. That means that for every frame, you must rewrite every character in your display area that may have changed (but only once, not twice as clearing first would require for at least some positions). There are other control sequences that can help with that by positioning the cursor and clearing all or part of the current line, or to the end of the screen.

    3. Manipulate the I/O buffer size and buffering mode for the console so that you can render a whole frame at a time to the buffer, then flush it out to the console all at once. Be sure to allow space in the buffer for any embedded terminal-control sequences you plan.

      For example, you might:

      #include <stdio.h>
      
      #define DISPLAY_WIDTH  80
      #define DISPLAY_HEIGHT 25
      // Provides for 16 bytes of control data per line, plus an extra 16:
      #define BUFFER_SIZE (DISPLAY_HEIGHT * (DISPLAY_WIDTH + 16) + 16)
      
      // ...
      
      int main(void) {
          char screen_buf[BUFFER_SIZE];
      
          setvbuf(stdout, screen_buf, _IOFBF, BUFFER_SIZE);
          // ...
      

      That sets fully-buffered mode, with the expectation that you will never fill the buffer completely. It anticipates that you will instead use fflush(stdout) to flush the buffer contents to the console once you have used stdio functions to write a complete frame.

      You could instead write directly into the buffer with sprintf() and / or string-manipulation functions, then print the whole string at once with fputs() (and flush), but then you introduce a needless extra buffer, and for each frame, a copy from the data buffer to the I/O buffer. On the other hand, that might make it easier to do the rendering you want to do.

    4. You probably want to hide the cursor. There's a control sequence for that: "\033[?25l". (That's a lowercase 'L' at the end.)