finish mirroring. abstract entity removal

This commit is contained in:
user
2025-07-07 17:25:29 -04:00
parent 5706c90f87
commit c54089ae19
17 changed files with 193 additions and 231 deletions

View File

@@ -1,7 +1,7 @@
# Compiler and flags
CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra -Os -fno-exceptions -fno-asynchronous-unwind-tables -fno-unwind-tables
LDFLAGS = -lncurses -ltinfo -Wl,--gc-sections -s
CXXFLAGS = -std=c++17 -Wall -Wextra -O3
LDFLAGS = -lncurses -ltinfo
# Directories
SRC_DIR = src

View File

@@ -14,7 +14,6 @@ public:
const std::vector<std::string> &getMask() const override { return mask; }
char getDefaultColor() const noexcept override { return 'K'; }
bool shouldBeRemoved() const noexcept override { return false; }
std::unique_ptr<Entity> createReplacement() const override { return nullptr; }
int getPreferredLayer() const noexcept override { return 0; }
};

View File

@@ -1,6 +1,5 @@
#include "Entity.h"
#include "Aquarium.h"
#include <algorithm>
void Entity::draw() const {
auto &aquarium = Aquarium::getInstance();
@@ -57,25 +56,11 @@ void Entity::draw() const {
}
}
size_t Entity::getMaxRowWidth(const std::vector<std::string> &image) noexcept {
if (image.empty()) {
return 0;
}
size_t max_width = 0;
for (const auto &row : image) {
max_width = std::max(max_width, row.length());
}
return max_width;
}
bool Entity::shouldBeRemoved() const noexcept {
const auto &aquarium = Aquarium::getInstance();
const auto &current_image = getImage();
const float entity_width = static_cast<float>(getMaxRowWidth(current_image));
// Default implementation: remove if completely off-screen
return (x + entity_width < 0) ||
(x > static_cast<float>(aquarium.getWidth()));
if (moving_right) {
return x > static_cast<float>(aquarium.getWidth());
} else {
return (x + static_cast<float>(getWidth())) < 0;
}
}

View File

