Robocode Tank Royale より知的に弾を打つ（三角比）

これらの続き

https://qiita.com/JQinglong/items/7ec02a689f78abbd3324

https://qiita.com/JQinglong/items/0415d200d66c152edd10

https://qiita.com/JQinglong/items/c43b60f0efa0742eaa7b

前回の最後に、知的に弾を打つためのメソッドをCornersからパクりましたが、今回は、これを発展させます。

三角比ネタが盛り上がっているうちにここまできたかった・・・

## 問題の所在
現在、基本的には敵をスキャンできたらそこに対して弾を打っているわけですが、そこに向けて打っても、止まっている敵でない限りはそこにはもう敵はいないことが多いわけです。
これに対応していきます。

## 時間による移動距離の計算

基本情報はこちらになります。

https://robocode-dev.github.io/tank-royale/articles/physics.html#movement

まず、とっかかりとなるのは、

[distance（距離） = speed（velocity:速度） × time（時間）](https://robocode-dev.github.io/tank-royale/articles/physics.html#movement)

距離の単位はunits、時間の単位はturnsとなっています。
速度が1turnあたり4unitsで、3turnsの時間がかかる距離は、12unitsですね。

逆に、60unitsの距離に対して、3untis/turnの速度で向かうと、20turnsの時間がかかるということになります。
これが基本です。

botについては、基本1unit/turnで、加速していくけど、最大8units/turn。
弾については、計算式がありますが、パワー1なら17unit/turn、パワー2なら14unit/turn、パワー3なら11unit/turnというのを押さえておきます。

また、物の位置移動だけでなく、回転速度も基本情報として必要になります。

bot自体の回転速度はやや複雑な計算式になっているので、下記文章で押さえておきます。

> これは、移動速度が速いほど回転速度が遅くなることを意味します。
> 最大速度8untis/turnで移動した場合、最大回転数は4°/turnにしかなりません。

そして、銃は20°/turm、レーダーは45°/turnとなっています。

## 大まかな戦略

上記基本情報レベルで、戦略を考えます。

### レーダーで敵を捉える

現状、大まかにはぐるぐる回る動きをしています。そうすると、その円弧の中にいる相手はずっと見つけられないことになります。適当なタイミングで盤面をスキャンすることが必要そうです。

また、敵とぶつかった時に、その方向に銃を向けて弾を撃っていますが、そこには敵は既にいない可能性があるので、まずはレーダーを向けて敵情報を取得するようにしたいです。

### 敵の移動を予測する

捉えた敵の情報は以下のようになっています。

https://robocode-dev.github.io/tank-royale/api/java/dev/robocode/tankroyale/botapi/events/ScannedBotEvent.html

getX()、getY()で位置を取得し、getDirection()、getSpeed()で移動方向・速度を取得できます。
もちろん、その方向・速度で移動し続けるかは不明ですが、同じような動きをし続ける敵や、変化の誤差を無視できるほどの距離にいる敵に対しては、それなりの予測ができそうです。

なお、smartFireで使ったように、distanceTo(e.getX(), e.getY())で簡単に距離を取得できます。

https://robocode-dev.github.io/tank-royale/api/java/dev/robocode/tankroyale/botapi/IBaseBot.html#distanceTo(double,double)

そうすると、次のような計算ができます。

簡単な例：
自分の位置：中央
敵の方角：東
敵までの距離：１００units
敵の移動方向：北に4untis/turn

パワー2の弾が100units移動するのは約７turn、その間に敵は28units北に移動している。
そうすると、真東よりもちょっと上に撃たないといけない。
それを計算します。

## 三角比の復習

まあ、避けては通れないですよね。ただ、忘れてしまっているので、軽く復習。
今回一番使いそうなタンジェントで言うと、直角三角形をイメージして、底辺と高さの比がタンジェント、これは角度によって決定されている、という感じですね。同じ角度であれば、比は同じ。比が同じなら角度は同じ。
例で言うと、30度なら1.73対１、45度なら１対１ということですね。
Javaの関数で言うと、Math.tan(60度※実際はラジアンで指定)=1.73ということです。

ただ、今回は、位置情報から角度を求めたいです。それは（習ったっけ・・・）アークタンジェント（逆正接）となります。

> Math.atan2(double y, double x )

これは使える。
先ほどの例はこれで解決できます。概念で言うと
角度＝Math.atan2(−100, 28)
ということですね。

## 実装

### 移動距離の計算

現在位置があり、方向と速度が分かれば、１単位時間後の場所が求められますね。
一旦加速度を無視して、1unit/turnの速度で移動するとします。
そうすると、三角形で言えば、斜辺が一の状態です。それに対して、底辺（x軸）の比率はコサイン、高さ（y軸）がサインですね。ざっくりと、1trun後の位置は
(getX() + cos(方向), getY() + sin(方向))
ですか。


これをRobocode実装に落とします。

getX、getYは座標なのでそのまま使います。
方向getDirectionでは、東が0度左回りの角度が取得できます。（これをやりやすくするために、Tank Royaleで座標系を見直したわけですね）

```java
        double nextX = e.getX() + Math.cos(e.getDirection()); 
        double nextY = e.getY() + Math.sin(e.getDirection());
```

これは単位時間当たりの移動なので、時間をかけてあげれば場所が求められます。
さらに、実際は加速しているので1unit/turnの速度想定ではかなり足りないようです。速度８で動いている想定にします。
結論としては、こんな感じにしてみました。

```java
    //弾が到達するまでの時間
    double t = distance / (20 - 3* pow);
    //銃を移動させる時間を適当にプラス（20度移動するとして1）
    t = t + 1;

    // 弾が届くまでの敵の移動距離
    // 方向と速度と時間によりその時間後の位置を求める
    double radDir = Math.toRadians(e.getDirection());
   // 速度は8想定
   double nextX = e.getX() + Math.cos(radDir) * 8 * t;
   double nextY = e.getY() + Math.sin(radDir) * 8 * t;

```

### 角度計算

移動先を決定したら、そこに向けて銃を回す角度を計算します。
仰角問題ですね。
ロボコードやってればもっと良い施設配置ができたのに。

上記のようにアークタンジェントです。javaはラジアンで返すので、これを度数に変換します。
ここもちょっと懐かしいと思いながら調べましたが、要するに180度がπラジアンなので、
１ラジアンは（π分の180）となります。

結論としてこんな感じにしました。

```java
    // 知的に撃つ
    private void smartFire(ScannedBotEvent e) {
        double distance = distanceTo(e.getX(), e.getY());
        double pow;

        if (distance > 200 || getEnergy() < 15) {
            pow = 1;
        } else if (distance > 50) {
            pow = 2;
        } else {
            pow = 3;
        }

        //弾が到達するまでの時間
        double t = distance / (20 - 3* pow);
        //銃を移動させる時間を適当にプラス（20度移動するとして1）
        t = t + 1;

        // 弾が届くまでの敵の移動距離
        // 方向と速度と時間によりその時間後の位置を求める
//        e.getDirection()
        double radDir = Math.toRadians(e.getDirection());
        System.out.println("ーーー" );
        System.out.println("e.getDirection():" + e.getDirection());
        System.out.println("radDir:" + radDir);
        System.out.println("Math.cos(radDir):" + Math.cos(radDir));
        System.out.println("Math.sin(radDir):" + Math.sin(radDir));

        // 速度は8想定
        double nextX = e.getX() + Math.cos(radDir) * 8 * t;
        double nextY = e.getY() + Math.sin(radDir) * 8 * t;

        System.out.println("distance:" + distance);
        System.out.println("e.getX():" + e.getX());
        System.out.println("nextX:" + nextX);
        System.out.println("e.getY():" + e.getY());
        System.out.println("nextY:" + nextY);

        double nextRad = Math.atan2(nextY - getY(),nextX - getX());
        double nextDeg = nextRad * 180d / Math.PI;
        System.out.println("nextDeg:" + nextDeg);

        double gunBearing = normalizeRelativeAngle(nextDeg - getGunDirection());

        if (gunBearing > 180) {
            gunBearing = gunBearing - 360;
        }
        turnGunLeft(gunBearing);

        fire(pow);

    }
```

ちゃんと計算されているのかわからず、過剰にログ出しして確認してますが（それで途中、ラジアンからの変換忘れ等に気づきましたが）、ここまでやると、Walls（直線的な動き）には多少当たるようになってきました。

## その他の課題

しかし、色々問題がありそうです。
・上記で適当に設定した、銃の移動時間
・自分自身の動きの影響
・相手の加速度計算
・接近戦になった時の集中砲火（相手のターゲティング）
・自分の位置調整
・位置取り（攻撃されにくい場所の考え方、相手との間合い）

まだまだ研究は続きます。