TL; DR
1~100までのFizzBuzz
Eb Eb Eb Eb Eb Eb Eb Eb Eb Eb F F F F F F F F |: C C C C C C Fm :| C Cm |: D B Cm :| F F F F F F F F F F |: Bb Bb Bb Bb Bb Bb Bb Bb Bb Bb Fm :| Em Em Em Em Em Em Em Em Em Em C Cm B Bm Bb Bb Bb Bb Bb Bb Bb Bb Bb Bb Bb |: F F F F F F C C C C C C C C C G G G G G G D D D D D D D D D D D A A A A A A A A A A A Bbm :| F F F F C C C C C C Dm Dm Dm Dm A Ebm Ebm Ebm Ebm Ebm C#m C#m C#m B Bm C Cm Ab |: G C B Gb E |: Cm :| C Cm |: Bm Em D Dm F#m :| C Cm |: D A Cm :| B Bm Ab Db |: G#m :| Ab G#m |: B Bm Gm B Bm Db C#m :| Ab G#m |: C#m C#m C#m F Fm X C Cm X A Am X X C Cm G#m :| Bb Eb |: Bbm :| Bb Bbm |: B Bm G Gm |: Gm :| B Bm Eb Ebm :| Bb Bbm |: Ebm Ebm Ebm Ebm Ebm G Gm X D Dm X A Am X X Bbm :| B Bm G Gm |: F A Am |: D Dm X Fm :| F Fm |: Fm :| A Am B Bm X Gm :| Eb Ebm X Bbm :|
はじめに
昨年、Brainf*ck系のEsolang 「Cholc」を作成しました。
Cholcでは、音楽のコード進行によって計算を行います("Chord" + "Calculate" でCholcです)。
| operator | role |
|---|---|
{メジャーコード} |
ポインタが指す値をインクリメント |
{マイナーコード} |
ポインタが指す値をデクリメント |
|: |
ポインタが指す値が0のとき、対応する :| の直後へ飛ぶ |
:| |
ポインタが指す値が0でないとき、対応する |: の直後へ飛ぶ |
v |
入力から1バイト読み込み(asciiとして)ポインタへ保存する |
X |
ポインタが指す値を(asciiとして)出力へ書き出す |
Playgroundでは実際に和音が鳴るのでぜひ試してみてください。
ポインタ移動はコードとコードの間で行われ、その距離は「完全5度いくつ分離れているか」によって決まります(詳しくは過去の記事をご覧ください)。
本記事では、CholcのFizzBuzz実装について紹介します。
余談ですが、1年以上前に作ったので思い出しながら書いてます。自分が書いたとは思えない......(esolangあるある)
ソースコード
冒頭と同じコードをもう少し見やすく書き直しました。
# 初期化
Eb Eb Eb Eb Eb Eb Eb Eb Eb Eb
F F F F F F F F
|: C C C C C C Fm :|
C Cm
|: D B Cm :|
F F F F F F F F F F
|: Bb Bb Bb Bb Bb Bb Bb Bb Bb Bb Fm :|
Em Em Em Em Em Em Em Em Em Em
C Cm B Bm Bb Bb Bb Bb Bb Bb Bb Bb Bb Bb Bb
|: F F F F F F C C C C C C C C C G G G G G G D D D D D D D D D D D A A A A A A A A A A A Bbm :|
F F F F C C C C C C Dm Dm Dm Dm A
Ebm Ebm Ebm Ebm Ebm
Dbm Dbm Dbm
B Bm C Cm
# メインループ
Ab
|:
# インクリメントと繰り上がり処理
G
C
B
Gb
E
|: Cm :|
C Cm
|: Bm Em D Dm Gbm :|
C Cm
|: D A Cm :|
# 3の倍数判定
B Bm Ab
Db
|: Abm :|
Ab Abm
|: B Bm Gm B Bm Db Dbm :|
Ab Abm
|:
Dbm Dbm Dbm # %3 = -3
F Fm X C Cm X A Am X X # print fizz
C Cm
Abm
:|
# 5の倍数判定
Bb
Eb
|: Bbm :|
Bb Bbm
|: B Bm G Gm |: Gm :| B Bm Eb Ebm :|
Bb Bbm
|:
Ebm Ebm Ebm Ebm Ebm
G Gm X D Dm X A Am X X
Bbm
:|
B Bm
# 倍数でない場合の数字の出力
G Gm
|:
F A Am |: D Dm X Fm :|
F Fm |: Fm :|
A Am
B Bm X # print digit1
Gm
:|
# 改行出力
Eb Ebm X
# cnt--
Bbm
:|
アドレスの使い方
アドレス 0 を F として、各アドレスを以下のように使用していました。整地とか一切していないので汚いです
| -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Eb | Bb | F | C | G | D | A | E | B | Gb | Db | Ab | Eb | Bb | F | C | G | D | A |
'\n' |
cnt | 一時変数 |
48/一時変数 |
flg | 10の位の文字 | 10の位 | 1の位繰り上がり | 1の位の文字 | 1の位 | 3で割ったあまり | 3の倍数かどうか | 5で割ったあまり | 5の倍数かどうか | 'f' |
'i' |
'B' |
'u' |
'z' |
| $\hspace{2em}$ | $\hspace{2em}$ | $\hspace{2em}$ | $\hspace{4em}$ | $\hspace{2em}$ | $\hspace{3em}$ | $\hspace{2em}$ | $\hspace{4em}$ | $\hspace{3em}$ | $\hspace{2em}$ | $\hspace{4em}$ | $\hspace{4em}$ | $\hspace{4em}$ | $\hspace{4em}$ | $\hspace{2em}$ | $\hspace{2em}$ | $\hspace{2em}$ | $\hspace{2em}$ | $\hspace{2em}$ |
-
i: ループ変数(1ずつ増える) -
cnt: ループをいつ打ち切るか(今回は100) -
flg:iが3の倍数でも5の倍数でもない(=数字を表示する)かどうか
初期化
まずは定数や初期値を書き込みます。
# '\n'
Eb Eb Eb Eb Eb Eb Eb Eb Eb Eb
# '0'
F F F F F F F F
|: C C C C C C Fm :|
# 10の位の文字 = 1の位の文字 = '0'
C Cm
|: D B Cm :|
# cnt = 100
F F F F F F F F F F
|: Bb Bb Bb Bb Bb Bb Bb Bb Bb Bb Fm :|
# 1の位繰り上がり = -10
Em Em Em Em Em Em Em Em Em Em
# 文字 'F'. 'i', 'b', 'u', 'z'
C Cm B Bm Bb Bb Bb Bb Bb Bb Bb Bb Bb Bb Bb
|: F F F F F F C C C C C C C C C G G G G G G D D D D D D D D D D D A A A A A A A A A A A Bbm :|
F F F F C C C C C C Dm Dm Dm Dm A
# 5で割ったあまり(※実装上-5している)
Ebm Ebm Ebm Ebm Ebm
# 3で割ったあまり(※実装上-3している)
Dbm Dbm Dbm
10の位と1の位の文字を '0' に初期化しています。これは出力に使うアドレスで、
i が増えるたびに10の位、1の位の実際の値と同じタイミングでインクリメントすることで、'1', '2',... と表示上の数字も増えていきます。
ループ直前の C Cm はポインタ移動のための命令です。ループの判定(ポインタの値が0でないならループ開始)に C のアドレスを参照しています。値を書き換えたくないためインクリメントとデクリメントで相殺しています。
(この実装はいたるところに出てきます)
あまりや1の位繰り上がりカウントをマイナスしているのは、判定処理をしやすくするためです(後述)。
メインループ
外側のループで i を1から1ずつ増やしていきます。
ループ内の初期化
G # flgを有効化
C # 一時変数 = 1(繰り上がりするかどうかのフラグ有効化)
B # 1の位の文字++
Gb # 1の位++
E # 1の位繰り上がりカウンター++
|: Cm :| # 繰り上がりカウンターが0でない(まだ繰り上がらない)場合、繰り上がりフラグを0に
C Cm # 繰り上がった場合
|: Bm Em D Dm Gbm :| # 1の位の表示を'0'、繰り上がりカウンターを-10、1の位を10に戻す
C Cm # 繰り上がった場合
|: D A Cm :| # 10の位と10の位の文字をインクリメント(Cmでフラグを0に戻す)
繰り上がりカウンター E を-10にしていたのは、E |: Cm :| のところで「カウンターが0でないなら繰り上がりフラグを0(無効化)」という実装が書きたかったからです。あらかじめ-10 しておけば、繰り上がりのタイミングで Eが 0 になります。
このように、境界条件がちょうど0になるようにつじつま合わせする設計パターンはあまりチェックのところでも登場します。
|: Bm Em D Dm Gbm :| のループでは、Bm, Em, Gbm でそれぞれ1の位の表示、繰り上がりカウンター、1の位をデクリメントしています。
Gb (1の位)の値ははこの時点で 10 なので、ループは Gb が 0 になるまで繰り返され B, Emも-10されます。結果、すべて1の位が 0 のときの値に戻ります。
また、このループ内に一見意味のない D Dm が入っていますが、これはポインタ移動のためのつなぎです。
Cholcでは+-6以上のアドレスを一度に移動できないため、長距離のポインタ移動が発生する場合中間地点のアドレスを踏む命令を入れる必要があります。
倍数判定
B Bm Ab # 3の倍数フラグを有効化(B Bmはポインタ移動のつなぎ)
Db # 3で割ったあまり++
|: Abm :| # 3で割ったあまりが0でないなら、3の倍数フラグを無効化
Ab Abm # 3の倍数の場合
|: B Bm Gm B Bm Db Dbm :| # 数字を表示するかどうかのフラグflgを無効化(B Bm、Db、Dbmはポインタ移動のつなぎ)
Ab Abm # 3の倍数の場合
|:
Dbm Dbm Dbm # 3で割ったあまりを-3に戻す
F Fm X C Cm X A Am X X # 文字列 "fizz" を出力
C Cm # ポインタ移動のつなぎ
Abm # 3の倍数フラグを0に戻してループを抜ける
:|
Db |: Abm :| は先ほどの繰り上がりカウンターと同じ設計パターンです。3で割ったあまり Db の初期値を -3 にしておいたので、インクリメントされて3の倍数のときちょうど 0 になります。
|: B Bm Gm B Bm Db Dbm :| の最後の Db、Dbm は(ループを抜けた直後の命令との)ポインタ移動のつなぎですが少しトリッキーです。
flgの減算は1回出来ればよいため、このループは1回で抜ける必要があります。つまり最後の命令の時点でポインタが指す値が0である必要があります。 Db は3で割ったあまりなので、今は0であることが確定しています。よって安全に最後の命令に使うことができます。
直後の5の倍数の処理も同様です。
数字を出力
G Gm # (数字出力フラグ)flgが有効な場合
|:
F A Am |: D Dm X Fm :| # 10の位が0でなければ10の位を表示
F Fm |: Fm :| # 0に戻しておく
A Am # つなぎ
B Bm X # 1の位を表示
Gm # ループを抜ける
:|
ここの F も少しトリッキーな使い方をしています。ループ開始直後でインクリメントし 1 にしてから、 |: D Dm X Fm :| では必ずループが1回以下で終わるよう最後に Fm を入れてポインタの値が 0 になるようにしています。
ループが回らなかった場合1が残ってしまうため、F Fm |: Fm :| で改めて0に戻しています。
おわりに
以上、CholcのFizzBuzz紹介でした。私のesolang力が低く、これ以上難しい実装は手に負えなそうです...
