pratica

Tratamento de Erros no Projeto e Loop Principal do Jogo

Aprenda sobre tratamento de erros no projeto e loop principal do jogo

30 min
Aula 4 de 5

Tratamento de Erros no Projeto e Loop Principal do Jogo

Olá, futuro mestre do Rust! 👋 Nesta aula prática, vamos elevar nosso jogo de adivinhação a um novo nível, tornando-o mais robusto e interativo. Aprenderemos a lidar com entradas inválidas do usuário, garantindo que nosso programa não "quebre" inesperadamente, e implementaremos um loop principal para que o jogador possa fazer múltiplas tentativas.

Prepare-se para mergulhar no mundo do tratamento de erros com Result e dominar os loops em Rust! 🚀

1. Introdução: Tornando Nosso Jogo Robusto e Interativo

Até agora, nosso jogo de adivinhação funciona, mas tem algumas limitações:

  1. Não trata erros: Se o usuário digitar algo que não seja um número (ex: "abc"), o programa entrará em pânico e encerrará. Isso não é uma boa experiência para o usuário! 💥
  2. Apenas uma tentativa: O jogo termina após a primeira tentativa, seja ela correta ou não. Queremos que o jogador possa adivinhar várias vezes até acertar. 🔄

Nesta aula, resolveremos esses dois problemas cruciais, transformando nosso jogo em uma aplicação mais amigável e funcional.

2. Tratamento de Erros com Result e match

Em Rust, o tratamento de erros é uma parte fundamental da linguagem e é feito de forma explícita, incentivando você a pensar sobre possíveis falhas. A enumeração Result<T, E> é a ferramenta principal para isso.

Result tem duas variantes:

  • Ok(T): Indica sucesso e contém o valor T resultante.
  • Err(E): Indica falha e contém um erro E.

Vamos focar em como lidar com a entrada do usuário que não é um número. A função parse::<u32>() (que usamos para converter a string de entrada em um número u32) retorna um Result.

Exemplo: Lidando com parse

Considere o seguinte trecho do nosso código:

let guess: u32 = guess.trim().parse().expect("Por favor, digite um número!");

O método .expect() é uma forma de lidar com Result. Se o Result for Ok, ele retorna o valor dentro de Ok. Se for Err, ele entra em pânico (panic!) e encerra o programa, exibindo a mensagem que passamos. Isso é útil para prototipagem, mas não para um programa robusto.

Para um tratamento de erros mais elegante, usamos a expressão match.

use std::io;
 
fn main() {
    println!("Adivinhe o número!");
 
    let mut guess = String::new();
 
    io::stdin()
        .read_line(&mut guess)
        .expect("Falha ao ler a linha");
 
    // ✨ Novo tratamento de erro com match ✨
    let guess: u32 = match guess.trim().parse() {
        Ok(num) => num, // Se a conversão for bem-sucedida, use o número
        Err(_) => {     // Se houver um erro (qualquer erro de parse)
            println!("Entrada inválida! Por favor, digite um número.");
            return;     // Encerra o programa ou faz outra coisa
        }
    };
 
    println!("Você adivinhou: {}", guess);
}

Neste exemplo:

  • Se parse() retornar Ok(num), a variável guess recebe num.
  • Se parse() retornar Err(_), imprimimos uma mensagem de erro e usamos return; para sair da função main (e, consequentemente, do programa).

Melhorando o Tratamento de Erros: continue em um Loop

Sair do programa ao primeiro erro não é o ideal para um jogo. Queremos que o usuário possa tentar novamente! É aqui que o loop principal entra em jogo.

3. O Loop Principal do Jogo

Para permitir múltiplas tentativas, vamos envolver a lógica principal do nosso jogo em um loop infinito usando a palavra-chave loop.

// ... código anterior ...
 
loop {
    println!("Por favor, digite seu palpite.");
 
    let mut guess = String::new();
 
    io::stdin()
        .read_line(&mut guess)
        .expect("Falha ao ler a linha");
 
    let guess: u32 = match guess.trim().parse() {
        Ok(num) => num,
        Err(_) => {
            println!("Entrada inválida! Por favor, digite um número.");
            continue; // ⬅️ Continue para a próxima iteração do loop
        }
    };
 
    println!("Você adivinhou: {}", guess);
 
    // ... lógica de comparação ...
    // if guess == secret_number {
    //     println!("Você acertou!");
    //     break; // ⬅️ Sai do loop quando o palpite está correto
    // }
}

Com o loop:

  • continue: Quando o usuário digita uma entrada inválida, continue faz com que o loop pule para a próxima iteração, pedindo um novo palpite.
  • break: Quando o usuário acerta o número, break encerra o loop, finalizando o jogo.

4. Integrando Tudo: O Jogo de Adivinhação Robusto

Agora, vamos juntar todas essas peças no nosso projeto guessing_game.

// main.rs
use std::io;
use std::cmp::Ordering;
use rand::Rng; // Certifique-se de que 'rand' está no seu Cargo.toml
 
