プログラミング: 2014年12月アーカイブ

前回書いたように、なるべく方向転換をしない経路(スタート地点、ゴール地点が向かい合う対角の頂点である平行四辺形の2辺を辿るコース。つまり2パターンある)を自動作成するためには、その各パターン一つだけの方向転換点をまず算出しなくてはならない。

20141224_hexer.jpg
上の例では、スタート地点が 15-17-18(黄色のヘックス)、ゴール地点が 2-8-15(緑のヘックス)だが、この場合の方向転換点は、コマの進行方向(この場合、ヘックスの 0時方向)の右と左に向かう2パターンがあり、それぞれ X,Y,Z軸の値が以下の計算で求められる。
※スタート地点を(A)、ゴール地点を(B)とする。

<右に向かうコースの転換点の座標>
X = X(A) - (Z(A) - Z(B)) = 15 - (18 - 14) = 11
Y = Y(A) = 17
Z = Z(B) = 14 

X-Y-Z = 11-17-14

<左に向かうコースの転換点の座標>
X = X(A) - (Y(A) - Y(B)) = 15 - (17 - 8) = 6
Y = Y(B) = 8
Z = Z(A) = 18 

X-Y-Z = 6-8-18

実際の図に照らし合わせてみると、ちゃんと合っているのが確認できる。

その他のゴールの場合も、例えば 3-11-12(青)に向かう場合の転換地点は 9-17-12(右)と 9-11-18(左)で上記の計算方法のとおりで、ゴールが 1-12-9(橙)の場合の転換地点、6-17-9(右)、10-12-18(左)の場合もそうである。

進路の決め方としては、

1.まず、転換地点を算出。
2.スタートからその転換地点までの最短経路を作成
3.その転換地点からゴールまでの最短経路を作成

となる。
例えば、「スタートからその転換地点までの最短経路」であれば、前回作った「目的地の方向へひたすら進む」というプログラムをそのまま使えば良いし、「転換地点からゴールまでの最短経路」も同じである。

さて、ゴールが上図で薄い黄色で塗った三角形の内側であればこの計算でいけることが実証できた。

次は、三角形の部分とは別の方向にゴールがある場合、この計算式でいいのか?別の計算式はどういう内容か・・・といった辺りを考えてみることにする。

今日はもう肩(五十肩)が痛いので寝るよ(笑)
この間書いた「ヘックスへの最短経路の自動計算」の話で、真上のヘックスに対してジグザグに進んでいく案を示したのだが、それに対して「移動するヘックス数は同じでも、それでは運動量が大きくなりすぎるので最短経路とはいえない」という意見をいただいた。

この手のヘックスマップの場合、例えば右横に行こうとすると「右に必要なだけ回転して進路を変える」という動作が必要になる。そして、ジグザグに動くということは、「右に向きを変える」「1つヘックスを進む」「左に向きを変える」「1つヘックスを進む」という具合に、間に向きを変える動きが入るので、最短経路とはいえないということだ。
なぜかというと、「向きを変える」という動きに、例えば「1ヘックス進む」という動きと同じ運動量が想定されている場合が多い。
つまり、ジグザグに「右、左」と進んだ時は、2移動量ではなく4移動量分の運動量が消費されるというわけだ。

なるほど。

例えば、前回の話で使用したマップであれば、6-5-7 から 2-3-5 に向かうのに、

6-5-7→5-5-6→(L)→4-4-6→(R)→3-4-5→(L)→2-3-5

と進む場合、移動量は4ではなく(5-5-6 へのまっすぐ進んだとして)間に3回の方向転換を含むので、7である・・・という考え方やね。(L は左転換、R は右転換)
なので、本当の最短経路は、できるだけ斜めに突っ切って、途中で方向転換してまた斜めに目標に向かう、

6-5-7→5-5-6→4-5-5→(L)→3-4-5→2-3-5

であると。これであれば、4-5-5 から 3-4-5 に進む時に1回だけ方向転換をすればいいので、移動量は5ですむと。

