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/Doom: Unterschied zwischen den Versionen

 
(Kommentare)
 
(34 dazwischenliegende Versionen von 22 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
      '''ASCII-DOOM'''  
+
'''ASCII-DOOM'''  
  
In Ascii Doom geht es darum ein kleines Spiel zu implementieren mit ASCII Grafiken wird
+
In Ascii-Doom geht es darum, ein kleines [http://de.wikipedia.org/wiki/Rogue-like rogue-like] Spiel zu implementieren.  
es möglich sein mit einer Spielfigur auf dem Spielfeld zu laufen, später könnt ihr auch
 
Items, Waffen und Liv Tyler einbauen! Wir werden Klasse für Klasse das Spiel
 
implementieren.
 
  
 +
D.h. es wird möglich sein, mittels [http://en.wikipedia.org/wiki/ASCII_art ASCII-Art], mit einer Spielfigur auf dem Spielfeld zu laufen, später könnt ihr auch
 +
Items, Waffen und [http://de.wikipedia.org/wiki/Liv_Tyler Liv Tyler] einbauen!
  
 +
Wir werden Schritt für Schritt das Spiel implementieren. Wenn ihr verloren geht, keine Angst, wir können euch helfen ...
  
----
 
  
== class GameMain Teil 1 ==
 
  
* Die Klasse GameMain ist die Klasse, die die main Methode enthalten wird und somit ausführbar ist.
+
== Die Klasse ''GameMain'' - Der Anfang ==
 
* Sie wird die Schnittstelle zur Außenwelt sein und das printen auf die Konsole tätigen.
 
  
* Erstellt zunächst nur diese Klsse. Sobald ihr Klassen aus den späteren Aufgaben implentiert habt, könnt ihr in der main Methode ihre Funktionalität testen.  
+
Die Klasse ''GameMain'' ist die Klasse, die die main Methode enthalten wird und somit ausführbar ist. Sie wird die Schnittstelle zur Außenwelt sein und die Ausgabe auf die Konsole tätigen.
  
 +
* Erstellt zunächst nur diese Klasse. Sobald ihr Klassen aus den späteren Aufgaben implementiert habt, könnt ihr in der ''main''-Methode ihre Funktionalität testen.
  
----
 
  
 +
== Positionen für alle Objekte ==
  
== class Position Teil 1 ==
+
Die Klasse ''Position'' repräsentiert eine Position auf einem 2D-Spielfeld. Also karthesische 2 dimensionale Koordinaten, wie ihr sie von einem kariertem Papier kennt. Des Weiteren enthält Sie private die folgende Attribute:  ''x'' und ''y'', gekapselt mit ''Gettern'' und ''Settern''.
 
 
* Position repräsentiert eine Position auf einem 2D Spielfeld.
 
 
 
* Enthält eine private x und eine y Variable gekapselt mit gettern/settern.
 
 
 
* Implementiert eine equals Methode, die 2 Positions vergleicht.
+
* Schreibt diese ''Getter'' und ''Setter''
 
+
* Implementiert eine equals-Methode, die 2 Objekte vom Typ ''Position'' vergleicht.
* '''TESTET euer equals!'''
+
* Testet nun eure ''equals''-Methode in der ''main''. (Tested sie solange bis sie funktioniert)
 
   
 
   
  
----
 
  
 +
== Repräsentation eines Gebietes ==
  
== class GameTile Teil 1 ==
+
''GameTile'' wird unsere Klasse sein, die ein Tile, also ein Feld, im späteren Spiel repräsentiert. Alle weiteren Felder werden von ihr erben. So wird es später ein ''leeres Feld'' geben, eine ''Mauer'', und Alles was euch noch einfällt.
  
* GameTile repräsentiert ein Tile ( ein Feld ) im späteren Spiel.
+
* Es enthält außerdem eine private Variable vom Typ ''Position'', welche die Position des GameTiles auf dem Feld speichert.
 
 
* Enthält eine private Variable, die für den Typ des Feldes steht ( am besten ein int ).
+
* Implementiert eine ''toString''-Methode, die jedem ''GameTile'' einen ''String'' mit Länge 1 zuordnet.
** 1 entspricht wand
+
** Diese Methode wird von allen erbenden Klassen überschrieben werden
** 2 entspricht leer
+
 
+
* Schreibt nun einige Klassen die von ''GameTile'' erben
* Also auch hier braucht ihr getter und setter.
+
** Als Namen bietet sich ''WallTile'' und ''EmptyTile''
+
** Die ''toString()''-Methoden sollten dann sinvolle Zeichen zurückgeben, wie " " oder "#".
* Enthält außerdem eine private Variable vom Typ Position, welche die Position des GameTiles auf dem Feld speichert.
 
 
* Implementiert eine toString methode, die jedem GameTile einen String mit Länge 1 zuordnet.
 
** wand = "x"
 
** leer = " "
 
 
 
* '''TESTET euer toString.'''
+
* Testet auch hier euere ''toString''-Methoden, in dem ihr euch einzelne Tiles erzeugt und diese ausgeben last'''
 
 
----
 
  
 
+
== Das GameField ==
== class GameField Teil 1 ==
+
Euer ''GameField'' ist eine zentrale Klasse in diesem Spiel, sie repräsentiert das Spielfeld als Ganzes und besteht aus mehreren ''GameTiles''.
 
 
 
 
* GameField ist eine zentrale Klasse in diesem Spiel, sie repräsentiert das Spielfeld als ganzes und besteht aus GameTiles.
 
 
 
* Enthält eine private Variable für das Level ( 2D Array aus GameTiles ).  
+
* Enthält eine private Variable für das Level (2D-Array aus GameTiles).  
 
 
* Über den Konstruktor wird ein String dieser Form übergeben:
+
* Über den Konstruktor wird ein String dieser Form übergeben(ebenso wie die Breite des Spielfeldes):
  ########  
+
        String s = "########" +
    #      #  
+
    "#      #" +
    #      #
+
    "#      #" +
    ###    #
+
    "###    #" +
    #      #
+
    "#      #" +
    ########  
+
    "########";
** Es soll mit einer Schleife der String Zeichen für Zeichen ausgelesen werden und aus jedem Zeichen ein neues GameTile ( fest oder leer ) erzeugt werden Diese GameTiles werden dann in dem Array gespeichert!
+
** Es soll mit einer Schleife der String Zeichen für Zeichen ausgelesen werden und aus jedem Zeichen ein neues ''GameTile'' (Wand oder leer) erzeugt werden. Diese ''GameTiles'' werden dann in dem Array gespeichert!
** Wie man Strings Zeichen für Zeichen ausliest, findet ihr in der API ( java.lang.String ).
+
** Wie man Strings Zeichen für Zeichen ausliest, findet ihr in der API (java.lang.String).
** Implementiert ein toString, dieses soll folgenden String liefern:
+
** Implementiert ein toString, dieser soll folgenden String liefern:
  12345678
+
########  
1 ########  
+
#      #
2 #      #
+
#      #
3 #      #
+
###    #
4 ###    #
+
#      #
5 #      #
+
  ########
  6 ########
+
** Tipp: "\n" steht in einem String für einen Zeilenumbruch.
** tip: "\n" steht in einem String für Zeilenumbruch.
+
** Es soll also aus dem ''GameField''-Array wieder ein ''String'' generiert werden.
** Es soll also aus dem GameField-Array wieder ein String generiert werden.
+
** Die ''toString''-Methode von ''GameField'' soll die ''toString''-Methode der ''GameTile''s verwenden, um diese Ausgabe zu erzeugen.
** Die toString Methode von GameField soll die toString Methode von GameTile verwenden um diese Ausgabe zu erzeugen.
 
 
 
* '''TESTET euer Programm mit verschiedenen Levels'''
 
* '''TESTET euer Programm mit verschiedenen Levels'''
 
  
----
+
== Eine Welt aus Feldern ==
 
+
 +
Die Klasse ''World'' ist die Klasse, die Alles verbinden wird, Spieler, Items, Spielfeld etc.
  
== class World Teil 1 ==
+
Bei Java funktioniert es bei grafischen Anwendungen etwa so: Man hat ein Objekt, das eine Leinwand repräsentiert (Canvas), man darf auf diesem Objekt Sachen malen. In einem Spiel wird das Objekt in jedem Frame leer gemacht, danach werden alle beteiligten grafischen Objekte (Spieler, Spielfeld, Gegner) nacheinander auf diese Leinwand gemalt. Letzendlich wird die Leinwand auf dem Bildschirm ausgegeben. Das hat den Vorteil, dass jede Klasse die darauf Zugriff bekommt, irgendetwas malen kann ohne zu wissen, was bereits darauf ist oder noch darauf kommt. (Jedes Objekt kümmert sich selbst um seine grafische Entsprechung.)
 +
Das werden wir hier auch machen, allerdings mit einem String, da uns die Grundlagen für Grafisches fehlen.
 
 
* World ist die Klasse die alles verbinden wird, Spieler, Items, Spielfeld etc.
+
* Zunächst einmal gibt es nur das ''GameField'', was wir einbinden können (später den Spieler, etc.).
 +
** So muss es auch ein Variable vom Typ ''GameField'' geben.
 +
* Die ''Main'' Klasse erzeugt ab jetzt nur noch eine Instanz von ''World'' nicht mehr von ''GameField''. Denn ''World'' selbst soll sich das ''GameField'' erzeugen (im Konstruktor).
 
 
* Zunächst einmal gibt es nur das GameField, was wir einbinden können ( später der Spieler ).
+
* Definiert eine ''canvas'' Variable, die für die grafische Repräsentierung des Spielfelds stehen wird.
+
**  ''canvas'' ist dabei ein String der der linear das GameField speichert.
* Die Main Klasse erzeugt ab jetzt nur noch eine Instanz von World nicht mehr von GameField. Denn World selbst soll sich das GameField erzeugen ( im Konstruktor ).
+
BSP: Dieses Feld
 +
###
 +
# ##
 +
#@ #
 +
####
 +
Entspricht dann (für eine Breite von 4)
 +
### # ###@ #####
  
* Bei Java funktioniert es bei grafischen Anwendungen etwa so: Man hat ein Objekt, das eine Leinwand repräsentiert ( Canvas ), man darf auf diesem Objekt Sachen malen. In einem Spiel wird das Objekt in jedem Frame leer gemacht, danach werden alle beteiligten grafischen Objekte ( Spieler, Spielfeld, Gegner ) nacheinander auf diese Leinwand gemalt. Letzendlich wird die Leinwand auf dem Bildschirm ausgegeben. Das hat den Vorteil, dass jede Klasse die darauf Zugriff bekommt irgendetwas malen kann ohne zu wissen, was bereits darauf ist oder noch darauf kommt. ( Jedes Objekt kümmert sich selbst um seine grafische Entsprechung. )
+
Unter der Annahme, dass das Feld n Einheiten breit ist, kann man berechnen auf welchem Index des Strings ein Tile liegen soll. Beispiel: Ein Tile hat die Koordinaten (4,7) und das Feld ist 16 Einheiten breit, dann berechnet sich der Index aus i = 4 + 7*16, also i = x + y*w.
Das werden wir hier auch machen, allerdings mit Strings, da uns die Grundlagen für Grafisches noch fehlen.
 
 
* Definiert eine solche Canvas Variable, die für die grafische Repräsentierung des Spielfelds stehen wird.
 
** Das soll ein 2D Array aus Strings der Länge 1 sein. Ein String pro Feld, dieses enthält dann "#" oder " ".
 
  
  
----
 
  
 +
== Zeichnen der Welt ==
  
== class GameField Teil 2 ==
+
Die ''World'' sollte sich nun selbst zeichnen können. Diese Methode sollte ''draw'' heißen. Sie muss  die ''toString''-Methode des GameFields aufrufen. Diese Methode liefert einen linearisierte Variante des Feldes zurück und diese ''draw''-Methode müsste dann nur die linearisierte Variante in Abhängigkeit von der Breite der ''Welt'', 2D ausgeben.
  
* Wir brauchen nun eine draw Methode, die eine solche Leinwand (String[][]) aus World übergeben bekommt und sich selbst darauf malt. Diese Methode funktioniert nun ähnlich wie unsere toString Methode, sie gibt nur nichts zurück, sondern modifiziert direkt unser Leinwand Array und malt dort Zeichen für Zeichen die Felder rein.( Arrays sind keine primitiven Datentypen daher wird hier nicht mit einer Kopie gearbeitet )
+
Dazu ein Beispiel: Angenommen ''GameField.toString()'' liefert folgendes:
 
+
#### ## ##@####
  public void draw( String[][] canvas ){ }
+
Unter der Annahme die Welt sei 3 Einheiten breit, dann sollte die Ausgabe so aussehen:
 
+
###
* Was passiert eigentlich wenn das GameField größer ist als unser String[][] ??
+
# #
 +
  # #
 +
#@#
 +
###
  
  
 +
* ''' TESTET nun eure Grafik Ausgabe anhand verschiedener Level'''
  
----
 
  
  
== class World Teil 2 ==
+
== Schönheit der Ausgabe im GameMain ==
  
 +
Die Klasse GameMain enthält als einzige eine Instanz der Klasse World und kann somit das Spielfeld zeichnen.
 +
Sofern ihr beim Testen versucht habt, das Spielfeld mehrmals auf die Konsole auszugeben, seht ihr, dass das alte Spielfeld  noch immer sichtbar ist über dem neuen. Das sieht irgendwie blöd aus.
 +
 
 +
* Zählt nach, wie viele Zeilen eure Konsole hat.
 +
* Bevor man das Level ausgibt, kann man so viele leere Zeilen ausgeben lassen, dass das alte Level nicht mehr sichtbar ist. ''System.out.println("");'' gibt eine leere Zeile aus.
 +
* '''TESTEN ob das klappt und gut aussieht'''
  
* Nun kommt unsere wichtigste Methode public void draw(). Draw soll alle Inhalte ( bisher nur GameField )  auf unser Canvas Objekt malen, indem es die draw Methode von GameField aufruft und das Canvas Array übergibt. Der Code beschränkt sich hier also auf eine Zeile.
 
** Später fügen wir hier mehr ein.
 
   
 
* Implementiert auch eine toString Methode, diese sollte das aktuelle Canvas Array in der richtigen Form für die Konsolenausgabe liefern.
 
  
* ''' TESTET nun euere Grafik Ausgabe und euer toString().'''
 
  
 +
== Der Spieler ==
  
----
+
Der Spieler wird die Möglichkeit haben, auf dem Spielfeld rumzulaufen und je nachdem, wie weit ihr kommt, noch viel mehr. Perfekt wäre es wenn dieser Spieler auch von ''GameField'' erbt, damit ihr die Funktionalitäten eines Feldes weiter benutzen könnt (Position, etc.).
 
+
   
 
+
* Der Spieler wird durch ein "@" auf dem ''GameField'' repräsentiert.
== class GameMain Teil 2 ==
+
* Schreibt eine ''draw''-Methode, die einen String bekommt und anhand der obigen Formel zur Indexberechnung und der Position des Spielers den übergebenen String um den Spieler selber ergänzt.
  
* Die Klasse GameMain enthält als einzige eine Instanz der Klasse World und kann somit das Spielfeld zeichnen.
+
Wenn wir das ganze als Ausgabe betrachten, könnte dies der Eingabe-String sein:
 
+
###
* Sofern ihr beim Testen versucht habt das Spielfeld mehrmals auf die Konsole auszugeben, seht ihr, dass das alte Spielfeld noch immer sichtbar ist über dem neuen. Das sieht irgendwie blöd aus.
+
  # #
 
+
# #####
* Zählt nach wieviele Zeilen euere Konsole hat.
+
#    #
+
#######
* Bevor man das Level ausgibt kann man soviele leere Zeilen ausgeben lassen, dass das alte Level nicht mehr sichtbar ist. System.out.println("");  gibt eine leere Zeile aus.
 
 
 
* '''TESTEN ob das klappt und gut aussieht'''
 
  
   
+
oder linear:
----
+
  ###    # #    # ######    ########
  
  
== class Player Teil 1 ==
+
Wenn der Player (bzw. Spieler) jetzt eine Position von (5,4) hat, und die Welt eine Breite von 7 hat, so muss der Index 5+4*7=33 um den Spieler ergänzt werden.
  
* Der Spieler wird die Möglichkeit haben auf dem Spielfeld rumzulaufen und je nachdem wie weit ihr kommt noch viel mehr ...
+
Des Weiteren wollen wir auch den Spieler bewegen können, also muss er folgende Funktionalitäten besitzen:
   
+
* public void moveLeft()  
* Der Spieler besitzt eine Position ( Variable vom Typ Position ), wie immer private mit getter/setter
+
* public void moveRight()
+
* public void moveUp()
* Der Spieler wird durch ein "*"  auf dem Spielfeld repräsentiert. Implementiert eine draw Methode, die genau so wie die draw Methode aus GameField den Spieler auf unsere Leinwand malt, an die Position die inder positions Variable gepseichert ist.
+
* public void moveDown()
 
+
** Welche einfach nur die X- oder Y-Position um 1 erhöhen oder verringern.
* Player soll auch vier Methoden haben
 
** public void moveLeft()  
 
** public void moveRight()
 
** public void moveUp()
 
** public void moveDown()
 
* Welche einfach nur die X oder Y Position um 1 erhöhen oder dekrementieren
 
  
 
   
 
   
----
 
  
 +
== Der Spieler in der Welt ==
  
== class World Teil 3 ==
+
* Im Konstruktor soll nun auch eine Spielerinstanz erzeugt werden und zunächst an einer beliebigen Position gesetzt werden.
 
 
* Im Konstruktor soll nun auch eine Spielerinstanz erzeugt werden und zunächst an einer bel. Position gesetzt werden
 
 
   
 
   
* Erweitert die draw Methode so, dass nun auch der Spieler angezeigt wird.  
+
* Erweitert die draw-Methode so, dass nun auch der Spieler angezeigt wird.  
 
 
 
* '''TESTET ob das funktioniert. Was passiert eigentlich, wenn man den Spieler an der Position  -1 | -1 setzt ? '''
 
* '''TESTET ob das funktioniert. Was passiert eigentlich, wenn man den Spieler an der Position  -1 | -1 setzt ? '''
 
 
  
----
 
  
 +
== Interaktive Welt ==
  
== class World Teil 4 ==
+
Nun ist es an der Zeit eurem Spieler das Bewegen beizubringen. Dazu solltet ihr die ''World''-Klasse überarbeiten.
  
* Implementiert eine Methode public boolean onKeyPressed( String key ){ ... } Sie wird dann aufgerufen werden wenn eine Taste gedrückt wurde und soll true zurückgeben wenn der Spieler sich bewegt hat, ansonsten false.    
+
* Implementiert eine Methode ''public boolean onKeyPressed( String key ){ ... }''. Sie wird dann aufgerufen werden, wenn eine Taste gedrückt wurde und soll ''true'' zurückgeben, wenn der Spieler sich bewegt hat, ansonsten ''false''.    
 
   
 
   
* Zunächst müssen wir in onKeyPressed prüfen ob key einer der Strings "a" (links) , "s"(unten) , "d"(rechts) , "w"(oben) ist. ( equals benutzen! ) Falls ja wurde schonmal sicherlich eine gültige Taste gedrückt. Falls nein können wir false zurückliefern.
+
* Zunächst müssen wir in ''onKeyPressed'' prüfen, ob key einer der Strings "a" (links) , "s"(unten) , "d"(rechts) , "w"(oben) ist (''equals'' benutzen!). Falls ja, wurde schon mal eine gültige Taste gedrückt. Falls nein, können wir ''false'' zurückliefern.
 
 
* Wir müssen als nächstes prüfen ob der Spieler durch die Bewegung nicht in eine Mauer reinläuft, dazu müssen wir die Spielerposition vergleichen und schauen in welchem Tile er stehen würde wenn wir rechts/links/hoch/runter liefen. Sofern dieses Tile leer ist. Dürfen wir in diese Richtung laufen.
+
* Wir müssen als nächstes prüfen, ob der Spieler durch die Bewegung nicht in eine Mauer reinläuft, dazu müssen wir die Spielerposition vergleichen und schauen, in welchem Tile er stehen würde, wenn wir rechts/links/hoch/runter liefen. Sofern dieses Tile leer ist, dürfen wir in diese Richtung laufen. Denkt dabei an die Getter der ''GameField''-Klasse
 
   
 
   
* Nun wo wir das alles geprüft haben bewegen wir den Spieler mit einer der 4 Bewegungsfunktionen und geben true zurück
+
* Nun wo wir das alles geprüft haben, bewegen wir den Spieler mit einer der vier Bewegungsfunktionen und geben "true" zurück.
 
   
 
   
 
* '''TESTET unbedingt manuell diese Methode, sie muss korrekt funktionieren! '''
 
* '''TESTET unbedingt manuell diese Methode, sie muss korrekt funktionieren! '''
  
  
----
 
  
 +
== Final Touch ==
  
== class GameMain Teil 2 ==
+
Jetzt müsst ihr nur noch eine passende Main-Schleife schreiben, und ihr habt eure erste Sandbox zum weiteren Experimentieren fertig.
  
* Nun kommt alles zusammen.
+
* Für die Eingabe von der Konsole benutzt ihr am Besten die ''Terminal''-Klasse.
+
 
* Fügt am Ende eurer main Methode folgenden Code für das auslesen aus der Tastatur ein:
+
* Es muss geprüft werden ob es sich um ein gültiges Zeichen handelt und ob es eines der Zeichen ''a,s,w,d'' ist.
while( true ){
 
InputStreamReader reader = new InputStreamReader(System.in);
 
BufferedReader lineReader = new BufferedReader(reader);
 
System.out.print(">");
 
 
char[] line = new char[1];
 
try {
 
line[0] = (char)lineReader.read();
 
} catch (IOException e) {
 
e.printStackTrace();
 
}
 
String s = new String( line );
 
if( s.equals("x") ){
 
return;
 
}else if(  s.equals("a") || s.equals("s")
 
||s.equals("d") || s.equals("w") ){
 
 
/*TODO: MODIFIZIERT DEN NAMEN DER WORLD INSTANZ HIER! */
 
if( world.onKeyPressed( s ) ){
 
/*TODO: HIER MUSS NEUGEMALT WERDEN, DER SPIELER HAT SICH BEWEGT*/
 
}
 
 
 
}
 
}
 
 
* Ihr müsst "world" durch den Namen, den ihr euerer World Instanz gegeben habt ersetzen.
 
  
* In die if-Anweisung am Ende fügt ihr euren Code für das neuzeichnen des Levels. Also viellciht soetwas wie :   System.out.println( world ).
+
* Das eingegebene Zeichen soll dann an die ''World''-Klasse übergeben werden und diese soll den Spieler bewegen, falls dies möglich ist!
 
     
 
     
 +
Das ist das Basisgerüst für ein Spiel, ihr könnt nun weitere Sachen einfügen wie z.B. Items, die man einsammeln kann. Wie könnte man das machen? Was hat Liv Tyler in diesem Spiel für eine Aufgabe?
  
----
 
   
 
  
* Nun kann der Spieler im Level rumlaufen und es gibt eine Art interaktion...Das ist das Basisgerüst für ein Spiel, ihr könnt nun weitere Sachen einfügen wie zB items die man einsammeln kann. Wie könnte man das machen ?? Was hat Liv Tyler in diesem Spiel für eine Aufgabe?
+
 
 +
 
 +
== Kommentare ==
 +
Wenn du Anmerkungen zur Aufgabe hast oder Lob und Kritik loswerden möchtest, ist hier die richtige Stelle dafür. Klicke einfach ganz rechts auf "bearbeiten" und schreibe deinen Kommentar direkt ins Wiki. Keine Scheu, es geht nichts kaputt ;)
 +
 
 +
Ich wollte nur mal nachfragen, ob es auch für die letzten Aufgaben, wie z.B. dieser hier, ASCII-DOOM, Musterlösungen gibt? Manchmal wäre es gut, wenn man sich verirrt hat, wieder einen roten Faden zu finden;-) Vielen Dank und liebe Grüße Franzi!
 +
 
 +
==Anonymer Kommentar==
 +
#!/bin/bash
 +
pwd
 +
cd Desktop/javakurs10/DOOM/
 +
TERM_ATTRS="-isig -icanon -iexten -echo -echoe -echok -echonl -noflsh -tostop -echoprt -echoctl -echoke" 
 +
 +
/bin/stty $TERM_ATTRS
 +
java -cp . GameMain
 +
 +
[[Kategorie:Java]]
 +
[[Kategorie:Java_Aufgaben]]

Aktuelle Version vom 11. März 2014, 13:57 Uhr

ASCII-DOOM

In Ascii-Doom geht es darum, ein kleines rogue-like Spiel zu implementieren.

D.h. es wird möglich sein, mittels ASCII-Art, mit einer Spielfigur auf dem Spielfeld zu laufen, später könnt ihr auch Items, Waffen und Liv Tyler einbauen!

Wir werden Schritt für Schritt das Spiel implementieren. Wenn ihr verloren geht, keine Angst, wir können euch helfen ...


Die Klasse GameMain - Der Anfang

Die Klasse GameMain ist die Klasse, die die main Methode enthalten wird und somit ausführbar ist. Sie wird die Schnittstelle zur Außenwelt sein und die Ausgabe auf die Konsole tätigen.

  • Erstellt zunächst nur diese Klasse. Sobald ihr Klassen aus den späteren Aufgaben implementiert habt, könnt ihr in der main-Methode ihre Funktionalität testen.


Positionen für alle Objekte

Die Klasse Position repräsentiert eine Position auf einem 2D-Spielfeld. Also karthesische 2 dimensionale Koordinaten, wie ihr sie von einem kariertem Papier kennt. Des Weiteren enthält Sie private die folgende Attribute: x und y, gekapselt mit Gettern und Settern.

  • Schreibt diese Getter und Setter
  • Implementiert eine equals-Methode, die 2 Objekte vom Typ Position vergleicht.
  • Testet nun eure equals-Methode in der main. (Tested sie solange bis sie funktioniert)


Repräsentation eines Gebietes

GameTile wird unsere Klasse sein, die ein Tile, also ein Feld, im späteren Spiel repräsentiert. Alle weiteren Felder werden von ihr erben. So wird es später ein leeres Feld geben, eine Mauer, und Alles was euch noch einfällt.

  • Es enthält außerdem eine private Variable vom Typ Position, welche die Position des GameTiles auf dem Feld speichert.
  • Implementiert eine toString-Methode, die jedem GameTile einen String mit Länge 1 zuordnet.
    • Diese Methode wird von allen erbenden Klassen überschrieben werden
  • Schreibt nun einige Klassen die von GameTile erben
    • Als Namen bietet sich WallTile und EmptyTile
    • Die toString()-Methoden sollten dann sinvolle Zeichen zurückgeben, wie " " oder "#".
  • Testet auch hier euere toString-Methoden, in dem ihr euch einzelne Tiles erzeugt und diese ausgeben last


Das GameField

Euer GameField ist eine zentrale Klasse in diesem Spiel, sie repräsentiert das Spielfeld als Ganzes und besteht aus mehreren GameTiles.

  • Enthält eine private Variable für das Level (2D-Array aus GameTiles).
  • Über den Konstruktor wird ein String dieser Form übergeben(ebenso wie die Breite des Spielfeldes):
       String s = "########" +
		  "#      #" +
		  "#      #" +
		  "###    #" +
		  "#      #" +
		  "########";
    • Es soll mit einer Schleife der String Zeichen für Zeichen ausgelesen werden und aus jedem Zeichen ein neues GameTile (Wand oder leer) erzeugt werden. Diese GameTiles werden dann in dem Array gespeichert!
    • Wie man Strings Zeichen für Zeichen ausliest, findet ihr in der API (java.lang.String).
    • Implementiert ein toString, dieser soll folgenden String liefern:
		 ######## 
		 #      #
		 #      #
		 ###    #
		 #      #
 		 ########
    • Tipp: "\n" steht in einem String für einen Zeilenumbruch.
    • Es soll also aus dem GameField-Array wieder ein String generiert werden.
    • Die toString-Methode von GameField soll die toString-Methode der GameTiles verwenden, um diese Ausgabe zu erzeugen.
  • TESTET euer Programm mit verschiedenen Levels

Eine Welt aus Feldern

Die Klasse World ist die Klasse, die Alles verbinden wird, Spieler, Items, Spielfeld etc.

Bei Java funktioniert es bei grafischen Anwendungen etwa so: Man hat ein Objekt, das eine Leinwand repräsentiert (Canvas), man darf auf diesem Objekt Sachen malen. In einem Spiel wird das Objekt in jedem Frame leer gemacht, danach werden alle beteiligten grafischen Objekte (Spieler, Spielfeld, Gegner) nacheinander auf diese Leinwand gemalt. Letzendlich wird die Leinwand auf dem Bildschirm ausgegeben. Das hat den Vorteil, dass jede Klasse die darauf Zugriff bekommt, irgendetwas malen kann ohne zu wissen, was bereits darauf ist oder noch darauf kommt. (Jedes Objekt kümmert sich selbst um seine grafische Entsprechung.) Das werden wir hier auch machen, allerdings mit einem String, da uns die Grundlagen für Grafisches fehlen.

  • Zunächst einmal gibt es nur das GameField, was wir einbinden können (später den Spieler, etc.).
    • So muss es auch ein Variable vom Typ GameField geben.
  • Die Main Klasse erzeugt ab jetzt nur noch eine Instanz von World nicht mehr von GameField. Denn World selbst soll sich das GameField erzeugen (im Konstruktor).
  • Definiert eine canvas Variable, die für die grafische Repräsentierung des Spielfelds stehen wird.
    • canvas ist dabei ein String der der linear das GameField speichert.

BSP: Dieses Feld

###
# ##
#@ #
####

Entspricht dann (für eine Breite von 4)

### # ###@ #####

Unter der Annahme, dass das Feld n Einheiten breit ist, kann man berechnen auf welchem Index des Strings ein Tile liegen soll. Beispiel: Ein Tile hat die Koordinaten (4,7) und das Feld ist 16 Einheiten breit, dann berechnet sich der Index aus i = 4 + 7*16, also i = x + y*w.


Zeichnen der Welt

Die World sollte sich nun selbst zeichnen können. Diese Methode sollte draw heißen. Sie muss die toString-Methode des GameFields aufrufen. Diese Methode liefert einen linearisierte Variante des Feldes zurück und diese draw-Methode müsste dann nur die linearisierte Variante in Abhängigkeit von der Breite der Welt, 2D ausgeben.

Dazu ein Beispiel: Angenommen GameField.toString() liefert folgendes:

#### ## ##@####

Unter der Annahme die Welt sei 3 Einheiten breit, dann sollte die Ausgabe so aussehen:

###
# #
# #
#@#
###


  • TESTET nun eure Grafik Ausgabe anhand verschiedener Level


Schönheit der Ausgabe im GameMain

Die Klasse GameMain enthält als einzige eine Instanz der Klasse World und kann somit das Spielfeld zeichnen. Sofern ihr beim Testen versucht habt, das Spielfeld mehrmals auf die Konsole auszugeben, seht ihr, dass das alte Spielfeld noch immer sichtbar ist über dem neuen. Das sieht irgendwie blöd aus.

  • Zählt nach, wie viele Zeilen eure Konsole hat.
  • Bevor man das Level ausgibt, kann man so viele leere Zeilen ausgeben lassen, dass das alte Level nicht mehr sichtbar ist. System.out.println(""); gibt eine leere Zeile aus.
  • TESTEN ob das klappt und gut aussieht


Der Spieler

Der Spieler wird die Möglichkeit haben, auf dem Spielfeld rumzulaufen und je nachdem, wie weit ihr kommt, noch viel mehr. Perfekt wäre es wenn dieser Spieler auch von GameField erbt, damit ihr die Funktionalitäten eines Feldes weiter benutzen könnt (Position, etc.).

  • Der Spieler wird durch ein "@" auf dem GameField repräsentiert.
  • Schreibt eine draw-Methode, die einen String bekommt und anhand der obigen Formel zur Indexberechnung und der Position des Spielers den übergebenen String um den Spieler selber ergänzt.

Wenn wir das ganze als Ausgabe betrachten, könnte dies der Eingabe-String sein:

###
# #
# #####
#     #
#######

oder linear:

###    # #    # ######     ########


Wenn der Player (bzw. Spieler) jetzt eine Position von (5,4) hat, und die Welt eine Breite von 7 hat, so muss der Index 5+4*7=33 um den Spieler ergänzt werden.

Des Weiteren wollen wir auch den Spieler bewegen können, also muss er folgende Funktionalitäten besitzen:

  • public void moveLeft()
  • public void moveRight()
  • public void moveUp()
  • public void moveDown()
    • Welche einfach nur die X- oder Y-Position um 1 erhöhen oder verringern.


Der Spieler in der Welt

  • Im Konstruktor soll nun auch eine Spielerinstanz erzeugt werden und zunächst an einer beliebigen Position gesetzt werden.
  • Erweitert die draw-Methode so, dass nun auch der Spieler angezeigt wird.
  • TESTET ob das funktioniert. Was passiert eigentlich, wenn man den Spieler an der Position -1 | -1 setzt ?


Interaktive Welt

Nun ist es an der Zeit eurem Spieler das Bewegen beizubringen. Dazu solltet ihr die World-Klasse überarbeiten.

  • Implementiert eine Methode public boolean onKeyPressed( String key ){ ... }. Sie wird dann aufgerufen werden, wenn eine Taste gedrückt wurde und soll true zurückgeben, wenn der Spieler sich bewegt hat, ansonsten false.
  • Zunächst müssen wir in onKeyPressed prüfen, ob key einer der Strings "a" (links) , "s"(unten) , "d"(rechts) , "w"(oben) ist (equals benutzen!). Falls ja, wurde schon mal eine gültige Taste gedrückt. Falls nein, können wir false zurückliefern.
  • Wir müssen als nächstes prüfen, ob der Spieler durch die Bewegung nicht in eine Mauer reinläuft, dazu müssen wir die Spielerposition vergleichen und schauen, in welchem Tile er stehen würde, wenn wir rechts/links/hoch/runter liefen. Sofern dieses Tile leer ist, dürfen wir in diese Richtung laufen. Denkt dabei an die Getter der GameField-Klasse
  • Nun wo wir das alles geprüft haben, bewegen wir den Spieler mit einer der vier Bewegungsfunktionen und geben "true" zurück.
  • TESTET unbedingt manuell diese Methode, sie muss korrekt funktionieren!


Final Touch

Jetzt müsst ihr nur noch eine passende Main-Schleife schreiben, und ihr habt eure erste Sandbox zum weiteren Experimentieren fertig.

  • Für die Eingabe von der Konsole benutzt ihr am Besten die Terminal-Klasse.
  • Es muss geprüft werden ob es sich um ein gültiges Zeichen handelt und ob es eines der Zeichen a,s,w,d ist.
  • Das eingegebene Zeichen soll dann an die World-Klasse übergeben werden und diese soll den Spieler bewegen, falls dies möglich ist!

Das ist das Basisgerüst für ein Spiel, ihr könnt nun weitere Sachen einfügen wie z.B. Items, die man einsammeln kann. Wie könnte man das machen? Was hat Liv Tyler in diesem Spiel für eine Aufgabe?



Kommentare

Wenn du Anmerkungen zur Aufgabe hast oder Lob und Kritik loswerden möchtest, ist hier die richtige Stelle dafür. Klicke einfach ganz rechts auf "bearbeiten" und schreibe deinen Kommentar direkt ins Wiki. Keine Scheu, es geht nichts kaputt ;)

Ich wollte nur mal nachfragen, ob es auch für die letzten Aufgaben, wie z.B. dieser hier, ASCII-DOOM, Musterlösungen gibt? Manchmal wäre es gut, wenn man sich verirrt hat, wieder einen roten Faden zu finden;-) Vielen Dank und liebe Grüße Franzi!

Anonymer Kommentar

#!/bin/bash
pwd
cd Desktop/javakurs10/DOOM/
TERM_ATTRS="-isig -icanon -iexten -echo -echoe -echok -echonl -noflsh -tostop -echoprt -echoctl -echoke"  

/bin/stty $TERM_ATTRS 
java -cp . GameMain