Python
python3

伝説のゲーム「2048」をPythonで自作してAIに解かせる(1) ~Webページ上の「2048」をプログラミングで操ってみる~

伝説のゲーム「2048」をPythonで自作し、自作のAIに解かせます。
これ自体はありふれたテーマで、ググれば色々出てくるのですが、僕みたいな初心者向けに書かれた詳しい記事は見つからなかったので、敢えて書いてみることにしました。

この記事では手始めに、(自作ではなく)Webページ上の「2048」をプログラミングで操作してみます。
AIが「2048」を解いている様子を今すぐご覧になりたい方は、以下の僕のツイートをどうぞ。
https://twitter.com/masa_ramen/status/962957952683450368

環境

  • MacBook Pro (15-inch, 2016)
  • OS: macOS High Sierra (10.13.5)
  • Python 3.6.4
$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.13.5
BuildVersion:   17F77
$ python --version
Python 3.6.4 :: Anaconda custom (64-bit)

「2048」とは?

「2048」は最高に面白いパズルゲームです。
2014年に公開されましたが、当時高校生だった僕の周囲では結構流行ってました。
もしご存知なければ、実際にプレイしてみるのが早いと思います。
以下のページで遊ぶことができます[1](iPhone6とMacBook Proで動作を確認しました)。
https://gabrielecirulli.github.io/2048/
僕みたいに2の累乗が好きな人はハマると思います。

「2048」をプログラミングで操ってみよう

「2048」は、「上」「右」「下」「左」の4方向のどれかを受け取り、タイルの位置と得点を出力するシステムと捉えることができます。
そこで、方向を送信するプログラムを作りたくなります。
例えばfor文で「上」「右」「下」「左」を100回送信すれば、

「上」「右」「下」「左」「上」「右」「下」「左」「上」「右」「下」「左」「上」「右」「下」「左」「上」「右」「下」「左」「上」「右」「下」「左」「上」「右」「下」「左」「上」「右」「下」「左」「上」「右」「下」「左」「上」「右」「下」「左」・・・「上」「右」「下」「左」

という操作を一瞬で実行させることができます。(僕ら人間が素手でやったら指が疲れちゃいますね)
知性のカケラもありませんが、ある程度の得点は期待できるでしょう。
というわけで、まずはこれをやってみます。

Seleniumを使う準備

先ほどご紹介したWeb上の「2048」を遊ぶためには、矢印キーを押す必要がありました。
ここではプログラムに矢印キーを「押させる」ために、Seleniumというライブラリを使用します。
SeleniumはWebブラウザの起動から制御までしてくれる素敵なライブラリです。
terminalで以下を実行して、Seleniumをダウンロードします。

$ pip install selenium

ただしSeleniumがサポートしているブラウザは限られていて、ここではFirefoxを使用します。Firefoxをお持ちでない方は、ご面倒でしょうがダウンロードをお願いします🙏
https://www.mozilla.org/ja/firefox/new/

さて、FirefoxをSeleniumから制御するためには、geckodriverというドライバーが必要です。
どうやら使用するブラウザごとにそれぞれ別のドライバーが必要なようで、例えばChromeを使う場合はchromedriverというドライバーが必要みたいです。(僕もよくわかってません)

自分のOSに合ったgeckodriverを以下からダウンロードします。
https://github.com/mozilla/geckodriver/releases
この時、geckodriverがどこに保存されたかを覚えておいてください。(自分の分かりやすい場所に移動した方がいいかも?)

お疲れ様でした。
以上で、Seleniumを使う準備が整いました。

Seleniumで「2048」を起動し、操る

いよいよ、パソコンに「2048」を遊ばせる時がやってきました。
コードを以下のselenium2048.pyに示します。(参考文献[2]の11章を参考にして書きました。)
browser = webdriver.Firefox(executable_path="")の「" "」に、geckodriverの場所を書いてから実行してください。
例えば、hogeさんが/Users/hoge/geckodriverというフォルダを作り、そこにgeckodriverを保存した場合は、
"/Users/hoge/geckodriver/geckodriver"
と書いてください。

selenium2048.py
# seleniumのインポート
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

# Firefoxの起動と「2048」への移動
browser = webdriver.Firefox(executable_path="/Users/hoge/geckodriver/geckodriver")# ここにgeckodriverの場所を書いてください
browser.get('https://gabrielecirulli.github.io/2048/')#「2048」のページのURL

