Skip to content
Alrik Neihouser edited this page Jun 16, 2026 · 2 revisions

Server

The server is written in C++ and runs as a single process, single thread. It uses poll() for socket multiplexing so it never blocks waiting for one client.

Usage

./zappy_server -p port -x width -y height -n name1 name2 ... -c clientsNb -f freq
Flag Meaning
-p Port number
-x Map width
-y Map height
-n One or more team names (space-separated)
-c Max clients per team
-f Frequency — reciprocal of time unit (default 100)

Argument parsing is not yet implemented; the server currently hard-codes port 4242, a 10×10 map, and two teams.

The action duration formula from the spec is: action_time = action_cost / f seconds. Internally the server converts this to a tick duration of (7.0 / f) * 1000 milliseconds.

Architecture overview

main.cpp
└── Server
    ├── run()               main loop: game_tick + poll_clients
    ├── Polling.cpp         poll() + accept + per-client read/dispatch
    ├── server_helper.cpp   socket setup, client accept/create/remove
    ├── client_helpers.cpp  move_player, parse_resource
    └── game_loop.cpp       game_tick, populate_map_resources

Main loop (game_loop.cpp)

Server::run() drives two things in lock-step:

  1. Game tick — fires every time_unit milliseconds. Every 20 ticks (and at startup) it refills map resources via populate_map_resources().
  2. poll_clients(timeout) — calls poll() with a timeout calculated as the time remaining until the next tick, so the server never over-sleeps.

Socket multiplexing (Polling.cpp)

All file descriptors — the listening socket and every client socket — are kept in a std::vector<pollfd> _fds. poll() is called with the whole list. When POLLIN fires on the server fd, a new client is accepted. When it fires on a client fd, the client's buffer is read and any complete newline-terminated commands are dispatched.

Connection handshake (server_helper.cpp:accept_new_client)

  1. Server sends WELCOME\n.
  2. Client replies with GRAPHIC\n → becomes a GUI client, or a team name → becomes a player.
  3. For a player: server validates the team name and checks spots_left > 0, then sends <client_num>\n<x> <y>\n.
  4. For a GUI: server calls gui_start() which immediately sends the full game state snapshot.

Class hierarchy

Client  (abstract)
├── Player
└── Gui

Client (include/Client/Client.hpp)

Base class for both player and GUI connections. Holds the socket fd (control_fd) and a string buffer (ctrl_buffer) for incomplete commands. Pure virtual parse_command() dispatches incoming lines to the concrete type.

Implements the Observer interface: each Client is attached to a Subject and receives Update(message) calls when the subject notifies.

Player (include/Client/Player.hpp)

Represents an AI client. State:

Field Type Description
position array<int,2> X, Y on the map
orientation enum NORTH/EAST/SOUTH/WEST
level int Current elevation level (starts at 0, effectively 1 after connect)
team_name string Team this player belongs to
inventory Inventory 7-slot resource array; starts with 10 food

Every player gets a unique auto-incrementing player_id (static counter).

Player commands

Command Action
Forward Move one tile in facing direction (map wraps)
Right / Left Rotate 90°
Look Returns a vision grid — 1+3+5+7 = 16 tiles in a pyramid ahead
Inventory Returns current resource counts
Take <res> / Set <res> Pick up / drop a resource on the current tile
Eject Push all other players on the tile one step in facing direction
Broadcast <text> Send a message to all clients with a direction hint
Connect_nbr Returns open slots in player's team
Fork Lay an egg (egg logic not yet implemented)
Incantation Start an elevation ritual (see below)

Unknown commands return ko\n.

Look grid

The look response is [tile0, tile1, ..., tile15]. Tile 0 is the player's own tile. The grid grows one row per level of vision (hardcoded to depth 3 — rows of 1, 3, 5, 7 tiles, oriented relative to the player's facing direction). Each tile lists player tokens for occupants and resource names for items present.

Incantation (Incantation.cpp)

Requirements are checked immediately when the command is received (start check). The server checks players_on_tile >= required and stone quantities on the tile. If met:

  • All participating players advance one level.
  • Required stones are consumed from the tile.
  • GUI is notified (pic then pie).

The 300/f wait between Elevation underway and Current level: K is not yet implemented — the level-up is currently instant.

Gui (include/Client/Gui.hpp)

Receives commands from the GUI client and also receives unsolicited events pushed by the server via the Observer pattern.

GUI query commands (client → server)

Command Response Description
msz msz X Y Map dimensions
bct X Y bct X Y q0..q6 Tile resource counts
mct all tiles as bct lines Full map dump
tna tna N per team Team names
ppo #n ppo #n X Y O Player position + orientation
plv #n plv #n L Player level
pin #n pin #n X Y q0..q6 Player inventory
sgt sgt T Current time unit
sst T sst T Set server time unit

Unknown GUI commands return suc\n.

Passive server → GUI events

These are pushed via _gui_subject.Notify(...) whenever the corresponding action happens, without the GUI requesting them:

Message Trigger
pnw #n X Y O L N New player connects
ppo #n X Y O Player moves
plv #n L Player levels up
pin #n X Y q0..q6 Player inventory changes
pex #n Player ejects others
pbc #n M Player broadcasts
pic X Y L #n... Incantation starts
pie X Y R Incantation ends
pfk #n Player starts fork
pdr #n i Player drops resource
pgt #n i Player picks up resource
pdi #n Player dies
enw #e #n X Y Egg laid
ebo #e Egg hatches
edi #e Egg dies
seg N Game over, team N wins
smg M Server message

Observer pattern

The server holds two Subject instances:

  • _gui_subject — all Gui clients subscribe to this. Used to push passive events to every connected GUI simultaneously.
  • _player_subject — reserved for player-facing broadcast events (not yet wired up to most player actions).

When an action happens (e.g., a player drops a resource), the action handler calls _gui_subject.Notify(lambda) where the lambda casts each observer to Gui* and calls the appropriate passive method.

Map and Tiles

_map is a vector<vector<Tiles>> indexed as _map[y][x].

Each Tiles holds:

  • Inventory inventory — how many of each resource are on this tile.
  • vector<shared_ptr<Player>> players — who is currently standing here.
  • shared_ptr<Egg> egg — at most one egg per tile (nullable).

The map is a torus: move_forward and eject compute new coordinates modulo map width/height.

Resource system

Resource quantities are computed as floor(width * height * density) and distributed randomly across tiles at startup (and every 20 ticks). The current distribution algorithm places a random amount per tile in raster order, subtracting from a running total — this can leave the last tiles with near-zero amounts and is marked for improvement.

Resource densities (Struct.hpp):

Resource Density
food 0.50
linemate 0.30
deraumere 0.15
sibur 0.10
mendiane 0.10
phiras 0.08
thystame 0.05

Known limitations / TODO

  • Argument parsing (handle_arguments) is a stub — all parameters are hardcoded.
  • The 300/f incantation duration is not implemented; level-up is instant.
  • Fork creates no actual Egg object and does not update team slots.
  • _player_subject is not used for most player-facing events.
  • Resource distribution is uneven (last tiles can be starved).
  • seg (end of game) always reports the first team as winner.
  • Food depletion / player death on starvation is not yet implemented.

Clone this wiki locally