5.2 Debugger nutzen in einem Labyrinth#

Debugger sind leistungsstarke Werkzeuge in der Programmierung, die es ermöglichen, Fehler zu finden, zu verstehen und zu beheben. Mit einem Debugger können so Entwickler Code schrittweise ausführen, einen sich ändernden Wert von Variablen überwachen und den Programmfluss analysieren. Mit einem Debugger können Entwickler Fehler wie logische Fehler, unerwartetes Verhalten oder Speicherprobleme effizienter identifizieren und diagnostizieren. Sie können den Programmablauf genau analysieren, um zu verstehen, warum bestimmte Probleme auftreten, und effektivere Lösungen entwickeln.

Ziel des Tasks#

Einführen in die Arbeit mit einem Debugger und dabei die unterliegende Datenstruktur aufdecken.

Aufgabe 2: Debuggen eines Labyrinths#

In der nächsten Aufgabe soll ein neues Labyrinth erkundet werden. Dabei kannst du hier ein individuelles Labyrinth erzeugen lassen, was automatisch durch einen Startcode aus einem verwendeten Codewort (zum Beispiel deinem Namen) generiert wird. Die Aufgabe ist es, einen Weg durch das Labyrinth zu finden, auf dem du alle drei Gegenstände einsammeln kannst, damit du entkommen kannst.

Öffne dazu die Datei main.cpp. Als Erklärung zum Ablauf:

  1. kYourName dient als Ausgangswort, um ein Labyrinth zu erzeugen: In der ersten Hälfte von main wird anhand dieser Konstante kYourName ein personalisiertes Labyrinth erstellt und ein Zeiger auf eine Startzelle in diesem Labyrinth zurückgegeben.

  2. Dann wird geprüft, ob die Konstante kPathOutOfNormalMaze eine Sequenz ist, mit der man aus diesem Labyrinth entkommen kann. Hier musst du später eine Zeichenkette einsetzen, die einen Lösungsweg darstellt.

Um diesen Weg aus dem Labyrinth zu finden, benutze den Debugger: Setze einen Haltepunkt an der angegebenen Zeile in main. Führe das Programm in deiner IDE mit eingeschaltetem Debugger aus. Dabei wird ein Fenster mit den lokalen Variablen angezeigt, zusammen mit dem Inhalt von startLocation, der MazeCell, in der gestartet wurde. Der Inhalt des Feldes whatsHere von startLocation (Item::NOTHING) und die vier Zeiger werden dazu angezeigt. Die Zeiger in den Richtungen, in die sich nicht bewegt werden kann, sind alle gleich nullptr (im Debugger-Fenster als 0x0 angezeigt). Die Zeiger, die die Richtungen angeben, in die man gehen kann, haben alle Dropdown-Pfeile neben sich. Durch klicken auf einen dieser Pfeile, werden die entsprechenden MazeCells in der jeweiligen Richtung angezeigt. So kann das Maze erkundet werden durch einem verfolgen der Speicherstruktur. Fange an die Struktur des Labyrinths so zu verstehen und zeichne sie auf einem Papier auf.

Wenn alle Objekte im Labyrinth gefunden wurden, kann ein Weg durch das Labyrinth konstruiert werden als Sequenz von Richtungsanweisungen. Diese soll in der Konstante kPathOutOfNormalMaze gespeichert werden – und anschliessend mit der Funktion isPathToFreedom getestet werden.

Grundlegende Struktur#

#include "Labyrinth.h"
#include <iostream>

