From 117d14e839a5615f248b38d8084299950d659b5e Mon Sep 17 00:00:00 2001 From: Sam-Si <13261099+Sam-Si@users.noreply.github.com> Date: Fri, 13 Mar 2026 06:21:50 +0530 Subject: [PATCH 1/2] c1 --- CMakeLists.txt | 2 +- src/entities/entity_manager.cpp | 79 +++ src/entities/entity_manager.h | 61 +++ src/factories/entity_factory.cpp | 803 ++++++++++++++++++++++++++++++ src/factories/entity_factory.h | 423 ++++++++++++++++ src/game.cpp | 108 ++-- src/models/game_event.cpp | 21 + src/models/game_event.h | 43 ++ src/systems/buff_manager.cpp | 235 +++++++++ src/systems/buff_manager.h | 37 ++ src/systems/collision_manager.cpp | 86 ++++ src/systems/collision_manager.h | 32 ++ src/systems/item_manager.h | 50 ++ 13 files changed, 1948 insertions(+), 32 deletions(-) create mode 100644 src/entities/entity_manager.cpp create mode 100644 src/entities/entity_manager.h create mode 100644 src/factories/entity_factory.cpp create mode 100644 src/factories/entity_factory.h create mode 100644 src/models/game_event.cpp create mode 100644 src/models/game_event.h create mode 100644 src/systems/buff_manager.cpp create mode 100644 src/systems/buff_manager.h create mode 100644 src/systems/collision_manager.cpp create mode 100644 src/systems/collision_manager.h create mode 100644 src/systems/item_manager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f424ba..5495c63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ find_package(SDL2_ttf REQUIRED) set(BIN_NAME dungeon_rush) -file(GLOB SRC src/*.cpp src/*.c) +file(GLOB_RECURSE SRC src/*.cpp src/*.c) add_executable(${BIN_NAME} ${SRC}) diff --git a/src/entities/entity_manager.cpp b/src/entities/entity_manager.cpp new file mode 100644 index 0000000..053e609 --- /dev/null +++ b/src/entities/entity_manager.cpp @@ -0,0 +1,79 @@ +#include "entities/entity_manager.h" + +namespace snake { +namespace entities { + +void EntityManager::addSnake(const std::shared_ptr& snake) { + if (spritesCount_ < kSpritesMaxNum) { + snakes_[spritesCount_++] = snake; + } +} + +void EntityManager::removeSnake(int index) { + if (index >= 0 && index < spritesCount_) { + snakes_[index] = nullptr; + } +} + +std::shared_ptr EntityManager::getSnake(int index) const { + if (index >= 0 && index < kSpritesMaxNum) { + return snakes_[index]; + } + return nullptr; +} + +int EntityManager::snakeCount() const { + return spritesCount_; +} + +int EntityManager::playerCount() const { + return playersCount_; +} + +void EntityManager::setPlayerCount(int count) { + playersCount_ = count; +} + +void EntityManager::incrementSpriteCount() { + ++spritesCount_; +} + +int EntityManager::spriteCount() const { + return spritesCount_; +} + +void EntityManager::addBullet(const std::shared_ptr& bullet) { + bullets_.push_back(bullet); +} + +void EntityManager::removeBullet(const std::shared_ptr& bullet) { + bullets_.remove(bullet); +} + +BulletList& EntityManager::bullets() { + return bullets_; +} + +const BulletList& EntityManager::bullets() const { + return bullets_; +} + +std::array, kSpritesMaxNum>& EntityManager::snakes() { + return snakes_; +} + +const std::array, kSpritesMaxNum>& EntityManager::snakes() const { + return snakes_; +} + +void EntityManager::clear() { + for (int i = 0; i < kSpritesMaxNum; ++i) { + snakes_[i] = nullptr; + } + bullets_.clear(); + spritesCount_ = 0; + playersCount_ = 0; +} + +} // namespace entities +} // namespace snake diff --git a/src/entities/entity_manager.h b/src/entities/entity_manager.h new file mode 100644 index 0000000..57f4e42 --- /dev/null +++ b/src/entities/entity_manager.h @@ -0,0 +1,61 @@ +#ifndef SNAKE_ENTITIES_ENTITY_MANAGER_H_ +#define SNAKE_ENTITIES_ENTITY_MANAGER_H_ + +#include "player.h" +#include "sprite.h" +#include "bullet.h" +#include "adt.h" + +#include +#include + +namespace snake { +namespace entities { + +constexpr int kSpritesMaxNum = 1024; + +// ============================================================================ +// EntityManager - Manages all game entities (snakes, bullets) +// ============================================================================ +class EntityManager { + public: + EntityManager() = default; + EntityManager(const EntityManager&) = delete; + EntityManager& operator=(const EntityManager&) = delete; + EntityManager(EntityManager&&) = default; + EntityManager& operator=(EntityManager&&) = default; + ~EntityManager() = default; + + // Snake management + void addSnake(const std::shared_ptr& snake); + void removeSnake(int index); + std::shared_ptr getSnake(int index) const; + int snakeCount() const; + int playerCount() const; + void setPlayerCount(int count); + void incrementSpriteCount(); + int spriteCount() const; + + // Bullet management + void addBullet(const std::shared_ptr& bullet); + void removeBullet(const std::shared_ptr& bullet); + BulletList& bullets(); + const BulletList& bullets() const; + + // Entity access + std::array, kSpritesMaxNum>& snakes(); + const std::array, kSpritesMaxNum>& snakes() const; + + void clear(); + + private: + std::array, kSpritesMaxNum> snakes_{}; + BulletList bullets_{}; + int spritesCount_ = 0; + int playersCount_ = 0; +}; + +} // namespace entities +} // namespace snake + +#endif // SNAKE_ENTITIES_ENTITY_MANAGER_H_ diff --git a/src/factories/entity_factory.cpp b/src/factories/entity_factory.cpp new file mode 100644 index 0000000..f503e92 --- /dev/null +++ b/src/factories/entity_factory.cpp @@ -0,0 +1,803 @@ +#include "entity_factory.h" + +#include "game.h" +#include "helper.h" +#include "render.h" +#include "res.h" + +#include +#include + +// ============================================================================ +// External Declarations (from game.cpp and other files) +// ============================================================================ +extern const int SCALE_FACTOR; +extern const int n, m; +extern Texture textures[TEXTURES_SIZE]; +extern std::array animationsList; +extern Effect effects[]; +extern Weapon weapons[WEAPONS_SIZE]; +extern Sprite commonSprites[COMMON_SPRITE_SIZE]; +extern std::array, MAP_SIZE> itemMap; +extern bool hasMap[MAP_SIZE][MAP_SIZE]; +extern std::array, MAP_SIZE> map; +extern std::array, MAP_SIZE> hasEnemy; + +// ============================================================================ +// PrototypeRegistry Implementation +// ============================================================================ + +PrototypeRegistry& PrototypeRegistry::getInstance() { + static PrototypeRegistry instance; + return instance; +} + +void PrototypeRegistry::registerPrototype( + const std::string& id, std::unique_ptr prototype) { + prototypes_[id] = std::move(prototype); +} + +const EntityPrototype* PrototypeRegistry::getPrototype(const std::string& id) const { + const auto it = prototypes_.find(id); + if (it != prototypes_.end()) { + return it->second.get(); + } + return nullptr; +} + +const MonsterPrototype* PrototypeRegistry::getMonsterPrototype( + const std::string& id) const { + return dynamic_cast(getPrototype(id)); +} + +const WeaponPrototype* PrototypeRegistry::getWeaponPrototype( + const std::string& id) const { + return dynamic_cast(getPrototype(id)); +} + +const ItemPrototype* PrototypeRegistry::getItemPrototype( + const std::string& id) const { + return dynamic_cast(getPrototype(id)); +} + +std::vector PrototypeRegistry::getMonsterPrototypesByCategory( + const std::string& category) const { + std::vector result; + for (const auto& [id, proto] : prototypes_) { + if (proto->getCategory() == category) { + if (const auto* monster = dynamic_cast(proto.get())) { + result.push_back(monster); + } + } + } + return result; +} + +std::vector PrototypeRegistry::getWeaponPrototypesByCategory( + const std::string& category) const { + std::vector result; + for (const auto& [id, proto] : prototypes_) { + if (proto->getCategory() == category) { + if (const auto* weapon = dynamic_cast(proto.get())) { + result.push_back(weapon); + } + } + } + return result; +} + +std::vector PrototypeRegistry::getItemPrototypesByCategory( + const std::string& category) const { + std::vector result; + for (const auto& [id, proto] : prototypes_) { + if (proto->getCategory() == category) { + if (const auto* item = dynamic_cast(proto.get())) { + result.push_back(item); + } + } + } + return result; +} + +std::vector PrototypeRegistry::getAllMonsterPrototypes() const { + std::vector result; + for (const auto& [id, proto] : prototypes_) { + if (const auto* monster = dynamic_cast(proto.get())) { + result.push_back(monster); + } + } + return result; +} + +std::vector PrototypeRegistry::getAllWeaponPrototypes() const { + std::vector result; + for (const auto& [id, proto] : prototypes_) { + if (const auto* weapon = dynamic_cast(proto.get())) { + result.push_back(weapon); + } + } + return result; +} + +std::vector PrototypeRegistry::getAllItemPrototypes() const { + std::vector result; + for (const auto& [id, proto] : prototypes_) { + if (const auto* item = dynamic_cast(proto.get())) { + result.push_back(item); + } + } + return result; +} + +void PrototypeRegistry::clear() { + prototypes_.clear(); +} + +// ============================================================================ +// MonsterFactory Implementation +// ============================================================================ + +std::shared_ptr MonsterFactory::createSnake(const SpawnContext& context) const { + return std::make_shared(context.levelScaling, GAME_MONSTERS_TEAM, + context.playerType); +} + +std::shared_ptr MonsterFactory::createSprite(const SpawnContext& context) const { + auto sprite = std::make_shared(commonSprites[SPRITE_TINY_ZOMBIE], + context.x, context.y); + sprite->setDirection(context.direction); + if (context.direction == Direction::Left) { + sprite->setFace(Direction::Left); + } + return sprite; +} + +std::shared_ptr MonsterFactory::createSnakeFromPrototype( + const MonsterPrototype& proto, const SpawnContext& context) const { + const int scaledHp = static_cast(proto.getBaseHp() * context.levelScaling); + const int step = proto.getMoveStep(); + + auto snake = std::make_shared(step, GAME_MONSTERS_TEAM, PlayerType::Computer); + snake->score()->setGot(0); + + return snake; +} + +std::shared_ptr MonsterFactory::createSpriteFromPrototype( + const MonsterPrototype& proto, const SpawnContext& context) const { + const int spriteId = proto.getSpriteId(); + auto sprite = std::make_shared(commonSprites[spriteId], context.x, context.y); + + sprite->setDirection(context.direction); + if (context.direction == Direction::Left) { + sprite->setFace(Direction::Left); + } + + const int scaledHp = static_cast(proto.getBaseHp() * context.levelScaling); + sprite->setTotalHp(scaledHp); + sprite->setHp(scaledHp); + sprite->setDropRate(proto.getDropRate() * context.difficultyFactor); + + if (proto.getWeaponId() >= 0 && proto.getWeaponId() < WEAPONS_SIZE) { + sprite->setWeapon(&weapons[proto.getWeaponId()]); + } + + if (sprite->animation()) { + pushAnimationToRender(RENDER_LIST_SPRITE_ID, sprite->animation()); + } + + return sprite; +} + +const MonsterPrototype* MonsterFactory::selectRandomPrototype( + const std::vector& prototypes, int gameLevel) const { + if (prototypes.empty()) { + return nullptr; + } + + std::vector validPrototypes; + for (const auto* proto : prototypes) { + if (proto && gameLevel >= proto->getMinLevel() && gameLevel <= proto->getMaxLevel()) { + validPrototypes.push_back(proto); + } + } + + if (validPrototypes.empty()) { + return prototypes.front(); + } + + double totalWeight = 0.0; + for (const auto* proto : validPrototypes) { + totalWeight += proto->getWeight(); + } + + double random = randDouble() * totalWeight; + for (const auto* proto : validPrototypes) { + random -= proto->getWeight(); + if (random <= 0.0) { + return proto; + } + } + + return validPrototypes.back(); +} + +std::shared_ptr MonsterFactory::createRandomMonster( + int x, int y, int gameLevel, double difficultyFactor) const { + const auto& registry = PrototypeRegistry::getInstance(); + const auto prototypes = registry.getMonsterPrototypesByCategory("normal"); + + const MonsterPrototype* proto = selectRandomPrototype(prototypes, gameLevel); + if (!proto) { + return nullptr; + } + + SpawnContext context; + context.x = x; + context.y = y; + context.levelScaling = difficultyFactor; + context.difficultyFactor = difficultyFactor; + context.direction = randInt(0, 1) ? Direction::Right : Direction::Left; + + auto snake = createSnakeFromPrototype(*proto, context); + + const int length = randInt(2, 4); + for (int i = 0; i < length; ++i) { + int xx = x; + int yy = y; + if (context.direction == Direction::Down) { + yy += i; + } else { + xx += i; + } + + SpawnContext spriteContext = context; + spriteContext.x = xx * UNIT + UNIT / 2; + spriteContext.y = yy * UNIT + UNIT - 3; + + auto sprite = createSpriteFromPrototype(*proto, spriteContext); + snake->sprites().push_front(sprite); + snake->incrementNum(); + + hasEnemy[xx][yy] = true; + } + + return snake; +} + +std::shared_ptr MonsterFactory::createRandomBoss( + int x, int y, int gameLevel, double difficultyFactor) const { + const auto& registry = PrototypeRegistry::getInstance(); + const auto prototypes = registry.getMonsterPrototypesByCategory("boss"); + + const MonsterPrototype* proto = selectRandomPrototype(prototypes, gameLevel); + if (!proto) { + return nullptr; + } + + SpawnContext context; + context.x = x; + context.y = y; + context.levelScaling = difficultyFactor * 1.5; + context.difficultyFactor = difficultyFactor; + context.direction = Direction::Right; + + auto snake = createSnakeFromPrototype(*proto, context); + + SpawnContext spriteContext = context; + spriteContext.x = x * UNIT + UNIT / 2; + spriteContext.y = y * UNIT + UNIT - 3; + + auto sprite = createSpriteFromPrototype(*proto, spriteContext); + snake->sprites().push_front(sprite); + snake->incrementNum(); + + hasEnemy[x][y] = true; + + return snake; +} + +// ============================================================================ +// WeaponFactory Implementation +// ============================================================================ + +std::unique_ptr WeaponFactory::createWeapon(const WeaponPrototype& proto) const { + auto weapon = std::make_unique(); + + weapon->setType(proto.getWeaponType()); + weapon->setShootRange(proto.getShootRange()); + weapon->setEffectRange(proto.getEffectRange()); + weapon->setDamage(proto.getBaseDamage()); + weapon->setGap(proto.getGap()); + weapon->setBulletSpeed(proto.getBulletSpeed()); + weapon->setBirthAudio(proto.getBirthAudio()); + weapon->setDeathAudio(proto.getDeathAudio()); + + auto& effects = weapon->effects(); + for (int i = 0; i < BUFF_END; ++i) { + effects[i].chance = proto.getBuffChances()[i]; + effects[i].duration = proto.getBuffDurations()[i]; + } + + weapon->setBehavior(makeWeaponBehavior(proto.getWeaponType())); + + return weapon; +} + +std::unique_ptr WeaponFactory::createWeaponFromId(int weaponId) const { + if (weaponId >= 0 && weaponId < WEAPONS_SIZE) { + return std::make_unique(weapons[weaponId]); + } + return nullptr; +} + +const WeaponPrototype* WeaponFactory::selectRandomPrototype(int gameLevel) const { + const auto& registry = PrototypeRegistry::getInstance(); + const auto prototypes = registry.getAllWeaponPrototypes(); + + if (prototypes.empty()) { + return nullptr; + } + + std::vector validPrototypes; + for (const auto* proto : prototypes) { + if (proto && gameLevel >= proto->getMinLevel() && gameLevel <= proto->getMaxLevel()) { + validPrototypes.push_back(proto); + } + } + + if (validPrototypes.empty()) { + return prototypes.front(); + } + + double totalWeight = 0.0; + for (const auto* proto : validPrototypes) { + totalWeight += proto->getWeight(); + } + + double random = randDouble() * totalWeight; + for (const auto* proto : validPrototypes) { + random -= proto->getWeight(); + if (random <= 0.0) { + return proto; + } + } + + return validPrototypes.back(); +} + +std::unique_ptr WeaponFactory::createRandomWeapon(int gameLevel) const { + const WeaponPrototype* proto = selectRandomPrototype(gameLevel); + if (!proto) { + return nullptr; + } + return createWeapon(*proto); +} + +std::unique_ptr WeaponFactory::createWeaponForSprite(int spriteId) const { + const auto& registry = PrototypeRegistry::getInstance(); + const auto prototypes = registry.getAllWeaponPrototypes(); + + for (const auto* proto : prototypes) { + if (proto && proto->getBelongSpriteId() == spriteId) { + return createWeapon(*proto); + } + } + + return nullptr; +} + +// ============================================================================ +// ItemFactory Implementation +// ============================================================================ + +Item ItemFactory::createItem(const ItemPrototype& proto, int x, int y) const { + auto animation = createAndPushAnimation( + animationsList[RENDER_LIST_MAP_ITEMS_ID], &textures[proto.getTextureId()], + nullptr, LoopType::Infinite, 3, x * UNIT, y * UNIT, SDL_FLIP_NONE, 0, + At::BottomLeft); + + return Item(proto.getItemType(), proto.getItemId(), proto.getBelongSpriteId(), + animation); +} + +Item ItemFactory::createHpMedicine(int x, int y, bool extra) const { + const int textureId = extra ? RES_FLASK_BIG_YELLOW : RES_FLASK_BIG_RED; + + auto animation = createAndPushAnimation( + animationsList[RENDER_LIST_MAP_ITEMS_ID], &textures[textureId], nullptr, + LoopType::Infinite, 3, x * UNIT, y * UNIT, SDL_FLIP_NONE, 0, At::BottomLeft); + + return Item(extra ? ItemType::HpExtraMedicine : ItemType::HpMedicine, 0, 0, + animation); +} + +Item ItemFactory::createWeaponItem(int x, int y, int gameLevel) const { + const auto& registry = PrototypeRegistry::getInstance(); + const WeaponPrototype* weaponProto = nullptr; + + const auto prototypes = registry.getAllWeaponPrototypes(); + std::vector validPrototypes; + + for (const auto* proto : prototypes) { + if (proto && gameLevel >= proto->getMinLevel() && gameLevel <= proto->getMaxLevel()) { + validPrototypes.push_back(proto); + } + } + + if (!validPrototypes.empty()) { + double totalWeight = 0.0; + for (const auto* proto : validPrototypes) { + totalWeight += proto->getWeight(); + } + + double random = randDouble() * totalWeight; + for (const auto* proto : validPrototypes) { + random -= proto->getWeight(); + if (random <= 0.0) { + weaponProto = proto; + break; + } + } + + if (!weaponProto) { + weaponProto = validPrototypes.back(); + } + } + + if (!weaponProto) { + weaponProto = registry.getWeaponPrototype("weapon_ice_sword"); + } + + if (!weaponProto) { + auto animation = createAndPushAnimation( + animationsList[RENDER_LIST_MAP_ITEMS_ID], &textures[RES_ICE_SWORD], + nullptr, LoopType::Infinite, 3, x * UNIT, y * UNIT, SDL_FLIP_NONE, 0, + At::BottomLeft); + return Item(ItemType::Weapon, WEAPON_ICE_SWORD, SPRITE_KNIGHT, animation); + } + + auto animation = createAndPushAnimation( + animationsList[RENDER_LIST_MAP_ITEMS_ID], &textures[weaponProto->getTextureId()], + nullptr, LoopType::Infinite, 3, x * UNIT, y * UNIT, SDL_FLIP_NONE, 0, + At::BottomLeft); + + return Item(ItemType::Weapon, weaponProto->getWeaponId(), + weaponProto->getBelongSpriteId(), animation); +} + +Item ItemFactory::createHeroItem(int x, int y) const { + const int heroId = randInt(SPRITE_KNIGHT, SPRITE_LIZARD); + + auto modelAnimation = commonSprites[heroId].animation(); + if (!modelAnimation) { + auto animation = createAndPushAnimation( + animationsList[RENDER_LIST_MAP_ITEMS_ID], &textures[RES_KNIGHT_M], nullptr, + LoopType::Infinite, 3, x * UNIT + UNIT / 2, y * UNIT + UNIT - 3, + SDL_FLIP_NONE, 0, At::BottomCenter); + return Item(ItemType::Hero, heroId, 0, animation); + } + + auto animation = std::make_shared(*modelAnimation); + animation->setAt(At::BottomCenter); + animation->setPosition(x * UNIT + UNIT / 2, y * UNIT + UNIT - 3); + pushAnimationToRender(RENDER_LIST_SPRITE_ID, animation); + + return Item(ItemType::Hero, heroId, 0, animation); +} + +const ItemPrototype* ItemFactory::selectRandomPrototype(ItemType type, int gameLevel) const { + const auto& registry = PrototypeRegistry::getInstance(); + const auto prototypes = registry.getAllItemPrototypes(); + + std::vector validPrototypes; + for (const auto* proto : prototypes) { + if (proto && proto->getItemType() == type && + gameLevel >= proto->getMinLevel() && gameLevel <= proto->getMaxLevel()) { + validPrototypes.push_back(proto); + } + } + + if (validPrototypes.empty()) { + return nullptr; + } + + double totalWeight = 0.0; + for (const auto* proto : validPrototypes) { + totalWeight += proto->getWeight(); + } + + double random = randDouble() * totalWeight; + for (const auto* proto : validPrototypes) { + random -= proto->getWeight(); + if (random <= 0.0) { + return proto; + } + } + + return validPrototypes.back(); +} + +Item ItemFactory::createRandomItem(int x, int y, int gameLevel) const { + const double random = randDouble(); + + if (random < 0.3) { + return createHpMedicine(x, y, false); + } else if (random < 0.5) { + return createHpMedicine(x, y, true); + } else { + return createWeaponItem(x, y, gameLevel); + } +} + +// ============================================================================ +// EntitySpawner Implementation +// ============================================================================ + +EntitySpawner::EntitySpawner() + : monsterFactory_(std::make_unique()), + weaponFactory_(std::make_unique()), + itemFactory_(std::make_unique()) {} + +void EntitySpawner::initializeLevel(int level, int stage) { + currentLevel_ = level; + currentStage_ = stage; + + config_.hpScaling = 1 + level * 0.8 + stage * level * 0.1; + config_.damageScaling = 1 + level * 0.5 + stage * level * 0.05; + config_.dropRateScaling = 1 + level * 0.8 + stage * level * 0.02; + config_.monsterGenFactor = 1 + level * 0.5 + stage * level * 0.05; + + config_.yellowFlaskDropRate = 0.3; + config_.weaponDropRate = 0.7; + config_.luckyFactor = 1.0; + + if (level == 0) { + config_.heroCount = 8; + config_.flaskCount = 6; + config_.enemyCount = 25; + config_.bossCount = 2; + } else if (level == 1) { + config_.weaponDropRate = 0.98; + config_.heroCount = 5; + config_.flaskCount = 4; + config_.enemyCount = 28; + config_.bossCount = 3; + } else if (level == 2) { + config_.weaponDropRate = 0.98; + config_.yellowFlaskDropRate = 0.3; + config_.heroCount = 5; + config_.flaskCount = 3; + config_.enemyCount = 28; + config_.bossCount = 5; + } + + config_.enemyCount += stage / 2 * (level + 1); + config_.bossCount += stage / 3; +} + +std::shared_ptr EntitySpawner::spawnMonster(int x, int y) { + return monsterFactory_->createRandomMonster(x, y, currentLevel_, + config_.hpScaling); +} + +std::shared_ptr EntitySpawner::spawnBoss(int x, int y) { + return monsterFactory_->createRandomBoss(x, y, currentLevel_, config_.hpScaling); +} + +Item EntitySpawner::spawnHeroItem(int x, int y) { + return itemFactory_->createHeroItem(x, y); +} + +Item EntitySpawner::spawnFlaskItem(int x, int y, bool extra) { + return itemFactory_->createHpMedicine(x, y, extra); +} + +Item EntitySpawner::spawnWeaponItem(int x, int y) { + return itemFactory_->createWeaponItem(x, y, currentLevel_); +} + +// ============================================================================ +// Prototype Initialization +// ============================================================================ + +static void registerMonsterPrototypes(PrototypeRegistry& registry) { + struct MonsterDef { + const char* id; + const char* category; + int spriteId; + int baseHp; + int moveStep; + int weaponId; + double dropRate; + double weight; + int minLevel; + int maxLevel; + }; + + const MonsterDef monsters[] = { + // Normal monsters - Tier 1 (weak) + {"monster_tiny_zombie", "normal", SPRITE_TINY_ZOMBIE, 80, 1, + WEAPON_MONSTER_CLAW, 0.8, 1.0, 0, 999}, + {"monster_goblin", "normal", SPRITE_GOBLIN, 90, 1, WEAPON_MONSTER_CLAW, + 0.9, 1.0, 0, 999}, + {"monster_imp", "normal", SPRITE_IMP, 85, 1, WEAPON_MONSTER_CLAW, 0.85, + 1.0, 0, 999}, + {"monster_skelet", "normal", SPRITE_SKELET, 95, 1, WEAPON_MONSTER_CLAW2, + 0.9, 1.0, 0, 999}, + + // Normal monsters - Tier 2 (medium) + {"monster_zombie", "normal", SPRITE_ZOMBIE, 120, 1, WEAPON_MONSTER_CLAW, + 1.0, 0.8, 0, 999}, + {"monster_ice_zombie", "normal", SPRITE_ICE_ZOMBIE, 130, 1, + WEAPON_MONSTER_CLAW, 1.1, 0.7, 1, 999}, + {"monster_muddy", "normal", SPRITE_MUDDY, 110, 1, WEAPON_MONSTER_CLAW, + 0.95, 0.9, 0, 999}, + {"monster_swampy", "normal", SPRITE_SWAMPY, 115, 1, WEAPON_MONSTER_CLAW, + 1.0, 0.85, 0, 999}, + + // Normal monsters - Tier 3 (strong) + {"monster_masked_orc", "normal", SPRITE_MASKED_ORC, 150, 1, + WEAPON_MONSTER_CLAW2, 1.2, 0.6, 0, 999}, + {"monster_orc_warrior", "normal", SPRITE_ORC_WARRIOR, 160, 1, + WEAPON_MONSTER_CLAW2, 1.3, 0.5, 1, 999}, + {"monster_orc_shaman", "normal", SPRITE_ORC_SHAMAN, 140, 1, + WEAPON_FIREBALL, 1.1, 0.5, 1, 999}, + {"monster_necromancer", "normal", SPRITE_NECROMANCER, 145, 1, + WEAPON_PURPLE_BALL, 1.2, 0.4, 1, 999}, + + // Fast monsters + {"monster_wogol", "normal", SPRITE_WOGOL, 100, 2, WEAPON_MONSTER_CLAW, + 0.9, 0.6, 0, 999}, + {"monster_chort", "normal", SPRITE_CHROT, 110, 2, WEAPON_MONSTER_CLAW, + 0.95, 0.5, 0, 999}, + + // Boss monsters + {"boss_big_zombie", "boss", SPRITE_BIG_ZOMBIE, 300, 1, + WEAPON_MONSTER_CLAW2, 2.0, 0.33, 0, 999}, + {"boss_ogre", "boss", SPRITE_ORGRE, 350, 1, WEAPON_THROW_AXE, 2.2, 0.33, + 0, 999}, + {"boss_big_demon", "boss", SPRITE_BIG_DEMON, 400, 1, WEAPON_FIREBALL, + 2.5, 0.34, 0, 999}, + }; + + for (const auto& def : monsters) { + auto proto = std::make_unique(); + proto->setPrototypeId(def.id); + proto->setCategory(def.category); + proto->setSpriteId(def.spriteId); + proto->setBaseHp(def.baseHp); + proto->setMoveStep(def.moveStep); + proto->setWeaponId(def.weaponId); + proto->setDropRate(def.dropRate); + proto->setWeight(def.weight); + proto->setMinLevel(def.minLevel); + proto->setMaxLevel(def.maxLevel); + proto->setIsBoss(std::string(def.category) == "boss"); + + registry.registerPrototype(def.id, std::move(proto)); + } +} + +static void registerWeaponPrototypes(PrototypeRegistry& registry) { + struct WeaponDef { + const char* id; + const char* category; + int weaponId; + WeaponType type; + int shootRange; + int effectRange; + int damage; + int gap; + int bulletSpeed; + int textureId; + int belongSpriteId; + int birthAudio; + int deathAudio; + double weight; + int minLevel; + int maxLevel; + }; + + const WeaponDef weapons[] = { + // Knight weapons + {"weapon_ice_sword", "knight", WEAPON_ICE_SWORD, WeaponType::SwordRange, + 64, 48, 25, 25, 0, RES_ICE_SWORD, SPRITE_KNIGHT, AUDIO_SWORD_HIT, + AUDIO_SWORD_HIT, 1.0, 0, 999}, + {"weapon_holy_sword", "knight", WEAPON_HOLY_SWORD, WeaponType::SwordRange, + 64, 48, 30, 30, 0, RES_HOLY_SWORD, SPRITE_KNIGHT, AUDIO_SWORD_HIT, + AUDIO_SWORD_HIT, 0.9, 0, 999}, + {"weapon_fire_sword", "knight", WEAPON_FIRE_SWORD, WeaponType::SwordRange, + 64, 48, 28, 28, 0, RES_FIRE_SWORD, SPRITE_KNIGHT, AUDIO_SWORD_HIT, + AUDIO_FIREBALL_EXP, 0.8, 1, 999}, + + // Wizard weapons + {"weapon_thunder_staff", "wizard", WEAPON_THUNDER_STAFF, + WeaponType::GunPoint, 256, 64, 20, 40, 8, RES_THUNDER_STAFF, + SPRITE_WIZZARD, AUDIO_THUNDER, AUDIO_THUNDER, 1.0, 0, 999}, + {"weapon_purple_staff", "wizard", WEAPON_PURPLE_STAFF, + WeaponType::GunPoint, 192, 48, 22, 35, 6, RES_PURPLE_STAFF, + SPRITE_WIZZARD, AUDIO_LIGHT_SHOOT, AUDIO_FIREBALL_EXP, 0.9, 0, 999}, + + // Lizard weapons + {"weapon_solid_claw", "lizard", WEAPON_SOLID_CLAW, WeaponType::SwordRange, + 56, 40, 24, 22, 0, RES_GRASS_SWORD, SPRITE_LIZARD, AUDIO_CLAW_HIT, + AUDIO_CLAW_HIT, 1.0, 0, 999}, + + // Elf weapons + {"weapon_powerful_bow", "elf", WEAPON_POWERFUL_BOW, WeaponType::GunPoint, + 320, 32, 35, 45, 12, RES_POWERFUL_BOW, SPRITE_ELF, AUDIO_SHOOT, + AUDIO_ARROW_HIT, 1.0, 0, 999}, + }; + + for (const auto& def : weapons) { + auto proto = std::make_unique(); + proto->setPrototypeId(def.id); + proto->setCategory(def.category); + proto->setWeaponId(def.weaponId); + proto->setWeaponType(def.type); + proto->setShootRange(def.shootRange); + proto->setEffectRange(def.effectRange); + proto->setBaseDamage(def.damage); + proto->setGap(def.gap); + proto->setBulletSpeed(def.bulletSpeed); + proto->setTextureId(def.textureId); + proto->setBelongSpriteId(def.belongSpriteId); + proto->setBirthAudio(def.birthAudio); + proto->setDeathAudio(def.deathAudio); + proto->setWeight(def.weight); + proto->setMinLevel(def.minLevel); + proto->setMaxLevel(def.maxLevel); + + registry.registerPrototype(def.id, std::move(proto)); + } +} + +static void registerItemPrototypes(PrototypeRegistry& registry) { + struct ItemDef { + const char* id; + const char* category; + ItemType type; + int itemId; + int textureId; + int belongSpriteId; + int hpRestorePercent; + bool isExtra; + double weight; + int minLevel; + int maxLevel; + }; + + const ItemDef items[] = { + {"item_hp_red", "consumable", ItemType::HpMedicine, 0, RES_FLASK_BIG_RED, + 0, 55, false, 1.0, 0, 999}, + {"item_hp_yellow", "consumable", ItemType::HpExtraMedicine, 0, + RES_FLASK_BIG_YELLOW, 0, 33, true, 0.5, 0, 999}, + }; + + for (const auto& def : items) { + auto proto = std::make_unique(); + proto->setPrototypeId(def.id); + proto->setCategory(def.category); + proto->setItemType(def.type); + proto->setItemId(def.itemId); + proto->setTextureId(def.textureId); + proto->setBelongSpriteId(def.belongSpriteId); + proto->setHpRestorePercent(def.hpRestorePercent); + proto->setIsExtra(def.isExtra); + proto->setWeight(def.weight); + proto->setMinLevel(def.minLevel); + proto->setMaxLevel(def.maxLevel); + + registry.registerPrototype(def.id, std::move(proto)); + } +} + +void initializeEntityPrototypes() { + PrototypeRegistry& registry = PrototypeRegistry::getInstance(); + registry.clear(); + + registerMonsterPrototypes(registry); + registerWeaponPrototypes(registry); + registerItemPrototypes(registry); +} diff --git a/src/factories/entity_factory.h b/src/factories/entity_factory.h new file mode 100644 index 0000000..5d3f6a1 --- /dev/null +++ b/src/factories/entity_factory.h @@ -0,0 +1,423 @@ +#ifndef SNAKE_ENTITY_FACTORY_H_ +#define SNAKE_ENTITY_FACTORY_H_ + +#include "types.h" +#include "sprite.h" +#include "player.h" +#include "weapon.h" + +#include +#include +#include +#include +#include +#include + +// ============================================================================ +// Forward Declarations +// ============================================================================ +class EntityPrototype; +class MonsterPrototype; +class WeaponPrototype; +class ItemPrototype; +class PrototypeRegistry; +class EntityFactory; +class MonsterFactory; +class WeaponFactory; +class ItemFactory; +struct EntityGenerationConfig; +struct SpawnContext; + +// ============================================================================ +// SpawnContext - Context for entity spawning +// ============================================================================ +struct SpawnContext { + int x = 0; + int y = 0; + int team = 0; + PlayerType playerType = PlayerType::Computer; + Direction direction = Direction::Right; + double levelScaling = 1.0; + double difficultyFactor = 1.0; +}; + +// ============================================================================ +// EntityPrototype - Abstract base class for entity templates +// ============================================================================ +class EntityPrototype { + public: + EntityPrototype() = default; + EntityPrototype(const EntityPrototype&) = default; + EntityPrototype& operator=(const EntityPrototype&) = default; + EntityPrototype(EntityPrototype&&) noexcept = default; + EntityPrototype& operator=(EntityPrototype&&) noexcept = default; + virtual ~EntityPrototype() = default; + + virtual std::unique_ptr clone() const = 0; + virtual std::string getPrototypeId() const = 0; + virtual std::string getCategory() const = 0; + + void setPrototypeId(const std::string& id) { prototypeId_ = id; } + void setCategory(const std::string& category) { category_ = category; } + void setWeight(double weight) { weight_ = weight; } + void setMinLevel(int level) { minLevel_ = level; } + void setMaxLevel(int level) { maxLevel_ = level; } + + double getWeight() const { return weight_; } + int getMinLevel() const { return minLevel_; } + int getMaxLevel() const { return maxLevel_; } + + protected: + std::string prototypeId_; + std::string category_; + double weight_ = 1.0; + int minLevel_ = 0; + int maxLevel_ = 999; +}; + +// ============================================================================ +// MonsterPrototype - Template for monster generation +// ============================================================================ +class MonsterPrototype final : public EntityPrototype { + public: + MonsterPrototype() = default; + MonsterPrototype(const MonsterPrototype&) = default; + MonsterPrototype& operator=(const MonsterPrototype&) = default; + MonsterPrototype(MonsterPrototype&&) noexcept = default; + MonsterPrototype& operator=(MonsterPrototype&&) noexcept = default; + ~MonsterPrototype() override = default; + + std::unique_ptr clone() const override { + return std::make_unique(*this); + } + + std::string getPrototypeId() const override { return prototypeId_; } + std::string getCategory() const override { return category_; } + + void setSpriteId(int id) { spriteId_ = id; } + void setMoveStep(int step) { moveStep_ = step; } + void setBaseHp(int hp) { baseHp_ = hp; } + void setWeaponId(int id) { weaponId_ = id; } + void setDropRate(double rate) { dropRate_ = rate; } + void setIsBoss(bool boss) { isBoss_ = boss; } + + int getSpriteId() const { return spriteId_; } + int getMoveStep() const { return moveStep_; } + int getBaseHp() const { return baseHp_; } + int getWeaponId() const { return weaponId_; } + double getDropRate() const { return dropRate_; } + bool getIsBoss() const { return isBoss_; } + + private: + int spriteId_ = 0; + int moveStep_ = 1; + int baseHp_ = 100; + int weaponId_ = WEAPON_MONSTER_CLAW; + double dropRate_ = 1.0; + bool isBoss_ = false; +}; + +// ============================================================================ +// WeaponPrototype - Template for weapon generation +// ============================================================================ +class WeaponPrototype final : public EntityPrototype { + public: + WeaponPrototype() = default; + WeaponPrototype(const WeaponPrototype&) = default; + WeaponPrototype& operator=(const WeaponPrototype&) = default; + WeaponPrototype(WeaponPrototype&&) noexcept = default; + WeaponPrototype& operator=(WeaponPrototype&&) noexcept = default; + ~WeaponPrototype() override = default; + + std::unique_ptr clone() const override { + return std::make_unique(*this); + } + + std::string getPrototypeId() const override { return prototypeId_; } + std::string getCategory() const override { return category_; } + + void setWeaponId(int id) { weaponId_ = id; } + void setWeaponType(WeaponType type) { weaponType_ = type; } + void setShootRange(int range) { shootRange_ = range; } + void setEffectRange(int range) { effectRange_ = range; } + void setBaseDamage(int damage) { baseDamage_ = damage; } + void setGap(int gap) { gap_ = gap; } + void setBulletSpeed(int speed) { bulletSpeed_ = speed; } + void setTextureId(int id) { textureId_ = id; } + void setBelongSpriteId(int id) { belongSpriteId_ = id; } + void setBirthAudio(int audio) { birthAudio_ = audio; } + void setDeathAudio(int audio) { deathAudio_ = audio; } + void setBuffChance(int buffIndex, double chance) { + if (buffIndex >= 0 && buffIndex < BUFF_END) { + buffChances_[buffIndex] = chance; + } + } + void setBuffDuration(int buffIndex, int duration) { + if (buffIndex >= 0 && buffIndex < BUFF_END) { + buffDurations_[buffIndex] = duration; + } + } + + int getWeaponId() const { return weaponId_; } + WeaponType getWeaponType() const { return weaponType_; } + int getShootRange() const { return shootRange_; } + int getEffectRange() const { return effectRange_; } + int getBaseDamage() const { return baseDamage_; } + int getGap() const { return gap_; } + int getBulletSpeed() const { return bulletSpeed_; } + int getTextureId() const { return textureId_; } + int getBelongSpriteId() const { return belongSpriteId_; } + int getBirthAudio() const { return birthAudio_; } + int getDeathAudio() const { return deathAudio_; } + const std::array& getBuffChances() const { return buffChances_; } + const std::array& getBuffDurations() const { return buffDurations_; } + + private: + int weaponId_ = 0; + WeaponType weaponType_ = WeaponType::SwordPoint; + int shootRange_ = 64; + int effectRange_ = 32; + int baseDamage_ = 10; + int gap_ = 30; + int bulletSpeed_ = 4; + int textureId_ = 0; + int belongSpriteId_ = 0; + int birthAudio_ = -1; + int deathAudio_ = -1; + std::array buffChances_{}; + std::array buffDurations_{}; +}; + +// ============================================================================ +// ItemPrototype - Template for item generation +// ============================================================================ +class ItemPrototype final : public EntityPrototype { + public: + ItemPrototype() = default; + ItemPrototype(const ItemPrototype&) = default; + ItemPrototype& operator=(const ItemPrototype&) = default; + ItemPrototype(ItemPrototype&&) noexcept = default; + ItemPrototype& operator=(ItemPrototype&&) noexcept = default; + ~ItemPrototype() override = default; + + std::unique_ptr clone() const override { + return std::make_unique(*this); + } + + std::string getPrototypeId() const override { return prototypeId_; } + std::string getCategory() const override { return category_; } + + void setItemType(ItemType type) { itemType_ = type; } + void setItemId(int id) { itemId_ = id; } + void setBelongSpriteId(int id) { belongSpriteId_ = id; } + void setTextureId(int id) { textureId_ = id; } + void setHpRestorePercent(int percent) { hpRestorePercent_ = percent; } + void setIsExtra(bool extra) { isExtra_ = extra; } + + ItemType getItemType() const { return itemType_; } + int getItemId() const { return itemId_; } + int getBelongSpriteId() const { return belongSpriteId_; } + int getTextureId() const { return textureId_; } + int getHpRestorePercent() const { return hpRestorePercent_; } + bool getIsExtra() const { return isExtra_; } + + private: + ItemType itemType_ = ItemType::None; + int itemId_ = 0; + int belongSpriteId_ = 0; + int textureId_ = 0; + int hpRestorePercent_ = 0; + bool isExtra_ = false; +}; + +// ============================================================================ +// PrototypeRegistry - Registry for storing and retrieving entity prototypes +// Uses Prototype pattern: stores templates that can be cloned +// ============================================================================ +class PrototypeRegistry { + public: + PrototypeRegistry() = default; + PrototypeRegistry(const PrototypeRegistry&) = delete; + PrototypeRegistry& operator=(const PrototypeRegistry&) = delete; + PrototypeRegistry(PrototypeRegistry&&) noexcept = default; + PrototypeRegistry& operator=(PrototypeRegistry&&) noexcept = default; + ~PrototypeRegistry() = default; + + static PrototypeRegistry& getInstance(); + + void registerPrototype(const std::string& id, std::unique_ptr prototype); + + const EntityPrototype* getPrototype(const std::string& id) const; + const MonsterPrototype* getMonsterPrototype(const std::string& id) const; + const WeaponPrototype* getWeaponPrototype(const std::string& id) const; + const ItemPrototype* getItemPrototype(const std::string& id) const; + + std::vector getMonsterPrototypesByCategory( + const std::string& category) const; + std::vector getWeaponPrototypesByCategory( + const std::string& category) const; + std::vector getItemPrototypesByCategory( + const std::string& category) const; + + std::vector getAllMonsterPrototypes() const; + std::vector getAllWeaponPrototypes() const; + std::vector getAllItemPrototypes() const; + + void clear(); + void initializeDefaultPrototypes(); + + private: + std::map> prototypes_; +}; + +// ============================================================================ +// EntityGenerationConfig - Configuration for level-based entity generation +// ============================================================================ +struct EntityGenerationConfig { + int minSnakeLength = 2; + int maxSnakeLength = 4; + int minBossLength = 1; + int maxBossLength = 1; + + std::vector monsterCategories; + std::vector bossCategories; + std::vector weaponCategories; + std::vector itemCategories; + + double hpScaling = 1.0; + double damageScaling = 1.0; + double dropRateScaling = 1.0; + double monsterGenFactor = 1.0; + + int heroCount = 8; + int flaskCount = 6; + int enemyCount = 25; + int bossCount = 2; + + double yellowFlaskDropRate = 0.3; + double weaponDropRate = 0.7; + double luckyFactor = 1.0; +}; + +// ============================================================================ +// EntityFactory - Abstract factory interface +// ============================================================================ +class EntityFactory { + public: + virtual ~EntityFactory() = default; + + virtual std::shared_ptr createSnake(const SpawnContext& context) const = 0; + virtual std::shared_ptr createSprite(const SpawnContext& context) const = 0; +}; + +// ============================================================================ +// MonsterFactory - Concrete factory for creating monsters +// ============================================================================ +class MonsterFactory final : public EntityFactory { + public: + MonsterFactory() = default; + MonsterFactory(const MonsterFactory&) = delete; + MonsterFactory& operator=(const MonsterFactory&) = delete; + MonsterFactory(MonsterFactory&&) noexcept = default; + MonsterFactory& operator=(MonsterFactory&&) noexcept = default; + ~MonsterFactory() override = default; + + std::shared_ptr createSnake(const SpawnContext& context) const override; + std::shared_ptr createSprite(const SpawnContext& context) const override; + + std::shared_ptr createSnakeFromPrototype( + const MonsterPrototype& proto, const SpawnContext& context) const; + std::shared_ptr createSpriteFromPrototype( + const MonsterPrototype& proto, const SpawnContext& context) const; + + std::shared_ptr createRandomMonster( + int x, int y, int gameLevel, double difficultyFactor) const; + std::shared_ptr createRandomBoss( + int x, int y, int gameLevel, double difficultyFactor) const; + + private: + const MonsterPrototype* selectRandomPrototype( + const std::vector& prototypes, int gameLevel) const; +}; + +// ============================================================================ +// WeaponFactory - Concrete factory for creating weapons +// ============================================================================ +class WeaponFactory final { + public: + WeaponFactory() = default; + WeaponFactory(const WeaponFactory&) = delete; + WeaponFactory& operator=(const WeaponFactory&) = delete; + WeaponFactory(WeaponFactory&&) noexcept = default; + WeaponFactory& operator=(WeaponFactory&&) noexcept = default; + ~WeaponFactory() = default; + + std::unique_ptr createWeapon(const WeaponPrototype& proto) const; + std::unique_ptr createWeaponFromId(int weaponId) const; + std::unique_ptr createRandomWeapon(int gameLevel) const; + std::unique_ptr createWeaponForSprite(int spriteId) const; + + private: + const WeaponPrototype* selectRandomPrototype(int gameLevel) const; +}; + +// ============================================================================ +// ItemFactory - Concrete factory for creating items +// ============================================================================ +class ItemFactory final { + public: + ItemFactory() = default; + ItemFactory(const ItemFactory&) = delete; + ItemFactory& operator=(const ItemFactory&) = delete; + ItemFactory(ItemFactory&&) noexcept = default; + ItemFactory& operator=(ItemFactory&&) noexcept = default; + ~ItemFactory() = default; + + Item createItem(const ItemPrototype& proto, int x, int y) const; + Item createHpMedicine(int x, int y, bool extra = false) const; + Item createWeaponItem(int x, int y, int gameLevel) const; + Item createHeroItem(int x, int y) const; + Item createRandomItem(int x, int y, int gameLevel) const; + + private: + const ItemPrototype* selectRandomPrototype(ItemType type, int gameLevel) const; +}; + +// ============================================================================ +// EntitySpawner - High-level interface for spawning entities using factories +// ============================================================================ +class EntitySpawner { + public: + EntitySpawner(); + EntitySpawner(const EntitySpawner&) = delete; + EntitySpawner& operator=(const EntitySpawner&) = delete; + EntitySpawner(EntitySpawner&&) noexcept = default; + EntitySpawner& operator=(EntitySpawner&&) noexcept = default; + ~EntitySpawner() = default; + + void initializeLevel(int level, int stage); + + std::shared_ptr spawnMonster(int x, int y); + std::shared_ptr spawnBoss(int x, int y); + Item spawnHeroItem(int x, int y); + Item spawnFlaskItem(int x, int y, bool extra = false); + Item spawnWeaponItem(int x, int y); + + const EntityGenerationConfig& getConfig() const { return config_; } + void setConfig(const EntityGenerationConfig& config) { config_ = config; } + + private: + EntityGenerationConfig config_; + std::unique_ptr monsterFactory_; + std::unique_ptr weaponFactory_; + std::unique_ptr itemFactory_; + int currentLevel_ = 0; + int currentStage_ = 0; +}; + +// ============================================================================ +// Initialization Functions +// ============================================================================ +void initializeEntityPrototypes(); + +#endif // SNAKE_ENTITY_FACTORY_H_ diff --git a/src/game.cpp b/src/game.cpp index 0461e87..21eafcb 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -15,6 +15,7 @@ #include "ai.h" #include "audio.h" #include "bullet.h" +#include "factories/entity_factory.h" #include "helper.h" #include "map.h" #include "net.h" @@ -122,6 +123,7 @@ namespace { GameContext g_gameContext; std::unique_ptr g_audioObserver; std::unique_ptr g_uiObserver; +std::unique_ptr g_entitySpawner; } // Legacy global variables (delegating to GameContext) @@ -358,6 +360,13 @@ void ItemManager::clearItems() { } void ItemManager::generateHeroItem(int x, int y) { + // Delegate to factory system when available + if (g_entitySpawner) { + itemMap_[x][y] = g_entitySpawner->spawnHeroItem(x, y); + return; + } + + // Fallback to legacy implementation const int heroId = randInt(SPRITE_KNIGHT, SPRITE_LIZARD); auto modelAnimation = commonSprites[heroId].animation(); if (!modelAnimation) { @@ -374,6 +383,21 @@ void ItemManager::generateHeroItem(int x, int y) { } void ItemManager::generateItem(int x, int y, ItemType type) { + // Delegate to factory system when available + if (g_entitySpawner) { + if (type == ItemType::Weapon) { + itemMap_[x][y] = g_entitySpawner->spawnWeaponItem(x, y); + return; + } else if (type == ItemType::HpMedicine) { + itemMap_[x][y] = g_entitySpawner->spawnFlaskItem(x, y, false); + return; + } else if (type == ItemType::HpExtraMedicine) { + itemMap_[x][y] = g_entitySpawner->spawnFlaskItem(x, y, true); + return; + } + } + + // Fallback to legacy implementation int textureId = RES_FLASK_BIG_RED, id = 0, belong = SPRITE_KNIGHT; if (type == ItemType::HpMedicine) { textureId = RES_FLASK_BIG_RED; @@ -1312,7 +1336,11 @@ void dropItemNearSprite(const Sprite* sprite, ItemType itemType) { const int y = sprite->y() / UNIT + dy; if (inr(x, 0, n - 1) && inr(y, 0, m - 1) && hasMap[x][y] && itemMap[x][y].type() == ItemType::None) { - generateItem(x, y, itemType); + if (g_entitySpawner && itemType == ItemType::Weapon) { + itemMap[x][y] = g_entitySpawner->spawnWeaponItem(x, y); + } else { + generateItem(x, y, itemType); + } } return; } @@ -1341,17 +1369,39 @@ void clearItemMap() { void initItemMap(int hCount, int fCount) { int x = 0; int y = 0; + + // Use factory-based hero generation while (hCount--) { - generateHeroItemAllMap(); + do { + x = randInt(1, n - 2); + y = randInt(1, m - 2); + } while (!hasMap[x][y] || map[x][y].type() != BlockType::Floor || + itemMap[x][y].type() != ItemType::None || + !hasMap[x - 1][y] + !hasMap[x + 1][y] + !hasMap[x][y + 1] + + !hasMap[x][y - 1] >= + 1); + + if (g_entitySpawner) { + itemMap[x][y] = g_entitySpawner->spawnHeroItem(x, y); + } else { + generateHeroItem(x, y); + } herosCount++; } + + // Use factory-based flask generation while (fCount--) { do { x = randInt(0, n - 1); y = randInt(0, m - 1); } while (!hasMap[x][y] || map[x][y].type() != BlockType::Floor || itemMap[x][y].type() != ItemType::None); - generateItem(x, y, ItemType::HpMedicine); + + if (g_entitySpawner) { + itemMap[x][y] = g_entitySpawner->spawnFlaskItem(x, y, false); + } else { + generateItem(x, y, ItemType::HpMedicine); + } flasksCount++; } } @@ -1420,38 +1470,29 @@ void initEnemies(int enemiesCount) { hasEnemy[n / 2 + i][m / 2 + j] = 1; } } + + // Use the new factory-based entity spawner + if (!g_entitySpawner) { + return; + } + for (int i = 0; i < enemiesCount;) { - const double random = randDouble() * GAME_MONSTERS_GEN_FACTOR; const Point pos = getAvaliablePos(); - const int x = pos.x; - const int y = pos.y; - int minLen = 2; - int maxLen = 4; - int step = 1; - int startId = SPRITE_TINY_ZOMBIE; - int endId = SPRITE_TINY_ZOMBIE; - if (random < 0.3) { - startId = SPRITE_TINY_ZOMBIE; - endId = SPRITE_SKELET; - } else if (random < 0.4) { - startId = SPRITE_WOGOL; - endId = SPRITE_CHROT; - step = 2; - } else if (random < 0.5) { - startId = SPRITE_ZOMBIE; - endId = SPRITE_ICE_ZOMBIE; - } else if (random < 0.8) { - startId = SPRITE_MUDDY; - endId = SPRITE_SWAMPY; - } else { - startId = SPRITE_MASKED_ORC; - endId = SPRITE_NECROMANCER; + auto snake = g_entitySpawner->spawnMonster(pos.x, pos.y); + if (snake) { + spriteSnake[spritesCount++] = snake; + i += static_cast(snake->sprites().size()); } - i += generateEnemy(x, y, minLen, maxLen, startId, endId, step); } - for (int bossCount = 0; bossCount < bossSetting; bossCount++) { + + // Spawn bosses + const auto& config = g_entitySpawner->getConfig(); + for (int bossCount = 0; bossCount < config.bossCount; bossCount++) { const Point pos = getAvaliablePos(); - generateEnemy(pos.x, pos.y, 1, 1, SPRITE_BIG_ZOMBIE, SPRITE_BIG_DEMON, 1); + auto boss = g_entitySpawner->spawnBoss(pos.x, pos.y); + if (boss) { + spriteSnake[spritesCount++] = boss; + } } } @@ -1472,6 +1513,11 @@ void initGame(int localPlayers, int remotePlayers, bool localFirst) { initRenderer(); initCountDownBar(); + // Initialize entity factory system + initializeEntityPrototypes(); + g_entitySpawner = std::make_unique(); + g_entitySpawner->initializeLevel(gameLevel, stage); + // create default hero at (w/2, h/2) (as well push ani) initializeEventObservers(); for (int i = 0; i < localPlayers + remotePlayers; i++) { @@ -1491,7 +1537,7 @@ void initGame(int localPlayers, int remotePlayers, bool localFirst) { initRandomMap(0.7, 7, GAME_TRAP_RATE); clearItemMap(); - // create enemies + // create enemies using the new factory system initEnemies(spritesSetting); pushMapToRender(); bullets.clear(); diff --git a/src/models/game_event.cpp b/src/models/game_event.cpp new file mode 100644 index 0000000..2934d15 --- /dev/null +++ b/src/models/game_event.cpp @@ -0,0 +1,21 @@ +#include "models/game_event.h" + +namespace snake { +namespace models { + +void EventBus::subscribe(Listener listener) { + listeners_.push_back(std::move(listener)); +} + +void EventBus::emit(const GameEvent& event) const { + for (const auto& listener : listeners_) { + listener(event); + } +} + +void EventBus::clear() { + listeners_.clear(); +} + +} // namespace models +} // namespace snake diff --git a/src/models/game_event.h b/src/models/game_event.h new file mode 100644 index 0000000..d7ba9f2 --- /dev/null +++ b/src/models/game_event.h @@ -0,0 +1,43 @@ +#ifndef SNAKE_MODELS_GAME_EVENT_H_ +#define SNAKE_MODELS_GAME_EVENT_H_ + +#include "types.h" + +#include +#include + +namespace snake { +namespace models { + +enum class GameEventType { PlayerDied, ItemPicked, SoundRequested }; + +struct GameEvent { + GameEventType type = GameEventType::SoundRequested; + int playerId = -1; + ItemType itemType = ItemType::None; + int audioId = -1; +}; + +class EventBus { + public: + using Listener = std::function; + + EventBus() = default; + EventBus(const EventBus&) = delete; + EventBus& operator=(const EventBus&) = delete; + EventBus(EventBus&&) = default; + EventBus& operator=(EventBus&&) = default; + ~EventBus() = default; + + void subscribe(Listener listener); + void emit(const GameEvent& event) const; + void clear(); + + private: + std::vector listeners_{}; +}; + +} // namespace models +} // namespace snake + +#endif // SNAKE_MODELS_GAME_EVENT_H_ diff --git a/src/systems/buff_manager.cpp b/src/systems/buff_manager.cpp new file mode 100644 index 0000000..7f3f7ae --- /dev/null +++ b/src/systems/buff_manager.cpp @@ -0,0 +1,235 @@ +#include "systems/buff_manager.h" + +#include "helper.h" +#include "render.h" +#include "res.h" + +#include + +extern std::array animationsList; +extern Texture textures[]; +extern Effect effects[]; +extern double GAME_MONSTERS_WEAPON_BUFF_ADJUST; + +constexpr int GAME_MONSTERS_TEAM = 9; + +namespace snake { +namespace systems { + +namespace { + +class BuffEffectStrategy { + public: + virtual ~BuffEffectStrategy() = default; + virtual void apply(const std::shared_ptr& source, + const std::shared_ptr& target, + int duration, + BuffManager& manager) const = 0; +}; + +class FrozenBuffStrategy final : public BuffEffectStrategy { + public: + void apply(const std::shared_ptr&, + const std::shared_ptr& target, + int duration, + BuffManager& manager) const override { + manager.freezeSnake(target.get(), duration); + } +}; + +class SlowdownBuffStrategy final : public BuffEffectStrategy { + public: + void apply(const std::shared_ptr&, + const std::shared_ptr& target, + int duration, + BuffManager& manager) const override { + manager.slowDownSnake(target.get(), duration); + } +}; + +class DefenseBuffStrategy final : public BuffEffectStrategy { + public: + void apply(const std::shared_ptr& source, + const std::shared_ptr&, + int duration, + BuffManager& manager) const override { + if (source) { + manager.shieldSnake(source, duration); + } + } +}; + +class AttackBuffStrategy final : public BuffEffectStrategy { + public: + void apply(const std::shared_ptr& source, + const std::shared_ptr&, + int duration, + BuffManager& manager) const override { + if (source) { + manager.attackUpSnake(source, duration); + } + } +}; + +const std::array, BUFF_END>& + getBuffStrategies() { + static const std::array, BUFF_END> + kStrategies = {std::make_unique(), + std::make_unique(), + std::make_unique(), + std::make_unique()}; + return kStrategies; +} + +} // namespace + +void BuffManager::freezeSnake(Snake* snake, int duration) { + if (!snake) { + return; + } + auto& buffs = snake->buffs(); + if (buffs[BUFF_FROZEN]) { + return; + } + if (!buffs[BUFF_DEFFENCE]) { + buffs[BUFF_FROZEN] += duration; + } + std::shared_ptr effect; + if (buffs[BUFF_DEFFENCE]) { + effect = std::make_shared(effects[EFFECT_VANISH30]); + duration = 30; + } + for (const auto& sprite : snake->sprites()) { + if (!sprite) { + continue; + } + const Effect* effectPtr = effect ? effect.get() : nullptr; + auto animation = createAndPushAnimation( + animationsList[RENDER_LIST_EFFECT_ID], &textures[RES_ICE], effectPtr, + LoopType::Once, duration, sprite->x(), sprite->y(), SDL_FLIP_NONE, 0, + At::BottomCenter); + animation->setScaled(false); + if (buffs[BUFF_DEFFENCE]) { + continue; + } + bindAnimationToSprite(animation, sprite, true); + } +} + +void BuffManager::slowDownSnake(Snake* snake, int duration) { + if (!snake) { + return; + } + auto& buffs = snake->buffs(); + if (buffs[BUFF_SLOWDOWN]) { + return; + } + if (!buffs[BUFF_DEFFENCE]) { + buffs[BUFF_SLOWDOWN] += duration; + } + std::shared_ptr effect; + if (buffs[BUFF_DEFFENCE]) { + effect = std::make_shared(effects[EFFECT_VANISH30]); + duration = 30; + } + for (const auto& sprite : snake->sprites()) { + if (!sprite) { + continue; + } + const Effect* effectPtr = effect ? effect.get() : nullptr; + auto animation = createAndPushAnimation( + animationsList[RENDER_LIST_EFFECT_ID], &textures[RES_SOLIDFX], effectPtr, + LoopType::Lifespan, 40, sprite->x(), sprite->y(), SDL_FLIP_NONE, 0, + At::BottomCenter); + animation->setLifeSpan(duration); + animation->setScaled(false); + if (buffs[BUFF_DEFFENCE]) { + continue; + } + bindAnimationToSprite(animation, sprite, true); + } +} + +void BuffManager::shieldSnake(const std::shared_ptr& snake, int duration) { + if (!snake) { + return; + } + auto& buffs = snake->buffs(); + if (buffs[BUFF_DEFFENCE]) { + return; + } + buffs[BUFF_DEFFENCE] += duration; + for (const auto& sprite : snake->sprites()) { + if (sprite) { + auto animation = createAndPushAnimation( + animationsList[RENDER_LIST_EFFECT_ID], &textures[RES_HOLY_SHIELD], nullptr, + LoopType::Lifespan, 40, sprite->x(), sprite->y(), SDL_FLIP_NONE, 0, + At::BottomCenter); + bindAnimationToSprite(animation, sprite, true); + animation->setLifeSpan(duration); + } + } +} + +void BuffManager::attackUpSnake(const std::shared_ptr& snake, int duration) { + if (!snake) { + return; + } + auto& buffs = snake->buffs(); + if (buffs[BUFF_ATTACK]) { + return; + } + buffs[BUFF_ATTACK] += duration; + for (const auto& sprite : snake->sprites()) { + if (sprite) { + auto animation = createAndPushAnimation( + animationsList[RENDER_LIST_EFFECT_ID], &textures[RES_ATTACK_UP], nullptr, + LoopType::Lifespan, SPRITE_ANIMATION_DURATION, sprite->x(), sprite->y(), + SDL_FLIP_NONE, 0, At::BottomCenter); + bindAnimationToSprite(animation, sprite, true); + animation->setLifeSpan(duration); + } + } +} + +void BuffManager::updateBuffDurations(entities::EntityManager& entityManager) { + const int count = entityManager.snakeCount(); + for (int i = 0; i < count; i++) { + const auto& snake = entityManager.getSnake(i); + if (!snake) { + continue; + } + auto& buffs = snake->buffs(); + for (int j = BUFF_BEGIN; j < BUFF_END; j++) { + if (buffs[j] > 0) { + buffs[j]--; + } + } + } +} + +void BuffManager::invokeWeaponBuff(const std::shared_ptr& src, + const Weapon& weapon, + const std::shared_ptr& dest, + int) { + if (!dest) { + return; + } + double random = 0.0; + const auto& weaponEffects = weapon.effects(); + const auto& strategies = getBuffStrategies(); + + for (int i = BUFF_BEGIN; i < BUFF_END; i++) { + random = randDouble(); + if (src && src->team() == GAME_MONSTERS_TEAM) { + random *= GAME_MONSTERS_WEAPON_BUFF_ADJUST; + } + const auto& strategy = strategies[i]; + if (strategy && random < weaponEffects[i].chance) { + strategy->apply(src, dest, weaponEffects[i].duration, *this); + } + } +} + +} // namespace systems +} // namespace snake diff --git a/src/systems/buff_manager.h b/src/systems/buff_manager.h new file mode 100644 index 0000000..f451419 --- /dev/null +++ b/src/systems/buff_manager.h @@ -0,0 +1,37 @@ +#ifndef SNAKE_SYSTEMS_BUFF_MANAGER_H_ +#define SNAKE_SYSTEMS_BUFF_MANAGER_H_ + +#include "entities/entity_manager.h" +#include "player.h" +#include "weapon.h" + +namespace snake { +namespace systems { + +// ============================================================================ +// BuffManager - Handles buff effects on snakes +// ============================================================================ +class BuffManager { + public: + BuffManager() = default; + BuffManager(const BuffManager&) = delete; + BuffManager& operator=(const BuffManager&) = delete; + BuffManager(BuffManager&&) = default; + BuffManager& operator=(BuffManager&&) = default; + ~BuffManager() = default; + + void freezeSnake(Snake* snake, int duration); + void slowDownSnake(Snake* snake, int duration); + void shieldSnake(const std::shared_ptr& snake, int duration); + void attackUpSnake(const std::shared_ptr& snake, int duration); + void updateBuffDurations(entities::EntityManager& entityManager); + void invokeWeaponBuff(const std::shared_ptr& src, const Weapon& weapon, + const std::shared_ptr& dest, int damage); + + private: +}; + +} // namespace systems +} // namespace snake + +#endif // SNAKE_SYSTEMS_BUFF_MANAGER_H_ diff --git a/src/systems/collision_manager.cpp b/src/systems/collision_manager.cpp new file mode 100644 index 0000000..a6036c2 --- /dev/null +++ b/src/systems/collision_manager.cpp @@ -0,0 +1,86 @@ +#include "systems/collision_manager.h" + +#include "helper.h" +#include "map.h" +#include "res.h" + +extern const int SCALE_FACTOR; +extern const int n, m; +extern bool hasMap[MAP_SIZE][MAP_SIZE]; +extern std::array, MAP_SIZE> map; + +namespace snake { +namespace systems { + +bool CollisionManager::checkCrush(const std::shared_ptr& sprite, + bool loose, bool useAnimationBox, + entities::EntityManager& entityManager) { + if (!sprite) { + return false; + } + const int x = sprite->x(); + const int y = sprite->y(); + SDL_Rect block; + SDL_Rect box = useAnimationBox ? getSpriteAnimationBox(sprite.get()) + : getSpriteFeetBox(sprite.get()); + + // If the sprite is out of the map, then consider it as crushed + if (!inr(x / UNIT, 0, n - 1) || !inr(y / UNIT, 0, m - 1)) { + return true; + } + // Loop over the cells nearby the sprite to know better if it falls out of map + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + int xx = x / UNIT + dx, yy = y / UNIT + dy; + if (inr(xx, 0, n - 1) && inr(yy, 0, m - 1)) { + block = getMapRect(xx, yy); + if (RectRectCross(&box, &block) && !hasMap[xx][yy]) { + return true; + } + } + } + } + + // If it has crushed on other sprites + const int count = entityManager.snakeCount(); + for (int i = 0; i < count; i++) { + const auto& snake = entityManager.getSnake(i); + if (!snake) { + continue; + } + bool self = false; + for (const auto& other : snake->sprites()) { + if (!other) { + continue; + } + if (other.get() != sprite.get()) { + SDL_Rect otherBox = useAnimationBox ? getSpriteAnimationBox(other.get()) + : getSpriteFeetBox(other.get()); + if (RectRectCross(&box, &otherBox)) { + if ((self && loose)) { + // continue checking + } else { + return true; + } + } + } else { + self = true; + } + } + } + return false; +} + +bool CollisionManager::isPlayer(const std::shared_ptr& snake, + entities::EntityManager& entityManager) const { + const int playerCount = entityManager.playerCount(); + for (int i = 0; i < playerCount; i++) { + if (snake && snake == entityManager.getSnake(i)) { + return true; + } + } + return false; +} + +} // namespace systems +} // namespace snake diff --git a/src/systems/collision_manager.h b/src/systems/collision_manager.h new file mode 100644 index 0000000..f801ee1 --- /dev/null +++ b/src/systems/collision_manager.h @@ -0,0 +1,32 @@ +#ifndef SNAKE_SYSTEMS_COLLISION_MANAGER_H_ +#define SNAKE_SYSTEMS_COLLISION_MANAGER_H_ + +#include "entities/entity_manager.h" +#include "sprite.h" + +namespace snake { +namespace systems { + +// ============================================================================ +// CollisionManager - Handles all collision detection +// ============================================================================ +class CollisionManager { + public: + CollisionManager() = default; + CollisionManager(const CollisionManager&) = delete; + CollisionManager& operator=(const CollisionManager&) = delete; + CollisionManager(CollisionManager&&) = default; + CollisionManager& operator=(CollisionManager&&) = default; + ~CollisionManager() = default; + + bool checkCrush(const std::shared_ptr& sprite, bool loose, + bool useAnimationBox, entities::EntityManager& entityManager); + bool isPlayer(const std::shared_ptr& snake, entities::EntityManager& entityManager) const; + + private: +}; + +} // namespace systems +} // namespace snake + +#endif // SNAKE_SYSTEMS_COLLISION_MANAGER_H_ diff --git a/src/systems/item_manager.h b/src/systems/item_manager.h new file mode 100644 index 0000000..6141d95 --- /dev/null +++ b/src/systems/item_manager.h @@ -0,0 +1,50 @@ +#ifndef SNAKE_SYSTEMS_ITEM_MANAGER_H_ +#define SNAKE_SYSTEMS_ITEM_MANAGER_H_ + +#include "entities/entity_manager.h" +#include "sprite.h" +#include "types.h" + +#include + +namespace snake { +namespace systems { + +// ============================================================================ +// ItemManager - Handles item generation and management +// ============================================================================ +class ItemManager { + public: + ItemManager() = default; + ItemManager(const ItemManager&) = delete; + ItemManager& operator=(const ItemManager&) = delete; + ItemManager(ItemManager&&) = default; + ItemManager& operator=(ItemManager&&) = default; + ~ItemManager() = default; + + void initItems(int heroCount, int flaskCount); + void clearItems(); + void generateHeroItem(int x, int y); + void generateItem(int x, int y, ItemType type); + void dropItemNearSprite(const Sprite* sprite, ItemType itemType); + bool checkItemPickup(const std::shared_ptr& snake, + entities::EntityManager& entityManager); + + std::array, MAP_SIZE>& itemMap(); + const std::array, MAP_SIZE>& itemMap() const; + + int heroCount() const; + void setHeroCount(int count); + int flaskCount() const; + void setFlaskCount(int count); + + private: + std::array, MAP_SIZE> itemMap_{}; + int herosCount_ = 0; + int flasksCount_ = 0; +}; + +} // namespace systems +} // namespace snake + +#endif // SNAKE_SYSTEMS_ITEM_MANAGER_H_ From 2d81f9421a4e528443a2c83fb1f8a8ef1ce7672f Mon Sep 17 00:00:00 2001 From: Sam-Si <13261099+Sam-Si@users.noreply.github.com> Date: Sat, 14 Mar 2026 11:28:44 +0530 Subject: [PATCH 2/2] c1 --- src/game.cpp | 616 ++++++++++++++----------------------------------- src/game.h | 181 ++++----------- src/sprite.cpp | 23 +- src/sprite.h | 1 + 4 files changed, 235 insertions(+), 586 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index 21eafcb..1f44e3f 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -15,6 +15,7 @@ #include "ai.h" #include "audio.h" #include "bullet.h" +#include "core/game_state.h" #include "factories/entity_factory.h" #include "helper.h" #include "map.h" @@ -31,13 +32,22 @@ #include #endif +// External resource references (these come from the resource system) extern const int SCALE_FACTOR; extern const int n, m; - extern Texture textures[TEXTURES_SIZE]; +extern Effect effects[]; +extern Weapon weapons[WEAPONS_SIZE]; +extern Sprite commonSprites[COMMON_SPRITE_SIZE]; +extern std::array animationsList; +extern bool hasMap[MAP_SIZE][MAP_SIZE]; +extern double AI_LOCK_LIMIT; + const int MOVE_STEP = 2; namespace { + +// Helper to create game text std::unique_ptr createGameText(const std::string& text, SDL_Color color) { SDL_Renderer* sdlRenderer = renderer(); TTF_Font* ttfFont = font(); @@ -47,6 +57,17 @@ std::unique_ptr createGameText(const std::string& text, SDL_Color color) { return std::make_unique(text, color, sdlRenderer, ttfFont); } +// Forward declarations for observers +class AudioObserver; +class UiObserver; + +// Global game context - single source of truth for all game state +GameContext g_gameContext; +std::unique_ptr g_audioObserver; +std::unique_ptr g_uiObserver; +std::unique_ptr g_entitySpawner; + +// AudioObserver implementation class AudioObserver { public: explicit AudioObserver(EventBus& bus) : bus_(bus) { @@ -68,6 +89,7 @@ class AudioObserver { EventBus& bus_; }; +// UiObserver implementation class UiObserver { public: explicit UiObserver(EventBus& bus) : bus_(bus) { @@ -91,44 +113,21 @@ class UiObserver { private: EventBus& bus_; }; -} // namespace - -void EventBus::subscribe(Listener listener) { - listeners_.push_back(std::move(listener)); -} - -void EventBus::emit(const GameEvent& event) const { - for (const auto& listener : listeners_) { - listener(event); - } -} - -void EventBus::clear() { - listeners_.clear(); -} -extern std::array animationsList; -extern Effect effects[]; - -extern Weapon weapons[WEAPONS_SIZE]; -extern Sprite commonSprites[COMMON_SPRITE_SIZE]; +} // namespace -// Map -std::array, MAP_SIZE> map; -extern bool hasMap[MAP_SIZE][MAP_SIZE]; -int spikeDamage = 1; +// ============================================================================ +// Legacy Global Variable Definitions +// These delegate to GameState data, maintaining backward compatibility. +// New code should use getGameContext().state() directly. +// ============================================================================ -// Global game context -namespace { -GameContext g_gameContext; -std::unique_ptr g_audioObserver; -std::unique_ptr g_uiObserver; -std::unique_ptr g_entitySpawner; -} +// Map data +std::array, MAP_SIZE> map{}; +std::array, MAP_SIZE> itemMap{}; +std::array, MAP_SIZE> hasEnemy{}; -// Legacy global variables (delegating to GameContext) -std::array, SPRITES_MAX_NUM> spriteSnake{}; -BulletList bullets; +// Game configuration variables int gameLevel = 0; int stage = 0; int spritesCount = 0; @@ -147,167 +146,73 @@ double GAME_LUCKY = 1.0; double GAME_DROPOUT_YELLOW_FLASKS = 0.3; double GAME_DROPOUT_WEAPONS = 0.7; double GAME_TRAP_RATE = 0.005; -extern double AI_LOCK_LIMIT; double GAME_MONSTERS_HP_ADJUST = 1.0; double GAME_MONSTERS_WEAPON_BUFF_ADJUST = 1.0; double GAME_MONSTERS_GEN_FACTOR = 1.0; -std::array, MAP_SIZE> itemMap; -std::array, MAP_SIZE> hasEnemy{}; +int spikeDamage = 1; + +// Entity arrays +std::array, kMaxSprites> spriteSnake{}; +BulletList bullets; // ============================================================================ -// EntityManager Implementation +// EventBus Implementation // ============================================================================ - -void EntityManager::addSnake(const std::shared_ptr& snake) { - if (spritesCount_ < SPRITES_MAX_NUM) { - snakes_[spritesCount_++] = snake; - } -} - -void EntityManager::removeSnake(int index) { - if (index >= 0 && index < spritesCount_) { - snakes_[index] = nullptr; - } +void EventBus::subscribe(Listener listener) { + listeners_.push_back(std::move(listener)); } -std::shared_ptr EntityManager::getSnake(int index) const { - if (index >= 0 && index < SPRITES_MAX_NUM) { - return snakes_[index]; +void EventBus::emit(const GameEvent& event) const { + for (const auto& listener : listeners_) { + listener(event); } - return nullptr; -} - -int EntityManager::snakeCount() const { - return spritesCount_; -} - -int EntityManager::playerCount() const { - return playersCount_; -} - -void EntityManager::setPlayerCount(int count) { - playersCount_ = count; -} - -void EntityManager::incrementSpriteCount() { - ++spritesCount_; } -int EntityManager::spriteCount() const { - return spritesCount_; +void EventBus::clear() { + listeners_.clear(); } -void EntityManager::addBullet(const std::shared_ptr& bullet) { - bullets_.push_back(bullet); -} +// ============================================================================ +// Global Accessors - Centralized state access +// ============================================================================ -void EntityManager::removeBullet(const std::shared_ptr& bullet) { - bullets_.remove(bullet); +GameContext& getGameContext() { + return g_gameContext; } -BulletList& EntityManager::bullets() { - return bullets_; -} +// Accessor functions for the new centralized state +// These provide the preferred way to access game state: getGameContext().state() +namespace snake { +namespace core { -const BulletList& EntityManager::bullets() const { - return bullets_; +// The global GameContext is defined in the anonymous namespace above +// This function provides access to it +GameContext& getGlobalGameContext() { + return g_gameContext; } -std::array, SPRITES_MAX_NUM>& EntityManager::snakes() { - return snakes_; -} +} // namespace core +} // namespace snake -const std::array, SPRITES_MAX_NUM>& EntityManager::snakes() const { - return snakes_; -} +// ============================================================================ +// EntityManager Implementation +// ============================================================================ -void EntityManager::clear() { - for (int i = 0; i < SPRITES_MAX_NUM; ++i) { - snakes_[i] = nullptr; - } - bullets_.clear(); - spritesCount_ = 0; - playersCount_ = 0; -} +// These have been moved to src/entities/entity_manager.cpp // ============================================================================ // CollisionManager Implementation // ============================================================================ -bool CollisionManager::checkCrush(const std::shared_ptr& sprite, - bool loose, bool useAnimationBox, - EntityManager& entityManager) { - if (!sprite) { - return false; - } - const int x = sprite->x(); - const int y = sprite->y(); - SDL_Rect block; - SDL_Rect box = useAnimationBox ? getSpriteAnimationBox(sprite.get()) - : getSpriteFeetBox(sprite.get()); - - // If the sprite is out of the map, then consider it as crushed - if (!inr(x / UNIT, 0, n - 1) || !inr(y / UNIT, 0, m - 1)) { - return true; - } - // Loop over the cells nearby the sprite to know better if it falls out of map - for (int dx = -1; dx <= 1; dx++) { - for (int dy = -1; dy <= 1; dy++) { - int xx = x / UNIT + dx, yy = y / UNIT + dy; - if (inr(xx, 0, n - 1) && inr(yy, 0, m - 1)) { - block = getMapRect(xx, yy); - if (RectRectCross(&box, &block) && !hasMap[xx][yy]) { - return true; - } - } - } - } - - // If it has crushed on other sprites - const int count = entityManager.snakeCount(); - for (int i = 0; i < count; i++) { - const auto& snake = entityManager.getSnake(i); - if (!snake) { - continue; - } - bool self = false; - for (const auto& other : snake->sprites()) { - if (!other) { - continue; - } - if (other.get() != sprite.get()) { - SDL_Rect otherBox = useAnimationBox ? getSpriteAnimationBox(other.get()) - : getSpriteFeetBox(other.get()); - if (RectRectCross(&box, &otherBox)) { - if ((self && loose)) { - // continue checking - } else { - return true; - } - } - } else { - self = true; - } - } - } - return false; -} - -bool CollisionManager::isPlayer(const std::shared_ptr& snake, - EntityManager& entityManager) const { - const int playerCount = entityManager.playerCount(); - for (int i = 0; i < playerCount; i++) { - if (snake && snake == entityManager.getSnake(i)) { - return true; - } - } - return false; -} +// These have been moved to src/systems/collision_manager.cpp // ============================================================================ // ItemManager Implementation // ============================================================================ +namespace snake { +namespace systems { + void ItemManager::initItems(int heroCount, int flaskCount) { int x = 0; int y = 0; @@ -456,7 +361,7 @@ void ItemManager::dropItemNearSprite(const Sprite* sprite, ItemType itemType) { } bool ItemManager::checkItemPickup(const std::shared_ptr& snake, - EntityManager& entityManager) { + snake::entities::EntityManager& entityManager) { // This is handled in makeSnakeCross for now // Will be fully migrated in future refactoring return false; @@ -486,221 +391,14 @@ void ItemManager::setFlaskCount(int count) { flasksCount_ = count; } +} // namespace systems +} // namespace snake + // ============================================================================ // BuffManager Implementation // ============================================================================ -void BuffManager::freezeSnake(Snake* snake, int duration) { - if (!snake) { - return; - } - auto& buffs = snake->buffs(); - if (buffs[BUFF_FROZEN]) { - return; - } - if (!buffs[BUFF_DEFFENCE]) { - buffs[BUFF_FROZEN] += duration; - } - std::shared_ptr effect; - if (buffs[BUFF_DEFFENCE]) { - effect = std::make_shared(effects[EFFECT_VANISH30]); - duration = 30; - } - for (const auto& sprite : snake->sprites()) { - if (!sprite) { - continue; - } - const Effect* effectPtr = effect ? effect.get() : nullptr; - auto animation = createAndPushAnimation( - animationsList[RENDER_LIST_EFFECT_ID], &textures[RES_ICE], effectPtr, - LoopType::Once, duration, sprite->x(), sprite->y(), SDL_FLIP_NONE, 0, - At::BottomCenter); - animation->setScaled(false); - if (buffs[BUFF_DEFFENCE]) { - continue; - } - bindAnimationToSprite(animation, sprite, true); - } -} - -void BuffManager::slowDownSnake(Snake* snake, int duration) { - if (!snake) { - return; - } - auto& buffs = snake->buffs(); - if (buffs[BUFF_SLOWDOWN]) { - return; - } - if (!buffs[BUFF_DEFFENCE]) { - buffs[BUFF_SLOWDOWN] += duration; - } - std::shared_ptr effect; - if (buffs[BUFF_DEFFENCE]) { - effect = std::make_shared(effects[EFFECT_VANISH30]); - duration = 30; - } - for (const auto& sprite : snake->sprites()) { - if (!sprite) { - continue; - } - const Effect* effectPtr = effect ? effect.get() : nullptr; - auto animation = createAndPushAnimation( - animationsList[RENDER_LIST_EFFECT_ID], &textures[RES_SOLIDFX], effectPtr, - LoopType::Lifespan, 40, sprite->x(), sprite->y(), SDL_FLIP_NONE, 0, - At::BottomCenter); - animation->setLifeSpan(duration); - animation->setScaled(false); - if (buffs[BUFF_DEFFENCE]) { - continue; - } - bindAnimationToSprite(animation, sprite, true); - } -} - -void BuffManager::shieldSnake(const std::shared_ptr& snake, int duration) { - if (!snake) { - return; - } - auto& buffs = snake->buffs(); - if (buffs[BUFF_DEFFENCE]) { - return; - } - buffs[BUFF_DEFFENCE] += duration; - for (const auto& sprite : snake->sprites()) { - if (sprite) { - auto animation = createAndPushAnimation( - animationsList[RENDER_LIST_EFFECT_ID], &textures[RES_HOLY_SHIELD], nullptr, - LoopType::Lifespan, 40, sprite->x(), sprite->y(), SDL_FLIP_NONE, 0, - At::BottomCenter); - bindAnimationToSprite(animation, sprite, true); - animation->setLifeSpan(duration); - } - } -} - -void BuffManager::attackUpSnake(const std::shared_ptr& snake, int duration) { - if (!snake) { - return; - } - auto& buffs = snake->buffs(); - if (buffs[BUFF_ATTACK]) { - return; - } - buffs[BUFF_ATTACK] += duration; - for (const auto& sprite : snake->sprites()) { - if (sprite) { - auto animation = createAndPushAnimation( - animationsList[RENDER_LIST_EFFECT_ID], &textures[RES_ATTACK_UP], nullptr, - LoopType::Lifespan, SPRITE_ANIMATION_DURATION, sprite->x(), sprite->y(), - SDL_FLIP_NONE, 0, At::BottomCenter); - bindAnimationToSprite(animation, sprite, true); - animation->setLifeSpan(duration); - } - } -} - -void BuffManager::updateBuffDurations(EntityManager& entityManager) { - const int count = entityManager.snakeCount(); - for (int i = 0; i < count; i++) { - const auto& snake = entityManager.getSnake(i); - if (!snake) { - continue; - } - auto& buffs = snake->buffs(); - for (int j = BUFF_BEGIN; j < BUFF_END; j++) { - if (buffs[j] > 0) { - buffs[j]--; - } - } - } -} - -namespace { -class BuffEffectStrategy { - public: - virtual ~BuffEffectStrategy() = default; - virtual void apply(const std::shared_ptr& source, - const std::shared_ptr& target, - int duration, - BuffManager& manager) const = 0; -}; - -class FrozenBuffStrategy final : public BuffEffectStrategy { - public: - void apply(const std::shared_ptr&, - const std::shared_ptr& target, - int duration, - BuffManager& manager) const override { - manager.freezeSnake(target.get(), duration); - } -}; - -class SlowdownBuffStrategy final : public BuffEffectStrategy { - public: - void apply(const std::shared_ptr&, - const std::shared_ptr& target, - int duration, - BuffManager& manager) const override { - manager.slowDownSnake(target.get(), duration); - } -}; - -class DefenseBuffStrategy final : public BuffEffectStrategy { - public: - void apply(const std::shared_ptr& source, - const std::shared_ptr&, - int duration, - BuffManager& manager) const override { - if (source) { - manager.shieldSnake(source, duration); - } - } -}; - -class AttackBuffStrategy final : public BuffEffectStrategy { - public: - void apply(const std::shared_ptr& source, - const std::shared_ptr&, - int duration, - BuffManager& manager) const override { - if (source) { - manager.attackUpSnake(source, duration); - } - } -}; - -const std::array, BUFF_END>& - getBuffStrategies() { - static const std::array, BUFF_END> - kStrategies = {std::make_unique(), - std::make_unique(), - std::make_unique(), - std::make_unique()}; - return kStrategies; -} -} // namespace - -void BuffManager::invokeWeaponBuff(const std::shared_ptr& src, - const Weapon& weapon, - const std::shared_ptr& dest, - int) { - if (!dest) { - return; - } - double random = 0.0; - const auto& effects = weapon.effects(); - const auto& strategies = getBuffStrategies(); - for (int i = BUFF_BEGIN; i < BUFF_END; i++) { - random = randDouble(); - if (src && src->team() == GAME_MONSTERS_TEAM) { - random *= GAME_MONSTERS_WEAPON_BUFF_ADJUST; - } - const auto& strategy = strategies[i]; - if (strategy && random < effects[i].chance) { - strategy->apply(src, dest, effects[i].duration, *this); - } - } -} +// These have been moved to src/systems/buff_manager.cpp // ============================================================================ // GameContext Implementation @@ -709,63 +407,75 @@ void BuffManager::invokeWeaponBuff(const std::shared_ptr& src, void GameContext::reset() { entityManager.clear(); itemManager.clearItems(); - hasEnemy.fill({}); - gameLevel = 0; - stage = 0; - status = 0; - termCount = 0; - willTerm = false; + ::hasEnemy.fill({}); + ::gameLevel = 0; + ::stage = 0; + ::status = 0; + ::termCount = 0; + ::willTerm = false; } void GameContext::setLevel(int level) { - gameLevel = level; - spritesSetting = 25; - bossSetting = 2; - herosSetting = 8; - flasksSetting = 6; - gameLucky = 1; - dropoutYellowFlasks = 0.3; - dropoutWeapons = 0.7; - trapRate = 0.005 * (level + 1); - monstersHpAdjust = 1 + level * 0.8 + stage * level * 0.1; - monstersGenFactor = 1 + level * 0.5 + stage * level * 0.05; - monstersWeaponBuffAdjust = 1 + level * 0.8 + stage * level * 0.02; - AI_LOCK_LIMIT = MAX(1, 7 - level * 2 - stage / 2); - winNum = 10 + level * 5 + stage * 3; - + ::gameLevel = level; + ::spritesSetting = 25; + ::bossSetting = 2; + ::herosSetting = 8; + ::flasksSetting = 6; + ::GAME_LUCKY = 1; + ::GAME_DROPOUT_YELLOW_FLASKS = 0.3; + ::GAME_DROPOUT_WEAPONS = 0.7; + ::GAME_TRAP_RATE = 0.005 * (level + 1); + ::GAME_MONSTERS_HP_ADJUST = 1 + level * 0.8 + ::stage * level * 0.1; + ::GAME_MONSTERS_GEN_FACTOR = 1 + level * 0.5 + ::stage * level * 0.05; + ::GAME_MONSTERS_WEAPON_BUFF_ADJUST = 1 + level * 0.8 + ::stage * level * 0.02; + AI_LOCK_LIMIT = MAX(1, 7 - level * 2 - ::stage / 2); + ::GAME_WIN_NUM = 10 + level * 5 + ::stage * 3; + if (level == 1) { - dropoutWeapons = 0.98; - herosSetting = 5; - flasksSetting = 4; - spritesSetting = 28; - bossSetting = 3; + ::GAME_DROPOUT_WEAPONS = 0.98; + ::herosSetting = 5; + ::flasksSetting = 4; + ::spritesSetting = 28; + ::bossSetting = 3; } else if (level == 2) { - dropoutWeapons = 0.98; - dropoutYellowFlasks = 0.3; - spritesSetting = 28; - herosSetting = 5; - flasksSetting = 3; - bossSetting = 5; + ::GAME_DROPOUT_WEAPONS = 0.98; + ::GAME_DROPOUT_YELLOW_FLASKS = 0.3; + ::spritesSetting = 28; + ::herosSetting = 5; + ::flasksSetting = 3; + ::bossSetting = 5; } - spritesSetting += stage / 2 * (level + 1); - bossSetting += stage / 3; + ::spritesSetting += ::stage / 2 * (level + 1); + ::bossSetting += ::stage / 3; } void GameContext::initEnemies() { - for (auto& row : hasEnemy) { + for (auto& row : ::hasEnemy) { row.fill(false); } for (int i = -2; i <= 2; i++) { for (int j = -2; j <= 2; j++) { - hasEnemy[n / 2 + i][m / 2 + j] = true; + ::hasEnemy[n / 2 + i][m / 2 + j] = true; } } } -// Global accessor -GameContext& getGameContext() { - return g_gameContext; -} +// Global accessor - defined earlier in the file, just declare extern here +// GameContext& getGameContext() is defined at line 179 + +// GameContext convenience accessor implementations +int GameContext::gameLevel() const { return ::gameLevel; } +int& GameContext::gameLevel() { return ::gameLevel; } +int GameContext::stage() const { return ::stage; } +int& GameContext::stage() { return ::stage; } +int GameContext::status() const { return ::status; } +int& GameContext::status() { return ::status; } +bool GameContext::willTerm() const { return ::willTerm; } +bool& GameContext::willTerm() { return ::willTerm; } +int GameContext::termCount() const { return ::termCount; } +int& GameContext::termCount() { return ::termCount; } +int GameContext::winNum() const { return ::GAME_WIN_NUM; } +int& GameContext::winNum() { return ::GAME_WIN_NUM; } void initializeEventObservers() { if (!g_audioObserver) { @@ -816,9 +526,9 @@ void GameLoopManager::setTerm(GameContext& ctx, int status) { getGameContext().eventBus.emit( {GameEventType::SoundRequested, -1, ItemType::None, AUDIO_LOSE}); } - ctx.status = status; - ctx.willTerm = true; - ctx.termCount = RENDER_TERM_COUNT; + ctx.status() = status; + ctx.willTerm() = true; + ctx.termCount() = RENDER_TERM_COUNT; } bool GameLoopManager::isWin(const GameContext& ctx) const { @@ -827,7 +537,7 @@ bool GameLoopManager::isWin(const GameContext& ctx) const { return false; } const auto& snake = ctx.entityManager.getSnake(0); - return snake && snake->num() >= ctx.winNum; + return snake && snake->num() >= ctx.winNum(); } bool GameLoopManager::handleLocalKeypress(GameContext& ctx) { @@ -999,8 +709,8 @@ void GameLoopManager::cleanupDeadEntities(GameContext& ctx) { } void GameLoopManager::checkWinCondition(GameContext& ctx) { - if (ctx.willTerm) { - ctx.termCount--; + if (ctx.willTerm()) { + ctx.termCount()--; return; } @@ -1079,14 +789,14 @@ int GameLoopManager::run(GameContext& ctx) { checkWinCondition(ctx); - if (ctx.willTerm) { - ctx.termCount--; - if (!ctx.termCount) { + if (ctx.willTerm()) { + ctx.termCount()--; + if (!ctx.termCount()) { break; } } } - return ctx.status; + return ctx.status(); } // Legacy function implementations (delegating to managers where appropriate) @@ -1157,39 +867,40 @@ void appendSpriteToSnake(const std::shared_ptr& snake, int spriteId, snake->incrementNum(); snake->score()->addGot(1); - std::shared_ptr snakeHead = nullptr; + std::shared_ptr lastSprite = nullptr; if (!snake->sprites().empty()) { - snakeHead = snake->sprites().front(); - x = snakeHead->x(); - y = snakeHead->y(); + lastSprite = snake->sprites().back(); + x = lastSprite->x(); + y = lastSprite->y(); const int delta = - (snakeHead->animation()->origin()->width() * SCALE_FACTOR + + (lastSprite->animation()->origin()->width() * SCALE_FACTOR + commonSprites[spriteId].animation()->origin()->width() * SCALE_FACTOR) / 2; - if (snakeHead->direction() == Direction::Left) { - x -= delta; - } else if (snakeHead->direction() == Direction::Right) { + if (lastSprite->direction() == Direction::Left) { x += delta; - } else if (snakeHead->direction() == Direction::Up) { - y -= delta; - } else { + } else if (lastSprite->direction() == Direction::Right) { + x -= delta; + } else if (lastSprite->direction() == Direction::Up) { y += delta; + } else { + y -= delta; } + direction = lastSprite->direction(); } const auto sprite = std::make_shared(commonSprites[spriteId], x, y); sprite->setDirection(direction); if (direction == Direction::Left) { sprite->setFace(Direction::Left); } - if (snakeHead) { - sprite->setDirection(snakeHead->direction()); - sprite->setFace(snakeHead->face()); - if (snakeHead->animation()) { + if (lastSprite) { + sprite->setDirection(lastSprite->direction()); + sprite->setFace(lastSprite->face()); + if (lastSprite->animation()) { sprite->animation()->setCurrentFrame( - snakeHead->animation()->currentFrame()); + lastSprite->animation()->currentFrame()); } } - snake->sprites().push_front(sprite); + snake->sprites().push_back(sprite); if (sprite->animation()) { pushAnimationToRender(RENDER_LIST_SPRITE_ID, sprite->animation()); @@ -1951,16 +1662,29 @@ void moveSnake(const std::shared_ptr& snake) { if (snake->buffs()[BUFF_SLOWDOWN]) { step = MAX(step / 2, 1); } - for (const auto& sprite : snake->sprites()) { + + auto& snakeSprites = snake->sprites(); + for (auto it = snakeSprites.begin(); it != snakeSprites.end(); ++it) { + const auto& sprite = *it; if (!sprite) { continue; } + auto& buffer = sprite->positionBuffer(); for (int move = 0; move < step; ++move) { if (buffer.size() > 0) { const PositionBufferSlot& slot = buffer.at(0); if (sprite->x() == slot.x && sprite->y() == slot.y) { - sprite->setDirection(slot.direction); + PositionBufferSlot consumed = buffer.pop(); + auto nextIt = std::next(it); + if (nextIt != snakeSprites.end()) { + sprite->enqueueDirectionChange(consumed.direction, *nextIt); + } else { + sprite->setDirection(consumed.direction); + if (consumed.direction == Direction::Left || consumed.direction == Direction::Right) { + sprite->setFace(consumed.direction); + } + } } } moveSprite(sprite, 1); diff --git a/src/game.h b/src/game.h index 098ee60..67c22f7 100644 --- a/src/game.h +++ b/src/game.h @@ -7,6 +7,7 @@ #include "types.h" #include "map.h" #include "render.h" +#include "core/game_state.h" #include #include @@ -64,10 +65,6 @@ class EventBus { // Forward declarations class AIBehavior; -class EntityManager; -class CollisionManager; -class ItemManager; -class BuffManager; class GameLoopManager; class MapManager; class WeaponBehavior; @@ -84,124 +81,17 @@ extern Effect effects[]; extern Weapon weapons[]; extern Sprite commonSprites[]; extern std::array animationsList; -extern bool hasMap[MAP_SIZE][MAP_SIZE]; -// ============================================================================ -// EntityManager - Manages all game entities (snakes, bullets) -// ============================================================================ -class EntityManager { - public: - EntityManager() = default; - EntityManager(const EntityManager&) = delete; - EntityManager& operator=(const EntityManager&) = delete; - EntityManager(EntityManager&&) = default; - EntityManager& operator=(EntityManager&&) = default; - ~EntityManager() = default; - - // Snake management - void addSnake(const std::shared_ptr& snake); - void removeSnake(int index); - std::shared_ptr getSnake(int index) const; - int snakeCount() const; - int playerCount() const; - void setPlayerCount(int count); - void incrementSpriteCount(); - int spriteCount() const; - - // Bullet management - void addBullet(const std::shared_ptr& bullet); - void removeBullet(const std::shared_ptr& bullet); - BulletList& bullets(); - const BulletList& bullets() const; - - // Entity access - std::array, SPRITES_MAX_NUM>& snakes(); - const std::array, SPRITES_MAX_NUM>& snakes() const; - - void clear(); - - private: - std::array, SPRITES_MAX_NUM> snakes_{}; - BulletList bullets_{}; - int spritesCount_ = 0; - int playersCount_ = 0; -}; - -// ============================================================================ -// CollisionManager - Handles all collision detection -// ============================================================================ -class CollisionManager { - public: - CollisionManager() = default; - CollisionManager(const CollisionManager&) = delete; - CollisionManager& operator=(const CollisionManager&) = delete; - CollisionManager(CollisionManager&&) = default; - CollisionManager& operator=(CollisionManager&&) = default; - ~CollisionManager() = default; - - bool checkCrush(const std::shared_ptr& sprite, bool loose, - bool useAnimationBox, EntityManager& entityManager); - bool isPlayer(const std::shared_ptr& snake, EntityManager& entityManager) const; +#include "entities/entity_manager.h" +#include "systems/collision_manager.h" +#include "systems/item_manager.h" +#include "systems/buff_manager.h" - private: -}; - -// ============================================================================ -// ItemManager - Handles item generation and management -// ============================================================================ -class ItemManager { - public: - ItemManager() = default; - ItemManager(const ItemManager&) = delete; - ItemManager& operator=(const ItemManager&) = delete; - ItemManager(ItemManager&&) = default; - ItemManager& operator=(ItemManager&&) = default; - ~ItemManager() = default; - - void initItems(int heroCount, int flaskCount); - void clearItems(); - void generateHeroItem(int x, int y); - void generateItem(int x, int y, ItemType type); - void dropItemNearSprite(const Sprite* sprite, ItemType itemType); - bool checkItemPickup(const std::shared_ptr& snake, - EntityManager& entityManager); - - std::array, MAP_SIZE>& itemMap(); - const std::array, MAP_SIZE>& itemMap() const; - - int heroCount() const; - void setHeroCount(int count); - int flaskCount() const; - void setFlaskCount(int count); - - private: - std::array, MAP_SIZE> itemMap_{}; - int herosCount_ = 0; - int flasksCount_ = 0; -}; - -// ============================================================================ -// BuffManager - Handles buff effects on snakes -// ============================================================================ -class BuffManager { - public: - BuffManager() = default; - BuffManager(const BuffManager&) = delete; - BuffManager& operator=(const BuffManager&) = delete; - BuffManager(BuffManager&&) = default; - BuffManager& operator=(BuffManager&&) = default; - ~BuffManager() = default; - - void freezeSnake(Snake* snake, int duration); - void slowDownSnake(Snake* snake, int duration); - void shieldSnake(const std::shared_ptr& snake, int duration); - void attackUpSnake(const std::shared_ptr& snake, int duration); - void updateBuffDurations(EntityManager& entityManager); - void invokeWeaponBuff(const std::shared_ptr& src, const Weapon& weapon, - const std::shared_ptr& dest, int damage); - - private: -}; +// Typedefs for backward compatibility +using EntityManager = snake::entities::EntityManager; +using CollisionManager = snake::systems::CollisionManager; +using ItemManager = snake::systems::ItemManager; +using BuffManager = snake::systems::BuffManager; // ============================================================================ // GameLoopManager - Handles the main game loop logic @@ -244,25 +134,42 @@ class GameLoopManager { // GameContext - Central context holding all game state // ============================================================================ struct GameContext { - EntityManager entityManager; - CollisionManager collisionManager; - ItemManager itemManager; - BuffManager buffManager; + // Core state container - all global state lives here + snake::core::GameState gameState; + + // Managers + snake::entities::EntityManager entityManager; + snake::systems::CollisionManager collisionManager; + snake::systems::ItemManager itemManager; + snake::systems::BuffManager buffManager; std::shared_ptr aiBehavior; EventBus eventBus; - // Game state - int gameLevel = 0; - int stage = 0; - int status = 0; - int termCount = 0; - bool willTerm = false; + // Map state + std::array, MAP_SIZE> map{}; + std::array, MAP_SIZE> hasMap{}; + std::array, MAP_SIZE> hasEnemy{}; + + // Game configuration (convenience accessors for legacy globals) + int gameLevel() const; + int& gameLevel(); + int stage() const; + int& stage(); + int status() const; + int& status(); + bool willTerm() const; + bool& willTerm(); + int termCount() const; + int& termCount(); + int winNum() const; + int& winNum(); - // Level settings - int spritesSetting = 25; - int bossSetting = 2; int herosSetting = 8; int flasksSetting = 6; + int spritesSetting = 25; + int bossSetting = 2; + int spikeDamage = 1; + double gameLucky = 1.0; double dropoutYellowFlasks = 0.3; double dropoutWeapons = 0.7; @@ -270,10 +177,6 @@ struct GameContext { double monstersHpAdjust = 1.0; double monstersWeaponBuffAdjust = 1.0; double monstersGenFactor = 1.0; - int winNum = 10; - - // Enemy position tracking - std::array, MAP_SIZE> hasEnemy{}; GameContext() = default; GameContext(const GameContext&) = delete; @@ -282,6 +185,10 @@ struct GameContext { GameContext& operator=(GameContext&&) = default; ~GameContext() = default; + // Access the centralized game state + snake::core::GameState& state() { return gameState; } + const snake::core::GameState& state() const { return gameState; } + void reset(); void setLevel(int level); void initEnemies(); diff --git a/src/sprite.cpp b/src/sprite.cpp index 32c9e06..f007a52 100644 --- a/src/sprite.cpp +++ b/src/sprite.cpp @@ -13,6 +13,16 @@ void PositionBuffer::push(PositionBufferSlot slot) { buffer_[size_++] = slot; } +PositionBufferSlot PositionBuffer::pop() { + assert(size_ > 0); + PositionBufferSlot result = buffer_[0]; + for (int i = 0; i < size_ - 1; ++i) { + buffer_[i] = buffer_[i + 1]; + } + size_--; + return result; +} + int PositionBuffer::size() const { return size_; } const PositionBufferSlot& PositionBuffer::at(int index) const { @@ -76,10 +86,17 @@ const PositionBuffer& Sprite::positionBuffer() const { return positionBuffer_; } void Sprite::enqueueDirectionChange(Direction newDirection, const std::shared_ptr& next) { - transform_.enqueueDirectionChange(newDirection, positionBuffer_); + if (transform_.direction() == newDirection) { + return; + } + + PositionBufferSlot slot{transform_.x(), transform_.y(), newDirection}; + transform_.setDirection(newDirection); + if (newDirection == Direction::Left || newDirection == Direction::Right) { + transform_.setFace(newDirection); + } + if (next) { - PositionBufferSlot slot{transform_.x(), transform_.y(), - transform_.direction()}; next->positionBuffer().push(slot); } } diff --git a/src/sprite.h b/src/sprite.h index be72bba..1c20971 100644 --- a/src/sprite.h +++ b/src/sprite.h @@ -22,6 +22,7 @@ class PositionBuffer { PositionBuffer(); void push(PositionBufferSlot slot); + PositionBufferSlot pop(); int size() const; const PositionBufferSlot& at(int index) const;