# HG changeset patch # User June Park # Date 1767219081 28800 # Node ID 35b1abc379690dc041ceb79ed0e39bbd23e86473 # Parent ae6a88e6e4849d2cc3c7677ac19e812fe54e681a Updating my website to use seobeo library. diff -r ae6a88e6e484 -r 35b1abc37969 .claude/settings.local.json --- a/.claude/settings.local.json Wed Dec 31 11:20:08 2025 -0800 +++ b/.claude/settings.local.json Wed Dec 31 14:11:21 2025 -0800 @@ -2,7 +2,10 @@ "permissions": { "allow": [ "Bash(ls:*)", - "Bash(hg status:*)" + "Bash(hg status:*)", + "Bash(hg:*)", + "Bash(sqlite3:*)", + "Bash(bazel build:*)" ] } } diff -r ae6a88e6e484 -r 35b1abc37969 color_game/BUILD --- a/color_game/BUILD Wed Dec 31 11:20:08 2025 -0800 +++ b/color_game/BUILD Wed Dec 31 14:11:21 2025 -0800 @@ -5,6 +5,7 @@ srcs = ["main.c"], deps = [ "//third_party/raylib:raylib", + "//dowa:dowa_generic", ], static = True ) diff -r ae6a88e6e484 -r 35b1abc37969 color_game/main.c --- 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 -#include -#include -#include #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; } diff -r ae6a88e6e484 -r 35b1abc37969 dowa/BUILD --- a/dowa/BUILD Wed Dec 31 11:20:08 2025 -0800 +++ b/dowa/BUILD Wed Dec 31 14:11:21 2025 -0800 @@ -18,7 +18,6 @@ hdrs = [ "dowa.h", "dowa_internal.h", - "stb_ds.h" ], visibility = ["//visibility:public"], ) diff -r ae6a88e6e484 -r 35b1abc37969 mrjunejune/pages/base.css --- a/mrjunejune/pages/base.css Wed Dec 31 11:20:08 2025 -0800 +++ b/mrjunejune/pages/base.css Wed Dec 31 14:11:21 2025 -0800 @@ -208,3 +208,52 @@ display: flex; justify-content: center; } + +/* Auto dark mode based on system preference */ +@media (prefers-color-scheme: dark) { + :root:not(.light-mode) { + --white: hsl(224, 10%, 10%); + --black: hsl(0, 0%, 90%); + --darkgray: #cccccc; + --lightgray: #666666; + --green: #3d1ea0; + --orange: #025ccc; + --purple: #2b5b06; + --red: #04bb7a; + --blue: #932f0e; + --darktext: #bebebe; + --awesome: #23cade; + --accent: #ffcc00; + --accent-dark: #ffb275; + --gray: 159, 140, 96; + --gray-light: 26, 22, 15; + --gray-dark: 221, 214, 198; + --gray-gradient: rgba(26, 22, 15, 50%), #000; + --box-shadow: 0 -2px -6px rgba(159, 140, 96, 25%), + 0 -8px -24px rgba(159, 140, 96, 33%), 0 -16px -32px rgba(159, 140, 96, 33%); + } +} + +/* Dark mode toggle button */ +.dark-mode-toggle { + position: fixed; + top: 20px; + left: 20px; + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--white); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + transition: all 0.3s ease; + z-index: 1000; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); +} + +.dark-mode-toggle:hover { + transform: scale(1.1); + box-shadow: 0 4px 12px rgba(0,0,0,0.3); +} diff -r ae6a88e6e484 -r 35b1abc37969 mrjunejune/pages/index.html --- a/mrjunejune/pages/index.html Wed Dec 31 11:20:08 2025 -0800 +++ b/mrjunejune/pages/index.html Wed Dec 31 14:11:21 2025 -0800 @@ -1,243 +1,18 @@ -
-
-

Hi, my name is Juntae, but most people call me June.

-

I am a software engineer with experience spanning a wide range of companies, from small startups to FAANG.

-

Feel free to check out my resume below, and if you're interested, don’t hesitate to contact me for contract work ranging from web/app development to embedded programming.

-
-
-

JUNTAE PARK

-

FULL STACK DEVELOPER · SOFTWARE ENGINEER

-
- Bay Area, CA, USA -
-
-

📱(US) 650-531-1728 |📱(CA) 437-580-8026 | ✉️ parkjune1995@gmail.com | - mrjunejune - | - - - - junepark - -

-
-
- -
-

Summary

-
-
-

Software Engineer with 8 years of hands-on experience across diverse tech stacks, from early-stage startups to FANG-scale systems. Adept in designing and delivering robust software solutions using modern languages, frameworks, and cloud platforms. Open to impactful work.

-
- -

Skills

-
-
-
-

- Programming Languages: - TypeScript, Python, C++/C, Ruby, Java, MATLAB -

-

