Projeto Jogo de Adivinhação

O computador pensa num número de 1 a 100, e você tenta adivinhar. A cada palpite ele diz "é maior" ou "é menor", e você vai fechando o cerco. É o projeto clássico para treinar laços (o jogo repete até você acertar), decisões (maior, menor ou acertou) e números aleatórios. No fim, o jogo tem limite de tentativas e pergunta se você quer jogar outra partida.

Você vai praticar
  • Comparar o palpite com o segredo usando if e else if.
  • Repetir com while e contar as tentativas.
  • Sortear um número com Random.Shared.Next (e o cuidado com o limite).
  • Aceitar só números com TryParse e tratar o fim da entrada.
  • Limitar as tentativas com uma constante e um bool.
  • Perguntar "jogar de novo?" com do while e separar a partida num método.

O que vamos construir

O jogo pronto, numa tarde em que a Ana jogou duas partidas:

$ dotnet run adivinhacao.cs
=== Jogo de Adivinhação ===

Partida 1: pensei num número de 1 a 100. Você tem 7 tentativas.
Palpite 1 de 7: 10
O número é maior que 10.
Palpite 2 de 7: 20
O número é maior que 20.
Palpite 3 de 7: 30
O número é maior que 30.
Palpite 4 de 7: 40
O número é maior que 40.
Palpite 5 de 7: 60
O número é maior que 60.
Palpite 6 de 7: 70
O número é maior que 70.
Palpite 7 de 7: 80
O número é menor que 80.
Suas 7 tentativas acabaram. O número era 78.
Jogar de novo? (s/n): talvez
Jogar de novo? (s/n): s

Partida 2: pensei num número de 1 a 100. Você tem 7 tentativas.
Palpite 1 de 7: 50
O número é menor que 50.
Palpite 2 de 7: 25
O número é maior que 25.
Palpite 3 de 7: 37
O número é maior que 37.
Palpite 4 de 7: 43
Acertou! Tentativas: 4.
Jogar de novo? (s/n): n
Partidas: 2. Até a próxima!

Na segunda partida, a Ana usou a melhor estratégia: chutar sempre o meio do que sobrou (50, depois 25 ou 75…). Cada palpite corta as possibilidades pela metade, e assim 7 palpites sempre bastam para 100 números. Essa ideia tem nome: busca binária.

  1. Um palpite contra um número fixo.
  2. Repetir até acertar e contar as tentativas.
  3. Sortear o número e aceitar só números.
  4. No máximo 7 tentativas.
  5. Jogar de novo (do while e um método).

Antes de começar

Você precisa ter visto if e else, else if, while, Ler do Teclado e Parse e TryParse; para as versões finais, Números Aleatórios, do while e Métodos. É um arquivo só, adivinhacao.cs (sem acento no nome do arquivo, para evitar problemas no terminal):

$ mkdir -p cs/adivinhacao
$ cd cs/adivinhacao

Versão 1 · Um palpite contra um número fixo

Antes de sortear, vamos fazer o jogo funcionar com um número que nós escolhemos: 42. Assim dá para testar e saber a resposta certa. O programa lê um palpite e diz se acertou, se o número é maior ou se é menor.

const int Segredo = 42;

Console.WriteLine("=== Jogo de Adivinhação ===");
Console.WriteLine("Pensei num número de 1 a 100.");
Console.Write("Seu palpite: ");
int palpite = int.Parse(Console.ReadLine()!);

if (palpite == Segredo)
{
    Console.WriteLine("Acertou!");
}
else if (palpite < Segredo)
{
    Console.WriteLine($"O número é maior que {palpite}.");
}
else
{
    Console.WriteLine($"O número é menor que {palpite}.");
}
const int Segredo = 42;
Uma constante: um valor com nome que nunca muda. Por convenção, constantes têm nome em PascalCase.
int.Parse(Console.ReadLine()!)
Lê o palpite e converte em int (inteiro). Por enquanto, sem proteção: a versão 3 troca por TryParse.
if (palpite == Segredo)
== compara ("é igual a?"). Não confunda com =, que guarda um valor (Operadores de Comparação).
else if (palpite < Segredo)
Só é testado se o primeiro if foi falso. Se o palpite é menor que o segredo, a dica é "o número é maior".
else
Sobrou um caso: o palpite é maior que o segredo.
$ dotnet run adivinhacao.cs
=== Jogo de Adivinhação ===
Pensei num número de 1 a 100.
Seu palpite: 30
O número é maior que 30.
$ dotnet run adivinhacao.cs
=== Jogo de Adivinhação ===
Pensei num número de 1 a 100.
Seu palpite: 50
O número é menor que 50.
$ dotnet run adivinhacao.cs
=== Jogo de Adivinhação ===
Pensei num número de 1 a 100.
Seu palpite: 42
Acertou!

