Week 6: Flow State
Week 6: Flow State
Week 6's project: turn the multiplayer movement server into an actual game.
Treasures to collect. Scores to track. Multiple players competing. Something tangible.
Monday afternoon I planned the architecture. Random treasure spawning across a 100x100 map. Collision detection. A 10x10 viewport that follows the player. Score tracking and display.
Mapping the World
Week 3 taught me that starting without a plan creates tangled code. Week 4 proved I could plan without over-planning.
Monday I applied that lesson.
I sketched the core structure. How treasures spawn randomly. How collision detection works (server validates all movement). How the viewport calculates what 10x10 section to show based on player position.
Not every detail. But the foundation.
By Monday evening I had a clear picture of what to build and how to structure it.
Constructing the Rules of Reality
Tuesday I built the core mechanics.
Server-side collision detection. Players can't move through boundaries (0, 0, 100, 100). Players can't move through each other. If a client sends an invalid move, the server sends back their actual position to force correction. This is the beginning of "Authoritative Server" logic. The client can request a move, but the Server is the only god in this 100x100 world. If the Server says you're at (10,10), that's where you are.
Single treasure spawning at random coordinates. When a player reaches the treasure, their score increments and a new treasure spawns elsewhere.
Viewport rendering. Instead of showing the entire 100x100 map, the client shows a 10x10 grid centered on the player. If the player is near a boundary, clamp the viewport to the edge.
Commands: /help, /reset, /quit.
By Tuesday evening, the game worked. One treasure, multiple players competing for it, viewport following movement.
Solid foundation.
Refactoring the Friction
Wednesday I hit flow state.
I got a lot done. The kind of day where you look up and realize you've been working longer than you planned because you lost track of time.
The breakthrough came when I was thinking about how to add more treasures. I'd started with one treasure and one treasure struct. Simple. But it wouldn't scale.
I realized I needed to rethink the entire state organization.
Up until now, I'd been passing everything as function parameters. Socket file descriptor, players list, treasure, ID counters. Every function signature had 5+ arguments. Congested and inconvenient. Every time I added a feature, I had to update every function that touched that state.
I needed a unified State struct. One place to hold the socket, player list, treasure list, and ID counters. Pass state instead of five separate arguments. When I add new features, I add them to State, not to every function signature.
This wasn't planned Monday. It emerged Wednesday from the frustration of parameter bloat.
With the State struct in place, I could implement multiple treasures cleanly. I created a Treasures struct using the same linked list pattern as Players. Add, remove, get by ID or coordinate. Server maintains the authoritative list, clients keep a local synchronized copy.
I set MAXTREASURES to 80. Server spawns 80 treasures at startup. When a player collects one, the server broadcasts the removal to all clients and immediately spawns a new one.
For bots, I added nearest-treasure pathfinding. Calculate Euclidean distance to every treasure, move toward the closest one. The bots started racing each other naturally.
I stayed until 5pm. Not because I had to. Because I was enjoying it.
The bugs that came up felt like curious challenges instead of chores. I was constantly solving problems and was entranced by what I was doing.
I think what enabled flow was that I was finally building something tangible. I could test it in real time, play it, see what worked and what didn't. My creativity came out. I fully understood everything that was going on, which let me focus on "what's the best way to do this" rather than "how do I even start."
This was something I could show someone. Something that could actually be used. Not the coolest game, but I felt proud of it.
The current state of the terminal:
Nearest Treasure: (1,4) - 2 points
________________________________________
| Player | Coords | Score |
|_____________|_____________|__________|
| ethan | (+55,+70) | 14 pts |
|_____________|_____________|__________|
| bot | (+58,+68) | 32 pts |
| james | (+12,+45) | 08 pts |
|_____________|_____________|__________|
X . , . , .
X
X , , , $ , ,
X e
X . , . , .
X $
X , , o , e ,
X
X . , . , .
X X X X X X X X X X
*The 10x10 viewport rendering the player (o), boundary walls (X), treasures ($), and competing players (e).*
Breaking the Buffer
Thursday I stress tested the game.
I wanted to see how many bots I could run simultaneously. 5 bots, then 8, then 10. They raced around the map, collecting treasures, accumulating scores.
Then I added a 13th bot. The map stopped updating. The scoreboard froze.
Server crashed.
I immediately knew what it might be. The buffer size. I'd set MAXBUFSIZE to 100 bytes. Each position update broadcasts every player's ID, coordinates, and score. With 13 players, the packet exceeded 100 bytes.
I added MAXPLAYERS 12 and created error handling for when the server is full. Now if someone tries to connect to a full server, they receive: "Connection rejected. Server is full."
The second bug I found while testing movement validation. I tried walking into a boundary and noticed my score jumped to zero. As soon as I made a valid move, it returned to normal.
The issue was in how the server handled position corrections. When rejecting an invalid move, it sends back the player's ID and coordinates but not their score. The client unpacks all position updates expecting ID, x, y, and score. Since the score wasn't in the correction packet, it unpacked garbage and defaulted to zero.
Simple fix. Always include score in correction packets. Bonus side effect: this enables client-side prediction later. If a client predicts they collected a treasure but the server corrects them, the server can correct both position and score.
After fixing both bugs, I played the game for a few minutes. Added bots, raced them. Watched 12 bots compete. Turned up the bot movement speed and watched them zoom around the map.
I realized there wasn't anything missing. There's always more that could be added, but it was complete. Anything more would've been for fun, and as much as I love fun, I'm doing this to learn.
I documented everything. Added comprehensive comments to the code. Wrote a 274-line README with build instructions, architecture explanation, packet protocol documentation, and a customization guide.
Turning the Lights On
Friday I added a small feature.
Color output for the game display. Red boundaries, blue for my player, yellow for treasures, green for other players. ANSI escape codes in the terminal.
Small polish, but I learned something new.
Then Week 7 planning.
Slower is Faster
Thursday night, Day 29, I quit rowing.
I'd been on the team for two weeks. 5:30am wakeups, six days a week. I loved it. Loved my teammates. Found community there, people I relied on and who relied on me. I enjoyed the sport.
But I was exhausted. Mentally and physically. Hardly able to focus in classes. Getting home and just wanting to sleep.
The schedule added more pressure than I'd expected. Rush through shower and breakfast, leave for class. Classes until 2pm. Deep work 2pm to 4:30pm. Then homework and internship work until around 9pm when I had to collapse into bed. Only about an hour each evening for everything else.
I was behind on blog posts. Behind on internship work (4 hours a week instead of 10-15). Behind on homework, completing assignments right before deadlines. Started skipping classes occasionally to catch up.
I was stressed, anxious, unhappy.
Something had to give. School, internship, deep work, relationship. Those weren't negotiable.
That left rowing.
As I typed the message to my coach Thursday night, I hesitated. I really didn't want to step away. But it was necessary.
Friday morning was different. No 5:30am alarm. I woke up at a reasonable time. Showered without rushing. Made breakfast instead of speeding through it. Spent an hour on internship work before leaving for class.
The difference: slower mornings, time to think, space to breathe. Deep work still 2pm to 4:30pm, but everything around it less frantic.
That evening I had time to wind down. Read before bed. Wasn't rushing to sleep by 9pm.
30 Days of Ownership
Flow requires foundation.
Week 6 worked because Weeks 1-5 built the skills. I couldn't have flowed through building this in Week 2. I would've been constantly learning basics, fighting syntax, referencing Beej every 10 minutes.
By Week 6, I fully understood UDP sockets, binary protocols, client-server architecture, state management. That understanding freed me to focus on design decisions instead of fighting fundamentals.
Architecture emerges from pain.
The State struct wasn't planned Monday. It emerged Wednesday from the frustration of parameter bloat. The best designs are often discovered, not planned. You write the code, feel the friction, refactor.
Complete is better than perfect.
I could've added more features. Power-ups, team modes, internal obstacles, persistent leaderboards. But the game was complete. Anything more would've been for fun, not learning.
Knowing when to ship is a skill.
You can't do everything.
I tried to do rowing plus school plus internship plus deep work plus relationship. Something had to give.
2.5 hours a day of focused work is sustainable. Scattered effort across too many commitments isn't.
Slower is sometimes faster.
Rushed mornings led to exhaustion which led to falling behind. Intentional mornings with time to plan lead to better work. Time to wind down at night means better sleep which means better focus.
Priorities reveal themselves under pressure.
When everything was chaotic, what stayed? Deep work.
Not because it had to be non-negotiable. Because it became non-negotiable.
Somewhere over 30 days, this shifted from commitment to identity.
Week 6 Stats
What I built:
- Complete multiplayer treasure hunt game
- 80 concurrent treasures on 100x100 map
- Up to 12 simultaneous players
- Real-time state sync (33ms tick rate)
- Collision detection
- Score tracking and leaderboard
- 10x10 moving viewport
- Bot AI with nearest-treasure pathfinding
- Commands and error handling
- Color-coded terminal output
Code:
- Server: 538 lines
- Client: 733 lines
- Utils: ~478 lines
- Total: ~1,749 lines of C
- README: 274 lines
Days: 26-30 (30 consecutive days complete)
Week 6 was flow state. Building something tangible, testing in real time, staying late because I was enjoying it.
Also the week I learned you can't do everything. Made a hard decision. Freed up mornings. Created space.
Same table. 2pm. Day 31.
Related Posts
Week 7: Hostile Networks
Building a UDP proxy to stress test the treasure hunt game. Discovering that real networks break everything. Rethinking the architecture from the ground up.
Week 8: Prediction
Fixing the broken game. Client-side prediction, server reconciliation, a comprehensive reliability system, and the transformation from networking prototype to something production-ready.
Week 3: When Planning Matters
Building a reliable UDP protocol. Discovering why architecture matters. Friday's disconnection.