Compare commits
10 Commits
e191e1b508
...
fdf30d0446
| Author | SHA1 | Date | |
|---|---|---|---|
| fdf30d0446 | |||
| 6a6249798e | |||
| ee5cb71560 | |||
| 17663a1f75 | |||
| 77cb83cfd5 | |||
| d6dff2a539 | |||
| 49751ea09e | |||
| b77af66c9c | |||
| 275da1bd65 | |||
| 53f1264c5f |
@@ -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})
|
||||
314
main.cpp
314
main.cpp
@@ -1,58 +1,80 @@
|
||||
#include <iterator>
|
||||
#include <mutex>
|
||||
#include <print>
|
||||
#include <random>
|
||||
#include <raylib.h>
|
||||
#include <thread>
|
||||
|
||||
#include "raymath.h"
|
||||
#include "rng.h"
|
||||
#include "spawner.h"
|
||||
#include "types.h"
|
||||
|
||||
auto screen_width = 800;
|
||||
auto screen_height = 600;
|
||||
Rectangle walls[] = {
|
||||
{.x = 100, .y = 100, .width = 5, .height = 100}
|
||||
float screen_width = 800;
|
||||
float screen_height = 600;
|
||||
|
||||
float padding = 50;
|
||||
float level_width = screen_width - padding * 2;
|
||||
float level_height = screen_height - padding * 2;
|
||||
float level_left = padding;
|
||||
float level_top = padding;
|
||||
float level_right = level_width + padding;
|
||||
float level_bottom = level_height + padding;
|
||||
|
||||
Rectangle walls[] = {};
|
||||
|
||||
float player_width = 50;
|
||||
Player player = {
|
||||
.rect = {
|
||||
.x = level_width / 2 - player_width / 2,
|
||||
.y = level_height / 2 - player_width / 2,
|
||||
.width = player_width,
|
||||
.height = player_width,
|
||||
},
|
||||
.lives = 3,
|
||||
.speed = 5.0,
|
||||
.invulnerability_secs = 1,
|
||||
.last_hit = -10,
|
||||
.tear_speed = 10,
|
||||
.tear_range = 300,
|
||||
.tear_radius = 10.0f,
|
||||
.last_fired = 0,
|
||||
.fire_rate = .5,
|
||||
};
|
||||
|
||||
Player player;
|
||||
auto player_speed = 5.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::array<Tear, 100> tears;
|
||||
|
||||
std::random_device dev;
|
||||
std::mt19937 rng(dev());
|
||||
float enemy_max_speed = 3.0;
|
||||
float enemy_radius = 25.0;
|
||||
|
||||
std::mutex enemy_mutex;
|
||||
float enemy_max_speed = 5.0;
|
||||
float enemy_radius = 50.0;
|
||||
Enemy enemies[100] = {};
|
||||
float item_radius = 10.0;
|
||||
|
||||
Vector2 Player::center() const noexcept {
|
||||
return {.x = this->x + this->width / 2, .y = this->y + this->height / 2};
|
||||
return {.x = this->rect.x + this->rect.width / 2, .y = this->rect.y + this->rect.height / 2};
|
||||
}
|
||||
|
||||
void Player::move(const float delta_x, const float delta_y) {
|
||||
auto x = std::min(
|
||||
std::max(this->x + delta_x, 0.0f),
|
||||
static_cast<float>(screen_width) - this->width);
|
||||
bool Player::is_invulnerable(const double now) const noexcept {
|
||||
return now < last_hit + invulnerability_secs;
|
||||
}
|
||||
|
||||
auto y = std::min(
|
||||
std::max(this->y + delta_y, 0.0f),
|
||||
static_cast<float>(screen_height) - this->height);
|
||||
void Player::move(const Vector2 delta) {
|
||||
const auto [next_x, next_y] = Vector2Clamp(
|
||||
Vector2Add({this->rect.x, this->rect.y}, delta),
|
||||
{level_left, level_top},
|
||||
Vector2Subtract({level_right, level_bottom}, {this->rect.width, this->rect.height})
|
||||
);
|
||||
|
||||
bool collided = false;
|
||||
const Rectangle next_position = {
|
||||
.x = x,
|
||||
.y = y,
|
||||
.width = this->width,
|
||||
.height = this->height
|
||||
const Rectangle next_rect = {
|
||||
.x = next_x,
|
||||
.y = next_y,
|
||||
.width = this->rect.width,
|
||||
.height = this->rect.height
|
||||
};
|
||||
|
||||
for (const auto wall: walls) {
|
||||
if (CheckCollisionRecs(wall, next_position)) {
|
||||
if (CheckCollisionRecs(wall, next_rect)) {
|
||||
collided = true;
|
||||
break;
|
||||
}
|
||||
@@ -60,74 +82,72 @@ void Player::move(const float delta_x, const float delta_y) {
|
||||
|
||||
if (collided) {
|
||||
} else {
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
this->rect.x = next_x;
|
||||
this->rect.y = next_y;
|
||||
}
|
||||
}
|
||||
|
||||
struct EnemySpawner {
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
std::atomic<bool> running;
|
||||
std::atomic<bool> paused;
|
||||
std::thread timer;
|
||||
long long rate_secs;
|
||||
|
||||
struct EnemySpawner final : Spawner<Enemy, 100> {
|
||||
explicit EnemySpawner(const long long rate_secs)
|
||||
: rate_secs(rate_secs) {
|
||||
: Spawner<Enemy, 100>(rate_secs) {
|
||||
}
|
||||
|
||||
void start() {
|
||||
running.store(true);
|
||||
timer = std::thread([&] {
|
||||
while (running.load()) {
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
void spawn() override {
|
||||
const auto num = Rng::generate();
|
||||
const auto starting_x = Rng::generate(static_cast<int>(level_left), static_cast<int>(level_right));
|
||||
const auto starting_y = Rng::generate(static_cast<int>(level_top), static_cast<int>(level_bottom));
|
||||
|
||||
if (!paused.load()) {
|
||||
std::lock_guard lock(enemy_mutex);
|
||||
const auto num = static_cast<float>(rng()) / static_cast<float>(std::mt19937::max());
|
||||
const auto dir = static_cast<float>(rng()) / static_cast<float>(std::mt19937::max());
|
||||
|
||||
for (auto &[center, radius, moving, alive]: enemies) {
|
||||
for (auto &[center, radius, speed, alive]: this->values) {
|
||||
if (alive) continue;
|
||||
alive = true;
|
||||
radius = enemy_radius;
|
||||
if (num < 0.25) {
|
||||
center = {.x = static_cast<float>(screen_width) / 2, .y = 0};
|
||||
moving = {.x = (dir - 1) * 2, .y = 1};
|
||||
center = {.x = starting_x, .y = level_top};
|
||||
} else if (0.25 <= num && num < 0.5) {
|
||||
center = {.x = static_cast<float>(screen_width) / 2, .y = static_cast<float>(screen_height)};
|
||||
moving = {.x = (dir - 1) * 2, .y = -1};
|
||||
center = {
|
||||
.x = starting_x, .y = level_bottom
|
||||
};
|
||||
} else if (0.5 <= num && num < 0.75) {
|
||||
center = {.x = 0, .y = static_cast<float>(screen_height) / 2};
|
||||
moving = {.x = 1, .y = (dir - 1) * 2};
|
||||
center = {.x = level_left, .y = starting_y};
|
||||
} else {
|
||||
center = {.x = static_cast<float>(screen_width), .y = static_cast<float>(screen_height) / 2};
|
||||
moving = {.x = -1, .y = (dir - 1) * 2};
|
||||
center = {
|
||||
.x = level_right, .y = starting_y
|
||||
};
|
||||
}
|
||||
|
||||
const auto speed_ratio = static_cast<float>(rng()) / static_cast<float>(std::mt19937::max());
|
||||
const auto speed = speed_ratio * enemy_max_speed;
|
||||
moving = Vector2Scale(moving, speed);
|
||||
speed = Rng::generate(static_cast<uint32_t>(enemy_max_speed));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto next = start + std::chrono::seconds(rate_secs);
|
||||
std::unique_lock lock{m};
|
||||
cv.wait_until(lock, next, [&] { return !running.load(); });
|
||||
}
|
||||
});
|
||||
EnemySpawner enemy_spawner{2};
|
||||
|
||||
struct ItemSpawner final : Spawner<Item, 100> {
|
||||
explicit ItemSpawner(const long long rate_secs) : Spawner<Item, 100>(rate_secs) {
|
||||
}
|
||||
|
||||
~EnemySpawner() {
|
||||
running.store(false);
|
||||
cv.notify_all();
|
||||
timer.join();
|
||||
void spawn() override {
|
||||
const auto item_type = static_cast<ItemType>(Rng::generate(ITEM_TYPE_COUNT));
|
||||
const auto x = Rng::generate(static_cast<int>(level_left), static_cast<int>(level_right));
|
||||
const auto y = Rng::generate(static_cast<int>(level_top), static_cast<int>(level_bottom));
|
||||
|
||||
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) {
|
||||
@@ -144,27 +164,36 @@ void update_tears() {
|
||||
for (auto &[center, direction, active, starting_center]: tears) {
|
||||
if (active) {
|
||||
for (const auto wall: walls) {
|
||||
if (CheckCollisionCircleRec(center, tear_radius, wall)) {
|
||||
if (CheckCollisionCircleRec(center, player.tear_radius, wall)) {
|
||||
active = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
enemy_spawner.for_each([¢er, &active](auto &enemy) {
|
||||
if (enemy.alive && CheckCollisionCircles(center, player.tear_radius, enemy.center, enemy.radius)) {
|
||||
active = false;
|
||||
enemy.alive = false;
|
||||
score++;
|
||||
}
|
||||
});
|
||||
|
||||
switch (direction) {
|
||||
case UP:
|
||||
center.y -= tear_speed;
|
||||
center.y -= player.tear_speed;
|
||||
break;
|
||||
case DOWN:
|
||||
center.y += tear_speed;
|
||||
center.y += player.tear_speed;
|
||||
break;
|
||||
case LEFT:
|
||||
center.x -= tear_speed;
|
||||
center.x -= player.tear_speed;
|
||||
break;
|
||||
case RIGHT:
|
||||
center.x += tear_speed;
|
||||
center.x += player.tear_speed;
|
||||
break;
|
||||
}
|
||||
|
||||
if (tear_range < Vector2Distance(center, starting_center)) {
|
||||
if (player.tear_range < Vector2Distance(center, starting_center)) {
|
||||
active = false;
|
||||
}
|
||||
}
|
||||
@@ -172,30 +201,43 @@ void update_tears() {
|
||||
}
|
||||
|
||||
int main() {
|
||||
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
|
||||
InitWindow(800, 600, "Isaac++");
|
||||
SetTargetFPS(60);
|
||||
|
||||
EnemySpawner spawner{3};
|
||||
spawner.start();
|
||||
player.x = 10;
|
||||
player.y = 10;
|
||||
player.width = 50;
|
||||
player.height = 50;
|
||||
enemy_spawner.start();
|
||||
item_spawner.start();
|
||||
|
||||
std::string score_text_buffer;
|
||||
score_text_buffer.reserve(64);
|
||||
|
||||
std::string lives_text_buffer;
|
||||
lives_text_buffer.reserve(64);
|
||||
|
||||
std::string item_pickup_text_buffer;
|
||||
item_pickup_text_buffer.reserve(64);
|
||||
double item_last_picked_up = 0;
|
||||
double item_pickup_message_duration = 3;
|
||||
|
||||
while (!WindowShouldClose()) {
|
||||
if (IsWindowResized()) {
|
||||
screen_height = GetScreenHeight();
|
||||
screen_width = GetScreenWidth();
|
||||
screen_height = static_cast<float>(GetScreenHeight());
|
||||
screen_width = static_cast<float>(GetScreenWidth());
|
||||
level_width = screen_width - padding * 2;
|
||||
level_height = screen_height - padding * 2;
|
||||
level_left = padding;
|
||||
level_top = padding;
|
||||
level_right = level_width + padding;
|
||||
level_bottom = level_height + padding;
|
||||
}
|
||||
|
||||
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);
|
||||
Vector2 delta{};
|
||||
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);
|
||||
|
||||
|
||||
std::optional<Direction> tear_direction;
|
||||
@@ -212,16 +254,18 @@ int main() {
|
||||
tear_direction = DOWN;
|
||||
}
|
||||
|
||||
if (const auto now = GetTime(); last_fired + fire_rate < now && tear_direction.has_value()) {
|
||||
last_fired = now;
|
||||
const auto now = GetTime();
|
||||
if (player.last_fired + player.fire_rate < now && tear_direction.has_value()) {
|
||||
player.last_fired = now;
|
||||
spawn_tear(player.center(), tear_direction.value());
|
||||
}
|
||||
|
||||
update_tears();
|
||||
|
||||
BeginDrawing();
|
||||
ClearBackground(RAYWHITE);
|
||||
DrawRectangleRec(player, BLUE);
|
||||
ClearBackground(GRAY);
|
||||
DrawRectangle(50, 50, static_cast<int>(level_width), static_cast<int>(level_height), RAYWHITE);
|
||||
DrawRectangleRec(player.rect, player.is_invulnerable(now) ? GOLD : BLUE);
|
||||
|
||||
for (const auto wall: walls) {
|
||||
DrawRectangleRec(wall, GREEN);
|
||||
@@ -229,15 +273,79 @@ int main() {
|
||||
|
||||
for (const auto tear: tears) {
|
||||
if (tear.active) {
|
||||
DrawCircleV(tear.center, tear_radius, SKYBLUE);
|
||||
DrawCircleV(tear.center, player.tear_radius, SKYBLUE);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& enemy: enemies) {
|
||||
enemy_spawner.for_each([now](Enemy &enemy) {
|
||||
if (enemy.alive) {
|
||||
enemy.center = Vector2Add(enemy.center, enemy.moving);
|
||||
enemy.center = Vector2MoveTowards(enemy.center, player.center(), enemy.speed);
|
||||
DrawCircleV(enemy.center, enemy.radius, RED);
|
||||
|
||||
if (enemy.center.x < level_left || level_right < enemy.center.x ||
|
||||
enemy.center.y < level_top || level_bottom < enemy.center.y) {
|
||||
enemy.alive = false;
|
||||
}
|
||||
|
||||
if (CheckCollisionCircleRec(enemy.center, enemy.radius, player.rect) &&
|
||||
player.last_hit + player.invulnerability_secs < now) {
|
||||
player.lives--;
|
||||
player.last_hit = now;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
item_spawner.for_each([&item_pickup_text_buffer, now, &item_last_picked_up](Item &item) {
|
||||
if (!item.active) return;
|
||||
|
||||
DrawCircleV(item.center, item.radius, item.color());
|
||||
|
||||
if (CheckCollisionCircleRec(item.center, item.radius, player.rect)) {
|
||||
switch (item.type) {
|
||||
case TEARS_UP:
|
||||
player.fire_rate = std::max(player.fire_rate - 0.1, 0.1);
|
||||
break;
|
||||
case TEARS_DOWN:
|
||||
player.fire_rate += 0.1;
|
||||
break;
|
||||
case RANGE_UP:
|
||||
player.tear_range += 100.0f;
|
||||
break;
|
||||
case RANGE_DOWN:
|
||||
player.tear_range = std::max(player.tear_range - 100.0f, 100.0f);
|
||||
break;
|
||||
case ITEM_TYPE_COUNT:
|
||||
break;
|
||||
}
|
||||
if (item_pickup_text_buffer.empty()) {
|
||||
std::format_to(std::back_inserter(item_pickup_text_buffer), "{:t}!", item.type);
|
||||
} else {
|
||||
std::format_to(std::back_inserter(item_pickup_text_buffer), " {:t}!", item.type);
|
||||
}
|
||||
item_last_picked_up = now;
|
||||
item.active = false;
|
||||
}
|
||||
});
|
||||
|
||||
const int text_left = static_cast<int>(level_left) + 10;
|
||||
|
||||
DrawRectangle(static_cast<int>(level_left), static_cast<int>(level_height) - 110, 200, 110,
|
||||
{130, 130, 130, 100});
|
||||
|
||||
std::format_to(std::back_inserter(score_text_buffer), "Score: {}", score);
|
||||
DrawText(score_text_buffer.c_str(), text_left, static_cast<int>(level_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(), text_left, static_cast<int>(level_height) - 75, 20, BLACK);
|
||||
lives_text_buffer.clear();
|
||||
|
||||
if (now < item_last_picked_up + item_pickup_message_duration && !item_pickup_text_buffer.empty()) {
|
||||
DrawText(item_pickup_text_buffer.c_str(), text_left, static_cast<int>(level_height) - 50, 20, BLACK);
|
||||
}
|
||||
|
||||
if (item_last_picked_up + item_pickup_message_duration <= now && !item_pickup_text_buffer.empty()) {
|
||||
item_pickup_text_buffer.clear();
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
|
||||
17
rng.cpp
Normal file
17
rng.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by Grant Horner on 12/1/25.
|
||||
//
|
||||
|
||||
#include "rng.h"
|
||||
|
||||
float Rng::generate() {
|
||||
return static_cast<float>(rng()) / static_cast<float>(std::mt19937::max());
|
||||
}
|
||||
|
||||
float Rng::generate(const uint32_t max) {
|
||||
return generate() * static_cast<float>(max);
|
||||
}
|
||||
|
||||
float Rng::generate(const int min, const int max) {
|
||||
return generate() * (static_cast<float>(max) - static_cast<float>(min)) + static_cast<float>(min);
|
||||
}
|
||||
17
rng.h
Normal file
17
rng.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by Grant Horner on 12/1/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <random>
|
||||
|
||||
struct Rng {
|
||||
inline static std::random_device dev;
|
||||
inline static std::mt19937 rng{dev()};
|
||||
|
||||
static float generate();
|
||||
|
||||
static float generate(uint32_t max);
|
||||
|
||||
static float generate(int min, int max);
|
||||
};
|
||||
66
spawner.h
Normal file
66
spawner.h
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// Created by Grant Horner on 12/1/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
template<typename T, size_t N>
|
||||
struct Spawner {
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
std::atomic<bool> paused;
|
||||
std::jthread timer;
|
||||
std::atomic<unsigned long long> rate_secs;
|
||||
std::array<T, N> values;
|
||||
|
||||
explicit Spawner(long long rate_secs);
|
||||
|
||||
virtual void spawn();
|
||||
|
||||
template <typename F>
|
||||
void for_each(F&& func) {
|
||||
std::lock_guard lock{mutex};
|
||||
for (auto& value : values) {
|
||||
func(value);
|
||||
}
|
||||
}
|
||||
|
||||
void start();
|
||||
|
||||
virtual ~Spawner();
|
||||
};
|
||||
|
||||
template<typename T, size_t N>
|
||||
Spawner<T, N>::Spawner(const long long rate_secs) : rate_secs(rate_secs) {
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
void Spawner<T, N>::spawn() {
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
void Spawner<T, N>::start() {
|
||||
timer = std::jthread([&] (const std::stop_token &st) {
|
||||
while (!st.stop_requested()) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
Spawner<T, N>::~Spawner() {
|
||||
timer.request_stop();
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
93
types.h
93
types.h
@@ -4,13 +4,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "raymath.h"
|
||||
#include <cassert>
|
||||
|
||||
#include "raylib.h"
|
||||
|
||||
struct Player : Rectangle {
|
||||
struct Player {
|
||||
Rectangle rect;
|
||||
uint8_t lives;
|
||||
float speed;
|
||||
float invulnerability_secs;
|
||||
double last_hit;
|
||||
float tear_speed;
|
||||
float tear_range;
|
||||
float tear_radius;
|
||||
double last_fired;
|
||||
double fire_rate;
|
||||
|
||||
[[nodiscard]] Vector2 center() const noexcept;
|
||||
|
||||
void move(float delta_x, float delta_y);
|
||||
[[nodiscard]] bool is_invulnerable(double now) const noexcept;
|
||||
|
||||
void move(Vector2 delta);
|
||||
};
|
||||
|
||||
enum Direction {
|
||||
@@ -34,6 +48,77 @@ struct Tear {
|
||||
struct Enemy {
|
||||
Vector2 center;
|
||||
float radius;
|
||||
Vector2 moving;
|
||||
float speed;
|
||||
bool alive;
|
||||
};
|
||||
|
||||
enum ItemType {
|
||||
TEARS_UP,
|
||||
TEARS_DOWN,
|
||||
RANGE_UP,
|
||||
RANGE_DOWN,
|
||||
ITEM_TYPE_COUNT
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<ItemType> : std::formatter<string_view> {
|
||||
bool title_case = false;
|
||||
|
||||
constexpr auto parse(std::format_parse_context& ctx) {
|
||||
auto it = ctx.begin();
|
||||
if (it == ctx.end()) return it;
|
||||
|
||||
if (*it == 't') {
|
||||
title_case = true;
|
||||
++it;
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
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 = title_case ? "Tears Up" : "TEARS_UP";
|
||||
break;
|
||||
case TEARS_DOWN:
|
||||
item_type_str = title_case ? "Tears Down" : "TEARS_DOWN";
|
||||
break;
|
||||
case RANGE_UP:
|
||||
item_type_str = title_case ? "Range Up" : "RANGE_UP";
|
||||
break;
|
||||
case RANGE_DOWN:
|
||||
item_type_str = title_case ? "Range Down" : "RANGE_DOWN";
|
||||
break;
|
||||
case ITEM_TYPE_COUNT:
|
||||
item_type_str = title_case ? "Item Type Count" : "ITEM_TYPE_COUNT";
|
||||
break;
|
||||
}
|
||||
return std::formatter<string_view>::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.");
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user