feature image

2020年3月13日 | ブログ記事

SECCON CTF 2019 International 問題伍(5) writeup

15B / 19M の @nari です。

2019年12月21日(土) と 2019年12月22日(日) に開催された SECCON CTF 2019 International に、traP からチーム NaruseJunとして出て 優勝 しました 🎉

この記事は、SECCON CTF 2019 International の問題伍(5)の writeup です。

参加記は こちら です。

この記事の内容

SECCON 2019 International の伍の writeup として、出題された問題と実際に使った解法を紹介します。

また、解法に使用したモンテカルロ木探索 (Monte Carlo Tree Search; MCTS) の解説にも取り組んでいます。

問題概要

いくつかの Python ファイル[1]と、サーバの IP アドレスとユーザ名とパスワードが与えられます。
各チームが用意されたサーバ内でゲーム AI サーバを起動すると、運営のシステムがその AI サーバにアクセスして参加チーム間で対戦が行われます。
対戦によって Elo Rating 値[2]が変動し、5 分ごとにレートが一番高かったチームに 20pts が加算されます。

図で書くと次のようになっています。

システムの図

client.py は内部で snake.py で定義されるゲームを、各サーバの server.py に接続して実行される AI を元に動かしていきます。
各チームはこの server.py 以下をいじることで AI を変更することが出来ます。
デフォルトでは Python プログラムとして AI が書かれており、それを server.py から呼び出すことで AI が実装されています。

ゲーム概要

ゲームの図

ゲームは二人対戦版スネークゲームと形容できるもので、おおよそ次のようなルールです。

いくつかパラメータ (盤面の幅、高さ、林檎の数、ターン数) が不定ですが、これらに制約があったかどうかは読み取れませんでした。基本的にはこれらは可変としてプログラムを書いたほうが良かったのだと思われます。

AI 概要

詳細は割愛しますが、snake.py の 155 行目と 156 行目に次のような記述があります。

_input = self.players[k]["module"].main(
	{ "map": self.draw_map(False), "snakes": snakes, "turn": my_turn })

ここで main に与えられている情報が、AI プログラムに渡されている情報です。
map は現在の盤面、snakes は各プレイヤーの蛇の形、turn はプレイヤーの残りターン数です。
なお、サンプルの AI プログラムでは turn の値は利用されていません。これは snake.py を読まないとわからない情報なので、ちゃんと読んだかどうかで使える情報に差が出たものと思われます。
また、プレイヤーの残りターン数はわかりますが、どちらが先行かという情報はわかりません。これは多分どうしようもないと思うので、AI プログラム側で工夫が必要になるものと考えられます。

AI プログラムはこれらの情報から、次にどの方向に蛇を動かすかを、上下右左を 0123 に対応させて、整数 1 つを返す関数として実装します。

なお、client.py から server.py への接続部分で、タイムアウトまでの時間が 1.5 秒に設定されているため、AI プログラムが使える計算時間もおよそ 1.5 秒となります。

モンテカルロ木探索

私が最終的に実装した AI アルゴリズムはモンテカルロ木探索 (Monte Carlo Tree Search; MCTS) と呼ばれるもので、二人対戦ゲームにおいて 盤面の評価関数無しに 強力な AI を構成出来るものです。有名なものとして囲碁の AI である Alpha Go などが MCTS をベースとしています。

もちろん、今回のような規模が小さめのゲームにおいては、盤面の評価関数を設計できればより強い AI を作れると思いますが、たった 2 日未満で実質 1 人では調べられることが少ないため、ドメイン知識が少なくてもなんとかなる MCTS に頼ることにしました。

MCTS は簡単に言えば動的なゲーム木探索で、有望な選択肢は深く探索を行うことで、その選択肢が本当に価値のあるものなのかを探る、ということを何度も繰り返すアルゴリズムです。ただし一番有望なものだけを調べると、一回も調べてない選択肢を調べる動機が無くなってしまうので、あまり調べてない選択肢も調べるように適切に仕向けます。また「有望さ」はその選択肢を取った時の勝率なのですが、これを評価するためにゲームをランダムに最後までプレイすることで擬似的な勝率を記録します(プレイアウト)。

選択方針の図

例えば上図のように選択肢が A/B/C の 3 つあり、探索度と勝率がそれぞれ記録されていたとします。この時、選択肢 A/B の二者択一ならば A を選んだほうが良いため、A の方を探索します。一方で、選択肢 A/C の比較は単純には出来ません。というのは選択肢 C は探索度が低く、潜在的に高い勝率を持っているかもしれない からです。

ノードの展開

また、各ノードについて一定の回数探索を行った場合、上図のようにそのノードを展開します。こうすることで 良い選択肢についてはより詳細に勝率を調べることが出来、正確な勝率推定に近づきます。

