Rust-Werkstatt
Werkstatt · Wattläufer · Etappe 5 von 6

Fähnchen, Sieg und Niederlage

Unser Spiel hat noch eine große Lücke: Man kann nur verlieren. Heute bekommt der Wattläufer rote Warnfähnchen, die man per Rechtsklick dort aufstellt, wo man einen Krebs vermutet. Dabei malen wir unsere erste eigene Grafik, finden gemeinsam einen echten Fehler und reparieren ihn. Und am Ende kann man endlich gewinnen.

4 Schritte · ca. 40 Minuten · baut auf Etappe 4 auf

Das Watt mit aufgedeckten Flächen und mehreren roten Warnfähnchen auf verdeckten Feldern
So sieht der Wattläufer am Ende dieser Etappe aus.
Vorab

Bevor wir loslegen

Wir machen da weiter, wo Etappe 4 aufgehört hat. Den kompletten Stand findest du am Ende von Etappe 4.

Ein Fähnchen ist ein dritter Zustand für ein Feld: nicht verdeckt, nicht aufgedeckt, sondern markiert. Wir erweitern also unser enum Zustand. Das Schöne an Rust: Sobald ein enum eine neue Möglichkeit bekommt, zeigt dir der Compiler jede Stelle, an der du sie noch berücksichtigen musst. Du kannst sie also gar nicht vergessen.

Schritt 1

Ein dritter Zustand

Zuerst bekommt das Feld seinen neuen Zustand:

src/main.rsdas enum Zustand
enum Zustand {
    Verdeckt,    Markiert,    Aufgedeckt,
}

Dann eine neue Briefsorte. Ein Rechtsklick auf Feld 12 wird zum Brief Markieren(12):

src/main.rsdas enum Message
enum Message {
    Aufdecken(usize),    Markieren(usize),}

Jetzt würde Rust sich beschweren, denn update kennt den neuen Brief noch nicht. Genau das meinte ich vorhin: match muss alle Möglichkeiten abdecken. Also bekommt update einen zweiten Zweig:

src/main.rsin der Funktion update, hinter dem Zweig für Aufdecken
                    self.aufdecken(i);
                }
            }            Message::Markieren(i) => {
                if self.spiel != Spiel::Laeuft {
                    return;
                }
                let feld = &mut self.felder[i];
                feld.zustand = match feld.zustand {
                    Zustand::Verdeckt => Zustand::Markiert,
                    Zustand::Markiert => Zustand::Verdeckt,
                    Zustand::Aufgedeckt => Zustand::Aufgedeckt,
                };
            }        }
    }

Ein Rechtsklick wirkt wie ein Lichtschalter: Verdeckt wird zu markiert, markiert wird wieder zu verdeckt. Auf aufgedeckte Felder kann man kein Fähnchen stellen, da bleibt alles, wie es ist. Neu ist let feld = &mut self.felder[i];: Wir leihen uns das Feld zum Verändern aus und geben ihm einen kurzen Namen, damit wir nicht dreimal self.felder[i] schreiben müssen.

Bleiben zwei kleine Änderungen in feld_ansicht. Ein markiertes Feld ist ja noch nicht aufgedeckt, es soll also weiter wie trockener Sand aussehen. Deshalb fragen wir jetzt „nicht aufgedeckt“ statt „verdeckt“. Und der Mausbereich lauscht zusätzlich auf die rechte Maustaste:

