Skip to content

Repository files navigation

C++20 Multiplayer Snake — Flecs ECS & ZeroMQ

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.

Overview

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.

Architecture

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.

Multiplayer Data Flow

Startup

  1. The server loads its bind endpoint and local account-verification configuration.
  2. A client loads the server endpoint, creates a ZeroMQ REQ socket, and connects.
  3. The client sends an AUTH request from its local runtime configuration.
  4. After verification, the server creates a snake entity and returns its Flecs entity identifier.
  5. The client stores that identifier with its local SnakeController state.

Frame/update loop

  1. The client reads keyboard input into a local Direction component.
  2. It sends a compressed CONTROL request containing its entity identifier and direction.
  3. The server validates the command and updates the corresponding server-side Direction.
  4. The client sends a GRAPH request.
  5. The server queries every Rectangle/Color pair in the shared world and returns the arrays as compressed JSON.
  6. 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.

Flecs ECS Design

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:

  • ComponentBundle uses variadic templates, tuples, and compile-time iteration to create repeatable component sets.
  • IntoSystemBuilder uses C++20 concepts and type traits to derive Flecs system terms from callback signatures.

Network Protocol

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++ Engineering

  • C++20: required by CMake, with concepts, type traits, variadic templates, structured bindings, and std::optional used 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_core static library supports three executable targets and a focused CTest executable.

Build & Run

Prerequisites

  • 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.

Build and test

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-failure

On 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.

Configure local runtime files

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.json

Never commit real credentials. The JSON account check is local runtime configuration for the multiplayer flow, not a secure account service.

Run

Start the server first, then one or more clients from separate terminals:

./build/snake_server
./build/snake_client

Run the local mode independently with:

./build/snake_singleplayer

Engineering Trade-offs

  • ZeroMQ REQ/REP keeps 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.

Repository Structure

.
├── 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

About

C++20 multiplayer game with Flecs ECS simulation, ZeroMQ client/server messaging, and compressed render-state synchronization.

Topics

Resources

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages