Merge pull request 'add sea monster' (#2) from abstract into master
Reviewed-on: #2
This commit is contained in:
4
Makefile
4
Makefile
@@ -1,7 +1,7 @@
|
|||||||
# Compiler and flags
|
# Compiler and flags
|
||||||
CXX = g++
|
CXX = g++
|
||||||
CXXFLAGS = -std=c++17 -Wall -Wextra -O3
|
CXXFLAGS = -std=c++17 -Wall -Wextra -O3 -fno-exceptions -fno-asynchronous-unwind-tables -fno-unwind-tables
|
||||||
LDFLAGS = -lncurses -ltinfo
|
LDFLAGS = -lncurses -ltinfo -Wl,--gc-sections -s
|
||||||
|
|
||||||
# Directories
|
# Directories
|
||||||
SRC_DIR = src
|
SRC_DIR = src
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "Bubble.h"
|
#include "Bubble.h"
|
||||||
#include "Castle.h"
|
#include "Castle.h"
|
||||||
#include "Fish.h"
|
#include "Fish.h"
|
||||||
|
#include "SeaMonster.h"
|
||||||
#include "Seaweed.h"
|
#include "Seaweed.h"
|
||||||
#include "Ship.h"
|
#include "Ship.h"
|
||||||
#include "Waterline.h"
|
#include "Waterline.h"
|
||||||
@@ -46,6 +47,8 @@ void Aquarium::ensureEntitiesSorted() {
|
|||||||
void Aquarium::redraw() {
|
void Aquarium::redraw() {
|
||||||
clearCurrentFrame();
|
clearCurrentFrame();
|
||||||
|
|
||||||
|
ensureBigEntityExists();
|
||||||
|
|
||||||
std::vector<std::unique_ptr<Entity>> newEntities;
|
std::vector<std::unique_ptr<Entity>> newEntities;
|
||||||
bool entities_modified = false;
|
bool entities_modified = false;
|
||||||
|
|
||||||
@@ -93,7 +96,7 @@ void Aquarium::redraw() {
|
|||||||
|
|
||||||
// Draw all entities
|
// Draw all entities
|
||||||
for (const auto &entity : entities) {
|
for (const auto &entity : entities) {
|
||||||
entity->draw(0);
|
entity->draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderToScreen();
|
renderToScreen();
|
||||||
@@ -119,7 +122,6 @@ 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++)
|
||||||
@@ -132,6 +134,24 @@ void Aquarium::addSeaweed() { addEntityImpl<Seaweed>(); }
|
|||||||
void Aquarium::addWaterline() { addEntityImpl<Waterline>(); }
|
void Aquarium::addWaterline() { addEntityImpl<Waterline>(); }
|
||||||
void Aquarium::addCastle() { addEntityImpl<Castle>(); }
|
void Aquarium::addCastle() { addEntityImpl<Castle>(); }
|
||||||
void Aquarium::addShip() { addEntityImpl<Ship>(); }
|
void Aquarium::addShip() { addEntityImpl<Ship>(); }
|
||||||
|
void Aquarium::addSeaMonster() { addEntityImpl<SeaMonster>(); }
|
||||||
|
void Aquarium::ensureBigEntityExists() {
|
||||||
|
// Check if any big entities exist on screen
|
||||||
|
for (const auto &entity : entities) {
|
||||||
|
if (dynamic_cast<Ship *>(entity.get()) ||
|
||||||
|
dynamic_cast<SeaMonster *>(entity.get())) {
|
||||||
|
return; // Big entity found, do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No big entity found, spawn next in cycle
|
||||||
|
if (big_entity_index % 2 == 0) {
|
||||||
|
addEntityImpl<Ship>();
|
||||||
|
} else {
|
||||||
|
addEntityImpl<SeaMonster>();
|
||||||
|
}
|
||||||
|
++big_entity_index;
|
||||||
|
}
|
||||||
|
|
||||||
void Aquarium::clearCurrentFrame() {
|
void Aquarium::clearCurrentFrame() {
|
||||||
for (auto &row : currentFrame) {
|
for (auto &row : currentFrame) {
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ private:
|
|||||||
std::vector<std::vector<Cell>> currentFrame;
|
std::vector<std::vector<Cell>> currentFrame;
|
||||||
std::vector<std::vector<Cell>> previousFrame;
|
std::vector<std::vector<Cell>> previousFrame;
|
||||||
std::vector<std::unique_ptr<Entity>> entities;
|
std::vector<std::unique_ptr<Entity>> entities;
|
||||||
|
size_t big_entity_index = 0;
|
||||||
|
void ensureBigEntityExists();
|
||||||
|
|
||||||
bool entities_need_sorting = true;
|
bool entities_need_sorting = true;
|
||||||
static inline short colorLookup[256] = {0};
|
static inline short colorLookup[256] = {0};
|
||||||
@@ -49,6 +51,7 @@ public:
|
|||||||
void addWaterline();
|
void addWaterline();
|
||||||
void addCastle();
|
void addCastle();
|
||||||
void addShip();
|
void addShip();
|
||||||
|
void addSeaMonster();
|
||||||
|
|
||||||
void redraw();
|
void redraw();
|
||||||
void initColors();
|
void initColors();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "Aquarium.h"
|
#include "Aquarium.h"
|
||||||
|
|
||||||
void Entity::draw(int layer) const {
|
void Entity::draw() const {
|
||||||
auto &aquarium = Aquarium::getInstance();
|
auto &aquarium = Aquarium::getInstance();
|
||||||
|
|
||||||
const auto &image = getImage();
|
const auto &image = getImage();
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ public:
|
|||||||
virtual char getDefaultColor() const noexcept = 0;
|
virtual char getDefaultColor() const noexcept = 0;
|
||||||
|
|
||||||
virtual bool shouldBeRemoved() const noexcept = 0;
|
virtual bool shouldBeRemoved() const noexcept = 0;
|
||||||
virtual std::unique_ptr<Entity> createReplacement() const = 0;
|
virtual std::unique_ptr<Entity> createReplacement() const { return nullptr; }
|
||||||
virtual int getPreferredLayer() const noexcept = 0;
|
virtual int getPreferredLayer() const noexcept = 0;
|
||||||
|
|
||||||
void draw(int) const;
|
void draw() const;
|
||||||
};
|
};
|
||||||
|
|||||||
43
src/Fish.cpp
43
src/Fish.cpp
@@ -4,24 +4,7 @@
|
|||||||
#include "assets/FishAssets.h"
|
#include "assets/FishAssets.h"
|
||||||
#include "defs.h"
|
#include "defs.h"
|
||||||
|
|
||||||
std::array<std::unordered_map<char, char>, 20> Fish::color_map_cache;
|
std::unordered_map<char, char> Fish::color_map;
|
||||||
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<int>(AVAILABLE_COLORS.size()) - 1)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
color_maps_initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Fish::Fish() : Fish(getRandomAssetIndex()) {}
|
Fish::Fish() : Fish(getRandomAssetIndex()) {}
|
||||||
|
|
||||||
@@ -31,24 +14,30 @@ 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) {
|
||||||
|
|
||||||
if (!color_maps_initialized) {
|
|
||||||
initializeColorMaps();
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
||||||
applyRandomColorMapping();
|
randomizeMask();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick one of the pre-generated color mappings
|
void Fish::randomizeMask() {
|
||||||
void Fish::applyRandomColorMapping() {
|
// Clear and rebuild color map with fresh random colors each time
|
||||||
const auto &selected_map = color_map_cache[Random::intInRange(0, 9)];
|
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<int>(AVAILABLE_COLORS.size()) - 1)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply color mapping to mask
|
||||||
for (auto &line : mask) {
|
for (auto &line : mask) {
|
||||||
for (char &ch : line) {
|
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;
|
ch = it->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,11 @@ private:
|
|||||||
const float speed;
|
const float speed;
|
||||||
const bool moving_right;
|
const bool moving_right;
|
||||||
|
|
||||||
static std::array<std::unordered_map<char, char>, 20> color_map_cache;
|
static std::unordered_map<char, char> color_map;
|
||||||
static bool color_maps_initialized;
|
|
||||||
static void initializeColorMaps();
|
|
||||||
|
|
||||||
explicit Fish(int asset_index);
|
explicit Fish(int asset_index);
|
||||||
static int getRandomAssetIndex();
|
static int getRandomAssetIndex();
|
||||||
void applyRandomColorMapping();
|
void randomizeMask();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Fish();
|
Fish();
|
||||||
|
|||||||
56
src/SeaMonster.cpp
Normal file
56
src/SeaMonster.cpp
Normal file
@@ -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<float>(frame1[0].length());
|
||||||
|
} else {
|
||||||
|
x = static_cast<float>(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<std::string> &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<float>(aquarium.getWidth());
|
||||||
|
} else {
|
||||||
|
return (x + static_cast<float>(frame1[0].length())) < 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int SeaMonster::getPreferredLayer() const noexcept { return 8; }
|
||||||
35
src/SeaMonster.h
Normal file
35
src/SeaMonster.h
Normal file
@@ -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<std::string> &frame1;
|
||||||
|
const std::vector<std::string> &frame2;
|
||||||
|
const std::vector<std::string> &mask;
|
||||||
|
const float speed;
|
||||||
|
const bool moving_right;
|
||||||
|
|
||||||
|
bool current_frame = false;
|
||||||
|
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'; }
|
||||||
|
|
||||||
|
bool shouldBeRemoved() const noexcept override;
|
||||||
|
int getPreferredLayer() const noexcept override;
|
||||||
|
};
|
||||||
12
src/Ship.cpp
12
src/Ship.cpp
@@ -6,8 +6,8 @@
|
|||||||
Ship::Ship() : Ship(getRandomDirection()) {}
|
Ship::Ship() : Ship(getRandomDirection()) {}
|
||||||
|
|
||||||
Ship::Ship(int asset_index)
|
Ship::Ship(int asset_index)
|
||||||
: Entity(), image(pirateShipAssets[asset_index].image),
|
: Entity(), image(shipAssets[asset_index].image),
|
||||||
mask(pirateShipAssets[asset_index].mask), speed(SHIP_SPEED),
|
mask(shipAssets[asset_index].mask), speed(SHIP_SPEED),
|
||||||
moving_right(asset_index == 0) {
|
moving_right(asset_index == 0) {
|
||||||
|
|
||||||
const auto &aquarium = Aquarium::getInstance();
|
const auto &aquarium = Aquarium::getInstance();
|
||||||
@@ -32,10 +32,4 @@ bool Ship::shouldBeRemoved() const noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Entity> Ship::createReplacement() const {
|
int Ship::getPreferredLayer() const noexcept { return 9; }
|
||||||
return std::make_unique<Ship>();
|
|
||||||
}
|
|
||||||
|
|
||||||
int Ship::getPreferredLayer() const noexcept {
|
|
||||||
return 9; // Ships on layer 9
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -24,6 +24,5 @@ public:
|
|||||||
char getDefaultColor() const noexcept override { return 'W'; }
|
char getDefaultColor() const noexcept override { return 'W'; }
|
||||||
|
|
||||||
bool shouldBeRemoved() const noexcept override;
|
bool shouldBeRemoved() const noexcept override;
|
||||||
std::unique_ptr<Entity> createReplacement() const override;
|
|
||||||
int getPreferredLayer() const noexcept override;
|
int getPreferredLayer() const noexcept override;
|
||||||
};
|
};
|
||||||
|
|||||||
38
src/assets/SeaMonsterAssets.h
Normal file
38
src/assets/SeaMonsterAssets.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../Entity.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct SeaMonsterAsset {
|
||||||
|
std::vector<std::string> frame1;
|
||||||
|
std::vector<std::string> frame2;
|
||||||
|
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"()"}}};
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
#include "../Entity.h"
|
#include "../Entity.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
inline std::vector<AssetPair> pirateShipAssets = {
|
inline std::vector<AssetPair> shipAssets = {
|
||||||
{{
|
{{
|
||||||
R"( | | |)",
|
R"( | | |)",
|
||||||
R"( )_) )_) )_))",
|
R"( )_) )_) )_))",
|
||||||
|
|||||||
Reference in New Issue
Block a user