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

Wir bauen ein Spiel – das Watt

Schön, dass du da bist! In dieser Werkstatt bauen wir zusammen ein kleines Spiel: den Wattläufer. Es ist Ebbe, unter dem Sand haben sich Krebse versteckt, und du musst übers Watt laufen, ohne einem auf die Scheren zu treten. Wer Minesweeper kennt, kennt die Regeln schon. Heute legen wir das Spielfeld an: ein Fenster mit Meer drumherum und 81 Sandfeldern.

7 Schritte · ca. 40 Minuten · iced 0.14 · Rust 1.88 oder neuer

Ein Fenster mit dunkelblauem Meer, der Überschrift WATTLÄUFER und einem Gitter aus 9 mal 9 Sandfeldern
So sieht der Wattläufer am Ende dieser Etappe aus.
Vorab

Bevor wir loslegen

Worum geht es?

Das Spielfeld besteht aus 9 mal 9 Feldern Sand. Unter zehn davon steckt ein Krebs. Klickst du ein Feld an, deckst du es auf. Ist dort kein Krebs, erscheint eine Zahl: Sie verrät dir, wie viele Krebse in den acht Feldern rundherum stecken. Mit etwas Nachdenken kannst du so herausfinden, wo die Krebse sitzen, und dort mit einem Rechtsklick ein Warnfähnchen aufstellen. Hast du alle sicheren Felder aufgedeckt, hast du gewonnen. Trittst du auf einen Krebs, zwickt er zu, und das Spiel ist vorbei.

Bis das alles funktioniert, gehen wir sechs Etappen. Heute geht es nur um das Aussehen, die Spielregeln kommen danach Stück für Stück dazu.

Was du brauchst

  • Rust, installiert über rustup. Falls du das noch nicht hast: Buch, Kapitel 1 zeigt dir Schritt für Schritt, wie es geht.
  • Einen Editor, am besten VS Code mit der Erweiterung rust-analyzer.
  • Ein Terminal, in dem du Befehle eintippst.

Mehr nicht. Du musst weder iced kennen noch schon viel Rust können. Alles, was wir brauchen, erkläre ich genau in dem Moment, in dem wir es brauchen.

So liest du die Code-Kästen

Wir schreiben nicht bei jedem Schritt das ganze Programm neu. Stattdessen zeige ich dir genau die Stelle, an der sich etwas ändert. Oben im Kasten stehen der Dateiname und ein kurzer Hinweis, wo die Stelle liegt. Die Zeilen im Kasten sind farbig markiert:

fn main() {Grau: steht schon in deiner Datei. Nur zur Orientierung, nicht noch einmal eintippen. let x = 1;Türkiser Balken: das ist neu, das tippst du ein. let y = 2;Roter Balken, durchgestrichen: diese Zeile löschst du (meistens, weil direkt darunter der Ersatz steht). ⋮Drei Punkte: hier steht Code, den wir gerade nicht anfassen.

Nach fast jedem Schritt machen wir einen Probelauf. Kleine Schritte, oft prüfen: So findest du Tippfehler sofort und nicht erst zwanzig Minuten später.

Abgehängt? Kein Problem. Am Ende jeder Etappe findest du unter Zwischenstand den kompletten Code. Damit kannst du jederzeit vergleichen oder neu einsteigen. Und ein Tipp aus Erfahrung: Tipp den Code lieber ab, statt ihn zu kopieren. Deine Finger lernen mit.
Schritt 1

Das Projekt anlegen

Jedes Rust-Programm beginnt mit einem Projektordner, und den legt cargo für uns an. Öffne ein Terminal, geh in den Ordner, in dem du deine Projekte sammelst, und tippe:

cargo new wattlaeufer
cd wattlaeufer

cargo new erzeugt einen Ordner wattlaeufer mit allem, was ein Rust-Projekt braucht, und mit cd gehen wir hinein. Jetzt holen wir uns iced dazu, die Bibliothek, mit der wir Fenster, Texte und Spielfelder bauen:

cargo add iced@0.14

In der Datei Cargo.toml, sozusagen dem Personalausweis deines Projekts, steht jetzt ganz unten iced als Abhängigkeit:

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

[dependencies]
iced = "0.14"
Worauf du achten musst Im Projektnamen stehen ae statt ä, weil Rust in Projektnamen nur einfache Buchstaben, Ziffern, - und _ erlaubt. Im Fenster darf später natürlich „Wattläufer“ stehen. Mit @0.14 legen wir die Version von iced fest, damit diese Anleitung auch dann noch funktioniert, wenn eine neuere Version erscheint, die manches anders macht.
Schritt 2

Das erste Fenster

Jetzt bringen wir ein Fenster auf den Bildschirm. Öffne src/main.rs. Dort steht schon ein kleines „Hello, world!“-Programm, das wir nicht brauchen. Lösch den ganzen Inhalt und tippe stattdessen Folgendes ein. Das ist das einzige Mal, dass wir eine Datei komplett schreiben. Danach kommen nur noch einzelne Stellen dazu.

src/main.rsdie komplette Datei (alles Alte vorher löschen)
use iced::widget::text;
use iced::Element;

fn main() -> iced::Result {
    iced::application(Watt::default, Watt::update, Watt::view)
        .title("Wattläufer")
        .run()
}

#[derive(Default)]
struct Watt {}

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

impl Watt {
    fn update(&mut self, message: Message) {
        match message {}
    }

    fn view(&self) -> Element<'_, Message> {
        text("Ebbe! Das Watt liegt vor dir.").into()
    }
}

