add ship, improve inheritance
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
#include "defs.h"
|
#include "defs.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
#include <ncurses.h>
|
#include <ncurses.h>
|
||||||
|
|
||||||
int g_maxCells = 0;
|
int g_maxCells = 0;
|
||||||
@@ -72,6 +73,7 @@ void Aquarium::resize() {
|
|||||||
|
|
||||||
addWaterline();
|
addWaterline();
|
||||||
addCastle();
|
addCastle();
|
||||||
|
addShip();
|
||||||
for (int i = 0; i < width / 15; i++)
|
for (int i = 0; i < width / 15; i++)
|
||||||
addSeaweed();
|
addSeaweed();
|
||||||
for (int i = 0; i < width * (height - 9) / 350; i++)
|
for (int i = 0; i < width * (height - 9) / 350; i++)
|
||||||
@@ -112,10 +114,13 @@ void Aquarium::redraw() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int baseFishLayer = 10;
|
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;
|
auto &fish = *it;
|
||||||
fish->draw(baseFishLayer +
|
|
||||||
|
static_cast<const Entity *>(fish.get())
|
||||||
|
->draw(baseFishLayer +
|
||||||
static_cast<int>(std::distance(fishes.begin(), it)));
|
static_cast<int>(std::distance(fishes.begin(), it)));
|
||||||
|
|
||||||
fish->update();
|
fish->update();
|
||||||
|
|
||||||
float fx = fish->getX();
|
float fx = fish->getX();
|
||||||
@@ -123,17 +128,22 @@ void Aquarium::redraw() {
|
|||||||
addBubble(fx, fish->getY());
|
addBubble(fx, fish->getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fx > width || fx < -30) {
|
if (fish->isOffScreen()) {
|
||||||
it = fishes.erase(it); // erase and update iterator
|
it = fishes.erase(it);
|
||||||
addFish();
|
addFish();
|
||||||
} else {
|
} else {
|
||||||
++it; // only increment if not erasing
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
waterline->draw();
|
waterline->draw();
|
||||||
waterline->update();
|
waterline->update();
|
||||||
|
|
||||||
|
static_cast<const Entity *>(ship.get())->draw(9);
|
||||||
|
if (ship->isOffScreen())
|
||||||
|
addShip();
|
||||||
|
ship->update();
|
||||||
|
|
||||||
applyBackBuffer();
|
applyBackBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +161,8 @@ void Aquarium::addCastle() { castle = std::make_unique<Castle>(); }
|
|||||||
|
|
||||||
void Aquarium::addFish() { fishes.emplace_back(std::make_unique<Fish>()); }
|
void Aquarium::addFish() { fishes.emplace_back(std::make_unique<Fish>()); }
|
||||||
|
|
||||||
|
void Aquarium::addShip() { ship = std::make_unique<Ship>(); }
|
||||||
|
|
||||||
void Aquarium::clearBackBuffer() {
|
void Aquarium::clearBackBuffer() {
|
||||||
for (auto &row : backBuffer)
|
for (auto &row : backBuffer)
|
||||||
std::fill(row.begin(), row.end(), Cell());
|
std::fill(row.begin(), row.end(), Cell());
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
#include "Castle.h"
|
#include "Castle.h"
|
||||||
#include "Fish.h"
|
#include "Fish.h"
|
||||||
#include "Seaweed.h"
|
#include "Seaweed.h"
|
||||||
|
#include "Ship.h"
|
||||||
#include "Waterline.h"
|
#include "Waterline.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <ncurses.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
extern int g_maxCells;
|
extern int g_maxCells;
|
||||||
@@ -67,6 +69,7 @@ private:
|
|||||||
std::vector<std::unique_ptr<Fish>> fishes;
|
std::vector<std::unique_ptr<Fish>> fishes;
|
||||||
std::vector<std::unique_ptr<Bubble>> bubbles;
|
std::vector<std::unique_ptr<Bubble>> bubbles;
|
||||||
std::vector<std::unique_ptr<Seaweed>> seaweeds;
|
std::vector<std::unique_ptr<Seaweed>> seaweeds;
|
||||||
|
std::unique_ptr<Ship> ship;
|
||||||
std::unique_ptr<Waterline> waterline;
|
std::unique_ptr<Waterline> waterline;
|
||||||
std::unique_ptr<Castle> castle;
|
std::unique_ptr<Castle> castle;
|
||||||
|
|
||||||
@@ -84,6 +87,7 @@ public:
|
|||||||
void addSeaweed();
|
void addSeaweed();
|
||||||
void addWaterline();
|
void addWaterline();
|
||||||
void addCastle();
|
void addCastle();
|
||||||
|
void addShip();
|
||||||
void redraw();
|
void redraw();
|
||||||
void initColors();
|
void initColors();
|
||||||
void initColorLookup();
|
void initColorLookup();
|
||||||
|
|||||||
59
src/Entity.cpp
Normal file
59
src/Entity.cpp
Normal file
@@ -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<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] : "";
|
||||||
|
|
||||||
|
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<int>(i), cursor_x,
|
||||||
|
layer, current_segment, current_colors);
|
||||||
|
cursor_x += static_cast<int>(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<int>(i), cursor_x, layer,
|
||||||
|
current_segment, current_colors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/Entity.h
11
src/Entity.h
@@ -1,8 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cctype>
|
|
||||||
#include <ncurses.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct AssetPair {
|
struct AssetPair {
|
||||||
@@ -21,4 +18,12 @@ public:
|
|||||||
|
|
||||||
float getX() const noexcept { return x; }
|
float getX() const noexcept { return x; }
|
||||||
float getY() const noexcept { return y; }
|
float getY() const noexcept { return y; }
|
||||||
|
|
||||||
|
virtual void update() noexcept = 0;
|
||||||
|
virtual bool isOffScreen() const 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;
|
||||||
|
|
||||||
|
void draw(int layer) const;
|
||||||
};
|
};
|
||||||
|
|||||||
58
src/Fish.cpp
58
src/Fish.cpp
@@ -14,12 +14,9 @@ Fish::Fish(int asset_index)
|
|||||||
speed(Random::floatInRange(0.25f, 2.25f)),
|
speed(Random::floatInRange(0.25f, 2.25f)),
|
||||||
moving_right(asset_index % 2 == 0) {
|
moving_right(asset_index % 2 == 0) {
|
||||||
const auto &aquarium = Aquarium::getInstance();
|
const auto &aquarium = Aquarium::getInstance();
|
||||||
|
|
||||||
y = Random::intInRange(static_cast<int>(image.size()) + 6,
|
y = Random::intInRange(static_cast<int>(image.size()) + 6,
|
||||||
aquarium.getHeight() - static_cast<int>(image.size()));
|
aquarium.getHeight() - static_cast<int>(image.size()));
|
||||||
|
|
||||||
x = moving_right ? -20.0f : static_cast<float>(aquarium.getWidth());
|
x = moving_right ? -20.0f : static_cast<float>(aquarium.getWidth());
|
||||||
|
|
||||||
randomizeMask();
|
randomizeMask();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +24,6 @@ void Fish::randomizeMask() {
|
|||||||
// Clear and rebuild color map
|
// Clear and rebuild color map
|
||||||
color_map.clear();
|
color_map.clear();
|
||||||
color_map['4'] = 'W'; // White is always '4'
|
color_map['4'] = 'W'; // White is always '4'
|
||||||
|
|
||||||
// Assign random colors to digits 1-3, 5-9
|
// Assign random colors to digits 1-3, 5-9
|
||||||
for (char digit = '1'; digit <= '9'; ++digit) {
|
for (char digit = '1'; digit <= '9'; ++digit) {
|
||||||
if (digit != '4') {
|
if (digit != '4') {
|
||||||
@@ -35,7 +31,6 @@ void Fish::randomizeMask() {
|
|||||||
0, static_cast<int>(AVAILABLE_COLORS.size()) - 1)];
|
0, static_cast<int>(AVAILABLE_COLORS.size()) - 1)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply color mapping to mask
|
// Apply color mapping to mask
|
||||||
for (auto &line : mask) {
|
for (auto &line : mask) {
|
||||||
for (char &ch : line) {
|
for (char &ch : line) {
|
||||||
@@ -52,50 +47,13 @@ int Fish::getRandomAssetIndex() {
|
|||||||
|
|
||||||
void Fish::update() noexcept { x += moving_right ? speed : -speed; }
|
void Fish::update() noexcept { x += moving_right ? speed : -speed; }
|
||||||
|
|
||||||
void Fish::draw(int layer) const {
|
bool Fish::isOffScreen() const noexcept {
|
||||||
auto &aquarium = Aquarium::getInstance();
|
const auto &aquarium = Aquarium::getInstance();
|
||||||
|
if (moving_right) {
|
||||||
// Pre-allocate strings to avoid repeated allocations
|
// Fish is off screen when its left edge is past the right border
|
||||||
std::string current_segment;
|
return x > static_cast<float>(aquarium.getWidth());
|
||||||
std::string current_colors;
|
} else {
|
||||||
current_segment.reserve(32); // Reserve reasonable capacity
|
// Fish is off screen when its right edge is past the left border
|
||||||
current_colors.reserve(32);
|
return (x + static_cast<float>(image[0].length())) < 0;
|
||||||
|
|
||||||
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] : "";
|
|
||||||
|
|
||||||
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<int>(i), cursor_x,
|
|
||||||
layer, current_segment, current_colors);
|
|
||||||
cursor_x += static_cast<int>(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<int>(i), cursor_x, layer,
|
|
||||||
current_segment, current_colors);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/Fish.h
12
src/Fish.h
@@ -2,6 +2,7 @@
|
|||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "assets/FishAssets.h"
|
#include "assets/FishAssets.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
class Fish : public Entity {
|
class Fish : public Entity {
|
||||||
private:
|
private:
|
||||||
@@ -9,11 +10,10 @@ private:
|
|||||||
'c', 'C', 'r', 'R', 'y', 'Y', 'b', 'B', 'g', 'G', 'm', 'M'};
|
'c', 'C', 'r', 'R', 'y', 'Y', 'b', 'B', 'g', 'G', 'm', 'M'};
|
||||||
|
|
||||||
const std::vector<std::string> ℑ
|
const std::vector<std::string> ℑ
|
||||||
std::vector<std::string> mask; // Copy needed for color randomization
|
std::vector<std::string> mask;
|
||||||
const float speed;
|
const float speed;
|
||||||
const bool moving_right;
|
const bool moving_right;
|
||||||
|
|
||||||
// Static color map to avoid recreation
|
|
||||||
static std::unordered_map<char, char> color_map;
|
static std::unordered_map<char, char> color_map;
|
||||||
|
|
||||||
explicit Fish(int asset_index);
|
explicit Fish(int asset_index);
|
||||||
@@ -22,6 +22,10 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
Fish();
|
Fish();
|
||||||
void update() noexcept;
|
|
||||||
void draw(int layer) const;
|
void update() noexcept override;
|
||||||
|
bool isOffScreen() const 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'; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,10 @@
|
|||||||
|
|
||||||
class Seaweed {
|
class Seaweed {
|
||||||
private:
|
private:
|
||||||
const int y;
|
const size_t x, y;
|
||||||
static constexpr char PATTERN_LEFT = '(';
|
static constexpr char PATTERN_LEFT = '(';
|
||||||
static constexpr char PATTERN_RIGHT = ')';
|
static constexpr char PATTERN_RIGHT = ')';
|
||||||
|
|
||||||
const size_t x;
|
|
||||||
const size_t height;
|
const size_t height;
|
||||||
const float speed;
|
const float speed;
|
||||||
|
|
||||||
|
|||||||
39
src/Ship.cpp
Normal file
39
src/Ship.cpp
Normal file
@@ -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<float>(
|
||||||
|
image[0].length()); // Start completely off left side
|
||||||
|
} else {
|
||||||
|
x = static_cast<float>(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<float>(aquarium.getWidth());
|
||||||
|
} else {
|
||||||
|
// Ship is off screen when its right edge is past the left border
|
||||||
|
return (x + static_cast<float>(image[0].length())) < 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/Ship.h
Normal file
26
src/Ship.h
Normal file
@@ -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<std::string> ℑ
|
||||||
|
const std::vector<std::string> &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<std::string> &getImage() const override { return image; }
|
||||||
|
const std::vector<std::string> &getMask() const override { return mask; }
|
||||||
|
char getDefaultColor() const noexcept override { return 'W'; }
|
||||||
|
};
|
||||||
37
src/assets/ShipAssets.h
Normal file
37
src/assets/ShipAssets.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../Entity.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
inline std::vector<AssetPair> 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)",
|
||||||
|
}}};
|
||||||
Reference in New Issue
Block a user