initial commit

This commit is contained in:
2025-05-21 11:29:48 -04:00
commit 614ada45fd
19 changed files with 1093 additions and 0 deletions

263
src/Aquarium.cpp Normal file
View File

@@ -0,0 +1,263 @@
#include "Aquarium.h"
#include "Random.h"
#include "defs.h"
#include <algorithm>
#include <iostream>
#include <ncurses.h>
int g_maxCells = 0;
Aquarium::Aquarium() {
initscr();
noecho();
cbreak();
nodelay(stdscr, TRUE);
curs_set(0);
initColors();
initColorLookup();
timeout(100);
getmaxyx(stdscr, height, width);
frontBuffer.assign(height, std::vector<Cell>(width));
backBuffer.assign(height, std::vector<Cell>(width));
layeredMap.assign(height, std::vector<LayeredCell>(width));
}
void Aquarium::initColors() {
if (has_colors()) {
start_color();
init_pair(1, COLOR_RED, COLOR_BLACK); // 'r'
init_pair(2, COLOR_GREEN, COLOR_BLACK); // 'g'
init_pair(3, COLOR_YELLOW, COLOR_BLACK); // 'y'
init_pair(4, COLOR_BLUE, COLOR_BLACK); // 'b'
init_pair(5, COLOR_MAGENTA, COLOR_BLACK); // 'm'
init_pair(6, COLOR_CYAN, COLOR_BLACK); // 'c'
init_pair(7, COLOR_WHITE, COLOR_BLACK); // 'w'
init_pair(8, COLOR_BLACK, COLOR_BLACK); // 'k'
}
}
short colorLookup[256];
void Aquarium::initColorLookup() {
for (int i = 0; i < 256; ++i)
colorLookup[i] = 8; // default to 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::resize() {
clear();
getmaxyx(stdscr, height, width);
if (g_maxCells && height * width > g_maxCells) {
endwin(); // Cleanly shut down ncurses
std::cerr << "Error: Terminal too large. Maximum allowed area is "
<< g_maxCells << " cells, but current size is "
<< (height * width) << ".\n";
std::exit(1);
}
frontBuffer.assign(height, std::vector<Cell>(width));
backBuffer.assign(height, std::vector<Cell>(width));
layeredMap.assign(height, std::vector<LayeredCell>(width));
fishes.clear();
bubbles.clear();
seaweeds.clear();
addWaterline();
addCastle();
for (int i = 0; i < width / 15; i++)
addSeaweed();
for (int i = 0; i < width * (height - 9) / 350; i++)
addFish();
}
Aquarium::~Aquarium() { endwin(); }
void Aquarium::redraw() {
clearBackBuffer();
for (auto &row : layeredMap)
for (auto &cell : row)
cell.layers.clear();
for (auto it = bubbles.begin(); it != bubbles.end();) {
auto &bubble = *it;
bubble->draw();
bubble->update();
if (bubble->getY() < 9)
it = bubbles.erase(it);
else
++it;
}
castle->draw();
for (auto it = seaweeds.begin(); it != seaweeds.end();) {
auto &seaweed = *it;
seaweed->draw();
seaweed->update();
if (seaweed->getLifetime() < 0) {
it = seaweeds.erase(it);
addSeaweed();
} else {
++it;
}
}
int baseFishLayer = 10;
for (auto it = fishes.begin(); it != fishes.end();) { // use an iterator
auto &fish = *it;
fish->draw(baseFishLayer +
static_cast<int>(std::distance(fishes.begin(), it)));
fish->update();
float fx = fish->getX();
if (Random::floatInRange(0, 1) < BUBBLE_SPAWN_CHANCE) {
addBubble(fx, fish->getY());
}
if (fx > width || fx < -30) {
it = fishes.erase(it); // erase and update iterator
addFish();
} else {
++it; // only increment if not erasing
}
}
waterline->draw();
waterline->update();
applyBackBuffer();
}
void Aquarium::addBubble(float x, float y) {
bubbles.emplace_back(std::make_unique<Bubble>(x, y));
}
void Aquarium::addWaterline() { waterline = std::make_unique<Waterline>(); };
void Aquarium::addSeaweed() {
seaweeds.emplace_back(std::make_unique<Seaweed>());
}
void Aquarium::addCastle() { castle = std::make_unique<Castle>(); }
void Aquarium::addFish() { fishes.emplace_back(std::make_unique<Fish>()); }
void Aquarium::clearBackBuffer() {
for (auto &row : backBuffer)
std::fill(row.begin(), row.end(), Cell());
}
inline char fastToLower(char c) { return (c >= 'A' && c <= 'Z') ? c + 32 : c; }
inline bool fastIsUpper(char c) { return (c >= 'A' && c <= 'Z'); }
void Aquarium::drawToBackBuffer(int y, int x, int layer,
const std::string &line,
const std::string &colorLine) {
if (y < 0 || y >= height)
return;
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 = fastIsUpper(static_cast<unsigned char>(colorChar));
Cell cell{
ch,
static_cast<char>(fastToLower(static_cast<unsigned char>(colorChar))),
isBold};
auto &cellLayers = layeredMap[y][cx].layers;
bool replaced = false;
for (auto &p : cellLayers) {
if (p.first == layer) {
p.second = cell;
replaced = true;
break;
}
}
if (!replaced) {
cellLayers.emplace_back(layer, cell);
}
// Set back buffer to topmost layer (assume highest layer is last)
if (!cellLayers.empty()) {
backBuffer[y][cx] = cellLayers.back().second;
}
}
}
void Aquarium::removeFromBackBuffer(int y, int x, int layer,
const std::string &line) {
for (size_t j = 0; j < line.size(); ++j) {
int cx = x + static_cast<int>(j);
if (y < 0 || y >= height || cx < 0 || cx >= width)
continue;
auto &cellLayers = layeredMap[y][cx].layers;
auto it = std::find_if(cellLayers.begin(), cellLayers.end(),
[layer](const auto &p) { return p.first == layer; });
if (it != cellLayers.end())
cellLayers.erase(it);
if (!cellLayers.empty())
backBuffer[y][cx] = cellLayers.back().second;
else
backBuffer[y][cx] = Cell(); // Clear
}
}
void Aquarium::applyBackBuffer() {
for (int y = 0; y < height; ++y) {
std::string rowStr;
int lastPair = -1;
bool lastBold = false;
move(y, 0);
for (int x = 0; x < width; ++x) {
const Cell &newCell = backBuffer[y][x];
Cell &oldCell = frontBuffer[y][x];
if (newCell != oldCell) {
oldCell = newCell;
int pairId = colorLookup[static_cast<unsigned char>(newCell.colorChar)];
bool bold = newCell.bold;
// Change attr if needed
if (pairId != lastPair || bold != lastBold) {
attrset(COLOR_PAIR(pairId) | (bold ? A_BOLD : A_NORMAL));
lastPair = pairId;
lastBold = bold;
}
addch(newCell.ch);
} else {
// Still move the cursor to stay aligned
move(y, x + 1);
}
}
}
refresh();
}