Tutoriel guidant vers la réalisation d'un petit jeux de simulation de colonie de fourmis (jeu de Langton)


Je peux vous guider à travers la création d'un petit jeu de simulation de colonie de fourmis, également connu sous le nom de Jeu de Langton, en mode console. C'est un excellent projet pour s'entraîner à la programmation en Rust tout en explorant les concepts d'automates cellulaires et de simulations.

Voici les étapes que nous allons suivre :

  1. Introduction au jeu de Langton
  2. Mise en place du projet Rust
  3. Représentation de la grille du jeu
  4. Implémentation des règles du jeu
  5. Boucle principale du jeu
  6. Améliorations supplémentaires

À chaque étape, je vous fournirai des explications détaillées, des exemples de code Rust et vous guiderai dans l'implémentation. N'hésitez pas à poser des questions si quelque chose n'est pas clair.

Ce projet vous permettra de travailler sur les structures de données, les algorithmes, les boucles, les entrées/sorties et bien d'autres concepts de programmation. Êtes-vous prêt à commencer ce tutoriel étape par étape ?


Très bien, commençons par l'introduction au Jeu de Langton, également connu sous le nom de "Colonies de Fourmis". C'est un automate cellulaire à deux dimensions imaginé par Chris Langton en 1986.

Règles du jeu :

  1. Le jeu se déroule sur une grille cartésienne infinie théoriquement, mais de taille finie dans notre implémentation.

  2. Chaque cellule de la grille peut être soit vide, soit occupée par une colonie (généralement représentée en noir).

  3. Une "fourmi" se déplace sur la grille selon des règles précises :

  4. Au départ, la grille est vide à l'exception d'une seule colonie, et la fourmi se trouve sur cette cellule, faisant face à une direction initiale (généralement vers le haut).

Le but du jeu est d'observer l'évolution des motifs formés par les colonies. Des motifs complexes et intéressants émergent souvent après de nombreux tours.

Représentation de la grille :

Nous allons représenter la grille comme une matrice 2D, où chaque élément est soit 0 (case vide), soit 1 (colonie présente). Par exemple, une grille 5x5 pourrait être initialisée comme ceci :

let mut grille = [
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0], // La fourmi commence ici
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
];

Dans l'exemple ci-dessus, la fourmi commence sur la cellule (2, 2) de la grille.

Pour afficher la grille dans le terminal, nous pouvons simplement parcourir la matrice et imprimer des caractères représentant les cases vides et occupées.

Êtes-vous prêt à passer à l'étape suivante et configurer le projet Rust pour commencer l'implémentation ?


Très bien, passons à l'étape suivante qui est la mise en place du projet Rust.

Création d'un nouveau projet Cargo

Ouvrez un terminal, naviguez jusqu'au répertoire où vous souhaitez créer votre projet, et exécutez la commande suivante pour créer un nouveau projet Cargo :

cargo new jeu-langton

Cette commande créera un répertoire jeu-langton contenant les fichiers de base pour un projet Rust. Naviguez dans ce répertoire :

cd jeu-langton

Ouvrir le projet dans votre éditeur

Ouvrez le projet dans votre éditeur de code préféré. Vous devriez voir les fichiers Cargo.toml (fichier de configuration) et src/main.rs (fichier source principal).

Dépendances

Pour ce projet, nous n'aurons besoin d'aucune dépendance externe. Tout sera implémenté en utilisant la bibliothèque standard de Rust.

Fichier source principal

Ouvrez "src/main.rs" et remplacez le contenu par le code suivant qui définit une fonction main vide pour l'instant :

fn main() {
    // Votre code ira ici
}

Compiler et exécuter

Vous pouvez compiler et exécuter votre projet à tout moment avec les commandes suivantes :

cargo build   # Compiler le projet
cargo run     # Compiler et exécuter le projet

Pour l'instant, rien ne s'affichera puisque notre fonction main est vide, mais cela vous permettra de vérifier que votre configuration fonctionne correctement.

Maintenant que notre projet est correctement configuré, nous pouvons passer à l'étape suivante qui est la représentation de la grille du jeu. Je vous guiderai à travers la définition des structures nécessaires et l'implémentation des fonctions pour initialiser et afficher la grille.

