2

If I have a grid of characters displayed in the console, is there any practical way to re-write those multiple lines in order to output an altered grid on those same lines of the console.

e.g, I would like this code:

#include <iostream>

using namespace std;

int main() {
        for (int i=0; i<5; i++) {
                for (int j=0; j<5; j++) {
                        cout << "-";
                }
                cout << endl;
        }

        getchar();

        for (int i=0; i<5; i++) {
                cout << '\r';
                for (int j=0; j<5; j++) {
                        cout << "x";
                }
                cout.flush();
        }

        return 0;
}

to output:

-----
-----
-----
-----
-----

then, upon user input, overwrite that with;

xxxxx
xxxxx
xxxxx
xxxxx
xxxxx

I see other examples of people displaying loading bar type displays by outputting '\r' to re-write a single line, but I'm not sure if there is any straightforward way to accomplish that for multiple lines?

I'm using MinGW.

One Solution:

#include <iostream>
#include <stdio.h>
#include <windows.h>

using namespace std;

void gotoxy( int column, int line )
  {
  COORD coord;
  coord.X = column;
  coord.Y = line;
  SetConsoleCursorPosition(
    GetStdHandle( STD_OUTPUT_HANDLE ),
    coord
    );
  }

int main() {
        for (int i=0; i<5; i++) {
                for (int j=0; j<5; j++) {
                        cout << "-";
                }
                cout << endl;
        }

        getchar();

        gotoxy(0,0);

        for (int i=0; i<5; i++) {
                for (int j=0; j<5; j++) {
                        cout << "x";
                }
                cout << endl;
        }

        return 0;
}
Gerald
  • 521
  • 1
  • 6
  • 16
  • 1
    Practical, no. Possible, yes. Nothing that SetConsoleCursorPosition() can't do. How far you want to take that is up to you, the sky is the limit and it is not like you can't use C++ to move a GUI progress bar. – Hans Passant Nov 16 '17 at 23:08
  • 1
    C++ streams are too simple to do this. You can't suck the stuff back in and the underlying console may not support backspacing and overwriting it. You will have to go outside of standard C++. Research the curses library to see if it is a good fit for your needs. – user4581301 Nov 16 '17 at 23:10
  • 1
    There is no standard way to do what you're asking for, i.e. manipulate the console in this manner. – PaulMcKenzie Nov 16 '17 at 23:10

3 Answers3

2

You can use SetConsoleCursorPosition to set the cursor position.

Abhijit Pritam Dutta
  • 5,521
  • 2
  • 11
  • 17
2

You can add this block of code between your two loops to clear the screen.

    printf("\033[2J");
    printf("\033[%d;%dH", 0, 0);

It uses ANSI escape sequences to do so. You also need to add #include <stdio.h> to support printf().

There's also no need for the \r and you should replace cout.flush(); with cout << endl;

You code should finally look like this :

#include <iostream>
#include <stdio.h>
using namespace std;

int main() 
{
    for (int i=0; i<5; i++) {
            for (int j=0; j<5; j++) {
                    cout << "-";
            }
            cout << endl;
    }

    getchar();
    printf("\033[2J");
    printf("\033[%d;%dH", 0, 0);

    for (int i=0; i<5; i++) {
            for (int j=0; j<5; j++) {
                    cout << "x";
            }
            cout<<endl;
    }

    return 0;
}
Ahmed Karaman
  • 523
  • 3
  • 12
  • 2
    You should run your code in an external terminal not inside an IDE. – Ahmed Karaman Nov 16 '17 at 23:42
  • It is running in the Windows command prompt - the screenshot is cropped in a way that is misleading. – Gerald Nov 16 '17 at 23:45
  • The solution I mentioned is just a hack to get around your problem. What's actually happening is that the terminal just scrolls down so you won't be able to see the previously printed dashes and then it prints the X's. There is no possible way you can literally **CLEAR** the terminal and re-print again. – Ahmed Karaman Nov 17 '17 at 00:21
2

