自分から見て、相手が左右どちらにいるのかを判定したいことがよくあります。コンパイラ系であれば、内積を使えば一発なのですが、Z80 ではなかなかそういう訳にもいきません。そこで今回は、8方向に移動している自分から見て、相手が左右どちらにいるのかをどう判定すれば良いのか解説していきます。


  • アルゴリズム
「自分から見て」という判断を行う場合、まずは自分座標系に相手の座標を変換する事から始めます。自分の座標が X0, Y0、相手の座標が X1, Y1 という状態であれば、自分座標系は、自分を起点とするため…

X = X1 - X0 Y = Y1 - Y0

このような単純な計算を行います。これはローカル座標変換ですね。そして、状況を確認するために、エクセルで座標テーブルを作成してみます。以下は自分が右方向に移動中のときの、相手との座標の差を全て取りまとめたものです。
右方向のときの相手との座標の差
矢印は自分の進行方向を示しています。そのすぐ正面の枠は[1,0]となってます。これは、自分から見てその位置は X=1, Y=0 という表現となります。

ここから、何か見えてきませんか?自分の右側は、この表示では下半分に相当します。下半分は、Y座標が全て正の値になっていますよね。このように、共通の条件を目視で確認すると分かりやすいと思います。もう少し分かりやすく色を付けてみます。
右方向移動での座標の差に色を付けてみた
 赤色 が右側、 青色 が左側、 黄色 は正面前方で、が後方です。Y座標で判断すれば良いことが一発で分かります。進行方向の直線上にいる時は、Y は 0 です。この場合は、X が正の値なら正面にいて、負の値なら後方にいることになります。後方にいたら、今回はとにかく右に曲がることにしてしまいましょう。このように全ての方向に対して、状況をまとめて整理すれば、どうすれば解決できるか判断しやすくなります。


  • 処理の分岐
HL が相手の XY座標、DE が自分の XY座標だとすると…

ld a, h sub d
ld d, a
ld a, l
sub e
ld e, a

このような単純な計算を行います。sbc hl, de とすると、キャリーを正しく反映させられないため、面倒でも X,Y 別々に計算します。自分の進行方向は 8方向です。右向きが 0 起点で、時計方向回りに +1 していきます。Acc が最初から 8方向(0~7)の場合は、ここから8方向別に処理を分岐します。

add a, a
add a, .dirtbl.@L ld l, a ld h, .dirtbl.@H ld a, (hl) inc l ld h, (hl) ld l, a jp (hl) ; 各8方向別の処理に ; テーブル CHECK ALIGN 256 .dirtbl dw .dir0, .dir1, .dir2, .dir3, dw .dir4, .dir5, .dir6, .dir7, ENDC
.@Lはアドレスの下位を取得するマクロです。同じく .@H はアドレスの上位を取得します。CHECK ALIGN 256 は、ENDC で挟まれた範囲が、256バイトの境界を跨がないよう監視するアセンブラ命令です。計算速度を上げるため、下位8bitだけで計算していますが、もし、テーブルが256バイトの境界を跨いでしまうと正しく動作しなくなります。そのため、CHECK ALIGN 256 を指定しておくことで、アセンブル時にアドレスを跨いだ時はエラーとして報告してくれるようになります。これは AILZ80ASM の独自機能ですが、とても便利で私にはなくてはならない機能になっています。


  • 共通ルーチン
8方向の結果をどう戻すかですが、方向は右回りが基本となっていますので、右側にいれば +1、左側にいれば -1、正面にいれば 0 を返却すれば良さそうです。どの方向でも同様に値を返却しますので、共通化させてしまいます。

; 左折 .dleft ld a, -1 ret ; 右折 .dright ld a, +1 ret ; 直進 .dahead xor a ret
2-3バイトの処理なので、ここにジャンプさせるほうが遅くはなるし、コードは少しだけ長くなるしと Z80 的には不利な書き方なのですが、将来、方向を左回りに変更する際は、ここを変えるだけで全ての処理が変わるので良いかなと。まあ、ラベル定義で

DLEFT equ -1 DRIGHT equ +1 DAHEAD equ 0

と書いておけば良いだけなのですが…^^;


  • 右移動
右方向移動での座標の差に色を付けてみた
最初にYの値がゼロかどうかを判定します。ゼロじゃなければプラスなら右折、マイナスなら左折で確定です。Yがゼロの時はXが正なら直進、負なら真後ろなので右折と判定します。

; 右移動 .dir0 ld a, e or a jr z, .@0 ; 直線上にいる jp p, .dright ; 正の値なら右にいる jp .dleft ; 違うなら左折 .@0 ld a, d ; Xが or a ; 正の値なら jp p, .dahead ; 直進 jp .dright ; 右折


  • 右下移動
