Kürzlich wurde mir jenes Bild zugespielt:
Im Grunde fasst es das Grundproblem gut zusammen. Benutzer und Entwickler denken auf sehr unterschiedlichen Abstraktionsebenen. Für einen Entwickler ist die Benutzeroberfläche lediglich “eine weitere Schale” um den Kern herum und das meiste komische Verhalten in selbiger kann er problemlos anhand anderer Teile des Systems erklären. Für den Benutzer allerdings ist die Benutzeroberfläche das Programm. Und er wird sich nicht die Mühe machen, viel mehr als das zu durchschauen und zu begreifen. Für alle praktischen Belange ist der Rest, also, wie ein Programm eigentlich funktioniert, tatsächlich Magie.
Nun ist obiges Bild leider etwas klein und ich fand weder den Originalautor noch eine hoch aufgelöste Version. Also habe ich mich mal hingesetzt und eine Vektorgrafik auf der Basis erstellt, die hier angehängt ist. Tatsächlich sind es sogar zwei geworden: Einmal weiß auf schwarz und einmal schwarz auf weiß, was sich wohl etwas tonersparender drucken lassen sollte.
Die Schrift, die in den Grafiken verwendet wurde, ist die freie (und hübsche) Fontin.
Ich habe auch die Ausgangsdateien von Expression Design angehängt. Vielleicht mag jemand ja die “Quell”dateien haben. Sowohl die Design- als auch die PDF-Dateien veröffentliche ich hiermit gemeinfrei.
Angehängt sind hier sowohl die deutschen als auch die englischen Varianten.
Eindimensionale zelluläre Automaten lassen sich relativ einfach beschreiben: Letztlich hat man nur eine Kette von Zellen, die jeweils einen von zwei Zuständen haben: An oder Aus (0 oder 1, lebendig oder tot, wie auch immer man es bezeichnen will). Das kann man natürlich dann auch einfach mit Bits repräsentieren.
Zustände in allen zellulären Automaten ändern sich aufgrund der Nachbarschaft einer Zelle im jeweils vorhergehenden Zustand. Nehmen wir an, man hat eine lebendige Zelle (An, 1) und ihre Nachbarn in beide Richtungen sind tot (Aus, 0). Nehmen wir uns weiterhin eine Tabelle, in der steht, daß für exakt diese Konfiguration der Zelle und ihrer Nachbarn ihr Zustand zu tot wechselt (kann gut passieren, vielleicht starb sie ja aus Vereinsamung). In einer anderen Konfiguration sind die Nachbarn vielleicht nicht tot, sondern lebendig und in diesem Falle lebt die Zelle weiter. In noch einer anderen Konfiguration hat man vielleicht eine tote Zelle und egal, wie ihre Nachbarn aussehen, ändert sie ihren Zustand zu lebendig.
So eine Tabelle braucht im Grunde nicht viele Dinge: Der Zustand einer Zelle, die Zustände aller Nachbarzellen (nur zwei in diesem Fall) und der Zustand, den die Zelle in der nächsten Generation haben soll. Wir können das auch folgendermaßen darstellen:
In der oberen Zeile haben wir jeweils eine Zelle mit ihren zwei Nachbarn, links und rechts, also immer drei Zellen. Die untere Zeile liefert den jeweiligen Folgezustand für jede Konfiguration. Wie man vielleicht bemerkt hat, gibt es nur acht mögliche verschiedene Konfigurationen. Auf diesen können wir eine einfache Ordnung definieren, indem wir sie jeweils als drei Bits und damit als Zahl auffassen. Diese absteigende Ordnung habe ich hier auch verwendet. Haben wir diese Ordnung erst einmal, besteht diese komplette Regel nur noch aus acht Folgezuständen, die sich ebenfalls als Nullen und Einsen und damit als einzelne acht-bittige Binärzahl auffassen lassen. Diese Numerierung wurde vom britischen Mathematiker Stephen Wolfram erfunden und wird demzufolge auch als “Wolfram-Regel” bezeichnet.
Die Regel in obigem Bild ist Regel 30.
Nun wissen wir, daß so ein zellulärer Automat als einzelne Zahl beschrieben werden kann und daß er im Laufe der Generationen seinen Zustand ändert. Nur was bringt uns das?
Wir könnten zum Beispiel einen einzelnen Zustand dieses Automaten als eine Reihe von schwarzen und weißen Kästchen darstellen:
und dann könnten wir jede Generation under der jeweils vorhergehenden darstellen:
und siehe da, wir kriegen ein hübsches Bild. Ein wenig chaotisch, aber das ist nun mal bei der Regel so. Es gibt auch welche, die regelmäßigere Muster liefern.
Also war der Sinn und Zweck des Ganzen lediglich, ein merkwürdiges Bild zu erzeugen. Nun also zum spaßigen Teil: So etwas zu programmieren.
Es ist nicht gerade sonderlich schwer, das zu programmieren, also fangen wir einfach oben an:
Wir brauchen natürlich ein wenig Kontrolle darüber, wo das Programm aufhört zu berechnen. Ich setze eine einzelne Zelle mit Zustand 1 in die Mitte der ersten Generation (Anfangszustand), daher ist eine ungerade Breite nicht unsinnig, da sich viele Muster gleichmäßig nach links und rechts ausbreiten. Die Höhe hängt in ähnlicher Weise damit zusammen, da die Ergebnisse meist aufhören, sinnvoll zu werden, sobald ein sich ausbreitendes Muster den rechten und linken Rand erreicht.
Wenn die Regel als Parameter für das Programm gegeben wird, brauchen wir nicht danach fragen, lediglich, wenn sie außerhalb des zulässigen Bereiches (0–255) liegt. Gefragt wird so lange bis eine korrekte Regel eingegeben wurde (ja, hier kann man noch hilfreiche Hinweise geben, aber da hatte ich keine Lust zu).
Ich habe hier ein kleines Unterprogramm geschrieben, welches die Regel in ihre acht Einzelkonfigurationen zerlegt:
Hiernach haben wir acht Variablen, wolfram_x mit x zwischen 0 und 7, die jeweils die Folgezustände für jede Konfiguration beinhalten.
Danach initialisieren wir den Bereich, wo die Zustände jeder Zelle für jede Generation gespeichert werden:
Im Prinzip wird nur jede Zelle mit 0 initialisiert und eine einzelne 1 in der Mitte der ersten Generation hinzugefügt.
Wir haben allerdings noch ein kleines Problem mit diesem Ansatz: Wenn folgende Generationen berechnet werden, braucht jede Zelle eine Nachbarschaft. Nur wie sieht diese Nachbarschaft für die jeweils ersten und letzten Zellen einer Generation aus? Anfangs habe ich einfach nur eine weitere Null links und rechts an jede Zeile gehängt. Das funktioniert gut für die meisten Regeln und wir belassen es erstmal dabei. Wie man das nun in obigem Quelltext umsetzt, lasse ich als Übung für den Leser.
Was auch praktisch wäre, ist ein Unterprogramm, welches das komplette Bild ausgibt:
Es sind tatsächlich sogar zwei Unterprogramme, diese werden später noch praktisch.
Was natürlich immer noch fehlt, ist die Berechnung der Folgezustände. Also tun wir dies mal:
Nichts außeregwöhnliches hier, wir delegieren lediglich die Berechnung einer einzelnen Zelle an ein weiteres Unterprogramm. Wie vielleicht auffällt, zeige ich die Zeile sofort nachdem sie berechnet worden ist, was das Zuschauen während das Programm läuft, etwas weniger langweilig macht, da wir dann alle paar Sekunden eine neue Zeile sehen (ja, das Ganze ist so langsam).
Hier wird der neue Zustand einer einzelnen Zelle berechnet unter Zuhilfenahme eines weiteren Unterprogramms, welches den spezifischen Fall aus der Tabelle sucht. Wir machen uns hier die Tatsache zunutze, daß die Zelle und ihre Nachbarn im Grunde eine drei-Bit-Zahl ist und die Tabelle auch zugreifbar ist, indem wir diese drei Bit in eine Dezimalzahl zwischen 0 und 7 überführen. Der Code dafür ist leider ein wenig unschön, da viel Escaping nötig ist (die Klammern habe ich jedoch lediglich aus Vorsicht so behandelt, da Klammern gern etwas kaputt machen, besonders, wenn man anfängt, Strukturen zu schachteln).
Aber das war eigentlich schon alles. Führt man diese Batchdatei nun ohne Argumente aus, kommt die folgende Abfrage:
geben wir hier nun sagen wir 54 ein, kommt das folgende Bild zustande:
Der eigentliche Quelltext der Batchdatei ist ein wenig länger, da ich inzwischen auch eine Option anbiete, was mit dem linken und rechten Rand geschehen soll (alles null, alles eins, zylinderförmig und kopieren, letzteres ist nun die Standardeinstellung, da zum Beispiel Regeln wie 169 sehr merkwürdig aussehen, wenn sie mit Null-Kanten berechnet werden).
Momentan arbeite ich noch an SVG-Export aus dieser Batch-Datei (der Grund, warum ich die eigentlich geschrieben habe) und hoffe, inzwischen alle größeren Bugs gefunden zu haben. Die erste funktionierende Version hatte übrigens nur 54 Zeilen. Ich denke, hätte ich Java benutzt (was hier gerade die einzige andere Alternative war), hätte ich deutlich mehr gebraucht.
UPDATE (2008–12–26 16:21): SVG-Export ist fertig und werkelt nun auch wie er soll. Momentan lasse ich den Terminal Server in der Uni an allen 256 Automaten gleichzeitig rechnen:
@echo off
set X=0
echo set /a X+=1 >>%0
echo echo %%X%%>>%0
... es funktioniert ... jedes Mal, wenn man diese Batch-Datei ausführt, wächst die Liste der ausgegebenen Zahlen um eins :-)
Der interessante Teil hieran ist, daß die Zeilen, die erst zur Laufzeit des Skriptes hinzugefügt werden, noch im gleichen Durchlauf der Batch ausgeführt werden.
d.IsNullity() statt d.IsNaN() aufruft.
Wikinews hat da noch ein wenig mehr zu dem Thema. Wenn es etwas gibt, was die Windows-Kommandozeile (cmd.exe) ganz gut kann (außer das Starten anderer Programme), dann ist das Verarbeiten von Zeichenfolgen. Nicht gerade auf Perls Niveau, aber sicher schon angenehmer zu nutzen als die C-Standardbibliothek (Reguläre Ausdrücke lassen wir hier mal weg).
Ich spielte nur ein wenig herum und heraus kam folgendes:setlocal mit den üblichen Optionen (ich setze das eigentlich schon aus Gewohnheit fast immer, egal ob ich es brauche oder nicht). Alle Kommandozeilenargumente werden in einer Variablen gespeichert und dann Zeichen für Zeichen auseinandergekommen. Sobald der Orignaltext leer ist, können wir aufhören und das Resultat ausgeben.
Und es funktioniert sogar mit Unicode:
Diese harmlos aussehende Batchdatei enthält einen endlichen Automaten um die Parameter zu parsen. Diese werden von dem folgenden regulären Ausdruck beschrieben:
Ich erlaube sowohl die deutsche als auch die englische Notation (w und d) als Trennzeichen für die Anzahl und die Seiten der Würfel sowie die Option, alle Würfe und eine Konstante aufzusummieren (für Initiativewürfe in Shadowrun 3). Aufgrund des Parsers kriegt der Benutzer relativ genaue Angaben über Fehler und wo genau ein Fehler auftrat (vielleicht wäre es mal an der Zeit für ein allgemeines Tool, welches endliche Automaten in Batch generiert :-)).
Einige Beschränkungen:
Der Code ist fast undokumentiert, aber das macht beim Lesen nur um so mehr Spaß :-)
Earlier this year I bought a ThinkPad R60. Upgraded to one and a half Gibibyte of RAM I was pretty sure it at least should be able to run Vista. The built-in Intel GMA 945 graphics chip isn't impressive but at least it's able to meet Vista's requirements for the Aero user interface.
So far my impressions are mixed and for people willing to upgrade it may be safer to wait for Service Pack 1 to be released (somewhen in first quarter 2008).