@Sarius: Wo hast Du denn den Code gefunden? Im Buch? *So*? Das sieht nämlich sehr danach aus als wurde dort ein Programm in mehreren Schritten entwickelt und anstatt die neuen, erweiterten Teile an der passenden Stelle in das Programm einzubauen, hat da jemand einfach alles aus dem Buch der Reihe nach in eine Datei abgeschrieben ohne zu verstehen was da in welcher Reihenfolge gemacht werden soll.
Aber selbst wenn man die Reihenfolge in Ordnung bringt, ist das kein gutes Programm von dem man sich abschauen sollte wie man programmiert.
Zu der Reihenfolgenänderung gehört unter anderem das die Importe alle oben im Programm stehen, gefolgt von der Definition der Konstanten. Dann kommen die Funktionsdefinitionen. Und der ganze Code zwischen den Funktionsdefinitionen der nichts importiert oder Konstanten definiert, das Hauptprogramm also, gehört ebenfalls in eine Funktion.
Durch dieses ganze Vermischen von Importen, Funktionsdefinitionen, und Hauptprogramm schleichen sich nämlich schnell mal fehler ein. Wenn man aufräumt, stellt man beispielsweise fest, dass die Konstante `BUB_CHANCE` zwei mal definiert ist. Glücklicherweise mit dem gleichen Wert. Aber man Stelle sich mal vor eine *Konstante* hätte in verschiedenen Abschnitten eines Programms unterschiedliche Werte. Viel Spass bei der Fehlersuche.
Die Namen sind teilweise schlecht bis sehr schlecht. Ich hoffe im Originalbuch sind die etwas besser, denn ich habe die Vermutung das ein paar von den Abkürzungen darauf zurück zu führen sind, das die originalen englischen Namen kürzer sind, und die Übersetzer die Vorgabe hatten die Länge beim Übersetzen nicht zu stark zu ändern, damit der Satz der Quelltexte möglichst nicht angepasst werden muss.
Es wurde dann aber auch sehr inkonsequent übersetzt. Beispielsweise die Funktion `zeige_punkte()` bekommt trotzdem noch ein englisches `score`-Argument übergeben.
Bevor man das Hauptprogramm in eine Funktion steckt und damit die ganzen globalen Variablen verschwinden und man die an die anderen Funktionen als Argumente übergeben muss, sollte man noch versuchen die Anzahl der Variablen zu reduzieren. Das beispielsweise die Blasen aus drei parallel geführten Listen bestehen, macht das Programm unnötig komplexer und auch fehleranfälliger.
Wenn man die einzelnen Blasen in Tupel steckt, kommt man mit *einer* Liste für alle Blasen aus. Die beiden Einzelteile des Schiffs kann man auch in ein Tupel stecken und damit zu einem Wert machen. Wenn man die IDs in umgekehrter Reihenfolge in das Tupel steckt, dann ist genau wie bei den Blasen das erste Element die ID, die beispielsweise von der Funktion zur Entfernungsmessung verwendet wird.
Dann kann man sich daran machen aus den Funktionen tatsächliche Funktionen zu machen, die was sie brauchen als Argumente übergeben bekommen und Ergebnisse als Rückgabewerte zurück geben. `erstelle_bubble()` beispielsweise braucht den `canvas` und muss selbst keine Liste(n) manipulieren, sondern könnte das Tupel mit den Daten der erzeugten Blase als Rückgabewert liefern.
Was ziemlich unpythonisch ist, sind die die beiden Funktionen die Blasen entfernen. Statt da rückwärts über einen Laufindex zu iterieren um Blasen aus der/den originalen Liste(n) zu entfernen, würde man in Python einfach neue Listen ohne die Blasen erstellen die entfernt werden sollen.
Eigentlich kommt man bei GUI-Programmierung nicht um objektorientierte Programmierung herum, oder zumindest Closures. In diesem Fall muss man mindestens in einem Fall `functools.partial()` oder einen ``lambda``-Ausdruck verwenden. Nämlich bei der Rückruffunktion für Tastendrücke. Denn die braucht den `Canvas` und das Schiff zusätzlich zum Ereignisobjekt.
Zwischenstand sähe dann so aus:
Code: Alles auswählen
from functools import partial
from math import sqrt
from random import randint
from time import sleep, time
import tkinter as tk
HEIGHT = 500
WIDTH = 800
MID_X = WIDTH / 2
MID_Y = HEIGHT / 2
MIN_BUBBLE_RADIUS = 10
MAX_BUBBLE_RADIUS = 30
MAX_BUBBLE_SPEED = 10
BUBBLE_CHANCE = 10
GAP = 100
assert GAP >= MAX_BUBBLE_RADIUS * 2
SHIP_RADIUS = 15
SHIP_SPEED = 10
TIME_LIMIT = 30
BONUS_SCORE = 1000
def move_ship(canvas, ship, x_delta, y_delta):
canvas.move(ship[0], x_delta, y_delta)
canvas.move(ship[1], x_delta, y_delta)
def on_key(canvas, ship, event):
if event.keysym == 'Up':
move_ship(canvas, ship, 0, -SHIP_SPEED)
elif event.keysym == 'Down':
move_ship(canvas, ship, 0, SHIP_SPEED)
elif event.keysym == 'Left':
move_ship(canvas, ship, -SHIP_SPEED, 0)
elif event.keysym == 'Right':
move_ship(canvas, ship, SHIP_SPEED, 0)
def create_bubble(canvas):
x = WIDTH + GAP
y = randint(0, HEIGHT)
radius = randint(MIN_BUBBLE_RADIUS, MAX_BUBBLE_RADIUS)
bubble_id = canvas.create_oval(
x - radius, y - radius, x + radius, y + radius, outline='white'
)
speed = randint(1, MAX_BUBBLE_SPEED)
return (bubble_id, radius, speed)
def move_bubbles(canvas, bubbles):
for bubble_id, _, speed in bubbles:
canvas.move(bubble_id, -speed, 0)
def get_center(canvas, obj):
x_1, y_1, x_2, y_2 = canvas.coords(obj[0])
return ((x_1 + x_2) / 2, (y_1 + y_2) / 2)
def remove_bubbles(canvas, bubbles):
result = list()
for bubble in bubbles:
bubble_id = bubble[0]
x, _ = get_center(canvas, bubble)
if x < -GAP:
canvas.delete(bubble_id)
else:
result.append(bubble)
return result
def distance(canvas, obj_a, obj_b):
x_1, y_1 = get_center(canvas, obj_a)
x_2, y_2 = get_center(canvas, obj_b)
return sqrt((x_2 - x_1)**2 + (y_2 - y_1)**2)
def handle_collisions(canvas, ship, bubbles):
points = 0
remaining_bubbles = list()
for bubble in bubbles:
bubble_id, radius, speed = bubble
if distance(canvas, ship, bubble) < (SHIP_RADIUS + radius):
points += radius + speed
canvas.delete(bubble_id)
else:
remaining_bubbles.append(bubble)
return (points, remaining_bubbles)
def show_score(canvas, score_text, score):
canvas.itemconfig(score_text, text=str(score))
def show_time(canvas, time_text, time_left):
canvas.itemconfig(time_text, text=str(time_left))
def main():
window = tk.Tk()
canvas = tk.Canvas(window, width=WIDTH, height=HEIGHT, bg='darkblue')
canvas.pack()
window.attributes('-topmost', 1)
ship = (
canvas.create_oval(0, 0, 30, 30, outline='red'),
canvas.create_polygon(5, 5, 5, 25, 30, 15, fill='red')
)
move_ship(canvas, ship, MID_X, MID_Y)
canvas.bind_all('<Key>', partial(on_key, canvas, ship))
bubbles = list()
canvas.create_text(50, 30, text='ZEIT', fill='white')
canvas.create_text(150, 30, text='PUNKTE', fill='white')
time_text = canvas.create_text(50, 50, fill='white')
score_text = canvas.create_text(150, 50, fill='white')
score = 0
bonus = 0
end_time = time() + TIME_LIMIT
while time() < end_time:
if randint(1, BUBBLE_CHANCE) == 1:
bubbles.append(create_bubble(canvas))
move_bubbles(canvas, bubbles)
bubbles = remove_bubbles(canvas, bubbles)
points, bubbles = handle_collisions(canvas, ship, bubbles)
score += points
if int(score / BONUS_SCORE) > bonus:
bonus += 1
end_time += TIME_LIMIT
show_score(canvas, score_text, score)
show_time(canvas, time_text, int(end_time - time()))
window.update()
sleep(0.01)
canvas.create_text(
MID_X, MID_Y, text='GAME OVER', fill='white', font=('Helvetica', 30)
)
canvas.create_text(
MID_X, MID_Y + 30, text='Punkte: ' + str(score), fill='white'
)
canvas.create_text(
MID_X, MID_Y + 45,
text='Bonus-Zeit: ' + str(bonus * TIME_LIMIT),
fill='white',
)
window.mainloop()
if __name__ == '__main__':
main()