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

矢印は自分の進行方向を示しています。そのすぐ正面の枠は[1,0]となってます。これは、自分から見てその位置は X=1, Y=0 という表現となります。
ここから、何か見えてきませんか?自分の右側は、この表示では下半分に相当します。下半分は、Y座標が全て正の値になっていますよね。このように、共通の条件を目視で確認すると分かりやすいと思います。もう少し分かりやすく色を付けてみます。

赤色 が右側、 青色 が左側、 黄色 は正面前方で、 黒 が後方です。Y座標で判断すれば良いことが一発で分かります。進行方向の直線上にいる時は、Y は 0 です。この場合は、X が正の値なら正面にいて、負の値なら後方にいることになります。後方にいたら、今回はとにかく右に曲がることにしてしまいましょう。このように全ての方向に対して、状況をまとめて整理すれば、どうすれば解決できるか判断しやすくなります。

最初にYの値がゼロかどうかを判定します。ゼロじゃなければプラスなら右折、マイナスなら左折で確定です。Yがゼロの時はXが正なら直進、負なら真後ろなので右折と判定します。

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

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

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

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

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

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

8方向最後の右上です。XとYを加算してゼロなら直線上です。Xが正の値なら直進…は.dir0.@0でも判定していますので、そちらにジャンプさせます。XとYを加算した結果が正の値なら右折、そうじゃなければ左折となります。
なお、参考に上記で解説した処理を、もう少し最適化したソースコードをダウンロードしておきました。ご確認頂ければ幸いです。
CheckLR.zip
20231207-1400
※ 5TB もの大容量を持ち運べます。動画などのデカいファイルもこれなら簡単に運べますね!
- アルゴリズム
このような単純な計算を行います。これはローカル座標変換ですね。そして、状況を確認するために、エクセルで座標テーブルを作成してみます。以下は自分が右方向に移動中のときの、相手との座標の差を全て取りまとめたものです。
X = X1 - X0 Y = Y1 - Y0

矢印は自分の進行方向を示しています。そのすぐ正面の枠は[1,0]となってます。これは、自分から見てその位置は X=1, Y=0 という表現となります。
ここから、何か見えてきませんか?自分の右側は、この表示では下半分に相当します。下半分は、Y座標が全て正の値になっていますよね。このように、共通の条件を目視で確認すると分かりやすいと思います。もう少し分かりやすく色を付けてみます。

赤色 が右側、 青色 が左側、 黄色 は正面前方で、 黒 が後方です。Y座標で判断すれば良いことが一発で分かります。進行方向の直線上にいる時は、Y は 0 です。この場合は、X が正の値なら正面にいて、負の値なら後方にいることになります。後方にいたら、今回はとにかく右に曲がることにしてしまいましょう。このように全ての方向に対して、状況をまとめて整理すれば、どうすれば解決できるか判断しやすくなります。
- 処理の分岐
このような単純な計算を行います。sbc hl, de とすると、キャリーを正しく反映させられないため、面倒でも X,Y 別々に計算します。自分の進行方向は 8方向です。右向きが 0 起点で、時計方向回りに +1 していきます。Acc が最初から 8方向(0~7)の場合は、ここから8方向別に処理を分岐します。
ld a, h sub d
ld d, a
ld a, l
sub e
ld e, a
.@Lはアドレスの下位を取得するマクロです。同じく .@H はアドレスの上位を取得します。CHECK ALIGN 256 は、ENDC で挟まれた範囲が、256バイトの境界を跨がないよう監視するアセンブラ命令です。計算速度を上げるため、下位8bitだけで計算していますが、もし、テーブルが256バイトの境界を跨いでしまうと正しく動作しなくなります。そのため、CHECK ALIGN 256 を指定しておくことで、アセンブル時にアドレスを跨いだ時はエラーとして報告してくれるようになります。これは AILZ80ASM の独自機能ですが、とても便利で私にはなくてはならない機能になっています。
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
- 共通ルーチン
2-3バイトの処理なので、ここにジャンプさせるほうが遅くはなるし、コードは少しだけ長くなるしと Z80 的には不利な書き方なのですが、将来、方向を左回りに変更する際は、ここを変えるだけで全ての処理が変わるので良いかなと。まあ、ラベル定義で
; 左折 .dleft ld a, -1 ret ; 右折 .dright ld a, +1 ret ; 直進 .dahead xor a ret
と書いておけば良いだけなのですが…^^;
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 より大きければ左側、その逆なら右側と判断すれば良いことが分かります。これをコード化します。
※ 今から40年以上前、カシオのこんな感じの腕時計をして高校に通っていました。懐かしいです。それが今や捨て値に近い金額で。とりあえず時間が分かればOKというニーズにはぴったりです。
; 右下移動 .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 ; 右折
- 下移動

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 ; 違うなら左折
- あとがき
なお、参考に上記で解説した処理を、もう少し最適化したソースコードをダウンロードしておきました。ご確認頂ければ幸いです。
CheckLR.zip
20231207-1400
ウエスタンデジタル(Western Digital)
2019-09-20
コメント
コメント一覧 (2)
内藤時浩
が
しました