Chapter.17 点数処理
[Chapter17 制作物]

今回は点数の演算ルーチンを作成します。

■BCD符号を使う
点数には、BCD符号を使うことにします。
BCD符号とは、数値を 16進数表記 にした状態を、強引に 10進数と見なす符号です。
たとえば、0x1234 を、10進数の 1234 と見なします。

こうすることによって、表示処理がやりやすくなります

たとえば、1234 をそのままレジスタに格納しておいて、表示しようとすると、
「1000を何回引けるか」「100を何回引けるか」・・などと、上の桁から何度も引き算を繰り返すことになります。
4桁なら、9999 が最悪ケースで、4桁 * 10回の引き算 = 40回の引き算 が発生することになります。

BCD符号の場合は、1桁 = 4bit と、ビット単位で区切られているので、簡単に1桁を取り出すことができます。
0x1234 の上位 8bit は 0x12 ですが、右に 4bit シフトすれば 1 になり、and #0x0F すれば 2 が得られます。

■BCD符号の演算に便利な命令
Z80 には、BCD符号を取り扱う場合に便利な命令が用意されています。

・DAA (Decimal Adjust Accumlator)
 たとえば、0x09 に 0x01 を加算すると、0x0A になってしまいます。
 DAA を通すと、これが 0x10 に補正されます。
 この補正処理のことを Decimal Adjust と呼びます。
 加算の場合と、減算の場合で Decimal Adjust の具体的な演算が異なります。
 加算命令を実行した場合は Nフラグが 0 に、減算命令を実行した場合は Nフラグが 1 になるのですが、
 DAA命令は、このフラグを見ることで、加算用・減算用の Decimal Adjust を自動的に切り替えてくれるように出来てます。
 もちろん、0x90 に 0x10 を加算した 0xA0 のあとに DAA すると、結果は 0x00 になり、しっかりと Cフラグが立ちます。
 なので、加算は下位桁から順次実行して、1byte目は add, 2byte目以降は adc を使うことで、いくらでも多い桁を扱える
 ようになってます。

 余談:
 BIOS の中には、BASIC の浮動小数点演算(実数演算)に用いられる MathPack という算術演算ルーチンが用意されています。
 その算術演算ルーチンの中でも、BCDが使われています。ただし、浮動小数点なので、符号部と指数部が別途付いてきます。
 浮動小数点に関しては、今でも使われている方式なので、興味のある人はググってみれば見つかるはずです。
 今時の CPU は、ハードウェアで浮動小数点演算が実現されているので、浮動小数点の仮数部に BCD は使ってません。

■sca_score.asm 作成
点数計算は、BCD符号表現の「加算」「比較」などを必要とします。
たとえば、敵を倒したときに点数が入る=得点を加算する という処理が必要ですし、
ハイスコアを記録する=現状ハイスコアに記録されているスコアと現在のスコアを比較して大きい方を新たなハイスコアにする
といった処理が必要になるわけです。
その辺を、一通り作成して sca_score.asm に作り込んでいきたいと思います。

■メモリ
メモリは、プログラム1 に示す内容で確保します。

プログラム1. 点数用メモリ
; -----------------------------------------------------------------------------
current_is_top:
		.db		#0			; current_score が high_score の第1位を超えた場合 1 にする

; -----------------------------------------------------------------------------
;	点数テーブル(BCD符号, 表示の都合で BigEndian, 8桁)
; -----------------------------------------------------------------------------
current_score:
		.db		#0x00, #0x00, #0x00, #0x00

high_score:
		.db		#0x00, #0x00, #0x50, #0x00, #11, #11, #11
		.db		#0x00, #0x00, #0x40, #0x00, #11, #11, #11
		.db		#0x00, #0x00, #0x30, #0x00, #11, #11, #11
		.db		#0x00, #0x00, #0x20, #0x00, #11, #11, #11
		.db		#0x00, #0x00, #0x18, #0x00, #11, #11, #11
		.db		#0x00, #0x00, #0x15, #0x00, #11, #11, #11
		.db		#0x00, #0x00, #0x12, #0x00, #11, #11, #11
		.db		#0x00, #0x00, #0x10, #0x00, #11, #11, #11
		.db		#0x00, #0x00, #0x08, #0x00, #11, #11, #11
		.db		#0x00, #0x00, #0x05, #0x00, #11, #11, #11
		


current_is_top は、current_score がトップスコアであることを示すフラグです。
current_score は、現在のスコアになります。
high_score は、第10位までのハイスコアと、その達成者の名前です。

ゲーム中には、現在のスコア(current_score) と、high_score の第1位(トップスコア)が表示されています。
現在のスコアがトップスコアを超えた場合、現在のスコアがトップスコアに取って代わります。
そのとき、点数表示更新処理で、HISCORE の表示もリアルタイムで反映するために、current_is_top というフラグを用意してます。

current_score, high_score の点数部分は、4byte から成っています。
BCD符号なので 8桁の 10進数を示しています。
00000000 〜 99999999 の点数を表現できます。

high_score の各行は、ハイスコアの各順位に対応していて、上にあるほど高順位を意味しています。
各行の先頭 4byte は対応する得点、残り 3byte は、その得点達成者の名前になります。
#11 は、'A' の文字を示しています(このゲームでは ASCIIコードを採用していないので 0x41ではありません)

■初期化ルーチン score_init
ゲーム開始時に実施する初期化ルーチンとして score_init を作成します。
内容は、プログラム2 のようになります。