fn main() {
    println!("Adivinhe o número!");
 
    let secret_number = rand::thread_rng().gen_range(1..=100);
 
    // Descomente a linha abaixo para ver o número secreto durante o desenvolvimento
    // println!("O número secreto é: {}", secret_number);
 
    loop { // ⬅️ Loop principal do jogo
        println!("Por favor, digite seu palpite.");
 
        let mut guess = String::new();
 
        io::stdin()
            .read_line(&mut guess)
            .expect("Falha ao ler a linha");
 
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num, // Se a conversão for bem-sucedida, use o número
            Err(e) => { // Se houver um erro (ParseIntError)
                // Podemos imprimir o erro para depuração, mas para o usuário, uma mensagem genérica é melhor
                // println!("Erro de parse: {}", e);
                println!("Entrada inválida! Por favor, digite um número.");
                continue; // ⬅️ Pula para a próxima iteração do loop
            }
        };
 
        println!("Você adivinhou: {}", guess);
 
        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Muito pequeno!"),
            Ordering::Greater => println!("Muito grande!"),
            Ordering::Equal => {
                println!("🎉 Você acertou! 🎉");
                break; // ⬅️ Sai do loop e encerra o jogo
            }
        }
    }
 
    println!("Obrigado por jogar!");
}

Para que este código funcione, certifique-se de ter rand no seu Cargo.toml:

# Cargo.toml
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
 
[dependencies]
rand = "0.8.5" # ⬅️ Adicione esta linha

Após adicionar rand, execute cargo build ou cargo run para que o Cargo baixe a dependência.

Agora, tente rodar o jogo (cargo run) e digite letras ou símbolos. Você verá que o jogo não "quebra" mais! Ele simplesmente pede um novo palpite. Além disso, você pode continuar adivinhando até acertar o número.

5. Exercícios/Desafios Práticos 🧑‍💻

É hora de colocar a mão na massa e aprimorar ainda mais nosso jogo!

Tarefas:

  • 1. Mensagem de Erro Específica para Números Fora do Intervalo:

    • Atualmente, nosso número secreto está entre 1 e 100. Se o usuário digitar um número como 0 ou 101, ele será aceito, mas a comparação dirá "Muito pequeno!" ou "Muito grande!".
    • Desafio: Adicione uma validação extra para verificar se o palpite do usuário está dentro do intervalo 1..=100. Se não estiver, imprima uma mensagem como "Seu palpite deve ser entre 1 e 100!" e use continue para pedir um novo palpite.
    • Dica: Você pode adicionar um if dentro do bloco Ok(num) do match ou logo após a atribuição de guess.
  • 2. Limite de Tentativas:

    • O jogo atual permite infinitas tentativas.
    • Desafio: Implemente um limite de tentativas (ex: 7 tentativas). Se o jogador exceder esse limite, o jogo deve terminar, revelando o número secreto e informando que as tentativas acabaram.
    • Dica: Use uma variável mut attempts = 0; e incremente-a a cada palpite. Use um if para verificar se attempts atingiu o limite.
  • 3. Opção de Reiniciar o Jogo:

    • Após o jogador acertar ou esgotar as tentativas, o jogo simplesmente termina.
    • Desafio: Pergunte ao jogador se ele gostaria de jogar novamente ("Deseja jogar novamente? (s/n)"). Se ele digitar 's', o jogo deve reiniciar (gerar um novo número secreto e resetar as tentativas). Se digitar 'n', o programa deve sair.
    • Dica: Você precisará de um loop externo para o jogo inteiro e um match para lidar com a entrada 's' ou 'n'.
  • 4. Melhoria na Mensagem de Erro de Parse:

    • Atualmente, usamos Err(_), que captura qualquer erro de parse. O tipo de erro retornado por parse() é std::num::ParseIntError.
    • Desafio (Opcional): Em vez de Err(_), use Err(e) e imprima a mensagem de erro específica (e) para depuração, mas ainda mostre uma mensagem amigável para o usuário.
    • Dica: Você já tem um exemplo disso no código da seção 4, mas está comentado. Descomente e observe a diferença!

6. Resumo e Próximos Passos

Nesta aula, demos um salto gigante na robustez e interatividade do nosso jogo de adivinhação:

  • Aprendemos a usar Result e a expressão match para lidar de forma segura com erros de entrada do usuário, evitando que nosso programa entre em pânico.
  • Implementamos um loop principal com loop, continue e break para permitir que o jogador faça múltiplas tentativas até acertar o número secreto.
  • Integramos todas essas funcionalidades para criar um jogo de adivinhação mais completo e amigável.

Com essas ferramentas, você está começando a ver o poder de Rust para construir aplicações confiáveis!

Próximos Passos:

Na próxima aula, vamos explorar como organizar nosso código em módulos e funções, tornando-o mais limpo e fácil de manter. Prepare-se para aprender sobre a estrutura de projetos em Rust! 📁✨

© 2025 Escola All Dev. Todos os direitos reservados.

Tratamento de Erros no Projeto e Loop Principal do Jogo - Curso gratuito de Rust: A linguagem mais amada | escola.all.dev.br