src/main.rsin der Funktion feld_ansicht
        let feld = self.felder[i];        let verdeckt = feld.zustand == Zustand::Verdeckt;        let verdeckt = feld.zustand != Zustand::Aufgedeckt;        let grund = if verdeckt {    ⋮        mouse_area(kachel)
            .on_press(Message::Aufdecken(i))            .on_right_press(Message::Markieren(i))            .interaction(mouse::Interaction::Pointer)
Warum != statt ==? Vorher gab es nur zwei Zustände, da war „verdeckt“ dasselbe wie „nicht aufgedeckt“. Mit drei Zuständen ist das nicht mehr so. Für das Aussehen zählt: Liegt noch Sand drauf? Und das ist bei verdeckten und markierten Feldern der Fall.
Probelauf Speichern, dann im Terminal:
cargo run
Zu sehen ist noch nichts, denn wir zeichnen noch kein Fähnchen. Aber probier mal: Rechtsklick auf ein Feld, dann Linksklick auf dasselbe Feld. Es passiert nichts, das Feld bleibt zu. Das unsichtbare Fähnchen steht also schon. Zumindest meistens, dazu gleich mehr.
Schritt 2

Ein Fähnchen malen

Jetzt machen wir das Fähnchen sichtbar. Wir malen es als SVG. Das ist ein Bildformat, das nicht aus Pixeln besteht, sondern aus einer Beschreibung wie „ein Strich hier, ein Dreieck dort“. Dadurch bleibt es in jeder Größe gestochen scharf. iced kann SVGs anzeigen, aber diese Fähigkeit ist ein Extra, das wir erst einschalten müssen. Solche Extras heißen bei Crates Features:

cargo add iced@0.14 --features svg

Danach sieht die iced-Zeile in der Cargo.toml so aus:

[package]
name = "wattlaeufer"
version = "0.1.0"
edition = "2024"

[dependencies]
iced = { version = "0.14", features = ["svg"] }
rand = "0.10"

Und in unserer Datei holen wir das Widget svg dazu:

src/main.rsganz oben, erste Zeile
use iced::widget::{column, container, mouse_area, row, text};use iced::widget::{column, container, mouse_area, row, svg, text};

Jetzt das Fähnchen selbst. Die Grafik schreiben wir direkt in unser Programm, als Konstante hinter KREBSE:

src/main.rshinter der Konstanten KREBSE
const KREBSE: usize = 10;// Ein kleines Warnfähnchen als SVG-Grafik
const FAHNE: &[u8] = br##"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
  <rect x="6" y="3" width="2" height="18" fill="#3b2a1a"/>
  <path d="M8 4 L19 8 L8 12 Z" fill="#d94a2b"/>
  <ellipse cx="7" cy="21" rx="5" ry="1.5" fill="#8a6a3a"/>
</svg>"##;

Das sieht wilder aus, als es ist. Die vier Zeilen in der Mitte sind die Zeichnung: ein dunkler Fahnenstab (rect, ein Rechteck), ein rotes Dreieck als Fahne (path, eine Linie von Punkt zu Punkt) und eine kleine Sandmulde darunter (ellipse). Rund herum steht br##"…"##:

  • Das b heißt: Das Programm bekommt keinen Text, sondern die rohen Bytes. Genau so will iced Bilddaten haben, deshalb ist der Typ &[u8], eine Reihe von Bytes.
  • Das r und die Rauten ## machen daraus einen Rohtext: Alles zwischen "## und ##" wird genau so übernommen, auch die vielen Anführungszeichen und das # in den Farbangaben. Ohne diesen Trick würde Rust beim ersten " denken, der Text sei zu Ende.

Zum Schluss zeigen wir das Fähnchen auf markierten Feldern an:

src/main.rsin der Funktion feld_ansicht
        let inhalt = if !verdeckt && !feld.krebs && feld.nachbarn > 0 {
            text(feld.nachbarn).size(20).color(zahlfarbe(feld.nachbarn))
        } else {
            text("")
        };        let inhalt: Element<'_, Message> = if feld.zustand == Zustand::Markiert {
            svg(svg::Handle::from_memory(FAHNE)).width(26).height(26).into()
        } else if !verdeckt && !feld.krebs && feld.nachbarn > 0 {
            text(feld.nachbarn).size(20).color(zahlfarbe(feld.nachbarn)).into()
        } else {
            text("").into()
        };

