In dieser Aufgabe sollt ihr einen Algorithmus entwickeln, der Minesweeper spielen kann. Hier findet ihr weitere Informationen zum Spiel: https://de.wikipedia.org/wiki/Minesweeper.
Unter diesem Link könnt ihr das Spiel ausprobieren: http://minesweeperonline.com/#beginner.
Minesweeper ist ein einfaches Denkspiel, das bekannt wurde, da es mit Microsoft Windows mitgeliefert wurde. Das Spielfeld ist in Zellen unterteilt; je nach Schwierigkeitsgrad gibt es mehr oder weniger Zellen. Im Spielfeld wird eine Anzahl Minen zufällig verteilt; auch diese Anzahl bestimmt den Schwierigkeitsgrad. Minesweeper wird mit der Maus gespielt: mit einem Linksklick deckt man eine Zelle auf und mit einem Rechtsklick markiert man eine Zelle mit einer Fahne.
Wenn man eine Zelle aufdeckt, kann diese eine Mine enthalten, oder auch nicht. Deckt man eine Zelle auf, die keine Mine enthält, so wird eine Zahl sichtbar. Diese gibt an, wie viele Minen sich in der Nachbarschaft befinden. Die Zellen, in denen Minen versteckt sind, müssen nun durch Kombinatorik der aufgedeckten Zahlen gefunden und mit Fahnen markiert werden. Hat man alle Minen korrekt markiert, so ist das Spiel gewonnen. Deckt man jedoch eine Mine auf, so hat man sofort verloren.
Beim ersten Klicken kann man niemals eine Mine aufdecken, da die Minen erst danach zufällig platziert werden.
Oft kann man durch Kombinieren verschiedener Zahlen herausfinden, wo eine Mine sein muss, oder ob ein Feld minenfrei ist. Es kann jedoch auch vorkommen, dass man durch die Zahlen nicht zweifelsfrei feststellen kann, ob sich eine Mine unter einem Feld befindet. In diesem Fall muss man raten, doch dazu später mehr.
Zellen in der Mitte des Spielfelds haben 8 Nachbarn und können somit 0 bis 8 Minen in der Umgebung haben. Zellen am Rand oder in der Ecke des Spielfelds haben weniger Nachbarn (5 oder 3) und können dementsprechend auch 0 bis 5 oder 0 bis 3 Minen in der Nachbarschaft haben. Das Spielfeld bei Minesweeper geht also *nicht* über die Ränder hinaus und *nicht* auf der anderen Seite weiter.
Für diese Aufgabe verwenden wir wieder eine lokale Python-Installation, wie sie auch schon für die Aufgaben "Breakout" und "Gravitationssimulation" verwendet wurde. Die Installationsanleitung haben wir noch einmal hier angehängt:
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 "minesweeper.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 Spiel dargestellt wird.
Du musst den Code in der Datei "minesweeper.py" erweitern.
In der Vorlage ist ein spielbares Minesweeper Spiel gegeben, das du auch zunächst ausprobieren und spielen kannst. Mit der Taste "R" kannst du ein neues Spiel starten und mit der Taste "A" die Funktion aufrufen, in der du den Algorithmus entwickeln sollst.
Es gibt verschiedenste Situationen in Minesweeper, und oft ist es nicht klar, wo sich die Minen befinden.
Ein wiederkehrendes Muster beim Spielen ist folgendes: eine Zelle muss eine Mine enthalten, da eine Nachbarzelle, die nur über eine Ecke verbunden ist, eine Zahl anzeigt, die eine Mine in dieser Zelle erzwingt.
Da somit die Zahlen mancher der Nachbarzellen "erfüllt" sind, können auch diese aufgedeckt werden. Es muss also, so lange das Spiel nicht gewonnen wurde, für alle Zellen überprüft werden, ob sie sicher eine Mine sind, oder sicher aufgedeckt werden können. Die Regeln, die ein sicheres Markieren oder Aufdecken ermöglichen sollst du in dieser Aufgabe herausfinden und implementieren. Diese Aufgabe ist also wieder eine Knobelaufgabe, es gibt verschiedenste Wege, einen solchen Algorithmus zu entwickeln.
Jedoch kann es auch Situationen geben, bei denen man raten muss. Schau dir dazu die folgenden Bilder an.
In der untersten Reihe der Zellen kann man nur raten, welche der beiden Möglichkeiten (Minen in den roten oder gelben Zellen) die tatsächlich auftretende ist.
Man muss also auf gut Glück eine Zelle aufdecken und danach wieder kombinatorisch weiter machen.
In einem solchen Fall darf dein Algorithmus selber eine Entscheidung treffen oder aber einfach anhalten, so dass die Spielerin oder der Spieler die Entscheidung treffen muss. Die Entscheidung kann ja natürlich auch schief gehen, und dann ist das Spiel vorbei...
Wir sind gespannt auf eure Ideen, aber wir schätzen die Aufgabe als nicht ganz so einfach ein. Viel Spaß beim Knobeln und Tüfteln! Ihr dürft beliebig Funktionen definieren, um euren Algorithmus zu strukturieren und euch die Arbeit zu erleichtern.
# Das ist die Vorlage, die du in die neue leere Datei "minesweeper.py" kopieren kannst.
# Hier kannst du eine Online-Version von Minesweeper ausprobieren:
# http://minesweeperonline.com/#beginner
# Das Encoding muss UTF-8 sein wegen der Sonderzeichen in der deutschen Sprache:
# -*- coding: utf-8 -*-
################################################################################
## Unterhalb dieser Linie kannst du deinen Algorithmus implementieren. ##
################################################################################
# Die Konstanten, wie groß das Spielfeld ist und wie viele Minen darin enthalten
# sind, werden beim Start des Spiels in der Konsole ausgegeben.
# In dieser Funktion soll dein Algorithmus zur automatischen Lösung von
# Minesweeper implementiert werden.
# Wenn du die Taste "A" im Spielfenster drückst, wird diese Funktion aufgerufen.
# Das erste Feld kannst du zufällig aufdecken, da man beim ersten Zug nicht
# verlieren kann. Danach musst du dir eine Strategie überlegen
def automatische_lösung():
print("Jetzt löst der Algorithmus das Spiel.")
# dein Algorithmus kann Felder aufdecken und markieren
# welches Feld zuerst aufgedeckt wird, ist nicht wirklich entscheidend
# beispielsweise:
s = spielfeld_zusammenfassung()
decke_auf(s[31])
markiere(s[63])
# dein Algorithmus soll so lange weiter spielen, bis er gewonnen hat
# wenn er an eine Stelle kommt, an der man raten muss, kann das auch
# der Spielerin oder dem Spieler überlassen werden
# beispielsweise:
while not bereits_gewonnen() and not bereits_verloren():
# mit der Funktion 'time.sleep(0.5)' kannst du eine halbe Sekunde warten,
# so dass du sehen kannst, wie dein Algorithmus arbeitet.
time.sleep(0.5)
# du kannst den folgenden Hinweis entfernen, wenn du anfängst, deinen Algorithmus zu implementieren:
print("Achtung: wenn du noch nichts implementiert hast, ist das hier eine Dauerschleife!")
# Diese Funktion gibt eine Datenstruktur zurück, die nur das enthält, was
# menschliche Spieler auch sehen können.
# Nur mit dieser Funktion darf dein Algorithmus das Spielfeld betrachten!
# Du darfst alle Attribute, die die Klasse "ZelleAlgorithmus" besitzt, verwenden.
def spielfeld_zusammenfassung():
spielfeld = []
for zelle in zellen:
z = ZelleAlgorithmus(zelle.x, zelle.y, zelle.markiert, zelle.aufgedeckt, zelle.minen_in_nachbarschaft)
spielfeld.append(z)
return spielfeld
# Mit dieser Funktion kannst dein Algorithmus auf eine Zelle "klicken"
def decke_auf(zelle):
zelle.decke_auf()
# Mit dieser Funktion kannst dein Algorithmus eine Zelle mit einer Flagge markieren
def markiere(zelle):
zelle.markiere()
# Mit dieser Funktion kann dein Algorithmus abfragen, ob er das Spiel bereits gelöst hat
def bereits_gewonnen():
return gewonnen
# Mit dieser Funktion kann dein Algorithmus abfragen, ob er bereits gescheitert ist
def bereits_verloren():
return verloren
################################################################################
## Der Code unterhalb dieser Linie ist für deinen Algorithmus nicht sichtbar! ##
## Du kannst natürlich nachvollziehen, wie das Spiel implementiert ist, aber ##
## du darfst für deinen Algorithmus nur die Funktionen oberhalb dieser Linie ##
## verwenden! Es wäre ja keine besonders schwere Aufgabe, wenn du nachschauen ##
## könntest, ob in einer bestimmten Zelle eine Mine versteckt ist... ##
################################################################################
# importiere benötigte Module
from tkinter import *
import random
import math
import time
# einige Konstanten für benötigte Größen
zellen_breite_px = 20
zellen_höhe_px = 20
anzahl_zellen_x = 8
anzahl_zellen_y = 8
fenster_breite = max(400, zellen_breite_px * anzahl_zellen_x)
fenster_höhe = 50 + zellen_höhe_px * anzahl_zellen_y
abstand_x = (fenster_breite - zellen_breite_px * anzahl_zellen_x) / 2
abstand_y = 2
# die Minen
anzahl_minen = int(anzahl_zellen_x * anzahl_zellen_y / 8)
zellen = []
# worauf zeigt der Mauszeiger gerade?
maus_über_zelle = None
# der Zähler für die Spielzeit benötigt eine Variable
startzeit = 0
# wenn das Spiel verloren ist, wird diese Variable zu True
verloren = False
# wenn das Spiel gewonnen ist, wird diese Variable zu True
gewonnen = False
# wenn das Spiel gewonnen ist, beinhaltet diese Variable die
# Zeit, die dafür gebraucht wurde
gewonnen_zeit = -1
# zähle die markierten Zellen
anzahl_markierte = 0
# zu Beginn gibt es noch keine Minen, so dass man nicht am Anfang gleich verlieren kann
noch_nicht_geklickt = True
# diese Klasse enthält die Information, die der Algorithmus über eine Zelle wissen darf
# auch leiten die Funktionen "decke_auf()" und "markiere()" auf die Funktionen
# der "echten" Zellen weiter
class ZelleAlgorithmus:
def __init__(self, x, y, markiert, aufgedeckt, minen_in_nachbarschaft):
self.x = x
self.y = y
self.markiert = markiert
self.aufgedeckt = aufgedeckt
self.minen_in_nachbarschaft = minen_in_nachbarschaft
def decke_auf(self):
for zelle in zellen:
if zelle.x == self.x and zelle.y == self.y:
zelle.decke_auf()
def markiere(self):
for zelle in zellen:
if zelle.x == self.x and zelle.y == self.y:
zelle.markiere()
# diese Klasse enthält alle Informationen, die eine Zelle ausmachen
class Zelle:
def __init__(self, x, y, mine):
self.x = x
self.y = y
self.mine = mine
self.markiert = False
self.aufgedeckt = False
self.minen_in_nachbarschaft = 0
def findeAlleNachbarn(self):
nachbarn = []
koordinaten_nachbarn_x = [self.x - 1, self.x, self.x + 1]
koordinaten_nachbarn_y = [self.y - 1, self.y, self.y + 1]
for y in koordinaten_nachbarn_y:
for x in koordinaten_nachbarn_x:
if not (x == self.x and y == self.y) and x >= 0 and y >= 0 and x < anzahl_zellen_x and y < anzahl_zellen_y:
nachbarn.append(zellen[x + anzahl_zellen_y * y])
return nachbarn
def berechneAnzahlMinenInNachbarschaft(self):
self.minen_in_nachbarschaft = 0
for nachbar_zelle in self.findeAlleNachbarn():
if nachbar_zelle.mine and not self.mine:
self.minen_in_nachbarschaft += 1
# decke, wenn es eine Zelle ohne Minen in der Nachbarschaft ist,
# rekursiv auch benachbarte Zellen auf, die ebenfalls keine Mine in der
# Nachbarschaft haben
def decke_auf(self):
global verloren
global noch_nicht_geklickt
if self.markiert:
return
self.aufgedeckt = True
if noch_nicht_geklickt:
noch_nicht_geklickt = False
initialisiere_minen(self)
elif self.mine:
verloren = True
if self.minen_in_nachbarschaft == 0:
for nachbar_zelle in self.findeAlleNachbarn():
if not nachbar_zelle.aufgedeckt and not nachbar_zelle.mine:
nachbar_zelle.decke_auf()
def markiere(self):
global anzahl_markierte
if self.aufgedeckt:
return
if self.markiert:
self.markiert = False
anzahl_markierte -= 1
elif anzahl_markierte < anzahl_minen:
self.markiert = True
anzahl_markierte += 1
def initialisiere():
# mache die benötigten Variablen sichtbar
global verloren
global gewonnen
global gewonnen_zeit
global anzahl_markierte
global noch_nicht_geklickt
global maus_über_zelle
# beim Neustart ist das Spiel nicht verloren und nicht gewonnen
verloren = False
gewonnen = False
# initialisiere weitere Variablen neu
gewonnen_zeit = -1
noch_nicht_geklickt = True
maus_über_zelle = None
anzahl_markierte = 0
# leere die Liste der Zellen, um sie zunächst ohne Minen neu zu initialisieren
zellen.clear()
for y in range(anzahl_zellen_y):
for x in range(anzahl_zellen_x):
zelle = Zelle(x, y, False)
zellen.append(zelle)
# in der Umgebung der zuerst angeklickten Zelle darf es keine Minen geben
# nur so hat man einen Startpunkt
def initialisiere_minen(zuerst_angeklickte_zelle):
global startzeit
# speichere die Unixzeit zu der Zeit, als das Spiel neu gestartet wurde
startzeit = time.time()
# erstelle eine Liste, so dass jede Zelle ein Element erhält
# setze die ersten Elemente darin auf 1, so dass insgesamt
# "anzahl_minen" Elemente den Wert 1 haben
minen_in_feld = []
for i in range(anzahl_zellen_x * anzahl_zellen_y):
if i < anzahl_minen:
minen_in_feld.append(1)
else:
minen_in_feld.append(0)
def mine_in_umgebung(x, y):
umgebung = 2
for j in range(y - umgebung, y + umgebung):
for i in range(x - umgebung, x + umgebung):
if minen_in_feld[i + j * anzahl_zellen_y] == 1:
return True
# verteile die Minen zufällig
random.shuffle(minen_in_feld)
while mine_in_umgebung(zuerst_angeklickte_zelle.x, zuerst_angeklickte_zelle.y):
# verteile die Minen zufällig
random.shuffle(minen_in_feld)
# leere die Liste der Zellen, um sie nun mit Minen neu zu initialisieren
zellen.clear()
for y in range(anzahl_zellen_y):
for x in range(anzahl_zellen_x):
zelle = Zelle(x, y, minen_in_feld[x + y * anzahl_zellen_y] == 1)
zellen.append(zelle)
# berechne auch für alle Zellen, wie viele Minen sich in der Nachbarschaft bfinden
# das ist die Zahl, die auf der Zelle beim Aufdecken angezeigt wird
for z in zellen:
z.berechneAnzahlMinenInNachbarschaft()
# diese Funktion wird jeden Zeitschritt aufgerufen,
# das Spiel ist also eine Simulation
def zeitschritt():
# mache die benötigten Variablen sichtbar
global gewonnen
global gewonnen_zeit
# die Zeichenfläche leeren
zeichenfläche.delete("all")
# alle Zellen zeichen
for z in zellen:
x = z.x * zellen_breite_px + abstand_x
y = z.y * zellen_höhe_px + abstand_y
if not z.aufgedeckt:
zeichenfläche.create_rectangle(x, y, x + zellen_breite_px, y + zellen_höhe_px, fill='gray')
else:
zeichenfläche.create_rectangle(x, y, x + zellen_breite_px, y + zellen_höhe_px, fill='light gray')
# zeichne aufgedeckte Felder
if z.aufgedeckt and z.minen_in_nachbarschaft > 0:
zeichenfläche.create_text(x + zellen_breite_px / 2, y + zellen_höhe_px / 1.5, text=str(z.minen_in_nachbarschaft), font=('Helvetica', 15, 'bold'), justify=CENTER)
# zeichne minen
if verloren and z.mine:
zeichenfläche.create_text(x + zellen_breite_px / 2, y + zellen_höhe_px, text="*", font=('Helvetica', 20, 'bold'), justify=CENTER)
# zeichne markierte Felder
if z.markiert:
zeichenfläche.create_text(x + zellen_breite_px / 2, y + zellen_höhe_px / 1.5, text="~", font=('Helvetica', 15, 'bold'), justify=CENTER)
# male die Zelle, über der die Maus ist, in einer anderen Farbe
if maus_über_zelle != None:
x = maus_über_zelle.x * zellen_breite_px + abstand_x
y = maus_über_zelle.y * zellen_höhe_px + abstand_y
zeichenfläche.create_rectangle(x, y, x + zellen_breite_px, y + zellen_höhe_px, fill='snow3', stipple='gray25')
# überprüfe, ob das Spiel gewonnen ist
korrekt_markierte_minen = 0
for z in zellen:
if z.mine and z.markiert:
korrekt_markierte_minen += 1
# ist das Spiel bereits gewonnen?
if not gewonnen and korrekt_markierte_minen == anzahl_minen:
gewonnen = True
# überprüfe, ob das Spiel gewonnen ist
anzahl_aufgedeckter_felder = 0
for z in zellen:
if z.aufgedeckt:
anzahl_aufgedeckter_felder += 1
# ist das Spiel bereits gewonnen?
if not gewonnen and anzahl_aufgedeckter_felder == anzahl_zellen_x * anzahl_zellen_y - anzahl_minen:
gewonnen = True
# wenn das Spiel gewonnen ist, löse es auf
if gewonnen and not verloren:
for z in zellen:
if not z.mine:
z.aufgedeckt = True
else:
z.markiert = True
# zeige die Spielinformation an
spielzeit = 0
if not noch_nicht_geklickt:
spielzeit = int(time.time() - startzeit)
nachricht = str(anzahl_minen - anzahl_markierte) + " Minen verbleiben | <R> für Neustart | Spielzeit: " + str(spielzeit) + " s"
if verloren:
nachricht = str(anzahl_minen - anzahl_markierte) + " Minen verbleiben | <R> für Neustart | Verloren!"
if gewonnen:
if gewonnen_zeit < 0:
gewonnen_zeit = spielzeit
nachricht = str(anzahl_minen - anzahl_markierte) + " Minen verbleiben | <R> für Neustart | >>>Gewonnen<<<: " + str(gewonnen_zeit) + " s"
nachricht += "\n <A> zum Start der automatischen Lösung"
zeichenfläche.create_text(fenster_breite / 2, fenster_höhe - 21, text=nachricht, font=('Helvetica', 10, 'bold'), justify=CENTER)
# zeichne die Zeichenfläche neu
zeichenfläche.update()
# einen Timer starten, dass die Funktion "zeitschritt" nach einigen Millisekunden erneut aufgerufen wird
# das ist das Herzstück der Simulation
fenster.after(10, 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))
# werte die auf der Tastatur gedrückte Taste aus
def tastatur_event(event):
taste = event.keysym
if taste == 'r':
initialisiere()
if taste == 'a':
automatische_lösung()
# werte die auf der Maus gedrückte Taste aus
def maus_links_gedrückt(event):
pass
# werte die auf der Maus gedrückte Taste aus
def maus_links_losgelassen(event):
if maus_über_zelle != None and not verloren and not gewonnen:
maus_über_zelle.decke_auf()
# werte die auf der Maus gedrückte Taste aus
def maus_rechts_gedrückt(event):
pass
# werte die auf der Maus gedrückte Taste aus
def maus_rechts_losgelassen(event):
if maus_über_zelle != None and not verloren and not gewonnen:
maus_über_zelle.markiere()
# bestimme, auf welche der Zellen die Maus gerade zeigt
def maus_bewegung(event):
global maus_über_zelle
maus_über_zelle = None
x_pos_maus = event.x
y_pos_maus = event.y
for z in zellen:
x = z.x * zellen_breite_px + abstand_x
y = z.y * zellen_höhe_px + abstand_y
maus_darüber = True
maus_darüber = maus_darüber and x <= x_pos_maus
maus_darüber = maus_darüber and x_pos_maus <= x + zellen_breite_px
maus_darüber = maus_darüber and y <= y_pos_maus
maus_darüber = maus_darüber and y_pos_maus <= y + zellen_höhe_px
if maus_darüber:
maus_über_zelle = z
return
# öffne das Fenster, in dem das Spiel dargestellt wird
fenster = Tk(className = ' Minesweeper ')
zeichenfläche = Canvas(fenster, width=fenster_breite, height=fenster_höhe)
zeichenfläche.pack()
zentriereFenster(fenster)
# gebe die Information über die Spielgröße und Anzahl an Minen aus
print("Spielfeldgröße: " + str(anzahl_zellen_x) + "x" + str(anzahl_zellen_y))
print("Anzahl an Minen: " + str(anzahl_minen))
# binde die Eventhandler-Funktionen an das Fenster,
# so dass diese bei Tasten- und Mausevents aufgerufen werden
fenster.bind('<Key>', tastatur_event)
fenster.bind('<ButtonPress-1>', maus_links_gedrückt)
fenster.bind('<ButtonRelease-1>', maus_links_losgelassen)
fenster.bind('<ButtonPress-3>', maus_rechts_gedrückt)
fenster.bind('<ButtonRelease-3>', maus_rechts_losgelassen)
fenster.bind('<Motion>', maus_bewegung)
# initialisiere
initialisiere()
# 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):