Regeln mit typisierten Kategorien ausdrücken

RustBeginner
Jetzt üben

Einführung

Dieses Spiel mit fünf Würfeln bietet drei Möglichkeiten: sum, pair und straight. Derselbe Wurf kann je nach Auswahl unterschiedliche Punkte bringen. Bei [2, 2, 4, 4, 6] beträgt die Summe 18, während das höchste Paar 8 Punkte erzielt. Du setzt diese kurzen Regeln in typisierten Rust-Code um und probierst bekannte sowie zufällige Würfe aus.

Du solltest Rust-Funktionen, Arrays, enum, match und Option sowie grundlegende Terminalpfade kennen. Cargo und das Eingabeprogramm sind vorbereitet; du implementierst die Wertungsentscheidungen. Dieses Lab ist unabhängig von anderen VMs. Plane etwa 40 Minuten einschließlich Lesen und üblicher Fehler ein; das ist eine Schätzung des Autors.

Eine Auswahl modellieren und die Summe berechnen

In diesem Schritt liest du die vorbereiteten Typen, sagst eine Wertung voraus und schreibst eine kurze funktionierende Summenberechnung in die echte Spielbibliothek. Beginne im Tab Terminal. Die Vorbereitung hat unter /home/labex/project/dice-game ein Cargo-Projekt angelegt. src/lib.rs ist die Regelbibliothek; src/main.rs stellt Eingaben und Zufallswürfel bereit. Lass src/main.rs und Cargo.toml unverändert.

Die Werkzeugkette liegt unter /home/labex/.cargo/bin. export ergänzt dieses Verzeichnis in PATH, der Liste, in der die Shell nach Befehlen sucht. Die Einstellung gilt für diese Terminal-Sitzung; wiederhole sie in einer neuen Shell. cd wählt das Projekt und pwd bestätigt deinen Ort.

export PATH=/home/labex/.cargo/bin:$PATH
cd /home/labex/project/dice-game
pwd

Erwartet wird /home/labex/project/dice-game. Korrigiere einen Pfadtippfehler mit dem gezeigten cd; setze die Umgebung nicht zurück. Sage vor dem Bearbeiten die Summe von [2, 2, 4, 4, 6] voraus. Sie ist 18. Zuerst bringst du diesen gewöhnlichen Wertungsbefehl zum Funktionieren.

Lies die vorhandene Bibliothek mit cat, das eine Datei ausgibt, ohne sie zu ändern:

cat src/lib.rs

Dice ist ein Alias für [u8; 5], genau fünf vorzeichenlose Ein-Byte-Werte. Das Eingabeprogramm lässt nur Augenzahlen von 1 bis 6 zu. Category benennt drei Möglichkeiten. Category::parse liefert bei einem erkannten Wort Some(category), bei einem unbekannten None. Sage voraus, was es für sum und sums zurückgibt, und suche dann die entsprechenden match-Zweige. Eingabeablehnung gehört zum Eingabeprogramm; eine erkannte Kategorie darf rechtmäßig null Punkte erzielen.

Typen und Hilfsmethoden sind vorbereitet. pub macht Namen für das Eingabeprogramm zugänglich. impl Category gruppiert Methoden dieses Typs; Self bedeutet darin Category. derive stellt Kopierbarkeit, Debug-Anzeige und Gleichheit für dieses Enum bereit. name gibt ein festes Zeichenkettenliteral für die Anzeige zurück. Erhalte diese Definitionen. Deine Änderung beschränkt sich auf score, dessen aktueller Körper für jede Kategorie null liefert.

Bewahre vor dieser Änderung einen Wiederherstellungspunkt auf. mktemp -d erstellt ein neues Verzeichnis und ersetzt die sechs X durch ein eindeutiges Suffix, statt eine ältere Sicherung wiederzuverwenden. $() erfasst den ausgegebenen Pfad in der Sitzungsvariablen rs02_before_sum. && führt die Kopie nur aus, wenn das Verzeichnis erfolgreich erstellt wurde. cp -p kopiert den Quelltext und erhält seine Metadaten; Anführungszeichen halten den Variablenpfad zusammen. Diese Sicherung enthält nur deine bearbeitbare Bibliothek, nicht das vorbereitete Eingabeprogramm oder die Cargo-Konfiguration. Schlägt eine der Operationen fehl, lies den Fehler und erstelle vor dem Bearbeiten eine erfolgreiche Sicherung.