Hier steckt ein kleines Rust-Rätsel. Bisher lieferten beide Zweige des if einen Text. Jetzt liefert einer ein Bild und die anderen einen Text, und das sind verschiedene Typen. Rust verlangt aber, dass alle Zweige dasselbe liefern. Die Lösung: Wir verpacken jeden Zweig mit .into() in ein Element, also in die allgemeine Kiste für „irgendwas Anzeigbares“. Damit Rust weiß, in welche Kiste, steht der Typ ausdrücklich hinter dem Namen: let inhalt: Element<'_, Message>.

Worauf du achten musst Wenn du die Fehlermeldung `if` and `else` have incompatible types bekommst, fehlt irgendwo ein .into() oder die Typangabe hinter inhalt. Und wenn Rust meldet, dass es svg nicht findet, ist das Feature nicht eingeschaltet: Schau in die Cargo.toml. Der erste Start dauert wieder etwas, weil iced mit dem neuen Feature neu übersetzt wird.
Probelauf Speichern, dann im Terminal:
cargo run
Rechtsklick auf ein Feld: Ein rotes Fähnchen steckt im Sand. Nochmal Rechtsklick, und es ist wieder weg.
Schritt 3

Ein Fehler im Watt

Erinnerst du dich an das „meistens“ aus Schritt 1? Probier Folgendes: Stell ein Fähnchen auf ein Feld und klick es dann mit der linken Maustaste an. Meistens passiert nichts. Aber wenn unter dem Fähnchen zufällig ein Krebs steckt, ist das Spiel verloren! Das ist ein echter Fehler, ein Bug. Ein Fähnchen soll ja gerade davor schützen.

Woran liegt es? Schauen wir in update. Dort prüfen wir, ob unter dem Feld ein Krebs ist, bevor wir irgendetwas anderes fragen. Ob das Feld markiert ist, spielt überhaupt keine Rolle. Nur bei sicheren Feldern landen wir in aufdecken, und das prüft den Zustand, deshalb passiert dort nichts. Die Reparatur ist eine einzige Prüfung, an der richtigen Stelle:

src/main.rsin der Funktion update, Zweig Aufdecken
                if self.spiel != Spiel::Laeuft {
                    return; // Spiel vorbei: Klicks werden ignoriert
                }                if self.felder[i].zustand != Zustand::Verdeckt {
                    return; // Fähnchen oder schon offen: nicht aufdecken
                }                if self.felder[i].krebs {

Jetzt gilt: Nur ein verdecktes Feld lässt sich aufdecken. Markierte Felder sind geschützt, und ein Klick auf ein schon aufgedecktes Feld tut auch nichts, was ja sowieso richtig ist.

So findet man Bugs Dieser Fehler ist typisch: Der Code tut genau, was dasteht, aber es steht nicht das da, was wir meinen. Solche Fehler findet kein Compiler. Man findet sie, indem man ungewöhnliche Dinge ausprobiert und sich fragt: „Was passiert eigentlich, wenn …?“ Das nennt man Testen, und gute Spieler sind oft die besten Tester.
Probelauf Speichern, dann im Terminal:
cargo run
Stell Fähnchen auf ein paar Felder und klick sie mit links an: Nichts passiert, egal was darunter steckt. Die Fähnchen schützen jetzt zuverlässig.
Schritt 4

Endlich gewinnen

Wann hat man gewonnen? Nicht, wenn man alle Krebse markiert hat, denn Fähnchen kann man auch falsch setzen. Gewonnen hat, wer jedes Feld ohne Krebs aufgedeckt hat. Zuerst bekommt das Spiel einen dritten Ausgang:

src/main.rsdas enum Spiel
enum Spiel {
    Laeuft,    Gewonnen,    Verloren,
}

Nach jedem erfolgreichen Aufdecken fragen wir, ob damit alles geschafft ist:

src/main.rsin der Funktion update, Zweig Aufdecken
                } else {                    self.aufdecken(i);                    self.aufdecken(i);
                    if self.alles_sicher_aufgedeckt() {
                        self.spiel = Spiel::Gewonnen;
                    }                }

