Minecraft spricht den Lego‐Fan in uns allen an. Mit Minecraft können Sie umfangreiche 3D‐Welten mit Blöcken erstellen, die aus verschiedenen Materialien bestehen. Es hat die Fantasie derart beflügelt, dass für die verschiedenen Plattformen wie PC und Xbox über 100 Millionen Kopien verkauft wurden.
Auch für den Raspberry Pi ist eine Version von Minecraft erhältlich. Sie enthält allerdings nur den Kreativmodus, in dem Sie Elemente in aller Ruhe erstellen können, ohne von Monsterangriffen oder Hunger bedroht zu werden. Die beste Funktion besteht darin, dass sie mit mehreren Sprachen programmiert werden kann, zu denen auch Python zählt. Wie Sie in diesem Kapitel sehen werden, können Sie also große Paläste erstellen, ohne jeden einzelnen Block manuell setzen zu müssen, und Sie können Programme schreiben, die neue Originalstrukturen für Sie erstellen, in denen Sie herumwandern und die Sie erforschen können.
Das Projekt in diesem Kapitel nutzt ein Python‐Programm, um ein Labyrinth in Minecraft zu erstellen. Beim Ausführen des Programms wird jeweils ein neues Labyrinth für Sie erstellt, für das Sie die Größe und die zu verwendenden Materialien festlegen können. Im Verlauf des Projekts werden Sie erfahren, wie Sie Blöcke in Minecraft mit Python setzen und entfernen können, damit Sie Ihre eigenen Programme erstellen, die Ihre Konstruktionsarbeiten übertreffen.
Zum Zeitpunkt der Erstellung dieses Manuskripts ist Minecraft in der Pi‐Edition noch Alpha‐Software und damit eine recht frühe Testversion (weniger schlecht entwickelt als eine Beta‐Version). Wir hatten damit allerdings nur ein Problem. Der Cursor verhielt sich beim Maximieren des Fensters nicht korrekt. Ihnen werden vielleicht weitere Probleme mit dem Bildschirmaufbau auffallen, weil dieser nicht korrekt im Fenster ausgerichtet wird. Zudem gibt es keinen Sound.
In der Einführung erfahren Sie mehr darüber, wo Sie den Code für dieses Kapitel herunterladen können. Das vollständige Listing, mit dessen Hilfe Sie sich beim Erstellen dieses Projekts besser im Code orientieren können, finden Sie am Ende des Kapitels.
Minecraft wird unter Raspbian vorinstalliert. Um es zu starten, klicken Sie es im Menü oben links auf dem Bildschirm an, wo Sie Minecraft Pi in der Kategorie SPIELE finden. Wenn Sie Minecraft auf dem Raspberry Pi starten, haben Sie im Titelbildschirm zwei Optionen:
Klicken Sie auf START GAME und wählen Sie dann CREATE NEW. Anschließend erzeugt Minecraft dann eine neue Welt mit eigenem Gelände mit Bergen, Wäldern und Meeren für Sie. Wenn dieser Schritt abgeschlossen ist, sehen Sie diese aus Ihrer persönlichen Perspektive (siehe Abbildung 13.1).
Wenn Sie zu Ende gespielt haben, können Sie das Spiel durch Drücken von verlassen. Damit öffnen Sie das Spielmenü und wählen dann QUIT TO TITLE.
Minecraft lässt sich am einfachsten spielen, wenn Sie zwei Hände benutzen, wobei Sie mit einer die Maus und mit der anderen die Tastatur bedienen. Benutzen Sie die Maus, um sich umzusehen und Ihre Richtung zu ändern, schieben Sie diese nach links und rechts, um sich seitlich zu drehen, und vor‐ oder rückwärts, um nach oben oder unten zu sehen. Um sich zu bewegen, benutzen Sie die Tasten und für vor‐ und rückwärts sowie und für einen Ausfallschritt nach links oder rechts. Diese Tasten bilden auf der Tastatur eine Gruppe, was das Wechseln zwischen ihnen vereinfacht.
Wenn Sie in niedrige Blöcke hineinlaufen, springt Ihre Spielfigur automatisch darauf. Wenn Sie drücken, können Sie aber auch gezielt springen.
Die beste Ansicht Ihrer Welt bekommen Sie, wenn Sie mit ein wenig in den Himmel abheben. Wenn Sie fliegen, können Sie drücken, um aufzusteigen, und mit der linken ‐Taste wieder tiefer gehen. Wenn Sie doppelt tippen, hören Sie auf zu fliegen und sinken auf den Boden. In dieser Edition von Minecraft gibt es keine Gefahren und Gesundheitsrisiken und Sie können den freien Fall so lange genießen, wie Sie wollen.
Um Blöcke in Ihrer Welt zu zerstören, zielen Sie mit dem Fadenkreuzzeiger der Maus auf den zu zerstörenden Block. Dann klicken Sie und halten die linke Maustaste gedrückt. Einige Blöcke lassen sich leichter als andere zerstören. Sie dürfen nicht allzu weit entfernt sein. Wenn Sie beim Versuch, sie zu zerschlagen, keine Späne von den Blöcken fliegen sehen, müssen Sie daher näher herangehen.
In dem Feld unten im Fenster werden die Blöcke angezeigt, die Sie in der Welt platzieren können (siehe Abbildung 13.1). Sie können über das Mausrad oder Drücken einer der Tasten von bis (von links nach rechts) zwischen ihnen auswählen. Wenn Sie drücken, wird Ihr vollständiges Inventar geöffnet, in dem Sie mit den Bewegungstasten (, , und ) navigieren und mit einen Block auswählen. Alternativ können Sie den auszuwählenden Block auch einfach mit der Maus anklicken.
Um einen Block zu positionieren, klicken Sie die entsprechende Stelle mit der rechten Maustaste an. Sie können nur dann einen Block auf einen anderen setzen, wenn Sie dessen Oberseite sehen, müssen also möglicherweise fliegen, um hohe Bauten zu erstellen.
Auch wenn sich Dinge mit Python sehr viel leichter erbauen lassen, müssen wir Ihnen doch empfehlen, dass Sie sich ein wenig länger damit vertraut machen, wie Spieler die Welt erfahren. Insbesondere lohnt es sich, ein wenig damit zu experimentieren, wie Blöcke miteinander interagieren. Steinblöcke können ungesichert in die Luft entschweben, Sandblöcke fallen zu Boden. Kakteen können nicht in Gras gepflanzt werden, lassen sich aber auf Sand platzieren. Wenn Sie Blöcke am Ufer von Seen zerstören, fließt das Wasser in die entstandenen Lücken. Sie können im Spiel keine Wasser‐ oder Lavablöcke platzieren, können sie allerdings mit Python programmieren und mit ihnen stufenförmig große Bereiche bedecken. Wenn Wasser und Lava miteinander in Kontakt kommen, kühlt Wasser zuweilen Lava zu Stein ab.
Eine der Besonderheiten von Minecraft besteht darin, dass es die Kontrolle der Maus übernimmt, weshalb Sie drücken müssen, um diese wieder zu übernehmen, wenn Sie andere Programme auf Ihrem Desktop benutzen wollen. Um die Maus wieder in Minecraft benutzen zu können, klicken Sie das Minecraft‐Fenster an. Sie sollten sich schnell daran gewöhnt haben, zu drücken, bevor Sie programmieren. Betätigen Sie nun , damit Minecraft zwar weiter läuft, der Mauscursor aber zurück zum Desktop wechselt. Um Ihre Minecraft‐Programme zu erstellen, verwenden Sie Thonny. Öffnen Sie also das Menü oben links und wählen Sie in der Kategorie ENTWICKLUNG die Option THONNY PYTHON IDE . (Wenn Ihnen das lieber ist, können Sie Python 3 (IDLE) verwenden, das sich ebenfalls in der Kategorie ENTWICKLUNG des Menüs PROGRAMME befindet.) Möglicherweise müssen Sie dazu erst einmal auf die Titelleiste des Minecraft‐Fensters klicken und es aus dem Weg ziehen oder oben rechts im Fenster die Minimieren‐Schaltfläche anklicken.
Wenn Ihr Bildschirm nicht groß genug ist, müssen Sie bei Bedarf zwischen den Fenstern umschalten. Um Minecraft zu verbergen, klicken Sie es oben auf dem Desktop in der Taskleiste an und minimieren es dann über die Minimieren‐Schaltfläche in der Minecraft‐Titelleiste (oben rechts im Fenster). Um Minecraft wieder anzuzeigen oder zu aktivieren, klicken Sie es wieder in der Taskleiste an. Wenn Sie die Titelleiste von Minecraft nicht sehen können, reagiert es nicht auf die Maus‐ und Tastatur‐Steuerung, weshalb Sie es in der Taskleiste anklicken müssen. (In Kapitel 4 finden Sie eine Anleitung zur Benutzung des Desktops.)
Für Ihr erstes Python‐Programm für Minecraft werden wir Ihnen zeigen, wie Sie eine Nachricht an die Chat‐Funktion des Spiels senden können.
Wir benutzen den Editor in Thonny (den oberen Teil des Fensters). Geben Sie das Folgende
ein und benutzen Sie das Menü FILE, um es in Ihrem Ordner pi
zu speichern. Drücken Sie dann oder klicken Sie auf die Schaltfläche RUN, um das Programm zu starten (beachten Sie, dass eine Minecraft‐Spielesitzung laufen
muss, damit das funktioniert):
import sys, random
from mcpi import minecraft
mc = minecraft.Minecraft.create()
mc.postToChat("Welcome to Minecraft Labyrinth!")
In der ersten Zeile werden in Ihrem Quelltext die Module sys
und random
importiert. Das Modul random
werden Sie später bei der Entwicklung dieses Programms benötigen, um ein zufälliges
Labyrinth zu erstellen.
Um Python‐Kommandos an Minecraft zu senden, verwenden Sie die Funktion minecraft.Minecraft.create()
und fügen das Kommando am Ende hinzu. Um zum Beispiel einen Gruß in das Chat‐Fenster
zu setzen, könnten Sie diese Zeilen benutzen:
minecraft.Minecraft.create().postToChat("Willkommen im Minecraft‐ Labyrinth!")
Das wird schnell mühsam zu lesen, weshalb Sie in dem Programm, mit dem Sie arbeiten,
möglichst schnell mc
einrichten sollten, um es als Abkürzung für minecraft.Minecraft.create()
zu benutzen. Dadurch können Sie die im Programm benutzte kürzere Zeile verwenden,
um eine Nachricht zu senden.
Wenn Sie das Programm ausführen, wechseln Sie zurück zu Minecraft, um Ihre Nachricht auf dem Bildschirm sehen zu können. Sie verschwindet nach etwa zehn Sekunden wieder.
Wahrscheinlich können Sie sich vorstellen, dass alles in der Minecraft‐Welt eine Koordinate auf der Landkarte besitzt. Um eine Position im Spiel zu beschreiben, werden drei Koordinatenachsen benötigt:
Wir haben die Achsen bewusst in diese Reihenfolge gebracht, weil diese auch von Minecraft verwendet wird. Wenn man häufig x und y (wie in Scratch) für Positionen im 2D‐Raum benutzt, dauert es ein wenig, bis man begreift, dass es sich bei y um die Höhe handelt. Die meiste Zeit über werden Sie in diesem Kapitel die Position einer Wand über deren x‐ und z‐Koordinaten beschreiben. Diese sind je nach Wand verschieden, während sich die Höhe und damit die y‐Koordinate nicht ändert.
Während Sie sich im Spiel bewegen, werden die jeweils aktuellen Koordinaten für die Spielfigur jeweils oben links im Minecraft‐Fenster angezeigt. Wenn Sie die Spielwelt zu verlassen versuchen, treffen Sie auf eine als Himmel dargestellte Wand, die Sie nicht durchdringen können. Das erinnert ein wenig an Die Truman Show, nur dass die Wand da eine Tür hatte.
Mit dem folgenden Befehl können Sie Ihre Spielfigur an beliebige Positionen in der Minecraft‐Welt setzen:
mc.player.setTilePos(x, y, z)
Um beispielsweise mit dem Fallschirm in die Welt zu schweben, verwenden Sie
mc.player.setTilePos(0, 100, 0)
Sofern sich das Spiel nicht im Flugmodus befindet, fallen Sie vom Himmel in die Welt. Wenn es sich im Flugmodus befindet, klicken Sie Minecraft in der Taskleiste an und drücken doppelt, um ihn abzuschalten und Ihren Sinkflug zu beginnen.
Auch wenn Welten dieselbe Größe haben, müssen Sie beachten, dass diese Koordinate nicht immer in der Mitte der Welt liegt. In einer unserer Welten laufen die Koordinaten in der x‐Ebene von –85.7 bis 169.7 und von –98.7 bis 156.7 in der z‐Ebene.
Sie können die Spielfigur an eine beliebige Stelle in der Spielwelt setzen. Zuweilen heißt das, dass sie mitten in einem Berg oder einem anderen Bau auftaucht, wo sie sich nicht bewegen kann. Wenn das passiert, positionieren Sie die Spielfigur mit ein wenig Code neu. Sie irgendwo hoch über dem Boden auftauchen zu lassen, ist üblicherweise recht sicher, weil sie von da aus auf die höchste Stelle hinunterfallen kann.
Um einen Block zu der Welt hinzuzufügen, verwenden Sie diesen Befehl:
mc.setBlock(x, y, z, blockTypeId)
blockTypeId
ist eine Zahl, die das Material des gerade hinzugefügten Blocks angibt. Eine vollständige
Liste der Materialien finden Sie unter www.minecraftwiki.net/wiki/Data_values_(Pocket_Edition)
. (Übernehmen Sie die Nummer aus der Spalte Dec in der Tabelle auf der Seite. Sie
werden die Dezimalzahl der Hexadezimalzahl vorziehen.) Zulässig sind dabei beliebige
Zahlen zwischen 0 und 108. Tabelle 13.1 zeigt einige der Materialien, die für dieses Projekt und eigene Experimente möglicherweise
besonders interessant sein könnten.
blockTypeId |
Blocktyp |
0 |
Luft |
1 |
Stein |
2 |
Gras |
3 |
Dreck |
5 |
Holzplanke |
8 |
Wasser |
10 |
Lava |
12 |
Sand |
20 |
Glasziegel |
24 |
Sandstein |
41 |
Goldziegel |
45 |
Ziegel |
47 |
Buchregal |
53 |
Holztreppen |
57 |
Diamantblock |
64 |
Holztür |
81 |
Kaktus |
Es gibt einen anderen Befehl, mit dem Sie große würfelförmige Blöcke aus einheitlichem Material erstellen können. Wenn Sie die Funktion verwenden, geben Sie wie folgt die Koordinaten zweier gegenüberliegender Ecken und das zu verwendende Füllmaterial an:
mc.setBlocks(x1, y1, z1, x2, y2, z2, blockTypeId)
Sie können schnell einen Raum aus Ziegeln erstellen und dann einen Würfel Luft hineinsetzen. Luft ersetzt beliebige andere Blöcke und löscht sie damit letztlich aus der Welt. Dazu ein Beispiel:
mc.setBlocks(0, 0, 0, 10, 5, 7, 45) #Ziegel
mc.setBlocks(1, 0, 1, 9, 5, 6, 0) #Luft
Mit diesen Zeilen wird ein Unterschlupf mit 10 x 7 Blöcken Grundfläche erstellt, der 5 Blöcke hoch ist und bei den Koordinaten 0, 0, 0 beginnt. Die Wände sind einen Block dick, weil der Raum von 1 bis 9 auf der x‐Achse, von 1 bis 6 auf der z‐Achse und von 0 bis 5 auf der vertikalen Achse mit Luft gefüllt wird. Dadurch bleibt ein Block mit Ziegeln des ursprünglichen Würfels auf vier Seiten erhalten und das Dach bleibt offen.
Wenn das Minecraft‐Fenster schwarz wird, während Sie das versuchen, haben Sie die Wände wahrscheinlich über die Spielfigur gesetzt. Um wieder freie Sicht zu erhalten, positionieren Sie den Spieler mit Code um.
Spielfiguren können zwar Koordinaten mit Dezimalteilen verwenden (wie etwa 1.7), diese werden aber beim Platzieren von Blöcken auf die nächste ganze Zahl abgerundet.
Wir wissen, dass Sie nie schummeln würden, aber ein Labyrinth, durch das man sich versehentlich seine Bahn freihauen kann, würde doch keinen Spaß machen, oder? Um zu verhindern, dass Spieler Objekte in der Welt zerstören oder neue Blöcke hineinsetzen, verwenden Sie den folgenden Befehl:
mc.setting("world.immutable", True)
Das Wort immutable wird in der Programmierung häufig benutzt und bedeutet »unveränderlich«.
Da Sie nun wissen, wie Sie Blöcke in die Welt setzen und sie mit Luftblöcken wieder entfernen können, sind Sie bereit, das Labyrinth‐Programm zu erstellen. In diesem Programm verwenden Sie eine Reihe Konstanten, um wichtige Informationen über das Labyrinth zu speichern. Konstanten sind irgendwie auch nur Variablen, für die Sie festgelegt haben, dass sich deren Werte während der Ausführung des Programms nicht ändern und immer gleich bleiben. Üblicherweise werden die Namen von Konstanten in Großbuchstaben geschrieben, um beim späteren Lesen des Programms gleich als solche erkannt zu werden und sich auch selbst an die konstanten Werte zu erinnern. Wenn Sie Ihre Programme durch Konstanten ändern, lässt sich Ihr Programm später besser anpassen. Zudem lässt sich Ihr Programm deutlich leichter lesen und verstehen, wenn klar ist, was verschiedene Zahlen repräsentieren.
Das Programm fängt mit der Einrichtung der folgenden Konstanten an:
SIZE = 10
HEIGHT = 2
MAZE_X = 0
GROUND = 0
MAZE_Z = 0
MAZE_MATERIAL = 1 #Stein
GROUND_MATERIAL = 2 #Gras
CEILING = False
Wenn Sie das Labyrinth bauen, erstellen Sie erst ein Raster, das aus Wänden und einen
Block großen Zwischenräumen (Zellen – cells) besteht und ein wenig wie eine Waffel
aussieht (siehe Abbildung 13.2). Die einzelnen Zellen beginnen mit vier Wänden. Das Programm zerstört dann die Wände,
um Pfade zwischen den Wänden herzustellen und so das Labyrinth zu erzeugen. Das Labyrinth
ist quadratisch. Seine Größe (SIZE
) wird in Zellen angegeben. Ein Labyrinth mit dem Wert 10
für SIZE
besteht aus zehn Zellen in den x‐ und z‐Dimensionen, sie belegen aber in der Minecraft‐Welt in beiden Dimensionen den doppelten
Platz (also 20 x 20 Blöcke), weil sich zwischen den Zellen eine jeweils einen Block
dicke Wand befindet. Das wird klarer, wenn Sie mit dem Bau des Labyrinths beginnen.
Wir haben Labyrinthe bis zur Größe 40 erstellt. Deren Aufbau dauert aber einige Zeit,
und sie zu erforschen, dauert Ewigkeiten. Zehn dürfte erst einmal groß genug sein.
Die Höhe (HEIGHT
) gibt an, wie hoch die Mauern des Labyrinths sind. Wir haben den Wert 2
gewählt, weil die Spielfigur beim Wert 1
einfach über das Labyrinth hinweggehen würde. (Die Spielfigur steigt automatisch
über Blöcke hinweg, die nur eine Einheit hoch sind.) Höhere Werte verdecken bereits
weiter entfernt liegende Berge, die dem Spieler ansonsten nette optische Hinweise
geben können.
Die Konstanten MAZE_X
, GROUND
und MAZE_Z
werden als Startkoordinaten für das Labyrinth verwendet. Als Material für das Labyrinth
(MAZE_MATERIAL
) wird Stein verwendet (1
). Für den Boden haben wir als Material (GROUND_MATERIAL
) Gras (2
) verwendet. Wir haben eine Option für eine Decke hinzugefügt, um zu verhindern, dass
Spieler abheben und das Labyrinth einfach nach oben hin verlassen, diese aber vorerst
abgeschaltet, damit Sie das Labyrinth während seines Aufbaus erst einmal ungehindert
erforschen können.
Das Programm wird mit einem Fehler beendet, wenn es in Ihrer Welt nicht genug Platz
für das gesamte Labyrinth gibt. Dann können Sie versuchen, ein kleineres Labyrinth
zu verwenden, und versuchsweise die Koordinaten MAZE_X
und MAZE_Z
verschieben oder eine andere Welt ausprobieren.
Bevor Sie das Labyrinth bauen können, müssen Sie erst einmal dafür sorgen, dass es auf festem Grund steht. Da Minecraft‐Welten dynamisch erzeugt werden, werden Sie ansonsten möglicherweise feststellen müssen, dass Sie ein Labyrinth in einem Berg oder in die See bauen.
Neben dem vom Labyrinth beanspruchten Bereich müssen Sie darum herum einen Bereich von zehn Blöcken freiräumen, damit die Spieler sich leicht nähern und darum herumlaufen können. Um den Bereich zu räumen, füllen Sie ihn erst mit Luftblöcken. Dadurch wird in diesem Bereich alles andere gelöscht. Dann fügen Sie den Boden hinzu, für den Sie eine Schicht Blöcke aus dem Bodenmaterial verwenden.
Das Labyrinth belegt einen in Blöcken angegebenen Bodenbereich von MAZE_X
bis MAZE_X+(SIZE*2)
und von MAZE_Z
bis MAZE_Z+(SIZE*2)
. (Denken Sie daran, dass das Symbol *
als Operator für die Multiplikation verwendet wird.) Die Anzahl der Blöcke entspricht
der doppelten Anzahl der Zellen (SIZE
), da es bei allen Zellen rechts und darunter Mauern gibt. Die Mitte des Labyrinths
in der Minecraft‐Welt ist MAZE_X+SIZE,
.
MAZE_Z+SIZE
Sie müssen in den jeweiligen Richtungen zehn Blöcke mehr räumen. Der folgende Quelltext löscht alles, was sich bis zu 150 über der Bodenebene des Labyrinths befindet. Damit soll dafür gesorgt werden, dass keine Blöcke übrig bleiben, die vom Himmel in das Labyrinth fallen, und dort dann auf dem Boden herumliegen:
mc.setBlocks(MAZE_X‐10, GROUND, MAZE_Z‐10,
MAZE_X+(SIZE*2)+10, GROUND+150, MAZE_Z+(SIZE*2)+10, 0)
mc.setBlocks(MAZE_X‐10, GROUND, MAZE_Z‐10,
MAZE_X+(SIZE*2)+10, GROUND, MAZE_Z+(SIZE*2)+10,
GROUND_MATERIAL)
Wir empfehlen, einen Block zum Markieren der Startecke des Labyrinths hinzuzufügen
(wo MAZE_X
und MAZE_Z
liegen). Das wird nützlich, wenn Sie beim Schreiben des Programms nach Fehlern suchen
müssen. Auf diese Weise können Sie die Orientierung des Labyrinths beim Umfliegen
erkennen. Verwenden Sie dazu den folgenden Befehl:
mc.setBlock(MAZE_X, GROUND+HEIGHT+1, MAZE_Z, MAZE_MATERIAL)
Setzen Sie auch Ihre Spielfigur über die Mitte des Labyrinths, damit Sie hinunterblicken und beobachten können, wie die Welt aufgebaut wird. Wenn Sie nicht fliegen, werden Sie zwar auf die Labyrinth‐Wände fallen, können aber einfach wieder hochfliegen:
mc.player.setTilePos(MAZE_X+SIZE, GROUND+25, MAZE_Z+SIZE)
Um das waffelartige Gitter zu erstellen, verwenden Sie den folgenden Quelltext:
for line in range(0, (SIZE+1)*2, 2):
mc.setBlocks(MAZE_X+line, GROUND+1, MAZE_Z,
MAZE_X+line, GROUND+HEIGHT, MAZE_Z+(SIZE*2), MAZE_MATERIAL)
mc.setBlocks(MAZE_X, GROUND+1, MAZE_Z+line,
MAZE_X+(SIZE*2), GROUND+HEIGHT, MAZE_Z+line, MAZE_MATERIAL)
In der for
‐Schleife erhält die Variable line
von 0
bis SIZE*2
die Werte gerader Zahlen zugewiesen. Beachten Sie, dass Sie SIZE
vor dem Verdoppeln um 1
erhöhen müssen, weil die Funktion range
nicht die letzte Zahl in der Abfolge umfasst. Wenn Sie beispielsweise range(1, 10)
verwenden, erhalten Sie die Zahlen von 1 bis 9. Die 2
am Ende der range
‐Funktion gibt die Schrittweite an. Somit wird der Zähler bei jedem Schleifendurchlauf
um 2 erhöht, und es werden nur gerade Zahlen verwendet. Damit lassen Sie also zwischen
den Mauern Platz für die Zelle. Bei jedem Durchlauf der Schleife werden Quader gezeichnet,
um zwei Wände zu erstellen, die sich in x‐ und y‐Richtung von Seite zu Seite durch das Labyrinth ziehen. Dabei spielt es keine Rolle,
ob derselbe Block an Schnittpunkten zweimal gesetzt wird. Sie beginnen mit dem Bau
der Mauer bei GROUND+1
. Wenn Sie die Wände einreißen, befindet sich das Gras noch darunter.
Nun sollten Sie ein Gitter erzeugt haben, das wie in Abbildung 13.3 aussieht.
Bevor Sie in den Code eintauchen, der Ihre Waffel in ein Labyrinth verwandelt, sollen Sie erfahren, wie das funktioniert. Sie werden ein sogenanntes perfektes Labyrinth erstellen, wobei es sich um einen technischen Begriff und keine Prahlerei handelt: Er bedeutet, dass es darin keine Schleifen und auch keine Teile gibt, die Sie nicht betreten können. Es gibt nur einen Weg zwischen zwei Punkten im Labyrinth.
Ein Algorithmus beschreibt einen Regelsatz oder ein Verfahren zum Lösen eines bestimmten Problems und wird typischerweise von einem Computer ausgeführt. Sie könnten den Algorithmus für das Erstellen eines Labyrinths aber auch mit Papier und Bleistift und einem Radiergummi ausführen, um unterwegs Mauern auszuradieren. Das geht so:
Um diesen Algorithmus zu implementieren, verwenden Sie die folgenden Variablen:
numberOfCells
: Dies ist die Gesamtanzahl der Zellen im Labyrinth, die SIZE*SIZE
beträgt.
numberOfVisitedCells
: Diese Variable protokolliert, wie viele Zellen Sie aufgesucht haben. Wenn ihr Wert
mit numberOfCells
übereinstimmt, wurden alle Zellen besucht, besitzen eine zerstörte Wand und sind
daher erreichbar. Das Labyrinth ist fertiggestellt.
xposition
: Diese Variable speichert Ihre x‐Position, während Sie sich durch das Labyrinth bewegen, um es zu erzeugen. Sie wird
in Zellen angegeben und erhält anfangs den Wert einer Zufallszahl zwischen 1
und der Größe des Labyrinths (SIZE
) zugewiesen.
zposition
: Diese Variable speichert Ihre z‐Position, während Sie sich durch das Labyrinth bewegen, um es zu erzeugen. Sie wird
ebenfalls in Zellen angegeben und erhält anfangs auch den Wert einer Zufallszahl.
cellsVisitedList[]
: Dies ist eine Liste, in der der von Ihnen genommene Weg abgelegt wird, damit das
Programm die Schritte zurückverfolgen kann. Bei ihrer Einrichtung legen Sie Ihre Startposition
darin mit der Methode append()
ab.
playerx
und playerz
: Diese Variablen werden dazu verwendet, die Startposition zu speichern, damit Sie
die Spielfigur wieder dorthin setzen können, wenn das Labyrinth fertig ist.
Wenn ein Algorithmus für einen Labyrinth‐Generator (Tiefensuche) wie dieser implementiert wird (Depth‐First Labyrinth Generation Algorithm), erfordert er häufig eine Liste oder eine ähnliche Datenstruktur für die Speicherung der Position der Wände. Sie benötigen diese nicht, weil Wände in Minecraft vorhanden sind, die Sie betrachten können. Wenn Sie wollen, speichert die Spielwelt Ihr Labyrinth.
Mit den folgenden Quelltextzeilen richten Sie Ihre Startvariablen ein:
numberOfCells = SIZE * SIZE
numberOfVisitedCells = 1 # 1 die Zelle mit der Sie starten
cellsVisitedList = []
xposition = random.randint(1, SIZE)
zposition = random.randint(1, SIZE)
playerx = xposition
playerz = zposition
showMaker(xposition, zposition)
# siehe Abschnitt "Die Funktionen erstellen "
cellsVisitedList.append((xposition, zposition))
Es gibt eine Reihe von grundlegenden Funktionen, die Sie für Ihr Programm benötigen:
realx(x)
und realz(z)
: Diese Funktionen wandeln Koordinaten im Labyrinth (Maßstab: Zellen) in Koordinaten
in der Minecraft‐Welt um (Maßstab: Blöcke und Abstand von der Startposition des Labyrinths).
showMaker(x,z)
und hideMaker(x, z)
: Diese Funktionen benutzen einen goldenen Stein, um anzuzeigen, welche Zelle das
Programm beim Erstellen des Labyrinths erreicht hat. Es macht Spaß, das Ergebnis von
oben herab zu betrachten, und ist nützlich beim Erstellen und der Fehlersuche im Programm.
demolish(realx, realz)
: Diese Funktion reißt eine Wand im Labyrinth ein und übernimmt die reellen x‐ und
z‐Koordinaten in der Minecraft‐Welt als Parameter.
testAllWalls(cellx, cellz)
: Diese Funktion prüft, ob die vier Wände der Zelle intakt sind. Wenn das für alle
vier Wände gilt, gibt sie True
zurück. Ansonsten gibt sie False
zurück. Sie benutzt den Befehl mc.getBlock(x, y, z)
, die Ihnen blockTypeId
für eine bestimmte Position zurückgibt. Sie verwenden wie üblich zwei Gleichheitszeichen,
um zu prüfen, ob ein Block in einer Wandposition aus demselben Material (MAZE_MATERIAL
) besteht, was dann bedeutet, dass eine Wand vorhanden ist.
Fügen Sie diese Funktionsdefinitionen am Anfang Ihres Programms direkt hinter der Einrichtung des Minecraft‐Moduls ein:
def realx(x):
return MAZE_X + (x*2) ‐ 1
def realz(z):
return MAZE_Z + (z*2) ‐ 1
def showMaker(x, z):
mc.setBlock(realx(x), GROUND+1, realz(z), 41) # 41=gold
def hideMaker(x, z):
mc.setBlock(realx(x), GROUND+1, realz(z), 0)
def demolish(realx, realz):
mc.setBlocks(realx, GROUND+1, realz, realx,
HEIGHT+GROUND, realz, 0)
def testAllWalls(cellx, cellz):
if mc.getBlock(realx(cellx)+1, GROUND+1,
realz(cellz))==MAZE_MATERIAL and mc.getBlock
(realx(cellx)‐1, GROUND+1, realz(cellz))==MAZE_MATERIAL
and mc.getBlock(realx(cellx), GROUND+1, realz(cellz)+1)==
MAZE_MATERIAL and mc.getBlock(realx(cellx), GROUND+1,
realz(cellz)‐1)==MAZE_MATERIAL:
return True
else:
return False
Ihr Labyrinth‐Algorithmus läuft, bis Sie alle Zellen besucht haben, beginnt also mit dem folgenden Befehl:
while numberOfVisitedCells < numberOfCells:
Sie müssen prüfen, ob bei den Nachbarzellen Ihrer aktuellen Zelle alle Wände intakt
sind. Dazu prüfen Sie reihum die einzelnen Richtungen mit der Funktion testAllWalls(x, z)
. Wenn Sie eine Zelle finden, bei der noch alle Wände intakt sind, fügen Sie ihre
Richtung mit der Methode append()
zur Liste possibleDirections[]
hinzu. Dies wird durch Schritt 3 im Algorithmus implementiert, bei dem Sie daran
denken müssen, dass diese Anweisungen alle in einer while
‐Schleife laufen:
possibleDirections = []
if testAllWalls(xposition ‐ 1, zposition):
possibleDirections.append("left")
if testAllWalls(xposition + 1, zposition):
possibleDirections.append("right")
if testAllWalls(xposition, zposition ‐ 1):
possibleDirections.append("up")
if testAllWalls(xposition, zposition + 1):
possibleDirections.append("down")
Die Werte von up
, down
, left
und right
sind im 3D‐Raum zwar irgendwie willkürlich, wir haben sie aber dennoch verwendet,
weil sie leicht verstanden werden. Wenn Sie in die Luft gehen und auf das Labyrinth
während seiner Erzeugung hinabblicken und es einen Block gibt, der die Startecke des
Labyrinths (MAZE_X, MAZE_Z
) oben links identifiziert, werden Sie diese Richtungen als richtig empfinden.
Vielleicht ist Ihnen aufgefallen, dass nicht geprüft wird, ob sich all diese Zellpositionen innerhalb der Labyrinth‐Grenzen befinden. Was geschieht, wenn Sie eine Zelle betrachten, die jenseits des linken oder unteren Randes des Labyrinths liegt? Kein Problem. Die Implementierung des Programms beachtet die Grenzen des Labyrinths automatisch, weil die betrachteten »Zellen« außerhalb der Grenzen nicht alle vier Wände besitzen (deren einzige Wand ist die Grenze des Labyrinths) und deshalb nie besucht werden.
Wenn Sie einen noch nicht besuchten Nachbarn finden, besteht Schritt 4 im Algorithmus
in der Auswahl einer zufälligen Richtung, der Zerstörung der Wand in dieser Richtung
und dem Wechsel in diese Zelle. Um zu entscheiden, ob Sie mögliche Richtungen gefunden
haben, prüfen Sie die Länge der Liste possibleDirections
und handeln, wenn sie nicht gleich 0
ist (!=0
). All das sollte sich in der while
‐Schleife befinden. (Wenn Sie diesbezüglich die Orientierung verloren haben, kann
Ihnen der vollständige Quellcode in Listing 13.1 am Ende dieses Kapitels weiterhelfen.)
Bevor Sie damit beginnen, Ihre Position zu verschieben, verstecken Sie den Goldziegel, der anzeigt, wo Sie sich im Labyrinth befinden:
hideMaker(xposition, zposition)
if len(possibleDirections) != 0:
directionChosen=random.choice(possibleDirections)
if directionChosen == "left":
demolish(realx(xposition) ‐ 1, realz(zposition))
xposition ‐= 1
if directionChosen == "right":
demolish(realx(xposition) + 1, realz(zposition))
xposition += 1
if directionChosen == "up":
demolish(realx(xposition), realz(zposition) ‐ 1)
zposition ‐= 1
if directionChosen == "down":
demolish(realx(xposition), realz(zposition) + 1)
zposition += 1
Nachdem Sie in eine neue Zelle gewechselt sind, müssen Sie Ihren Zähler für die besuchten Zellen erhöhen und die neue Zelle zu der Liste hinzufügen, die den genommenen Weg speichert. Jetzt ist auch ein guter Zeitpunkt, um über den Goldblock in der Zelle zu zeigen, wie das Labyrinth gebaut wird:
numberOfVisitedCells += 1
cellsVisitedList.append((xposition, zposition))
showMaker(xposition, zposition)
Wie die Liste der besuchten Zellen gespeichert wird, bedarf einer gewissen Erklärung.
Sie haben xposition
und zposition
in Klammern gesetzt, die darauf hinweisen, dass es sich um ein Tupel handelt. Ein
Tupel
ist eine Datensequenz, die zwar einer Liste ähnelt, aber mit dem Hauptunterschied,
dass Sie deren Werte nicht ändern können. (Sie ist unveränderlich.) cellsVisitedList
ist also eine Liste mit Tupeln, die wiederum Paare von x‐ und z‐Koordinaten enthält. Sie können die Python‐Shell dazu benutzen, um einen Blick in
diese Liste zu werfen. Es folgt ein Beispiel von einem Programmlauf, das einen durch das Labyrinth
genommenen Weg zeigt:
>>> print(cellsVisitedList)
[(6, 6), (6, 7), (6, 8), (5, 8), (4, 8), (3, 8), (3, 7)]
Für Schritt 5 im Algorithmus kehren Sie zur vorherigen Position auf dem Weg zurück,
wenn Ihre Zelle keine unbesuchten Nachbarn hat. Dazu gehört es, die letzte Position
wieder aus der Liste zu entfernen. Zu diesem Zweck können Sie eine Methode namens
pop()
verwenden. Sie entfernt das letzte Element aus der Liste. In Ihrem Programm übertragen
Sie es in eine Variable namens retrace
, die dann ein Tupel für die x‐ und z‐Positionen im Labyrinth speichert. Wie bei einer Liste können Sie Indexnummern dazu
benutzen, um auf die einzelnen Elemente in einem Tupel zuzugreifen. Da die Indexnummern
mit 0
beginnen, enthält retrace[0]
Ihre vorherige x‐Position, während retrace[1]
Ihre vorherige z‐Position speichert. Es folgt der Code, der auch eine Zeile umfasst, die den goldenen
Block an seiner neuen Position zeigt:
else: # do this when there are no unvisited neighbors
retrace = cellsVisitedList.pop()
xposition = retrace[0]
zposition = retrace[1]
showMaker(xposition, zposition)
Achten Sie darauf, dass sich die else
‐Anweisung in einer Linie mit der zugehörigen if
‐Anweisung befindet, bei der es sich hier um die handelt, die prüft, ob Sie mögliche
Richtungen gefunden haben, in die Sie sich bewegen können.
Schritt 6 im Algorithmus wurde bereits implementiert, da die while
‐Schleife den eingerückten Code so lange wiederholt, bis alle Zellen besucht wurden.
Wir persönlich finden, dass es mehr Spaß macht, wenn die Decke geöffnet bleibt und
man in die Höhe schweben und das Labyrinth aus luftigen Höhen bewundern kann, um sich
dann an beliebiger Stelle hinabsinken zu lassen. Wenn Sie um Ihr Labyrinth herum ein
Spiel erstellen wollen und die Spieler vom Schummeln abgehalten werden sollen, können
Sie mit dem folgenden Code eine Decke einziehen. Dazu müssen Sie nur den Wert der
Variablen CEILING
am Anfang des Programms in True
abändern. Wir haben die Decke aus Glasziegeln erstellt, damit es innen nicht zu dunkel
wird:
if CEILING == True:
mc.setBlocks(MAZE_X, GROUND+HEIGHT+1, MAZE_Z,
MAZE_X+(SIZE*2), GROUND+HEIGHT+1, MAZE_Z+(SIZE*2), 20)
Schließlich setzen wir die Spielfigur an die zufällige Position, an der Sie mit der Erzeugung des Labyrinths angefangen haben. Sie könnten die Spielfigur irgendwo hinsetzen, dieser Platz scheint aber so gut wie jeder andere zu sein, und er verwendet Zufallszahlen, die Sie bereits erzeugt haben:
mc.player.setTilePos(realx(playerx), GROUND+1, realz(playerz))
Nun können Sie loslegen und spielen! Abbildung 13.4 zeigt das Innere des Labyrinths.
Listing 13.1 zeigt den abschließenden und vollständigen Quelltext:
import sys, random
from mcpi import minecraft
mc = minecraft.Minecraft.create()
mc.postToChat("Willkommen im Minecraft‐Labyrinth!")
def realx(x):
return MAZE_X + (x*2) ‐ 1
def realz(z):
return MAZE_Z + (z*2) ‐ 1
def showMaker(x, z):
mc.setBlock(realx(x), GROUND+1, realz(z), 41) # 41=gold
def hideMaker(x, z):
mc.setBlock(realx(x), GROUND+1, realz(z), 0)
def demolish(realx, realz):
mc.setBlocks(realx, GROUND+1, realz, realx,
HEIGHT+GROUND, realz, 0)
def testAllWalls(cellx, cellz):
if mc.getBlock(realx(cellx)+1, GROUND+1,
realz(cellz))==MAZE_MATERIAL and mc.getBlock
(realx(cellx)‐1, GROUND+1, realz(cellz))==MAZE_MATERIAL
and mc.getBlock(realx(cellx), GROUND+1, realz(cellz)+1)==
MAZE_MATERIAL and mc.getBlock(realx(cellx), GROUND+1,
realz(cellz)‐1)==MAZE_MATERIAL:
return True
else:
return False
mc.setting("world_immutable", True)
# Hier konfigurieren Sie Ihr Labyrinth
SIZE = 10
HEIGHT = 2
MAZE_X = 0
GROUND = 0
MAZE_Z = 0
MAZE_MATERIAL = 1 # 1=Stein
GROUND_MATERIAL = 2 # 2=Gras
CEILING = False
# Bereich löschen
mc.setBlocks(MAZE_X‐10, GROUND, MAZE_Z‐10, MAZE_X+
(SIZE*2)+10, GROUND+150, MAZE_Z+(SIZE*2)+10, 0) # air
# Den Boden legen
mc.setBlocks(MAZE_X‐10, GROUND, MAZE_Z‐10, MAZE_X+
(SIZE*2)+10, GROUND, MAZE_Z+(SIZE*2)+10, GROUND_MATERIAL)
# origin marker
mc.setBlock(MAZE_X, GROUND+HEIGHT+1, MAZE_Z, MAZE_MATERIAL)
# move player above middle of maze
mc.player.setTilePos(MAZE_X+SIZE, GROUND+25, MAZE_Z+SIZE)
mc.postToChat("Ihr Labyrinth wird nun erstellt …")
# Raster aus Wänden bauen
for line in range(0, (SIZE+1)*2, 2):
mc.setBlocks(MAZE_X+line, GROUND+1, MAZE_Z,
MAZE_X+line, GROUND+HEIGHT, MAZE_Z+(SIZE*2), MAZE_MATERIAL)
mc.setBlocks(MAZE_X, GROUND+1, MAZE_Z+line, MAZE_X+
(SIZE*2), GROUND+HEIGHT, MAZE_Z+line, MAZE_MATERIAL)
# Variablen für das zu erstellende Labyrinth einrichten
numberOfCells = SIZE * SIZE
numberOfVisitedCells = 1 # 1 für die, in der Sie starten
cellsVisitedList = []
xposition = random.randint(1, SIZE)
zposition = random.randint(1, SIZE)
playerx = xposition
playerz = zposition
showMaker(xposition, zposition)
cellsVisitedList.append((xposition, zposition))
while numberOfVisitedCells < numberOfCells:
possibleDirections = []
if testAllWalls(xposition ‐ 1, zposition):
possibleDirections.append("left")
if testAllWalls(xposition + 1, zposition):
possibleDirections.append("right")
if testAllWalls(xposition, zposition ‐ 1):
possibleDirections.append("up")
if testAllWalls(xposition, zposition + 1):
possibleDirections.append("down")
hideMaker(xposition, zposition)
if len(possibleDirections) != 0:
directionChosen=random.choice(possibleDirections)
#knock down wall between cell in direction chosen
if directionChosen == "left":
demolish(realx(xposition) ‐ 1, realz(zposition))
xposition ‐= 1
if directionChosen == "right":
demolish(realx(xposition) + 1, realz(zposition))
xposition += 1
if directionChosen == "up":
demolish(realx(xposition), realz(zposition) ‐ 1)
zposition ‐= 1
if directionChosen == "down":
demolish(realx(xposition), realz(zposition) + 1)
zposition += 1
numberOfVisitedCells += 1
# after the move, increase number of visited cells
cellsVisitedList.append((xposition, zposition))
showMaker(xposition, zposition)
else: # wenn es keine unbesuchten Nachbarn gibt
retrace = cellsVisitedList.pop()
xposition = retrace[0]
zposition = retrace[1]
showMaker(xposition, zposition)
if CEILING == True:
mc.setBlocks(MAZE_X, GROUND+HEIGHT+1, MAZE_Z,
MAZE_X+(SIZE*2), GROUND+HEIGHT+1, MAZE_Z+(SIZE*2), 20)
mc.postToChat("Ihr Labyrinth ist fertig!")
mc.postToChat("Viel Spaß beim Erforschen!")
mc.player.setTilePos(realx(playerx), GROUND+1,
realz(playerz))
Wenn das Labyrinth erstellt wird, bleibt der Goldziegel sichtbar. Auf diese Weise
können Sie den Ziegel finden und das Labyrinth lösen. Sie könnten auch andere Ziele
im Labyrinth aufstellen und festlegen, wie lange der Spieler brauchen darf, um es
zu finden. Der Befehl mc.player.getTilePos()
prüft, wo sich der Spieler in der Minecraft‐Welt befindet, und liefert Ihnen Ergebnisse
in der Form x,y,z
.
Sie könnten außen am Labyrinth auch einen Ein‐ und einen Ausgang an zufälligen Stellen einbauen. Das Ziel könnte dann darin bestehen, von einer Seite zur anderen zu gelangen. Sie könnten riesige Labyrinthe dadurch spielbarer gestalten, dass Sie Landmarken integrieren. (Probieren Sie dabei verschiedene Wandmaterialien aus oder setzen Sie Blöcke oben auf einige der Wände.) Wenn das Labyrinth erzeugt wird, könnten Sie einige Wände herausbrechen, um für Abkürzungen durch das Labyrinth zu sorgen. Oder Sie könnten einige Blöcke einfach durch Glasblöcke ersetzen, um einen verlockenden Ausblick in einen anderen Korridor zu bieten. Wie wäre es mit einem Labyrinth, bei dem mehrere Etagen durch Treppen miteinander verbunden sind? Die Möglichkeiten sind enorm.