- Tools & Platforms: - Bazel, PostgresSQL, Mercurial, Git, Pands, Raylib, XCode -

-

- Web Frameworks: - Django, Rails, React, Flask -

-

- DevOp: - Plummi, Heroku, DigitalOcean, AWS, Google Cloud -

-

- Language: - English, Korean, Japanese -

-
- - -
-

Experience

-
-
-
-

- Meta -

-

San Francisco, CA, USA

-
-
-

SOFTWARE ENGINEER

- -
-
    -
  • - Took initiative on Channel Value Rule, targeting the 16% of ad traffic with both app and web destinations to improve value attribution and ROI. -
  • -
  • - Built full-stack features using React and Hack/GraphQL, contributing to scalable, production-ready systems. -
  • -
  • - Partnered with data science to design A/B tests and analyze revenue impact of ads destination. -
  • -
  • - Proposed and implemented alpha improvements to internal testing infrastructure, reducing test time by 50% and enhancing developer velocity. -
  • -
-
-

- Warner Music Group -

-

Toronto, ON, Canada

-
-
-

TECHNICAL LEAD ENGINEER

- -
-
    -
  • - Implements bazel structure for the company for TypeScript and JavaScript code base for hermiticity and stablishing standards for JavaScript and -
  • -
  • - TypeScript testing and code structures. -
  • -
  • - Led a team of five engineers in building GraphQL endpoints for client-facing applications using Apollo and AppSync, supporting over 2000 RPS and auto scaling depending on request values. -
  • -
  • - Improved application response times by up to 85% for graphQL response by updating database schema and SQL queries, eliminating N+1 queries and lack of indexes. -
  • -
  • - Developed CI/CD pipelines for backend structures. -
  • -
  • - Designed infrastructure for pub/sub, caching, and media processing logic. -
  • +

    Useful scripts

    + - -
    -

    - Google -

    -

    Toronto, ON, Canada

    -
    -
    -

    SOFTWARE ENGINEER

    - -
    -
      -
    • - Implements and maintained new features relating to App Script across google workspace platform including Gmail, sheets, and Docs.
    • -
    • - Improved a response time and render time of App Script hover card components.
    • -
    • - Collaborated with a team of developers to ensure timely and accurate delivery of features.
    • -
    • - Conducted user testing and gathered feedback to iterate on features for optimal user experience.
    • -
    - -
    -

    - Everlywell -

    -

    Toronto, ON, Canada

    -
    -
    -

    SOFTWARE ENGINEER

    - -
    -
      -
    • - Maintained Amazon amplify apps to create and deploy React web applications for companies such as NBA, Tinder, and other companies for COVID-19 at-home test kits.
    • -
    • - Implemented a script that helps accurately access and refund unused covid test kits; helping company save up to 200,000 USD.
    • -
    • - Created several Rails controllers for internal purposes; mocking end to end user experience for QA, mass refund features for CX department, and more, ultimately reducing support tickets amount by 50 percent.
    • -
    • - Implemented an audit table to help debug problems and logged which process was responsible for the change of the record using PaperTrail gems
    • -
    - -
    -

    - Spiria -

    -

    Oakville, ON, Canada

    -
    -
    -

    SOFTWARE ENGINEER

    - -
    -
      -
    • - Constructed RESTful API endpoints in multiple different frameworks such as Django, Ruby on Rails, and Flask and automated API documentation process using swagger. -
    • -
    • - Designed custom rake tasks for importing production data into newly updated data structure to meet client's needs. -
    • -
    • - Maintained or updated staging/productions servers. Debugged problems in production postgres database using ssh and postgres console on Heroku or AWS servers -
    • -
    • - Collaborated in creating automation python scripts for websites and application using selenium covering for QA eliminating 80% of QA's manual work -
    • -
    - -
    -

    - Apex Score -

    -

    Oakville, ON, Canada

    -
    -
    -

    SOFTWARE ENGINEER

    - -
    -
      -
    • - Developed custom Shapley value regression model to calculate importance of independent variables of data sets using sklearn, pandas, and numpy. -
    • -
    • - Created custom image uploader to Amazon s3 bucket using boto3 library. -
    • -
    • - Built RESTful API application using Flask framework and automated extensive API documentation pages using flask-restplus, pytest, and swagger, covering 95% of the code base. -
    • -
    • - Created an interactive graph using D3.js in Vue.js with data from Flask backend API. -
    • -
    - -
    -

    Education

    -
    -
    -
    -

    - University of British Columbia -

    -

    Kelowna, British Columbia

    -
    -
    -

    BACHELOR OF SCIENCE IN PHYSICS

    - -
    -
