Curso gratuito de Rust: A linguagem mais amada
Tratamento de Erros no Projeto e Loop Principal do Jogo
Aprenda sobre tratamento de erros no projeto e loop principal do jogo
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:
- 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! 💥
- 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 valorTresultante.Err(E): Indica falha e contém um erroE.
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()retornarOk(num), a variávelguessrecebenum. - Se
parse()retornarErr(_), imprimimos uma mensagem de erro e usamosreturn;para sair da funçãomain(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,continuefaz com que o loop pule para a próxima iteração, pedindo um novo palpite.break: Quando o usuário acerta o número,breakencerra 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 linhaApó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 usecontinuepara pedir um novo palpite. - Dica: Você pode adicionar um
ifdentro do blocoOk(num)domatchou logo após a atribuição deguess.
-
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 umifpara verificar seattemptsatingiu 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
matchpara 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 porparse()éstd::num::ParseIntError. - Desafio (Opcional): Em vez de
Err(_), useErr(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!
- Atualmente, usamos
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
Resulte a expressãomatchpara 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,continueebreakpara 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! 📁✨