From b4e08ff28d604fa6974273d7a1a0f4fc52e8488d Mon Sep 17 00:00:00 2001 From: Andrew Kloet Date: Fri, 23 May 2025 11:48:30 -0400 Subject: [PATCH] add ship, improve inheritance --- src/Aquarium.cpp | 22 +++++++++++---- src/Aquarium.h | 4 +++ src/Entity.cpp | 59 +++++++++++++++++++++++++++++++++++++++++ src/Entity.h | 11 +++++--- src/Fish.cpp | 58 ++++++---------------------------------- src/Fish.h | 12 ++++++--- src/Seaweed.h | 3 +-- src/Ship.cpp | 39 +++++++++++++++++++++++++++ src/Ship.h | 26 ++++++++++++++++++ src/assets/ShipAssets.h | 37 ++++++++++++++++++++++++++ 10 files changed, 207 insertions(+), 64 deletions(-) create mode 100644 src/Entity.cpp create mode 100644 src/Ship.cpp create mode 100644 src/Ship.h create mode 100644 src/assets/ShipAssets.h diff --git a/src/Aquarium.cpp b/src/Aquarium.cpp index 3c87ba4..87010c1 100644 --- a/src/Aquarium.cpp +++ b/src/Aquarium.cpp @@ -3,6 +3,7 @@ #include "defs.h" #include #include +#include #include int g_maxCells = 0; @@ -72,6 +73,7 @@ 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++) @@ -112,10 +114,13 @@ void Aquarium::redraw() { } int baseFishLayer = 10; - for (auto it = fishes.begin(); it != fishes.end();) { // use an iterator + for (auto it = fishes.begin(); it != fishes.end();) { auto &fish = *it; - fish->draw(baseFishLayer + + + static_cast(fish.get()) + ->draw(baseFishLayer + static_cast(std::distance(fishes.begin(), it))); + fish->update(); float fx = fish->getX(); @@ -123,17 +128,22 @@ void Aquarium::redraw() { addBubble(fx, fish->getY()); } - if (fx > width || fx < -30) { - it = fishes.erase(it); // erase and update iterator + if (fish->isOffScreen()) { + it = fishes.erase(it); addFish(); } else { - ++it; // only increment if not erasing + ++it; } } waterline->draw(); waterline->update(); + static_cast(ship.get())->draw(9); + if (ship->isOffScreen()) + addShip(); + ship->update(); + applyBackBuffer(); } @@ -151,6 +161,8 @@ void Aquarium::addCastle() { castle = std::make_unique(); } void Aquarium::addFish() { fishes.emplace_back(std::make_unique()); } +void Aquarium::addShip() { ship = std::make_unique(); } + void Aquarium::clearBackBuffer() { for (auto &row : backBuffer) std::fill(row.begin(), row.end(), Cell()); diff --git a/src/Aquarium.h b/src/Aquarium.h index 913d29f..414e564 100644 --- a/src/Aquarium.h +++ b/src/Aquarium.h @@ -3,8 +3,10 @@ #include "Castle.h" #include "Fish.h" #include "Seaweed.h" +#include "Ship.h" #include "Waterline.h" #include +#include #include extern int g_maxCells; @@ -67,6 +69,7 @@ private: std::vector> fishes; std::vector> bubbles; std::vector> seaweeds; + std::unique_ptr ship; std::unique_ptr waterline; std::unique_ptr castle; @@ -84,6 +87,7 @@ public: void addSeaweed(); void addWaterline(); void addCastle(); + void addShip(); void redraw(); void initColors(); void initColorLookup(); diff --git a/src/Entity.cpp b/src/Entity.cpp new file mode 100644 index 0000000..d7fcb10 --- /dev/null +++ b/src/Entity.cpp @@ -0,0 +1,59 @@ +#include "Entity.h" +#include "Aquarium.h" + +void Entity::draw(int layer) const { + auto &aquarium = Aquarium::getInstance(); + + const auto &image = getImage(); + const auto &mask = getMask(); + const char default_color = getDefaultColor(); + + std::string current_segment; + std::string current_colors; + current_segment.reserve(32); + current_colors.reserve(32); + + const int base_x = static_cast(x); + const int base_y = static_cast(y); + + for (size_t i = 0; i < image.size(); ++i) { + const std::string &row = image[i]; + const std::string &mask_row = (i < mask.size()) ? mask[i] : ""; + + int cursor_x = base_x; + current_segment.clear(); + current_colors.clear(); + + for (size_t j = 0; j < row.size(); ++j) { + const char ch = row[j]; + + if (ch == '?') { + // Flush current segment if not empty + if (!current_segment.empty()) { + aquarium.drawToBackBuffer(base_y + static_cast(i), cursor_x, + layer, current_segment, current_colors); + cursor_x += static_cast(current_segment.size()); + current_segment.clear(); + current_colors.clear(); + } + ++cursor_x; // Skip transparent character + continue; + } + + current_segment.push_back(ch); + + // Use mask color if available, otherwise use default color for spaces + char color = default_color; + if (j < mask_row.size()) { + color = (mask_row[j] == ' ') ? default_color : mask_row[j]; + } + current_colors.push_back(color); + } + + // Flush remaining segment + if (!current_segment.empty()) { + aquarium.drawToBackBuffer(base_y + static_cast(i), cursor_x, layer, + current_segment, current_colors); + } + } +} diff --git a/src/Entity.h b/src/Entity.h index de9f3b4..e47ef95 100644 --- a/src/Entity.h +++ b/src/Entity.h @@ -1,8 +1,5 @@ #pragma once -#include -#include #include -#include #include struct AssetPair { @@ -21,4 +18,12 @@ public: float getX() const noexcept { return x; } float getY() const noexcept { return y; } + + virtual void update() noexcept = 0; + virtual bool isOffScreen() const noexcept = 0; + virtual const std::vector &getImage() const = 0; + virtual const std::vector &getMask() const = 0; + virtual char getDefaultColor() const noexcept = 0; + + void draw(int layer) const; }; diff --git a/src/Fish.cpp b/src/Fish.cpp index 8a77d65..a52d1f8 100644 --- a/src/Fish.cpp +++ b/src/Fish.cpp @@ -14,12 +14,9 @@ Fish::Fish(int asset_index) speed(Random::floatInRange(0.25f, 2.25f)), moving_right(asset_index % 2 == 0) { 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()); - randomizeMask(); } @@ -27,7 +24,6 @@ void Fish::randomizeMask() { // Clear and rebuild color map 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') { @@ -35,7 +31,6 @@ void Fish::randomizeMask() { 0, static_cast(AVAILABLE_COLORS.size()) - 1)]; } } - // Apply color mapping to mask for (auto &line : mask) { for (char &ch : line) { @@ -52,50 +47,13 @@ int Fish::getRandomAssetIndex() { void Fish::update() noexcept { x += moving_right ? speed : -speed; } -void Fish::draw(int layer) const { - auto &aquarium = Aquarium::getInstance(); - - // Pre-allocate strings to avoid repeated allocations - std::string current_segment; - std::string current_colors; - current_segment.reserve(32); // Reserve reasonable capacity - current_colors.reserve(32); - - const int base_x = static_cast(x); - const int base_y = static_cast(y); - - for (size_t i = 0; i < image.size(); ++i) { - const std::string &row = image[i]; - const std::string &mask_row = (i < mask.size()) ? mask[i] : ""; - - int cursor_x = base_x; - current_segment.clear(); - current_colors.clear(); - - for (size_t j = 0; j < row.size(); ++j) { - const char ch = row[j]; - - if (ch == '?') { - // Flush current segment if not empty - if (!current_segment.empty()) { - aquarium.drawToBackBuffer(base_y + static_cast(i), cursor_x, - layer, current_segment, current_colors); - cursor_x += static_cast(current_segment.size()); - current_segment.clear(); - current_colors.clear(); - } - ++cursor_x; // Skip transparent character - continue; - } - - current_segment.push_back(ch); - current_colors.push_back((j < mask_row.size()) ? mask_row[j] : 'k'); - } - - // Flush remaining segment - if (!current_segment.empty()) { - aquarium.drawToBackBuffer(base_y + static_cast(i), cursor_x, layer, - current_segment, current_colors); - } +bool Fish::isOffScreen() const noexcept { + const auto &aquarium = Aquarium::getInstance(); + if (moving_right) { + // Fish is off screen when its left edge is past the right border + return x > static_cast(aquarium.getWidth()); + } else { + // Fish is off screen when its right edge is past the left border + return (x + static_cast(image[0].length())) < 0; } } diff --git a/src/Fish.h b/src/Fish.h index c15c176..9f25ba1 100644 --- a/src/Fish.h +++ b/src/Fish.h @@ -2,6 +2,7 @@ #include "Entity.h" #include "assets/FishAssets.h" #include +#include class Fish : public Entity { private: @@ -9,11 +10,10 @@ private: 'c', 'C', 'r', 'R', 'y', 'Y', 'b', 'B', 'g', 'G', 'm', 'M'}; const std::vector ℑ - std::vector mask; // Copy needed for color randomization + std::vector mask; const float speed; const bool moving_right; - // Static color map to avoid recreation static std::unordered_map color_map; explicit Fish(int asset_index); @@ -22,6 +22,10 @@ private: public: Fish(); - void update() noexcept; - void draw(int layer) const; + + void update() noexcept override; + bool isOffScreen() const noexcept override; + const std::vector &getImage() const override { return image; } + const std::vector &getMask() const override { return mask; } + char getDefaultColor() const noexcept override { return 'k'; } }; diff --git a/src/Seaweed.h b/src/Seaweed.h index 074721d..600d8e4 100644 --- a/src/Seaweed.h +++ b/src/Seaweed.h @@ -3,11 +3,10 @@ class Seaweed { private: - const int y; + const size_t x, y; static constexpr char PATTERN_LEFT = '('; static constexpr char PATTERN_RIGHT = ')'; - const size_t x; const size_t height; const float speed; diff --git a/src/Ship.cpp b/src/Ship.cpp new file mode 100644 index 0000000..82d7b75 --- /dev/null +++ b/src/Ship.cpp @@ -0,0 +1,39 @@ +#include "Ship.h" +#include "Aquarium.h" +#include "Random.h" +#include "assets/ShipAssets.h" + +Ship::Ship() : Ship(getRandomDirection()) {} + +Ship::Ship(int asset_index) + : Entity(), image(pirateShipAssets[asset_index].image), + mask(pirateShipAssets[asset_index].mask), speed(SHIP_SPEED), + moving_right(asset_index == 0) { + const auto &aquarium = Aquarium::getInstance(); + // Ships move along the water surface (top of aquarium) + y = WATER_SURFACE_OFFSET; + // Start off-screen on appropriate side + if (moving_right) { + x = -static_cast( + image[0].length()); // Start completely off left side + } else { + x = static_cast(aquarium.getWidth()); // Start off right side + } +} + +int Ship::getRandomDirection() { + return Random::intInRange(0, 1); // 0 = right, 1 = left +} + +void Ship::update() noexcept { x += moving_right ? speed : -speed; } + +bool Ship::isOffScreen() const noexcept { + const auto &aquarium = Aquarium::getInstance(); + if (moving_right) { + // Ship is off screen when its left edge is past the right border + return x > static_cast(aquarium.getWidth()); + } else { + // Ship is off screen when its right edge is past the left border + return (x + static_cast(image[0].length())) < 0; + } +} diff --git a/src/Ship.h b/src/Ship.h new file mode 100644 index 0000000..d719e16 --- /dev/null +++ b/src/Ship.h @@ -0,0 +1,26 @@ +#pragma once +#include "Entity.h" +#include "assets/ShipAssets.h" + +class Ship : public Entity { +private: + static constexpr float SHIP_SPEED = 1.0f; + static constexpr int WATER_SURFACE_OFFSET = 0; + + const std::vector ℑ + const std::vector &mask; + const float speed; + const bool moving_right; + + explicit Ship(int asset_index); + static int getRandomDirection(); + +public: + Ship(); + + void update() noexcept override; + bool isOffScreen() const noexcept override; + const std::vector &getImage() const override { return image; } + const std::vector &getMask() const override { return mask; } + char getDefaultColor() const noexcept override { return 'W'; } +}; diff --git a/src/assets/ShipAssets.h b/src/assets/ShipAssets.h new file mode 100644 index 0000000..4d44d9a --- /dev/null +++ b/src/assets/ShipAssets.h @@ -0,0 +1,37 @@ +#pragma once +#include "../Entity.h" +#include + +inline std::vector pirateShipAssets = { + {{ + R"( | | |)", + R"( )_) )_) )_))", + R"( )___))___))___)\)", + R"( )____)____)_____)\\)", + R"(_____|____|____|____\\\__)", + R"(\ /)", + }, + { + R"( y y y)", + R"()", + R"( w)", + R"( ww)", + R"(yyyyyyyyyyyyyyyyyyyywwwyy)", + R"(y y)", + }}, + {{ + R"( | | |)", + R"( (_( (_( (_()", + R"( /(___((___((___()", + R"( //(_____(____(____()", + R"(__///____|____|____|_____)", + R"( \ /)", + }, + { + R"( y y y)", + R"()", + R"( w)", + R"( ww)", + R"(yywwwyyyyyyyyyyyyyyyyyyyy)", + R"( y y)", + }}};