@Tobs: Ein paar Anmerkungen zum Quelltext:
Die `Entfernung()`-Funktion wird nirgends verwendet. Der Name hat auch zwei Probleme: 1. er fängt mit einem Grossbuchstaben an, was per Konvention nur Klassennamen sollten (oder Konstanten die komplett in Grossbuchstaben benannt werden), und 2. beschreibt er keine Tätigkeit. Letzteres ist üblich um Funktionen leichter von Werten unterscheiden zu können. Denn `entfernung` könnte ja auch eine Zahl sein die eine Entfernung angibt. Für die Funktion drei Zeilen zu verwenden in denen der gleiche Name an zwei sehr unterschiedliche Datentypen gebunden wird, ist auch keine so gute Idee. Vom Typ her ist eine Entfernung oder ein Abstand ja auch keine Zeichenkette. Diese Umwandlung gehört deshalb auch nicht in so eine Funktion.
`Ball.int_pos()` als unabhängiges Attribut zu führen ist fehleranfällig. So muss man überall wo man das `pos`-Attribut verändert immer darauf achten, dass auch `int_pos` entsprechend gesetzt wird. Dafür würde sich ein `property()`, also ein berechnetes Attribut anbieten. Dazu muss `Ball` von `object` erben, was man generell machen sollte (in Python 2.x, und in Python 3.x schadet es zumindest nicht).
Etwas später werden die Werte dann auch sehr komisch ungleich behandelt. Wenn das Ziel überschritten wurde, wird nur `int_pos` auf das Ziel gesetzt, `pos` dagegen normal berechnet, also über das Ziel hinaus. Dazu wird auch noch ein Flag `Ueberschritten` über die gesamte Funktion verteilt. Wenn man die Bedingungen und Aktionen sinvoll umstellt, braucht man weder das `Ueberschritten`-Flag, noch den Test ob die Länge des Vektors grösser als Null ist bevor man teilt. Damit kann die Methode ein cirka ein Drittel kürzer und damit auch leichter verständlich werden. `vektor_distance` sollte man dann noch in `vector_length` umbenennen, damit es die richtige Bezeichnung für den Wert ist und nicht Deutsch und Englisch in einem Bezeichner vermischt werden.
Der Hauptprogramm-Quelltext sollte nicht auf Modulebene stehen, sondern in einer Funktion. Beziehungsweise in mehr, denn das ist eine ganz schöne Menge an Code der unterschiedliche Sachen tut und Wiederholungen enthält, die man sinnvoll auf Funktionen verteilen kann.
Ausserdem enthät das Hauptprogramm eine Menge „magischer” Zahlen die man entweder als Konstanten definieren, oder aus diesen Konstanten berechnen kann. Noch schlimmer ist, dass ein paar Zeichenketten diese magischen Zahlen auch noch mal die gleichen magischen Zahlen enthalten. Überleg mal wo Du überall Änderungen von Hand vornehmen musst, wenn Du die Anzeigegrösse ändern willst. Im Idealfall gibt es dafür *eine* Konstante im Programm die man ändern muss, und das war es dann.
Der `this_`-Präfix bei `this_ball` macht keinen Sinn. Und `step_distance` als Name für die Antwort auf die Frage nach der Geschwindigkeit hiesse besser `speed`. In der Frage steckt auch wieder eine magische Zahl, die man als Konstante festlegen sollte, damit man sie einfach ändern kann. Dann hätte man auch nicht das Problem dass der ganze Code von 100 FPS ausgeht *ausser* die Stelle an der mit der `clock` dann tatsächlich auf 60 FPS hingearbeitet wird. Das ist Dir wahrscheinlich nicht aufgefallen weil die FPS-Einbruchswarnung entweder *nie* oder *immer* ausgegeben wird, weil Du dort nicht den FPS-Wert mit einer Zahl vergleichst, sondern die *Methode*. Du hättest sie an der Stelle auch *aufrufen* müssen.
Warum hast Du Zeichenketten am Anfang an an so nichtssagende, durchnummerierte Namen wie `message1_str` gebunden die dann später benutzt werden und man an der Stelle dann nicht mehr sieht was da eigentlich ausgegeben wird. Also nicht mal mehr die Bedeutung am Namen, denn so etwas wie `message2_str` sagt nicht wirklich etwas aus, zumindest nicht dass was der Leser an der Stelle gerne wissen möchte.
Auch die anderen `message*`-Variablen werden relativ weit von der Verwendung entfernt definiert. Und unnötig vor der Schleife wegen diesem gruseligen Ausnahmedurcheinander was Du Dir mit `finish` eingehandelt hast. Wenn der Name vor der ersten Verwendung nicht definiert ist, dann „behandelt” man das nicht mit ``except``, schon gar nicht ohne eine konkrete Ausnahme dort anzugeben, sondern in dem man doch bitte vorher einfach mal `finish` definiert. Damit spart man sich dann eine ganze Menge von dem Code der nur existiert um diesen einen Spezialfall abzufangen.
Die ganzen ``if``-Bedingungen in der Ereignisverarbeitung schliessen sich gegenseitig aus. Hier sollte man also mit ``elif``\s arbeiten.
Wenn man bei den `blit()`-Aufrufen für die Texte mal die Zeilen die dafür benötigt werden direkt darüber gruppiert und das nicht alles über die gesamte Schleife verteilt, dann sieht man deutlich das immer die gleichen drei bis vier Schritte nur mit anderen Werten durchgeführt werden. Das lässt sich also prima in eine Funktion auslagern. Damit lassen sich einige Zeilen einsparen.
Statt mit `int()`, `str()`, und ``+`` zu hantieren um Werte und Zeichenketten zusammenzusetzen wie in BASIC, bietet Python Zeichenkettenformatierung mittels der `format()`-Methode auf Zeichenketten und Platzhaltern und Formatierungsangaben in der Zeichenkette.
Mal angefangen die Punkte umzusetzen:
Code: Alles auswählen
#!/usr/bin/env python
# coding: utf8
import easygui
import pygame
DISPLAY_WIDTH, DISPLAY_HEIGHT = DISPLAY_SIZE = (640, 480)
PIXEL_PER_SECOND = 100
DEFAULT_FONT_SIZE = 18
BLACK, WHITE, RED = (0, 0, 0), (255, 255, 255), (255, 0, 0)
BALL_COLOR = (128, 255, 128)
BALL_RADIUS = DISPLAY_HEIGHT // 20
def blit_text(
surface, text, position, color=WHITE, font_size=DEFAULT_FONT_SIZE
):
surface.blit(
pygame.font.Font(None, font_size).render(text, 1, color), position
)
class Ball(object):
def __init__(self, pos):
self.pos = pos
@property
def int_pos(self):
return (int(self.pos[0]), int(self.pos[1]))
def move(self, target, distance):
vector = (target[0] - self.pos[0] , target[1] - self.pos[1])
vector_length = (vector[0]**2 + vector[1]**2)**0.5
if distance >= vector_length:
self.pos = target
else:
self.pos = (
self.pos[0] + vector[0] / vector_length * distance,
self.pos[1] + vector[1] / vector_length * distance
)
def main():
speed = float(
easygui.enterbox(
'Geschwindigkeit in {} Pixel/Sekunde'.format(PIXEL_PER_SECOND)
)
)
pygame.init()
screen = pygame.display.set_mode(DISPLAY_SIZE)
clock = pygame.time.Clock()
ball = Ball(screen.get_rect().center)
target = ball.int_pos
any_mouse_button_down = False
running = True
while running:
clock.tick(PIXEL_PER_SECOND)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
any_mouse_button_down = True
target = event.pos
elif event.type == pygame.MOUSEBUTTONUP:
any_mouse_button_down = False
elif event.type == pygame.MOUSEMOTION:
if any_mouse_button_down:
target = event.pos
ball.move(target, speed)
screen.fill(BLACK)
pygame.draw.circle(screen, BALL_COLOR, ball.int_pos, BALL_RADIUS)
blit_text(screen, 'Klick mit der Maus irgendwo hin!', (10, 10))
blit_text(screen, u'Du kannst die Maus auch gedrückt halten!', (10, 30))
blit_text(screen, '{0:.0f} FPS'.format(clock.get_fps()), (560, 10), RED)
blit_text(screen, 'x: {0}'.format(target[0]), (500, 30))
blit_text(screen, 'y: {0}'.format(target[1]), (500, 50))
distance = int(
(
(target[0] - ball.int_pos[0])**2
+ (target[1] - ball.int_pos[1])**2
)**0.5
)
blit_text(screen, 'Abstand: {0}'.format(distance), (10, 90))
blit_text(screen, 'x: {0}'.format(ball.int_pos[0]), (400, 30))
blit_text(screen, 'y: {0}'.format(ball.int_pos[1]), (400, 50))
if clock.get_fps() < PIXEL_PER_SECOND * 0.95:
blit_text(screen, 'WARNUNG: FPS EINBRUCH!', (10, 110), RED, 28)
pygame.display.flip()
if __name__ == '__main__':
main()