WATOR: Simulation eines Mini-Ökosystems

Wator ist ein kleiner, idyllischer Planet, dessen Oberfläche (ring-) förmig ist. Und obschon er nahe des Grossen Wagens liegt, sind ihm Umweltprobleme fremd, aber es gibt dort auch keine Menschen ... Trotzdem tobt dort ein langer Kampf - ein Kampf um das pure Leben -, denn dort leben Fische, die unsterblich sind und sich alle paar Chrononen, so heißt dort die Zeiteinheit, vermehren. Aber es leben auch Haie dort, die sich zwar auch vermehren können, jedoch keinesfalls unsterblich sind, denn sie müssen Fische verspeisen, um nicht nach einigen Chrononen zu sterben.

Jeder Fisch kann sich auf eines der vier benachbarten Felder bewegen, sofern dieses nicht schon besetzt ist. Die Haie können sich ebenso auf eines der vier benachbarten Felder bewegen, sofern dieses nicht schon durch einen Hai besetzt ist. Kann der Hai jedoch auf ein benachbartes Feld ziehen, auf dem sich schon ein Fisch befindet, so tut er dies auch, um sein Überleben durch einen zu verspeisenden Fisch zu sichern. Der Zufall entscheidet, in welche Richtung sich ein Hai oder Fisch bewegt, wenn er hierzu mehrere Möglichkeiten hat. Hat ein Hai oder Fisch sein Brutalter überschritten, teilt er sich, und ein neuer Hai bleibt auf dem angestammten Platz, während der andere sich in die zuvor bestimmte Richtung bewegt. Ist gar kein Feld zur Bewegung frei, verbleibt das Tier an seinem Platz; sollte es in dieser Situation eigentlich teilungsfähig sein, wird die Teilung wegen lokaler Überbevölkerung unterdrückt. Zuerst bekommen immer die Fische die Chance, sich zu vermehren und sich zu bewegen, während die Haie erst nach ihnen dran sind. Das Zusammenleben beider Arten hängt empfindlich von den Brutdauern beider Arten und der Anzahl der Chrononen, die ein Hai überleben kann, ohne einen Fisch zu verspeisen, ab. Um dies näher zu beobachten, schrieb ich das anbei abgedruckte Programm auf einem ATARI 1040 STF in SOFTWAVE MODULA 2.

Überlegungen zur Geschwindigkeitsoptimierung oder wie das Programm so entstand

Am Anfang ist man versucht, in einer ersten Schleife, die den Bildschirmspeicher abtastet, die Fische zu bewegen und sich vermehren zu lassen, und in einer zweiten analogen Schleife entsprechend die Haie zu überprüfen, zu bewegen und sich vermehren zu lassen.

Bei näherer Überlegung stellt man jedoch fest, daß beim ersten Durchgang, in dem das Fischleben untersucht wird, die Mehrzahl der gefundenen Informationen unverwertet verlorengeht.

Denn hier werden alle Felder auf die Art ihrer Besetzung hin untersucht, während nur die Informationen über die mit Fischen besetzten Felder weiterverwendet werden. Dies ist um so unerträglicher, als die Mehrheit aller Individuen aus Fischen besteht, und bei o.g. Verfahren für die wenigen Haie noch einmal der ganze Bildschirmspeicher durchsucht werden muß.

Daher bietet es sich an, beim ersten Durchlauf, sollte hier auf einen Hai gestoßen werden, gleich seine Koordinate auf einer Art Stack abzuspeichern, den ich hier Hai-Stapel nenne. Alle Informationen über ein Individuum sind in folgender Weise in einer einzigen CARD-Variablen enthalten:

Bei der Brutdauer und der Restlebenszeit ist noch zu beachten, daß beide Werte am Anfang gesetzt werden, und dann im Bedarfsfall dekrementiert werden, so daß ihnen nur dann Bedeutung zukommt, wenn sie Null werden.