rs02_before_sum=$(mktemp -d .edit-before-sum.XXXXXX) && cp -p src/lib.rs "$rs02_before_sum/lib.rs"

Öffne diese vorhandene Datei mit nano. -c zeigt die Cursorposition an, hilfreich bei Compiler-Zeilennummern:

nano -c src/lib.rs

Nanos Schreibweise ^ bedeutet die Ctrl-Taste. Drücke Ctrl+W, gib pub fn score ein und drücke Enter, um die Funktion zu finden. Bewege dich mit Pfeiltasten und ändere Zeichen mit Backspace/Delete. Lies vor dem Bearbeiten von der öffnenden { bis zur passenden } der Funktion. Lösche nicht das davorstehende Enum oder die Hilfsmethoden.

Das Startprogramm nennt unbenutzte Parameter _dice und _category. Benenne nur diese Parameterbindungen in dice und category um, damit dein Körper sie verwenden kann; erhalte Funktionsname sowie Parameter- und Rückgabetypen. Ersetze den einzelnen Ausdruck 0 durch einen Ausdruck match category. Ein Zweig hat ein Muster, => und ein Ergebnis, beispielsweise den vorläufigen Zweig Category::Pair => 0,. Gib Category::Straight dasselbe vorläufige Nullergebnis. Diese beiden Regeln implementierst du als Nächstes.

Verwende für Category::Sum einen Block, der einen veränderlichen Akkumulator beginnt, jede Augenzahl besucht und den erweiterten Wert addiert. Diese kurze Schleife zeigt die Zahlenoperation:

let mut total = 0;
for face in dice {
    total += u16::from(face);
}

u16::from(face) erweitert die Zahl vor der Addition. Selbst fünf Sechsen passen in u16. Setze nach dieser Schleife total als letzten Ausdruck des Sum-Zweigs: kein Semikolon nach diesem letzten Ausdruck, damit der Zweig den Wert zurückgibt. Schließe die Klammern von match und Funktion. Du hast eine echte Kategorie im vorhandenen Programm implementiert; die beiden anderen Möglichkeiten bleiben bewusst unvollständig.

Drücke Ctrl+O zum Schreiben, Enter zum Bestätigen von src/lib.rs und dann Ctrl+X zum Beenden von nano. Gib keine Terminalbefehle ein, solange der Editor noch offen ist. Lies deine gespeicherte Änderung vor dem Kompilieren:

cat src/lib.rs

Cargo kompiliert und startet das vorbereitete Programm. --quiet verbirgt gewöhnliche Build-Meldungen; -- trennt Cargo-Optionen von Programmargumenten. Führe dieselben fünf Augenzahlen aus, die du vorhergesagt hast:

cargo run --quiet -- score sum 2 2 4 4 6
Dice: [2, 2, 4, 4, 6]
sum: 18

Die Würfel haben sich nicht geändert; deine gewählte Regel hat die Punkte erzeugt. Prüfe vor dem Fortfahren, dass deine Schleife auch für fünf Sechsen 30 vorhersagt.

Meldet Cargo einen Syntaxfehler, lies Dateiname und Zeilennummer, öffne nano -c src/lib.rs erneut, suche nach pub fn score und korrigiere die betreffende Klammer, den Zweig oder Ausdruck. Speichere und beende und wiederhole dann den fehlgeschlagenen Befehl. Solange eine Änderung ungespeichert ist, verwirft Ctrl+X und dann N an der Speicherfrage diese ungespeicherte Änderung; bereits geschriebene Änderungen macht das nicht rückgängig.

Für einen bereits gespeicherten Fehler, den du nicht korrigieren kannst, stelle deine eigene Bibliothek vor der Änderung mit dem folgenden Befehl wieder her. Er ersetzt bewusst nur die erlaubte src/lib.rs; Eingabeprogramm und Cargo-Dateien werden nicht wiederhergestellt. Die Sicherungsvariable gehört zu dieser Terminal-Sitzung. Verwende in einer anderen Shell die folgenden bedingten Wiederherstellungsschritte, um deine tatsächliche frühere Sicherung auszuwählen und zu prüfen.

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-sum.*

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_sum

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_sum/lib.rs"

Er sollte das Startprogramm vor Sum 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_sum/lib.rs" src/lib.rs