N'hésitez pas à me poser des questions si vous avez besoin d'éclaircissements sur cette étape de configuration.


Parfait, passons à l'étape de la représentation de la grille du jeu.

Définition d'une structure pour les cellules

Nous allons commencer par définir une structure pour représenter les cellules de la grille. Chaque cellule peut être soit vide (0), soit occupée par une colonie (1).

Dans src/main.rs, ajoutez la structure suivante :

#[derive(Clone, Copy, PartialEq)]
struct Cellule {
    etat: u8, // 0 pour vide, 1 pour colonie
}

Nous utilisons ici un u8 (entier non signé sur 8 bits) pour représenter l'état de la cellule. Le décorateur #[derive(Clone, Copy, PartialEq)] nous permettra de cloner, copier et comparer facilement les instances de Cellule.

Définition d'une structure pour la grille

Ensuite, nous devons définir une structure pour représenter la grille elle-même. Il s'agira essentiellement d'une matrice 2D de cellules.

Ajoutez la structure suivante :

struct Grille {
    cellules: Vec<Vec<Cellule>>,
    largeur: usize,
    hauteur: usize,
}

Nous utilisons un Vec<Vec<Cellule>> pour stocker les cellules, et nous gardons également une trace de la largeur et de la hauteur de la grille.

Fonction pour initialiser la grille

Implémentons maintenant une fonction pour initialiser la grille avec une taille donnée et une colonie initiale au centre.

Ajoutez cette fonction à votre fichier src/main.rs :

impl Grille {
    fn nouvelle(largeur: usize, hauteur: usize) -> Grille {
        let mut cellules = Vec::with_capacity(hauteur);
        for _ in 0..hauteur {
            let mut ligne = Vec::with_capacity(largeur);
            for _ in 0..largeur {
                ligne.push(Cellule { etat: 0 });
            }
            cellules.push(ligne);
        }

        let x = largeur / 2;
        let y = hauteur / 2;
        cellules[y][x] = Cellule { etat: 1 };

        Grille {
            cellules,
            largeur,
            hauteur,
        }
    }
}

Cette fonction crée une nouvelle instance de Grille avec les dimensions spécifiées. Elle initialise toutes les cellules à 0 (vide), sauf la cellule centrale qui est initialisée à 1 (colonie).

Fonction pour afficher la grille

Ajoutons une fonction pour afficher la grille dans le terminal :

impl Grille {
    // ... (fonction nouvelle ici)

    fn afficher(&self) {
        for ligne in &self.cellules {
            for cellule in ligne {
                let caractere = if cellule.etat == 0 { '.' } else { '#' };
                print!("{}", caractere);
            }
            println!();
        }
    }
}