Testamos os três caminhos do if: maior, menor e igual. É um bom hábito: para cada decisão do programa, um teste que passa por ela.

Missão (versão 1): falta o último caso. Acrescente o else que mostra O número é menor que 50. (use {palpite}, não o número escrito). A entrada da missão é o palpite 50.

const int Segredo = 42;

Console.WriteLine("=== Jogo de Adivinhação ===");
Console.WriteLine("Pensei num número de 1 a 100.");
Console.Write("Seu palpite: ");
int palpite = int.Parse(Console.ReadLine()!);

if (palpite == Segredo)
{
    Console.WriteLine("Acertou!");
}
else if (palpite < Segredo)
{
    Console.WriteLine($"O número é maior que {palpite}.");
}
// acrescente o else
const int Segredo = 42;

Console.WriteLine("=== Jogo de Adivinhação ===");
Console.WriteLine("Pensei num número de 1 a 100.");
Console.Write("Seu palpite: ");
int palpite = int.Parse(Console.ReadLine()!);

if (palpite == Segredo)
{
    Console.WriteLine("Acertou!");
}
else if (palpite < Segredo)
{
    Console.WriteLine($"O número é maior que {palpite}.");
}
else
{
    Console.WriteLine($"O número é menor que {palpite}.");
}

Versão 2 · Repetir até acertar e contar as tentativas

Um palpite só não é jogo. A pergunta e o if vão para dentro de um while (true), que repete até o break do acerto. E uma variável tentativas conta os palpites.

const int Segredo = 42;

Console.WriteLine("=== Jogo de Adivinhação ===");
Console.WriteLine("Pensei num número de 1 a 100.");

int tentativas = 0;
while (true)
{
    Console.Write("Seu palpite: ");
    int palpite = int.Parse(Console.ReadLine()!);
    tentativas++;

    if (palpite == Segredo)
    {
        Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
        break;
    }
    else if (palpite < Segredo)
    {
        Console.WriteLine($"O número é maior que {palpite}.");
    }
    else
    {
        Console.WriteLine($"O número é menor que {palpite}.");
    }
}
int tentativas = 0;
Fica fora do laço: se ficasse dentro, voltaria a 0 a cada palpite (veja Escopo).
while (true)
Repete "para sempre". Quem para o laço é o break.
int palpite = …;
Agora fica dentro do laço: cada volta lê um palpite novo.
tentativas++;
Soma 1 a cada palpite (++ e --).
break;
Acertou: sai do while, e o programa termina.
$ dotnet run adivinhacao.cs
=== Jogo de Adivinhação ===
Pensei num número de 1 a 100.
Seu palpite: 50
O número é menor que 50.
Seu palpite: 25
O número é maior que 25.
Seu palpite: 37
O número é maior que 37.
Seu palpite: 43
O número é menor que 43.
Seu palpite: 40
O número é maior que 40.
Seu palpite: 42
Acertou! Tentativas: 6.
"Uso de variável local não atribuída": erro CS0165

A Ana tentou um laço diferente, que repete "enquanto o palpite for diferente do segredo":

const int Segredo = 42;

int palpite;
while (palpite != Segredo)
{
    Console.Write("Seu palpite: ");
    palpite = int.Parse(Console.ReadLine()!);
}
Console.WriteLine("Acertou!");
$ dotnet run adivinhacao.cs
/home/ana/cs/adivinhacao/adivinhacao.cs(4,8): error CS0165: Uso de variável local não atribuída "palpite"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: na linha 4, coluna 8, o while compara palpite com o segredo, mas palpite ainda não recebeu nenhum valor (em inglês, Use of unassigned local variable). O C# não deixa ler uma variável local antes de guardar algo nela, para você nunca usar um valor "lixo".

Como consertar: dê um valor inicial que com certeza não é o segredo (int palpite = 0;), ou use o while (true) com break da versão 2, que lê o palpite antes de comparar. Um do while também resolve, porque roda o corpo antes de testar a condição.

