Compare commits

...

4 Commits

4 changed files with 109 additions and 85 deletions

145
main.cpp
View File

@@ -9,38 +9,28 @@
#include "spawner.h" #include "spawner.h"
#include "types.h" #include "types.h"
float screen_width = 800; Viewport viewport{800, 600};
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[] = {}; Rectangle walls[] = {};
float player_width = 50; float player_width = 50;
Player player = { Player player = {
.rect = { .rect = {
.x = level_width / 2 - player_width / 2, .x = viewport.level_width / 2 - player_width / 2,
.y = level_height / 2 - player_width / 2, .y = viewport.level_height / 2 - player_width / 2,
.width = player_width, .width = player_width,
.height = player_width, .height = player_width,
}, },
.lives = 3, .lives = 3,
.speed = 5.0,
.invulnerability_secs = 1,
.last_hit = -10, .last_hit = -10,
.tear_speed = 10, .tear_speed = 10,
.tear_range = 300, .tear_range = 300,
.tear_radius = 10.0f, .tear_radius = 10.0f,
.last_fired = 0, .last_fired = 0,
.fire_rate = .5, .fire_rate = .5,
}; };
auto player_speed = 5.0f;
auto player_invulnerability = 1.0f;
uint32_t score; uint32_t score;
@@ -56,28 +46,26 @@ Vector2 Player::center() const noexcept {
} }
bool Player::is_invulnerable(const double now) const noexcept { bool Player::is_invulnerable(const double now) const noexcept {
return now < this->last_hit + player_invulnerability; return now < last_hit + invulnerability_secs;
} }
void Player::move(const float delta_x, const float delta_y) { void Player::move(const Vector2 delta) {
const auto x = std::min( const auto [next_x, next_y] = Vector2Clamp(
std::max(this->rect.x + delta_x, 0.0f), Vector2Add({this->rect.x, this->rect.y}, delta),
level_right - this->rect.width); {viewport.level_left, viewport.level_top},
Vector2Subtract({viewport.level_right, viewport.level_bottom}, {this->rect.width, this->rect.height})
const auto y = std::min( );
std::max(this->rect.y + delta_y, 0.0f),
level_bottom - this->rect.height);
bool collided = false; bool collided = false;
const Rectangle next_position = { const Rectangle next_rect = {
.x = x, .x = next_x,
.y = y, .y = next_y,
.width = this->rect.width, .width = this->rect.width,
.height = this->rect.height .height = this->rect.height
}; };
for (const auto wall: walls) { for (const auto wall: walls) {
if (CheckCollisionRecs(wall, next_position)) { if (CheckCollisionRecs(wall, next_rect)) {
collided = true; collided = true;
break; break;
} }
@@ -85,8 +73,8 @@ void Player::move(const float delta_x, const float delta_y) {
if (collided) { if (collided) {
} else { } else {
this->rect.x = x; this->rect.x = next_x;
this->rect.y = y; this->rect.y = next_y;
} }
} }
@@ -97,35 +85,38 @@ struct EnemySpawner final : Spawner<Enemy, 100> {
void spawn() override { void spawn() override {
const auto num = Rng::generate(); const auto num = Rng::generate();
const auto starting_x = Rng::generate(static_cast<int>(level_left), static_cast<int>(level_right)); const auto starting_x = Rng::generate(static_cast<int>(viewport.level_left),
const auto starting_y = Rng::generate(static_cast<int>(level_top), static_cast<int>(level_bottom)); static_cast<int>(viewport.level_right));
const auto starting_y = Rng::generate(static_cast<int>(viewport.level_top),
static_cast<int>(viewport.level_bottom));
for (auto &[center, radius, speed, alive]: this->values) { for (auto &enemy: this->values) {
if (alive) continue; if (enemy.alive()) continue;
alive = true; enemy.radius = enemy_radius;
radius = enemy_radius; auto hp = Rng::generate(1, 4);
enemy.hp = static_cast<uint32_t>(hp);
if (num < 0.25) { if (num < 0.25) {
center = {.x = starting_x, .y = level_top}; enemy.center = {.x = starting_x, .y = viewport.level_top};
} else if (0.25 <= num && num < 0.5) { } else if (0.25 <= num && num < 0.5) {
center = { enemy.center = {
.x = starting_x, .y = level_bottom .x = starting_x, .y = viewport.level_bottom
}; };
} else if (0.5 <= num && num < 0.75) { } else if (0.5 <= num && num < 0.75) {
center = {.x = level_left, .y = starting_y}; enemy.center = {.x = viewport.level_left, .y = starting_y};
} else { } else {
center = { enemy.center = {
.x = level_right, .y = starting_y .x = viewport.level_right, .y = starting_y
}; };
} }
speed = Rng::generate(static_cast<uint32_t>(enemy_max_speed)); enemy.speed = Rng::generate(static_cast<uint32_t>(enemy_max_speed));
break; break;
} }
} }
}; };
EnemySpawner enemy_spawner{2}; EnemySpawner enemy_spawner{1};
struct ItemSpawner final : Spawner<Item, 100> { struct ItemSpawner final : Spawner<Item, 100> {
explicit ItemSpawner(const long long rate_secs) : Spawner<Item, 100>(rate_secs) { explicit ItemSpawner(const long long rate_secs) : Spawner<Item, 100>(rate_secs) {
@@ -133,8 +124,8 @@ struct ItemSpawner final : Spawner<Item, 100> {
void spawn() override { void spawn() override {
const auto item_type = static_cast<ItemType>(Rng::generate(ITEM_TYPE_COUNT)); 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 x = Rng::generate(static_cast<int>(viewport.level_left), static_cast<int>(viewport.level_right));
const auto y = Rng::generate(static_cast<int>(level_top), static_cast<int>(level_bottom)); const auto y = Rng::generate(static_cast<int>(viewport.level_top), static_cast<int>(viewport.level_bottom));
for (auto &item: this->values) { for (auto &item: this->values) {
if (item.active) continue; if (item.active) continue;
@@ -173,11 +164,11 @@ void update_tears() {
} }
} }
enemy_spawner.for_each([&center, &active](auto &enemy) { enemy_spawner.for_each([&center, &active](auto &enemy, auto& cancel) {
if (enemy.alive && CheckCollisionCircles(center, player.tear_radius, enemy.center, enemy.radius)) { if (enemy.alive() && CheckCollisionCircles(center, player.tear_radius, enemy.center, enemy.radius)) {
active = false; active = false;
enemy.alive = false; if (--enemy.hp == 0) score++;
score++; cancel = true;
} }
}); });
@@ -220,27 +211,21 @@ int main() {
std::string item_pickup_text_buffer; std::string item_pickup_text_buffer;
item_pickup_text_buffer.reserve(64); item_pickup_text_buffer.reserve(64);
double item_last_picked_up = 0; double item_last_picked_up = 0;
double item_pickup_message_duration = 3;
while (!WindowShouldClose()) { while (!WindowShouldClose()) {
constexpr double item_pickup_message_duration = 3;
// float dt_secs = GetFrameTime();
if (IsWindowResized()) { if (IsWindowResized()) {
screen_height = static_cast<float>(GetScreenHeight()); viewport = {static_cast<float>(GetScreenWidth()), 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;
} }
Vector2 delta{}; Vector2 delta{};
if (IsKeyDown(KEY_W)) delta.y -= player_speed; if (IsKeyDown(KEY_W)) delta.y -= player.speed;
if (IsKeyDown(KEY_S)) delta.y += player_speed; if (IsKeyDown(KEY_S)) delta.y += player.speed;
if (IsKeyDown(KEY_A)) delta.x -= player_speed; if (IsKeyDown(KEY_A)) delta.x -= player.speed;
if (IsKeyDown(KEY_D)) delta.x += player_speed; if (IsKeyDown(KEY_D)) delta.x += player.speed;
player.move(delta.x, delta.y); player.move(delta);
std::optional<Direction> tear_direction; std::optional<Direction> tear_direction;
@@ -267,7 +252,8 @@ int main() {
BeginDrawing(); BeginDrawing();
ClearBackground(GRAY); ClearBackground(GRAY);
DrawRectangle(50, 50, static_cast<int>(level_width), static_cast<int>(level_height), RAYWHITE); DrawRectangle(50, 50, static_cast<int>(viewport.level_width), static_cast<int>(viewport.level_height),
RAYWHITE);
DrawRectangleRec(player.rect, player.is_invulnerable(now) ? GOLD : BLUE); DrawRectangleRec(player.rect, player.is_invulnerable(now) ? GOLD : BLUE);
for (const auto wall: walls) { for (const auto wall: walls) {
@@ -280,25 +266,27 @@ int main() {
} }
} }
enemy_spawner.for_each([now](Enemy &enemy) { enemy_spawner.for_each([now](Enemy &enemy, auto& cancel) {
if (enemy.alive) { if (enemy.alive()) {
enemy.center = Vector2MoveTowards(enemy.center, player.center(), enemy.speed); enemy.center = Vector2MoveTowards(enemy.center, player.center(), enemy.speed);
DrawCircleV(enemy.center, enemy.radius, RED); const Color enemy_color = enemy.hp == 1 ? PINK : enemy.hp == 2 ? ORANGE : RED;
DrawCircleV(enemy.center, enemy.radius, enemy_color);
if (enemy.center.x < level_left || level_right < enemy.center.x || if (enemy.center.x < viewport.level_left || viewport.level_right < enemy.center.x ||
enemy.center.y < level_top || level_bottom < enemy.center.y) { enemy.center.y < viewport.level_top || viewport.level_bottom < enemy.center.y) {
enemy.alive = false; // Basically removing the enemy from the pool;
enemy.hp = 0;
} }
if (CheckCollisionCircleRec(enemy.center, enemy.radius, player.rect) && if (CheckCollisionCircleRec(enemy.center, enemy.radius, player.rect) &&
player.last_hit + player_invulnerability < now) { player.last_hit + player.invulnerability_secs < now) {
player.lives--; player.lives--;
player.last_hit = now; player.last_hit = now;
} }
} }
}); });
item_spawner.for_each([&item_pickup_text_buffer, now, &item_last_picked_up](Item &item) { item_spawner.for_each([&item_pickup_text_buffer, now, &item_last_picked_up](Item &item, auto& cancel) {
if (!item.active) return; if (!item.active) return;
DrawCircleV(item.center, item.radius, item.color()); DrawCircleV(item.center, item.radius, item.color());
@@ -330,21 +318,22 @@ int main() {
} }
}); });
const int text_left = static_cast<int>(level_left) + 10; const int text_left = static_cast<int>(viewport.level_left) + 10;
DrawRectangle(static_cast<int>(level_left), static_cast<int>(level_height) - 110, 200, 110, DrawRectangle(static_cast<int>(viewport.level_left), static_cast<int>(viewport.level_height) - 110, 200, 110,
{130, 130, 130, 100}); {130, 130, 130, 100});
std::format_to(std::back_inserter(score_text_buffer), "Score: {}", score); 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); DrawText(score_text_buffer.c_str(), text_left, static_cast<int>(viewport.level_height) - 100, 20, BLACK);
score_text_buffer.clear(); score_text_buffer.clear();
std::format_to(std::back_inserter(lives_text_buffer), "Lives: {}", player.lives); 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); DrawText(lives_text_buffer.c_str(), text_left, static_cast<int>(viewport.level_height) - 75, 20, BLACK);
lives_text_buffer.clear(); lives_text_buffer.clear();
if (now < item_last_picked_up + item_pickup_message_duration && !item_pickup_text_buffer.empty()) { 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); DrawText(item_pickup_text_buffer.c_str(), text_left, static_cast<int>(viewport.level_height) - 50, 20,
BLACK);
} }
if (item_last_picked_up + item_pickup_message_duration <= now && !item_pickup_text_buffer.empty()) { if (item_last_picked_up + item_pickup_message_duration <= now && !item_pickup_text_buffer.empty()) {

View File

@@ -13,5 +13,5 @@ float Rng::generate(const uint32_t max) {
} }
float Rng::generate(const int min, const int max) { float Rng::generate(const int min, const int max) {
return generate() * (static_cast<float>(max) - static_cast<float>(min)) + static_cast<float>(min); return generate(max - min) + static_cast<float>(min);
} }

View File

@@ -24,7 +24,9 @@ struct Spawner {
void for_each(F&& func) { void for_each(F&& func) {
std::lock_guard lock{mutex}; std::lock_guard lock{mutex};
for (auto& value : values) { for (auto& value : values) {
func(value); bool cancel = false;
func(value, cancel);
if (cancel) break;;
} }
} }

43
types.h
View File

@@ -11,6 +11,8 @@
struct Player { struct Player {
Rectangle rect; Rectangle rect;
uint8_t lives; uint8_t lives;
float speed;
float invulnerability_secs;
double last_hit; double last_hit;
float tear_speed; float tear_speed;
float tear_range; float tear_range;
@@ -22,7 +24,7 @@ struct Player {
[[nodiscard]] bool is_invulnerable(double now) const noexcept; [[nodiscard]] bool is_invulnerable(double now) const noexcept;
void move(float delta_x, float delta_y); void move(Vector2 delta);
}; };
enum Direction { enum Direction {
@@ -47,7 +49,8 @@ struct Enemy {
Vector2 center; Vector2 center;
float radius; float radius;
float speed; float speed;
bool alive; uint32_t hp;
[[nodiscard]] bool alive() const noexcept { return hp != 0; }
}; };
enum ItemType { enum ItemType {
@@ -58,11 +61,11 @@ enum ItemType {
ITEM_TYPE_COUNT ITEM_TYPE_COUNT
}; };
template <> template<>
struct std::formatter<ItemType> : std::formatter<string_view> { struct std::formatter<ItemType> : std::formatter<string_view> {
bool title_case = false; bool title_case = false;
constexpr auto parse(std::format_parse_context& ctx) { constexpr auto parse(std::format_parse_context &ctx) {
auto it = ctx.begin(); auto it = ctx.begin();
if (it == ctx.end()) return it; if (it == ctx.end()) return it;
@@ -74,7 +77,7 @@ struct std::formatter<ItemType> : std::formatter<string_view> {
return it; return it;
} }
auto format(const ItemType& item_type, std::format_context& ctx) const { auto format(const ItemType &item_type, std::format_context &ctx) const {
std::string_view item_type_str; std::string_view item_type_str;
switch (item_type) { switch (item_type) {
case TEARS_UP: case TEARS_UP:
@@ -120,3 +123,33 @@ struct Item {
assert(0 && "Unreachable."); assert(0 && "Unreachable.");
} }
}; };
struct Viewport {
Viewport() = default;
Viewport(const float screen_width, const float screen_height)
: screen_width(screen_width),
screen_height(screen_height),
padding(50),
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) {
}
Viewport& operator=(const Viewport &other) = default;
float screen_width;
float screen_height;
float padding;
float level_width;
float level_height;
float level_left;
float level_top;
float level_right;
float level_bottom;
};