lehrkraefte:snr:informatik:glf22:python:snake:snake-zu-verbessern

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:snake-zu-verbessern [2023/02/05 12:49] Olaf Schnürerlehrkraefte:snr:informatik:glf22:python:snake:snake-zu-verbessern [2023/02/05 13:01] (current) Olaf Schnürer
Line 1: Line 1:
 +====== Snake program ======
 +
 +<code python snake-zu-verbessern.py>
 +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.
 +#
 +# Könnte stattdessen "Punkt" durch "pygame.Vector2" ersetzen, 
 +# jedoch sind solche Vektoren wohl nicht "hashable", weshalb
 +# die Zeile zur Ausgabe der "current length" auskommentiert werden muss.
 +#
 +class Punkt:
 +    def __init__(self, x, y):
 +        self.x = x
 +        self.y = y
 +
 +    def __str__(self):
 +        return f'({self.x},{self.y})'
 +    
 +    def __repr__(self):
 +        return f'({self.x},{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()
 +
 +laenge = 4
 +
 +### GEMEINSAM ANSCHAUEN: 
 +# 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.
 +schlange = [Punkt(4, 7), Punkt(3, 7), Punkt(2, 7), Punkt(1, 7)]
 +###
 +
 +# Oder besser, da abhängig von der Variablen "länge"
 +#I schlange = []
 +#I for i in range(laenge):
 +#I     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, f'{gefressene_aepfel=}', 0.8)
 +    schreibe(3 * MAXX // 4, MAXY + 1, f'{laenge=}', 0.8)
 +    schreibe(MAXX // 4, MAXY + 2, f'{apfel=}', 0.8)
 +    schreibe(3 * MAXX // 4, MAXY + 2, f'current length={len(set(schlange))}', 0.8)
 +    schreibe(MAXX // 4, MAXY + 3, f'{schlange[0]=}', 0.8)
 +    schreibe(3 * MAXX // 4, MAXY + 3, f'{frames_per_second=:.1f}', 0.8)
 +    text = f'{schlange=}'
 +    schreibe(MAXX // 2, MAXY + 4, text, min(1, 1.5 * MAXX / len(text)))
 +    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 , f'{apfel}', 0.25)
 +    else:
 +        zeige_bild(bild_apfel, apfel)
 +        schreibe_transparent(apfel.x, apfel.y , f'{apfel}', 0.25)
 +    
 +### GEMEINSAM ANSCHAUEN: 
 +    # Schlange zeichnen
 +    for element in schlange:
 +        zeichne_box(element, farbe_schlange)
 +        schreibe_transparent(element.x, element.y , f'{element}', 0.25)
 +###
 +
 +    if not BOXEN_STATT_BILDER:
 +        zeige_bild(bild_kopf_der_schlange, schlange[0])
 +        schreibe_transparent(schlange[0].x, schlange[0].y , f'{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("Taste ?...? gedrückt.")
 +                    neue_richtung = Punkt(-1, -1)
 +                elif ereignis.key == K_f:
 +                    print("Taste ?,,,? gedrückt.")
 +                    neue_richtung = Punkt(-1, 1)
 +                elif ereignis.key == K_t:
 +                    print("Taste ?;;;? gedrückt.")
 +                    neue_richtung = Punkt(1, -1)
 +                elif ereignis.key == K_g:
 +                    print("Taste ?:::? gedrückt.")
 +                    neue_richtung = Punkt(1, 1)
 +                # Die folgende if-Bedingung verhindert, dass die
 +                # Snake die Richtung umkehren kann (und mit sich selbst kollidiert).
 +### GEMEINSAM ANSCHAUEN: 
 +                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:
 +### GEMEINSAM ANSCHAUEN: 
 +            schlange.pop()
 +###
 +
 +### GEMEINSAM ANSCHAUEN: 
 +        # 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
 +
 +### GEMEINSAM ANSCHAUEN: 
 +        # Selbstkollision:
 +        if neue_position in schlange:
 +            crash = True
 +###
 +
 +            spiel_aktiv = False
 +            if MIT_SOUND:
 +                pygame.mixer.Sound.play(crash_sound)
 +        else:
 +### GEMEINSAM ANSCHAUEN: 
 +            # 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>
 +
 +===== Link zur Snake-Seite =====
 +
 +  * [[lehrkraefte:snr:informatik:glf22:python:snake|Snake]]