-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #41 from SilasBerger/feature/game-projekt-gbsl
Text Adventure Tutorial
- Loading branch information
Showing
17 changed files
with
586 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
content/material/Programmieren/Advanced-Python/02-Random.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Zufall | ||
Mit der Funktion `randint()` aus dem Modul `random` können wir ganz einfach Zufallszahlen erzeugen. | ||
|
||
Dazu müssen wir das Modul zuerst importieren, damit es in unserem Programm verfügbar wird[^1]. Das `random`-Modul enthält eine Vielzahl verschiedener Funktionen, wovon wir hier aber nur die Funktion `randint()` benötigen. Den entsprechenden Import formulieren wir also folgendermassen: | ||
```python showLineNumbers | ||
from random import randint | ||
``` | ||
|
||
Mit der Funktion `randint(x, y)` können wir nun zufällige Ganzzahlen im Bereich `x` bis `y` generieren. Zum Beispiel generiert folgender Code eine zufällige Ganzzahl von `0` bis `10` und gibt sie in der Kommandozeile aus: | ||
|
||
```python showLineNumbers | ||
from random import randint | ||
|
||
zufallszahl = randint(0, 10) | ||
print(zufallszahl) | ||
``` | ||
|
||
## Anwendungsmöglichkeiten | ||
Damit lassen sich bereits einige nützliche Zufallsprogramme schreiben. Eine kleine Auswahl an Ideen finden Sie hier. | ||
|
||
### Würfel | ||
```python showLineNumbers | ||
from random import randint | ||
|
||
augenzahl = randint(1, 6) | ||
print("Gewürfelt:", augenzahl) | ||
``` | ||
|
||
### Münzwurf-Generator | ||
```python showLineNumbers | ||
from random import randint | ||
|
||
wurf = randint(0, 1) | ||
if wurf == 0: | ||
print("Kopf") | ||
else: | ||
print("Zahl") | ||
``` | ||
|
||
### Zufällige Spielkarte | ||
_Hinweis: In diesem Beispiel werden zur Erläuterung [Kommentare](Kommentare) verwendet: Alles, was auf einer Zeile nach einem `#` steht, wird von Python ignoriert._ | ||
|
||
```python showLineNumbers | ||
from random import randint | ||
|
||
# Zufallszahl zwischen 0 und 3 für vier Farben | ||
farbe = randint(0, 3) | ||
if farbe == 0: | ||
farbe_name = "Karo" | ||
elif farbe == 1: | ||
farbe_name = "Herz" | ||
elif farbe == 2: | ||
farbe_name = "Pik" | ||
else: | ||
farbe_name = "Kreuz" | ||
|
||
# Zahlenwerte 2-10, Bube=11, Dame=12, König=13, As=14 | ||
wert = randint(2, 14) | ||
if wert == 11: | ||
wert_name = "Bube" | ||
elif wert == 12: | ||
wert_name = "Dame" | ||
elif wert == 13: | ||
wert_name = "König" | ||
elif wert == 14: | ||
wert_name = "As" | ||
else: | ||
wert_name = wert | ||
|
||
print(farbe_name, wert_name) | ||
``` | ||
|
||
[^1]: Das _Importieren_ kennen Sie bereits aus der Turtle-Zeit: `from turtle import *`. |
File renamed without changes.
File renamed without changes.
102 changes: 102 additions & 0 deletions
102
content/material/Programmieren/Advanced-Python/06-Module.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Module | ||
Je komplexer unsere Applikation wird, desto grösser wird auch die entsprechende Python-Datei. Irgendwann steht dort so viel Code drin, dass wir uns kaum mehr zurechtfinden. | ||
|
||
Da kommen **Module** ins Spiel: Sie erlauben es uns, ein Programm auf mehrere Python-Dateien zu verteilen. | ||
|
||
Für dieses Beispiel gehen wir davon aus, dass wir eine Datei `rechner.py` mit folgendem Code haben: | ||
```python showLineNumbers | ||
def plus(a, b): | ||
print(a + b) | ||
|
||
def minus(a, b): | ||
print(a - b) | ||
|
||
def mal(a, b): | ||
print(a * b) | ||
|
||
def durch(a, b): | ||
print(a / b) | ||
|
||
plus(10, 5) | ||
minus(10, 5) | ||
durch(10, 5) | ||
``` | ||
|
||
## Module erstellen | ||
Um unser Programm übersichtlicher zu gestalten, könnten wir nun beispielsweise die Funktionen `plus()` und `minus()` in eine Datei `strich_operationen.py` und die Funktionen `mal()` und `durch()` in eine Datei `punkt_operationen.py` verschieben. Diese beiden Dateien sehen also folgendermassen aus: | ||
|
||
**strich_operationen.py:** | ||
```python showLineNumbers | ||
def plus(a, b): | ||
print(a + b) | ||
|
||
def minus(a, b): | ||
print(a - b) | ||
``` | ||
|
||
**punkt_operationen.py:** | ||
```python showLineNumbers | ||
def mal(a, b): | ||
print(a * b) | ||
|
||
def durch(a, b): | ||
print(a / b) | ||
``` | ||
|
||
Damit haben wir zwei neue Module erstellt: `strich_operationen` und `punkt_operationen`. | ||
|
||
:::key[Module und Modulnamen] | ||
Jede Python-Datei ist auch ein Modul. Ihr Modulname entspricht ihrem Dateinamen ohne die `.py`-Endung. | ||
|
||
Sprich: Die Datei `strich_operationen.py` ist zugleich ein Modul namens `strich_operationen`, `punkt_operationen.py` ist zugleich ein Modul namens `punkt_operationen`. | ||
::: | ||
|
||
## Module verwenden | ||
Unser ursprüngliches Programm können wir nun wie folgt vereinfachen: | ||
|
||
**rechner.py:** | ||
```python showLineNumbers | ||
plus(10, 5) | ||
minus(10, 5) | ||
durch(10, 5) | ||
``` | ||
|
||
Doch etwas fehlt noch: Wir müssen Python noch mitteilen, dass wir die Funktionen aus den beiden neuen Modulen verwenden wollen. Das machen wir mit einem **Import**. | ||
|
||
Den _Import_ kennen Sie bereits aus der Turtle-Zeit: `from turtle import *`. Damit sagen wir Python, dass wir aus dem Turtle-Modul (`from turtle`) sämtliche Funktionen und Variablen importieren wollen (`import *`[^1]). Analog importieren wir die beiden Module `strich_operationen` und `punkt_operationen`: | ||
|
||
**rechner.py:** | ||
```python showLineNumbers | ||
from strich_operationen import * | ||
from punkt_operationen import * | ||
|
||
plus(10, 5) | ||
minus(10, 5) | ||
durch(10, 5) | ||
``` | ||
|
||
:::warning[Speicherort] | ||
Damit dieser Import funktioniert, müssen alle drei Dateien nebeneinander liegen (also im gleichen Ordner gespeichert sein)! | ||
::: | ||
|
||
Nun funktioniert unser Rechner auch wieder korrekt und verwendet die Funktionen aus unseren neuen Modulen. | ||
|
||
### Import optimieren | ||
Tatsächlich verwenden wir aus dem modul `punkt_operationen` eigentlich nur die Funktion `durch()`. Mit dem aktuellen Import auf Zeile `2` im vorherigen Code-Ausschnitt importieren wir aber alle Funktionen aus `punkt_operationen` — also auch die Funktion `mal()`, welche wir momentan gar nicht brauchen. | ||
|
||
In Python gehört es zur _Best Practice_ (empfohlene Vorgehensweise), dass wir nur die Dinge importieren, die wir auch wirklich benötigen. Das machen wir, indem wir den import nicht überall mit `import *` spezifizieren, sondern dort genau diejenigen Dinge auflisten, die wir benötigen. | ||
|
||
```python showLineNumbers | ||
from strich_operationen import plus, minus | ||
from punkt_operationen import durch | ||
|
||
plus(10, 5) | ||
minus(10, 5) | ||
durch(10, 5) | ||
``` | ||
|
||
:::insight[Äquivalenz] | ||
Da wir aus dem Modul `strich_operationen` alle Funktionen benötigen, ist der Import auf Zeile `1` in diesem spezifischen Fall gleichwertig mit `from strich_operationen import *`. | ||
::: | ||
|
||
[^1]: Das `*`-Zeichen bezeichnen wir in diesem Zusammenhang als _Wildcard_. Es bedeutet so viel wie "alles". |
88 changes: 88 additions & 0 deletions
88
content/material/Programmieren/Advanced-Python/07-Benutzereingaben-Reinigen.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# Sanitization: Benutzereingaben reinigen | ||
Schauen Sie sich folgendes Beispielprogramm an: | ||
|
||
```python showLineNumbers | ||
entscheidung = input("Möchten Sie Ihre Änderungen speichern? (ja/nein) ") | ||
|
||
if entscheidung == "ja": | ||
print("✅ Änderungen gespeichert") | ||
else: | ||
print("🚮 Alle Änderungen wurden verworfen") | ||
``` | ||
|
||
Das Programm funktioniert zwar, doch es gibt ein Problem in der Anwendung: | ||
|
||
``` | ||
Möchten Sie Ihre Änderungen speichern? (ja/nein) Ja | ||
🚮 Alle Änderungen wurden verworfen | ||
``` | ||
|
||
oder | ||
|
||
``` | ||
Möchten Sie Ihre Änderungen speichern? (ja/nein) ja | ||
🚮 Alle Änderungen wurden verworfen | ||
``` | ||
|
||
:::insight[Was ist hier passiert?] | ||
Der Wert der Variable `entscheidung` entspricht in diesen Fällen nicht genau dem String `"ja"`. Somit ist die Bedingung auf Zeile `3` `False` und wir führen den `else`-Fall auf Zeile `6` aus. | ||
::: | ||
|
||
Um dieses Problem zu umgehen und unser Programm benutzerfreundlicher zu machen, gibt es zwei Arten, auf die wir solche Benutzereingaben meistens zuerst anpassen, bevor wir sie mit einem erwarteten Wert vergleichen: | ||
1. **Die Eingabe wird zu Kleinbuchstaben verwandelt.** Wenn wir in den Vergleichswerten (hier Zeile `3`) ebenfalls nur Kleinbuchstaben verwenden, erreichen wir so den Effekt, dass die Gross- und Kleinschreibung in der Benutzereingabe "ignoriert" wird. | ||
2. **Die Eingabe wird _getrimmt_**. Alle Leerzeichen am Anfang und am Ende des Strings werden entfernt. | ||
|
||
Diesen Prozess des _Standardisierens_ von Benutzereingaben nennt man **Sanitization** (engl.: _to sanitize_, reinigen, desinfizieren). | ||
|
||
## String zu Kleinbuchstaben verwandeln | ||
Dazu rufen wir auf einem beliebigen String die Funktion[^1] `.lower()` auf. Der Rückgabewert[^2] ist ein identischer String mit nur Kleinbuchstaben: | ||
|
||
```python showLineNumbers | ||
wert = "HaLLo" | ||
wert = wert.lower() | ||
print(wert) | ||
``` | ||
|
||
produziert in der Kommandozeile die Ausgabe | ||
|
||
``` | ||
hallo | ||
``` | ||
|
||
## Leerzeichen am Anfang und am Ende entfernen | ||
Dazu rufen wir auf einem beliebigen String die Funktion[^1] `.strip()` auf. Der Rückgabewert[^2] ist ein identischer String ohne Leerzeichen am Anfang und am Ende: | ||
|
||
```python showLineNumbers | ||
wert = " hallo " | ||
wert = wert.strip() | ||
print(wert) | ||
``` | ||
|
||
produziert in der Kommandozeile die Ausgabe | ||
|
||
``` | ||
hallo | ||
``` | ||
|
||
## Das verbesserte Beispielprogramm | ||
Mit diesen beiden Veränderungen können wir unser Beispielprogramm nun benutzerfreundlicher machen: | ||
```python showLineNumbers | ||
entscheidung = input("Möchten Sie Ihre Änderungen speichern? (ja/nein) ") | ||
entscheidung = entscheidung.lower() | ||
entscheidung = entscheidung.strip() | ||
|
||
if entscheidung == "ja": | ||
print("✅ Änderungen gespeichert") | ||
else: | ||
print("🚮 Alle Änderungen wurden verworfen") | ||
``` | ||
|
||
Wichtig ist dabei nur, dass der Vergleichswert (der String `ja` auf Zeile `5`) selbst ebenfalls nur Kleinbuchstaben und keine Leerzeichen am Anfang und am Ende hat. | ||
|
||
``` | ||
Möchten Sie Ihre Änderungen speichern? (ja/nein) Ja | ||
✅ Änderungen gespeichert | ||
``` | ||
|
||
[^1]: Genaugenommen sprechen wir hier von einer Methode; siehe [OOP](OOP). | ||
[^2]: siehe [Rückgabewerte](Return). |
File renamed without changes.
File renamed without changes.
26 changes: 26 additions & 0 deletions
26
content/material/Programmieren/Text-Adventure-GYM2/01-Erste-Entscheidung.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Eine erste Entscheidung | ||
Unser Text Adventure beginnt ganz einfach: Wir heissen den Spieler willkommen und erklären ihm das Setting (Zeilen `1`-`3`). Bei Ihrem eigenen Spiel dürfen Sie hier ruhig etwas kreativer und ausführlicher werden. Mit gelungenem Storytelling versetzen Sie den Spieler im Handumdrehen mitten ins Geschehen Ihrer Spielwelt! | ||
|
||
Auf Zeile `5` setzen wir den Spieler anschliessend vor eine erste Entscheidung: Wohin willst du gehen? Links, rechts, oder geradeaus? Die entsprechende Texteingabe speichern wir in der Variable `auswahl`. | ||
|
||
Basierend auf dem Wert der Variable `auswahl` entscheiden wir anschliessend in der `if-elif-else`-Bedingung auf den Zeilen `6`-`13`, wie das Spiel weitergehen soll. Hat der Spieler beispielsweise `links` eingegeben, so bedeutet das für uns, dass er in der Spielwelt nach links gelaufen ist. Dort lassen wir ihn auf einen Fluss treffen (Zeilen `6`-`7`). Rechts findet er ein Rudel Wölfe, während geradeaus eine Riesenspinne auf ihn wartet. Gibt der Benutzer etwas anderes als genau eines der Worte `links`, `rechts` oder `geradeaus` ein, so lassen wir ihn wissen, dass diese Eingabe ungültig ist (Zeilen `12`-`13`). | ||
|
||
```python showLineNumbers | ||
print("Willkommen beim Schatzsuche-Spiel!") | ||
print("Du befindest dich in einem Wald. Du musst den versteckten Schatz finden.") | ||
print("Du stehst an einer Wegkreuzung. Du kannst nach links, rechts oder geradeaus gehen.") | ||
|
||
auswahl = input("In welche Richtung möchtest du gehen? (links/rechts/geradeaus): ") | ||
if auswahl == "links": | ||
print("Du stösst auf einen Fluss. Du kannst schwimmen oder eine Brücke suchen.") | ||
elif auswahl == "rechts": | ||
print("Du begegnest einem Rudel Wölfe. Du kannst entweder rennen oder versuchen, sie zu zähmen.") | ||
elif auswahl == "geradeaus": | ||
print("Du gehst geradeaus und triffst auf eine Riesenspinne.") | ||
else: | ||
print("Ungültige Wahl! Spiel vorbei!") | ||
``` | ||
|
||
Nach dieser ersten Entscheidung und dem Ausgeben der entsprechenden Konsequenz ist das Spiel fürs Erste auch bereits wieder vorbei. In den nächsten paar Schritten werden wir die Reise unseres Spielers aber noch etwas länger und spannender gestalten. Bleiben Sie also dran! | ||
|
||
--- |
51 changes: 51 additions & 0 deletions
51
content/material/Programmieren/Text-Adventure-GYM2/02-Funktionen.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# Was passiert am Fluss? | ||
Wenn der Spieler bei dieser ersten Entscheidung aus dem vorherigen Kapitel nach links geht, so trifft er auf einen Fluss. Aber was passiert denn nun an diesem Fluss? Das entscheiden wir in diesem Kapitel. | ||
|
||
Doch bevor wir zu sehr in die Story unseres Abenteurers eintauchen, sollten wir uns ein paar Gedanken zu unserem Code machen: Der wird jetzt nämlich langsam länger, und damit leider auch irgendwann unübersichtlich. Um das zu verhindern, definieren wir folgende Regel: für jede Szene gibt es eine eigene Funktion, die wir möglichst der Szene entsprechend benennen. Da wir in diesem Kapitel die Szene am Fluss beschreiben wollen, erstellen wir also eine neue Funktion `fluss()` (ab Zeile `1`). | ||
|
||
Innerhalb dieser neuen `fluss()`-Funktion machen wir nun wieder genau das gleiche wie in der Anfangsszene: Wir erzählen dem Spieler ein Stück der Geschichte (den Satz von wegen `Du stösst auf einen Fluss...` haben wir einfach in die neue Funktion verschoben), stellen ihn vor eine nächste Entscheidung, und setzen den Spielverlauf entsprechend fort (Zeilen `2-10`). | ||
|
||
Für die Entscheidung `schwimmen` definieren wir auf Zeile `5` ein erstes reguläres Spielende. Der Spieler wurde von einem Krokodil gefressen — das Spiel ist vorbei. Hier folgt also keine nächste Szene. | ||
|
||
Auf Zeile `10` behandeln wir wiederum den Fall, dass der Spieler eine Entscheidung eingetippt hat, die es gar nicht gibt. Das haben wir auch bereits bei der Anfangsszene gemacht, auf Zeile `24`. | ||
|
||
Die einzige Entscheidung, die den Spieler vorwärtsbringt, ist `brücke` — hier lassen wir ihn tatsächlich eine versteckte Brücke finden. Mit dem Satz auf Zeile `7` beginnt also eine nächste Szene. Gemäss unserer "Pro Szene eine Funktion"-Regel müsste dieser Satz also auch bereits wieder in eine eigene neue Funktion verschoben werden. Das erledigen wir aber in einem nächsten Schritt. | ||
|
||
:::insight[Iterative Entwicklung] | ||
In der Softwareentwicklung gibt es ein wichtiges Arbeitsprinzip: _First do it, then do it right._ | ||
|
||
Das Programmieren braucht viel mentale Kapazität. Erfahrene Programmiererinnen und Programmierer gehen deshalb am liebsten Schrittweise vor. Wir schreiben unseren Code zuerst einfach mal so, dass er läuft. In einem zweiten Schritt kümmern wir uns dann um die "Code-Qualität" — also um solche Dinge wie die "Pro Szene eine Funktion"-Regel. | ||
|
||
Deshalb lassen wir den Satz auf Zeile `7` vorerst einfach mal dort stehen, auch wenn der später dann noch in seine eigene Funktion verschoben werden muss. | ||
::: | ||
|
||
```python showLineNumbers {1-10,18} | ||
def fluss(): | ||
print("Du stösst auf einen Fluss. Du kannst schwimmen oder eine Brücke suchen.") | ||
auswahl = input("Was möchtest du tun? (schwimmen/brücke): ") | ||
if auswahl == "schwimmen": | ||
print("Du wurdest von einem Krokodil angegriffen! Spiel vorbei!") | ||
elif auswahl == "brücke": | ||
print("Du hast eine versteckte Brücke gefunden und den Fluss sicher überquert.") | ||
print("Du siehst eine Höhle vor dir.") | ||
else: | ||
print("Ungültige Wahl! Spiel vorbei!") | ||
|
||
print("Willkommen beim Schatzsuche-Spiel!") | ||
print("Du befindest dich in einem Wald. Du musst den versteckten Schatz finden.") | ||
print("Du stehst an einer Wegkreuzung. Du kannst nach links, rechts oder geradeaus gehen.") | ||
|
||
auswahl = input("In welche Richtung möchtest du gehen? (links/rechts/geradeaus): ") | ||
if auswahl == "links": | ||
fluss() | ||
elif auswahl == "rechts": | ||
print("Du begegnest einem Rudel Wölfe. Du kannst entweder rennen oder versuchen, sie zu zähmen.") | ||
elif auswahl == "geradeaus": | ||
print("Du gehst geradeaus und triffst auf eine Riesenspinne.") | ||
else: | ||
print("Ungültige Wahl! Spiel vorbei!") | ||
``` | ||
|
||
Damit haben Sie bereits das ganze Kernprinzip kennengelernt, nach welchen wir unser Text Adventure entwickeln. In den nächsten Schritten machen wir noch ein paar kleine Anpassungen und bauen die restlichen Szenen weiter aus. | ||
|
||
--- |
Oops, something went wrong.