プチコンTips

プログラムリスト短縮テクニック

【プチコン/mkII 両対応】

 プチコンはフリーメモリが1MBあり一般的な使い方でプログラムリストを短縮する必要性を感じることはほとんどないかもしれません。それでも、行数や文字数に制限のある1行プログラム1画面プログラムなどを作ろうとしている人には意味があるし、コード短縮の方法にはどのようなものがあるのかという興味を持っている人はいるでしょう。
 そういう人のために少しでも手助けになるようにここでまとめてみることにしました。(一部、honoPさんをはじめ他者が見つけたテクニックも含まれている)
なお、上記のうち剰余においては量が多いため別のページとなっています。

 剰余を使ったリスト短縮テクニック


個人的には剰余(%)と論理否定(!)が大好きです。1文字で済む上に応用範囲が広いので(笑)
ポケコンではIFが好きでしたが、ポケコンと違ってTHENが省略できないし、プチコンは演算速度が速いため高速化の恩恵は小さいので・・・。



スペースと「:」(コロン)の省略



 プチコンのBASICはスペースや「:」(コロン)は省略できる場合が多いので省略を行えばリスト短縮ができます。
 どういう場合に省略できるかというと数字の後(引数を完全に省略可能な命令においては省略をしていない場合)やカッコの後ならば基本的に省略できます。これは機械的な作業であるため難しいことは何もありません。
 
 プチコンプログラムリスト短縮 (外部サイトへのリンク)
 (※省略可能なものを自動的に省略してくれるWeb上で動作するソフトです)

 省略するとリストが見づらくなるためおすすめできるものではありませんが、省略できる場面は多いため短縮効果は結構大きいです。省略そのものは機械的な作業ではあるため簡単なのですが、1画面プログラムでは命令の順序を入れ替えていかに省略できる形にもっていけるかということを考える難しさ(面白さ)が残されています。

 《 IF文において並び替えによってスペースが省略できる例 》

 IF A*!B THEN 〜 → IF!B*A THEN 〜 (スペースを省略OK)
 IF A$==""THEN 〜 → IF""==A$THEN 〜 (スペースを省略OK)

 これはIF命令だけに限ったものではなくほとんどの命令において命令の後に英数字以外が続いた場合にはスペースが省略が可能なためです。これによって順番を入れ替えるだけでスペースやコロンが省略可能な形になる場合が多くあります。(上記の「プチコンプログラムリスト短縮」ソフトではそこまで行ってくれません)

↑TOP


( )カッコの省略



 リスト短縮をする場合にはマニュアルに記載されている演算優先順位をしっかり把握しておく必要があります。

演算優先順位
()[] で囲まれた部分
マイナス NOT !
関数
* / %(乗除余)
+ − (加減)
== != < <= > >= (比較)
AND OR XOR

 この演算優先順位を把握しておけば不要なカッコを省略することができリスト短縮ができます。

 例えば論理式は演算優先順位が低いためカッコを省略できるのは他の演算子を含まない場合に限られます。

  A=(B>2) → A=B>2 ・・・ これはOK
  A=4*(B>2) → A=4*B>2 ・・・ これは不可

 しかし、特定の条件下においては他の演算子を含んでいてもカッコの省略が可能になります。

  A=(C==1)*(B>2) → A=(C==1)*B>2  ・・・ これはOK

 カッコを省略した場合は論理式の演算優先順位が低いため A=((C==1)*B)>2 と解釈されてしまうのですが、論理式 (C==1) が取りうる値は0と1のみであるため (C==1) が成立する場合にBの値が2より大きい場合には (C==1)*B の値も2よりも大きくなりカッコを外して優先順位が変わっても問題ないことが分かります。(Cの値が1以外ならば(C==1)の値は0になるためBの値に関係なくカッコを外す前も外した後もAの値は0になるため問題はない)

 このようにカッコを外して演算優先順位が変わっても結果的に問題が無ければカッコの省略は可能です。

  A=-(C==1)*(B>2) → A=-(C==1)*B>2  ・・・ これは不可

 カッコを外して演算優先順位が変わる場合には、その変数や演算によって取る値をすべて把握しておく必要があり省略して問題ないかを判断するのは難しいです。(今回の例のように「論理式同士の乗算で、それ以外の演算子を含まず、一方の論理式が「変数>正の数」のようになっている場合」を除けばかなり限られるかも)

  A*(B+1) → A*B+A

 このようにただの展開式ならば変数の値に無関係に成立するためカッコを外すのは容易です。(二項式でカッコ内に定数「1」があるときのみリスト短縮が可能)

↑TOP


