※画像は公式Visualizerよりお借りしました
はじめに
何らかのコンテストへの参加はおよそ半年ぶりになります。本日は4/21 - 5/2の期間にCodinGameで開催されたコンテスト「Spring Challenge 2022」についての記事になります。全文に目を通してられない方は上の目次だけでもぜひ見ていってください。
CodinGameコンテストに参加するのは今回が2回目だったのですが、前回に続きLegendに到達することができ、結果273位/7695人(Legend内 273位/390人)でした。
いまだ用語がよくわかっておらずところどころ間違っていたりすると思いますが、発見された方は優しくご指摘いただけると嬉しいです。
前回書いたHTTF for Youth+ のPostmortemはこちら
ゲーム概要
ゲームルールにつきましては、おそらくここにたどり着いている方のほとんどが既知だと思いますので割愛します。雑にいえばモンスターを敵陣地に侵入させライフを削るゲームです。
最終提出AIの詳細(戦術、アルゴリズム)
言語:Go言語
今回コンテストに提出したAIはSpringChallenge2021に続きGo言語を使用したものになります。ただ、今回提出したのは探索0なのであまり実行速度は気にしなくてよかったかも・・・。一番触れていてなじみのあるPython3で提出してもよかったかもしれません。
アルゴリズム:ルールベース
CodinGameのbot programmingでは必ずと言っていいほどビームサーチによる探索を行っていたのですが、ゲームの不完全さやマルチエージェントであることなどが原因で難航し、最終提出はif文による分岐だらけのルールベースのプログラムになりました。(実は、ビームサーチのものが作りたくて、最初の週末が終わってから月~金の間はコードは書かず頭の中で評価関数のアイデアを出していました。自分の中ではそれなりによさそうなものが頭の中にできていたのですが、土曜日に作ってみると無茶苦茶弱かったのでルールベースを考えることにしました。結果評価関数含めほとんどが粗大ごみになりましたので、以下はごみにならなかった方のプログラムの話になります。)
以下、AIの概要です。
・DEFENDER、SUPPORTER、ATTACKERの3人
この3役職はそれぞれidが0,1,2(red側なら3,4,5)のheroがこの順で担当することになっています。それぞれのheroはほかの味方heroの状況をまったく知らされていないのでどのような状況になっても役割のスイッチは行なわれません。連携×
・マナを一定までためると攻撃モード、マナが減ったら収集モード
攻撃モードに切り替わる基準は250です。後半戦になるとこの基準値は下がり、積極的に攻撃モードに変わります。
一方、収集モードに切り替わる基準は50で、これは固定です。
・それぞれの配置は完全に持ち場制
各heroは味方のheroの存在を知らないので、それぞれ一人で戦っていると錯覚しています(その割に敵heroやmonsterの存在は知ってるらしい)。したがって、戦闘を繰り広げるにあたって、あらかじめ各ヒーローはどこを待機場所とするか、どのエリアの敵が自分の管轄かを明確に定められています。味方のheroがキャパオーバーであったとしても、それを理由にサポートに入ることは絶対になく、結果、非常に脆いbotになりました。下にみんなへ伝えた勤務担当を載せておきます。
DEFENDERくん「別マニュアル?( ;∀;)(if文約30個)」(一人体を張らせてしまってごめんね・・・)
SUPPORTERくん「え、担当ここだけ?( ・ิω・ิ)」(彼は積極的にコントロールすることで持ち場を離れないこともあって隅で一人ぼっちなことが多いです)
ATTACKERくん「別マニュアル??( ;∀;)(if文約60個)」(攻撃が多彩なのはうれしいけれど、パスは取りに行こうね)
諸々の小細工
上だけだと他のルールベースの人と差がつけられない(むしろ連携等ないので弱い)ため、ボリューム層からLegend昇格できるまで順位を上げるためにいろいろと細工しました。その一部を簡単に書いておきます。
・スポーン後control,windされていないmonsterを見つけたら、対称な位置にmonsterがいるとして登録
若干見通しが良くなりますが、あまり有効に使っているところは見ません。
・対象を攻撃する際、複数巻き込めるなら巻き込む
なんとなくマナ回収効率が良くなったのが実感できます。
(Last battlesで対応しているセリフ:「両取り」)
・ATTACKERはshieldをかけたmonsterを定期的に送り込む
一人で敵の守備を破るにはshield,control,wind全てを使うのがよいと考えました。上で述べたshieldの使い方ですが、敵が複数Base前にいてWIND合戦だけではそもそも5000以内に入れさせてもらえない場合に有効でした。リプレイで見ると実装前に比べて攻撃的に見えました。
(Last battlesで対応しているセリフ:ATTACKERの「突破口にする」)
・DEFENDERの自己Shieldは敵が一度でもControlしてきた場合のみ
shieldはそれだけでマナを10消費するので無駄遣いしたくないところです。ただし、結局DEFENDERは一人なので、shieldを張ったとしても張りかえの時にcontrolされたりshield中にmonsterがWINDで飛び越えてきたりして簡単に負けてしまいます。
(Last battlesで対応しているセリフ:DEFENDERの「あの人、怖い」)
・敵3人を自分のbase周りで検知したらbase外からWINDでライフを削る戦法(いわゆるロマン砲)に警戒
ロマン砲に勘付いたら3人をコントロールし続け時間を稼ぎます。ただしLegend昇格のための雑対応なのでしっかりとしたロマン砲にはかないません。
(Last battlesで対応しているセリフ:ATTACKERの「時間ないぞ」「ごりおし」「ボールほしい」)
(Last battlesで対応しているセリフ:DEFENDERの「様子が変」)
・敵2人を自分のbase周りで検知したら攻撃モードに変更
こちらのDEFENDERは一人なので瞬殺されます。ここは一縷の希望に賭けてATACCKERが敵baseで1on1を申し込みます。
・monsterが多すぎる場合は森へ捨てる
敵がcontrolでmonsterを集めることで集中砲火を狙う場合、DEFENDER1人だけでは押し負けてしまいます。この場合は近くの森にWINDでポイ捨てすることにしています。monsterはマナの源でもあるので、辛くもないのにむやみやたらに捨てさせると逆に弱くなります。毎回できるような配置とは限らないので、これも特定の戦法に対する全敗回避策です。
(Last battlesで対応しているセリフ:DEFENDERの「捨てたい」)
・ATTACKERのWINDは確実に前に進めたい
攻撃をする際のWINDは、敵にとってmonsterを飛ばされるとまずい場合、ほぼ確実に逆向きのWINDによって打ち消されてしまいます。それとは違い、敵の位置の都合上相殺されない攻撃ができるだろう場合は少しWINDを緩い状況で放ちます。なお、たいていの場合その次のターンにWINDが返ってくるのでいうほど関係ないです。
(Last battlesで対応しているセリフ:ATTACKERの「フリーで打てそう」)
おわりに
ここまで読んでくださった方、ありがとうございます。いろいろと工夫してみたつもりでしたが、ほかの方々のPostmortemを見ると「あ、そんな考え方があるのか」と驚かされました。
CodinGameのbot programmingコンテストは比較的期間内の言及がしやすく、みんなで同時に取り組んでいる感が強いのでとても楽しめました。また次のコンテストが楽しみです。(次回いつなんだろう・・・)
おまけ: