一攫千金プログラミングpaizajackを自動化して１位を目指す

# はじめに
この記事は [Ruby Advent Calendar 2017](https://qiita.com/advent-calendar/2017/ruby) 21日目の記事です。
paizajackというゲームで１位を取るために、Rubyを使って自動化したので、そのことについて書いていこうと思います。

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

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

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

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

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

[一攫千金プログラミングpaizajackを遊んでみた](https://qiita.com/kithz39/items/5550246c40ac5cab170c)

# 自動化 STEP 1 ~ 何を自動化すればいいか考える ~
ここから実際に自動化するにあたって取り組んだことをSTEPごとに書いていきます。

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

# 自動化 STEP 2 ~ 環境を整える ~
さて、それでは早速ログインからと言いたいところですが、まずは環境構築からです。
ここができないと何も進めない・・・

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

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

この`Selenium`の`image`についてはこちらを参考にさせていただきました。
[docker-selenium 使って見た](https://qiita.com/yutaChaos/items/4a1da5d55a3bf0df889e)

あとはこの2つのimageから作られるコンテナを同時に制御したいので、そこは`docker-compose`を使って制御するようにしました。

これで`Ruby`が動く環境と`Selenium`が動く環境を準備できました。

あとは`Ruby`から`Selenium`を使うために`selenium-webdriver`というgemをインストールしておきます。

```ruby
gem install selenium-webdriver
```
かなり省略していますが、これでひと通り、環境構築は完了です。


# 自動化 STEP 3 ~ paizajackページへのアクセス ~
環境も整ったことなので、ここから実際にWebブラウザの操作を自動で行っていきます。

まずは、操作するWebブラウザを選択しておきます。
今回はブラウザに`chrome`を使うので、`:chrome`を指定しました。
また、`Selenium`のコンテナへ接続しないといけないので、`url`オプションに`Selenium`コンテナのURLを指定しておきます。
（どうやらSeleniumサーバが起動するデフォルトポートが4444番のようなので、4444番を指定しています。）

```ruby
driver = Selenium::WebDriver.for(:chrome, url: "http://selenium_container:4444/wd/hub")
```

どのブラウザを使うか決まったので、paizajackページへのアクセスを行ってみます。
URLアクセスとか小難しそうだなと思いますが、実はかなり簡単で、以下の一行を記述するだけです。

```ruby
driver.navigate.to "https://paiza.jp/paizajack/"
```

この一行でpaizajackのトップページへアクセスすることができます。
<img width="1241" alt="スクリーンショット 2017-12-20 3.09.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/68663/d78379e2-af36-4845-c597-7230368700ee.png">

# 自動化 STEP 4 ~ paizajackへのログイン ~
次はログインです。
まずは`paiza会員の方はこちら`のボタンを押して、ログイン用のダイアログを表示させます。
このボタンの構成ですが以下のような構成になっています。

```html
<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`を持っているので、以下のような記述で要素を特定し、クリックすることが出来ます。

```ruby
driver.find_element(:id, 'lp_login_btn').click
```

クリックすると以下のようなログインダイアログが表示されます。
<img width="1242" alt="スクリーンショット 2017-12-20 3.57.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/68663/566894cb-cc86-8e14-fbfb-39c89b341440.png">

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

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

```html
<!-- メールアドレスの要素 -->
<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`メソッドを呼び出すことで実現できます。

```ruby
# メールアドレス
driver.find_element(:name, 'user[email]').send_key('入力したいメールアドレス')

# パスワード
driver.find_element(:name, 'user[password]').send_key('入力したいパスワード')
```

あとは、ログインボタンをクリックしてあげれば無事ログイン完了です。

```html
<!-- ログインボタン -->
<input type="submit" name="commit" value="ログインする" class="btn btn-primary btn-block btn_login">
```

```ruby
# ログインするボタンをクリック
driver.find_element(:name, 'commit').click
```

<img width="1245" alt="スクリーンショット 2017-12-20 4.20.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/68663/44b22a76-8f3f-44b0-e84b-b9fd6f7e8351.png">


# 自動化 STEP 5 ~ 対戦するディーラーの選択 ~
さてさて、次はディーラーの選択です。
まずは、ディーラーの選択画面に移動します。

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

選択画面へ移動すると以下のようなページが現れます。
<img width="1240" alt="スクリーンショット 2017-12-20 4.24.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/68663/dcb2598f-2306-744f-054e-3e2876c0ebf6.png">

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

```html
<!-- 猫先生（上の行の左から１番目）の要素 -->
<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 + 連番`という規則があるようです。
この規則を利用し、ディーラーを選択してみます。

ディーラーの名前を渡すと、この連番を返す以下のようなメソッドを定義します。

```ruby
# ディーラーの番号
# 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セレクタの書き方もできるので、かなり便利です。）

```ruby
# 指定されたディーラーの選択ボタンを押す
driver.find_element(:css, ".dealer_select_list#{dealer_no(dealer_name)} .js-load-dealer-select-dialog").click
```

選択すると以下のような選択確認ダイアログが表示されます。
<img width="1242" alt="スクリーンショット 2017-12-20 4.47.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/68663/cb246f75-2bb6-f205-98c5-d72360fc7de3.png">

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

```html
<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>
```

```ruby
# 対戦相手決定
driver.find_element(:css, "#dialog_wrap .float_left").click
```

対戦相手が決定すると、ようやくブラックジャックを行うコードを記述するページへ移動します。
<img width="1243" alt="スクリーンショット 2017-12-20 5.03.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/68663/3f8fd67e-0bf2-24fb-b5d0-c1f833a2ea93.png">


# 自動化 STEP 6 ~ エディタへの書き込み ~
ようやく、コードを記述するページまでやってきました。
ここまででコードにすればほんの数十行なんですが、文章にしていくと長いですね。

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

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

まずは、セレクトボックスから使うプログラミング言語を選択します。
selectタグから`Selenium::WebDriver::Support::Select`クラスを作成し、そのクラスに対して指定したいプログラミング言語を渡してあげれば、セレクトボックスの選択は完了です。

```ruby
# 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`で提出するコードをエディタに入力していくと、なぜかうまく入力できないという現象に陥りました。

```ruby
# 入力するコードを返す
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のエディタに貼り付ける

```ruby
# メモ帳サイト 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を押すことができます。

```ruby
# プログラムを実行ボタンを押す
driver.find_element(:css, ".program_execution_btn a").click

# JSのアラートダイアログに対してOKを押す
driver.switch_to.alert.accept
```

これで、コードの提出が完了です。

# 自動化 STEP 7 ~ リトライボタンを押して何回も対戦できるようにする ~
コードの提出が終わると以下のような対戦画面に入ります。
<img width="1242" alt="スクリーンショット 2017-12-21 2.07.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/68663/e330accf-4a1a-49c5-363d-c34d70f7c1e1.png">

さらに対戦が終わると以下のような対戦結果画面が表示されます。
<img width="1237" alt="スクリーンショット 2017-12-21 2.09.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/68663/ea98a236-5efb-0dac-0064-d9a8344c1529.png">

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

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

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

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

# 実際に動かしてみた

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">一攫千金プログラミング 〜ポットdeジャックポット〜を自動化してみた: <a href="https://t.co/lCcooUUIrs">https://t.co/lCcooUUIrs</a> via <a href="https://twitter.com/YouTube?ref_src=twsrc%5Etfw">@YouTube</a></p>&mdash; TomoProg (@TomoHelloxxx) <a href="https://twitter.com/TomoHelloxxx/status/943550306419429376?ref_src=twsrc%5Etfw">2017年12月20日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# そして・・・
道程は長かったですが、この自動化のおかげでついに・・・
<img width="1237" alt="スクリーンショット 2017-12-21 2.18.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/68663/e663e143-7769-aa27-d3a2-6432c20b8fe7.png">
1位を取ることができました！！！

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

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

1位とったどーーーー！！！

それではまた。
TomoProg