整数化の省略



 プチコンは命令における引数の小数部分はすべて無視されるためわざわざ整数化をする必要はありません。(プチコンは「命令」と「関数」はカッコが必要か否かで分かるようになっていて「命令」というのは引数にカッコを使用しないもの)

 GPSET FLOOR (X),FLOOR (Y),15  → GPSET X,Y,15

 BEEPにおいては音高指定は1オクターブが4096なので半音あたりでは4096/12341.333…となります。ここで小数部分が無視されるからといって341×半音数としてしまうと0.3333…×2オクターブで最大で値が8ずれてしまいます。掛ける前の値の小数部分を省略しては駄目で掛けた後の値の小数部分を省略する必要があります。
 半音の指定値を341.34にすれば2オクターブの範囲内ならば最大でも8192.16なので小数部分が無視されてちょうど上限の8192になります。ただし、負数の場合は-8192.16で単純に無視されるならば問題ないのですが、正しくは引数の小数部分は無視されるのではなくFLOORが実行されたのと同じ状態になるため-8193となって範囲外となりエラーになります。そこで、半音あたりの指定値は最大で1ずれるけど341.33とするのがベターといえます。(8ずれても普通の人には分からないため341でもほとんど問題になることはない)

 小数部分が無視されたらなぜFLOORが実行されたのと同じ状態(それを越えない最大の整数)になるかというとプチコンにおける32bit固定小数点がどのように扱われているかを把握していれば簡単に分かります。(詳しくはプチコン講座の第5回のコラムを参照)
 引数が負数になる場合には小数の状態だと指定値が1ずれることになるためそれが問題になるのかどうか(例えばGPSETで負数の指定だと画面外になるため全く問題はない)は良く考えておく必要があります。問題になる場合には事前に小数部分を省略した見た目通りの整数化を行うか負数になってないかの判定を行う必要があります。また、引数に負数を受け付けない命令の場合はプチコンの演算誤差によって0ではなくわずかに負数になってしまう場合があるためこの場合も引数を小数で指定している場合には負数にならないように気を付ける必要があります。

 命令の引数だけではなく配列変数の添え字においても小数部分は無視されます。
 関数においてもRND()RAD()BUTTON()、文字関数全般は引数の小数部分が無視されます。
 これらを小数で指定することはほぼないと思いますが、小数で指定することの多いRADは小数部分が無視されるということはちゃんと知っておく必要があり、「度」から「ラジアン」へ正確に変換するためにはRAD(A)よりもPI()/180*Aの方がベターといえます。一見長くなっているように見えますが、実はRADは0〜360以外は受け付けないため範囲外にならないような処理を行った場合にはこれでも短縮できています。(さらにリスト短縮するにはRADの項目を参照)

↑TOP


論理式



基本なのであえてここで書くまでもないことですが一応書いておきます。

論理式は条件を満たす時は「1」、満たさない時は「0」の値をとります。
例えば、十字ボタンで左右移動を行う場合にはBにBUTTON関数の値が入っているならばIF文で書くと下記のようになります。

 IF (B AND 4)==4 THEN X=X-1
 IF (B AND 8)==8 THEN X=X+1


 これを論理式で書けば次のようになります。

 X=X-((B AND 4)==4)+((B AND 8)==8)

これだけ見ると短縮されているようですが、他にも短縮可能な方法はいくらでもあります。

 X=X+SGN((8AND B)-(4AND B)) SGNを使用)
 X=X-(4AND B)/4+(8AND B)/8 (ビット演算ANDで得られた値をそのまま使用)
 X=X+!(4AND B)-!(8AND B) 論理否定を使用)
 X=X-(B%16-B%4+1)%3+1 剰余を使用)

 まぁ、論理式は基本ですから・・・。


 また論理式を使うときに注意しなくてはならないのは演算優先順位です。論理式では一般的にカッコを使うことが多いのは比較を示す演算子の優先順位が低いためですが他の演算子を含まないものにおいてはカッコを省略が可能です。

 A=(B==1) → A=B==1 (カッコを省略OK)

しかし、A=A+(B==1)のように他の演算子が含まれる場合A=A+B==1という風にカッコを省略してしまうと演算優先順位が変わってしまうためA=((A+B)==1)という意味になり誤動作を招いてしまいます。

↑TOP


!(論理否定)



!(論理否定)は、「TrueとFalseを入れ替える」ものですが簡単に言えば「1(厳密に言えば0以外)を0」に「0を1」にできます。(論理否定はmkIIのみ使用可能)
これを使えば「Aが0と1を繰り返す」というのも簡単にできます。

 A=(A==0) (論理式) → A=!A (論理否定)

IF文では活用できる場面が非常に多いです。

この論理否定を使えばBにBUTTON関数の値が入っている場合は下記のようにすれば左右移動も可能です。

 X=X-!(B-4)+!(B-8)

 ただし、これは論理式で表した場合にX=X-(B==4)+(B==8)になってしまうため左右移動以外の操作はできません。
 この式を他のボタンと併用動作が可能な形に変形すれば下記のようになります。

 X=X+!(4AND B)-!(8AND B)
 ※それぞれの符号がプラスマイナス反転していることに注意

 これは「左を押していない時(B AND 4の値が0の時)はプラス方向(画面右)に進む」「右を押していない時(B AND 8の値が0の時)はマイナス方向(画面左)に進む」という意味です。したがって、他のボタンを押しているか否かに関係なく、左を押せば左に進み、右を押せば右に進み、左右両方とも押していないときは相殺されて静止するわけです。
 これを論理式で表せば次のようになります。

 X=X+((B AND 4)==0)-((B AND 8)==0)

 これを見てのように論理否定を使用時にはいかに「イコールゼロ」の形にすることができるかが重要になってきます。


 また、この論理否定を2つ重ねるとSGN的な使い方も可能になります。

 SGN (A) → !!A (A≧0の場合)
          つまり、(A!=0) と同等の意味

