Compare commits
8 Commits
4f44c2fc34
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e15b5db7ff | ||
|
|
78d6209818 | ||
| 75e147f511 | |||
|
|
2fe0538857 | ||
|
|
f236af2709 | ||
|
|
16d8858841 | ||
|
|
8342e60547 | ||
|
|
b36242c78d |
36
Makefile
36
Makefile
@@ -1,40 +1,36 @@
|
|||||||
# Compiler and flags
|
CXX = c++
|
||||||
CXX = g++
|
CXXFLAGS = -Wall -Wextra -O3 -Isrc
|
||||||
CXXFLAGS = -Wall -Wextra -O3
|
|
||||||
LDFLAGS = -static
|
LDFLAGS = -static
|
||||||
|
|
||||||
# Directories
|
|
||||||
SRC_DIR = src
|
SRC_DIR = src
|
||||||
OBJ_DIR = build
|
OBJ_DIR = build
|
||||||
BIN_DIR = bin
|
BIN_DIR = bin
|
||||||
|
|
||||||
# File extensions
|
|
||||||
SRC_EXT = cpp
|
SRC_EXT = cpp
|
||||||
OBJ_EXT = o
|
OBJ_EXT = o
|
||||||
|
|
||||||
# Find all source files and generate object files
|
# Find all source files recursively in subdirectories
|
||||||
SOURCES = $(wildcard $(SRC_DIR)/*.$(SRC_EXT))
|
SOURCES = $(shell find $(SRC_DIR) -name '*.$(SRC_EXT)')
|
||||||
|
# Create corresponding object files maintaining directory structure
|
||||||
OBJECTS = $(SOURCES:$(SRC_DIR)/%.$(SRC_EXT)=$(OBJ_DIR)/%.$(OBJ_EXT))
|
OBJECTS = $(SOURCES:$(SRC_DIR)/%.$(SRC_EXT)=$(OBJ_DIR)/%.$(OBJ_EXT))
|
||||||
|
|
||||||
# Output executable
|
|
||||||
EXEC = $(BIN_DIR)/fissh
|
EXEC = $(BIN_DIR)/fissh
|
||||||
|
|
||||||
# Default target: build everything
|
# Get all unique subdirectories for build structure
|
||||||
|
OBJ_DIRS = $(sort $(dir $(OBJECTS)))
|
||||||
|
|
||||||
all: $(EXEC)
|
all: $(EXEC)
|
||||||
|
|
||||||
# Rule to link the object files into the final executable
|
|
||||||
$(EXEC): $(OBJECTS)
|
$(EXEC): $(OBJECTS)
|
||||||
@mkdir -p $(BIN_DIR) # Make sure the bin dir exists
|
@mkdir -p $(BIN_DIR)
|
||||||
$(CXX) $(OBJECTS) -o $(EXEC) $(LDFLAGS)
|
$(CXX) $(OBJECTS) -o $(EXEC) $(LDFLAGS)
|
||||||
|
|
||||||
# Rule to compile .cpp files into .o files
|
# Create object files, maintaining directory structure
|
||||||
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.$(SRC_EXT)
|
$(OBJ_DIR)/%.$(OBJ_EXT): $(SRC_DIR)/%.$(SRC_EXT)
|
||||||
@mkdir -p $(OBJ_DIR) # Make sure the obj dir exists
|
@mkdir -p $(dir $@)
|
||||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||||
|
|
||||||
# Clean up the build directory
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(OBJ_DIR) $(BIN_DIR)
|
rm -rf $(OBJ_DIR) $(BIN_DIR)
|
||||||
|
|
||||||
# Phony targets
|
install: $(EXEC)
|
||||||
.PHONY: all clean run
|
install -m 755 $(EXEC) /usr/local/bin/
|
||||||
|
|
||||||
|
.PHONY: all clean install
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../Entity.h"
|
#include "../entities/Entity.h"
|
||||||
#include "../SpriteUtils.h"
|
#include "../utils/SpriteUtils.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
/*
|
||||||
|
Mask Definitions:
|
||||||
|
1: body
|
||||||
|
2: dorsal fin
|
||||||
|
3: flippers
|
||||||
|
4: eye
|
||||||
|
5: mouth
|
||||||
|
6: tailfin
|
||||||
|
7: gills
|
||||||
|
*/
|
||||||
|
|
||||||
inline const std::vector<AssetPair>& getFishAssets() {
|
inline const std::vector<AssetPair>& getFishAssets() {
|
||||||
static const std::vector<AssetPair> fishAssets = {
|
static const std::vector<AssetPair> fishAssets = {
|
||||||
{
|
{
|
||||||
@@ -99,8 +110,22 @@ inline const std::vector<AssetPair>& getFishAssets() {
|
|||||||
R"(6 11222 )",
|
R"(6 11222 )",
|
||||||
R"(6661111111 11111 )",
|
R"(6661111111 11111 )",
|
||||||
R"( 6 3 77 4 1)",
|
R"( 6 3 77 4 1)",
|
||||||
R"(6661111111311311111 )",
|
R"(6661111111311311111 )",}},
|
||||||
}}};
|
{
|
||||||
|
{
|
||||||
|
R"(???????,-.?????)",
|
||||||
|
R"(__ ????) \.????)",
|
||||||
|
R"(\ \_.!` '-.?)",
|
||||||
|
R"(?> _ |<( O <)",
|
||||||
|
R"(/_/?';,,....-`?)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
R"( 222 )",
|
||||||
|
R"(66 2 22 )",
|
||||||
|
R"(6 61111 111 )",
|
||||||
|
R"( 6 1 777 4 5)",
|
||||||
|
R"(666 '333111111 )",}}
|
||||||
|
};
|
||||||
return fishAssets;
|
return fishAssets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "../Entity.h"
|
#include "../entities/Entity.h"
|
||||||
#include "../SpriteUtils.h"
|
#include "../utils/SpriteUtils.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct SeaMonsterAsset {
|
struct SeaMonsterAsset {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "../Entity.h"
|
#include "../entities/Entity.h"
|
||||||
#include "../SpriteUtils.h"
|
#include "../utils/SpriteUtils.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
inline const AssetPair &getShip() {
|
inline const AssetPair &getShip() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "../Entity.h"
|
#include "../entities/Entity.h"
|
||||||
#include "../SpriteUtils.h"
|
#include "../utils/SpriteUtils.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct WhaleAsset {
|
struct WhaleAsset {
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
#include "Aquarium.h"
|
#include "Aquarium.h"
|
||||||
#include "Bubble.h"
|
#include "../core/Config.h"
|
||||||
#include "Castle.h"
|
#include "../entities/Bubble.h"
|
||||||
#include "Fish.h"
|
#include "../entities/Castle.h"
|
||||||
#include "SeaMonster.h"
|
#include "../entities/Fish.h"
|
||||||
#include "Seaweed.h"
|
#include "../entities/SeaMonster.h"
|
||||||
#include "Ship.h"
|
#include "../entities/Seaweed.h"
|
||||||
#include "Waterline.h"
|
#include "../entities/Ship.h"
|
||||||
#include "Whale.h"
|
#include "../entities/Waterline.h"
|
||||||
|
#include "../entities/Whale.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <sstream>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -75,7 +79,7 @@ Aquarium::Aquarium() {
|
|||||||
|
|
||||||
// Set terminal to raw mode
|
// Set terminal to raw mode
|
||||||
struct termios raw = original_termios;
|
struct termios raw = original_termios;
|
||||||
raw.c_lflag &= ~(ECHO | ICANON | ISIG);
|
raw.c_lflag &= ~(ECHO | ICANON);
|
||||||
raw.c_iflag &= ~(IXON | ICRNL);
|
raw.c_iflag &= ~(IXON | ICRNL);
|
||||||
raw.c_oflag &= ~(OPOST);
|
raw.c_oflag &= ~(OPOST);
|
||||||
raw.c_cc[VMIN] = 0; // Non-blocking read
|
raw.c_cc[VMIN] = 0; // Non-blocking read
|
||||||
@@ -106,9 +110,9 @@ void Aquarium::getTerminalSize() {
|
|||||||
height = ws.ws_row;
|
height = ws.ws_row;
|
||||||
width = ws.ws_col;
|
width = ws.ws_col;
|
||||||
} else {
|
} else {
|
||||||
// Fallback
|
cleanup_terminal(0);
|
||||||
height = 24;
|
std::cerr << "Error: Unable to determine terminal size.\n";
|
||||||
width = 80;
|
std::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +132,10 @@ void Aquarium::ensureEntitiesSorted() {
|
|||||||
|
|
||||||
void Aquarium::redraw() {
|
void Aquarium::redraw() {
|
||||||
clearCurrentFrame();
|
clearCurrentFrame();
|
||||||
ensureBigEntityExists();
|
|
||||||
|
if (g_config.enable_big_entities) {
|
||||||
|
ensureBigEntityExists();
|
||||||
|
}
|
||||||
|
|
||||||
static std::vector<std::unique_ptr<Entity>> newEntities;
|
static std::vector<std::unique_ptr<Entity>> newEntities;
|
||||||
static std::vector<size_t> entitiesToRemove;
|
static std::vector<size_t> entitiesToRemove;
|
||||||
@@ -136,26 +143,40 @@ void Aquarium::redraw() {
|
|||||||
newEntities.clear();
|
newEntities.clear();
|
||||||
entitiesToRemove.clear();
|
entitiesToRemove.clear();
|
||||||
|
|
||||||
|
// Count current bubbles
|
||||||
|
int bubble_count = 0;
|
||||||
|
for (const auto &entity : entities) {
|
||||||
|
if (dynamic_cast<Bubble *>(entity.get())) {
|
||||||
|
bubble_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update all entities and collect changes
|
// Update all entities and collect changes
|
||||||
for (size_t i = 0; i < entities.size(); ++i) {
|
for (size_t i = 0; i < entities.size(); ++i) {
|
||||||
auto &entity = entities[i];
|
auto &entity = entities[i];
|
||||||
entity->update();
|
entity->update();
|
||||||
|
|
||||||
// Handle fish bubble spawning
|
// Handle fish bubble spawning with configuration
|
||||||
if (auto *fish = dynamic_cast<Fish *>(entity.get())) {
|
if (g_config.enable_bubbles && bubble_count < g_config.max_bubbles) {
|
||||||
if (fish->shouldSpawnBubble()) {
|
if (auto *fish = dynamic_cast<Fish *>(entity.get())) {
|
||||||
newEntities.emplace_back(
|
if (fish->shouldSpawnBubble()) {
|
||||||
std::make_unique<Bubble>(fish->getX(), fish->getY()));
|
// Use configured bubble rate
|
||||||
|
if (rand() % g_config.fish_bubble_rate == 0) {
|
||||||
|
newEntities.emplace_back(
|
||||||
|
std::make_unique<Bubble>(fish->getX(), fish->getY()));
|
||||||
|
bubble_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity->shouldBeRemoved()) {
|
if (entity->shouldBeRemoved()) {
|
||||||
auto replacement = entity->createReplacement();
|
auto replacement = entity->createReplacement();
|
||||||
if (replacement) {
|
if (replacement) {
|
||||||
entity = std::move(replacement); // Replace in-place
|
entity = std::move(replacement);
|
||||||
entities_need_sorting = true;
|
entities_need_sorting = true;
|
||||||
} else {
|
} else {
|
||||||
entitiesToRemove.push_back(i); // Mark for removal
|
entitiesToRemove.push_back(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,11 +188,10 @@ void Aquarium::redraw() {
|
|||||||
entities_need_sorting = true;
|
entities_need_sorting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new entities if we have them
|
// Add new entities if we have them and haven't exceeded max
|
||||||
if (!newEntities.empty()) {
|
if (!newEntities.empty() &&
|
||||||
// Reserve space to minimize reallocations
|
entities.size() < static_cast<size_t>(g_config.max_entities)) {
|
||||||
entities.reserve(entities.size() + newEntities.size());
|
entities.reserve(entities.size() + newEntities.size());
|
||||||
|
|
||||||
for (auto &newEntity : newEntities) {
|
for (auto &newEntity : newEntities) {
|
||||||
entities.emplace_back(std::move(newEntity));
|
entities.emplace_back(std::move(newEntity));
|
||||||
}
|
}
|
||||||
@@ -185,9 +205,49 @@ void Aquarium::redraw() {
|
|||||||
entity->draw();
|
entity->draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw debug information
|
||||||
|
if (g_config.show_fps || g_config.show_entity_count) {
|
||||||
|
drawDebugInfo();
|
||||||
|
}
|
||||||
|
|
||||||
renderToScreen();
|
renderToScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Aquarium::drawDebugInfo() {
|
||||||
|
static double last_fps = 0.0;
|
||||||
|
static int frame_counter = 0;
|
||||||
|
static auto last_time = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
frame_counter++;
|
||||||
|
auto current_time = std::chrono::steady_clock::now();
|
||||||
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
current_time - last_time)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if (elapsed >= 1000) {
|
||||||
|
last_fps = (frame_counter * 1000.0) / elapsed;
|
||||||
|
frame_counter = 0;
|
||||||
|
last_time = current_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream debug_info;
|
||||||
|
if (g_config.show_fps) {
|
||||||
|
debug_info << "FPS: " << std::fixed << std::setprecision(1) << last_fps;
|
||||||
|
}
|
||||||
|
if (g_config.show_entity_count) {
|
||||||
|
if (g_config.show_fps)
|
||||||
|
debug_info << " | ";
|
||||||
|
debug_info << "Entities: " << entities.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string debug_str = debug_info.str();
|
||||||
|
if (!debug_str.empty()) {
|
||||||
|
std::string color_str(debug_str.length(), 'w'); // White color
|
||||||
|
drawToFrame(0, width - static_cast<int>(debug_str.length()), debug_str,
|
||||||
|
color_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Aquarium::resize() {
|
void Aquarium::resize() {
|
||||||
printf("%s%s%s", ANSI::CLEAR_SCREEN, ANSI::CURSOR_HOME, ANSI::BG_BLACK);
|
printf("%s%s%s", ANSI::CLEAR_SCREEN, ANSI::CURSOR_HOME, ANSI::BG_BLACK);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
@@ -202,10 +262,24 @@ void Aquarium::resize() {
|
|||||||
|
|
||||||
addWaterline();
|
addWaterline();
|
||||||
addCastle();
|
addCastle();
|
||||||
for (int i = 0; i < width / 15; i++)
|
|
||||||
|
// Use configured seaweed count or auto-calculate
|
||||||
|
int seaweed_count = g_config.initial_seaweed_count;
|
||||||
|
if (seaweed_count == -1) {
|
||||||
|
seaweed_count = width / 15;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < seaweed_count; i++) {
|
||||||
addSeaweed();
|
addSeaweed();
|
||||||
for (int i = 0; i < width * (height - 9) / 350; i++)
|
}
|
||||||
|
|
||||||
|
// Use configured fish count or auto-calculate
|
||||||
|
int fish_count = g_config.initial_fish_count;
|
||||||
|
if (fish_count == -1) {
|
||||||
|
fish_count = width * (height - 9) / 350;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < fish_count; i++) {
|
||||||
addFish();
|
addFish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Aquarium::addFish() { addEntityImpl<Fish>(); }
|
void Aquarium::addFish() { addEntityImpl<Fish>(); }
|
||||||
@@ -240,8 +314,10 @@ void Aquarium::ensureBigEntityExists() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Aquarium::clearCurrentFrame() {
|
void Aquarium::clearCurrentFrame() {
|
||||||
|
static const Cell empty_cell{};
|
||||||
|
|
||||||
for (auto &row : currentFrame) {
|
for (auto &row : currentFrame) {
|
||||||
std::fill(row.begin(), row.end(), Cell());
|
row.assign(width, empty_cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,35 +356,45 @@ void Aquarium::initColorLookup() {
|
|||||||
void Aquarium::renderToScreen() {
|
void Aquarium::renderToScreen() {
|
||||||
static std::string output;
|
static std::string output;
|
||||||
output.clear();
|
output.clear();
|
||||||
output.reserve(height * width * 20); // Reserve space for efficiency
|
output.reserve(height * width * 20);
|
||||||
|
|
||||||
|
int cursor_y = -1, cursor_x = -1;
|
||||||
|
|
||||||
for (int y = 0; y < height; ++y) {
|
for (int y = 0; y < height; ++y) {
|
||||||
for (int x = 0; x < width; ++x) {
|
for (int x = 0; x < width; ++x) {
|
||||||
const Cell &newCell = currentFrame[y][x];
|
const Cell &newCell = currentFrame[y][x];
|
||||||
Cell &oldCell = previousFrame[y][x];
|
Cell &oldCell = previousFrame[y][x];
|
||||||
|
|
||||||
if (newCell != oldCell) {
|
if (newCell == oldCell)
|
||||||
oldCell = newCell;
|
continue;
|
||||||
|
|
||||||
// Move cursor to position
|
oldCell = newCell;
|
||||||
|
|
||||||
|
// Move cursor only when needed
|
||||||
|
if (cursor_y != y || cursor_x != x) {
|
||||||
output += ANSI::moveTo(y, x);
|
output += ANSI::moveTo(y, x);
|
||||||
|
cursor_y = y;
|
||||||
// Reset cell
|
cursor_x = x;
|
||||||
output += ANSI::RESET_BLACK_BG;
|
|
||||||
|
|
||||||
// Set color and attributes
|
|
||||||
if (newCell.bold) {
|
|
||||||
output += ANSI::BOLD;
|
|
||||||
}
|
|
||||||
output += colorLookup[static_cast<unsigned char>(newCell.colorChar)];
|
|
||||||
|
|
||||||
// Add the character
|
|
||||||
output += newCell.ch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply cell formatting and character
|
||||||
|
output += ANSI::RESET_BLACK_BG;
|
||||||
|
|
||||||
|
// Only apply bold if configured
|
||||||
|
if (g_config.use_bold && newCell.bold) {
|
||||||
|
output += ANSI::BOLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only apply colors if configured
|
||||||
|
if (g_config.use_colors) {
|
||||||
|
output += colorLookup[static_cast<unsigned char>(newCell.colorChar)];
|
||||||
|
}
|
||||||
|
|
||||||
|
output += newCell.ch;
|
||||||
|
++cursor_x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output everything at once
|
|
||||||
if (!output.empty()) {
|
if (!output.empty()) {
|
||||||
std::cout << output << std::flush;
|
std::cout << output << std::flush;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "Entity.h"
|
#include "../entities/Entity.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ private:
|
|||||||
bool entities_need_sorting = true;
|
bool entities_need_sorting = true;
|
||||||
static inline const char *colorLookup[256] = {nullptr};
|
static inline const char *colorLookup[256] = {nullptr};
|
||||||
static inline bool colorLookupInitialized = false;
|
static inline bool colorLookupInitialized = false;
|
||||||
|
void drawDebugInfo();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Aquarium();
|
Aquarium();
|
||||||
175
src/core/Config.cpp
Normal file
175
src/core/Config.cpp
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#include "Config.h"
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
AquariumConfig g_config;
|
||||||
|
|
||||||
|
void printUsage(const char *program_name) {
|
||||||
|
std::cout << "Usage: " << program_name << " [OPTIONS]\n\n";
|
||||||
|
std::cout << "Aquarium screensaver with customizable settings\n\n";
|
||||||
|
std::cout << "OPTIONS:\n";
|
||||||
|
std::cout << " -h, --help Show this help message\n";
|
||||||
|
std::cout
|
||||||
|
<< " -f, --fish COUNT Number of initial fish (default: auto)\n";
|
||||||
|
std::cout << " -s, --seaweed COUNT Number of seaweed plants (default: "
|
||||||
|
"auto)\n";
|
||||||
|
std::cout << " -b, --max-bubbles COUNT Maximum bubbles on screen "
|
||||||
|
"(default: 50)\n";
|
||||||
|
std::cout << " -d, --delay MS Frame delay in milliseconds "
|
||||||
|
"(default: 100)\n";
|
||||||
|
std::cout << " -r, --bubble-rate RATE Fish bubble spawn rate 1/N "
|
||||||
|
"(default: 10)\n";
|
||||||
|
std::cout << " -m, --max-entities COUNT Maximum entities on screen "
|
||||||
|
"(default: 200)\n";
|
||||||
|
std::cout << " --no-big-entities Disable ships, whales, and sea "
|
||||||
|
"monsters\n";
|
||||||
|
std::cout << " --no-bubbles Disable bubble generation\n";
|
||||||
|
std::cout << " --no-colors Disable colors (monochrome mode)\n";
|
||||||
|
std::cout << " --no-bold Disable bold text\n";
|
||||||
|
std::cout << " --show-fps Display FPS counter\n";
|
||||||
|
std::cout << " --show-count Display entity count\n";
|
||||||
|
std::cout << " --debug Enable debug mode\n";
|
||||||
|
std::cout << "\nShort options can be combined: -f20 -s5 -d50\n";
|
||||||
|
std::cout << "Long options accept = syntax: --fish=20 --delay=50\n";
|
||||||
|
std::cout << "\nExamples:\n";
|
||||||
|
std::cout << " " << program_name
|
||||||
|
<< " -f20 -s5 -d50 # Fast aquarium with lots of fish\n";
|
||||||
|
std::cout << " " << program_name
|
||||||
|
<< " --fish=10 --no-colors # 10 fish, monochrome\n";
|
||||||
|
std::cout << " " << program_name
|
||||||
|
<< " --debug --show-fps # Debug mode with FPS\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parseArguments(int argc, char *argv[]) {
|
||||||
|
// Define long options
|
||||||
|
static const struct option long_options[] = {
|
||||||
|
{"help", no_argument, nullptr, 'h'},
|
||||||
|
{"fish", required_argument, nullptr, 'f'},
|
||||||
|
{"seaweed", required_argument, nullptr, 's'},
|
||||||
|
{"max-bubbles", required_argument, nullptr, 'b'},
|
||||||
|
{"delay", required_argument, nullptr, 'd'},
|
||||||
|
{"bubble-rate", required_argument, nullptr, 'r'},
|
||||||
|
{"max-entities", required_argument, nullptr, 'm'},
|
||||||
|
{"no-big-entities", no_argument, nullptr, 'B'},
|
||||||
|
{"no-bubbles", no_argument, nullptr, 'N'},
|
||||||
|
{"no-colors", no_argument, nullptr, 'C'},
|
||||||
|
{"no-bold", no_argument, nullptr, 'O'},
|
||||||
|
{"show-fps", no_argument, nullptr, 'F'},
|
||||||
|
{"show-count", no_argument, nullptr, 'S'},
|
||||||
|
{"debug", no_argument, nullptr, 'D'},
|
||||||
|
{nullptr, 0, nullptr, 0}};
|
||||||
|
|
||||||
|
// Short options string - : after letter means it takes an argument
|
||||||
|
const char *short_options = "hf:s:b:d:r:m:BNCOFSD";
|
||||||
|
|
||||||
|
int option;
|
||||||
|
int option_index = 0;
|
||||||
|
|
||||||
|
// Reset getopt state (important for testing)
|
||||||
|
optind = 1;
|
||||||
|
|
||||||
|
while ((option = getopt_long(argc, argv, short_options, long_options,
|
||||||
|
&option_index)) != -1) {
|
||||||
|
switch (option) {
|
||||||
|
case 'h':
|
||||||
|
printUsage(argv[0]);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
g_config.initial_fish_count = std::atoi(optarg);
|
||||||
|
if (g_config.initial_fish_count < -1) {
|
||||||
|
std::cerr << "Error: Fish count must be >= -1\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
g_config.initial_seaweed_count = std::atoi(optarg);
|
||||||
|
if (g_config.initial_seaweed_count < -1) {
|
||||||
|
std::cerr << "Error: Seaweed count must be >= -1\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'b':
|
||||||
|
g_config.max_bubbles = std::atoi(optarg);
|
||||||
|
if (g_config.max_bubbles < 0) {
|
||||||
|
std::cerr << "Error: Max bubbles must be >= 0\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'd':
|
||||||
|
g_config.frame_delay_ms = std::atoi(optarg);
|
||||||
|
if (g_config.frame_delay_ms < 1) {
|
||||||
|
std::cerr << "Error: Frame delay must be >= 1ms\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r':
|
||||||
|
g_config.fish_bubble_rate = std::atoi(optarg);
|
||||||
|
if (g_config.fish_bubble_rate < 1) {
|
||||||
|
std::cerr << "Error: Bubble rate must be >= 1\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'm':
|
||||||
|
g_config.max_entities = std::atoi(optarg);
|
||||||
|
if (g_config.max_entities < 1) {
|
||||||
|
std::cerr << "Error: Max entities must be >= 1\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'B':
|
||||||
|
g_config.enable_big_entities = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'N':
|
||||||
|
g_config.enable_bubbles = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'C':
|
||||||
|
g_config.use_colors = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'O':
|
||||||
|
g_config.use_bold = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'F':
|
||||||
|
g_config.show_fps = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'S':
|
||||||
|
g_config.show_entity_count = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'D':
|
||||||
|
g_config.debug_mode = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '?':
|
||||||
|
// getopt_long already printed an error message
|
||||||
|
std::cerr << "Use --help for usage information\n";
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
std::cerr << "Error: Unexpected option character\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for non-option arguments
|
||||||
|
if (optind < argc) {
|
||||||
|
std::cerr << "Error: Unexpected argument: " << argv[optind] << "\n";
|
||||||
|
std::cerr << "Use --help for usage information\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
33
src/core/Config.h
Normal file
33
src/core/Config.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct AquariumConfig {
|
||||||
|
// Entity spawn settings
|
||||||
|
int initial_fish_count = -1; // -1 means auto-calculate
|
||||||
|
int initial_seaweed_count = -1; // -1 means auto-calculate
|
||||||
|
int max_bubbles = 50;
|
||||||
|
|
||||||
|
// Animation settings
|
||||||
|
int frame_delay_ms = 100;
|
||||||
|
bool enable_big_entities = true;
|
||||||
|
bool enable_bubbles = true;
|
||||||
|
|
||||||
|
// Visual settings
|
||||||
|
bool use_colors = true;
|
||||||
|
bool use_bold = true;
|
||||||
|
|
||||||
|
// Spawn rates (lower = more frequent)
|
||||||
|
int fish_bubble_rate = 10; // 1 in N chance per frame
|
||||||
|
int big_entity_spawn_delay = 100; // frames between big entities
|
||||||
|
|
||||||
|
// Performance settings
|
||||||
|
bool optimize_rendering = true;
|
||||||
|
int max_entities = 200;
|
||||||
|
|
||||||
|
// Debug settings
|
||||||
|
bool show_fps = false;
|
||||||
|
bool show_entity_count = false;
|
||||||
|
bool debug_mode = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern AquariumConfig g_config;
|
||||||
115
src/core/main.cpp
Normal file
115
src/core/main.cpp
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#include "Aquarium.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#ifdef __OpenBSD__
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Forward declaration
|
||||||
|
bool parseArguments(int argc, char *argv[]);
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
// Parse command line arguments
|
||||||
|
if (!parseArguments(argc, argv)) {
|
||||||
|
return 1; // Exit if parsing failed or help was shown
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print configuration if debug mode is enabled
|
||||||
|
if (g_config.debug_mode) {
|
||||||
|
std::cout << "Configuration:\n";
|
||||||
|
std::cout << " Fish count: "
|
||||||
|
<< (g_config.initial_fish_count == -1
|
||||||
|
? "auto"
|
||||||
|
: std::to_string(g_config.initial_fish_count))
|
||||||
|
<< "\n";
|
||||||
|
std::cout << " Seaweed count: "
|
||||||
|
<< (g_config.initial_seaweed_count == -1
|
||||||
|
? "auto"
|
||||||
|
: std::to_string(g_config.initial_seaweed_count))
|
||||||
|
<< "\n";
|
||||||
|
std::cout << " Max bubbles: " << g_config.max_bubbles << "\n";
|
||||||
|
std::cout << " Frame delay: " << g_config.frame_delay_ms << "ms\n";
|
||||||
|
std::cout << " Big entities: "
|
||||||
|
<< (g_config.enable_big_entities ? "enabled" : "disabled")
|
||||||
|
<< "\n";
|
||||||
|
std::cout << " Bubbles: "
|
||||||
|
<< (g_config.enable_bubbles ? "enabled" : "disabled") << "\n";
|
||||||
|
std::cout << " Colors: " << (g_config.use_colors ? "enabled" : "disabled")
|
||||||
|
<< "\n";
|
||||||
|
std::cout << " Bold text: " << (g_config.use_bold ? "enabled" : "disabled")
|
||||||
|
<< "\n";
|
||||||
|
std::cout << " Max entities: " << g_config.max_entities << "\n";
|
||||||
|
std::cout << "Press any key to continue...\n";
|
||||||
|
std::cin.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Aquarium &aquarium = Aquarium::getInstance();
|
||||||
|
|
||||||
|
#ifdef __OpenBSD__
|
||||||
|
if (pledge("stdio tty", NULL) == -1) {
|
||||||
|
perror("pledge");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Initialize the aquarium display
|
||||||
|
aquarium.resize(); // Setup initial entities
|
||||||
|
|
||||||
|
// Variables for FPS calculation
|
||||||
|
auto last_time = std::chrono::steady_clock::now();
|
||||||
|
int frame_count = 0;
|
||||||
|
double fps = 0.0;
|
||||||
|
|
||||||
|
// Main game loop
|
||||||
|
while (true) {
|
||||||
|
auto frame_start = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
int input = aquarium.checkInput();
|
||||||
|
if (input == 'q' || input == 'Q' || input == 27) { // ESC key
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle debug info with 'd' key
|
||||||
|
if (input == 'd' || input == 'D') {
|
||||||
|
g_config.show_fps = !g_config.show_fps;
|
||||||
|
g_config.show_entity_count = !g_config.show_entity_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aquarium.checkResize()) {
|
||||||
|
aquarium.resize();
|
||||||
|
}
|
||||||
|
|
||||||
|
aquarium.redraw();
|
||||||
|
|
||||||
|
// Calculate FPS
|
||||||
|
if (g_config.show_fps) {
|
||||||
|
++frame_count;
|
||||||
|
auto current_time = std::chrono::steady_clock::now();
|
||||||
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
current_time - last_time)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if (elapsed >= 1000) { // Update FPS every second
|
||||||
|
fps = (frame_count * 1000.0) / elapsed;
|
||||||
|
frame_count = 0;
|
||||||
|
last_time = current_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep for configured frame delay
|
||||||
|
auto frame_end = std::chrono::steady_clock::now();
|
||||||
|
auto frame_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
frame_end - frame_start);
|
||||||
|
auto sleep_duration =
|
||||||
|
std::chrono::milliseconds(g_config.frame_delay_ms) - frame_duration;
|
||||||
|
|
||||||
|
if (sleep_duration > std::chrono::milliseconds(0)) {
|
||||||
|
std::this_thread::sleep_for(sleep_duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "Castle.h"
|
#include "Castle.h"
|
||||||
#include "Aquarium.h"
|
#include "../assets/CastleAssets.h"
|
||||||
#include "assets/CastleAssets.h"
|
#include "../core/Aquarium.h"
|
||||||
|
|
||||||
Castle::Castle()
|
Castle::Castle()
|
||||||
: Entity(Aquarium::getInstance().getWidth() - 32,
|
: Entity(Aquarium::getInstance().getWidth() - 32,
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "Aquarium.h"
|
#include "../core/Aquarium.h"
|
||||||
|
|
||||||
void Entity::draw() const {
|
void Entity::draw() const {
|
||||||
auto &aquarium = Aquarium::getInstance();
|
auto &aquarium = Aquarium::getInstance();
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "Fish.h"
|
#include "Fish.h"
|
||||||
#include "Aquarium.h"
|
#include "../assets/FishAssets.h"
|
||||||
#include "Random.h"
|
#include "../core/Aquarium.h"
|
||||||
#include "assets/FishAssets.h"
|
#include "../utils/Random.h"
|
||||||
#include "defs.h"
|
#include "../utils/defs.h"
|
||||||
|
|
||||||
std::unordered_map<char, char> Fish::color_map;
|
std::unordered_map<char, char> Fish::color_map;
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "../assets/FishAssets.h"
|
||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "assets/FishAssets.h"
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "SeaMonster.h"
|
#include "SeaMonster.h"
|
||||||
#include "Aquarium.h"
|
#include "../assets/SeaMonsterAssets.h"
|
||||||
#include "Random.h"
|
#include "../core/Aquarium.h"
|
||||||
#include "assets/SeaMonsterAssets.h"
|
#include "../utils/Random.h"
|
||||||
|
|
||||||
SeaMonster::SeaMonster() : SeaMonster(getRandomDirection()) {}
|
SeaMonster::SeaMonster() : SeaMonster(getRandomDirection()) {}
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "../assets/SeaMonsterAssets.h"
|
||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "assets/SeaMonsterAssets.h"
|
|
||||||
|
|
||||||
class SeaMonster : public Entity {
|
class SeaMonster : public Entity {
|
||||||
private:
|
private:
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "Seaweed.h"
|
#include "Seaweed.h"
|
||||||
#include "Aquarium.h"
|
#include "../core/Aquarium.h"
|
||||||
#include "Random.h"
|
#include "../utils/Random.h"
|
||||||
#include "defs.h"
|
#include "../utils/defs.h"
|
||||||
|
|
||||||
Seaweed::Seaweed()
|
Seaweed::Seaweed()
|
||||||
: Entity(),
|
: Entity(),
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "Ship.h"
|
#include "Ship.h"
|
||||||
#include "Aquarium.h"
|
#include "../assets/ShipAssets.h"
|
||||||
#include "Random.h"
|
#include "../core/Aquarium.h"
|
||||||
#include "assets/ShipAssets.h"
|
#include "../utils/Random.h"
|
||||||
|
|
||||||
Ship::Ship() : Ship(getRandomDirection()) {}
|
Ship::Ship() : Ship(getRandomDirection()) {}
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "../assets/ShipAssets.h"
|
||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "assets/ShipAssets.h"
|
|
||||||
|
|
||||||
class Ship : public Entity {
|
class Ship : public Entity {
|
||||||
private:
|
private:
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "Waterline.h"
|
#include "Waterline.h"
|
||||||
#include "Aquarium.h"
|
#include "../core/Aquarium.h"
|
||||||
#include "Random.h"
|
#include "../utils/Random.h"
|
||||||
#include "defs.h"
|
#include "../utils/defs.h"
|
||||||
|
|
||||||
Waterline::Waterline() : Entity(0, WATERLINE_Y) {
|
Waterline::Waterline() : Entity(0, WATERLINE_Y) {
|
||||||
shape[0] = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~";
|
shape[0] = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~";
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "Whale.h"
|
#include "Whale.h"
|
||||||
#include "Aquarium.h"
|
#include "../assets/WhaleAssets.h"
|
||||||
#include "Random.h"
|
#include "../core/Aquarium.h"
|
||||||
#include "assets/WhaleAssets.h"
|
#include "../utils/Random.h"
|
||||||
|
|
||||||
Whale::Whale() : Whale(getRandomDirection()) {}
|
Whale::Whale() : Whale(getRandomDirection()) {}
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "../assets/WhaleAssets.h"
|
||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "assets/WhaleAssets.h"
|
|
||||||
|
|
||||||
class Whale : public Entity {
|
class Whale : public Entity {
|
||||||
private:
|
private:
|
||||||
33
src/main.cpp
33
src/main.cpp
@@ -1,33 +0,0 @@
|
|||||||
// main.cpp
|
|
||||||
#include "Aquarium.h"
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
// Get the singleton instance
|
|
||||||
Aquarium &aquarium = Aquarium::getInstance();
|
|
||||||
|
|
||||||
#ifdef __OpenBSD__
|
|
||||||
if (pledge("stdio tty", NULL) == -1) {
|
|
||||||
perror("pledge");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Initialize the aquarium display
|
|
||||||
aquarium.resize(); // Setup initial entities
|
|
||||||
|
|
||||||
// Main game loop
|
|
||||||
while (true) {
|
|
||||||
int input = aquarium.checkInput();
|
|
||||||
if (input == 'q' || input == 'Q' || input == 27) { // ESC key
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aquarium.checkResize()) {
|
|
||||||
aquarium.resize();
|
|
||||||
}
|
|
||||||
|
|
||||||
aquarium.redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user