はじめに

この記事は Ruby Advent Calendar 2017 21日目の記事です。
paizajackというゲームで1位を取るために、Rubyを使って自動化したので、そのことについて書いていこうと思います。

この自動化ツールは以下のGitHubページに公開していますので、気になる方は参照ください。
https://github.com/TomoProg/paizajack-automation

それでは頑張っていきましょう。

paizajackって何さ?

ITエンジニアのための転職サービスサイト Paiza が提供しているサービスでブラックジャックを対戦するコードを書いて億万長者を目指そうというゲームです。
https://paiza.jp/paizajack

BETする処理や山札からもう一枚引くといった処理を追加しながら運営側が用意したディーラーとブラックジャックで対戦していきます。

今回はpaizajackの説明ではなく、自動化が目的なので、もっと詳しく知りたい方は一度やってみるか、下記の記事をおすすめします。

一攫千金プログラミングpaizajackを遊んでみた

自動化 STEP 1 ~ 何を自動化すればいいか考える ~

ここから実際に自動化するにあたって取り組んだことをSTEPごとに書いていきます。

まずは手で動かしてみて、何を自動化すればいいかを考えてみます。
実際に手で動かしてみたところ、以下のことを自動化できればある程度自動化できそうだと考えました。
1. paizajackページへのアクセス
2. paizajackへのログイン
3. 対戦するディーラーの選択
4. エディタへの書き込み
5. コードの提出
6. 対戦のリトライボタンの押下

自動化 STEP 2 ~ 環境を整える ~

さて、それでは早速ログインからと言いたいところですが、まずは環境構築からです。
ここができないと何も進めない・・・

今回の環境構築はDockerを使って構築するようにしました。
Dockerさえ入っていれば同じ環境をすぐに再現できるというのは本当にありがたいです。

今回の自動化には以下の2つのimageを使います。
・ ruby:2.4.2
・ selenium/standalone-chrome-debug:3.8.1

このSeleniumimageについてはこちらを参考にさせていただきました。
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のトップページへアクセスすることができます。
スクリーンショット 2017-12-20 3.09.38.png

自動化 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&amp;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

クリックすると以下のようなログインダイアログが表示されます。
スクリーンショット 2017-12-20 3.57.05.png

メールアドレスパスワードを入力して、ログインするボタンを押せればログイン出来そうです。

まずは、メールアドレスパスワードを入力します。
メールアドレスパスワードの入力フォームは以下のような構成になっています。

<!-- メールアドレスの要素 -->
<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

スクリーンショット 2017-12-20 4.20.03.png

自動化 STEP 5 ~ 対戦するディーラーの選択 ~

さてさて、次はディーラーの選択です。
まずは、ディーラーの選択画面に移動します。

# ディーラー選択画面へ移動
driver.navigate.to "https://paiza.jp/paizajack/dealer_release_states/"

選択画面へ移動すると以下のようなページが現れます。
スクリーンショット 2017-12-20 4.24.01.png

ここからどうやってディーラーを選択するかですが、各ディーラーの選択ボタンの要素を確認してみます。

<!-- 猫先生(上の行の左から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>

<!-- ほか続く -->
・・・

どうやらliclassdealer_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

選択すると以下のような選択確認ダイアログが表示されます。
スクリーンショット 2017-12-20 4.47.14.png

ここで選択ボタンを押して、ディーラーの選択が完了です。

<div id="dialog_wrap">
  ・・・
  <!-- 選択ボタン -->
  <a class="float_left" onclick="$(&quot;#js-event-choose_dealer&quot;).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

対戦相手が決定すると、ようやくブラックジャックを行うコードを記述するページへ移動します。
スクリーンショット 2017-12-20 5.03.41.png

自動化 STEP 6 ~ エディタへの書き込み ~

ようやく、コードを記述するページまでやってきました。
ここまででコードにすればほんの数十行なんですが、文章にしていくと長いですね。

気を取り直して頑張っていきましょう。

まずは、このページでの処理ですが、
1. 使いたいプログラミング言語をセレクトボックスから選択する
2. 提出するコードをエディタに入力する
3. プログラムを実行する
この手順で進めていきたいと思います。

まずは、セレクトボックスから使うプログラミング言語を選択します。
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メソッドに書いた通りのコードにならないようです・・・。
いつもは大活躍の自動インデントとオートコンプリートにここで悩まされることになるとは・・・。

しかたがないので、別の方法を試みます。
コピペできればいいんだよなということで、以下の方法でコードを入力することにしました。
1. 一度どこか適当なWebページにコードを全て入力し、コピーする
2. 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 ~ リトライボタンを押して何回も対戦できるようにする ~

コードの提出が終わると以下のような対戦画面に入ります。
スクリーンショット 2017-12-21 2.07.53.png

さらに対戦が終わると以下のような対戦結果画面が表示されます。
スクリーンショット 2017-12-21 2.09.56.png

あとはこの対戦結果画面の右上にあるリトライボタンを押したら自動化完了です。

# リトライボタンを押す
driver.find_element(:css, "#page_game_btn .page_game_retry_btn a").click

これ以降は、リトライボタンを押したあとは前回対戦したときのコードが残ったままプログラムの提出画面(自動化STEP6の時点)に戻るので、自動化STEP6と自動化STEP7を延々と繰り返していきます。

これで自動化が完了です。

実際に動かしてみた

そして・・・

道程は長かったですが、この自動化のおかげでついに・・・
スクリーンショット 2017-12-21 2.18.10.png
1位を取ることができました!!!

まとめ

今回初めてブラウザ操作の自動化ということをやってみました。
自分が書いた通りにブラウザが自動に動くのは見ていて気持ちいいですね。
Rubyから扱うのもかなり簡単だったので、Rubyでブラウザ操作の自動化を考えている方はぜひこの組み合わせを試してみてください。

今回の自動化ツールはGitHubに載せているので、使ってみたい方はどうぞ。
https://github.com/TomoProg/paizajack-automation

1位とったどーーーー!!!

それではまた。
TomoProg

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.