これは、最初の論理否定で「A>0の場合は0、A=0の場合は1」になり、次の論理否定で「A>0は0だったのが1、A=0は1だったのが0」になるためです。
こちらは負の数にならない限りはSGNと同様に使えるので活用は容易だと思います。

 X=X-SGN(4AND B)+SGN(8AND B) → X=X-!!(4AND B)+!!(8AND B)

 あと除算を行う場合においては0除算対策が必要不可欠となってきます。これは除算を行う前にあらかじめ分母がゼロになるかどうかをIF文で判定して例外処理を行えばいいだけのことですが、リストが長くなるし、処理速度がダウンしてしまう場合もあります。
 しかし、「分母がゼロになるときは代わりに1で割る」という方法によって短縮化が可能です。これは10/Aならば10/(A+!A)とするだけで済むからです。分母がゼロの時は分子にゼロを掛ける(この例では10*!!Aとすればいい)などのフォローが必要になる場合もありますが、ポリゴン表示プログラムなど多数のプログラムでリスト短縮や高速化のために使用しています。

↑TOP


*(文字列の掛け算)



 プチコンmkIIでは文字列の掛け算ができるようになりました。例えばPRINT "A"*32とすればAを32個表示可能です。
 これによって同じ文字が5文字以上続く場合はリスト短縮が可能です。

 また0を掛けた場合には文字を表示しないこと(=0文字表示)も可能であるため特定の条件の時に文字を表示させる場合には次のように短縮が可能です。

 IF A==2THEN PRINT "HELLO" → ?"HELLO"*!(A-2) (A=2以外のときは0文字表示している)

 また、この0を掛けるというのを応用すれば次のように文字列の初期化処理が短縮ができます。

 A$=""FOR I=0TO 9A$=A$+B$(I)NEXT → FOR I=0TO 9A$=A$*!!I+B$(I)NEXT (1文字短縮)

 それから1画面プログラムを作っているとLOCATE命令1つでも長く感じてしまうのですがそれはLOCATEはスペース込みで7文字あるだけではなく指定座標がX、Yともに2桁ならば全部で12文字も消費してしまうからです。

 これも文字列の掛け算を使えば短縮が可能になる場合があります。
 例えばメインルーチン内で毎回CLSを実行しているならばPRINT " "*256"A"でLOCATE 0,8:PRINT "A"と同じ場所に表示が可能です。これはCLSを実行していない場合でもメインルーチン内の最初に1回LOCATEを実行していればそれ以降は省略が可能になります。
 ただし、制御文字が使えないプチコンではY座標が小さくなる場所には表示できないためPRINTを実行する順番には注意が必要です。そして、スコアなど表示文字数が変わるものの後にこれを使うと全体的なずれがあるためそれが問題ない場面でしか使うことができません。そして、リアルタイムで表示する場所が変わるキャラ表示にも使えません。あと、多くのスペースを表示することになるので処理速度では不利になります。

 しかし、使用できる場面は多いため「あと数文字分短縮できないと1行(1画面)に収まらない」という場合には活用できると思います。

↑TOP


AND



 ビット演算が行える演算子ANDはボタンの同時入力判定を行う場合には必須の基本テクニックなのですが、それを使って剰余の変わりにすることもできます。

 A=B%16 → A=B AND 15 (B≧0の場合)

 これは、16に限らず2の累乗の剰余が可能です。
一見長くなっているけどビット演算は整数化も行ってくれるので結果に整数値が欲しい場合に短縮できます。

 これを活用すればA=A+1AND 3で0、1、2、3・・・を繰り返すカウンタになります。(ビット演算の演算優先順位は最も低く他の演算子が含まれる場合はそれが優先して実行されるためカッコは不要)
 まぁ剰余ならばA=(A+1)%4で済みますが・・・。

 また、ANDの前と後ろの引数は入れ替えても問題がないためA=B AND 4A=4AND Bとすることでスペース1つ分短縮できます。


 次に、十字ボタンで左右移動させる場合に論理式で書くと下記のようになります。(BにBUTTON関数の値が入っているとき)

 X=X-((B AND 4)==4)+((B AND 8)==8)

 ここで、論理式を使わずにビット演算ANDで得た値をそのまま使えば短縮することができます。

 X=X-(4AND B)/4+(8AND B)/8
 ※上記のように4AND Bとすることでスペースが省略できるため2文字分短縮しており、トータルでは8文字短縮できている)

 この方法はX座標移動量が2の時も追加コストがかからず、移動量が4や8のときはさらに2文字分短縮できるというのがメリットになっています。

↑TOP


ASC



 A=ASC(Z$)で変数Z$に含まれる文字列の先頭の文字コードA=ASC("ABC") ではAの値は65になる)が取得できますが、Z$の値がNull(つまり、Z$="" )の場合はエラーを返してしまいます。
 このためASCを使う場合にはその文字変数がNullかどうかを判断するIF文がどうしても必要になってきます。

 しかし、A=ASC(Z$+CHR$(0)) とすればZ$がNullの場合もエラーにならずA=0となるためIF文は不要になります。

↑TOP


BEEP



 マニュアルにも記載されていますが、BEEPはすべての引数を省略した場合には0番の音を鳴らします。

  BEEP 0 → BEEP

 また、特定条件の時のみ音を鳴らす(もしくは、音を鳴らさない)という場合は、IF文を使って条件判断しなくても BEEP ,,Z*127 のように音量を調整することによって可能です。(この場合は鳴らす場合はZ=1、鳴らさない場合はZ=0となる)

 その場合は次のようにすればさらに短縮が可能です。

  BEEP ,,Z*127 → BEEP,,-Z (カンマ「,」は英数字でないためBEEPとの間のスペースも省略できる)

 これは、第3引数が負の数の場合は127(最大)と同じ扱いになるためです。(0〜127の値以外はすべて127扱いになる)

 IF文を使わずこのように音量調節で音を鳴らしたり鳴らなくしたりする場合には正常動作をさせるためにはメインルーチンにWAITもしくはVSYNCを挟んでおくかそれに相当する時間分のウェイトを入れておく必要があります。これはプチコンのBEEPは最大8音まで重ねることができますが、WAIT 1がない場合は1フレーム以内に無音のBEEPが8回実行される恐れがあり、その場合には最初の音を出す前に消えてしまうためです。(60fpsより遙かに速い場合で無ければメインルーチンそのものがウェイト代わりになるためWAITが無くても音は鳴る)

