rust nannouで1次元セルオートマトンを描く

nannou

1次元セルオートマトンをRust nannouで描いてみました。

セルオートマトンといえば、ライフゲームが有名ですが、今回は1次元のセルオートマトンです。

今回は力業でやったので、それを解説します。

そもそもセルオートマトンって何?

セルオートマトンは、格子状に並んだセルをある単純なルールに従って離散計算モデルです。

英語で書くとcellular automatonで、細胞状の自動装置みたいな?まぁ細胞状(格子状)に並んだものを自動的に計算してく感じ?ですね。

で、これが、単純なモデルなのに、複雑な模様を描いたり、ライフゲームのように増えたり進んだり消えたりする、不思議なものです。

生物や物理、暗号などいろんな世界で、応用されているような広がりを持ったものです。(詳しくは調べてください)

1次元セルオートマトンをrust nannouで描く方法

では、1次元セルオートマトンを描いていきましょう。まずは1次元セルオートマトンについて解説して、そのあと、コードに進んでいきます。

1次元セルオートマトンについて

今回は、自身とその両端の状態から次の状態を決めることを考えます。

例えば111なら中央が0、110なら1とか、そういう風にルールを決めて、次の状態を求めていきます。

そうすると組み合わせは次のように8通りできます。

組み合わせ111110101100011010001000

あとは、これの8パターンのルールを決めていきます。

例えば今回描いたのはルール90というやつで

現在の状態111110101100011010001000
次の中止セルの状態01011010

0,1,0,1,1,0,1,0この2進数を10進数にすると90になるっていうことでルール90って言います。Wolfram codeって言うそうです。

1次元セルオートマトンを収納するStructを作っていく

まずは、modelに1次元セルオートマトンを収納する配列を作っていきます。

1次元セルオートマトンは先ほども説明した通り、現在の状態と次の状態の2つの状態があるので、それぞれを格納するために2つ配列作っていきます。

life_1が現在の状態life_2が次の状態。

struct Model {
    life_1: [u8; 100],
    life_2: [u8; 100],
}

今回は、1列100個のセルで進めます。(別に1,000でも1万でもいいですよ)

セルオートマトンの初期状態を作る

モデルでセルオートマトンの配列を確保したら、まずは、初期状態をmodelに作っていきます。
これは簡単

fn model(app: &App) -> Model {
    let _window = app
        .new_window()
        .view(view)
        .key_pressed(key_pressed)
        .build()
        .unwrap();
    Model {
        life_1: [
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        ],
        life_2: [
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        ],
    }
}

ゼロがいっぱい(笑)

life_1のところに一か所だけ1がありますが、これがきっかけで進みます。
life_1の初期状態によって、この後の形状が変わります。

現在から次の状態を規則に従って更新する

保存する場所と初期状態を定義できたので、現在の状態から、次の状態を計算して、更新していきましょう。

fn update(_app: &App, model: &mut Model, _update: Update) {
    for i in 1..model.life_1.len() - 1 {
        if model.life_1[i - 1] == 0 && model.life_1[i] == 0 && model.life_1[i + 1] == 0 {
            model.life_2[i] = 0;
        } else if model.life_1[i - 1] == 0 && model.life_1[i] == 0 && model.life_1[i + 1] == 1 {
            model.life_2[i] = 1;
        } else if model.life_1[i - 1] == 0 && model.life_1[i] == 1 && model.life_1[i + 1] == 0 {
            model.life_2[i] = 0;
        } else if model.life_1[i - 1] == 0 && model.life_1[i] == 1 && model.life_1[i + 1] == 1 {
            model.life_2[i] = 1;
        } else if model.life_1[i - 1] == 1 && model.life_1[i] == 0 && model.life_1[i + 1] == 0 {
            model.life_2[i] = 1;
        } else if model.life_1[i - 1] == 1 && model.life_1[i] == 0 && model.life_1[i + 1] == 1 {
            model.life_2[i] = 0;
        } else if model.life_1[i - 1] == 1 && model.life_1[i] == 1 && model.life_1[i + 1] == 0 {
            model.life_2[i] = 1;
        } else if model.life_1[i - 1] == 1 && model.life_1[i] == 1 && model.life_1[i + 1] == 1 {
            model.life_2[i] = 0;
        }
    }
    model.life_1 = model.life_2;
}

状態の更新に使うルールはルール90を使っていきます。

現在の状態111110101100011010001000
次の中止セルの状態01011010
for i in 1..model.life_1.len() - 1

forで現在の状態を読み取っていきます。

自身と前後両端の状態によって、次の状態を更新していくので、

スタートは最初の1セル目(配列でいうとゼロ番目)を飛ばして2セル目から始めます。最後は1セル前まで読み取っていきます。
なんでかっていうと基本は自身は3つのうちの中間だからです。

if model.life_1[i - 1] == 0 && model.life_1[i] == 0 && model.life_1[i + 1] == 0 {
             model.life_2[i] = 0;

以下if文で、それぞれの状態を判別して、life_2に更新された情報を入れていきます。

そして、ルールに沿ってlife_1からlife_2を生成できたら、それをlife_1に代入して、現在の状態に更新します。

まぁ、life_2は次の更新のためのバッファーみたいなもんです。

描画する

では、生成された、データを描画していきましょう。

描画する内容は、現在の状態(life_1)を描画します。

fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();
    if frame.nth() == 0 || app.keys.down.contains(&Key::R) {
        draw.background().color(BLACK);
    }
    let f = frame.nth();
    for i in 0..model.life_1.len() {
        if model.life_1[i] == 1 {
            draw.rect()
                .w_h(5.0, 5.0)
                .x((i as f32) * 5.0 - 200.0)
                .y(-5.0 * (f as f32) + 300.0)
                .color(WHITE);
        }
    }
    draw.to_frame(app, &frame).unwrap();
}

forで、life_1の配列の長さ文、データを取得していきます。

取得したデータで1のが入っているところについて、■で塗りつぶしていけば、描画できます。

これは、マンデルブロ集合ご描いた時とほぼ同じ手法です。気になる方は、マンデルブロ集合の描画の記事もどうぞ>>rust nannouでマンデルブロ集合を描いてみよう!

こんな感じに出来上がります。

まとめ

というわけで、今回はrust nannouで一次セルオートマトンの描画をしました。

今回は1行が100個のセルで作りましたが、もっと増やしてもいいですし、開始セルをランダムに与えてみたり、ルールを変えてみるとか、あとは、色を付けてみるとか、様々な工夫によって表現できるものがたくさんあります。

二次のセルオートマトンに拡張してみるのも面白いとおもいますので、是非チャレンジしてみたいですね。

では今回はこの辺で、またね!

コメント

  1. […] 前回、rust nannouで1次元セルオートマトンを描くでRule90でセルオートマトンを描くっていうプログラムをしました。 […]

タイトルとURLをコピーしました