Das sind gut 20 Zeilen, aber es steckt schon das ganze Prinzip von iced darin. Gehen wir sie gemeinsam durch:

  • use … holt Dinge aus iced in unsere Datei, damit wir sie mit ihrem kurzen Namen benutzen können. text zeigt einen Text an, Element ist der Oberbegriff für „irgendetwas, das man anzeigen kann“.
  • fn main() ist der Startpunkt jedes Rust-Programms. Hier sagen wir iced: „Starte eine Anwendung.“ Dafür bekommt iced drei Dinge: wie man den Anfangszustand erzeugt (Watt::default), was bei einem Ereignis passieren soll (Watt::update) und wie der Bildschirm aussieht (Watt::view). .title(…) setzt den Fenstertitel, .run() startet das Ganze.
  • struct Watt ist das Gedächtnis unseres Spiels. Später steht hier, wo die Krebse stecken und welche Felder schon aufgedeckt sind. Im Moment ist es noch leer.
  • enum Message ist der Briefkasten. Jedes Ereignis, auf das das Spiel reagieren soll, zum Beispiel „Feld Nummer 12 wurde angeklickt“, wird ein Brief in diesem Kasten. Noch gibt es keine Ereignisse, also ist er leer.
  • update öffnet die Briefe und entscheidet, was passiert. Ohne Briefe steht in match message {} nichts drin.
  • view beschreibt, was auf dem Bildschirm zu sehen ist. Das .into() verpackt den Text in ein Element, denn genau das will iced zurückbekommen.

Dieses Dreieck aus Zustand, update und view begleitet uns durch das ganze Spiel. Im Buch gibt es dazu eine ausführliche Erklärung: Buch, Kapitel 17.

Worauf du achten musst Bei Watt::default, Watt::update und Watt::view stehen keine runden Klammern dahinter. Wir rufen die Funktionen hier nicht selbst auf, sondern geben sie iced in die Hand, und iced ruft sie auf, wann immer es sie braucht. Das #[derive(Default)] über dem struct sorgt dafür, dass es Watt::default überhaupt gibt.
Probelauf Speichern, dann im Terminal:
cargo run
Der erste Start dauert ein paar Minuten, weil cargo iced und alles, was dazugehört, einmal übersetzt. Das passiert nur beim ersten Mal. Wenn alles klappt, öffnet sich ein Fenster mit dem Titel „Wattläufer“ und dem Text Ebbe! Das Watt liegt vor dir. Schließ es wieder, bevor wir weitermachen.
Schritt 3

Schrift und Fenstergröße

Unser Spiel soll ein bisschen nach alter Spielkonsole aussehen. Dazu passt eine Monospace-Schrift, bei der jeder Buchstabe gleich breit ist, wie auf einer Schreibmaschine. Außerdem geben wir dem Fenster eine feste Größe, in die unser Spielfeld später genau hineinpasst. Zuerst brauchen wir Font aus iced:

