diff --git a/Makefile b/Makefile index 28bc548..29eac28 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Compiler and flags CXX = g++ CXXFLAGS = -std=c++17 -Wall -Wextra -O3 -LDFLAGS = -static -lncurses -ltinfo +LDFLAGS = -static # Directories SRC_DIR = src diff --git a/src/Aquarium.cpp b/src/Aquarium.cpp index ea67c77..209f2e7 100644 --- a/src/Aquarium.cpp +++ b/src/Aquarium.cpp @@ -15,9 +15,7 @@ #include #include #include - int g_maxCells = 0; - // ANSI color codes namespace ANSI { const char *RESET = "\033[0m"; @@ -26,7 +24,6 @@ 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"; @@ -36,7 +33,17 @@ const char *BLUE = "\033[34m"; const char *MAGENTA = "\033[35m"; const char *CYAN = "\033[36m"; const char *WHITE = "\033[37m"; - +// Background colors +const char *BG_BLACK = "\033[40m"; +const char *BG_RED = "\033[41m"; +const char *BG_GREEN = "\033[42m"; +const char *BG_YELLOW = "\033[43m"; +const char *BG_BLUE = "\033[44m"; +const char *BG_MAGENTA = "\033[45m"; +const char *BG_CYAN = "\033[46m"; +const char *BG_WHITE = "\033[47m"; +// Combined reset with black background +const char *RESET_BLACK_BG = "\033[0;40m"; // Move cursor to position std::string moveTo(int row, int col) { char buffer[32]; @@ -44,11 +51,9 @@ std::string moveTo(int row, int col) { return std::string(buffer); } } // namespace ANSI - // 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) { @@ -60,18 +65,15 @@ void cleanup_terminal(int sig) { exit(sig); } } - Aquarium::Aquarium() { // 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); @@ -79,37 +81,31 @@ Aquarium::Aquarium() { 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", ANSI::CLEAR_SCREEN, ANSI::CURSOR_HOME, ANSI::HIDE_CURSOR); + // Initialize display with black background + 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(width)); previousFrame.assign(height, std::vector(width)); - if (!colorLookupInitialized) { initColorLookup(); colorLookupInitialized = true; } } - void Aquarium::getTerminalSize() { struct winsize ws; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) { height = ws.ws_row; width = ws.ws_col; } else { - // Fallback to reasonable defaults + // Fallback height = 24; width = 80; } } - void Aquarium::ensureEntitiesSorted() { if (entities_need_sorting) { std::sort(entities.begin(), entities.end(), @@ -123,23 +119,18 @@ void Aquarium::ensureEntitiesSorted() { entities_need_sorting = false; } } - void Aquarium::redraw() { clearCurrentFrame(); ensureBigEntityExists(); - // Use static vectors to avoid per-frame allocations static std::vector> newEntities; static std::vector entitiesToRemove; - newEntities.clear(); entitiesToRemove.clear(); - // Update all entities and collect changes for (size_t i = 0; i < entities.size(); ++i) { auto &entity = entities[i]; entity->update(); - // Handle fish bubble spawning if (auto *fish = dynamic_cast(entity.get())) { if (fish->shouldSpawnBubble()) { @@ -147,7 +138,6 @@ void Aquarium::redraw() { std::make_unique(fish->getX(), fish->getY())); } } - if (entity->shouldBeRemoved()) { auto replacement = entity->createReplacement(); if (replacement) { @@ -158,41 +148,33 @@ void Aquarium::redraw() { } } } - // Remove entities in reverse order to maintain indices for (auto it = entitiesToRemove.rbegin(); it != entitiesToRemove.rend(); ++it) { entities.erase(entities.begin() + *it); entities_need_sorting = true; } - // Add new entities if we have them if (!newEntities.empty()) { // Reserve space to minimize reallocations entities.reserve(entities.size() + newEntities.size()); - for (auto &newEntity : newEntities) { entities.emplace_back(std::move(newEntity)); } entities_need_sorting = true; } - ensureEntitiesSorted(); - // Draw all entities for (const auto &entity : entities) { entity->draw(); } - renderToScreen(); } - void Aquarium::resize() { - printf("%s%s", ANSI::CLEAR_SCREEN, ANSI::CURSOR_HOME); + // Clear screen and set black background + printf("%s%s%s", ANSI::CLEAR_SCREEN, ANSI::CURSOR_HOME, ANSI::BG_BLACK); fflush(stdout); - getTerminalSize(); - if (g_maxCells && height * width > g_maxCells) { cleanup_terminal(0); std::cerr << "Error: Terminal too large. Maximum allowed area is " @@ -200,13 +182,10 @@ void Aquarium::resize() { << (height * width) << ".\n"; std::exit(1); } - currentFrame.assign(height, std::vector(width)); previousFrame.assign(height, std::vector(width)); - entities.clear(); entities_need_sorting = true; - addWaterline(); addCastle(); for (int i = 0; i < width / 15; i++) @@ -214,7 +193,6 @@ void Aquarium::resize() { for (int i = 0; i < width * (height - 9) / 350; i++) addFish(); } - void Aquarium::addFish() { addEntityImpl(); } void Aquarium::addBubble(float x, float y) { addEntityImpl(x, y); } void Aquarium::addSeaweed() { addEntityImpl(); } @@ -223,7 +201,6 @@ void Aquarium::addCastle() { addEntityImpl(); } void Aquarium::addShip() { addEntityImpl(); } void Aquarium::addSeaMonster() { addEntityImpl(); } void Aquarium::addWhale() { addEntityImpl(); } - void Aquarium::ensureBigEntityExists() { // Check if any big entities exist on screen for (const auto &entity : entities) { @@ -233,8 +210,7 @@ void Aquarium::ensureBigEntityExists() { return; // Big entity found, do nothing } } - - // 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(); @@ -245,35 +221,28 @@ void Aquarium::ensureBigEntityExists() { } ++big_entity_index; } - void Aquarium::clearCurrentFrame() { for (auto &row : currentFrame) { std::fill(row.begin(), row.end(), Cell()); } } - void Aquarium::drawToFrame(int y, int x, const std::string &line, const std::string &colorLine) { const size_t len = std::min(line.size(), colorLine.size()); - for (size_t j = 0; j < len; ++j) { int cx = x + static_cast(j); if (cx < 0 || cx >= width) continue; - const char ch = line[j]; const char colorChar = colorLine[j]; const bool isBold = (colorChar >= 'A' && colorChar <= 'Z'); - currentFrame[y][cx] = { ch, static_cast(isBold ? colorChar + 32 : colorChar), isBold}; } } - void Aquarium::initColorLookup() { for (int i = 0; i < 256; ++i) colorLookup[i] = ANSI::BLACK; // Default black - colorLookup['r'] = ANSI::RED; colorLookup['g'] = ANSI::GREEN; colorLookup['y'] = ANSI::YELLOW; @@ -283,48 +252,34 @@ void Aquarium::initColorLookup() { colorLookup['w'] = ANSI::WHITE; colorLookup['k'] = ANSI::BLACK; } - -void Aquarium::initColors() { - // This function is kept for compatibility but does nothing - // since we're using ANSI colors directly -} - 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]; Cell &oldCell = previousFrame[y][x]; - if (newCell != oldCell) { oldCell = newCell; - // Move cursor to position output += ANSI::moveTo(y, x); - - // Set color and attributes - output += ANSI::RESET; // Reset first + // Set color and attributes with black background + output += ANSI::RESET_BLACK_BG; // Reset with black background if (newCell.bold) { output += ANSI::BOLD; } output += colorLookup[static_cast(newCell.colorChar)]; - // Add the character output += newCell.ch; } } } - - // Output everything at once for better performance + // Output everything at once if (!output.empty()) { - printf("%s", output.c_str()); - fflush(stdout); + std::cout << output << std::flush; } } - // Check for input (non-blocking) int Aquarium::checkInput() { char c; @@ -333,8 +288,7 @@ int Aquarium::checkInput() { } return -1; // No input available } - -// Check if terminal was resized (you'll need to call this periodically) +// Check if terminal was resized bool Aquarium::checkResize() { struct winsize ws; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) { @@ -344,5 +298,4 @@ bool Aquarium::checkResize() { } return false; } - Aquarium::~Aquarium() { cleanup_terminal(0); } diff --git a/src/main.cpp b/src/main.cpp index b749ffe..aadd1f1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,18 +1,11 @@ // main.cpp #include "Aquarium.h" -#ifdef __OpenBSD__ -#include -#endif -#include -#include -#include int main() { // Get the singleton instance Aquarium &aquarium = Aquarium::getInstance(); #ifdef __OpenBSD__ - // Most restrictive pledge - no file access needed! if (pledge("stdio tty", NULL) == -1) { perror("pledge"); return 1; @@ -24,22 +17,16 @@ int main() { // Main game loop while (true) { - // Check for user input int input = aquarium.checkInput(); if (input == 'q' || input == 'Q' || input == 27) { // ESC key break; } - // Check if terminal was resized if (aquarium.checkResize()) { aquarium.resize(); } - // Redraw the aquarium aquarium.redraw(); - - // Control frame rate (~20 FPS) - // std::this_thread::sleep_for(std::chrono::milliseconds(1)); } return 0;