Robocode Tank Royale 衝突時の対応

こちらの続き

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

## 動きを変える

近場をうろちょろせず、混戦は避けるために
・盤面を動き回る

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

・敵に衝突したらそこから離れる

止まっている敵にも、意外と弾が命中しない。
・せめて止まっている敵には確実に命中させる

これらをやっていきます。

## 衝突したらそこから離れる

IBaseBot のAPI一覧を見ていると、衝突イベントにもいろいろあるのだなと分かります。

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

衝突するのは敵だけではなく、壁もある。
しかも、敵は体当たり得点もあるけど、壁はダメージを受ける。

https://robocode-dev.github.io/tank-royale/articles/scoring.html#calculation-scores

その辺も考えていきます。

衝突イベントの中から、
- onHitWall
- onHitBot
に対応し、さらにちょっと応用編で、
- 近づきすぎずに適度に距離をとる
というのをやってみようと思います。

### onHitWall

サンプルだとこのイベントを処理しているのは Crazyボットで、単純に前進かバックかを切り替えています。が、ちょっと面白くない気がする。
もう少し、滑らかに壁から離れられるようにしたいと思います。

Wallsボットがこんなことをしています。

```java
// turn to face a wall.
// `getDirection() % 90` means the remainder of getDirection() divided by 90.
turnRight(getDirection() % 90);
forward(moveAmount);
```

ちょうど東西南北を向いている場合は 0度回転なのでそのまま直進、例えば100度の角度（真北より10度分西向き）を向いている場合は、10度右回転して真北を向いてから直進、という感じですね。

これと同じような感じで、壁にぶつかった時に、45度の角度で壁から離れようと思います。
例えば真北（90度）を向いているのであれば、北の壁にぶつかったということですから、225度の角度（南西）に向かって前進したいので、左に135（225−90）度回転して前進します。
もし100度を向いていたのであれば、125(225−100)度左回転です。
もし80度を向いていた場合は、南西だと時間がかかる（145度回転が必要）ので、右回転して南東に向かいたいです。南東は315度ですが、-45度でもあります。（-45-80）度左回転（実際は125度右回転）したいということです。
ところで、100度を向いていた場合は、もしかしたら西の壁にぶつかった可能性もあります。その場合は、北東に向かいたいので、(45-100)度左回転（実際は55度右回転）したいです。

条件分岐を整理するとこんな感じでしょうか。

- 0 < 向き <= 180　（上向き）
    - x座標0（西壁に衝突）
        - 北東に向かう
    - else
        - y座標が最大値（北壁に衝突）
            - 向き >= 90
                - 南西に向かう
            - else
                - 南東に向かう
        - else（東壁に衝突）
            - 北西に向かう
- else （下向き）
    - x座標0（西壁に衝突）
        - 南東に向かう
    - else
        - y座標0（南壁に衝突）
            - 向き >=270
                - 北東に向かう
            - else
                - 北西に向かう
        - else （東壁に衝突）
            - 南西に向かう

合ってるかな・・・
戦略だけ決めて一旦検討を進めます。

### onHitBot

当たられた場合と体当りした場合がありえて、体当たりで得点を稼ぐ考えもあると思いますが、ヒットアンドアウェイの観点からは、どちらの場合も引くことにします。

ただ近くにいるということは弾も当てやすいということではあるので、ここでは銃だけ動かして撃ちつつ、敵と反対側に移動します。

ということで、まずは、敵の方角取得。

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

座標を取得できるので、そこから方角を取得します。
これについて参考になるのは、Fireボット。というかそのままやってました。
そして、敵角度−銃角度分左回転。

```java
var direction = directionTo(e.getX(), e.getY());
var gunBearing = normalizeRelativeAngle(direction - getGunDirection());
turnGunLeft(gunBearing);
```

ただ、このままだと、180度以上回転する場合も出てくるので、その場合は右回転するようにしたい。

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

もう少しスマートに書けそうだけど、思いついたら直します。

撃ってから、銃の位置を戻しつつ次に進む。