なお、ノードが無くなった先の勝率推定は、ランダムなプレイアウト で行います。これには完全にランダムにゲームをプレイする方法や、ある程度賢いヒューリスティックを実装する方法などがあります。

モンテカルロ木探索自体は比較的古くからある手法のため、より詳細な情報や実装方法については Wikipedia や Google 検索、適切な書籍が参考になると思います。

モンテカルロ木探索 - Wikipedia

今回のゲームへの適用

今回のゲームは二人対戦で、ターン数が決まっていて最後までプレイすれば勝敗もわかるので、MCTS を適用出来る条件は整っています。……と言いたいところですが、新しい林檎が発生するマスの選択に乱数が関わっています。これは不確定ゲームと呼ばれるジャンルになり、扱いが非常に面倒です。

これを解決する手法の一つに、本来の双方のプレイヤーの選択以外に、林檎がどこに発生するかを(ランダムに)定める選択肢が仮想的に存在する、とする方法があります。これは「自然手番 (Move by nature - Wikipedia)」と呼ばれる考え方で、乱数の関わる不確定ゲームを扱いやすい確定ゲームへと変換します。MCTS ならば適切に自然手番を挟み、自然手番では等確率で林檎が発生するマスが選ばれるとすれば表現できると思います。

上述の問題点として、自然手番で分岐する分メモリ効率が悪くなる上、学習すべきノード数が増えてしまうというものがあります。例えば林檎の発生する可能性のあるマスが 20 マスあるとすると、ゲーム木が 20 分岐し、しかもそれぞれが完全にランダムに選ばれて探索されるため、そのノードの学習に 20 倍のメモリ/探索回数が必要となります。

今回はこの問題を回避するため(または実装する気力が無かったため)、自然手番は排し、MCTS の 1 プレイ(探索)ごとに林檎の発生の仕方がランダムになる ような実装を行いました。つまり、プレイごとに林檎の配置が変わり、それに伴い蛇の長さも変わるので、プレイごとに取れる選択肢が変わる可能性があり、表現の正確性では劣ります。

またプレイアウトについては、より正確な確率評価のため、完全にランダムなプレイではなく、自滅しにくいヒューリスティックを導入したランダムなプレイを行っています。

実際にサーバに置いた AI の実装は以下の gist のとおりです。C++ 言語で実装しており、これを python から呼び出すことで実行していました。競技プログラミングをする際の手癖が大量で非常に読みづらいですが参考程度にお読みください。

https://gist.github.com/n-ari/7771d6450d1098d18e9e91ee77895767

コンテストでの結果

実際に問題伍で獲得したポイントの推移は以下の写真のとおりです。1 日目の深夜に上述 AI を実装し、2 日目開始後にサーバに置いたため、2 日目の開始 20 分頃から ポイントを独占 出来ていることが分かります。

ディフェンスポイント推移

また、2 日目の AI のレーティング値の推移は以下の図のとおりです。赤いラインが私の AI のレーティングです。レーティングのログを自動で取るようにしていなかったため中盤は曲線が粗いですが、レーティングが安定してからは 2000~2150 ぐらいのレートを取り続け、2 位の AI と 100~300 ほどのレーティング差をキープ していました。

レート推移

まとめ

MCTS をマスターして最強のゲーム AI エンジニアになろう!

ところで、不確定ゲームや不完全情報ゲームでは不確定な要素が入るため、やはりそのままでは MCTS の適用が難しいという話があります(例: 麻雀 AI)。この辺りは今後調べられたら調べたいですね。


  1. これらのファイル群を公開して良いのか判断が付かなかったため、この記事では配布ファイルについて詳細に説明しません。 ↩︎

  2. 参考: イロレーティング - Wikipedia ↩︎

nari icon
この記事を書いた人
nari

競プロ(C++) / CTF(Crypto/PPC) / ゲーム作成(C++/Java/JavaScript) 参加プロジェクト: Traps of Typing(プログラマ) / Polar Snow Fantasy(プログラマ)

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2017年11月14日
IBIS2017参加報告
Keijan icon Keijan
2019年4月22日
アセンブリを読んでみよう【新歓ブログリレー2019 45日目】
eiya icon eiya
2018年12月23日
線形解読法
nari icon nari
2020年3月13日
CTFを始めよう【新歓ブログリレー2020 5日目】
nari icon nari
2020年3月13日
SECCON CTF 2019 International 参加記
nari icon nari
U22プログラミングコンテストに参加しました feature image
2018年10月31日
U22プログラミングコンテストに参加しました
uynet icon uynet
記事一覧 タグ一覧 Google アナリティクスについて