Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Game of Life - wie torusförmiges Spielfeld erstellen?


galagher - Do 05.06.14 20:09
Titel: Game of Life - wie torusförmiges Spielfeld erstellen?
Hallo!

Momentan ist es so, dass an den (unsichbaren) Rändern im Array immer tote Zellen sind. Ich möchte das Spielfeld nun aber als Torus haben, also jede lebende Zelle, die an einer Seite verschwindet, soll an der gegenüberliegenden Seite wieder auftauchen.
Leider habe ich keine Ahnung, wie ich das Array dazu umändern muss.

Für Vorschläge bin ich jederzeit zu haben! :mrgreen:


Jann1k - Do 05.06.14 20:24

Eigentlich musst du nur die Funktion, die dir die neue Generation berechnet ändern. Den Randfall hast du sicherlich schon abgefangen, damit du nicht auf Felder außerhalb deiner Arrays zugreifst. Hier musst du einfach auf die gegenüberliegende Seite zugreifen.


Hidden - Do 05.06.14 20:28

Hallo user profile icongalagher, :wave:

du könntest an jeder Stelle, wo auf das Array zugegriffen wird, statt Index den Rest von Index mod Feldbreite nehmen.

viele Grüße,
Daniel


mandras - Do 05.06.14 20:30

Ich kenne Dein Programm nicht,
als erste Idee kommt mir aber ein einfaches Modulo für die Array-Indizies -> nach dem Rechten Rand beginnt wieder der linke usw.
Im Ergebnis bekomme ich somit eine Art Kugeloberfläche, welche wiederum der Oberfläche eines Torus entspricht - man kann von jedem Punkt aus in eine Richtung gehen, wenn man das lange genug macht kommt man wieder an wo man loslief.

AAAAber:
Man müßte sich noch Gedanken machen ob das in dieser einfachen Form gewünscht ist.
Bei einer diskreten Ebene (also dem "Original"-GOL) sind die Punkten äquidistant.
Das wird man auf einer Kugel bzw. einem Torus nicht hinbekommen.
Das ist für das Verhalten nicht relevant, wichtig ist ja die Anzahl der Nachbarpunkte eines gegebenen Punkts.

So könnte man sowohl bei Kugel als auch beim Torus für die Berechnung auf die Ebene mit Modulo-Indizierung zurückgreifen, nur für die Darstellung müßte wiederum aus den Koordinaten der Fläche auf solche bei Kugel/Torus wandeln, aus den Indizies würden Winkel (bei der Kugel zB für Längen/Breitengrad, beim Torus entsprechend).
Allerdings wären die "Zellen" an den Kugelpolen dichtgedrängt, am Äquator weiter voneinander entfernt.


galagher - Do 05.06.14 20:33

Das Einfache zuerst:
user profile iconJann1k hat folgendes geschrieben Zum zitierten Posting springen:
Den Randfall hast du sicherlich schon abgefangen, damit du nicht auf Felder außerhalb deiner Arrays zugreifst.

Ja, und zwar, indem das Array an den Rändern um jeweils 1 grösser ist, als der Bereich, der gezeichnet wird. Also im Array for x := -1 to 97, gezeichnet wird aber nur for x := 0 to 96
-1 und 97 belege ich beim Durchlauf mit toten Zellen.

Nun die Knackpunkte:
user profile iconJann1k hat folgendes geschrieben Zum zitierten Posting springen:
Hier musst du einfach auf die gegenüberliegende Seite zugreifen.
und:
user profile iconJann1k hat folgendes geschrieben Zum zitierten Posting springen:
Eigentlich musst du nur die Funktion, die dir die neue Generation berechnet ändern.

Naja - wie?

user profile iconHidden hat folgendes geschrieben Zum zitierten Posting springen:
du könntest an jeder Stelle, wo auf das Array zugegriffen wird, statt Index den Rest von Index mod Feldbreite nehmen.
Das versuche ich mal! Klingt ja nicht so schwer, mal sehen.


Hidden - Do 05.06.14 20:38

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Das Einfache zuerst:
user profile iconJann1k hat folgendes geschrieben Zum zitierten Posting springen:
Den Randfall hast du sicherlich schon abgefangen, damit du nicht auf Felder außerhalb deiner Arrays zugreifst.

Ja, und zwar, indem das Array an den Rändern um jeweils 1 grösser ist, als der Bereich, der gezeichnet wird. Also im Array for x := -1 to 97, gezeichnet wird aber nur for x := 0 to 96
-1 und 97 belege ich beim Durchlauf mit toten Zellen.
Den Rand bräuchtest du ja beim Torus nicht mehr. Man kann ihn als ein Array darstellen, das in sich selbst überläuft statt in einen Rand.

user profile iconmandras hat folgendes geschrieben Zum zitierten Posting springen:
Bei einer diskreten Ebene (also dem "Original"-GOL) sind die Punkten äquidistant.
Das wird man auf einer Kugel bzw. einem Torus nicht hinbekommen.

