MSXでプログラムを作ろう

## MSXでプログラムを作ろう

最近MSXで何か作りたいと思っていくつかプログラムに役立つ記事を書きたいと思います。
普段JavaやC言語などを使っていますが、MSXの基本はBASIC言語ですよね。
80年代はBASICで色々作っていました。
今の時代はwebmsxという便利なものがあり、重宝しています。
ちょっとしたプログラムの実験が気軽にできるからです。

今回はBASICで速い（効率のいい）プログラムはなにかを考えてみたいと思います。

### BASICの遅さ

8ビットパソコンなので今のＰＣに比べると1000倍以上遅いです。
この記事を書いているPC(surface go)のCPUは1.6GHz。
MSXのCPUはZ80Aで3.58MHzです。
3桁違います。
そんな昔はBASICマガジンを見て色んな人のプログラムを研究してどのように書けば早くなるのかを考えていました。
基本的に以下は注意する必要があります。

1. IF文が遅い
1. 乗算、除算が遅い
1. 浮動小数点計算が遅い

といったことから、無駄なIF文は削除し、なるべく除算を使わないようにし、プログラムの最初はdefinta-zを呪文のように入力していました。

今回はよく使うキー入力の処理と当たり判定について実験してみます。
実験は実機ではなくwebmsx(msxpen)を使います。

### キー入力

msxはstick命令でカーソルキーまたはジョイスティックから入力された値（1～8)を取得できます。
数値は上を1とし右回りに2,3,4・・・8となっています。
この数値を判定してＸＹ座標を変化させますが、その判定方法はいくつかあります。
とりあえず3種類用意して実験します。

```
10 screen1,2:sprite$(0)=string$(32,255):defint a-z:x=100:y=100:time=0
40 fora=0to100
50 s=stick(0)
60 x=x+(s=7)-(s=3):y=y+(s=1)-(s=5)
80 putsprite0,(x,y),15,0
90 locate 0,0:print s,x,y
110 NEXT:t1=TIME:BEEP:time=0
120 fora=0to100
130 s=stick(0)
140 if s=7 then x=x-1
150 if s=3 then x=x+1
160 if s=1 then y=y-1
170 if s=5 then y=y+1
180 putsprite0,(x,y),15,0
190 locate 0,0:print s,x,y
210 NEXT:t2=TIME
230 xx[3]=1:xx[7]=-1:yy[1]=-1:yy[5]=1
235 beep:time=0
240 fora=0to100
250 s=stick(0)
260 x=x+xx[s]:y=y+yy[s]
270 putsprite0,(x,y),15,0
280 locate 0,0:print s,x,y
290 NEXT:t3=TIME
1000 screen1
1010 print t1
1020 print t2
1030 print t3
```

この結果は以下の通り。
時間は1/60秒タイマーでのカウントの値です。

|処理方式|時間|
|:--|:--|
|条件式|261|
|IF文|250|
|配列|230|

配列は判定がないので速いのは分かっていましたが、予想に反して条件式はIF文より遅かった・・・。
昔は条件式が一番速いと思って使っていたんですけどね。
ちゃんと実験はやるものです。


### 当たり判定

２つの物体のXY座標を比較して当たっているか判定するものです。
ここでは物体AをX1,Y1とし、物体BをX2,Y2とします。
それぞれ同じサイズ16*16ドットとしてその16ドット以内に入っていれば「当たっている」ということにします。
判定方式は4パターン作成します。
当たっている or 当たっていないで処理が変わるので3つの条件を用意しました。

まずはプログラムソースから。
判定の中でz変数に1か0を代入していますが、当たっている場合は1、そうでなければ0ということにしてください。

