Zum Inhalt
wannacode

Wie du über ein Spiel nachdenkst

Kurz gesagt: Ein Spiel besteht aus Zustand, Regeln, Eingabe, Schleife und Ende; Zufall mit Startwert macht es testbar, und die Logik steckt in Methoden, die einen Wert zurückgeben und sich testen lassen.

Problem

Wo fängt ein Spiel an?

Du willst ein Spiel schreiben. Zum Beispiel ein Würfelrennen: Eine Figur startet auf Feld 0, du würfelst, und sie rückt vor, bis sie Feld 20 erreicht.

Du öffnest den Editor und ... was schreibst du zuerst? Das Menü? Den Würfel? Das Spielbrett? Wenn du mit allem gleichzeitig anfängst, hast du nach einer Stunde viel Code, der immer noch nicht läuft.

Es hilft, dem Spiel fünf Fragen zu stellen, bevor du Code schreibst.

Die Idee

Fünf Fragen an jedes Spiel

  1. Zustand. Welche Zahlen beschreiben die Welt gerade? Im Rennen sind es das Feld der Figur und die Nummer des Zugs. Im Schach ist es die Position jeder Figur und wer am Zug ist.
  2. Regeln. Was ist erlaubt, und was passiert danach? „Die Figur rückt so viele Felder vor, wie der Würfel zeigt.“
  3. Eingabe. Was kommt von außen? Die Wahl des Spielers oder die Zahl auf dem Würfel.
  4. Schleife. Zug um Zug: Eingabe, Zustand aktualisieren, anzeigen. Und wieder von vorn.
  5. Ende. Wann ist das Spiel aus, und wer gewinnt? „Die Figur steht auf Feld 20.“

Die Antworten sind fast schon der fertige Code: Der Zustand sind Variablen, die Regeln sind Methoden, die Eingabe ist Scanner oder Random, die Schleife ist while oder for, und das Ende ist die Bedingung der Schleife.

Sieh es dir an

Zug um Zug

Random random = new Random(42);
int field = 0;
for (int turn = 1; field < 20; turn++) {
int roll = random.nextInt(6) + 1;
field = Math.min(field + roll, 20);
System.out.println("Zug " + turn + ": Wurf " + roll + ", Feld " + field);
}
System.out.println("Ziel! Die Figur hat Feld 20 erreicht.");
Schritt für Schritt, in Worten
  1. Ein Generator mit Startwert 42 und eine Figur auf Feld 0.
  2. Zug 1: Die Eingabe ist ein Wurf von 3, der Zustand wird zu Feld 3, der Bildschirm zeigt die Zeile.
  3. Zug 2: Die Eingabe ist ein Wurf von 4, der Zustand wird zu Feld 7, der Bildschirm zeigt die Zeile.
  4. Zug 3: Die Eingabe ist ein Wurf von 1, der Zustand wird zu Feld 8, der Bildschirm zeigt die Zeile.
  5. Zug 4: Die Eingabe ist ein Wurf von 3, der Zustand wird zu Feld 11, der Bildschirm zeigt die Zeile.
  6. Zug 5: Die Eingabe ist ein Wurf von 1, der Zustand wird zu Feld 12, der Bildschirm zeigt die Zeile.
  7. Zug 6: Die Eingabe ist ein Wurf von 2, der Zustand wird zu Feld 14, der Bildschirm zeigt die Zeile.
  8. Zug 7: Die Eingabe ist ein Wurf von 6, der Zustand wird zu Feld 20, der Bildschirm zeigt die Zeile. Feld 20: Die Bedingung field < 20 ist jetzt falsch.
  9. Die Schleife ist zu Ende, weil das Feld 20 erreicht hat. Das ist das Ende des Spiels.

Das ganze Spiel ist eine einzige Schleife. In jedem Zug kommt eine Eingabe (der Würfel), der Zustand wird aktualisiert (das neue Feld) und angezeigt (eine Zeile auf dem Bildschirm). Die Bedingung der Schleife ist das Ende: Solange das Feld unter 20 liegt, gibt es einen weiteren Zug.

Math.min hält die Figur auf dem Brett: Schickt der Würfel sie über 20 hinaus, bleibt sie auf 20 stehen.

Genauer, für Neugierige
  • Dieses Spiel ist rundenbasiert: Nichts passiert, bis die nächste Eingabe kommt. Schach, Kartenspiele und die meisten Spiele in diesem Abschnitt funktionieren so.
  • Echtzeitspiele (Shooter, Autorennen) haben ebenfalls eine Schleife, aber sie wartet nicht auf den Spieler. Sie läuft 60-mal pro Sekunde: Sie liest die Tasten, die gerade gedrückt sind, bewegt alles um die vergangene Zeit weiter und zeichnet das Bild. Auch wenn der Spieler nichts drückt, bewegt sich die Welt.