Missão (versão 2): o jogo repete, mas sempre diz Tentativas: 0.. Conte cada palpite com tentativas++; logo depois de ler o número. A entrada da missão são os palpites 50, 25 e 42.

const int Segredo = 42;

Console.WriteLine("=== Jogo de Adivinhação ===");
Console.WriteLine("Pensei num número de 1 a 100.");

int tentativas = 0;
while (true)
{
    Console.Write("Seu palpite: ");
    int palpite = int.Parse(Console.ReadLine()!);
    // conte o palpite

    if (palpite == Segredo)
    {
        Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
        break;
    }
    else if (palpite < Segredo)
    {
        Console.WriteLine($"O número é maior que {palpite}.");
    }
    else
    {
        Console.WriteLine($"O número é menor que {palpite}.");
    }
}
const int Segredo = 42;

Console.WriteLine("=== Jogo de Adivinhação ===");
Console.WriteLine("Pensei num número de 1 a 100.");

int tentativas = 0;
while (true)
{
    Console.Write("Seu palpite: ");
    int palpite = int.Parse(Console.ReadLine()!);
    tentativas++;

    if (palpite == Segredo)
    {
        Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
        break;
    }
    else if (palpite < Segredo)
    {
        Console.WriteLine($"O número é maior que {palpite}.");
    }
    else
    {
        Console.WriteLine($"O número é menor que {palpite}.");
    }
}

Versão 3 · Sortear o número e aceitar só números

Com o 42 fixo, o jogo perde a graça na segunda vez. Agora o computador sorteia. E, já que o jogo vai ser usado de verdade, ele precisa aguentar um palpite como abc sem cair.

int segredo = Random.Shared.Next(1, 101);

Console.WriteLine("=== Jogo de Adivinhação ===");
Console.WriteLine("Pensei num número de 1 a 100.");

int tentativas = 0;
while (true)
{
    Console.Write("Seu palpite: ");
    string? texto = Console.ReadLine();
    if (texto is null)
    {
        Console.WriteLine();
        Console.WriteLine("Jogo interrompido.");
        return;
    }
    if (!int.TryParse(texto, out int palpite))
    {
        Console.WriteLine("Digite um número inteiro.");
        continue;
    }
    tentativas++;

    if (palpite == segredo)
    {
        Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
        break;
    }
    else if (palpite < segredo)
    {
        Console.WriteLine($"O número é maior que {palpite}.");
    }
    else
    {
        Console.WriteLine($"O número é menor que {palpite}.");
    }
}
Random.Shared.Next(1, 101)
Sorteia um inteiro de 1 a 100. O primeiro número entra no sorteio; o segundo não (é o "limite de fora"). Por isso 101. Random.Shared é um sorteador pronto, que o .NET cria uma vez e todo o programa compartilha (veja Números Aleatórios).
int segredo
Não é mais const: o valor só é conhecido quando o programa roda. Por isso o nome volta para camelCase.
string? texto = Console.ReadLine();
Primeiro guardamos o texto. O ? diz que ele pode ser null: é o que o ReadLine devolve quando a entrada acaba (com Ctrl+D no terminal, ou no fim de um arquivo). Veja Referências Anuláveis.
if (texto is null) { …; return; }
Sem entrada, não há jogo: avisa e termina. O cenário "laço infinito" abaixo mostra o que acontece sem essas linhas.
if (!int.TryParse(texto, out int palpite)) { …; continue; }
Se não é um número, avisa e volta para o começo do laço (continue), sem contar a tentativa.
$ dotnet run adivinhacao.cs
=== Jogo de Adivinhação ===
Pensei num número de 1 a 100.
Seu palpite: abc
Digite um número inteiro.
Seu palpite: 50
O número é maior que 50.
Seu palpite: 75
O número é maior que 75.
Seu palpite: 88
O número é maior que 88.
Seu palpite: 94
O número é maior que 94.
Seu palpite: 97
O número é maior que 97.
Seu palpite: 99
O número é maior que 99.
Seu palpite: 100
Acertou! Tentativas: 7.

O abc não contou como tentativa, e o 100 foi sorteado: o limite de 101 funcionou. Rodando de novo, o número é outro.

Next(1, 100): o 100 nunca sai

Na primeira tentativa, a Ana escreveu Random.Shared.Next(1, 100), achando que era "de 1 a 100". Para conferir, ela sorteou um milhão de vezes e guardou o maior:

