Skip to main content

Ethan Thornberg

Week 5: From Copying to Creating

Ethan Thornberg9 min read
Deep WorkNetworkingTCPChat ServerSystems DesignMonth 1

Week 5: From Copying to Creating

Monday, January 26. 2pm. Day 21.

I sat down at the same table with a different kind of energy. Month 1 was done. I had the fundamentals. TCP, UDP, sockets, protocols. The patterns that make networked systems work.

Week 1 through 4 were about learning those patterns. Week 5 was about using them to build something I wanted to see exist.

Month 2 was game networking, and Week 5 was the foundation: a multiplayer movement server with real-time state synchronization.


Day 21: First Principles, First Draft

I spent the first hour reading Gabriel Gambetta's "Client-Server Game Architecture" series. All four parts. It's what real game developers reference when building multiplayer systems.

The concepts weren't foreign this time. Tick loops. State synchronization. Client prediction. I'd read about these before, but now I had context. I knew what UDP meant. I understood why you'd send position updates without waiting for acknowledgment. The reading made more sense when I already had the foundation.

After reading, I opened a blank markdown file and started planning. Not vague ideas. Actual architecture.

Client sends input via UDP. Server receives it, updates player position, broadcasts new positions to all connected clients. Each client renders what the server says is true.

I drew a diagram on paper. Boxes and arrows. Data flow. Message types.

Simple. Clear. Doable.

Then I started coding. Not the server yet—just utilities. Functions to track time in milliseconds. Functions to check if an interval had elapsed. The foundational pieces I'd need for a game loop that ticked at a steady rate.

By the end of the session, I had my plan and the first building blocks.

Week 4's lesson stuck: planning makes building easier.


Days 22-23: Low-Level Friction

Tuesday was server foundation. I built the tick loop first. A game server doesn't wait for input like a chat server does. It runs on a clock. Every 500 milliseconds, the server wakes up, processes any input it received, updates positions, and broadcasts the new state to all clients.

I set the tick rate to 500ms—slow enough to watch the logs and ensure the sync was perfect, but fast enough to feel the 'pulse' of the server.

Get that tick loop working, everything else follows.

Then player management. A linked list of connected clients, each with a username, position, and socket address. When a client sends input (up, down, left, right), the server updates their coordinates. New client connects, add them to the list. Disconnect, remove them cleanly.

Tuesday felt productive. Foundation work. No bugs yet because I wasn't connecting the pieces.

Wednesday I started on the client.

And Wednesday is when I hit the wall.

I needed the client to respond immediately when you pressed 'w', 'a', 's', or 'd'. But that's not how terminals work by default. Normally, you type something out, press Enter, then it gets processed. fgets() blocks the program until you hit Enter.

I needed non-blocking input. Single character at a time. No echo. Just press 'w' and move immediately.

I spent an hour fighting with termios.

