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 :
À 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.
Le jeu se déroule sur une grille cartésienne infinie théoriquement, mais de taille finie dans notre implémentation.
Chaque cellule de la grille peut être soit vide, soit occupée par une colonie (généralement représentée en noir).
Une "fourmi" se déplace sur la grille selon des règles précises :
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.
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.
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
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).
Pour ce projet, nous n'aurons besoin d'aucune dépendance externe. Tout sera implémenté en utilisant la bibliothèque standard de Rust.
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
}
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.
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.
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).
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).
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.
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.
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 :
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 :
deplacer_fourmi pour déplacer la fourmi selon les règles du jeu.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