Mercurial
diff color_game/main.c @ 76:35b1abc37969
Updating my website to use seobeo library.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Wed, 31 Dec 2025 14:11:21 -0800 |
| parents | fff1b048dda6 |
| children | 8d17f6e6e290 |
line wrap: on
line diff
--- a/color_game/main.c Wed Dec 31 11:20:08 2025 -0800 +++ b/color_game/main.c Wed Dec 31 14:11:21 2025 -0800 @@ -1,913 +1,17 @@ -#include <stdio.h> -#include <stdlib.h> -#include <time.h> -#include <math.h> #include "third_party/raylib/include/raylib.h" - +#include "dowa/dowa.h" #define INIT_SCREEN_WIDTH 1200 #define INIT_SCREEN_HEIGHT 700 -#define PLAYER_SPEED 200.0f -#define PLAYER_RADIUS 20.0f -#define PLAYER_MAX_HEALTH 100.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 MONSTER_WEIGHT 10.0f -#define MONSTER_CONTACT_DAMAGE 5.0f - -#define BASE_DAMAGE 10.0f -#define BOSS_HEALTH_MULTIPLIER 5.0f -#define BOSS_MONSTER_WEIGHT 10.0f -#define BOSS_SPAWN_TIME 10.0f - -#define COLOR_UNLOCK_TIME 5.0f -#define MAX_DAMAGE_NUMBERS 100 -#define DAMAGE_NUMBER_LIFETIME 1.0f -#define EXP_PER_MONSTER 10.0f -#define EXP_PER_BOSS 50.0f -#define EXP_TO_LEVEL 100.0f - -typedef struct Player { - Vector2 position; - float health; - float maxHealth; - float invulnerabilityTimer; - int unlockedBulletTypes[MAX_PASSIVE_NODES]; - int unlockedCount; - int passivePoints; - int level; - float experience; - float expToNextLevel; - Color color; -} Player; - -typedef struct DamageNumber { - Vector2 position; - float damage; - float lifetime; - bool active; - Color color; -} DamageNumber; - -typedef struct Map { - float width; - float height; -} Map; - -typedef struct Monster { - Vector2 position; - float hue; - float saturation; - float health; - float maxHealth; - float weight; - 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 SpawnDamageNumber(DamageNumber* damageNumbers, int maxCount, Vector2 position, float damage, Color color) -{ - for (int i = 0; i < maxCount; i++) - { - if (!damageNumbers[i].active) - { - damageNumbers[i].position = position; - damageNumbers[i].damage = damage; - damageNumbers[i].lifetime = DAMAGE_NUMBER_LIFETIME; - damageNumbers[i].active = true; - damageNumbers[i].color = color; - break; - } - } -} - -void UpdateDamageNumbers(DamageNumber* damageNumbers, int count, float deltaTime) -{ - for (int i = 0; i < count; i++) - { - if (!damageNumbers[i].active) continue; - - damageNumbers[i].lifetime -= deltaTime; - damageNumbers[i].position.y -= 30.0f * deltaTime; // Float upward - - if (damageNumbers[i].lifetime <= 0) - { - damageNumbers[i].active = false; - } - } -} - -void AddExperience(Player* player, float exp) -{ - player->experience += exp; - - // Check for level up - while (player->experience >= player->expToNextLevel) - { - player->experience -= player->expToNextLevel; - player->level++; - player->passivePoints++; - // Increase exp requirement for next level - player->expToNextLevel = EXP_TO_LEVEL * player->level; - } -} - -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 canMove = true; - 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) - { - // If this monster is heavier, push the other monster away - if (monsters[i].weight > monsters[j].weight) - { - // Calculate push direction (away from current monster) - Vector2 pushDir = {dx, dy}; - float pushLength = sqrtf(pushDir.x * pushDir.x + pushDir.y * pushDir.y); - if (pushLength > 0) - { - pushDir.x /= pushLength; - pushDir.y /= pushLength; - } - - // Push the lighter monster away - float pushForce = (monsters[i].weight - monsters[j].weight) * MONSTER_SPEED * deltaTime * 0.5f; - monsters[j].position.x += pushDir.x * pushForce; - monsters[j].position.y += pushDir.y * pushForce; - } - else - { - // This monster is lighter or equal weight, can't move through - canMove = false; - break; - } - } - } - - if (canMove) - { - 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, DamageNumber* damageNumbers) -{ - 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; - - // Spawn damage number - Color damageColor = damage > bullets[i].damage * 1.5f ? ORANGE : WHITE; - SpawnDamageNumber(damageNumbers, MAX_DAMAGE_NUMBERS, monsters[j].position, damage, damageColor); - - // Check if monster died - if (monsters[j].health <= 0) - { - monsters[j].alive = false; - - // Award experience - float expGain = monsters[j].isBoss ? EXP_PER_BOSS : EXP_PER_MONSTER; - AddExperience(player, expGain); - - // 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) - break; - } - - 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 - }; - - Player player = { - .position = {map.width / 2, map.height / 2}, - .color = BLUE, - .health = PLAYER_MAX_HEALTH, - .maxHealth = PLAYER_MAX_HEALTH, - .unlockedCount = 0, - .passivePoints = 3, - .invulnerabilityTimer = 0.0f, - .level = 1, - .experience = 0.0f, - .expToNextLevel = EXP_TO_LEVEL - }; - - PassiveNode passiveTree[MAX_PASSIVE_NODES]; - InitializePassiveTree(passiveTree); - passiveTree[0].unlocked = true; - player.unlockedBulletTypes[player.unlockedCount++] = 0; - - // Initialize damage numbers array - DamageNumber damageNumbers[MAX_DAMAGE_NUMBERS]; - for (int i = 0; i < MAX_DAMAGE_NUMBERS; i++) - { - damageNumbers[i].active = false; - } - - 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 - }; - - 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 1000 - Monster live_monsters[MAX_MONSTERS]; - int monsterCount = 0; - - // Spawn initial monsters (grayscale) - for (int i = 0; i < 30; i++) - { - live_monsters[monsterCount++] = (Monster){ - .position = {RandomFloat(0, map.width), RandomFloat(0, map.height)}, - .hue = currentMonsterHue, - .saturation = currentMonsterSaturation, - .weight = MONSTER_WEIGHT, - .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()) + Vector2 pos = {.x=INIT_SCREEN_WIDTH/2, .y=INIT_SCREEN_HEIGHT/2}; + InitWindow(INIT_SCREEN_WIDTH, INIT_SCREEN_HEIGHT, "Color Game"); + 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, - .weight = BOSS_MONSTER_WEIGHT, - .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); - - // Update damage numbers - UpdateDamageNumbers(damageNumbers, MAX_DAMAGE_NUMBERS, deltaTime); - - // Check collisions - int bossKills = CheckCollisions(bullets, MAX_BULLETS, live_monsters, monsterCount, &player, damageNumbers); - 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 damage numbers - for (int i = 0; i < MAX_DAMAGE_NUMBERS; i++) - { - if (damageNumbers[i].active) - { - printf("True\n"); - float alpha = damageNumbers[i].lifetime / DAMAGE_NUMBER_LIFETIME; - Color color = damageNumbers[i].color; - color.a = (unsigned char)(255 * alpha); - DrawText(TextFormat("%.0f", damageNumbers[i].damage), - damageNumbers[i].position.x - 10, - damageNumbers[i].position.y, - 20, - color); - } - } - - // 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); - - // Experience bar - float expPercent = player.experience / player.expToNextLevel; - DrawRectangle(10, 35, 200, 15, DARKGRAY); - DrawRectangle(10, 35, 200 * expPercent, 15, SKYBLUE); - DrawText(TextFormat("Lvl %d", player.level), 15, 36, 12, WHITE); - DrawText(TextFormat("%.0f/%.0f XP", player.experience, player.expToNextLevel), 70, 36, 12, WHITE); - - // Passive points - DrawText(TextFormat("Passive Points: %d", player.passivePoints), 10, 55, 18, PURPLE); - - // Controls - DrawText("WASD: Move | P: Menu | R: Toggle Colors", 10, 80, 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, 105, 18, DARKGRAY); - DrawText(TextFormat("Unlocked bullets: %d/%d", player.unlockedCount, MAX_PASSIVE_NODES), 10, 130, 16, DARKGRAY); - DrawText(TextFormat("Bosses defeated: %d", bossesDefeated), 10, 155, 16, DARKGRAY); - } - + DrawPixelV(pos, RED); EndDrawing(); } - return 0; }