Mit Langton's Ameise kann eine interessante Grafik erstellt werden. Für die Darstellung verwende ich Processing und schreibe das Script in Java.
Wie funktioniert Langton's Ameise (Langton's ant)?
Zunächst wird eine Ameise (ein Pixel) auf ein Raster gesetzt. Im Fall meines Beispiels besteht das Raster aus der Breite und Höhe des Fensters und somit ist jeder Pixel im Fenster ein Teil des Rasters. Also wird die Ameise zunächst auf das Feld x = Breite/2
und y = Höhe/2
gesetzt. Somit wird die Ameise in die Mitte des Fensters gesetzt.
Nun gibt es ein paar Regeln welche wir der Ameise geben müssen. Diese sehen wie folgt aus:
- Befindet sich die Ameise auf einem weißen Pixel, soll diese ihre Laufrichtung um 90° im Uhrzeigersinn verändern.
- Befindet sie sich auf einem schwarzen Pixel, soll sie sich gegen den Uhrzeigersinn um 90° drehen.
Hat sich die Ameise auf dem Pixel nun gedreht, soll diese um einen Pixel nach vorne laufen und den Pixel welchen sie verlässt verändern. Wenn der letze Pixel schwarz war, wird dieser nun weiß. War er weiß, so wird er schwarz.
In meinem Beispiel habe ich der Ameise die Regel gegeben, dass sie sich um 90° im Uhrzeigersinn bewegt sobald sie auf einem Schwarzen Pixel steht. Wenn sie sich auf einem nicht schwarzen Feld befindet, soll sie sich um 90° gegen den Uhrzeigersinn drehen. Bewegt sie sich vorwärts, so verändert sie einen schwarzen Pixel zu einer Farbe die nach der Zeit bestimmt wird. Ein nicht schwarzer Pixel wird zu einem schwarzen Pixel.
Dieser Vorgang (Farbe erkennen, Drehen, Farbe invertieren, Vorwärts laufen) muss nun mehrmals wiederholt werden. So bewegt sich die Ameise vorwärts und hinterlässt ihre Spuren welche nachher unsere Grafik ergibt.
Wie setze ich das Ganze nun in Processing um?
Zunächst brauchen wir eine Variable in welcher wir den Status jedes Pixels unseres Rasters speicher können. Dies können wir in einem Zweidimensionalen-Array machen. Der erste Wert wird die X-Koordinate sein und der zweite Wert die Y-Koordinate.
boolean[][] grid;
Weiter benötigen wir die Position und die Richtung in welche die Ameise schaut. Die Position speichern wir als Int in den Variablen x
und y
. Die Richtung in welche die Ameise schaut wird auch als Int in der Variable dir
gespeichert. dir
kann dabei eine Zahl von 0 bis 3 sein. Dabei ist 0 = Oben, 1 = Rechts, 2 = Unten und 3 = Links. Da wir die Richtung im Uhrzeigersinn angeben, können wir diese nachher durch einfaches addieren und subtrahieren auf die neue Richtung einstellen. Damit es einfacher wird, lege ich die Richtungen noch als Konstanten fest.
int x = 0;
int y = 0;
int dir = 0;
final int ANTUP = 0;
final int ANTRIGHT = 1;
final int ANTDOWN = 2;
final int ANTLEFT = 3;
Der nächste Schritt beinhaltet das Definieren der setup()
-Funktion. Hier legen wir die Größe des Fensters fest und initialisieren dann das Raster. Die Hintergrundfarbe wird als schwarz festgelegt und die X und Y-Position des Startpunktes der Ameise wird festgelegt.
void setup(){
size(1280,720,P2D);
grid = new boolean[width][height];
background(0);
x = width/2;
y = height/2;
}
Nun definieren wir die draw()
-Funktion. Hier muss jetzt der Status des Feldes auf welchem sich die Ameise befindet geprüft werden. Wir können dazu einfach die X und Y-Position in das Zweidimensionale-Array eintragen und bekommen den Status (true/false) zurück. Aufgrund dessen können wir entscheiden was die Ameise als nächstes machen soll.
void draw(){
if(grid[x][y] == true){
// Mache etwas
}else{
// Mache etwas anderes
}
// Mache noch etwas
}
Als erstes wollen wir den Status des Feldes auf welchem wir uns befinden zum gegenteil verändern (Aus true wird false und umgekehrt). Danach Legen wir die neue Farbe des Feldes auf welchem wir uns befinden fest. Von beginn an sind alle Felder im Raster mit false gekennzeichnet. Also verändern wir die Farbe bei false (Schwarzes Feld) zu einer anderen Farbe und bei true zu schwarz. Welche Farbe bei nicht schwarz gewählt wird entscheidet dann folgende Funktion: stroke(map(sin(i),-1,1,0,255),map(cos(i),-1,1,0,255),map(-sin(i),-1,1,0,255));
Die Variable i
wird zunächst als float mit dem Wert 0 initialisiert und anschließend innerhalb von der Funktion draw()
um einen kleinen Betrag (z.B.: 0.0001) erhöht (Wert sollte innerhalb von draw()
zurückgesetzt werden wenn z.B. ein Wert von 1000 erreicht wird, dies verhindert ein überlaufen). Soll das Feld schwarz werden so ist die Funktion: stroke(0);
Nachdem die Farbe angepasst wurde wird eine Funktion ausgeführt welche wir noch definieren werden und die neue Richtung bestimmen wird. Diese wird MoveRight()
im falle von true und MoveLeft()
im falle von false sein.
Haben wir dann die Überprüfung des Status abgeschlossen, können wir das Feld auf welchem wir immer noch stehen in der Vorher eingestellten Farbe färben. Die machen wir mit der X und Y-Position der Ameise und tragen diese in die Funktion point(x,y);
ein. Abschließend wird die Ameise sich in eingestellte Richtung um ein Feld fortbewegen. Dafür müssen wir auch noch eine Funktion anlegen welche wir MoveForward()
nennen werden.
float i = 0;
void draw(){
if(i > 1000){i = 0;}
i += 0.0001;
if(grid[x][y] == true){
grid[x][y] = false;
stroke(0);
MoveRight();
}else{
grid[x][y] = true;
stroke(map(sin(i),-1,1,0,255),map(cos(i),-1,1,0,255),map(-sin(i),-1,1,0,255));
MoveLeft();
}
point(x,y);
MoveForward();
}
Die Funktionen MoveRight()
und MoveLeft()
müssen jetzt den Wert von dir
anpassen. MoveRight()
muss den Wert um 1 erhöhen, währen MoveLeft()
diesen um 1 verringern muss. Sie muss auch darauf achten das Wenn der Wert 0 unterschritten und der Wert 3 überschritten wurde dieser jeweils so angepasst wird das aus < 0 eine 3 wird und aus > 3 eine 0.
void MoveRight(){
dir++;
if(dir > 3){
dir = ANTUP;
}
}
void MoveLeft(){
dir--;
if(dir < 0){
dir = ANTLEFT;
}
}
Die letzte Funktion welche wir benötigen ist die MoveForward()
-Funktion. Diese soll die X und Y-Position der Ameise in Abhängigkeit der Richtung verändern. Des weiteren soll sie darauf achten, dass wenn die Ameise aus dem Raster ausbricht, sie auf die jeweils gegenüberliegende Seite gesetzt wird (wenn x < 0 dann x = Breite-1).
void MoveForward(){
switch(dir){
case ANTUP:
y--;
break;
case ANTRIGHT:
x++;
break;
case ANTDOWN:
y++;
break;
case ANTLEFT:
x--;
break;
}
if(y < 0){
y = height-1;
}else if(y > height-1){
y = 0;
}
if(x < 0){
x = width-1;
}else if(x > width-1){
x = 0;
}
}

