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

Snake program

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()
  • lehrkraefte/snr/informatik/glf22/python/snake/snake-zu-verbessern.txt
  • Last modified: 2023/02/05 14:01
  • by Olaf Schnürer