1次元セルオートマトンをRust nannouで描いてみました。
セルオートマトンといえば、ライフゲームが有名ですが、今回は1次元のセルオートマトンです。
今回は力業でやったので、それを解説します。
そもそもセルオートマトンって何?
セルオートマトンは、格子状に並んだセルをある単純なルールに従って離散計算モデルです。
英語で書くとcellular automatonで、細胞状の自動装置みたいな?まぁ細胞状(格子状)に並んだものを自動的に計算してく感じ?ですね。
で、これが、単純なモデルなのに、複雑な模様を描いたり、ライフゲームのように増えたり進んだり消えたりする、不思議なものです。
生物や物理、暗号などいろんな世界で、応用されているような広がりを持ったものです。(詳しくは調べてください)
1次元セルオートマトンをrust nannouで描く方法
では、1次元セルオートマトンを描いていきましょう。まずは1次元セルオートマトンについて解説して、そのあと、コードに進んでいきます。
1次元セルオートマトンについて
今回は、自身とその両端の状態から次の状態を決めることを考えます。
例えば111なら中央が0、110なら1とか、そういう風にルールを決めて、次の状態を求めていきます。
そうすると組み合わせは次のように8通りできます。
組み合わせ | 111 | 110 | 101 | 100 | 011 | 010 | 001 | 000 |
あとは、これの8パターンのルールを決めていきます。
例えば今回描いたのはルール90というやつで
現在の状態 | 111 | 110 | 101 | 100 | 011 | 010 | 001 | 000 |
次の中止セルの状態 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 0 |
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を使っていきます。
現在の状態 | 111 | 110 | 101 | 100 | 011 | 010 | 001 | 000 |
次の中止セルの状態 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 0 |
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個のセルで作りましたが、もっと増やしてもいいですし、開始セルをランダムに与えてみたり、ルールを変えてみるとか、あとは、色を付けてみるとか、様々な工夫によって表現できるものがたくさんあります。
二次のセルオートマトンに拡張してみるのも面白いとおもいますので、是非チャレンジしてみたいですね。
では今回はこの辺で、またね!
コメント
[…] 前回、rust nannouで1次元セルオートマトンを描くでRule90でセルオートマトンを描くっていうプログラムをしました。 […]