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
deadto the AI then closes its connection; - sets
isDead = trueand 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;
};
addEggappends an egg and increments_slotsAvailable(server/srcs/player/Team.cpp:8-12).popRandomEggpicks a random egg from the vector, removes it and decrements_slotsAvailable; returnsnullptrif empty (Team.cpp:14-23). This is the operation used on every AI connection.removeEggremoves a designated egg (used by hatching and byEject).
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¶
- Game mechanics — vision, incantation, broadcast, ejection.
- Commands & delays — delays of
Fork,Connect_nbr, etc. - Handshake — exact connection sequence.
- AI ↔ Server protocol — wire formats.