Skip to content

Server — Overview

The Zappy server is the authoritative core of the simulation. It — and it alone — holds the truth of the game: the state of the map, the resources, the position and level of every player, the progress of incantations. Clients (AI and graphical interface) merely observe that state and submit actions; every decision is arbitrated by the server.

One arbiter, two audiences

The server serves two kinds of clients over TCP:

  • the AI clients (the autonomous drones), which play the game by sending text commands;
  • the graphical client (GUI), which receives a stream of events to render the world in real time.

The server never trusts a client: every command is validated against the real state of the world.

Technical characteristics

  • Language: C++17 (g++ -Wall -Wextra -std=c++17).
  • Produced binary: build/zappy_server.
  • Static library: the network sources are first compiled into build/libnetwork.a, then linked into the final binary.
  • Execution model: single-threaded, event-driven — a single poll() multiplexes all sockets, and a Scheduler orders deferred actions.

Internal architecture

The code is organised into layers under server/srcs/, each with a clear responsibility:

flowchart TD
    main["core/main.cpp<br/>entry point, signals"]
    config["core/Config<br/>CLI parsing"]
    server["core/Server<br/>poll() loop + orchestration"]

    subgraph net["network/ (→ libnetwork.a)"]
        sock["SocketUtils<br/>socket creation"]
        clients["AClient / PendingClient<br/>AIClient / GUIClient"]
        buf["NetworkBuffer"]
    end

    sched["scheduler/<br/>Scheduler + Event"]

    subgraph world["world/"]
        wmap["World / Tile"]
        spawn["RessourceSpawner"]
    end

    subgraph player["player/"]
        pl["Player / Team / Egg"]
    end

    subgraph proto["protocol/"]
        cp["CommandParser (AI)"]
        gp["GUIProtocol (GUI)"]
    end

    cmds["commands/<br/>one class per AI command"]
    algos["algorithms/<br/>VisionAlgo / BroadcastAlgo / IncantationAlgo"]

    main --> config --> server
    server --> net
    server --> sched
    server --> world
    server --> proto
    server --> cmds
    cmds --> algos
    world --> spawn
    world --> player
    cp --> cmds

Role of each layer

Layer Directory Responsibility
Core core/ Entry point, configuration parsing, main loop, dispatchers (AI, GUI, incantation, sessions).
Network network/ Socket creation, client hierarchy, buffering of input/output. Compiled into libnetwork.a.
World world/ Toroidal map, tiles, resources, resource generation.
Player player/ Players, teams, eggs.
Scheduler scheduler/ Queue of timed events (the game's "clock").
Protocol protocol/ Parsing of AI commands (CommandParser) and serialisation of GUI events (GUIProtocol).
Commands commands/ One ACommand class per AI command (Forward, Look, Incantation…), each carrying its delay and effect.
Algorithms algorithms/ Shared game logic: vision cone, sound propagation (broadcast), elevation.

Source tree

server/srcs/
├── core/        main.cpp, Server, Config, AICommandDispatcher,
│                ClientSessionManager, GuiCommandHandler, IncantationHandler
├── network/     AClient, PendingClient, AIClient, GUIClient,
│                NetworkBuffer, SocketUtils
├── world/       World, Tile, RessourceSpawner
├── player/      Player, Team, Egg
├── scheduler/   Scheduler, Event
├── protocol/    CommandParser (AI), GUIProtocol (GUI)
├── commands/    Cmd* (Forward, Right, Left, Look, Inventory,
│                Broadcast, ConnectNbr, Fork, Eject, Take, Set, Incantation)
└── algorithms/  VisionAlgo, BroadcastAlgo, IncantationAlgo

Event-driven design

The server has no fixed-rate game loop in the classic sense (no global "tick" continuously incremented). It rests on two complementary mechanisms:

  1. poll() multiplexes all sockets — listening for new connections, client reads and client writes — in a single blocking call.
  2. The Scheduler is a binary heap (min-heap) of dated events. Every deferred action (the completion of a command after its delay, death by starvation, resource respawn) is scheduled there.

On each wake-up, the server handles the ready sockets, then runs through Scheduler::tick() every event whose deadline has passed. The poll() timeout is exactly the time until the next event (Scheduler::nextDeadlineMs()). The server therefore sleeps precisely until the moment something must happen: no busy-waiting, no needless latency.

// core/Server.cpp:114-115
int timeout = _scheduler->nextDeadlineMs();
int pollResult = poll(_pollFds.data(), _pollFds.size(), timeout);

Why this is elegant

By tying the poll() timeout to the next deadline, you get a server that consumes zero CPU when it has nothing to do, while remaining responsive to the millisecond for game actions. This is the detail explored in depth in Game loop & time.

Pages in this section

  • Network & sockets — socket creation, the poll() loop, client hierarchy, buffers, the 10-command limit.
  • Game loop & time — the Scheduler, the ticks → milliseconds conversion, the effect of -f, the absence of busy-waiting.
  • World & resources — the toroidal map, tiles, the resource enumeration, densities and respawn.

For the detail of commands and their delays, see Commands & delays; for the format of exchanged messages, see Protocol.