bool isPathToFreedom(MazeCell* start, const std::string& moves) {
    MazeCell* currentCell = start;
    bool wand = false;
    bool potion = false;
    bool spellbook = false;
    for (const char& direction : moves) {
        if (direction == 'N' && currentCell->north != nullptr) {
            currentCell = currentCell->north;
        } else if (direction == 'S' && currentCell->south != nullptr) {
            currentCell = currentCell->south;
        } else if (direction == 'E' && currentCell->east != nullptr) {
            currentCell = currentCell->east;
        } else if (direction == 'W' && currentCell->west != nullptr) {
            currentCell = currentCell->west;
        } else {
            std::cout << "Not possible!" << std::endl;
            return false;
        }

        //std::cout << direction << std::endl;
        switch (currentCell->whatsHere) {
            case Item::POTION:
                std::cout << "Found POTION" << std::endl;
                potion = true;
                break;
            case Item::SPELLBOOK:
                std::cout << "Found SPELLBOOK" << std::endl;
                spellbook = true;
                break;
            case Item::WAND:
                std::cout << "Found WAND" << std::endl;
                wand = true;
                break;
            default:
                // Do nothing for other cases
                break;
        }
    }
    if (potion && spellbook && wand) {
        return true;
    } else {
        return false;
    }
}
#include "MazeGenerator.h"
#include "Labyrinth.h"
#include <iostream>
#include <string>

/* Change this constant to contain your name.
 *
 * WARNING: Once you've set set this constant and started exploring your maze,
 * do NOT edit the value of kYourName. Changing kYourName will change which
 * maze you get back, which might invalidate all your hard work!
 */
const std::string kYourName = "TODO: Replace this string with your name.";

/* Change these constants to contain the paths out of your mazes. */
const std::string kPathOutOfNormalMaze = "SESSWENNENSESS"; //TODO: Replace this string with your path out of the normal maze.";
const std::string kPathOutOfTwistyMaze = "ESWEESWENE"; //TODO: Replace this string with your path out of the twisty maze.";

int main(int argc, char* argv[]) {
    /* Generate the maze.
     *
     * Note: Don't set a breakpoint on this line. Otherwise, you'll see startLocation before
     * it's been initialized.
     */
    MazeCell* startLocation = mazeFor(kYourName);
    
    /* Set a breakpoint here to explore your maze! */
    if (isPathToFreedom(startLocation, kPathOutOfNormalMaze)) {
        std::cout << "Congratulations! You've found a way out of your labyrinth." << std::endl;
    } else {
        std::cout << "Sorry, but you're still stuck in your labyrinth." << std::endl;
    }
    
    
    /* Generate the twisty maze.
     *
     * Note: Don't set a breakpoint on this line. Otherwise, you'll see twistyStartLocation before
     * it's been initialized.
     */
    MazeCell* twistyStartLocation = twistyMazeFor(kYourName);
    
    /* Set a breakpoint here to explore your twisty maze! */
    
    if (isPathToFreedom(twistyStartLocation, kPathOutOfTwistyMaze)) {
        std::cout << "Congratulations! You've found a way out of your twisty labyrinth." << std::endl;
    } else {
        std::cout << "Sorry, but you're still stuck in your twisty labyrinth." << std::endl;
    }
    
    return 0;
}

Angegeben ist eine einfache Lösung der vorherigen Aufgabe und die Main.cpp, die im Debugger durchlaufen werden soll.

Anweisungen

  1. Ändern der Konstante kYourName oben in Main.cpp mit einer Zeichenkette wie z.B. dem eigenen Namen.

  2. Setzen eines Haltepunkts an der ersten angegebenen Zeile in main.cpp und ausführen des Programms im Debug-Modus.

  3. Zeichnung des Labyrinths erstellen und

  4. Pfad finden, der alle drei Gegenstände enthält. Diese Sequenz von Richtungsanweisungen in die Konstante kPathOutOfNormalMaze in main.cpp eintragen.

  5. Testen des Pfads (mit ausgeschaltetem Debugger) über aufrufen der Funktion isPathToFreedom.

Ergebnis, Fragen#

Diese Aufgabe führt ein in die Nutzung eines Debugger, um verknüpfte Strukturen zu analysieren und so zum Beispiel die vollständige Form einer verknüpften Struktur zu rekonstruieren.

Zusammenfassend: Ein Debugger ermöglicht so Entwicklern, ihre C++-Programme zu untersuchen und zu analysieren, um Fehler zu finden und zu beheben. Es ist ein unverzichtbares Werkzeug, das die Entwicklung und Wartung von C++-Code erleichtert und die Effizienz und Zuverlässigkeit der Software verbessert.

Referenzen#

Dies Task ist abgewandelt von einem Task des Stanford-Kurses 106B/X zur Programmierung in C++.