↑TOP


BGMPLAY



 特定条件の時のみ音楽を鳴らす(もしくは、音楽を鳴らさない)という場合は、IF文を使って条件判断しなくても BGMPLAY 0,5,Z*127 のように音量を調整することによって可能です。(この場合は鳴らす場合はZ=1、鳴らさない場合はZ=0となる)

 その場合は次のようにすればさらに短縮が可能です。

  BGMPLAY 0,5,Z*127 → BGMPLAY 128-Z*123 (128番をユーザー定義していない場合)

 これだと短縮できてないですが、鳴らす場合はZ=0、鳴らさない場合はZ=1 ならば、BGMPLAY Z*5+123となり2文字短縮できています。
 鳴らす曲が0番の曲ならば、BGMPLAY Z*128 となるため4文字短縮可能です。

 これは、BGMPLAYのユーザー定義が可能な曲番号(128〜255)は未定義状態でも単に音が鳴らないだけでエラーにはならないためです。

↑TOP


BGPUT



 BGPUTは7つも引数があって長いですが、パレット0で横反転、縦反転をしない場合は最後の3つの引数を省略できます。

 BGPUT 0,X,Y,10,0,0,0 → BGPUT 0,X,Y,10

 また、パレット変更等をする場合にはマニュアルに記載されているスクリーンデータ文字列を使用することでリスト短縮が可能になります。
 これは、マニュアルには詳細が記載されていますが、パッケージと同様に使い方が分からない人もいるかと思います。

パレット番号
縦反転横反転
キャラ番号
2進数
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
16進数
F
F
F
F

 16桁の2進数で表した場合には、上位4桁がパレット(0〜15)を示し、次が縦反転(あり=1、なし=0)、横反転(あり=1、なし=0)、キャラ番号(0〜1023)を示しています。上記の例だとパレット15、縦反転あり、横反転あり、キャラ番号1023を示します。

 スクリーンデータ文字列にするためにはこれを16進数にします。2進数を16進数に変換するのは4桁ずつ区切っていけば簡単にできます。注意しないといけないのは16進数文字列ですがパッケージと同様に&Hは不要ということです。2進数で&B1111111111111111は16進数では&HFFFFとなりますが、スクリーンデータ文字列にするためにはダブルクォーテーションで括るだけで良いです。つまり"FFFF"でいいのです。
 いちいち2進数で考えるのが難しそうという場合は、縦反転、横反転をしないならば16進数4桁のスクリーンデータ文字列の上1桁はパレット番号を16進数化したもので下位3桁はキャラ番号を16進数化したものなので単純に記すことができます。例えば、パレット番号が12でキャラ番号が345ならば&HC&H159なのでスクリーンデータ文字列は"C159"となります。(パレット番号を変数P、キャラ番号を変数CとしたときはHEX$(P)+HEX$(C,3)でスクリーン文字列は求められる)

 スクリーンデータは文字列だけではなく数値の状態でも受け付けてくれます。この場合は&Hを付けた4桁の16進数で記載する"C159"ならば&HC159とする)のですが、&Hを付けない10進数のまま記すことが可能です。その場合は次のような式で表すことができます。(0〜65535の範囲で指定できるけどその範囲外を指定した場合にはエラーにはならず指定した値 AND &HFFFFとして処理される)

 通常の書き方 BGPUT 0,X,Y,C,P,H,V
 スクリーンデータを使用 BGPUT 0,X,Y,C+H*1024+V*2048+P*4096
 (キャラ番号を変数C、パレット番号を変数P、横反転の有無を変数H 縦反転の有無を変数Vとした場合)

 これだと元のものより長くなっているように見えますが、例えば、パレット番号が12でキャラ番号が345、横反転、縦反転の両方がありならばBGPUT 0,X,Y,345,12,1,1BGPUT 0,X,Y,52569とすることができるためリスト短縮(および高速化)ができます。(BGPUTが最大で約1.6倍高速になる)

 このスクリーンデータ文字列はBGFILLではさらに便利に活用することができます。BGFILLによって一定のパターンで塗りつぶす場合にはループや複雑な計算式が必要ですが、スクリーン文字列を使えば次のように単純に書くことが可能です。

 BGFILL 0,0,0,33,23,"90D690D790F690F7"

 これで、キャラ番号214、215、246、247の草をパレット番号9(それぞれのスクリーン文字列は"90D6"、"90D7"、"90F6"、"90F7")で画面一杯に埋め尽くすことができます。16進数4桁のスクリーンデータ文字列は複数連続で羅列することが可能になっており、その場合は塗りつぶす矩形の左上から自動的に順番に並べてくれます(終点より始点の方が大きい場合でも常に左上からになる)
 ちなみに画面は32キャラ×24キャラで構成されるためBGFILL 0,0,0,31,23,"90D690D790F690F7"としてしまいがちですが、横に4キャラごとに並べていくと32は4で割り切れるため縦方向にはすべて同じキャラが並んでしまい都合が悪いです。Y座標が奇数の時にはX座標が2ずれるような形にしたいため上記では本来ならば矩形の右端を示す座標は31にするところを2つ加算して33にしています。(Y座標が1つずつずれるごとにX座標は2つずれるためY座標が2つずれたらX座標は4つずれて元に戻る)

 上記では意図的にずらした状態で草を表示していますが、縦横整列した状態で画面一杯に表示する場合には次のようにすればできます。

 BGFILL 0,0,0,31,23,"90D690D7"*16+"90F690F7"*16

 これはスクリーンデータを2キャラ×16個(画面1行)の2列分用意してそれを順番に表示することで実現しています。2×2キャラクタ単位(縦横16ドット単位)で異なるものを並べる場合には4キャラ分のスクリーンデータを2キャラおきにループを使って並べると簡単にできます。

 このようにスクリーンデータは数値、文字列のどちらで指定しても問題ないですが、数値を指定した方が短く速くなる場合が多くBGFILLを使う場合は複数キャラによるパターン化は数値による指定ではできないため文字列を使用した方がリスト短縮と大幅な高速化(画面一杯に同一パターンを表示した場合は20〜30倍の速度)が可能になります。

