fissh

termios terminal aquarium. demo at ssh://fish@kloet.net
Download | Log | Files | Refs

commit 039d38da9df30a76921275780419b05e1ea18b65
parent 04133e71173affddbf4b53562da9795d77c9dcef
Author: amrfti <andrew@kloet.net>
Date:   Mon, 26 May 2025 06:41:38 -0400

add sea monster

Diffstat:
MMakefile | 4++--
Msrc/Aquarium.cpp | 24++++++++++++++++++++++--
Msrc/Aquarium.h | 3+++
Msrc/Entity.cpp | 2+-
Msrc/Entity.h | 4++--
Msrc/Fish.cpp | 43++++++++++++++++---------------------------
Msrc/Fish.h | 6++----
Asrc/SeaMonster.cpp | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/SeaMonster.h | 35+++++++++++++++++++++++++++++++++++
Msrc/Ship.cpp | 12+++---------
Msrc/Ship.h | 1-
Asrc/assets/SeaMonsterAssets.h | 38++++++++++++++++++++++++++++++++++++++
Msrc/assets/ShipAssets.h | 2+-
13 files changed, 181 insertions(+), 49 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ # Compiler and flags CXX = g++ -CXXFLAGS = -std=c++17 -Wall -Wextra -O3 -LDFLAGS = -lncurses -ltinfo +CXXFLAGS = -std=c++17 -Wall -Wextra -O3 -fno-exceptions -fno-asynchronous-unwind-tables -fno-unwind-tables +LDFLAGS = -lncurses -ltinfo -Wl,--gc-sections -s # Directories SRC_DIR = src diff --git a/src/Aquarium.cpp b/src/Aquarium.cpp @@ -2,6 +2,7 @@ #include "Bubble.h" #include "Castle.h" #include "Fish.h" +#include "SeaMonster.h" #include "Seaweed.h" #include "Ship.h" #include "Waterline.h" @@ -46,6 +47,8 @@ void Aquarium::ensureEntitiesSorted() { void Aquarium::redraw() { clearCurrentFrame(); + ensureBigEntityExists(); + std::vector<std::unique_ptr<Entity>> newEntities; bool entities_modified = false; @@ -93,7 +96,7 @@ void Aquarium::redraw() { // Draw all entities for (const auto &entity : entities) { - entity->draw(0); + entity->draw(); } renderToScreen(); @@ -119,7 +122,6 @@ void Aquarium::resize() { addWaterline(); addCastle(); - addShip(); for (int i = 0; i < width / 15; i++) addSeaweed(); for (int i = 0; i < width * (height - 9) / 350; i++) @@ -132,6 +134,24 @@ 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::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())) { + return; // Big entity found, do nothing + } + } + + // No big entity found, spawn next in cycle + if (big_entity_index % 2 == 0) { + addEntityImpl<Ship>(); + } else { + addEntityImpl<SeaMonster>(); + } + ++big_entity_index; +} void Aquarium::clearCurrentFrame() { for (auto &row : currentFrame) { diff --git a/src/Aquarium.h b/src/Aquarium.h @@ -26,6 +26,8 @@ private: std::vector<std::vector<Cell>> currentFrame; std::vector<std::vector<Cell>> previousFrame; std::vector<std::unique_ptr<Entity>> entities; + size_t big_entity_index = 0; + void ensureBigEntityExists(); bool entities_need_sorting = true; static inline short colorLookup[256] = {0}; @@ -49,6 +51,7 @@ public: void addWaterline(); void addCastle(); void addShip(); + void addSeaMonster(); void redraw(); void initColors(); diff --git a/src/Entity.cpp b/src/Entity.cpp @@ -1,7 +1,7 @@ #include "Entity.h" #include "Aquarium.h" -void Entity::draw(int layer) const { +void Entity::draw() const { auto &aquarium = Aquarium::getInstance(); const auto &image = getImage(); diff --git a/src/Entity.h b/src/Entity.h @@ -31,8 +31,8 @@ public: virtual char getDefaultColor() const noexcept = 0; virtual bool shouldBeRemoved() const noexcept = 0; - virtual std::unique_ptr<Entity> createReplacement() const = 0; + virtual std::unique_ptr<Entity> createReplacement() const { return nullptr; } virtual int getPreferredLayer() const noexcept = 0; - void draw(int) const; + void draw() const; }; diff --git a/src/Fish.cpp b/src/Fish.cpp @@ -4,24 +4,7 @@ #include "assets/FishAssets.h" #include "defs.h" -std::array<std::unordered_map<char, char>, 20> Fish::color_map_cache; -bool Fish::color_maps_initialized = false; - -// Pre-generate 20 different color mappings at startup -void Fish::initializeColorMaps() { - for (auto &color_map : color_map_cache) { - color_map.clear(); - color_map['4'] = 'W'; // White is always '4' - - for (char digit = '1'; digit <= '9'; ++digit) { - if (digit != '4') { - color_map[digit] = AVAILABLE_COLORS[Random::intInRange( - 0, static_cast<int>(AVAILABLE_COLORS.size()) - 1)]; - } - } - } - color_maps_initialized = true; -} +std::unordered_map<char, char> Fish::color_map; Fish::Fish() : Fish(getRandomAssetIndex()) {} @@ -31,24 +14,30 @@ Fish::Fish(int asset_index) speed(Random::floatInRange(0.25f, 2.25f)), moving_right(asset_index % 2 == 0) { - if (!color_maps_initialized) { - initializeColorMaps(); - } - const auto &aquarium = Aquarium::getInstance(); y = Random::intInRange(static_cast<int>(image.size()) + 6, aquarium.getHeight() - static_cast<int>(image.size())); x = moving_right ? -20.0f : static_cast<float>(aquarium.getWidth()); - applyRandomColorMapping(); + randomizeMask(); } -// Pick one of the pre-generated color mappings -void Fish::applyRandomColorMapping() { - const auto &selected_map = color_map_cache[Random::intInRange(0, 9)]; +void Fish::randomizeMask() { + // Clear and rebuild color map with fresh random colors each time + color_map.clear(); + color_map['4'] = 'W'; // White is always '4' + + // Assign random colors to digits 1-3, 5-9 + for (char digit = '1'; digit <= '9'; ++digit) { + if (digit != '4') { + color_map[digit] = AVAILABLE_COLORS[Random::intInRange( + 0, static_cast<int>(AVAILABLE_COLORS.size()) - 1)]; + } + } + // Apply color mapping to mask for (auto &line : mask) { for (char &ch : line) { - if (auto it = selected_map.find(ch); it != selected_map.end()) { + if (auto it = color_map.find(ch); it != color_map.end()) { ch = it->second; } } diff --git a/src/Fish.h b/src/Fish.h @@ -14,13 +14,11 @@ private: const float speed; const bool moving_right; - static std::array<std::unordered_map<char, char>, 20> color_map_cache; - static bool color_maps_initialized; - static void initializeColorMaps(); + static std::unordered_map<char, char> color_map; explicit Fish(int asset_index); static int getRandomAssetIndex(); - void applyRandomColorMapping(); + void randomizeMask(); public: Fish(); diff --git a/src/SeaMonster.cpp b/src/SeaMonster.cpp @@ -0,0 +1,56 @@ +#include "SeaMonster.h" +#include "Aquarium.h" +#include "Random.h" +#include "assets/SeaMonsterAssets.h" + +SeaMonster::SeaMonster() : SeaMonster(getRandomDirection()) {} + +SeaMonster::SeaMonster(int asset_index) + : Entity(), frame1(seaMonsterAssets[asset_index].frame1), + frame2(seaMonsterAssets[asset_index].frame2), + mask(seaMonsterAssets[asset_index].mask), speed(SEAMONSTER_SPEED), + moving_right(asset_index == 0) { + + const auto &aquarium = Aquarium::getInstance(); + y = WATER_SURFACE_OFFSET; + + if (moving_right) { + x = -static_cast<float>(frame1[0].length()); + } else { + x = static_cast<float>(aquarium.getWidth()); + } + + current_image = frame1; +} + +int SeaMonster::getRandomDirection() { return Random::intInRange(0, 1); } + +void SeaMonster::update() noexcept { + x += moving_right ? speed : -speed; + + ++animation_counter; + if (animation_counter >= ANIMATION_DELAY) { + current_frame = !current_frame; + animation_counter = 0; + } +} + +const std::vector<std::string> &SeaMonster::getImage() const { + if (current_frame) { + current_image = frame2; + } else { + current_image = frame1; + } + return current_image; +} + +bool SeaMonster::shouldBeRemoved() const noexcept { + const auto &aquarium = Aquarium::getInstance(); + if (moving_right) { + return x > static_cast<float>(aquarium.getWidth()); + } else { + return (x + static_cast<float>(frame1[0].length())) < 0; + } +} + +int SeaMonster::getPreferredLayer() const noexcept { return 8; } diff --git a/src/SeaMonster.h b/src/SeaMonster.h @@ -0,0 +1,35 @@ +#pragma once +#include "Entity.h" +#include "assets/SeaMonsterAssets.h" + +class SeaMonster : public Entity { +private: + static constexpr float SEAMONSTER_SPEED = 0.8f; + static constexpr int WATER_SURFACE_OFFSET = 2; + + const std::vector<std::string> &frame1; + const std::vector<std::string> &frame2; + const std::vector<std::string> &mask; + const float speed; + const bool moving_right; + + bool current_frame = false; + int animation_counter = 0; + mutable std::vector<std::string> current_image; + + static constexpr int ANIMATION_DELAY = 5; + + explicit SeaMonster(int asset_index); + static int getRandomDirection(); + +public: + SeaMonster(); + + void update() noexcept override; + const std::vector<std::string> &getImage() const override; + const std::vector<std::string> &getMask() const override { return mask; } + char getDefaultColor() const noexcept override { return 'G'; } + + bool shouldBeRemoved() const noexcept override; + int getPreferredLayer() const noexcept override; +}; diff --git a/src/Ship.cpp b/src/Ship.cpp @@ -6,8 +6,8 @@ Ship::Ship() : Ship(getRandomDirection()) {} Ship::Ship(int asset_index) - : Entity(), image(pirateShipAssets[asset_index].image), - mask(pirateShipAssets[asset_index].mask), speed(SHIP_SPEED), + : Entity(), image(shipAssets[asset_index].image), + mask(shipAssets[asset_index].mask), speed(SHIP_SPEED), moving_right(asset_index == 0) { const auto &aquarium = Aquarium::getInstance(); @@ -32,10 +32,4 @@ bool Ship::shouldBeRemoved() const noexcept { } } -std::unique_ptr<Entity> Ship::createReplacement() const { - return std::make_unique<Ship>(); -} - -int Ship::getPreferredLayer() const noexcept { - return 9; // Ships on layer 9 -} +int Ship::getPreferredLayer() const noexcept { return 9; } diff --git a/src/Ship.h b/src/Ship.h @@ -24,6 +24,5 @@ public: char getDefaultColor() const noexcept override { return 'W'; } bool shouldBeRemoved() const noexcept override; - std::unique_ptr<Entity> createReplacement() const override; int getPreferredLayer() const noexcept override; }; diff --git a/src/assets/SeaMonsterAssets.h b/src/assets/SeaMonsterAssets.h @@ -0,0 +1,38 @@ +#pragma once +#include "../Entity.h" +#include <vector> + +struct SeaMonsterAsset { + std::vector<std::string> frame1; + std::vector<std::string> frame2; + std::vector<std::string> mask; +}; + +inline const std::vector<SeaMonsterAsset> seaMonsterAssets = { + {{ + R"( _???_?????????????????????_???_???????_a_a)", + R"( _{.`=`.}_??????_???_??????_{.`=`.}_????{/ ''\_)", + R"(?_????{.' _ '.}????{.`'`.}????{.' _ '.}??{| ._oo))", + R"({ \??{/ .'?'. \}??{/ .-. \}??{/ .'?'. \}?{/ |)", + }, + { + R"( _???_????????????????????_a_a)", + R"(??_??????_???_??????_{.`=`.}_??????_???_??????{/ ''\_)", + R"(?{ \????{.`'`.}????{.' _ '.}????{.`'`.}????{| ._oo))", + R"(??\ \??{/ .-. \}??{/ .'?'. \}??{/ .-. \}???{/ |)", + }, + { + R"( W W)", + R"()", + R"()", + R"()", + }}, + {{R"( a_a_???????_???_?????????????????????_???_)", + R"( _/'' \}????_{.`=`.}_??????_???_??????_{.`=`.}_)", + R"((oo_. |}??{.' _ '.}????{.`'`.}????{.' _ '.}????_)", + R"(????| \}?{/ .'?'. \}??{/ .-. \}??{/ .'?'. \}??/ })"}, + {R"( a_a_????????????????????_ _)", + R"( _/'' \}??????_???_??????_{.`=`.}_??????_???_??????_)", + R"((oo_. |}????{.`'`.}????{.' _ '.}????{.`'`.}????/ })", + R"(????| \}???{/ .-. \}??{/ .'?'. \}??{/ .-. \}??/ /)"}, + {R"( W W)", R"()", R"()", R"()"}}}; diff --git a/src/assets/ShipAssets.h b/src/assets/ShipAssets.h @@ -2,7 +2,7 @@ #include "../Entity.h" #include <vector> -inline std::vector<AssetPair> pirateShipAssets = { +inline std::vector<AssetPair> shipAssets = { {{ R"( | | |)", R"( )_) )_) )_))",