Die Idee

Gleicher Startwert, gleiches Spiel

Wenn die Würfe wirklich zufällig sind, ist jeder Lauf ein anderes Spiel. Wie prüfst du dann, ob das Spiel funktioniert? Du weißt nicht, was du erwarten sollst.

Deshalb bekommt Random in diesen Lektionen eine Startzahl, einen Startwert (englisch: Seed). Derselbe Startwert liefert jedes Mal dieselbe Folge:

Random first = new Random(42);
Random second = new Random(42);
for (int i = 0; i < 5; i++) {
int a = first.nextInt(6) + 1;
int b = second.nextInt(6) + 1;
System.out.println(a + " und " + b);
}
Ausgabe
3 und 3
4 und 4
1 und 1
3 und 3
1 und 1

Die beiden Generatoren mit 42 liefern dieselben Zahlen. Starte das Programm noch einmal, und sie sind wieder gleich. Deshalb haben die Beispiele und Aufgaben hier eine genaue Ausgabe, die sich prüfen lässt.

Sieh dir die Ausgabe auch in den anderen Tabs an. Jede Sprache hat ihren eigenen Generator: Aus demselben Startwert entstehen andere Zahlen. Deshalb haben Beispiele mit Zufallszahlen für jede Sprache eine eigene Ausgabe.

Genauer, für Neugierige
  • Wenn das Spiel für echte Spieler fertig ist, entfernst du den Startwert: new Random(). Dann wählt der Generator den Startwert selbst anhand der Uhr, und jedes Spiel ist anders.
  • Üblich ist, den Startwert am Anfang des Spiels auszugeben. Findet ein Spieler einen Fehler, kannst du mit seinem Startwert genau dasselbe Spiel nachspielen.

Die Idee

Zuerst die kleinste laufende Version

Das Programm aus der Szene ist ein ganzes Spiel in etwa zehn Zeilen. Es hat keinen zweiten Spieler, kein Menü und keine Regel außer „vorwärts“. Aber es läuft von Anfang bis Ende und zeigt, dass Schleife und Ende stimmen.

Von dort wächst das Spiel Regel für Regel. Die zweite Regel: Die Figur muss genau auf Feld 20 landen. Schickt der Würfel sie weiter, springt sie um so viele Felder zurück, wie sie darüber hinaus war. Von 17 mit einer 5 kommt sie auf 20 und geht zurück auf 18.

Eine neue Regel kommt erst dazu, wenn die vorige funktioniert. Geht etwas kaputt, weißt du: Es liegt an dem, was du zuletzt geschrieben hast.

Die Idee

Die Logik gibt zurück, der Bildschirm gibt aus

Die Regel „genau 20“ steckt in einer Methode, die das neue Feld zurückgibt. Das Ende ist eine Methode, die true oder false zurückgibt:

static int move(int field, int roll) {
int next = field + roll;
if (next > 20) {
return 20 - (next - 20);
}
return next;
}
static boolean isFinished(int field) {
return field == 20;
}
Random random = new Random(7);
int field = 0;
int turn = 0;
while (!isFinished(field)) {
turn++;
int roll = random.nextInt(6) + 1;
field = move(field, roll);
System.out.println("Zug " + turn + ": Wurf " + roll + ", Feld " + field);
}
System.out.println("Genau 20 nach " + turn + " Zügen");
Ausgabe
Zug 1: Wurf 5, Feld 5
Zug 2: Wurf 3, Feld 8
Zug 3: Wurf 4, Feld 12
Zug 4: Wurf 5, Feld 17
Zug 5: Wurf 5, Feld 18
Zug 6: Wurf 5, Feld 17
Zug 7: Wurf 5, Feld 18
Zug 8: Wurf 6, Feld 16
Zug 9: Wurf 1, Feld 17
Zug 10: Wurf 1, Feld 18
Zug 11: Wurf 1, Feld 19
Zug 12: Wurf 1, Feld 20
Genau 20 nach 12 Zügen

Weder move noch isFinished gibt etwas aus. Sie rechnen nur. Die Ausgabe steht in main.

Warum so? Eine Methode, die eine Zahl zurückgibt, kannst du mit beliebigen Werten aufrufen und das Ergebnis prüfen: move(17, 5) muss 18 sein. Eine Methode, die ausgibt, prüfst du nur mit den Augen. Und wenn das Spiel morgen von der Konsole in ein Fenster umzieht, bleiben die Regeln gleich; nur die Anzeige ändert sich.

