Contactez-nous

Compétences mises en oeuvre : lecture d'entrée utilisateur, génération de nombres aléatoires (crate `rand`), boucles, conditions, gestion simple d'erreurs

Découvrez les compétences Rust essentielles pour créer un jeu de devinette : lecture d'entrée, nombres aléatoires avec `rand`, boucles, conditions et gestion d'erreurs. Maîtrisez les bases.

Les piliers de votre jeu : compétences Rust essentielles à mobiliser

La création de notre jeu de devinette en Rust va bien au-delà d'un simple exercice de codage. C'est une opportunité concrète de mettre en pratique et de maîtriser un ensemble de compétences fondamentales du langage. Ce sous-chapitre détaille les techniques et concepts clés que vous allez activement utiliser : la lecture des saisies de l'utilisateur, la génération de valeurs imprévisibles, la structuration de la logique répétitive et conditionnelle, ainsi que les premiers pas dans la gestion des imprévus.

Chacune de ces compétences est un bloc de construction indispensable pour tout développeur Rust. En les appliquant dans le contexte ludique d'un jeu, vous ancrez plus facilement leur utilité et leur fonctionnement. Nous allons explorer comment Rust, avec sa bibliothèque standard et son écosystème de crates, nous fournit les outils pour implémenter ces fonctionnalités de manière efficace et sécurisée.

Préparez-vous à interagir avec le monde extérieur via la console, à introduire une part de hasard dans votre programme, à contrôler le flux d'exécution avec précision, et à anticiper les petites erreurs qui peuvent survenir. C'est un véritable concentré de programmation pratique qui vous attend.

Interagir avec le joueur : la lecture d'entrée utilisateur en Rust

Au coeur de tout jeu interactif se trouve la capacité du programme à recevoir des informations de l'utilisateur. Dans notre cas, il s'agira de lire le nombre que le joueur propose. Pour cela, Rust nous offre le module std::io, et plus particulièrement la fonction stdin().read_line(). Cette fonction lit une ligne de texte depuis l'entrée standard (généralement le clavier) et la stocke dans une chaîne de caractères (String).

Voici un aperçu de son utilisation :

use std::io;

fn main() {
    println!("Devinez le nombre !");
    println!("Veuillez entrer votre proposition.");

    let mut guess = String::new(); // Variable mutable pour stocker l'entrée

    io::stdin()
        .read_line(&mut guess) // Lit la ligne et la place dans `guess`
        .expect("Echec de la lecture de l'entrée utilisateur"); // Gestion d'erreur basique

    println!("Vous avez proposé : {}", guess);
}

Notez l'utilisation de &mut guess : read_line prend une référence mutable à une chaîne pour y écrire l'entrée. De plus, l'entrée lue inclura le caractère de nouvelle ligne (Entrée). Il faudra souvent le supprimer avec la méthode trim(). Un autre défi sera de convertir cette chaîne en un type numérique (comme u32) pour pouvoir la comparer au nombre secret. Cette conversion peut échouer si l'utilisateur saisit autre chose qu'un nombre, ce qui nous amènera naturellement à la gestion des erreurs.

Introduire l'imprévu : génération de nombres aléatoires avec la crate `rand`

Pour que notre jeu de devinette soit intéressant, le nombre secret doit être différent à chaque partie. Rust, dans sa bibliothèque standard, ne fournit pas nativement de générateur de nombres aléatoires complexes. C'est un choix délibéré pour garder la bibliothèque standard légère. Pour cette fonctionnalité, nous nous tournons vers l'écosystème de crates, et la crate rand est la solution la plus populaire et recommandée.

Pour utiliser rand, nous devons d'abord l'ajouter comme dépendance dans notre fichier Cargo.toml :

[dependencies]
rand = "0.8.5" # Vérifiez la dernière version sur crates.io

Ensuite, dans notre code, nous pouvons l'utiliser pour générer un nombre dans une plage spécifique. Par exemple, pour un nombre entre 1 et 100 (inclus) :

use rand::Rng; // Trait nécessaire pour utiliser les méthodes de génération

