Rust

Any donation is very welcome
Fork me on GitHub

I. Les bases de la programmation en Rust

13. 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.

Relativement simple. Je pense que vous commencez déjà à voir comment tout ça va s'articuler. 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é !

(Je sais, je suis vraiment trop fort à ce jeu.)

"Mais comment fait-on pour générer un nombre aléatoire ?"

Bonne question ! On va utiliser la bibliothèque externe rand. Ajoutez-la comme dépendance dans votre fichier Cargo.toml et ensuite importez-la dans votre fichier principal. Maintenant, pour générer un nombre il vous suffira de faire :

Runuse rand::Rng;

let nombre_aleatoire = rand::thread_rng().gen_range(1, 101);

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 !

Maintenant vous savez ce que vous avez à faire. 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;

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

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

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

Runuse std::io::Write; // utilisé pour "flusher" la sortie console

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

    println!("Génération du nombre...");
    let nombre_aleatoire = rand::thread_rng().gen_range(1, 101);
    println!("C'est parti !");
    while tentative > 0 {
        print!("Entrez un nombre : "); // on ne veut pas de retour à la ligne !
        io::stdout().flush(); // si on n'utilise pas cette méthode, on ne verra pas l'affichage de print! tout de suite
        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 main et le tour est joué !

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

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

Runextern crate rand;

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

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

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

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

    println!("Génération du nombre...");
    let nombre_aleatoire = rand::thread_rng().gen_range(1, 101);
    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() == true {
        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 à venir en parler sur #rust-fr ou directement sur github en ouvrant une issue.

Améliorations

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

  • Un mode 2 joueurs.
  • 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 !