🐜 Lem-in C — Guide Complet

Lem-in est un projet de la 42 School qui consiste à faire passer un nombre donné de fourmis d'un point de départ à un point d'arrivée dans le minimum de tours, en évitant les collisions. Le projet utilise un algorithme de BFS (Breadth-First Search) combiné à une gestion de flot maximum inspirée d'Edmonds-Karp.

▶️ Lancer le Visualiseur Interactif

📋 Sommaire

🏗️ Structures de Données

Le projet utilise 5 structures principales, définies dans include/graph.h, include/farm.h et include/solve.h.

t_coord — Coordonnées 2D

typedef struct s_coord {
    int x;
    int y;
} t_coord;

t_room — La Structure Centrale

typedef struct s_room {
    char *name;           // Nom de la salle (ex: "1", "A", "start")
    t_coord c;            // Coordonnées (x, y)
    int ant;              // ID de la fourmi actuellement dans la salle (0 si vide)
    int start;            // 1 si c'est le point de départ, 0 sinon
    int end;              // 1 si c'est le point d'arrivée, 0 sinon
    t_list *lnks;         // Liste chaînée des salles connectées (voisins)
    struct s_room *f_to;  // Salle suivante dans le flux de fourmis (forward)
    struct s_room *f_fm;  // Salle précédente dans le flux (from)
    struct s_room *next;  // Salle suivante dans la liste chaînée globale
} t_room;
Champs critiques pour la résolution :

t_farm — Conteneur Principal

typedef struct s_farm {
    int ants;      // Nombre total de fourmis à déplacer
    t_room *rms;   // Pointeur vers la première salle (liste chaînée)
} t_farm;

t_bfs — Pour la Résolution

typedef struct s_bfs {
    int path_size;      // Longueur du chemin depuis le départ
    int send_ants;      // Nombre de fourmis à envoyer sur ce chemin (temporaire)
    int min_ants;       // Nombre optimal de fourmis calculé (final)
    t_room **room;      // Pointeur vers le pointeur de salle (double indirection)
    struct s_bfs *prev; // Noeud précédent dans le chemin BFS (backtracking)
    struct s_bfs *next; // Noeud suivant dans la file BFS
} t_bfs;
Pourquoi t_room ** (double pointeur) ? Pour pouvoir modifier les références de salles lors du backtracking et de la mise à jour du flux. C'est une technique nécessaire en C qui n'a pas de références natives.

t_list — Liste Chaînée Générique (libft)

typedef struct s_list {
    void *obj;          // Pointeur vers l'objet stocké
    size_t dim;         // Taille de l'objet
    struct s_list *next;
} t_list;

Utilisée pour stocker les liens entre salles dans t_room.lnks. Les fonctions ft_lstadd (ajout en tête, LIFO) et ft_lstnew2 sont de la libft personnalisée.

🔍 Parsing et Validation

Le parsing utilise une machine à états finis (FSM) à 3 sections, gérée par fill_farm() et is_cmd() :

SectionContenuFormat
0Nombre de fourmisEntier positif
1Sallesnom x y (3 tokens séparés par espaces)
2Liensroom1-room2 (un seul -)

Commandes spéciales

Règles de validation

Lecture de l'input

La lecture se fait via get_next_line2(), une version custom de get_next_line avec un buffer statique pitcher qui permet la lecture ligne par ligne depuis stdin.

🧮 Algorithmes

1. Vue d'ensemble du flux de résolution

solve()
  │
  ├── endpoints() : valider 1 start + 1 end
  │
  ├── BOUCLE de découverte de chemins :
  │   ├── bfs() : BFS récursif pour trouver un chemin start → end
  │   ├── update_flow() : marquer f_to/f_fm sur le chemin trouvé
  │   └── répéter jusqu'à ce qu'aucun chemin ne soit trouvé
  │
  └── send_ants() : simulation tour par tour
      ├── path_list() : extraire les chemins via f_to
      ├── path_config() : optimiser la distribution des fourmis
      └── boucle de simulation (move_ants + select_paths)

2. BFS (Breadth-First Search) — bfs()

Le BFS est récursif et utilise une file chaînée FIFO. Pour chaque noeud courant, il explore tous les voisins :

Conditions de flow_to(from, to) : Cela empêche de réutiliser un flux existant, sauf en mode undo (reverse edge).

3. Mise à jour du flux — update_flow()

Après un BFS réussi, on remonte depuis end vers start via les pointeurs prev :