@@ -10,33 +10,33 @@ struct AssetPair {
class Entity {
protected:
const bool moving_right;
float x;
float y;
static inline size_t next_id = 0;
const size_t entity_id;
virtual const std::vector<std::string> &getImage() const = 0;
virtual const std::vector<std::string> &getMask() const = 0;
virtual char getDefaultColor() const noexcept = 0;
public:
Entity() : x(0.0f), y(0.0f), entity_id(++next_id) {}
Entity() : moving_right(false), x(0.0f), y(0.0f), entity_id(++next_id) {}
Entity(bool moving_right)
: moving_right(moving_right), x(0.0f), y(0.0f), entity_id(++next_id) {}
Entity(float init_x, float init_y)
: x(init_x), y(init_y), entity_id(++next_id) {}
: moving_right(false), x(init_x), y(init_y), entity_id(++next_id) {}
virtual ~Entity() = default;
float getX() const noexcept { return x; }
float getY() const noexcept { return y; }
virtual size_t getWidth() const noexcept { return getImage()[0].length(); }
size_t getId() const noexcept { return entity_id; }
virtual void update() noexcept = 0;
virtual const std::vector<std::string> &getImage() const = 0;
virtual const std::vector<std::string> &getMask() const = 0;
virtual char getDefaultColor() const noexcept = 0;
virtual bool shouldBeRemoved() const noexcept;
virtual std::unique_ptr<Entity> createReplacement() const { return nullptr; }
virtual int getPreferredLayer() const noexcept = 0;
void draw() const;
protected:
// Helper function to get the maximum width of any row in an image
static size_t getMaxRowWidth(const std::vector<std::string> &image) noexcept;
};

View File

@@ -9,10 +9,9 @@ std::unordered_map<char, char> Fish::color_map;
Fish::Fish() : Fish(getRandomAssetIndex()) {}
Fish::Fish(int asset_index)
: Entity(), image(fishAssetPairs[asset_index].image),
: Entity(asset_index % 2 == 0), image(fishAssetPairs[asset_index].image),
mask(fishAssetPairs[asset_index].mask),
speed(Random::floatInRange(0.25f, 2.25f)),
moving_right(asset_index % 2 == 0) {
speed(Random::floatInRange(0.25f, 2.25f)) {
const auto &aquarium = Aquarium::getInstance();
y = Random::intInRange(static_cast<int>(image.size()) + 6,
@@ -50,15 +49,6 @@ int Fish::getRandomAssetIndex() {
void Fish::update() noexcept { x += moving_right ? speed : -speed; }
bool Fish::shouldBeRemoved() const noexcept {
const auto &aquarium = Aquarium::getInstance();
if (moving_right) {
return x > static_cast<float>(aquarium.getWidth());
} else {
return (x + static_cast<float>(image[0].length())) < 0;
}
}
std::unique_ptr<Entity> Fish::createReplacement() const {
return std::make_unique<Fish>();
}

View File

@@ -12,7 +12,6 @@ private:
const std::vector<std::string> &image;
std::vector<std::string> mask;
const float speed;
const bool moving_right;
static std::unordered_map<char, char> color_map;
@@ -28,7 +27,6 @@ public:
const std::vector<std::string> &getMask() const override { return mask; }
char getDefaultColor() const noexcept override { return 'k'; }
bool shouldBeRemoved() const noexcept override;
std::unique_ptr<Entity> createReplacement() const override;
int getPreferredLayer() const noexcept override;

View File

@@ -6,21 +6,21 @@
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) {
: Entity(asset_index == 0), frames(seaMonsterAssets[asset_index].frames),
mask(seaMonsterAssets[asset_index].mask), speed(SEAMONSTER_SPEED) {
const auto &aquarium = Aquarium::getInstance();
y = WATER_SURFACE_OFFSET;
// Use first frame for positioning calculations
const auto &first_frame = frames[0];
if (moving_right) {
x = -static_cast<float>(frame1[0].length());
x = -static_cast<float>(first_frame[0].length());
} else {
x = static_cast<float>(aquarium.getWidth());
}
current_image = frame1;
current_image = first_frame;
}
int SeaMonster::getRandomDirection() { return Random::intInRange(0, 1); }
@@ -30,27 +30,14 @@ void SeaMonster::update() noexcept {
++animation_counter;
if (animation_counter >= ANIMATION_DELAY) {
current_frame = !current_frame;
current_frame_index = (current_frame_index + 1) % frames.size();
animation_counter = 0;
}
}
const std::vector<std::string> &SeaMonster::getImage() const {
if (current_frame) {
current_image = frame2;
} else {
current_image = frame1;
}
current_image = frames[current_frame_index];
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; }

View File

@@ -7,13 +7,11 @@ 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::vector<std::string>> frames;
const std::vector<std::string> &mask;
const float speed;
const bool moving_right;
bool current_frame = false;
int current_frame_index = 0;
int animation_counter = 0;
mutable std::vector<std::string> current_image;
@@ -30,6 +28,5 @@ public:
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;
};

View File

@@ -6,12 +6,11 @@
Ship::Ship() : Ship(getRandomDirection()) {}
Ship::Ship(int asset_index)
: Entity(), image(shipAssets[asset_index].image),
mask(shipAssets[asset_index].mask), speed(SHIP_SPEED),
moving_right(asset_index == 0) {
: Entity(asset_index == 0), image(shipAssets[asset_index].image),
mask(shipAssets[asset_index].mask), speed(SHIP_SPEED) {
const auto &aquarium = Aquarium::getInstance();
y = WATER_SURFACE_OFFSET;
y = 0;
if (moving_right) {
x = -static_cast<float>(image[0].length());
} else {
@@ -23,13 +22,4 @@ int Ship::getRandomDirection() { return Random::intInRange(0, 1); }
void Ship::update() noexcept { x += moving_right ? speed : -speed; }
bool Ship::shouldBeRemoved() const noexcept {
const auto &aquarium = Aquarium::getInstance();
if (moving_right) {
return x > static_cast<float>(aquarium.getWidth());
} else {
return (x + static_cast<float>(image[0].length())) < 0;
}
}
int Ship::getPreferredLayer() const noexcept { return 9; }

View File

@@ -5,12 +5,10 @@
class Ship : public Entity {
private:
static constexpr float SHIP_SPEED = 1.0f;
static constexpr int WATER_SURFACE_OFFSET = 0;
const std::vector<std::string> &image;
const std::vector<std::string> &mask;
const float speed;
const bool moving_right;
explicit Ship(int asset_index);
static int getRandomDirection();
@@ -23,6 +21,5 @@ public:
const std::vector<std::string> &getMask() const override { return mask; }
char getDefaultColor() const noexcept override { return 'W'; }
bool shouldBeRemoved() const noexcept override;
int getPreferredLayer() const noexcept override;
};

View File

@@ -1,3 +1,4 @@
// SpriteUtils.h - Unified mirroring for all asset types
#pragma once
#include <algorithm>
#include <string>
@@ -6,63 +7,75 @@
struct AssetPair;
namespace SpriteUtils {
// Character mapping for directional elements
const std::unordered_map<char, char> charFlipMap = {
{'(', ')'}, {')', '('}, {'[', ']'}, {']', '['}, {'{', '}'}, {'}', '{'},
{'<', '>'}, {'>', '<'}, {'/', '\\'}, {'\\', '/'}, {'`', '\''}, {'\'', '`'},
};
{'(', ')'}, {')', '('}, {'[', ']'}, {']', '['}, {'{', '}'},
{'}', '{'}, {'<', '>'}, {'>', '<'}, {'/', '\\'}, {'\\', '/'}};
// Mirror a single character (handle directional flipping)
inline char mirrorChar(char c) {
auto it = charFlipMap.find(c);
return (it != charFlipMap.end()) ? it->second : c;
}
// Mirror a single row with proper character flipping
inline std::string mirrorRow(std::string row) {
// First reverse the string
std::reverse(row.begin(), row.end());
// Then flip directional characters
for (char &c : row) {
c = mirrorChar(c);
}
return row;
}
// Mirror an entire sprite
// Mirror an entire sprite (vector of strings)
inline std::vector<std::string>
mirrorSprite(const std::vector<std::string> &sprite) {
std::vector<std::string> mirrored;
mirrored.reserve(sprite.size());
for (const auto &row : sprite) {
mirrored.push_back(mirrorRow(row));
}
return mirrored;
}
// Mirror an AssetPair
inline AssetPair mirrorAssetPair(const AssetPair &asset) {
return {mirrorSprite(asset.image), mirrorSprite(asset.mask)};
}
// Create bidirectional asset pairs from right-facing assets
// Create bidirectional assets from simple AssetPair vector
inline std::vector<AssetPair>
createBidirectionalAssets(const std::vector<AssetPair> &rightFacingAssets) {
std::vector<AssetPair> result;
result.reserve(rightFacingAssets.size() * 2);
for (const auto &asset : rightFacingAssets) {
// Add right-facing version
result.push_back(asset);
// Add left-facing (mirrored) version
result.push_back(mirrorAssetPair(asset));
result.push_back(asset); // Right-facing
result.push_back(mirrorAssetPair(asset)); // Left-facing (mirrored)
}
return result;
}
} // namespace SpriteUtils
// Generic template for any asset type with frames and mask
template <typename AssetType>
AssetType mirrorFramedAsset(const AssetType &asset) {
AssetType mirrored;
// Mirror all frames
for (const auto &frame : asset.frames) {
mirrored.frames.push_back(mirrorSprite(frame));
}
// Mirror mask
mirrored.mask = mirrorSprite(asset.mask);
return mirrored;
}
// Create bidirectional assets from single framed asset
template <typename AssetType>
std::vector<AssetType>
createBidirectionalFramedAssets(const AssetType &rightFacingAsset) {
return {
rightFacingAsset, // [0] = Right-facing
mirrorFramedAsset(rightFacingAsset) // [1] = Left-facing (mirrored)
};
}

View File

@@ -24,7 +24,6 @@ public:
const std::vector<std::string> &getMask() const override;
char getDefaultColor() const noexcept override { return WATERLINE_COLOR; }
bool shouldBeRemoved() const noexcept override { return false; }
std::unique_ptr<Entity> createReplacement() const override { return nullptr; }
int getPreferredLayer() const noexcept override { return 0; }

View File

@@ -6,9 +6,8 @@
Whale::Whale() : Whale(getRandomDirection()) {}
Whale::Whale(int asset_index)
: Entity(), frames(whaleAssets[asset_index].frames),
mask(whaleAssets[asset_index].mask), speed(WHALE_SPEED),
moving_right(asset_index == 0) {
: Entity((asset_index = 0)), frames(whaleAssets[asset_index].frames),
mask(whaleAssets[asset_index].mask), speed(WHALE_SPEED) {
const auto &aquarium = Aquarium::getInstance();
y = 0;
@@ -46,15 +45,4 @@ const std::vector<std::string> &Whale::getImage() const {
return current_image;
}
bool Whale::shouldBeRemoved() const noexcept {
const auto &aquarium = Aquarium::getInstance();
const auto &first_frame = frames[0];
if (moving_right) {
return x > static_cast<float>(aquarium.getWidth());
} else {
return (x + static_cast<float>(first_frame[6].length())) < 0;
}
}
int Whale::getPreferredLayer() const noexcept { return 8; }

View File

@@ -9,7 +9,6 @@ private:
const std::vector<std::vector<std::string>> frames;
const std::vector<std::string> &mask;
const float speed;
const bool moving_right;
int current_frame_index = 0;
int animation_counter = 0;
@@ -28,7 +27,5 @@ public:
const std::vector<std::string> &getImage() const override;
const std::vector<std::string> &getMask() const override { return mask; }
char getDefaultColor() const noexcept override { return 'B'; }
bool shouldBeRemoved() const noexcept override;
int getPreferredLayer() const noexcept override;
};

View File

@@ -1,53 +1,105 @@
#pragma once
#include "../Entity.h"
#include "SpriteUtils.h"
#include "../SpriteUtils.h"
#include <vector>
// Store only right-facing fish assets (half the data!)
const std::vector<AssetPair> rightFacingFishAssets = {
{{R"(???\??)", R"(??/ \?)", R"(>=_('>)", R"(??\_/?)", R"(???/??)"},
{R"( 1 )", R"( 1 1 )", R"(663745)", R"( 111 )", R"( 3 )"}},
{{R"(?????,????)", R"(?????}\???)", R"(\??.' `\?)", R"(}}< ( 6>)",
R"(/??`, .'?)", R"(?????}/???)", R"(?????'????)"},
{R"( 2 )", R"( 22 )", R"(6 11 11 )", R"(661 7 45)",
R"(6 11 11 )", R"( 33 )", R"( 3 )"}},
{{R"(???????,--,_????)", R"(__????_\.---'-.?)", R"(\ '.-" // o\)",
R"(/_.'-._ \\ /)", R"(???????`"--(/"`?)"},
{R"( 22222 )", R"(66 121111211 )", R"(6 6111 77 41)",
R"(6661111 77 1)", R"( 11113311 )"}},
{{R"(??__?)", R"(><_'>)", R"(???'?)"},
{R"( 11 )", R"(61145)", R"( 3 )"}},
{{R"(????????_.-`\??????)", R"(?????-:`_..,_\?????)",
R"(('-..:-` , '-.,?)", R"(?} _ ;':( o :)",
R"((.-`/'-.,__'` _.-`?)", R"(???`'-.,/??//`?????)"},
{R"( 22222 )", R"( 222111112 )",
R"(66661111 7 1111 )", R"( 6 1 7777 4 1)",
R"(6666211111177 1111 )", R"( 222222 333 )"}},
{{
R"(????????/\??????)",
R"(????????\.\_????)",
R"(\'-,.:-` '-,?)",
R"( ) _ (>( o <)",
R"(/.-`?':._ _.-`?)",
R"(??????;/?``?????)",
const std::vector<AssetPair> fishAssets = {
{
{
R"(???\??)",
R"(??/ \?)",
R"(>=_('>)",
R"(??\_/?)",
R"(???/??)"},
{
R"( 1 )",
R"( 1 1 )",
R"(663745)",
R"( 111 )",
R"( 3 )"}},
{
{
R"(?????,????)",
R"(?????}\???)",
R"(\??.' `\?)",
R"(}}< ( 6>)",
R"(/??`, .'?)",
R"(?????}/???)",
R"(?????'????)"},
{
R"( 2 )",
R"( 22 )",
R"(6 11 11 )",
R"(661 7 45)",
R"(6 11 11 )",
R"( 33 )",
R"( 3 )"}},
{
{
R"(???????,--,_????)",
R"(__????_\.---'-.?)",
R"(\ '.-" // o\)",
R"(/_.'-._ \\ /)",
R"(???????`"--(/"`?)"},
{
R"( 22222 )",
R"(66 121111211 )",
R"(6 6111 77 41)",
R"(6661111 77 1)",
R"( 11113311 )"}},
{
{
R"(??__?)",
R"(><_'>)",
R"(???'?)"},
{
R"( 11 )",
R"(61145)",
R"( 3 )"}},
{
{
R"(????????_.-`\??????)",
R"(?????-:`_..,_\?????)",
R"(('-..:-` , '-.,?)",
R"(?} _ ;':( o :)",
R"((.-`/'-.,__'` _.-`?)",
R"(???`'-.,/??//`?????)"},
{
R"( 22222 )",
R"( 222111112 )",
R"(66661111 7 1111 )",
R"( 6 1 7777 4 1)",
R"(6666211111177 1111 )",
R"( 222222 333 )"}},
{
{
R"(????????/\??????)",
R"(????????\.\_????)",
R"(\'-,.:-` '-,?)",
R"( ) _ (>( o <)",
R"(/.-`?':._ _.-`?)",
R"(??????;/?``?????)",
},
{
R"( 22 )",
R"( 2121 )",
R"(66661111 111 )",
R"( 6 1 777 4 1)",
R"(6666 1111 1111 )",
R"( 22 33 )",
}},
{{R"(_?????????_.*"\??????)", R"(\'-._..-*` `'*-.??)",
R"(?) , (( o >)", R"(/.`"*--.__)_.`_.-*`??)"},
R"( 22 )",
R"( 2121 )",
R"(66661111 111 )",
R"( 6 1 777 4 1)",
R"(6666 1111 1111 )",
R"( 22 33 )",}},
{
{
R"(_?????????_.*"\??????)",
R"(\'-._..-*` `'*-.??)",
R"(?) , (( o >)",
R"(/.`"*--.__)_.`_.-*`??)"},
{
R"(6 11222 )",
R"(6661111111 11111 )",
R"( 6 3 77 4 1)",
R"(6661111111311311111 )",
R"(6 11222 )",
R"(6661111111 11111 )",
R"( 6 3 77 4 1)",
R"(6661111111311311111 )",
}}};
inline const std::vector<AssetPair> fishAssetPairs =
SpriteUtils::createBidirectionalAssets(rightFacingFishAssets);
createBidirectionalAssets(fishAssets);

View File

@@ -1,38 +1,30 @@
#pragma once
#include "../Entity.h"
#include "../SpriteUtils.h"
#include <vector>
struct SeaMonsterAsset {
std::vector<std::string> frame1;
std::vector<std::string> frame2;
std::vector<std::vector<std::string>> frames;
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"()"}}};
const SeaMonsterAsset seaMonster = {
{// Frame 1
{
R"(?????????_???_?????????????????????_???_???????_a_a???)",
R"(???????_{.`=`.}_??????_???_??????_{.`=`.}_????{/ ''\_?)",
R"(?_????{.' _ '.}????{.`'`.}????{.' _ '.}??{| ._oo))",
R"({ \??{/ .'?'. \}??{/ .-. \}??{/ .'?'. \}?{/ |????)"},
{
R"(??????????????????????_???_????????????????????_a_a???)",
R"(??_??????_???_??????_{.`=`.}_??????_???_??????{/ ''\_?)",
R"(?{ \????{.`'`.}????{.' _ '.}????{.`'`.}????{| ._oo))",
R"(??\ \??{/ .-. \}??{/ .'?'. \}??{/ .-. \}???{/ |????)"}},
{
R"( W W )",
R"()",
R"()",
R"()"}};
inline const std::vector<SeaMonsterAsset> seaMonsterAssets =
createBidirectionalFramedAssets(seaMonster);

View File

@@ -1,37 +1,15 @@
#pragma once
#include "../Entity.h"
#include "../SpriteUtils.h"
#include <vector>
inline std::vector<AssetPair> shipAssets = {
{{
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)",
}}};
const AssetPair ship = {
{R"( | | | )", R"( )_) )_) )_) )",
R"( )___))___))___)\ )", R"( )____)____)_____)\\ )",
R"(_____|____|____|____\\\__)", R"(\ /????)"},
{R"( y y y )", R"()", R"( w )",
R"( ww )", R"(yyyyyyyyyyyyyyyyyyyywwwyy)",
R"(y y )"}};
inline const std::vector<AssetPair> shipAssets =
createBidirectionalAssets({ship});