IchigoJam では、実行できるプログラムの最大容量は1024バイトである。
少し複雑なプログラムを書こうとすると、すぐにオーバーしてしまいがちである。
LRUN 命令を用いてプログラムを切り替えながら実行することで、より長いプログラムを扱うこともできる。
しかし、数バイト~数十バイト程度のオーバーであれば、プログラムの書き方を工夫して短くすることで、最大容量以内に収めることができるかもしれない。
ここでは、そのようなプログラムを短くするためのテクニックをいくつか紹介する。
※IchigoJamはjig.jpの登録商標です。
プログラムを短くしようとすると、可読性を損ねる可能性がある。
小手先のテクニックによるプログラムの短縮は、最低限にとどめるのがよいだろう。
行の長さを奇数から偶数に縮める
IchigoJam では、それぞれの行を以下のような形式でメモリ上に格納する。
要素 | バイト数 |
---|---|
行番号 | 2 |
行データのバイト数 | 1 |
行データ | 可変 |
ゼロ | 1 |
以下は、いくつかの行をメモリに格納し、格納されたデータを観察した例である。
(コメント ' のつもりで誤って別の記号を使用しており、実行すると Syntax error になるけど、そこは今回の本質じゃないから許して)
これを見るとわかるように、(行番号を除いた) 行の長さが奇数のときはゼロが1個追加され、行データのバイト数は常に偶数で格納される。
そのため、行の長さを1バイト減らすとき、偶数から奇数にしても容量は減らないが、奇数から偶数にすると容量が2バイト減る。
よって、1バイトだけ減らすときは、現在長さが偶数の行よりも奇数の行を狙ったほうが効果が期待できる。
長さが偶数の行では、1バイトしか減らせないテクニックは適用せず、可読性をとったほうがいいかもしれない。
行番号を1刻みにする
IchigoJam を含む BASIC でのプログラミングでは、行番号は10刻みや100刻みで用いるのが通例である。
これを1刻みにすることで、余計なゼロを省き、長さを短縮することが出来る。
とはいえ、IchigoJamにおいては、行番号はバイナリで格納されるため、これにより直接プログラムの容量を減らすことはできない。
ただし、GOTO 命令や GOSUB 命令 で用いる行番号の桁数を減らすことにより、プログラムを短くすることには繋がる。
文字列以外の空白を全て消去する
IchigoJam のプログラムでは、文字列内以外の空白は全て無視される。
逆にいえば、空白を入れても意味が無いので、このような空白は全て削除することでプログラムを短くできる。
できるだけ1行に詰め込む
IchigoJam では、:
で区切ることにより、複数の命令を1行に書くことができる。
命令を複数行ではなく1行にまとめることで、行のまわりのデータ (行番号・長さ・終端のゼロ) を削減し、プログラムの容量を減らすことができる。
ただし、以下の場合は、行を分割するべきである。
- タイトル (最初の行) に余計なものを入れたくない場合
-
GOTO
やGOSUB
での飛び先として用いる場合 -
IF
による条件付き実行の範囲を終わらせる場合 - 1行の最大の長さの制限 (行番号のテキストを含めて200バイトまで) を超えそうな場合
数値は原則として十進数を用いる
IchigoJam における数値の表記では、二進数、十進数、十六進数を用いることができる。
二進数はかなり長くなるため、プログラムを短くしたい場合は論外である。
また、十六進数を用いると十進数より短くなりそうであるが、十六進数であることを表す記号 #
をつけなければいけないため、IchigoJam で扱う数値の範囲では効果が打ち消され、長さが変わらないか、逆に長くなることもある。
よって、数値は原則として十進数を用いることで、プログラムを短くできる。
例外として、十進数で5桁の負の数は、十進数で書く (符号を入れて6文字) より十六進数で書く (#
を入れて5文字) ほうが短くなる。
同じ意味で、短い命令・関数・演算子を用いる
IchigoJam では、同じ意味の命令・関数・演算子が複数用意されていることがある。
このような場合は、当然短い命令などを用いた方がプログラムが短くなる。
ただし、一部の命令などはバージョンによって使えないことがあるので、古いバージョンにも対応させたい場合は注意するべきである。
具体的には、以下のような命令・関数・演算子がある。
種類 | 長い表現 | 短い表現 |
---|---|---|
命令 | PRINT |
? |
命令 | LOCATE |
LC |
命令 | GOSUB |
GSB |
命令 | RETURN |
RTN |
命令 | CLEAR |
CLV |
命令 | REM |
' |
関数 | VPEEK |
SCR |
演算子 | == |
= |
演算子 | MOD |
% |
演算子 | AND |
&& |
演算子 | NOT |
! |
定数を使わない
LEFT などの定数は、それが表す数値に置き換えることで、短く表現できる。
ただし、UP は数値に置き換えても長さが変わらない。(もしくは、数値の表現方法によってはより長くなってしまう)
言い換えにより式を短くする
同じ内容の計算を言い換えて用いる演算子を工夫することで、式を短くできることがある。
ここでは、いくつかの具体例を示す。
IF 命令の条件式では、0 を偽、0 以外を真とみなす。
よって、「一致しない」ことの比較 A<>B
のかわりに「引き算」 A-B
を用いることができる。
固定の数値と比較する場合、「以下」A<=5
のかわりに、数値をずらして「未満」A<6
を用いることができる。
同様に、「以上」A>=5
も「超」A>4
に変換できる。
「0である」A=0
は、論理否定 !A
に置き換えることができる。
「0でない」 A<>0
は、論理否定の否定 !!A
に置き換えることができる。また、条件式ではそのまま A
としてもよい。
2のべき乗 (128、256など) の掛け算 A*256
は、左シフト A<<8
に置き換えることができる。
割られる数が非負の値しかとらない場合、同様に割り算 A/256
も右シフト A>>8
に置き換えることができる。
逆に、左シフト A<<1
を掛け算 A*2
に、非負の値の右シフト A>>1
を割り算 A/2
に置き換えることもできる。
さらに、演算子の優先順位をしっかり把握することで、余計な (見やすくするための) カッコの削減に繋がることがあある。
ルールメイカーになろう! 1+2x3=? 演算子優先順位仕様変更の言い訳
今回挙げた例では、A
などのシンプルな計算対象を扱った。
しかし、計算対象が複数の項からなる式になると、その中で用いられる演算子の優先順位によっては、カッコをつけないと式の意味が変わってしまう可能性がある。
たとえば、A+B=0
と !A+B
は意味が異なる。
今回挙げた例は1バイトだけ短縮するものばかりなので、カッコをつけて2バイト増やしてしまうと逆効果である。
条件分岐を逆転する
たとえば
IF!A 処理内容1 ELSE 処理内容2
という処理があったとすると、ELSE
の前後を反転させて
IFA 処理内容2 ELSE 処理内容1
とすることで、!
を消して1バイト短縮できる。
また、たとえば
100 IFAGOTO120
110 処理内容1
120 処理内容2
という処理があったとすると、
100 IF!A 処理内容1
110 処理内容2
とすることで、GOTO
を消去するとともに行数を減らして容量を減らすことができる。
「処理内容」に IF
を含む場合や、「処理内容」が長い場合など、単純にこのような置き換えができない場合もある。
条件分岐を計算で置き換える
条件分岐を計算式に置き換えることで、短く表現できることがある。
単に文字数を減らすだけでなく、IF
を消去することで、より多くの処理を1行にまとめることができるようになることもある。
たとえば、
IFAB=1ELSEB=-1
という処理は、論理否定は0か1を返すという性質を利用して
B=1-2*!A
と表現できる。
また、
IFAB=B+1
という処理は、
B=B+!!A
と表現できる。
A
に0か1しか格納しない場合、
B=B+A
としてもいいかもしれない。
データの埋め込みに配列を用いる
POKE 命令を用いてバイト列を書き込む場合、1バイトずつしか指定できない。
一方、配列を用いると、2バイトずつ指定できる。
このため、たとえば2バイトを表現するとき、POKE
では 123,234
のように最大で7文字使うが、配列では #EA7B
と最大でも5文字で表現できる。
長いデータになると、この差が積み重なって大きな効果を生む可能性がある。
さらに、配列においても、LET 命令を用いて複数の要素の値をまとめて設定できる。
POKE
では書き込み先のアドレスを指定するのに対し、LET
では書き込み先の添字を指定するので、ここでも長さの差に繋がる。
そもそも、命令自体も POKE
より LET
が短い。
POKE#800,123,234,123,234
LET[0],#EA7B,#EA7B
キャラクターパターンなど配列以外のデータを埋め込みたい場合、一旦配列の領域に起き、COPY 命令で目的の場所にコピーする方法が考えられる。
もちろんその前に配列に格納されていたデータは失われるが、配列に使うデータを格納する前であるプログラムの最初でこれを行えば、問題にはなりにくいだろう。
データの埋め込みに文字列を用いる
POKE
や配列などによる数値を用いたデータの埋め込みでは、1文字で最大16種類 (0
~F
) の情報しか表現できない。
さらに、数バイトおきに情報の無い区切り文字 ,
を書かなければならない。
そのため、文字数に対する情報の密度が少なくなりがちである。
一方、文字列では、使いやすい範囲 (#
~~
) だけでも1文字で92種類の情報を表現でき、文字間の区切り文字も不要である。
よって、数値よりも高密度で情報を乗せることができる。
ただし、複雑なデコードが必要な表現方法で埋め込んでしまうと、デコーダの実装で文字数を食ってしまう。
「埋め込む値に 35 (#
の文字コード) を足した文字コードの文字を使う」など、目的に合わせつつ、なるべく単純なエンコードを用いるのがよいだろう。
密度の高いエンコードにより削減できる長さと、デコーダにかかるコスト (長さ) のバランスを取ることも大事である。
埋め込むデータを圧縮する
埋め込むデータの性質 (例えば、0の連続が多い画像) によっては、データを圧縮することでより少ない文字数で表現できることがある。
圧縮したデータを文字列などで表現し、キャラクターパターンRAMや配列などのメモリ上にプログラムで展開して用いるのが基本となるだろう。
どのようなアルゴリズムで圧縮し、それをどう表現すれば効率がよいかは、扱うデータに大きく依存する。
シンプルな圧縮アルゴリズムの例としては、ランレングス圧縮などがある。
プログラムの仕様を削る
どうしてもプログラムが短縮できないときの最終手段として、実現しようとする内容を妥協し、より単純な仕様に変更するという選択肢もある。
たとえば、
- 複数の操作方法 (矢印キーとWASDなど) への対応をやめ、1個だけにする
- タイトル画面やゲームオーバー画面などをなくす
- リトライ機能をなくし、1回終わったらプログラムを終了するようにする
- 効果音をなくす
- よりシンプルなルールにする
- 表示する画像をより短く表現できるものに変更する
などが考えられる。