Cette fonction parcourt chaque cellule de la grille et imprime un caractère représentant son état (. pour vide, # pour colonie).

Mise à jour de la fonction main

Finalement, mettons à jour la fonction main pour tester notre implémentation :

fn main() {
    let grille = Grille::nouvelle(10, 10);
    grille.afficher();
}

Cette fonction main crée une nouvelle grille de 10x10 cellules et l'affiche dans le terminal.

Vous pouvez maintenant compiler et exécuter votre programme avec cargo run. Vous devriez voir une grille 10x10 avec une colonie au centre, similaire à ceci :

........
........
....#...
........
........
........
........
........
........
........

Félicitations ! Vous avez implémenté avec succès la représentation de la grille pour le Jeu de Langton. Dans la prochaine étape, nous implémenterons les règles du jeu et la logique de la fourmi.

N'hésitez pas si vous avez des questions ou si quelque chose n'est pas clair. Nous progressons bien dans la réalisation de ce petit jeu de simulation.


Passons maintenant à l'étape suivante qui consiste à implémenter les règles du jeu et le déplacement de la fourmi. Nous allons ajouter une boucle qui effectuera 20 itérations du jeu.

Représentation de la fourmi

Commençons par ajouter une structure pour représenter la fourmi et sa position sur la grille :

struct Fourmi {
    x: usize,
    y: usize,
    direction: Direction,
}
#[derive(Clone, Copy, PartialEq)]
enum Direction {
    Haut,
    Droite,
    Bas,
    Gauche,
}

La structure Fourmi contient les coordonnées x et y de sa position actuelle, ainsi que sa direction de déplacement. L'énumération Direction représente les quatre directions possibles.

Fonction pour déplacer la fourmi

Ajoutons une fonction pour déplacer la fourmi selon les règles du jeu :

impl Grille {
    // ... (fonctions précédentes ici)

    fn deplacer_fourmi(&mut self, fourmi: &mut Fourmi) {
        let (x, y) = (fourmi.x, fourmi.y);
        let cellule = &mut self.cellules[y][x];

        match cellule.etat {
            0 => {
                cellule.etat = 1;
                fourmi.direction = match fourmi.direction {
                    Direction::Haut => Direction::Droite,
                    Direction::Droite => Direction::Bas,
                    Direction::Bas => Direction::Gauche,
                    Direction::Gauche => Direction::Haut,
                };
            }
            1 => {
                cellule.etat = 0;
                fourmi.direction = match fourmi.direction {
                    Direction::Haut => Direction::Gauche,
                    Direction::Droite => Direction::Haut,
                    Direction::Bas => Direction::Droite,
                    Direction::Gauche => Direction::Bas,
                };
            }
            _ => unreachable!(),
        }

        match fourmi.direction {
            Direction::Haut => fourmi.y = fourmi.y.wrapping_sub(1) % self.hauteur,
            Direction::Droite => fourmi.x = (fourmi.x + 1) % self.largeur,
            Direction::Bas => fourmi.y = (fourmi.y + 1) % self.hauteur,
            Direction::Gauche => fourmi.x = fourmi.x.wrapping_sub(1) % self.largeur,
        }
    }
}

Cette fonction prend une référence mutable de la grille et de la fourmi. Elle effectue les opérations suivantes :

  1. Récupère l'état de la cellule courante (0 ou 1).
  2. Si la cellule est vide (0), elle est occupée (1) et la direction de la fourmi est tournée de 90 degrés vers la droite.
  3. Si la cellule est occupée (1), elle est vidée (0) et la direction de la fourmi est tournée de 90 degrés vers la gauche.
  4. Selon la nouvelle direction de la fourmi, ses coordonnées x et y sont mises à jour pour la déplacer d'une case. La fonction wrapping_sub est utilisée pour gérer les déplacements qui sortent de la grille, en revenant de l'autre côté (comportement "toroïdal").

Boucle principale du jeu

Modifions maintenant la fonction main pour effectuer 20 itérations du jeu :

fn main() {
    let mut grille = Grille::nouvelle(10, 10);
    let mut fourmi = Fourmi {
        x: grille.largeur / 2,
        y: grille.hauteur / 2,
        direction: Direction::Haut,
    };

    for _ in 0..20 {
        grille.deplacer_fourmi(&mut fourmi);
        grille.afficher();
        println!();
    }
}

Ici, nous créons une nouvelle grille de 10x10 cellules et une fourmi au centre, faisant face vers le haut. Puis, nous entrons dans une boucle qui effectue 20 itérations du jeu :

  1. Appelle la fonction deplacer_fourmi pour déplacer la fourmi selon les règles du jeu.
  2. Affiche la grille mise à jour dans le terminal.
  3. Imprime une ligne vide pour séparer les itérations.

Lorsque vous exécutez cargo run maintenant, vous devriez voir la grille s'afficher à chaque itération, avec la fourmi se déplaçant et laissant une traînée de colonies derrière elle.

Vous pouvez ajuster le nombre d'itérations (ici 20) pour observer l'évolution des motifs sur un plus grand nombre de tours.

N'hésitez pas si vous avez des questions ou si quelque chose n'est pas clair. Dans la prochaine étape, nous pourrons ajouter des améliorations supplémentaires, comme des couleurs ou un menu principal.


Résultat : langton.rs


Installez le compilateur : rustc

Compilez avec :

$ rustc langton.rs

Puis démarez le programme avec :

$ ./langton

Tutoriel: Claude-3-Sonnet