Breakout (heute auch bekannt als Brick Breaker) ist ein Computerspiel, das 1976 u. a. von Steve Wozniak entwickelt wurde. Steve Wozniak war später maßgeblich an der Gründung der Firma Apple beteiligt. Weitere Informationen zum Spiel findest du auf https://de.wikipedia.org/wiki/Breakout_(Computerspiel).
Hier kannst du eine JavaScript-Version ausprobieren: http://breakout.enclavegames.com/lesson10.html
Diese Aufgabe beschäftigt sich mit der Entwicklung eines Breakout-Spiels. Dazu musst du dir eine lokale Python-Installation aufsetzen.
Möglicherweise funktioniert Python 3 auf deinem System bereits. Du musst Python 3 für diese Aufgabe verwenden, denn der Umgang mit Sonderzeichen (Umlaute etc.) ist in Python 2 nicht besonders komfortabel. Überprüfe zunächst deine Python Version; dazu musst du zunächst ein Terminal öffnen:
Führe dann den folgenden Befehl aus: "python3 --version" (oder wenn dies eine Fehlermeldung wirft, versuche es mit "python --version"). Wenn du bei einem der Befehle keine Fehlermeldung bekommst und in der Konsole etwas ähnliches steht wie "Python 3.7.5", kannst du den dazugehörigen Befehl ab nun zum Starten von Python 3 verwenden. Wichtig: wenn hier etwas wie "Python 2.7.17" steht, hast du eine Installation der Version 2, welche für die Aufgabe nicht verwendet werden kann. Wenn du beidesmal eine Fehlermeldung bekommst, gehe direkt zur Installation.
Ab hier wird die Aufgabe mit dem Befehl "python3" gestellt, auf deinem System kann aber auch der Befehl "python" die korrekte Installation der Version 3 starten (wie zuvor beschrieben).
Führe nun den folgenden Befehl aus, um zu testen, ob du das GUI-Toolkit Tkinter installiert hast: "python3 -m tkinter". Wenn du ein kleines Testfenster siehst, hast du Python und Tkinter bereits korrekt installiert.
Wenn du eine Fehlermeldung bekommen hast, lade dir Python (mit Tkinter) kostenfrei hier herunter:
Jetzt sollte deine Python 3-Installation mit Tkinter funktionieren. Um dies zu testen, kopiere die folgende Vorlage in eine neue leere Datei "breakout.py". Achte darauf, dass du in deinem Editor zum Abspeichern "UTF-8" als Encoding verwendest, sonst funktionieren die Umlaute und Sonderzeichen in der deutschen Sprache nicht.
Mögliche Editoren zum Bearbeiten von Python-Skripten sind etwa: PyCharm, Notepad++, Sublime Text, Atom Editor, Eclipse mit Python Plugins, vim (Linux und MacOS), gedit (Linux). Aber die Wahl deines Lieblingseditors steht dir frei.
Auf Windows solltest du die Datei mit einem Doppelklick ausführen können. Auf anderen Systemen musst du mit dem Terminal in das Verzeichnis wechseln, in dem diese neue Datei liegt. Das kannst du mit dem Befehl "cd </vollständiger/Pfad/zu/der/Datei>" machen. Dann kannst du die Datei mit "python3 breakout.py" ausführen. Achte darauf, dass du Python 3 verwendest.
Wenn alles richtig installiert ist, erscheint ein Fenster, in dem das Breakout-Spiel Form annehmen soll.
Du musst den Code in der Datei "breakout.py" bei "..." erweitern.
# Das ist die Vorlage, die du in die neue leere Datei "breakout.py" kopieren kannst.
# Das Encoding muss UTF-8 sein wegen der Sonderzeichen in der deutschen Sprache:
# -*- coding: utf-8 -*-
# importiere benötigte Module
from tkinter import *
# folgende Elemente gibt es im Spiel:
#
# _____________________________ obere Begrenzung
# |=== === === === === === ===|
# |=== === === === === === ===| Ziegelsteine, die abzuräumen sind
# |=== === === === === === ===|
# | |
# | | linke und rechte Begrenzung
# | |
# | O | Ball
# | |
# | |
# | |
# | ##### | Plattform, mit der der Ball im Spiel gehalten werden muss
# das Koordinatensystem des Spielfensters is folgendermaßen aufgebaut:
# der Ursprung ist in der linken oberen Ecke und die x-Koordinate läuft nach rechts.
# die y-Koordinate läuft nach unten.
# Alle Koordinaten in diesem Fenster beziehen sich auf dieses Koordinatensystem.
# Das bedeutet, dass die Plattform eine große y-Koordinate hat, auch wenn sie
# unten im Fenster angezeigt wird und das zunächst nicht intuitiv erscheint.
# Jedoch entstammt diese Denkweise der Art, wie man in Europa schreibt:
# von links nach rechts und von oben nach unten. Auf einem Blatt Papier ist der
# "Ursprung" auch links oben; dort beginnt man zu schreiben.
#
# O___x->______________________
# | === === === === === |
# y === === === === === |
# | === === === === === |
# | |
# | |
# | |
# | |
# | |
# | |
# | |
# | ##### |
# der Faktor bestimmt, wie groß das Fenster dargestellt wird
skalierungs_faktor = 2
# definiere dafür benötigte Größen in Pixelwertenspielfeld_breite = 195 * skalierungs_faktor
spielfeld_breite = 195 * skalierungs_faktor
spielfeld_höhe = 195 * skalierungs_faktor
ball_radius = 3 * skalierungs_faktor
plattform_breite = 50 * skalierungs_faktor
plattform_höhe = 5 * skalierungs_faktor
ziegelstein_breite = 26 * skalierungs_faktor
ziegelstein_höhe = 5 * skalierungs_faktor
ziegelsteine_abstand = 2 * skalierungs_faktor
# am Anfang ist das Spiel pausiert
pausiert = True
# am Anfang ist das Spiel noch nicht gewonnen
gewonnen = False
# am Anfang ist das Spiel noch nicht verloren
verloren = False
# lege die Startpositionen der Plattform und des Balls fest
# die Position des Balls hat x- und y-Koordinaten;
# der Ball beginnt sitzend auf der Mitte der Plattform
ball_position = [spielfeld_breite / 2 - ball_radius / 2, spielfeld_höhe - plattform_höhe - ball_radius]
# die Position der Plattform hat nur eine x-Koordinate,
# da die Pattform am unteren Spielfeldrand bleibt;
# sie startet in der Bildschirmmitte unten
plattform_position = spielfeld_breite / 2 - plattform_breite / 2
# außerdem haben der Ball und die Plattform eine Geschwindigkeit, mit der sie sich bewegen
# der Ball hat eine Geschwindigkeit in x- und y-Richtung,
# die Plattform nur in x-Richtung
# (die Pattform bleibt am unteren Spielfeldrand)
ball_geschwindigkeit = [1 * skalierungs_faktor, -3 * skalierungs_faktor]
plattform_geschwindigkeit = 0 * skalierungs_faktor
# die Liste der Ziegelsteine, die man abräumen muss
ziegelsteine = []
# In dieser Funktion wird der Ball um eine Position weiter bewegt
# und dann ausgewertet, ob eine Begrenzung, ein Ziegelstein oder
# die Plattform getroffen wurde.
# Dazu wird nur die Geschwindigkit des Balls verwendet und die Position
# des Balls geändert.
def bewege_ball():
# mache die benötigten Variablen sichtbar
global ball_position
global ball_geschwindigkeit
global gewonnen
global verloren
# wenn das Spiel pausiert ist, bewege den Ball nicht
if pausiert == True:
return;
# bewege den Ball um seine aktuelle Geschwindigkeit weiter
ball_position[0] += ball_geschwindigkeit[0]
ball_position[1] += ball_geschwindigkeit[1]
# überprüfe, ob etwas getroffen wurde und reagiere entsprechend
if ball_trifft_plattform():
# mit dieser Formel wird beschrieben, wo der Ball die Plattform getroffen hat
getroffene_stelle = (ball_position[0] - (plattform_position + plattform_breite / 2))
# der x-Wert der Geschwindigkeit des Balls wird daraufhin angepasst
# so kann man mit den Ecken der Plattform den Ball etwas "zielen"
ball_geschwindigkeit[0] += getroffene_stelle / 15
# die y-Geschwindigkeit wird umgekehrt, damit prallt der Ball zurück
ball_geschwindigkeit[1] = -ball_geschwindigkeit[1]
elif ball_trifft_ziegelstein() != None:
# der Ball hat einen Ziegelstein getroffen
# der Ziegestein muss abgeräumt werden und der Ball zurückprallen
ziegelstein = ball_trifft_ziegelstein()
ziegelsteine.remove(ziegelstein)
ball_geschwindigkeit[1] = -ball_geschwindigkeit[1]
# wenn es keine Ziegelsteine mehr gibt, hat man gewonnen
if len(ziegelsteine) == 0:
gewonnen = True
elif ball_trifft_begrenzung_oben():
# der Ball hat die Begrenzung oben getroffen
# und muss zurückprallen
ball_geschwindigkeit[1] = -ball_geschwindigkeit[1]
elif ball_trifft_begrenzung_unten():
# der Ball hat die Begrenzung unten getroffen
# er ist aus dem Spielfeld raus und man hat verloren
# ja, das ist Hardcore-Breakout :-)
verloren = True
elif ball_trifft_begrenzung_links():
# der Ball hat die Begrenzung links getroffen
# und muss zurückprallen
ball_geschwindigkeit[0] = -ball_geschwindigkeit[0]
elif ball_trifft_begrenzung_rechts():
# der Ball hat die Begrenzung rechts getroffen
# und muss zurückprallen
ball_geschwindigkeit[0] = -ball_geschwindigkeit[0]
# In dieser Funktion wird die Plattform um eine Position weiter bewegt
# und dann ausgewertet, ob eine Begrenzung getroffen wurde.
# Dazu wird nur die Geschwindigkit der Plattform verwendet und die Position
# der Plattform geändert.
def bewege_plattform():
# mache die benötigten Variablen sichtbar
global plattform_position
global plattform_geschwindigkeit
# wenn das Spiel pausiert ist, bewege die Plattform nicht
if pausiert == True:
return;
# die Plattform bremst von selbst ab, sonst wäre sie unmöglich zu kontrollieren
plattform_geschwindigkeit /= 1.2
# bewege die Plattform um ihre aktuelle Geschwindigkeit weiter
plattform_position += plattform_geschwindigkeit
# überprüfe, ob eine Begrenzung getroffen wurde und reagiere entsprechend
if plattform_stoppt_links():
# die Plattform hat die Begrenzung links getroffen und muss stoppen
plattform_position = 0
plattform_geschwindigkeit = 0
elif plattform_stoppt_rechts():
# die Plattform hat die Begrenzung rechts getroffen und muss stoppen
plattform_position = spielfeld_breite - plattform_breite
plattform_geschwindigkeit = 0
# In dieser Funktion wird ausgewertet, ob der Ball mit seiner aktuellen
# Position die Plattform berührt oder sogar schon in der Plattform ist.
# Da wir das Spiel als Simulation aufgebaut haben und sich der Ball in
# schnellen kleinen Schritten fortbewegt, kann es passieren, dass wir den
# Moment verpasst haben, als sich der Ball über die Umrandung der Plattform
# in diese hinein bewegt hat. Wenn wir nun aber schnell genug reagieren und
# den Ball direkt zurückprallen lassen, sieht man das bei den schnellen kleinen
# Schritten nicht.
# Du kannst immer mit der Variable "ball_radius" versuchen, dass nicht das
# Zentrum des Balls für die Berechnung verwendet wird, sondern dessen Rand,
# dann sieht das Spiel etwas hübscher aus.
def ball_trifft_plattform():
# die x-Koordinate des Balls muss innerhalb der Grenzen der Plattform sein
# wenn der Ball geradeaus nach unten fliegen würde, träfe er also auf die Plattform
horizontal_berührt = False # ... hier musst du "False" durch passenden Code ersetzen
# die y-Koordinate des Balls muss so sein, dass der Ball die Plattform gerade berührt
# oder aber der Ball fliegt gerade seitlich vorbei
vertikal_berührt = False # ... hier musst du "False" durch passenden Code ersetzen
# wenn diese beiden Bedingungen zugleich zutreffen, prallt der Ball zurück
return horizontal_berührt and vertikal_berührt
# In dieser Funktion wird ausgewertet, ob der Ball mit seiner aktuellen
# Position einen der Ziegelsteine berührt oder sogar schon darin ist.
# Hier gilt das Gleiche wie bei der Plattform, jedoch müssen wir die Liste der
# Ziegelsteine durchschauen, ob einer davon getroffen wurde.
def ball_trifft_ziegelstein():
for z in ziegelsteine:
# die x-Koordinate des Balls muss innerhalb der Grenzen des Ziegelsteins sein
# wenn der Ball geradeaus nach oben fliegen würde, träfe er also den Ziegelstein
horizontal_berührt = False # ... hier musst du "False" durch passenden Code ersetzen
# die y-Koordinate des Balls muss so sein, dass der Ball den Ziegelstein gerade berührt
# oder aber der Ball fliegt gerade seitlich vorbei
vertikal_berührt = False # ... hier musst du "False" durch passenden Code ersetzen
# wenn diese beiden Bedingungen zugleich zutreffen, prallt der Ball zurück
if horizontal_berührt and vertikal_berührt:
return z
# wenn kein Ziegelstein getroffen wurde, gib None zurück
return None
# In dieser Funktion wird ausgewertet, ob der Ball mit seiner aktuellen
# Position oben an die Begrenzung stößt, oder sogar schon darüber hinaus ist.
def ball_trifft_begrenzung_oben():
return False # ... hier musst du "False" durch passenden Code ersetzen
# In dieser Funktion wird ausgewertet, ob der Ball mit seiner aktuellen
# Position unten an die Begrenzung stößt, oder sogar schon darüber hinaus ist.
def ball_trifft_begrenzung_unten():
return False # ... hier musst du "False" durch passenden Code ersetzen
# In dieser Funktion wird ausgewertet, ob der Ball mit seiner aktuellen
# Position links an die Begrenzung stößt, oder sogar schon darüber hinaus ist.
def ball_trifft_begrenzung_links():
return False # ... hier musst du "False" durch passenden Code ersetzen
# In dieser Funktion wird ausgewertet, ob der Ball mit seiner aktuellen
# Position rechts an die Begrenzung stößt, oder sogar schon darüber hinaus ist.
def ball_trifft_begrenzung_rechts():
return False # ... hier musst du "False" durch passenden Code ersetzen
# In dieser Funktion wird ausgewertet, ob die Plattform mit ihrer aktuellen
# Position links an die Begrenzung stößt, oder sogar schon darüber hinaus ist.
def plattform_stoppt_links():
return False # ... hier musst du "False" durch passenden Code ersetzen
# In dieser Funktion wird ausgewertet, ob die Plattform mit ihrer aktuellen
# Position rechts an die Begrenzung stößt, oder sogar schon darüber hinaus ist.
def plattform_stoppt_rechts():
return False # ... hier musst du "False" durch passenden Code ersetzen
# initialisiere die Ziegelsteine
# die Liste der Ziegelsteine enthält die linken oberen Eckpositionen der Ziegelsteine
# da wir bereits festgelegt haben, wie hoch und breit ein Ziegelstein ist, sind diese
# damit eindeutig definiert
def initialisiere_ziegelsteine():
global ziegelsteine
ziegelsteine = []
for y in range(0, 3 * ziegelstein_höhe, ziegelstein_höhe + ziegelsteine_abstand):
for x in range(0, spielfeld_breite - ziegelstein_breite, ziegelstein_breite + ziegelsteine_abstand):
ziegelsteine.append([x, y])
# Funktion, welche die Tastaturevents verwaltet
# damit lässt sich die Plattform mit der Tastatur steuern,
# es werden die Pfeiltasten nach links und rechts verwendet.
def tastatur_events(event):
# mache die benötigten Variablen sichtbar
global pausiert
global gewonnen
global verloren
global ball_position
global ball_geschwindigkeit
global plattform_position
global plattform_position
global plattform_geschwindigkeit
# wertet die gedrückte Taste aus
taste = event.keysym
if taste == 'space':
# mit der Leertaste kann man pausieren und fortfahren
pausiert = not pausiert
# starte ein neues Spiel, wenn eines gewonnen oder verloren wurde
if gewonnen == True or verloren == True:
initialisiere_ziegelsteine()
gewonnen = False
verloren = False
ball_position = [spielfeld_breite / 2 - ball_radius / 2, spielfeld_höhe - plattform_höhe - ball_radius]
ball_geschwindigkeit = [1, -3]
plattform_position = spielfeld_breite / 2 - plattform_breite / 2
plattform_geschwindigkeit = 0
pausiert = True
elif taste == 'Left':
# beschleunige die Plattform, wenn die Taste links gedrückt wurde
plattform_geschwindigkeit -= 4 * skalierungs_faktor
elif taste == 'Right':
# beschleunige die Plattform, wenn die Taste rechts gedrückt wurde
plattform_geschwindigkeit += 4 * skalierungs_faktor
# diese Funktion wird jeden Zeitschritt aufgerufen,
# das Spiel ist also eine Simulation
def zeitschritt():
# mache die benötigten Variablen sichtbar
global pausiert
global verloren
global gewonnen
# jeden Zeitschritt bewegt sich der Ball und
bewege_ball()
bewege_plattform()
# die Zeichenfläche leeren
zeichenfläche.delete("all")
# alle nötigen Objekte zeichen
# zeichne zunächst die Ziegelsteine
for z in ziegelsteine:
zeichenfläche.create_rectangle(z[0], z[1], z[0] + ziegelstein_breite, z[1] + ziegelstein_höhe, fill='blue')
# zeichne den Ball
y0 = ball_position[1] - ball_radius
x0 = ball_position[0] - ball_radius
y1 = ball_position[1] + ball_radius
x1 = ball_position[0] + ball_radius
zeichenfläche.create_oval(int(x0), int(y0), int(x1), int(y1), fill='green')
# zeichne die Plattform
zeichenfläche.create_rectangle(plattform_position, spielfeld_höhe - plattform_höhe, plattform_position + plattform_breite, spielfeld_höhe, fill='red')
# wenn das Spiel verloren ist, zeige einen Text
if verloren == True:
nachricht = "Verloren!\n<Leertaste> zum Neustart."
zeichenfläche.create_text(spielfeld_breite / 2, spielfeld_höhe / 2, text=nachricht, font=('Helvetica', 10, 'bold'), justify=CENTER)
pausiert = True
# wenn das Spiel gewonnen ist, zeige einen Text
if gewonnen == True:
nachricht = "Gewonnen!\n<Leertaste> zum Neustart."
zeichenfläche.create_text(spielfeld_breite / 2, spielfeld_höhe / 2, text=nachricht, font=('Helvetica', 10, 'bold'), justify=CENTER)
pausiert = True
# wenn das Spiel pausiert ist, zeige einen Text
if pausiert == True and gewonnen == False and verloren == False:
nachricht = "Pausiert.\n<Leertaste> zum Fortfahren."
zeichenfläche.create_text(spielfeld_breite / 2, spielfeld_höhe / 2, text=nachricht, font=('Helvetica', 10, 'bold'), justify=CENTER)
# zeichne die Zeichenfläche (also das gesamte Spielfeld) neu
zeichenfläche.update()
# einen Timer starten, dass die Funktion "zeitschritt" nach 20 Millisekunden erneut aufgerufen wird
# das ist das Herzstück der Simulation
fenster.after(20, zeitschritt)
# verschiebe das Fenster in die Bildschirmmitte
def zentriereFenster(fenster):
fenster.update_idletasks()
b = fenster.winfo_width()
h = fenster.winfo_height()
x = (fenster.winfo_screenwidth() // 2) - (b // 2)
y = (fenster.winfo_screenheight() // 2) - (h // 2)
fenster.geometry('{}x{}+{}+{}'.format(b, h, x, y))
# öffne das Fenster, in dem das Spiel dargestellt wird
fenster = Tk(className = 'Breakout')
zeichenfläche = Canvas(fenster, width=spielfeld_breite, height=spielfeld_höhe)
zeichenfläche.pack()
zentriereFenster(fenster)
# binde die Funktion "tastatur_events" an das Fenster,
# so dass diese bei Tastenevents aufgerufen wird
fenster.bind('<Key>', tastatur_events)
# initialisiere die Ziegelsteine
initialisiere_ziegelsteine()
# starte die Simulation
zeitschritt()
# Tkinter behandelt Timer- und Tastaturevents in der Funktion "mainloop()"
mainloop()
Wenn ihr Fragen oder Anregungen zu der Aufgabe (oder Lösung) habt, dann tauscht euch gerne im Chat darüber aus oder schreibt uns eine E-Mail (an info@bw-ki.de).
Wir freuen uns auch immer über Feedback (auch unter info@bw-ki.de):