Skip to main content
Became Hot Network Question
added 377 characters in body
Source Link
Loki Astari
  • 96.6k
  • 5
  • 125
  • 338

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);

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);

Source Link
Loki Astari
  • 96.6k
  • 5
  • 125
  • 338

Terminal based game

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).

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();
}