Inspiration

While Tic-Tac-Toe is a conceptually simple game, I wanted to use it as a sandbox to explore low-level multiplayer networking and engine architecture in Python. By keeping the game logic minimal, I could focus entirely on building a robust Local Area Network (LAN) infrastructure and experimenting with different design patterns without the overhead of a massive physics engine.

What it does

It is a fully functional, zero-latency LAN multiplayer Tic-Tac-Toe game. One player acts as the Host, spinning up a local server, while the second player connects via IP address. The game features a custom lobby system with "Ready" states, real-time board synchronization, and an interactive UI with hover states, text-editing, and dynamic turn indicators.

How I built it

The project was built purely in Python using Pygame-CE for rendering and the built-in socket library for networking. The codebase is heavily structured around two distinct architectural patterns:

  1. Entity-Component-System (ECS) for UI: Instead of relying on standard object-oriented GUI classes, I built a custom ECS. Entities are defined by discrete data payloads (SpatialComponent, HoverComponent, TextComponent), which are processed independently by decoupled systems (RenderingSystem, ClickingSystem, HoverSystem).
  2. Client-Server Model for Networking: The network layer (ServerNetworkSystem and ClientNetworkSystem) utilizes raw TCP sockets to transmit JSON-encoded byte packets, ensuring reliable state synchronization.

Challenges I ran into

The most significant challenge was architectural: understanding when to implement a strict ECS design and when to opt for simplicity.

Initially, I debated forcing the entire game—including the networking—into the ECS framework. However, I realized that while ECS is incredible for decoupling rendering and UI logic, using it for network state management would introduce unnecessary complexity. I ultimately decided to split the architecture. I used ECS exclusively for the front-end UI and board rendering, but reverted to a straightforward, event-driven Client-Server design for the networking layer. Bridging these two paradigms so they communicated cleanly without bottlenecking the main Pygame loop was a massive, but rewarding, hurdle.

Accomplishments that I'm proud of

I am incredibly proud of how clean and scalable the codebase turned out. By strictly separating the UI components from the rendering systems, adding new interactive elements (like the IP text-entry box or lobby buttons) became as simple as appending a dictionary of components. I also successfully implemented a non-blocking network architecture that allows the custom UI to render smoothly at 60 FPS while concurrently listening for incoming TCP packets.

What I learned

This project was a masterclass in architectural trade-offs. I learned that there is no "silver bullet" design pattern. ECS is incredibly powerful for visual processing and state-independent logic, but strict Client-Server models are far superior for linear, state-dependent network synchronization. Learning how to identify the right tool for the specific layer of the engine was the biggest takeaway from this build.

Built With

Share this project:

Updates