changeset 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 ea9ef388ab97
children a30944e5719e
files MODULE.bazel.lock color_game/main.c dowa/d_string.c dowa/dowa.h postdog/main.c
diffstat 5 files changed, 271 insertions(+), 79 deletions(-) [+]
line wrap: on
line diff
--- a/MODULE.bazel.lock	Tue Dec 23 11:48:11 2025 -0800
+++ b/MODULE.bazel.lock	Tue Dec 23 14:00:37 2025 -0800
@@ -1,5 +1,5 @@
 {
-  "lockFileVersion": 24,
+  "lockFileVersion": 18,
   "registryFileHashes": {
     "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
     "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
@@ -16,8 +16,7 @@
     "https://bcr.bazel.build/modules/abseil-py/2.1.0/source.json": "0e8fc4f088ce07099c1cd6594c20c7ddbb48b4b3c0849b7d94ba94be88ff042b",
     "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896",
     "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85",
-    "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442",
-    "https://bcr.bazel.build/modules/apple_support/1.23.1/source.json": "d888b44312eb0ad2c21a91d026753f330caa48a25c9b2102fae75eb2b0dcfdd2",
+    "https://bcr.bazel.build/modules/apple_support/1.15.1/source.json": "517f2b77430084c541bc9be2db63fdcbb7102938c5f64c17ee60ffda2e5cf07b",
     "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b",
     "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd",
     "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
@@ -28,7 +27,6 @@
     "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
     "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
     "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a",
-    "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65",
     "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d",
     "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9",
     "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
@@ -228,6 +226,37 @@
   },
   "selectedYankedVersions": {},
   "moduleExtensions": {
+    "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": {
+      "general": {
+        "bzlTransitiveDigest": "p7Ghcq3+nnQxCrf+U3xnhdn7yOSTDbcFyGHK7Ja+rU4=",
+        "usagesDigest": "EJfdUgbJdIRboG70S9dz8HhGVkL2/s3qFQ9xht8y2Rs=",
+        "recordedFileInputs": {},
+        "recordedDirentsInputs": {},
+        "envVariables": {},
+        "generatedRepoSpecs": {
+          "local_config_apple_cc_toolchains": {
+            "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf_toolchains",
+            "attributes": {}
+          },
+          "local_config_apple_cc": {
+            "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf",
+            "attributes": {}
+          }
+        },
+        "recordedRepoMappingEntries": [
+          [
+            "apple_support+",
+            "bazel_tools",
+            "bazel_tools"
+          ],
+          [
+            "bazel_tools",
+            "rules_cc",
+            "rules_cc+"
+          ]
+        ]
+      }
+    },
     "@@rules_android+//rules/android_sdk_repository:rule.bzl%android_sdk_repository_extension": {
       "general": {
         "bzlTransitiveDigest": "NAy+0M15JNVEBb8Tny6t7j3lKqTnsAMjoBB6LJ+C370=",
@@ -246,7 +275,7 @@
     },
     "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
       "general": {
-        "bzlTransitiveDigest": "rL/34P1aFDq2GqVC2zCFgQ8nTuOC6ziogocpvG50Qz8=",
+        "bzlTransitiveDigest": "OlvsB0HsvxbR8ZN+J9Vf00X/+WVz/Y/5Xrq2LgcVfdo=",
         "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=",
         "recordedFileInputs": {},
         "recordedDirentsInputs": {},
@@ -310,7 +339,7 @@
     },
     "@@rules_python+//python/extensions:config.bzl%config": {
       "general": {
-        "bzlTransitiveDigest": "W97kKxM+lW7l/kO0rQa7Jm31CA1j+W1bNHGKjwX5xMg=",
+        "bzlTransitiveDigest": "xaCns8Qt+8bJqVLy8r6nc/eL2AjEIX/vOdjqoh5xYac=",
         "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=",
         "recordedFileInputs": {},
         "recordedDirentsInputs": {},
@@ -545,7 +574,7 @@
     },
     "@@rules_python+//python/uv:uv.bzl%uv": {
       "general": {
-        "bzlTransitiveDigest": "zyNsrbgVKwpA0B3zI84imAfuC424VSzYNPgjr/HJy5M=",
+        "bzlTransitiveDigest": "N8SCcKcL6KnzBLApxvY2jR9vhXjA2VCBZMLZfY3sDRA=",
         "usagesDigest": "H8dQoNZcoqP+Mu0tHZTi4KHATzvNkM5ePuEqoQdklIU=",
         "recordedFileInputs": {},
         "recordedDirentsInputs": {},
@@ -584,6 +613,5 @@
         ]
       }
     }
-  },
-  "facts": {}
+  }
 }
