#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 int g_maxCells = 0; Aquarium::Aquarium() { initscr(); noecho(); cbreak(); nodelay(stdscr, TRUE); curs_set(0); initColors(); timeout(100); getmaxyx(stdscr, height, width); currentFrame.assign(height, std::vector(width)); previousFrame.assign(height, std::vector(width)); if (!colorLookupInitialized) { initColorLookup(); colorLookupInitialized = true; } } 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(); getmaxyx(stdscr, height, width); 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); } 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 (Ship, SeaMonster, Whale) 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] = 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; } void Aquarium::renderToScreen() { 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(y, x); int colorPair = colorLookup[static_cast(newCell.colorChar)]; attrset(COLOR_PAIR(colorPair) | (newCell.bold ? A_BOLD : A_NORMAL)); addch(newCell.ch); } } } refresh(); } 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); } } Aquarium::~Aquarium() { endwin(); }