src/main.rsganz oben, zweite Zeile
use iced::widget::text;use iced::Element;use iced::{Element, Font};

Die geschweiften Klammern bedeuten: „Hol mir aus iced gleich mehrere Dinge auf einmal.“ Dann ergänzen wir in main zwei Zeilen direkt unter dem Titel:

src/main.rsin fn main(), direkt unter .title(…)
fn main() -> iced::Result {
    iced::application(Watt::default, Watt::update, Watt::view)
        .title("Wattläufer")        .default_font(Font::MONOSPACE)
        .window_size((460.0, 560.0))        .run()
}

default_font legt die Schrift für das ganze Programm fest. window_size bekommt Breite und Höhe in Pixeln. Die doppelten Klammern sind kein Tippfehler: Die inneren packen die zwei Zahlen zu einem Paar zusammen, die äußeren gehören zum Funktionsaufruf.

Worauf du achten musst Die neuen Zeilen beginnen mit einem Punkt und enden ohne Semikolon. Sie hängen sich an die Zeile davor an wie Waggons an einen Zug: application(…) ist die Lok, .run() der letzte Wagen. Ein Semikolon mittendrin würde den Zug auseinanderreißen.
Probelauf Speichern, dann im Terminal:
cargo run
Das Fenster ist jetzt schmaler und höher, und der Text sieht aus wie getippt.
Schritt 4

Das Meer

Rund um unser Spielfeld soll das Meer sein, ein tiefes Nordsee-Blau. Dafür brauchen wir zwei Dinge: eine Farbe und etwas, das diese Farbe als Hintergrund tragen kann. Dieses Etwas heißt in iced Container. Stell ihn dir als Kiste vor: Die Kiste kann eine Farbe haben, eine Größe und einen Innenabstand zum Inhalt. Für all das brauchen wir wieder ein paar Dinge aus iced. Diesmal ändern sich beide use-Zeilen:

src/main.rsganz oben, die ersten beiden Zeilen
use iced::widget::text;
use iced::{Element, Font};use iced::widget::{container, text};
use iced::{Background, Color, Element, Fill, Font, Theme};

Jetzt legen wir unsere Farben fest, und zwar zwischen den use-Zeilen und fn main():

