add config refactor structure
This commit is contained in:
27
src/entities/Bubble.cpp
Normal file
27
src/entities/Bubble.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "Bubble.h"
|
||||
|
||||
Bubble::Bubble(float x, float y) : Entity(x, y) {
|
||||
current_image.resize(1);
|
||||
current_mask.resize(1);
|
||||
updateFrame();
|
||||
}
|
||||
|
||||
void Bubble::update() noexcept {
|
||||
--y;
|
||||
++lifetime;
|
||||
updateFrame();
|
||||
}
|
||||
|
||||
void Bubble::updateFrame() {
|
||||
int frameIndex = std::min(lifetime / FRAMES_PER_ANIMATION, MAX_FRAME_INDEX);
|
||||
current_image[0] = BUBBLE_FRAMES[frameIndex];
|
||||
current_mask[0] = BUBBLE_COLOR;
|
||||
}
|
||||
|
||||
// Waterline collision
|
||||
bool Bubble::shouldBeRemoved() const noexcept { return y < 10; }
|
||||
|
||||
// Bubbles don't create replacements
|
||||
std::unique_ptr<Entity> Bubble::createReplacement() const { return nullptr; }
|
||||
|
||||
int Bubble::getPreferredLayer() const noexcept { return 5; }
|
||||
33
src/entities/Bubble.h
Normal file
33
src/entities/Bubble.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include "Entity.h"
|
||||
#include <string>
|
||||
|
||||
class Bubble : public Entity {
|
||||
private:
|
||||
static constexpr const char *BUBBLE_FRAMES[3] = {".", "o", "O"};
|
||||
static constexpr int FRAMES_PER_ANIMATION = 9;
|
||||
static constexpr int MAX_FRAME_INDEX = 2;
|
||||
static constexpr char BUBBLE_COLOR = 'c';
|
||||
|
||||
int lifetime = 0;
|
||||
std::vector<std::string> current_image;
|
||||
std::vector<std::string> current_mask;
|
||||
|
||||
void updateFrame();
|
||||
|
||||
public:
|
||||
Bubble(float x, float y);
|
||||
|
||||
void update() noexcept override;
|
||||
const std::vector<std::string> &getImage() const override {
|
||||
return current_image;
|
||||
}
|
||||
const std::vector<std::string> &getMask() const override {
|
||||
return current_mask;
|
||||
}
|
||||
char getDefaultColor() const noexcept override { return BUBBLE_COLOR; }
|
||||
|
||||
bool shouldBeRemoved() const noexcept override;
|
||||
std::unique_ptr<Entity> createReplacement() const override;
|
||||
int getPreferredLayer() const noexcept override;
|
||||
};
|
||||
8
src/entities/Castle.cpp
Normal file
8
src/entities/Castle.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "Castle.h"
|
||||
#include "../assets/CastleAssets.h"
|
||||
#include "../core/Aquarium.h"
|
||||
|
||||
Castle::Castle()
|
||||
: Entity(Aquarium::getInstance().getWidth() - 32,
|
||||
Aquarium::getInstance().getHeight() - 13),
|
||||
image(getCastleAsset().image), mask(getCastleAsset().mask) {}
|
||||
19
src/entities/Castle.h
Normal file
19
src/entities/Castle.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
#include "Entity.h"
|
||||
|
||||
class Castle : public Entity {
|
||||
private:
|
||||
const std::vector<std::string> image;
|
||||
const std::vector<std::string> mask;
|
||||
|
||||
public:
|
||||
Castle();
|
||||
|
||||
void update() noexcept override {} // Castle doesn't move
|
||||
const std::vector<std::string> &getImage() const override { return image; }
|
||||
const std::vector<std::string> &getMask() const override { return mask; }
|
||||
char getDefaultColor() const noexcept override { return 'K'; }
|
||||
|
||||
std::unique_ptr<Entity> createReplacement() const override { return nullptr; }
|
||||
int getPreferredLayer() const noexcept override { return 0; }
|
||||
};
|
||||
63
src/entities/Entity.cpp
Normal file
63
src/entities/Entity.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "Entity.h"
|
||||
#include "../core/Aquarium.h"
|
||||
|
||||
void Entity::draw() const {
|
||||
auto &aquarium = Aquarium::getInstance();
|
||||
|
||||
const auto &image = getImage();
|
||||
const auto &mask = getMask();
|
||||
const char default_color = getDefaultColor();
|
||||
|
||||
const int base_x = static_cast<int>(x);
|
||||
const int base_y = static_cast<int>(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] : "";
|
||||
|
||||
// Build complete line at once instead of segments
|
||||
std::string line;
|
||||
std::string colors;
|
||||
line.reserve(row.size());
|
||||
colors.reserve(row.size());
|
||||
|
||||
int start_x = base_x;
|
||||
|
||||
for (size_t j = 0; j < row.size(); ++j) {
|
||||
const char ch = row[j];
|
||||
|
||||
if (ch == '?') {
|
||||
// Flush current line if not empty
|
||||
if (!line.empty()) {
|
||||
aquarium.drawToFrame(base_y + static_cast<int>(i), start_x, line,
|
||||
colors);
|
||||
start_x += static_cast<int>(line.size()) + 1; // +1 for the '?' skip
|
||||
line.clear();
|
||||
colors.clear();
|
||||
} else {
|
||||
++start_x; // Just skip the '?' position
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
line.push_back(ch);
|
||||
|
||||
char color = default_color;
|
||||
if (j < mask_row.size() && mask_row[j] != ' ') {
|
||||
color = mask_row[j];
|
||||
}
|
||||
colors.push_back(color);
|
||||
}
|
||||
|
||||
// Flush remaining line
|
||||
if (!line.empty()) {
|
||||
aquarium.drawToFrame(base_y + static_cast<int>(i), start_x, line, colors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Entity::shouldBeRemoved() const noexcept {
|
||||
const auto &aquarium = Aquarium::getInstance();
|
||||
// unsigned nonsense
|
||||
return x < -static_cast<int>(getWidth()) || x > aquarium.getWidth();
|
||||
}
|
||||
42
src/entities/Entity.h
Normal file
42
src/entities/Entity.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct AssetPair {
|
||||
std::vector<std::string> image;
|
||||
std::vector<std::string> mask;
|
||||
};
|
||||
|
||||
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() : 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)
|
||||
: 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 int getWidth() const noexcept { return getImage()[0].length(); }
|
||||
size_t getId() const noexcept { return entity_id; }
|
||||
|
||||
virtual void update() 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;
|
||||
};
|
||||
63
src/entities/Fish.cpp
Normal file
63
src/entities/Fish.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "Fish.h"
|
||||
#include "../assets/FishAssets.h"
|
||||
#include "../core/Aquarium.h"
|
||||
#include "../utils/Random.h"
|
||||
#include "../utils/defs.h"
|
||||
|
||||
std::unordered_map<char, char> Fish::color_map;
|
||||
|
||||
Fish::Fish() : Fish(getRandomAssetIndex()) {}
|
||||
|
||||
Fish::Fish(int asset_index)
|
||||
: Entity(asset_index % 2 == 0),
|
||||
image(getFishAssetPairs()[asset_index].image),
|
||||
mask(getFishAssetPairs()[asset_index].mask),
|
||||
speed(Random::floatInRange(0.25f, 2.25f)) {
|
||||
|
||||
const auto &aquarium = Aquarium::getInstance();
|
||||
y = Random::intInRange(static_cast<int>(image.size()) + 6,
|
||||
aquarium.getHeight() - static_cast<int>(image.size()));
|
||||
x = moving_right ? -this->getWidth()
|
||||
: static_cast<float>(aquarium.getWidth());
|
||||
randomizeMask();
|
||||
}
|
||||
|
||||
void Fish::randomizeMask() {
|
||||
// Clear and rebuild color map with random colors each time
|
||||
color_map.clear();
|
||||
color_map['4'] = 'W'; // White is always '4' for eyes
|
||||
|
||||
// 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 = color_map.find(ch); it != color_map.end()) {
|
||||
ch = it->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Fish::getRandomAssetIndex() {
|
||||
return Random::intInRange(0,
|
||||
static_cast<int>(getFishAssetPairs().size()) - 1);
|
||||
}
|
||||
|
||||
void Fish::update() noexcept { x += moving_right ? speed : -speed; }
|
||||
|
||||
std::unique_ptr<Entity> Fish::createReplacement() const {
|
||||
return std::make_unique<Fish>();
|
||||
}
|
||||
|
||||
int Fish::getPreferredLayer() const noexcept { return 10; }
|
||||
|
||||
bool Fish::shouldSpawnBubble() const {
|
||||
return Random::floatInRange(0, 1) < BUBBLE_SPAWN_CHANCE;
|
||||
}
|
||||
34
src/entities/Fish.h
Normal file
34
src/entities/Fish.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "../assets/FishAssets.h"
|
||||
#include "Entity.h"
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
|
||||
class Fish : public Entity {
|
||||
private:
|
||||
static constexpr std::array<char, 12> AVAILABLE_COLORS = {
|
||||
'c', 'C', 'r', 'R', 'y', 'Y', 'b', 'B', 'g', 'G', 'm', 'M'};
|
||||
|
||||
const std::vector<std::string> ℑ
|
||||
std::vector<std::string> mask;
|
||||
const float speed;
|
||||
|
||||
static std::unordered_map<char, char> color_map;
|
||||
|
||||
explicit Fish(int asset_index);
|
||||
static int getRandomAssetIndex();
|
||||
void randomizeMask();
|
||||
|
||||
public:
|
||||
Fish();
|
||||
|
||||
void update() noexcept override;
|
||||
const std::vector<std::string> &getImage() const override { return image; }
|
||||
const std::vector<std::string> &getMask() const override { return mask; }
|
||||
char getDefaultColor() const noexcept override { return 'k'; }
|
||||
|
||||
std::unique_ptr<Entity> createReplacement() const override;
|
||||
int getPreferredLayer() const noexcept override;
|
||||
|
||||
bool shouldSpawnBubble() const;
|
||||
};
|
||||
44
src/entities/SeaMonster.cpp
Normal file
44
src/entities/SeaMonster.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "SeaMonster.h"
|
||||
#include "../assets/SeaMonsterAssets.h"
|
||||
#include "../core/Aquarium.h"
|
||||
#include "../utils/Random.h"
|
||||
|
||||
SeaMonster::SeaMonster() : SeaMonster(getRandomDirection()) {}
|
||||
|
||||
SeaMonster::SeaMonster(int asset_index)
|
||||
: Entity(asset_index == 0),
|
||||
frames(getSeaMonsterAssets()[asset_index].frames),
|
||||
mask(getSeaMonsterAssets()[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>(first_frame[0].length());
|
||||
} else {
|
||||
x = static_cast<float>(aquarium.getWidth());
|
||||
}
|
||||
|
||||
current_image = first_frame;
|
||||
}
|
||||
|
||||
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_index = (current_frame_index + 1) % frames.size();
|
||||
animation_counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string> &SeaMonster::getImage() const {
|
||||
current_image = frames[current_frame_index];
|
||||
return current_image;
|
||||
}
|
||||
|
||||
int SeaMonster::getPreferredLayer() const noexcept { return 8; }
|
||||
32
src/entities/SeaMonster.h
Normal file
32
src/entities/SeaMonster.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include "../assets/SeaMonsterAssets.h"
|
||||
#include "Entity.h"
|
||||
|
||||
class SeaMonster : public Entity {
|
||||
private:
|
||||
static constexpr float SEAMONSTER_SPEED = 0.8f;
|
||||
static constexpr int WATER_SURFACE_OFFSET = 2;
|
||||
|
||||
const std::vector<std::vector<std::string>> frames;
|
||||
const std::vector<std::string> &mask;
|
||||
const float speed;
|
||||
|
||||
int current_frame_index = 0;
|
||||
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'; }
|
||||
|
||||
int getPreferredLayer() const noexcept override;
|
||||
};
|
||||
63
src/entities/Seaweed.cpp
Normal file
63
src/entities/Seaweed.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "Seaweed.h"
|
||||
#include "../core/Aquarium.h"
|
||||
#include "../utils/Random.h"
|
||||
#include "../utils/defs.h"
|
||||
|
||||
Seaweed::Seaweed()
|
||||
: Entity(),
|
||||
height(Random::intInRange(SEAWEED_MIN_HEIGHT, SEAWEED_MAX_HEIGHT)),
|
||||
speed(Random::floatInRange(0.1f, 0.3f)),
|
||||
lifetime(Random::intInRange(SEAWEED_MIN_LIFETIME, SEAWEED_MAX_LIFETIME)) {
|
||||
|
||||
x = Random::intInRange(0, Aquarium::getInstance().getWidth());
|
||||
y = Aquarium::getInstance().getHeight() - height;
|
||||
|
||||
current_image.resize(height);
|
||||
current_mask.resize(height);
|
||||
generateCurrentFrame();
|
||||
}
|
||||
|
||||
void Seaweed::update() noexcept {
|
||||
frame += speed;
|
||||
if (frame >= 1.0f) {
|
||||
pattern_flipped = !pattern_flipped;
|
||||
frame -= 1.0f;
|
||||
frame_dirty = true; // mark frame as needing regeneration
|
||||
}
|
||||
--lifetime;
|
||||
}
|
||||
|
||||
const std::vector<std::string> &Seaweed::getImage() const {
|
||||
if (frame_dirty) {
|
||||
generateCurrentFrame();
|
||||
frame_dirty = false;
|
||||
}
|
||||
return current_image;
|
||||
}
|
||||
|
||||
const std::vector<std::string> &Seaweed::getMask() const {
|
||||
if (frame_dirty) {
|
||||
generateCurrentFrame();
|
||||
frame_dirty = false;
|
||||
}
|
||||
return current_mask;
|
||||
}
|
||||
|
||||
void Seaweed::generateCurrentFrame() const {
|
||||
for (size_t i = 0; i < height; ++i) {
|
||||
const bool use_left = (i % 2 == 0) ^ pattern_flipped;
|
||||
const char ch = use_left ? PATTERN_LEFT : PATTERN_RIGHT;
|
||||
const int offset = use_left ? 0 : 1;
|
||||
|
||||
current_image[i] = std::string(offset, ' ') + ch;
|
||||
current_mask[i] = std::string(current_image[i].size(), 'g');
|
||||
}
|
||||
}
|
||||
|
||||
bool Seaweed::shouldBeRemoved() const noexcept { return lifetime == 0; }
|
||||
|
||||
std::unique_ptr<Entity> Seaweed::createReplacement() const {
|
||||
return std::make_unique<Seaweed>();
|
||||
}
|
||||
|
||||
int Seaweed::getPreferredLayer() const noexcept { return 1; }
|
||||
33
src/entities/Seaweed.h
Normal file
33
src/entities/Seaweed.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include "Entity.h"
|
||||
|
||||
class Seaweed : public Entity {
|
||||
private:
|
||||
static constexpr char PATTERN_LEFT = '(';
|
||||
static constexpr char PATTERN_RIGHT = ')';
|
||||
|
||||
const size_t height;
|
||||
const float speed;
|
||||
|
||||
float frame = 0.0f;
|
||||
size_t lifetime;
|
||||
bool pattern_flipped = false;
|
||||
mutable bool frame_dirty = true;
|
||||
|
||||
mutable std::vector<std::string> current_image;
|
||||
mutable std::vector<std::string> current_mask;
|
||||
|
||||
void generateCurrentFrame() const;
|
||||
|
||||
public:
|
||||
Seaweed();
|
||||
|
||||
void update() noexcept override;
|
||||
const std::vector<std::string> &getImage() const override;
|
||||
const std::vector<std::string> &getMask() const override;
|
||||
char getDefaultColor() const noexcept override { return 'g'; }
|
||||
|
||||
bool shouldBeRemoved() const noexcept override;
|
||||
std::unique_ptr<Entity> createReplacement() const override;
|
||||
int getPreferredLayer() const noexcept override;
|
||||
};
|
||||
25
src/entities/Ship.cpp
Normal file
25
src/entities/Ship.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "Ship.h"
|
||||
#include "../assets/ShipAssets.h"
|
||||
#include "../core/Aquarium.h"
|
||||
#include "../utils/Random.h"
|
||||
|
||||
Ship::Ship() : Ship(getRandomDirection()) {}
|
||||
|
||||
Ship::Ship(int asset_index)
|
||||
: Entity(asset_index == 0), image(getShipAssets()[asset_index].image),
|
||||
mask(getShipAssets()[asset_index].mask), speed(SHIP_SPEED) {
|
||||
|
||||
const auto &aquarium = Aquarium::getInstance();
|
||||
y = 0;
|
||||
if (moving_right) {
|
||||
x = -static_cast<float>(image[0].length());
|
||||
} else {
|
||||
x = static_cast<float>(aquarium.getWidth());
|
||||
}
|
||||
}
|
||||
|
||||
int Ship::getRandomDirection() { return Random::intInRange(0, 1); }
|
||||
|
||||
void Ship::update() noexcept { x += moving_right ? speed : -speed; }
|
||||
|
||||
int Ship::getPreferredLayer() const noexcept { return 9; }
|
||||
25
src/entities/Ship.h
Normal file
25
src/entities/Ship.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "../assets/ShipAssets.h"
|
||||
#include "Entity.h"
|
||||
|
||||
class Ship : public Entity {
|
||||
private:
|
||||
static constexpr float SHIP_SPEED = 1.0f;
|
||||
|
||||
const std::vector<std::string> ℑ
|
||||
const std::vector<std::string> &mask;
|
||||
const float speed;
|
||||
|
||||
explicit Ship(int asset_index);
|
||||
static int getRandomDirection();
|
||||
|
||||
public:
|
||||
Ship();
|
||||
|
||||
void update() noexcept override;
|
||||
const std::vector<std::string> &getImage() const override { return image; }
|
||||
const std::vector<std::string> &getMask() const override { return mask; }
|
||||
char getDefaultColor() const noexcept override { return 'W'; }
|
||||
|
||||
int getPreferredLayer() const noexcept override;
|
||||
};
|
||||
83
src/entities/Waterline.cpp
Normal file
83
src/entities/Waterline.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "Waterline.h"
|
||||
#include "../core/Aquarium.h"
|
||||
#include "../utils/Random.h"
|
||||
#include "../utils/defs.h"
|
||||
|
||||
Waterline::Waterline() : Entity(0, WATERLINE_Y) {
|
||||
shape[0] = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~";
|
||||
shape[1] = "^^^^ ^^^ ^^^ ^^^ ^^^^ ";
|
||||
shape[2] = "^^^^ ^^^^ ^^^ ^^ ";
|
||||
shape[3] = "^^ ^^^^ ^^^ ^^^^^^ ";
|
||||
|
||||
const size_t width = Aquarium::getInstance().getWidth();
|
||||
|
||||
for (size_t i = 0; i < NUM_WAVE_LAYERS; ++i) {
|
||||
const std::string &original = shape[i];
|
||||
const size_t pattern_len = original.length();
|
||||
|
||||
// Calculate how many full patterns + remainder we need
|
||||
const size_t full_patterns = width / pattern_len;
|
||||
const size_t remainder = width % pattern_len;
|
||||
|
||||
shape[i].reserve(width);
|
||||
for (size_t p = 0; p < full_patterns; ++p) {
|
||||
shape[i] += original;
|
||||
}
|
||||
if (remainder > 0) {
|
||||
shape[i] += original.substr(0, remainder);
|
||||
}
|
||||
|
||||
// Create color line
|
||||
colorLines[i].assign(shape[i].size(), WATERLINE_COLOR);
|
||||
}
|
||||
|
||||
// Initialize cache vectors
|
||||
cached_image.assign(shape.begin(), shape.end());
|
||||
cached_mask.assign(colorLines.begin(), colorLines.end());
|
||||
}
|
||||
|
||||
void Waterline::update() noexcept {
|
||||
// Use cached probability calculations
|
||||
static constexpr float thresholds[NUM_WAVE_LAYERS] = {
|
||||
0.0f, // Layer 0 never moves
|
||||
0.25f / WAVE_MOVE_CHANCE, 0.5f / WAVE_MOVE_CHANCE,
|
||||
0.75f / WAVE_MOVE_CHANCE};
|
||||
|
||||
for (size_t i = 1; i < NUM_WAVE_LAYERS; ++i) {
|
||||
if (Random::floatInRange(0.0f, 1.0f) < thresholds[i]) {
|
||||
if (Random::intInRange(0, 1) == 0) {
|
||||
shiftStringLeft(shape[i]);
|
||||
} else {
|
||||
shiftStringRight(shape[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update cached vectors
|
||||
std::copy(shape.begin(), shape.end(), cached_image.begin());
|
||||
std::copy(colorLines.begin(), colorLines.end(), cached_mask.begin());
|
||||
}
|
||||
|
||||
void Waterline::shiftStringLeft(std::string &str) {
|
||||
if (!str.empty()) {
|
||||
char first = str.front();
|
||||
str.erase(0, 1);
|
||||
str.push_back(first);
|
||||
}
|
||||
}
|
||||
|
||||
void Waterline::shiftStringRight(std::string &str) {
|
||||
if (!str.empty()) {
|
||||
char last = str.back();
|
||||
str.pop_back();
|
||||
str.insert(0, 1, last);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string> &Waterline::getImage() const {
|
||||
return cached_image;
|
||||
}
|
||||
|
||||
const std::vector<std::string> &Waterline::getMask() const {
|
||||
return cached_mask;
|
||||
}
|
||||
34
src/entities/Waterline.h
Normal file
34
src/entities/Waterline.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "Entity.h"
|
||||
#include <array>
|
||||
|
||||
class Waterline : public Entity {
|
||||
private:
|
||||
static constexpr int WATERLINE_Y = 5;
|
||||
static constexpr char WATERLINE_COLOR = 'c';
|
||||
static constexpr size_t NUM_WAVE_LAYERS = 4;
|
||||
|
||||
// Use arrays instead of vectors for fixed-size data
|
||||
std::array<std::string, NUM_WAVE_LAYERS> shape;
|
||||
std::array<std::string, NUM_WAVE_LAYERS> colorLines;
|
||||
|
||||
// Pre-compute shift operations
|
||||
void shiftStringLeft(std::string &str);
|
||||
void shiftStringRight(std::string &str);
|
||||
|
||||
public:
|
||||
Waterline();
|
||||
|
||||
void update() noexcept override;
|
||||
const std::vector<std::string> &getImage() const override;
|
||||
const std::vector<std::string> &getMask() const override;
|
||||
char getDefaultColor() const noexcept override { return WATERLINE_COLOR; }
|
||||
|
||||
std::unique_ptr<Entity> createReplacement() const override { return nullptr; }
|
||||
int getPreferredLayer() const noexcept override { return 0; }
|
||||
|
||||
private:
|
||||
// Cache vectors to avoid allocation each frame
|
||||
mutable std::vector<std::string> cached_image;
|
||||
mutable std::vector<std::string> cached_mask;
|
||||
};
|
||||
48
src/entities/Whale.cpp
Normal file
48
src/entities/Whale.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "Whale.h"
|
||||
#include "../assets/WhaleAssets.h"
|
||||
#include "../core/Aquarium.h"
|
||||
#include "../utils/Random.h"
|
||||
|
||||
Whale::Whale() : Whale(getRandomDirection()) {}
|
||||
|
||||
Whale::Whale(int asset_index)
|
||||
: Entity((asset_index == 0)), frames(getWhaleAssets()[asset_index].frames),
|
||||
mask(getWhaleAssets()[asset_index].mask), speed(WHALE_SPEED) {
|
||||
|
||||
const auto &aquarium = Aquarium::getInstance();
|
||||
y = 0;
|
||||
|
||||
// Use first frame for positioning calculations
|
||||
const auto &first_frame = frames[0];
|
||||
if (moving_right) {
|
||||
x = -static_cast<float>(first_frame[6].length());
|
||||
} else {
|
||||
x = static_cast<float>(aquarium.getWidth());
|
||||
}
|
||||
|
||||
current_image = first_frame;
|
||||
}
|
||||
|
||||
int Whale::getRandomDirection() { return Random::intInRange(0, 1); }
|
||||
|
||||
void Whale::update() noexcept {
|
||||
x += moving_right ? speed : -speed;
|
||||
|
||||
++animation_counter;
|
||||
|
||||
// Use longer delay for first frame (no water spout)
|
||||
int current_delay =
|
||||
(current_frame_index == 0) ? FIRST_FRAME_PAUSE : ANIMATION_DELAY;
|
||||
|
||||
if (animation_counter >= current_delay) {
|
||||
current_frame_index = (current_frame_index + 1) % frames.size();
|
||||
animation_counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string> &Whale::getImage() const {
|
||||
current_image = frames[current_frame_index];
|
||||
return current_image;
|
||||
}
|
||||
|
||||
int Whale::getPreferredLayer() const noexcept { return 8; }
|
||||
31
src/entities/Whale.h
Normal file
31
src/entities/Whale.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include "../assets/WhaleAssets.h"
|
||||
#include "Entity.h"
|
||||
|
||||
class Whale : public Entity {
|
||||
private:
|
||||
static constexpr float WHALE_SPEED = 1.0f;
|
||||
|
||||
const std::vector<std::vector<std::string>> frames;
|
||||
const std::vector<std::string> &mask;
|
||||
const float speed;
|
||||
|
||||
int current_frame_index = 0;
|
||||
int animation_counter = 0;
|
||||
mutable std::vector<std::string> current_image;
|
||||
|
||||
static constexpr int ANIMATION_DELAY = 1;
|
||||
static constexpr int FIRST_FRAME_PAUSE = 10;
|
||||
|
||||
explicit Whale(int asset_index);
|
||||
static int getRandomDirection();
|
||||
|
||||
public:
|
||||
Whale();
|
||||
|
||||
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 'B'; }
|
||||
int getPreferredLayer() const noexcept override;
|
||||
};
|
||||
Reference in New Issue
Block a user