diff --git a/CMakeLists.txt b/CMakeLists.txt index ba893ea..6974ae9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,8 @@ add_executable(${MAIN_EXECUTABLE_NAME} src/Pickup.cpp src/Pickup.h src/ResourceHolder.h + src/EnemyStrategies.cpp + src/EnemyStrategies.h ) # NOTE: Add all defined targets (e.g. executables, libraries, etc. ) diff --git a/README.md b/README.md index 8f24a90..292a371 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ The current objective of the game is to accumulate the highest possible Bounty b ## Tema 3 #### Cerințe -- [ ] 2 șabloane de proiectare (design patterns) -- [ ] o clasă șablon cu sens; minim **2 instanțieri** - - [ ] preferabil și o funcție șablon (template) cu sens; minim 2 instanțieri +- [x] 2 șabloane de proiectare (design patterns) +- [x] o clasă șablon cu sens; minim **2 instanțieri** + - [x] preferabil și o funcție șablon (template) cu sens; minim 2 instanțieri - [x] minim 85% din codul propriu să fie C++ - [ ] tag de `git` pe commit cu **toate bifele**: de exemplu `v0.3` sau `v1.0` diff --git a/src/Enemy.cpp b/src/Enemy.cpp index 16d6b70..635bd90 100644 --- a/src/Enemy.cpp +++ b/src/Enemy.cpp @@ -4,6 +4,7 @@ #include "GameExceptions.h" #include "Game.h" #include "Map.h" +#include "Utils.h" #include #include @@ -17,15 +18,8 @@ Enemy::Enemy(const std::string& n, const int damage_, const int bountyScore_, co bountyScore{bountyScore_}, target{target_}, isMoving{false}, - canDealDamage{false}, - state{EnemyState::Patrolling}, + strategy{std::make_unique()}, detectionRange{400.f}, - attackCooldown{0.8f}, - currentAttackTimer{attackCooldown}, - aggroTimer{0.f}, - patrolTimer{0.f}, - patrolDuration{3.f}, - patrolDirection{1.f}, currentFrame{0}, animationTimer{0.f}, frameDuration{0.1f}, @@ -66,68 +60,74 @@ Enemy::Enemy(const std::string& n, const int damage_, const int bountyScore_, co exclamationSprite.setScale({0.f, 0.f}); } -void Enemy::updateAI(const float deltaTime) { - updateAttack(deltaTime); - if (aggroTimer > 0.0f) aggroTimer -= deltaTime; - - if (!target || !target->isAlive()) { - state = EnemyState::Patrolling; - } - - const float distToPlayer = getDistanceToTarget(); - const bool isTouching = isTouchingTarget(); - - switch (state) { - case EnemyState::Patrolling: - processPatrolState(deltaTime, distToPlayer); - break; - - case EnemyState::Chasing: - processChaseState(deltaTime, distToPlayer, isTouching); - break; - - case EnemyState::Attacking: - processAttackState(isTouching); - break; +Enemy::Enemy(const Enemy &other) + : Entity(other), + damage(other.damage), + bountyScore(other.bountyScore), + target(other.target), + isMoving(other.isMoving), + detectionRange(other.detectionRange), + frameSize(other.frameSize), + currentFrame(other.currentFrame), + animationTimer(other.animationTimer), + frameDuration(other.frameDuration), + animationFrameCount(other.animationFrameCount), + alertTexture(other.alertTexture), + exclamationSprite(other.exclamationSprite), + alertFrameSize(other.alertFrameSize), + alertAnimTimer(other.alertAnimTimer), + alertActive(other.alertActive), + activeSounds(other.activeSounds), + hitSound(other.hitSound), + deathSound(other.deathSound) { + + if (other.strategy) { + strategy = other.strategy->clone(); + } else { + strategy = std::make_unique(); } + + activeEnemyCount++; } -void Enemy::processPatrolState(const float deltaTime, const float distToPlayer) { - if (distToPlayer < detectionRange) { - state = EnemyState::Chasing; - return; - } - updatePatrol(deltaTime); +void swap(Enemy &lhs, Enemy &rhs) noexcept { + using std::swap; + swap(static_cast(lhs), static_cast(rhs)); + swap(lhs.damage, rhs.damage); + swap(lhs.bountyScore, rhs.bountyScore); + swap(lhs.target, rhs.target); + swap(lhs.isMoving, rhs.isMoving); + swap(lhs.strategy, rhs.strategy); + swap(lhs.detectionRange, rhs.detectionRange); + swap(lhs.frameSize, rhs.frameSize); + swap(lhs.currentFrame, rhs.currentFrame); + swap(lhs.animationTimer, rhs.animationTimer); + swap(lhs.frameDuration, rhs.frameDuration); + swap(lhs.animationFrameCount, rhs.animationFrameCount); + swap(lhs.alertTexture, rhs.alertTexture); + swap(lhs.exclamationSprite, rhs.exclamationSprite); + swap(lhs.alertFrameSize, rhs.alertFrameSize); + swap(lhs.alertAnimTimer, rhs.alertAnimTimer); + swap(lhs.alertActive, rhs.alertActive); + swap(lhs.activeSounds, rhs.activeSounds); + swap(lhs.hitSound, rhs.hitSound); + swap(lhs.deathSound, rhs.deathSound); } -void Enemy::processChaseState(const float deltaTime, const float distToPlayer, const bool isTouching) { - if (distToPlayer > detectionRange * 1.5f && aggroTimer <= 0.0f) { - state = EnemyState::Patrolling; - return; - } - - if (isTouching) { - startAttackSequence(); - return; +void Enemy::updateAI(const float deltaTime) { + if (!target || !target->isAlive()) { + if (!strategy || strategy->isChasing() || strategy->isAttacking()) { + setStrategy(std::make_unique()); + } } - - updateChase(deltaTime); -} - -void Enemy::processAttackState(const bool isTouching) { - if (!isTouching) { - state = EnemyState::Chasing; + + if (strategy) { + strategy->update(*this, deltaTime); } } -void Enemy::startAttackSequence() { - if (state != EnemyState::Attacking) { - if (currentAttackTimer <= 0.f) { - currentAttackTimer = 0.35f; - canDealDamage = false; - } - state = EnemyState::Attacking; - } +void Enemy::setStrategy(std::unique_ptr newStrategy) { + strategy = std::move(newStrategy); } float Enemy::getDistanceToTarget() const { @@ -140,44 +140,19 @@ bool Enemy::isTouchingTarget() const { return this->getBounds().findIntersection(target->getBounds()).has_value(); } -void Enemy::updatePatrol(const float deltaTime) { - patrolTimer += deltaTime; - isMoving = true; - - if (patrolTimer >= patrolDuration) { - patrolTimer = 0.0f; - patrolDirection *= -1.0f; - } - - posX += speed * 0.5f * patrolDirection * deltaTime; - - if (patrolDirection > 0) sprite.setScale({1.f, 1.f}); - else sprite.setScale({-1.f, 1.f}); +sf::Vector2f Enemy::getTargetPos() const { + if (target) return target->getPos(); + return {posX, posY}; } -void Enemy::updateChase(const float deltaTime) { - if (const float diffX = target->getPos().x - posX; std::abs(diffX) > 5.0f) { - isMoving = true; - - if (diffX < 0) { - posX -= speed * deltaTime; - sprite.setScale({-1.f, 1.f}); - } else { - posX += speed * deltaTime; - sprite.setScale({1.f, 1.f}); +void Enemy::tryDealDamageToTarget(const int dmgAmount) const { + if (target && isTouchingTarget()) { + if (auto* playerPtr = dynamic_cast(target)) { + playerPtr->tryHit(dmgAmount); } } } -void Enemy::updateAttack(const float deltaTime) { - if (currentAttackTimer > 0.0f) { - currentAttackTimer -= deltaTime; - canDealDamage = false; - } - else { - canDealDamage = true; - } -} void Enemy::grantReward(Player& player) const { player.processKill(this->bountyScore); @@ -196,7 +171,7 @@ void Enemy::onDeath(Map& map) { map.onEnemyKilled(); - if (Game::generateRandomInt(1, 100) <= 25) { + if (Utils::getRandom(1, 100) <= 25) { try { constexpr float groundOffsetY = 40; auto drop = PickupFactory::create( @@ -216,20 +191,21 @@ void Enemy::onDeath(Map& map) { } } -void Enemy::onCollision(Player &player) { - if (this->canDealDamage && this->state == EnemyState::Attacking) { - player.tryHit(this->damage); - this->resetAttackTimer(); - } -} +// void Enemy::onCollision(Player &player) { +// if (this->canDealDamage && this->state == EnemyState::Attacking) { +// player.tryHit(this->damage); +// this->resetAttackTimer(); +// } +// } +// i can use this later on if implementing a knockback sf::FloatRect Enemy::doGetBounds() const{ return hitbox; } void Enemy::takeDamage(const int damageAmount) { - state = EnemyState::Chasing; - aggroTimer = 5.0f; + setStrategy(std::make_unique(5.0f)); + const bool wasAlive = this->isAlive(); health -= damageAmount; checkDeath(); @@ -260,10 +236,10 @@ void Enemy::doBehavior(const float deltaTime, const Map& map) { void Enemy::updatePhysics(const float deltaTime, const Map &map) { updateHitbox(); if (map.isWall(hitbox, true)) { - if (state == EnemyState::Patrolling) { - patrolDirection *= -1.f; - patrolTimer = 0.0f; + if (strategy) { + strategy->onWallCollision(*this); } + const float dir = sprite.getScale().x > 0.f ? 1.f : -1.f; posX -= speed * dir * deltaTime; updateHitbox(); @@ -281,7 +257,10 @@ void Enemy::updatePhysics(const float deltaTime, const Map &map) { } void Enemy::updateAnimation(const float deltaTime) { - if (state == EnemyState::Attacking) { + const bool attacking = strategy && strategy->isAttacking(); + const bool chasing = strategy && strategy->isChasing(); + + if (attacking) { currentFrame = animationFrameCount - 1; animationTimer = 0.0f; const int rectLeft = currentFrame * frameSize.x; @@ -310,13 +289,13 @@ void Enemy::updateAnimation(const float deltaTime) { constexpr float padding = 5.f; float yCorrection = 0.f; - if (state == EnemyState::Attacking) { + if (attacking) { yCorrection = -5.f; } exclamationSprite.setPosition({posX, headTopY - padding - halfAlertHeight + yCorrection}); - bool shouldShowAlert = (state == EnemyState::Chasing || state == EnemyState::Attacking); + bool shouldShowAlert = (chasing || attacking); if (shouldShowAlert) { if (!alertActive) { @@ -333,11 +312,11 @@ void Enemy::updateAnimation(const float deltaTime) { exclamationSprite.setScale({scale, scale}); - if (state == EnemyState::Chasing) { + if (chasing) { exclamationSprite.setTextureRect(sf::IntRect({0, 0}, {alertFrameSize.x, alertFrameSize.y})); } - else if (state == EnemyState::Attacking) { + else if (attacking) { exclamationSprite.setTextureRect(sf::IntRect({0, alertFrameSize.y}, {alertFrameSize.x, alertFrameSize.y})); } @@ -349,16 +328,11 @@ void Enemy::updateAnimation(const float deltaTime) { } } -void Enemy::resetAttackTimer() { - canDealDamage = false; - currentAttackTimer = attackCooldown; -} - void Enemy::draw(sf::RenderWindow& window) const { if (!alive) return; window.draw(sprite); - if (state == EnemyState::Chasing || state == EnemyState::Attacking) { + if (strategy && (strategy->isChasing() || strategy->isAttacking())) { window.draw(exclamationSprite); } } diff --git a/src/Enemy.h b/src/Enemy.h index 4f98734..07587a7 100644 --- a/src/Enemy.h +++ b/src/Enemy.h @@ -4,34 +4,23 @@ #include "Weapon.h" #include "Entity.h" #include "EnemyFactory.h" +#include "EnemyStrategies.h" class Map; class Pickup; class Player; -enum class EnemyState { - Patrolling, - Chasing, - Attacking -}; - class Enemy : public Entity{ int damage; int bountyScore; static int activeEnemyCount; Entity* target; bool isMoving; - bool canDealDamage; - EnemyState state; - float detectionRange; - float attackCooldown; - float currentAttackTimer; - float aggroTimer; - - float patrolTimer; - float patrolDuration; - float patrolDirection; + + std::unique_ptr strategy; + float detectionRange; + sf::Vector2i frameSize; int currentFrame; float animationTimer; @@ -54,19 +43,10 @@ class Enemy : public Entity{ void takeDamage(int damageAmount) override; void doBehavior(float deltaTime, const Map& map) override; - void startAttackSequence(); - float getDistanceToTarget() const; - bool isTouchingTarget() const; - void processPatrolState(float deltaTime, float distToPlayer); - void processChaseState(float deltaTime, float distToPlayer, bool isTouching); - void processAttackState(bool isTouching); + void updateAI(float deltaTime); - void updatePatrol(float deltaTime); - void updateChase(float deltaTime); - void updateAttack(float deltaTime); void updatePhysics(float deltaTime, const Map& map); void updateAnimation(float deltaTime); - void resetAttackTimer(); void draw(sf::RenderWindow& window) const override; friend class EnemyFactory; @@ -90,67 +70,9 @@ class Enemy : public Entity{ activeEnemyCount--; } - Enemy(const Enemy &other) - : Entity(other), - damage(other.damage), - bountyScore(other.bountyScore), - target(other.target), - isMoving(other.isMoving), - canDealDamage(other.canDealDamage), - state(other.state), - detectionRange(other.detectionRange), - attackCooldown(other.attackCooldown), - currentAttackTimer(other.currentAttackTimer), - aggroTimer(other.aggroTimer), - patrolTimer(other.patrolTimer), - patrolDuration(other.patrolDuration), - patrolDirection(other.patrolDirection), - frameSize(other.frameSize), - currentFrame(other.currentFrame), - animationTimer(other.animationTimer), - frameDuration(other.frameDuration), - animationFrameCount(other.animationFrameCount), - alertTexture(other.alertTexture), - exclamationSprite(other.exclamationSprite), - alertFrameSize(other.alertFrameSize), - alertAnimTimer(other.alertAnimTimer), - alertActive(other.alertActive), - activeSounds(other.activeSounds), - hitSound(other.hitSound), - deathSound(other.deathSound) { - activeEnemyCount++; - } + Enemy(const Enemy &other); - friend void swap(Enemy &lhs, Enemy &rhs) noexcept { - using std::swap; - swap(static_cast(lhs), static_cast(rhs)); - swap(lhs.damage, rhs.damage); - swap(lhs.bountyScore, rhs.bountyScore); - swap(lhs.target, rhs.target); - swap(lhs.isMoving, rhs.isMoving); - swap(lhs.canDealDamage, rhs.canDealDamage); - swap(lhs.state, rhs.state); - swap(lhs.detectionRange, rhs.detectionRange); - swap(lhs.attackCooldown, rhs.attackCooldown); - swap(lhs.currentAttackTimer, rhs.currentAttackTimer); - swap(lhs.aggroTimer, rhs.aggroTimer); - swap(lhs.patrolTimer, rhs.patrolTimer); - swap(lhs.patrolDuration, rhs.patrolDuration); - swap(lhs.patrolDirection, rhs.patrolDirection); - swap(lhs.frameSize, rhs.frameSize); - swap(lhs.currentFrame, rhs.currentFrame); - swap(lhs.animationTimer, rhs.animationTimer); - swap(lhs.frameDuration, rhs.frameDuration); - swap(lhs.animationFrameCount, rhs.animationFrameCount); - swap(lhs.alertTexture, rhs.alertTexture); - swap(lhs.exclamationSprite, rhs.exclamationSprite); - swap(lhs.alertFrameSize, rhs.alertFrameSize); - swap(lhs.alertAnimTimer, rhs.alertAnimTimer); - swap(lhs.alertActive, rhs.alertActive); - swap(lhs.activeSounds, rhs.activeSounds); - swap(lhs.hitSound, rhs.hitSound); - swap(lhs.deathSound, rhs.deathSound); - } + friend void swap(Enemy &lhs, Enemy &rhs) noexcept; Enemy & operator=(Enemy other) { Entity::operator=(other); @@ -165,8 +87,22 @@ class Enemy : public Entity{ static int getActiveEnemyCount(); void grantReward(Player& player) const; void onDeath(Map &map) override; - void onCollision(Player &player) override; + // void onCollision(Player &player) override; int getCollisionPriority() const override { return PRIORITY_HAZARD; } + + void setStrategy(std::unique_ptr newStrategy); + + float getDistanceToTarget() const; + bool isTouchingTarget() const; + float getDetectionRange() const { return detectionRange; } + sf::Vector2f getTargetPos() const; + float getSpeed() const { return speed; } + int getDamage() const { return damage; } + + void setMoving(const bool moving) { isMoving = moving; } + void setX(const float x) { posX = x; } + void setScale(float x, float y) { sprite.setScale({x, y}); } + void tryDealDamageToTarget(int dmgAmount) const; }; #endif diff --git a/src/EnemyStrategies.cpp b/src/EnemyStrategies.cpp new file mode 100644 index 0000000..7992d72 --- /dev/null +++ b/src/EnemyStrategies.cpp @@ -0,0 +1,121 @@ +#include "EnemyStrategies.h" +#include "Enemy.h" +#include "Utils.h" +#include +#include + +// +// Patrol Strategy +// +PatrolStrategy::PatrolStrategy(const float duration, const float direction) + : patrolTimer(Utils::getRandom(0.0f, duration)), patrolDuration(duration), patrolDirection(direction) {} + +void PatrolStrategy::update(Enemy& enemy, const float deltaTime) { + if (enemy.getDistanceToTarget() < enemy.getDetectionRange()) { + enemy.setStrategy(std::make_unique()); + return; + } + + patrolTimer += deltaTime; + enemy.setMoving(true); + + if (patrolTimer >= patrolDuration) { + patrolTimer = 0.0f; + patrolDirection *= -1.0f; + } + + const float currentX = enemy.getPos().x; + const float speed = enemy.getSpeed(); + + enemy.setX(currentX + speed * 0.5f * patrolDirection * deltaTime); + + if (patrolDirection > 0) enemy.setScale(1.f, 1.f); + else enemy.setScale(-1.f, 1.f); +} + +void PatrolStrategy::onWallCollision(Enemy&) { + patrolDirection *= -1.0f; + patrolTimer = 0.0f; +} + +std::unique_ptr PatrolStrategy::clone() const { + return std::make_unique(*this); +} + +// +// Chase Strategy +// +ChaseStrategy::ChaseStrategy(const float aggroDuration) : aggroTimer(aggroDuration) {} + +void ChaseStrategy::update(Enemy& enemy, const float deltaTime) { + float distToPlayer = enemy.getDistanceToTarget(); + + if (distToPlayer > enemy.getDetectionRange() * 1.5f && aggroTimer <= 0.0f) { + enemy.setStrategy(std::make_unique()); + return; + } + + if (enemy.isTouchingTarget()) { + enemy.setStrategy(std::make_unique()); + return; + } + + if (aggroTimer > 0.0f) aggroTimer -= deltaTime; + + const sf::Vector2f targetPos = enemy.getTargetPos(); + const float currentX = enemy.getPos().x; + const float diffX = targetPos.x - currentX; + const float speed = enemy.getSpeed(); + + if (std::abs(diffX) > 5.0f) { + enemy.setMoving(true); + + if (diffX < 0) { + enemy.setX(currentX - speed * deltaTime); + enemy.setScale(-1.f, 1.f); + } else { + enemy.setX(currentX + speed * deltaTime); + enemy.setScale(1.f, 1.f); + } + } else { + enemy.setMoving(false); + } +} + +std::unique_ptr ChaseStrategy::clone() const { + return std::make_unique(*this); +} + +// +// Attack Strategy +// +AttackStrategy::AttackStrategy(const float cooldown) + : currentAttackTimer(0.35f), attackCooldown(cooldown), canDealDamage(false) {} + +void AttackStrategy::update(Enemy& enemy, const float deltaTime) { + if (!enemy.isTouchingTarget()) { + enemy.setStrategy(std::make_unique()); + return; + } + + if (currentAttackTimer > 0.0f) { + currentAttackTimer -= deltaTime; + canDealDamage = false; + } else { + canDealDamage = true; + } + + if (canDealDamage) { + enemy.tryDealDamageToTarget(enemy.getDamage()); + resetAttack(); + } +} + +void AttackStrategy::resetAttack() { + canDealDamage = false; + currentAttackTimer = attackCooldown; +} + +std::unique_ptr AttackStrategy::clone() const { + return std::make_unique(*this); +} \ No newline at end of file diff --git a/src/EnemyStrategies.h b/src/EnemyStrategies.h new file mode 100644 index 0000000..b7f8c8a --- /dev/null +++ b/src/EnemyStrategies.h @@ -0,0 +1,56 @@ +#ifndef ENEMY_STRATEGIES_H +#define ENEMY_STRATEGIES_H + +#include + +class Enemy; + +class EnemyStrategy { +public: + virtual ~EnemyStrategy() = default; + virtual void update(Enemy& enemy, float deltaTime) = 0; + virtual std::unique_ptr clone() const = 0; + virtual void onWallCollision(Enemy&) {} + + virtual bool isAttacking() const { return false; } + virtual bool isChasing() const { return false; } +}; + +class PatrolStrategy : public EnemyStrategy { + float patrolTimer; + float patrolDuration; + float patrolDirection; + +public: + explicit PatrolStrategy(float duration = 3.0f, float direction = 1.0f); + void update(Enemy& enemy, float deltaTime) override; + void onWallCollision(Enemy& enemy) override; + std::unique_ptr clone() const override; +}; + +class ChaseStrategy : public EnemyStrategy { + float aggroTimer; + +public: + explicit ChaseStrategy(float aggroDuration = 0.0f); + void update(Enemy& enemy, float deltaTime) override; + std::unique_ptr clone() const override; + bool isChasing() const override { return true; } +}; + +class AttackStrategy : public EnemyStrategy { + float currentAttackTimer; + float attackCooldown; + bool canDealDamage; + +public: + explicit AttackStrategy(float cooldown = 0.8f); + void update(Enemy& enemy, float deltaTime) override; + std::unique_ptr clone() const override; + + // bool canAttack() const { return canDealDamage; } + void resetAttack(); + bool isAttacking() const override { return true; } +}; + +#endif // ENEMY_STRATEGIES_H \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp index 34004f1..e19b850 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -8,6 +8,7 @@ #include "Camera.h" #include "ResourceManager.h" #include "GameExceptions.h" +#include "Utils.h" #include #include @@ -525,9 +526,4 @@ void Game::renderUI(sf::RenderWindow& window) { m_uiText.setPosition({static_cast(m_width) / 2.0f, static_cast(m_height) / 2.0f}); window.draw(m_uiText); } -} - -int Game::generateRandomInt(const int min, const int max) { - static std::mt19937 gen(std::random_device{}()); - return std::uniform_int_distribution(min, max)(gen); } \ No newline at end of file diff --git a/src/Game.h b/src/Game.h index 2ecc0cc..1c9dfea 100644 --- a/src/Game.h +++ b/src/Game.h @@ -71,5 +71,4 @@ class Game { Game(); ~Game(); void run(); - static int generateRandomInt(int min, int max); }; \ No newline at end of file diff --git a/src/Map.cpp b/src/Map.cpp index cacd19e..0d46498 100644 --- a/src/Map.cpp +++ b/src/Map.cpp @@ -9,6 +9,7 @@ #include "ResourceManager.h" #include "GameExceptions.h" #include "EnemyFactory.h" +#include "Utils.h" #include #include @@ -235,7 +236,7 @@ void Map::spawnEnemies() { int enemyIndex = 0; if (enemies.size() > 1) { - enemyIndex = Game::generateRandomInt(0, static_cast(enemies.size()) - 1); + enemyIndex = Utils::getRandom(0, static_cast(enemies.size()) - 1); } const auto&[fst, snd] = enemies[enemyIndex]; std::unique_ptr newEnemy = EnemyFactory::createEnemy( @@ -300,7 +301,7 @@ void Map::spawnAdditionalEnemies(const int count) { try { int enemyIndex = 0; if (enemies.size() > 1) { - enemyIndex = Game::generateRandomInt(0, static_cast(enemies.size()) - 1); + enemyIndex = Utils::getRandom(0, static_cast(enemies.size()) - 1); } std::string enemyName = enemies[enemyIndex].first; auto [x, y] = generateRandomEnemySpawn(); @@ -362,7 +363,7 @@ std::pair Map::generateRandomEnemySpawn() const{ {-999.f, -999.f}); } - const int randomIndex = Game::generateRandomInt(0, static_cast(enemySpawns.size()) - 1); + const int randomIndex = Utils::getRandom(0, static_cast(enemySpawns.size()) - 1); auto [x, y] = enemySpawns[randomIndex]; float spawnX = x * TILE_SIZE; diff --git a/src/Utils.h b/src/Utils.h new file mode 100644 index 0000000..6dd16b9 --- /dev/null +++ b/src/Utils.h @@ -0,0 +1,21 @@ +#ifndef UTILS_H +#define UTILS_H + +#include + +namespace Utils { + template + T getRandom(T min, T max) { + static std::mt19937 gen(std::random_device{}()); + + if constexpr (std::is_floating_point_v) { + std::uniform_real_distribution dist(min, max); + return dist(gen); + } else { + std::uniform_int_distribution dist(min, max); + return dist(gen); + } + } +} + +#endif // UTILS_H