↑TOP


BUTTON()



 BUTTON関数はゲームなどでリアルタイムにボタン入力をする場合には必須です。
 mkIIからはBUTTON関数に引数設定ができるようになりました。ボタンを押した瞬間(瞬間といっても1フレームの間は同じ値を返す)のみ入力を行うならばBUTTON(1)となりますが、これはBTRIG()と同等なので短縮が可能です。

 また、ボタン入力判定においては複雑な操作をする場合にはIF文の羅列となり長くなりがちです。論理式を使っても長くなってしまう場合が多いため押したボタンを押した後の処理がパターン化された数式処理ができる場合にはDIMを使ったテーブル処理が有用です。(棒歌ロイドキーボード参照)

 テーブル処理はリスト短縮だけではなく高速化においても非常に有用なのですが、そこまで高速化は必要なくリスト短縮を重視する場合には[L][R]ボタン以外であればmkIIから加わった文字列を検索するINSTR関数を使うという方法があります。
 変数BにBUTTON関数の値が入っている場合、プログラム内で使用するボタンを押した時に返す値のキャラコードを羅列した文字列を用意してそれをCHR$(B%256)で検索%256を付けているのはエラー防止対策)すれば何番目のボタンを押したかがすぐに分かります。使用するボタンを押していないときには-1を返すためそれに対する処理のみ必要になります。これでどうやったらリスト短縮に繋がるかはバンブラ風演奏プログラムを参照してください。同等の処理を論理式で書いた場合には8個分のボタン入力に対応させるためには81文字になってしまいますが、それがこの方法を使うことで36文字へと45文字分短縮できています。
 ちなみにこのバンブラ風演奏プログラムで検索対象文字列の中にあるCはキャラコード67でありBUTTON関数でこれを満たすことはないためスペース代わりとして挿入することで0、2、4、5、7、9、11、12という値のみを取得可能になっています。

 また使用するのが十字ボタンのみならばSTR$(B-1)で検索が可能なのでさらに短縮が可能です。この場合には検索される文字列に羅列するのはBUTTON関数の値-1となります。 左下ならば4+2-1で5という値になります。(プチコンハンマー投げ参照)。十字ボタンの右下を使用しないならばSTR$(B-1)ではなくSTR$(B)とすることでさらに短縮が可能です。

 ボタンは任意の順番ではなくBUTTON関数の値が小さい順に0(上)、1(下)、2(左)、3(右)、・・・としたい場合には同時入力を考慮しなければ0OR LOG(B+!B)*1.5とすることが可能です。この1.5という数字はLOG(EXP(1))/LOG(2)を限界まで短縮化したものでこの条件の範囲内のみ正しい値になります。またB+!Bゼロ除算対策です。(カウンター参照)

 また十字ボタンによる8方向移動などの場合には上記のようにINSTRやLOGを使ってBUTTON関数の値を処理しやすい単純な値に変換してもそこからの処理が短縮ができないためトータルではごく普通に論理式を使って書いたものよりも長くなってしまいます。そういう場合には剰余を使った8方向移動処理の方が短縮できます。

↑TOP


CLS



 mkIIから加わったACLS命令は非常に便利ですが、処理が重い(1回の実行につき0.966フレームかかる)ため開始時以外では使いづらいです。その点、CLSはそれよりも軽い(1回につき0.116フレーム)ためコンソール画面において消去処理が必要な場合はCLSで全部消してその都度すべて再表示すればリスト短縮ができる場合があります。これは昔の8ビットパソコンだと処理速度が遅かったためCLSで毎回画面消去するとちらつきが起きるためオススメできなかったけどプチコンならば60fpsで動作している場合はちらつきは全く起きないので問題ないです。(ちらつきの原因についてはプチコン講座 第2回のコラム参照)
 ACLSを使用した場合には60fpsの動作はほぼ不可能になるためちらつきが確実に発生してしまうけどアナログテレビプログラムのようにそれを意図的に利用することもできます。

 CLSを実行することでLOCATEを省略時の表示座標は0,0にリセットされるためそこに何か表示している場合はLOCATE命令1つ分リスト短縮が可能です。LOCATE 0,0に表示しない場合でも文字列の掛け算を使えばLOCATEの省略が可能です。

 また上下の画面を両方使用時にCLSを実行すると上下両方の画面の文字が消えてしまい困るという場合もあると思います。その場合は、文字列の演算は256文字まで可能であるため C$=""*256?C$C$C$ はスペースを表す)で上画面のみ消去可能です。
 ただし、CHKCHR()を使うプログラムにおいては誤動作防止のため C$=CHR$(0)*256?C$C$C$ の方がいいです。(これはCLSとは異なり大量の文字を表示して上書きしているだけであるため1回につき0.936フレームもかかってしまうので速度が要求される場面では使いにくい)

