はじめに
パイソン山中:「TypeScriptでAIに作らせる?そんな誰もが思いつくような安っぽい作り方はあなた(ライフゲーム)には似合いません。サブスクの代償としても対価に合わない。もっともフィジカルで、もっともプリミティブで、そしてもっともフェティッシュなやり方(Brainf*ck)でいかせていただきます。」
ということで、Brainf*ckというプログラミング言語に入門してみました。
入門した私はHello worldを試した後、ちょうどいい難易度に思えたライフゲーム(Conway's Game of Life)を実装することにしました。当初、三日くらいで終わるかなと考えていたのですが、実際には2週間以上費やしてしまいました。
この記事では、2週間で学んだこととBrainf*ckによるライフゲームの実装を解説します。
Brainf*ckとは
Brainf*ckとは、Urban Müllerという人が開発したプログラミング言語で、><+-.,[]
の8つのコマンドのみでチューリング完全を実現しています。
メモリのインデックス | 0 | 1 | 2 | 3 | ... |
---|---|---|---|---|---|
メモリの値 | 0 | 0 | 0 | 0 | ... |
ポインタの位置 | ^ | ... |
上記のように0で初期化されたメモリセルの配列とそのポインタを、下記の通りに操作することで動作します。
コマンド | 処理 |
---|---|
> | ポインタをインクリメント |
< | ポインタをデクリメント |
+ | ポインタが指すメモリセルの値をインクリメント |
- | ポインタが指すメモリセルの値をデクリメント |
. | ポインタが指すメモリセルの値をASCIIコードに対応させて出力 |
, | 入力から1バイト読み込んで、ポインタが指すメモリセルに代入 |
[ | ポインタの指すメモリセルの値が0なら、対応する] までジャンプ |
] | ポインタの指すメモリセルの値が0でなければ、対応する[ までジャンプ |
上記のコマンドのどれにも当てはまらない文字は全てコメントとして扱う |
例えば、下記のコードは>
でポインタを1移動させ、移動後に72個の+
によってメモリセルの値を72(HのASCIIコード)までインクリメントし、.
で「H」を出力、最後に<
で元々ポインタが差していたメモリセルに戻るコードです。
>
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.
<
実行後、メモリセルの状態は下記のようになっています。
メモリのインデックス | 0 | 1 | 2 | 3 | ... |
---|---|---|---|---|---|
メモリの値 | 0 | 72 | 0 | 0 | ... |
ポインタの位置 | ^ | ... |
作ったもの
3*3サイズのライフゲームを作りました。
ライフゲームとは、生命の個体数や分布をシミュレーションする簡単なゲームです。
オセロの盤のようなフィールドで、フィールド上のセルはそれぞれ 「生」 か 「死」 の状態にあります。
それぞれのセルがお互いに下記のルールで影響を及ぼし合いながら生きたり死んだりを繰り返します。
ルール
誕生:死んだセルに隣接する生きたセルがちょうど3つの場合、次のステップでは生きたセルになる。
生存:生きたセルに隣接する生きたセルが2つまたは2つなら、生き続ける。
過疎:生きたセルに隣接する生きたセルが1つ以下なら、死ぬ。
過密:生きたセルに隣接する生きたセルが4つ以上なら、死ぬ。
私が作ったライフゲームでは、生きたセルを*
、死んだセルを-
で表現します。
ライフゲームでは、周期的に繰り返し同じ形になるパターンがいくつも存在し、振動子と呼ばれます。
振動子の中で代表的なものが、ブリンカーと呼ばれる縦に3つ生きたセルが繋がった状態と横に3つ生きたセルが繋がった状態で振動するものです。
これを、私が作ったライフゲームで試してみると下記のように出力されます。
-*-
-*-
-*-
---
***
---
# 以下決められたループ回数まで繰り返し
実装はこちらです。
迫力を出すために、コメントとインデントを外してみました。
この後詳しく解説するのでご安心ください。
>>+>>>>>>>+>+>>>>>>>>+>>>>>>>>+>>>>>>>+>+>>>>>>>>+>>>>>>>>+>>>>>>>+>+>>>>>>>>+
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+++++[->[>-
>>>++++++++++++++++++++++++++++++++++++++++++.[-]>>>>>>>+>>>>>>>>>>>>>>>>+>>>>>>>>+
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>[->>>+++++++++++++++++++++++++++++++++++++++++++++.
[-]<<]<+>>>>>>>[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]
<<<<<<<<<+>>>>>>>>>>>>>>>>+>>>>>>>>+>>>>>>>>+>>>>>>>>+
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>[->>>+++++++++++++++++++++++++++++++++++++++++++++.
[-]<<]<+>>>>>>>[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]
<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>+>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<]>[-
>>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]<+>>>>>>>>>>>++++++++++.[-]
<<<<[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]<<<<<<<<<<<<<<<<<+
<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+>>>>>>>>>>>>>>>>+>>>>>>>>+
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>[->>>+++++++++++++++++++++++++++++++++++++++++++++.
[-]<<]<+>>>>>>>[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]<<<<<<<<<+<<<<<<<<+
<<<<<<<<+<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+>>>>>>>>+>>>>>>>>+>>>>>>>>+
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]>[->>>+++++++++++++++++++++++++++++++++++++++++++++.
[-]<<]<+>>>>>>>[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]<<<<<<<<<+
<<<<<<<<<<<<<<<<+<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+>>>>>>>>+
<<<<<<<<<<<<<<<<<<<<<<<<<<]>[->>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]
<+>>>>>>>>>>>++++++++++.[-]<<<<[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]
<<<<<<<<<<<<<<<<<+<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<]>[-
>>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]<+>>>>>>>[>-
>>>++++++++++++++++++++++++++++++++++++++++++.[-]<<<<<<<<<+<<<<<<<<+<<<<<<<<+
<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<]>[-
>>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]<+>>>>>>>[>-
>>>++++++++++++++++++++++++++++++++++++++++++.[-]<<<<<<<<<+<<<<<<<<<<<<<<<<+
<<<<<<<<+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]>[-
>>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]<+>>>>>>>>>>>++++++++++.[-
]++++++++++.[-]
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[->->>-->+
<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]>[->>--->+<[+++[-]>-]>[-
<<<<+>>>>>]<<<]<+>>>>>>>[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-
<<<<+>>>>>]<<<<]>[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]<+>>>>>>>[->->>-->+<[->->+
<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]>[->>--->+<[+++[-]>-]>[-
<<<<+>>>>>]<<<]<+>>>>>>>[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-
<<<<+>>>>>]<<<<]>[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]<+>>>>>>>[->->>-->+<[->->+
<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]>[->>--->+<[+++[-]>-]>[-
<<<<+>>>>>]<<<]<+>>>>>>>[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-
<<<<+>>>>>]<<<<]>[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]<+>>>>>>>[->->>-->+<[->->+
<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]>[->>--->+<[+++[-]>-]>[-
<<<<+>>>>>]<<<]<+>>>>>>>[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-
<<<<+>>>>>]<<<<]>[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]<+>>>>>>>[->->>-->+<[->->+
<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]>[->>--->+<[+++[-]>-]>[-
<<<<+>>>>>]<<<]<+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<]
Brainf*ckの実行環境
Brainf*ckをターミナル上で動かすのはとても簡単で、下記のコマンドでインタプリタをインストールするだけで完了します。
brew install brainfuck
実行するには、.bf
拡張子をつけてコードを保存した後brainfuck
コマンドで実行します。
brainfuck hello.bf
しかし、初心者がいきなりターミナル上で実行しながらコーディングするのはお勧めしません。ターミナル上に出力されるのは.
で出力した文字のみであり、メモリセルの状態が分からないためデバッグが非常に難しいためです。
そこで、私はカチカチさんという方が公開している下記のインタプリタを使いました。
こちらのインタプリタは、#
をブレークポイントとして使うことができ、メモリの状態を確認しながら実行できるので、ターミナルで実行しながら開発する時と比べてかなり楽になります。
注意
先ほど登場したライフゲームのコードはカチカチさん作成のインタプリタでは動きません。
おそらく使用するメモリの数が多すぎるためです。
動かしてみたい方はターミナルでお試しください。
Brainf*ckでループを書く
+++++ // ループ回数(5)
[
-
// 繰り返したい処理はじめ
>+++
// 繰り返したい処理おわり
<
]
Brainf*ckでループを書くには、まずループ回数を保持するメモリセルを決めて、ループ回数分だけインクリメントします。
+++++ // ループ回数(5)
メモリのインデックス | 0 | 1 | 2 | 3 | ... |
---|---|---|---|---|---|
メモリの値 | 5 | 0 | 0 | 0 | ... |
ポインタの位置 | ^ | ... |
次に、[
を使います。現在ポインタが差しているメモリセルの値は5(0でない)なので、対応する]
に飛ばずループ内の処理が実行されます。
ループ内では、ループ回数を保持しているメモリセルの値を1デクリメントします。
// ループ開始
[ // ループに入る
- ループ回数をデクリメント
メモリのインデックス | 0 | 1 | 2 | 3 | ... |
---|---|---|---|---|---|
メモリの値 | 4 | 0 | 0 | 0 | ... |
ポインタの位置 | ^ | ... |
その後、ループ回数を保持しているメモリセルから移動し、繰り返したい処理を実行します。
今回は、一つポインタを移動した後そのセルの値3増やします。
// 繰り返したい処理はじめ
>+++
// 繰り返したい処理おわり
その後、ループ回数を保持しているメモリセルにポインタを戻します。こうすることで、ループ一周ごとにループ回数を保持しているメモリセルの値が減り、0になったところでループを抜けます。
<
]
メモリのインデックス | 0 | 1 | 2 | 3 | ... |
---|---|---|---|---|---|
メモリの値 | 0 | 15 | 0 | 0 | ... |
ポインタの位置 | ^ | ... |
豆知識
[-]
と書くことで、メモリセルの値が0以上のどんな値でも0になるまでデクリメントすることができるので、値をリセットしたい時に便利です。
Brainf*ckで分岐を書く
+>+<
[
[-]>-
// 対象のセルが0以外の時に実行したい処理はじめ
>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.
[-]
// 対象のセルが0以外の時に実行したい処理おわり
]>[
-
// 対象のセルが0の時に実行したい処理はじめ
>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.
[-]
// 対象のセルが0の時に実行したい処理おわり
>
]
セットアップ
Brainf*ckの分岐は、判定対象が0か、0以外かで判定を行います。
今回はインデックスが0のメモリセルを判定対象のセルとして、対象のセルの値が0以外の時は"A"
、0の時は"B"
を出力してみます。
まずは判定対象が0以外の時の解説をするので判定対象のセルに1を入れ、その隣のセル(以降フラグセルと呼ぶ)にも1をセットしてポインタの位置を判定対象のセルに戻します。
+>+<
メモリのインデックス | 0 | 1 | 2 | 3 | ... |
---|---|---|---|---|---|
メモリの値 | 1 | 1 | 0 | 0 | ... |
ポインタの位置 | ^ | ... |
判定対象のメモリセルが0以外の場合の処理
判定対象のメモリセルが0以外なので、一つ目のループに入ります。
ただし、入った直後にある[-]>-
で判定対象のセルとフラグセルの値はどちらも0にリセットされます。
判定対象のセルの値が0になるということは、繰り返し回数が1
のループです。Brainf*uckでの分岐は、ループの応用で作成します。
// ポインタは判定対象のメモリセル上からスタート
[
[-]>-
// 対象のセルが1の時に実行したい処理はじめ
>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.
[-]
// 対象のセルが1の時に実行したい処理おわり
]
メモリのインデックス | 0 (判定対象) | 1 (フラグセル) | 2 | 3 | ... |
---|---|---|---|---|---|
メモリの値 | 0 | 0 | 0 | 0 | ... |
ポインタの位置 | ^ | ... |
判定対象のメモリセルが0の場合の処理
判定対象のメモリセルが0の場合、最初のループには入りません。
そのため、最初のループの次に書かれている>
の処理でフラグセルに移動します。フラグセルは、セットアップ時に1になっているので二つ目のループに入ります。
もし判定対象のメモリセルが0以外だった場合、一つ目のループ内の処理でフラグセルが0になっているので二つ目のループには入りません。感動です。
>[
-
// 対象のセルが0の時に実行したい処理はじめ
>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.
[-]
// 対象のセルが0の時に実行したい処理おわり
>
]
Brainf*ckによるライフゲームの実装方法
だいぶ迫力が減って、さらにリファクタリングの余地がありありなことがバレてしまってお恥ずかしいですが、コメントとインデントを戻したコードを見ながら解説します。
> ループ用のメモリを空ける
初期設定 size = 3*3
>+>>>> >>> x0
+>+>>>> >>> x1
>+>>>> >>> x2
>+>>>> >>> x3
+>+>>>> >>> x4
>+>>>> >>> x5
>+>>>> >>> x6
+>+>>>> >>> x7
>+>>>> >>> x8
74th cell
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<< x0
< ループ用のメモリへ移動
+++++ ループ回数を設定
[
-
>
====メインの処理====
print x0
[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]
increment x1 x3 x4
>>>>> >>+
>>>>> >>>
>>>>> >>>+
>>>>> >>>+
<<<<< <<<
<<<<< <<<
<<<<< <<<
<<<<< <<
<<<]
>
[->>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]
<+
>>>>> >> x1
print x1
[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]
increment x0 x2 x3 x4 x5
<<<<< <<<<+>>>> >>>>> 0
>>>>> >>+ 2
>>>>> >>>+ 3
>>>>> >>>+ 4
>>>>> >>>+ 5
<<<<< <<<
<<<<< <<<
<<<<< <<<
<<<<< <<
<<<]
>
[->>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]
<+
>>>>> >>
print x2
[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]
increment x1 x4 x5
<<<<< <<<<+>>>> >>>>> 1
>>>>> >> 3
>>>>> >>>+ 4
>>>>> >>>+ 5
<<<<< <<<
<<<<< <<<
<<<<< <<
<<<]
>
[->>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]
<+
>>>>> >>
print LF
>>>>+++++ +++++.[-]<<<<
print x3
[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]
increment x0 x1 x4 x6 x7
<<<<< <<<< 2
<<<<< <<<+ 1
<<<<< <<<+ 0
>>>>> >>>
>>>>> >>>
>>>>> >>>>
>>>>> >>+ 4
>>>>> >>> 5
>>>>> >>>+ 6
>>>>> >>>+ 7
<<<<< <<<
<<<<< <<<
<<<<< <<<
<<<<< <<
<<<]
>
[->>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]
<+
>>>>> >>
print x4
[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]
increment x0 x1 x2 x3 x5 x6 x7 x8
<<<<< <<<<+ 3
<<<<< <<<+ 2
<<<<< <<<+ 1
<<<<< <<<+ 0
>>>>> >>>
>>>>> >>>
>>>>> >>>
>>>>> >>>>
>>>>> >>+ 5
>>>>> >>>+ 6
>>>>> >>>+ 7
>>>>> >>>+ 8
<<<<< <<<
<<<<< <<<
<<<<< <<<
<<<<< <<
<<<]
>
[->>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]
<+
>>>>> >>
print x5
[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]
increment x1 x2 x4 x7 x8
<<<<< <<<<+ 4
<<<<< <<<
<<<<< <<<+ 2
<<<<< <<<+ 1
>>>>> >>>
>>>>> >>>
>>>>> >>>
>>>>> >>>>
>>>>> >> 6
>>>>> >>>+ 7
>>>>> >>>+ 8
<<<<< <<<
<<<<< <<<
<<<<< <<
<<<]
>
[->>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]
<+
>>>>> >>
print LF
>>>>+++++ +++++.[-]<<<<
print x6
[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]
increment x3 x4 x7
<<<<< <<<< 5
<<<<< <<<+ 4
<<<<< <<<+ 3
>>>>> >>>
>>>>> >>>
>>>>> >>>>
>>>>> >>+ 7
<<<<< <<
<<<]
>
[->>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]
<+
>>>>> >>
print x7
[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]
increment x3 x4 x5 x6 x8
<<<<< <<<<+ 6
<<<<< <<<+ 5
<<<<< <<<+ 4
<<<<< <<<+ 3
>>>>> >>>
>>>>> >>>
>>>>> >>>
>>>>> >>>>
>>>>> >>+ 8
<<<<< <<
<<<]
>
[->>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]
<+
>>>>> >>
print x8
[>->>>++++++++++++++++++++++++++++++++++++++++++.[-]
increment x4 x5 x7
<<<<< <<<<+ 7
<<<<< <<< 6
<<<<< <<<+ 5
<<<<< <<<+ 4
>>>>> >>>
>>>>> >>>
>>>>> >>>
>>>>> >>>>
<<<]
>
[->>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<<]
<+
>>>>> >>
print LF
>>>>+++++ +++++.[-]<<<<
print LF
>>>>+++++ +++++.[-]<<<<
74th cell
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<< x0
update x0
[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]
>
[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]
<+
>>>>> >> x1
update x1
[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]
>
[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]
<+
>>>>> >> x2
update x2
[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]
>
[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]
<+
>>>>> >> x3
update x3
[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]
>
[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]
<+
>>>>> >> x4
update x4
[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]
>
[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]
<+
>>>>> >> x5
update x5
[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]
>
[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]
<+
>>>>> >> x6
update x6
[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]
>
[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]
<+
>>>>> >> x7
update x7
[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]
>
[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]
<+
>>>>> >> x7
update x8
[->->>-->+<[->->+<<[++++++[-]>>-<]>>[-<<<<<+>>>>>>]<<]>[-<<<<+>>>>>]<<<<]
>
[->>--->+<[+++[-]>-]>[-<<<<+>>>>>]<<<]
<+
>>>>> >> 74th cell
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<< x0
====ここまでメインの処理====
< ループ回数を保持しているセルに戻る
]
準備
最初の24行は、メインループに入る前の準備を行います。
インデックスが0のメモリセル(以降mem[0]
)は、ループ回数を保持するために使用します。
> ループ用のメモリを空ける
その後は、9マスあるライフゲームの盤面を初期化します。
9マスをそれぞれ下記のようにX0~X8と呼ぶことにします。
X0~X8は下記のようにメモリセルに対応させ、それぞれ0
(死に対応)または1
(生に対応)を取ります。
mem[1] = X0
mem[9] = X1
...
mem[1+8n] = Xn
...
mem[65] = x8
Xn
を保持するを保持するメモリセルと、Xn+1
を保持するメモリセルの間を8セル空けているのは、出力、ループ、分岐などに使用するためです。
初期設定 size = 3*3
>+>>>> >>> x0
>+>>>> >>> x1
>+>>>> >>> x2
+>+>>>> >>> x3
+>+>>>> >>> x4
+>+>>>> >>> x5
>+>>>> >>> x6
>+>>>> >>> x7
>+>>>> >>> x8
74th cell
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<<<<< <<<<<
<< x0
< ループ用のメモリへ移動
+++++ ループ回数を設定
>+>>>> >>> x8
74th cell
<<<<< <<<<<
こちらの箇所は、8回ポインタを右に移動した後何もせずに10回左に移動しているので、左に2回移動するコードに置き換えることができます。
しかし、私の頭のメモリが足りずこういうのをいちいちリファクタリングしているとすぐに今何やってるのか分からなくなってしまうのでそのまま残しています。
セルの値に応じて文字を出力する
先述した分岐を使って生きたセルなら*
を、死んだセルなら-
を出力する部分です。
X0
の状態を保存しているmem[1]
が生きたセル、つまり1なら一つ目のループに入り*
を出力します。
そうでないなら、二つ目のループに入り-
を出力します。
一つ目のループでは、セルの状態を更新するときに使う隣接するセルに生きたセルがいくつあるかという情報も同時に用意しておきます。
mem[4+8n]
(X0
の場合はmem[4]
)を、Xn
の近傍にいくつ生きたセルがあるかを保持しておくためのメモリセルとしています。
print x0
[
>->>>++++++++++++++++++++++++++++++++++++++++++.[-] // *を出力
increment x1 x3 x4
>>>>> >>+
>>>>> >>>
>>>>> >>>+
>>>>> >>>+
<<<<< <<<
<<<<< <<<
<<<<< <<<
<<<<< <<
<<<
]>[
->>>+++++++++++++++++++++++++++++++++++++++++++++.[-]<< // ーを出力
]
<+
>>>>> >> x1
X1~X8
でも同じように出力と生きたセルのカウントを行います。
近傍の生きたセルの数に応じてセルの状態を更新する
最後の難関、セルの状態を更新する処理です。
その前にルールをもう一度おさらいしておきます。
ルール
誕生:死んだセルに隣接する生きたセルがちょうど3つの場合、次のステップでは生きたセルになる。
生存:生きたセルに隣接する生きたセルが2つまたは2つなら、生き続ける。
過疎:生きたセルに隣接する生きたセルが1つ以下なら、死ぬ。
過密:生きたセルに隣接する生きたセルが4つ以上なら、死ぬ。
通常のプログラミング言語を使っている時はルールを愚直に実装しても良いですが、今回はBrainf*ckを使っているので、メモリセルの値が0か、0以外かでしか判定できません。
そこで、次のように実装しました。
let counter = 隣接する生きたセルの数;
if (Xn === 1) { // Xnが生きているセルなら
countr = counter -2;
Xn = 0; // Xnを一度死んだセルにする
if (counter !== 0) {
counter = counter - 1;
if (counter !== 0) {
return;
} else { // 隣接する生きたセルの数がちょうど3なら
Xn = 1; // Xnを生きたセルにする
}
} else { // 隣接する生きたセルの数がちょうど2なら
Xn = 1; // Xnを生きたセルにする
}
} else { // Xnが死んだセルなら
counter = counter -3;
if (counter !== 0) {
return;
} else { // 隣接する生きたセルの数がちょうど3なら
Xn = 1; // Xnを生きたセルにする
}
}
Brainf*ckで書くとこうなります。
コメント中+
をplus
、-
をminus
と書いているのは、+
や-
と書くとコードとして扱われてしまうからです。
コメントしていない箇所は基本分岐のためのセットアップとなります。
ポインタがXnを指している状態からスタート
[ Xnが生きているセルなら
->- // Xnを一度死んだセルにする
>>-- counter = counter minus 2
>+<
[
->- counter = counter minus 1
>+<<
[
+++++ +[-] counter = 0 counterが負の数になっていたらリセットできないのでプラスしてからマイナスしていますが、こんなにプラスする必要ないです。
>>-<
]>>[ 隣接する生きたセルの数がちょうど3なら
-
<<<<<+ Xnを生きたセルにする
>>>>> > ]
<<
]>[ 隣接する生きたセルの数がちょうど2なら
-
<<<<+ Xnを生きたセルにする
>>>>>
]
<<<<
]>[ Xnが死んだセルなら
-
>>--- counter = counter minus 3
>+<
[
+++[-] counter = 0
>-
]>[ 隣接する生きたセルの数がちょうど3なら
-
<<<<+ Xnを生きたセルにする
>>>>>
]
<<<
]
<+
>>>>> >> X(n plus 1)へ移動
あとは、出力と更新を好きなだけ繰り返せば完成です。
おわりに
Brainf*ck
面白いです。
Brainf*ck
の案件をお持ちの方は是非弊社までご一報下さい。