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