333 lines
9.0 KiB
C++
333 lines
9.0 KiB
C++
#include <iterator>
|
|
#include <mutex>
|
|
#include <print>
|
|
#include <random>
|
|
#include <raylib.h>
|
|
#include <thread>
|
|
|
|
#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<float>(rng()) / static_cast<float>(std::mt19937::max());
|
|
}
|
|
|
|
static float generate(const uint32_t max) {
|
|
return generate() * static_cast<float>(max);
|
|
}
|
|
|
|
static float generate(const int min, const int max) {
|
|
return generate() * (static_cast<float>(max) - static_cast<float>(min)) + static_cast<float>(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 <typename T, size_t N>
|
|
struct Spawner {
|
|
std::mutex mutex;
|
|
std::condition_variable cv;
|
|
std::atomic<bool> running;
|
|
std::atomic<bool> paused;
|
|
std::optional<std::thread> 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<Enemy, 100> {
|
|
explicit EnemySpawner(const long long rate_secs)
|
|
: Spawner<Enemy, 100>(rate_secs) {
|
|
}
|
|
|
|
void spawn() override {
|
|
const auto num = Rng::generate();
|
|
const auto starting_x = Rng::generate(static_cast<uint32_t>(screen_width));
|
|
const auto starting_y = Rng::generate(static_cast<uint32_t>(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<uint32_t>(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<float>(GetScreenHeight());
|
|
screen_width = static_cast<float>(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<Direction> 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<int>(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<int>(screen_height) - 50, 20, BLACK);
|
|
lives_text_buffer.clear();
|
|
|
|
EndDrawing();
|
|
}
|
|
CloseWindow();
|
|
return 0;
|
|
}
|