Jetzt nur noch das Programm starten und beobachten was passiert. Nach den ersten 11.000 Wiederholungen sollte das Ergebnis etwa so wie auf der Abbildung rechts aussehen. Das Programm kann natürlich nach belieben abgeändert werden (z.B.: so wie ich es mit der Farbe gemacht habe). Mann könnte verschiedene Feldgrößen, andere Regeln, verschiedene Formen und andere Dinge machen und schauen was dabei herauskommt.
Viel Spaß beim ausprobieren. :)
Mehr zu Langton's ant: Wikipedia
Processing: Processing
Der vollständige Code:
boolean[][] grid;
int x = 0;
int y = 0;
int dir = 0;
final int ANTUP = 0;
final int ANTRIGHT = 1;
final int ANTDOWN = 2;
final int ANTLEFT = 3;
void setup(){
size(1280,720,P2D);
grid = new boolean[width][height];
background(0);
x = width/2;
y = height/2;
}
float i = 0;
void draw(){
if(i > 1000){i = 0;}
i += 0.0001;
if(grid[x][y] == true){
grid[x][y] = false;
stroke(0);
MoveRight();
}else{
grid[x][y] = true;
stroke(map(sin(i),-1,1,0,255),map(cos(i),-1,1,0,255),map(-sin(i),-1,1,0,255));
MoveLeft();
}
point(x,y);
MoveForward();
}
void MoveRight(){
dir++;
if(dir > 3){
dir = ANTUP;
}
}
void MoveLeft(){
dir--;
if(dir < 0){
dir = ANTLEFT;
}
}
void MoveForward(){
switch(dir){
case ANTUP:
y--;
break;
case ANTRIGHT:
x++;
break;
case ANTDOWN:
y++;
break;
case ANTLEFT:
x--;
break;
}
if(y < 0){
y = height-1;
}else if(y > height-1){
y = 0;
}
if(x < 0){
x = width-1;
}else if(x > width-1){
x = 0;
}
}