TL/1....スコアを表示しろ

TL/1 PCが復活したので、久々にコードを書いてみました。Pascalっぽい文法なのでさほど戸惑いはないですが、いろんなところで、言語を超えた処理が必要になります。

TL/1 PCむけにはゲームも多く発表されていましたが、すぐに困るのが「スコアの表示」でしょう。

TL/1は変数はすべて8bit整数なので、ゲームの得点を管理しようとすると、すぐにあふれてしまいます。

スコア自体を変数を二つ使って16bit幅に拡張すること自体は簡単です。

  SCL := SCL + POINT
SCH := SCH.ADC.0

みたいにすれば、16bit幅のスコアを管理できます。ただ、これを表示する術がないんですよね。0~255の数値を二個表示して、頭の中で16bitの数値に変換しろというわけにもいきません。

ならば、変換する処理を組み込めばいいじゃないかと、始めて見たものの、すぐに壁にぶつかります。一般的には、10で割って剰余を表示する、を繰り返せばいいんですが、これが難題。TL/1は乗算に関しては、結果が16bit幅になったら、MHIGHというシステム変数に上位を保存してくれますが、除算はあくまで8bit幅。16bit/8bitはできないのです。2のべきで割るならシフト/ローテートで逃げられますが、10だとそうもいきません。結局、表示可能なデータをつくるサブルーチンをアセンブラで記述することにしました。

最初は10で割ることを考えたんですが、別に割らなくてもいいよな? と。せっかくZ80にはDAAというイカした命令があるので、これで行きます。

符号なし16進数は0~65535の値をとるので、表示としては20bitあれば各桁を4bitずつ、0~9の範囲で保持できます。なので、00 00 00で初期化した3bytesを結果領域として確保。

あとは、作業用に同じサイズの領域を確保します。作業領域には 1に相当するBCD値を書き込みます。01 00 00です。

ターゲットの数値の1ビット目をみて、1なら作業値を出力値に足します。0なら飛ばします。次にのビットに移る前に、作業値を二倍します。

  LD  HL,TBL
LD A,(HL)
ADD A,(HL)
DAA
LD (HL),A
INC HL
LD A,(HL)
ADC A,(HL)
DAA
LD (HL),A
INC HL
LD A,(HL)
ADC A,(HL)
DAA
LD (HL),A

こんな感じで、作業値を倍にして、次のビットが1なら出力値に足します。これを全ビットに対してやれば、結果には表示可能な値が残ります。

  ; BCにはターゲットの数値が入っている
LOOP:
LD A,B
OR C
JR Z,DONE
LD DE,TBL
LD HL,RES
SRL B
RR C ; BCを右にシフト
JR NC,NEXT
LD A,(DE)
ADD A,(HL)
DAA
LD (HL),A INC HL
INC DE ; INC L/INC Eではなく16bit INCを使う。フラグを触らないので
LD A,(DE)
ADC A,(HL)
DAA
LD (HL),A
INC HL
INC DE
LD A,(DE)
ADC A,(HL)
DAA
LD (HL),A
NEXT:
; ここに、先のTBLを倍にするコードを置く
JR LOOP
DONE:

これで、あとは、次のような感じで、TL/1から呼び出してやればOKです。

  % スコアはSCH,SCLに、サブルーチンは $e020~、出力値は$e070-$e072にあるとする。
CALL($e0,$20,0,SCH,SCL)
TMP := MEM($e0,$72) TMP := TMP / 16 WRITE(0:TMP,MOD)
TMP := MEM($e0,$71) TMP := TMP / 16 WRITE(0:TMP,MOD)
TMP := MEM($e0,$70) TMP := TMP / 16 WRITE(0:TMP,MOD)

と、まあ、かように面倒くさい処理が必要なのがTL/1なのです。

余談ですが、こういったアセンブラのサブルーチンを簡単に書けるように、インラインアセンブラとはいいませんが、DB(xx)みたいな命令があってもよかったかなあ。評価はコンパイル時のみのプリプロセッサ命令的なもので、その場に、バイナリコードを置くっていう感じで。今は、MEM命令で書き込ませているので、面倒くさいのです。まあ、今更ですが。