↑TOP


DIM



配列変数においてリスト短縮ができるのは下記の2つでしょう。

・要素数が10以下の配列変数は宣言無しに使用できる。
・テーブル化することでIF文を大幅に減らせる。

 前者は説明不要なのでここではテーブル化について書いておきます。
例えば十字ボタン上を押してZの値が1になり、それから時計回りに45度動かすごとにZの値が2、3、4、5、6と増えていく場合には単純に考えれば下記のように8つのIF文が必要になります。(BにBUTTON関数の値が入っている場合)

 IF B==1 THEN Z=1
 IF B==9 THEN Z=2
 IF B==8 THEN Z=3
 IF B==10 THEN Z=4
 IF B==2 THEN Z=5
 IF B==6 THEN Z=6
 IF B==4 THEN Z=7
 IF B==5 THEN Z=8

 ここで事前にZ(1)=1:Z(9)=2:Z(8)=3:Z(10)=4:Z(2)=5:Z(6)=6:Z(4)=7:Z(5)=8という感じで配列変数に値を入れておきます。(これは分かりやすく書いただけであって実際にはFOR〜NEXTを使えば短くなります)
こうすることでメインルーチン内ではZ=Z(B%16)だけで済みます。(この例だとBの値から計算してZの値が求められますがテーブル化しておけば単純計算が難しい値でも対応可能になる)

 使用するボタン数が増えればすべてのボタンの組み合わせをあらかじめ配列変数に入れておくのは大変になりますが、組み合わせによってはIF文を羅列するよりは大幅に短縮可能です。(さらにリスト短縮したい場合はBUTTON()の項目を参照)

 また、そのプログラムで使用するボタンの一番BUTTON関数の値が大きなボタンにおいては下記の方法で短縮可能です。

 Rボタンが最も大きいならば
 IF B AND 512 THEN A=12 → A=Z(B/512)  ただし、事前にZ(1)=12としておく

最大数のみに限定しているのは「配列変数の添え字の小数部は無視される」「確保するのが1つだけなので未定義でも確実に使える」ということによって「他のボタンの影響を考える必要がない」からです。([Y]ボタンが最大ならば512を128に置き換えてやればいいけどその場合は[L][R]ボタンを押した時に正常動作しなくなるという点に注意する必要がある)

 テーブル化によって上記のように複雑なボタン入力処理を簡単に行えるようになりますが、あらかじめ計算した値を入れておくことでそれを読み出すだけで済むようになるため高速化のために使用することが多いです。

↑TOP


FLOOR



 プチコンで整数化を行いたい場合はFLOOR関数を使うことになるのですが、これは一般的なBASICで多く使われているINTよりも長くて残念な部分です。
 しかし、FLOORを使わなくても別の方法で整数化は可能です。

 例えば変数Aの値を整数化する場合
 
 ビット演算子 OR を使う  0 OR A (0とORの間のスペースは省略可能)
 剰余(%)を使う A-A%1
 乗除算を使う  A/Z*Z (あらかじめZ=4096としておく)

 剰余を使う場合にははA>0の時はFLOORと全く同じですが、負数のときは単純に小数部分を省略してしまう形になるため注意が必要です。FLOOR (-3.5)-4だけど-3.5-(-3.5%1)-3になる)
 この辺は「そもそも-3.5を整数化したら-4ではなく-3になるべきなのでFLOORの挙動がおかしい」という意見もありますが、プチコン内部では数値は2進数で記録しており負数は補数で記録している(プチコンでの数値の保存形式は第5回プチコン講座のコラムを参照)という関係から単純に小数部分を切り捨てて整数化をすると10進数での整数化とは異なり「絶対値が等しくなる整数」ではなく「その値を超えない最大の整数」となります。

 除算でなぜ整数化ができるかというとプチコンは固定小数点による演算をしており内部では1/4096単位で数値が記録されている(こちらも上記のコラムを参照)ためです。だから4096で割ってやれば元の小数部分は無くなりそれを4096倍すれば整数化できるということです。
 ただし、0OR Aは5文字でA/Z*Zも5文字なのでリスト短縮効果は望めません。しかも、あらかじめZ=4096としておかないといけない分だけ不利になります。
 とはいえ、この乗除算だけで整数化を行う方法は特定条件下ではリスト短縮の効果が期待できるのです。

 8単位で整数化したい場合(Aの値を8、16、24、32・・・という形に丸めたい場合)は、FLOOR(A/8)*8(12文字)、0OR(A/8)*8(10文字)に対してA/8/Z*Z*8(9文字)で1文字分短縮できています。(Petit Brainfuckで使用)
 これでも、あらかじめZ=4096としないといけない分不利になるため6回以上整数化を行わないと短縮できませんが、1画面プログラムでは特定行は数文字余裕があるのにある行だけ1文字分収まらないという場合にこの方法を使えば解決できることがあります。

 また、小数第1位までに丸めたいという場合はFLOOR(A*10)/10(14文字)、0OR(A*10)/10(12文字)に対して(あらかじめZ=409.6としておいた場合)A/Z/Z(5文字)で7文字分も縮まるため短縮化のメリットは非常に大きくなります。

↑TOP