プログラム2. 初期化ルーチン
score_init::
		; スコアを 00000000 にする
		ld		hl, #0
		ld		(current_score + 0), hl
		ld		(current_score + 2), hl
		; スコアはまだトップスコアには到達していない
		xor		a
		ld		(current_is_top), a
		; トップスコアの表示を更新する
		ld		hl, #(PATTERN_NAME1 + 24 + 32*2)
		call	SETWRT
		ld		hl, #high_score
		call	score_outport
		; スコアの表示を更新する
		call	score_update
		ret
		


記載されているコメントを見て貰えば分かると思いますが、下記のような処理を実施しています。

(1) 現在のスコア current_score を 00000000 にリセットする (2) フラグ current_is_top を 0 にリセットする (3) HISCORE の下の位置 (24,2) に トップスコア high_score を表示する (4) SCORE の下に現在のスコアを表示する
無駄な計算を避けるために、点数の表示は、メインループの毎回更新するのではなく、点数が入ったときだけにします。
そのため、初期化時に表示を更新しておきます。

現在のスコアの表示更新は、得点が入るたびに発生するため、専用のサブルーチン score_update として用意します。
初期化でも、その score_update を利用することで、表示更新します。

HISCOREの表示も、数字部分の表示処理は現在のスコアと同じなので、「点数文字列を VDPに伝えるルーチン」を一つ
作り(score_outport)、様々な点数表示処理にて再利用することにします。
ただし、表示位置は用途によって異なるので、SETWRT にて指示してやることにします。

■スコアの表示 score_update
現在のスコアを表示するルーチンを作ります。
内容は、プログラム3 のようになります。

プログラム3. 現在のスコアのルーチン
score_update::
		; 点数を表示する
		ld		hl, #(PATTERN_NAME1 + 24 + 32*6)
		call	SETWRT
		ld		hl, #current_score
		call	score_outport
		; 現在の点数はトップスコアか?
		ld		a, (current_is_top)
		or		a
		ret		z						; トップスコアでなければ戻る
		; トップスコアの表示も更新する
		ld		hl, #(PATTERN_NAME1 + 24 + 32*2)
		call	SETWRT
		ld		hl, #current_score
		jp		score_outport
		


SCOREの下に表示される「現在のスコア」は、(24,6) の位置から表示することになります。
BASIC で書くなら、LOCATE 24,6:PRINT SC といった感じでしょうか。
その LOCATE に相当するのが call SETWRT です。HL レジスタに目的の VRAM アドレスをセットして呼び出すことで、
VDP にそのアドレスを保持するように通知します。
call SETWRT の後に、out (VDP_VRAM_IO), a などとすると、a レジスタの内容が HLレジスタで指示した VRAMアドレスに書き込まれます。
そして、VDP はアドレスを 1 進めます。
連続して out (VDP_VRAM_IO), a すれば、次々と連続する VRAMアドレスへ書き込めるわけです。

out (VDP_VRAM_IO), a は、score_outport というサブルーチンに任せていますので、具体的な内容は後述します。

そのようにして現在のスコアを表示させたら、次に current_is_top の内容を確認します。
もし 0 であれば、「現在のスコアはトップスコアではない」という約束なので、このルーチンの役割は終わります。
もし 1 であれば、「現在のスコアはトップスコアである」という約束なので、HISCORE の下に SCORE と同じ内容を表示させます。

HISCORE の表示更新は、座標が (24,2) に変わっただけで、現在のスコアの表示更新と同じになります。

■表示更新処理 score_outport
汎用のスコア表示処理ルーチンを作ります。
内容は、プログラム4 のようになります。

プログラム3. スコア表示更新処理ルーチン
score_outport:
		xor		a
		ld		b, #4
score_outport_loop:
		rld
		inc		a
		out		(VDP_VRAM_IO), a
		dec		a
		rld
		inc		a
		out		(VDP_VRAM_IO), a
		dec		a
		rld
		inc		hl
		djnz	score_outport_loop
		ret
		


4bit交換命令 rld を使います。
Aレジスタの下位4bit, (HL)のメモリ内容8bitを、4bit単位で左回転させる命令です。
かなり特殊な動作ですが、おそらく BCD演算で使われることを想定している命令ではないかと思います。
動作イメージを図1.に示します。


図1. RLD命令の動作イメージ

RLD命令は、3回実行すれば Aレジスタとメモリ(HL)の内容は、元に戻ります。
プログラム3は、これを利用して、点数のメモリを破壊せずに表示しています。

文字フォントは、"0" を 1, "1" を 2 ... "9" を 10 の文字コードに定義しました。
なので、得られた桁情報に 1 を加算すれば、文字コードに変換されます。

プログラム3. のサブルーチンは、HLレジスタに表示したいスコアが記録されているメモリアドレスを代入して呼び出すことに
してあります。
スコアメモリは、上位桁から順に記録されている(BigEndian)ので、HL は最上位の2桁を指していることになります。

最初の rld, inc a で、一番上の 4bit を文字コードに変換してます。
次の out (VDP_VRAM_IO), a で、その文字コードを VRAM へ書き込みます。
aレジスタ の値は、inc a をキャンセルするように dec a を実施して元に戻しておきます。

次の rld, inc a は、次の 4bit を文字コードに変換します。
次の out (VDP_VRAM_IO), a で、その文字コードを VRAM へ書き込みます。
aレジスタ の値は、inc a をキャンセルするように dec a を実施して元に戻しておきます。

rld は、3回で元に戻るので、最後に3回目の rld を実行してます。
inc hl で次のアドレスへ進みます。
これを、4回繰り返すことによって、合計8桁の表示を実現しています。

■得点加算処理 score_add





工事中




■得点比較処理 score_compare





工事中




■現在のスコアがトップスコアになったか判定する処理 top_score_check




工事中




■その他の修正
sca_clash
sca_main





工事中






[▲トップページへ]