@stef5i: Noch ein paar Anmerkungen zum Code:
`math` wird importiert, aber nirgends benutzt.
Namen schreibt man in Python klein_mit_unterstrichen. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).
Man sollte nicht mehrere Anweisungen in eine Zeile quetschen. Das macht den Code nur unnötig schwerer lesbar.
Einbuchstabige Namen sind in aller Regel keine guten Namen. `s` sollte beispielsweise `snake` heissen. Da die Klasse ja `Snake` heissen sollte, ist das auch kein Problem. Auch kryptische Abkürzungen und Nummerierungen sollte man nicht machen. Bei `sizeBtwn` musste ich ganz schön raten bis ich mir halbwegs sicher war, dass das wohl `size_between` heissen soll. Soll es?
``global`` hat in einem sauberen Programm nichts zu suchen. Alles was Funktionen und Methoden ausser Konstanten benötigen, bekommen sie als Argument(e) übergeben.
`width` ist nicht nur die Breite sondern auch die Höhe. Da wäre `size` besser als Name. Und `display_size` noch besser, weil man dann nicht raten muss was für eine Grösse das eigentlich ist. Ähnliches gilt für `rows` was ja gleichzeitig auch `columns` ist, beziehungsweise sein muss, weil sonst andere Stellen im Code nicht richtig funktionieren. `cells_per_axis` oder `grid_cells_per_axis` wären da passendere Namen. Beides wären gute Kandidaten für Konstanten.
Das die Werte obwohl sie die gleiche Bedeutung haben auf `Cube` noch mal als Klassenattribute `w` und `rows` definiert sind, macht keinen Sinn und macht das ganze nur unnötig fehleranfällig.
Beim erstellen von `Snake` wird eine Farbe übergeben die nirgends verwendet wird. Entweder weg damit oder tatsächlich verwenden.
`dirnx` und `dirny` sind schlechte Namen und als Argumente von `Cube` werden die weder übergeben, noch benutzt → weg damit.
Bei `random_snack()` die Schlange `items` zu nennen, wobei die noch nicht einmal iterierbar ist, ist keine gute Idee. In der Funktion ist `positions` dann falsch, denn das sind ja keine Positionen sondern `Cube`-Objekte. Man sollte da tatsächlich die Positionen der `Cube`-Objekte ab `positions` binden, am besten als `set()`. Dann wird der furchtbar kompliziert ausgedrückte Test (``len(list(filter(lambda z: z.pos == (x, y), positions))) > 0``) einfach zu einem ``(x, y) in positions``.
`pygame.time.delay()` *und* `Clock.tick()` zu verwenden ist unsinnig. `Clock.tick()` ist zu bevorzugen.
`flag` wird nur einmal auf `True` gesetzt und nur an einer Stelle verwendet. Da kann man an dieser Stelle dann auch gleich `True` schreiben. `flag` ist als Name auch ein bisschen nichtssagend.
Bei Snake dürfen `body` und `turns` keine Klassenattribute sein, die gehören in die `__init__()`.
In `Snake.move()` ist die ``for``-Schleife über die `keys` total unsinnig.
Das kopieren von Tupeln per slicing (``[:]``) ist überall überflüssig.
`dirnx` und `dirny` bei Snake ist redundant, denn das ist ja immer die Ausrichtung vom Kopf die schon im Kopf gespeichert wird. in sofern ist dann `turns` auch redundant und unnötig, weil die Ausrichtung der Teile ja bereits in den Teilen gespeichert ist. Wenn sich die Schlange weiterbewegt, dann kann man die Ausrichtung vom Vorgänger nehmen, beziehungsweise würde man statt einer Liste und dem anpassen der Positionen aller Teile besser eine `collections.deque` nehmen und das fortbewegen lösen in dem man einen ”neuen Kopf” vorne anhängt und das letzte Teil entfernt. Dann braucht man die Ausrichtung auch nicht in allen Teilen, sondern nur noch die des Kopfes in der Schlange.
Es wird in der Hauptfunktion auf ``snake.body[0]`` zugegriffen obwohl es ``snake.head`` gibt. Und ``snake.head`` sollte nicht als eigenständiges Attribut bestehen wenn das immer den gleichen Wert wie ``snake.body[0]`` hat. Dafür gibt es `property()`.
Der Test auf Kollision ist auch extrem überkompliziert. Man muss nur schauen ob der Kopf mit einem der anderen Körperelemente kollidiert. Körperelemente können nicht mit anderen Körperelementen kollidieren ohne das vorher der Kopf das mal gemacht hätte.
Eine `reset()`-Methode sollte man nicht verwenden wenn man einfach ein neues Objekt erstellen kann. Das ist weniger fehleranfällig.
Die `main()`-Funktion könnte dann am Ende so aussehen:
Code: Alles auswählen
def main():
surface = pygame.display.set_mode((DISPLAY_SIZE, DISPLAY_SIZE))
snake = Snake(SNAKE_START_POSITION, "red")
snack = Cube(random_snack_position(GRID_CELLS_PER_AXIS, snake), "green")
clock = pygame.time.Clock()
while True:
clock.tick(10)
snake.move()
if snake.head.position == snack.position:
snake.grow()
snack = Cube(
random_snack_position(GRID_CELLS_PER_AXIS, snake), "green"
)
parts = iter(snake.body)
head = next(parts)
if head.position in (part.position for part in parts):
print("Score: ", len(snake.body))
message_box("Du hast leider verloren!", "Nochmal Spielen")
snake = Snake(SNAKE_START_POSITION, "green")
break
redraw_window(surface)
Es gäbe noch mehr zu sagen, aber das ist ja erst einmal genug Stoff zum überarbeiten.
