Botters of the Galaxy
CodinGameの10日間コンテスト。LoLのようなゲームです。 ヒーローを2体操作して、相手のヒーローを倒す or 相手の塔を破壊する or 相手よりkill数を稼ぐということが目的です。
リプレイ : https://www.codingame.com/replay/294861078
世界5位/1713人、国内2位/90人でした。
アルゴリズム概要
結論から言うとif文連打のルールベースでした。
今回は戦略と戦略のぶつかり合いになると思ってました。つまり、環境を席巻する戦略が生まれて、それをメタる戦略が生まれて・・の繰り返しになると思ってました。 なので正確なシミューレーションを元にして細かく稼いでいく事よりも、素早く環境に適応できる事が重要だと考えました。
シュミレータと評価関数を元にした探索アルゴリズムは、人間より深い思考を行い人間より頭の良い手を打つことは得意ですが、 自分の思った通りの行動を行わせることには向いてないので、今回は探索系のアルゴリズムを採用せずにルールベースを採用しました。
アルゴリズムを改善した際によくやる自分vs自分も今回は何も意味をなさないので、アルゴリズムを弄ったらアリーナ上の誰かしらをボコすことで改善を確認してました。
なので今回はシミュレータを作ってません。
ゲームの戦略
IRONMAN&DOCTORで戦いました。
- 相手が近距離戦を挑んできた場合 → 塔に引きこもって迎え撃つ
- 近づいてこない場合 → FIREBALLを撃って殺す
- 隠れて引きこもる場合 → kill数で勝つ
- ミラーマッチ → 頑張る
という戦略で、理論上どんな相手にも勝てるという布陣です。
なお
- 塔なんて気にしねぇ!ガチムチ特攻だ! → 殺される
- FIREBALLで死なないように命だいじに作戦 → KILL数で負ける
という戦略に敗北した模様。
各種行動
リーダーの行動
IRONMANはひたすらFIREBALL連打をするのが仕事です。 DOCTORはそのサポートみたいな感じでPULLして敵ヒーローのHPを削る手伝いをしたり回復をしたりです。
お買い物
IRONMANだけがアイテムを装備します。買う装備は最初に決めていて、500gold分を使ってマナ増加装備だけを買います。無かったらマナ増加量を上げる装備を買います。 残りのgoldは全てポーションに消えます。
ポーション使用法
普通のポーションは死にそうになったら使います。 マナポーションはIRONMANのマナを満タンに維持するぐらいの勢いで使います。
スキル
- BLINK : 何故かマナが回復するので使えるときに無制限に使用してます。
- FIREBALL : HPの低い相手にぶっ放してます。小癪にもFIREBALLを避けてくる相手もいるので、そういう相手には動きを先読みして当てています。grootにFIREBALLでちょっかいを出して敵ヒーローを殴らせる戦術もパクりました。
- BURNING : 自タワー付近に押し込まれている場合、敵兵士を一掃するために使ってます。
- PULL : 引き込んだ先に味方兵士が3体以上いるなら引き込みます。
- SHIELD : PULL等で引き込まれて、周りが敵だらけになった場合自分にSHIELDを打ちます。
- AOEHEAL : 雑に使います。
攻撃
敵ヒーローの周りに味方兵士が居るなら敵ヒーローに攻撃します。 敵兵士を1撃でkill出来るならkillします。 味方兵士を1撃でdeny出来るならdenyもしますが、相手がハルクで殴り殺すマンだった場合denyはしないようにしました。 あとは雑に射程圏内に敵が居たら攻撃してました。
移動
自分のリプレイを見てたらわかりますが、基本的に自兵士の後ろについていくように行動します。 ただし、移動先がやばそうなスキル圏内(PULL・CHARGE・SPEARFLIP)とかだったら移動しません。 移動できるところがない場合は、自タワー付近のBUSHに逃走します。
コーディングテク
メインの関数は↓みたいな感じで見通しを良くしてます。
MOVEは↓のようなマクロで、「行動できたらその行動を採用して、行動できなければ次のMOVEを試す」みたいな感じです
#define MOVE(move_func) { Cmd cmd = move_func; if (!Cmd::IsNone(cmd)) return cmd; }
関数は↓みたいな感じで出来るだけ細切れになるように作ってます。(全部で約60関数)
なので、例えば「操作していない方の味方ヒーローに一番近い敵を攻撃する」みたいな処理は↓みたいなこんな感じで30秒で書けます。
Unit* other_hero = GetOtherHero(hero); if (other_hero != NULL) { Unit* near = GetNearUnit(_enemy_units, *other_hero); if (near != NULL) { return AttackOutRangeMove(hero, *near, "Sample Attack"); } } return Cmd::None();
できるだけ考えた作戦をノータイムでかつバグらないように書けることを重視しました。
反省点
5位に入れたので概ねは満足。
最終日にメタり合いになるのは分かりきっていたので、気力・モチベーションのピークを最終日に持っていくべきだった。
なんだかんだでゲームのセンスは重要。