Kapitel 13

Minecraft mit Python programmieren

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 spielen

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).

images

Abbildung 13.1: Minecraft auf dem Pi

Tipp Sie können Ihre Perspektive ändern, um die Spielfigur im Fenster anzuzeigen. Drücken Sie dazu keycaps, um das Spielmenü zu öffnen, und klicken Sie dann oben links das Symbol neben dem Lautsprecher an.

Wenn Sie zu Ende gespielt haben, können Sie das Spiel durch Drücken von keycaps verlassen. Damit öffnen Sie das Spielmenü und wählen dann QUIT TO TITLE.

Im Spiel herumlaufen

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 keycaps und keycaps für vor‐ und rückwärts sowie keycaps und keycaps 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 keycaps drücken, können Sie aber auch gezielt springen.

Die beste Ansicht Ihrer Welt bekommen Sie, wenn Sie mit keycaps ein wenig in den Himmel abheben. Wenn Sie fliegen, können Sie keycaps drücken, um aufzusteigen, und mit der linken keycaps‐Taste wieder tiefer gehen. Wenn Sie keycaps 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.

Objekte erstellen und zerstören

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 keycaps bis keycaps (von links nach rechts) zwischen ihnen auswählen. Wenn Sie keycaps drücken, wird Ihr vollständiges Inventar geöffnet, in dem Sie mit den Bewegungstasten (keycaps, keycaps, keycaps und keycaps) navigieren und mit keycaps 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.

Tipp Sie können Türme bauen und auf ihnen in die Luft aufsteigen, wenn Sie darauf hinuntersehen und dabei wiederholt springen und einen Block unter sich platzieren.

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.

Vorbereitungen für Python

Eine der Besonderheiten von Minecraft besteht darin, dass es die Kontrolle der Maus übernimmt, weshalb Sie keycaps 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, keycaps zu drücken, bevor Sie programmieren. Betätigen Sie nun keycaps, 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.

Erinnerung Anfangs werden Sie wahrscheinlich zunächst bemerken, dass Minecraft vor anderen Fenstern angezeigt wird. Ihr Python‐Fenster wird dahinter angezeigt, weshalb Sie die Fensterpositionen möglicherweise umpositionieren müssen. Wenn die Bildschirmgröße bei Ihnen reicht, können Sie vielleicht die Fenster von Minecraft und Thonny gleichzeitig auf dem Bildschirm anzeigen lassen.

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.)

Das Minecraft‐Modul benutze

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 keycaps 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.

Warnung Wenn Ihr Code nicht funktioniert, sollten Sie besonders auf die Schreibweisen achten. Python unterscheidet zwischen Groß‐ und Kleinschreibung, weshalb die Schreibweise genau der Darstellung hier entsprechen muss. Achten Sie auf die gemischte Schreibweise in postToChat und den Großbuchstaben M in minecraft.Minecraft.create().

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.

Koordinaten in Minecraft verstehen

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.

Die Spielfigur neu positionieren

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)

Tipp Sie müssen diesen Befehl nicht in ein Programm setzen und es dann ausführen. Wenn Sie das Programm bereits ausgeführt haben, um das Minecraft‐Modul einzurichten, können Sie Befehle eintippen, um die Spielfigur zu verschieben und Blöcke in der Python‐Shell hinzuzufügen.

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 keycaps 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.

Blöcke hinzufügen

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

Tabelle 13.1: Materialien in der Pi‐Edition von Minecraft

Tipp Wenn Sie die Wasser‐ und Lavablöcke verwenden, könnten Sie Ihre Welt fluten. Erzeugen Sie deshalb besser eine neue Welt, um damit zu experimentieren.

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.

Erinnerung Mit dem #‐Symbol werden Kommentare gekennzeichnet, die Ihnen nur als Erinnerung dienen sollen. Der Computer ignoriert alles, was dem # in derselben Zeile folgt.

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.

Verhindern, dass Spieler die Welt ändern

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«.

Die Labyrinth‐Parameter einrichten

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.

Erinnerung Variablennamen sind schreibweisenabhängig. Für Python sind SIZE und size also zwei verschiedene Variablen. Es wäre aber trotzdem kaum sinnvoll, beide Variablen im selben Programm zu verwenden!

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.

images

Abbildung 13.2: Das Anfangsgitter.

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.

Tipp Ein aus Bücherregalen (MAZE_MATERIAL=47) bestehendes Labyrinth ist wirklich beeindruckend!

Das Fundament legen

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)

Die Labyrinth‐Mauern setzen

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.

Erinnerung Vergessen Sie am Ende der for‐Anweisung nicht den Doppelpunkt. Die nächsten beiden Zeilen sollten um vier Leerzeichen eingerückt werden, um Python mitzuteilen, dass sie zu der Schleife gehören.

Nun sollten Sie ein Gitter erzeugt haben, das wie in Abbildung 13.3 aussieht.

images

Abbildung 13.3: Ihr Gitter in Minecraft

Den Algorithmus für das Labyrinth verstehen

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:

  1. Sie beginnen mit der von Ihnen erstellten »Waffel«, wobei die Zellen noch alle vier Wände besitzen.
  2. Um zu beginnen, wählen Sie eine zufällige Zelle im Labyrinth aus.
  3. Sie betrachten die Nachbarn Ihrer aktuellen Zelle und erstellen eine Liste aller Zellen, bei denen noch alle vier Wände intakt sind. Das sind die bisher noch nicht besuchten Zellen.
  4. Wenn Sie einige unbesuchte Nachbarn finden, wählen Sie zufällig einen aus, reißen die Wand zwischen ihm und Ihrer aktuellen Zelle ein und gehen dann in diese Zelle, wodurch sie zu Ihrer aktuellen Zelle wird.
  5. Wenn es bei Ihrer Zelle keine unbesuchten Nachbarn gibt, gehen Sie eine Zelle auf dem von Ihnen genommenen Pfad zurück und machen diese zu Ihrer aktuellen Zelle.
  6. Wiederholen Sie die Schritte 3 bis 5, bis Sie alle Zellen aufgesucht haben.

Die Variablen und Listen einrichten

Um diesen Algorithmus zu implementieren, verwenden Sie die folgenden Variablen:

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))

Die Funktionen erstellen

Es gibt eine Reihe von grundlegenden Funktionen, die Sie für Ihr Programm benötigen:

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

Tipp Prüfen Sie bei auftretenden Fehlern, ob Doppelpunkte am Zeilenende Ihrer def‐ und if‐Anweisungen fehlen.

Die Hauptschleife erstellen

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.

Eine Decke einziehen

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)

Die Spielfigur positionieren

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.

images

Abbildung 13.4: Ihren Weg durch das Labyrinth finden

Der fertige Quelltext

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))

Listing 13.1: Minecraft‐Mazemaker

Das Programm anpassen

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.