Auch darf die Brutdauer 127 und die Restlebensdauer 255 nicht überschreiten. Ich habe ein 2-dimensionales Array eingerichtet, um die jeweilige Situation am Bildschirm abspeichern zu können. Die erste Koordinate wird zur Unterscheidung zweier Bildschirmspeicher 0,..] ist der momentan angezeigte Bildschirminhalt, während in bildschirm[n1,..] der Inhalt der noch auszugebenden Bildschirmsituation abgespeichert ist.

Die zweite Komponente gibt die Nummer des gewünschten Bildschirmfeldes an, wobei mit der Numerierung in der linken oberen Ecke begonnen wird, und diese dann nach rechts ansteigt.

Auf die Nummer der rechten oberen Ecke folgt die der zweitobersten linken Zelle usw. Dieses Verfahren bedingt allerdings die Eigenheit, daß der simulierte Torus zusätzlich verdreht ist, daß heißt, der Zylinder, aus dem der Torus durch Zusammenkleben entstand, ist zuvor verdreht worden.

Wenn Fische oder Haie in dem aktuellen Bildschirmspeicher gesetzt werden, wird jeweils ihre Brutdauer bzw. bei den Haien auch ihre Restlebenszeit um einen Zufallswert verringert. Dies hat zur Folge, daß nicht mit einem Schlag sich alle Fische vermehren, sich alle Haie vermehren oder aussterben. Durch diese Maßnahme gewinnen Hai- und Fischentwicklung wesentlich an Kontinuität.

Was sonst noch zu beachten ist

Bei einer Simulation Wators mit einer Oberfläche von etwa 75 * 20 Feldern, erreicht der Computer eine Geschwindigkeit von ca. 20 Generationen pro Sekunde. Diese Zeit ist jedoch nur ein Mittelwert, denn wenn sehr viele Individuen leben, ist die Rechenzeit höher, als wenn nur sehr wenige leben.

Beigefügt ist im inneren Modul Random auch noch ein (Pseudo-) Zufallszahlengenerator, da ein solcher wohl nicht in allen Standardsystemen vorkommt. Um diesen zu initialisieren, wird der Benutzer aufgefordert, einen Startwert einzugeben. Durch diesen Startwert wird leider der weitere Spielverlauf etwas beeinflußt, doch besteht durch ihn auch die Möglichkeit, bestimmte Simulationsergebnisse beliebig zu reproduzieren.

Durch Drücken einer beliebigen Taste bekommt man die Möglichkeit, entweder das Programm abzubrechen, oder die Simulationsparameter zu verändern.

