Aller au contenu

Serveur — Vue d'ensemble

Le serveur Zappy est le cœur autoritaire de la simulation. C'est lui — et lui seul — qui détient la vérité du jeu : l'état de la carte, les ressources, la position et le niveau de chaque joueur, le déroulement des incantations. Les clients (IA et interface graphique) ne font qu'observer cet état et soumettre des actions ; toute décision est arbitrée par le serveur.

Un seul arbitre, deux publics

Le serveur sert deux types de clients via TCP :

  • les clients IA (les drones autonomes), qui jouent la partie en envoyant des commandes texte ;
  • le client graphique (GUI), qui reçoit un flux d'événements pour afficher le monde en temps réel.

Le serveur ne fait jamais confiance à un client : chaque commande est validée contre l'état réel du monde.

Caractéristiques techniques

  • Langage : C++17 (g++ -Wall -Wextra -std=c++17).
  • Binaire produit : build/zappy_server.
  • Bibliothèque statique : les sources réseau sont d'abord compilées dans build/libnetwork.a, puis liées au binaire final.
  • Modèle d'exécution : mono-thread, événementiel — un unique poll() multiplexe toutes les sockets, et un Scheduler ordonnance les actions différées.

Architecture interne

Le code est organisé en couches sous server/srcs/, chacune avec une responsabilité claire :

flowchart TD
    main["core/main.cpp<br/>point d'entrée, signaux"]
    config["core/Config<br/>parsing CLI"]
    server["core/Server<br/>boucle poll() + orchestration"]

    subgraph net["network/ (→ libnetwork.a)"]
        sock["SocketUtils<br/>création socket"]
        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 (IA)"]
        gp["GUIProtocol (GUI)"]
    end

    cmds["commands/<br/>une classe par commande IA"]
    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

Rôle de chaque couche

Couche Répertoire Responsabilité
Core core/ Point d'entrée, parsing de la configuration, boucle principale, dispatchers (IA, GUI, incantation, sessions).
Network network/ Création des sockets, hiérarchie de clients, mise en mémoire tampon des entrées/sorties. Compilée dans libnetwork.a.
World world/ Carte torique, tuiles, ressources, génération des ressources.
Player player/ Joueurs, équipes, œufs.
Scheduler scheduler/ File d'événements temporisés (le « temps » du jeu).
Protocol protocol/ Analyse des commandes IA (CommandParser) et sérialisation des événements GUI (GUIProtocol).
Commands commands/ Une classe ACommand par commande IA (Forward, Look, Incantation…), chacune portant son délai et son effet.
Algorithms algorithms/ Logique métier partagée : cône de vision, propagation du son (broadcast), élévation.

Arborescence des sources

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 (IA), GUIProtocol (GUI)
├── commands/    Cmd* (Forward, Right, Left, Look, Inventory,
│                Broadcast, ConnectNbr, Fork, Eject, Take, Set, Incantation)
└── algorithms/  VisionAlgo, BroadcastAlgo, IncantationAlgo

Conception événementielle

Le serveur n'a pas de boucle de jeu cadencée au sens classique (pas de « tick » global incrémenté en continu). Il repose sur deux mécanismes complémentaires :

  1. poll() multiplexe toutes les sockets — l'écoute des nouvelles connexions, les lectures et les écritures clients — en un seul appel bloquant.
  2. Le Scheduler est un tas binaire (min-heap) d'événements datés. Toute action différée (l'achèvement d'une commande après son délai, la mort par faim, le respawn des ressources) y est planifiée.

À chaque réveil, le serveur traite les sockets prêtes, puis exécute via Scheduler::tick() tous les événements dont l'échéance est dépassée. Le délai d'attente de poll() est exactement la durée avant le prochain événement (Scheduler::nextDeadlineMs()). Le serveur dort donc précisément jusqu'au moment utile : ni attente active, ni latence inutile.

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

Pourquoi c'est élégant

En liant le timeout de poll() à la prochaine échéance, on obtient un serveur qui consomme zéro CPU lorsqu'il n'a rien à faire, tout en restant réactif à la milliseconde près pour les actions de jeu. C'est le détail décrit en profondeur dans Boucle de jeu & temps.

Pages de cette section

  • Réseau & sockets — création de socket, boucle poll(), hiérarchie de clients, tampons, limite des 10 commandes.
  • Boucle de jeu & temps — le Scheduler, la conversion ticks → millisecondes, l'effet de -f, l'absence d'attente active.
  • Monde & ressources — la carte torique, les tuiles, l'énumération des ressources, les densités et le respawn.

Pour le détail des commandes et de leurs délais, voir Commandes & délais ; pour le format des messages échangés, voir Protocole.