# 「上右下左」をn回送信する関数
def send_allows(n):
    for i in range(n):
        html_elem.send_keys(Keys.UP) # 「上」を送信
        html_elem.send_keys(Keys.RIGHT) # 「右」を送信
        html_elem.send_keys(Keys.DOWN) # 「下」を送信
        html_elem.send_keys(Keys.LEFT) # 「左」を送信

# 「上右下左」を100回送信
html_elem = browser.find_element_by_tag_name('html')
send_allows(100)

send_allows(n)関数を変更するだけで、自由自在に「2048」をプレイできます。
さらに別の関数(上上上上・・・とか)を作って順番に実行してみると、より複雑なプレイスタイルが構築できます。
「ぼくのかんがえたさいきょうの関数」を作ってみんなで対戦しましょう!

そしてAIへ・・・

先ほどのselenium2048.pyではそこそこの得点は取れるものの、僕ら人間様の足元にも及ばない成績しか取れなかったと思います。
では、どうして僕ら人間様は高得点が取れるのでしょうか?
具体的な戦略やテクニック(右下に集めると高得点が取れるよ、とか)についてはググれば無数に出てくるわけですが、それ以前に人間様とパソコンの間には大いなる隔たりがあります。

人間様は
①盤面を見て、
②それぞれの手を実行した結果を計算(想像)して、
③一番良さそうな手を選ぶ
ということをしれっとやっています。さすがですね。

一方のパソコンは
①盤面は見えないし、(「目隠し状態」)
②当然先も読めないし、
③やたらめったら矢印を叩きまくるだけ
というキチガイ仕様です。クソ。

そこで、当面の目標は

盤面を認識して、
ある方向を送信した時の結果(=「1手先」の盤面)を計算して、
それがどれくらい「良い」かを評価し、一番「良い手」を選び続ける
プログラム

の作成です。

今回はここまで!
次回の記事をお楽しみに!

おまけ

実は今回がQiita初投稿だったのですが、今後の方針を迷い中です。
「2048」自体は手垢まみれのありふれたテーマだと思うのですが、色々と面白いことができるんじゃないかと考えています。
以下にいくつかアイデアを挙げるので、どのテーマが一番興味あるか、Twitter( https://twitter.com/masa_ramen )かQiitaのコメントで要望を送っていただけると参考になります。

「パソコンが盤面を見ていない」と本当にダメなのか?
僕ら人間は盤面を見て状況を「判断」しますが、先ほどのプログラムでは目隠し状態でプレイしてました。
僕らも目隠し状態でプレイすれば、パソコンと同じ得点になるはずですね。
でも、「目隠しには目隠しなりに戦略があるんじゃないか?」とか気になりませんか。
以下のツイートをご覧ください。
https://twitter.com/masa_ramen/status/963682627545952257
これは、目隠し状態のまま「遺伝的アルゴリズム」で学習させてみました、という結果です。
遺伝的アルゴリズムを用いた「2048」の学習について、要望があれば別の記事で書きたいと思います。

成績評価の方法はどうすれば良い?
「2048」は確率の宝庫です。
どこに「2」のタイルが出現するのかはランダムですから、当然ゲームの得点は確率的なものになるはずです。
つまり、「運がいい」時は得点が高いけど、「運がわるい」時は得点が低いのです。
よって、1回プログラムを実行して100点だったとしても、次の実行では100000点になるかもしれません。
どうやってプログラムの「良さ」を評価すれば良いでしょうか?

僕は大学で物理を学んでいて、趣味で確率モデルの研究をしている変態です。
今回のプログラムが出力する得点はある種の確率変数とみることができるはずで、平均や分散等を調べることでより正しくモデルを「評価」したくなります。
これに関しても、要望があれば別記事で書きたいと思っています。

そんなことはいいから早く「2048」を自作してAIに解かせろよ!
はい、頑張ります。

参考文献

[1] 2048 (2048が実際に遊べます。iPhone6とMacBook Proで動作確認しました。2018/8/9にアクセスを確認しました。)
https://gabrielecirulli.github.io/2048/

[2] Al Sweigart 著、相川 愛三 訳(2017) 『退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング』 オライリージャパン