#include "Aquarium.h" #include "Bubble.h" #include "Castle.h" #include "Fish.h" #include "SeaMonster.h" #include "Seaweed.h" #include "Ship.h" #include "Waterline.h" #include "Whale.h" #include #include #include #include #include #include #include #include int g_maxCells = 0; // 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"; // 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]; snprintf(buffer, sizeof(buffer), "\033[%d;%dH", row + 1, col + 1); 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) { tcsetattr(STDIN_FILENO, TCSANOW, &original_termios); } printf("%s%s", ANSI::SHOW_CURSOR, ANSI::RESET); fflush(stdout); if (sig != 0) { 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); 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 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 height = 24; width = 80; } } void Aquarium::ensureEntitiesSorted() { if (entities_need_sorting) { std::sort(entities.begin(), entities.end(), [](const auto &a, const auto &b) { int layerA = a->getPreferredLayer(); int layerB = b->getPreferredLayer(); if (layerA != layerB) return layerA < layerB; return a->getId() < b->getId(); }); 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()) { newEntities.emplace_back( std::make_unique(fish->getX(), fish->getY())); } } if (entity->shouldBeRemoved()) { auto replacement = entity->createReplacement(); if (replacement) { entity = std::move(replacement); // Replace in-place entities_need_sorting = true; } else { entitiesToRemove.push_back(i); // Mark for removal } } } // 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 " << g_maxCells << " cells, but current size is " << (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++) addSeaweed(); 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(); } void Aquarium::addWaterline() { addEntityImpl(); } 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) { if (dynamic_cast(entity.get()) || dynamic_cast(entity.get()) || dynamic_cast(entity.get())) { 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) { addEntityImpl(); } else if (entity_type == 1) { addEntityImpl(); } else { addEntityImpl(); } ++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; 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]; 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 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; if (read(STDIN_FILENO, &c, 1) == 1) { return c; } return -1; // No input available } // 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() { cleanup_terminal(0); }