Files
fissh/src/Aquarium.cpp
2025-07-08 13:25:56 -04:00

240 lines
6.4 KiB
C++

#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 <algorithm>
#include <iostream>
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<Cell>(width));
previousFrame.assign(height, std::vector<Cell>(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<std::unique_ptr<Entity>> newEntities;
static std::vector<size_t> 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<Fish *>(entity.get())) {
if (fish->shouldSpawnBubble()) {
newEntities.emplace_back(
std::make_unique<Bubble>(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<Cell>(width));
previousFrame.assign(height, std::vector<Cell>(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<Fish>(); }
void Aquarium::addBubble(float x, float y) { addEntityImpl<Bubble>(x, y); }
void Aquarium::addSeaweed() { addEntityImpl<Seaweed>(); }
void Aquarium::addWaterline() { addEntityImpl<Waterline>(); }
void Aquarium::addCastle() { addEntityImpl<Castle>(); }
void Aquarium::addShip() { addEntityImpl<Ship>(); }
void Aquarium::addSeaMonster() { addEntityImpl<SeaMonster>(); }
void Aquarium::addWhale() { addEntityImpl<Whale>(); }
void Aquarium::ensureBigEntityExists() {
// Check if any big entities exist on screen
for (const auto &entity : entities) {
if (dynamic_cast<Ship *>(entity.get()) ||
dynamic_cast<SeaMonster *>(entity.get()) ||
dynamic_cast<Whale *>(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<Ship>();
} else if (entity_type == 1) {
addEntityImpl<SeaMonster>();
} else {
addEntityImpl<Whale>();
}
++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<int>(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<char>(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<unsigned char>(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(); }