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 Funktionen, 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 Funktionen, die Eingabe ist input() 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(42)
field = 0
turn = 1
while field < 20:
roll = random.randint(1, 6)
field = min(field + roll, 20)
print(f"Zug {turn}: Wurf {roll}, Feld {field}")
turn += 1
print("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. Die Zugzählung beginnt bei 1.
  2. Zug 1: Die Eingabe ist ein Wurf von 6, der Zustand wird zu Feld 6, der Bildschirm zeigt die Zeile.
  3. Zug 2: Die Eingabe ist ein Wurf von 1, 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 6, der Zustand wird zu Feld 14, der Bildschirm zeigt die Zeile.
  6. Zug 5: Die Eingabe ist ein Wurf von 3, der Zustand wird zu Feld 17, der Bildschirm zeigt die Zeile.
  7. Zug 6: Die Eingabe ist ein Wurf von 2, der Zustand wird zu Feld 19, der Bildschirm zeigt die Zeile.
  8. Zug 7: Die Eingabe ist ein Wurf von 2, der Zustand wird zu Feld 20, der Bildschirm zeigt die Zeile. Feld 20: Die Bedingung while 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.

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

Python hat kein for mit drei Teilen wie Java. Deshalb wird der Zug mit turn gezählt, und die Schleife ist while: solange das Feld unter 20 liegt.

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 ist der Generator in diesen Lektionen Random aus dem Modul random, und er bekommt eine Startzahl, einen Startwert (englisch: Seed). Derselbe Startwert liefert jedes Mal dieselbe Folge. randint(1, 6) liefert eine ganze Zahl von 1 bis 6, beide eingeschlossen:

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: Random(). Dann wählt der Generator den Startwert selbst, 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 Funktion, die das neue Feld zurückgibt. Das Ende ist eine Funktion, 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 is_finished gibt etwas aus. Sie rechnen nur. Die Ausgabe steht in dem Code unter den Funktionen.

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

Das neue Feld heißt in move new_field und nicht next: In Python ist next der Name einer eingebauten Funktion, und es ist besser, sie nicht zu überdecken.

Probier es aus

Wo landet die Figur?

Was gibt das Programm aus?

def move(field, roll):
new_field = field + roll
if new_field > 20:
return 20 - (new_field - 20)
return new_field
def is_finished(field):
return field == 20
print(move(10, 4))
print(move(17, 3))
print(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 3, Feld 0
Zug 2: Wurf 2, 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?

def is_finished(field):
    return field  20

Übung

Drei kleine Spiele

Löse diese Aufgaben in deinem Editor. Die Logik steckt in Funktionen, die einen Wert zurückgeben, und die Ausgabe steht in dem Code darunter.

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

Ausgabe
Anna: Wurf 2, Feld 2
Jonas: Wurf 5, Feld 5
Anna: Wurf 5, Feld 7
Jonas: Wurf 2, Feld 7
Anna: Wurf 3, Feld 10
Jonas: Wurf 5, Feld 12
Anna: Wurf 4, Feld 14
Jonas: Wurf 6, Feld 18
Anna: Wurf 5, Feld 19
Jonas: Wurf 1, Feld 19
Anna: Wurf 5, Feld 16
Jonas: Wurf 1, Feld 20
Jonas gewinnt

2. Zahl raten. Das Programm wählt heimlich eine Zahl von 1 bis 100 mit 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 Funktion hint(guess, secret), die den Text zurückgibt.

Eingabe
50
25
12
18
15
16
Ausgabe
50: Niedriger
25: Niedriger
12: Höher
18: Niedriger
15: Höher
16: Richtig
Erraten in 6 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 Funktion winner(board), die X, O oder . zurückgibt, wenn es keine drei in einer Zeile, Spalte oder Diagonale gibt.

Das Brett kann eine Liste aus den drei eingelesenen Zeilen sein: Jede Zeile ist ein Text, und board[r][c] ist das Zeichen in Zeile r, Spalte c. Python hat keinen eigenen Typ für ein einzelnes Zeichen wie char in Java: Ein Zeichen ist ein Text der Länge 1.

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 Random(42) und nicht Random()?
3. Warum ist die Zugregel eine Funktion, 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 ·