Vue + SVGでセル・オートマトンルール30を描く
セル・オートマトンについて触れる機会があり、実際に手を動かしてみた方が勉強になるかと、学習してみたのでそのメモnoteを残します。
セル・オートマトン?
ウィキペディアより
セル・オートマトン(英: cellular automaton、略称:CA)とは、格子状のセルと単純な規則による、離散的計算モデルである。計算可能性理論、数学、物理学、複雑適応系、数理生物学、微小構造モデリングなどの研究で利用される。
ざっくりと砕けた説明をするならば、「格子状のセルの色(状態:0 or 1)を、他のセルの色とあるルールに従って決めるもの」でしょうか...。実際に手を動かした方が理解が早いので、実際にVue(JavaScript)コードを書いてみましょう。
ルール30?
セル・オートマトンにはいくつかのルールがあり、その1つがルール30と呼ばれるものです。
ルール30を適応すると、上の画像のような入力に対する出力になります。Vueの関数(methods)にしてみるとこんな感じ:
methods: {
rule30(p1, p2, p3) {
if (p1 === 1 && p2 === 1 && p3 === 1) {
return 0;
}
if (p1 === 1 && p2 === 1 && p3 === 0) {
return 0;
}
if (p1 === 1 && p2 === 0 && p3 === 1) {
return 0;
}
if (p1 === 1 && p2 === 0 && p3 === 0) {
return 1;
}
if (p1 === 0 && p2 === 1 && p3 === 1) {
return 1;
}
if (p1 === 0 && p2 === 1 && p3 === 0) {
return 1;
}
if (p1 === 0 && p2 === 0 && p3 === 1) {
return 1;
}
return 0;
},
},
実際にSVGでセル・オートマトンを描いてみる
rule30の関数は用意できたので、実際にSVGでセル・オートマトンを描いてみましょう。
はじめにSVGで縦100コ × 横200コの格子(セル)を用意します。(SVGのviewBoxの設定は0 0 200 100)にします。
<template>
<div>
<svg viewBox="0 0 200 100" stroke="currentColor" stroke-width=".1" fill="none">
<g v-for="(row, rowIndex) in 100" :key="rowIndex">
<g v-for="(cell, cellIndex) in 200" :key="cellIndex">
<rect :x="cellIndex" :y="rowIndex" width="1" height="1">
</rect>
</g>
</g>
</svg>
</div>
</template>
ブラウザで表示するとこんな感じ(モアレが発生していますが...):
マス目ができましたね!この1つ1つのマス目(SVGのrect要素)に、初期条件を設定して、セル・オートマトンのルール30を適応させましょう。
セルの初期条件を設定し、ルール30を描画する
今回は縦100コ × 横200コの格子を用意しました。初期条件は、1行目の200コの格子に設定します。1行目の中央の格子だけを黒に設定し、残りの199行はルール30を反映させます。
export default {
data() {
return {
cells: [], // 縦100 × 横200の行列(2次元配列)を入れる変数
};
},
methods: {
// ルール30の関数を定義
rule30(p1, p2, p3) {
if (p1 === 1 && p2 === 1 && p3 === 1) {
return 0;
}
if (p1 === 1 && p2 === 1 && p3 === 0) {
return 0;
}
if (p1 === 1 && p2 === 0 && p3 === 1) {
return 0;
}
if (p1 === 1 && p2 === 0 && p3 === 0) {
return 1;
}
if (p1 === 0 && p2 === 1 && p3 === 1) {
return 1;
}
if (p1 === 0 && p2 === 1 && p3 === 0) {
return 1;
}
if (p1 === 0 && p2 === 0 && p3 === 1) {
return 1;
}
return 0;
},
},
created() {
this.cells[0] = []; // 1行目の格子の情報を入れる配列(初期条件)を定義
for (let i = 0; i < 200; i += 1) {
this.cells[0][i] = i === 100 ? 1 : 0; // 横100番目の格子だけ1(黒)を設定
}
// 残りの199行分をループする
for (let i = 1; i < 100; i += 1) {
this.cells[i] = [];
// i行目の200格子分をループする
for (let j = 0; j < 200; j += 1) {
// methodsで定義したrule30関数を利用する
this.cells[i][j] = this.rule30(
this.cells[i - 1][j - 1], // 色を決める格子の左上の格子の情報を引数として渡す
this.cells[i - 1][j], // 色を決める格子の上の格子の情報を引数として渡す
this.cells[i - 1][j + 1], // 色を決める格子の右上の格子の情報を引数として渡す
);
}
}
},
}
Vueのコードはこれで完成です。あとはルール30とVueによって生成した二次元配列を、SVGのrect要素に反映させます。
<template>
<div>
<svg class="bg-white" viewBox="0 0 200 100"
stroke="currentColor" stroke-width=".1" fill="none"
>
<g v-for="(row, rowIndex) in cells" :key="rowIndex">
<g v-for="(cell, cellIndex) in row" :key="cellIndex">
<rect :x="cellIndex" :y="rowIndex" width="1" height="1"
:fill="cell === 1 ? 'black' : 'none'"
></rect>
</g>
</g>
</svg>
</div>
</template>
これをブラウザで見てみると...?
できた!こんな単純な関数でこんな不思議な形が出来上がります。
ルール90も描画してみる
ついでですが、ルール90も描画してみましょう。ルール90の関数はこんな感じ:
export default {
data() {
return {
cells: [], // 縦100 × 横200の行列(2次元配列)を入れる変数
};
},
methods: {
rule90(p1, p2, p3) {
if (p1 === 1 && p2 === 1 && p3 === 1) {
return 0;
}
if (p1 === 1 && p2 === 1 && p3 === 0) {
return 1;
}
if (p1 === 1 && p2 === 0 && p3 === 1) {
return 0;
}
if (p1 === 1 && p2 === 0 && p3 === 0) {
return 1;
}
if (p1 === 0 && p2 === 1 && p3 === 1) {
return 1;
}
if (p1 === 0 && p2 === 1 && p3 === 0) {
return 0;
}
if (p1 === 0 && p2 === 0 && p3 === 1) {
return 1;
}
return 0;
},
},
created() {
this.cells[0] = [];
for (let i = 0; i < 200; i += 1) {
this.cells[0][i] = i === 100 ? 1 : 0;
}
for (let i = 1; i < 100; i += 1) {
this.cells[i] = [];
for (let j = 0; j < 200; j += 1) {
this.cells[i][j] = this.rule90(
this.cells[i - 1][j - 1],
this.cells[i - 1][j],
this.cells[i - 1][j + 1],
);
}
}
},
},
こうすると...?
Wow!