Aller au contenu

IA (Python) — Vue d'ensemble

Le client IA de Zappy est un drone autonome écrit en Python (3.11+). Il se connecte au serveur de jeu par une socket TCP, observe son environnement, décide seul de ses actions et tente de faire monter son équipe en niveau via les incantations.

Aucune dépendance externe

Le projet n'utilise que la bibliothèque standard : pyproject.toml déclare dependencies = [] et requires-python >= 3.11. Pas de framework réseau, pas de selectors, pas de asyncio — uniquement socket, select, argparse, dataclasses, etc. (ai/pyproject.toml).

Lancement

Le client s'invoque via le lanceur zappy_ai, qui insère src/ dans le sys.path puis appelle main() :

./zappy_ai -p <port> -n <team_name> -h <machine>
Option Rôle Défaut
-p/--port Port TCP du serveur aucun
-n/--name Nom de l'équipe (handshake) aucun
-h/--host Adresse de la machine serveur 127.0.0.1
--debug Active les logs de débogage désactivé

Arguments « requis » non contraints

Le sujet décrit -p et -n comme obligatoires, mais parse_args (ai/src/main.py:45-57) ne pose pas required=True : leur valeur par défaut est None. De même, config.DEFAULT_HOST = "localhost" (ai/src/config.py:106) n'est pas utilisé — le défaut effectif est 127.0.0.1. Voir Limitations connues.

Architecture en couches

Le code est organisé en couches nettes, du plus bas niveau (octets sur le réseau) au plus haut niveau (décisions stratégiques).

flowchart TD
    main["main.py<br/>(point d'entrée, boucle)"]

    subgraph strategy["strategy/ — décision"]
        fsm["FSM"]
        states["states/<br/>survive, rally, collect,<br/>explore, incant_prep, incant…"]
        path["pathfinding<br/>incantation"]
    end

    subgraph state["state/ — état interne"]
        drone["DroneState"]
        wmap["WorldMap"]
        inv["inventory / vision"]
    end

    subgraph protocol["protocol/ — sérialisation"]
        parser["parser"]
        commands["commands"]
        messages["messages"]
        update["update"]
    end

    subgraph network["network/ — transport"]
        client["Client"]
        conn["Connection"]
        cq["CommandQueue"]
        buf["InputBuffer / OutputBuffer"]
    end

    team["team/ — coordination<br/>(crypto, protocol, decoy)"]
    utils["utils/ — logger"]

    main --> strategy
    main --> network
    strategy --> state
    strategy --> protocol
    state --> protocol
    protocol --> messages
    network --> protocol
    network --> conn
    network --> cq
    network --> buf
    strategy -.->|prévu, non câblé| team
    main --> utils

La couche team/ n'est pas câblée

Le paquet team/ (chiffrement, protocole inter-drones, leurres) est entièrement implémenté mais jamais appelé par le réseau ou la stratégie. Voir Coordination d'équipe et Limitations connues.

Arborescence des sources

ai/
├── zappy_ai                 # lanceur exécutable (insère src/ dans sys.path)
├── pyproject.toml           # zappy_ai, requires-python >=3.11, deps=[]
└── src/
    ├── main.py              # parse_args, run() : boucle principale
    ├── config.py            # constantes du sujet (coûts, prérequis, seuils)
    ├── network/
    │   ├── connection.py    # socket TCP + handshake
    │   ├── client.py        # orchestration envoi/réception
    │   ├── command_queue.py # file des 10 commandes en vol (FIFO)
    │   └── buffers.py       # découpage du flux en lignes
    ├── protocol/
    │   ├── parser.py        # lignes serveur -> messages typés
    │   ├── commands.py      # constructeurs de commandes (chaînes pures)
    │   ├── messages.py      # dataclasses des messages
    │   └── update.py        # callbacks de mise à jour de l'état
    ├── state/
    │   ├── drone.py         # DroneState (niveau, food, position…)
    │   ├── inventory.py     # helpers d'inventaire
    │   ├── vision.py        # géométrie du champ de vision
    │   └── world_map.py     # carte mentale torique
    ├── strategy/
    │   ├── fsm.py           # machine à états + pilotage des commandes
    │   ├── incantation.py   # prérequis d'élévation
    │   ├── pathfinding.py   # distances/déplacements toriques
    │   └── states/          # 8 états concrets de la FSM
    ├── team/                # crypto.py, protocol.py, decoy.py (non câblés)
    └── utils/
        └── logger.py

Déroulé du démarrage

La fonction run(args) (ai/src/main.py:83) enchaîne :

sequenceDiagram
    participant M as main.run
    participant C as Client
    participant Cx as Connection
    participant D as DroneState
    participant F as FSM

    M->>C: setup_connection(host, port, name)
    C->>Cx: connect() puis do_handshake()
    Cx-->>C: (nb_per_team, (W, H))
    C-->>M: (nb_per_team, (W, H))
    M->>D: DroneState(nb_per_team, (W,H), client)
    Note over D: envoie immédiatement<br/>Inventory + Look
    M->>F: FSM(drone, client)
    M->>F: transition_to("survive")
    loop tant que vivant
        M->>C: receive()  (lit, route vers les callbacks)
        loop tant que command_queue.can_send()
            M->>F: decide_next_command()
        end
    end
  1. Connexion & handshakeClient.setup_connection ouvre la Connection, déroule le handshake (WELCOME, envoi du nom d'équipe, lecture du CLIENT-NUM puis X Y) et renvoie (nb_per_team, (largeur, hauteur)).
  2. État initialDroneState(nb_per_team, world_size, client) place le drone au centre de la carte, oriente au N, construit la WorldMap, et envoie d'emblée Inventory et Look pour amorcer l'état (ai/src/state/drone.py:76-86).
  3. FSMFSM(drone, client) enregistre les états et démarre sur l'état survive (ai/src/main.py:124-125).

État initial survive, pas explore

Le README et certains schémas décrivent un démarrage en explore. Le code démarre en réalité sur survive. Voir Limitations connues.

Boucle principale

while 1:
    client.receive()                          # lit le réseau, route les réponses
    while client.command_queue.can_send():    # tant qu'il reste des slots
        if not fsm.decide_next_command():     # demande une action à la FSM
            break
    if drone.is_dead() and config.INIT:        # famine confirmée
        break

La boucle (ai/src/main.py:127-144) reçoit d'abord (les réponses serveur mettent à jour l'état via les callbacks), puis demande des commandes à la FSM tant que la file de commandes en vol n'est pas pleine (plafond de 10).

Boucle active, pas de multiplexage selectors

Malgré les docstrings, il n'y a pas de boucle selectors sans attente active : c'est un while 1 qui appelle select.select(..., 0.1) (poll de 100 ms). Voir Limitations connues.

Pour aller plus loin