Aller au contenu

Couche protocole

La couche protocol/ est la frontière de (dé)sérialisation entre les chaînes brutes du réseau et l'état typé du client. Elle ne fait aucune E/S : le réseau lui passe des lignes, elle rend des objets, et inversement.

Module Fichier Rôle
parser ai/src/protocol/parser.py Ligne serveur brute → message typé
commands ai/src/protocol/commands.py Constructeurs de commandes (chaînes pures)
messages ai/src/protocol/messages.py Dataclasses/enums des messages
update ai/src/protocol/update.py Callbacks de mise à jour de l'état

parser — lignes serveur vers messages

parse_server_line(line, client) (ai/src/protocol/parser.py:26) aiguille chaque ligne via un match :

match line:
    case "ok":   return OkKo.OK
    case "ko":   return OkKo.KO
    case "dead": return DeadMessage()
    case _ if line.startswith("message "):           return parse_broadcast(line)
    case _ if line.startswith("eject: "):            return parse_eject(line)
    case _ if line.startswith("Elevation underway"): return ElevationResult(success=True, level=None)
    case _ if line.startswith("Current level: "):    ...  # -> ElevationResult(success=True, level=k)
    case _ if line.startswith("["):                  ...  # Look ou Inventory
    case _:                                           raise ValueError(...)

Points clés :

  • ok / ko deviennent l'enum OkKo ; dead un DeadMessage.
  • Elevation underway et Current level: k deviennent tous deux un ElevationResult(success=True, ...), le second portant le nouveau niveau.
  • Lignes [...] : ambiguës entre Look et Inventory. La désambiguïsation s'appuie sur client.command_queue.peek_last_command() (ai/src/protocol/parser.py:54-60) — si la dernière commande était Look on appelle parse_look, si c'était Inventory on appelle parse_inventory.

parse_look & parse_inventory

parse_look (ai/src/protocol/parser.py:66) retire les crochets, découpe sur les virgules, puis découpe chaque tuile sur les espaces → LookResult(tiles=...) (une tuile vide donne une liste vide).

parse_inventory (ai/src/protocol/parser.py:82) découpe de même mais attend des paires ressource quantitéInventoryResult(items={...}).

parse_eject (ai/src/protocol/parser.py:124) extrait l'entier K après eject:EjectMessage(direction=K).

parse_broadcast est un stub

parse_broadcast (ai/src/protocol/parser.py:102) lève NotImplementedError : les broadcasts entrants ne sont pas traités. La coordination d'équipe en pâtit. Voir Limitations connues.

commands — constructeurs de commandes

commands.py (ai/src/protocol/commands.py) fournit des fonctions pures qui renvoient la chaîne exacte de la commande, sans \n final :

def forward() -> str:        return "Forward"
def look() -> str:           return "Look"
def inventory() -> str:      return "Inventory"
def broadcast(text) -> str:  return f"Broadcast {text}"
def take(obj) -> str:        return f"Take {obj}"
def set(obj) -> str:         return f"Set {obj}"
def incantation() -> str:    return "Incantation"
# … right, left, connect_nbr, fork, eject

Le \n est ajouté à l'envoi

Ces constructeurs ne produisent pas le saut de ligne final : c'est Client.send_command qui pousse command + "\n" dans l'OutputBuffer (cf. Réseau). Garder cette convention évite les doubles \n.

messages — messages typés

messages.py (ai/src/protocol/messages.py) définit les structures de données — aucune logique, uniquement des conteneurs (@dataclass(frozen=True) et un enum) :

Type Contenu
OkKo Enum OK / KO
LookResult tiles ; pos et direction optionnels (réinjectés par la FSM)
InventoryResult items: dict[str, int]
BroadcastMessage direction: int, text: str
EjectMessage direction: int
DeadMessage (vide)
ElevationResult success: bool, level: int \| None
ConnectNbrResult slots: int

Message est l'union typant toute ligne serveur parsée.

ConnectNbrResult jamais produit

ConnectNbrResult est défini mais aucun parseur ne le construit, car la reproduction (Fork / Connect_nbr) n'est pas implémentée. Voir Stratégie (FSM) et Limitations connues.

update — callbacks de mise à jour

update.py (ai/src/protocol/update.py) fournit les callbacks branchés par DroneState à la construction (cf. État interne) :

  • update_inventory(drone, client, line) (ai/src/protocol/update.py:15) parse la ligne, vérifie que c'est bien un InventoryResult, puis met à jour drone.inventory et drone.food. Surtout, il pose config.INIT = True — c'est ce drapeau qui autorise ensuite la boucle principale à sortir sur drone.is_dead() (on ne déclare pas le drone mort tant que le premier inventaire n'est pas arrivé).
  • update_vision(drone, client, line) (ai/src/protocol/update.py:31) parse la ligne et stocke le LookResult dans drone.last_vision (sinon None).

Pour aller plus loin