Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
lehrkraefte:snr:informatik:glf22:python:snake [2023/01/27 00:41] Olaf Schnürer [Aufgabe: Listen in Python] |
lehrkraefte:snr:informatik:glf22:python:snake [2023/02/14 20:01] (current) Olaf Schnürer [Richtung Snake: Listen in Python] |
||
---|---|---|---|
Line 1: | Line 1: | ||
~~NOTOC~~ | ~~NOTOC~~ | ||
+ | |||
+ | Online programmieren über dieses trinket... funktioniert nicht sinnvoll: Tastendrücke werden nicht (oder sehr spät) erkannt, f-strings klappen nicht (was nicht so schlimm wäre), Grösse des Spielfeldes muss man anpassen, damit alles angezeigt wird. Letzteres Problem auch bei manch kleinem Laptop-Bildschirm. | ||
==== Eventuelle Nachträge ==== | ==== Eventuelle Nachträge ==== | ||
Line 142: | Line 144: | ||
</ | </ | ||
- | ===== Aufgabe: Listen in Python ===== | + | ===== Richtung Snake: Listen in Python ===== |
- | <WRAP center round todo> | + | <WRAP center round info box> |
Um Snake zu programmieren, | Um Snake zu programmieren, | ||
- | Aufgabe: Beginne, den Abschnitt [[lehrkraefte: | + | In gewisser Weise kommen wir hier an's Ende des Programmierkurses: Neben den Kontrollstrukturen (if-statements für Bedingungen, for- und while-Schleifen für Wiederholungen, |
</ | </ | ||
+ | |||
+ | Statt auf den Abschnitt [[lehrkraefte: | ||
<hidden Das hier verborgene Programm enthält alles über Listen, was in Snake benötigt wird> | <hidden Das hier verborgene Programm enthält alles über Listen, was in Snake benötigt wird> | ||
+ | Per Python-Shell erklären oder direkt das Programm laufen lassen, am besten mit Breakpoint ganz am Anfang. | ||
<code python listen-demo.py> | <code python listen-demo.py> | ||
# Beispiel 1: | # Beispiel 1: | ||
- | # Top 5 der Vornamen in der italienschen | + | # Top 5 der Vornamen in der italienischen |
- | namens_liste | + | namensliste |
- | print(namens_liste) | + | print(namensliste) |
- | print(namens_liste[3]) | + | print(namensliste[3]) |
- | print(namens_liste[0]) | + | print(namensliste[0]) |
- | print(" | + | namensliste[3] = ' |
- | print(" | + | print(namensliste) |
- | print(len(namens_liste)) | + | print(" |
- | for name in namens_liste: | + | print(" |
+ | print(len(namensliste)) | ||
+ | for name in namensliste: | ||
print(' | print(' | ||
- | print(len(namens_liste)) | + | namensliste.insert(0, ' |
- | namens_liste.insert(0, 'Nathan') | + | print(namensliste) |
- | namens_liste.pop() | + | namensliste.insert(3, 'Ephraim') |
- | print(namens_liste) | + | print(namensliste) |
- | print(namens_liste[2:6]) | + | namensliste.pop() |
+ | print(namensliste) | ||
+ | namensliste.pop(2) | ||
+ | print(namensliste) | ||
+ | print(namensliste[1:3]) | ||
# Beispiel 2: | # Beispiel 2: | ||
Line 174: | Line 185: | ||
for note in notenliste: | for note in notenliste: | ||
summe = summe + note | summe = summe + note | ||
- | print(f' | + | print(summe / len(notenliste)) |
# Beispiel 3: | # Beispiel 3: | ||
Line 181: | Line 192: | ||
while liste_der_wuerfe[0: | while liste_der_wuerfe[0: | ||
liste_der_wuerfe.insert(0, | liste_der_wuerfe.insert(0, | ||
- | print(liste_der_wuerfe) | + | print(liste_der_wuerfe) |
</ | </ | ||
</ | </ | ||
- | Ausserdem sollte man verstehen, warum eine Liste die naheliegende Datenstruktur für Snake ist. | + | ---- |
+ | Ausserdem sollte man verstehen, warum eine Liste die naheliegende Datenstruktur für Snake ist (verbale Erklärung mit Snake-Beispielprogramm, | ||
+ | |||
+ | <hidden Tafelfoto dazu> | ||
+ | {{lehrkraefte: | ||
+ | </ | ||
===== Aufgabe: Ein Snake-Programm in Grundzügen verstehen ===== | ===== Aufgabe: Ein Snake-Programm in Grundzügen verstehen ===== | ||
<WRAP center round todo> | <WRAP center round todo> | ||
- | Hist ist ein Link zu einem in '' | + | Hist ist ein Link zu einem in '' |
+ | |||
+ | (Zusammen die per ''## | ||
* [[lehrkraefte: | * [[lehrkraefte: | ||
Line 199: | Line 217: | ||
* Pro gefressenem Apfel soll die Länge der Schlange um eins wachsen. | * Pro gefressenem Apfel soll die Länge der Schlange um eins wachsen. | ||
* Wenn die Schlange gegen eine der vier Wände des Spielfelds läuft, endet das Spiel. | * Wenn die Schlange gegen eine der vier Wände des Spielfelds läuft, endet das Spiel. | ||
+ | * Färbe den Kopf der Schlange in einer anderen Farbe, damit man weiss, wo er ist. | ||
Hinweis: Es sind nur wenige Änderungen innnerhalb der "game loop" (ab Zeile 171) nötig. | Hinweis: Es sind nur wenige Änderungen innnerhalb der "game loop" (ab Zeile 171) nötig. | ||
+ | |||
+ | <hidden Für diejenigen, die Online programmieren (da dort f-strings wohl nicht funktionieren; | ||
+ | <code python snake-zu-verbessern-fuer-online> | ||
+ | import pygame | ||
+ | from pygame.locals import * | ||
+ | from random import * | ||
+ | |||
+ | # Geschwindigkeit des Spiels bzw. genauer | ||
+ | # Frequenz, mit der das Spielfeld neu gezeichnet wird. | ||
+ | FRAMES_PER_SECOND_AM_ANFANG = 7 | ||
+ | VERGROESSERUNG_PRO_APFEL = 0.5 | ||
+ | |||
+ | # Das Spielfeld besteht aus kleinen rechteckigen Boxen mit Koordinaten | ||
+ | # 0 bis MAXX (jeweils einschliesslich) in x-Richtung und | ||
+ | # 0 bis MAXY (jeweils einschliesslich) in y-Richtung. | ||
+ | # Achtung: y-Achse zeigt nach unten (wie Zeilennummern) | ||
+ | MAXX = 24 | ||
+ | MAXY = 15 | ||
+ | |||
+ | # Festlegung von Breite und Höhe einer Box in Pixel. | ||
+ | # Meist sind Breite und Höhe gleich, was quadratische Boxen liefert. | ||
+ | SEITENLAENGE_BOX = 40 | ||
+ | |||
+ | # Es geht auch mit Bildern (Hintergrund, | ||
+ | # und Sound (Crash bzw. Game over). | ||
+ | # Suche dafür geeignete Dateien und speichere diese am besten | ||
+ | # in demselben Verzeichnis wie dieses Programm. | ||
+ | # In der Funktion '' | ||
+ | # dann die Namen deiner Dateien (evtl. inklusive Pfad) angeben. | ||
+ | |||
+ | BOXEN_STATT_BILDER = True | ||
+ | EINFARBIGER_HINTERGRUND = True | ||
+ | MIT_SOUND = False | ||
+ | |||
+ | # Ab hier Konstanten, die von den obigen abhängen. | ||
+ | ANZAHL_BOXEN_X = MAXX + 1 | ||
+ | ANZAHL_BOXEN_Y = MAXY + 1 | ||
+ | |||
+ | FENSTER_BREITE = ANZAHL_BOXEN_X * SEITENLAENGE_BOX | ||
+ | FENSTER_HOEHE = ANZAHL_BOXEN_Y * SEITENLAENGE_BOX | ||
+ | PLATZ_FUER_TEXT = 5 * SEITENLAENGE_BOX | ||
+ | |||
+ | # Farben per Rot-, Grün- und Blauwert | ||
+ | # (jeweils auf Skala von 0 bis 255). | ||
+ | # Wer will, kann hier zusätzliche eigene Farben festlegen. | ||
+ | # | ||
+ | ROT | ||
+ | GRUEN | ||
+ | BLAU = ( 0, 0, 255) | ||
+ | GELB = (255, 255, 0) | ||
+ | MAGENTA = (255, 0, 255) | ||
+ | CYAN = ( 0, 255, 255) | ||
+ | WEISS | ||
+ | SCHWARZ = ( 0, 0, 0) | ||
+ | HELLGRAU = | ||
+ | DUNKELGRAU = (160, 160, 160) | ||
+ | HINTERGRUND_FARBE = SCHWARZ | ||
+ | # HINTERGRUND_FARBE = HELLGRAU | ||
+ | |||
+ | # | ||
+ | # Definition der Klasse Punkt. Bitte einfach akzeptieren. | ||
+ | # | ||
+ | class Punkt: | ||
+ | def __init__(self, | ||
+ | self.x = x | ||
+ | self.y = y | ||
+ | |||
+ | def __str__(self): | ||
+ | return ' | ||
+ | |||
+ | def __repr__(self): | ||
+ | return ' | ||
+ | |||
+ | def __eq__(self, | ||
+ | return self.x == other.x and self.y == other.y | ||
+ | |||
+ | def __add__(self, | ||
+ | return Punkt(self.x + other.x, self.y + other.y) | ||
+ | |||
+ | def __hash__(self): | ||
+ | return hash(str(self)) | ||
+ | # | ||
+ | # Ende der Definition der Klasse Punkt. | ||
+ | # | ||
+ | |||
+ | def zeichne_box(p, | ||
+ | rechteck = pygame.Rect(p.x * SEITENLAENGE_BOX + 1, p.y * SEITENLAENGE_BOX + 1, SEITENLAENGE_BOX - 1, SEITENLAENGE_BOX - 1) | ||
+ | pygame.draw.rect(leinwand, | ||
+ | |||
+ | def zeichne_gitter(): | ||
+ | for x in range(ANZAHL_BOXEN_X + 1): | ||
+ | pygame.draw.line(leinwand, | ||
+ | for y in range(ANZAHL_BOXEN_Y + 1): | ||
+ | pygame.draw.line(leinwand, | ||
+ | |||
+ | def schreibe(x, y, text, groesse): | ||
+ | schrift = pygame.font.SysFont(' | ||
+ | formatierter_text = schrift.render(text, | ||
+ | rechteck = formatierter_text.get_rect() | ||
+ | rechteck.center = (round((x + 0.5) * SEITENLAENGE_BOX), | ||
+ | leinwand.blit(formatierter_text, | ||
+ | |||
+ | def schreibe_transparent(x, | ||
+ | schrift = pygame.font.SysFont(' | ||
+ | formatierter_text = schrift.render(text, | ||
+ | rechteck = formatierter_text.get_rect() | ||
+ | rechteck.center = (round((x + 0.5) * SEITENLAENGE_BOX), | ||
+ | if rechteck.x >= 0 and rechteck.y >= 0: | ||
+ | text_flaeche = pygame.Surface((rechteck.x, | ||
+ | text_flaeche.fill(WEISS) | ||
+ | text_flaeche.blit(formatierter_text, | ||
+ | text_flaeche.set_alpha(50) | ||
+ | leinwand.blit(formatierter_text, | ||
+ | |||
+ | def neue_apfelposition(): | ||
+ | a = Punkt(randrange(0, | ||
+ | while a in schlange: | ||
+ | a = Punkt(randrange(0, | ||
+ | return a | ||
+ | |||
+ | def lade_bilder_und_sound(): | ||
+ | global hintergrundbild, | ||
+ | |||
+ | if not EINFARBIGER_HINTERGRUND: | ||
+ | hintergrundbild = pygame.image.load(' | ||
+ | hintergrundbild = pygame.transform.scale(hintergrundbild, | ||
+ | |||
+ | if not BOXEN_STATT_BILDER: | ||
+ | bild_apfel = pygame.image.load(' | ||
+ | bild_apfel = pygame.transform.scale(bild_apfel, | ||
+ | bild_kopf_der_schlange = pygame.image.load(' | ||
+ | bild_kopf_der_schlange = pygame.transform.scale(bild_kopf_der_schlange, | ||
+ | |||
+ | if MIT_SOUND: | ||
+ | crash_sound = pygame.mixer.Sound(' | ||
+ | apfel_ess_sound = pygame.mixer.Sound(' | ||
+ | |||
+ | def zeige_bild(bild, | ||
+ | rechteck = pygame.Rect(p.x * SEITENLAENGE_BOX + 1, p.y * SEITENLAENGE_BOX + 1, SEITENLAENGE_BOX - 1, SEITENLAENGE_BOX - 1) | ||
+ | leinwand.blit(bild, | ||
+ | |||
+ | pygame.init() | ||
+ | uhr = pygame.time.Clock() | ||
+ | leinwand = pygame.display.set_mode((FENSTER_BREITE + 1, FENSTER_HOEHE + 1 + PLATZ_FUER_TEXT)) | ||
+ | pygame.display.set_caption(' | ||
+ | lade_bilder_und_sound() | ||
+ | |||
+ | # Initialisiere die Liste, deren Einträge die Koordinaten der Quadrate der Schlange sind. | ||
+ | # Der 0-te Eintrag enhält die Position des Kopfes der Schlange. | ||
+ | laenge = 4 | ||
+ | #I schlange = [Punkt(4, 7), Punkt(3, 7), Punkt(2, 7), Punkt(1, 7)] | ||
+ | # Oder besser, da abhängig von der Variablen " | ||
+ | schlange = [] | ||
+ | for i in range(laenge): | ||
+ | schlange.insert(0, | ||
+ | # Und noch besser bzw. kürzer geht das so: | ||
+ | # schlange = [Punkt(laenge - i, MAXY // 2) for i in range(laenge)] | ||
+ | farbe_schlange = GRUEN | ||
+ | |||
+ | # Änderung der Spielerposition pro Spielzyklus: | ||
+ | bewegungsrichtung = Punkt(0, 0) | ||
+ | |||
+ | # Hilfsvariablen, | ||
+ | neue_richtung = bewegungsrichtung | ||
+ | richtung_vor_stopp = Punkt(0, 0) | ||
+ | |||
+ | # Initialisiere Apfelposition (nicht auf Schlange!) und Farbe. | ||
+ | apfel = neue_apfelposition() | ||
+ | farbe_apfel = ROT | ||
+ | |||
+ | # Initialisierung der " | ||
+ | frames_per_second = FRAMES_PER_SECOND_AM_ANFANG | ||
+ | spiel_aktiv = True | ||
+ | crash = False | ||
+ | gefressene_aepfel = 0 | ||
+ | |||
+ | # Hier startet die "game loop". | ||
+ | while spiel_aktiv: | ||
+ | # Graphische Darstellung des Spielgeschehens: | ||
+ | |||
+ | # Hintergrund | ||
+ | if EINFARBIGER_HINTERGRUND: | ||
+ | leinwand.fill(HINTERGRUND_FARBE) | ||
+ | else: | ||
+ | leinwand.fill(HINTERGRUND_FARBE) | ||
+ | leinwand.blit(hintergrundbild, | ||
+ | |||
+ | # Ausgabe diverser Zahlen unter dem Spielfeld | ||
+ | schreibe(MAXX // 4, MAXY + 1, 'apple count: ' + str(gefressene_aepfel), | ||
+ | schreibe(3 * MAXX // 4, MAXY + 1, ' | ||
+ | schreibe(MAXX // 4, MAXY + 2, ' | ||
+ | schreibe(3 * MAXX // 4, MAXY + 2, ' | ||
+ | schreibe(MAXX // 4, MAXY + 3, 'head of snake: ' + str(schlange[0]), | ||
+ | schreibe(3 * MAXX // 4, MAXY + 3, ' | ||
+ | text = str(schlange) | ||
+ | schreibe(MAXX // 2, MAXY + 4, text, 0.6) | ||
+ | schreibe(MAXX // 2, MAXY + 5, 'Space key: pause game', 0.8) | ||
+ | |||
+ | # Raster zeichnen | ||
+ | zeichne_gitter() | ||
+ | |||
+ | # Apfel zeichnen | ||
+ | if BOXEN_STATT_BILDER: | ||
+ | zeichne_box(apfel, | ||
+ | schreibe_transparent(apfel.x, | ||
+ | else: | ||
+ | zeige_bild(bild_apfel, | ||
+ | schreibe_transparent(apfel.x, | ||
+ | |||
+ | # Schlange zeichnen | ||
+ | for element in schlange: | ||
+ | zeichne_box(element, | ||
+ | schreibe_transparent(element.x, | ||
+ | if not BOXEN_STATT_BILDER: | ||
+ | zeige_bild(bild_kopf_der_schlange, | ||
+ | schreibe_transparent(schlange[0].x, | ||
+ | |||
+ | # Alles bis jetzt " | ||
+ | pygame.display.update() | ||
+ | uhr.tick(frames_per_second) | ||
+ | |||
+ | # Verarbeitung von Tastatureingaben: | ||
+ | for ereignis in pygame.event.get(): | ||
+ | if ereignis.type == QUIT: | ||
+ | print(" | ||
+ | spiel_aktiv = False | ||
+ | elif ereignis.type == KEYDOWN: | ||
+ | if ereignis.key == K_ESCAPE: | ||
+ | print(" | ||
+ | spiel_aktiv = False | ||
+ | elif ereignis.key == K_q: | ||
+ | print(" | ||
+ | spiel_aktiv = False | ||
+ | elif ereignis.key == K_SPACE: | ||
+ | print(" | ||
+ | if bewegungsrichtung != Punkt(0, 0): | ||
+ | richtung_vor_stopp = bewegungsrichtung | ||
+ | bewegungsrichtung = Punkt(0, 0) | ||
+ | else: | ||
+ | bewegungsrichtung =richtung_vor_stopp | ||
+ | else: | ||
+ | if ereignis.key == K_r: | ||
+ | print(" | ||
+ | neue_richtung = Punkt(-1, -1) | ||
+ | elif ereignis.key == K_f: | ||
+ | print(" | ||
+ | neue_richtung = Punkt(-1, 1) | ||
+ | elif ereignis.key == K_t: | ||
+ | print(" | ||
+ | neue_richtung = Punkt(1, -1) | ||
+ | elif ereignis.key == K_g: | ||
+ | print(" | ||
+ | neue_richtung = Punkt(1, 1) | ||
+ | # Die folgende if-Bedingung verhindert, dass die | ||
+ | # Snake die Richtung umkehren kann (und mit sich selbst kollidiert). | ||
+ | if schlange[1] != schlange[0] + neue_richtung: | ||
+ | bewegungsrichtung = neue_richtung | ||
+ | |||
+ | # Bewegen der Schlange: | ||
+ | if bewegungsrichtung != Punkt(0, 0): | ||
+ | # Falls die Schlange die gewünschte Länge hat: | ||
+ | # Beseitige das letzte Element aus der Liste " | ||
+ | if len(schlange) == laenge: | ||
+ | schlange.pop() | ||
+ | |||
+ | # Neues Feld, auf das sich die Schlange bewegt: | ||
+ | neue_position = schlange[0] + bewegungsrichtung | ||
+ | |||
+ | # Verhalten am Spielfeldrand: | ||
+ | if neue_position.x < 0: | ||
+ | neue_position.x = MAXX | ||
+ | if neue_position.x > MAXX: | ||
+ | neue_position.x = 0 | ||
+ | if neue_position.y < 0: | ||
+ | neue_position.y = MAXY | ||
+ | if neue_position.y > MAXY: | ||
+ | neue_position.y = 0 | ||
+ | |||
+ | # Selbstkollision: | ||
+ | if neue_position in schlange: | ||
+ | crash = True | ||
+ | spiel_aktiv = False | ||
+ | if MIT_SOUND: | ||
+ | pygame.mixer.Sound.play(crash_sound) | ||
+ | else: | ||
+ | # Hänge die neue Position vorne an die Liste " | ||
+ | schlange.insert(0, | ||
+ | # Apfel erreicht? | ||
+ | if neue_position == apfel: | ||
+ | gefressene_aepfel = 1 | ||
+ | if MIT_SOUND: | ||
+ | pygame.mixer.Sound.play(apfel_ess_sound) | ||
+ | laenge = laenge * 2 | ||
+ | frames_per_second = frames_per_second + VERGROESSERUNG_PRO_APFEL | ||
+ | apfel = neue_apfelposition() | ||
+ | # Ende der game loop. | ||
+ | |||
+ | # Nach Abbruch oder Crash: | ||
+ | |||
+ | if crash: | ||
+ | schreibe(MAXX // 2, MAXY // 2, 'GAME OVER', 2) | ||
+ | schreibe(MAXX // 2, MAXY // 2 + 3, 'press any key', 1) | ||
+ | pygame.display.update() | ||
+ | pygame.time.delay(500) | ||
+ | for ereignis in pygame.event.get(): | ||
+ | pygame.time.delay(100) | ||
+ | while pygame.event.get() == []: | ||
+ | pygame.time.delay(100) | ||
+ | |||
+ | pygame.quit() | ||
+ | exit() | ||
+ | </ | ||
+ | </ | ||
</ | </ | ||
- | ==== Bonusaufgaben ==== | ||
<WRAP center round todo> | <WRAP center round todo> | ||
- | Überlege dir, wie man das Spiel weiterentwickeln könnte. | + | Überlege dir, wie man das Spiel weiterentwickeln könnte. Verstehe das Programm dazu möglichst gut bzw. wenigstens so weit wie nötig. |
- | <hidden einige | + | <hidden einige |
* Füge ein Hindernis ein: Die Schlange stirbt, wenn sie dagegen fährt. Das Hindernis könnte sich auch bewegen (hin und her oder gar Richtung Schlange). | * Füge ein Hindernis ein: Die Schlange stirbt, wenn sie dagegen fährt. Das Hindernis könnte sich auch bewegen (hin und her oder gar Richtung Schlange). | ||
* Programmiere eine zweite Schlange, die sich etwa mit den Tasten '' | * Programmiere eine zweite Schlange, die sich etwa mit den Tasten '' | ||
Line 214: | Line 545: | ||
* Es könnte auch mehrere Hindernisse geben oder mehrere Schlangen. | * Es könnte auch mehrere Hindernisse geben oder mehrere Schlangen. | ||
* Verschiedene Level einführen. | * Verschiedene Level einführen. | ||
- | | + | |
+ | Schwieriger: | ||
+ | | ||
+ | * gleichmässige Bewegung | ||
+ | * Kopf der Schlange (also geeignetes Bild) zeigt in Laufrichtung | ||
+ | * Schlange schlanker (also Rechtecke statt Quadrate mit richtig gekrümmten Abbiegefeldern) | ||
* Spielen übers Netzwerk? | * Spielen übers Netzwerk? | ||
</ | </ |