Naja, für den Torus schon. Die Transformationsabbildung von der Ebene zum Zylinder sieht in jedem Punkt gleich aus, und vom Zylinder zum Torus erfolgt noch einmal das gleiche um eine andere Achse. <- Gleiche Abbildung, aber auf den Zylinder angewandt nicht mehr isometrisch :(


Horst_H - Do 05.06.14 21:44

Hallo,

kopiere statt des toten Randes die Spalten vom jeweils gegenüberliegendem Ende und anschliessend die Zeilen

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
0 00000 0

0 abcde 0
0 abcde 0
0 abcde 0
0 yxcvz 0

0 00000 0
wird zu
z yxcvz y

e abcde a
e abcde a
e abcde a
z yxcvz y

e abcde a


Dann bleibt der Abstand gleich ;-)
Mit Modulo Rechnen geht natürlich auf wenn das Feld von 0..Anzahl-1 geht kannst Du leicht mod Anzahl rechnen.Das ist aber sehr langwierig, also lieber nur auf die Felder beschränken, die es Betrifft ( Zeile 1und 2 und Zeile n-1/n-2 und die entsprechenden Spalten.

Wenn man mit drei temporären benachbarten Zeilen arbeiten geht es sehr leicht, aber Du möchtest Dich sicher selbst herausbekommen.

Gruß Horst

P.S.
Zitat:
Man kann ihn als ein Array darstellen, das in sich selbst überläuft statt in einen Rand.
das wäre eine umlaufende Spirale, oder nicht?


Hidden - Do 05.06.14 22:27

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Zitat:
Man kann ihn als ein Array darstellen, das in sich selbst überläuft statt in einen Rand.
das wäre eine umlaufende Spirale, oder nicht?

So, wie Arrays für gewöhnlich im Speicher liegen, wäre es eine Spirale. Für Den Torus läuft der rechte Rand in den linken über und der untere in den oberen (d.h. sowohl Zeilen- als auch Spaltenweise ist es die Struktur eines Kreises). (Das hat natürlich nicht mehr viel mit Überlauf in der Informatik zu tun, insofern war der Begriff missverständlich und doppelt belegt.)

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Mit Modulo Rechnen geht natürlich auf wenn das Feld von 0..Anzahl-1 geht kannst Du leicht mod Anzahl rechnen.Das ist aber sehr langwierig, also lieber nur auf die Felder beschränken, die es Betrifft ( Zeile 1und 2 und Zeile n-1/n-2 und die entsprechenden Spalten.

Wenn es viele Quelltextstellen sind oder einige Quelltextzeilen dadurch sehr lang werden, würde ich Properties verwenden:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
// statt ..
public
  Spielfeld: Array[0..150..15of Boolean;

// mit Properties ..
private
  function GetSpielfeldAt(x,y: Integer): Boolean;
  procedure SetSpielfeldAt(x,y: Integer; Value: Boolean);
private
  FSpielfeld: Array[0..150..15of Boolean;
public
  Property Spielfeld[x,y: Integer]: Boolean read GetSpieldeldAt write SetSpielfeldAt;

// und weiter unten ..

function GetSpielfeldAt(x,y: Integer): Boolean;
begin
  x := x mod Length(Spielfeld);
  y := y mod Length(Spielfeld[x]);
  result := FSpielfeld[x,y];
end;

procedure SetSpielfeldAt(x,y: Integer; Value: Boolean);
begin
  x := x mod Length(Spielfeld);
  y := y mod Length(Spielfeld[x]);
  FSpielfeld[x,y] := Value;
end;


ub60 - Do 05.06.14 22:51

Also ich habe es mit dem Torus so gemacht. Sollte sich leicht abändern lassen.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
function TSpielfeld.ErmittleNachbarn(x, y: Integer): Byte;
var Anzahl : Byte;
    xa, xe, ya, ye : Integer;
begin
  // Array geht von 1..100, 1..100
  Anzahl:=0;
  xa:=x-1;   // eine Spalte links
  xe:=x+1;   // eine Spalte rechts
  ya:=y-1;   // eine Zeile hoch
  ye:=y+1;   // eine Zeile runter
  if xa=0    // links von 1 geht zum rechten Rand
    then xa:=100
    else if xe>100 // rechts von 100 geht zum linken Rand
      then xe:=1;
  if ya=0
    then ya:=100
    else if ye>100
      then ye:=1;
  Anzahl:=Anzahl+EArray[xa, ya]; // Feld links darüber
  Anzahl:=Anzahl+EArray[x,  ya]; // Feld direkt darüber
  Anzahl:=Anzahl+EArray[xe, ya]; // Feld rechts darüber

  Anzahl:=Anzahl+EArray[xa, y ]; // Feld links daneben
  Anzahl:=Anzahl+EArray[xe, y ]; // Feld rechts daneben

  Anzahl:=Anzahl+EArray[xa, ye]; // Feld links darunter
  Anzahl:=Anzahl+EArray[x,  ye]; // Feld direkt darunter
  Anzahl:=Anzahl+EArray[xe, ye]; // Feld rechts darunter
  result:=Anzahl;
end;


ub60


galagher - Fr 06.06.14 17:32

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
kopiere statt des toten Randes die Spalten vom jeweils gegenüberliegendem Ende und anschliessend die Zeilen

Diesen Vorschlag könnte ich eventuell realisieren, bei den anderen steige ich nicht durch!

Also habe ich es testweise so versucht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
  for x := -1 to 97 do
    for y := -1 to 97 do
    begin
      if (aBoard[-1, y].clCell = clLivingCell) then
        atmpBoard[96, y].clCell := clLivingCell;
      //...
    end;

Das Ergebnis: links und rechts am Rand jeweils ein paar lebende Zellen, aber nicht in der Anordnung, in der sie am linken Rand verschwunden waren. ZB. wenn ein "Segler" über den linken Rand hinauswandert, tauchen rechts dann drei Zellen auf und links bleiben zwei. Das war's...


Hidden - Fr 06.06.14 18:02

Hallo user profile icongalagher, :)

wenn dein Array über [0..960..96] indiziert ist, solltest du für einen Torus auch diese Grenzen abfahren. (:?:) Den Rand (-1 und 97) gab es ja nur für das "ummauerte" Rechteck.

Mit dem Quelltextstück kann ich ansonsten nicht viel anfangen, da ist ja noch ein //... drin; ist das schon vollständig?

viele Grüße,
Daniel

PS: Hat der Ansatz mod Seitenlaenge nicht geklappt?


galagher - Fr 06.06.14 18:36

user profile iconHidden hat folgendes geschrieben Zum zitierten Posting springen:
Mit dem Quelltextstück kann ich ansonsten nicht viel anfangen, da ist ja noch ein //... drin; ist das schon vollständig?
Nein, da kommen dann noch die Regeln wie in http://de.wikipedia.org/wiki/Game_of_Life#Conways_Herausforderung beschrieben, also im Wesentlichen:
if (aBoard[x, y].clCell = clDeathCell) then und if (aBoard[x, y].clCell = clLivingCell) then.
Der Code ist noch ziemlich unkultiviert, muss ich noch optimieren. Er funktioniert aber korrekt.

user profile iconHidden hat folgendes geschrieben Zum zitierten Posting springen:
PS: Hat der Ansatz mod Seitenlaenge nicht geklappt?
Keine Ahnung, wie ich das machen soll! Wo muss der mod-Code hin und bleiben for x := 0 to 96 und for y := 0 to 96 so bestehen?
Und noch eine Frage: Eine Kugelform ist ja unbegenzt, muss die for-Schleife dann noch in einem Timer laufen?


Hidden - Fr 06.06.14 20:12

Angenommen, dein berandetes Spielfeld [-1..16, -1..16] wurde für den Übergang zur nächsten Generation wie folgt behandelt:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
for i := 0 to 15 do begin
  for j := 0 to 15 do
    Propagate(i,j);
end;

// ..

procedure Propagate(x,y: Integer);
begin
  // hier Aktion für diese Zelle einfügen,
  // in neues Array kopieren,
  // oder was sonst noch passieren muss.
  // zum Beispiel:
  if Spielfeld[x + 1, y + 1] = foo then
    bar;
end;

Das zugehörige Torus-Spielfeld wäre dann [0..150..15] (der Rand ist nicht mehr nötig), und der Übergang zur nächsten Generation würde sich wie folgt verändern:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
for i := 0 to 15 do begin
  for j := 0 to 15 do
    Propagate(i,j);
end;

// ..

procedure Propagate(x,y: Integer);
begin
  if Spielfeld[  (x + 1mod 16, (y + 1mod 16  ] = foo then
    bar;
end;

Also überall dort, wo Spielfeld[{..}{..}] vorkommt, dieses durch Spielfeld[  ({..}mod (a+1), ({..}mod (b+1)  ] ersetzen wenn dein Array über [0..a, 0..b] indiziert ist.

Der Aufruf mod 16 zieht so lange 16 vom Ausgangswert ab, bis das Ergebnis kleiner ist als 16.
Für negative Ausgangswerte wird 16 addiert statt subtrahiert, und zwar so lange bis das Ergebnis >= 0 ist.
Beispiel:


Quelltext
1:
2:
3:
4:
5:
6:
7:
 0   mod 16 =  0
11   mod 16 = 11
15   mod 16 = 15
16   mod 16 =  0 (rechts vom rechten Rand ist linker Rand)
(-1) mod 16 = 15 (links vom linken Rand is rechter Rand)          <- Das müsstest du mal testen, ich habe gerade kein Delphi da. Es gibt Programmiersprachen, die hier -1 ausgeben, das ist mathematisch falsch und wäre hier fatal
17   mod 16 =  2
46   mod 16 = 14


catweasel - Fr 06.06.14 20:42

Hi,

Ich habe mir das hier mal durchgelesen und verstehe nicht das Problem dabei.
Der Modulo ist schon der richtige Weg. Ich sehe einfach nicht wo Probleme mit "Rändern" entstehen können..
Ich habe es bei meinem Civilazatio Klon auch so gemacht (da kann man ja auf der Karte auch ewig in eine Richtung scrollen...)

Hier mal ein kleiner Auszug:
(Anmerkung: TIntegerStreamClass ist eine Klasse die nur einen Integerwert published (Data) und diesen Streamen kann, sehr simpel)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
type
  TMapData = array of array of TMapCellClass;

type
  TMapClass=class(TObject)
  private
    // Anderes Zeug
    FColCount : TIntegerStreamClass;
    FRowCount : TIntegerStreamClass;
    FCells : TMapData;
    function GetCells(X, Y: integer): TMapCellClass;
    procedure SetCells(X, Y: integer; const Value: TMapCellClass);
    // Noch mehr Zeug
  public 
     property Cells[X,Y:integer]:TMapCellClass read GetCells write SetCells;
  end;

function TMapClass.GetCells(X, Y: integer): TMapCellClass;
begin
Result := FCells[X mod FColCount.Data,Y mod FRowCount.Data];
end;

procedure TMapClass.SetCells(X, Y: integer; const Value: TMapCellClass);
begin
FCells[X mod FColCount.Data,Y mod FRowCount.Data] := Value;
end;


und da geht auch nichts schief im Bezug auf Ränder..
Wenn man jetzt den linken Nachbarn eines Feldes aus der ersten Spalte (0/n) sucht dann muss man lediglich dafür sorgen das (-m/n) in (max-m/n) umgewandelt werden muss. DAs selbe natürlich auch bei den Zeilen..

also muss oben in die Methoden als Erstes noch ein

Delphi-Quelltext
1:
2:
if X < 0 then X := FColcount+X;    // Addieren weil der Wert ja schon negativ ist :)
if Y < 0 then Y := FRowcount+Y;

rein und das sollte doch funktionieren.

Also entweder habe ich jetzt komplett an der Frage vorbei gelesen, oder es sollte doch nicht so schwierig sein ..??


Bei genauerem Hinsehen:

>>Den Randfall hast du sicherlich schon abgefangen, damit du nicht auf Felder außerhalb deiner Arrays zugreifst.
Ja, und zwar, indem das Array an den Rändern um jeweils 1 grösser ist, als der Bereich, der gezeichnet wird

Und da machst du es unnötig kompliziert. Wenn du die MOD Varinte nimmst greifst du die Randfälle automatisch mit ab.
Dan verschwinden auch keine Zellen mehr. Und: selbst wenn due es irgendwie hinbekommst mit dem unsichtbaren Rand, so müssten diese Randzellen das Kunststück vollbringen beim Queren des Randes drei zellen weit zu springen (beide Ränder und hin zur Zielzelle).
Da Zellen aber eben nicht wnadern sondern bleiben wo sie sind und nur leben oder sterben, propagiert diese Wechselwirkung ncht über die Ränder......
Hab mein erstes WATOR auch erst versucht so zu bauen wie du.... und daraus gelernt :)


Cheers,
Catweasel


galagher - Fr 06.06.14 21:01

user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:
Also entweder habe ich jetzt komplett an der Frage vorbei gelesen, oder es sollte doch nicht so schwierig sein ..??

Ich blicke einfach nicht durch...
Hier die komplette Prozedur. Wo muss was geändert werden, um einen Torus zu erhalten?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
procedure TForm1.Timer1Timer(Sender: TObject);
var
  i, n, x, y: Integer;
  b: Boolean;
  atmpBoard: array [-1..97, -1..97of record
    X, Y: Integer;
    clCell: TColor;
  end;
begin
  {Automatischer Abbruch, wenn MaxInt erreicht ist}
  if iGeneration = MaxInt then
  begin
    Button4Click(Sender);
    exit;
  end;

  Inc(iGeneration);
  Label1.Caption := IntToStr(iGeneration)+'. Generation';

  {Temporäres Array mit toten Zellen füllen}
  for x := -1 to 97 do
    for y := -1 to 97 do
    begin
      atmpBoard[x, y].clCell := clDeathCell;
    end;

  for x := 0 to 96 do
    for y := 0 to 96 do
    begin
      if (x-1 = -2or (y-1 = -2or (x+1 = 97or (y+1 = 97then
        atmpBoard[x, y].clCell := clDeathCell
      else
      begin  
        {Eine tote Zelle mit genau drei lebenden Nachbarn
         wird in der Folgegeneration neu geboren.}

        if (aBoard[x, y].clCell = clDeathCell) then
        begin
          i := 0;
          if (aBoard[x-1, y].clCell = clLivingCell) then Inc(i);
          if (aBoard[x+1, y].clCell = clLivingCell) then Inc(i);
          if (aBoard[x, y-1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x, y+1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x-1, y-1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x+1, y+1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x-1, y+1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x+1, y-1].clCell = clLivingCell) then Inc(i);

          if i = 3 then
          begin
            atmpBoard[x, y].clCell := clLivingCell;
          end
          else
          begin
            atmpBoard[x, y].clCell := clDeathCell;
           end;
        end;

        {Lebende Zellen mit weniger als zwei lebenden Nachbarn
         sterben in der Folgegeneration an Einsamkeit.}

        if (aBoard[x, y].clCell = clLivingCell) then
        begin
          i := 8;
          if (aBoard[x-1, y].clCell = clDeathCell) then Dec(i);
          if (aBoard[x+1, y].clCell = clDeathCell) then Dec(i);
          if (aBoard[x, y-1].clCell = clDeathCell) then Dec(i);
          if (aBoard[x, y+1].clCell = clDeathCell) then Dec(i);
          if (aBoard[x-1, y-1].clCell = clDeathCell) then Dec(i);
          if (aBoard[x+1, y+1].clCell = clDeathCell) then Dec(i);
          if (aBoard[x-1, y+1].clCell = clDeathCell) then Dec(i);
          if (aBoard[x+1, y-1].clCell = clDeathCell) then Dec(i);

          if i < 2 then
          begin
            atmpBoard[x, y].clCell := clDeathCell;
          end
          else
          begin
            atmpBoard[x, y].clCell := clLivingCell;
          end;

          {Eine lebende Zelle mit zwei oder drei lebenden
           Nachbarn bleibt in der Folgegeneration am Leben}

          i := 0;
          if (aBoard[x-1, y].clCell = clLivingCell) then Inc(i);
          if (aBoard[x+1, y].clCell = clLivingCell) then Inc(i);
          if (aBoard[x, y-1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x, y+1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x-1, y-1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x+1, y+1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x-1, y+1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x+1, y-1].clCell = clLivingCell) then Inc(i);

          if (i = 2or (i = 3then
          begin
            atmpBoard[x, y].clCell := clLivingCell;
          end
          else
          {Lebende Zellen mit mehr als drei lebenden Nachbarn
           sterben in der Folgegeneration an Überbevölkerung.}

          if i > 3 then
          begin
            atmpBoard[x, y].clCell := clDeathCell;
          end;
        end;
      end;  {else}
    end;  {for}


  {Die Ränder (-1 und 97) sind immer tote Zellen}
  for x := -1 to 97 do
    for y := -1 to 97 do
    begin
      if (x = -2or (y = -2or (x = 97or (y = 97then
        atmpBoard[x, y].clCell := clDeathCell;
    end;

  b := False;


  {Prüfen, ob es noch sich bewegende Zellen gibt oder ob die Welt erstarrt ist}
  i := 0;
  for x := 0 to 96 do
    for y := 0 to 96 do
    begin
      if atmpBoard[x, y].clCell = aBoard[x, y].clCell then
        Inc(i);
    end;

    if i = 9409 then
    begin
      Label1.Caption := 'Nach der '+IntToStr(iGeneration)+'. Generation erstarrt';
      iGeneration := 0;
      Timer1.Enabled := False;
      Button3.Enabled := False;
      Button4.Caption := '&Löschen';
    end;


  {Nach aBoard zurückschreiben, ...}
  n := 0;
  for x := -1 to 97 do
    for y := -1 to 97 do
    begin
      if atmpBoard[x, y].clCell = clDeathCell then
        aBoard[x, y].clCell := clDeathCell
      else
      begin
        aBoard[x, y].clCell := clLivingCell;
        b := True;
        Inc(n);  {... lebende Zellen zählen ...}
      end;
    end;
  Label2.Caption := IntToStr(n)+' lebende Zellen';

  {... und schiesslich zeichnen}
  DrawBoard(False);


  if not b then
  begin
    Label1.Caption := 'Nach der '+IntToStr(iGeneration)+'. Generation ausgestorben';
    iGeneration := 0;
    Timer1.Enabled := False;
    Button3.Enabled := False;
    Button4.Caption := '&Löschen';
  end;


catweasel - Fr 06.06.14 21:13

Alllso, der Quellcode ist ja ganz schön unübersichtlich :)

Warum zum Beispiel ist:

if i = 9409 then ...
Was ist das für eine magische Zahl und warum gerade 9409?

Wofür benötigst du ein temporäres Array mit toten Zellen?


Wie wäre es mit folgendem Ansatz:

Zwei Arrays A und B (oder meinetwegen auch Carl und Lenny).

Und das ganz geht dann so:
- zähle nachbarn in A, schreibe in B
- zeige B an
- zähle nachbarn in B, schreibe in A
- zeige A an.

Sozusagen ein double buffering....

.. und wenn A=B dann ist die welt eingefroren...

Ich hab gerade mal ein bischen Zeit, ich bastel mal was kleines.
Ungefähr ne Stunde oder zwei, dann antworte ich hier nochmal :)

CU
Catweasel


Hidden - Fr 06.06.14 21:23

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Wo muss was geändert werden, um einen Torus zu erhalten?



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
procedure TForm1.Timer1Timer(Sender: TObject);
var
  i, n, x, y: Integer;
  b: Boolean;
  atmpBoard: array [-1..97, -1..97of record  // hier: Array verkleinern, Rand wegschmeißen
    X, Y: Integer;
    clCell: TColor;
  end;
begin
  {Automatischer Abbruch, wenn MaxInt erreicht ist}
  if iGeneration = MaxInt then
  begin
    Button4Click(Sender);
    exit;
  end;

  Inc(iGeneration);
  Label1.Caption := IntToStr(iGeneration)+'. Generation';

  {Temporäres Array mit toten Zellen füllen}
  for x := -1 to 97 do    // hier: Array wurde verkleinert, 
    for y := -1 to 97 do  // Schleifen anpassen
    begin
      atmpBoard[x, y].clCell := clDeathCell;
    end;

  for x := 0 to 96 do    // das ist die Schleife,
    for y := 0 to 96 do  // die in meinem letzten Post erwähnt war


    // überall, wo in dieser Schleife auf aBoard[] oder atmpBoard[] zugegriffen wird,
    // sollten die Koordinaten vorher mod 97 gerechnet werden
    // der Kürper dieser Schleife wäre in meinem Beispiel die Prozedur Propegate


    begin
//      if (x-1 = -2) or (y-1 = -2) or (x+1 = 97) or (y+1 = 97) then
//        // das heißt also x=-1 oder y=-1 oder x=96 oder y=96
//        atmpBoard[x, y].clCell := clDeathCell  // die ersten beiden Bedingungen treten nie ein..
//                                               // da x=0..96, -1 wird nie erreicht.
//                                               // Die 97 sollte wohl eine 98 sein?
//                                               // kann jedenfalls komplett raus, da kein Rand mehr vorhanden
//                                               // alle Zellen gehören zum Inneren des Feldes
//      else
//      begin  
        {Eine tote Zelle mit genau drei lebenden Nachbarn
         wird in der Folgegeneration neu geboren.}

        if (aBoard[x, y].clCell = clDeathCell) then
        begin
          i := 0;
          if (aBoard[x-1, y].clCell = clLivingCell) then Inc(i);
          if (aBoard[x+1, y].clCell = clLivingCell) then Inc(i);
          if (aBoard[x, y-1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x, y+1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x-1, y-1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x+1, y+1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x-1, y+1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x+1, y-1].clCell = clLivingCell) then Inc(i);

          if i = 3 then
          begin
            atmpBoard[x, y].clCell := clLivingCell;
          end
          else
          begin
            atmpBoard[x, y].clCell := clDeathCell;
           end;
        end;

        {Lebende Zellen mit weniger als zwei lebenden Nachbarn
         sterben in der Folgegeneration an Einsamkeit.}

        if (aBoard[x, y].clCell = clLivingCell) then
        begin
          i := 8;
          if (aBoard[x-1, y].clCell = clDeathCell) then Dec(i);
          if (aBoard[x+1, y].clCell = clDeathCell) then Dec(i);
          if (aBoard[x, y-1].clCell = clDeathCell) then Dec(i);
          if (aBoard[x, y+1].clCell = clDeathCell) then Dec(i);
          if (aBoard[x-1, y-1].clCell = clDeathCell) then Dec(i);
          if (aBoard[x+1, y+1].clCell = clDeathCell) then Dec(i);
          if (aBoard[x-1, y+1].clCell = clDeathCell) then Dec(i);
          if (aBoard[x+1, y-1].clCell = clDeathCell) then Dec(i);

          if i < 2 then
          begin
            atmpBoard[x, y].clCell := clDeathCell;
          end
          else
          begin
            atmpBoard[x, y].clCell := clLivingCell;
          end;

          {Eine lebende Zelle mit zwei oder drei lebenden
           Nachbarn bleibt in der Folgegeneration am Leben}

          i := 0;
          if (aBoard[x-1, y].clCell = clLivingCell) then Inc(i);
          if (aBoard[x+1, y].clCell = clLivingCell) then Inc(i);
          if (aBoard[x, y-1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x, y+1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x-1, y-1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x+1, y+1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x-1, y+1].clCell = clLivingCell) then Inc(i);
          if (aBoard[x+1, y-1].clCell = clLivingCell) then Inc(i);

          if (i = 2or (i = 3then
          begin
            atmpBoard[x, y].clCell := clLivingCell;
          end
          else
          {Lebende Zellen mit mehr als drei lebenden Nachbarn
           sterben in der Folgegeneration an Überbevölkerung.}

          if i > 3 then
          begin
            atmpBoard[x, y].clCell := clDeathCell;
          end;
        end;
//      end;  {else}
    end;  {for}


  {Die Ränder (-1 und 97) sind immer tote Zellen}
  for x := -1 to 97 do
    for y := -1 to 97 do
    begin
      if (x = -2or (y = -2or (x = 97or (y = 97then
        atmpBoard[x, y].clCell := clDeathCell;
    end;

  b := False;


  {Prüfen, ob es noch sich bewegende Zellen gibt oder ob die Welt erstarrt ist}
  i := 0;
  for x := 0 to 96 do
    for y := 0 to 96 do
    begin
      if atmpBoard[x, y].clCell = aBoard[x, y].clCell then
        Inc(i);
    end;

    if i = 9409 then
    begin
      Label1.Caption := 'Nach der '+IntToStr(iGeneration)+'. Generation erstarrt';
      iGeneration := 0;
      Timer1.Enabled := False;
      Button3.Enabled := False;
      Button4.Caption := '&Löschen';
    end;


  {Nach aBoard zurückschreiben, ...}
  n := 0;
  for x := -1 to 97 do
    for y := -1 to 97 do
    begin
      if atmpBoard[x, y].clCell = clDeathCell then
        aBoard[x, y].clCell := clDeathCell
      else
      begin
        aBoard[x, y].clCell := clLivingCell;
        b := True;
        Inc(n);  {... lebende Zellen zählen ...}
      end;
    end;
  Label2.Caption := IntToStr(n)+' lebende Zellen';

  {... und schiesslich zeichnen}
  DrawBoard(False);


  if not b then
  begin
    Label1.Caption := 'Nach der '+IntToStr(iGeneration)+'. Generation ausgestorben';
    iGeneration := 0;
    Timer1.Enabled := False;
    Button3.Enabled := False;
    Button4.Caption := '&Löschen';
  end;


catweasel - Fr 06.06.14 23:49

Hi,

Da bin ich wieder. Hat 2,5 statt 2 Stunden gedauert, weil zwischendurch das Telefon ging :)

Ich hab mal ein kleines Conway game of life gebastelt und du kannst dir mal anschauen wie das mit den Koordinaten und der (alternierenden) Darstellung gelöst ist. (Hb soweit keine bugs gefunden).
Der Quellcode ist nicht kommentiert, aber auch nicht sonderlich schwierig...
Wenn Fragen? - Dann Fragen! :D

Ahja: Das Startmuster einfach in die Welt zeichnen.

Cheers,
Catweasel

PS:
Doch einen kleinen Bug entdeckt: Die eingestellte Weltgröße wird erst am dem "Start" übernomen. (Ich müsste WorldA.SetSize() noch in die UpDown.Change events packen)...
Workaround: Width und Height einstellen. Auf Start und dann wieder auf Stopp drücken. Schon kann man auf einer grösseren Welt zeichnen.
Übrigens: Das Zeichnene geht auch während der "Evolution"...


PPS: Gerade kommt mir die Idee durch den Kopf ein Conway GOL basierend auf einem Hex-Grid zu basteln.
Da gibt es nur 6 Nachbarn. Wer weiss welche Regeln man da aufstellen kann und wie sich so eine Welt verhält....
Meint ihr das könnte interessant sein??


galagher - Sa 07.06.14 07:17

user profile iconHidden hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
    // überall, wo in dieser Schleife auf aBoard[] oder atmpBoard[] zugegriffen wird,
    // sollten die Koordinaten vorher mod 97 gerechnet werden
    // der Kürper dieser Schleife wäre in meinem Beispiel die Prozedur Propegate

Wie und wo und wann vorher? :eyecrazy:

user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:
Der Quellcode ist nicht kommentiert, aber auch nicht sonderlich schwierig...

Jedenfalls ist er völlig anders als mein Code!


Jann1k - Sa 07.06.14 10:33

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconHidden hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
    // überall, wo in dieser Schleife auf aBoard[] oder atmpBoard[] zugegriffen wird,
    // sollten die Koordinaten vorher mod 97 gerechnet werden
    // der Kürper dieser Schleife wäre in meinem Beispiel die Prozedur Propegate

Wie und wo und wann vorher? :eyecrazy:


Also irgendwie hakt es ja ganz schön bei dir. Hidden möchte nur, dass du jedesmal aus atmpBoard[x, y] ein atmpBoard[x mod 97, y mod 97] machst. Durch die modulo Funktion bleiben die Indices immer im Bereich [0..96] und du kriegst deinen Torus. Die Konstruktion mit dem Rand voller toter Zellen kannst du dir dann sparen.


galagher - Sa 07.06.14 11:07

user profile iconJann1k hat folgendes geschrieben Zum zitierten Posting springen:
Also irgendwie hakt es ja ganz schön bei dir. Hidden möchte nur, dass du jedesmal aus atmpBoard[x, y] ein atmpBoard[x mod 97, y mod 97] machst. Durch die modulo Funktion bleiben die Indices immer im Bereich [0..96] und du kriegst deinen Torus. Die Konstruktion mit dem Rand voller toter Zellen kannst du dir dann sparen.

Und warum habe ich das nicht längst gemacht? Geht ja! :mrgreen:

user profile iconJann1k hat folgendes geschrieben Zum zitierten Posting springen:
Also irgendwie hakt es ja ganz schön bei dir.

Jetzt nicht mehr!
Sorry, aber ich konnte (kann) mir das einfach nicht vorstellen! Ein zweidimensionales Array ist ja kein Problem, aber mit so etwas habe ich mich noch nie beschäftigt.
Jetzt funktioniert es so, wie ich mir das wünsche: Eine "Kugel"-Welt ist einfach eleganter, denn da werden Objekte am Rand nicht einfach aufgelöst, sondern treten gegenüber wieder ein - eine Kugel eben. Danke!


catweasel - Sa 07.06.14 12:27

Zitat:
user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:
Der Quellcode ist nicht kommentiert, aber auch nicht sonderlich schwierig...

Jedenfalls ist er völlig anders als mein Code!


Ja, weil der Ansatz ein Anderer ist :)
Dein Ansatz ist : Array nehmen und analysieren und Ergennisse temporär speichern und dann die Ergebnisse in das Array zurückschreiben.
Und das macht es unnötig kompliziert. Vergegenwärtige dir das das "temp" array wenn es gefüllt ist, im Prinzip schon die nächste Generation darstellt. Wenn ich WorldA berechne dient WorldA quasi als temp array für WorldB. (Geschieht bei WorldA.Process(WorldB) )

Da pro Generation nr ein Array einmal glesen/geschrieben wird ist es auch schneller als Arry -> temp und dann temp -> array.

Die Sache mit den Koordinaten und dem Torus steckt in TWorldClass.Transform() die dann von TWorldClass.CountNeighbors() aufegrufen wird.
Da steckt der ganze Gag drin :)

Aber wenn du willst kann ich gerne den Rest des Codes kommentieren...
Wie du siehst ist wes wirklich simpler. Der ganze Quellcode ist weniger als deine TimerRoutine...

Evtl. könntest du auch die Timer Prozedur in kleinere Subroutinen zerlegen. Das würde die Übersichtlichkeit erhöhen.
Ich persönlich vermeide Methodenkörper mit mehr als 30 Zeilen.

Ist jetzt wirklich nur aus Neugier gefragt: Wie lange programmierst du schon mit Delphi?

Cheers,
Catweasel


galagher - Sa 07.06.14 13:57

user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:
Wenn ich WorldA berechne dient WorldA quasi als temp array für WorldB. (Geschieht bei WorldA.Process(WorldB) )

Da pro Generation nr ein Array einmal glesen/geschrieben wird ist es auch schneller als Arry -> temp und dann temp -> array.

Naja, jetzt läuft es und ich belasse es so!

user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:
Evtl. könntest du auch die Timer Prozedur in kleinere Subroutinen zerlegen. Das würde die Übersichtlichkeit erhöhen.

Ist sicher sinnvoll und werde ich auch umsetzen.

user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:
Ist jetzt wirklich nur aus Neugier gefragt: Wie lange programmierst du schon mit Delphi?

Schon etliche Jahre, aber ich habe nur wenige Spiele programmiert. Und über ein torusförmiges Array habe ich mir noch nie Gedanken gemacht, ich brauchte es schlicht nicht.

lg
galagher


catweasel - Sa 07.06.14 14:25

Zitat:
Naja, jetzt läuft es und ich belasse es so!


Ja klar. Wenns einaml fertig ist :D
Ich wollte dir eigentlich nur aufzeigen wie der objektorientierte Ansatz hier die Aufgabenstellung radikal vereinfachen kann. :beer:
Mann müsste noch nichtmal Objekte nehmen. Das ganze ginge auch mit ein paar globalen Variablen....
Mal so gefragt: Wenn du das nochmal programmieren müsstest, würdest du wieder den gleichen Ansatz wählen?

Cheers,
Catweasel


galagher - So 08.06.14 09:41

user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:
Mal so gefragt: Wenn du das nochmal programmieren müsstest, würdest du wieder den gleichen Ansatz wählen?

Ja, würde ich. Es fällt mir ganz einfach leichter, ein quadratisches oder rechteckiges Spielfeld, das ich am Bildschirm ja sehen kann, im Kopf in ein zweidimensionales Array umzurechnen und umgekehrt.
Was ich mir nicht vorstellen konnte, war das Verbinden der Ränder. Aber dazu muss ich sagen, dass ich Mathematik seit der Schulzeit nicht mag. War immer der blanke Horror! :hair:


Mathematiker - So 08.06.14 10:47

Hallo,
user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Aber dazu muss ich sagen, dass ich Mathematik seit der Schulzeit nicht mag. War immer der blanke Horror! :hair:

Richtig! :zustimm:

Augustinus: "Der gute Christ soll sich hüten vor den Mathematikern und all denen, die leere Voraussagen zu machen pflegen, schon gar dann, wenn diese Vorhersagen zutreffen. Es besteht nämlich die Gefahr, dass die Mathematiker mit dem Teufel im Bunde den Geist trüben und in die Bande der Hölle verstricken."

Paul Epstein: "Die Mehrheit bringt der Mathematik Gefühle entgegen, wie sie nach Aristoteles durch die Tragödie geweckt werden sollen, nämlich Mitleid und Furcht. Mitleid mit denen, die sich mit der Mathematik plagen müssen, und Furcht, dass man selbst einmal in diese gefährliche Lage geraten könne."

Robert Niemann: "Mathematik ist nichts für den Wühltisch - Mathematik ist etwas für eine Handvoll Erleuchtete. Und das soll auch so bleiben."

In dem Sinne. Lass dich nicht von der Mathematik ärgern. Als Programmierer kann man darauf sicher verzichten. :mrgreen:

Beste Grüße
Mathematiker


catweasel - So 08.06.14 12:31

Diese Männer habane natürlich völlig Recht :P
Mthematik ist natürlich nicht nur Teufelszeug (Zubersprüche des Herrn Binomi), sondern erfüllt sogar alle hinreichenden Bedingungen für total nerdige Freakscheisse :lol:
Dafür können Mthematiker ber sehr gut Löwen fangen...

(Bei meiner Version arbeit ich auch mit 2D arrays, ich meinte der Ansatz mit dem Double buffering)

Cheers,
Catweasel


Mathematiker - So 08.06.14 22:00

Hallo,
user profile iconcatweasel hat folgendes geschrieben Zum zitierten Posting springen:
Mthematik ... erfüllt sogar alle hinreichenden Bedingungen für total nerdige Freakscheisse

Das ist nicht mehr "lustig"! :puke:

Mathematiker


catweasel - Mo 09.06.14 02:18

Leider bekomme ich das immer zu hören wenn ich mit Mathematik anfange ... :cry:
:wink:

Catweasel


Horst_H - Sa 14.06.14 09:35

Hallo,

ich habe ein wenig an der "Berechnung" herumgeschraubt.
Meine Uralt Version mit 3 _Zeilen als Zwischenspeicher ist heutzutage nicht mehr schnell :-(
Ich habe user profile iconcatweasel Einwand, gegen das unnötige Kopieren beherzigt, aber schöner wird es durch Zeiger auf die Boards nicht.
Tatsächlich braucht es bei mir ohne Ausgabe pro Koordinatenpunkt nur 11 CPU-Takte.
Wie immer gilt, Entscheidungen fallen auch einer CPU schwer ;-) , also mittels cSurvives die möglichen Werte vorgeben.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
Program Gol;
// Game of life als gol.dpr speichern
{$IFDEF FPC}
  {$Mode delphi}
{$ELSE}
  {$Apptype Console}
{$ENDIF}
uses
  sysutils,crt;

const
  colMax = 79;
  rowMax = 23;
//Feld um eine Zeile,Spalte  davor und dahinter erweitern,
//das laesst sich einfeacher bearbeiten, ohne Sonderregeln für den Rand
type
  tFeld = byte;
  tRow = array[0..colMax+1of tFeld;//0..1 oder 0..3 /sum
  tprow = ^tRow;
  tBoard = array[0..rowMax+1of tRow;
  tpBoard = ^tBoard;
  tpBoards = array[0..1of tpBoard;
const
  dr = sizeOf(trow);
type
  tIntArr = array[0..2*dr+2of tFeld;
  tpIntArr = ^tIntArr;

var
  aBoard,bBoard : tBoard;
  pBoards :tpBoards;
  gblAktBoard : integer;

  gblGenCount : integer;

function Survive(p: tpIntArr):byte;
//p zeigt auf aktuelles Board adresse [row-1,col-1]
const
  cSurvives : array[boolean,0..8of integer=
              //0,1,2,3,4,5,6,7,8     Nachbarn
              ((0,0,0,1,0,0,0,0,0),   {alive =false}
               (0,0,1,1,0,0,0,0,0));  {alive =true}
var
  sum : integer;
begin
  // Zeile darueber
  // sum := byte(aBoard[row-1,col-1])+byte(aBoard[row-1,col])+byte(aBoard[row-1,col+1]);
  sum :=      integer(p[     0])+integer(p[    1]) +integer(p[     2]);
  sum := sum+ integer(p[  dr+0])                   +integer(p[  dr+2]);
  sum := sum +integer(p[2*dr+0])+integer(p[2*dr+1])+integer(p[2*dr+2]);

  survive := cSurvives[p[dr+1]<>0,sum];
end;

procedure Raender;
var
  p0 : tpIntArr;
  row: integer;
begin
  //Spalten ergaenzen
  p0 := @pBoards[gblAktBoard]^[1,0];
  For row := 1 to rowMax do
    begin
    p0^[0] := p0^[colMax];
    p0^[colmax+1] := p0^[1];
    p0 := Pointer(PtrUint(p0)+SizeOf(tRow));
    end;
  //oberste Zeile 1 nach unten rowMax+1 kopieren
  move(pBoards[gblAktBoard]^[1,0],pBoards[gblAktBoard]^[rowMax+1,0],sizeof(trow));
  //unterste Zeile rowMax nach oben 0 kopieren
  move(pBoards[gblAktBoard]^[rowMax,0],pBoards[gblAktBoard]^[0,0],sizeof(trow));
end;

procedure NextGen;
var
  p0,p1 : tpIntArr;
  col,row: integer;
begin
  Raender;
  For row := 1 to rowMax do
  begin
    p0 := @pBoards[gblAktBoard]^[row-1,0];
    p1 := @pBoards[1-gblAktBoard]^[row,0];
    For col := 1 to colMax do
      p1[col] := Survive(@p0[col-1]);
  end;
  //aBoard := bBoard;
  gblAktBoard :=1-gblAktBoard;
  inc(gblGenCount);
end;

procedure PrintGen;
const
  cChar: array[0..1of char = (' ','#');
var
  p0 : tpIntArr;
  col,row: integer;
  s : string;
begin
  setlength(s,colmax);
  gotoxy(1,1);
  writeln(gblGenCount:10);
  For row := 1 to rowMax do
  begin
    p0 := @pBoards[gblAktBoard]^[row,0];;
    For col := 1 to colMax do
      s[col] := cChar[p0[col]];
    writeln(s);
  end;
end;

procedure Init;
var
  col,row : integer;
begin
  randseed := 1;
  clrscr;
  fillchar(aBoard,SizeOf(aBoard),#0);
  pBoards[0] := @aBoard;
  pBoards[1] := @bBoard;
  For row := 1 to rowMax do
    For col := 1 to colMax do
      aBoard[row,col]:= Byte(random>0.9);
  gblAktBoard := 0;
end;

var
  cnt : integer;
  T1,T0 :TDateTime;
begin
  Init;
  cnt := 100000;
  T0 := Time;
  repeat
    PrintGen;
    NextGen;
    dec(cnt);
  until (cnt <= 0OR keypressed;
  // (Leerschleife also nur dec(cnt) etwa 24 ms ~ 840 CPU-Takte
  T1 := Time;
  Writeln((T1-t0)*86400*1000:0:3,' ms');
end.


Wie so oft ist die Ausgabe die Bremse schlechthin.
Es lohnt sich also bei grafischer Ausgabe, zwischen den beiden Boards zu vergleichen und nur die Unterschiede auszugeben.
Bei sehr großen Boards könnte sogar die Bestimmung von aktiven Lebenszonen etwas bringen.
Gruß Horst


catweasel - So 15.06.14 22:56

Zitat:
Wie so oft ist die Ausgabe die Bremse schlechthin.
Es lohnt sich also bei grafischer Ausgabe, zwischen den beiden Boards zu vergleichen und nur die Unterschiede auszugeben.
Bei sehr großen Boards könnte sogar die Bestimmung von aktiven Lebenszonen etwas bringen.


Hmm bei einer Konsolenanwendung weiss ich nicht ob man da noch etwas optimieren kann, evtl Textausgabe per Assembler, anstelle von writeln. Müsste dann irgendwas mit MOV und dem guten alten INT 21h sein :)

Ich hatte es ja mit einer VCL Komponente gelöst.
Ich habe festgestellt das hier das OnPaint eines TDrawGrid schnell genug ist. Vorteil ist auch das sich die Zellengröße leicht anpassen kann, man einzele Zellen schön über SelectCell ansprchbar machen kann. Die Scrollbars für große Felder gibts auch gratis dazu; und dann werden auch nir die sichtbaren Zellen gezeichnet.

Das mit der aktiven Lebenszone kann schwierig werden. Ein Glider zum beispiel schleppt seine Zone immer mit sich rum. Einen Test in der Umgebung von zwei Zellen zu machen (Glider trifft auf Rand, die Zelle auf der anderen Seite der Randzelle muss auch gestet werden, deshalb zwei), ist aufwändiger als jede Zelle zu testen.

Wenn ich ein Array darstellen will ist das bei mir die Standardlösung, zumal ja die ACol und ARow Variablen sich gleich bequem als Arrayindizes einsetzen lassen (ausser man hat Fixed Rows/Cols).

Cheers,
Catweasel


Fiete - Di 17.06.14 11:23

Moin galagher,
vielleicht helfen diese Anregungen
http://www.entwickler-ecke.de/viewtopic.php?t=82838&highlight=life+fiete

Gruß Fiete


catweasel - Di 17.06.14 12:22

Jau. :)
Wenn man mit den Lebensregeln herumspielt kommen interessante Dinge raus.
Versuchmal : Bei ungeraden Nachbarn stirbt die Zelle, bei geraden Nachbarn lebt sie (nachbarn MOD 2 // 0 =leben; 1=tot)
Dann erhältst du die "Kopierwelt"... Zeichne ein Smiley oder sonstwas. Es werden dann Kopien darum erstellt, usw...

Cheers,
Ctweasel


galagher - So 29.06.14 20:21

Hallo!

War auf Urlaub ohne Laptop (!) und antworte daher erst jetzt.
Danke für die Tipps, ich werde das eine oder andere eventuell noch einbauen!