Die Frage selbst beantwortet eine neue Funktion hinter alle_krebse_zeigen:

src/main.rsin impl Watt, hinter alle_krebse_zeigen
            if feld.krebs {
                feld.zustand = Zustand::Aufgedeckt;
            }
        }
    }    // Gewonnen hat, wer jedes Feld ohne Krebs aufgedeckt hat.
    fn alles_sicher_aufgedeckt(&self) -> bool {
        self.felder
            .iter()
            .all(|feld| feld.krebs || feld.zustand == Zustand::Aufgedeckt)
    }

Das ist eine neue, sehr rustige Art, mit Listen zu arbeiten. Statt einer for-Schleife sagen wir: .iter() „geh die Felder durch“ und .all(…) „stimmt das hier für alle?“. In den Klammern steht eine Closure, die für jedes Feld prüft: Ist es ein Krebs oder (||) schon aufgedeckt? Nur wenn das für jedes einzelne Feld stimmt, liefert all true. Man kann es fast laut vorlesen, und genau das ist der Vorteil.

Zum Schluss passen wir die Statuszeile an. Sie bekommt den Siegestext, und der Krebszähler zählt jetzt rückwärts, sobald Fähnchen stehen:

src/main.rsin der Funktion view
        let status = match self.spiel {
            Spiel::Laeuft => format!("Krebse im Watt: {}", KREBSE),        let fahnen = self
            .felder
            .iter()
            .filter(|feld| feld.zustand == Zustand::Markiert)
            .count();

        let status = match self.spiel {
            Spiel::Laeuft => format!("Krebse im Watt: {}", KREBSE as i32 - fahnen as i32),
            Spiel::Gewonnen => String::from("Geschafft! Trockene Füße."),            Spiel::Verloren => String::from("Autsch! Gezwickt."),
        };

Wieder dieselbe Idee: .iter() geht die Felder durch, .filter(…) behält nur die markierten, und .count() zählt, wie viele übrig sind. Beim Abziehen wandeln wir beide Zahlen in i32 um. Wer mehr Fähnchen setzt, als es Krebse gibt, sieht so eine negative Zahl, und das ist sogar ein hilfreicher Hinweis, dass irgendwo eines zu viel steht.

Worauf du achten musst Sobald Spiel die Variante Gewonnen hat, verlangt Rust auch in der Statuszeile einen Zweig dafür. Fehlt er, meldet der Compiler non-exhaustive patterns: `Spiel::Gewonnen` not covered. Das ist genau die Hilfe, von der am Anfang dieser Etappe die Rede war.
Probelauf Speichern, dann im Terminal:
cargo run
Spiel eine Runde, diesmal richtig: Setz Fähnchen, wo du Krebse vermutest, und deck alle anderen Felder auf. Der Zähler läuft mit jedem Fähnchen herunter. Und wenn das letzte sichere Feld offen ist, steht da: Geschafft! Trockene Füße.
Ein laufendes Spiel mit Fähnchen
Stand nach Schritt 4, mitten im Spiel
Zwischenstand

Der komplette Code nach Etappe 5

Hier ist der vollständige Stand nach Etappe 5. Nimm ihn zum Vergleichen, wenn irgendwo ein Fehler steckt, den du nicht findest, oder als Einstiegspunkt, wenn du erst bei der nächsten Etappe dazustößt.

Cargo.toml und src/main.rs anzeigen

Cargo.toml

[package]
name = "wattlaeufer"
version = "0.1.0"
edition = "2024"

[dependencies]
iced = { version = "0.14", features = ["svg"] }
rand = "0.10"

src/main.rs

use iced::widget::{column, container, mouse_area, row, svg, text};
use iced::{mouse, Background, Border, Color, Element, Fill, Font, Theme};

// Die Farben des Watts
const MEER: Color = Color::from_rgb8(0x1f, 0x4e, 0x6b);
const SCHAUM: Color = Color::from_rgb8(0xf4, 0xf1, 0xe8);
const SAND: Color = Color::from_rgb8(0xe8, 0xc9, 0x8f);
const SAND_KANTE: Color = Color::from_rgb8(0xc9, 0xa4, 0x62);
const NASS: Color = Color::from_rgb8(0xb9, 0xa8, 0x86);
const ROT: Color = Color::from_rgb8(0xd9, 0x4a, 0x2b);

// So groß ist ein Feld, in Pixeln
const FELD: f32 = 44.0;

// So groß ist das Watt
const BREITE: usize = 9;
const HOEHE: usize = 9;

// So viele Krebse verstecken sich im Watt
const KREBSE: usize = 10;

// Ein kleines Warnfähnchen als SVG-Grafik
const FAHNE: &[u8] = br##"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
  <rect x="6" y="3" width="2" height="18" fill="#3b2a1a"/>
  <path d="M8 4 L19 8 L8 12 Z" fill="#d94a2b"/>
  <ellipse cx="7" cy="21" rx="5" ry="1.5" fill="#8a6a3a"/>
</svg>"##;

fn main() -> iced::Result {
    iced::application(Watt::neu, Watt::update, Watt::view)
        .title("Wattläufer")
        .default_font(Font::MONOSPACE)
        .window_size((460.0, 560.0))
        .run()
}

// Ein Feld ist entweder noch verdeckt oder schon aufgedeckt.
#[derive(Clone, Copy, PartialEq)]
enum Zustand {
    Verdeckt,
    Markiert,
    Aufgedeckt,
}

// Alles, was wir über ein einzelnes Feld wissen.
#[derive(Clone, Copy)]
struct Feld {
    krebs: bool,
    nachbarn: u8,
    zustand: Zustand,
}

// Läuft das Spiel noch, oder ist es vorbei?
#[derive(PartialEq)]
enum Spiel {
    Laeuft,
    Gewonnen,
    Verloren,
}

// Das ganze Watt: eine Liste aller Felder.
struct Watt {
    felder: Vec<Feld>,
    spiel: Spiel,
}

#[derive(Debug, Clone)]
enum Message {
    Aufdecken(usize),
    Markieren(usize),
}

impl Watt {
    // Legt ein neues Watt an, in dem alle Felder verdeckt sind.
    fn neu() -> Self {
        let leer = Feld {
            krebs: false,
            nachbarn: 0,
            zustand: Zustand::Verdeckt,
        };
        let mut felder = vec![leer; BREITE * HOEHE];

        // Zehn verschiedene Feldnummern auslosen und dort Krebse verstecken
        let mut rng = rand::rng();
        for i in rand::seq::index::sample(&mut rng, BREITE * HOEHE, KREBSE) {
            felder[i].krebs = true;
        }

        // Für jedes Feld zählen, wie viele Krebse in den Nachbarfeldern stecken
        for i in 0..felder.len() {
            let mut anzahl = 0;
            for n in nachbarn(i) {
                if felder[n].krebs {
                    anzahl += 1;
                }
            }
            felder[i].nachbarn = anzahl;
        }

        Watt { felder, spiel: Spiel::Laeuft }
    }

    fn update(&mut self, message: Message) {
        match message {
            Message::Aufdecken(i) => {
                if self.spiel != Spiel::Laeuft {
                    return; // Spiel vorbei: Klicks werden ignoriert
                }
                if self.felder[i].zustand != Zustand::Verdeckt {
                    return; // Fähnchen oder schon offen: nicht aufdecken
                }
                if self.felder[i].krebs {
                    self.spiel = Spiel::Verloren;
                    self.alle_krebse_zeigen();
                } else {
                    self.aufdecken(i);
                    if self.alles_sicher_aufgedeckt() {
                        self.spiel = Spiel::Gewonnen;
                    }
                }
            }
            Message::Markieren(i) => {
                if self.spiel != Spiel::Laeuft {
                    return;
                }
                let feld = &mut self.felder[i];
                feld.zustand = match feld.zustand {
                    Zustand::Verdeckt => Zustand::Markiert,
                    Zustand::Markiert => Zustand::Verdeckt,
                    Zustand::Aufgedeckt => Zustand::Aufgedeckt,
                };
            }
        }
    }

    // Deckt Feld i auf. Hat es keine Krebs-Nachbarn, deckt es auch alle Nachbarn auf.
    fn aufdecken(&mut self, i: usize) {
        if self.felder[i].zustand != Zustand::Verdeckt {
            return; // schon aufgedeckt: nichts zu tun
        }
        self.felder[i].zustand = Zustand::Aufgedeckt;

        if self.felder[i].nachbarn == 0 && !self.felder[i].krebs {
            for n in nachbarn(i) {
                self.aufdecken(n);
            }
        }
    }

    // Deckt alle Krebse auf, damit man sieht, wo sie steckten.
    fn alle_krebse_zeigen(&mut self) {
        for feld in &mut self.felder {
            if feld.krebs {
                feld.zustand = Zustand::Aufgedeckt;
            }
        }
    }

    // Gewonnen hat, wer jedes Feld ohne Krebs aufgedeckt hat.
    fn alles_sicher_aufgedeckt(&self) -> bool {
        self.felder
            .iter()
            .all(|feld| feld.krebs || feld.zustand == Zustand::Aufgedeckt)
    }

    fn view(&self) -> Element<'_, Message> {
        let titel = text("WATTLÄUFER").size(22).color(SCHAUM);

        let fahnen = self
            .felder
            .iter()
            .filter(|feld| feld.zustand == Zustand::Markiert)
            .count();

        let status = match self.spiel {
            Spiel::Laeuft => format!("Krebse im Watt: {}", KREBSE as i32 - fahnen as i32),
            Spiel::Gewonnen => String::from("Geschafft! Trockene Füße."),
            Spiel::Verloren => String::from("Autsch! Gezwickt."),
        };

        let mut watt = column![].spacing(3);
        for y in 0..HOEHE {
            let mut reihe = row![].spacing(3);
            for x in 0..BREITE {
                let i = y * BREITE + x;
                reihe = reihe.push(self.feld_ansicht(i));
            }
            watt = watt.push(reihe);
        }

        let inhalt = column![titel, text(status).color(SCHAUM), watt].spacing(14);

        container(inhalt)
            .padding(20)
            .width(Fill)
            .height(Fill)
            .style(|_theme: &Theme| container::Style {
                background: Some(Background::Color(MEER)),
                ..container::Style::default()
            })
            .into()
    }

    // Zeichnet das Feld Nummer i, je nachdem, in welchem Zustand es ist.
    fn feld_ansicht(&self, i: usize) -> Element<'_, Message> {
        let feld = self.felder[i];
        let verdeckt = feld.zustand != Zustand::Aufgedeckt;
        let grund = if verdeckt {
            SAND
        } else if feld.krebs {
            ROT
        } else {
            NASS
        };

        let inhalt: Element<'_, Message> = if feld.zustand == Zustand::Markiert {
            svg(svg::Handle::from_memory(FAHNE)).width(26).height(26).into()
        } else if !verdeckt && !feld.krebs && feld.nachbarn > 0 {
            text(feld.nachbarn).size(20).color(zahlfarbe(feld.nachbarn)).into()
        } else {
            text("").into()
        };

        let kachel = container(inhalt)
            .center(FELD)
            .style(move |_theme: &Theme| container::Style {
                background: Some(Background::Color(grund)),
                border: Border {
                    color: SAND_KANTE,
                    width: if verdeckt { 2.0 } else { 0.0 },
                    radius: 6.0.into(),
                },
                ..container::Style::default()
            });

        mouse_area(kachel)
            .on_press(Message::Aufdecken(i))
            .on_right_press(Message::Markieren(i))
            .interaction(mouse::Interaction::Pointer)
            .into()
    }
}

