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
- 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.
- Regeln. Was ist erlaubt, und was passiert danach? „Die Figur rückt so viele Felder vor, wie der Würfel zeigt.“
- Eingabe. Was kommt von außen? Die Wahl des Spielers oder die Zahl auf dem Würfel.
- Schleife. Zug um Zug: Eingabe, Zustand aktualisieren, anzeigen. Und wieder von vorn.
- 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 = 0turn = 1while field < 20: roll = random.randint(1, 6) field = min(field + roll, 20) print(f"Zug {turn}: Wurf {roll}, Feld {field}") turn += 1print("Ziel! Die Figur hat Feld 20 erreicht.")Schritt für Schritt, in Worten
- Ein Generator mit Startwert 42 und eine Figur auf Feld 0. Die Zugzählung beginnt bei 1.
- Zug 1: Die Eingabe ist ein Wurf von 6, der Zustand wird zu Feld 6, der Bildschirm zeigt die Zeile.
- Zug 2: Die Eingabe ist ein Wurf von 1, der Zustand wird zu Feld 7, der Bildschirm zeigt die Zeile.
- Zug 3: Die Eingabe ist ein Wurf von 1, der Zustand wird zu Feld 8, der Bildschirm zeigt die Zeile.
- Zug 4: Die Eingabe ist ein Wurf von 6, der Zustand wird zu Feld 14, der Bildschirm zeigt die Zeile.
- Zug 5: Die Eingabe ist ein Wurf von 3, der Zustand wird zu Feld 17, der Bildschirm zeigt die Zeile.
- Zug 6: Die Eingabe ist ein Wurf von 2, der Zustand wird zu Feld 19, der Bildschirm zeigt die Zeile.
- 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.
- 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);}3 und 34 und 41 und 13 und 31 und 1Random first = new Random(42);Random second = new Random(42);for (int i = 0; i < 5; i++){ int a = first.Next(1, 7); int b = second.Next(1, 7); Console.WriteLine(a + " und " + b);}5 und 51 und 11 und 14 und 42 und 2from random import Random
first = Random(42)second = Random(42)for i in range(5): a = first.randint(1, 6) b = second.randint(1, 6) print(f"{a} und {b}")6 und 61 und 11 und 16 und 63 und 3function makeRandom(seed) { return () => { seed = (seed + 0x9e3779b9) | 0; let z = seed; z = Math.imul(z ^ (z >>> 16), 0x85ebca6b); z = Math.imul(z ^ (z >>> 13), 0xc2b2ae35); return ((z ^ (z >>> 16)) >>> 0) / 4294967296; };}
const first = makeRandom(42);const second = makeRandom(42);for (let i = 0; i < 5; i++) { const a = Math.floor(first() * 6) + 1; const b = Math.floor(second() * 6) + 1; console.log(`${a} und ${b}`);}2 und 26 und 61 und 15 und 51 und 1Die 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");Zug 1: Wurf 5, Feld 5Zug 2: Wurf 3, Feld 8Zug 3: Wurf 4, Feld 12Zug 4: Wurf 5, Feld 17Zug 5: Wurf 5, Feld 18Zug 6: Wurf 5, Feld 17Zug 7: Wurf 5, Feld 18Zug 8: Wurf 6, Feld 16Zug 9: Wurf 1, Feld 17Zug 10: Wurf 1, Feld 18Zug 11: Wurf 1, Feld 19Zug 12: Wurf 1, Feld 20Genau 20 nach 12 Zügenstatic int Move(int field, int roll){ int next = field + roll; if (next > 20) { return 20 - (next - 20); } return next;}
static bool IsFinished(int field){ return field == 20;}
Random random = new Random(7);int field = 0;int turn = 0;while (!IsFinished(field)){ turn++; int roll = random.Next(1, 7); field = Move(field, roll); Console.WriteLine("Zug " + turn + ": Wurf " + roll + ", Feld " + field);}Console.WriteLine("Genau 20 nach " + turn + " Zügen");Zug 1: Wurf 3, Feld 3Zug 2: Wurf 6, Feld 9Zug 3: Wurf 4, Feld 13Zug 4: Wurf 1, Feld 14Zug 5: Wurf 3, Feld 17Zug 6: Wurf 5, Feld 18Zug 7: Wurf 1, Feld 19Zug 8: Wurf 6, Feld 15Zug 9: Wurf 6, Feld 19Zug 10: Wurf 6, Feld 15Zug 11: Wurf 3, Feld 18Zug 12: Wurf 6, Feld 16Zug 13: Wurf 3, Feld 19Zug 14: Wurf 1, Feld 20Genau 20 nach 14 Zügendef 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
random = Random(7)field = 0turn = 0while not is_finished(field): turn += 1 roll = random.randint(1, 6) field = move(field, roll) print(f"Zug {turn}: Wurf {roll}, Feld {field}")print(f"Genau 20 nach {turn} Zügen")Zug 1: Wurf 3, Feld 3Zug 2: Wurf 2, Feld 5Zug 3: Wurf 4, Feld 9Zug 4: Wurf 6, Feld 15Zug 5: Wurf 1, Feld 16Zug 6: Wurf 1, Feld 17Zug 7: Wurf 5, Feld 18Zug 8: Wurf 1, Feld 19Zug 9: Wurf 3, Feld 18Zug 10: Wurf 5, Feld 17Zug 11: Wurf 1, Feld 18Zug 12: Wurf 5, Feld 17Zug 13: Wurf 2, Feld 19Zug 14: Wurf 1, Feld 20Genau 20 nach 14 Zügenfunction move(field, roll) { const next = field + roll; if (next > 20) { return 20 - (next - 20); } return next;}
function isFinished(field) { return field === 20;}
const random = makeRandom(7);let field = 0;let turn = 0;while (!isFinished(field)) { turn++; const roll = Math.floor(random() * 6) + 1; field = move(field, roll); console.log(`Zug ${turn}: Wurf ${roll}, Feld ${field}`);}console.log(`Genau 20 nach ${turn} Zügen`);Zug 1: Wurf 1, Feld 1Zug 2: Wurf 3, Feld 4Zug 3: Wurf 6, Feld 10Zug 4: Wurf 4, Feld 14Zug 5: Wurf 3, Feld 17Zug 6: Wurf 2, Feld 19Zug 7: Wurf 5, Feld 16Zug 8: Wurf 2, Feld 18Zug 9: Wurf 2, Feld 20Genau 20 nach 9 ZügenWeder 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))10 + 4 = 14. 17 + 3 = 20, genau. 18 + 5 = 23 liegt 3 über 20, also springt die Figur auf 20 − 3 = 17 zurück.
Probier es aus
Finde den Fehler
Die Figur sollte vorrücken, aber das Programm gibt in jedem Zug Feld 0 aus:
Zug 1: Wurf 3, Feld 0Zug 2: Wurf 2, Feld 0Zug 3: Wurf 4, Feld 0In welcher Zeile steckt der Fehler?
Wähle die Zeile mit dem Fehler.
move gibt das neue Feld zurück, aber das Ergebnis wird weggeworfen. Die Funktion kann das field des Aufrufers nicht ändern: Sie bekommt nur den Wert 0, nicht das field des Aufrufers selbst. Du brauchst field = move(field, roll). Steht eine Zeile wie diese in while not is_finished(field):, endet das Spiel nie.Probier es aus
Wann endet das Spiel?
Was fehlt, damit das Ende genau auf Feld 20 liegt?
def is_finished(field):
return field 20== vergleicht. Ein einzelnes = ist eine Zuweisung, und Python bricht hier schon vor dem Start mit SyntaxError ab. >= funktioniert ebenfalls, weil move nie mehr als 20 zurückgibt, aber == sagt die Regel klarer: Das Ende liegt genau auf 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.
Anna: Wurf 2, Feld 2Jonas: Wurf 5, Feld 5Anna: Wurf 5, Feld 7Jonas: Wurf 2, Feld 7Anna: Wurf 3, Feld 10Jonas: Wurf 5, Feld 12Anna: Wurf 4, Feld 14Jonas: Wurf 6, Feld 18Anna: Wurf 5, Feld 19Jonas: Wurf 1, Feld 19Anna: Wurf 5, Feld 16Jonas: Wurf 1, Feld 20Jonas gewinnt2. 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.
50251218151650: Niedriger25: Niedriger12: Höher18: Niedriger15: Höher16: RichtigErraten in 6 VersuchenDie 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.
3XOXOXOOOXXXOOOOX.XXOXXOOOXXX gewinntO gewinntKein SiegerWeitere Übungen
- Fallen. Feld 13 schickt die Figur zurück auf Feld 5. Füge die Regel zu
movehinzu 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
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Zweidimensionale Arrays · Strings: mit Text arbeiten · Funktionen · Parameter und return
- Aufgaben zum Thema: Wir bauen ein Spiel