はじめに
この記事は Ruby Advent Calendar 2017 21日目の記事です。
paizajackというゲームで1位を取るために、Rubyを使って自動化したので、そのことについて書いていこうと思います。
この自動化ツールは以下のGitHubページに公開していますので、気になる方は参照ください。
https://github.com/TomoProg/paizajack-automation
それでは頑張っていきましょう。
paizajackって何さ?
ITエンジニアのための転職サービスサイト Paiza が提供しているサービスでブラックジャックを対戦するコードを書いて億万長者を目指そうというゲームです。
https://paiza.jp/paizajack
BETする処理や山札からもう一枚引くといった処理を追加しながら運営側が用意したディーラーとブラックジャックで対戦していきます。
今回はpaizajackの説明ではなく、自動化が目的なので、もっと詳しく知りたい方は一度やってみるか、下記の記事をおすすめします。
自動化 STEP 1 ~ 何を自動化すればいいか考える ~
ここから実際に自動化するにあたって取り組んだことをSTEPごとに書いていきます。
まずは手で動かしてみて、何を自動化すればいいかを考えてみます。
実際に手で動かしてみたところ、以下のことを自動化できればある程度自動化できそうだと考えました。
- paizajackページへのアクセス
- paizajackへのログイン
- 対戦するディーラーの選択
- エディタへの書き込み
- コードの提出
- 対戦のリトライボタンの押下
自動化 STEP 2 ~ 環境を整える ~
さて、それでは早速ログインからと言いたいところですが、まずは環境構築からです。
ここができないと何も進めない・・・
今回の環境構築はDocker
を使って構築するようにしました。
Docker
さえ入っていれば同じ環境をすぐに再現できるというのは本当にありがたいです。
今回の自動化には以下の2つのimage
を使います。
・ ruby:2.4.2
・ selenium/standalone-chrome-debug:3.8.1
このSelenium
のimage
についてはこちらを参考にさせていただきました。
docker-selenium 使って見た
あとはこの2つのimageから作られるコンテナを同時に制御したいので、そこはdocker-compose
を使って制御するようにしました。
これでRuby
が動く環境とSelenium
が動く環境を準備できました。
あとはRuby
からSelenium
を使うためにselenium-webdriver
というgemをインストールしておきます。
gem install selenium-webdriver
かなり省略していますが、これでひと通り、環境構築は完了です。
自動化 STEP 3 ~ paizajackページへのアクセス ~
環境も整ったことなので、ここから実際にWebブラウザの操作を自動で行っていきます。
まずは、操作するWebブラウザを選択しておきます。
今回はブラウザにchrome
を使うので、:chrome
を指定しました。
また、Selenium
のコンテナへ接続しないといけないので、url
オプションにSelenium
コンテナのURLを指定しておきます。
(どうやらSeleniumサーバが起動するデフォルトポートが4444番のようなので、4444番を指定しています。)
driver = Selenium::WebDriver.for(:chrome, url: "http://selenium_container:4444/wd/hub")
どのブラウザを使うか決まったので、paizajackページへのアクセスを行ってみます。
URLアクセスとか小難しそうだなと思いますが、実はかなり簡単で、以下の一行を記述するだけです。
driver.navigate.to "https://paiza.jp/paizajack/"
この一行でpaizajackのトップページへアクセスすることができます。
自動化 STEP 4 ~ paizajackへのログイン ~
次はログインです。
まずはpaiza会員の方はこちら
のボタンを押して、ログイン用のダイアログを表示させます。
このボタンの構成ですが以下のような構成になっています。
<a class="colorbox_fit_width disable_overlay_click disable_esc_key lp_top_btn2 cboxElement" id="lp_login_btn" href="/user_sessions/new_cbox?ref=%2Fpaizajack%2Fmypage&sid=paizajack" style="display: inline-block; width: 234px; height: 50px;"><img src="https://paiza.jp/paizajack/images/lp/lp_contents1_top_btn2_on.png" style="position: absolute; opacity: 0;"><img alt="paiza会員の方はこちら" src="https://paiza.jp/paizajack/images/lp/lp_contents1_top_btn2_off.png" width="234" height="50">
<!-- マウスカーソルが乗った時の画像 -->
<img src="https://paiza.jp/paizajack/images/lp/lp_contents1_top_btn2_on.png" style="position: absolute; opacity: 0;">
<!-- マウスカーソルが乗っていない時の画像 -->
<img alt="paiza会員の方はこちら" src="https://paiza.jp/paizajack/images/lp/lp_contents1_top_btn2_off.png" width="234" height="50">
</a>
a
タグと画像でボタンのように見せているようなので、a
タグのリンクをクリックできればよさそうです。
このa
タグはlp_login_btn
というid
を持っているので、以下のような記述で要素を特定し、クリックすることが出来ます。
driver.find_element(:id, 'lp_login_btn').click
クリックすると以下のようなログインダイアログが表示されます。
メールアドレス
とパスワード
を入力して、ログインする
ボタンを押せればログイン出来そうです。
まずは、メールアドレス
とパスワード
を入力します。
メールアドレス
、パスワード
の入力フォームは以下のような構成になっています。
<!-- メールアドレスの要素 -->
<input class="form-control" type="text" name="user[email]" id="user_email">
<!-- パスワードの要素 -->
<input class="form-control" type="password" name="user[password]" id="user_password">
キーの入力は特定した要素に対してsend_key
メソッドを呼び出すことで実現できます。
# メールアドレス
driver.find_element(:name, 'user[email]').send_key('入力したいメールアドレス')
# パスワード
driver.find_element(:name, 'user[password]').send_key('入力したいパスワード')
あとは、ログインボタンをクリックしてあげれば無事ログイン完了です。
<!-- ログインボタン -->
<input type="submit" name="commit" value="ログインする" class="btn btn-primary btn-block btn_login">
# ログインするボタンをクリック
driver.find_element(:name, 'commit').click
自動化 STEP 5 ~ 対戦するディーラーの選択 ~
さてさて、次はディーラーの選択です。
まずは、ディーラーの選択画面に移動します。
# ディーラー選択画面へ移動
driver.navigate.to "https://paiza.jp/paizajack/dealer_release_states/"
ここからどうやってディーラーを選択するかですが、各ディーラーの選択ボタンの要素を確認してみます。
<!-- 猫先生(上の行の左から1番目)の要素 -->
<li class="dealer_select_list dealer_select_list1 js-tooltip" data-tooltip-target="#release_condition1">
<p class="dealer_status_magnification">0.0</p>
<p class="dealer_status_game">1</p>
<p class="dealer_status_bet" style="font-size: 90%;">0</p>
<p class="dealer_status_btn">
<a class="js-load-dealer-select-dialog" data-uri="/paizajack/dealer_release_states/15194/dialog_dealer_select" href="javascript:void(0);" style="display: inline-block; width: 137px; height: 29px;"><img src="https://paiza.jp/paizajack/images/common/choice_btn_on.png" style="position: absolute; opacity: 0;"><img src="https://paiza.jp/paizajack/images/common/choice_btn_off.png" alt="Choice btn off" width="137" height="29"></a>
</p>
</li>
<!-- 霧島 京子(上の行の左から2番目)の要素 -->
<li class="dealer_select_list dealer_select_list2 js-tooltip" data-tooltip-target="#release_condition2">
<p class="dealer_status_magnification">2.0</p>
<p class="dealer_status_game">1</p>
<p class="dealer_status_bet" style="font-size: 90%;">50</p>
<p class="dealer_status_btn">
<a class="js-load-dealer-select-dialog" data-uri="/paizajack/dealer_release_states/15191/dialog_dealer_select" href="javascript:void(0);"><img src="https://paiza.jp/paizajack/images/common/choosing_btn.png" alt="Choosing btn" width="137" height="29"></a>
</p>
</li>
<!-- ほか続く -->
・・・
どうやらli
のclass
にdealer_select_list + 連番
という規則があるようです。
この規則を利用し、ディーラーを選択してみます。
ディーラーの名前を渡すと、この連番を返す以下のようなメソッドを定義します。
# ディーラーの番号
# dealer_name: ディーラーの名前
def dealer_no(dealer_name)
case dealer_name
when 'kirishima' then 2 # 霧島 京子
when 'midorikawa' then 3 # 緑川 つばめ
when 'rokumura' then 4 # 六村 リオ
when 'kirishima_bunny' then 5 # 霧島 京子 バニー
when 'midorikawa_bunny' then 6 # 緑川 つばめ バニー
# ディーラーが増えたら追加していく
else 1 # 猫先生
end
end
あとは今までと同様に選択ボタンを押下します。
(find_elementはCSSセレクタの書き方もできるので、かなり便利です。)
# 指定されたディーラーの選択ボタンを押す
driver.find_element(:css, ".dealer_select_list#{dealer_no(dealer_name)} .js-load-dealer-select-dialog").click
ここで選択ボタンを押して、ディーラーの選択が完了です。
<div id="dialog_wrap">
・・・
<!-- 選択ボタン -->
<a class="float_left" onclick="$("#js-event-choose_dealer").submit();" href="javascript:void(0);"><img alt="選択" src="https://paiza.jp/paizajack/images/common/choice_btn_off.png" width="137" height="29"></a>
<!-- 閉じるボタン -->
<a class="js-event-close float_right" href="javascript:void(0);"><img alt="閉じる" src="https://paiza.jp/paizajack/images/common/close_btn_off.png" width="137" height="29"></a>
・・・
</div>
# 対戦相手決定
driver.find_element(:css, "#dialog_wrap .float_left").click
対戦相手が決定すると、ようやくブラックジャックを行うコードを記述するページへ移動します。
自動化 STEP 6 ~ エディタへの書き込み ~
ようやく、コードを記述するページまでやってきました。
ここまででコードにすればほんの数十行なんですが、文章にしていくと長いですね。
気を取り直して頑張っていきましょう。
まずは、このページでの処理ですが、
- 使いたいプログラミング言語をセレクトボックスから選択する
- 提出するコードをエディタに入力する
- プログラムを実行する
この手順で進めていきたいと思います。
まずは、セレクトボックスから使うプログラミング言語を選択します。
selectタグからSelenium::WebDriver::Support::Select
クラスを作成し、そのクラスに対して指定したいプログラミング言語を渡してあげれば、セレクトボックスの選択は完了です。
# selectタグからSelenium::WebDriver::Support::Selectを作成
select = Selenium::WebDriver::Support::Select.new(driver.find_element(:id, 'js-language_selectbox'))
# 選択したいテキストに合わせる
select.select_by(:text, 'Ruby')
次にエディタへの入力ですが、これが一番厄介でした。
以下のように、send_key
で提出するコードをエディタに入力していくと、なぜかうまく入力できないという現象に陥りました。
# 入力するコードを返す
def jackpot_code
<<~CODE
# ここにコードを記述
CODE
end
# send_keyでコードをエディタに入力
driver.find_element(:class, "ace_text-input").send_key(jackpot_code)
原因を探ってみると、どうやらエディタの自動インデントとオートコンプリートの機能が邪魔をして、jackpot_code
メソッドに書いた通りのコードにならないようです・・・。
いつもは大活躍の自動インデントとオートコンプリートにここで悩まされることになるとは・・・。
しかたがないので、別の方法を試みます。
コピペできればいいんだよなということで、以下の方法でコードを入力することにしました。
- 一度どこか適当なWebページにコードを全て入力し、コピーする
- paizajackのエディタに貼り付ける
# メモ帳サイト Onlinememoに一度すべてのコードを貼り付け
driver.navigate.to "http://onlinememo.net/users/login"
driver.find_element(:id, 'content').send_key(jackpot_code)
# Ctrl + a、Ctrl + cで入力したコードをコピー
driver.find_element(:id, 'content').send_key([:control, 'a'], [:control, 'c'])
# ログイン処理や対戦相手の選択などの処理を実行・・・
# コードを貼り付け
driver.find_element(:class, "ace_text-input").send_keys([:control, 'a'], [:control, 'v'])
これでなんとかコードをエディタに反映させることができました。
あとは、プログラムを実行
ボタンを押すだけです。
今回のボタンは押した後にJSのアラートダイアログも表示されるため、そのダイアログも処理しないといけません。
アラートダイアログに対しては以下のように記述することで、簡単にOKを押すことができます。
# プログラムを実行ボタンを押す
driver.find_element(:css, ".program_execution_btn a").click
# JSのアラートダイアログに対してOKを押す
driver.switch_to.alert.accept
これで、コードの提出が完了です。
自動化 STEP 7 ~ リトライボタンを押して何回も対戦できるようにする ~
さらに対戦が終わると以下のような対戦結果画面が表示されます。
あとはこの対戦結果画面の右上にあるリトライボタンを押したら自動化完了です。
# リトライボタンを押す
driver.find_element(:css, "#page_game_btn .page_game_retry_btn a").click
これ以降は、リトライボタンを押したあとは前回対戦したときのコードが残ったままプログラムの提出画面(自動化STEP6の時点)に戻るので、自動化STEP6と自動化STEP7を延々と繰り返していきます。
これで自動化が完了です。
実際に動かしてみた
一攫千金プログラミング 〜ポットdeジャックポット〜を自動化してみた: https://t.co/lCcooUUIrs via @YouTube
— TomoProg (@TomoHelloxxx) 2017年12月20日
そして・・・
道程は長かったですが、この自動化のおかげでついに・・・
1位を取ることができました!!!
まとめ
今回初めてブラウザ操作の自動化ということをやってみました。
自分が書いた通りにブラウザが自動に動くのは見ていて気持ちいいですね。
Rubyから扱うのもかなり簡単だったので、Rubyでブラウザ操作の自動化を考えている方はぜひこの組み合わせを試してみてください。
今回の自動化ツールはGitHubに載せているので、使ってみたい方はどうぞ。
https://github.com/TomoProg/paizajack-automation
1位とったどーーーー!!!
それではまた。
TomoProg