---
title: "Quiz no Terminal em Zig — Tutorial Passo a Passo"
url: "https://ziglang.com.br/projetos/quiz-no-terminal-em-zig-tutorial-passo-a-passo/"
markdown_url: "https://ziglang.com.br/projetos/quiz-no-terminal-em-zig-tutorial-passo-a-passo.MD"
description: "Construa um jogo de perguntas e respostas interativo no terminal em Zig com categorias, pontuação e ranking."
date: "2026-02-21"
author: "Zig Brasil"
---

# Quiz no Terminal em Zig — Tutorial Passo a Passo

Construa um jogo de perguntas e respostas interativo no terminal em Zig com categorias, pontuação e ranking.


# Quiz no Terminal em Zig — Tutorial Passo a Passo

Neste tutorial, vamos construir um **jogo de quiz interativo** no terminal. O jogador responde perguntas de múltipla escolha, acumula pontos e recebe um ranking no final. Este projeto é excelente para praticar arrays, enums, structs e lógica de controle em Zig.

## O Que Vamos Construir

Nosso quiz vai:

- Apresentar perguntas de múltipla escolha com 4 alternativas
- Organizar perguntas por categorias (Ciência, História, Tecnologia)
- Calcular pontuação com bônus por respostas rápidas consecutivas
- Embaralhar a ordem das perguntas
- Exibir um ranking final (Mestre, Especialista, Novato, etc.)

## Por Que Este Projeto?

Um quiz é um projeto que parece simples, mas nos força a pensar em como modelar dados estruturados em Zig. Precisamos de arrays de structs, enums para categorias, e lógica de seleção aleatória. É o tipo de projeto que consolida o uso de tipos compostos da linguagem.

## Pré-requisitos

- Zig 0.13+ instalado ([guia de instalação](/tutoriais/instalacao/))
- Familiaridade com conceitos básicos de Zig ([fundamentos](/tutoriais/fundamentos/))

## Passo 1: Estrutura do Projeto

```bash
mkdir quiz-terminal
cd quiz-terminal
zig init
```

Vamos trabalhar em `src/main.zig`.

## Passo 2: Modelando os Dados

O primeiro passo é definir como representamos perguntas, alternativas e categorias. Em Zig, preferimos tipos explícitos e comptime-known sempre que possível.

```zig
const std = @import("std");
const io = std.io;
const fmt = std.fmt;
const mem = std.mem;

/// Categorias das perguntas.
/// Usamos enum para que o compilador garanta que tratamos todas as categorias.
const Categoria = enum {
    ciencia,
    historia,
    tecnologia,

    pub fn nome(self: Categoria) []const u8 {
        return switch (self) {
            .ciencia => "Ciência",
            .historia => "História",
            .tecnologia => "Tecnologia",
        };
    }

    pub fn cor(self: Categoria) []const u8 {
        return switch (self) {
            .ciencia => "\x1b[32m",   // verde
            .historia => "\x1b[33m",  // amarelo
            .tecnologia => "\x1b[36m", // ciano
        };
    }
};

/// Uma pergunta do quiz com suas alternativas.
/// As alternativas são um array fixo de 4 strings.
/// A resposta correta é indexada de 0 a 3.
const Pergunta = struct {
    texto: []const u8,
    alternativas: [4][]const u8,
    resposta_correta: u2, // 0-3, u2 é suficiente
    categoria: Categoria,
    dificuldade: u8, // 1-3, afeta pontuação
};

/// Resultado final do jogador.
const Ranking = enum {
    mestre,
    especialista,
    competente,
    aprendiz,
    novato,

    pub fn dePercentual(pct: f64) Ranking {
        if (pct >= 90.0) return .mestre;
        if (pct >= 70.0) return .especialista;
        if (pct >= 50.0) return .competente;
        if (pct >= 30.0) return .aprendiz;
        return .novato;
    }

    pub fn titulo(self: Ranking) []const u8 {
        return switch (self) {
            .mestre => "Mestre Supremo",
            .especialista => "Especialista",
            .competente => "Competente",
            .aprendiz => "Aprendiz",
            .novato => "Novato",
        };
    }

    pub fn emoji(self: Ranking) []const u8 {
        return switch (self) {
            .mestre => "[*****]",
            .especialista => "[****-]",
            .competente => "[***--]",
            .aprendiz => "[**---]",
            .novato => "[*----]",
        };
    }
};
```

