まずはこれを見てください

LEDが並んだだけのシンプルなシールド。しかし、そこには**「単純なルールから複雑な世界が生まれる」**という、自然界の根本原理が動いています。

動画では、Rule 30 → Rule 90 → Rule 110 の3つのルールが自動的に切り替わりながら、それぞれ64世代分のパターンを生成しています。各ルールの終了時には、全LEDが点滅する「クライマックス演出」が入り、次のルールへと移行していきます。

今回は、当ショップで販売している Arduino UNO用LEDシールド を使って、1次元セル・オートマトン(Cellular Automaton) を実装してみました。

Arduino UNO R4 MinimaにLEDシールドを装着

Arduino UNO R4 MinimaにLEDシールドを装着

使用したハードウェア

このプロジェクトで使用したのは、Boothで販売中の 「Arduino UNO用LEDシールド」 です。

BOOTHで購入する

本来は デバッグ用 として設計したシールドですが、全ピン(D0-D13, A0-A5)にLEDがついているという特性を活かして、今回は 「データの可視化装置」 として使ってみました。

使用機材

  • Arduino UNO R4 Minima (R3でも動作します)
  • Arduino UNO用LEDシールド (全ピンLED搭載)
Arduino UNO R4 Minima

Arduino UNO R4 Minima

Arduino UNO R4 Minima starter kit

Arduino UNO R4 Minima starter kit


セル・オートマトンとは何か?

セル・オートマトン(Cellular Automaton) とは、「マス目(セル)が、隣のマス目の状態を見て、次の自分の状態を決める」という単純な仕組みで動くシミュレーションです。

2次元と1次元の違い

最も有名なのは 「ライフゲーム」 という2次元のセル・オートマトンです。格子状のマス目が「生」と「死」の2つの状態を持ち、周囲8マスの状態から次の世代の自分の状態が決まります。

しかし今回は、LEDが 一列に並んでいる ため、1次元セル・オートマトン を実装しました。

1次元セル・オートマトンのルール

1次元セル・オートマトンでは、各セルは 左隣・自分・右隣 の3つの状態(ON/OFF)を見て、次の世代の自分の状態を決定します。

[左][中][右]  → 3つの状態を見る(例: 1, 0, 1 → 「101」という3ビットパターン)
      ↓
    [ 次 ]    → ルール表に従って決まる(例: 0)

3つのセルがそれぞれ2つの状態(0 or 1)を持つので、組み合わせは 2³ = 8通り です。 この8通りそれぞれに対して「次の状態」を定義することで、256種類(2⁸)のルールが作れます。

左中右の組み合わせ:  111  110  101  100  011  010  001  000
次の状態(例):        0    0    0    1    1    1    1    0

この例は「Rule 30」と呼ばれるルールで、8ビットのパターン 00011110 を10進数にすると30になります。

たったこれだけの単純なルールから、驚くほど複雑で美しいパターン が生まれます。


セル・オートマトンは何の役に立つのか?

「光って綺麗」というだけではありません。セル・オートマトンは、自然界の模倣計算科学 の分野で実際に役立っています。

1. 自然界の模様を説明できる

🐚 イモガイの貝殻の模様

イモガイ(Conus textile)の殻の模様(イメージ)

イモガイ(Conus textile)の殻の模様(イメージ)

イモガイ(Conus textile)という貝殻の模様は、Rule 30のセル・オートマトン で生成されるパターンと酷似しています。

貝殻は成長の過程で、「貝殻の端(成長線)」にある細胞が、隣の細胞の色素の有無を見て、自分が色素を出すか決めるという単純なメカニズムで模様を作っていると考えられています。

つまり、自然界は 「複雑な計算をせずに、単純なルールだけで美しいデザインを生成している」 のです。

❄️ 雪の結晶

雪の結晶も同様に、水分子が「隣の分子の配置」を見て結合する、という単純な物理法則から、複雑な幾何学模様を作り出しています。

2. 計算科学への応用

乱数生成(Rule 30)

Rule 30は 予測不可能なランダムパターン を生成します。このカオス的な性質は、コンピュータの乱数生成アルゴリズムに実際に応用されています。

数学者スティーブン・ウルフラムは、自身の開発した数式処理ソフト「Mathematica」の乱数生成にRule 30を採用していました。

