commit e83c6533788ac9f7bc4e030dfb08bfbc57010fde
parent 729efd457b15b460208011abe362a73b0b41cf71
Author: amrfti <andrew@kloet.net>
Date: Wed, 9 Jul 2025 02:50:41 +0300
Merge pull request 'termios' (#4) from termios into master
Reviewed-on: https://git.kloet.net/amrfti/fissh/pulls/4
Diffstat:
4 files changed, 175 insertions(+), 79 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,7 +1,7 @@
# Compiler and flags
CXX = g++
-CXXFLAGS = -std=c++17 -Wall -Wextra -O3
-LDFLAGS = -lncurses -ltinfo
+CXXFLAGS = -Wall -Wextra -O3
+LDFLAGS = -static
# Directories
SRC_DIR = src
diff --git a/src/Aquarium.cpp b/src/Aquarium.cpp
@@ -8,19 +8,88 @@
#include "Waterline.h"
#include "Whale.h"
#include <algorithm>
+#include <cstdio>
+#include <cstring>
#include <iostream>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+// ANSI color codes
+namespace ANSI {
+const char *RESET = "\033[0m";
+const char *BOLD = "\033[1m";
+const char *CLEAR_SCREEN = "\033[2J";
+const char *CURSOR_HOME = "\033[H";
+const char *HIDE_CURSOR = "\033[?25l";
+const char *SHOW_CURSOR = "\033[?25h";
+
+// Colors (foreground)
+const char *BLACK = "\033[30m";
+const char *RED = "\033[31m";
+const char *GREEN = "\033[32m";
+const char *YELLOW = "\033[33m";
+const char *BLUE = "\033[34m";
+const char *MAGENTA = "\033[35m";
+const char *CYAN = "\033[36m";
+const char *WHITE = "\033[37m";
+// Colors (background)
+const char *BG_BLACK = "\033[40m";
+const char *RESET_BLACK_BG = "\033[0;40m";
+
+// Move cursor to position
+std::string moveTo(int row, int col) {
+ char buffer[32];
+ snprintf(buffer, sizeof(buffer), "\033[%d;%dH", row + 1, col + 1);
+ return std::string(buffer);
+}
+} // namespace ANSI
-int g_maxCells = 0;
+// Global terminal state
+static struct termios original_termios;
+static bool termios_saved = false;
+
+// Signal handler for cleanup
+void cleanup_terminal(int sig) {
+ if (termios_saved) {
+ tcsetattr(STDIN_FILENO, TCSANOW, &original_termios);
+ }
+ printf("%s%s", ANSI::SHOW_CURSOR, ANSI::RESET);
+ fflush(stdout);
+ if (sig != 0) {
+ exit(sig);
+ }
+}
Aquarium::Aquarium() {
- initscr();
- noecho();
- cbreak();
- nodelay(stdscr, TRUE);
- curs_set(0);
- initColors();
- timeout(100);
- getmaxyx(stdscr, height, width);
+ // Save original terminal settings
+ if (tcgetattr(STDIN_FILENO, &original_termios) == 0) {
+ termios_saved = true;
+ }
+
+ // Set up signal handlers for cleanup
+ signal(SIGINT, cleanup_terminal);
+ signal(SIGTERM, cleanup_terminal);
+ signal(SIGQUIT, cleanup_terminal);
+
+ // Set terminal to raw mode
+ struct termios raw = original_termios;
+ raw.c_lflag &= ~(ECHO | ICANON | ISIG);
+ raw.c_iflag &= ~(IXON | ICRNL);
+ raw.c_oflag &= ~(OPOST);
+ raw.c_cc[VMIN] = 0; // Non-blocking read
+ raw.c_cc[VTIME] = 1; // 100ms timeout
+
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
+
+ // Initialize display
+ printf("%s%s%s%s", ANSI::CLEAR_SCREEN, ANSI::CURSOR_HOME, ANSI::HIDE_CURSOR,
+ ANSI::BG_BLACK);
+ fflush(stdout);
+
+ // Get terminal size
+ getTerminalSize();
currentFrame.assign(height, std::vector<Cell>(width));
previousFrame.assign(height, std::vector<Cell>(width));
@@ -31,6 +100,18 @@ Aquarium::Aquarium() {
}
}
+void Aquarium::getTerminalSize() {
+ struct winsize ws;
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) {
+ height = ws.ws_row;
+ width = ws.ws_col;
+ } else {
+ // Fallback
+ height = 24;
+ width = 80;
+ }
+}
+
void Aquarium::ensureEntitiesSorted() {
if (entities_need_sorting) {
std::sort(entities.begin(), entities.end(),
@@ -49,7 +130,6 @@ void Aquarium::redraw() {
clearCurrentFrame();
ensureBigEntityExists();
- // Use static vectors to avoid per-frame allocations
static std::vector<std::unique_ptr<Entity>> newEntities;
static std::vector<size_t> entitiesToRemove;
@@ -109,16 +189,10 @@ void Aquarium::redraw() {
}
void Aquarium::resize() {
- clear();
- getmaxyx(stdscr, height, width);
+ printf("%s%s%s", ANSI::CLEAR_SCREEN, ANSI::CURSOR_HOME, ANSI::BG_BLACK);
+ fflush(stdout);
- if (g_maxCells && height * width > g_maxCells) {
- endwin();
- std::cerr << "Error: Terminal too large. Maximum allowed area is "
- << g_maxCells << " cells, but current size is "
- << (height * width) << ".\n";
- std::exit(1);
- }
+ getTerminalSize();
currentFrame.assign(height, std::vector<Cell>(width));
previousFrame.assign(height, std::vector<Cell>(width));
@@ -153,7 +227,7 @@ void Aquarium::ensureBigEntityExists() {
}
}
- // No big entity found, spawn next in cycle (Ship, SeaMonster, Whale)
+ // No big entity found, spawn next in cycle
int entity_type = big_entity_index % 3;
if (entity_type == 0) {
addEntityImpl<Ship>();
@@ -191,18 +265,23 @@ void Aquarium::drawToFrame(int y, int x, const std::string &line,
void Aquarium::initColorLookup() {
for (int i = 0; i < 256; ++i)
- colorLookup[i] = 8; // Default black
- colorLookup['r'] = 1;
- colorLookup['g'] = 2;
- colorLookup['y'] = 3;
- colorLookup['b'] = 4;
- colorLookup['m'] = 5;
- colorLookup['c'] = 6;
- colorLookup['w'] = 7;
- colorLookup['k'] = 8;
+ colorLookup[i] = ANSI::BLACK; // Default black
+
+ colorLookup['r'] = ANSI::RED;
+ colorLookup['g'] = ANSI::GREEN;
+ colorLookup['y'] = ANSI::YELLOW;
+ colorLookup['b'] = ANSI::BLUE;
+ colorLookup['m'] = ANSI::MAGENTA;
+ colorLookup['c'] = ANSI::CYAN;
+ colorLookup['w'] = ANSI::WHITE;
+ colorLookup['k'] = ANSI::BLACK;
}
void Aquarium::renderToScreen() {
+ static std::string output;
+ output.clear();
+ output.reserve(height * width * 20); // Reserve space for efficiency
+
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
const Cell &newCell = currentFrame[y][x];
@@ -210,30 +289,49 @@ void Aquarium::renderToScreen() {
if (newCell != oldCell) {
oldCell = newCell;
- move(y, x);
- int colorPair =
- colorLookup[static_cast<unsigned char>(newCell.colorChar)];
- attrset(COLOR_PAIR(colorPair) | (newCell.bold ? A_BOLD : A_NORMAL));
- addch(newCell.ch);
+ // Move cursor to position
+ output += ANSI::moveTo(y, x);
+
+ // Reset cell
+ output += ANSI::RESET_BLACK_BG;
+
+ // Set color and attributes
+ if (newCell.bold) {
+ output += ANSI::BOLD;
+ }
+ output += colorLookup[static_cast<unsigned char>(newCell.colorChar)];
+
+ // Add the character
+ output += newCell.ch;
}
}
}
- refresh();
+
+ // Output everything at once
+ if (!output.empty()) {
+ std::cout << output << std::flush;
+ }
+}
+
+// Check for input (non-blocking)
+int Aquarium::checkInput() {
+ char c;
+ if (read(STDIN_FILENO, &c, 1) == 1) {
+ return c;
+ }
+ return -1; // No input available
}
-void Aquarium::initColors() {
- if (has_colors()) {
- start_color();
- init_pair(1, COLOR_RED, COLOR_BLACK);
- init_pair(2, COLOR_GREEN, COLOR_BLACK);
- init_pair(3, COLOR_YELLOW, COLOR_BLACK);
- init_pair(4, COLOR_BLUE, COLOR_BLACK);
- init_pair(5, COLOR_MAGENTA, COLOR_BLACK);
- init_pair(6, COLOR_CYAN, COLOR_BLACK);
- init_pair(7, COLOR_WHITE, COLOR_BLACK);
- init_pair(8, COLOR_BLACK, COLOR_BLACK);
+// Check if terminal was resized
+bool Aquarium::checkResize() {
+ struct winsize ws;
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) {
+ if (ws.ws_row != height || ws.ws_col != width) {
+ return true;
+ }
}
+ return false;
}
-Aquarium::~Aquarium() { endwin(); }
+Aquarium::~Aquarium() { cleanup_terminal(0); }
diff --git a/src/Aquarium.h b/src/Aquarium.h
@@ -1,7 +1,6 @@
#pragma once
#include "Entity.h"
#include <memory>
-#include <ncurses.h>
#include <vector>
extern int g_maxCells;
@@ -15,7 +14,6 @@ private:
char ch = ' ';
char colorChar = 'k';
bool bold = false;
-
bool operator==(const Cell &other) const {
return ch == other.ch && colorChar == other.colorChar &&
bold == other.bold;
@@ -28,9 +26,8 @@ private:
std::vector<std::unique_ptr<Entity>> entities;
size_t big_entity_index = 0;
void ensureBigEntityExists();
-
bool entities_need_sorting = true;
- static inline short colorLookup[256] = {0};
+ static inline const char *colorLookup[256] = {nullptr};
static inline bool colorLookupInitialized = false;
public:
@@ -53,17 +50,21 @@ public:
void addShip();
void addSeaMonster();
void addWhale();
-
void redraw();
void initColors();
void resize();
void drawToFrame(int y, int x, const std::string &line,
const std::string &colorLine);
+ // New termios-specific methods
+ int checkInput(); // Returns character code or -1 if no input
+ bool checkResize(); // Returns true if terminal was resized
+
private:
void clearCurrentFrame();
void renderToScreen();
void ensureEntitiesSorted();
+ void getTerminalSize();
static void initColorLookup();
template <typename T, typename... Args> void addEntityImpl(Args &&...args) {
diff --git a/src/main.cpp b/src/main.cpp
@@ -1,35 +1,32 @@
+// main.cpp
#include "Aquarium.h"
-#include <cstdio>
-#include <cstdlib>
-#include <unistd.h>
-int main(int argc, char *argv[]) {
- int opt;
- while ((opt = getopt(argc, argv, "r:")) != -1) {
- switch (opt) {
- case 'r':
- g_maxCells = std::atoi(optarg);
- break;
- default:
- fprintf(stderr, "Usage: %s [-r max_cells]\n", argv[0]);
- return 1;
- }
+int main() {
+ // Get the singleton instance
+ Aquarium &aquarium = Aquarium::getInstance();
+
+#ifdef __OpenBSD__
+ if (pledge("stdio tty", NULL) == -1) {
+ perror("pledge");
+ return 1;
}
+#endif
- Aquarium &aquarium = Aquarium::getInstance();
- aquarium.resize();
+ // Initialize the aquarium display
+ aquarium.resize(); // Setup initial entities
+ // Main game loop
while (true) {
- aquarium.redraw();
- int ch = getch();
- if (ch != ERR) {
- if (ch == 'q')
- break;
- if (ch == 'r')
- aquarium.resize();
- flushinp();
- usleep(100000);
+ int input = aquarium.checkInput();
+ if (input == 'q' || input == 'Q' || input == 27) { // ESC key
+ break;
+ }
+
+ if (aquarium.checkResize()) {
+ aquarium.resize();
}
+
+ aquarium.redraw();
}
return 0;