pratica

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

40 min
Aula 3 de 5

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 trait Rng. Traits em Rust definem funcionalidades que um tipo pode ter. Rng fornece o método gen_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 trait Rng, 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ódulo io para usar suas funções de entrada/saída.
  • let mut guess = String::new();: Declaramos uma nova String vazia e mutável. A mutabilidade é crucial porque read_line precisa modificar esta String para armazenar a entrada do usuário.
  • io::stdin(): Obtém uma instância de Stdin, que representa a entrada padrão do terminal.
  • .read_line(&mut guess): Este método lê a entrada do usuário e a anexa à String guess. Ele retorna um Result, que é um enum que pode ser Ok (sucesso) ou Err (erro).
  • .expect("Falha ao ler a linha"): É um método de conveniência para Result. Se o Result for um Err, expect fará o programa entrar em pânico (crashar) e exibirá a mensagem que você passou. Se for Ok, ele retornará o valor contido em Ok. Para um jogo simples como este, expect é aceitável, mas em aplicações mais robustas, você usaria match para 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étodo read_line inclui qualquer caractere de "nova linha" (\n no Linux/macOS, \r\n no 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. Como secret_number é um u32, Rust inferirá que queremos converter guess para u32.
  • let guess: u32 = ...: Estamos "sombreando" (shadowing) a variável guess anterior. Isso significa que criamos uma nova variável guess com o mesmo nome, mas com um tipo diferente (u32). A variável guess original (tipo String) ainda existe na memória, mas não podemos mais acessá-la diretamente pelo nome guess.
  • .expect("Por favor, digite um número!"): Novamente, parse() retorna um Result. Se a conversão falhar (ex: usuário digita "banana"), expect fará 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 enum Ordering, que tem três variantes: Less, Greater e Equal.
  • guess.cmp(&secret_number): O método cmp compara dois valores e retorna um Ordering que indica se o primeiro é menor que, maior que ou igual ao segundo. Note que passamos &secret_number porque cmp espera uma referência.
  • match ... { ... }: O match é como uma série de if/else if/else muito mais expressiva. Ele compara o valor retornado por guess.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ção break seja satisfeita.
  • let guess: u32 = match guess.trim().parse() { ... };: Aqui, substituímos expect por um match mais robusto para lidar com o Result retornado por parse().
    • Ok(num) => num: Se parse() for bem-sucedido, o Result será Ok(num), e num será o valor numérico convertido. Atribuímos num à nossa nova variável guess.
    • Err(_) => { ... }: Se parse() falhar (por exemplo, o usuário digitou "abc"), o Result será Err contendo 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;: Quando guess.cmp(&secret_number) retorna Ordering::Equal, o jogo imprime a mensagem de vitória e break encerra 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 rand para geração de números aleatórios.
  • Entrada/Saída: Lendo dados do usuário via std::io.
  • Manipulação de String: Usando trim() e parse() para converter entrada.
  • Tratamento de erros: Usando match com Result para lidar com entradas inválidas.
  • Controle de fluxo: Utilizando loop, break e continue para controlar o fluxo do jogo.
  • Comparação: Empregando match com Ordering para 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ê. 🌟

© 2025 Escola All Dev. Todos os direitos reservados.

Implementando a Lógica do Jogo e Geração de Números Aleatórios - Curso gratuito de Rust: A linguagem mais amada | escola.all.dev.br