これは、「大戦略」などのメジャーなゲームで採用されているパターンのようだ。つまり「コマの向き」がヘックスの「辺」に向かっているということだね。
いや、俺も Windows 3.1 の頃に「大戦略」買ってやったことあるけど(IIIかなあ?)、すぐ飽きちゃったので記憶にまったく残ってないのだ(^^;なので、上記のような指摘をいただいた時、「???」なのであった。

今回、俺が想定しているゲームは、国際通信社から出ている「WORLD TANK BATTLES」(ワールド タンク バトルズ~小さな戦車の大戦車戦!~)である。

20141223_hex1.jpg

このゲームは、上の図のように「コマの向き」がヘックスの「頂点」なのだ。そして、その頂点の左右にあるヘックスには「方向転換無し」で進めるのである。(青い矢印)
後退の場合も、同じように後ろの頂点の左右には「方向転換無し」で進める。(黄色い矢印)
つまり、

6-5-7→5-5-6→4-4-6→3-4-5→2-3-5

も、

6-5-7→5-5-6→4-5-5→3-4-5→2-3-5

も、同じ「移動量4(運動量4)」なのである。(L や R の転換がない)

もちろん、上の図で言うと、3-4-19 から 3-5-18 に向かおうとすると、右に1頂点分回転しないといけないので、そこで+1移動量となるが。

この前提を提示していなかったので色々混乱を呼んでしまった。
ただ、「可能な限り斜めに向かい、1回だけ方向転換して更に斜めに目標地点に向かう」という経路の取り方は、障害物の迂回経路を自動作成するのに使えそうである。

ということで、次回はこの斜めコースの自動作成方法について考えてみよう。

今回は、ヘックスマップ上のスタート地点とゴール地点を与えてやって、自動で最短距離の経路を取得する方法を考えてみよう。

20141219_hexer2.jpg

↑こういうマップをテストで使う。
青い線が X 軸、オレンジの線が Y軸、緑の線が Z 軸で、この軸の値を X-Y-Z のように並べてヘックスIDとする。

経路を決める方法としては、

・全ての経路を算出して、その中から最短のものを採用する(あるいは、人に選択させる)
・最短の(あるいは任意の条件の)経路のみを算出する

という2パターンがあると思うが、全ての経路(あるいは任意の数の経路)を算出するのは大変なので、今回は最短経路を求めてみる。

で、最短経路をどうやって求めればいいかだが、とりあえず「目的地の方向へひたすら進む」という戦略を取ってみることにする。

上の画像のような形で X,Y,Z 軸を決めると、ある任意のヘックスから見た目的地の方向は下の図のようになる。

20141219_hexer1-1.jpg

要は、一歩一歩(1ヘックス進む毎に)目的地の方向を調べその方向へ進むということを繰り返すのだ。単純かつ確実。

ちなみに、進める方向によって、元の X,Y,Z 軸の値に、下の表のように加減を行なう。

20141219_hexer1-2.jpg

例えば、2-3-5 から 2-4-4 に移動するには右横に進めばいいので、2-3-5 の X 軸の値はそのままに、Y 軸の値に 1を足し、Z軸の値から 1を引けば 2-4-4 になるというわけ。

この間のエントリーに載せていた Perl スクリプトに、

# 経路選択
$i = 0;
for (;;) {

# 方向を見極める
$xd = $x2 - $x1; # X軸差
$yd = $y2 - $y1; # Y軸差
$zd = $z2 - $z1; # Z軸差

$i++;

# 方向によって、進むヘックスを決める
if ($xd == 0 && $yd > $zd) { # 目的地は真右
$x1 = $x1;
$y1 = $y1 + 1;
$z1 = $z1 - 1;
print "\[$i]目的地は真右\n";
}
elsif ($xd == 0 && $yd < $zd) { # 目的地は真左
$x1 = $x1;
$y1 = $y1 - 1;
$z1 = $z1 + 1;
print "\[$i]目的地は真左\n";
}
elsif ($xd < 0) {
if ($yd == $zd) { # 目的地は真上(真上には行けないので、とりあえず右上に)
$x1 = $x1 - 1;
$y1 = $y1;
$z1 = $z1 - 1;
print "\[$i]目的地は真上(右上)\n";
}
elsif ($yd > $zd) { # この場合も右上
$x1 = $x1 - 1;
$y1 = $y1;
$z1 = $z1 - 1;
print "\[$i]目的地は右上\n";
}
elsif ($yd < $zd) { # 目的地は左上
$x1 = $x1 - 1;
$y1 = $y1 - 1;
$z1 = $z1;
print "\[$i]目的地は左上\n";
}
}
elsif ($xd > 0) {
if ($yd == $zd) { # 目的地は真下(真下には行けないので、とりあえず左下に)
$x1 = $x1 + 1;
$y1 = $y1;
$z1 = $z1 + 1;
print "\[$i]目的地は真下(左下)\n";
}
elsif ($yd < $zd) { # この場合も左下
$x1 = $x1 + 1;
$y1 = $y1;
$z1 = $z1 + 1;
print "\[$i]目的地は左下\n";
}
elsif ($yd > $zd) { # 目的地は右下
$x1 = $x1 + 1;
$y1 = $y1 + 1;
$z1 = $z1;
print "\[$i]目的地は右下\n";
}
}

# もうゴール?
if ($x1 == $x2 && $y1 == $y2 && $z1 == $z2) {
print "\[$i]ゴールです!$x1-$y1-$z1\n";
last;
}
else {
print "\[$i]経路=$x1-$y1-$z1\n";
}

}

こういう処理を足して実行してみる。

上の図の 2-3-5(黄色いヘックス)から 5-7-4(緑のヘックス)への経路を作成させてみると、

$ ./hex_dist2.pl
開始位置=2-3-5
終了位置=5-7-4
最短距離=4
[1]目的地は右下
[1]経路=3-4-5
[2]目的地は右下
[2]経路=4-5-5
[3]目的地は右下
[3]経路=5-6-5
[4]目的地は真右
[4]ゴールです!5-7-4

お、ちゃんと最短距離で正しく経路を作成してるぞ。

今度は逆方向に、6-5-7(青色のヘックス)から 2-3-5(黄色いヘックス)に向かう経路を作成させる。

$ ./hex_dist2.pl
開始位置=6-5-7
終了位置=2-3-5
最短距離=4
[1]目的地は真上(右上)
[1]経路=5-5-6
[2]目的地は左上
[2]経路=4-4-6
[3]目的地は真上(右上)
[3]経路=3-4-5
[4]目的地は左上
[4]ゴールです!2-3-5

ばっちりやん。

六角形のマスなので、真上や真下には動けないから、とりあえず右か左に一旦進んで、ようはジグザグに行くしかないのでこういう動きになる。

今回は、

・マップの端に達した(最短距離を取るので今回はあり得ないんだけど)
・障害物がある(マップ上に配置された岩や沼など)
・コマの向きの概念(方向転換しないと、向いている方向にしか進めない)

というようなケースには非対応なので、今後はこの辺のロジックを考えていくかな。
(連続する障害物をどう避けていくか・・・とか)

このアーカイブについて

このページには、2014年12月以降に書かれたブログ記事のうちプログラミングカテゴリに属しているものが含まれています。

前のアーカイブはプログラミング: 2014年11月です。

次のアーカイブはプログラミング: 2015年1月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。


月別 アーカイブ

電気ウナギ的○○ mobile ver.

携帯版「電気ウナギ的○○」はこちら