C#: 1次元セルオートマトン - ルール0からルール255まで全て試す


Wikipedia「[セル・オートマトン](https://ja.wikipedia.org/wiki/セル・オートマトン#1次元セル・オートマトン)」の記事を参考にして、1次元セルオートマトンのルール0からルール255までの全256パターンを描くプログラムを作成してみました。


このなかでは、「ルール30」と「ルール90」が有名ですね。

## ルール30のセルオートマトンとは

ある時刻 t+1の中央のセル状態は、時刻 t の 左、中央、右の３つのセルの状態で決定されます。 
そのルールを以下に示します。

```
時刻 t    時刻 t+1
   111  →    0
   110  →    0
   101  →    0
   100  →    1
   011  →    1
   010  →    1
   001  →    1
   000  →    0
```

時刻 t の値を並べると 0001110 となり、10進数で 30 となることから、ルール30と呼ばれます。

このルールで作成される模様が、ある種の貝殻の表面にある模様と大変よく似ているらしいです。自然が作り出す模様が数学的なルールに基づいていると考えると、とても興味深いですね。

![Rule30.png](https://qiita-image-store.s3.amazonaws.com/0/29415/f74557cc-26a2-558b-e7ac-0c41346b8585.png "Rule30.png")


## ルール90のセルオートマトンとは

ルール30で示した規則をルール90のものに置き換えると、典型的なフラクタル図形であるシェルピンスキーのギャスケットが現れます。  

そのルール90を以下に示します。

```
時刻 t    時刻 t+1
   111  →    0
   110  →    1
   101  →    0
   100  →    1
   011  →    1
   010  →    0
   001  →    1
   000  →    0
```

時刻 t の値を並べると 01011010 となり、10進数で 90 となることから、ルール90と呼ばれています。
通常のフラクタル図形を描く方法とは全く別の方法（1次元セルオートマトン)で描けるというのは不思議です。

![Rule90.png](https://qiita-image-store.s3.amazonaws.com/0/29415/233f3aeb-f01c-7899-164c-ed429bcf6ba2.png "Rule90.png")


つまり、このルールを変えることでいろんな図形を描くことができるわけです。ということで、ルール0からルール255までの全256パターンを描くプログラムを作成してみました。ルール番号を与えると、対応するルールを作成するようにしていますが、詳しくは後述のコードをみてください。

## NGraphicパッケージ

以前このようなプログラムを作成した時は、Silverlightで作成したのですが、もう死んでしまったテクノロージーなので、今回は、コンソールアプリケーションとして作成し、PNGファイルとしてセルオートマトンを描画し出力することにします。

PNGファイルの作成には、NGraphicパッケージを使っています。.NET Core版のNGraphicパッケージで動かそうとしたのですが、どうもコンソールアプリでは無理みたいなので、.NET Framework版の[NGraphic](https://github.com/praeclarum/NGraphics)パッケージを利用しています。

## NGraphicパッケージを隠蔽するEasyCanvasクラスを定義

まずは、EasyCanvasクラスを定義します。
このクラスは、NGraphicパッケージの機能をこの中に隠蔽するためのものです。
SVGにも書き出せればいいなと思ったのでこうしています。まあ、そうなったら、インターフェース定義して云々かんぬんやらないといけないのですが、まあそれは本当に必要になった時にやろうと思います。

必要なのは、点を打つ機能とファイルに出力する機能だけなので、SetPixelメソッドとWriteメソッドを定義しています。

```cs
using NGraphics;

namespace Cellautomaton {
    class EasyCanvas {
        private IImageCanvas canvas;

        static EasyCanvas() {
            // .NET Coreのパッケージの場合は、このコードが必要？
            //Platforms.SetPlatform<SystemDrawingPlatform>();
        }
        public EasyCanvas(int width, int height) {
            canvas = Platforms.Current.CreateImageCanvas(new Size(width, height), scale: 2);
        }

        public void SetPixel(int x, int y, SolidBrush brush) {
            canvas.FillRectangle(x, y, 1, 1, brush);
        }

        public void Write(string path) {
            canvas.GetImage().SaveAsPng(path);
        }
    }
}
```


### 1次元セルオートマトンを実現するCellularAutomatonクラスを定義

このEasyCanvasを使って、1次元セルオートマトンを実現するクラスCellularAutomatonを定義します。
なお、Ruleの数値はコンストラクタで与えられるようにしています。

```cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace Cellautomaton {
    class CellularAutomaton {

        private bool[] _cells;

        private Dictionary<string, bool> _rule;

        public CellularAutomaton(int rule, int width) {
            _cells = new bool[width];
            _cells[width / 2] = true;
            _rule = MakeRule(rule);
        }

        // ルール番号からルールを作成
        private Dictionary<string, bool> MakeRule(int rule) {
            var list = new[] {
                "111", "110", "101", "100", "011", "010", "001", "000"
            };
            return list.Reverse().Select(k => {
                var b = (rule & 0x01) == 0x01;
                rule = rule >> 1;
                return new { Key = k, Value = b };
            }).ToDictionary(p => p.Key, p => p.Value);
        }

        // すべての世代を列挙する。永遠に列挙するので注意。
        public IEnumerable<bool[]> AllGenerations() {
            yield return _cells;
            while (true) {
                List<bool> area = new List<bool>();
                foreach (var la in GetAdjoints()) {
                    area.Add(_rule[LocalAreaToString(la)]);
                }
                _cells = area.ToArray();
                yield return _cells;
            }
        }

        // ３つのセルを文字列に変換
        string LocalAreaToString(bool[] area) {
            string s = "";
            foreach (var a in area) {
                s += a == true ? "1" : "0";
            }
            return s;
        }

        // 隣接した３つのセルを配列として順に取り出す。
        IEnumerable<bool[]> GetAdjoints() {
            bool[] area = new bool[3];
            for (int i = 0; i < _cells.Length; i++) {
                area[0] = (i == 0) ? false : _cells[i - 1];
                area[1] = _cells[i];
                area[2] = (i == _cells.Length - 1) ? false : _cells[i + 1];
                yield return area;
            }
        }
    }
}
```


### Programクラスを定義

そして最後は、Programクラスです。
CellularAutomatonクラスを使ってルール0からルール255までのセルオートマトンを作成しています。

```cs
using NGraphics;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Cellautomaton {
    class Program {
        static void Main(string[] args) {
            for (int i = 0; i < 256; i++) {
                int rule = i;
                int generations = 180;
                int width = generations * 2;
                var ca = new CellularAutomaton(rule, width);
                var lines = ca.AllGenerations().Take(generations);
                MakeImage(rule, lines, width);
            }
        }

        static void MakeImage(int rule, IEnumerable<bool[]> lines, int width) {
            var canvas = new EasyCanvas(width, width / 2 + 10);
            int y = 10;
            foreach (var line in lines) {
                for (int x = 0; x < width; x++) {
                    if (line[x])
                        canvas.SetPixel(x, y, Brushes.DarkGray);
                }
                y++;
            }
            canvas.Write($"Rule{rule}.png");
        }
    }
}
```

このコードを実行すれば、Rule0.pngからRule255.pngまでの256個の画像ファイルが作成できます。

ソースコードは[GitHub](https://github.com/gushwell/CSharpAlgorithmCollection2)でも公開しています。


## 結果

ルール30とルール90以外で特徴的な図形をいくつかピックアップして掲載します。
<br><br>
### ルール110

![Rule110.png](https://qiita-image-store.s3.amazonaws.com/0/29415/8aab8c36-b24b-8f97-3f02-024d2a96daac.png "Rule110.png")
<br><br>
### ルール118
![Rule118.png](https://qiita-image-store.s3.amazonaws.com/0/29415/d6e06d1f-ebbe-8719-4051-2707fab57ca8.png "Rule118.png")

<br><br>

### ルール131
![Rule131.png](https://qiita-image-store.s3.amazonaws.com/0/29415/b707aae2-9599-1426-8e38-21c358b1f223.png "Rule131.png")

<br><br>
### ルール150
![Rule150.png](https://qiita-image-store.s3.amazonaws.com/0/29415/c0c94475-2e52-d777-f291-f6ee12815ba8.png "Rule150.png")

<br><br>
### ルール161
![Rule161.png](https://qiita-image-store.s3.amazonaws.com/0/29415/f2b6950c-0f39-fc17-dd76-d49cc8480780.png "Rule161.png")

<br><br>
### ルール241

![Rule241.png](https://qiita-image-store.s3.amazonaws.com/0/29415/71258dbc-7821-11af-ed66-f786cfff8ac4.png "Rule241.png")