```
10 definta-z
20 x1=100:y1=100
30 x2=110:y2=150
40 '
50 time=0
60 fora=0to99
70 cx=x1-x2:cy=y1-y2
90 if cx<16 and cx>-16 and cy<16 and cy>-16 then z=1 else z=0
100 NEXT
110 print TIME
120 '
130 time=0
140 fora=0to99
150 cx=abs(x1-x2):cy=abs(y1-y2)
160 if cx<16 then if cy<16 then z=1 else z=0 else z=0
170 next
180 print TIME
190 '
200 time=0
210 fora=0to99
220 cx=(x1-x2)\16:cy=(y1-y2)\16
230 if cx=0 then if cy=0 then z=1 else z=0 else z=0
240 NEXT
250 print TIME
300 '
350 time=0
360 fora=0to99
370 if x1>(x2+16) or x1<(x2-16) then z=0:goto 400
380 if y1>(y2+16) or y1<(x2-16) then z=0 else z=1
400 NEXT
410 print TIME
```

実行した結果は以下の通り。

|処理方式|条件１|条件２|条件３|
|:--|:--|:--|:--|
|距離比較で判定|96|96|97|
|ABS使って判定|71|80|80|
|除算使って判定|85|94|94|
|if分割で判定|52|96|97|

※条件１はx2=150:y2=150でお互いの距離が離れている場合
※条件２はx2=110:y2=150でX座標のみ射程内でY座標が離れている場合
※条件３はx2=119:y2=110でX座標、Y座標ともに射程内の場合
※数値は1/60秒タイマーのカウント値

3パターンをテストした理由はIF文の判定の数によって処理時間が変わるため。

この結果から4番目のif分割で判定が52であり一番速いが、物体１と２が重なっている
状態では一番遅くなってしまう。
通常はキャラが重ならない前提でゲームを進めるのでこれでもいいのかもしれない。
オールラウンドで考えるとabsで絶対値を使う方が処理が単純でいいんだけど。

他にいい方法があれば考えてみます。

---
## (4/28追記)キー入力の改善

コメント欄で藤田さんに教えてもらいました。
on～gotoの命令で複数の分岐を１つで行うことができます。
on命令はif文1個と比較すると遅いですが、if文2個よりは速いです。
そして分岐数が1個でも10個でも実行速度が変わらない特徴があります。

実際に実行した結果は4番目に追加しましたが、一番早かった。

|処理方式|時間|
|:--|:--|
|条件式|261|
|IF文|250|
|配列|230|
|on~goto|196|


配列より速いということは配列アクセス自体がアドレス計算で予想以上にかかっているのか。

なお4方向移動処理にも関わらず、配列方式はXとY座標同時に加算しているのがフェアじゃないので以下のように8方向入力に対応してみました。

```
230 xx[3]=1:xx[7]=-1:yy[1]=-1:yy[5]=1
231 xx[2]=1:xx[8]=-1:yy[2]=-1:yy[4]=1
232 xx[4]=1:xx[6]=-1:yy[8]=-1:yy[6]=1
235 beep:time=0
240 fora=0to100
250 s=stick(0)
260 x=x+xx[s]:y=y+yy[s]
270 putsprite0,(x,y),15,0
280 locate 0,0:print s,x,y
290 NEXT:t3=TIME
300 beep:time=0
310 fora=0to100
320 onstick(0)+1goto 370,350,323,340,324,360,322,330,321

321 x=x-1:y=y-1:goto370
322 x=x-1:y=y+1:goto370
323 x=x+1:y=y-1:goto370
324 x=x+1:y=y+1:goto370
330 x=x-1:goto370
340 x=x+1:goto370
350 y=y-1:goto370
360 y=y+1
370 putsprite0,(x,y),15,0
380 locate0,0:prints,x,y
390 NEXT:t4=TIME
```

結果は以下の通り。
|処理方式|時間（※１）|時間（※２）|
|:--|:--|:--|
|配列|230|240|
|on~goto|196|237|

※１　カーソルキーがニュートラルの場合
※２　カーソルキーを右と下同時入力している場合

差はだいぶ縮まりましたが、やっぱりon命令の方が速いようです。
奥が深いですね。