Einführung
Eine Wertung ist nur eine Entscheidung. Eine vollständige Partie muss sich bereits verwendete Kategorien merken und dieselben Würfel behalten, während du eine ungültige Auswahl korrigierst. Du vervollständigst ein Spiel mit drei Runden und speicherst ein Wertungsblatt, dessen Punkte sich anhand der aufgezeichneten Würfel und Kategorien nachrechnen lassen.
Die Wertungsregeln aus dem ersten Lab sind in diesem unabhängigen Projekt bereits installiert. Du solltest diese Regeln, Rust-Funktionen, enum, match, Option, Arrays und grundlegende Terminalpfade kennen. Dieses Lab führt die vorbereiteten Structs, veränderliche Methoden, Result und Slices bei ihrer Verwendung ein. Plane etwa 55 Minuten einschließlich Lesen und üblicher Fehler ein; das ist eine Schätzung des Autors.
Nur eine zulässige Auswahl übernehmen
In diesem Schritt entwirfst und implementierst du den Zustandsübergang für eine angenommene Auswahl. Beginne im Tab Terminal. Dieses unabhängige Projekt enthält bereits vollständige Wertungsregeln. Es verwendet keine Dateien aus dem ersten Lab. Setze den Suchpfad dieser Shell und wechsle ins Projekt; wiederhole diese Befehle in einer neuen Shell:
export PATH=/home/labex/.cargo/bin:$PATH
cd /home/labex/project/dice-game
Beginne mit einer normalen funktionierenden Wertung und sage sie vor dem Ausführen voraus:
cargo run --quiet -- score pair 2 2 4 4 6
Erwarte die angezeigten Würfel und pair: 8. Das beweist, dass die vorbereitete Regelebene nutzbar ist, bevor du den Spielzustand ergänzt. Lies das Gerüst:
cat src/state.rs
Ein struct gruppiert benannte Felder, die ein Objekt beschreiben. Round zeichnet fünf Würfel, eine Kategorie und Punkte auf. Game enthält current: Option<Dice> und rounds: Vec<Round>. Some(dice) bedeutet, dass eine Auswahl aussteht; None, dass es keinen aktuellen Wurf gibt. Vec<Round> ist eine erweiterbare Sammlung; Vec::new() erstellt eine leere, push hängt ein Element an.
Lies auch den Aufrufer im vorbereiteten Eingabeprogramm:
cat src/main.rs
Suche match game.submit(input.trim()). Sein Err-Zweig gibt eine Ablehnung aus. Sein Ok-Zweig gibt Punkte aus und holt nur dann einen weiteren Wurf, wenn weniger als drei Runden aufgezeichnet sind. Du bearbeitest dieses Eingabeprogramm nicht. Deine Methode muss bei Ablehnung den Zustand unverändert lassen, damit der nächste Prompt dieselben Würfel und dieselbe Wurfanzahl zeigt.
Sage die Felder nach jeder Art von Übernahme voraus:
| Übernahme | rounds nach dem Aufruf |
current nach dem Aufruf |
|---|---|---|
| Keine ausstehenden Würfel, unbekanntes Wort oder verwendete Kategorie | Unverändert | Unverändert |
| Gültige unbenutzte Kategorie, auch mit null Punkten | Ein vollständiger neuer Datensatz angehängt | None |
Das Eingabeprogramm, nicht submit, stellt nach Erfolg den nächsten Wurf bereit. Würfle nicht in deiner Methode. impl Game gruppiert Methoden von Game. &mut self erlaubt dieser Methode, die Partie über eine veränderliche Referenz zu aktualisieren. Result<u16, &'static str> bedeutet Ok(points) oder Err(message); &'static str ist der Typ des festen Fehlermeldungstextes. Anders als Option kann Result einen Fehler erklären.
ok_or(message) wandelt Some in Ok und None in Err um. Ein folgendes ? fährt mit dem erfolgreichen Wert fort oder gibt den Fehler sofort zurück. Die Prüfung auf einen ausstehenden Wurf kann beispielsweise so lauten:
let dice = self.current.ok_or("match finished")?;
Wende dieselbe Idee auf Category::parse(input) an. Leihe vor jeder Feldänderung die bisherigen Datensätze mit einer for-Schleife über &self.rounds aus und weise eine passende Kategorie ab. Du entscheidest über die Organisation der Prüfungen; die Tabelle ist der Vertrag. Ermittle nach der Validierung points aus score(dice, category). Die erfolgreiche Übernahme hat drei Aktionen:
self.rounds.push(Round { dice, category, points });
self.current = None;
Ok(points)
Die kurzen Feldnamen verwenden die gleichnamigen lokalen Variablen. Ein Ergebnis null ist weiterhin erfolgreich. Keine dieser Änderungen gehört auf einen abgewiesenen Pfad.
Lass src/main.rs, Cargo.toml, öffentliche Typen, Funktionsnamen und Typen sowie Modulexporte unverändert. use crate::{score, Category, Dice} importiert aus der aktuellen Bibliothek; mod state und pub use in src/lib.rs verbinden die Module. Dieser Schritt bearbeitet nur submit in src/state.rs; Structs, Methode new und unvollständiges replay bleiben bestehen.
Erstelle vor dem Bearbeiten eine eindeutige Sicherung dieser erlaubten Datei. Wie zuvor gelernt ersetzt mktemp -d sechs X durch ein eindeutiges Suffix. $() speichert den ausgegebenen Pfad; && führt cp -p erst nach erfolgreicher Verzeichniserstellung aus. Der zitierte Pfad bleibt ein Argument. Falls es scheitert, lies den Fehler und erstelle vor dem Bearbeiten eine Sicherung.
rs02_before_submit=$(mktemp -d .edit-before-submit.XXXXXX) && cp -p src/state.rs "$rs02_before_submit/state.rs"
Öffne die Datei mit nano; -c zeigt die Cursorposition:
nano -c src/state.rs
Drücke Ctrl+W, suche nach pub fn submit und drücke Enter. Prüfe die öffnenden und schließenden Klammern dieser Methode. Benenne die unbenutzte Parameterbindung _input in input um, ohne ihren Typ zu ändern. Ersetze nur den unvollständigen Körper durch deine Implementierung der obigen Prüfungen und erfolgreichen Übernahme. Erhalte andere Methoden und Datendefinitionen. Bearbeite mit Pfeiltasten und Backspace/Delete. Speichere mit Ctrl+O, Enter und beende mit Ctrl+X. Nanos ^ bedeutet Ctrl. Eine versehentliche ungespeicherte Änderung verwirfst du mit Ctrl+X und dann N an der Speicherfrage; das macht kein früheres Speichern rückgängig.
Lies die gespeicherte Methode vor dem Ausführen:
cat src/state.rs
Beobachte deine Zustandsentscheidungen mit der bekannten festen Folge. --fixture wählt die veröffentlichten Würfe statt der Zufallsquelle:
cargo run --quiet -- play --fixture
Warte auf jeden Prompt, gib dann ein Wort ein und drücke Enter:
Gib zuerst pairs ein. Es ist unbekannt: Erwarte nach der Ablehnung dieselben [2, 2, 4, 4, 6] und rolls 1.
Gib als Nächstes pair ein. Das bringt 8 Punkte und führt zu [1, 2, 3, 4, 6], rolls 2.
Gib pair erneut ein. Die benutzte Kategorie wird abgewiesen; Würfel und rolls 2 bleiben unverändert.
Gib dann straight ein. Diese gültige Auswahl bringt null Punkte und führt zum dritten Wurf.
Gib zuletzt sum ein. Das bringt 20 Punkte.
Der erwartete abschließende Fehler ist Error: replay not implemented mit Status 1. Deine Übernahmen haben funktioniert, aber replay ist noch die ursprüngliche unvollständige Funktion; daher verweigert das Eingabeprogramm das Speichern eines ungeprüften Blatts. Das ist der geplante Zwischenstand, kein erfolgreich gespeichertes Spiel. Wählst du versehentlich eine andere gültige Kategorie, beende die unbenutzten Auswahlen oder drücke Ctrl+C und beginne bewusst eine neue Fixture-Partie. Innerhalb einer Partie darf eine Ablehnung nichts neu starten oder neu würfeln.
Lies bei einem Compilerfehler die gemeldete Datei/Zeile, öffne nano erneut, suche die betroffene Methode und korrigiere sie. Stelle für einen gespeicherten Fehler, den du nicht korrigieren kannst, deine eigene Datei vor submit wieder her und implementiere und teste submit erneut:
Nur beim Wiederherstellen nach einem gespeicherten Fehler: Im ursprünglichen Terminal enthält die Checkpoint-Variable noch ihren Pfad. Wiederhole in einem neuen Terminal zuerst die früheren PATH- und Projektverzeichnisbefehle. Liste die Sicherungen dieser Phase auf:
ls -d .edit-before-submit.*
ls -d listet Verzeichnisnamen statt ihrer Inhalte; * gleicht die Suffixe der eindeutigen Verzeichnisse dieser Phase ab. Dies sind tatsächlich erzeugte Namen; gib kein erfundenes Suffix ein. Falls keine Sicherung passt, bestätige Projektverzeichnis und ob du diesen Wiederherstellungspunkt erstellt hast; kopiere nicht aus einer unpassenden Phase. Um einen der ausgegebenen Pfade auszuwählen, führe aus:
read rs02_before_submit
Die Shell wartet auf eine Zeile. Füge den genauen gewählten Verzeichnisnamen ein und drücke Enter. read speichert diese Zeile für diese Sitzung in der benannten Variablen. Prüfe den Sicherungsinhalt vor dem Wiederherstellen:
cat "$rs02_before_submit/state.rs"
Er sollte das Startprogramm vor submit enthalten. Sind Pfad oder Phase falsch, wiederhole read mit dem richtigen ausgegebenen Pfad und prüfe erneut. Verwende erst dann den Wiederherstellungsbefehl. Diese Prüfung gilt auch im ursprünglichen Terminal, wenn du unsicher bist, welchen Wiederherstellungspunkt seine Variable benennt.
cp -p "$rs02_before_submit/state.rs" src/state.rs
Dieser Befehl ersetzt bewusst nur die bearbeitbare Zustandsdatei. Die Sicherungsvariable gilt nur in diesem Terminal; verwende in einer anderen Shell die bedingten Wiederherstellungsschritte. Setze setup nicht zurück und ersetze nicht das vorbereitete Eingabeprogramm, um einen Zustandsfehler zu beheben.
Ein vollständiges Wertungsblatt nachrechnen und speichern
In diesem Schritt implementierst du eine unabhängige Prüfung jeder aufgezeichneten Runde, bevor eine Gesamtsumme angenommen wird. Arbeite im selben Terminal unter /home/labex/project/dice-game weiter; wiederhole bei Bedarf die früheren Befehle export PATH und cd. Erhalte deine fertige Methode submit.
Prüfe zuerst das vorgeschlagene Fixture-Blatt von Hand: pair für [2, 2, 4, 4, 6] bringt 8, straight für [1, 2, 3, 4, 6] bringt 0 und sum für [6, 5, 4, 3, 2] bringt 20. Die nachgerechnete Summe ist 28. Hätte jemand in der ersten Zeile 10 statt 8 eingetragen, würde das Addieren gespeicherter Punkte den falschen Datensatz verbergen. Ein Blatt kann sich nicht durch seine eigene gespeicherte Summe oder Punkte beweisen.
replay erhält &[Round], einen Slice: eine ausgeliehene Sicht auf Datensätze, ohne Besitzübertragung oder Änderung. Entwirf die Prüfungen vor dem Schreiben. Fordere genau drei Datensätze. Weise in jedem Datensatz Augenzahlen außerhalb 1 bis 6 vor dem Aufruf von score ab, da eine Wertungsimplementierung eine Augenzahl als Array-Index verwenden kann. Verfolge benutzte Kategorien in einem kleinen Vec; contains prüft, ob die aktuelle Kategorie schon vorkam. Weise Duplikate ab. Berechne Punkte aus den tatsächlichen Würfeln und der Kategorie neu und vergleiche sie mit den gespeicherten Punkten. Addiere nur das neu berechnete Ergebnis zur Summe und gib diese erst zurück, wenn jeder Datensatz gültig ist.
Für eine fehlgeschlagene Bedingung kannst du ein frühes return Err("...") verwenden. In for round in rounds ist jede Runde ausgeliehen; round.dice ist ein kleines Copy-Array, das du ohne Änderung des Datensatzes untersuchen kannst. Die Bedingung !(1..=6).contains(&face) bedeutet, dass die Augenzahl außerhalb des gültigen Bereichs einschließlich seiner Grenzen liegt. Wähle aussagekräftige Ablehnungsmeldungen; deine Implementierung darf diese Entscheidungen anders organisieren, wenn der Vertrag erhalten bleibt.
Speichere einen neuen Wiederherstellungspunkt mit deinem bereits funktionierenden submit. Er darf nicht mit der früheren Startprogrammsicherung verwechselt werden:
rs02_before_replay=$(mktemp -d .edit-before-replay.XXXXXX) && cp -p src/state.rs "$rs02_before_replay/state.rs"
Öffne dieselbe Datei und suche mit Ctrl+W nach pub fn replay:
nano -c src/state.rs
Benenne die unbenutzte Parameterbindung _rounds in rounds um, erhalte ihren Typ und implementiere nur diesen Funktionskörper. Lasse submit, Structs und Modulverbindungen unverändert. Verwende deine geordneten Prüfungen und den Akkumulator; gib nicht bloß 28 zurück, vertraue nicht gespeicherten Punkten und überspringe keinen ungültigen Datensatz stillschweigend. Speichere mit Ctrl+O, Enter und beende mit Ctrl+X. Lies den gespeicherten Code:
cat src/state.rs
Starte die feste Folge erneut:
cargo run --quiet -- play --fixture
Gib pair, dann straight und dann sum ein und warte jeweils auf den Prompt. Die abschließende Ausgabe enthält:
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
Das vorbereitete Eingabeprogramm schreibt match.tsv erst, wenn ein vollständiges Blatt replay besteht. Jede Zeile hat fünf durch Leerzeichen getrennte Augenzahlen, einen Tabulator, eine Kategorie, einen weiteren Tabulator und Punkte. Das vorbereitete Eingabeprogramm verarbeitet dieses Format; du implementierst keinen Dateiparser. Eine abgeschlossene neue Partie ersetzt dieses kursbezogene Blatt; eine unvollständige lässt das vorherige unverändert.
Lies deine tatsächlich gespeicherte Datei und rechne sie nach:
cat match.tsv
cargo run --quiet -- replay match.tsv
Erwarte dieselben Datensätze und die Summe 28 ohne Saved match.tsv. Replay liest den genauen Pfad, ohne ihn zu ändern oder ersatzweise ein Beispiel zu verwenden. Um zu prüfen, dass du die frühere Kategorienfolge nicht in submit fest kodiert hast, spiele die feste Folge noch einmal:
cargo run --quiet -- play --fixture
Verwende straight, sum und dann pair. Sage 0, 16 und 0 voraus: Alle drei Auswahlen sind gültig, die nachgerechnete Summe ist 16. Diese abgeschlossene Partie ersetzt nun das frühere Blatt. Rechne die aktuelle Datei nach:
cargo run --quiet -- replay match.tsv
Ihre Datensätze müssen diese zweite Reihenfolge wiedergeben, nicht das frühere 28-Punkte-Spiel. Eine andere Summe ist zu erwarten, weil die gewählte Regel für jeden Wurf anders ist. Der nächste Schritt verwendet echte Zufallswürfel mit denselben Entscheidungen.
Öffne bei einem Compilerfehler die genannte Datei erneut und korrigiere die angegebene replay-Zeile. Stelle bei Bedarf deinen eigenen Wiederherstellungspunkt vor replay wieder her, der submit erhält, und implementiere replay erneut:
Nur beim Wiederherstellen nach einem gespeicherten Fehler: Im ursprünglichen Terminal enthält die Checkpoint-Variable noch ihren Pfad. Wiederhole in einem neuen Terminal zuerst die früheren PATH- und Projektverzeichnisbefehle. Liste die Sicherungen dieser Phase auf:
ls -d .edit-before-replay.*
ls -d listet Verzeichnisnamen statt ihrer Inhalte; * gleicht die Suffixe der eindeutigen Verzeichnisse dieser Phase ab. Dies sind tatsächlich erzeugte Namen; gib kein erfundenes Suffix ein. Falls keine Sicherung passt, bestätige Projektverzeichnis und ob du diesen Wiederherstellungspunkt erstellt hast; kopiere nicht aus einer unpassenden Phase. Um einen der ausgegebenen Pfade auszuwählen, führe aus:
read rs02_before_replay
Die Shell wartet auf eine Zeile. Füge den genauen gewählten Verzeichnisnamen ein und drücke Enter. read speichert diese Zeile für diese Sitzung in der benannten Variablen. Prüfe den Sicherungsinhalt vor dem Wiederherstellen:
cat "$rs02_before_replay/state.rs"
Er sollte das funktionierende submit vor replay enthalten. Sind Pfad oder Phase falsch, wiederhole read mit dem richtigen ausgegebenen Pfad und prüfe erneut. Verwende erst dann den Wiederherstellungsbefehl. Diese Prüfung gilt auch im ursprünglichen Terminal, wenn du unsicher bist, welchen Wiederherstellungspunkt seine Variable benennt.
cp -p "$rs02_before_replay/state.rs" src/state.rs
Quelltextwiederherstellung ändert kein vorhandenes Blatt. Eine spätere abgeschlossene Partie ersetzt es erst nach erfolgreichem replay; fehlgeschlagene Partien und das Lesen eines Blatts tun das nicht. Die vollständige Referenz unten dient nach deinem Versuch dem Vergleichen einer bestimmten Funktion, nicht dem normalen Ersetzen des Moduls beim Lernen.
Vollständige Zustandsreferenz zum Vergleichen und Wiederherstellen
Erhalte deine Felder und bisherige erfolgreiche Arbeit. Vergleiche nur die Funktion, die du reparierst, bearbeite sie in nano und führe dann die feste Folge und das Nachrechnen des tatsächlichen Blatts erneut aus.
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)
}
Eine eigene Partie spielen und wiederverwenden
In diesem Schritt nutzt du die fertigen Regeln mit neuen Würfeln und behebst einen gewöhnlichen Dateipfadfehler. Arbeite im selben Terminal unter /home/labex/project/dice-game weiter.
Ohne --fixture liest das vorbereitete Programm Zufallsbytes aus /dev/urandom. Pro Wurf erzeugt es fünf Augenzahlen von 1 bis 6. Dies ist ein lokales Übungsspiel. Du brauchst weder eine bestimmte Gesamtsumme noch eine Gewinnstrategie; die Punkte müssen den Regeln entsprechen. sum, pair und straight müssen jeweils einmal in beliebiger Reihenfolge benutzt werden. Abgelehnte Wörter und wiederholte Kategorien behalten den aktuellen Wurf bei.
Beginne eine neue Partie:
cargo run --quiet -- play
Lies die ersten Würfel, wähle eine unbenutzte Kategorie und drücke Enter. Wiederhole dies für den zweiten und dritten Wurf. Du kannst einmal absichtlich pairs eingeben, um eine Ablehnung zu sehen, und danach zu einer verfügbaren Kategorie korrigieren. Beobachte, dass Würfel und rolls-Zähler während der Korrektur unverändert bleiben. Nach drei angenommenen Auswahlen prüfst du die drei Datensätze und die nachgerechnete Summe. Die Zufallswerte können von der festen Folge abweichen; die abgeschlossene Partie ersetzt match.tsv.
Lies das neue Blatt wieder ein:
cargo run --quiet -- replay match.tsv
Die Datensätze müssen deiner letzten Partie entsprechen, einschließlich zulässiger Nullwertungen. Um einen gewöhnlichen Pfadfehler zu üben, fordert der nächste Befehl absichtlich eine nicht vorhandene Datei an. Erwarte Error: missing-card.tsv: ... und Status 1; es darf kein Ersatzblatt erzeugt werden:
cargo run --quiet -- replay missing-card.tsv
Behebe den Fehler mit dem richtigen Dateinamen:
cargo run --quiet -- replay match.tsv
Dein Wertungsblatt bleibt lesbar und wird durch keinen der beiden Lesevorgänge verändert. Erhalte src/main.rs, Cargo.toml und die öffentliche API. Die Prüfung führt deine ursprüngliche Implementierung in einer isolierten Kopie aus und liest deine echte match.tsv; sie erstellt kein Wertungsblatt für dich.
Zusammenfassung
Du hast eine Partie und ihre Datensätze modelliert, Erfolg oder Ablehnung ausdrücklich zurückgegeben und den Zustand erst nach der Prüfung geändert. Ungültige Auswahlen behielten den Wurf bei, zulässige Nullwertungen führten zur nächsten Runde, und das Nachrechnen prüfte ein vollständiges Blatt. Du hast mit neuen Würfeln gespielt und die tatsächlich gespeicherte Datei ohne Abhängigkeit von einer früheren VM wiederverwendet.



