Dungeon Treasure Game – Aufgabenblatt
Start Template: Exercise - Browser Treasure Game JS
Die Karte besteht aus einem zweidimensionalen Array. Jedes Feld hat einen dieser Werte:
| Wert | Bedeutung |
|---|---|
"." | Freier Boden |
"S" | Startposition |
"#" | Wand |
"T" | Schatz |
"X" | Falle |
"E" | Ausgang |
Aufgabe 0: Code Analyse
Mache dich mit dem Code im Start Template vertraut und versuche alles zu verstehen.
Du wirst merken, dass schon ein Event-Listener auf das Drücken von Pfeiltasten reagiert, aber noch nichts passiert.
Versuche die Funktion movePlayer anzupassen, sodass die Position aktualisiert wird:
function movePlayer(rowChange, colChange) {
player.row += rowChange;
player.col += colChange;
}
Teste dein Spiel indem du die Datei neu abspeicherst und im Browser neu lädst.
Was funktioniert schon?
Was funktioniert noch nicht? \
Aufgabe 1: Spielinformationen anzeigen
Ziel: Die Info-Box soll aktuelle Lebenspunkte, Punkte und verbleibende Schätze anzeigen. Außerdem soll der Spieler sich auf dem Bildschirm bewegen.
Was ist zu tun?
- Implementiere
countTreasures()– zählt alle"T"-Felder in der Karte und gibt die Anzahl zurück. - Implementiere
updateInfo()– aktualisiert die HTML-Elemente#health,#scoreund#treasuresLeft. - Implementiere
showMessage(text)– setzt den Text des#message-Elements. - Ergänze
movePlayer(): Ruf am EndeshowMessage("You moved."),updateInfo()undrenderMap()auf. - Ergänze
restartGame(): Setzeplayer.row = 0undplayer.col = 0zurück und rufshowMessage(...)mit einem Starttext auf.
Hinweise
- Mit
document.getElementById("health")bekommst du das HTML-Element mit der IDhealth. - Mit
.textContent = wertsetzt du den sichtbaren Text eines Elements. countTreasures()braucht zwei verschachtelte Schleifen: eine über die Zeilen (for (let row of map)), eine über die Felder jeder Zeile.- Die Karte muss nach jeder Bewegung neu gezeichnet werden, damit der Spieler sich bewegt – das macht
renderMap().
Aufgabe 2: Kartengrenzen überprüfen
Ziel: Der Spieler soll nicht über den Rand der Karte hinaus laufen können.
Was ist zu tun?
- Schreibe eine Hilfsfunktion
isInsideMap(row, col), dietruezurückgibt, wenn die Koordinaten innerhalb der Karte liegen. - Ändere
movePlayer(): Berechne zuerstnewRowundnewCol, prüfe dann mitisInsideMap, ob die neue Position gültig ist – erst danach aktualisiereplayer.rowundplayer.col.
Hinweise
- Eine Position ist gültig, wenn
row >= 0 && row < map.length && col >= 0 && col < map[0].length. - Wenn die Position ungültig ist:
showMessage(...)mit einer passenden Meldung aufrufen und die Funktion mitreturnabbrechen. player.row = newRowundplayer.col = newColkommen erst ganz am Schluss, wenn alle Prüfungen bestanden sind.
Aufgabe 3: Wände überprüfen
Ziel: Der Spieler soll nicht durch Wände laufen können.
Was ist zu tun?
Ergänze movePlayer(): Lese nach der Grenzenprüfung das Zielfeld aus der Karte aus und prüfe, ob es eine Wand ist.
Hinweise
- Mit
map[newRow][newCol]bekommst du den Inhalt des Zielfelds. - Wände haben den Wert
"#". - Wenn das Zielfeld eine Wand ist:
showMessage(...)aufrufen und mitreturnabbrechen.
Aufgabe 4: Reichhaltigere Karte gestalten
Ziel: Die Funktion generateMaze() soll eine interessantere, handgefertigte Karte zurückgeben – mit mehreren Schätzen, Fallen und Wänden.
Was ist zu tun?
Erweitere den Inhalt von generateMaze(): Füge mehrere Wände, Schätze und Fallen hinzu. Die Karte muss weiterhin lösbar sein.
Hinweise
- Zeilen und Spalten beginnen bei Index 0. Die Karte ist 16×16 (Indizes 0–15).
- Einzelne Felder setzt du mit:
map[zeile][spalte] = "#"(oder"T","X"usw.) - Eine ganze Reihe Wände setzt du mit einer Schleife:
for (let col = 2; col <= 10; col++) map[4][col] = "#"; - Denk dir Durchgänge (Lücken in Wandreihen), damit du noch zu allen Schätzen und zum Ausgang kommst.
- Spiele die Karte durch, bevor du mit der nächsten Aufgabe weitermachst.
Aufgabe 5: Feldaktionen implementieren
Ziel: Wenn der Spieler ein bestimmtes Feld betritt, soll etwas passieren.
Was ist zu tun?
- Schreibe eine neue Funktion
handleTile(tile, row, col)mit folgendem Verhalten:"T"→ Punkte erhöhen (player.score += 10), Feld aus der Karte entfernen, Meldung anzeigen."X"→ Lebenspunkte verringern (player.health -= 1), Feld aus der Karte entfernen, Meldung anzeigen."E"→ Prüfen, ob alle Schätze eingesammelt wurden; passende Meldung anzeigen.- Alle anderen Felder →
"You moved."anzeigen.
- Rufe
handleTile(targetTile, newRow, newCol)inmovePlayer()auf (nach der Bewegung, anstelle des bisherigenshowMessage("You moved.")).
Hinweise
- Ein Feld entfernst du, indem du es durch freien Boden ersetzt:
map[row][col] = ".". - Ob alle Schätze weg sind, prüfst du mit
countTreasures() === 0.
Aufgabe 6: Spielende erkennen
Ziel: Das Spiel soll enden, wenn der Spieler alle Herzen verliert oder den Ausgang mit allen Schätzen erreicht.
Was ist zu tun?
- Schreibe eine neue Funktion
checkGameState():- Wenn
player.health <= 0:gameOver = truesetzen und eine Verlieren-Meldung anzeigen. - Wenn der Spieler auf
"E"steht und keine Schätze mehr übrig sind:gameOver = truesetzen und eine Gewinnen-Meldung anzeigen.
- Wenn
- Ergänze
movePlayer():- Ganz am Anfang: Wenn
gameOvergesetzt ist, sofort mitreturnabbrechen. - Ganz am Ende:
checkGameState()aufrufen.
- Ganz am Anfang: Wenn
Hinweise
- Das aktuelle Feld des Spielers:
map[player.row][player.col]. gameOverist eine globale Variable – wenn du sie auftruesetzt, verhindert die Prüfung am Anfang vonmovePlayer()weitere Eingaben.
Aufgabe 7: Einfache zufällige Karte
Ziel: Bei jedem Neustart soll eine andere Karte erzeugt werden – zunächst ohne Wände.
Was ist zu tun?
- Schreibe eine Hilfsfunktion
shuffleArray(array), die ein Array zufällig durchmischt. - Ersetze den Inhalt von
generateMaze()durch eine neue Version:- Erstelle eine Liste aller freien Felder (außer der Startposition
[0][0]). - Mische die Liste mit
shuffleArray. - Platziere Ausgang, Schätze und Fallen, indem du jeweils ein Element vom Ende der Liste nimmst.
- Erstelle eine Liste aller freien Felder (außer der Startposition
Hinweise
- Fisher-Yates-Mischen: Gehe mit einem Index
ivon hinten nach vorne durch das Array. Tauschearray[i]mit einem zufälligen Element an einem Index zwischen 0 undi. Math.floor(Math.random() * (i + 1))ergibt eine zufällige Ganzzahl von 0 bis einschließlichi.array.pop()entfernt das letzte Element und gibt es zurück – praktisch zum zufälligen Entnehmen nach dem Mischen.- Problem, das du dabei entdeckst: Schätze und der Ausgang können hinter zufällig platzierten Wänden (spätere Aufgabe) eingeschlossen sein. Das lösen wir in Aufgabe 8.
Aufgabe 8: Zufällige Wände mit Erreichbarkeitsprüfung
Ziel: Wände sollen zufällig hinzugefügt werden, aber nur dann, wenn danach noch alle Felder erreichbar sind.
Was ist zu tun?
- Schreibe
getReachableSet(map, startRow, startCol): Findet alle von[startRow][startCol]aus erreichbaren Felder per Breitensuche (BFS). Gibt einSetmit Koordinaten-Strings (z.B."3,7") zurück. - Schreibe
allWalkableTilesReachable(map): Gibttruezurück, wenn jedes Nicht-Wand-Feld imSetvongetReachableSet(map, 0, 0)enthalten ist. - Ergänze
generateMaze(): Mische die restlichen freien Felder und versuche, jedes als Wand zu setzen. Behalte die Wand nur, wennallWalkableTilesReachable(map)nochtruezurückgibt.
Hinweise
- BFS-Grundprinzip: Starte mit einer Warteschlange, die nur den Startpunkt enthält. Nimm immer das erste Element heraus, prüfe alle 4 Nachbarn – falls begehbar und noch nicht besucht, füge sie zur Warteschlange hinzu.
- Koordinaten als String:
`${row},${col}`(Template-Literal). new Set()erstellt eine leere Menge,.add(key)fügt einen Wert hinzu,.has(key)prüft ob er enthalten ist.- Wenn eine Wand die Erreichbarkeit zerstört:
map[row][col] = "."rückgängig machen.
Aufgabe 9 (Bonus): Keine 2×2-Wandblöcke
Ziel: Klobige 2×2-Wandblöcke sollen vermieden werden, damit die Karte besser und spannender aussieht.
Was ist zu tun?
- Schreibe
createsWallCluster(map, row, col): Gibttruezurück, wenn durch Setzen einer Wand an[row][col]ein vollständiges 2×2-Wandquadrat entsteht. - Füge in
generateMaze()beim Wand-Platzieren eine zusätzliche Bedingung hinzu: Wand nur behalten, wenncreatesWallClusterfalsezurückgibt.
Hinweise
- Es gibt 4 mögliche 2×2-Quadrate, in denen
[row][col]vorkommt. Definiere alle vier als Listen von je 4 Zell-Koordinaten. - Für jedes Quadrat: Prüfe zuerst, ob alle 4 Felder innerhalb der Karte liegen. Zähle dann die Wände. Wenn alle 4 Wände sind, gibt es ein Cluster.
- Da
map[row][col]beim Aufruf bereits auf"#"gesetzt ist, zählt es beim Überprüfen mit.
Aufgabe 10 (Bonus): Lösbarkeit trotz Fallen prüfen
Ziel: Die Karte soll immer lösbar sein – auch wenn man bedenkt, dass Fallen Lebenspunkte kosten.
Was ist zu tun?
- Schreibe
getPermutations(array): Gibt alle möglichen Reihenfolgen eines Arrays zurück (rekursiv). - Schreibe
minTrapCostBetween(map, start, end): Findet den Weg zwischen zwei Punkten, der am wenigsten Fallen enthält (Dijkstra-Algorithmus). Gibt die minimale Fallenzahl zurück. - Schreibe
canSolveWithHealth(map, health): Probiert alle Reihenfolgen, alle Schätze einzusammeln und dann den Ausgang zu erreichen. Gibttruezurück, wenn mindestens eine Reihenfolge existiert, bei der die gesamten Fallenkosten kleiner alshealthsind. - Ergänze die Wand-Platzierung in
generateMaze(): Wand nur behalten, wenncanSolveWithHealth(map, 3)nochtruezurückgibt.
Hinweise
- Dijkstra ist wie BFS, aber mit Priorität: Immer das Element mit den geringsten bisherigen Kosten zuerst bearbeiten. Normale Felder kosten 0, Fallen kosten 1.
Infinityals Startwert für Kosten – ein Feld wurde noch nicht erreicht.getPermutationsgibt für ein leeres Array[[]]zurück (eine Permutation: die leere).- Die Reihenfolge: Start → Schatz₁ → Schatz₂ → … → Ausgang. Teste alle Reihenfolgen der Schätze.