// Pour chaque paire (prv, r) sur le chemin :
if (!(r->f_to == prv || prv->f_fm == r)) {
    r->f_fm = prv;    // marquer le backward
    prv->f_to = r;    // marquer le forward
}
// Annuler les conflits :
if (r->f_to == prv) r->f_to = NULL;
if (prv->f_fm == r) prv->f_fm = NULL;
Gestion des conflits : Si un tunnel existait déjà dans l'autre sens, il est annulé (f_to = NULL ou f_fm = NULL). C'est ce qui permet à l'algorithme de "défaire" un flux précédent si un meilleur chemin est trouvé — c'est le cœur d'Edmonds-Karp.

4. Extraction des chemins — path_list()

Après la boucle de découverte, on extrait tous les chemins valides en parcourant les voisins du start et en suivant f_to jusqu'à end. Les chemins sont triés par longueur (plus court en tête) via insertio() (insertion triée par path_size).

5. Optimisation du nombre de tours — path_config()

Calcule combien de fourmis envoyer sur chaque chemin pour minimiser le nombre total de tours. Teste toutes les combinaisons de chemins (1, 2, ..., N chemins) et garde la configuration optimale.

Formule : Tours = population + longest_path_size

La distribution se calcule via populate_path() :

nxt->send_ants = aux->send_ants - abs(nxt->path_size - aux->path_size);

Les chemins plus longs reçoivent moins de fourmis car ils "occupent" le pipeline plus longtemps. Le résultat est stocké dans min_ants de chaque chemin.

🐜 Simulation des Fourmis

La simulation se fait tour par tour dans send_ants() :

while (end->ant < ants) {
    turns++;
    entro = move_ants(&paths);        // faire avancer toutes les fourmis
    entro += select_paths(&paths);    // injecter de nouvelles fourmis
    ft_putendl("");                   // nouvelle ligne = nouveau tour
    if (!entro) break;                // plus aucun mouvement possible
}

push_ants() — Déplacement sur un chemin

Fait avancer les fourmis d'une case sur un chemin, en parcourant via f_to :

select_paths() — Injection de nouvelles fourmis

Pour chaque chemin avec min_ants > 0 et s'il reste des fourmis à envoyer :

Évitement de collision : Les fourmis ne se cognent jamais car chaque salle a au plus une fourmi (ant), le mouvement est synchronisé (toutes bougent en même temps), et les tunnels f_to garantissent un sens unique.

📚 Fonctions Principales

FonctionFichierDescription
main()lem_in.cPoint d'entrée : parse flags, init farm, solve, cleanup
fill_farm()lem_in.cFSM 3 sections : ants → rooms → links
is_cmd()lem_in.cDispatche chaque ligne vers is_room/is_link
is_room()valid_input.cValide le format salle (nom x y) + unicité
is_link()valid_input.cValide le format lien (room1-room2) + existence
add_link()list.cCrée un lien bidirectionnel entre deux salles
solve()solve.cOrchestration : BFS → update_flow → send_ants
bfs()bfs.cBFS récursif avec flow_to + undo (Edmonds-Karp)
update_flow()flow.cMarque f_to/f_fm + annule les conflits
path_list()flow_treatment.cExtrait les chemins via f_to depuis les voisins de start
path_config()path_computation.cCalcule min_ants par chemin (optimisation)
populate_path()path_computation.cDistribution : send_ants[i+1] = send_ants[i] - abs(diff)
send_ants()flow.cBoucle de simulation tour par tour
push_ants()flow.cDécale les fourmis d'une case sur un chemin
select_paths()flow.cInjecte de nouvelles fourmis depuis start

📝 Exemple Complet

Input :

4
##start
1 0 0
2 1 0
3 0 1
4 1 1
##end
5 2 0
1-2
1-3
2-4
3-4
4-5

Output (vérifié avec le binaire) :

L1-3
L1-4 L2-3
L2-4 L1-5 L3-3
L3-4 L2-5 L4-3
L4-4 L3-5
L4-5

6 tours — Toutes les fourmis prennent le même chemin 1→3→4→5 (ou 1→2→4→5, qui est équivalent). Comme les deux chemins partagent le nœud 4, ils ne sont pas disjoints en sommets. Par conséquent, l'algorithme d'Edmonds-Karp ne conserve qu'un seul chemin pour éviter les collisions sur le nœud 4, et les 4 fourmis y sont envoyées séquentiellement.

⏱️ Complexité

🔧 Compilation & Build WASM

Build native

make        # compile lem-in + libft
./lem-in < map.txt
./lem-in --d < map.txt    # debug : nombre de lignes
./lem-in --dp < map.txt   # debug avancé : infos nœuds + flux

Build WebAssembly (Emscripten)

make web    # nécessite emsdk installé
# génère docs/lem-in.js + docs/lem-in.wasm
Flags de debug :

⚠️ Pièges Courants


Projet Lem-in C — 42 School | Lancer le visualiseur | Voir sur GitHub