From d6dff2a5391ee3f741c3f4500173acc3de17e699 Mon Sep 17 00:00:00 2001 From: Grant Horner Date: Mon, 1 Dec 2025 10:38:44 -0500 Subject: [PATCH] add item spawner --- CMakeLists.txt | 4 ++ main.cpp | 132 ++++++++++++++++++++----------------------------- rng.cpp | 17 +++++++ rng.h | 17 +++++++ spawner.h | 68 +++++++++++++++++++++++++ types.h | 62 +++++++++++++++++++++++ 6 files changed, 221 insertions(+), 79 deletions(-) create mode 100644 rng.cpp create mode 100644 rng.h create mode 100644 spawner.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f3e533..780dfd6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 4.0) project(isaac__) set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library") include(FetchContent) @@ -17,6 +18,9 @@ FetchContent_MakeAvailable(raylib) add_executable(isaac__ main.cpp types.h + spawner.h + rng.cpp + rng.h ) target_link_libraries(${PROJECT_NAME} PUBLIC raylib) target_include_directories(${PROJECT_NAME} PUBLIC ${raylib_public_headers}) \ No newline at end of file diff --git a/main.cpp b/main.cpp index 02b3e40..af116cc 100644 --- a/main.cpp +++ b/main.cpp @@ -5,6 +5,8 @@ #include #include +#include "rng.h" +#include "spawner.h" #include "types.h" float screen_width = 800; @@ -24,26 +26,10 @@ float tear_radius = 10.0f; double last_fired = 0; double fire_rate = .5; -std::mutex enemy_mutex; float enemy_max_speed = 3.0; float enemy_radius = 25.0; -struct Rng { - static std::random_device dev; - static std::mt19937 rng; - - static float generate() { - return static_cast(rng()) / static_cast(std::mt19937::max()); - } - - static float generate(const uint32_t max) { - return generate() * static_cast(max); - } - - static float generate(const int min, const int max) { - return generate() * (static_cast(max) - static_cast(min)) + static_cast(min); - } -}; +float item_radius = 10.0; std::random_device Rng::dev{}; std::mt19937 Rng::rng{dev()}; @@ -91,50 +77,6 @@ void Player::move(const float delta_x, const float delta_y) { } } -template -struct Spawner { - std::mutex mutex; - std::condition_variable cv; - std::atomic running; - std::atomic paused; - std::optional timer; - long long rate_secs; - T values[N]; - - explicit Spawner(const long long rate_secs) - : rate_secs(rate_secs) { - } - - virtual void spawn() {} - - void start() { - running.store(true); - timer = std::thread([&] { - while (running.load()) { - const auto start = std::chrono::steady_clock::now(); - - if (!paused.load()) { - std::lock_guard lock(mutex); - - spawn(); - } - - auto next = start + std::chrono::seconds(rate_secs); - std::unique_lock lock{mutex}; - cv.wait_until(lock, next, [&] { return !running.load(); }); - } - }); - } - - virtual ~Spawner() { - running.store(false); - cv.notify_all(); - if (timer) { - timer.value().join(); - } - } -}; - struct EnemySpawner final : Spawner { explicit EnemySpawner(const long long rate_secs) : Spawner(rate_secs) { @@ -172,6 +114,30 @@ struct EnemySpawner final : Spawner { EnemySpawner enemy_spawner{2}; +struct ItemSpawner final : Spawner { + explicit ItemSpawner(const long long rate_secs) : Spawner(rate_secs) { + } + + void spawn() override { + const auto item_type = static_cast(Rng::generate(ItemType::ITEM_TYPE_COUNT)); + const auto x = Rng::generate(static_cast(screen_width)); + const auto y = Rng::generate(static_cast(screen_height)); + + for (auto &item: this->values) { + if (item.active) continue; + item.active = true; + + item.type = item_type; + item.center = Vector2{x, y}; + item.radius = item_radius; + + break; + } + } +}; + +ItemSpawner item_spawner{5}; + void spawn_tear(const Vector2 center, const Direction direction) { for (auto &[tear_center, tear_direction, tear_active, starting_center]: tears) { if (!tear_active) { @@ -194,13 +160,13 @@ void update_tears() { } } - for (auto &enemy: enemy_spawner.values) { + enemy_spawner.for_each([¢er, &active](auto &enemy) { if (enemy.alive && CheckCollisionCircles(center, tear_radius, enemy.center, enemy.radius)) { active = false; enemy.alive = false; score++; } - } + }); switch (direction) { case UP: @@ -230,6 +196,7 @@ int main() { enemy_spawner.start(); + item_spawner.start(); player.width = 50; player.height = 50; @@ -296,26 +263,33 @@ int main() { } } - { - std::lock_guard enemy_guard{enemy_mutex}; - for (auto &[center, radius, speed, alive]: enemy_spawner.values) { - if (alive) { - center = Vector2MoveTowards(center, player.center(), speed); - DrawCircleV(center, radius, RED); + enemy_spawner.for_each([now](auto &enemy) { + if (enemy.alive) { + enemy.center = Vector2MoveTowards(enemy.center, player.center(), enemy.speed); + DrawCircleV(enemy.center, enemy.radius, RED); - if (center.x < 0 || screen_width < center.x || - center.y < 0 || screen_height < center.y) { - alive = false; - } + if (enemy.center.x < 0 || screen_width < enemy.center.x || + enemy.center.y < 0 || screen_height < enemy.center.y) { + enemy.alive = false; + } - if (CheckCollisionCircleRec(center, radius, player.rect()) && - player.last_hit + player_invulnerability < now) { - player.lives--; - player.last_hit = now; - } + if (CheckCollisionCircleRec(enemy.center, enemy.radius, player.rect()) && + player.last_hit + player_invulnerability < now) { + player.lives--; + player.last_hit = now; } } - } + }); + + item_spawner.for_each([](auto &item) { + if (!item.active) return; + + DrawCircleV(item.center, item.radius, item.color()); + + if (CheckCollisionCircleRec(item.center, item.radius, player.rect())) { + item.active = false; + } + }); std::format_to(std::back_inserter(score_text_buffer), "Score: {}", score); DrawText(score_text_buffer.c_str(), 50, static_cast(screen_height) - 100, 20, BLACK); diff --git a/rng.cpp b/rng.cpp new file mode 100644 index 0000000..ad27483 --- /dev/null +++ b/rng.cpp @@ -0,0 +1,17 @@ +// +// Created by Grant Horner on 12/1/25. +// + +#include "rng.h" + +float Rng::generate() { + return static_cast(rng()) / static_cast(std::mt19937::max()); +} + +float Rng::generate(const uint32_t max) { + return generate() * static_cast(max); +} + +float Rng::generate(const int min, const int max) { + return generate() * (static_cast(max) - static_cast(min)) + static_cast(min); +} diff --git a/rng.h b/rng.h new file mode 100644 index 0000000..0a1f43c --- /dev/null +++ b/rng.h @@ -0,0 +1,17 @@ +// +// Created by Grant Horner on 12/1/25. +// + +#pragma once +#include + +struct Rng { + static std::random_device dev; + static std::mt19937 rng; + + static float generate(); + + static float generate(uint32_t max); + + static float generate(int min, int max); +}; diff --git a/spawner.h b/spawner.h new file mode 100644 index 0000000..959d077 --- /dev/null +++ b/spawner.h @@ -0,0 +1,68 @@ +// +// Created by Grant Horner on 12/1/25. +// + +#pragma once +#include +#include +#include + +template +struct Spawner { + std::mutex mutex; + std::condition_variable cv; + std::atomic running; + std::atomic paused; + std::jthread timer; + long long rate_secs; + T values[N]; + + explicit Spawner(long long rate_secs); + + virtual void spawn(); + + template + void for_each(F&& func) { + std::lock_guard lock{mutex}; + for (auto& value : values) { + func(value); + } + } + + void start(); + + virtual ~Spawner(); +}; + +template +Spawner::Spawner(const long long rate_secs) : rate_secs(rate_secs) { +} + +template +void Spawner::spawn() { +} + +template +void Spawner::start() { + running.store(true); + timer = std::jthread([&] { + while (running.load()) { + const auto start = std::chrono::steady_clock::now(); + + std::unique_lock lock{mutex}; + if (!paused.load()) { + spawn(); + } + + auto next = start + std::chrono::seconds(rate_secs); + cv.wait_until(lock, next, [&] { return !running.load(); }); + } + }); +} + +template +Spawner::~Spawner() { + running.store(false); + cv.notify_all(); +} + diff --git a/types.h b/types.h index b36f388..7aab6e2 100644 --- a/types.h +++ b/types.h @@ -4,14 +4,19 @@ #pragma once +#include + #include "raymath.h" #include "raylib.h" struct Player : Rectangle { uint8_t lives; double last_hit; + [[nodiscard]] Vector2 center() const noexcept; + [[nodiscard]] Rectangle rect() const noexcept; + [[nodiscard]] bool is_invulnerable(double now) const noexcept; void move(float delta_x, float delta_y); @@ -41,3 +46,60 @@ struct Enemy { float speed; bool alive; }; + +enum ItemType { + TEARS_UP, + TEARS_DOWN, + RANGE_UP, + RANGE_DOWN, + ITEM_TYPE_COUNT +}; + +template <> +struct std::formatter : std::formatter { + auto format(const ItemType& item_type, std::format_context& ctx) const { + std::string_view item_type_str; + switch (item_type) { + case TEARS_UP: + item_type_str = "TEARS_UP"; + break; + case TEARS_DOWN: + item_type_str = "TEARS_DOWN"; + break; + case RANGE_UP: + item_type_str = "RANGE_UP"; + break; + case RANGE_DOWN: + item_type_str = "RANGE_DOWN"; + break; + case ITEM_TYPE_COUNT: + item_type_str = "ITEM_TYPE_COUNT"; + break; + } + return std::formatter::format(item_type_str, ctx);; + } +}; + +struct Item { + Vector2 center; + float radius; + ItemType type; + bool active; + + [[nodiscard]] Color color() const noexcept { + switch (type) { + case TEARS_UP: + return BLUE; + case TEARS_DOWN: + return GREEN; + case RANGE_UP: + return RED; + case RANGE_DOWN: + return ORANGE; + case ITEM_TYPE_COUNT: + assert(0 && "ITEM_TYPE_COUNT should not be instantiated."); + } + + assert(0 && "Unreachable."); + } +};