交通渋滞や流体のシミュレーション

セル・オートマトンは、車や水分子のような「粒子」を単純なルールで動かすことで、渋滞の発生メカニズム流体の動き をシミュレートするのにも使われています。

複雑な微分方程式を解くよりも、単純なルールの積み重ね で現象を再現できるのです。


実装した3つのルール

今回のプログラムでは、Rule 30、Rule 90、Rule 110 の3つを順番に実行し、自動的に切り替えながら無限ループさせています。

各ルールは64世代(0〜63)実行され、終了時には全LEDが点滅するエフェクトで「このルールの終わり」を演出しています。

① Rule 30 — カオス・無秩序の世界

特徴

  • カオス的で予測不可能: たった1点の光(中央のLED)から始まり、不規則でランダムな三角形が広がる。
  • 左右非対称: 左右で異なるパターンが生まれ、「自然の荒々しさ」を感じさせる。

ルール定義

左中右: 111  110  101  100  011  010  001  000
次状態:  0    0    0    1    1    1    1    0

2進数で: 0001111010進数で30

見どころ

  • 開始時は1点だけが光っているが、世代を重ねるごとに、複雑で不規則なパターンが広がっていく。
  • まるで**「生命が生まれる瞬間」**を見ているような感覚。

② Rule 90 — フラクタル・幾何学美の世界

特徴

  • 完璧な幾何学模様: 三角形が入れ子になった「シェルピンスキーのギャスケット」というフラクタル図形が現れる。
  • 完全な左右対称性: 秩序と美しさが際立つ。

ルール定義

左中右: 111  110  101  100  011  010  001  000
次状態:  0    1    0    1    1    0    1    0

2進数で: 0101101010進数で90

計算式

実はこのルールは、単純な XOR演算 で表現できます。

Next = Left XOR Right

たったこれだけのロジックで、美しいフラクタルが生成されます。

見どころ

  • どこまで拡大しても同じパターンが現れる「自己相似性」が特徴。
  • 数学的な美しさ をLEDで表現したような、静謐な雰囲気。

③ Rule 110 — 計算万能性を持つ世界

特徴

  • 秩序とカオスの境界: 周期的なパターンが現れたかと思えば、突然崩れたり、移動したりする。
  • 計算万能性(チューリング完全): このルールだけで、コンピュータと同じ計算能力を持つことが証明されている。

ルール定義

左中右: 111  110  101  100  011  010  001  000
次状態:  0    1    1    0    1    1    1    0

2進数で: 0110111010進数で110

重要性

Rule 110は、数学者スティーブン・ウルフラムが「クラス4」と分類した特別なルールです。

  • クラス1: すぐに消滅する(面白くない)
  • クラス2: 単純な周期パターン(退屈)
  • クラス3: ランダムで混沌(Rule 30)
  • クラス4: 秩序とカオスの境界(Rule 110)

クラス4のルールは、「計算万能性(Universal Computation)」 を持つとされ、2004年にマシュー・クックによって、Rule 110がチューリング完全であることが証明されました。

つまり、このルールだけで、どんなプログラムでも実行できる のです。

見どころ

  • パターンが「生きている」ような動き。
  • 「構造が伝播する」「局所的な崩壊と再生」 といった、生命のような振る舞いが観察できる。

技術的な実装のポイント

ハードウェア構成

上段(D0-D13):「世界」の表示

デジタルピンD0〜D13の14個のLEDで、セル・オートマトンの「現在の世代」を表示します。 各LEDが1つのセルに対応し、ON(点灯)が「1」、OFF(消灯)が「0」を表します。

下段(A0-A5):「時間(世代)」の表示

アナログピンA0〜A5の6個のLEDを、デジタル出力として使用 し、現在の世代数を 2進数 で表示しています。

// A0-A5をデジタル出力として使用
pinMode(A0, OUTPUT);
pinMode(A1, OUTPUT);
pinMode(A2, OUTPUT);
pinMode(A3, OUTPUT);
pinMode(A4, OUTPUT);
pinMode(A5, OUTPUT);

// 世代数を2進数で表示
void displayGeneration(int gen) {
  digitalWrite(A0, (gen >> 0) & 1);
  digitalWrite(A1, (gen >> 1) & 1);
  digitalWrite(A2, (gen >> 2) & 1);
  digitalWrite(A3, (gen >> 3) & 1);
  digitalWrite(A4, (gen >> 4) & 1);
  digitalWrite(A5, (gen >> 5) & 1);
}

