Skip to content

Players, teams & eggs

This page describes the data model of the server's living entities: the player (Player), the team (Team) and the egg (Egg). It also explains the life and food model, as well as the relationship between eggs and connection slots that governs AI arrivals.

No Drone class

There is no Drone class on the server side. An AI client owns a Player object directly (AIClient holds a Player*). "Player", "drone" and "Trantorian" therefore all refer to the same entity.

The player — Player

A Player holds the entire state of a Trantorian on the map (server/srcs/player/Player.hpp:26-47).

enum class Direction { North, East, South, West };

class Player {
    public:
        int       id          = 0;
        int       x           = 0;
        int       y           = 0;
        Direction direction   = Direction::North;
        int       level       = 1;
        bool      isIncanting = false;
        std::string teamName;
        bool isDead = false;

    std::map<Resource, int> inventory = {
        {Resource::Food, 10},
        {Resource::Linemate, 0},
        ...
    };

        bool consumeFood();
};
Field Role
id Unique incremental identifier, assigned at hatching. Used by the GUI.
x, y Position on the toroidal map.
direction Orientation, enum class Direction { North, East, South, West }.
level Elevation level, from 1 to 8.
isIncanting true during an incantation: the player is frozen, its commands are rejected.
teamName Team name.
isDead Death marker (starvation or disconnection).
inventory map<Resource,int>, initialised to Food = 10 and all stones at 0.

Orientation and coordinates

The frame is toroidal: the map wraps around itself. The numeric mapping of directions for the GUI is N=1, E=2, S=3, W=4. See Game mechanics for the projection of vision and ejection according to direction.

Life and food model

At hatching the inventory contains 10 food units (Player.hpp:38). Hunger is handled by a periodic event rescheduled every 126 ticks in scheduleFoodTimeoutForPlayer (server/srcs/core/Server.cpp:671-701):

void Server::scheduleFoodTimeoutForPlayer(int playerId)
{
    _scheduler->schedule([this, playerId]() {
        ...
        if (!player->consumeFood()) {
            ai->queueWrite("dead\n");
            ai->markForClose();
            ...
            player->isDead = true;
            _world->getTile(player->x, player->y).removePlayer(player);
            broadcastToGUIs(... sendPdi(playerId) ...);
            return;
        }
        scheduleFoodTimeoutForPlayer(playerId);
    }, 126);
}

consumeFood() decrements food and returns false when it reaches zero (Player.hpp:46). Each trigger therefore consumes 1 unit every 126 ticks; the 10 starting units last 10 × 126 = 1260 ticks. On starvation the server:

  • sends dead to the AI then closes its connection;
  • sets isDead = true and removes the player from its tile;
  • broadcasts the pdi <id> event to the GUIs.
flowchart LR
    A["Hatching<br/>Food = 10"] -->|every 126 ticks| B{"consumeFood()"}
    B -->|Food > 0| C["Food -= 1"]
    C -->|reschedule 126| B
    B -->|Food == 0| D["dead + close<br/>isDead = true<br/>broadcast pdi"]

Inconsistent food unit

The AI Inventory command reports food × 126 (server/srcs/commands/CmdInventory.cpp:12), whereas the GUI pin event reports the raw value in units. The comment in Player.hpp:12 ("10 × 126 = 1260 ticks") describes the total in ticks, not the actual inventory content. Details in Known limitations.

The team — Team

A Team represents a team and its pool of available eggs (server/srcs/player/Team.hpp:20-30).

class Team {
    public:
        std::string _name;
        int _slotsAvailable = 0;
        std::vector<Egg*> _eggs;

        void addEgg(Egg* egg);       // pushes the egg, _slotsAvailable++
        Egg* popRandomEgg();         // picks a random egg, _slotsAvailable--
        void removeEgg(Egg *egg);
        const std::vector<Egg *> &getEggs() const;
};
  • addEgg appends an egg and increments _slotsAvailable (server/srcs/player/Team.cpp:8-12).
  • popRandomEgg picks a random egg from the vector, removes it and decrements _slotsAvailable; returns nullptr if empty (Team.cpp:14-23). This is the operation used on every AI connection.
  • removeEgg removes a designated egg (used by hatching and by Eject).

