p5.jsで鑑賞する1次元セル・オートマトン


2017年11月04日
With
p5.jsで鑑賞する1次元セル・オートマトン はコメントを受け付けていません

これまで数回にわたって「ライフゲーム」をはじめとする「2次元セル・オートマトン」をp5.jsで動かしてきました。個人的に「ライフゲーム」は大好きなのですが、世の中には「2次元」だけでなく、さまざまな次元のセル・オートマトンが存在します。なかでも「1次元セル・オートマトン」は、学術的な研究対象として2次元よりもメジャーなのだとか。

「1次元セル・オートマトン」の実装は多次元のものよりもシンプルにでき、にもかかわらず、なかなか面白いパターンが生成されます。せっかくなので「p5.jsでセル・オートマトン」シリーズの締めくくり(?)として、1次元セル・オートマトンのスケッチを作ってみました。

ウルフラム・コードで垣間見るカオスの深淵

2次元セル・オートマトンの代表格である「コンウェイのライフゲーム」では、次世代でのセル状態を決定するために、2次元平面上で隣接している「8つのセル」の状態を参照しました。そして、新しい状態に画面全体を描き変えることで「世代更新」による変化をアニメーションとして表現していました。

1次元の場合ですが、その呼び名のとおり、各セルの生死状態は、このデモの画面上では横軸(x軸)に沿って並ぶ1本のドット列として表現されます。ルールに従って変換された次世代の状態は縦軸(y軸)に沿って、下方向に次々と追加されていきます。

このブログでは基本的にスケッチのサイズを600×600ドットとしているのですが、この場合「横方向に600個並んだ1次元セル集合の状態変化を、600世代まで追った結果が1枚の静止画像として表示される」ことになります。

世代更新にあたっては、対象となるセルと、その左右にあるセルを合わせた「3つ」のセルの状態を参照します。各セルが「生」と「死」の2状態をとるばあい、並んだ3セルがとるパターンは「8種類(2の3乗)」。そのそれぞれについて、次世代で中央のセルが「生」となるか「死」となるかを定義したものが、1次元セル・オートマトンでの「ルール」となり、全部で「256ルール(2の8乗)」が存在することになります。

このルールには0~255までの番号が振られており、論理物理学者で、セル・オートマトン研究の権威、そして数学パッケージ「Mathematica」の開発者としても著名なStephen Wolfram氏の名前をとって「ウルフラム・コード」と呼ばれているのだそうです。かっこいいですね。

1次元セル・オートマトンについては、Wikipediaにも詳しい説明がありますので、ご興味がある方はそちらも併せてどうぞ。

1次元セル・オートマトン-セル・オートマトン(Wikipedia)

今回はHTML+p5.jsで実装してみました

さて、今回の実装ですが、こんな感じになりました。

●[DEMO] Mono Dimentional Cellular Automaton for p5.js(クリックすると別タブが開きます)

起動すると画面の上から下に向かって、1行ずつジワーとパターンが描かれていきます。デフォルトでは「ルール90」が選ばれていますが、これは「シェルピンスキーのギャスケット」と呼ばれる有名なフラクタル画像です。

今回、canvasの上にドロップダウンリストとチェックボックス、そして「restart」ボタンを設置しました。ドロップダウンリストには「オススメ」のルールを10個ほど用意しておきましたので、適当にどれかを選んでみて下さい。

チェックボックスでは、スタート時のセルの生死状態をランダムに決定するかどうかを指定します。オフの状態では、中央の1セル(1ドット)のみが「生」の状態で始まります。チェックボックスをオンにしておくと、それに加えて他の各セルが、それぞれ約1%の確率で「生」にセットされます。「restart」ボタンを押すと、その時に選択している「ルール」と「初期状態」で再描画が始まります。

たった8bitで表現されるルールの違いから、ほんと、いろんなパターンが生成されるのですねぇ…。パターンが左右逆になったり、白黒が反転したりというルールであれば何となく理屈も想像できそうなものですが、「ルール110」のような、一見繰り返しに見えない有機的なパターンを目の当たりにすると、カオスの神秘を感じてしまいます。ちなみに、ルール30や110は「イモガイ」のような生物が持つ殻の模様の生成過程のシミュレーションとも考えられるのだとか。