int maior = 0;
for (int i = 0; i < 1_000_000; i++)
{
    int sorteado = Random.Shared.Next(1, 100);
    maior = Math.Max(maior, sorteado);
}
Console.WriteLine($"Maior número sorteado em 1 milhão de vezes: {maior}");
$ dotnet run limite.cs
Maior número sorteado em 1 milhão de vezes: 99

O que significa: em Next(mínimo, máximo), o máximo não entra. Não é um erro de compilação nem uma exceção: o jogo funciona, só que quem pensa no 100 nunca ganha. Esse tipo de erro "por um" (em inglês, off-by-one) é um dos mais comuns em programação.

Como consertar: Next(1, 101). Para não esquecer, leia o segundo número como "até antes de".

Laço infinito quando a entrada acaba

Numa versão anterior, a Ana leu o palpite com string texto = Console.ReadLine() ?? "";, sem o if (texto is null). Para testar, ela mandou dois palpites de um arquivo (veja Sobre os Projetos):

$ cat palpites.txt
50
75
$ dotnet run adivinhacao.cs < palpites.txt
=== Jogo de Adivinhação ===
Pensei num número de 1 a 100.
Seu palpite: O número é menor que 50.
Seu palpite: O número é menor que 75.
Seu palpite: Digite um número inteiro.
Seu palpite: Digite um número inteiro.
Seu palpite: Digite um número inteiro.
Seu palpite: Digite um número inteiro.
Seu palpite: Digite um número inteiro.
…                                         « sem parar, até a Ana apertar Ctrl+C
^C

O que aconteceu: depois do 75, o arquivo acabou. Dali em diante, cada ReadLine() devolve null na hora, o ?? "" troca por texto vazio, o TryParse("") falha e o continue volta ao começo, para sempre. Com uma pessoa digitando isso não aparece, mas com um arquivo (ou num teste automático) o programa nunca termina.

Como consertar: trate o null separadamente, como na versão 3: "a entrada acabou, então o jogo acaba". Para parar um programa travado no terminal, aperte Ctrl+C.

Missão (versão 3): o segredo ainda é o 42 fixo. Troque a primeira linha por um sorteio de 1 a 100: int segredo = Random.Shared.Next(1, 101);, e use segredo no lugar de Segredo nas comparações. Para a missão funcionar com qualquer número sorteado, a entrada são os palpites 1, 2, 3… até 100: um deles acerta.

const int Segredo = 42;

Console.WriteLine("=== Jogo de Adivinhação ===");
Console.WriteLine("Pensei num número de 1 a 100.");

int tentativas = 0;
while (true)
{
    Console.Write("Seu palpite: ");
    string? texto = Console.ReadLine();
    if (texto is null)
    {
        Console.WriteLine();
        Console.WriteLine("Jogo interrompido.");
        return;
    }
    if (!int.TryParse(texto, out int palpite))
    {
        Console.WriteLine("Digite um número inteiro.");
        continue;
    }
    tentativas++;

    if (palpite == Segredo)
    {
        Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
        break;
    }
    else if (palpite < Segredo)
    {
        Console.WriteLine($"O número é maior que {palpite}.");
    }
    else
    {
        Console.WriteLine($"O número é menor que {palpite}.");
    }
}
int segredo = Random.Shared.Next(1, 101);

Console.WriteLine("=== Jogo de Adivinhação ===");
Console.WriteLine("Pensei num número de 1 a 100.");

int tentativas = 0;
while (true)
{
    Console.Write("Seu palpite: ");
    string? texto = Console.ReadLine();
    if (texto is null)
    {
        Console.WriteLine();
        Console.WriteLine("Jogo interrompido.");
        return;
    }
    if (!int.TryParse(texto, out int palpite))
    {
        Console.WriteLine("Digite um número inteiro.");
        continue;
    }
    tentativas++;

    if (palpite == segredo)
    {
        Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
        break;
    }
    else if (palpite < segredo)
    {
        Console.WriteLine($"O número é maior que {palpite}.");
    }
    else
    {
        Console.WriteLine($"O número é menor que {palpite}.");
    }
}

Versão 4 · No máximo 7 tentativas

Sem limite, qualquer um ganha chutando 1, 2, 3… Com 7 tentativas, só ganha quem pensa (7 é exatamente o que a busca binária precisa para 100 números). O laço deixa de ser "para sempre": ele roda enquanto ainda há tentativas. Ao sair, o programa precisa saber por que saiu (acertou ou acabaram as tentativas), e para isso usamos um bool.