The egg — Egg

An Egg materialises a connection slot: as long as it exists, an AI of the team can connect through it (server/srcs/player/Egg.hpp:18-26).

class Egg {
public:
    int         _id = 0;
    int         _x  = 0;
    int         _y  = 0;
    std::string _teamName;
    int _creatorId;
    Player hatch();
};

hatch() produces a Player placed on the egg's tile, with a random direction (server/srcs/player/Egg.cpp:8-18):

Player Egg::hatch()
{
    Direction dirs[4] = {North, South, East, West};
    Player player;
    player.x         = this->_x;
    player.y         = this->_y;
    player.teamName  = this->_teamName;
    player.direction = dirs[rand() % 4];
    return player;
}

Egg ↔ slot relationship and AI arrival

The server-side equivalence is simple: one egg = one available connection slot. The _slotsAvailable counter and the size of _eggs move together.

Initial eggs

In the server constructor, each team receives -c eggs (CLI option clientsPerTeam) at random positions on the map (server/srcs/core/Server.cpp:53-68):

for (const std::string &teamName : config.teamNames) {
    Team &team = _world->getTeam(teamName);
    for (int i = 0; i < config.clientsPerTeam; i++) {
        Egg *egg = new Egg();
        egg->_id = _nextEggId++;
        egg->_x = rand() % config.width;
        egg->_y = rand() % config.height;
        egg->_teamName = teamName;
        egg->_creatorId = 0;
        team.addEgg(egg);
        _world->getTile(egg->_x, egg->_y).addEgg(egg);
    }
}

-c = eggs per team

The -c option sets the number of initial eggs per team, hence the number of simultaneous AI connections possible at startup. See Known limitations.

AI connection → hatching

During the handshake, if the team is known and has an available egg, the server hatches an egg via popRandomEgg and upgrades the PendingClient into an AIClient (server/srcs/core/ClientSessionManager.cpp:104-127):

Player *player = new Player();
*player = egg->hatch();
player->id = nextPlayerId++;
player->teamName = name;
ai->setPlayer(player);
world.getTile(player->x, player->y).addPlayer(player);
clients[fd] = ai;
scheduleFoodTimeoutForPlayer(player->id);
...
int available = team.getEggs().size();
ai->queueWrite(std::to_string(available) + "\n");   // remaining slots
ai->queueWrite(std::to_string(config.width) + " " +
               std::to_string(config.height) + "\n"); // dimensions

The server therefore replies <remaining slots> then <width> <height>, broadcasts ebo (hatching) and pnw (new player) to the GUIs, then arms the food timer. If the team is unknown or has no egg, it replies ko and closes. Wire format details: Handshake.

Fork — new egg

The Fork command lays a new egg on the player's tile after 42 ticks. On dequeue the server pre-assigns the egg's id and broadcasts pfk, then CmdFork::execute creates the Egg and adds it to the team and the tile (server/srcs/commands/CmdFork.cpp:10-27, Server.cpp:209-266). Success broadcasts enw (new egg). See Game mechanics and Commands & delays.

Connect_nbr

The Connect_nbr command returns the number of remaining eggs of the team, i.e. the connection slots still free (server/srcs/core/Server.cpp:311-316):

Team &team = _world->getTeam(ai->getPlayer()->teamName);
cmd->setResponse(std::to_string(team.getEggs().size()) + "\n");

Slots not tracked separately

Connect_nbr returns team.getEggs().size(), not a distinct free-slot counter. Since "egg" and "slot" are conflated, the result is correct, but the decoupling does not exist. See Known limitations.

sequenceDiagram
    participant AI
    participant Server
    participant Team
    AI->>Server: team name (handshake)
    Server->>Team: popRandomEgg()
    Team-->>Server: Egg (or nullptr)
    alt egg available
        Server->>Server: egg.hatch() -> Player
        Server-->>AI: <remaining slots>
        Server-->>AI: <width> <height>
        Server-->>AI: (GUIs: ebo + pnw)
    else no egg / unknown team
        Server-->>AI: ko + close
    end

See also