移動方向が右下
右下移動ではパッと見で判定しにくそうに見えますが、よーく見ると X と Y が同じ値なら直線上にいることが分かります。そのため、X と Y の比較で左右判定ができそうです。さらによく見てみると、X のほうが Y より大きければ左側、その逆なら右側と判断すれば良いことが分かります。これをコード化します。

; 右下移動 .dir1 ld a, d sub e jr z, .@0 ; 直線上にいる jp p, .dleft ; X > Y なら左折 jp .dright ; 違うなら右折 .@0 ld a, d ; Xが or a ; 正の値なら jp p, .dahead ; 直進 jp .dright ; 右折
※ 今から40年以上前、カシオのこんな感じの腕時計をして高校に通っていました。懐かしいです。それが今や捨て値に近い金額で。とりあえず時間が分かればOKというニーズにはぴったりです。

  • 下移動
移動方向が下
Xがゼロなら直線上にいます。Xが正の値なら左側、負の値なら右側です。直線上の場合は、Yが正の値なら正面にいます。

; 下移動 .dir2 ld a, d or a ; Xがゼロなら jr z, .@0 ; 直線上にいる jp p, .dleft ; 正の値なら左折 jp .dright ; 違うなら右折 .@0 ld a, e ; Yが or a ; 正の値なら jp p, .dahead ; 直進 jp .dright ; 右折

  • 左下移動
移動方向が左下
ここも直線上の値で考えます。XとYは、符号が異なる同じ値になっていますので、加算してゼロで判定します。また加算した結果が正の値なら左折、負の値なら右折と判断します。直線上の場合は、Xの値が負の値なら正面となります。

; 左下移動 .dir3 ld a, d add a, e jr z, .@0 ; 直線上にいる jp p, .dleft ; 正の値なら左折 jp .dright ; 違うなら右折 .@0 ld a, d ; Xが or a ; 負の値なら jp m, .dahead ; 直進 jp .dright ; 右折

  • 左移動
移動方向が左
右移動と全て反対の判定となります。Yがゼロなら直線上にいます。Xがマイナスなら直進、そうじゃなければ右折…って、さきほど説明した内容なので、.dir3.@0 にジャンプして判定してしまいます。まあ、処理が短いのでそのまま記述しても良かったとは思います。あとは、Yが正の値なら左折、負の値なら右折です。


; 左移動 .dir4 ld a, e or a jr z, .dir3.@0 ; 直線上にいる jp p, .dleft ; 正の値なら左折 jp .dright ; 違うなら右折

  • 左上移動
移動方向が左上
右下移動のまるっきり反対です。180度向きが違うということは、左右判定においては方向が完全に反対になるだけなのです。XとYが同じかどうかで判断して、同じならXが負の値なら正面です。これも先程と同じく.dir3.@0 にジャンプして判定すれば良いかなと。Xが大きければ右折、小さければ左折となります。

; 左上移動 .dir5 ld a, d sub e jr z, .dir3.@0 ; 直進上にいる jp p, .dright ; X > Y なら右折 jp .dleft ; 違うなら左折


  • 上移動
移動方向が上
そろそろ怠くなってきて説明を飛ばす人が出てきてそうです^^; Xがゼロなら直線上でYが負の値なら正面です。そして、Xが正の値なら右折、そうじゃなければ左折となります。

; 上移動 .dir6 ld a, d or a jr z, .@0 ; 直線上にいる jp p, .dright ; 正の値なら右折 jp .dleft ; 違うなら左折 .@0 ld a, e ; Yが or a ; 負の値なら jp m, .dahead ; 直進 jp .dright ; 右折

  • 右上移動
移動方向が右上
8方向最後の右上です。XとYを加算してゼロなら直線上です。Xが正の値なら直進…は.dir0.@0でも判定していますので、そちらにジャンプさせます。XとYを加算した結果が正の値なら右折、そうじゃなければ左折となります。

; 左上移動 .dir7 ld a, d add a, e jr z, .dir0.@0 ; 直線上にいる jp p, .dright ; 正の値なら右折 jp .dleft ; 違うなら左折


  • あとがき
今回は8方向で解説しました。16方向の時は普通のやり方では判定が難しくなります。私なら、片方のオフセット座標を 1/2 にして同様に比較して判定します。正確な 22.5度の方向ではありません(26.565度)が、画面解像度が荒ければ全くわからないと思うためです。Z80のプログラミングでは、正確さよりも実行速度を重視すべきなので、近似計算が出来ればそれで良いと私は考えます。

なお、参考に上記で解説した処理を、もう少し最適化したソースコードをダウンロードしておきました。ご確認頂ければ幸いです。

CheckLR.zip
20231207-1400
※ 5TB もの大容量を持ち運べます。動画などのデカいファイルもこれなら簡単に運べますね!