6ビットで0〜63世代をカウントできます。


ソフトウェア実装の工夫

セル・オートマトンのルールを実装する際、if文を大量に書くのは非効率です。

そこで、ビット演算 を使った汎用的な実装を行いました。この方法なら、ルール番号を変えるだけで256種類すべてのセル・オートマトンを実行できます。

エッジの処理(ラップアラウンド)

今回の実装では、左端と右端を 循環させる(ラップアラウンド) ようにしました。

int leftIdx = (i - 1 + numCells) % numCells;   // 左端のとき右端を参照
int rightIdx = (i + 1) % numCells;              // 右端のとき左端を参照

これにより、LEDの列が「輪」のように繋がった世界として動作します。

3つのセルの状態を1つの数値にする

int pattern = (left << 2) | (center << 1) | right;
// 例: left=1, center=0, right=1 → pattern = 101₂ = 5

これで、3つのセルの組み合わせが 0〜7の整数 で表現できます。

ルール番号からビットマスクで次の状態を決定

int nextState = (rule >> pattern) & 1;

解説:

  • rule は30, 90, 110などのルール番号(0〜255)
  • pattern は0〜7の整数(左中右の組み合わせ)
  • rule >> pattern で、ルール番号を右シフトして、該当ビットを取り出す
  • & 1 で最下位ビットだけを取得

実装例(Rule 30の場合)

Rule 30は、2進数で 00011110 です。

  • pattern = 0(000)のとき: 00011110 >> 0 = 00011110 → 最下位ビット 0
  • pattern = 1(001)のとき: 00011110 >> 1 = 0001111 → 最下位ビット 1
  • pattern = 2(010)のとき: 00011110 >> 2 = 000111 → 最下位ビット 1
  • pattern = 3(011)のとき: 00011110 >> 3 = 00011 → 最下位ビット 1
  • pattern = 4(100)のとき: 00011110 >> 4 = 0001 → 最下位ビット 1
  • pattern = 5(101)のとき: 00011110 >> 5 = 000 → 最下位ビット 0
  • pattern = 6(110)のとき: 00011110 >> 6 = 00 → 最下位ビット 0
  • pattern = 7(111)のとき: 00011110 >> 7 = 0 → 最下位ビット 0

これで、if文を使わずに すべてのルールを統一的に処理できます。

完全なコード例

以下が、実際に動画で動作させているArduinoスケッチの全体です。

/*
  Demo: 3-Rule Cellular Automaton (30 -> 90 -> 110)
  Board: Arduino Uno R4
  
  D0-D13: Automaton Display
  A0-A5 : Generation Counter
*/

const int cellPins[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 };
const int numCells = sizeof(cellPins) / sizeof(cellPins[0]);
const int countPins[] = { A0, A1, A2, A3, A4, A5 };
const int numCountBits = sizeof(countPins) / sizeof(countPins[0]);

byte currentCells[14];
byte nextCells[14];

int generation = 0;
const int maxGenerations = 64; // 各ルールの実行時間(世代数)

// 順番に表示したいルールのリスト
int rules[] = { 30, 90, 110 }; 
int currentRuleIndex = 0;      // 現在どのルールを使っているか

void setup() {
  for (int i = 0; i < numCells; i++) pinMode(cellPins[i], OUTPUT);
  for (int i = 0; i < numCountBits; i++) pinMode(countPins[i], OUTPUT);
  resetWorld();
}

void loop() {
  // 1. 世界の表示
  for (int i = 0; i < numCells; i++) digitalWrite(cellPins[i], currentCells[i]);

  // 2. カウンター表示
  for (int i = 0; i < numCountBits; i++) digitalWrite(countPins[i], (generation >> i) & 1);

  delay(120); // 少しテンポアップ

  // 3. 次の世代を計算(現在のルール番号を使用)
  calculateNextGeneration(rules[currentRuleIndex]);

  // 4. 世代更新と切り替え判定
  generation++;
  if (generation >= maxGenerations) {
    finishedEffect(); // 5秒間の点滅演出
    
    // 次のルールへ切り替え
    currentRuleIndex++;
    if (currentRuleIndex >= 3) { // 3つ終わったら最初(30)に戻る
      currentRuleIndex = 0;
    }
    
    resetWorld();
  }
}

