Feinschliff mit Ferris
Der Wattläufer ist schon ein vollständiges Spiel. Heute machen wir daraus eines, das man gern spielt: Ein Knopf startet ein neues Spiel, ohne das Programm neu zu starten. Der erste Schritt ins Watt ist immer sicher, so wie bei den großen Vorbildern. Und statt roter Felder zeigt das Watt beim Verlieren, wer da eigentlich zwickt: Ferris, das Rust-Maskottchen.
Bevor wir loslegen
Wir machen da weiter, wo Etappe 5 aufgehört hat. Den kompletten Stand findest du am Ende von Etappe 5.
Diese Etappe besteht aus kleinen Verbesserungen, die jede für sich wenig Code brauchen, das Spiel aber spürbar schöner machen. Genau so sieht die Arbeit an echten Programmen oft aus: Wenn das Grundgerüst steht, beginnt die Politur. Und manchmal steckt in einer kleinen Verbesserung eine größere Umbauarbeit, so wie in Schritt 2.
Ein Knopf für ein neues Spiel
Bisher musst du nach jedem Spiel das Fenster schließen und neu starten. Ein Knopf oben
rechts soll das erledigen. iced hat dafür das Widget button. Außerdem brauchen
wir Center, um Überschrift und Knopf auf eine Höhe zu bringen:
use iced::widget::{column, container, mouse_area, row, svg, text}; use iced::{mouse, Background, Border, Color, Element, Fill, Font, Theme};use iced::widget::{button, column, container, mouse_area, row, svg, text}; use iced::{mouse, Background, Border, Center, Color, Element, Fill, Font, Theme};
Ein Klick auf den Knopf wird zu einem neuen Brief. Er braucht keine Feldnummer, denn es geht ja ums ganze Spiel:
Markieren(usize),NeuesSpiel,}
Und update weiß, was bei diesem Brief zu tun ist:
fn update(&mut self, message: Message) { match message {Message::NeuesSpiel => *self = Watt::neu(),Message::Aufdecken(i) => {
Eine einzige Zeile, und doch steckt eine hübsche Idee darin. Watt::neu() baut ein
komplett frisches Watt, genau wie beim Programmstart. Und *self = … ersetzt unser
bisheriges Watt vollständig durch das neue. Das Sternchen heißt: „Nicht die Ausleihe
überschreiben, sondern das, worauf sie zeigt.“ Alle Felder, der Spielstand, einfach alles ist
danach wie neu. Kurze Zweige ohne geschweifte Klammern sind übrigens erlaubt, wenn sie nur
aus einem Ausdruck bestehen.
Jetzt bauen wir den Knopf in view und stellen ihn neben die Überschrift:
let titel = text("WATTLÄUFER").size(22).color(SCHAUM);let neu_knopf = button(text("Neues Spiel")) .on_press(Message::NeuesSpiel) .style(|_theme: &Theme, _status| button::Style { background: Some(Background::Color(SAND)), text_color: MEER, border: Border { radius: 6.0.into(), ..Border::default() }, ..button::Style::default() }); let kopf = row![titel.width(Fill), neu_knopf].align_y(Center);⋮let inhalt = column![titel, text(status).color(SCHAUM), watt].spacing(14);let inhalt = column![kopf, text(status).color(SCHAUM), watt].spacing(14);
Beim Knopf kennst du fast alles schon: .on_press(…) sagt, welcher Brief beim
Klick verschickt wird, und .style(…) gestaltet ihn in Sandfarbe mit blauer
Schrift. Die Closure bekommt hier zwei Dinge: das Farbschema und den Zustand des Knopfes, zum
Beispiel ob gerade die Maus darüber schwebt. Beides brauchen wir nicht, daher die
Unterstriche.
In der Kopfzeile bekommt der Titel .width(Fill): Er nimmt sich allen Platz, den
der Knopf übrig lässt, und schiebt ihn damit ganz nach rechts. .align_y(Center)
stellt beide auf dieselbe Höhe.
cargo run
Oben rechts sitzt ein sandfarbener Knopf Neues Spiel. Tritt auf einen
Krebs, klick den Knopf, und schon liegt ein frisches Watt vor dir, mit neu versteckten
Krebsen.Der erste Schritt ist immer sicher
Kennst du das? Erster Klick, und schon zwickt es. Das ist nicht fair, denn beim ersten Klick kann man noch gar nichts wissen. Die bekannten Minesweeper-Spiele lösen das mit einem Trick, den wir übernehmen: Die Krebse werden erst versteckt, nachdem du zum ersten Mal geklickt hast, und zwar nie auf diesem Feld und nie direkt daneben. So öffnet der erste Klick immer eine Fläche.
Dafür muss sich das Watt merken, ob der erste Schritt noch bevorsteht:
struct Watt { felder: Vec<Feld>, spiel: Spiel,erster_schritt: bool,}
Jetzt kommt der Umbau. Das Verstecken und Zählen wandert aus neu heraus. In
neu bleibt nur das leere Watt übrig. Weil wir die Liste dort nicht mehr
verändern, wird aus let mut felder wieder let felder:
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 }let felder = vec![leer; BREITE * HOEHE]; Watt { felder, spiel: Spiel::Laeuft, erster_schritt: true, }}
Das Watt { … } am Ende ist auf mehrere Zeilen verteilt, weil es drei Angaben
sind. felder, allein ist eine Abkürzung für felder: felder,, also
„das Feld felder bekommt den Wert der Variablen felder“.
Das Verstecken und Zählen bekommt eine eigene Funktion, direkt hinter neu.
Das meiste davon kennst du aus Etappe 3, nur heißt es jetzt self.felder statt
felder:
erster_schritt: true, } }// Versteckt die Krebse, aber nie auf dem Startfeld oder direkt daneben. fn krebse_verstecken(&mut self, start: usize) { let mut sicher = nachbarn(start); sicher.push(start); let erlaubt: Vec<usize> = (0..BREITE * HOEHE) .filter(|i| !sicher.contains(i)) .collect(); let mut rng = rand::rng(); for k in rand::seq::index::sample(&mut rng, erlaubt.len(), KREBSE) { self.felder[erlaubt[k]].krebs = true; } // Für jedes Feld zählen, wie viele Krebse in den Nachbarfeldern stecken for i in 0..self.felder.len() { let mut anzahl = 0; for n in nachbarn(i) { if self.felder[n].krebs { anzahl += 1; } } self.felder[i].nachbarn = anzahl; } }
Neu ist der Anfang:
sicherist die Liste der geschützten Felder: alle Nachbarn des Startfelds, und mitpushhängen wir das Startfeld selbst noch an.erlaubtsind alle Feldnummern, die nicht insicherstehen.(0..BREITE * HOEHE)sind alle Nummern von 0 bis 80,.filter(…)behält nur die erlaubten, und.collect()sammelt sie in einem neuenVec. Die TypangabeVec<usize>sagtcollect, was für eine Sammlung es bauen soll.- Dann losen wir wie gehabt zehn aus, aber nicht direkt Feldnummern, sondern
Plätze in der Liste
erlaubt.erlaubt[k]ist die Feldnummer, die auf Platzksteht.
Bleibt, die neue Funktion beim ersten Klick aufzurufen. Das passiert in
update, direkt nach der Prüfung, ob das Feld verdeckt ist:
if self.felder[i].zustand != Zustand::Verdeckt { return; // Fähnchen oder schon offen: nicht aufdecken }if self.erster_schritt { self.krebse_verstecken(i); self.erster_schritt = false; }if self.felder[i].krebs {
cargo run
Starte ein paar Spiele mit dem Knopf und klick jedes Mal zuerst irgendwohin, auch
in eine Ecke: Der erste Klick öffnet immer eine Fläche und trifft nie einen Krebs.Ferris zieht ins Watt
Wer zwickt da eigentlich? Bisher nur ein rotes Feld. Jetzt bekommt der Krebs ein Gesicht: Ferris, das freundliche Rust-Maskottchen. Ferris ist gemeinfrei, jeder darf ihn benutzen. Und nur das Feld, auf das du getreten bist, soll rot leuchten. Die anderen Krebse zeigen sich auf nassem Sand.
Zuerst brauchen wir Ferris als Datei. Leg in deinem Projektordner, also neben
src, einen neuen Ordner assets an. Dann lade
Ferris als SVG herunter und
speichere ihn dort als ferris.svg. Dein Projekt sieht dann so aus:
wattlaeufer/
├── assets/
│ └── ferris.svg
├── src/
│ └── main.rs
└── Cargo.toml
Beim Fähnchen haben wir die Zeichnung direkt in den Code geschrieben. Ferris ist dafür zu
groß, er steht in einer eigenen Datei. Mit include_bytes! holt Rust die Datei
beim Übersetzen ins Programm hinein:
<ellipse cx="7" cy="21" rx="5" ry="1.5" fill="#8a6a3a"/> </svg>"##;// Ferris, das Rust-Maskottchen, aus der Datei assets/ferris.svg const FERRIS: &[u8] = include_bytes!("../assets/ferris.svg");
Der Pfad ist relativ zu src/main.rs: .. heißt
„einen Ordner hoch“, dann in assets zu ferris.svg. Das Schöne:
Ferris steckt danach im fertigen Programm selbst. Man muss die Datei nicht mitliefern.
Jetzt muss sich das Watt merken, auf welches Feld du getreten bist. Aber solange du auf
keins getreten bist, gibt es auch kein solches Feld. Für „vielleicht ein Wert, vielleicht
keiner“ hat Rust Option:
spiel: Spiel, erster_schritt: bool,getroffen: Option<usize>,}
Option<usize> ist entweder Some(12), „ja, Feld 12“, oder
None, „keins“. Mehr zu Option:
Buch, Kapitel 14. Am Anfang ist es natürlich
None:
spiel: Spiel::Laeuft, erster_schritt: true,getroffen: None,}
Beim Tritt auf einen Krebs merken wir uns das Feld:
if self.felder[i].krebs { self.spiel = Spiel::Verloren;self.getroffen = Some(i);self.alle_krebse_zeigen();
Und in feld_ansicht zeigen wir das Ganze an. Rot wird jetzt nur noch das
getroffene Feld, und auf jedem aufgedeckten Krebsfeld sitzt Ferris:
let grund = if verdeckt { SAND} else if feld.krebs { ROT} else if self.getroffen == Some(i) { ROT} else { NASS };⋮svg(svg::Handle::from_memory(FAHNE)).width(26).height(26).into()} else if !verdeckt && feld.krebs { svg(svg::Handle::from_memory(FERRIS)).width(34).into()} else if !verdeckt && !feld.krebs && feld.nachbarn > 0 {
couldn't read `src/../assets/ferris.svg`, liegt die
Datei nicht an der richtigen Stelle oder heißt anders. Der Ordner assets muss
direkt im Projektordner liegen, nicht in src. Und weil
include_bytes! beim Übersetzen arbeitet, ist das ein Fehler beim Kompilieren,
nicht erst beim Spielen. Das ist gut so: Ein fehlender Ferris fällt sofort auf.cargo run
Tritt absichtlich auf einen Krebs: Das getroffene Feld leuchtet rot, und überall,
wo Krebse steckten, sitzt jetzt Ferris im nassen Sand.
Das Fenster bleibt, wie es ist
Die letzte Kleinigkeit: Unser Watt hat eine feste Größe. Zieht man das Fenster größer, entsteht nur leeres Meer am Rand, zieht man es kleiner, wird das Watt abgeschnitten. Also verbieten wir das Ändern der Fenstergröße einfach:
.window_size((460.0, 560.0)).resizable(false).run()
Eine Zeile, noch ein Waggon am Zug, fertig. Manche Linux-Fenstermanager halten sich übrigens nicht an diesen Wunsch. Das ist kein Fehler in deinem Programm.
cargo run
Versuch, das Fenster an der Ecke größer zu ziehen: Es bleibt, wie es ist. Und
damit ist der Wattläufer fertig! Spiel ruhig eine Runde. Du hast sie dir verdient.
Der komplette Code nach Etappe 6
Das ist der komplette, fertige Wattläufer: gut 300 Zeilen Rust, Kommentare und Leerzeilen mitgezählt. Heb ihn gut auf, er ist ein prima Startpunkt für eigene Ideen.
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::{button, column, container, mouse_area, row, svg, text};
use iced::{mouse, Background, Border, Center, 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>"##;
// Ferris, das Rust-Maskottchen, aus der Datei assets/ferris.svg
const FERRIS: &[u8] = include_bytes!("../assets/ferris.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))
.resizable(false)
.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,
erster_schritt: bool,
getroffen: Option<usize>,
}
#[derive(Debug, Clone)]
enum Message {
Aufdecken(usize),
Markieren(usize),
NeuesSpiel,
}
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 felder = vec![leer; BREITE * HOEHE];
Watt {
felder,
spiel: Spiel::Laeuft,
erster_schritt: true,
getroffen: None,
}
}
// Versteckt die Krebse, aber nie auf dem Startfeld oder direkt daneben.
fn krebse_verstecken(&mut self, start: usize) {
let mut sicher = nachbarn(start);
sicher.push(start);
let erlaubt: Vec<usize> = (0..BREITE * HOEHE)
.filter(|i| !sicher.contains(i))
.collect();
let mut rng = rand::rng();
for k in rand::seq::index::sample(&mut rng, erlaubt.len(), KREBSE) {
self.felder[erlaubt[k]].krebs = true;
}
// Für jedes Feld zählen, wie viele Krebse in den Nachbarfeldern stecken
for i in 0..self.felder.len() {
let mut anzahl = 0;
for n in nachbarn(i) {
if self.felder[n].krebs {
anzahl += 1;
}
}
self.felder[i].nachbarn = anzahl;
}
}
fn update(&mut self, message: Message) {
match message {
Message::NeuesSpiel => *self = Watt::neu(),
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.erster_schritt {
self.krebse_verstecken(i);
self.erster_schritt = false;
}
if self.felder[i].krebs {
self.spiel = Spiel::Verloren;
self.getroffen = Some(i);
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 neu_knopf = button(text("Neues Spiel"))
.on_press(Message::NeuesSpiel)
.style(|_theme: &Theme, _status| button::Style {
background: Some(Background::Color(SAND)),
text_color: MEER,
border: Border {
radius: 6.0.into(),
..Border::default()
},
..button::Style::default()
});
let kopf = row![titel.width(Fill), neu_knopf].align_y(Center);
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![kopf, 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 self.getroffen == Some(i) {
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 {
svg(svg::Handle::from_memory(FERRIS)).width(34).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),
}
}
button verschickt beim Klick eine
Nachricht, und *self = Watt::neu() setzt den ganzen Zustand auf einmal zurück.
Mit .filter() und .collect() baut man aus einer Liste eine neue.
include_bytes! holt Dateien schon beim Übersetzen ins Programm.
Option steht für „vielleicht ein Wert, vielleicht keiner“. Und manchmal bedeutet
eine kleine Verbesserung, Code umzuziehen: vom Programmstart in den ersten Klick.Wie geht es weiter?
Der Wattläufer ist fertig, aber ein Spiel ist nie wirklich fertig. Hier ein paar Ideen, mit denen du ihn allein weiterbauen kannst. Sie sind nach Schwierigkeit sortiert, und für jede hast du in diesen sechs Etappen schon das Werkzeug kennengelernt.
- Schwierigkeitsstufen: drei Knöpfe für „Strand“ (8 Krebse), „Watt“ (10)
und „Priel“ (15). Tipp: Aus der Konstanten
KREBSEwird ein Feld inWatt, undNeuesSpielbekommt die Anzahl als Wert mit, so wieAufdeckendie Feldnummer. - Aufdecken per Doppelklick: Ein Doppelklick auf eine Zahl deckt alle
Nachbarn ohne Fähnchen auf, wenn genau so viele Fähnchen drumherum stehen, wie die Zahl
sagt. Profis spielen so.
mouse_areahat dafüron_double_click. - Eine Stoppuhr: Wie schnell schaffst du das Watt? Dafür brauchst du etwas, das jede Sekunde von selbst eine Nachricht schickt. In iced heißt das Subscription, und im Buch (Kapitel 28) lernst du, wie das geht.
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.
- Größeres Watt: Mach das Watt 12 mal 12 Felder groß mit 20 Krebsen. Welche Stellen musst du ändern? Denk an das Fenster!
- Warum nur zehn? In
krebse_versteckenlosen wir auserlaubt, nicht aus allen 81 Feldern. Wie viele Einträge haterlaubt, wenn der erste Klick in der Mitte ist, und wie viele bei einem Klick in die Ecke? - Siegesfarbe: Lass die Statuszeile bei einem Sieg nicht in
SCHAUM, sondern inSANDleuchten. Tipp: Du brauchst eine Variable für die Farbe, genau wiegrundinfeld_ansicht.
Lösungen anzeigen
1. BREITE und HOEHE auf 12, KREBSE auf 20, und die
Fenstergröße in main muss wachsen: 12 Felder mal 44 Pixel plus 11 Lücken mal 3
Pixel plus 40 Pixel Rand ergibt 601 Pixel Breite, also zum Beispiel
(601.0, 700.0). Alles andere rechnet sich aus den Konstanten von selbst, das
ist der Lohn für die ordentliche Vorarbeit.
2. In der Mitte hat das Startfeld 8 Nachbarn, zusammen sind 9 Felder gesperrt, bleiben 72 erlaubte. In der Ecke hat es nur 3 Nachbarn, gesperrt sind 4, bleiben 77.
3. In view: let statusfarbe = if self.spiel == Spiel::Gewonnen { SAND }
else { SCHAUM }; und dann text(status).color(statusfarbe) in der
Spalte. Weil Spiel PartialEq hat, darf man es mit
== vergleichen.
Der Wattläufer ist fertig! 🦀🏆
Aus einer leeren Datei ist in sechs Etappen ein richtiges Spiel geworden, mit Zufall, Rekursion, Grafiken und einem Maskottchen, das zwickt. Wenn du Lust auf mehr hast: Im Fährmann bauen wir als Nächstes ein echtes Werkzeug, einen Dateimanager mit FTP und SFTP.
Hat dir der Wattläufer Spaß gemacht? Ein Kaffee hilft, dass die nächste Werkstatt ein Stück schneller fertig wird.
☕ Kaffee spendieren