Universität Ulm, Fakultät für Mathematik und Wirtschaftswissenschaften, SAI, WS 1998/99, Allgemeine Informatik I

Lösung zu Blatt 11 --- Allgemeine Informatik I (WS 1998/99)

13. No way out?

(*
   Allgemeine Informatik I / Programmieren I    WS 1998/1999
   Musterloesung fuer das Blatt 11, Aufgabe 13
   Andreas Borchert, 21. Januar 1998
*)
MODULE PathFinder;

   FROM Arguments IMPORT InitArgs, GetFlag, GetArg, AllArgs, Usage;
   FROM ASCII IMPORT nl;
   FROM FtdIO IMPORT FwriteString, FwriteLn;
   FROM InOut IMPORT WriteString, Write, WriteLn, ReadString;
   FROM StdIO IMPORT FILE, MODE, Fopen, Fgetc, stdin, stderr;
   FROM SysExit IMPORT Exit;
   FROM SysPerror IMPORT Perror;

   CONST
      maxsize = 128;  (* maximale Seitenlaenge eines Labyrinths *)
      minsize = 3;    (* Minimalgroesse, alles darunter ist nicht sinnvoll *)
      space = " ";    (* alles andere wird als Wand betrachtet *)
      walk = "*";     (* Markierung des durchlaufenen Weges *)

   TYPE
      MazeSize = INTEGER [0..maxsize];
      MazeIndex = INTEGER [0..maxsize-1];
      Maze = ARRAY MazeIndex, MazeIndex OF CHAR;
	 (* Labyrinth in der graphischen Notierung auf Basis von
	    ASCII-Zeichen, genau so wie es auch in der Eingabe
	    erwartet wird. Hinzu kommen nur Wegemarkierungen.
	 *)
   VAR
      maze: Maze;
      width, height: MazeSize; (* Weite und Hoehe des Labyrinths *)
      entry, exit: MazeIndex; (* Zeilenindizes von Eingang und Ausgang *)

      (* Kommandozeilenargumente *)
      stepwise: BOOLEAN; (* alle Zwischenstaende ausgeben? *)
      query: BOOLEAN; (* impliziert stepwise, jeweils mit Rueckfrage *)
      input: FILE; (* von hier ist das Labyrinth einzulesen *)

   PROCEDURE Error(msg: ARRAY OF CHAR);
      (* Ausgabe einer Fehlermeldung auf stderr mitsamt Zeilenende *)
   BEGIN
      FwriteString(stderr, msg); FwriteLn(stderr);
   END Error;

   PROCEDURE ProcessArgs;
      VAR
	 flag: CHAR;
	 filename: ARRAY [0..511] OF CHAR;
   BEGIN
      query := FALSE; stepwise := FALSE;
      InitArgs("[-q] [-s] [maze]");
      WHILE GetFlag(flag) DO
	 CASE flag OF
	 | "q":   query := TRUE; stepwise := TRUE;
	 | "s":   stepwise := TRUE;
	 ELSE
	    Usage;
	 END;
      END;
      IF GetArg(filename) THEN
	 IF ~Fopen(input, filename, read, (* buffered = *) TRUE) THEN
	    Perror(filename); Exit(1);
	 END;
      ELSE
	 input := stdin;
	 IF query THEN
	    Error("-q vertraegt sich nicht mit einem Labyrinth von stdin!");
	    Exit(1);
	 END;
      END;
      AllArgs;
   END ProcessArgs;

   PROCEDURE ReadMaze(input: FILE;
                      VAR maze: Maze;
                      VAR width, height: MazeSize;
		      VAR entry, exit: MazeIndex) : BOOLEAN;
      (* Einlesen eines Labyrinths von ``input'':
         -  Bei Problemen wird eine Fehlermeldungen ausgegeben und
	    FALSE zurueckgeliefert.
         -  Im Erfolgsfall wird in maze das Labyrinth in der
	    vorgefundenen Form abgelegt.
	 -  Die beiden Seitenlaengen (des Rechtecks) werden in
	    width und height zurueckgegeben.
	 -  Auf der linken Seite wird ein Eingang erwartet und
	    auf der rechten Seite der Ausgang. Die entsprechenden
	    Zeilenindizes werden in entry und exit zurueckgeliefert.
      *)
      VAR
	 line, column: MazeSize; (* aktuelle Position im Labyrinth *)
	 ch: CHAR; (* zuletzt eingelesenes Zeichen *)
	 entryFound, exitFound: BOOLEAN; (* Ein- und Ausgang gefunden? *)
	 spaceSeen: BOOLEAN; (* Freiraum in der letzten Zeile? *)
   BEGIN
      line := 0; column := 0; width := 0;
      entryFound := FALSE; exitFound := FALSE;
      spaceSeen := FALSE;
      WHILE Fgetc(ch, input) DO
	 IF ch = nl THEN
	    IF line = 0 THEN
	       IF column < minsize THEN
		  WriteString("Labyrinth ist zu klein!"); WriteLn;
		  RETURN FALSE
	       END;
	       width := column;
	    ELSIF column # width THEN
	       WriteString("Das Labyrinth ist nicht rechteckig!"); WriteLn;
	       RETURN FALSE
	    END;
	    INC(line); column := 0;
	 ELSE
	    IF ch = space THEN
	       IF line = 0 THEN
		  WriteString("Bitte keinen Eingang in der 1. Zeile!"); WriteLn;
		  RETURN FALSE
	       ELSIF column = 0 THEN
		  IF entryFound THEN
		     WriteString("Mehr als einen Eingang gefunden!"); WriteLn;
		     RETURN FALSE
		  ELSE
		     entry := line; entryFound := TRUE;
		  END;
	       ELSIF column = width-1 THEN
		  IF exitFound THEN
		     WriteString("Mehr als einen Ausgang gefunden!"); WriteLn;
		     RETURN FALSE
		  ELSE
		     exit := line; exitFound := TRUE;
		  END;
	       END;
	       spaceSeen := TRUE;
	    ELSIF column = 0 THEN
	       spaceSeen := FALSE;
	    END;
	    IF (line >= maxsize) OR (column >= maxsize) THEN
	       WriteString("Das Labyrinth ist zu gross!"); WriteLn;
	       RETURN FALSE
	    END;
	    IF ch = walk THEN
	       WriteString("Bitte keine Markierungszeichen verwenden!");
	       WriteLn; RETURN FALSE
	    END;
	    maze[line, column] := ch; INC(column);
	 END;
      END;
      height := line;
      IF (width < minsize) OR (height < minsize) THEN
	 WriteString("Labyrinth ist zu klein!"); WriteLn; RETURN FALSE
      ELSIF spaceSeen THEN
	 WriteString("Bitte keinen Ausgang in der letzten Zeile!"); WriteLn;
	 RETURN FALSE
      ELSIF ~entryFound THEN
	 WriteString("Keinen Eingang gefunden!"); WriteLn;
	 RETURN FALSE
      ELSIF ~exitFound THEN
	 WriteString("Keinen Ausgang gefunden!"); WriteLn;
	 RETURN FALSE
      ELSIF maze[entry, 1] # space THEN
	 WriteString("Eingang ist verbarrikadiert!"); WriteLn;
	 RETURN FALSE
      END;
      IF column # 0 THEN
	 WriteString("Das Ende der letzten Zeile wird vermisst!"); WriteLn;
	 RETURN FALSE
      END;
      RETURN TRUE
   END ReadMaze;

   PROCEDURE SolveMaze(VAR maze: Maze;
                       width, height: MazeSize;
		       startx, starty: MazeIndex);
      (* Finden und Markieren eines Weges durch das Labyrinth `maze'
	 mit den gegebenen Seitenlaengen `width' und `height' und
         mit der Startposition (startx, starty)
	 nach der rechten Hand-Regel;
	 bei einer Startposition in der Mitte des Labyrinths kann
	 der Algorithmus beliebig lange laufen, wenn es Inseln oder
	 Einschluesse gibt;
	 die Optionen (globale Variablen) stepwise und query werden honoriert
      *)
      TYPE
	 Direction = (east, south, west, north);
	    (* Datentyp fuer die Richtung, wobei die Reihenfolge
	       signifikant ist:
	          (east, south, west, north)     linke Hand-Regel
		  (east, north, west, south)     rechte Hand-Regel
	       Wichtig ist, dass east zuerst genannt wird, weil
	       der Eingang auf der linken Seite liegt und wir
	       nicht das Labyrinth sofort wieder verlassen moechten.
	    *)
	 Delta = [-1..1];
	    (* Datentyp fuer die Differenz einer Zeilen- oder
	       Spaltenposition 
	    *)
      VAR
	 dx, dy: ARRAY Direction OF Delta;
	    (* Differenzen der Zeilen- und Spaltenpositionen, die
	       bei den einzelnen Richtungen zu verwenden sind, um
	       eine entsprechende Bewegung durchzufuehren
	    *)
	 x, y: MazeIndex;
	    (* aktuelle Zeilen- und Spaltenposition im Labyrinth *)
	 direction: Direction;
	    (* aktuelle Richtung, beginnt mit MIN(Direction) *)

      PROCEDURE Weiter() : BOOLEAN;
	 (* Implementierung der Option -q *)
	 VAR
	    answer: ARRAY[0..0] OF CHAR;
      BEGIN
	 WriteString("Weiter?"); ReadString(answer);
	 RETURN answer[0] # "n"
      END Weiter;

      PROCEDURE Exit(x, y: MazeIndex) : BOOLEAN;
	 (* Liegt (x,y) am Rand des Labyrinths? *)
      BEGIN
	 RETURN (x = 0) OR (x = height-1) OR
	        (y = 0) OR (y = width-1) OR
		(x = startx) & (y = starty)
      END Exit;

      PROCEDURE PreviousDirection(dir: Direction) : Direction;
	 (* nach links schauen *)
      BEGIN
	 IF dir = MIN(Direction) THEN
	    RETURN MAX(Direction)
	 ELSE
	    DEC(dir); RETURN dir
	 END;
      END PreviousDirection;

      PROCEDURE NextDirection(dir: Direction) : Direction;
	 (* nach rechts schauen *)
      BEGIN
	 IF dir = MAX(Direction) THEN
	    RETURN MIN(Direction)
	 ELSE
	    INC(dir); RETURN dir
	 END;
      END NextDirection;

      PROCEDURE Wall(x, y: MazeIndex; dir: Direction) : BOOLEAN;
	 VAR
	    ch: CHAR;
      BEGIN
	 ch := maze[x + dx[dir], y + dy[dir]];
	 RETURN (ch # space) & (ch # walk)
      END Wall;

      PROCEDURE Find(x, y: MazeIndex; dir: Direction) : Direction;
	 (* entsprechend der Rechte-Hand-Regel umschauen und
	    nach einem freien Weg suchen;
	    Vorbedingung: Es muss mindestens einen freien Weg geben
	 *)
      BEGIN
	 dir := PreviousDirection(dir); (* nach links schauen *)
	 LOOP
	    IF ~Wall(x, y, dir) THEN
	       RETURN dir
	    END;
	    dir := NextDirection(dir); (* naechste rechts untersuchen *)
	 END;
      END Find;

   BEGIN (* SolveMaze *)
      dx[east] := 0; dx[south] := 1; dx[west] := 0; dx[north] := -1;
      dy[east] := 1; dy[south] := 0; dy[west] := -1; dy[north] := 0;
      direction := MIN(Direction); x := startx; y := starty;
      REPEAT
	 maze[x, y] := walk;
	 IF stepwise THEN
	    WriteMaze(maze, width, height);
	    IF query & ~Weiter() THEN
	       RETURN
	    END;
	 END;
	 direction := Find(x, y, direction);
	 INC(x, dx[direction]); INC(y, dy[direction]);
      UNTIL Exit(x, y);
      maze[x, y] := walk;
   END SolveMaze;

   PROCEDURE WriteMaze(maze: Maze; width, height: MazeSize);
      (* Ausgabe des Labyrinths auf stdout *)
      VAR
	 line, column: MazeIndex;
   BEGIN
      FOR line := 0 TO height-1 DO
	 FOR column := 0 TO width-1 DO
	    Write(maze[line, column]);
	 END;
	 WriteLn;
      END;
   END WriteMaze;

BEGIN
   ProcessArgs;
   IF ReadMaze(input, maze, width, height, entry, exit) THEN
      SolveMaze(maze, width, height, entry, 0);
      WriteMaze(maze, width, height);
   END;
END PathFinder.

Andreas Borchert, 27. Januar 1999