@_squid_: Wie vermutet passiert da eine Menge über globale Variablen, was nicht sein sollte. Das ist unübersichtlich und fehleranfällig. Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Ein Modul mit dem Namen `button` und einer einzigen Klasse `Button` macht nicht wirklich Sinn. Das ist Python und kein Java, wo man jede Klasse in eine eigene Datei steckt.
Numpy zu importieren nur um daraus dann die `randint()`-Funktion für einzelne Zahlen zu verwende ist sehr übertrieben. Es gibt so eine Funktion auch im `random`-Modul in der Standardbibliothek.
Das `surface`-Modul aus `pygame` wird importiert, aber nirgends verwendet.
Der problematisch aussehende `set_mode()`-Aufruf ist überhaupt nicht notwendig, weil das Fenster ja schon besteht und auch schon die gleiche Grösse hat. Der Aufruf kann/muss also einfach nur entfernt werden.
`go` ist immer `True` und `pause` wird nirgends verwendet.
Namen sollten keine kryptischen Abkürzungen enthalten. `Cord` ist beispielsweise irreführend, weil es das Wort `cord` tatsächlich gibt, und es Schnur oder Kordel bedeutet, was ja aber nichts mit den Koordinaten zu tun hat, für die diese Abkürzung steht.
Es ist auch nicht gut solche Denglischen Namen wie `apfelCords` zu verwenden. Entweder komplett Deutsch oder komplett Englisch. Wonach entscheidest Du denn was Deutsch und was Englisch benannt ist?
`apfelCordGen()` ist sehr umständlich ausgedrückt. `notOk` ist überflüssig als Variable, weil das immer den Wert `True` hat. Und `change` und die beiden Schleifen sind überflüssig, weil man hier ganz einfach eine Bedingung mit ``in`` formulieren kann.
Magische Zahlen für Richtungen zu verwenden erschwert das lesen und damit das verstehen und ist deshalb eine Fehlerquelle beim schreiben oder überarbeiten des Code. Man sollte da wenigstens Konstanten für definieren. Oder einen `enum.Enum`-Typ.
Wenn sich mehrere ``if``-Zweige gegenseitig ausschliessen, kommt ``elif`` zum Einsatz.
`anhang.copy()` ist eine unnötige Kopie denn `anhang` selbst ist ja bereits eine Kopie. Insgesamt ist das auch keine so gute Idee ständig zweielementige Listen zu kopieren. Ja letztlich um zu verhindern dass dann vielleicht irgendwo eine Referenz auf eine Liste verwendet wird, die nicht verändert werden soll. Dafür gibt es aber Tupel, die sich bei Koordinaten sowieso anbieten würden.
Das elementweise von hinten kopieren der Schlangenkoordinaten ist auch irgendwie unsinnig. Denn was da eigentlich passiert, ist da das man das letzte Element weg nimmt und vorne eine neue Kopfkoordinate hinzufüht. Also ein `pop()` und ein `insert()`. Keine eigene Schleife.
Und vor dem Einfügen der neuen Kopfposition kann man mit ``in`` testen ob die im Schlangenkörper gelandet ist. Wieder eine ``for``-Schleife eingespart.
Was man sich auch sparen kann ist `anhang` komplizierter zu machen als notwendig, denn da reicht ein Wahrheitswert. Wenn der wahr ist, spart man sich einfach das entfernen der letzten Schlangenkoordinate.
Und auch `apfel_index` kann man sich sparen, weil man den Apfel auch gleich entfernen kann, wenn man ihn gefunden hat.
Bei dem Test ob der Kopf einen Apfel getroffen hat, kann man auch wieder ohne eigene Schleife auskommen, denn Listen haben eine `index()`-Methode um einen Wert in einer Liste zu suchen.
Mit ``and`` verknüpfte Teilbedingungen die gegen den gleichen Wert prüfen, kann man mit verketteten Vergleichsoperatoren prüfen. Also statt ``a < 0 and a > 27`` kann man ``not 0 <= a <= 27`` schreiben.
Die Bedingung ``if zufall <= 1 and len(aepfel) < 1 or len(aepfel) == 0:`` ist unnötig umständlich. Erst einmal ist ``len(aepfel) < 1`` das gleiche wie ``len(aepfel) == 0``. Und dann wird klar, dass der Zufall hier überhaupt keine Rolle spielt, weil der gesamte Ausdruck immer dann wahr ist, wenn die Äpfel alle sind, und unwahr sonst. Der Wert von `zufall` ist dabei vollkommen egal.
`textObjekt()` würde ich noch beliebige Schlüsselworte mitgeben und die an `get_rect()` weiterreichen, dann kann man so etwas wie `center=…` dort beim Aufruf mitgeben.
Beim Zeichnen von Schlange und Äpfeln sollte man sich die ganzen magischen 0 und 1 Indexzugriffe in die Koordinaten sparen und eifach die beiden Werte an Namen binden.
Statt `kopf` als Flag wäre es simpler die Farbe für den Kopf vor der Schleife als Variable zu setzen und in der Schleife dann zu ändern.
Da das `clicked`-Attribut von `Button`-Objekten nirgends verwendet wird, kann man die `draw()`-Methode deutlich vereinfachen.
`Surface.blit()` kann man als zweites Argument ein `Rect`-Objekt übergeben. Da muss man nicht extra ein Tupel aus `x` und `y` von dem `Rect`-Objekt erstellen.
Zwischenstand (ungetestet):
Code: Alles auswählen
#!/usr/bin/env python3
import random
from enum import Enum, auto
import pygame
PARTIKEL = 25
def create_text(text, font, **kwargs):
surface = font.render(text, True, (255, 255, 255))
return surface, surface.get_rect(**kwargs)
class Button:
def __init__(self, x, y, image, scale=1):
self.image = pygame.transform.scale(
image,
(int(image.get_width() * scale), int(image.get_height() * scale)),
)
self.rect = self.image.get_rect(topleft=(x, y))
def draw(self, surface):
surface.blit(self.image, self.rect)
return (
self.rect.collidepoint(pygame.mouse.get_pos())
and pygame.mouse.get_pressed()[0]
)
class Direction(Enum):
UP = auto()
RIGHT = auto()
DOWN = auto()
LEFT = auto()
def erzeuge_apfel_koordinaten(schlange, aepfel):
while True:
koordinaten = (random.randint(0, 28), random.randint(0, 28))
if not (koordinaten in schlange or koordinaten in aepfel):
return koordinaten
def zeichne(screen, schlange, aepfel):
screen.fill((0, 102, 0))
for x, y in aepfel:
pygame.draw.rect(
screen,
(255, 0, 0),
(x * PARTIKEL, y * PARTIKEL, PARTIKEL, PARTIKEL),
0,
)
farbe = (0, 150, 10)
for x, y in schlange:
pygame.draw.rect(
screen, farbe, (x * PARTIKEL, y * PARTIKEL, 20, 20), 0
)
farbe = (47, 79, 79)
def main():
try:
pygame.init()
clock = pygame.time.Clock()
font = pygame.font.SysFont("arial black", 35)
screen = pygame.display.set_mode([700, 700])
start_button = Button(
100, 200, pygame.image.load("WiederholenKnopf.png").convert_alpha()
)
exit_button = Button(
450, 200, pygame.image.load("ExitKnopf.png").convert_alpha()
)
schlange = [(13, 13), (13, 14)]
richtung = Direction.UP
aepfel = []
aepfel.append(erzeuge_apfel_koordinaten(schlange, aepfel))
score = 0
hat_apfel_gefressen = False
ende = False
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
if event.type == pygame.KEYDOWN:
if (
event.key == pygame.K_UP
and richtung is not Direction.DOWN
):
richtung = Direction.UP
elif (
event.key == pygame.K_RIGHT
and richtung is not Direction.LEFT
):
richtung = Direction.RIGHT
elif (
event.key == pygame.K_DOWN
and richtung is not Direction.UP
):
richtung = Direction.DOWN
elif (
event.key == pygame.K_LEFT
and richtung is not Direction.RIGHT
):
richtung = Direction.LEFT
x, y = schlange[0]
if hat_apfel_gefressen:
hat_apfel_gefressen = False
else:
schlange.pop()
if richtung is Direction.UP:
head = (x, y - 1)
elif richtung is Direction.RIGHT:
head = (x + 1, y)
elif richtung is Direction.DOWN:
head = (x, y + 1)
elif richtung is Direction.LEFT:
head = (x - 1, y)
else:
assert False, f"unknown direction {richtung!r}"
if head in schlange or not (0 <= x <= 27 and 0 <= y <= 27):
ende = True
schlange.insert(0, head)
try:
apfel_index = aepfel.index(head)
except ValueError:
pass
else:
aepfel.pop(apfel_index)
hat_apfel_gefressen = True
score += 1
if not aepfel:
aepfel.append(erzeuge_apfel_koordinaten(schlange, aepfel))
if not ende:
zeichne(screen, schlange, aepfel)
screen.blit(
*create_text(f"Score: {score}", font, center=(350, 40))
)
else:
screen.fill((255, 255, 255))
if start_button.draw(screen):
pass # TODO Hier sollte wohl irgendwas passieren.
if exit_button.draw(screen):
print(f"Du hast {score} Punkte erreicht")
break
pygame.display.update()
clock.tick(7)
finally:
pygame.quit()
if __name__ == "__main__":
main()