Curso gratuito de Rust: A linguagem mais amada
Implementando a Lógica do Jogo e Geração de Números Aleatórios
Aprenda sobre implementando a lógica do jogo e geração de números aleatórios
Projeto Prático: Jogo de Adivinhação - Implementando a Lógica do Jogo e Geração de Números Aleatórios 🎲
Olá, futuro mestre de Rust! 👋
Nesta aula prática, vamos mergulhar de cabeça na construção do nosso primeiro projeto em Rust: um Jogo de Adivinhação! 🎯 Você aprenderá a gerar números aleatórios, a interagir com o usuário para obter suas tentativas e a implementar a lógica de comparação para guiar o jogador. Prepare-se para colocar a mão na massa e ver a magia de Rust acontecer! ✨
1. Introdução: O Que Vamos Construir? 🚀
Nosso jogo de adivinhação será simples, mas poderoso para solidificar conceitos importantes:
- Geração de números aleatórios: Como fazer o computador "pensar" em um número secreto.
- Entrada do usuário: Como ler o que o jogador digita no terminal.
- Comparação lógica: Como verificar se a tentativa do jogador é maior, menor ou igual ao número secreto.
- Loops: Como manter o jogo rodando até que o jogador acerte.
Ao final desta aula, você terá um jogo funcional e uma compreensão mais profunda da interação entre diferentes partes de um programa Rust.
2. Geração de Números Aleatórios com a Crate rand 🎲
Rust, por padrão, não inclui funcionalidades de geração de números aleatórios em sua biblioteca padrão (std). Isso é uma escolha de design para manter a std o mais enxuta e focada possível. Para gerar números aleatórios, precisamos usar uma crate externa. A crate oficial e mais popular para isso é a rand.
2.1. Adicionando a Crate rand ao Seu Projeto
Primeiro, você precisa adicionar rand como uma dependência em seu arquivo Cargo.toml. Abra o arquivo Cargo.toml na raiz do seu projeto (criado com cargo new guessing_game) e adicione a linha sob a seção [dependencies]:
# Cargo.toml
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8.5" # Adicione esta linha!Nota: A versão 0.8.5 é a mais comum no momento da escrita. Verifique a documentação oficial da crate rand em crates.io/crates/rand para a versão mais recente e estável. O cargo se encarregará de baixar e compilar essa dependência para você.
2.2. Gerando um Número Secreto
Agora, vamos usar a crate rand para gerar nosso número secreto.
// src/main.rs
use std::io; // Módulo para entrada/saída
use rand::Rng; // Trait Rng para geração de números aleatórios
use std::cmp::Ordering; // Enum para comparação de valores
fn main() {
println!("Adivinhe o número!");
// Gerar um número aleatório entre 1 e 100 (inclusive)
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("O número secreto é: {}", secret_number); // Apenas para debug, removeremos depois!
// ... restante do jogo
}Explicação:
use rand::Rng;: Importamos o traitRng. Traits em Rust definem funcionalidades que um tipo pode ter.Rngfornece o métodogen_range.rand::thread_rng(): Retorna um gerador de números aleatórios local para o thread atual. Ele é "semeado" (seeded) pelo sistema operacional e é geralmente o que você quer para a maioria dos casos de uso..gen_range(1..=100): Este método, fornecido pelo traitRng, gera um número aleatório dentro do intervalo especificado. O..=cria um intervalo inclusivo, ou seja, de 1 a 100, incluindo ambos os números.
3. Lendo a Entrada do Usuário ⌨️
Para que o jogador possa adivinhar, precisamos ler o que ele digita no terminal. Usaremos o módulo std::io para isso.
// src/main.rs (continuando do exemplo anterior)
use std::io;
use rand::Rng;
use std::cmp::Ordering;
fn main() {
println!("Adivinhe o número!");
let secret_number = rand::thread_rng().gen_range(1..=100);
// println!("O número secreto é: {}", secret_number); // Remova esta linha após testar!
println!("Por favor, digite seu palpite.");
let mut guess = String::new(); // Declara uma String mutável para armazenar a entrada
io::stdin() // Retorna uma handle para a entrada padrão (terminal)
.read_line(&mut guess) // Lê a linha do usuário e coloca na String `guess`
.expect("Falha ao ler a linha"); // Trata um possível erro
println!("Você palpitou: {}", guess);
}Explicação:
use std::io;: Importamos o móduloiopara usar suas funções de entrada/saída.let mut guess = String::new();: Declaramos uma novaStringvazia e mutável. A mutabilidade é crucial porqueread_lineprecisa modificar estaStringpara armazenar a entrada do usuário.io::stdin(): Obtém uma instância deStdin, que representa a entrada padrão do terminal..read_line(&mut guess): Este método lê a entrada do usuário e a anexa àStringguess. Ele retorna umResult, que é um enum que pode serOk(sucesso) ouErr(erro)..expect("Falha ao ler a linha"): É um método de conveniência paraResult. Se oResultfor umErr,expectfará o programa entrar em pânico (crashar) e exibirá a mensagem que você passou. Se forOk, ele retornará o valor contido emOk. Para um jogo simples como este,expecté aceitável, mas em aplicações mais robustas, você usariamatchpara um tratamento de erro mais gracioso.
4. Comparando o Palpite com o Número Secreto 🤔
Agora que temos o número secreto e o palpite do usuário, precisamos compará-los e dar feedback.
4.1. Conversão da Entrada para Número
A entrada lida por read_line é sempre uma String. Para compará-la com nosso secret_number (que é um u32), precisamos converter a String para um número.
// ... (código anterior)
fn main() {
// ... (geração do número secreto e leitura da entrada)
println!("Você palpitou: {}", guess);
// Converter a String para um número (u32)
let guess: u32 = guess.trim().parse().expect("Por favor, digite um número!");
println!("Seu número (convertido) é: {}", guess);
// ... (comparação)
}Explicação:
guess.trim(): O métodoread_lineinclui qualquer caractere de "nova linha" (\nno Linux/macOS,\r\nno Windows) que o usuário digita ao pressionar Enter.trim()remove espaços em branco e caracteres de nova linha do início e do fim da string..parse(): Este método tenta converter a string para algum tipo numérico. Comosecret_numberé umu32, Rust inferirá que queremos converterguessparau32.let guess: u32 = ...: Estamos "sombreando" (shadowing) a variávelguessanterior. Isso significa que criamos uma nova variávelguesscom o mesmo nome, mas com um tipo diferente (u32). A variávelguessoriginal (tipoString) ainda existe na memória, mas não podemos mais acessá-la diretamente pelo nomeguess..expect("Por favor, digite um número!"): Novamente,parse()retorna umResult. Se a conversão falhar (ex: usuário digita "banana"),expectfará o programa crashar.
4.2. Usando match para Comparação
Rust possui um poderoso operador match que é ideal para lidar com diferentes resultados de uma comparação. Usaremos o enum std::cmp::Ordering.
// ... (código anterior)
fn main() {
// ... (geração do número secreto, leitura e conversão da entrada)
println!("Seu número (convertido) é: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Muito pequeno!"),
Ordering::Greater => println!("Muito grande!"),
Ordering::Equal => println!("Você acertou!"),
}
}Explicação:
use std::cmp::Ordering;: Importamos o enumOrdering, que tem três variantes:Less,GreatereEqual.guess.cmp(&secret_number): O métodocmpcompara dois valores e retorna umOrderingque indica se o primeiro é menor que, maior que ou igual ao segundo. Note que passamos&secret_numberporquecmpespera uma referência.match ... { ... }: Omatché como uma série deif/else if/elsemuito mais expressiva. Ele compara o valor retornado porguess.cmp(&secret_number)com cada "braço" (Ordering::Less,Ordering::Greater,Ordering::Equal) e executa o código do primeiro braço que corresponde.
5. Colocando Tudo em um Loop e Lidando com Erros 🔄
Para que o jogo continue até que o jogador acerte, precisamos de um loop. Também vamos melhorar o tratamento de erros para que o jogo não crasha se o usuário digitar algo que não seja um número.
// src/main.rs
use std::io;
use rand::Rng;
use std::cmp::Ordering;
fn main() {
println!("Adivinhe o número!");
let secret_number = rand::thread_rng().gen_range(1..=100);
// Loop principal do jogo
loop {
println!("Por favor, digite seu palpite.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Falha ao ler a linha");
// Tratamento de erro na conversão: usamos `match` em vez de `expect`
let guess: u32 = match guess.trim().parse() {
Ok(num) => num, // Se a conversão for bem-sucedida, usamos o número
Err(_) => { // Se houver erro, imprimimos uma mensagem e continuamos para a próxima iteração do loop
println!("Isso não é um número! Por favor, digite um número.");
continue; // Pula para a próxima iteração do loop 'loop'
}
};
println!("Você palpitou: {}", 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 'loop' quando o jogador acerta
}
}
}
}Explicação das Novas Partes:
loop { ... }: Cria um loop infinito. O jogo continuará a pedir palpites até que uma condiçãobreakseja satisfeita.let guess: u32 = match guess.trim().parse() { ... };: Aqui, substituímosexpectpor ummatchmais robusto para lidar com oResultretornado porparse().Ok(num) => num: Separse()for bem-sucedido, oResultseráOk(num), enumserá o valor numérico convertido. Atribuímosnumà nossa nova variávelguess.Err(_) => { ... }: Separse()falhar (por exemplo, o usuário digitou "abc"), oResultseráErrcontendo informações sobre o erro. Usamos_como um curinga para ignorar os detalhes do erro, pois só nos importa que houve um erro.println!("Isso não é um número! Por favor, digite um número.");: Informamos ao usuário sobre o erro.continue;: Esta palavra-chave faz com que o loop pule imediatamente para a próxima iteração, pedindo um novo palpite, sem tentar comparar o valor inválido.
break;: Quandoguess.cmp(&secret_number)retornaOrdering::Equal, o jogo imprime a mensagem de vitória ebreakencerra o loop, finalizando o programa.
6. Exercícios/Desafios 🧑💻
Agora é a sua vez de aprimorar o jogo!
Desafios Essenciais:
- Remova a dica do número secreto: O jogo é mais divertido se o jogador não souber o número de antemão! Remova a linha
println!("O número secreto é: {}", secret_number);. - Contador de tentativas: Adicione uma variável mutável para contar quantas tentativas o jogador levou para acertar o número. Imprima essa contagem quando o jogador vencer.
- Mensagem de boas-vindas: Adicione uma mensagem de boas-vindas mais elaborada no início do jogo, explicando as regras.
- Limitar o número de tentativas (Opcional): Faça com que o jogador tenha um número limitado de tentativas (ex: 7 tentativas). Se ele exceder, o jogo termina e revela o número secreto.
Desafios Avançados (Para os mais curiosos!):
- Dificuldade: Permita que o usuário escolha o nível de dificuldade (fácil, médio, difícil), que alteraria o intervalo do número secreto (ex: 1-50, 1-100, 1-1000). Você precisaria ler uma entrada adicional para isso.
- "Jogar Novamente?": Após o jogador acertar ou perder, pergunte se ele quer jogar novamente. Se sim, reinicie o jogo (gerando um novo número secreto, etc.). Isso exigiria um loop externo.
7. Resumo e Próximos Passos 🏁
Parabéns! 🎉 Você construiu um jogo de adivinhação funcional em Rust, utilizando conceitos fundamentais como:
- Crates externas: Como adicionar e usar a crate
randpara geração de números aleatórios. - Entrada/Saída: Lendo dados do usuário via
std::io. - Manipulação de
String: Usandotrim()eparse()para converter entrada. - Tratamento de erros: Usando
matchcomResultpara lidar com entradas inválidas. - Controle de fluxo: Utilizando
loop,breakecontinuepara controlar o fluxo do jogo. - Comparação: Empregando
matchcomOrderingpara dar feedback ao jogador.
Este projeto é uma base excelente para entender como programas interativos são construídos em Rust.
No próximo módulo, exploraremos mais sobre a estrutura de projetos maiores, funções e como organizar seu código de forma mais eficiente. Continue praticando e experimentando! O mundo de Rust está apenas começando a se abrir para você. 🌟