A networked multiplayer Snake implementation built in C++20 with a Flecs ECS simulation, ZeroMQ client/server messaging, JSON/Zstd serialization, and server-driven render-state synchronization.
- Separate single-player, headless-server, and graphical-client executables.
- A server-side Flecs world owns shared snake, food, map, movement, and render state.
- Clients send directional commands over ZeroMQ REQ/REP and draw server-returned snapshots with Raylib.
- Multiplayer control and render-state payloads use JSON serialization and Zstd compression.
- CMake integrates Flecs, Raylib, cppzmq/libzmq, nlohmann/json, and Zstd.
The repository builds three application modes around a shared C++ gameplay library:
| Executable | Role |
|---|---|
snake_singleplayer |
Local Flecs simulation, keyboard input, and Raylib rendering |
snake_server |
Headless shared simulation and ZeroMQ request handling |
snake_client |
Keyboard input, network requests, render-state mirroring, and Raylib drawing |
In multiplayer mode, the server holds the simulation state. Each client keeps only its local input/controller state and a render mirror reconstructed from the latest server response.
Raylib Client Flecs Server
keyboard input shared simulation state
| |
+-- CONTROL JSON + Zstd --> ZeroMQ ---->| update Direction
| | run ECS systems
+-- GRAPH request ---------- ZeroMQ ---->| query render state
|<-- Rectangle + Color snapshot + Zstd --+
|
v
rebuild render mirror -> draw frame
The server and clients are separate processes. Gameplay logic runs against the server's Flecs world; clients send commands and render the returned presentation state rather than running a second multiplayer simulation.
Single-player mode composes the same map, food, snake, movement, occupancy, and rendering systems into a local Flecs world without the network layer.
- The server loads its bind endpoint and local account-verification configuration.
- A client loads the server endpoint, creates a ZeroMQ
REQsocket, and connects. - The client sends an
AUTHrequest from its local runtime configuration. - After verification, the server creates a snake entity and returns its Flecs entity identifier.
- The client stores that identifier with its local
SnakeControllerstate.
- The client reads keyboard input into a local
Directioncomponent. - It sends a compressed
CONTROLrequest containing its entity identifier and direction. - The server validates the command and updates the corresponding server-side
Direction. - The client sends a
GRAPHrequest. - The server queries every
Rectangle/Colorpair in the shared world and returns the arrays as compressed JSON. - The client replaces its render entities with the returned snapshot and draws the frame with Raylib.
Synchronization is intentionally full render-state synchronization. The protocol sends complete Rectangle/Color snapshots, not semantic world deltas or client-predicted state.
Gameplay and networking are composed from Flecs components, entities, queries, and lifecycle phases.
| Area | Central components/state |
|---|---|
| Snake | SnakeSpawn, Snake, SnakeBody, Direction, SnakeController |
| Spatial/map | TilePos, TileSize, TileType, TileMapStorage, OccupiedTiles |
| Food | Food, FoodSpawner |
| Presentation | raylib::Rectangle, raylib::Color |
| Networking | ZmqServerRef, ZmqClientRef, ServerAddress, UserDatabase |
The main lifecycle composition is:
- OnStart: load configuration, connect/bind sockets, initialize walls, and create initial entities.
- PreUpdate: capture client input, poll server requests, initialize new snakes, and advance movement.
- OnUpdate: update Rectangle/Color presentation state and rebuild occupied-tile data.
- PostUpdate: spawn food, rebuild the food coordinate map, and complete local rendering updates.
Snake roots and body segments are separate entities. Food and wall tiles are entities with spatial and presentation components. Systems operate through component queries, while dynamic authentication and food events create entities at runtime.
Two reusable C++ helpers support this composition:
ComponentBundleuses variadic templates, tuples, and compile-time iteration to create repeatable component sets.IntoSystemBuilderuses C++20 concepts and type traits to derive Flecs system terms from callback signatures.
The shared contract lives in include/protocol.hpp. Both client and server use the same MessageType definition and message builders.
| Message | Direction | Purpose |
|---|---|---|
AUTH |
Client → server | Submit configuration-backed account verification and request a snake entity |
CONTROL |
Client → server | Submit player entity identifier and directional command |
GRAPH |
Client → server | Request the current render state |
REPLY |
Server → client | Return success, failure, or malformed-request status |
GRAPH_REPLY |
Server → client | Return complete Rectangle and Color arrays |
ZeroMQ REQ/REP provides ordered request/reply exchange. Control and graph requests and all server replies are compressed with Zstd; startup authentication is plain JSON. Client send/receive operations use bounded timeouts, and malformed payloads receive a BAD_REQUEST reply instead of terminating the server loop.
- C++20: required by CMake, with concepts, type traits, variadic templates, structured bindings, and
std::optionalused in the implementation. - Resource lifetime: ZeroMQ contexts/sockets and Raylib windows use scope-bound C++ objects; shared network resources are injected into Flecs worlds.
- Serialization: nlohmann/json conversions cover protocol messages plus Raylib Rectangle/Color values; the Zstd wrapper validates compression and decompression results.
- State isolation: focused tests verify that simultaneously initialized snakes do not share body entities and that one snake eating does not grow another.
- Failure bounds: client socket timeouts prevent an unavailable server from blocking a receive indefinitely; server parsing rejects malformed message types and fields.
- Build composition: one
snake_corestatic library supports three executable targets and a focused CTest executable.
- CMake 3.16+
- A C++20 compiler
- ZeroMQ/libzmq and Zstd development packages
- Platform graphics dependencies required by Raylib
Flecs, Raylib, raylib-cpp, cppzmq, and nlohmann/json are pinned Git submodules.
git clone --recursive https://github.com/yuyao-wang/cpp-networked-snake.git
cd cpp-networked-snake
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
cmake --build build --parallel
ctest --test-dir build --output-on-failureOn macOS, the external packages can be installed with brew install zeromq zstd. The CircleCI configuration installs the equivalent Ubuntu development packages and runs the same configure, build, and test path.
Only clearly fake examples are tracked. Create ignored local copies before starting multiplayer mode:
cp config_server.example.json config_server.json
cp config_client.example.json config_client.json
cp key.example.json key.json
cp clientInfo.example.json clientInfo.jsonNever commit real credentials. The JSON account check is local runtime configuration for the multiplayer flow, not a secure account service.
Start the server first, then one or more clients from separate terminals:
./build/snake_server
./build/snake_clientRun the local mode independently with:
./build/snake_singleplayer- ZeroMQ
REQ/REPkeeps command/state exchange ordered and explicit, while synchronous client receives couple network latency to the render loop. Bounded timeouts prevent indefinite waits. - Full render snapshots keep client state management small, but require more bandwidth and entity reconstruction than delta synchronization.
- Server-side simulation provides one shared game state while clients remain focused on input and rendering.
- Flecs lifecycle phases expose system composition; key network ordering is encoded through system dependencies rather than a separate scheduler.
- JSON keeps the protocol inspectable, while Zstd reduces the size of repeated control and render-state payloads.
.
├── app/ # single-player, server, and client entry points
├── include/ # components, protocol, networking, and ECS helpers
├── src/ # shared gameplay and network implementation
├── tests/ # focused protocol, serialization, state, and timeout tests
├── thirdparty/ # pinned Git submodules
├── *.example.json # fake runtime configuration templates
├── .circleci/config.yml # Linux configure, build, and test path
└── CMakeLists.txt # C++20 library, applications, and CTest targets