src/main.rszwischen den use-Zeilen und fn main()
use iced::{Background, 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);fn main() -> iced::Result {

Mit const geben wir einem festen Wert einen Namen, das nennt man eine Konstante. Jede Farbe mischt sich aus Rot, Grün und Blau, jeweils von 0 bis 255. Hier schreiben wir die Werte als Hexadezimalzahlen, also mit 0x davor: 0x1f ist 31, 0xf4 ist 244. So stehen Farben auch in Grafikprogrammen und auf Webseiten, deshalb ist diese Schreibweise praktisch zum Abgucken. MEER ist das Blau, SCHAUM ein warmes Weiß für Schrift.

Und nun zur Funktion view. Die alte Textzeile ersetzen wir durch eine Überschrift in einer blauen Kiste:

src/main.rsin impl Watt, Funktion view
    fn view(&self) -> Element<'_, Message> {        text("Ebbe! Das Watt liegt vor dir.").into()        let titel = text("WATTLÄUFER").size(22).color(SCHAUM);

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

Schauen wir es uns Stück für Stück an:

  • text("WATTLÄUFER").size(22).color(SCHAUM) baut die Überschrift, 22 Pixel groß, in Schaumweiß. Mit let titel = … geben wir ihr einen Namen.
  • container(titel) packt die Überschrift in eine Kiste. .padding(20) lässt 20 Pixel Abstand zum Rand, .width(Fill) und .height(Fill) machen die Kiste so groß wie das ganze Fenster.
  • .style(…) legt das Aussehen fest. In den Klammern steht eine kleine Funktion ohne Namen, eine Closure. Zwischen den Strichen |…| steht, was sie bekommt: das Farbschema (Theme). Das brauchen wir nicht, deshalb der Unterstrich vor dem Namen. Mehr über Closures: Buch, Kapitel 13.
  • Die Closure liefert einen container::Style: Hintergrund ist das Meer. Some(…) heißt „ja, hier gibt es einen Wert“, und ..container::Style::default() heißt „alles andere bitte wie üblich“.
Worauf du achten musst Die letzte Zeile .into() hat kein Semikolon. In Rust ist die letzte Zeile ohne Semikolon das, was eine Funktion zurückgibt. Mit Semikolon würde view nichts zurückgeben, und der Compiler würde sich beschweren.
Probelauf Speichern, dann im Terminal:
cargo run
Das ganze Fenster ist jetzt tiefblau, und oben links steht in hellen Buchstaben WATTLÄUFER. Das Meer ist da, fehlt nur noch der Sand.
Schritt 5

Ein einzelnes Sandfeld

Jetzt kommt der Sand. Das Spielfeld besteht aus 81 gleichen Feldern. Bevor wir uns an alle 81 wagen, bauen wir erst mal eines. Und weil wir es danach 81-mal brauchen, schreiben wir dafür eine Funktion: eine Ausstechform, die wir einmal bauen und dann beliebig oft benutzen.

Ein Sandfeld ist wieder ein Container, diesmal mit fester Größe, sandfarbenem Hintergrund und einem etwas dunkleren, abgerundeten Rand. Den Rand beschreibt iced mit Border. Um das Feld unter die Überschrift zu stellen, brauchen wir außerdem column. Also zuerst wieder die use-Zeilen:

src/main.rsganz oben, die ersten beiden Zeilen
use iced::widget::{container, text};
use iced::{Background, Color, Element, Fill, Font, Theme};use iced::widget::{column, container, text};
use iced::{Background, Border, Color, Element, Fill, Font, Theme};

Dann zwei neue Farben und die Feldgröße, direkt unter SCHAUM:

src/main.rsbei den Farben
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);

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

Bei FELD steht f32 statt Color: Das ist eine Kommazahl, denn Größen misst iced in Kommazahlen. Deshalb auch 44.0 statt 44. Jetzt die Ausstechform selbst. Sie kommt ganz ans Ende der Datei, hinter die letzte geschweifte Klammer von impl Watt:

src/main.rsganz am Ende der Datei
            .into()
    }
}// Baut ein einzelnes Sandfeld.
fn sandfeld() -> Element<'static, Message> {
    container(text(""))
        .width(FELD)
        .height(FELD)
        .style(|_theme: &Theme| container::Style {
            background: Some(Background::Color(SAND)),
            border: Border {
                color: SAND_KANTE,
                width: 2.0,
                radius: 6.0.into(),
            },
            ..container::Style::default()
        })
        .into()
}

text("") ist ein leerer Text, denn das Feld soll ja erst mal nichts anzeigen. Border beschreibt den Rand: dunklerer Sand, 2 Pixel breit, Ecken mit 6 Pixel Rundung. Das 'static in der ersten Zeile sagt Rust, dass das Feld nichts aus unserem Spielzustand ausleiht. Es lebt also völlig unabhängig, so lange es gebraucht wird. Mehr musst du dir dazu im Moment nicht merken.

Zum Schluss stellen wir in view die Überschrift und ein Sandfeld untereinander. Dafür ist column! da, eine Spalte, die ihren Inhalt von oben nach unten stapelt:

src/main.rsin der Funktion view
        let titel = text("WATTLÄUFER").size(22).color(SCHAUM);        container(titel)        let inhalt = column![titel, sandfeld()].spacing(14);

        container(inhalt)            .padding(20)

.spacing(14) lässt 14 Pixel Luft zwischen den Teilen der Spalte. Und in den Container kommt jetzt nicht mehr nur der Titel, sondern die ganze Spalte.

Worauf du achten musst sandfeld muss außerhalb von impl Watt { … } stehen. Landet sie aus Versehen vor der letzten schließenden Klammer, meldet Rust cannot find function `sandfeld`. Zähl im Zweifel die Klammern nach, oder lass cargo fmt den Code einrücken, dann sieht man es sofort. Und bei column! bitte das Ausrufezeichen und die eckigen Klammern nicht vergessen, denn column! ist ein Makro.
Probelauf Speichern, dann im Terminal:
cargo run
Unter der Überschrift liegt jetzt ein einzelnes, sandfarbenes Feld mit runden Ecken. Ein bisschen einsam, aber das ändern wir gleich.
Schritt 6

