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

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

GameOfLife Hauptprogramm

(*
   Allgemeine Informatik I / Programmieren I    WS 1998/1999
   Musterloesung fuer das Blatt 13, Aufgabe 15
*)
MODULE GameOfLife;

   FROM Arguments IMPORT InitArgs, GetFlag, FetchString, AllArgs, Usage;
   FROM ASCII IMPORT nl;
   FROM FtdIO IMPORT FwriteString, FwriteLn;
   FROM MainWin IMPORT WriteString, Write, WriteLn, Read,
      Flush, SetPos, Clear, lines, columns;
   FROM StdIO IMPORT FILE, MODE, Fopen, Fgetc, stdin, stderr;
   FROM SysExit IMPORT Exit;
   FROM SysPerror IMPORT Perror;

   FROM World IMPORT Init, Lines, Columns, GetMode, Get, Set, 
      Mode, NextGeneration;

   VAR
      (* Kommandozeilenargumente *)
      query: BOOLEAN; (* mit Rueckfragen? *)
      input: FILE; (* von hier ist die Ausgangssituation einzulesen *)

      (* Abfragestatus *)
      continue: BOOLEAN;

   PROCEDURE ProcessArgs;
      VAR
         flag: CHAR;
         filename: ARRAY [0..511] OF CHAR;
	 wmode: Mode; (* Mit oder ohne Rand *)
   BEGIN
      InitArgs("[-q] [-b] world");
      query := FALSE; wmode := torus;
      WHILE GetFlag(flag) DO
         CASE flag OF
         | "q":   query := TRUE;
         | "b":   wmode := border;
         ELSE
            Usage;
         END;
      END;
      FetchString(filename);
      IF ~Fopen(input, filename, read, (* buffered = *) TRUE) THEN
         Perror(filename); Exit(1);
      END;
      Init(columns, lines, wmode);
      AllArgs;
   END ProcessArgs;

   PROCEDURE ReadWorld(input: FILE): BOOLEAN;
      (* Einlesen der Welt von ``input'':
         -  Bei Problemen wird eine Fehlermeldung ausgegeben und
            FALSE zurueckgeliefert.
         -  Im Erfolgsfall wird in world die Eingabe in der
            vorgefundenen Form abgelegt.
         -  Die Limits, die durch die Bildschirmgroesse (lines + columns)
            und maxsize gegeben sind, werden beruecksichtigt.
      *)
      VAR
         line, column: CARDINAL; (* aktuelle Position in der Welt *)
         ch: CHAR; (* zuletzt eingelesenes Zeichen *)
   BEGIN
      line := 0; column := 0;
      WHILE Fgetc(ch, input) DO
         IF ch = nl THEN
            INC(line); column := 0;
         ELSE
            IF (line = Lines()) OR (column = Columns()) THEN
               WriteString("Die Eingabe ist zu gross!"); WriteLn;
               RETURN FALSE
            END;
            Set(line, column, ch); INC(column);
         END;
      END;
      IF (ORD(column) >= Columns()) OR (ORD(line) >= Lines()) THEN
         WriteString("Diese Welt ist zu gross fuer diesen Bildschirm!");
         WriteLn;
         RETURN FALSE
      END;
      RETURN TRUE
   END ReadWorld;

   PROCEDURE Weiter() : BOOLEAN;
      (* Implementierung der Option -q *)
      VAR
         answer: CHAR;
   BEGIN
      IF continue THEN
         RETURN TRUE
      ELSE
         SetPos(lines-1, 0);
         WriteString("Weiter?"); Read(answer);
         SetPos(lines-1, 0);
         WriteString("        "); Flush;
         CASE answer OF
         | "c":   continue := TRUE; RETURN TRUE
         | "n":   RETURN FALSE
         ELSE
            RETURN TRUE
         END;
      END;
   END Weiter;

   PROCEDURE WriteWorld(height, width: CARDINAL);
      (* Ausgabe von world auf stdout *)
      VAR
         line, column: CARDINAL;
   BEGIN
      FOR line := 0 TO height-1 DO
         SetPos(line, 0);
         FOR column := 0 TO width-1 DO
            Write(Get(line, column));
         END;
      END;
      Flush;
   END WriteWorld;

BEGIN
   Clear; Flush;
   ProcessArgs;
   IF ReadWorld(input) THEN
      continue := FALSE;
      REPEAT
         NextGeneration();
         WriteWorld(Lines(), Columns());
      UNTIL query & ~Weiter();
   END;
   SetPos(lines-1, 0); Flush;
END GameOfLife.

World.m2