--- 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();
--- a/dowa/d_string.c	Tue Dec 23 11:48:11 2025 -0800
+++ b/dowa/d_string.c	Tue Dec 23 14:00:37 2025 -0800
@@ -6,3 +6,16 @@
   return buffer;
 }
 
+const char *Dowa_String_Slice(const char *from, size_t start, size_t end)
+{
+  static char buffer[1024] = {0};
+  size_t buffer_pos = 0;
+  for (int i = start; start < strlen(from) || start < end; i++)
+  {
+    buffer[buffer_pos++] = from[i];
+  }
+  return buffer;
+}
+
+
+
--- a/dowa/dowa.h	Tue Dec 23 11:48:11 2025 -0800
+++ b/dowa/dowa.h	Tue Dec 23 14:00:37 2025 -0800
@@ -5,7 +5,12 @@
 #include <string.h> // stdup
 #include <stdlib.h> // only for malloc, free, stuff
 #include <assert.h> // mostly for TODO
+
+#ifdef DIRECTORY
+// Only for linux and mac since window do not support.  
+// Maybe move this into seobeo file directly?
 #include <dirent.h> // some functions loop through files
+#endif
 #include <math.h> // I am not re-writing stuff I guess...
 
 #include <sys/stat.h>
@@ -34,12 +39,6 @@
 typedef char           int8;     // 8-bit signed integer
 typedef char           boolean;  // Boolean type (0 = false, nonzero = true)
 
-
-// --- Miscellaneous --- //
-/* Just use atoid lmao*/
-char *Dowa_Int32ToString(uint32 value, char *buffer);
-
-
 // --- Arena Allocator --- //
 typedef struct {
   char   *buffer;
@@ -111,6 +110,12 @@
 /* Returns the number of entries currently in the hashmap. */
 extern uint32        Dowa_HashMap_Get_Count(Dowa_PHashMap p_hash_map);
 
+// --- String manipuliation -- //
+// Splice string from start to end
+const char *Dowa_String_Slice(const char *from, size_t start, size_t end);
+// --- Miscellaneous --- //
+char *Dowa_Int32ToString(uint32 value, char *buffer); /* Just use atoid lmao*/
+
 // --- Utility Functions --- //
 /* Prints all entries in the hashmap to stdout for debugging purposes. */
 extern void          Dowa_HashMap_Print(Dowa_PHashMap map);
--- a/postdog/main.c	Tue Dec 23 11:48:11 2025 -0800
+++ b/postdog/main.c	Tue Dec 23 14:00:37 2025 -0800
@@ -300,7 +300,7 @@
   return 0;
 }
 
-void updateUrlWithParams(char *url, size_t urlSize, const char *baseUrl, const char *params)
+void Postdog_UpdateUrlWithParams(char *url, size_t urlSize, const char *baseUrl, const char *params)
 {
   // Find if there's already a ? in the URL
   char *questionMark = strchr(baseUrl, '?');
@@ -338,7 +338,7 @@
 }
 
 // Save request to history file
