Acheter version papier

Rust

Fork me on GitHub

I. Les bases de la programmation en Rust

15. Jeu du plus ou moins

Le but de ce chapitre est de mettre en pratique ce que vous avez appris dans les chapitres précédents au travers de l'écriture d'un jeu du plus ou moins. Voici le déroulement :

  1. L'ordinateur choisit un nombre (on va dire entre 1 et 100).
  2. Vous devez deviner le nombre.
  3. Vous gagnez si vous le trouvez en moins de 10 essais.

Exemple d'une partie :

Génération du nombre...
C'est parti !
Entrez un nombre : 50
-> C'est plus grand
Entrez un nombre : 75
-> C'est plus petit
Entrez un nombre : 70
Vous avez gagné !

La grande inconnue de l’écriture de ce jeu est de savoir comment générer un nombre aléatoirement. Pour cela, nous allons utiliser la crate rand. Ajoutez-la comme dépendance dans votre fichier Cargo.toml comme vu dans le chapitre précédent. Maintenant, pour générer un nombre il vous suffira de faire :

Runuse rand::Rng;

fn main() {
    let nombre_aleatoire = rand::thread_rng().gen_range(1..=100);
}

Il va aussi falloir récupérer ce que l'utilisateur écrit sur le clavier. Pour cela, utilisez la méthode read_line de l'objet Stdin (qu'on peut récupérer avec la fonction stdin). Il ne vous restera plus qu'à convertir cette String en entier en utilisant la méthode from_str. Je pense vous avoir donné assez d'indications pour que vous puissiez vous débrouiller seuls. Bon courage !

Je propose une solution juste en dessous pour ceux qui n'y arriveraient pas ou qui souhaiteraient tout simplement comparer leur code avec le mien.

La solution

J'ai écrit cette solution en essayant de rester aussi clair que possible sur ce que je fais.

Commençons par la fonction qui se chargera de nous retourner le nombre entré par l'utilisateur :

Runuse std::io;
use std::str::FromStr;

// Elle ne prend rien en entrée et retourne un Option<isize> (dans le cas où ça
// ne fonctionnerait pas).
fn recuperer_entree_utilisateur() -> Option<isize> {
    let mut entree = String::new();

    // On récupère ce qu'a entré l'utilisateur dans la variable "entree".
    if let Err(err) = io::stdin().read_line(&mut entree).is_err() {
        // Une erreur s'est produite, on doit avertir l'utilisateur !
        println!("Erreur lors de la récupération de la saisie : {:?}", err);
        return None;
    }
    // Tout s'est bien passé, on peut convertir la String en entier.
    // La méthode "trim" enlève tous les caractères "blancs" en début et fin
    // de chaîne.
    match isize::from_str(&entree.trim()) {
        // Tout s'est bien déroulé, on retourne donc le nombre.
        Ok(nombre) => Some(nombre),
        // Si jamais la conversion échoue (si l'utilisateur n'a pas rentré un
        // nombre valide), on retourne "None".
        Err(_) => {
            println!("Veuillez entrer un nombre valide !");
            None
        }
    }
}

Voilà une bonne chose de faite ! Il va nous falloir à présent implémenter le coeur du jeu :

Run// Utilisé pour "flusher" la sortie console.
use std::io::Write;

fn jeu() -> bool {
    // On va mettre 10 tentatives avant de dire au joueur qu'il a perdu.
    let mut tentative = 10;

    println!("Génération du nombre...");
    let nombre_aleatoire = rand::thread_rng().gen_range(1..=100);
    println!("C'est parti !");
    while tentative > 0 {
        // On ne veut pas de retour à la ligne !
        print!("Entrez un nombre : ");
        // Si on n'utilise pas cette méthode, on ne verra pas l'affichage de
        // print! tout de suite
        io::stdout().flush();
        match recuperer_entree_utilisateur() {
            Some(nombre) => {
                if nombre < nombre_aleatoire {
                    println!("C'est plus grand !");
                } else if nombre > nombre_aleatoire {
                    println!("C'est plus petit !");
                } else {
                    return true;
                }
            }
            None => {}
        }
        tentative -= 1;
    }
    false
}

Il ne nous reste désormais plus qu'à appeler cette fonction dans notre fonction main et le tour est joué !

Runfn main() {
    println!("=== Jeu du plus ou moins ===");
    println!("");
    if jeu() {
        println!("Vous avez gagné !");
    } else {
        println!("Vous avez perdu…");
    }
}

Voici maintenant le code complet (non commenté) de ma solution :

Runuse rand::Rng;
use std::io::Write;
use std::io;
use std::str::FromStr;

fn recuperer_entree_utilisateur() -> Option<isize> {
    let mut entree = String::new();

    if io::stdin().read_line(&mut entree).is_err() {
        println!("Erreur lors de la récupération de la saisie...");
        return None;
    }
    match isize::from_str(&entree.trim()) {
        Ok(nombre) => Some(nombre),
        Err(_) => {
            println!("Veuillez entrer un nombre valide !");
            None
        }
    }
}

fn jeu() -> bool {
    let mut tentative = 10;

    println!("Génération du nombre...");
    let nombre_aleatoire = rand::thread_rng().gen_range(1..=100);
    println!("C'est parti !");
    while tentative > 0 {
        print!("Entrez un nombre : ");
        io::stdout().flush();
        match recuperer_entree_utilisateur() {
            Some(nombre) => {
                if nombre < nombre_aleatoire {
                    println!("C'est plus grand !");
                } else if nombre > nombre_aleatoire {
                    println!("C'est plus petit !");
                } else {
                    return true;
                }
            }
            None => {}
        }
        tentative -= 1;
    }
    false
}

fn main() {
    println!("=== Jeu du plus ou moins ===");
    println!("");
    if jeu() {
        println!("Vous avez gagné !");
    } else {
        println!("Vous avez perdu…");
    }
}

Si vous avez un problème, des commentaires ou autres à propos de cette solution, n'hésitez pas à ouvrir une issue sur github.

Améliorations

Il est possible d'ajouter quelques améliorations à cette version comme :

  • Un mode 2 joueurs (un joueur choisit un nombre, l'autre le devine).
  • Proposer la possibilité de recommencer quand on a fini une partie.
  • Afficher le nombre de coups qu'il a fallu pour gagner (et pourquoi pas sauvegarder les meilleurs scores ?).
  • Proposer plusieurs modes de difficulté.

Les choix sont vastes, à vous de faire ce qui vous tente le plus !