Aller au contenu

Joueurs, équipes & œufs

Cette page décrit le modèle de données des entités vivantes du serveur : le joueur (Player), l'équipe (Team) et l'œuf (Egg). Elle explique aussi le modèle de vie et de faim, ainsi que la relation entre œufs et slots de connexion qui régit l'arrivée des IA.

Pas de classe Drone

Il n'existe aucune classe Drone côté serveur. Un client IA possède directement un objet Player (AIClient détient un Player*). « Joueur », « drone » et « Trantorien » désignent donc la même entité.

Le joueur — Player

Un Player regroupe tout l'état d'un Trantorien sur la carte (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();
};
Champ Rôle
id Identifiant unique incrémental, attribué à l'éclosion. Utilisé par le GUI.
x, y Position sur la carte torique.
direction Orientation, enum class Direction { North, East, South, West }.
level Niveau d'élévation, de 1 à 8.
isIncanting true pendant une incantation : le joueur est gelé, ses commandes sont rejetées.
teamName Nom de l'équipe.
isDead Marqueur de mort (famine ou déconnexion).
inventory map<Resource,int>, initialisé à Food = 10 et toutes les pierres à 0.

Orientation et coordonnées

Le repère est torique : la carte se referme sur elle‑même. Le mapping numérique des directions pour le GUI est N=1, E=2, S=3, W=4. Voir Mécaniques de jeu pour la projection de la vision et de l'éjection selon la direction.

Modèle de vie et de faim

À l'éclosion, l'inventaire contient 10 unités de nourriture (Player.hpp:38). La faim est gérée par un événement périodique replanifié toutes les 126 ticks dans 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() décrémente la nourriture et renvoie false quand elle atteint zéro (Player.hpp:46). Chaque déclenchement consomme donc 1 unité tous les 126 ticks ; les 10 unités de départ tiennent 10 × 126 = 1260 ticks. À la famine, le serveur :

  • envoie dead à l'IA puis ferme sa connexion ;
  • marque isDead = true et retire le joueur de sa tuile ;
  • diffuse l'événement pdi <id> aux GUIs.
flowchart LR
    A["Éclosion<br/>Food = 10"] -->|toutes les 126 ticks| B{"consumeFood()"}
    B -->|Food > 0| C["Food -= 1"]
    C -->|replanifie 126| B
    B -->|Food == 0| D["dead + fermeture<br/>isDead = true<br/>diffusion pdi"]

Unité de nourriture incohérente

La commande IA Inventory rapporte la nourriture × 126 (server/srcs/commands/CmdInventory.cpp:12), alors que l'événement GUI pin rapporte la valeur brute en unités. Le commentaire de Player.hpp:12 (« 10 × 126 = 1260 ticks ») décrit le total en ticks, pas le contenu réel de l'inventaire. Détails dans Limitations connues.

L'équipe — Team

Une Team représente une équipe et son réservoir d'œufs disponibles (server/srcs/player/Team.hpp:20-30).

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

        void addEgg(Egg* egg);       // pousse l'œuf, _slotsAvailable++
        Egg* popRandomEgg();         // tire un œuf au hasard, _slotsAvailable--
        void removeEgg(Egg *egg);
        const std::vector<Egg *> &getEggs() const;
};
  • addEgg ajoute un œuf et incrémente _slotsAvailable (server/srcs/player/Team.cpp:8-12).
  • popRandomEgg tire un œuf au hasard dans le vecteur, le retire et décrémente _slotsAvailable ; renvoie nullptr si vide (Team.cpp:14-23). C'est l'opération utilisée à chaque connexion d'IA.
  • removeEgg retire un œuf désigné (utilisé par l'éclosion et par Eject).

L'œuf — Egg

Un Egg matérialise un slot de connexion : tant qu'il existe, une IA de l'équipe peut s'y connecter (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() produit un Player posé sur la tuile de l'œuf, avec une direction aléatoire (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;
}

Relation œuf ↔ slot et arrivée des IA

L'équivalence côté serveur est simple : un œuf = un slot de connexion disponible. Le compteur _slotsAvailable et la taille de _eggs évoluent ensemble.

Œufs initiaux

Dans le constructeur du serveur, chaque équipe reçoit -c œufs (option CLI clientsPerTeam) à des positions aléatoires sur la carte (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 = œufs par équipe

L'option -c fixe le nombre d'œufs initiaux par équipe, donc le nombre de connexions IA simultanées possibles au démarrage. Voir Limitations connues.

Connexion d'une IA → éclosion

Lors du handshake, si l'équipe est connue et possède un œuf disponible, le serveur éclot un œuf via popRandomEgg et transforme le PendingClient en 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");   // slots restants
ai->queueWrite(std::to_string(config.width) + " " +
               std::to_string(config.height) + "\n"); // dimensions

Le serveur répond donc <slots restants> puis <largeur> <hauteur>, diffuse ebo (éclosion) et pnw (nouveau joueur) aux GUIs, puis arme le timer de faim. Si l'équipe est inconnue ou sans œuf, il répond ko et ferme. Détails du wire format : Handshake.

Fork — nouvel œuf

La commande Fork pond un nouvel œuf sur la tuile du joueur après 42 ticks. À la sortie de file, le serveur pré‑attribue l'id de l'œuf et diffuse pfk, puis CmdFork::execute crée l'Egg et l'ajoute à l'équipe et à la tuile (server/srcs/commands/CmdFork.cpp:10-27, Server.cpp:209-266). Le succès diffuse enw (nouvel œuf). Voir Mécaniques de jeu et Commandes & délais.

Connect_nbr

La commande Connect_nbr renvoie le nombre d'œufs restants de l'équipe, c'est‑à‑dire les slots de connexion encore libres (server/srcs/core/Server.cpp:311-316) :

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

Slots non suivis séparément

Connect_nbr renvoie team.getEggs().size(), et non un compteur de slots libres distinct. Comme « œuf » et « slot » sont confondus, le résultat est correct, mais le découplage n'existe pas. Voir Limitations connues.

sequenceDiagram
    participant IA
    participant Serveur
    participant Team
    IA->>Serveur: nom d'équipe (handshake)
    Serveur->>Team: popRandomEgg()
    Team-->>Serveur: Egg (ou nullptr)
    alt œuf disponible
        Serveur->>Serveur: egg.hatch() -> Player
        Serveur-->>IA: <slots restants>
        Serveur-->>IA: <largeur> <hauteur>
        Serveur-->>IA: (GUIs : ebo + pnw)
    else aucun œuf / équipe inconnue
        Serveur-->>IA: ko + fermeture
    end

Voir aussi