const int MaxTentativas = 7;
int segredo = Random.Shared.Next(1, 101);

Console.WriteLine("=== Jogo de Adivinhação ===");
Console.WriteLine($"Pensei num número de 1 a 100. Você tem {MaxTentativas} tentativas.");

int tentativas = 0;
bool acertou = false;
while (tentativas < MaxTentativas)
{
    Console.Write($"Palpite {tentativas + 1} de {MaxTentativas}: ");
    string? texto = Console.ReadLine();
    if (texto is null)
    {
        Console.WriteLine();
        Console.WriteLine("Jogo interrompido.");
        return;
    }
    if (!int.TryParse(texto, out int palpite))
    {
        Console.WriteLine("Digite um número inteiro.");
        continue;
    }
    tentativas++;

    if (palpite == segredo)
    {
        acertou = true;
        break;
    }
    else if (palpite < segredo)
    {
        Console.WriteLine($"O número é maior que {palpite}.");
    }
    else
    {
        Console.WriteLine($"O número é menor que {palpite}.");
    }
}

if (acertou)
{
    Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
}
else
{
    Console.WriteLine($"Suas {MaxTentativas} tentativas acabaram. O número era {segredo}.");
}
const int MaxTentativas = 7;
O limite com nome. Quer um modo fácil, com 10? Muda uma linha.
bool acertou = false;
Uma "bandeira" (em inglês, flag): começa abaixada e só sobe quando a Ana acerta (bool).
while (tentativas < MaxTentativas)
Roda enquanto a Ana tiver tentativas: na 7ª, tentativas vira 7 e o laço para sozinho.
$"Palpite {tentativas + 1} de {MaxTentativas}: "
Mostra qual palpite é: o primeiro é o 1, quando tentativas ainda vale 0.
acertou = true; break;
Levanta a bandeira e sai do laço antes do fim.
if (acertou) … else …
Depois do laço, a bandeira diz como ele terminou.
$ dotnet run adivinhacao.cs
=== Jogo de Adivinhação ===
Pensei num número de 1 a 100. Você tem 7 tentativas.
Palpite 1 de 7: 50
O número é maior que 50.
Palpite 2 de 7: 75
O número é maior que 75.
Palpite 3 de 7: 88
O número é maior que 88.
Palpite 4 de 7: 94
O número é maior que 94.
Palpite 5 de 7: 97
O número é maior que 97.
Palpite 6 de 7: 99
Acertou! Tentativas: 6.
$ dotnet run adivinhacao.cs
=== Jogo de Adivinhação ===
Pensei num número de 1 a 100. Você tem 7 tentativas.
Palpite 1 de 7: 1
O número é maior que 1.
Palpite 2 de 7: 2
O número é maior que 2.
Palpite 3 de 7: 3
O número é maior que 3.
Palpite 4 de 7: 4
O número é maior que 4.
Palpite 5 de 7: 5
O número é maior que 5.
Palpite 6 de 7: 6
O número é maior que 6.
Palpite 7 de 7: 7
O número é maior que 7.
Suas 7 tentativas acabaram. O número era 60.

Missão (versão 4): o jogo ainda não tem limite. Troque o while (true) por while (tentativas < MaxTentativas) e, no else do fim, mostre Suas 7 tentativas acabaram. O número era … com o segredo. A entrada são os palpites 1 a 7: dependendo do sorteio, a Ana ganha ou perde, e as duas saídas valem.

const int MaxTentativas = 7;
int segredo = Random.Shared.Next(1, 101);

Console.WriteLine("=== Jogo de Adivinhação ===");
Console.WriteLine($"Pensei num número de 1 a 100. Você tem {MaxTentativas} tentativas.");

int tentativas = 0;
bool acertou = false;
while (true)
{
    Console.Write($"Palpite {tentativas + 1} de {MaxTentativas}: ");
    string? texto = Console.ReadLine();
    if (texto is null)
    {
        Console.WriteLine();
        Console.WriteLine("Jogo interrompido.");
        return;
    }
    if (!int.TryParse(texto, out int palpite))
    {
        Console.WriteLine("Digite um número inteiro.");
        continue;
    }
    tentativas++;

    if (palpite == segredo)
    {
        acertou = true;
        break;
    }
    else if (palpite < segredo)
    {
        Console.WriteLine($"O número é maior que {palpite}.");
    }
    else
    {
        Console.WriteLine($"O número é menor que {palpite}.");
    }
}

