From 2f44398664a41d1d0ee52b9eec267a60bdb9bfa1 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 8 Jul 2025 17:30:28 -0400 Subject: [PATCH] cleanup --- src/Aquarium.cpp | 73 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/src/Aquarium.cpp b/src/Aquarium.cpp index 209f2e7..b459523 100644 --- a/src/Aquarium.cpp +++ b/src/Aquarium.cpp @@ -15,7 +15,9 @@ #include #include #include + int g_maxCells = 0; + // ANSI color codes namespace ANSI { const char *RESET = "\033[0m"; @@ -24,6 +26,7 @@ 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"; @@ -33,17 +36,10 @@ const char *BLUE = "\033[34m"; const char *MAGENTA = "\033[35m"; const char *CYAN = "\033[36m"; const char *WHITE = "\033[37m"; -// Background colors +// Colors (background) 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]; @@ -51,9 +47,11 @@ 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) { @@ -65,15 +63,18 @@ 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); @@ -81,20 +82,26 @@ 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 with black background + + // 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(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) { @@ -106,6 +113,7 @@ void Aquarium::getTerminalSize() { width = 80; } } + void Aquarium::ensureEntitiesSorted() { if (entities_need_sorting) { std::sort(entities.begin(), entities.end(), @@ -119,18 +127,23 @@ 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()) { @@ -138,6 +151,7 @@ void Aquarium::redraw() { std::make_unique(fish->getX(), fish->getY())); } } + if (entity->shouldBeRemoved()) { auto replacement = entity->createReplacement(); if (replacement) { @@ -148,33 +162,41 @@ 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() { - // 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 " @@ -182,10 +204,13 @@ 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++) @@ -193,6 +218,7 @@ 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(); } @@ -201,6 +227,7 @@ 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) { @@ -210,6 +237,7 @@ void Aquarium::ensureBigEntityExists() { return; // Big entity found, do nothing } } + // No big entity found, spawn next in cycle int entity_type = big_entity_index % 3; if (entity_type == 0) { @@ -221,28 +249,35 @@ 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; @@ -252,34 +287,44 @@ void Aquarium::initColorLookup() { 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]; Cell &oldCell = previousFrame[y][x]; + if (newCell != oldCell) { oldCell = newCell; + // Move cursor to position output += ANSI::moveTo(y, x); - // Set color and attributes with black background - output += ANSI::RESET_BLACK_BG; // Reset with black background + + // Reset cell + output += ANSI::RESET_BLACK_BG; + + // Set color and attributes if (newCell.bold) { output += ANSI::BOLD; } output += colorLookup[static_cast(newCell.colorChar)]; + // Add the character output += newCell.ch; } } } + // Output everything at once if (!output.empty()) { std::cout << output << std::flush; } } + // Check for input (non-blocking) int Aquarium::checkInput() { char c; @@ -288,6 +333,7 @@ int Aquarium::checkInput() { } return -1; // No input available } + // Check if terminal was resized bool Aquarium::checkResize() { struct winsize ws; @@ -298,4 +344,5 @@ bool Aquarium::checkResize() { } return false; } + Aquarium::~Aquarium() { cleanup_terminal(0); }