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()