```java
fire(3);
setTurnGunRight(gunBearing);
go();
```


### 関数化

一旦上記のロジックそのままに書きますが、今後ブラッシュアップしちていくことを踏まえて、関数化はしておきます。

```java
    @Override
    public void onHitWall(HitWallEvent e) {
        leaveWall();
    }
    // 壁から離れる
    public void leaveWall() {
        var dist = 20;// botは36x36　なので中心位置は18
        double deg = 0; //
        if(0 < getDirection() && getDirection() <=180){ //上向き
            if (getX() < dist ){  // 西壁に衝突
                // 北東に向かう
                deg = 45 - getDirection();
            } else {
                if (getY() > getArenaHeight() - dist) { //北壁に衝突
                    if (getDirection() >= 90) {
                        // 南西に向かう
                        deg = 225 - getDirection();
                    } else {
                        // 南東に向かう
                        deg = -45 - getDirection();
                    }
                } else { // 東壁に衝突
                    // 北西に向かう
                    deg = 135 - getDirection();
                }
            }
        } else { // 下向き
            if (getX() < dist) { // 西壁に衝突
                // 南東に向かう
                deg = 315 - getDirection();
            } else {
                if (getY() < dist) { // 南壁に衝突
                    if (getDirection() >= 270 ) {
                        // 北東に向かう
                        deg = (360 + 45) - getDirection();
                    } else {
                        // 北西に向かう
                        deg = 135 - getDirection();
                    }
                } else { // 東壁に衝突
                    // 南西に向かう
                    deg = 225 - getDirection();
                }
            }
        }

        turnLeft(deg);

    }
```

予想通りの動きはしてくれてます。

### 壁に近づきすぎない

最初は敵との距離感も取ろうと思ったのですが、体当たりも得点になるというのと、まあ近いほうが弾が当たりやすいというのもあるので、上のヒットアンドアウェイで良しとします。

ここでは、壁になるべくぶつからないように考えます。


まず、上記のままだと、壁にぶつかった時に、一旦は離れようとするけど、すぐにまた 同じ方向に回転し出して、 結局壁に衝突を繰り返しがちです。
それを避けるのと、多少のバリエーションを持たせるために、右回転、左回転を適宜切り替えるようにします。

```java
    boolean turningLeft;

    public void run() {
        ///
        while (isRunning()) {

            setForward(400);
            if (turningLeft) {
                setTurnLeft(45);

            }else {
                setTurnRight(45);
            }
            go();

            ///
        }
    }

    public void onHitWall(HitWallEvent e) {
        leaveWall();
        turningLeft = !turningLeft;
    }
```

さらに、runの中で、壁からの距離が40未満になったら上のleaveWall関数発動。

```java
            if (getX() < (dist * 2) ||
                getY() < (dist * 2) ||
                getX() > (getArenaWidth() - dist * 2) ||
                getY() > (getArenaHeight() - dist *2) ) {

                leaveWall();

            }
```

これは、効いてるのか微妙なため、保留。

意図通りに動いているっぽいところと、そうでもないところが出てきてます。
もう少し冷たいところですが、それは応用編でやることにして。

### 弾を知的に打つ

もう一つ他のbotの技を盗んでおきます。
Cornersがこんなことをやっています。

```java
    private void smartFire(double distance) {
        if (distance > 200 || getEnergy() < 15) {
            fire(1);
        } else if (distance > 50) {
            fire(2);
        } else {
            fire(3);
        }
    }
```

遠いときや、余力がない時は、小さく打つ、ということですね。
ただ、応用編ではさらに知的に撃つようにするために、敵の情報そのものを受け取るようにしておきます。

```java
    // 知的に撃つ
    private void smartFire(ScannedBotEvent e) {
        var distance = distanceTo(e.getX(), e.getY());
        if (distance > 200 || getEnergy() < 15) {
            fire(1);
        } else if (distance > 50) {
            fire(2);
        } else {
            fire(3);
        }
    }
```

今回はここまで。