あと、チェックボックスをオンにして、初期状態をランダムにした場合でも、破綻なく(全部黒や全部白になったり、ぐちゃぐちゃになったりしないで)世代が進行していくのが面白いですね。

プログラムについて。当初、ドロップダウンリストやチェックボックス、ボタンなどのUI部品については「p5.dom.js」を使って作るつもりでいたのですが、扱い方がよく分からず、結局HTML上で書いて、値をJavaScriptに渡す形にしてしまいました。

JavaScript側で「rulePreset」という配列に、オススメルールのビットパターンをあらかじめ格納しておき、ドロップダウンリストから取得した0~9の数値を添字として「rule」に代入しています。チェックボックスの状態は、ブーリアンでとり、初期状態設定時にランダム要素を入れるかどうかのフラグにしました。

「restart」で「initialize()」を呼び出し、その時点での各UI部品の状態をもとに初期化を行って「draw」のループ実行を再スタートする形にしたかったので、結果的にこのやり方がラクでしたね。p5.dom.jsの使い方については、一度きちんと勉強し直さなければダメそうです。宿題にします。

HTMLファイルは以下のとおり。

<!DOCTYPE html>
<html>
<head>
    <title>Mono Dimentional Cellular Automaton</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=0.5" />
    <script src="p5.min.js"></script>
    <script src="018-1d-cellular-automaton.js"></script>
</head>
<body style="background-color : #eeeeee">
    <div id="control">
        <select name="ruleSelect" id="ruleSel">
            <option value="0">rule 30</option>
            <option value="1">rule 57</option>
            <option value="2">rule 60</option>
            <option value="3" selected>rule 90</option>
            <option value="4">rule 110</option>
            <option value="5">rule 137</option>
            <option value="6">rule 153</option>
            <option value="7">rule 157</option>
            <option value="8">rule 161</option>
            <option value="9">rule 182</option>
        </select>
        <input type="checkbox" id="check">set initial points at random</input>
        <input type="submit" value="restart" onClick="initialize()">
    </div>
</body>
</html>

JavaScriptは以下のとおりです。やはり2次元CAに比べるとシンプルですね。

var gen;
var maxX, maxY;
var cell = [];
var precell = [];
var randomFlag = false;
var rulePreset = [
      [0, 1, 1, 1, 1, 0, 0, 0],
      [1, 0, 0, 1, 1, 1, 0, 0],
      [0, 0, 1, 1, 1, 1, 0, 0],
      [0, 1, 0, 1, 1, 0, 1, 0],
      [0, 1, 1, 1, 0, 1, 1, 0],
      [1, 0, 0, 1, 0, 0, 0, 1],
      [1, 0, 0, 1, 1, 0, 0, 1],
      [1, 0, 1, 1, 1, 0, 0, 1],
      [1, 0, 0, 0, 0, 1, 0, 1],
      [0, 1, 1, 0, 1, 1, 0, 1]
];
var rule = rulePreset[3];

function setup() {
      createCanvas(600, 600);
      maxX = width, maxY = height;
      stroke(0);
      strokeWeight(1);
      initialize();
}

function draw() {
      for (var i = 0; i < maxX; i++) {
            if (cell[i] == 1) {
                  point(i, gen);
            }
      }
      update();
}

function initialize() {
      loop();
      background(255);
      gen = 0;
      rule = rulePreset[parseInt(document.getElementById("ruleSel").value)];
      randomFlag = document.getElementById("check").checked;
      for (var i = 0; i < maxX; i++) {
            cell[i] = 0;
            if (randomFlag) {
                  if (random() < .99) {
                        cell[i] = 0;
                  } else {
                        cell[i] = 1;
                  }
            }
      }
      cell[maxX / 2] = 1;
}

function update() {
      gen++;
      if (gen == maxY) { noLoop(); }
      for (var i = 0; i < maxX; i++) {
            precell[i] = cell[i];
      }
      for (var i = 0; i < maxX; i++) {
            var left = (i + maxX - 1) % maxX;
            var center = i;
            var right = (i + 1) % maxX;
            var code = precell[left] * 4 + precell[center] * 2 + precell[right] * 1
            cell[i] = rule[code];
      }
}

さて、セル・オートマトン祭りは、これでひと区切り。次回以降はしばらく「フラクタル」をゴチャゴチャしてみるつもりです。