Mercurial
diff color_game/main.c @ 63:fff1b048dda6
[Postdog] Fixed a problem where string did not wrap.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Tue, 23 Dec 2025 14:00:37 -0800 |
| parents | 9df5587cf23b |
| children | 35b1abc37969 |
line wrap: on
line diff
--- a/color_game/main.c Tue Dec 23 11:48:11 2025 -0800 +++ b/color_game/main.c Tue Dec 23 14:00:37 2025 -0800 @@ -9,34 +9,58 @@ #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 BASE_DAMAGE 10.0f -#define PLAYER_MAX_HEALTH 100.0f + +#define MONSTER_WEIGHT 10.0f #define MONSTER_CONTACT_DAMAGE 5.0f + +#define BASE_DAMAGE 10.0f #define BOSS_HEALTH_MULTIPLIER 5.0f -#define COLOR_UNLOCK_TIME 30.0f -#define BOSS_SPAWN_TIME 60.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; - Color color; float health; float maxHealth; + float invulnerabilityTimer; int unlockedBulletTypes[MAX_PASSIVE_NODES]; int unlockedCount; int passivePoints; - float invulnerabilityTimer; + 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; @@ -48,6 +72,7 @@ float saturation; float health; float maxHealth; + float weight; bool alive; bool hasCollision; bool isBoss; @@ -89,6 +114,53 @@ 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] = { @@ -175,7 +247,7 @@ // Check collision with other monsters if this monster has collision enabled if (monsters[i].hasCollision) { - bool collision = false; + bool canMove = true; for (int j = 0; j < monsterCount; j++) { if (i == j || !monsters[j].alive || !monsters[j].hasCollision) continue; @@ -186,12 +258,33 @@ if (distance < MONSTER_RADIUS * 2) { - collision = true; - break; + // 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 (!collision) + if (canMove) { monsters[i].position = newPos; } @@ -248,7 +341,7 @@ } } -int CheckCollisions(Bullet* bullets, int bulletCount, Monster* monsters, int monsterCount, Player* player) +int CheckCollisions(Bullet* bullets, int bulletCount, Monster* monsters, int monsterCount, Player* player, DamageNumber* damageNumbers) { int bossesKilled = 0; @@ -271,11 +364,19 @@ 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) { @@ -313,6 +414,9 @@ nearestPos = monsters[i].position; found = true; } + + if (found) + break; } if (!found) @@ -340,26 +444,31 @@ .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 + .passivePoints = 3, + .invulnerabilityTimer = 0.0f, + .level = 1, + .experience = 0.0f, + .expToNextLevel = EXP_TO_LEVEL }; - // 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 + // 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}; @@ -374,7 +483,6 @@ .y = INIT_SCREEN_HEIGHT / 2 }; - // Game progression variables float gameTime = 0.0f; float currentMonsterHue = 0.0f; float currentMonsterSaturation = 0.0f; // Start grayscale @@ -383,17 +491,18 @@ bool colorUnlocked = false; // Initialize monsters array - #define MAX_MONSTERS 100 + #define MAX_MONSTERS 1000 Monster live_monsters[MAX_MONSTERS]; int monsterCount = 0; // Spawn initial monsters (grayscale) - for (int i = 0; i < 10; i++) + 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, @@ -488,6 +597,7 @@ .saturation = currentMonsterSaturation, .health = 50.0f * BOSS_HEALTH_MULTIPLIER, .maxHealth = 50.0f * BOSS_HEALTH_MULTIPLIER, + .weight = BOSS_MONSTER_WEIGHT, .alive = true, .hasCollision = true, .isBoss = true @@ -580,8 +690,11 @@ // 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); + int bossKills = CheckCollisions(bullets, MAX_BULLETS, live_monsters, monsterCount, &player, damageNumbers); bossesDefeated += bossKills; // Update camera to follow player @@ -728,6 +841,23 @@ } } + // 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)) { @@ -748,11 +878,18 @@ 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, 40, 20, PURPLE); + DrawText(TextFormat("Passive Points: %d", player.passivePoints), 10, 55, 18, PURPLE); // Controls - DrawText("WASD: Move | P: Menu | R: Toggle Colors", 10, 70, 16, DARKGRAY); + DrawText("WASD: Move | P: Menu | R: Toggle Colors", 10, 80, 16, DARKGRAY); // Count alive monsters and bosses int aliveCount = 0; @@ -765,9 +902,9 @@ 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); + 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); } EndDrawing();