- + diff -r ae6a88e6e484 -r 35b1abc37969 mrjunejune/pages/index.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/pages/index.js Wed Dec 31 14:11:21 2025 -0800 @@ -0,0 +1,62 @@ +const STORAGE_KEY = 'theme-preference'; +// Get stored preference or detect system preference +function getThemePreference() +{ + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + return stored; + } + return 'auto'; +} + +function applyTheme(preference) +{ + const root = document.documentElement; + + if (preference === 'light') { + root.classList.add('light-mode'); + root.classList.remove('dark'); + } else if (preference === 'dark') { + root.classList.remove('light-mode'); + root.classList.add('dark'); + } else { + // Auto - remove manual overrides and let CSS media query handle it + root.classList.remove('light-mode', 'dark'); + } +} + +function populateHeader() +{ + fetch("/parts/headers.html") + .then(res => res.text()) + .then(headerHTML => { + const range = document.createRange(); + const fragment = range.createContextualFragment(headerHTML); + header.appendChild(fragment); + + const toggle = document.querySelector('#themeToggle'); + if (!toggle) return; + + toggle.addEventListener('click', function() { + let preference = getThemePreference(); + + // Cycle through: auto -> light -> dark -> auto + if (preference === 'auto') { + preference = 'light'; + } else if (preference === 'light') { + preference = 'dark'; + } else { + preference = 'auto'; + } + + localStorage.setItem(STORAGE_KEY, preference); + applyTheme(preference); + }); + }); +} +populateHeader(); + +(function() { + const currentPreference = getThemePreference(); + applyTheme(currentPreference); +})(); diff -r ae6a88e6e484 -r 35b1abc37969 mrjunejune/pages/markdown_to_html/index.html --- a/mrjunejune/pages/markdown_to_html/index.html Wed Dec 31 11:20:08 2025 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,257 +0,0 @@ - - - - - - Markdown to HTML Converter - - - -
-

Markdown to HTML Converter

