#include #include #include #include #include #include #include "types.h" float screen_width = 800; float screen_height = 600; Rectangle walls[] = {}; Player player; auto player_speed = 5.0f; auto player_invulnerability = 1.0f; uint32_t score; Tear tears[100] = {}; float tear_speed = 10; float tear_range = 500; 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); } }; std::random_device Rng::dev{}; std::mt19937 Rng::rng{dev()}; Vector2 Player::center() const noexcept { return {.x = this->x + this->width / 2, .y = this->y + this->height / 2}; } Rectangle Player::rect() const noexcept { return {.x = x, .y = y, .width = width, .height = height}; } bool Player::is_invulnerable(const double now) const noexcept { return now < this->last_hit + player_invulnerability; } void Player::move(const float delta_x, const float delta_y) { const auto x = std::min( std::max(this->x + delta_x, 0.0f), screen_width - this->width); const auto y = std::min( std::max(this->y + delta_y, 0.0f), screen_height - this->height); bool collided = false; const Rectangle next_position = { .x = x, .y = y, .width = this->width, .height = this->height }; for (const auto wall: walls) { if (CheckCollisionRecs(wall, next_position)) { collided = true; break; } } if (collided) { } else { this->x = x; this->y = 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) { } void spawn() override { const auto num = Rng::generate(); const auto starting_x = Rng::generate(static_cast(screen_width)); const auto starting_y = Rng::generate(static_cast(screen_height)); for (auto &[center, radius, speed, alive]: this->values) { if (alive) continue; alive = true; radius = enemy_radius; if (num < 0.25) { center = {.x = starting_x, .y = 0}; } else if (0.25 <= num && num < 0.5) { center = { .x = starting_x, .y = (screen_height) }; } else if (0.5 <= num && num < 0.75) { center = {.x = 0, .y = starting_y}; } else { center = { .x = (screen_width), .y = starting_y }; } speed = Rng::generate(static_cast(enemy_max_speed)); break; } } }; EnemySpawner enemy_spawner{2}; void spawn_tear(const Vector2 center, const Direction direction) { for (auto &[tear_center, tear_direction, tear_active, starting_center]: tears) { if (!tear_active) { tear_center = center; tear_direction = direction; tear_active = true; starting_center = center; break; } } } void update_tears() { for (auto &[center, direction, active, starting_center]: tears) { if (active) { for (const auto wall: walls) { if (CheckCollisionCircleRec(center, tear_radius, wall)) { active = false; break; } } for (auto &enemy: enemy_spawner.values) { if (enemy.alive && CheckCollisionCircles(center, tear_radius, enemy.center, enemy.radius)) { active = false; enemy.alive = false; score++; } } switch (direction) { case UP: center.y -= tear_speed; break; case DOWN: center.y += tear_speed; break; case LEFT: center.x -= tear_speed; break; case RIGHT: center.x += tear_speed; break; } if (tear_range < Vector2Distance(center, starting_center)) { active = false; } } } } int main() { InitWindow(800, 600, "Isaac++"); SetTargetFPS(60); enemy_spawner.start(); player.width = 50; player.height = 50; player.x = screen_width / 2 - player.width / 2; player.y = screen_height / 2 - player.height / 2; player.lives = 3; player.last_hit = -10; std::string score_text_buffer; score_text_buffer.reserve(64); std::string lives_text_buffer; lives_text_buffer.reserve(64); while (!WindowShouldClose()) { if (IsWindowResized()) { screen_height = static_cast(GetScreenHeight()); screen_width = static_cast(GetScreenWidth()); } float delta_x = 0; float delta_y = 0; if (IsKeyDown(KEY_W)) delta_y -= player_speed; if (IsKeyDown(KEY_S)) delta_y += player_speed; if (IsKeyDown(KEY_A)) delta_x -= player_speed; if (IsKeyDown(KEY_D)) delta_x += player_speed; player.move(delta_x, delta_y); std::optional tear_direction; if (IsKeyDown(KEY_LEFT)) { tear_direction = LEFT; } if (IsKeyDown(KEY_UP)) { tear_direction = UP; } if (IsKeyDown(KEY_RIGHT)) { tear_direction = RIGHT; } if (IsKeyDown(KEY_DOWN)) { tear_direction = DOWN; } const auto now = GetTime(); if (last_fired + fire_rate < now && tear_direction.has_value()) { last_fired = now; spawn_tear(player.center(), tear_direction.value()); } update_tears(); BeginDrawing(); ClearBackground(RAYWHITE); DrawRectangleRec(player.rect(), player.is_invulnerable(now) ? GOLD : BLUE); for (const auto wall: walls) { DrawRectangleRec(wall, GREEN); } for (const auto tear: tears) { if (tear.active) { DrawCircleV(tear.center, tear_radius, SKYBLUE); } } { 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); if (center.x < 0 || screen_width < center.x || center.y < 0 || screen_height < center.y) { alive = false; } if (CheckCollisionCircleRec(center, radius, player.rect()) && player.last_hit + player_invulnerability < now) { player.lives--; player.last_hit = now; } } } } 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); score_text_buffer.clear(); std::format_to(std::back_inserter(lives_text_buffer), "Lives: {}", player.lives); DrawText(lives_text_buffer.c_str(), 50, static_cast(screen_height) - 50, 20, BLACK); lives_text_buffer.clear(); EndDrawing(); } CloseWindow(); return 0; }