FOR〜NEXT



 FORとNEXTの間に処理ルーチンを入れて処理ルーチン内でIに終了条件を論理式で与えることでその終了条件を満たすまでループを実行することができます。

FOR I=0TO 1
(処理内容)
I=X<255
NEXT

※X≧255がループ終了条件の時

 上記ループでは終了条件を満たしている場合でも1回は実行されるためそれではまずい場合は下記のようにする必要があります。(※初期値を1にするのを忘れないように)

FOR I=1TO X<255
(処理内容)
I=0
NEXT

※X≧255がループ終了条件の時

 これで一般的なBASICにおけるREPEAT〜UNTILの代用ができます。

 上記においてI=0を省くとTOの後に記述している条件式を満たしている時に1回のみ実行可能なループとなります。

FOR I=1TO X<255
(処理内容)
NEXT
※X<255の時のみループ内が1回実行される

 この場合も条件式を満たしていない場合はループ内を実行しないためブロックIFの代用ができます。プチコンでは1行が改行を含めて最大100文字までしか入力できないため長いIF文は別途サブルーチン化して処理する必要がありラベル管理が面倒になりますが、こうすることで複数の行に渡って記述することが可能になります。
 しかし、「NEXTの前にはコロンを入れる」もしくは「NEXTは行頭に置く」という必要があります。これはプチコンではコロンが省略できる場合が多いのですが、このように条件によってはループ内を実行しないという場合にはNEXTの前のコロンを省略すると対応するNEXTを認識できないためです。(そもそもコロンを省略するのは正しい使い方ではないためバグとは言えないし、初期のプチコンでは行頭においたNEXT以外は認識できなかったからこれでも改善されていると言える)

 また、ループに使う変数を終了条件に使う変数と同一のものにすることで終了条件を省略可能になります。この場合、ループの終了値が終了条件の値になります。

FOR X=0TO 255
(メインルーチン)
NEXT
※X≧255が終了条件の時

 これはTCHXやBUTTON関数の値のように終了条件を示す変数の値がメインルーチン内で常時変化し終了値が最も大きな値になる場合のみ使用可能です。
 カウンタの変数がメインルーチン内で常時変化しない場合はループのカウンタを有効活用することでリスト短縮が可能になります。

IF〜GOTO〜でラベルジャンプをすると行数制約があるプログラムを作っている場合は不利になるためそういう場合はこのようなFOR〜NEXTによる条件分岐は必須になります。

↑TOP


IF



 IF文では条件式の値が「0」か「0以外」かで判断してTHEN以下を実行するかが決まります
したがって、IF A!=0 THEN 〜 は、IF A THEN 〜 とすることができます。
また、Aが負の数にならないならIF A>0THEN 〜IF A THEN 〜 とすることができます。

A、Bともに負の数にならない場合は下記のようなことができます。

 IF A>0 AND B>0 THEN 〜 → IF A*B THEN 〜
 IF A>0 AND B==3 THEN 〜 → IF A*(B==3)THEN 〜
 IF A!=1 THEN 〜 → IF A-1THEN 〜

 論理否定を使えば下記のようなこともできます。

 IF A==0 THEN 〜 → IF!A THEN 〜 (「IF」と「!」の間のスペースは省略できる)
 IF A==0 XOR B==0 THEN 〜 → IF!A-!B THEN 〜
 IF A==2 THEN 〜 → IF!(A-2)THEN 〜 (ただし、1文字長くなる)

 論理否定の演算優先順位は四則演算よりも上であるためカッコを付けずにIF !A-2THEN 〜としてしまうとAの値に関係なく常にTHEN以下が実行されるIF文になってしまうので注意が必要です。

 これらをすべて複合すればこんなこともできます。

 IF A>0 AND B==0 AND C!=2 THEN 〜 → IF C-!!A*!B*2THEN 〜

↑TOP


OR



 ビット演算子ORが最も活躍できるのは整数化でしょう。

 FLOOR(A) → 0OR A (A≧0の場合)

0 OR A」はAと等しい値になりますが、ビット演算は整数部分のみ有効であるため小数部分が切り捨てられるわけです。(-1AND Aでも同じ意味になるけど0OR Aの方が2文字分少なくて済む)
なお、ビット演算の演算優先順位は最も低いため例えばA/60を整数化したい場合において0OR (A/60)のようにカッコを付ける必要はありません。

↑TOP


PRINT



 コンソール画面では異なる座標への表示や異なる文字数のキャラを表示した場合にその残像が残ってしまうためスペースを表示して消去する必要があります。(※CHKCHR関数で当たり判定を行う場合はスペースではなくCHR$(0)で消去した方が良い)

FOR X=0TO 30
LOCATE X,0:PRINT ""
LOCATE X+1,0:PRINT "@"

VSYNC 3
NEXT
(※はスペースを示す。)

しかし、上記のように一方通行で動作させる場合には次のように簡略化できます。

FOR X=0TO 30
LOCATE X,0:PRINT "@"
VSYNC 3
NEXT

 プチコンで数値を表示する場合はLOCATEで同じ座標に表示しても桁数が減ったら残像が残ってしまうのですが上記の基本テクニックを踏まえて PRINT A"" とする場合も多いのではないかと思います。(PRINTで結合表示するためには「;(セミコロン)」か「+」を使いますが「””(ダブルクォーテーション)」で囲まれた文字列と変数との結合表示に限りそれは省略できます)
 しかし、 PRINT A, とすれば余分な残像は残らなくなるのでリスト短縮が可能です。

 キャラコード48〜57(「0」〜「9」)の文字のみを表示する場合はダブルクォーテーションは省略できます。

 PRINT "8" → PRINT 8

 あとマニュアルに記載されているので書くまでもないことですが、「PRINT」は「」と短縮できます。

 PRINT A → ?A (この短縮形を使うと命令との間のスペースも省略できる)