MODULE WATOR ; 
(**********************************************) 
(*                                            *)
(*  Copyright by S. de Vries aus Hamburg B.   *)
(*  geschrieben im 9. Monat des Jahres 1988   *)
(*                                            *)
(**********************************************)

    FROM In0ut IMPORT WriteLn, WriteString, Write, WriteCard, ReadCard, 
                      Read ;
    FROM Terminal IMPORT KeyPressed ;
         CONST x      = 75 ; (* Größe des Bildschirms in X- *) 
               y      = 20 ; (* u.i.Y-Richtung *)
               t0     = 0 ;  (* Wert der linken oberen Ecke *) 
               t1     = x * y ; (* Wert d.Feldes rechts der rechten unteren Ecke *) 
               haischalter = 32768 ;
               hairestlebensfaktor « 128 ;
    (* Faktor mit dem die Hairest- *)
    (* lebensdauer zu multiplizieren *)
    (* ist *)

    TYPE BILDSCHIRM = ARRAY [0..1],[t0..t1] OF CARDINAL ;
    VAR  bildschirm   (* Speichert die aktuelle- und eine
                         Hilfsbelegung des Bildschirms *)
                  : BILDSCHIRM ;

         haie, fische, (* Anzahl der Haie bzw. Fische *)
         haibrut, fischbrut, (* Brutdauer bei Haien bzw. Fischen*)

         haileben,   (* Zeitdauer, die ein Hai noch ohne Nahrung überleben kann *)

         hai, fisch, (* Wert, der in einer Bildschirm-
                        Speicherzelle für einen neuen Hai
                        bzw. Fisch abgespeichert werden muß *)

         n0 , n1,    (* aktueller, nichtaktueller Bildschirmspeicher *)

         q ,         (* Zwischenspeicher für verschiedenstes *)

         generation  (* Generationszähler *)

                  : CARDINAL ;

    PROCEDURE Initialisiere ;
        BEGIN ;
            haie        := 0 ;
            fische      := 0 ; 
            haibrut     := 0 ; 
            fischbrut   := 0 ; 
            haileben    := 0 ;
            END Initialisiere ;

    PROCEDURE Eingabe ;
        VAR zahl : CARDINAL ;

        PROCEDURE PrimitivEingabe ( text : ARRAY OF CHAR ; VAR zahl : CARDINAL);
            BEGIN ;
                WriteString ( text );
                WriteCard ( zahl , 5 );
                WriteLn;
                ReadCard ( zahl );
                WriteLn;
                END PrimitivEingabe ;

        BEGIN;
            REPEAT ;
                PrimitivEingabe ( 'Anzahl der Haie' , haie ) ; 
                PrimitivEingabe ( 'Anzahl der Fische' , fische ) ; 
                PrimitivEingabe ( 'Brutdauer der Haie ' , haibrut); PrimitivEingabe ( 'Brutdauer der Fische ' ,fischbrut ) ; PrimitivEingabe ( 'Überlebenszeit der Haie ' , haileben ) ; 
            UNTIL ( ( haie + fische ) <= t1 ) AND ( fischbrut < 63 )
                    AND ( haibrut < 127 ) AND ( haileben < 255 ); 
            hai := haischalter + haibrut + 128 * haileben + 1 ; 
            fisch := fischbrut + 1 ;
            WriteString (' Geben Sie jetzt bitte abschließend den' ); 
            WriteString (' Startwert für den Zufallsgenerator ein.');
            WriteLn ;
            ReadCard ( zahl ) ;
            WriteLn ;
            SetRandom ( zahl ) ;
            END Eingabe ;

    PROCEDURE Bildschirmlnit ;
        VAR t : CARDINAL ; (* SCHLEIFENVAR. *)
        BEGIN ;
            FOR t := t0 TO t1 DO
                bildschirm[0,t] := 0 ;
                bildschirm[1,t] := 0 ;
                END ;
            END BildschirmInit ;

    PROCEDURE Menue () : BOOLEAN ;

        VAR wert        : CARDINAL ;
            fische1,    (* Speicher für die alte Anzahl der Fische *) 
            haie1       (* Speicher für die alte Anzahl der Fische *)
                    : CARDINAL ;

        BEGIN ;
            WriteString (' Wenn sie aufhören wollen ' ) ; 
            WriteString ( 'drücken sie bitte die 1, ' ) ;
            WriteLn ;
            WriteString (' sonst drücken sie bitte eine beliebige ') ;
            WriteString (' Zahl gefolgt von <CR> . ' ) ;
            WriteLn ;
            ReadCard ( wert ) ;
            WriteLn ;
            IF wert = 1 
                THEN
                    RETURN ( TRUE ) ;
                ELSE
                    fische1 := fische ; 
                    haie1   := haie ;
                    Eingabe ;
                    IF fische > fische1 
                        THEN
                            FischeSetzen ((fische-fische1), fischbrut, bildschirm, TRUE) ; 
                    ELSIF fische1 > fische 
                        THEN
                            FischeSetzen ((fische1-fische), fischbrut, bildschirm, FALSE ) ;
                        END ;

                    IF haie > haie1 
                        THEN
                            HaieSetzen ((haie-haie1), haibrut, haileben, bildschirm, TRUE ) ;

        (* Haie setzen, weil zu wenig da sind *) 
                    ELSIF haiel > haie 
                        THEN
                            HaieSetzen ((haie1-haie), haibrut, haileben, bildschirm, FALSE ) ;

        (* Haie löschen, weil zu viel da sind *) 
                        END ;
                    RETURN ( FALSE ) ;
                END ;
            END Menue ;

    PROCEDURE FischeSetzen ( fische, fischbrut : CARDINAL; 
                    VAR bildschirm : BILDSCHIRM ;flag : BOOLEAN );

    (* ist flag wahr, so werden Fische gesetzt, sonst werden sie gelöscht *)

        VAR x : CARDINAL ;
        BEGIN ;
            WHILE fische > 0 DO
                x := Random ( t1 ) ;
                IF ( bildschirm[n0,x] = 0 ) AND flag 
                    THEN
                        bildschirm[n0,x) := fisch-Random (fischbrut )
                        DEC ( fische ) ;

                ELSIF ( bildschirm[n0,x] < haischalter ) AND ( NOT ( flag ) ) AND ( bildschirm[n0,xj # 0 ) 
                    THEN
                        bildschirm[n0,x] := 0 ;
                        DEC ( fische ) ;
                    END (* ELSIF *);

            END (*WHILE*);
        END FischeSetzen ;

    PROCEDURE HaieSetzen ( haie, haibrut, haileben : CARDINAL; 
                    VAR bildschirm : BILDSCHIRM ; flag : BOOLEAN K;

    (* ist flag wahr, so werden Haie gesetzt, sonst werden sie gelöscht *)

        VAR x : CARDINAL ;
        BEGIN ;
            WHILE haie > 0 DO
                x := Random ( t1 ) ;
                IF ( bildschirm[n0,x] = 0 ) AND flag 
                    THEN
                        bildschirm[n0,x] := hai - Random ( haibrut )-hairestlebensfaktor * Random( haileben - 1 );
                        DEC ( haie ) ;
                ELSIF (bildschirm[n0,x] >= haischalter ) AND ( NOT ( flag ) )
                    THEN
                        bildschirm[n0,x) := 0 ;
                        DEC ( haie ) ;
                    END (* ELSIF *);
            END (*WHILE*);
        END HaieSetzen ;

    PROCEDURE BildschirmAusgeben ( n , generation : CARDINAL ; 
                bildschirm : BILDSCHIRM ); 
        VAR t : CARDINAL ;

        PROCEDURE Status ( generation : CARDINAL) ;
            BEGIN ;
                WriteString(' Generation: ');
                WriteCard( generation , 4 ); 
                WriteString (' Haie: ');
                WriteCard( haie , 4 ); 
                WriteString(' Fische: ');
                WriteCard( fische , 4 ); WriteLn;
            END Status;

            BEGIN ;
                Status ( generation ) ;
                FOR t := t0 TO t1-1 DO
                    IF bildschirm[n, t] > haischalter 
                        THEN
                            Write ( '*' ) ;
                    ELSIF bildschirm[n,t] # 0
                        THEN
                            Write ( '-' ) ;
                        ELSE
                            Write ( ' ' ) ;
                    END (*IF*);
                    IF (t MOD x ) = (x - 1) THEN WriteLn ; END ;
                END (*FOR*);
                WriteLn ; WriteLn ; WriteLn ; 
            END BildschirmAusgeben ;



    MODULE HAISTAPEL ;
        IMPORT t0, t1 ;
        EXPORT PushHaistapel, GetHaistapel, InitHaiStapel ;
        TYPE STAPEL = ARRAY [t0..t1] OF CARDINAL ;
        VAR sp        : CARDINAL; (* Zeiger auf das nächste freie Stapelelement *)

            haistapel : STAPEL ;

    (* wird wie ein Stapelspeicher gehandhabt, um 
       die Hai-koordinaten zwischenzuspeichern, 
       während, die Fische zuerst bewegt werden *)

        PROCEDURE InitHaistapel ;
            BEGIN ;
                sp : = 0 
            END InitHaistapel ;

        PROCEDURE PushHaistapel ( zahl : CARDINAL );
            BEGIN ;
                haistapel[sp] := zahl ;
                INC (sp) ;
            END PushHaistapel ;

        PROCEDURE GetHaistapel ( ) : CARDINAL ;
            BEGIN ;
                IF sp = 0 
                    THEN
                        RETURN t1 ;
                    ELSE
                        DEC (sp);
                        RETURN( haistapel[sp] ) ;
                END (*IF*);
            END GetHaistapel ;

    END HAISTAPEL ;

    MODULE RANDOM ;
        EXPORT  Random , SetRandom ;
        VAR     old    : CARDINAL ;

        PROCEDURE Random ( zahl : CARDINAL ) : CARDINAL ;
            VAR speicher : CARDINAL ;
                wert     : CARDINAL ;
            BEGIN ;
                IF zahl <= 1 THEN RETURN ( 0 ); END ;
                wert := zahl ;
                WHILE NOT ( ODD ( zahl )) OR ( ( zahl MOD 5 ) = 0 ) DO 
                    INC ( zahl ) ;
                    END ; (* of WHILE *)
                REPEAT
                    old := ( old * 3 ) MOD 10240 ; 
                    old := ( old * 3 ) MOD 10240 ; 
                    old := ( old * 3 ) MOD 10240 ; 
                    old := ( old * 3 ) MOD 10240 ; 
                    old := ( old + 1293 ) MOD 10240 ; 
                    speicher := old MOD zahl;
                UNTIL speicher < wert ; 
            RETURN speicher ;
            END Random ;

        PROCEDURE SetRandom ( zahl : CARDINAL ); 
            BEGIN ;
                old := zahl MOD 10240 ;
                END SetRandom ;

    END RANDOM ;

    PROCEDURE Fischleben ( t , wert,n0,n1 : CARDINAL );
    (* überprüft einen Fisch, bewegt und teilt ihn ggf. *)

        VAR richtung : ARRAY [0..3] OF CARDINAL ; (* hier werden
            die möglichen Bewegungsrichtungen der Fische zwischengespeichert *) 
            sp       : CARDINAL ; (* Zeiger auf richtung *) 
            z        : CARDINAL ; (* Zeiger auf die ausgewaehlte Richtung *)

        BEGIN ;
            sp := 0 ;

            IF x > t THEN richtung[sp] := t + t1 - x 
                     ELSE richtung[sp] := t - x ; END ;
            IF bildschirm[n1,richtung[sp]] = 0 
                     THEN INC (sp) ; END ;

            richtung[sp]:= t + 1 ;
            IF richtung[sp] = t1
                THEN DEC( richtung[sp], t1 ); END ;
            IF bildschirm[n1,richtung[sp]] = 0 
                THEN INC (sp) ; END ;

            richtung[sp]:= t + x ;
            IF richtung[sp] >= t1
                THEN DEC( richtung[sp], t1 ); END ;
            IF bildschirm[n1,richtung[sp]] = 0 
                THEN INC (sp) ; END ;

            IF t = t0 THEN richtung[sp] := t - 1 + t1
                      ELSE richtung[sp]:= t - 1 ; END ;
            IF bildschirm[n1,richtung[sp]] = 0 
                THEN INC (sp) ; END ;

            IF sp = 0 THEN richtung[sp] := t ; INC (sp) ; END ;
            (* kein Platz *)
            DEC ( sp ) ;
            z := Random ( sp ) ;
            DEC ( wert ) ;
            IF wert = 0
                THEN    (* Vermehrung *)
                    bildschirm[n1,t] := fisch; 
                    bildschirm[n1,richtung[z]] := fisch ;
                    IF richtung[z] # t THEN INC (fische) ; END ;
                ELSE
                    bildschirm[n1,t] := 0 ; 
                    bildschirm[n1,richtung[z]] := wert ;
                END ;
                bildschirm[n0,t] := 0 ;
            END Fischleben ;

    PROCEDURE Fischtest (n0,n1: CARDINAL );
        VAR t    : CARDINAL ;
            wert : CARDINAL ;
        BEGIN (* FischTest *)
            bildschirm[n1] := bildschirm[n0] ; 
            FOR t := t0 TO t1 DO
                wert := bildschirm [n0,t] ;
                IF wert # 0
                    THEN
                        IF wert > haischalter 
                            THEN
                                PushHaistapel ( t ); 
                            ELSE
                                Fischleben(t,wert,n0,n1) ; 
                            END ;
                    END ;
            END (*FOR*);
        END Fischtest ;

    PROCEDURE Haitest (n0,n1: CARDINAL );

        VAR t           : CARDINAL ;
            wert        : CARDINAL ;
            richtung    : ARRAY [0..3] OF CARDINAL ; 
            sp          : CARDINAL ;
            p, q        : CARDINAL ;

        BEGIN (* HaiTest *)
            t := GetHaistapel () ;
            WHILE t # t1 DO
                wert := bildschirm [n0,t] ; 
                bildschirm [n0,t] := 0 ; 
                bildschirm [n1,t] := 0 ;
                sp := 0 ;

                IF x > t THEN richtung[sp] := t + t1 - x 
                         ELSE richtung[sp] := t - x ; END ;
                IF bildschirm[n1,richtung[sp]] < haischalter 
                    THEN INC (sp) ; END ;

                richtung[sp] := t + 1 ;
                IF richtung[sp] = t1
                    THEN DEC ( richtung[sp],t1 ) ; END ; 
                IF bildschirm[n1,richtung[sp]] < haischalter 
                    THEN INC (sp) ; END ;

                richtung[sp] := t + x ;
                IF richtung[sp] >= t1
                    THEN DEC ( richtung[sp],t1 ) ; END ;
                IF bildschirm[n1,richtung[sp]] < haischalter 
                    THEN INC (sp) ; END ;

                IF t = t0 THEN richtung[sp] := t + t1 - 1 
                          ELSE richtung[sp] := t - 1 ; END ;
                IF bildschirm[n1,richtung[sp]] < haischalter 
                    THEN INC (sp) ; END ;

                IF sp = 0
                    THEN richtung[sp] := t ; INC (sp) ; END ;

                p := 0 ;
                FOR q := 0 TO sp - 1 DO 
                    IF bildschirm [ n1, richtung [q] ] #0
                        THEN
                            richtung [p] := richtung [q]; 
                            INC (p) ;
                        END ;
                END ;

                IF p # 0 
                    THEN
                        sp := p ; 
                        wert := (wert MOD hairestlebensfaktor) 
                            + haileben
                            * hairestlebensfaktor 
                            + haischalter ;
                            DEC ( fische ) ;
                    ELSE
                        DEC (wert, hairestlebensfaktor) ; 
                    END ; (* IF *)
                DEC ( sp ) ;
                IF wert > haischalter
                    THEN    (* Hai lebt *)
                        q := Random (sp ) ;
                        DEC(wert) ;
                        IF ( wert MOD hairestlebensfaktor ) = 0 
                            THEN    (* Vermehrung *)
                                bildschirm [n1,t] :=hai ; 
                                bildschirm [n1,richtung[q]]:=wert + haibrut;
                                IF t # richtung[q] 
                                    THEN INC(haie) ; END ;
                                ELSE
                                    bildschirm [n1,richtung[q]] := wert ;
                                END ; (* ELSE *)
                            ELSE
                                DEC (haie) ;
                            END ; (* ELSE *)
                    t := GetHaistapel () ;
            END ; (*WHILE*)
        END Haitest ;


    PROCEDURE spiel ;
    BEGIN ;
        generation := 1 ;
        InitHaistapel ;
        Initialisiere ;
        BildschirmInit ; 
        n0 := 0 ; 
        n1 := 1 ;
        Eingabe ;
        FischeSetzen ( fische, fischbrut , bildschirm, TRUE ); 
        HaieSetzen ( haie, haibrut, haileben, bildschirm, TRUE ); 
        BildschirmAusgeben ( n0 , generation , bildschirm );
        LOOP ;
            INC ( generation ) ;
            Fischtest (n0, n1 ) ;
            Haitest (n0, n1 ) ; 
            BildschirmAusgeben ( n1 , generation , bildschirm ); 
            q := n1 ; 
            n1 := n0 ; 
            n0 := q ;
            IF ( KeyPressed () AND Menue () ) THEN EXIT ; END ;
        END ;
    END spiel ;

    BEGIN ;
        spiel ;
    END WATOR .


Sven de Vries
Aus: ST-Computer 05 / 1989, Seite 68

Links

Copyright-Bestimmungen: siehe Über diese Seite