Introdução
Uma pontuação é apenas uma decisão. Uma partida completa precisa lembrar as categorias usadas e manter os mesmos dados enquanto você corrige uma escolha inválida. Você terminará um jogo de três rodadas e salvará uma ficha que pode ser verificada recalculando os pontos a partir dos dados e das categorias registrados.
As regras de pontuação do primeiro laboratório já estão instaladas neste projeto independente. Você deve conhecer essas regras, funções Rust, enum, match, Option, arrays e caminhos básicos no terminal. Este laboratório apresenta as estruturas preparadas, os métodos mutáveis, Result e as fatias no momento de usá-los. Reserve cerca de 55 minutos, incluindo leitura e erros comuns; essa é uma estimativa do autor.
Confirmar somente uma escolha válida
Nesta etapa, planeje e implemente a transição de estado de uma escolha aceita. Comece na aba Terminal. Este projeto independente já tem regras de pontuação completas. Não usa arquivos do primeiro laboratório. Configure o caminho de busca deste shell e entre no projeto; repita os comandos em um novo shell:
export PATH=/home/labex/.cargo/bin:$PATH
cd /home/labex/project/dice-game
Comece com uma pontuação normal funcional e preveja-a antes de executar:
cargo run --quiet -- score pair 2 2 4 4 6
Espere os dados exibidos e pair: 8. Isso comprova que a camada de regras fornecida é utilizável antes de acrescentar estado à partida. Leia a estrutura inicial:
cat src/state.rs
Um struct reúne campos nomeados que descrevem um objeto. Round registra cinco dados, uma categoria e pontos. Game contém current: Option<Dice> e rounds: Vec<Round>. Some(dice) significa que uma escolha está pendente; None significa que não há rolagem atual. Vec<Round> é uma coleção expansível; Vec::new() cria uma vazia e push acrescenta um item.
Leia também o chamador no programa de entrada preparado:
cat src/main.rs
Localize match game.submit(input.trim()). Seu ramo Err imprime uma rejeição. Seu ramo Ok imprime pontos e só obtém outra rolagem se houver menos de três rodadas registradas. Você não edita esse programa. Seu método deve deixar o estado inalterado após rejeitar, para que a próxima solicitação mostre os mesmos dados e contagem de rolagens.
Preveja os campos depois de cada tipo de confirmação:
| Confirmação | rounds após a chamada |
current após a chamada |
|---|---|---|
| Sem dados pendentes, palavra desconhecida ou categoria usada | Inalterado | Inalterado |
| Categoria válida não usada, incluindo pontuação zero | Um novo registro completo acrescentado | None |
O programa de entrada, não submit, fornece a próxima rolagem após sucesso. Não obtenha dados aleatórios no método. impl Game agrupa métodos de Game. &mut self permite atualizar essa partida por uma referência mutável. Result<u16, &'static str> significa Ok(points) ou Err(message); &'static str é o tipo de texto fixo das mensagens de erro. Diferentemente de Option, Result pode explicar uma falha.
ok_or(message) transforma Some em Ok e None em Err. Um ? seguinte continua com o valor bem-sucedido ou retorna o erro imediatamente. Por exemplo, a verificação de rolagem pendente pode ser:
let dice = self.current.ok_or("match finished")?;
Aplique a mesma ideia a Category::parse(input). Antes de alterar qualquer campo, tome os registros anteriores por empréstimo com um laço for sobre &self.rounds e rejeite uma categoria correspondente. Você escolhe como organizar as verificações; a tabela é o contrato. Depois da validação, obtenha points com score(dice, category). A confirmação bem-sucedida tem três ações:
self.rounds.push(Round { dice, category, points });
self.current = None;
Ok(points)
Os campos abreviados usam variáveis locais com os mesmos nomes. Um resultado zero ainda é sucesso. Nenhuma dessas alterações pertence a um caminho rejeitado.
Mantenha src/main.rs, Cargo.toml, os tipos públicos, nomes e tipos das funções e as exportações de módulo inalterados. use crate::{score, Category, Dice} importa da biblioteca atual; mod state e pub use em src/lib.rs conectam os módulos. Esta etapa edita somente submit em src/state.rs, preservando as estruturas, o método new e replay incompleto.
Antes de editar, faça uma cópia única desse arquivo permitido. Como ensinado, mktemp -d substitui seis X por um sufixo único. $() guarda o caminho impresso; && executa cp -p somente depois da criação bem-sucedida do diretório. O caminho entre aspas continua como um argumento. Se falhar, leia o erro e obtenha uma cópia antes de editar.
rs02_before_submit=$(mktemp -d .edit-before-submit.XXXXXX) && cp -p src/state.rs "$rs02_before_submit/state.rs"
Abra o arquivo no nano; -c mostra a posição do cursor:
nano -c src/state.rs
Pressione Ctrl+W, procure pub fn submit e pressione Enter. Examine as chaves de abertura e fechamento desse método. Renomeie a variável de parâmetro não usada _input para input sem mudar seu tipo. Substitua apenas o corpo incompleto por sua implementação das verificações e da confirmação bem-sucedida acima. Preserve os outros métodos e definições de dados. Use setas e Backspace/Delete para editar. Salve com Ctrl+O, Enter e saia com Ctrl+X. A notação ^ do nano significa Ctrl. Se houver uma edição acidental ainda não salva, Ctrl+X e N à pergunta sobre salvar a descartam; isso não desfaz uma edição salva antes.
Leia o método salvo antes de executar:
cat src/state.rs
Use a sequência fixa conhecida para observar suas decisões de estado. --fixture seleciona rolagens divulgadas em vez da fonte aleatória:
cargo run --quiet -- play --fixture
Aguarde cada solicitação, digite uma palavra e pressione Enter:
Primeiro digite pairs. É desconhecida: espere os mesmos [2, 2, 4, 4, 6] e rolls 1 após a rejeição.
Depois digite pair. Vale 8 e avança para [1, 2, 3, 4, 6], rolls 2.
Digite pair novamente. A categoria usada é rejeitada, preservando esses dados e rolls 2.
Depois digite straight. Essa escolha válida vale zero e avança para a terceira rolagem.
Por fim digite sum. Vale 20.
O erro final esperado é Error: replay not implemented, com status 1. Suas confirmações funcionaram, mas replay ainda é a função original incompleta, portanto o programa se recusa a salvar uma ficha não verificada. Este é o estado intermediário planejado, não uma partida salva com sucesso. Se escolher outra categoria válida por engano, termine as opções não usadas ou use Ctrl+C e inicie intencionalmente uma nova partida fixa. Dentro de uma partida, uma rejeição não deve reiniciar nem rolar nada novamente.
Para um erro do compilador, leia o arquivo/linha indicado, reabra nano, procure o método afetado e repare-o. Para um erro salvo que não consiga reparar, restaure seu arquivo anterior a submit e implemente e teste submit novamente:
Somente ao recuperar um erro já salvo: no Terminal original, a variável do checkpoint ainda contém seu caminho. Em um novo Terminal, primeiro repita os comandos anteriores de PATH e diretório do projeto. Liste as cópias de segurança desta etapa:
ls -d .edit-before-submit.*
ls -d lista nomes de diretórios, não seu conteúdo; * corresponde aos sufixos dos diretórios únicos desta fase. Esses nomes foram realmente gerados, portanto não digite um sufixo inventado. Se nenhuma cópia corresponder, confirme o diretório do projeto e se você criou este checkpoint; não copie de uma fase sem relação. Para selecionar um dos caminhos exibidos, execute:
read rs02_before_submit
O shell aguarda uma linha. Cole o nome exato do diretório escolhido e pressione Enter. read guarda essa linha na variável indicada para esta sessão. Confirme o conteúdo da cópia antes de restaurar:
cat "$rs02_before_submit/state.rs"
Ela deve conter o código inicial anterior a submit. Se o caminho ou a etapa estiver errado, repita read com o caminho correto exibido e examine novamente. Só então use o comando de restauração. Essa verificação também vale no Terminal original se você não souber ao certo qual checkpoint a variável identifica.
cp -p "$rs02_before_submit/state.rs" src/state.rs
Esse comando substitui intencionalmente apenas o arquivo de estado editável. A variável da cópia é local a este Terminal; use as instruções condicionais de recuperação em outro shell. Não reinicialize setup nem substitua o programa de entrada preparado para corrigir um erro de estado.
Recalcular e salvar uma ficha completa
Nesta etapa, implemente uma verificação independente de cada rodada registrada antes de aceitar um total. Continue no mesmo Terminal, em /home/labex/project/dice-game; repita export PATH e cd anteriores se necessário. Preserve seu método submit concluído.
Primeiro confira à mão a ficha fixa proposta: pair em [2, 2, 4, 4, 6] vale 8, straight em [1, 2, 3, 4, 6] vale 0 e sum em [6, 5, 4, 3, 2] vale 20. O total recalculado é 28. Se alguém escreveu 10 em vez de 8 na primeira linha, somar seus pontos salvos ocultaria o registro errado. Uma ficha não pode comprovar sua correção com seu próprio total ou pontos salvos.
replay recebe &[Round], uma fatia: uma visão emprestada de registros, sem transferência de posse nem modificação. Planeje as verificações antes de escrever. Exija exatamente três registros. Para cada um, rejeite valores fora de 1 a 6 antes de chamar score, pois uma implementação de pontuação pode usar um valor como índice de array. Registre as categorias usadas em um pequeno Vec; contains testa se a categoria atual já apareceu. Rejeite duplicatas. Recalcule pontos a partir dos dados e categoria reais e compare com os pontos salvos. Some apenas o resultado recalculado ao total e só retorne o total depois que todos os registros forem válidos.
Você pode usar return Err("...") antecipado para uma condição que falhou. Em for round in rounds, cada rodada é emprestada; round.dice é um pequeno array Copy que pode examinar sem alterar o registro. A condição !(1..=6).contains(&face) significa que o valor está fora do intervalo válido inclusivo. Escolha mensagens de rejeição significativas; sua implementação pode organizar essas decisões de outra forma preservando o contrato.
Salve um novo ponto de restauração contendo seu submit já funcional. Ele não pode ser confundido com a cópia inicial anterior:
rs02_before_replay=$(mktemp -d .edit-before-replay.XXXXXX) && cp -p src/state.rs "$rs02_before_replay/state.rs"
Abra o mesmo arquivo e procure pub fn replay com Ctrl+W:
nano -c src/state.rs
Renomeie a variável de parâmetro não usada _rounds para rounds, preservando seu tipo, e implemente apenas o corpo dessa função. Deixe submit, as estruturas e conexões de módulo como estavam. Use suas verificações ordenadas e acumulador; não retorne apenas 28, não confie nos pontos salvos nem ignore silenciosamente um registro inválido. Salve com Ctrl+O, Enter e saia com Ctrl+X. Leia o código salvo:
cat src/state.rs
Execute a sequência fixa novamente:
cargo run --quiet -- play --fixture
Digite pair, depois straight e sum, aguardando cada solicitação. A saída final inclui:
1 | [2, 2, 4, 4, 6] | pair | 8
2 | [1, 2, 3, 4, 6] | straight | 0
3 | [6, 5, 4, 3, 2] | sum | 20
Replayed total: 28
Saved match.tsv
O programa preparado escreve match.tsv somente depois que uma ficha completa passa por replay. Cada linha tem cinco valores separados por espaços, uma tabulação, categoria, outra tabulação e pontos. O programa preparado trata esse formato; você não implementa um analisador de arquivos. Uma nova partida concluída substitui essa ficha do curso, enquanto uma incompleta deixa a anterior intacta.
Leia e recalcule seu arquivo realmente salvo:
cat match.tsv
cargo run --quiet -- replay match.tsv
Espere os mesmos registros e total 28, sem Saved match.tsv. Replay lê o caminho exato sem alterá-lo nem recorrer a um exemplo. Para verificar que não fixou a sequência anterior de categorias em submit, jogue a sequência fixa mais uma vez:
cargo run --quiet -- play --fixture
Use straight, sum e depois pair. Preveja 0, 16 e 0: as três escolhas são válidas e o total recalculado é 16. Essa partida concluída agora substitui a ficha anterior. Recalcule esse arquivo atual:
cargo run --quiet -- replay match.tsv
Seus registros devem refletir essa segunda sequência, não a partida anterior de 28 pontos. Um total diferente é esperado porque a regra escolhida para cada rolagem mudou. A próxima etapa usa dados realmente aleatórios com as mesmas decisões.
Para um erro do compilador, reabra o arquivo indicado e repare a linha de replay informada. Se necessário, restaure seu checkpoint anterior a replay, que preserva submit, e implemente replay novamente:
Somente ao recuperar um erro já salvo: no Terminal original, a variável do checkpoint ainda contém seu caminho. Em um novo Terminal, primeiro repita os comandos anteriores de PATH e diretório do projeto. Liste as cópias de segurança desta etapa:
ls -d .edit-before-replay.*
ls -d lista nomes de diretórios, não seu conteúdo; * corresponde aos sufixos dos diretórios únicos desta fase. Esses nomes foram realmente gerados, portanto não digite um sufixo inventado. Se nenhuma cópia corresponder, confirme o diretório do projeto e se você criou este checkpoint; não copie de uma fase sem relação. Para selecionar um dos caminhos exibidos, execute:
read rs02_before_replay
O shell aguarda uma linha. Cole o nome exato do diretório escolhido e pressione Enter. read guarda essa linha na variável indicada para esta sessão. Confirme o conteúdo da cópia antes de restaurar:
cat "$rs02_before_replay/state.rs"
Ela deve conter submit funcional antes de replay. Se o caminho ou a etapa estiver errado, repita read com o caminho correto exibido e examine novamente. Só então use o comando de restauração. Essa verificação também vale no Terminal original se você não souber ao certo qual checkpoint a variável identifica.
cp -p "$rs02_before_replay/state.rs" src/state.rs
Restaurar o código-fonte não altera uma ficha existente. Uma partida concluída posterior só a substitui depois do sucesso de replay; partidas incompletas e a leitura de uma ficha não. A referência completa abaixo serve para comparar uma função específica depois de sua tentativa, não para substituir o módulo como operação normal de aprendizado.
Referência completa de estado para comparação e recuperação
Preserve seus campos e o trabalho anterior bem-sucedido. Compare apenas a função que está reparando, edite-a no nano e execute a sequência fixa e o recálculo da ficha real novamente.
use crate::{score, Category, Dice};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Round {
pub dice: Dice,
pub category: Category,
pub points: u16,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Game {
pub current: Option<Dice>,
pub rounds: Vec<Round>,
}
impl Game {
pub fn new(first: Dice) -> Self {
Self { current: Some(first), rounds: Vec::new() }
}
pub fn submit(&mut self, input: &str) -> Result<u16, &'static str> {
let dice = self.current.ok_or("match finished")?;
let category = Category::parse(input).ok_or("unknown category")?;
for round in &self.rounds {
if round.category == category {
return Err("category already used");
}
}
let points = score(dice, category);
self.rounds.push(Round { dice, category, points });
self.current = None;
Ok(points)
}
}
pub fn replay(rounds: &[Round]) -> Result<u16, &'static str> {
if rounds.len() != 3 {
return Err("card needs three rounds");
}
let mut used = Vec::new();
let mut total = 0;
for round in rounds {
for face in round.dice {
if !(1..=6).contains(&face) {
return Err("card has an invalid face");
}
}
if used.contains(&round.category) {
return Err("card repeats a category");
}
let expected = score(round.dice, round.category);
if expected != round.points {
return Err("card has incorrect points");
}
used.push(round.category);
total += expected;
}
Ok(total)
}
Jogar e reutilizar sua própria partida
Nesta etapa, você usará as regras completas com dados novos e corrigirá um erro comum de caminho de arquivo. Continue no mesmo Terminal, em /home/labex/project/dice-game.
Sem --fixture, o programa preparado lê bytes aleatórios de /dev/urandom. Ele produz cinco valores de 1 a 6 em cada rolagem. Este é um jogo local de prática. Você não precisa atingir um total específico nem seguir uma estratégia vencedora, mas os pontos devem respeitar as regras. Cada uma das categorias sum, pair e straight deve ser usada uma vez, em qualquer ordem. Palavras rejeitadas e categorias repetidas preservam a rolagem atual.
Comece uma nova partida:
cargo run --quiet -- play
Leia os primeiros dados, escolha uma categoria ainda não utilizada e pressione Enter. Repita na segunda e na terceira rolagem. Você pode digitar pairs uma vez de propósito para observar uma rejeição e então corrigir para uma categoria disponível. Observe que os dados e o contador rolls não mudam durante a correção. Após três escolhas aceitas, examine os três registros e o total recalculado. Os valores aleatórios podem diferir da sequência fixa, e a partida concluída substitui match.tsv.
Leia a nova ficha novamente:
cargo run --quiet -- replay match.tsv
Os registros devem corresponder à sua partida mais recente, incluindo zeros legítimos. Para praticar um erro comum de caminho, o próximo comando solicita intencionalmente um arquivo inexistente. Espere a mensagem Error: missing-card.tsv: ... e status 1; ele não deve produzir uma ficha substituta:
cargo run --quiet -- replay missing-card.tsv
Corrija usando o nome real do arquivo:
cargo run --quiet -- replay match.tsv
Sua ficha continua legível e não é alterada por nenhum dos dois recálculos. Preserve src/main.rs, Cargo.toml e a API pública. A verificação executa sua implementação original em uma cópia isolada e lê o verdadeiro match.tsv; ela não cria uma ficha por você.
Resumo
Você modelou uma partida e seus registros, retornou sucesso ou rejeição explícitos e alterou o estado somente após a validação. Escolhas inválidas preservaram a rolagem, zeros legítimos fizeram a partida avançar e o recálculo verificou uma ficha completa. Você jogou com novos dados e reutilizou o arquivo realmente salvo sem depender de uma VM anterior.



