コマンド入力
パッド入力の話題はこれで最後。最初は3つまとめて1つの話題で考えていたのですが、プログラムが長くなりすぎるので分割しました。
PADHIST に貯められた、過去16回分の入力を見て、「コマンド」が入力されたかどうか調べます。
ゲーム中のコマンド入力って、スト2のブームで出できたとおもうので、本当はファミコン時代ではあまり使われてないはずですけどね。
目次
Z80 vs 6502 トップページ(別ページ)
概要
以前の例題では PADHIST はリングバッファなのですが、面倒なので16バイトがリニアに並んだバッファとします。
最新データがバッファの先頭に入っているのか、末尾に入っているのかはどちらでも良いとします。
検出すべきコマンドは複数あり、メモリ内に入っています。
コマンド長さは不定なため、メモリ上のどこかに「コマンドの長さ」をいれるか、末尾に 0 を入れるかして、コマンドの終わりがわかるようにしてください。
コマンドを意味するデータの並びは、頭からでも末尾からでも、方向は問いません。
コマンドは、入力を完了した時点ですぐに検出されなくてはなりません。
その意味では、「末尾」を起点として一致を確認するほうがやりやすいかもしれません。
コマンド長が不定なので、コマンド冒頭アドレスを記録したテーブル、COMTABLEがあります。このテーブルのサイズはわかっているものとします。
コマンドを認識すると、コマンドの「番号」を返します。該当コマンドが無い場合は 0 を返します。
次の3つのコマンドがあるとします。かならず、1から順に識別させてください。
(頭・末尾のどちらから確認しても、1,2 のどちらかで1文字目が一致します)
ただし、この数字はコマンド番号ではありません。一意な数字が帰れば、コマンド番号がどのようになっているかは任意です。
1) ↓↘→A
2) →↓↘B
3) →→BBBA
PADHIST の「最新部分」に 3 のコマンド列が入っているとき、識別成功するまでのクロック数を計測します。
(頭・末尾のどちらから確認しても、1,2 のどちらかで1文字だけ一致します)
入力値はビットマップで次のようになっていますが、これはデータの問題なのであまり重要ではありません。
A B - - 上 下 左 右
例題の意図としては、文字列一致の検索と、ポインタ配列の扱いとなります。
Z80基本
メモリアクセスを極力減らすため、裏レジスタまで駆使しています。
また、COM1~COM2 および PADHIST は、256バイトの境界を超えない部分に配置されていることにして、上位8bit は最初にセットしたあと計算しません。
LD DE,PADHIST ; 10 (11)
LD H,COM1 / 256; 7 (8)
EXX ; 4 (5)
LD HL,COMTABLE ; 10 (11)
LD B,3 ; 7 (8)
LOOP:
LD A,(HL) ; 7 (8)
EXX ; 4 (5)
LD L,A ; 4 (5)
LD B,(HL) ; 7 (8)
INC L ; 4 (5)
LOOP2:
LD A,(HL) ; 7 (8)
EX DE,HL ; 4 (5)
LD C,(HL) ; 7 (8)
CP C ; 4 (5)
EX DE,HL ; 4 (5)
JR NZ,BAD ; 12 / 7 (13 / 8)
INC L ; 4 (5)
INC E ; 4 (5)
DJNZ LOOP2 ; 13 / 8 (14 / 9)
HIT:
EXX ; 4 (5)
LD A,B ; 4 (5)
JMP END ; 10 (11)
BAD:
LD E,PADHIST & 0FFH ; 7 (8)
EXX ; 4 (5)
INC L ; 4 (5)
DJNZ LOOP ; 13 / 8 (14 / 9)
XOR A ; 4 (5)
END:
COMTABLE:
DB COM1 & 0FFH,COM2 & 0FFH,COM3 & 0FFH
COM1:
DB 4,80H,01H,05H,04H
COM2:
DB 4,40H,01H,04H,05H
COM3:
DB 6,80H,40H,40H,40H,01H,01H
Z80 にそれほど詳しくない人…が、こんなページを読んでいるかは疑問ですが、詳しくない人のために説明すると、Z80 は豊富なレジスタを持ち、「スイッチ」でいつでも切り替えられるようになっていました。
A レジスタは 8bit ですが、フラグレジスタ F とあわせて 16bit のセットです。この AF には、「裏レジスタ」AF' があり、いつでも内容を入れ替えることができました。
同じように、BC / DE / HL には BC' / DE' / HL' があり、この3本まとめて、いつでも内容を入れ替えられました。(1本づつの入れ替えはできない)
さらに、DE と HL の間でも内容の入れ替えができました。
Z80 はメモリアクセスが遅く、レジスタによるアドレス指定には基本的に HL を使う、等の決まりがあるため、複数のレジスタをうまく切り替えながらプログラムするのが高速化の上では効果がありました。
以下、上記プログラムで使われている変数の意味です。(もちろん、適宜入れ替わります)
HL | コマンド列へのポインタ | HL' | COMTABLE へのポインタ |
DE | PADHIST へのポインタ | ||
B | LOOP2 のループカウンタ | B' | LOOP のループカウンタ |
A | コマンド列から取り出した値 | ||
C | PADHISTから取り出した値 |
PADHIST は、アドレス番号が小さい側に最新の情報が入っているものとしています。それにあわせ、COM1~COM3 の内容も、末尾から入っています。
COM1~COM3 の先頭は、コマンドの長さが入っています。
最終的な結果(コマンド番号)は A レジスタに入ります。
全体の前処理(LOOP まで)に 43クロック。
内部ループの前処理(LOOP~LOOP2)に 31クロック。
内部ループ内は、データバイト(以下文字と書きます)が不一致だと 76 クロックで、一致すると 63クロック。ただし、最後の1バイトの処理は5クロック速くなります。
後処理は、不一致時には 32クロックで、一致時は 21クロック。
全体前処理は1回、ループ内前処理は3回通ります。
ループ内は、1文字目で不一致が1回、1文字目が一致して2文字目で不一致が1回、6文字一致が1回で。この時は完全一致なので 5クロック減ります。
後処理は、不一致が2回で、一致が1回。
43 +21*3 +76 +63+76 +63*6-5 +32*2 +21 = 779
総計は 779クロックです。