Das Projekt 'Life'

In diesem Projekt soll das Simulationsspiel 'Life' nach John Conway dargestellt werden. Grundidee ist, das Schicksal einer Ausgangspopulation primitiver 'Lebewesen' unter bestimmten Bedingungen schrittweise zu simulieren.

Die Ausgangspopulation wird auf einem Gitternetz aus Zeilen und Spalten angesiedelt und durch geeignete Zeichen dargestellt. Jedes Element der Population kann dabei bis zu acht Nachbarn auf den angrenzenden Feldern haben.

Für den Ablauf der Simulation sollen folgende 'Spielregeln' gelten:

  1. Jedes Element mit 2 oder 3 Nachbarn überlebt in dieser Generation
  2. Jedes Element mit mehr als 3 Nachbarn stirbt (Übervölkerung)
  3. In einem leeren Feld mit genau 3 Nachbarn wird ein neues Element geboren

Zur Lösung der gesamten Aufgabe sind einige Teilprobleme zu lösen:

Darstellung der Elemente Auf dem Bildschirm sollen besetzte Zellen mit '*' und freie Zellen mit '.' dargestellt werden. Intern kann dafür der Datentyp CHAR verwendet werden. Alternativ wäre auch der Datentyp BOOLEAN möglich.
Implementation des Spielfeldes Oberon bietet hierfür die Datenstruktur 'ARRAY' an. Mit der Definition
VAR Gitter: ARRAY xmax,ymax OF CHAR;
werden die Elemente Gitter[0,0] bis Gitter[xmax-1,ymax-1] bereitgestellt.
Erzeugen der neuen Generation Bevor eine neue Generation ausgegeben werden kann, muss erst die gesamte Nachbarnzahl für alle Felder bestimmt werden! Es ist also ein weiteres ARRAY zur Zwischenspeicherung der Nachbarnzahl notwendig:
VAR Nachbarn: ARRAY xmax,ymax OF INTEGER;
Bestimmung der Anzahl der Nachbarn Jede Zelle Gitter[x,y]  hat genau 8 unmittelbare Nachbarfelder. Zur Bestimmung der Anzahl der Nachbarn würde sich folgender Programmabschnitt eignen:

Anzahl:=0;
FOR i:=x-1 TO x+1 DO
   FOR j:=y-1 TO y+1 DO
      IF Gitter[i,j]='*' THEN INC(Anzahl) END;
   END;
END;
IF Gitter[x,y]='*' THEN DEC(Anzahl) END;
Nachbarn[x,y]:=Anzahl;

Günstiger ist es allerdings, in der Umgebung der besetzten Zellen die Nachbarnzahl zu erhöhen, weil im Allgemeinen nur wenige Zellen besetzt sind:

IF Gitter[x,y]='*' THEN
   FOR i:=x-1 TO x+1 DO
      FOR j:=y-1 TO y+1 DO
          INC(Nachbarn[i,j]);
      END;
   END;
   DEC(Nachbarn[x,y]);
END;   

Problematik des Randes Um unangenehme Fallunterscheidungen an den Rändern des Spielfeldes zu vermeiden, definiert man das Spielfeld einfach um je 2 Zeilen und Spalten größer, so dass sich um das Feld ein 'Rasen' aus einer leeren Zeile bzw. Spalte bildet. Will man die Zellen wie gewohnt von 1 bis xmax bzw. ymax durchnummerieren, muss man in Oberon definieren:
VAR Gitter: ARRAY xmax+2,ymax+2 OF CHAR;

Damit erhält man z.B. in der Zeile 1 xmax+2 Elemente, die als Gitter[0,1] bis Gitter[xmax+1,1] angesprochen werden können.
Bestimmung der Startpopulation Die Besetzung des Spielfeldes zu Beginn könnte durch direkte Eingabe am Bildschirm erfolgen, was allerdings sehr zeitraubend wäre. Günstiger ist es sicher, hierfür Zufallszahlen zu verwenden:
x:=Random(xmax)+1; y:=Random(ymax+1);
Gitter[x,y]:='*';

Allerdings ist in Oberon kein Zufallszahlengenerator integriert.
Zufallszahlen Ein einfacher Zufallszahlengenerator lässt sich durch folgende Funktion implementieren:

PROCEDURE Random(Obergrenze:INTEGER):INTEGER;
BEGIN
   Seed:=SHORT((Seed*899+123) MOD 32767);
   RETURN (SHORT(Seed) MOD Obergrenze);
END Random;

Diese Funktion geht von der Existenz einer globalen Variablen Seed vom Typ LONGINT aus, die in geeigneter Weise initialisiert werden muss, z.B. durch
Seed:=Windows.GetTickCount() MOD MAX(INTEGER).
Die Qualität dieses Zufallszahlengenerators ist nicht allzu groß, reicht aber für diesen Zweck aus.