Looking to build a terminal based game.

I am assuming X-Term like terminal.  
This means:

 * I can use the X-Term control codes to clear and move around the screen.
 * I can use `tcsetattr` functions to make standard input none buffered (I think).

I know this game loop runs as basically a busy wait. I will sove that at the `select()` command by adding the appropriate timeout there (I just need to calculate an appropriate value).

Note: I installed the signal handlers so that when the application is being forced to quit I can still restore the terminal to its original state before the application quite. You will notice the `std::cout << showCursor;` to turn the cursor back on and the destructor sets the terminal state back to its original value: `tcsetattr(STDIN_FILENO, TCSAFLUSH, &originalConfig);`

This is the shell for my game (I have not implemented game logic). I wanted to make sure I am not messing stuff up around display and terminal manipulation before I go any further.


    #include <iostream>
    
    #include <signal.h>
    #include <termios.h>
    #include <stdio.h>
    #include <unistd.h>
    
    static volatile sig_atomic_t  done = 0;
    
    extern "C" void handle_done(int signum)
    {
        if (!done) {
            done = signum;
        }
    }
    
    class Game
    {
        static constexpr int    width   = 40;
        static constexpr int    height  = 20;
        static constexpr char   clearScreen[]       = "\033[2J";
        static constexpr char   movetoZeroZero[]    = "\033[0;0H";
        static constexpr char   hideCursor[]        = "\033[?25l";
        static constexpr char   showCursor[]        = "\033[?25h";
    
        termios originalConfig;
        bool    gameOver;
    
        char getChar(int x, int y)
        {
            if (y == 0 || y == height -1 || x == 0 || x == width -1 ) {
                return '#';
            }
            return ' ';
        }
        void draw(bool dirty)
        {
            std::cout << movetoZeroZero
                      << "Snake V1\n";
            for (int y = 0; y < height; ++y) {
                for (int x = 0; x < width; ++x) {
                    std::cout << getChar(x, y);
                }
                std::cout << "\n";
            }
            std::cout << std::flush;
        }
    
        bool input()
        {
            if (done) {
                gameOver = true;
            }
            fd_set  input;
            FD_ZERO(&input);
            FD_SET(STDIN_FILENO, &input);
    
            timeval timeout{0, 0};
    
            if (select(STDIN_FILENO+1, &input, nullptr, nullptr, &timeout) > 0) {
                char key = std::cin.get();
                if (key == 'Q') {
                    gameOver = true;
                }
            }
            return false;
        }
        bool logic()
        {
            return false;
        }
    
        int install_done(const int signum)
        {
            struct sigaction  act {0};
            memset(&act, 0, sizeof act);
            sigemptyset(&act.sa_mask);
            act.sa_handler = handle_done;
            act.sa_flags = 0;
            if (sigaction(signum, &act, NULL) == -1) {
                return errno;
            }
            return 0;
        }
    
        public:
            Game()
                : gameOver(false)
            {
                if (install_done(SIGINT) || install_done(SIGTERM) || install_done(SIGHUP)) {
                    throw std::runtime_error("Fail: Installing handlers\n");
                }
                termios  config;
                if (!isatty(STDIN_FILENO) || tcgetattr(STDIN_FILENO, &originalConfig) || tcgetattr(STDIN_FILENO, &config)) {
                    throw std::runtime_error("Fail: Setting up input stream\n");
                }
    
                config.c_lflag &= ~(ICANON | ECHO);
                config.c_cc[VMIN] = 1; /* Blocking input */
                config.c_cc[VTIME] = 0;
                tcsetattr(STDIN_FILENO, TCSANOW, &config);
            }
            ~Game()
            {
                tcsetattr(STDIN_FILENO, TCSAFLUSH, &originalConfig);
            }
            void run()
            {
                std::cout << clearScreen
                          << hideCursor;
    
                bool dirty = true;
    
                while (!gameOver)
                {
                    draw(dirty);
                    dirty   = input();
                    dirty   = logic() || dirty;
                }
    
                std::cout << showCursor;
            }
    };
    
    int main()
    {
        Game    game;
    
        game.run();
    }