Das stellt das Startprogramm wieder her; wiederhole daher die Sum-Implementierung und ihren Wertungsbefehl. Behalte diese Terminal-Sitzung und deine funktionierende Summe für den nächsten Schritt.

Das höchste Paar und eine Straße werten

In diesem Schritt entwirfst und implementierst du die übrigen Wertungsentscheidungen und erhältst dabei deinen funktionierenden Sum-Zweig. Arbeite im selben Terminal unter /home/labex/project/dice-game weiter. Wiederhole in einer neuen Shell die früheren Befehle export PATH und cd.

Ein Paar bringt das Doppelte der höchsten Augenzahl, die mindestens zweimal vorkommt. Drei, vier oder fünf gleiche Werte tragen weiterhin nur zwei Würfel bei. Sollen bei [2, 2, 4, 4, 6] beide Paare zählen oder nur die Vieren? Nur die Vieren: 8 Punkte. Bei fünf Sechsen ist das Ergebnis 12, nicht 30. Ohne Paar sind null Punkte gültig.

Eine nützliche Darstellung ist [0u8; 7]: sieben Zähler mit Indizes 0 bis 6; 0 bleibt unbenutzt. Für jeden Würfel wandelt usize::from(face) den Wert in einen Array-Index um; counts[usize::from(face)] += 1 erhöht den Zähler dieser Augenzahl. Eine zweite Schleife kann Augenzahlen in 1..=6 prüfen, einschließlich beider Grenzen. Ersetze bei einem Zähler von mindestens zwei eine veränderliche beste Punktzahl durch das Doppelte dieser Augenzahl; aufsteigendes Durchlaufen lässt das höchste zulässige Paar übrig. as u16 wandelt dieses kleine Zahlenergebnis in den Punktetyp um. Du darfst stattdessen einen gleichwertigen Zähl- oder Sortieralgorithmus verwenden; er muss weiterhin zwei verfügbare Paare sowie drei bis fünf gleiche Würfel korrekt behandeln.

Eine Straße bringt nur für genau die verschiedenen Augenzahlen 1–5 oder 2–6 in beliebiger Reihenfolge 15 Punkte. Sage den Unterschied zwischen [6, 5, 4, 3, 2] und [1, 2, 3, 4, 6] voraus. Der erste Wurf ist eine Straße, der zweite hat eine Lücke. Kopieren mit let mut sorted = dice und Aufrufen von sorted.sort() lässt dich das sortierte Array mit den beiden genauen Arrays vergleichen, ohne den ursprünglichen Wurf zu ändern. || bedeutet, dass einer der Vergleiche wahr sein darf. Entscheide, wann du 15 und wann null zurückgibst; doppelte Werte dürfen nicht bestehen.

Sichere vor dem Bearbeiten die bereits funktionierende Phase. Wie zuvor verhindert das eindeutige Verzeichnis das Überschreiben einer älteren Sicherung, und die Kopie erfolgt nur nach erfolgreicher Verzeichniserstellung:

rs02_before_rules=$(mktemp -d .edit-before-rules.XXXXXX) && cp -p src/lib.rs "$rs02_before_rules/lib.rs"

Öffne die vorhandene Datei, suche mit Ctrl+W nach pub fn score und ändere nur die Zweige Pair und Straight. Erhalte Sum, die öffentliche API und alle Hilfsdefinitionen. Schreibe deine Implementierung aus diesen Regeln heraus, statt das ganze Modul zu ersetzen.

nano -c src/lib.rs

Speichere mit Ctrl+O, Enter und beende mit Ctrl+X. Lies vor dem Ausführen deine gespeicherten Zweige und sage jedes Ergebnis voraus:

cat src/lib.rs
cargo run --quiet -- score pair 2 2 4 4 6
Dice: [2, 2, 4, 4, 6]
pair: 8

Vergleiche nun fünf gleiche Würfel, beide Straßenbereiche und gültige Nullwertungen:

cargo run --quiet -- score pair 6 6 6 6 6
cargo run --quiet -- score straight 6 5 4 3 2
cargo run --quiet -- score straight 1 2 3 4 5
cargo run --quiet -- score pair 1 2 3 4 6
cargo run --quiet -- score straight 1 2 3 4 6