IMPLEMENTATION MODULE World;

   TYPE
      NeighbourCount = [0..8];
      NeighbourCountSet = SET OF NeighbourCount;
   CONST
      maxsize = 80;  (* maximale Seitenlaenge der Welt  *)
      space = " ";       (* unbesetzt *)
      inhabitated = "X"; (* besetzt *)
      lonely = NeighbourCountSet{0, 1};
      birth = NeighbourCountSet{3};
      overpop = NeighbourCountSet{4..8};
   TYPE
      WorldSize = INTEGER [0..maxsize];
      WorldIndex = INTEGER [-1..maxsize];
      World = ARRAY WorldIndex, WorldIndex OF CHAR;
   
   VAR
      world, newWorld: World;
      noflines, nofcolumns: INTEGER; (* Maximale Groesse wg. BS und maxsize *)
      wmode: Mode;

   PROCEDURE InitWorld(VAR world: World);
      (* Initialisiert die Feld fuer die Eingabe *)
      VAR i, j: WorldIndex;
   BEGIN
      FOR i := MIN(WorldIndex) TO MAX(WorldIndex) DO
	 FOR j := MIN(WorldIndex) TO MAX(WorldIndex) DO
	    world[i, j] := space;
	 END;
      END;
   END InitWorld;

   PROCEDURE Init(columns, lines: CARDINAL; mode: Mode);
   BEGIN
      wmode := mode;
      InitWorld(world);
      nofcolumns := columns - 1; noflines := lines - 1;
      IF noflines > maxsize THEN noflines := maxsize -1 ; END;
      IF nofcolumns > maxsize THEN nofcolumns := maxsize -1 ; END;
   END Init;

   PROCEDURE GetMaximumSize(): CARDINAL;
   BEGIN
      RETURN maxsize;
   END GetMaximumSize;

   PROCEDURE Columns():CARDINAL;
   BEGIN
      RETURN nofcolumns;
   END Columns;

   PROCEDURE Lines(): CARDINAL;
   BEGIN
      RETURN noflines;
   END Lines;

   PROCEDURE GetMode(): Mode;
   BEGIN
      RETURN wmode;
   END GetMode;

   PROCEDURE Get(line, column: CARDINAL): CHAR;
   BEGIN
      RETURN world[line, column];
   END Get;

   PROCEDURE Set(line, column: CARDINAL; char: CHAR);
   BEGIN
      world[line, column] := char;
   END Set;

   PROCEDURE NextGeneration();

      PROCEDURE Neighbors (l, c: WorldIndex):CARDINAL;
	 (* Zaehlt die Nachbarn der gleichen Art *)
	 VAR
	    neighbors: [0..8];
	    i, j: [-1 .. 1];
	    nl, nc: WorldIndex;
      BEGIN
	 neighbors := 0;
	 FOR i := -1 TO 1 DO 
	    FOR j := -1 TO 1 DO 
	       IF (i # 0) OR (j # 0) THEN (* eigenes Feld ignorieren *)
		  IF wmode = border THEN
		     nl := l + i; nc := c + j;
		  ELSE (* Torus *)
		     nl := (noflines + l + i) MOD noflines;
		     nc := (nofcolumns + c + j) MOD nofcolumns;
		  END;
		  IF world[nl, nc] # space THEN
		     INC(neighbors);
		  END;
	       END;
	    END;
	 END;
	 RETURN neighbors
      END Neighbors;

      VAR
	 i, j: WorldIndex;
	 neighbors: CARDINAL;

   BEGIN (* NextGeneration *)
      IF wmode = border THEN
	 FOR i := -1 TO noflines DO
	    world[i, -1] := space; world[i, nofcolumns] := space;
	 END;
	 FOR j := -1 TO nofcolumns DO
	    world[-1, j] := space; world[noflines, j] := space;
	 END;
      END;
      FOR i := 0 TO noflines - 1 DO
	 FOR j := 0 TO nofcolumns -1 DO
	    neighbors := Neighbors(i, j);
	    IF neighbors IN (lonely + overpop) THEN
	       (* Gleich mit Neighbors < 2 or Neighbors > 3 *)
	       newWorld[i, j] := space;
	    ELSIF neighbors IN birth THEN
	       (* Gleich mit Neighbors = 3 *)
	       newWorld[i, j] := inhabitated;
	    ELSE
	       newWorld[i, j] := world[i, j];
	    END;
	 END;
      END;
      world := newWorld;
   END NextGeneration;

BEGIN
   wmode := torus;
END World.

Ingo Melzer, 10. Februar 1999