if (acertou)
{
    Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
}
else
{
    // mostre que as tentativas acabaram e qual era o número
}
const int MaxTentativas = 7;
int segredo = Random.Shared.Next(1, 101);

Console.WriteLine("=== Jogo de Adivinhação ===");
Console.WriteLine($"Pensei num número de 1 a 100. Você tem {MaxTentativas} tentativas.");

int tentativas = 0;
bool acertou = false;
while (tentativas < MaxTentativas)
{
    Console.Write($"Palpite {tentativas + 1} de {MaxTentativas}: ");
    string? texto = Console.ReadLine();
    if (texto is null)
    {
        Console.WriteLine();
        Console.WriteLine("Jogo interrompido.");
        return;
    }
    if (!int.TryParse(texto, out int palpite))
    {
        Console.WriteLine("Digite um número inteiro.");
        continue;
    }
    tentativas++;

    if (palpite == segredo)
    {
        acertou = true;
        break;
    }
    else if (palpite < segredo)
    {
        Console.WriteLine($"O número é maior que {palpite}.");
    }
    else
    {
        Console.WriteLine($"O número é menor que {palpite}.");
    }
}

if (acertou)
{
    Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
}
else
{
    Console.WriteLine($"Suas {MaxTentativas} tentativas acabaram. O número era {segredo}.");
}

Versão 5 · Jogar de novo

Para jogar outra partida, a Ana tinha de rodar o programa de novo. Na versão final, o programa pergunta "Jogar de novo?". Duas mudanças:

  • A partida inteira vai para um método Jogar(). O programa principal fica só com o "jogar enquanto a Ana quiser".
  • Dois do while: um para as partidas e outro para a pergunta. O do while roda o corpo pelo menos uma vez e só depois testa a condição: perfeito para "jogue uma partida, depois pergunte" e para "pergunte até a resposta ser s ou n".
const int MaxTentativas = 7;

Console.WriteLine("=== Jogo de Adivinhação ===");

int partidas = 0;
string resposta;
do
{
    partidas++;
    Console.WriteLine();
    Console.WriteLine($"Partida {partidas}: pensei num número de 1 a 100. Você tem {MaxTentativas} tentativas.");
    Jogar();

    do
    {
        Console.Write("Jogar de novo? (s/n): ");
        resposta = (Console.ReadLine() ?? "n").Trim().ToLower();
    } while (resposta != "s" && resposta != "n");
} while (resposta == "s");

Console.WriteLine($"Partidas: {partidas}. Até a próxima!");

static void Jogar()
{
    int segredo = Random.Shared.Next(1, 101);
    int tentativas = 0;
    while (tentativas < MaxTentativas)
    {
        Console.Write($"Palpite {tentativas + 1} de {MaxTentativas}: ");
        string? texto = Console.ReadLine();
        if (texto is null)
        {
            Console.WriteLine();
            Console.WriteLine("Jogo interrompido.");
            return;
        }
        if (!int.TryParse(texto, out int palpite))
        {
            Console.WriteLine("Digite um número inteiro.");
            continue;
        }
        tentativas++;

        if (palpite == segredo)
        {
            Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
            return;
        }
        if (palpite < segredo)
        {
            Console.WriteLine($"O número é maior que {palpite}.");
        }
        else
        {
            Console.WriteLine($"O número é menor que {palpite}.");
        }
    }
    Console.WriteLine($"Suas {MaxTentativas} tentativas acabaram. O número era {segredo}.");
}
string resposta;
Declarada antes do do, porque o while (resposta == "s") do fim fica fora das chaves e precisa enxergá-la (Escopo). Não precisa de valor inicial: o C# vê que o do sempre guarda algo nela antes do teste.
do { … } while (resposta == "s");
Joga uma partida, pergunta, e repete se a resposta for s. Repare no ; depois do while: no do while ele é obrigatório.
(Console.ReadLine() ?? "n").Trim().ToLower()
Se a entrada acabou, vale "n". O Trim() tira os espaços das pontas e o ToLower() passa para minúsculas: " S " vira "s" (Métodos de String).
while (resposta != "s" && resposta != "n")
Pergunta de novo enquanto a resposta não for nem s nem n: talvez não vale (&&).
static void Jogar()
A partida inteira. void quer dizer "não devolve nada". O segredo e as tentativas agora são variáveis do método: cada partida começa do zero.
return; (dentro de Jogar)
Termina só a partida e volta para quem chamou. O bool acertou não é mais necessário: se o laço acabar sem return, as tentativas acabaram.
MaxTentativas dentro de Jogar()
Um static local enxerga as constantes do programa principal (mas não as variáveis comuns, como partidas; veja Funções Locais).

