# HG changeset patch # User June Park # Date 1766256793 28800 # Node ID e06bc03d9618281e6f4b028da9d8aeb0ac1096e4 # Parent ccb42d5bf8fdf311bab7d94c9a262cf3390cc71b [Color Game] Making game with a friend. diff -r ccb42d5bf8fd -r e06bc03d9618 color_game/BUILD --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/color_game/BUILD Sat Dec 20 10:53:13 2025 -0800 @@ -0,0 +1,11 @@ +load("//third_party/raylib:raylib.bzl", "raylib_binary") + +raylib_binary( + name = "main", + srcs = ["main.c"], + deps = [ + "//third_party/raylib:raylib", + "//dowa:dowa", + ], + static = True +) diff -r ccb42d5bf8fd -r e06bc03d9618 color_game/main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/color_game/main.c Sat Dec 20 10:53:13 2025 -0800 @@ -0,0 +1,775 @@ +#include +#include +#include +#include "dowa/dowa.h" +#include "third_party/raylib/include/raylib.h" + +#define INIT_SCREEN_WIDTH 1200 +#define INIT_SCREEN_HEIGHT 700 +#define PLAYER_SPEED 200.0f +#define PLAYER_RADIUS 20.0f +#define MONSTER_SPEED 100.0f +#define MONSTER_RADIUS 15.0f +#define BULLET_SPEED 300.0f +#define BULLET_RADIUS 5.0f +#define BULLET_LIFETIME 3.0f +#define SHOOT_INTERVAL 0.5f +#define HIT_PROBABILITY 0.7f // 70% aim accuracy (0.0 = random, 1.0 = perfect) +#define MAX_PASSIVE_NODES 12 +#define PASSIVE_TREE_RADIUS 250.0f +#define PASSIVE_NODE_RADIUS 20.0f +#define BASE_DAMAGE 10.0f +#define PLAYER_MAX_HEALTH 100.0f +#define MONSTER_CONTACT_DAMAGE 5.0f +#define BOSS_HEALTH_MULTIPLIER 5.0f +#define COLOR_UNLOCK_TIME 30.0f +#define BOSS_SPAWN_TIME 60.0f + +typedef struct Player { + Vector2 position; + Color color; + float health; + float maxHealth; + int unlockedBulletTypes[MAX_PASSIVE_NODES]; + int unlockedCount; + int passivePoints; + float invulnerabilityTimer; +} Player; + +typedef struct Map { + float width; + float height; +} Map; + +typedef struct Monster { + Vector2 position; + float hue; + float saturation; + float health; + float maxHealth; + bool alive; + bool hasCollision; + bool isBoss; +} Monster; + +typedef struct Bullet { + Vector2 position; + Vector2 velocity; + float lifetime; + float hue; + float damage; + bool active; +} Bullet; + +typedef struct PassiveNode { + float angle; + float hue; + char description[64]; + float damageBonus; + bool unlocked; +} PassiveNode; + +float RandomFloat(float min, float max) +{ + return min + ((float)rand() / (float)RAND_MAX) * (max - min); +} + +float CalculateColorDamage(float bulletHue, float monsterHue, float baseDamage) +{ + // Calculate hue difference (0-180 degrees) + float diff = fabs(bulletHue - monsterHue); + if (diff > 180.0f) diff = 360.0f - diff; + + // Opposite colors (180 degrees) = 2x damage + // Same color (0 degrees) = 0.5x damage + // Linear scale between them + float damageMultiplier = 0.5f + (diff / 180.0f) * 1.5f; + + return baseDamage * damageMultiplier; +} + +void InitializePassiveTree(PassiveNode* nodes) +{ + const char* nodeDescriptions[MAX_PASSIVE_NODES] = { + "Red Bullet", // 0° + "Orange Bullet", // 30° + "Yellow Bullet", // 60° + "Chartreuse", // 90° + "Green Bullet", // 120° + "Spring Green", // 150° + "Cyan Bullet", // 180° + "Azure Bullet", // 210° + "Blue Bullet", // 240° + "Violet Bullet", // 270° + "Magenta Bullet", // 300° + "Rose Bullet" // 330° + }; + + for (int i = 0; i < MAX_PASSIVE_NODES; i++) + { + nodes[i].angle = (360.0f / MAX_PASSIVE_NODES) * i; + nodes[i].hue = nodes[i].angle; + snprintf(nodes[i].description, sizeof(nodes[i].description), "%s", nodeDescriptions[i]); + nodes[i].damageBonus = 5.0f + (i * 2.0f); // Scaling damage bonus + nodes[i].unlocked = false; + } +} + +void HandlePlayerMovement(Player* player, Map* map, float deltaTime) +{ + Vector2 movement = {0}; + + if (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP)) movement.y -= 1.0f; + if (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN)) movement.y += 1.0f; + if (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT)) movement.x -= 1.0f; + if (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT)) movement.x += 1.0f; + + // Normalize diagonal movement + float length = sqrtf(movement.x * movement.x + movement.y * movement.y); + if (length > 0) + { + movement.x /= length; + movement.y /= length; + } + + // Apply movement + player->position.x += movement.x * PLAYER_SPEED * deltaTime; + player->position.y += movement.y * PLAYER_SPEED * deltaTime; + + // Keep player within map bounds + if (player->position.x < PLAYER_RADIUS) player->position.x = PLAYER_RADIUS; + if (player->position.x > map->width - PLAYER_RADIUS) + player->position.x = map->width - PLAYER_RADIUS; + if (player->position.y < PLAYER_RADIUS) player->position.y = PLAYER_RADIUS; + if (player->position.y > map->height - PLAYER_RADIUS) + player->position.y = map->height - PLAYER_RADIUS; +} + +void UpdateMonsters(Monster* monsters, int monsterCount, Vector2 playerPos, float deltaTime) +{ + for (int i = 0; i < monsterCount; i++) + { + if (!monsters[i].alive) continue; + + // Calculate direction to player + Vector2 direction = { + playerPos.x - monsters[i].position.x, + playerPos.y - monsters[i].position.y + }; + + // Normalize direction + float length = sqrtf(direction.x * direction.x + direction.y * direction.y); + if (length > 0) + { + direction.x /= length; + direction.y /= length; + } + + // Move toward player + Vector2 newPos = { + monsters[i].position.x + direction.x * MONSTER_SPEED * deltaTime, + monsters[i].position.y + direction.y * MONSTER_SPEED * deltaTime + }; + + // Check collision with other monsters if this monster has collision enabled + if (monsters[i].hasCollision) + { + bool collision = false; + for (int j = 0; j < monsterCount; j++) + { + if (i == j || !monsters[j].alive || !monsters[j].hasCollision) continue; + + float dx = newPos.x - monsters[j].position.x; + float dy = newPos.y - monsters[j].position.y; + float distance = sqrtf(dx * dx + dy * dy); + + if (distance < MONSTER_RADIUS * 2) + { + collision = true; + break; + } + } + + if (!collision) + { + monsters[i].position = newPos; + } + } + else + { + monsters[i].position = newPos; + } + } +} + +void CheckPlayerMonsterCollision(Player* player, Monster* monsters, int monsterCount, float deltaTime) +{ + if (player->invulnerabilityTimer > 0) + { + player->invulnerabilityTimer -= deltaTime; + return; + } + + for (int i = 0; i < monsterCount; i++) + { + if (!monsters[i].alive) continue; + + float dx = player->position.x - monsters[i].position.x; + float dy = player->position.y - monsters[i].position.y; + float distance = sqrtf(dx * dx + dy * dy); + + if (distance < PLAYER_RADIUS + MONSTER_RADIUS) + { + player->health -= MONSTER_CONTACT_DAMAGE; + player->invulnerabilityTimer = 0.5f; // 0.5 second invulnerability + if (player->health < 0) player->health = 0; + break; + } + } +} + +void UpdateBullets(Bullet* bullets, int bulletCount, float deltaTime) +{ + for (int i = 0; i < bulletCount; i++) + { + if (!bullets[i].active) continue; + + // Update position + bullets[i].position.x += bullets[i].velocity.x * deltaTime; + bullets[i].position.y += bullets[i].velocity.y * deltaTime; + + // Update lifetime + bullets[i].lifetime -= deltaTime; + if (bullets[i].lifetime <= 0) + { + bullets[i].active = false; + } + } +} + +int CheckCollisions(Bullet* bullets, int bulletCount, Monster* monsters, int monsterCount, Player* player) +{ + int bossesKilled = 0; + + for (int i = 0; i < bulletCount; i++) + { + if (!bullets[i].active) continue; + + for (int j = 0; j < monsterCount; j++) + { + if (!monsters[j].alive) continue; + + // Check distance between bullet and monster + float dx = bullets[i].position.x - monsters[j].position.x; + float dy = bullets[i].position.y - monsters[j].position.y; + float distance = sqrtf(dx * dx + dy * dy); + + if (distance < BULLET_RADIUS + MONSTER_RADIUS) + { + // Calculate damage based on color difference + float damage = CalculateColorDamage(bullets[i].hue, monsters[j].hue, bullets[i].damage); + monsters[j].health -= damage; + + // Check if monster died + if (monsters[j].health <= 0) + { + monsters[j].alive = false; + + // Award passive point if boss was killed + if (monsters[j].isBoss) + { + player->passivePoints++; + bossesKilled++; + } + } + + bullets[i].active = false; + break; + } + } + } + + return bossesKilled; +} + +Vector2 FindNearestMonster(Monster* monsters, int monsterCount, Vector2 playerPos) +{ + float minDistance = INFINITY; + Vector2 nearestPos = playerPos; + bool found = false; + + for (int i = 0; i < monsterCount; i++) + { + if (!monsters[i].alive) continue; + + float dx = monsters[i].position.x - playerPos.x; + float dy = monsters[i].position.y - playerPos.y; + float distance = sqrtf(dx * dx + dy * dy); + + if (distance < minDistance) + { + minDistance = distance; + nearestPos = monsters[i].position; + found = true; + } + } + + if (!found) + { + // No monsters, return random direction + float angle = RandomFloat(0.0f, 2.0f * PI); + return (Vector2){ + playerPos.x + cosf(angle) * 100.0f, + playerPos.y + sinf(angle) * 100.0f + }; + } + + return nearestPos; +} + +int main() +{ + InitWindow(INIT_SCREEN_WIDTH, INIT_SCREEN_HEIGHT, "color game"); + SetTargetFPS(60); + srand(time(NULL)); + + // --- All Global Variables --- + Map map = { + .width = INIT_SCREEN_WIDTH * 3, + .height = INIT_SCREEN_HEIGHT * 3 + }; + + // Initialize player at map center + Player player = { + .position = {map.width / 2, map.height / 2}, + .color = BLUE, + .health = PLAYER_MAX_HEALTH, + .maxHealth = PLAYER_MAX_HEALTH, + .unlockedCount = 0, + .passivePoints = 0, + .invulnerabilityTimer = 0.0f + }; + + // Initialize passive tree + PassiveNode passiveTree[MAX_PASSIVE_NODES]; + InitializePassiveTree(passiveTree); + + // Unlock first node only (grayscale bullet to start) + passiveTree[0].unlocked = true; + player.unlockedBulletTypes[player.unlockedCount++] = 0; + + // Initialize camera + Camera2D camera = {0}; + camera.target = player.position; + camera.offset = (Vector2){INIT_SCREEN_WIDTH / 2.0f, INIT_SCREEN_HEIGHT / 2.0f}; + camera.rotation = 0.0f; + camera.zoom = 1.0f; + + bool menuOpen = false; + bool showMonsterColors = false; + + Vector2 menuCenter = { + .x = INIT_SCREEN_WIDTH / 2, + .y = INIT_SCREEN_HEIGHT / 2 + }; + + // Game progression variables + float gameTime = 0.0f; + float currentMonsterHue = 0.0f; + float currentMonsterSaturation = 0.0f; // Start grayscale + int bossesDefeated = 0; + float nextBossTime = BOSS_SPAWN_TIME; + bool colorUnlocked = false; + + // Initialize monsters array + #define MAX_MONSTERS 100 + Monster live_monsters[MAX_MONSTERS]; + int monsterCount = 0; + + // Spawn initial monsters (grayscale) + for (int i = 0; i < 10; i++) + { + live_monsters[monsterCount++] = (Monster){ + .position = {RandomFloat(0, map.width), RandomFloat(0, map.height)}, + .hue = currentMonsterHue, + .saturation = currentMonsterSaturation, + .health = 50.0f, + .maxHealth = 50.0f, + .alive = true, + .hasCollision = true, + .isBoss = false + }; + } + + // Initialize bullets array + #define MAX_BULLETS 200 + Bullet bullets[MAX_BULLETS]; + int bulletCount = 0; + for (int i = 0; i < MAX_BULLETS; i++) + { + bullets[i].active = false; + } + + // Shooting timer + float shootTimer = 0.0f; + float hitProbability = HIT_PROBABILITY; + + while (!WindowShouldClose()) + { + float deltaTime = GetFrameTime(); + + if (IsKeyPressed(KEY_P)) + { + menuOpen = !menuOpen; + } + + if (IsKeyPressed(KEY_R)) + { + showMonsterColors = !showMonsterColors; + } + + // Handle passive tree menu interactions + if (menuOpen) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + Vector2 mousePos = GetMousePosition(); + + // Check if clicked on a passive node + for (int i = 0; i < MAX_PASSIVE_NODES; i++) + { + float angleRad = passiveTree[i].angle * DEG2RAD; + Vector2 nodePos = { + menuCenter.x + cosf(angleRad) * PASSIVE_TREE_RADIUS, + menuCenter.y + sinf(angleRad) * PASSIVE_TREE_RADIUS + }; + + float dx = mousePos.x - nodePos.x; + float dy = mousePos.y - nodePos.y; + float distance = sqrtf(dx * dx + dy * dy); + + if (distance < PASSIVE_NODE_RADIUS && !passiveTree[i].unlocked && player.passivePoints > 0) + { + // Unlock the node + passiveTree[i].unlocked = true; + player.unlockedBulletTypes[player.unlockedCount++] = i; + player.passivePoints--; + break; + } + } + } + } + + // Only handle game logic when menu is closed + if (!menuOpen) + { + // Update game time and progression + gameTime += deltaTime; + + // Unlock color after COLOR_UNLOCK_TIME + if (!colorUnlocked && gameTime >= COLOR_UNLOCK_TIME) + { + colorUnlocked = true; + currentMonsterSaturation = 1.0f; + } + + // Spawn boss and unlock new color + if (gameTime >= nextBossTime && monsterCount < MAX_MONSTERS) + { + // Generate new color for this boss cycle + currentMonsterHue = RandomFloat(0, 360); + nextBossTime += BOSS_SPAWN_TIME; + + // Spawn boss monster + live_monsters[monsterCount++] = (Monster){ + .position = {RandomFloat(0, map.width), RandomFloat(0, map.height)}, + .hue = currentMonsterHue, + .saturation = currentMonsterSaturation, + .health = 50.0f * BOSS_HEALTH_MULTIPLIER, + .maxHealth = 50.0f * BOSS_HEALTH_MULTIPLIER, + .alive = true, + .hasCollision = true, + .isBoss = true + }; + } + + // Spawn regular monsters periodically + static float monsterSpawnTimer = 0.0f; + monsterSpawnTimer += deltaTime; + if (monsterSpawnTimer >= 3.0f && monsterCount < MAX_MONSTERS) + { + monsterSpawnTimer = 0.0f; + live_monsters[monsterCount++] = (Monster){ + .position = {RandomFloat(0, map.width), RandomFloat(0, map.height)}, + .hue = currentMonsterHue, + .saturation = currentMonsterSaturation, + .health = 50.0f, + .maxHealth = 50.0f, + .alive = true, + .hasCollision = true, + .isBoss = false + }; + } + + HandlePlayerMovement(&player, &map, deltaTime); + + // Check player-monster collision + CheckPlayerMonsterCollision(&player, live_monsters, monsterCount, deltaTime); + + // Update shoot timer and shoot bullets + shootTimer += deltaTime; + if (shootTimer >= SHOOT_INTERVAL && player.unlockedCount > 0) + { + shootTimer = 0.0f; + + // Find inactive bullet slot + for (int i = 0; i < MAX_BULLETS; i++) + { + if (!bullets[i].active) + { + // Choose random bullet type from unlocked types + int randomIndex = (int)RandomFloat(0, player.unlockedCount); + int nodeIndex = player.unlockedBulletTypes[randomIndex]; + PassiveNode* selectedNode = &passiveTree[nodeIndex]; + + // Find nearest monster to aim at + Vector2 targetPos = FindNearestMonster(live_monsters, monsterCount, player.position); + + // Calculate direction to target + Vector2 direction = { + targetPos.x - player.position.x, + targetPos.y - player.position.y + }; + + // Normalize + float length = sqrtf(direction.x * direction.x + direction.y * direction.y); + if (length > 0) + { + direction.x /= length; + direction.y /= length; + } + + // Calculate base angle + float baseAngle = atan2f(direction.y, direction.x); + + // Add random deviation based on accuracy + float maxDeviation = (1.0f - hitProbability) * PI; + float deviation = RandomFloat(-maxDeviation, maxDeviation); + float finalAngle = baseAngle + deviation; + + bullets[i] = (Bullet){ + .position = player.position, + .velocity = { + cosf(finalAngle) * BULLET_SPEED, + sinf(finalAngle) * BULLET_SPEED + }, + .lifetime = BULLET_LIFETIME, + .hue = selectedNode->hue, + .damage = BASE_DAMAGE + selectedNode->damageBonus, + .active = true + }; + break; + } + } + } + + // Update monsters + UpdateMonsters(live_monsters, monsterCount, player.position, deltaTime); + + // Update bullets + UpdateBullets(bullets, MAX_BULLETS, deltaTime); + + // Check collisions + int bossKills = CheckCollisions(bullets, MAX_BULLETS, live_monsters, monsterCount, &player); + bossesDefeated += bossKills; + + // Update camera to follow player + camera.target = player.position; + + // Clamp camera to map edges + float minX = INIT_SCREEN_WIDTH / 2.0f; + float maxX = map.width - INIT_SCREEN_WIDTH / 2.0f; + float minY = INIT_SCREEN_HEIGHT / 2.0f; + float maxY = map.height - INIT_SCREEN_HEIGHT / 2.0f; + + if (camera.target.x < minX) camera.target.x = minX; + if (camera.target.x > maxX) camera.target.x = maxX; + if (camera.target.y < minY) camera.target.y = minY; + if (camera.target.y > maxY) camera.target.y = maxY; + } + + // --- Drawings --- + BeginDrawing(); + ClearBackground(RAYWHITE); + + if (menuOpen) + { + // Draw color wheel background + for (int i = 0; i < 360; i++) + { + Color color = ColorFromHSV((float)i, 1.0f, 1.0f); + DrawCircleSector( + menuCenter, + PASSIVE_TREE_RADIUS + 30.0f, + (float)i, + (float)(i + 1), + 1, + color + ); + } + + // Draw passive tree nodes + for (int i = 0; i < MAX_PASSIVE_NODES; i++) + { + float angleRad = passiveTree[i].angle * DEG2RAD; + Vector2 nodePos = { + menuCenter.x + cosf(angleRad) * PASSIVE_TREE_RADIUS, + menuCenter.y + sinf(angleRad) * PASSIVE_TREE_RADIUS + }; + + Color nodeColor = ColorFromHSV(passiveTree[i].hue, 1.0f, 1.0f); + + if (passiveTree[i].unlocked) + { + // Draw unlocked node + DrawCircleV(nodePos, PASSIVE_NODE_RADIUS, nodeColor); + DrawCircleV(nodePos, PASSIVE_NODE_RADIUS - 3, WHITE); + DrawCircleV(nodePos, PASSIVE_NODE_RADIUS - 6, nodeColor); + } + else + { + // Draw locked node + DrawCircleV(nodePos, PASSIVE_NODE_RADIUS, DARKGRAY); + DrawCircleV(nodePos, PASSIVE_NODE_RADIUS - 3, GRAY); + } + + // Draw node info on hover + Vector2 mousePos = GetMousePosition(); + float dx = mousePos.x - nodePos.x; + float dy = mousePos.y - nodePos.y; + float distance = sqrtf(dx * dx + dy * dy); + + if (distance < PASSIVE_NODE_RADIUS) + { + DrawText(passiveTree[i].description, nodePos.x - 50, nodePos.y - 40, 12, BLACK); + DrawText(TextFormat("+%.0f dmg", passiveTree[i].damageBonus), nodePos.x - 30, nodePos.y - 28, 10, DARKGRAY); + } + } + + // Draw menu instructions + DrawText("PASSIVE TREE MENU", menuCenter.x - 100, menuCenter.y - 350, 20, BLACK); + DrawText("Click nodes to unlock bullet types", menuCenter.x - 120, menuCenter.y - 330, 16, DARKGRAY); + DrawText(TextFormat("Passive Points Available: %d", player.passivePoints), menuCenter.x - 120, menuCenter.y - 310, 16, PURPLE); + DrawText("Defeat bosses to earn passive points!", menuCenter.x - 120, menuCenter.y - 290, 14, DARKGRAY); + DrawText("Press P to close", menuCenter.x - 70, menuCenter.y + 320, 18, BLACK); + } + else + { + // Draw game world (with camera) + BeginMode2D(camera); + + // Draw map background + DrawRectangle(0, 0, map.width, map.height, (Color){240, 240, 240, 255}); + + // Draw map border + DrawRectangleLinesEx((Rectangle){0, 0, map.width, map.height}, 5.0f, DARKGRAY); + + // Draw grid to show map scale + for (int i = 0; i <= map.width; i += 100) + { + DrawLine(i, 0, i, map.height, LIGHTGRAY); + } + for (int i = 0; i <= map.height; i += 100) + { + DrawLine(0, i, map.width, i, LIGHTGRAY); + } + + // Draw monsters + for (int i = 0; i < monsterCount; i++) + { + if (live_monsters[i].alive) + { + Color monsterColor = ColorFromHSV(live_monsters[i].hue, live_monsters[i].saturation, 1.0f); + float radius = live_monsters[i].isBoss ? MONSTER_RADIUS * 2 : MONSTER_RADIUS; + + DrawCircleV(live_monsters[i].position, radius, monsterColor); + + // Draw boss crown indicator + if (live_monsters[i].isBoss) + { + DrawCircleV(live_monsters[i].position, radius - 5, YELLOW); + DrawCircleV(live_monsters[i].position, radius - 10, monsterColor); + } + + // Draw health bar + float healthPercent = live_monsters[i].health / live_monsters[i].maxHealth; + Vector2 barPos = {live_monsters[i].position.x - 15, live_monsters[i].position.y - radius - 10}; + DrawRectangle(barPos.x, barPos.y, 30, 4, DARKGRAY); + DrawRectangle(barPos.x, barPos.y, 30 * healthPercent, 4, GREEN); + + // Draw color label if toggle is on + if (showMonsterColors) + { + char colorText[32]; + snprintf(colorText, sizeof(colorText), "H:%.0f S:%.1f", live_monsters[i].hue, live_monsters[i].saturation); + DrawText(colorText, live_monsters[i].position.x - 25, live_monsters[i].position.y + radius + 5, 10, BLACK); + } + } + } + + // Draw bullets + for (int i = 0; i < MAX_BULLETS; i++) + { + if (bullets[i].active) + { + Color bulletColor = ColorFromHSV(bullets[i].hue, 1.0f, 1.0f); + DrawCircleV(bullets[i].position, BULLET_RADIUS, bulletColor); + } + } + + // Draw player (flash when invulnerable) + if (player.invulnerabilityTimer <= 0 || ((int)(player.invulnerabilityTimer * 10) % 2 == 0)) + { + DrawCircleV(player.position, PLAYER_RADIUS, player.color); + } + + EndMode2D(); + + // Draw UI (screen space) + // Timer at top center + int minutes = (int)(gameTime / 60); + int seconds = (int)gameTime % 60; + DrawText(TextFormat("Time: %02d:%02d", minutes, seconds), INIT_SCREEN_WIDTH / 2 - 60, 10, 30, BLACK); + + // Player health bar + float healthPercent = player.health / player.maxHealth; + DrawRectangle(10, 10, 200, 20, DARKGRAY); + DrawRectangle(10, 10, 200 * healthPercent, 20, RED); + DrawText(TextFormat("HP: %.0f/%.0f", player.health, player.maxHealth), 15, 12, 16, WHITE); + + // Passive points + DrawText(TextFormat("Passive Points: %d", player.passivePoints), 10, 40, 20, PURPLE); + + // Controls + DrawText("WASD: Move | P: Menu | R: Toggle Colors", 10, 70, 16, DARKGRAY); + + // Count alive monsters and bosses + int aliveCount = 0; + int bossCount = 0; + for (int i = 0; i < monsterCount; i++) + { + if (live_monsters[i].alive) + { + aliveCount++; + if (live_monsters[i].isBoss) bossCount++; + } + } + DrawText(TextFormat("Monsters: %d | Bosses: %d", aliveCount, bossCount), 10, 95, 18, DARKGRAY); + DrawText(TextFormat("Unlocked bullets: %d/%d", player.unlockedCount, MAX_PASSIVE_NODES), 10, 120, 16, DARKGRAY); + DrawText(TextFormat("Bosses defeated: %d", bossesDefeated), 10, 145, 16, DARKGRAY); + } + + EndDrawing(); + } + return 0; +} diff -r ccb42d5bf8fd -r e06bc03d9618 dowa/dowa.h --- a/dowa/dowa.h Sat Dec 20 09:33:15 2025 -0800 +++ b/dowa/dowa.h Sat Dec 20 10:53:13 2025 -0800 @@ -6,6 +6,7 @@ #include // only for malloc, free, stuff #include // mostly for TODO #include // some functions loop through files +#include // I am not re-writing stuff I guess... #include #include @@ -70,7 +71,7 @@ DOWA_HASH_MAP_TYPE_INT // Integer value } Dowa_HashMap_ValueType; -typedef struct { +typedef struct Dowa_HashEntry { char *key; void *buffer; size_t capacity;