fn main() {
    let secret_number = rand::thread_rng().gen_range(1..=100);
    // 1..=100 est une expression de portée inclusive

    // println!("Le nombre secret est : {}", secret_number); // Pour le débogage
    // ... reste du code du jeu
}
rand::thread_rng() nous donne un générateur de nombres aléatoires qui est local au thread courant et initialisé par le système d'exploitation. La méthode gen_range(range) génère ensuite une valeur aléatoire dans la portée spécifiée. L'utilisation de crates externes est une compétence clé en Rust, et rand est un excellent premier exemple.

Contrôler le jeu : boucles et conditions pour la logique

La mécanique principale du jeu repose sur la répétition (l'utilisateur fait plusieurs tentatives) et la prise de décision (comparer la proposition au secret). Rust offre plusieurs structures pour cela.

Les boucles : Pour permettre à l'utilisateur de jouer jusqu'à ce qu'il trouve le bon nombre, une boucle loop est idéale. Une boucle loop s'exécute indéfiniment jusqu'à ce qu'on l'interrompe explicitement avec le mot-clé break.
// ... (génération du nombre secret, etc.)
loop {
    println!("Veuillez entrer votre proposition.");
    // ... (lecture de l'entrée utilisateur)

    // ... (conversion de l'entrée en nombre)
    // let guess: u32 = ... ;

    println!("Vous avez proposé : {}", guess);

    // if guess == secret_number { 
    //     println!("Bravo !");
    //     break; // Sort de la boucle
    // } else if guess < secret_number {
    //     println!("Trop petit !");
    // } else {
    //     println!("Trop grand !");
    // }
}
Les conditions : Pour comparer la proposition de l'utilisateur au nombre secret et afficher le message approprié, nous utiliserons les expressions if/else if/else. Rust propose également une construction plus puissante, match, qui est particulièrement utile pour comparer une valeur à une série de motifs. Pour notre jeu, match peut être utilisé sur le résultat de la comparaison des nombres, qui est de type std::cmp::Ordering (Less, Greater, Equal).
use std::cmp::Ordering;

// ... à l'intérieur de la boucle
// let guess: u32 = ... ;
// let secret_number: u32 = ... ;

match guess.cmp(&secret_number) { // cmp retourne un Ordering
    Ordering::Less => println!("Trop petit !"),
    Ordering::Greater => println!("Trop grand !"),
    Ordering::Equal => {
        println!("Bravo, vous avez trouvé !");
        break; // Sort de la boucle loop
    }
}

La combinaison de loop et match (ou if/else) constitue l'épine dorsale de la logique de notre jeu.

Anticiper les imprévus : gestion simple des erreurs

Lorsqu'on interagit avec l'utilisateur, il faut s'attendre à des saisies incorrectes. Par exemple, si l'utilisateur tape "abc" alors que le programme attend un nombre, la conversion de la chaîne de caractères en type numérique échouera. Rust nous encourage à gérer explicitement ces situations grâce à ses types Result<T, E> et Option<T>.

La méthode read_line() retourne un io::Result, indiquant le nombre d'octets lus ou une erreur d'entrée/sortie. Nous avons utilisé .expect("..."), qui est une manière simple de gérer un Result : si c'est Ok, la valeur est retournée ; si c'est Err, le programme panique et affiche le message fourni.

De même, la conversion d'une chaîne en nombre (par exemple, avec parse()) retourne un Result. Si la chaîne n'est pas un nombre valide, parse() retourne une variante Err.

// ... (après avoir lu `guess` dans une String)
let guess: u32 = match guess.trim().parse() { // trim() enlève les espaces et 
                                             // parse() retourne un Result
    Ok(num) => num, // Si la conversion réussit, `num` est le nombre
    Err(_)  => {      // `_` ignore le type d'erreur spécifique
        println!("Veuillez entrer un nombre valide !");
        continue; // Retourne au début de la boucle `loop` pour une nouvelle tentative
    }
};

Ici, au lieu de faire paniquer le programme avec expect(), nous utilisons un match pour traiter les deux cas. Si la conversion échoue (Err), nous affichons un message à l'utilisateur et utilisons continue pour sauter le reste de l'itération actuelle de la boucle et demander une nouvelle saisie. C'est une forme de gestion d'erreur plus robuste et conviviale.

Ces mécanismes de gestion d'erreur, même simples, sont cruciaux pour créer des programmes fiables et agréables à utiliser. Rust, par son système de types, nous pousse à considérer ces cas dès le départ.