Sitzung: Jeden Freitag in der Vorlesungszeit ab 16 Uhr c. t. im MAR 0.005. In der vorlesungsfreien Zeit unregelmäßig (Jemensch da?). Macht mit!

Javakurs/Übungsaufgaben/AsciiCraft: Unterschied zwischen den Versionen

(draft for asciicraft aufgabe)
 
K (Installation: Neue Version der Vorgaben. Die alten scheinen nicht richtig zu gehen.)
 
(11 dazwischenliegende Versionen von 4 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
Eine Komplexe Aufgabe, die Codelesen vertiefen soll, den Kurs zusammenfasst und einen etwas größeren Umfang als andere Aufgaben bietet.
+
Eine komplexe Aufgabe, die die Fähigket Quellcode zu lesen vertiefen soll, den Kurs zusammenfasst und einen etwas größeren Umfang als andere Aufgaben bietet.
 
 
 
 
  
 
= Aufgabe =
 
= Aufgabe =
  
Im Folgenden wird Schritt für Schritt erklärt werden, wie das Spiel AsciiCraft, eine Anlehnung an [http://www.minecraft.net/ Minecraft], geschrieben wurde. Ziel ist es in dieser Aufgabe Quellode zu lesen, diesen zu verstehen und diesen durch Erweiterungen an eigene Vorlieben anzupassen. Dazu wirde es keine Konkreten Aufgaben geben, ziel ist es eher euch einen Plattform für eigene Experimente zu geben.
+
Im Folgenden wird Schritt für Schritt erklärt werden wie das Spiel AsciiCraft, eine Anlehnung an [http://www.minecraft.net/ Minecraft], geschrieben wurde. Ziel dieser Aufgabe ist es Quellode zu lesen, diesen zu verstehen und durch Erweiterungen an eigene Vorlieben anzupassen. Dazu gibt es keine konkreten Aufgaben, wir bieten euch hier eine Plattform für eigene Experimente.
 
 
 
 
  
 
= Installation =
 
= Installation =
 +
[[Bild:asciicraft.png|thumb|280px|Javakurs 2011 - AsciiCraft]]
 +
Um das Programm zu verstehen ist es wichtig, dass man eine Vorstellung bekommt was es eigentlich tun soll. Es bietet sich daher an, dieses auf seinem Rechner/Account zu installieren. Bitte ladet euch dafür
 +
das [http://docs.freitagsrunde.org/Veranstaltungen/javakurs_2011/uebungen/Javakurs2011-AsciiCraft_V3.tar.gz Archiv von unserem Server] herunter.
  
Um das Programm zu verstehen, ist es wichtig, dass man eine Vorstellung bekommt, was es eigentlich tun soll. Daher bietet es sich an, dieses auf seinem Rechner/Account zu installieren. Bitte ladet euch dafür
+
In diesem Archiv (zu entpacken mittels ''tar -xf'', oder per grafischer Oberfläche (Linksklick auf das Archiv)) findet ihr einen Ordner und drei Skripte. Die Skripte sind zum compilieren, ausführen und löschen des Programms da und sollten selbsterklärend sein. Der Ordner ''src'' beinhaltet den Quellcode und wird später für uns sehr interessant werden.
das Archiv [https://docs.freitagsrunde.org/Veranstaltungen/Javakurs2011/asciicraft/asciicraft.tar.gz | auf unserem Server] herunter.
 
 
 
In diesem Archiv (zu entpacken mittels ''tar -xf'', oder per grafischer Oberfläche (Linkklick auf das Archiv)) findet ihr einen Ordner und drei Skripte. Die Skripte sind zum compilieren, ausführen und löschen der Kompilate da und sollten selbsterklärend sein. Der Ordner ''src'' beinhaltet den Quellcode und wird später für uns sehr interessant werden.
 
  
 
Ihr solltet nun das Projekt kompilieren (''./build.sh'') und es dann einmal starten (''./run.sh'', von der Kommandozeile aus).
 
Ihr solltet nun das Projekt kompilieren (''./build.sh'') und es dann einmal starten (''./run.sh'', von der Kommandozeile aus).
  
Nun solltet ihr ein ähnliches Bild wie das folgende sehen: [image].
+
Nun solltet ihr ein ähnliches Bild wie das hier abgebildete zu sehen bekommen.  
  
Ist dies nicht der Falls, solltet ihr die Ausgaben kontrollieren und eventuelle Fehler einem Tutor melden. Auch könntet ihr probieren die Kommandozeilen Optionen euch anzusehen:  
+
Ist dies nicht der Falls, solltet ihr die Ausgaben kontrollieren und eventuelle Fehler einem Tutor melden. Auch könntet ihr probieren euch die Kommandozeilenoptionen anzusehen:  
 
   
 
   
 
  ./run.sh -h
 
  ./run.sh -h
  
 
+
= Ziel des Spieles: ❁ =
 
 
 
 
= Ziel des Spieles =
 
  
 
Nach dem ihr im letzten Abschnitt erfolgreich das Spiel installiert und kompiliert habt, ist es nun an der Zeit die Aufgabe des Spieles kennen zu lernen: Euer Ziel ist es mit dem kleinen Pfeil (meist in der Mitte der Konsole) den Diamanten aus dem Bergwerk zu fördern.  
 
Nach dem ihr im letzten Abschnitt erfolgreich das Spiel installiert und kompiliert habt, ist es nun an der Zeit die Aufgabe des Spieles kennen zu lernen: Euer Ziel ist es mit dem kleinen Pfeil (meist in der Mitte der Konsole) den Diamanten aus dem Bergwerk zu fördern.  
  
Dieses Ziel erreicht ihr, in dem ihr mit den Tasten ''A'' und ''D'' euch nach ''links'' oder ''rechts'' bewegt. Da ihr an der Oberfläche den Diamanten nicht finden können wird, müsst ihr mit den folgen Tasten euch in den Berg eingraben:
+
Dieses Ziel erreicht ihr in dem ihr euch mit den Tasten ''A'' und ''D'' nach ''links'' oder ''rechts'' bewegt. Da ihr an der Oberfläche den Diamanten nicht finden können werdet, müsst ihr euch mit den folgen Tasten in den Berg eingraben:
  
 
* ''E'' .. baut das Feld direkt über dem Spieler ab
 
* ''E'' .. baut das Feld direkt über dem Spieler ab
* ''R'' .. gräbt das Feld ein Feld über dem das ihr anschaut an.
+
* ''R'' .. gräbt das Feld diagonal über dem das ihr anschaut an.
 
* ''F'' .. gräbt in die Richtung in die ihr gerade schaut.
 
* ''F'' .. gräbt in die Richtung in die ihr gerade schaut.
* ''C'' .. Analog zu ''R'' legt das Feld eins unter dem Feld das ihr anschaut frei.
+
* ''C'' .. Analog zu ''R'', legt das Feld diagonal unter dem Feld das ihr anschaut frei.
 
* ''X'' .. Gräbt das Feld direkt unter euch ab (''Achtung: Mit Vorsicht zu genießen!!!'')
 
* ''X'' .. Gräbt das Feld direkt unter euch ab (''Achtung: Mit Vorsicht zu genießen!!!'')
  
Damit ihr auch evtl. wieder nach oben gelangen könnt, kann der Spieler mit der Leertaste ('' '') springen, aber nur, wenn dafür genug Platz frei ist.
+
Damit ihr auch wieder nach oben gelangen könnt, kann die Spielfigur mit der Leertaste ('' '') springen, aber nur wenn dafür genug Platz frei ist.
 
 
Die ''Pfeiltasten'' und ''Esc'' sorgen dafür, dass sofort das Programm beendet wird und der GameOver-Bildschirm angezeigt wird.
 
 
 
 
 
Sobald ihr euch einen Weg zum Diamanten gegraben habt, müsst ihr ihn nur noch ausgraben, um zum Ende des Spieles zu kommen.
 
  
 +
Die ''Pfeiltasten'' und ''Esc'' sorgen dafür, dass das Programm sofort beendet und der GameOver-Bildschirm angezeigt wird.
  
 +
Sobald ihr euch einen Weg zum Diamanten gegraben habt müsst ihr ihn nur noch ausgraben, um zum Ende des Spieles zu kommen.
  
 
= Konzept des Codes =
 
= Konzept des Codes =
  
Um diese Aufgabe erfolgreich und sinnvol zu bearbeiten, ist es am besten, wenn ihr diesen Aufgaben Text parallel zum Code offen lasst, damit ihr schnell zwischen der Beschreibung und dem Code hin und her schalten könnt.
+
Um diese Aufgabe erfolgreich und sinnvoll zu bearbeiten, ist es am besten, wenn ihr diesen Text parallel zum Code offen lasst, damit ihr schnell zwischen der Beschreibung und dem Code hin und her schalten könnt.
 
 
Dieser Code wurde designed, damit er möglichst viele Techniken einsetzt, die ihr schon kennen solltet. Es wurde des Weiteren versucht den Code zu dokumentieren und zu kommentieren. So werdet ihr nicht nur die Vererbeung wiederfinden, sondern auch die Kapselung, Schleifen, Arrays, Ausgabe, Variablen und eigentlich Alles was ihr wissen müsst.
 
 
 
Basisidee ist die der Wiederverwendung. Es wurde versucht alle Klassen die an dem Programm beteiligt sind so zu gestallten, dass sie möglichst leicht auszutauschen sind. So lässt sich ohne großen Aufwand weitere Felder hinzufügen. Doch nun zu den bereits vorhandenen Klassen.  
 
  
 +
Dieser Code wurde so designed, dass er möglichst viele Techniken einsetzt die ihr schon kennen solltet.  Er ist außerdem dokumentiert und zu kommentiert. Ihr findet hier nicht nur die Vererbeung wieder, sondern auch die Kapselung, Schleifen, Arrays, Ausgabe, Variablen und alles andere was ihr wissen müsst.
  
 +
Dir Grundidee des Codes ist die Wiederverwendbarkeit und Austauschbarkeit bereits bestehender Klassen. So lassen sich ohne großen Aufwand weitere Felder hinzufügen. Doch nun zu den bereits vorhandenen Klassen.
  
 
== Pakete ==
 
== Pakete ==
Die Klassen die an dem Spiel beteiligt sind, findet ihr unter ''src/...'' alle Klassen sind unter dem gleichen Paket gespeichert: ''javakurs2011.asciicraft''. Wobei ein Paket eine Unterteilung von Klassen ist. So ist es möglich, dass es Klassen gibt, die gleich heißen, aber in anderen Paketen definiert wurden.
+
Die Klassen die an dem Spiel beteiligt sind, findet ihr unter ''src/...''. Alle Klassen sind unter dem gleichen Paket gespeichert: ''javakurs2011.asciicraft''. Wobei ein Paket eine Sammlung von Klassen ist. So ist es möglich, dass es Klassen gibt die gleich heißen, aber in anderen Paketen definiert wurden.  
 
 
Eine Klasse markiert man als zu einem Paket gehörend, in dem man es in einen Unterordner der Form ''paket/unterpaket'' steckt. Des Weiteren muss jede Klasse am Anfang seiner .java Datei einen Eintrag wie folgt haben: ''package paket.unterpaket;'', welches dafür sorgt, dass die Klasse auch als zu dem Paket gehörig erkannt wird.  (bitte beachtet, dass der ''/'' in der JavaDatei durch einen Punkt (''.'') ersetzt wurde.) Die Technik der Pakete wird meist in größeren Produktionen eingesetzt und wird euch in eurem weiteren Studium bestimmt noch häufiger begegegen.
 
  
 +
Eine Klasse markiert man als zu einem Paket gehörend, in dem man sie in einem Unterordner der Form ''paket/unterpaket'' ablegt. Außerdem muss jede Klasse am Anfang ihrer .java Datei folgenden Eintrag haben: ''package paket.unterpaket;''. Dieser sorgt dafür, dass die Klasse auch als zu dem Paket gehörig erkannt wird (bitte beachtet, dass der ''/'' in der JavaDatei durch einen Punkt ''.'' ersetzt wurde). Die Technik der Pakete wird meist in größeren Produktionen eingesetzt und wird euch in eurem weiteren Studium bestimmt noch häufiger begegegen.
  
 
== Abstrakte Klasse ''Tile'' ==
 
== Abstrakte Klasse ''Tile'' ==
 +
[[Bild:AsciiCraftTiles.png|thumb|280px|Invisible, Player, Diamond, Coal, Iron und Dirt ''Tile''s]]
  
Jedes der Felder, die ihr im Spiel seht, ist abgeleitet von der '''abstrakten''' Klasse ''Tile'' (konkret ''AirTile.java  CoalTile.java  DiamondTile.java  DirtTile.java  IronTile.java'', in allen steht bei der Klassendefinition die Zeile ''extends Tile''). Der Sinn dahinter ist, dass ihr in der abstrakten Klasse Methoden angebt, die von allen Unterklassen implementiert werden ''müssen''. Dieser Mechanismus macht es möglich, dass man im Folgenden von der genauen Implementierung der Klasse (bspw. ''IronTile'') abstrahiert und dieses nur noch als ''Tile'' auffasst.
+
Jedes der Felder die ihr im Spiel seht, ist abgeleitet von der '''abstrakten''' Klasse ''Tile'' (konkret ''AirTile.java  CoalTile.java  DiamondTile.java  DirtTile.java  IronTile.java'', in allen steht bei der Klassendefinition die Zeile ''extends Tile''). Der Sinn dahinter ist, dass ihr in der abstrakten Klasse Methoden angebt die von allen Unterklassen implementiert werden ''müssen''. Dieser Mechanismus macht es möglich, dass man im Folgenden von der genauen Implementierung der Klasse (bspw. ''IronTile'') abstrahiert und dieses nur noch als ''Tile'' auffasst.
 
 
Dies wird etwas deutlicher, wenn man sich vor Augen hält, wie das Spielfeld aussieht: In der Datei ''World.java'' wird in ''Zeile 14'' ein Feld von mehreren Objekten der Klasse ''Tile'' erzeugt. Das bedeutet, dass man in diesem Array alle Unterklassen, also alle Klassen, die von ''Tile'' erben, speichern kann, und alle mindestens die Funktionalitäten von ''Tile'' anbieten. In unserem Fall ist dies noch sehr wenig, da lediglich eine Methode, die ''toString''-Methode, von Tile als zu implementieren gekennzeichnet wurde. Vorstellbar ist aber, dass in einem späteren Zeitpunkt jedes ''Tile'' einen neue Funktionalität haben soll, wie dass beim Abbauen des Tiles eine bestimmte Methode ausgeführt werden soll. Diese Methode könnte zum Beispiel ''mine()'' heißen und könnte dafür sorgen, dass das ''IronTile'' zwei Versuche braucht um abgebaut zu werden.  
 
  
 +
Dies wird etwas deutlicher, wenn man sich vor Augen hält wie das Spielfeld aussieht: In der Datei ''World.java'' wird in Zeile 14 ein Feld von mehreren Objekten der Klasse ''Tile'' erzeugt. Das hat zur Folge, dass man in diesem Array alle Unterklassen, also alle Klassen die von ''Tile'' erben, speichern kann und alle mindestens die Funktionalitäten von ''Tile'' anbieten.
 +
In unserem Fall ist dies noch sehr wenig, da lediglich eine Methode, die ''toString''-Methode, von Tile als zu implementieren gekennzeichnet wurde. Vorstellbar ist, dass zu einem späteren Zeitpunkt jedes ''Tile'' einen neue Funktionalität hat, z.B. dass beim Abbauen des Tiles eine bestimmte Methode ausgeführt wird. Diese Methode könnte zum Beispiel ''mine()'' heißen und könnte dafür sorgen, dass das ''IronTile'' zwei Versuche braucht, um abgebaut zu werden.
  
 
== Die Welt ==
 
== Die Welt ==
Wie bereits im vorherigen Kapitel erwähnt, speichert die Klasse ''World'' alles was zur Spielwelt gehört. Dies ist nicht nur die Größe des Spielfeldes, die Spielfelder selbst, sondern auch den Spieler. Der Spieler ansich ist auch nur ein klügeres ''Tile''. So kann er zwar genau wie anderer ''Tiles'' auch angezeigt werden, siehe ''toString()'', aber er kann auch in der Methode ''simulate()'' sich den aktuellen Gegebenheiten anpassen. So wird gegenwärtig diese Methode dazu genutzt den Spieler auf den Boden fallen zu lassen, sobald er auf einem 'AirTile''-Feld steht. Damit das Programm diese Entscheidung treffen kann, muss es wissen auf welcher Welt er sich befindet, also wird der ''simulate''-Methode die Welt als Parameter mit angegeben.
+
Wie bereits im vorherigen Kapitel erwähnt, speichert die Klasse ''World'' alles was zur Spielwelt gehört. Das umfasst nicht nur die Größe des Spielfeldes und die Spielfelder selbst, sondern auch die Spielfigur. Die Spielfigur ist tatsächlich auch nur ein klügeres ''Tile''! Sie kann also genau wie anderer ''Tiles'' auch angezeigt werden, siehe ''toString()'', aber zusätzlich kann sie sich auch mit der Methode ''simulate()'' den aktuellen Gegebenheiten anpassen.  
 
+
So wird gegenwärtig diese Methode dazu genutzt die Spielfigur auf den Boden fallen zu lassen, sobald sie auf einem 'AirTile''-Feld steht. Damit das Programm diese Entscheidung treffen kann, muss es wissen auf welcher Welt die Figur sich befindet, deswegen wird der ''simulate''-Methode die Welt als Parameter übergeben.
Dies Welt ist des Weiteren auch für das Anzeigen und Erstellen der neuen Spielwelt zuständig. Anzeigen tut sie, indem sie zuerst durch das Feld der ''Tiles'' geht und jedes aus dem Array heraus nimmt und dessen ''toString()''-Methode implizit aufruft. (vgl. Zeile 154 in World.java, dort wird nicht .toString() benutzt, sondern implizit das Tile in einen String durch addieren des Tiles zu einem String, aufgerufen). Das ganze passiert jedoch nur, falls das Tile überhaupt sichtbar (''.isVisible()'' liefert ''true'') ist. Ist dies nicht der Fall wird lediglich ein graues Viereck angezeigt. Das Tile ist nur dann nicht sichtbar, wenn es bisher noch nicht entdeckt worden ist und unterhalb der Oberfläche liegt.
 
  
Dies führt uns zur Erstellung des Spielfeldes. Das Spielfeld wird in der Zeile 48 der Datei ''World.java'' erzeugt. Das Erstellen des Feldes basiert sehr stark auf ''kontrolliertem'' Zufall: So wird in Zeile 50 zunächst das gesamte Spielfeld auf ''Luft'' gesetzt. Dies wird erreicht, indem an jedem Index des Feldes ein neues Element vom Typ ''AirTile'' erstellt wird. Ein Teil dieser Luft-Teile wird im Folgenden durch zufällige ''DirtTiles'' ersetzt. Basis dafür ist dass innerhalb der for-Schleife in Zeile 56 an einer zufälligen Höhe nahe der Hälfte des Spielfeldes für jeden Spalteneintrag neu bestimmt wird, ob das Tile nach oben oder unten plaziert werden soll. Nachdem die Höhe des ''Horizontes'' erstellt wurde, werden alle folgenden Teile auch als ''DirtTiles'' erstellt.
+
Dies Welt ist des Weiteren auch für das Anzeigen und Erstellen der neuen Spielwelt zuständig. Das Anzeigen funktoniert, indem sie zuerst durch das Feld der ''Tiles'' geht und jedes aus dem Array heraus nimmt und dessen ''toString()''-Methode implizit aufruft (vgl. Zeile 154 in World.java, dort wird nicht .toString() benutzt, sondern implizit das Tile in einen String umgewandelt, indem es zu einem anderen String addiert wird). Das passiert jedoch nur, falls das Tile überhaupt sichtbar (''.isVisible()'' liefert ''true'') ist. Ist das nicht der Fall, wird lediglich ein graues Viereck angezeigt.
 +
Das Tile ist nur dann nicht sichtbar, wenn es bisher noch nicht entdeckt worden ist und unterhalb der Oberfläche liegt.
  
Sobald alle ''DirtTile''s erstellt wurden, werden erneut alle ''DirtTiles'' angefasst (Zeile 72) und zufällig durch entweder ein ''IronTile'' oder ein ''CoalTile'' ersetzt.
+
Dies führt uns zur Erstellung des Spielfeldes. Das Spielfeld wird in der Zeile 48 der Datei ''World.java'' erzeugt. Das Erstellen basiert stark auf ''kontrolliertem'' Zufall: So wird in Zeile 50 zunächst das gesamte Spielfeld auf ''Luft'' gesetzt. Das wird erreicht, indem an jedem Index des Feldes ein neues Element vom Typ ''AirTile'' erstellt wird. Ein Teil dieser Luft-Teile wird dann im Folgenden durch zufällige ''DirtTiles'' ersetzt. Basis dafür ist, dass innerhalb der for-Schleife in Zeile 56 an einer zufälligen Höhe nahe der Hälfte des Spielfeldes für jeden Spalteneintrag neu bestimmt wird, ob das Tile nach oben oder unten plaziert werden soll. Nachdem die Höhe des ''Horizontes'' erstellt wurde, werden alle folgenden Teile auch als ''DirtTiles'' erstellt.
  
Nun sind prinzipiell alle ''normalen'' Tiles erstellt und die Welt ansich ist einsatzbereit. Nun der Spaß des Programmes beginnt dann, wenn man die Welt langsam anfängt zu erforschen. Daher müssen noch alle Tiles auf ''unsichtbar'' gesetzt werden, die nicht direkt an der Oberfläche liegen. Das passiert, in dem für alle Spalten des Feldes gezählt wird, wieviele nicht-''AirTiles'' bereits gefunden wurden. Ist diese Zahl größer als eins, bedeutet dies, dass wir unterhalb der Oberfläche sind, und somit, zumindest initial alle Tiles unsichtbar sind. Diese Annahme gilt nur, wenn wir keine Überhänge in der Oberwelt zulassen.
+
Sobald alle ''DirtTile''s erstellt wurden, werden diese erneut angefasst (Zeile 72) und zufällig durch entweder ein ''IronTile'' oder ein ''CoalTile'' ersetzt. Nun sind prinzipiell alle ''normalen'' Tiles erstellt und die Welt ansich ist einsatzbereit.  
  
Nun wird der Diamant plaziert. Dies passiert auch wieder sehr zufällig, indem einfach eine zufällige Position eines ''DirtTile''s gesucht wird, die nahe am unteren Ende des Spielfeldes liegt.  
+
Der Spaß des Spiels liegt nun u.A. drin, die Welt stückweise zu erforschen. Daher müssen noch alle Tiles auf ''unsichtbar'' gesetzt werden, die nicht direkt an der Oberfläche liegen. Das passiert, indem für alle Spalten des Feldes gezählt wird, wieviele nicht-''AirTiles'' bereits gefunden wurden. Ist diese Zahl größer als eins, bedeutet dies, dass wir unterhalb der Oberfläche sind, und somit, zumindest initial alle Tiles unsichtbar sind.
 +
*Wichtig:* Diese Annahme gilt nur, wenn wir keine Überhänge in der Oberwelt zulassen.
  
Nach dem Der Diamant gesetzt wurde, wird versucht, den Spieler leicht mittig auf dem Feld zu plazieren, indem nach einem ''AirTile'' in der Mittleren Spalte gesucht wird. Wurde kein Leeres Tile gefunden, so wird es bei der nächsten Spalte weiter probiert. Vorstellen kann man sich dies wie bei dem alten ''VierGewinnt''-Spiel: Der Spieler wird in einer Spalte herein geworfen und geschaut, ob er gleich wieder oben heraus fällt. Ist dies der Fall wird er einfach bei der nächsten Spalte eingeworfen und das ganze nochmal probiert.
+
Nachdem alle anderne Felder definiert sind, wird der Diamant plaziert. Dies passiert auch wieder zufällig, indem einfach eine zufällige Position eines ''DirtTile''s gesucht wird, die nahe am unteren Ende des Spielfeldes liegt.  
  
 +
Nach dem Der Diamant gesetzt wurde, wird versucht den Spieler leicht mittig auf dem Feld zu plazieren, indem nach einem ''AirTile'' in der mittleren Spalte gesucht wird. Wurde kein leeres Tile gefunden, wird es bei der nächsten Spalte weiter probiert. Vorstellen kann man sich dies wie bei dem alten ''VierGewinnt''-Spiel: Die Figur wird in einer Spalte herein geworfen und getestet, ob sie gleich wieder oben heraus fällt. Ist dies der Fall, wird sie einfach bei der nächsten Spalte eingeworfen und das ganze nochmal probiert.
  
 
== Interaktion ==  
 
== Interaktion ==  
  
Nachdem nun die Welt erstellt wurde, wir nun wissen wie wir sie anzeigen können, ist das Einzige was noch fehlt die Interaktion mit ihr. Zum Anfang des Programmes wird, wie bereits bekannt, die ''main''-Methode ausgeführt (siehe ''Main.java''). Diese sorgt dafür, dass wir auf dem gesamten Terminal schreiben können. Siehe dazu auch die [http://jline.sourceforge.net/ benutzte Bibliothek ''JLine''], da diese genauer beschreibt wie genau es funkioniert. Nachdem die Bibliothek JLine initialisiert wurde, werden die Variablen von Benutzer eingelesen (Mittels der Kommandozeilen Parameter) und das Spielfeld erstellt. Folgend wird die Hauptschleife, also die, die das gesamte Programm kontrolliert gestartet. Die Idee ist, dass nach dem die Methode ''runMainLoop(..)' zurück gekommen ist, das Programm beendet werden soll. Daher ist der letzte Aufruf der ''main''-Methode ein Reset des Terminals, damit Alles wieder auf seinen initialen Zustand zurück gesetzt wird.
+
Nachdem nun die Welt erstellt wurde und wir wissen wie wir sie anzeigen können, ist das Einzige was noch fehlt die Interaktion mit ihr.  
  
Das eigentlich Spannende passiert in der ''runMainLoopMethode(..)''. Hier werden zunächst einige statistische Variablen erzeugt, die speichern wieviele ''Tile'' von den unterschiedlichen Typen abgetragen worden sind. Nach der Initialisierung dieser Variablen wird die Schleife gestartet, die erst beendet wird, wenn das Spiel beendet werden soll (also, entweder wenn der Diamant gefunden wurde, oder aber sobald eine der Pfeiltasten oder ESC gedrückt wurde.)
+
Zum Anfang des Programmes wird, wie bereits bekannt, die ''main''-Methode ausgeführt (siehe ''Main.java''). Diese sorgt dafür, dass wir auf dem gesamten Terminal schreiben können. Siehe dazu auch die [http://jline.sourceforge.net/ benutzte Bibliothek ''JLine''], dort wird genauer beschrieben wie das funkioniert.  
 +
Nachdem die Bibliothek JLine initialisiert wurde, werden die Variablen des Spielers/der Spielerin eingelesen (über die übergebenen Kommandozeilenparameter) und das Spielfeld erstellt. Darauf folgend wird die Hauptschleife, also die, die das gesamte Programm kontrolliert, gestartet.
 +
Die Idee dahinter ist, dass nach dem die Methode ''runMainLoop(..)' zurück gekommen ist, das Programm beendet werden soll. Deshalb ist der letzte Aufruf der ''main''-Methode ein Reset des Terminals, damit Alles wieder auf seinen initialen Zustand zurück gesetzt wird.
  
Zu Allererst wird die Welt aktualisiert, indem w.simulate() aufgerufen wird. Diese Methode aktualisiert zur Zeit lediglich den Player, aber könnte Theoretisch auch für allgemeinere Situationen benutzt werden. Nachdem die SpielWelt aktualisiert wurde, ist es an der Zeit die Welt anzuzeigen. Nach dem Anzeigen wird ausgewertet, ob der Benutzer einer der Tasten gedrückt hat. Ist dies der Fall, wird geschaut, ob eines der Tile gemined werden soll. Die Überprüfung passiert in der bereits erwähnten ''mine()''-Methode, die das Tile zurück liefert, welches gerade gemined wurde (dabei kann es sich prinzipiell auch um ein ''AirTile'' handeln.) Als nächstes wird diese Tile in der runMainLoop() ausgewertet, und die loop beendet, wenn ein ''DiamondTile'' gefunden wurde. Sonst werden einfach die Zähler der passenden Typen hochgesetzt. Nun beginnt die Schleife wieder von vorne, in dem sie zuerst die Welt aktualisiert, dann sie anzeigt und letztlich erneut die Eingaben des Benutzers auswertet.
+
Das eigentlich Spannende passiert in der ''runMainLoopMethode(..)''. Hier werden zunächst einige statistische Variablen erzeugt, die speichern sollen wieviele ''Tile'' von den unterschiedlichen Typen bereits abgetragen worden sind. Nach der Initialisierung dieser Variablen wird die Endlosschleife gestartet, die erst dann beendet wird, wenn das Spiel beendet werden soll (also, entweder wenn der Diamant gefunden wurde, oder sobald eine der Pfeiltasten oder ESC gedrückt wurde.)
  
Nach dem die Schleife verlassen wurde, wird der ''GameOver''-Bildschirm angezeigt, der die statistischen Variablen ausgibt.
+
Jetzt wird zu allererst die Welt aktualisiert, indem w.simulate() aufgerufen wird. Diese Methode aktualisiert zur Zeit lediglich die Spielfigur, könnte aber theoretisch auch für allgemeinere Situationen benutzt werden.
 +
Nachdem die Spielwelt aktualisiert wurde, ist es an der Zeit sie anzuzeigen. Nach dem Anzeigen wird ausgewertet, ob der Benutzer einer der Tasten gedrückt hat. Ist dies der Fall, wird festgestellt, ob eines der Tile abgebaut werden soll. Die Überprüfung passiert in der bereits erwähnten ''mine()''-Methode, die das Tile zurück liefert, welches gerade gemined wurde (dabei kann es sich prinzipiell auch um ein ''AirTile'' handeln).
 +
Als nächstes wird dieses Tile in der runMainLoop() ausgewertet und ggf. die Schleife beendet, falls ein ''DiamondTile'' gefunden wurde. Sonst werden einfach die Zähler der passenden Typen hochgesetzt. Nun beginnt die Schleife wieder von vorne, indem sie zuerst die Welt aktualisiert, sie dann anzeigt und letztlich erneut die Eingaben des Benutzers auswertet.
  
 +
Wird die Schleife verlassen, folgt die Anzeige des ''GameOver''-Bildschirms.  Dort werden die statistischen Variablen als Highscore ausgegen.
  
 +
= Ende =
 +
[[Bild:asciicraft-score.png|thumb|280px|Javakurs 2011 - AsciiCraft GameOver]]
  
= Ende =
+
Damit sind wir auch schon am Ende des kurzen Überblicks über das Spiel angelangt. Jetzt bleibt euch nur zu tun, das eben Gelesene noch mal revue passieren zu lassen und evtl. eigenen Erweiterungen zu schreiben. Wie wäre es zum Beispiel mit einem Mehrspielermodus, oder mit Lava, welche der Figur Schaden zufügt? Auch könntet ihr einfach von vorne Anfangen und ein ganz eigenes Spiel schreiben.
Damit sind wir auch schon am Ende des kurzen Überblicks über das Spiel angelangt. Jetzt bleibt euch nur zu tun, das ebend gelesene noch mal revue passieren zu lassen und evtl. eigenen Erweiterungen zu schreiben (Wie wäre es zum Beispiel mit einem Mehrspielermodus, oder mit Lava, welche dem Spieler Schaden zufügt). Auch könntet ihr einfach von vorne Anfangen und ein ganz eigenes Spiel schreiben.
 

Aktuelle Version vom 8. März 2013, 11:42 Uhr

Eine komplexe Aufgabe, die die Fähigket Quellcode zu lesen vertiefen soll, den Kurs zusammenfasst und einen etwas größeren Umfang als andere Aufgaben bietet.

Aufgabe

Im Folgenden wird Schritt für Schritt erklärt werden wie das Spiel AsciiCraft, eine Anlehnung an Minecraft, geschrieben wurde. Ziel dieser Aufgabe ist es Quellode zu lesen, diesen zu verstehen und durch Erweiterungen an eigene Vorlieben anzupassen. Dazu gibt es keine konkreten Aufgaben, wir bieten euch hier eine Plattform für eigene Experimente.

Installation

Javakurs 2011 - AsciiCraft

Um das Programm zu verstehen ist es wichtig, dass man eine Vorstellung bekommt was es eigentlich tun soll. Es bietet sich daher an, dieses auf seinem Rechner/Account zu installieren. Bitte ladet euch dafür das Archiv von unserem Server herunter.

In diesem Archiv (zu entpacken mittels tar -xf, oder per grafischer Oberfläche (Linksklick auf das Archiv)) findet ihr einen Ordner und drei Skripte. Die Skripte sind zum compilieren, ausführen und löschen des Programms da und sollten selbsterklärend sein. Der Ordner src beinhaltet den Quellcode und wird später für uns sehr interessant werden.

Ihr solltet nun das Projekt kompilieren (./build.sh) und es dann einmal starten (./run.sh, von der Kommandozeile aus).

Nun solltet ihr ein ähnliches Bild wie das hier abgebildete zu sehen bekommen.

Ist dies nicht der Falls, solltet ihr die Ausgaben kontrollieren und eventuelle Fehler einem Tutor melden. Auch könntet ihr probieren euch die Kommandozeilenoptionen anzusehen:

./run.sh -h

Ziel des Spieles: ❁

Nach dem ihr im letzten Abschnitt erfolgreich das Spiel installiert und kompiliert habt, ist es nun an der Zeit die Aufgabe des Spieles kennen zu lernen: Euer Ziel ist es mit dem kleinen Pfeil (meist in der Mitte der Konsole) den Diamanten aus dem Bergwerk zu fördern.

Dieses Ziel erreicht ihr in dem ihr euch mit den Tasten A und D nach links oder rechts bewegt. Da ihr an der Oberfläche den Diamanten nicht finden können werdet, müsst ihr euch mit den folgen Tasten in den Berg eingraben:

  • E .. baut das Feld direkt über dem Spieler ab
  • R .. gräbt das Feld diagonal über dem das ihr anschaut an.
  • F .. gräbt in die Richtung in die ihr gerade schaut.
  • C .. Analog zu R, legt das Feld diagonal unter dem Feld das ihr anschaut frei.
  • X .. Gräbt das Feld direkt unter euch ab (Achtung: Mit Vorsicht zu genießen!!!)

Damit ihr auch wieder nach oben gelangen könnt, kann die Spielfigur mit der Leertaste ( ) springen, aber nur wenn dafür genug Platz frei ist.

Die Pfeiltasten und Esc sorgen dafür, dass das Programm sofort beendet und der GameOver-Bildschirm angezeigt wird.

Sobald ihr euch einen Weg zum Diamanten gegraben habt müsst ihr ihn nur noch ausgraben, um zum Ende des Spieles zu kommen.

Konzept des Codes

Um diese Aufgabe erfolgreich und sinnvoll zu bearbeiten, ist es am besten, wenn ihr diesen Text parallel zum Code offen lasst, damit ihr schnell zwischen der Beschreibung und dem Code hin und her schalten könnt.

Dieser Code wurde so designed, dass er möglichst viele Techniken einsetzt die ihr schon kennen solltet. Er ist außerdem dokumentiert und zu kommentiert. Ihr findet hier nicht nur die Vererbeung wieder, sondern auch die Kapselung, Schleifen, Arrays, Ausgabe, Variablen und alles andere was ihr wissen müsst.

Dir Grundidee des Codes ist die Wiederverwendbarkeit und Austauschbarkeit bereits bestehender Klassen. So lassen sich ohne großen Aufwand weitere Felder hinzufügen. Doch nun zu den bereits vorhandenen Klassen.

Pakete

Die Klassen die an dem Spiel beteiligt sind, findet ihr unter src/.... Alle Klassen sind unter dem gleichen Paket gespeichert: javakurs2011.asciicraft. Wobei ein Paket eine Sammlung von Klassen ist. So ist es möglich, dass es Klassen gibt die gleich heißen, aber in anderen Paketen definiert wurden.

Eine Klasse markiert man als zu einem Paket gehörend, in dem man sie in einem Unterordner der Form paket/unterpaket ablegt. Außerdem muss jede Klasse am Anfang ihrer .java Datei folgenden Eintrag haben: package paket.unterpaket;. Dieser sorgt dafür, dass die Klasse auch als zu dem Paket gehörig erkannt wird (bitte beachtet, dass der / in der JavaDatei durch einen Punkt . ersetzt wurde). Die Technik der Pakete wird meist in größeren Produktionen eingesetzt und wird euch in eurem weiteren Studium bestimmt noch häufiger begegegen.

Abstrakte Klasse Tile

Invisible, Player, Diamond, Coal, Iron und Dirt Tiles

Jedes der Felder die ihr im Spiel seht, ist abgeleitet von der abstrakten Klasse Tile (konkret AirTile.java CoalTile.java DiamondTile.java DirtTile.java IronTile.java, in allen steht bei der Klassendefinition die Zeile extends Tile). Der Sinn dahinter ist, dass ihr in der abstrakten Klasse Methoden angebt die von allen Unterklassen implementiert werden müssen. Dieser Mechanismus macht es möglich, dass man im Folgenden von der genauen Implementierung der Klasse (bspw. IronTile) abstrahiert und dieses nur noch als Tile auffasst.

Dies wird etwas deutlicher, wenn man sich vor Augen hält wie das Spielfeld aussieht: In der Datei World.java wird in Zeile 14 ein Feld von mehreren Objekten der Klasse Tile erzeugt. Das hat zur Folge, dass man in diesem Array alle Unterklassen, also alle Klassen die von Tile erben, speichern kann und alle mindestens die Funktionalitäten von Tile anbieten. In unserem Fall ist dies noch sehr wenig, da lediglich eine Methode, die toString-Methode, von Tile als zu implementieren gekennzeichnet wurde. Vorstellbar ist, dass zu einem späteren Zeitpunkt jedes Tile einen neue Funktionalität hat, z.B. dass beim Abbauen des Tiles eine bestimmte Methode ausgeführt wird. Diese Methode könnte zum Beispiel mine() heißen und könnte dafür sorgen, dass das IronTile zwei Versuche braucht, um abgebaut zu werden.

Die Welt

Wie bereits im vorherigen Kapitel erwähnt, speichert die Klasse World alles was zur Spielwelt gehört. Das umfasst nicht nur die Größe des Spielfeldes und die Spielfelder selbst, sondern auch die Spielfigur. Die Spielfigur ist tatsächlich auch nur ein klügeres Tile! Sie kann also genau wie anderer Tiles auch angezeigt werden, siehe toString(), aber zusätzlich kann sie sich auch mit der Methode simulate() den aktuellen Gegebenheiten anpassen. So wird gegenwärtig diese Methode dazu genutzt die Spielfigur auf den Boden fallen zu lassen, sobald sie auf einem 'AirTile-Feld steht. Damit das Programm diese Entscheidung treffen kann, muss es wissen auf welcher Welt die Figur sich befindet, deswegen wird der simulate-Methode die Welt als Parameter übergeben.

Dies Welt ist des Weiteren auch für das Anzeigen und Erstellen der neuen Spielwelt zuständig. Das Anzeigen funktoniert, indem sie zuerst durch das Feld der Tiles geht und jedes aus dem Array heraus nimmt und dessen toString()-Methode implizit aufruft (vgl. Zeile 154 in World.java, dort wird nicht .toString() benutzt, sondern implizit das Tile in einen String umgewandelt, indem es zu einem anderen String addiert wird). Das passiert jedoch nur, falls das Tile überhaupt sichtbar (.isVisible() liefert true) ist. Ist das nicht der Fall, wird lediglich ein graues Viereck angezeigt. Das Tile ist nur dann nicht sichtbar, wenn es bisher noch nicht entdeckt worden ist und unterhalb der Oberfläche liegt.

Dies führt uns zur Erstellung des Spielfeldes. Das Spielfeld wird in der Zeile 48 der Datei World.java erzeugt. Das Erstellen basiert stark auf kontrolliertem Zufall: So wird in Zeile 50 zunächst das gesamte Spielfeld auf Luft gesetzt. Das wird erreicht, indem an jedem Index des Feldes ein neues Element vom Typ AirTile erstellt wird. Ein Teil dieser Luft-Teile wird dann im Folgenden durch zufällige DirtTiles ersetzt. Basis dafür ist, dass innerhalb der for-Schleife in Zeile 56 an einer zufälligen Höhe nahe der Hälfte des Spielfeldes für jeden Spalteneintrag neu bestimmt wird, ob das Tile nach oben oder unten plaziert werden soll. Nachdem die Höhe des Horizontes erstellt wurde, werden alle folgenden Teile auch als DirtTiles erstellt.

Sobald alle DirtTiles erstellt wurden, werden diese erneut angefasst (Zeile 72) und zufällig durch entweder ein IronTile oder ein CoalTile ersetzt. Nun sind prinzipiell alle normalen Tiles erstellt und die Welt ansich ist einsatzbereit.

Der Spaß des Spiels liegt nun u.A. drin, die Welt stückweise zu erforschen. Daher müssen noch alle Tiles auf unsichtbar gesetzt werden, die nicht direkt an der Oberfläche liegen. Das passiert, indem für alle Spalten des Feldes gezählt wird, wieviele nicht-AirTiles bereits gefunden wurden. Ist diese Zahl größer als eins, bedeutet dies, dass wir unterhalb der Oberfläche sind, und somit, zumindest initial alle Tiles unsichtbar sind.

  • Wichtig:* Diese Annahme gilt nur, wenn wir keine Überhänge in der Oberwelt zulassen.

Nachdem alle anderne Felder definiert sind, wird der Diamant plaziert. Dies passiert auch wieder zufällig, indem einfach eine zufällige Position eines DirtTiles gesucht wird, die nahe am unteren Ende des Spielfeldes liegt.

Nach dem Der Diamant gesetzt wurde, wird versucht den Spieler leicht mittig auf dem Feld zu plazieren, indem nach einem AirTile in der mittleren Spalte gesucht wird. Wurde kein leeres Tile gefunden, wird es bei der nächsten Spalte weiter probiert. Vorstellen kann man sich dies wie bei dem alten VierGewinnt-Spiel: Die Figur wird in einer Spalte herein geworfen und getestet, ob sie gleich wieder oben heraus fällt. Ist dies der Fall, wird sie einfach bei der nächsten Spalte eingeworfen und das ganze nochmal probiert.

Interaktion

Nachdem nun die Welt erstellt wurde und wir wissen wie wir sie anzeigen können, ist das Einzige was noch fehlt die Interaktion mit ihr.

Zum Anfang des Programmes wird, wie bereits bekannt, die main-Methode ausgeführt (siehe Main.java). Diese sorgt dafür, dass wir auf dem gesamten Terminal schreiben können. Siehe dazu auch die benutzte Bibliothek JLine, dort wird genauer beschrieben wie das funkioniert. Nachdem die Bibliothek JLine initialisiert wurde, werden die Variablen des Spielers/der Spielerin eingelesen (über die übergebenen Kommandozeilenparameter) und das Spielfeld erstellt. Darauf folgend wird die Hauptschleife, also die, die das gesamte Programm kontrolliert, gestartet. Die Idee dahinter ist, dass nach dem die Methode runMainLoop(..)' zurück gekommen ist, das Programm beendet werden soll. Deshalb ist der letzte Aufruf der main-Methode ein Reset des Terminals, damit Alles wieder auf seinen initialen Zustand zurück gesetzt wird.

Das eigentlich Spannende passiert in der runMainLoopMethode(..). Hier werden zunächst einige statistische Variablen erzeugt, die speichern sollen wieviele Tile von den unterschiedlichen Typen bereits abgetragen worden sind. Nach der Initialisierung dieser Variablen wird die Endlosschleife gestartet, die erst dann beendet wird, wenn das Spiel beendet werden soll (also, entweder wenn der Diamant gefunden wurde, oder sobald eine der Pfeiltasten oder ESC gedrückt wurde.)

Jetzt wird zu allererst die Welt aktualisiert, indem w.simulate() aufgerufen wird. Diese Methode aktualisiert zur Zeit lediglich die Spielfigur, könnte aber theoretisch auch für allgemeinere Situationen benutzt werden. Nachdem die Spielwelt aktualisiert wurde, ist es an der Zeit sie anzuzeigen. Nach dem Anzeigen wird ausgewertet, ob der Benutzer einer der Tasten gedrückt hat. Ist dies der Fall, wird festgestellt, ob eines der Tile abgebaut werden soll. Die Überprüfung passiert in der bereits erwähnten mine()-Methode, die das Tile zurück liefert, welches gerade gemined wurde (dabei kann es sich prinzipiell auch um ein AirTile handeln). Als nächstes wird dieses Tile in der runMainLoop() ausgewertet und ggf. die Schleife beendet, falls ein DiamondTile gefunden wurde. Sonst werden einfach die Zähler der passenden Typen hochgesetzt. Nun beginnt die Schleife wieder von vorne, indem sie zuerst die Welt aktualisiert, sie dann anzeigt und letztlich erneut die Eingaben des Benutzers auswertet.

Wird die Schleife verlassen, folgt die Anzeige des GameOver-Bildschirms. Dort werden die statistischen Variablen als Highscore ausgegen.

Ende

Javakurs 2011 - AsciiCraft GameOver

Damit sind wir auch schon am Ende des kurzen Überblicks über das Spiel angelangt. Jetzt bleibt euch nur zu tun, das eben Gelesene noch mal revue passieren zu lassen und evtl. eigenen Erweiterungen zu schreiben. Wie wäre es zum Beispiel mit einem Mehrspielermodus, oder mit Lava, welche der Figur Schaden zufügt? Auch könntet ihr einfach von vorne Anfangen und ein ganz eigenes Spiel schreiben.