// Liefert die Nummern aller Nachbarfelder von Feld i (höchstens acht).
fn nachbarn(i: usize) -> Vec<usize> {
    let x = (i % BREITE) as i32;
    let y = (i / BREITE) as i32;
    let mut liste = Vec::new();

    for dy in -1..=1 {
        for dx in -1..=1 {
            if dx == 0 && dy == 0 {
                continue; // das Feld selbst ist nicht sein eigener Nachbar
            }
            let nx = x + dx;
            let ny = y + dy;
            if nx >= 0 && nx < BREITE as i32 && ny >= 0 && ny < HOEHE as i32 {
                liste.push(ny as usize * BREITE + nx as usize);
            }
        }
    }
    liste
}

// Jede Zahl bekommt ihre eigene Farbe, damit man sie schneller erkennt.
fn zahlfarbe(anzahl: u8) -> Color {
    match anzahl {
        1 => MEER,
        2 => Color::from_rgb8(0x2e, 0x7d, 0x4f),
        3 => Color::from_rgb8(0xc2, 0x3b, 0x22),
        _ => Color::from_rgb8(0x5a, 0x2a, 0x6e),
    }
}
ZusammenfassungEin neuer Wert in einem enum zeigt dir über den Compiler jede Stelle, an der er noch fehlt. mouse_area kann auf beide Maustasten hören. Crates haben Features, die man mit --features einschaltet, zum Beispiel svg bei iced. Rohtexte wie br##"…"## übernehmen alles wörtlich. Verschiedene Widgets verpackt .into() in dasselbe Element. Und mit .iter(), .all(), .filter() und .count() stellt man einer Liste Fragen, ohne eine Schleife zu schreiben.
Übungen