**Decisao de design:** Usamos `u2` para o indice da resposta correta. Como temos exatamente 4 alternativas (0-3), um inteiro de 2 bits e suficiente. Isso documenta no tipo que o valor nunca sera maior que 3.

## Passo 3: Banco de Perguntas

```zig
/// Banco de perguntas compilado estaticamente.
/// Em Zig, arrays const em escopo de arquivo sao armazenados
/// na secao .rodata do binario — sem alocacao em runtime.
const perguntas = [_]Pergunta{
    .{
        .texto = "Qual é o elemento químico com símbolo 'O'?",
        .alternativas = .{ "Ouro", "Oxigênio", "Ósmio", "Oganesson" },
        .resposta_correta = 1,
        .categoria = .ciencia,
        .dificuldade = 1,
    },
    .{
        .texto = "Em que ano o Brasil se tornou independente?",
        .alternativas = .{ "1808", "1822", "1889", "1500" },
        .resposta_correta = 1,
        .categoria = .historia,
        .dificuldade = 1,
    },
    .{
        .texto = "Quem criou a linguagem de programação Zig?",
        .alternativas = .{ "Graydon Hoare", "Andrew Kelley", "Bjarne Stroustrup", "Guido van Rossum" },
        .resposta_correta = 1,
        .categoria = .tecnologia,
        .dificuldade = 2,
    },
    .{
        .texto = "Qual é a velocidade da luz no vácuo (aprox.)?",
        .alternativas = .{ "300.000 km/s", "150.000 km/s", "1.000.000 km/s", "30.000 km/s" },
        .resposta_correta = 0,
        .categoria = .ciencia,
        .dificuldade = 2,
    },
    .{
        .texto = "Qual civilização construiu Machu Picchu?",
        .alternativas = .{ "Maia", "Asteca", "Inca", "Olmeca" },
        .resposta_correta = 2,
        .categoria = .historia,
        .dificuldade = 2,
    },
    .{
        .texto = "O que significa 'comptime' em Zig?",
        .alternativas = .{ "Compile Time", "Computer Time", "Complete Time", "Compact Time" },
        .resposta_correta = 0,
        .categoria = .tecnologia,
        .dificuldade = 2,
    },
    .{
        .texto = "Qual é o maior osso do corpo humano?",
        .alternativas = .{ "Úmero", "Tíbia", "Fêmur", "Fíbula" },
        .resposta_correta = 2,
        .categoria = .ciencia,
        .dificuldade = 1,
    },
    .{
        .texto = "Em que ano começou a Primeira Guerra Mundial?",
        .alternativas = .{ "1912", "1914", "1916", "1918" },
        .resposta_correta = 1,
        .categoria = .historia,
        .dificuldade = 1,
    },
    .{
        .texto = "Qual sistema operacional Zig usa como target padrão de compilação cruzada?",
        .alternativas = .{ "Windows", "macOS", "Linux (musl)", "FreeBSD" },
        .resposta_correta = 2,
        .categoria = .tecnologia,
        .dificuldade = 3,
    },
    .{
        .texto = "Qual partícula subatômica tem carga negativa?",
        .alternativas = .{ "Próton", "Nêutron", "Elétron", "Fóton" },
        .resposta_correta = 2,
        .categoria = .ciencia,
        .dificuldade = 1,
    },
    .{
        .texto = "Quem pintou a Mona Lisa?",
        .alternativas = .{ "Michelangelo", "Rafael", "Leonardo da Vinci", "Donatello" },
        .resposta_correta = 2,
        .categoria = .historia,
        .dificuldade = 1,
    },
    .{
        .texto = "O que é um 'allocator' em Zig?",
        .alternativas = .{ "Um tipo de loop", "Um gerenciador de memória", "Um tipo de erro", "Um compilador" },
        .resposta_correta = 1,
        .categoria = .tecnologia,
        .dificuldade = 2,
    },
};
```

## Passo 4: Embaralhamento

Para tornar o quiz mais interessante, embaralhamos as perguntas a cada execucao.

```zig
/// Embaralha um array in-place usando o algoritmo Fisher-Yates.
/// Este é o algoritmo padrão para embaralhamento uniforme —
/// cada permutação tem a mesma probabilidade de ocorrer.
fn embaralhar(comptime T: type, slice: []T, rng: std.Random) void {
    if (slice.len <= 1) return;

    var i: usize = slice.len - 1;
    while (i > 0) : (i -= 1) {
        const j = rng.intRangeAtMost(usize, 0, i);
        const tmp = slice[i];
        slice[i] = slice[j];
        slice[j] = tmp;
    }
}
```

