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

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