A saída é a do começo da página, em "O que vamos construir".

Missão (versão 5): o jogo repete, mas o fim sempre diz Partidas: 0. (e as partidas aparecem como "Partida 0"). No começo do do de fora, conte a partida com partidas++;. A entrada joga duas partidas (palpites 1 a 7, s, palpites 1 a 7, n); quando a Ana acerta antes do 7º palpite, os números que sobram são respondidos na pergunta "Jogar de novo?", que os recusa até chegar o s ou o n.

const int MaxTentativas = 7;

Console.WriteLine("=== Jogo de Adivinhação ===");

int partidas = 0;
string resposta;
do
{
    // conte a partida
    Console.WriteLine();
    Console.WriteLine($"Partida {partidas}: pensei num número de 1 a 100. Você tem {MaxTentativas} tentativas.");
    Jogar();

    do
    {
        Console.Write("Jogar de novo? (s/n): ");
        resposta = (Console.ReadLine() ?? "n").Trim().ToLower();
    } while (resposta != "s" && resposta != "n");
} while (resposta == "s");

Console.WriteLine($"Partidas: {partidas}. Até a próxima!");

static void Jogar()
{
    int segredo = Random.Shared.Next(1, 101);
    int tentativas = 0;
    while (tentativas < MaxTentativas)
    {
        Console.Write($"Palpite {tentativas + 1} de {MaxTentativas}: ");
        string? texto = Console.ReadLine();
        if (texto is null)
        {
            Console.WriteLine();
            Console.WriteLine("Jogo interrompido.");
            return;
        }
        if (!int.TryParse(texto, out int palpite))
        {
            Console.WriteLine("Digite um número inteiro.");
            continue;
        }
        tentativas++;

        if (palpite == segredo)
        {
            Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
            return;
        }
        if (palpite < segredo)
        {
            Console.WriteLine($"O número é maior que {palpite}.");
        }
        else
        {
            Console.WriteLine($"O número é menor que {palpite}.");
        }
    }
    Console.WriteLine($"Suas {MaxTentativas} tentativas acabaram. O número era {segredo}.");
}
const int MaxTentativas = 7;

Console.WriteLine("=== Jogo de Adivinhação ===");

int partidas = 0;
string resposta;
do
{
    partidas++;
    Console.WriteLine();
    Console.WriteLine($"Partida {partidas}: pensei num número de 1 a 100. Você tem {MaxTentativas} tentativas.");
    Jogar();

    do
    {
        Console.Write("Jogar de novo? (s/n): ");
        resposta = (Console.ReadLine() ?? "n").Trim().ToLower();
    } while (resposta != "s" && resposta != "n");
} while (resposta == "s");

Console.WriteLine($"Partidas: {partidas}. Até a próxima!");

static void Jogar()
{
    int segredo = Random.Shared.Next(1, 101);
    int tentativas = 0;
    while (tentativas < MaxTentativas)
    {
        Console.Write($"Palpite {tentativas + 1} de {MaxTentativas}: ");
        string? texto = Console.ReadLine();
        if (texto is null)
        {
            Console.WriteLine();
            Console.WriteLine("Jogo interrompido.");
            return;
        }
        if (!int.TryParse(texto, out int palpite))
        {
            Console.WriteLine("Digite um número inteiro.");
            continue;
        }
        tentativas++;

        if (palpite == segredo)
        {
            Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
            return;
        }
        if (palpite < segredo)
        {
            Console.WriteLine($"O número é maior que {palpite}.");
        }
        else
        {
            Console.WriteLine($"O número é menor que {palpite}.");
        }
    }
    Console.WriteLine($"Suas {MaxTentativas} tentativas acabaram. O número era {segredo}.");
}
Como testar um jogo que muda a cada vez?

As missões desta página nunca dependem do número sorteado: a entrada cobre todos os casos (os palpites de 1 a 100, ou "ganhou ou perdeu, tanto faz"). Outra técnica, muito usada em testes automáticos, é criar o sorteador com uma semente fixa, new Random(123), que sorteia sempre a mesma sequência. Veja Números Aleatórios e Testes.