is any straightforward way to accomplish that for multiple lines?

Yes. Ansi terminals are a straight forward and portable solution. Every PC I have ever worked with has Ansi terminal emulations. On ubuntu, gnome-terminal works fine. I have long used ansi escape sequences.

For this effort, you probably only need goto and clear screen, but there are many more ansi terminal functions.

Since you have marked this post as C++, consider the following.

#include <chrono>
// 'compressed' chrono access --------------vvvvvvv
typedef std::chrono::high_resolution_clock  HRClk_t; // std-chrono-hi-res-clk
typedef HRClk_t::time_point                 Time_t;  // std-chrono-hi-res-clk-time-point
typedef std::chrono::milliseconds           MS_t;    // std-chrono-milliseconds
typedef std::chrono::microseconds           US_t;    // std-chrono-microseconds
typedef std::chrono::nanoseconds            NS_t;    // std-chrono-nanoseconds
using   namespace std::chrono_literals;  // support suffixes like 100ms, 2s, 30us
#include <iostream>
#include <iomanip>
#include <thread>


class Ansi_t   // use ansi features of gnome-terminal, 
{              // or any ansi terminal
   // note: Ubuntu 15.10 gnome-terminal ansi term cursor locations 
   //       are 1-based, with origin 1,1 at top left corner

   enum ANSI : int { ESC = 27 }; // escape 

public:

   static std::string clrscr(void)
      {
         std::stringstream ss;
         ss << static_cast<char>(ESC)<< "[H"   // home
            << static_cast<char>(ESC)<< "[2J"; // clrbos
         return(ss.str());
      }

   //       r/c are 0 based------v------v------0 based from C++
   static std::string gotoRC(int r, int c)
      {
         std::stringstream ss;
         // Note: row/col of my apps are 0 based (as in C++)
         // here I fix xy to 1 based, by adding 1 while forming output
         ss << static_cast<char>(ESC)<< "[" 
            << (r+1) << ';' << (c+1) << 'H';
         return(ss.str());
      }

   // tbr - add more ansi functions when needed

}; // Ansi_t

Note: I have, on occasion, added asserts to enforce that r and c are >= 0.

Usage example:

int main(int, char**)
{    
   int retVal = -1;
   {
      Time_t start_us = HRClk_t::now();

      {
         std::cout << Ansi_t::clrscr() << std::flush;  // leaves cursor at top left of screen

         for (int i=0; i<10; ++i)
         {

            for (int r=0; r<5; ++r) // 0 based
            {
               std::cout << Ansi_t::gotoRC(r+5, 5)  // set cursor location
                         << "-----" << std::flush;
            }

            std::this_thread::sleep_for(500ms);

            // to overwrite
            for (int r=0; r<5; ++r)
            {
               std::cout << Ansi_t::gotoRC(r+5, 5)  // set cursor location
                         << "xxxxx" << std::flush;
            }

            std::this_thread::sleep_for(500ms);

            std::cout << Ansi_t::gotoRC(11,5) << 9-i << std::flush;

         }// for i


         std::cout << "\n\n" << std::endl;

         return 0;
      }

      auto  duration_us = std::chrono::duration_cast<US_t>(HRClk_t::now() - start_us);

      std::cout << "\n\n  duration  " << duration_us.count() << " us" << std::endl;
   }

   return(retVal);
}

Memoization is straight forward using these Ansi_t methods. Example snippets from working code:

  1. add a data attribute to the class
std::string     m_toRowCol;  // cursor position string
  1. pre-fill the string in ctor
 FOO_t::FOO_t( ... )
 : m_seqId     (a_gb.gBoardSz())     
 , m_toRowCol  (a_gb.gotoRC(aRow, aCol)) // pre-compute
 // other data attribute init
 {
    // ctor body
 }
  1. use the pre-filled string to position cursor prior to state output
m_gb.termUpdate(m_toRowCol + // position cursor
                <output string>); // provide state info
2785528
  • 5,438
  • 2
  • 18
  • 20