↑TOP


RAD



 プチコンでは三角関数の計算は「度」ではなく「ラジアン」で計算しているため「度」から「ラジアン」への変換を行ってくれるRADを活用している人も多いでしょう。しかし、RADは0〜360の値しか受け付けてくれないためゲーム内で常に角度が変化している場合に「角度がA度」のSIN関数の値を求める場合には次のようにする必要があります。

IF A<0 THEN A=A+360
IF A>360 THEN A=A-360
X=SIN(RAD(A))

 これは剰余を使えば X=SIN(RAD((A+360)%360)) で表記可能です。(※+360とするのは負の値を正の値にするため)
 しかし、いちいちRADを使わなくても X=SIN(A/57.3) だけで良いのです。この57.3は「度」を「ラジアン」に変換するときの定数である180/PI()を計算したものなのですが、プチコンの場合は演算誤差が大きいため「57.3」と定数表記しても演算結果はほとんど変わらないので心配する必要はありません。(それでも気になる人は57.296とすればいい)
 0.5%くらいの角度のずれが許容できるようなゲームならば定数は「57」でも良いでしょう。(60度ならば0.3度くらいのずれになるのでほとんど気にならない)

↑TOP


RND



 複数の乱数を発生させる場合には最初に大きな乱数(基本的に公倍数となる値)を発生させてその剰余をとることによってリスト短縮が可能になる場合があります。

 X=RND(256)Y=RND(192) → A=RND(768)X=A%256Y=A%192

 この例では20文字から24文字へと増えていますが、事前に大きな乱数(この例だとA=RND(768)のこと)を事前に発生させているためであり、それ以外は乱数を1つ発生させるごとに3文字分短縮できています。その大きな乱数はリストの開いた部分に入れておけばいいので行によって行末に空きができたり1行に収まらない行が出てくる1画面プログラムでは有用でしょう。
 ただし、プチコンのRND関数は線形合同法によって求められているためこのような使い方はパターン化を招くという点に注意が必要となります。(例えば画面全体にドットを表示したくても画面に線状に分布されてしまう)

 また、プチコンのRND関数はシードを与えることができず再現性がないためゲームによっては非常に使いづらいのですが、これは疑似乱数ルーチンを作ることで解決できます。線形合同法による疑似乱数ならばコンパクトサイズなので1画面プログラム(JUMPING ISLAND1画面版)にも十分に使うことができます。私が作った実用的で最も短い疑似乱数ルーチンはこちらです。

 ちなみにRNDは引数に負数も受け付けてくれてその場合は負数の値を返します。
 RND(-10)は0〜-9の値を返すことになり-RND(10)と同じ意味となります。(リスト短縮にはなっていない)

↑TOP


SGN



十字ボタンによる左右移動ですが、論理式では次のようになります。(BにBUTTON関数の値が入っているとき)

 X=X-((B AND 4)==4)+((B AND 8)==8)

これはSGNを使うことで下記のようにできます。

 X=X+SGN((8AND B)-(4AND B))
 ※B AND 4ではなく4AND Bとすることで2文字分縮めている。


 また、SGNを使えば1、0、-1の3通りの条件判断ができるためそれを使えばリスト短縮可能です。
 例え、Aに相手(コンピュータ側)の得点、Bに自分の得点が入っている時はIF文を使った場合は下記のような形で勝敗を表示できます。

 IF A==B THEN PRINT "DRAW"
 IF A>B THEN PRINT "LOSE"
 IF A<B THEN PRINT "WIN!"

 これはSGNを使って判断することで下記のようにできます。

 PRINT MID$("WIN!DRAWLOSE",SGN(A-B)*4+4,4)

 0と1の2通りの判断だけで良いならば論理否定を使えばSGNよりもリスト短縮が可能です。

↑TOP


最後に



 今回書いたようなリスト短縮はプログラムリストの可読性を大幅に下げる場合が多いため無理をしてまでやる必要は全くありません。しかし、1行、3行、1画面のように行数制限を課すプログラムを作ることに興味がある人であれば十分にその価値を見いだせるでしょう。(省略することで高速化が可能になることも多いため高速化重視で省略するという手もあり)
 1画面プログラムの場合は単純にリスト短縮をするというだけではなくて処理順序に依存性がないものを並べ替えてできるだけ行末が開かないようにする(29文字にいかに近づけるか)というのがポイントとなってくるため1行や3行プログラムとはまた違った難しさ(面白さ)があります。(1文字削るのに一晩悩んだりということもあったり・・・)
 趣旨は若干変わりますが、同じような動作をするプログラムを別のアルゴリズムで書くというのもリスト短縮においては有効な場合も多いです。

 今回書いたことはすでに行数制限を課したプログラム作りに慣れている人であれば知っているものばかりかもしれませんし、そういうものに興味がない人にとってはほとんど無価値なものかもしれませんが、何かの役に立てば幸いです。
 これを機会に1画面プログラムに興味を持つ人が増えると個人的にはうれしいです(笑)

 あと、ここには掲載されてないけど「こうやればさらにリスト短縮ができるよ」というアイデアがあればぜひ教えてください。

↑TOP


RETURN/RETURN *MAIN

inserted by FC2 system