O programa completo

A versão final é a versão 5, acima. O que o projeto juntou: const, if e else if, while, do while, break e continue, Random.Shared, TryParse, string? e um método void.

Agora, jogue de verdade. Esta caixa traz a versão final, sem nenhuma entrada pronta. Quando o jogo pedir um palpite, aparece um campo no terminal da caixa, logo depois da pergunta: digite o número e tecle Enter. O número secreto continua o mesmo durante toda a partida, e Ctrl+D encerra a entrada (o jogo mostra Jogo interrompido. e termina).

const int MaxTentativas = 7;

Console.WriteLine("=== Jogo de Adivinhação ===");

int partidas = 0;
string resposta;
do
{
    partidas++;
    Console.WriteLine();
    Console.WriteLine($"Partida {partidas}: pensei num número de 1 a 100. Você tem {MaxTentativas} tentativas.");
    Jogar();

    do
    {
        Console.Write("Jogar de novo? (s/n): ");
        resposta = (Console.ReadLine() ?? "n").Trim().ToLower();
    } while (resposta != "s" && resposta != "n");
} while (resposta == "s");

Console.WriteLine($"Partidas: {partidas}. Até a próxima!");

static void Jogar()
{
    int segredo = Random.Shared.Next(1, 101);
    int tentativas = 0;
    while (tentativas < MaxTentativas)
    {
        Console.Write($"Palpite {tentativas + 1} de {MaxTentativas}: ");
        string? texto = Console.ReadLine();
        if (texto is null)
        {
            Console.WriteLine();
            Console.WriteLine("Jogo interrompido.");
            return;
        }
        if (!int.TryParse(texto, out int palpite))
        {
            Console.WriteLine("Digite um número inteiro.");
            continue;
        }
        tentativas++;

        if (palpite == segredo)
        {
            Console.WriteLine($"Acertou! Tentativas: {tentativas}.");
            return;
        }
        if (palpite < segredo)
        {
            Console.WriteLine($"O número é maior que {palpite}.");
        }
        else
        {
            Console.WriteLine($"O número é menor que {palpite}.");
        }
    }
    Console.WriteLine($"Suas {MaxTentativas} tentativas acabaram. O número era {segredo}.");
}

Desafios extras

  1. Recorde. Faça o Jogar() devolver um int (as tentativas, ou 0 se perdeu) e mostre no fim o menor número de tentativas das partidas (veja Valor de Retorno).
  2. Níveis. Pergunte o nível antes de cada partida: fácil (1 a 50, 10 tentativas), normal (1 a 100, 7) e difícil (1 a 1000, 10). Um enum e uma expressão switch ajudam.
  3. Esquentando. Além de "maior" ou "menor", diga "está quente!" quando o palpite estiver a menos de 5 do segredo (Math.Abs(palpite - segredo) < 5, veja Math).
  4. Fora da faixa. Recuse palpites menores que 1 ou maiores que 100 sem gastar tentativa.
  5. O computador adivinha. Inverta o jogo: você pensa no número, e o programa chuta o meio e pergunta "maior, menor ou acertei?".

Exercícios

1. Quais números Random.Shared.Next(1, 7) pode sortear?

2. Qual a diferença entre while e do while?

3. Por que int tentativas = 0; fica fora do while?

4. O que o CS0165 ("Uso de variável local não atribuída") quer dizer?

5. Complete o sorteio de um número de 1 a 100:

int segredo = Random.Shared.Next(1, );

6. Complete o laço que pergunta até a resposta ser s ou n:

} while (resposta != "s" resposta != "n");

Resumo

  • Comece com um segredo fixo para testar; sorteie só quando o jogo funcionar.
  • Random.Shared.Next(1, 101) sorteia de 1 a 100: o segundo número não entra.
  • while (true) com break repete até acertar; while (tentativas < MaxTentativas) repete enquanto há tentativas, e um bool diz como o laço terminou.
  • TryParse com continue recusa palpites que não são números sem gastar tentativa.
  • Trate o null do ReadLine (fim da entrada), senão um laço com continue nunca termina.
  • do while roda pelo menos uma vez: ótimo para "jogar de novo?" e para repetir uma pergunta até a resposta valer.

Próximo projeto: Boletim de Notas.