-
- -
-
-
Markdown Input
- -
- -
-
HTML Output
-
-
-
- - - - - diff -r ae6a88e6e484 -r 35b1abc37969 mrjunejune/pages/parts/headers.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/pages/parts/headers.html Wed Dec 31 14:11:21 2025 -0800 @@ -0,0 +1,103 @@ +
+ +
+ + - diff -r ae6a88e6e484 -r 35b1abc37969 mrjunejune/pages/resume.css --- a/mrjunejune/pages/resume.css Wed Dec 31 11:20:08 2025 -0800 +++ b/mrjunejune/pages/resume.css Wed Dec 31 14:11:21 2025 -0800 @@ -13,7 +13,7 @@ .line { flex-grow: 1; - border-bottom: 1px solid #333; /* Adjust color and thickness as needed */ + border-bottom: 1px solid var(--darkgray); } .header-firstname-style { diff -r ae6a88e6e484 -r 35b1abc37969 mrjunejune/pages/resume/index.html --- a/mrjunejune/pages/resume/index.html Wed Dec 31 11:20:08 2025 -0800 +++ b/mrjunejune/pages/resume/index.html Wed Dec 31 14:11:21 2025 -0800 @@ -6,6 +6,7 @@ +

JUNTAE PARK

@@ -232,6 +233,7 @@

+ diff -r ae6a88e6e484 -r 35b1abc37969 mrjunejune/pages/tools/index.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/pages/tools/index.css Wed Dec 31 14:11:21 2025 -0800 @@ -0,0 +1,63 @@ +body { + line-height: 1.6; + padding: 20px; + max-width: 1200px; + margin: 0 auto; + background: rgb(var(--gray-light)); +} + +.title { + color: var(--darkgray); +} + +.nav-list { + list-style: none; + padding: 0; +} + +.nav-list li { + display: flex; + align-items: center; + margin-bottom: 20px; + cursor: pointer; + group; +} + +/* The Mini Sphere */ +.sphere { + width: 12px; + height: 12px; + border-radius: 50%; + margin-right: 15px; + background: var(--darkgray); + box-shadow: 0 0 10px rgba(68, 136, 255, 0.5); + transition: all 0.3s ease; + opacity: 0.5; /* Dim when not active */ +} + +/* The Text */ +.nav-list a { + color: var(--darkgray); + text-decoration: none; + font-size: 1.2rem; + transition: color 0.3s ease; +} + +/* Hover Effects */ +.nav-list li:hover .sphere { + opacity: 1; + transform: scale(1.5); + box-shadow: 0 0 20px var(--awesome); + animation: pulse 1.5s infinite; /* Only pulses on hover */ +} + +.nav-list li:hover a { + color: var(--awesome); +} + +/* Simple pulse animation for the list version */ +@keyframes pulse { + 0% { box-shadow: 0 0 0 0 rgba(68, 136, 255, 0.7); } + 70% { box-shadow: 0 0 0 10px rgba(68, 136, 255, 0); } + 100% { box-shadow: 0 0 0 0 rgba(68, 136, 255, 0); } +} diff -r ae6a88e6e484 -r 35b1abc37969 mrjunejune/pages/tools/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/pages/tools/index.html Wed Dec 31 14:11:21 2025 -0800 @@ -0,0 +1,18 @@ + + + + + + + + + +
+

Tools

+ +
+ + + diff -r ae6a88e6e484 -r 35b1abc37969 mrjunejune/pages/tools/markdown_to_html/index.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/pages/tools/markdown_to_html/index.css Wed Dec 31 14:11:21 2025 -0800 @@ -0,0 +1,183 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + line-height: 1.6; + padding: 20px; + max-width: 1200px; + margin: 0 auto; + background: rgb(var(--gray-light)); +} + +button { + background: var(--accent); + color: var(--white); + border: none; + padding: 10px 20px; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + margin-top: 10px; +} + +.title button { + margin-top: 0px; +} + +button:hover { + background: var(--accent-dark); +} + +h1 { + color: var(--darkgray); + margin-bottom: 20px; +} + +textarea { + width: 100%; + height: 500px; + padding: 15px; + border: 1px solid rgb(var(--gray-light)); + border-radius: 4px; + font-family: 'Courier New', monospace; + font-size: 14px; + resize: vertical; +} + +.container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + margin-top: 20px; +} + +.title { + display: grid; + place-items: center; + grid-template-columns: 1fr 1fr; + margin-bottom: 10px; +} + +.panel { + background: var(--white); + border-radius: 8px; + padding: 20px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +#output { + min-height: 500px; + padding: 15px; + border: 1px solid rgb(var(--gray-light)); + border-radius: 4px; + background: rgb(var(--gray-light)); +} + +#output h1, #output h2, #output h3, #output h4, #output h5, #output h6 { + margin: 20px 0 10px 0; + color: var(--darkgray); +} + +#output h1 { font-size: 2em; } +#output h2 { font-size: 1.5em; } +#output h3 { font-size: 1.3em; } + +#output p { + margin: 10px 0; +} + +#output ul, #output ol { + margin: 10px 0; + padding-left: 30px; +} + +#output li { + margin: 5px 0; +} + +#output code { + background: rgb(var(--gray-light)); + padding: 2px 6px; + border-radius: 3px; + font-family: 'Courier New', monospace; + font-size: 0.9em; +} + +#output pre { + background: #282c34; + color: #abb2bf; + padding: 15px; + border-radius: 4px; + overflow-x: auto; + margin: 10px 0; +} + +#output pre code { + background: none; + color: inherit; + padding: 0; +} + +#output blockquote { + border-left: 4px solid rgb(var(--gray-light)); + padding-left: 15px; + margin: 10px 0; + color: var(--gray); + font-style: italic; +} + +#output a { + color: var(--accent); + text-decoration: none; +} + +#output a:hover { + text-decoration: underline; +} + +#output hr { + border: none; + border-top: 2px solid rgb(var(--gray-light)); + margin: 20px 0; +} + +#output img { + max-width: 100%; + height: auto; +} + +#output strong { + font-weight: bold; +} + +#output em { + font-style: italic; +} + +#output del { + text-decoration: line-through; +} + +.header { + text-align: center; + margin-bottom: 30px; +} + +.label { + font-weight: bold; + margin-bottom: 10px; + color: var(--gray); +} + +.title .label { + margin-bottom: 0px; +} + +@media (max-width: 768px) { + .container { + grid-template-columns: 1fr; + } +} diff -r ae6a88e6e484 -r 35b1abc37969 mrjunejune/pages/tools/markdown_to_html/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/pages/tools/markdown_to_html/index.html Wed Dec 31 14:11:21 2025 -0800 @@ -0,0 +1,103 @@ + + + + + + Markdown to HTML Converter + + + + + +
+

Markdown to HTML Converter

+
+ +
+
+
Markdown Input
+ +
+ +
+
+
HTML Output
+ +
+
+
+
+ + + + + + + diff -r ae6a88e6e484 -r 35b1abc37969 seobeo/s_web.c --- a/seobeo/s_web.c Wed Dec 31 11:20:08 2025 -0800 +++ b/seobeo/s_web.c Wed Dec 31 14:11:21 2025 -0800 @@ -29,9 +29,9 @@ ); } -// Load file from disk and cache it static char* Seobeo_Web_LoadFile(const char *file_path, size_t *p_file_size) { + printf("file_path: %s\n", file_path); char full_path[1024]; snprintf(full_path, sizeof(full_path), "%s/%s", g_folder_path, file_path); @@ -60,8 +60,9 @@ return p_content; } -void Seobeo_Web_Header_Generate(void *buffer, int status, - const char *content_type, const int content_length) +void Seobeo_Web_Header_Generate( + void *buffer, int status, + const char *content_type, const int content_length) { const char *status_text; switch(status)