## Passo 5: Logica do Jogo

```zig
/// Estado de uma sessao de quiz.
const SessaoQuiz = struct {
    acertos: u32 = 0,
    erros: u32 = 0,
    pontuacao: u32 = 0,
    sequencia: u32 = 0,       // Acertos consecutivos
    melhor_sequencia: u32 = 0,

    pub fn registrarResposta(self: *SessaoQuiz, correta: bool, dificuldade: u8) void {
        if (correta) {
            self.acertos += 1;
            self.sequencia += 1;
            if (self.sequencia > self.melhor_sequencia) {
                self.melhor_sequencia = self.sequencia;
            }
            // Pontos base * dificuldade + bonus de sequencia
            const base: u32 = 100 * @as(u32, dificuldade);
            const bonus: u32 = if (self.sequencia >= 3) 50 else 0;
            self.pontuacao += base + bonus;
        } else {
            self.erros += 1;
            self.sequencia = 0;
        }
    }

    pub fn total(self: *const SessaoQuiz) u32 {
        return self.acertos + self.erros;
    }

    pub fn percentual(self: *const SessaoQuiz) f64 {
        const t = self.total();
        if (t == 0) return 0.0;
        return @as(f64, @floatFromInt(self.acertos)) / @as(f64, @floatFromInt(t)) * 100.0;
    }
};

/// Apresenta uma pergunta e retorna se o jogador acertou.
fn apresentarPergunta(
    p: *const Pergunta,
    numero: usize,
    total_perguntas: usize,
    reader: anytype,
    writer: anytype,
) !bool {
    const reset = "\x1b[0m";
    const cor = p.categoria.cor();

    try writer.print("\n{s}[{s}]{s} Pergunta {d}/{d} (dificuldade: {d}/3)\n", .{
        cor, p.categoria.nome(), reset,
        numero, total_perguntas, p.dificuldade,
    });
    try writer.print("\n  {s}\n\n", .{p.texto});

    // Exibir alternativas
    const letras = "ABCD";
    for (p.alternativas, 0..) |alt, i| {
        try writer.print("    {c}) {s}\n", .{ letras[i], alt });
    }

    try writer.print("\n  Sua resposta (A/B/C/D): ", .{});

    var buf: [64]u8 = undefined;
    const linha = reader.readUntilDelimiterOrEof(&buf, '\n') catch return false orelse return false;
    const entrada = mem.trim(u8, linha, " \t\r\n");

    if (entrada.len != 1) {
        try writer.print("  Resposta inválida!\n", .{});
        return false;
    }

    const resposta: u8 = switch (entrada[0]) {
        'A', 'a' => 0,
        'B', 'b' => 1,
        'C', 'c' => 2,
        'D', 'd' => 3,
        else => {
            try writer.print("  Resposta inválida!\n", .{});
            return false;
        },
    };

    const correta = resposta == p.resposta_correta;
    if (correta) {
        try writer.print("  \x1b[32mCorreto!\x1b[0m\n", .{});
    } else {
        try writer.print("  \x1b[31mErrado!\x1b[0m A resposta era: {c}) {s}\n", .{
            letras[p.resposta_correta], p.alternativas[p.resposta_correta],
        });
    }

    return correta;
}
```

## Passo 6: Loop Principal e Resultado