Zum Ausprobieren

Die Übungen sind freiwillig, aber sie helfen enorm. Mach nach jeder Übung einen Probelauf, und stell danach den alten Zustand wieder her, damit die nächste Etappe bei dir genauso weitergeht wie bei allen anderen.

  1. Ehrlicher Zähler: Ändere die Statuszeile so, dass sie nie unter 0 fällt. Tipp: Es gibt die Methode .max(0).
  2. Grüne Fahne: Ändere in FAHNE die Farbe #d94a2b auf #2e7d4f. Was ändert sich, und warum musstest du dafür keinen Rust-Code anfassen?
  3. Siegerehrung: Beim Gewinnen bleiben die Krebse unter dem Sand. Sorg dafür, dass nach einem Sieg auf allen Krebsfeldern automatisch ein Fähnchen steht.
Lösungen anzeigen

1. format!("Krebse im Watt: {}", (KREBSE as i32 - fahnen as i32).max(0)). max(0) liefert die größere von zwei Zahlen, also nie weniger als 0.

2. Das Fähnchen wird grün. Die Farbe steht in der SVG-Zeichnung selbst, und die ist für Rust nur eine Reihe von Bytes, die an iced weitergereicht wird. Was in der Zeichnung steht, interessiert den Compiler nicht.

3. Direkt nach self.spiel = Spiel::Gewonnen; eine Schleife: for feld in &mut self.felder { if feld.krebs { feld.zustand = Zustand::Markiert; } }. Das ist fast dieselbe Schleife wie in alle_krebse_zeigen, nur mit einem anderen Zustand.

Etappe 5 geschafft 🚩

Der Wattläufer ist jetzt ein vollständiges Spiel: Man kann Fähnchen setzen, verlieren und gewinnen, und nebenbei hast du einen echten Bug gefunden und repariert. In der letzten Etappe polieren wir: ein Knopf für ein neues Spiel, ein sicherer erster Schritt, und Ferris zieht ins Watt.

Hat dir das Mitbauen Spaß gemacht? Ein Kaffee hilft, dass die nächste Werkstatt ein Stück schneller fertig wird.

☕ Kaffee spendieren