Erwarte der Reihe nach 12, 15, 15, 0 und 0. Null bedeutet, dass die erkannte Regel keine punktbringende Kombination gefunden hat, nicht, dass die Eingabe abgewiesen wurde. Wähle ein weiteres gültiges Beispiel mit fünf Würfeln, sage seine Punkte für eine Kategorie voraus und führe score mit dieser Kategorie und allen fünf Augenzahlen aus. Erkläre eine Abweichung anhand der Regel, statt sie durch Ändern des Beispiels zu verbergen.

Lässt du die fünf Augenzahlen weg, wird die Zufallsquelle des Betriebssystems verwendet. Dies ist ein lokales Spiel, kein Sicherheits- oder Wettsystem. Führe es einmal aus; du darfst es wiederholen:

cargo run --quiet -- score pair

Deine Würfel und Punkte können variieren; ein Zufallswurf kann sich zufällig wiederholen. Wende die Regel für das höchste Paar selbstständig auf die angezeigten Würfel an. Ein unbekanntes Wort unterscheidet sich von einer gültigen Nullwertung: Dieser absichtliche Schreibfehler endet mit Status 1 und gibt Error: choose sum, pair, or straight aus, ohne einen Wurf zu werten:

cargo run --quiet -- score pairs 2 2 4 4 6

Korrigiere die Kategorie an der Befehlszeile:

cargo run --quiet -- score pair 2 2 4 4 6

Erwarte pair: 8. Ein Compilerfehler verweist stattdessen auf deinen Quelltext: Öffne nano bei score erneut, korrigiere die genannte Zeile, speichere und versuche es erneut. Stelle bei Bedarf deine eigene funktionierende Sum-Phase wieder her und implementiere danach die beiden übrigen Zweige 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-rules.*

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_rules

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_rules/lib.rs"

Er sollte die funktionierende Summe vor Pair und Straight 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_rules/lib.rs" src/lib.rs

Das stellt bewusst nur deine bearbeitbare Bibliothek wieder her. Setze die Umgebung nicht zurück und überschreibe weder src/main.rs noch Cargo.toml. Die folgende Referenz steht nach deinem Versuch zum Vergleichen oder Wiederherstellen einer bestimmten Funktion bereit; sie ist nicht der normale Bearbeitungsablauf.

Vollständige Bibliotheksreferenz zum Vergleichen und Wiederherstellen

Vergleiche den betreffenden Zweig und übertrage mit nano nur die benötigte Korrektur. Führe danach die bekannten Beispiele und Grenzfälle erneut aus. Die vollständige Bibliothek unten ist zugleich die einzige gepflegte Quelle, aus der die bereits vollständigen Wertungsregeln des nächsten Labs vorbereitet werden.

pub type Dice = [u8; 5];

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Category {
    Sum,
    Pair,
    Straight,
}

impl Category {
    pub fn parse(input: &str) -> Option<Self> {
        match input {
            "sum" => Some(Self::Sum),
            "pair" => Some(Self::Pair),
            "straight" => Some(Self::Straight),
            _ => None,
        }
    }
    pub fn name(self) -> &'static str {
        match self {
            Self::Sum => "sum",
            Self::Pair => "pair",
            Self::Straight => "straight",
        }
    }
}

pub fn score(dice: Dice, category: Category) -> u16 {
    match category {
        Category::Sum => {
            let mut total = 0;
            for face in dice {
                total += u16::from(face);
            }
            total
        }
        Category::Pair => {
            let mut counts = [0u8; 7];
            for face in dice {
                counts[usize::from(face)] += 1;
            }
            let mut best = 0;
            for face in 1..=6 {
                if counts[face] >= 2 {
                    best = (face * 2) as u16;
                }
            }
            best
        }
        Category::Straight => {
            let mut sorted = dice;
            sorted.sort();
            if sorted == [1, 2, 3, 4, 5] || sorted == [2, 3, 4, 5, 6] {
                15
            } else {
                0
            }
        }
    }
}

Zusammenfassung

Du hast mit einer typisierten Kategorie, match, einer laufenden Summe, Häufigkeitszählern und einer sortierten Kopie drei vollständige Regeln ausgedrückt. Feste Beispiele erklären die Ergebnisse, während Zufallswürfe dieselben Regeln auf neue Würfel anwenden. Fehlt eine passende Kombination, gibt es null Punkte; eine unbekannte Kategorie ist ein Eingabefehler.