```zig
pub fn main() !void {
    const stdout = io.getStdOut().writer();
    const stdin = io.getStdIn().reader();

    // Inicializa PRNG
    var prng = std.Random.DefaultPrng.init(blk: {
        var seed: u64 = undefined;
        std.posix.getrandom(std.mem.asBytes(&seed)) catch unreachable;
        break :blk seed;
    });
    const rng = prng.random();

    try stdout.print(
        \\
        \\  =============================================
        \\       QUIZ ZIG - Teste Seus Conhecimentos
        \\  =============================================
        \\
        \\  Categorias: Ciencia, Historia, Tecnologia
        \\  Acerte em sequencia para ganhar bonus!
        \\
        \\  Pressione ENTER para comecar...
        \\
    , .{});

    var buf: [64]u8 = undefined;
    _ = stdin.readUntilDelimiterOrEof(&buf, '\n') catch {};

    // Copia e embaralha perguntas
    var perguntas_jogo = perguntas;
    embaralhar(Pergunta, &perguntas_jogo, rng);

    var sessao = SessaoQuiz{};
    const total_p = perguntas_jogo.len;

    for (perguntas_jogo, 0..) |*p, i| {
        const correta = try apresentarPergunta(p, i + 1, total_p, stdin, stdout);
        sessao.registrarResposta(correta, p.dificuldade);

        // Mostrar sequencia se >= 2
        if (sessao.sequencia >= 2) {
            try stdout.print("  Sequencia: {d} acertos seguidos!\n", .{sessao.sequencia});
        }
    }

    // Resultado final
    const pct = sessao.percentual();
    const ranking = Ranking.dePercentual(pct);

    try stdout.print(
        \\
        \\  =============================================
        \\           RESULTADO FINAL
        \\  =============================================
        \\
        \\  Acertos:          {d}/{d}
        \\  Percentual:       {d:.1}%
        \\  Pontuacao:        {d} pontos
        \\  Melhor sequencia: {d} acertos
        \\
        \\  Ranking: {s} {s}
        \\
        \\  =============================================
        \\
    , .{
        sessao.acertos, sessao.total(),
        pct,
        sessao.pontuacao,
        sessao.melhor_sequencia,
        ranking.emoji(), ranking.titulo(),
    });
}
```

## Passo 7: Testes

```zig
test "ranking de percentual" {
    try std.testing.expectEqual(Ranking.mestre, Ranking.dePercentual(95.0));
    try std.testing.expectEqual(Ranking.especialista, Ranking.dePercentual(75.0));
    try std.testing.expectEqual(Ranking.competente, Ranking.dePercentual(55.0));
    try std.testing.expectEqual(Ranking.aprendiz, Ranking.dePercentual(35.0));
    try std.testing.expectEqual(Ranking.novato, Ranking.dePercentual(10.0));
}

test "sessao quiz - acerto com bonus" {
    var sessao = SessaoQuiz{};
    sessao.registrarResposta(true, 1);
    sessao.registrarResposta(true, 1);
    sessao.registrarResposta(true, 1); // 3a consecutiva = bonus

    try std.testing.expectEqual(@as(u32, 3), sessao.acertos);
    try std.testing.expectEqual(@as(u32, 350), sessao.pontuacao); // 100+100+150
    try std.testing.expectEqual(@as(u32, 3), sessao.melhor_sequencia);
}

test "sessao quiz - erro reseta sequencia" {
    var sessao = SessaoQuiz{};
    sessao.registrarResposta(true, 1);
    sessao.registrarResposta(true, 1);
    sessao.registrarResposta(false, 1);

    try std.testing.expectEqual(@as(u32, 0), sessao.sequencia);
    try std.testing.expectEqual(@as(u32, 2), sessao.melhor_sequencia);
}

test "embaralhar preserva elementos" {
    var prng_test = std.Random.DefaultPrng.init(42);
    var arr = [_]u32{ 1, 2, 3, 4, 5 };
    embaralhar(u32, &arr, prng_test.random());

    // Verifica que todos os elementos ainda existem
    var soma: u32 = 0;
    for (arr) |v| soma += v;
    try std.testing.expectEqual(@as(u32, 15), soma);
}
```

## Compilando e Executando

```bash
zig build test   # Rodar testes
zig build run    # Jogar o quiz
```

## Desafios Extras

1. **Perguntas de arquivo** — carregue perguntas de um arquivo JSON usando [parsing JSON](/receitas/json/)
2. **Temporizador** — adicione um limite de tempo por pergunta
3. **Categorias selecionaveis** — deixe o jogador escolher quais categorias quer
4. **Persistencia de recordes** — salve os melhores scores em arquivo

## Conceitos Aprendidos

- Modelagem de dados com `struct` e `enum`
- Arrays fixos de structs em comptime
- Algoritmo de embaralhamento Fisher-Yates
- Tipos inteiros de tamanho preciso (`u2`)
- Sequencias ANSI para cores no terminal
- Testes unitarios nativos

## Proximos Passos

- Aprenda mais sobre [manipulacao de strings em Zig](/receitas/strings/)
- Explore o [modulo std.Random](/stdlib/rand/) da biblioteca padrao
- Construa o proximo projeto: [Cifra de Cesar](/projetos/cifra-cesar/)
