@ThomasL: Ein paar Anmerkungen zu dem Quelltext:
In der Hauptfunktion ist das ``for i in range(len(sequence)):``-Anti-Pattern. Man kann da direkt, ohne einen Umweg über einen Index über die `Pipe`-Objekte iterieren.
Vom Datentyp her wäre eine `collections.deque` passender für `pipes`.
Klassen die nur aus einer `__init__()` bestehen sind fast nie wirklich Klassen. Denn selbst Klassen die aus einer `__init__()` und einer zusätzlichen Methode bestehen, sind sehr oft einfach nur umständlich geschriebene Funktionen.
Das betrifft `Canvas`. Da werden zudem alle Argumente der `__init__()` an das Objekt gebunden, obwohl später nur zwei davon tatsächlich benötigt werden.
Man kann die ”Klasse” in einem ersten Schritt durch eine Funktion ersetzen, die ein von `collections.namedtuple` abgeleitetes Objekt zurück gibt das die Attribute `screen`, `width`, `height`, und `clock` besitzt. Wobei ich `clock` da auch gleich heraus nehmen würde, denn das gehört eigentlich nicht zu einer Leinwand und könnte problemlos ein lokaler Name in der Hauptfunktion sein.
Im nächsten Schritt kann man dann auch dieses `namedtuple` loswerden, denn das enthält redundante Daten. `screen` ist ein `Surface` und von diesen Objekten kann man die Höhe und Breite abfragen. Es macht wenig Sinn die gleichen Werte extern noch einmal zu speichern.
`Pipe.hits()` hat inkonsistente Rückgabewerte. Statt `True` und `None` sollte die Methode `True` und `False` liefern.
Die verschachtelten ``if``\s kann man alle zusammenfassen.
``bird.y <= self.top or bird.y >= self.bottom`` hätte ich wahrscheinlich eher als ``not self.top <= bird.y < self.bottom`` ausgedrückt.
Das `fullhd`-Argument ist ein bisschen komisch, denn man kann da `True` angeben, aber trotzdem eine Auflösung die nicht Full-HD ist.
Code: Alles auswählen
#!/usr/bin/env python3
import os
import random
from collections import deque
import pygame as pg
from pygame.locals import *
BLACK = [0, 0, 0]
WHITE = [255, 255, 255]
RED = [255, 0, 0]
def create_canvas(fullhd=False, width=800, height=800, mode=0):
os.environ['SDL_VIDEO_CENTERED'] = '1'
pg.init()
if not fullhd:
pg.display.set_caption('Flappy Bird')
screen = pg.display.set_mode([width, height], mode)
screen.fill(WHITE)
pg.display.update()
return screen
class Bird:
GRAVITY = 0.5
LIFT = -10
def __init__(self, canvas):
self.canvas = canvas
self.y = self.canvas.get_height() // 2
self.x = 64
self.velocity = 0
def show(self):
pg.draw.circle(self.canvas, BLACK, [self.x, self.y], 10, 0)
def up(self):
self.velocity += self.LIFT
def update(self):
self.velocity = (self.velocity + self.GRAVITY) * 0.9
self.y = int(self.y + self.velocity)
if self.y > self.canvas.get_height():
self.y = self.canvas.get_height()
self.velocity = 0
if self.y < 0:
self.y = 0
self.velocity = 0
class Pipe:
WIDTH = 20
SPEED = 1
def __init__(self, canvas):
self.canvas = canvas
self.top = random.randint(100, self.canvas.get_height() - 200)
self.bottom = self.top + random.randint(100, 150)
self.x = self.canvas.get_width() + self.WIDTH // 2
self.highlight = False
def hits(self, bird):
if (
not self.highlight
and not self.top <= bird.y < self.bottom
and self.x <= bird.x <= self.x + 1
):
self.highlight = True
return True
return False
def show(self):
color = RED if self.highlight else BLACK
pg.draw.rect(self.canvas, color, [self.x, 0, self.WIDTH, self.top], 0)
pg.draw.rect(
self.canvas,
color,
[self.x, self.bottom, self.WIDTH, self.canvas.get_height()],
0
)
def update(self):
self.x -= self.SPEED
def is_offscreen(self):
return self.x < -self.WIDTH
def main():
canvas = create_canvas() # windowed mode
# canvas = create_canvas(
# fullhd=True, width=1920, height=1080, mode=FULLSCREEN
# ) # fullscreen mode
bird = Bird(canvas)
pipes = deque([Pipe(canvas)])
clock = pg.time.Clock()
frame_count = 0
pause = main_exit = False
while not main_exit:
clock.tick(60) # set fps rate
for event in pg.event.get():
if event.type == QUIT:
main_exit = pause = True
break
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
main_exit = pause = True
if event.key == K_UP or event.key == K_w:
bird.up()
if event.key == K_p:
pause = not pause # pause the action
if not pause:
frame_count += 1
canvas.fill(WHITE) # clear surface
for pipe in pipes:
pipe.update()
pipe.show()
if pipe.hits(bird):
print(' HIT')
if pipes[0].is_offscreen():
pipes.popleft()
bird.update()
bird.show()
if frame_count % 300 == 0:
pipes.append(Pipe(canvas))
pg.display.update()
pg.quit()
if __name__ == '__main__':
main()