lehrkraefte:snr:informatik:glf22:python:snake

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
lehrkraefte:snr:informatik:glf22:python:snake [2023/01/27 08:35]
Olaf Schnürer [Richtung Snake: 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 156: Line 158:
 <code python listen-demo.py> <code python listen-demo.py>
 # Beispiel 1: # Beispiel 1:
-# Top 5 der Vornamen in der italienschen Schweiz 2021 laut BFS, https://babynames-stat.ch/de/index.html +# Top 5 der Vornamen in der italienischen Schweiz 2021 laut BFS, https://babynames-stat.ch/de/index.html 
-namens_liste = ['Leonardo', 'Alessandro', 'Liam', 'Noah', 'Tommaso'+namensliste = ['Leonardo', 'Alessandro', 'Liam', 'Noah', 'Tommaso'
-print(namens_liste+print(namensliste
-print(namens_liste[3]) +print(namensliste[3]) 
-print(namens_liste[0]) +print(namensliste[0]) 
-namens_liste[2] = 'Urs' +namensliste[3] = 'Urs' 
-print("Liam" in namens_liste+print(namensliste) 
-print("Luca" in namens_liste+print("Liam" in namensliste
-print(len(namens_liste)) +print("Luca" in namensliste
-for name in namens_liste:+print(len(namensliste)) 
 +for name in namensliste:
     print('Mi chiamo ' + name + '.')     print('Mi chiamo ' + name + '.')
-print(len(namens_liste)+namensliste.insert(0, 'Nathan'
-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 178: Line 185:
 for note in notenliste: for note in notenliste:
     summe = summe + note     summe = summe + note
-print(f'Durchschnitt = {summe / len(notenliste)}')+print(summe / len(notenliste))
  
 # Beispiel 3: # Beispiel 3:
Line 185: Line 192:
 while liste_der_wuerfe[0:2] != [0, 0]: while liste_der_wuerfe[0:2] != [0, 0]:
     liste_der_wuerfe.insert(0, randrange(2))     liste_der_wuerfe.insert(0, randrange(2))
-    print(liste_der_wuerfe)+    print(liste_der_wuerfe)    
 </code> </code>
 </hidden> </hidden>
Line 193: Line 200:
 Ausserdem sollte man verstehen, warum eine Liste die naheliegende Datenstruktur für Snake ist (verbale Erklärung mit Snake-Beispielprogramm, anhalten mit Leertaste, Liste wird unten angezeigt). Ausserdem sollte man verstehen, warum eine Liste die naheliegende Datenstruktur für Snake ist (verbale Erklärung mit Snake-Beispielprogramm, anhalten mit Leertaste, Liste wird unten angezeigt).
  
 +<hidden Tafelfoto dazu>
 +{{lehrkraefte:snr:informatik:glf22:python:snake-mit-listen.jpg}} 
 +</hidden>
 ===== 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 ''pygame'' programmierten snake-ähnlichen Spiel (es funktioniert noch nicht: Ich erkläre die wesentlichen Punkte, wo wir für die Schlange eine Liste namens ''schlange'' verwenden).+Hist ist ein Link zu einem in ''pygame'' programmierten snake-ähnlichen Spiel
 + 
 +(Zusammen die per ''## GEMEINSAM ANSCHAUEN:'' markierten Stellen anschauen: An diesen Stellen wird eine Liste namens ''schlange'' verwendet, um die aktuellen Positionen der Schlangenquadrate zu speichern.)
  
   * [[lehrkraefte:snr:informatik:glf22:python:snake:snake-zu-verbessern|Link zum Programm ''snake-zu-verbessern.py'']]   * [[lehrkraefte:snr:informatik:glf22:python:snake:snake-zu-verbessern|Link zum Programm ''snake-zu-verbessern.py'']]
Line 208: Line 220:
  
 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; hier sind noch diverse auskommentierte Sachen einzukommentieren - bitte mich fragen; erfahrungsgemäss ist die Tastatureingabe online so langsam, dass es keinen Spass macht.):>
 +<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, Apfel, Schlangenkopf) 
 +# 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 ''lade_bilder_und_sound'' musst du 
 +# 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  grün  blau
 +ROT        (255,    0,    0)
 +GRUEN      (  0,  255,    0)
 +BLAU    =    (  0,    0,  255)
 +GELB    =    (255,  255,    0)
 +MAGENTA =    (255,    0,  255)
 +CYAN    =    (  0,  255,  255)
 +WEISS      (255,  255,  255)
 +SCHWARZ =    (  0,    0,    0)
 +HELLGRAU =   (80,  80,  80)
 +DUNKELGRAU = (160,  160,  160)
 +HINTERGRUND_FARBE = SCHWARZ
 +# HINTERGRUND_FARBE = HELLGRAU
 +
 +#
 +# Definition der Klasse Punkt. Bitte einfach akzeptieren.
 +#
 +class Punkt:
 +    def __init__(self, x, y):
 +        self.x = x
 +        self.y = y
 +
 +    def __str__(self):
 +        return '(' + str(self.x) + ',' + str(self.y) + ')'
 +
 +    def __repr__(self):
 +        return '(' + str(self.x) + ',' + str(self.y) + ')'
 +
 +    def __eq__(self, other):
 +        return self.x == other.x and self.y == other.y
 +
 +    def __add__(self, other):
 +        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, farbe):
 +    rechteck = pygame.Rect(p.x * SEITENLAENGE_BOX + 1, p.y * SEITENLAENGE_BOX + 1, SEITENLAENGE_BOX - 1, SEITENLAENGE_BOX - 1)
 +    pygame.draw.rect(leinwand, farbe, rechteck)
 +
 +def zeichne_gitter():
 +    for x in range(ANZAHL_BOXEN_X + 1):
 +        pygame.draw.line(leinwand, HELLGRAU, (x * SEITENLAENGE_BOX, 0), (x * SEITENLAENGE_BOX, FENSTER_HOEHE))
 +    for y in range(ANZAHL_BOXEN_Y + 1):
 +        pygame.draw.line(leinwand, HELLGRAU, (0, y * SEITENLAENGE_BOX), (FENSTER_BREITE, y * SEITENLAENGE_BOX))
 +
 +def schreibe(x, y, text, groesse):
 +    schrift = pygame.font.SysFont('freemono', round(groesse * SEITENLAENGE_BOX))
 +    formatierter_text = schrift.render(text, True, WEISS, SCHWARZ)
 +    rechteck = formatierter_text.get_rect()
 +    rechteck.center = (round((x + 0.5) * SEITENLAENGE_BOX), round((y + 0.5) * SEITENLAENGE_BOX))
 +    leinwand.blit(formatierter_text, rechteck)
 +
 +def schreibe_transparent(x, y, text, groesse):
 +    schrift = pygame.font.SysFont('freemono', round(groesse * SEITENLAENGE_BOX))
 +    formatierter_text = schrift.render(text, True, SCHWARZ)
 +    rechteck = formatierter_text.get_rect()
 +    rechteck.center = (round((x + 0.5) * SEITENLAENGE_BOX), round((y + 0.5) * SEITENLAENGE_BOX))
 +    if rechteck.x >= 0 and rechteck.y >= 0:
 +        text_flaeche = pygame.Surface((rechteck.x, rechteck.y))
 +        text_flaeche.fill(WEISS)
 +        text_flaeche.blit(formatierter_text, rechteck)
 +        text_flaeche.set_alpha(50)
 +        leinwand.blit(formatierter_text, rechteck)
 +
 +def neue_apfelposition():
 +    a = Punkt(randrange(0, MAXX + 1), randrange(0, MAXY + 1))
 +    while a in schlange:
 +        a = Punkt(randrange(0, MAXX + 1), randrange(0, MAXY + 1))
 +    return a
 +
 +def lade_bilder_und_sound():
 +    global hintergrundbild, bild_kopf_der_schlange, bild_apfel, apfel_ess_sound, crash_sound
 +
 +    if not EINFARBIGER_HINTERGRUND:
 +        hintergrundbild = pygame.image.load('landschaft.jpg')
 +        hintergrundbild = pygame.transform.scale(hintergrundbild, (FENSTER_BREITE, FENSTER_HOEHE))
 +
 +    if not BOXEN_STATT_BILDER:
 +        bild_apfel = pygame.image.load('apple.svg')
 +        bild_apfel = pygame.transform.scale(bild_apfel, (SEITENLAENGE_BOX, SEITENLAENGE_BOX))
 +        bild_kopf_der_schlange = pygame.image.load('Excited-Smiley-Face.svg')
 +        bild_kopf_der_schlange = pygame.transform.scale(bild_kopf_der_schlange, (SEITENLAENGE_BOX, SEITENLAENGE_BOX))
 +
 +    if MIT_SOUND:
 +        crash_sound = pygame.mixer.Sound('mixkit-funny-game-lose-tone-2877.wav')
 +        apfel_ess_sound = pygame.mixer.Sound('mixkit-video-game-retro-click-237.wav')
 +
 +def zeige_bild(bild, p):
 +    rechteck = pygame.Rect(p.x * SEITENLAENGE_BOX + 1, p.y * SEITENLAENGE_BOX + 1, SEITENLAENGE_BOX - 1, SEITENLAENGE_BOX - 1)
 +    leinwand.blit(bild, rechteck)
 +
 +pygame.init()
 +uhr = pygame.time.Clock()
 +leinwand = pygame.display.set_mode((FENSTER_BREITE + 1, FENSTER_HOEHE + 1 + PLATZ_FUER_TEXT))
 +pygame.display.set_caption('Snake game')
 +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 "länge"
 +schlange = []
 +for i in range(laenge):
 +    schlange.insert(0, Punkt(1 + i, MAXY // 2)) 
 +# 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, anfangs am besten ignorieren.
 +neue_richtung = bewegungsrichtung
 +richtung_vor_stopp = Punkt(0, 0)
 +
 +# Initialisiere Apfelposition (nicht auf Schlange!) und Farbe.
 +apfel = neue_apfelposition()
 +farbe_apfel = ROT
 +
 +# Initialisierung der "Spielvariablen"
 +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, (0, 0))
 +
 +    # Ausgabe diverser Zahlen unter dem Spielfeld
 +    schreibe(MAXX // 4, MAXY + 1, 'apple count: ' + str(gefressene_aepfel), 0.8)
 +    schreibe(3 * MAXX // 4, MAXY + 1, 'length: ' + str(laenge), 0.8)
 +    schreibe(MAXX // 4, MAXY + 2, 'apple: ' + str(apfel), 0.8)
 +    schreibe(3 * MAXX // 4, MAXY + 2, 'current length: ' + str(len(set(schlange))), 0.8)
 +    schreibe(MAXX // 4, MAXY + 3, 'head of snake: ' + str(schlange[0]), 0.8)
 +    schreibe(3 * MAXX // 4, MAXY + 3, 'frames per second: ' + str(frames_per_second), 0.8)
 +    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, farbe_apfel)
 +        schreibe_transparent(apfel.x, apfel.y , str(apfel), 0.25)
 +    else:
 +        zeige_bild(bild_apfel, apfel)
 +        schreibe_transparent(apfel.x, apfel.y , str(apfel), 0.25)
 +
 +    # Schlange zeichnen
 +    for element in schlange:
 +        zeichne_box(element, farbe_schlange)
 +        schreibe_transparent(element.x, element.y , str(element), 0.25)
 +    if not BOXEN_STATT_BILDER:
 +        zeige_bild(bild_kopf_der_schlange, schlange[0])
 +        schreibe_transparent(schlange[0].x, schlange[0].y , str(schlange[0]), 0.25)
 +
 +    # Alles bis jetzt "versteckt" Gezeichnete sichtbar machen.
 +    pygame.display.update()
 +    uhr.tick(frames_per_second)
 +
 +    # Verarbeitung von Tastatureingaben:
 +    for ereignis in pygame.event.get():
 +        if ereignis.type == QUIT:
 +            print("Button zum Schliessen des Pygame-Fensters angeklickt.")
 +            spiel_aktiv = False
 +        elif ereignis.type == KEYDOWN:
 +            if ereignis.key == K_ESCAPE:
 +                print("Escape-Taste gedrückt.")
 +                spiel_aktiv = False
 +            elif ereignis.key == K_q:
 +                print("Taste 'q' gedrückt.")
 +                spiel_aktiv = False
 +            elif ereignis.key == K_SPACE:
 +                print("Leer-Taste gedrückt.")
 +                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("Pfeiltaste links gedrückt.")
 +                    neue_richtung = Punkt(-1, -1)
 +                elif ereignis.key == K_f:
 +                    print("Pfeiltaste rechts gedrückt.")
 +                    neue_richtung = Punkt(-1, 1)
 +                elif ereignis.key == K_t:
 +                    print("Pfeiltaste hoch gedrückt.")
 +                    neue_richtung = Punkt(1, -1)
 +                elif ereignis.key == K_g:
 +                    print("Pfeiltaste runter gedrückt.")
 +                    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 "schlange".
 +        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".
 +            schlange.insert(0, neue_position)
 +            # 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()
 +</code>
 +</hidden>
 </WRAP> </WRAP>
  
  • lehrkraefte/snr/informatik/glf22/python/snake.1674804914.txt.gz
  • Last modified: 2023/01/27 08:35
  • by Olaf Schnürer