Eine Reihe Sand

Jetzt benutzen wir unsere Ausstechform mehrmals. Neun Felder sollen nebeneinander liegen. Wir könnten sandfeld() einfach neunmal hinschreiben, aber es geht eleganter: mit einer Schleife, die dieselbe Arbeit so oft wiederholt, wie wir wollen. Wie viele Felder breit das Watt ist, halten wir in einer Konstanten fest:

src/main.rsbei der Feldgröße
const FELD: f32 = 44.0;// So groß ist das Watt
const BREITE: usize = 9;fn main() -> iced::Result {

usize ist der Zahlentyp, den Rust für Anzahlen und Positionen benutzt. Er kann nicht negativ werden, denn eine Reihe mit minus drei Feldern gibt es nicht. Für „nebeneinander“ brauchen wir das Gegenstück zu column!: row!, eine Reihe von links nach rechts.

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

Und nun die Schleife in view:

src/main.rsin der Funktion view
        let titel = text("WATTLÄUFER").size(22).color(SCHAUM);        let inhalt = column![titel, sandfeld()].spacing(14);        let mut reihe = row![].spacing(3);
        for _x in 0..BREITE {
            reihe = reihe.push(sandfeld());
        }

        let inhalt = column![titel, reihe].spacing(14);

Das liest sich fast wie ein Satz:

  • let mut reihe = row![].spacing(3); legt eine leere Reihe an, mit 3 Pixeln Abstand zwischen den Feldern. Das mut bedeutet „veränderbar“: Wir wollen gleich Felder hinzufügen, und Rust erlaubt das nur, wenn wir es vorher ankündigen.
  • for _x in 0..BREITE heißt: „Mach das Folgende für jede Zahl von 0 bis 8.“ 0..BREITE zählt nämlich bis vor 9. Der Unterstrich vor _x sagt Rust wieder: „Den Wert brauche ich gerade nicht, alles gut.“
  • reihe = reihe.push(sandfeld()); hängt ein neues Feld an und merkt sich die verlängerte Reihe. Das push gibt nämlich eine neue, längere Reihe zurück, und die speichern wir wieder unter demselben Namen.
Probelauf Speichern, dann im Terminal:
cargo run
Unter der Überschrift liegen jetzt neun Sandfelder in einer Reihe, fast genau so breit wie das Fenster.
Schritt 7

Das ganze Watt

Eine Reihe haben wir. Jetzt brauchen wir neun davon, übereinander. Und das Schöne ist: Wir können einfach eine Schleife um unsere Schleife legen. Die äußere Schleife sorgt für die Reihen, die innere für die Felder in jeder Reihe. Erst die Höhe des Watts als Konstante:

src/main.rsbei BREITE
const BREITE: usize = 9;const HOEHE: usize = 9;

Rust schreibt man HOEHE mit OE, denn in Namen im Code verzichten wir auf Umlaute. Rust würde sie sogar erlauben, aber es ist unter Rust-Programmierern weltweit üblich, Namen nur aus einfachen Buchstaben zu bilden. Und nun die Schleifen:

src/main.rsin der Funktion view
        let titel = text("WATTLÄUFER").size(22).color(SCHAUM);        let mut reihe = row![].spacing(3);
        for _x in 0..BREITE {
            reihe = reihe.push(sandfeld());
        }

        let inhalt = column![titel, reihe].spacing(14);        let mut watt = column![].spacing(3);
        for _y in 0..HOEHE {
            let mut reihe = row![].spacing(3);
            for _x in 0..BREITE {
                reihe = reihe.push(sandfeld());
            }
            watt = watt.push(reihe);
        }

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

Stell dir vor, du legst Fliesen: Für jede Reihe (_y) legst du neun Fliesen nebeneinander (_x), und wenn die Reihe fertig ist, legst du sie auf den Stapel (watt.push(reihe)). Neun Reihen mal neun Fliesen, macht 81 Sandfelder. Das Watt ist eine Spalte aus Reihen, darum ist watt ein column!.

Worauf du achten musst Achte darauf, dass let mut reihe = … jetzt innerhalb der äußeren Schleife steht. Für jede Reihe fangen wir mit einer neuen, leeren Reihe an. Stünde sie außerhalb, würde Rust sich beschweren, dass wir eine Reihe benutzen, die wir schon auf den Stapel gelegt haben.
Probelauf Speichern, dann im Terminal:
cargo run
Da ist es: das ganze Watt, 9 mal 9 Sandfelder, umgeben von tiefblauem Meer. Klicken kannst du noch nicht, aber das Spielfeld steht.
Das fertige Watt: 81 Sandfelder auf blauem Grund
Stand nach Schritt 7
Zwischenstand

Der komplette Code nach Etappe 1

Hier ist der vollständige Stand nach Etappe 1. 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 = "0.14"

src/main.rs

use iced::widget::{column, container, row, text};
use iced::{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);

// 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;

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

#[derive(Default)]
struct Watt {}

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

impl Watt {
    fn update(&mut self, message: Message) {
        match message {}
    }

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

        let mut watt = column![].spacing(3);
        for _y in 0..HOEHE {
            let mut reihe = row![].spacing(3);
            for _x in 0..BREITE {
                reihe = reihe.push(sandfeld());
            }
            watt = watt.push(reihe);
        }

        let inhalt = column![titel, 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()
    }
}

// Baut ein einzelnes Sandfeld.
fn sandfeld() -> Element<'static, Message> {
    container(text(""))
        .width(FELD)
        .height(FELD)
        .style(|_theme: &Theme| container::Style {
            background: Some(Background::Color(SAND)),
            border: Border {
                color: SAND_KANTE,
                width: 2.0,
                radius: 6.0.into(),
            },
            ..container::Style::default()
        })
        .into()
}
ZusammenfassungEin iced-Programm besteht aus einem Zustand (struct), einer update-Funktion für Ereignisse und einer view-Funktion für den Bildschirm. Mit container geben wir Dingen Farbe, Größe und Rand, mit column! stapeln wir sie, mit row! legen wir sie nebeneinander. Wiederkehrende Bausteine schreiben wir einmal als Funktion, und mit for-Schleifen bauen wir daraus beliebig viele. Werte, die sich nie ändern, stehen als const an einer einzigen Stelle.
Ü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. Riesenwatt: Ändere BREITE und HOEHE auf 12. Was passiert mit dem Fenster? Was müsstest du zusätzlich ändern, damit wieder alles hineinpasst?
  2. Ohne Unterstrich: Schreib in einer der Schleifen x statt _x und starte das Programm. Was sagt dir Rust? Ist das ein Fehler oder eine Warnung?
  3. Farbenlehre: Mach das Meer mit Color::from_rgb8(0x2e, 0x7d, 0x4f) grün. Wie viele Zeilen musstest du ändern, und warum nicht mehr?
Lösungen anzeigen

1. Das Watt wird größer als das Fenster und rechts und unten abgeschnitten, denn das Fenster ist fest auf 460 mal 560 Pixel eingestellt. Du müsstest in main die window_size vergrößern (etwa auf (600.0, 700.0)) oder FELD kleiner machen.

2. Rust gibt eine gelbe Warnung aus: unused variable: `x`, mit dem Hinweis, man solle _x schreiben, falls das Absicht ist. Das Programm läuft trotzdem. Rust weist dich nur darauf hin, dass du einer Sache einen Namen gegeben hast, die du nie benutzt, denn oft ist so etwas ein Versehen.

3. Genau eine Zeile: die Konstante MEER. Weil die Farbe nur an dieser einen Stelle festgelegt ist, ändert sie sich überall, wo MEER verwendet wird. Genau dafür sind Konstanten da.

Etappe 1 geschafft 🏖️

Du hast ein Rust-Projekt angelegt, ein Fenster gebaut und mit zwei Schleifen ein ganzes Watt aus 81 Sandfeldern gelegt. In der nächsten Etappe bekommt das Spiel ein Gedächtnis, und die Felder lassen sich anklicken.

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

☕ Kaffee spendieren