// 汎用計算ロジック:ルール番号から自動でビット演算します
void calculateNextGeneration(int ruleNumber) {
  for (int i = 0; i < numCells; i++) {
    int leftIdx = (i - 1 + numCells) % numCells;
    int rightIdx = (i + 1) % numCells;
    
    byte left = currentCells[leftIdx];
    byte center = currentCells[i];
    byte right = currentCells[rightIdx];

    // 周囲3つの状態を3ビットの整数(0〜7)にする
    // 例: left=1, center=0, right=1 なら "101" = 5
    int pattern = (left << 2) | (center << 1) | right;

    // ルール番号のビットを見て、次の状態を決める
    // 例: Rule 30 (00011110) で patternが5なら、5ビット目は1なので次は1
    nextCells[i] = (ruleNumber >> pattern) & 1;
  }

  for (int i = 0; i < numCells; i++) currentCells[i] = nextCells[i];
}

void resetWorld() {
  generation = 0;
  for (int i = 0; i < numCells; i++) currentCells[i] = 0;
  currentCells[numCells / 2] = 1; // 中央一点スタート
}

void finishedEffect() {
  // 5秒間のクライマックス演出
  for(int k=0; k<10; k++){
    for (int i = 0; i < numCells; i++) digitalWrite(cellPins[i], HIGH);
    for (int i = 0; i < numCountBits; i++) digitalWrite(countPins[i], HIGH);
    delay(250);
    for (int i = 0; i < numCells; i++) digitalWrite(cellPins[i], LOW);
    for (int i = 0; i < numCountBits; i++) digitalWrite(countPins[i], LOW);
    delay(250);
  }
  delay(500);
}

コードのポイント解説

1. ルールの自動切り替え

int rules[] = { 30, 90, 110 }; 
int currentRuleIndex = 0;

配列で3つのルールを定義し、currentRuleIndex で現在のルールを管理しています。64世代終了後に次のルールへ切り替わります。

2. 汎用的な計算関数

void calculateNextGeneration(int ruleNumber) {
  int pattern = (left << 2) | (center << 1) | right;
  nextCells[i] = (ruleNumber >> pattern) & 1;
}

ルール番号を引数で受け取ることで、どのルールでも同じロジックで計算できます。if文の羅列は一切不要です。

3. クライマックス演出

void finishedEffect() {
  for(int k=0; k<10; k++){
    // 全LED点滅を10回繰り返す(計5秒間)
    ...
  }
}

各ルールの終了時に、全LEDが点滅する演出を入れることで、「ここで切り替わった」という視覚的なフィードバックを与えています。


まとめ — デバッグツールが「思考の可視化」装置に

セル・オートマトンが動作する様子

セル・オートマトンが動作する様子

今回、デバッグ用のLEDシールド を使って、単純なルールから生まれる複雑な世界を可視化しました。

得られた気づき

  • 「LEDが光るだけ」のシンプルさ が、アルゴリズムの本質を際立たせる。
  • たった8ビットのルールで、カオス・秩序・計算万能性 という異なる性質が生まれる不思議。
  • 自然界も、同じように 単純なルールの積み重ねで複雑な美を生み出している という実感。

用途の広がり

当初は「デバッグツール」として設計したこのシールドですが、以下のような使い方もできることが分かりました:

  • アルゴリズム学習の教材: セル・オートマトン、ソート、探索アルゴリズムの可視化
  • インテリアガジェット: Rule 30の不規則な光は、見ていて飽きない
  • プレゼンテーション: 「複雑系」や「創発」を説明するときのデモツール

購入・技術資料

BOOTHで販売中

Arduino UNO用LEDシールドを購入する

価格: ¥2,000(税込・送料別)

オープンソース資料

回路図と、今回のセル・オートマトンのArduinoスケッチは、GitHubで公開しています:

GitHubで詳細を見る

回路図・基板データ・サンプルコードを自由に利用できます


単純なルールから生まれる複雑な世界。それは、自然界の営みと同じです。

ぜひ、あなたもこのLEDシールドで、「アルゴリズムの美しさ」 を体験してみてください。