Probier es aus

Wo landet die Figur?

Was gibt das Programm aus?

static int move(int field, int roll) {
int next = field + roll;
if (next > 20) {
return 20 - (next - 20);
}
return next;
}
static boolean isFinished(int field) {
return field == 20;
}
System.out.println(move(10, 4));
System.out.println(move(17, 3));
System.out.println(move(18, 5));
Antwort

Probier es aus

Finde den Fehler

Die Figur sollte vorrücken, aber das Programm gibt in jedem Zug Feld 0 aus:

Ausgabe
Zug 1: Wurf 5, Feld 0
Zug 2: Wurf 3, Feld 0
Zug 3: Wurf 4, Feld 0

In welcher Zeile steckt der Fehler?

Wähle die Zeile mit dem Fehler.

Probier es aus

Wann endet das Spiel?

Was fehlt, damit das Ende genau auf Feld 20 liegt?

static boolean isFinished(int field) {
    return field  20;
}

Übung

Drei kleine Spiele

Löse diese Aufgaben in deinem Editor. Die Logik steckt in Methoden, die einen Wert zurückgeben, und die Ausgabe steht in main.

1. Ein Rennen für zwei. Anna und Jonas würfeln abwechselnd, mit new Random(3). Es gilt die Regel „genau 20“, mit move aus der Lektion. Ihre Felder stehen in einem Array int[2]. Wer zuerst 20 erreicht, gewinnt.

Ausgabe
Anna: Wurf 3, Feld 3
Jonas: Wurf 3, Feld 3
Anna: Wurf 1, Feld 4
Jonas: Wurf 2, Feld 5
Anna: Wurf 1, Feld 5
Jonas: Wurf 1, Feld 6
Anna: Wurf 4, Feld 9
Jonas: Wurf 5, Feld 11
Anna: Wurf 2, Feld 11
Jonas: Wurf 2, Feld 13
Anna: Wurf 4, Feld 15
Jonas: Wurf 5, Feld 18
Anna: Wurf 6, Feld 19
Jonas: Wurf 5, Feld 17
Anna: Wurf 4, Feld 17
Jonas: Wurf 5, Feld 18
Anna: Wurf 1, Feld 18
Jonas: Wurf 4, Feld 18
Anna: Wurf 5, Feld 17
Jonas: Wurf 2, Feld 20
Jonas gewinnt

2. Zahl raten. Das Programm wählt heimlich eine Zahl von 1 bis 100 mit new Random(2026). Der Spieler tippt Vermutungen ein, eine pro Zeile. Nach jeder sagt das Programm „Höher“, „Niedriger“ oder „Richtig“. Am Ende sagt es, wie viele Versuche es gebraucht hat. Der Hinweis kommt aus einer Methode String hint(int guess, int secret), die den Text zurückgibt.

Eingabe
50
75
88
94
97
99
100
Ausgabe
50: Höher
75: Höher
88: Höher
94: Höher
97: Höher
99: Höher
100: Richtig
Erraten in 7 Versuchen

Die Eingabe ist in jeder Sprache anders, weil ihre Generatoren aus demselben Startwert eine andere Geheimzahl wählen.

3. Wer hat Tic-Tac-Toe gewonnen? Die erste Zeile ist die Anzahl der Bretter. Jedes Brett besteht aus drei Zeilen zu je drei Zeichen: X, O oder . für ein leeres Feld. Schreibe eine Methode char winner(char[][] board), die X, O oder . zurückgibt, wenn es keine drei in einer Zeile, Spalte oder Diagonale gibt.

Eingabe
3
XOX
OXO
OOX
XXO
OOO
X.X
XOX
XOO
OXX
Ausgabe
X gewinnt
O gewinnt
Kein Sieger

Weitere Übungen

  • Fallen. Feld 13 schickt die Figur zurück auf Feld 5. Füge die Regel zu move hinzu und lass das Rennen noch einmal laufen.
  • Startwert ausgeben. Mach den Startwert zu einer Variablen, gib ihn am Anfang aus und lass das Spiel mit drei verschiedenen Startwerten laufen.
  • Tic-Tac-Toe für zwei. Die Spieler tippen Zeile und Spalte ihres Zugs ein. Zeige nach jedem Zug das Brett und prüfe mit winner, ob das Spiel zu Ende ist.

Test

Teste dich

1. Was ist der Zustand des Würfelrennens?
2. Warum steht im Spiel new Random(42) und nicht new Random()?
3. Warum ist die Zugregel eine Methode, die das neue Feld zurückgibt, und keine, die ausgibt?
4. Was ist eine gute erste Version eines Spiels?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·