-void saveRequestToHistory(const char *url, const char *method, const char *headers, const char *body)
+void Postdog_SaveRequestToHistory(const char *url, const char *method, const char *headers, const char *body)
 {
   PostDog_HistoryDirectory_Exists();
 
@@ -364,9 +364,12 @@
 
   // Write headers
   fprintf(f, "Headers:\n");
-  if (headers && strlen(headers) > 0) {
+  if (headers && strlen(headers) > 0)
+  {
     fprintf(f, "%s\n", headers);
-  } else {
+  }
+  else
+  {
     fprintf(f, "None\n");
   }
 
@@ -374,9 +377,12 @@
 
   // Write body
   fprintf(f, "Body:\n");
-  if (body && strlen(body) > 0) {
+  if (body && strlen(body) > 0)
+  {
     fprintf(f, "%s\n", body);
-  } else {
+  }
+  else
+  {
     fprintf(f, "None\n");
   }
 
@@ -384,23 +390,43 @@
   printf("Request saved to %s\n", filename);
 }
 
-void PostDog_Render_TextWithScroll(Rectangle textArea, Vector2 scroll, char * input) {
+void PostDog_Render_TextWithScroll(Rectangle textArea, Vector2 scroll, char *input)
+{
   BeginScissorMode(textArea.x, textArea.y, textArea.width, textArea.height);
-
+  
   int yPos = textArea.y + 5 - (int)scroll.y;
-  char *textCopy = strdup(input);
-  char *line = strtok(textCopy, "\n");
-
-  while (line != NULL && yPos < textArea.y + textArea.height)
+  int charactersPerLine = (int)(textArea.width / (TEXT_SIZE/1.5)); // Account for padding
+  int totalPos = 0;
+  int inputLen = strlen(input);
+  
+  while (totalPos < inputLen)
   {
-    if (yPos + LINE_HEIGHT > textArea.y) {
-      DrawText(line, textArea.x + 5, yPos, TEXT_SIZE, DARKGRAY);
+    int lineEnd = totalPos;
+    int lineLength = 0;
+    
+    while (lineEnd < inputLen && lineLength < charactersPerLine && input[lineEnd] != '\n')
+    {
+      lineEnd++;
+      lineLength++;
     }
+    
+    if (yPos + LINE_HEIGHT > textArea.y && yPos < textArea.y + textArea.height)
+    {
+      char savedChar = input[lineEnd];
+      input[lineEnd] = '\0';
+      DrawText(&input[totalPos], textArea.x + 5, yPos, TEXT_SIZE, DARKGRAY);
+      input[lineEnd] = savedChar;
+    }
+    
     yPos += LINE_HEIGHT;
-    line = strtok(NULL, "\n");
+    
+    totalPos = lineEnd;
+    if (totalPos < inputLen && input[totalPos] == '\n')
+    {
+      totalPos++;
+    }
   }
-  free(textCopy);
-
+  
   EndScissorMode();
 }
 
@@ -646,7 +672,7 @@
         const char *requestHeaders = headersInput;
 
         // Save request to history
-        saveRequestToHistory(urlInput, selectedMethod, requestHeaders, requestBody);
+        Postdog_SaveRequestToHistory(urlInput, selectedMethod, requestHeaders, requestBody);
 
         // Reload history to show the new request
         historyCount = PostDog_HistoryDistory_ItemsLoad(historyItems, MAX_HISTORY_ITEMS);
@@ -780,7 +806,7 @@
             char *questionMark = strchr(tempUrl, '?');
             if (questionMark) *questionMark = '\0';
 
-            updateUrlWithParams(urlInput, sizeof(urlInput), tempUrl, paramsInput);
+            Postdog_UpdateUrlWithParams(urlInput, sizeof(urlInput), tempUrl, paramsInput);
           }
           break;
         }
@@ -819,24 +845,7 @@
       DrawRectangleLinesEx(responseArea, 1, GRAY);
 
       // Draw response text with scroll
-      BeginScissorMode(responseArea.x, responseArea.y, responseArea.width, responseArea.height);
-
-      int yPos = responseArea.y + 5 - (int)responseScroll.y;
-      char *textCopy = strdup(responseText);
-      char *line = strtok(textCopy, "\n");
-
-      while (line != NULL && yPos < responseArea.y + responseArea.height)
-      {
-        if (yPos + LINE_HEIGHT > responseArea.y)
-        {
-          DrawText(line, responseArea.x + 5, yPos, TEXT_SIZE, DARKGRAY);
-        }
-        yPos += LINE_HEIGHT;
-        line = strtok(NULL, "\n");
-      }
-      free(textCopy);
-
-      EndScissorMode();
+      PostDog_Render_TextWithScroll(responseArea, responseScroll, responseText);
 
       if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyPressed(KEY_C))
       {