termios is the C library for controlling terminal settings. You can disable canonical mode (the "wait for Enter" behavior), turn off echo (so keypresses don't print to screen), and make read() non-blocking. In theory.

In practice, I kept breaking my terminal.

I'd change the terminal settings to non-blocking mode, test the client, and it would work. But if the program crashed or I forgot to reset the settings before exiting, the terminal would stay in that broken mode. I couldn't type normally. I couldn't even press Ctrl+C to stop the program.

The terminal was just... stuck.

I kept having to close the terminal window and open a new one. Test. Break terminal. Close. Reopen. Test. Break. Close. Reopen.

After the fifth time, I decided to add a quick quit option. Press 'q', and the program would reset the termios settings back to normal before exiting cleanly. I added error handling. Made sure every exit path (clean shutdown, errors, everything) restored the terminal to its original state.

It took an hour. But when it finally worked, when I pressed 'w' and my player moved instantly without hitting Enter, it felt incredible.

Not because it was glamorous. Because I'd fought with low-level terminal control and won.


Day 24: Convergence

Thursday afternoon, I had three terminal windows open.

One running the server. Two running clients.

I pressed 'w' in the first client. Its position moved up. The server saw the input, updated the position, broadcast the change. The second client's display refreshed instantly:

=== Current Positions ===
ethan: (2, 8)
bobby: (10, -5)

I moved bobby's client. Ethan's client updated.

I opened a third client. All three saw each other. Moving in real-time. WASD controls. Positions syncing across all clients. No noticeable lag at this scale. No desyncs.

Just clean, working multiplayer.

No crashes. No desyncs. No obvious bugs. After Wednesday's terminal fight, I half-expected Thursday to surface new issues. But the planning on Monday and the foundation on Tuesday held up. When the hard part is getting input to work, the server listening is straightforward.

I finished the session feeling actually capable. The planning worked, the code synced, and I didn't have to fight the basics to get there. It was just a good day of building.

This wasn't groundbreaking. It wasn't innovative. Multiplayer movement servers have existed for decades. What I built wasn't new to the world.

But it was new to me. And I built it from the ground up.

Every piece (the tick loop, the UDP sockets, the binary protocol, the terminal input handling, the state synchronization) I understood how it worked because I'd spent a month learning the concepts behind it. Week 1's TCP fundamentals. Week 2's UDP patterns. Week 3's failed attempt at reliability. Week 4's client management.

This wasn't me copying a tutorial. This was the accumulation of 25 days of learning, struggling, and showing up even when I didn't want to.

Thursday felt different because I'd earned it.


Day 25: Planting the Flag

Friday was polish.

I tested edge cases. What happens when someone disconnects mid-movement? What if input comes in faster than the tick rate? Small bugs surfaced. Buffer artifacts, disconnect handling issues. I fixed them.

Then I made a GitHub repository for the entire journey.

Not just this week's project—everything. Week 1's TCP servers. Week 2's UDP experiments. Week 3's abandoned reliable UDP. Week 4's chat server. And now Week 5's multiplayer movement server.

I wrote a comprehensive README. Not for a class or an interview. For me. To document what I'd built and why it mattered.

Project structure. Build instructions. Protocol specification (APPID for identification, UPDATE_ID for positions, USERUPDATE_ID for usernames, EXIT_ID for disconnects). What I learned. Known limitations.

I treated it like professional work because that's what it was becoming.

Making the repo felt significant. It was the first time I'd collected everything from this journey in one place. Like I was saying: this matters. This is worth documenting publicly.


The Freedom of Fundamentals

This week was different from any before it.

Week 1 was copying patterns from Beej's Guide. Week 2 was pushing through exhaustion to understand UDP. Week 3 was getting tangled without a plan. Week 4 was executing a plan and shipping clean.

Week 5 was building something I wanted to see exist.

I wasn't grinding through documentation trying to understand bind() and listen(). I was using those tools to make something move on a screen. Multiple screens. Controlled by different people. In real-time.

The work was hard enough to stay focused but not hard enough to get stuck. The terminal input fight on Wednesday was exactly that. Hard enough to be interesting, solvable enough to be satisfying. I was in that zone most of this week.

Monday I planned. Tuesday I built the foundation. Wednesday I fought terminals and won. Thursday I watched it work. Friday I made it real.

But here's what surprised me: Week 5 was smoother than Week 2 or Week 3.

Not easier—I still spent an hour debugging termios. But smoother. I knew what I was building toward. I had the fundamentals to pull from. When I chose UDP for the game server, I knew why. Week 2 taught me that position updates don't need reliability. If one packet is lost, the next update (arriving 500ms later) is more accurate anyway. TCP would add latency I didn't need.

That's not something I learned this week. That's something I already knew.

When I built the binary message protocol (packing positions into bytes), I wasn't learning how byte-packing works. I'd done that in Week 3. I was just using the skill.

When I managed multiple clients, I wasn't learning linked lists. I'd used them in Week 4.

Week 5 wasn't about new concepts. It was about combining old ones into something new.

You don't learn and move on. You learn, apply, combine, build. After 25 days straight, the knowledge compounds.


Rowing and Balance

Second week of rowing now. 6am to 8am, six days a week. 5:30am alarms. Cold mornings. Long days. Rowing, classes, work, deep work, homework.

I'm more tired. I almost fell asleep in discrete math twice this week. My body's still adjusting to the schedule.

But it hasn't affected my 2pm sessions. I still show up. I still focus. I still build for the full 2.5 hours.

The physical exhaustion is real. The mental exhaustion hasn't hit yet.


25 Days Straight

Twenty-five days. Zero missed.

Week 1 proved I could start something. Week 2 proved I could continue when it stopped being exciting. Week 3 proved I'd fail if I didn't plan. Week 4 proved I could ship something clean.

Week 5 proved something different: I can build something I'm actually proud of. Not just functional. Not just complete. Something I enjoyed making. Something I wanted to document. Something creative.

And that pride isn't about innovation or complexity. It's about seeing the direct line from Week 1's fundamentals to Week 5's working multiplayer server. Every concept I learned, every struggle I pushed through, every session I showed up for. It all added up to something I built myself.

I'm not learning networking anymore. I'm building with it.

That's the difference five weeks makes.


Same table. 2pm. Day 26.