Beispielimplementierung Langtons Ameisen (Langton's Ants) (Delphi/Pascal)

{#############################################################################
# Unit zu Langton's Ants - im Programm unter Ameise                          #
#############################################################################}

unit Unit_Ameise;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Grids, StdCtrls, Buttons, ExtCtrls, RXSpin, ColorGrd, Menus;

type
  TAmeise = Record
    Col: integer;
    Row: integer;
    Sichtrichtung: integer;   //1 bis 4 entsprechend der Himmelsrichtung
    Farbe: TColor;            //Darstellungsfarbe
  end;
  TMain = class(TForm)
    Panel2: TPanel;
    SG_Feld: TStringGrid;
    Panel1: TPanel;
    L_Pause: TLabel;
    Panel3: TPanel;
    BB_Reset: TBitBtn;
    BB_Start: TBitBtn;
    BB_Stop: TBitBtn;
    Label1: TLabel;
    Label2: TLabel;
    L_Fs: TLabel;
    Label3: TLabel;
    L_Frame: TLabel;
    Label5: TLabel;
    L_Ameisen: TLabel;
    Panel4: TPanel;
    Label6: TLabel;
    RxSpinEdit1: TRxSpinEdit;
    Label7: TLabel;
    RxSE_Ameisenzahl: TRxSpinEdit;
    CB_Zufall: TCheckBox;
    MainMenu1: TMainMenu;
    Datei1: TMenuItem;
    Ameise1: TMenuItem;
    N1: TMenuItem;
    Beenden1: TMenuItem;
    Hilfe1: TMenuItem;
    Info1: TMenuItem;
    N2: TMenuItem;
    Info2: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure BB_StartClick(Sender: TObject);
    procedure BB_ResetClick(Sender: TObject);
    procedure BB_StopClick(Sender: TObject);
    procedure Ameise1Click(Sender: TObject);
    procedure RxSE_AmeisenzahlChange(Sender: TObject);
    procedure Info2Click(Sender: TObject);
    procedure Info1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Beenden1Click(Sender: TObject);
    //----
    procedure Delay(Milliseconds: Integer);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var   Main: TMain;

implementation

uses Unit_Wator;

var  //globale Definition, um die unübersichtliche Parameterübergabe zu umgehen
  Ameisen: Array [1..10] of TAmeise;  //Ameisendaten
  Ameisenzahl: integer;
  Pause: integer;                     //Delaydauer
  Stop: boolean;                      //boolean, um Simulation zu beenden
  Frame, StopFrames: integer;         //Daten zur Errechnung der F/s

{$R *.DFM}

procedure TMain.FormCreate(Sender: TObject);
begin
  Randomize;                        //initialisiert Random
  Ameisen[1].Farbe := clBlack;      //weist unterschiedliche Ameisenfarben als Standard zu
  Ameisen[2].Farbe := clMaroon;
  Ameisen[3].Farbe := clYellow;
  Ameisen[4].Farbe := clBlue;
  Ameisen[5].Farbe := clLtGray;
  Ameisen[6].Farbe := clTeal;
  Ameisen[7].Farbe := clLime;
  Ameisen[8].Farbe := clRed;
  Ameisen[9].Farbe := clFuchsia;
  Ameisen[10].Farbe := clPurple;
  Stop := true;                     //initialisiert die Stopbedingung
end;

procedure TMain.BB_StartClick(Sender: TObject); //Start der Simulation
var Rect: TRect;
    j: Integer;
    Tick: integer;
begin

  Stop := false;                      //Initialisierung/Buttons
  BB_Stop.Enabled := true;
  BB_Start.Enabled := false;
  Tick := GetTickCount;               //nimmt Zeit für F/s-Berechnung
  repeat
    Delay(Pause);                     //Unterbrechung

    for j := 1 to Ameisenzahl do      //pro Ameise bei jedem Zyklus ein Durchlauf
    begin
      //nimmt die Koorninaten der Zelle, um auf dem Formular (Grid) zu zeichnen
      Rect := SG_Feld.CellRect(Ameisen[j].Col, Ameisen[j].Row);
      //Daten ob Zelle 'schwarz' oder 'weiß' werden über Leerzeichen in Grid gespeichert
      if SG_Feld.Cells[Ameisen[j].Col, Ameisen[j].Row] = ' ' then
      begin  //Zelle Schwarz, also:
        SG_Feld.Canvas.Brush.Color := clWhite;               //Zelle färben
        SG_Feld.Cells[Ameisen[j].Col, Ameisen[j].Row] := ''; //ummarkieren
        Ameisen[j].Sichtrichtung := Ameisen[j].Sichtrichtung - 1; //Ameise dreht sich nach links
      end
      else
      begin  //anderenfalls Zelle weiß, und entgegengesetzte Operationen
        SG_Feld.Canvas.Brush.Color := Ameisen[j].Farbe;
        SG_Feld.Cells[Ameisen[j].Col, Ameisen[j].Row] := ' ';
        Ameisen[j].Sichtrichtung := Ameisen[j].Sichtrichtung + 1;
      end;

      SG_Feld.Canvas.FillRect(Rect);  //Zelle im Stringgrid umfärben

      case Ameisen[j].Sichtrichtung of      //Prüfung ob die Sichtrichtung außerhalb
        4: Ameisen[j].Sichtrichtung := 0;   //der 4 Möglichkeiten liegt, wenn ja
        -1: Ameisen[j].Sichtrichtung := 3;  //wird sie auf den entsprechenden Wert
      end;                                  //zurückgesetzt, quasi als Loop von 1 bis 4
      case Ameisen[j].Sichtrichtung of      //neue Zelle wird aus Sichtrichtung ermittelt
        0: Ameisen[j].Col := Ameisen[j].Col - 1;
        1: Ameisen[j].Row := Ameisen[j].Row - 1;
        2: Ameisen[j].Col := Ameisen[j].Col + 1;
        3: Ameisen[j].Row := Ameisen[j].Row + 1;
      end;
      //wie Sichtrichtung, jedoch jetzt als Loop über das Gitter, die Ameise befindet sich auf einem Torus; einmal für vertikal und einmal für horizontal
      case Ameisen[j].Col of
        -1: Ameisen[j].Col := SG_Feld.ColCount - 1;
        125: Ameisen[j].Col := 0;
      end;
      case Ameisen[j].Row of
        -1: Ameisen[j].Row := SG_Feld.RowCount - 1;
        125: Ameisen[j].Row := 0;
      end;

    end;

    inc(Frame);                          //Erhöhung der Berechnungszahl um 1
    L_Frame.Caption := IntToStr(Frame);  //Ausgabe der Berechnungsanzahl
    if Frame mod 100 = 0 then            //Prüfung auf Vielfache von 100, um die
    begin                                //Funktionen nicht ständig auszuführen
      Application.ProcessMessages;       //Abfangen von Nachrichten, recht Zeitintensiv
      //nötig, um Eingaben zu verarbeiten (z.Bsp: BB_StopClick), alternative wäre eine Ausführung der Procedure als Tread
      L_Fs.Caption := IntToStr (Round((Frame - StopFrames) / (((GetTickCount - Tick) + 1) / 1000))); //Ausgeben der F/s; +1 verhindert den teilweise auftretenden Fehler einer Division by Zero
    end;
  until Stop = true;                   //Durchläuft Zyklus, bis der Stop-Button gedrückt wurde

  StopFrames := Frame;  //speichert Framezahl, zur richtigen Errechnung von f/s, falls die Simulation fortgesetzt wird
end;

procedure TMain.BB_ResetClick(Sender: TObject);
var Col, Row, i: integer;
    Rect: TRect;
begin                                        //Initalisierungen
  Frame := 0;
  StopFrames := 0;
  L_Fs.Caption := '';
  L_Frame.Caption := '';
  Pause := RxSpinEdit1.AsInteger;
  L_Pause.Caption := IntToStr(Pause);
  Ameisenzahl := RxSE_Ameisenzahl.AsInteger;
  L_Ameisen.Caption := IntToStr(Ameisenzahl);

  if (Ameisenzahl = 1) and (CB_Zufall.Checked = false) then
  begin //setzt Ameise falls kein Zufallsfeld genutzt werden soll
    SG_Feld.Cells[61, 61] := '';
    Ameisen[1].Col := 61;
    Ameisen[1].Row := 61;
    Ameisen[1].Sichtrichtung := Random(4);
  end
  else  //bestimmt Zufallsfelder für die eingestellte Ameisenzahl
  begin
    for i := 1 to Ameisenzahl do
    begin
      Ameisen[i].Col := Random(SG_Feld.ColCount - 1);
      Ameisen[i].Row := Random(SG_Feld.RowCount - 1);
      Ameisen[i].Sichtrichtung := Random(4);
      SG_Feld.Cells[Ameisen[i].Col, Ameisen[i].Row] := '';
    end;
  end;

  for Col := 0 to SG_Feld.ColCount - 1 do
    for Row := 0 to SG_Feld.RowCount - 1 do
    begin
      Rect := SG_Feld.CellRect(Col, Row);     //Canvas-Koordinaten der Gridzellen
      SG_Feld.Canvas.Brush.Color := clgreen;  //Standardfarbe wird zugewiesen,

      for i := 1 to Ameisenzahl do
        if (Ameisen[i].Col = Col) and (Ameisen[i].Row = Row) then
          SG_Feld.Canvas.Brush.Color := Ameisen[i].Farbe   //Farbgebung der Ameise
        else
          SG_Feld.Cells[Col, Row] := '';  //wenn keine Ameise, Feld als 'weiß' markieren

      SG_Feld.Canvas.FillRect(Rect);      //Feld färben
    end;

  BB_Start.Enabled := True;
end;

procedure TMain.BB_StopClick(Sender: TObject);
begin
  Stop := true;               //setzt Abbruchbedingung
  BB_Stop.Enabled := false;
  BB_Start.Enabled := true;
end;

procedure TMain.Ameise1Click(Sender: TObject);
begin
  Stop := true;      //wechselt zu Wator
  Form2.Show;
  Main.Hide;
end;

procedure TMain.RxSE_AmeisenzahlChange(Sender: TObject); //enabled Zufallsfeld
begin
  if RxSE_Ameisenzahl.AsInteger > 1 then
    CB_Zufall.Enabled := false
  else
    CB_Zufall.Enabled := true;
end;

procedure TMain.Info1Click(Sender: TObject);   //Erklärung anzeigen
begin
  ShowMessage ('Ameise (Turingmaschine)' + #13 + #13 +
               'Chris Langtons Ameise ist eine Turingmaschine mit einem zweidimensionalen' + #13 +
               'Speicher, mit sehr einfachen Regeln und sehr verblüffenden Ergebnissen.' + #13 +
               'Die Ameise ist ein schönes Beispiel dafür, dass ein einfaches System' + #13 +
               'mit einfachen Regeln sowohl komplexe chaotische, als auch komplexe' + #13 +
               'geordnete Strukturen aufbauen kann, und das ganz ohne die Verwendung' + #13 +
               'des Zufalls.' + #13 +
               'Eine Ameise sitzt auf einem großen weißen, quadratisch Gitter' + #13 +
               'und blickt in Richtung eines Nachbarfeldes. Wenn das Feld, auf' + #13 +
               'dem sie sitzt, weiß ist, dann färbt sie es schwarz, dreht sich um 90°' + #13 +
               'nach rechts, und geht auf das nächste Feld. Wenn das Feld, auf dem sie' + #13 +
               'sitzt, schwarz ist, dann färbt sie es weiß, dreht sich um 90 Grad nach' + #13 +
               'links, und geht auf das nächste Feld.' + #13 +
               'In den ersten 10000 Schritten entsteht ein komplexes chaotisches Muster.' + #13 +
               'Dann baut die Ameise eine regelmäßige Straße. Zu allem Überfluss hat' + #13 +
               'die Ameise zwar symmetrische Verhaltensregeln, aber die Muster die sie damit' + #13 +
               'erzeugt, sind asymmetrisch. Das Muster ist zwar nur dann vorhersagbar,' + #13 +
               'wenn man nachmacht, was die Ameise tut, aber es ist streng deterministisch.' + #13 + #13 +
               'aus Wikipedia, der freien Enzyklopädie');
end;

procedure TMain.Info2Click(Sender: TObject);   //Info anzeigen
begin
  ShowMessage ('Simulationen in Delphi' + #13 +
              'Version: 0.6.2' + #13 +
              'Enthalten:' + #13 +
              '    Langtons Ants' + #13 +
              '    WATOR' + #13 + #13 +
              '(C) 2005-2006 Johannes Müller');
end;

procedure TMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  BB_Stop.Click;              //beendet Berechnung, und anschließend das Programm
  Application.Terminate;      //über die Schaltfläche Schließen der Titlebar
end;

procedure TMain.Beenden1Click(Sender: TObject);
begin
  BB_Stop.Click;              //beendet Berechnung, und anschließend das Programm
  Application.Terminate;      //über Menü-Datei-Beenden
end;

//------------------------------------------------------------------------------
procedure TMain.Delay(Milliseconds: Integer);
{pausiert für die Zahl an Milisekunden die Anwendung, als Alternative zu Sleep()
entnohmen aus der Codelib der Delphipraxis www.delphipraxis.de}
var
  Tick: DWord;
  Event: THandle;
begin
  Event := CreateEvent(nil, False, False, nil);
  try
    Tick := GetTickCount + DWord(Milliseconds);
    while (Milliseconds > 0) and
          (MsgWaitForMultipleObjects(1, Event, False, Milliseconds, QS_ALLINPUT) <> WAIT_TIMEOUT) do
    begin
      Application.ProcessMessages;
      Milliseconds := Tick - GetTickcount;
    end;
  finally
    CloseHandle(Event);
  end;
end;

end.