From 90ae6678c5232268dfce63296282520a7ff0d14f Mon Sep 17 00:00:00 2001 From: Andrew Kloet Date: Mon, 26 May 2025 06:41:38 -0400 Subject: [PATCH] add sea monster --- Makefile | 4 +-- src/Aquarium.cpp | 24 +++++++++++++-- src/Aquarium.h | 3 ++ src/Entity.cpp | 2 +- src/Entity.h | 4 +-- src/Fish.cpp | 43 ++++++++++----------------- src/Fish.h | 6 ++-- src/SeaMonster.cpp | 56 +++++++++++++++++++++++++++++++++++ src/SeaMonster.h | 35 ++++++++++++++++++++++ src/Ship.cpp | 12 ++------ src/Ship.h | 1 - src/assets/SeaMonsterAssets.h | 38 ++++++++++++++++++++++++ src/assets/ShipAssets.h | 2 +- 13 files changed, 181 insertions(+), 49 deletions(-) create mode 100644 src/SeaMonster.cpp create mode 100644 src/SeaMonster.h create mode 100644 src/assets/SeaMonsterAssets.h diff --git a/Makefile b/Makefile index 78e859c..21495d5 100644 --- 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 index c5fd080..9d12401 100644 --- 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> 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(); } void Aquarium::addWaterline() { addEntityImpl(); } void Aquarium::addCastle() { addEntityImpl(); } void Aquarium::addShip() { addEntityImpl(); } +void Aquarium::addSeaMonster() { 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())) { + return; // Big entity found, do nothing + } + } + + // No big entity found, spawn next in cycle + if (big_entity_index % 2 == 0) { + addEntityImpl(); + } else { + addEntityImpl(); + } + ++big_entity_index; +} void Aquarium::clearCurrentFrame() { for (auto &row : currentFrame) { diff --git a/src/Aquarium.h b/src/Aquarium.h index 033f510..3ad11d5 100644 --- a/src/Aquarium.h +++ b/src/Aquarium.h @@ -26,6 +26,8 @@ private: std::vector> currentFrame; std::vector> previousFrame; std::vector> 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 index 3d35815..9a1e9c3 100644 --- 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 index 110b927..0d8c65b 100644 --- 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 createReplacement() const = 0; + virtual std::unique_ptr 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 index b85cce3..019eaac 100644 --- a/src/Fish.cpp +++ b/src/Fish.cpp @@ -4,24 +4,7 @@ #include "assets/FishAssets.h" #include "defs.h" -std::array, 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(AVAILABLE_COLORS.size()) - 1)]; - } - } - } - color_maps_initialized = true; -} +std::unordered_map 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(image.size()) + 6, aquarium.getHeight() - static_cast(image.size())); x = moving_right ? -20.0f : static_cast(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(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 index e377f26..e6bff4a 100644 --- a/src/Fish.h +++ b/src/Fish.h @@ -14,13 +14,11 @@ private: const float speed; const bool moving_right; - static std::array, 20> color_map_cache; - static bool color_maps_initialized; - static void initializeColorMaps(); + static std::unordered_map 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 new file mode 100644 index 0000000..a016168 --- /dev/null +++ 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(frame1[0].length()); + } else { + x = static_cast(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 &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(aquarium.getWidth()); + } else { + return (x + static_cast(frame1[0].length())) < 0; + } +} + +int SeaMonster::getPreferredLayer() const noexcept { return 8; } diff --git a/src/SeaMonster.h b/src/SeaMonster.h new file mode 100644 index 0000000..22a22a0 --- /dev/null +++ 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 &frame1; + const std::vector &frame2; + const std::vector &mask; + const float speed; + const bool moving_right; + + bool current_frame = false; + int animation_counter = 0; + mutable std::vector 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 &getImage() const override; + const std::vector &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 index 50dae74..f001566 100644 --- 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 Ship::createReplacement() const { - return std::make_unique(); -} - -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 index 7f0efe4..a8bba84 100644 --- 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 createReplacement() const override; int getPreferredLayer() const noexcept override; }; diff --git a/src/assets/SeaMonsterAssets.h b/src/assets/SeaMonsterAssets.h new file mode 100644 index 0000000..5fe98a6 --- /dev/null +++ b/src/assets/SeaMonsterAssets.h @@ -0,0 +1,38 @@ +#pragma once +#include "../Entity.h" +#include + +struct SeaMonsterAsset { + std::vector frame1; + std::vector frame2; + std::vector mask; +}; + +inline const std::vector 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 index 8f17d05..9dd3fae 100644 --- a/src/assets/ShipAssets.h +++ b/src/assets/ShipAssets.h @@ -2,7 +2,7 @@ #include "../Entity.h" #include -inline std::vector pirateShipAssets = { +inline std::vector shipAssets = { {{ R"( | | |)", R"( )_) )_) )_))",