桃鉄ガチャをつくってみた(Heroku + Flask + LINEbot)

追記　2103312100　LINE版がバグっていたので修正。それをデプロイした時にデータベースも更新してしまいランキングがリセットされてしまった。これまで遊んでくださった皆さん、どうもすみません。


# はじめに
面白いのに役に立つ。役に立ってしかも面白い。プログラミング業界だけでなく[世間一般で大きな話題](https://nlab.itmedia.co.jp/nl/articles/1905/12/news018.html)となり[今や社会に欠かすことのできないインフラとなっている](https://nlab.itmedia.co.jp/nl/articles/2007/03/news109.html)[**サイゼリヤ1000円ガチャ**](https://qiita.com/marusho_summers/items/a2d3681fac863734ec8a)。
私も面白くて役に立つアプリを作りたかった。そして思い付いたのが**桃鉄ガチャ**だ。

旬を逃さぬよう冬休み中に作りたかったが、11月の時点で[湯婆婆](https://qiita.com/mo256man/items/2e9310dc41de657d069e)レベルだった私にそんな短納期で作れるわけがなかった。
ある程度カタチになったものをオンラインもくもく会で先行公開して自分を追い込み、何とか決算となる3月末までに完成させることができた。

# 成果物
### Heroku版
http://momogacha.herokuapp.com/

|ガチャ画面|決算（ランキング）画面|
|:-:|:-:|
|![momo_pc_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/ca4acc05-3c74-4ae3-45e2-f950f1a6f882.png)|![momo_pc_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/d1636241-1c4c-02ef-9b62-79461f7b00da.png)|

レスポンシブ対応してません。

### LINE版
[フリープランは月当たり1000メッセージ](https://www.linebiz.com/jp/service/line-official-account/plan/)まで。
![qr2line.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/cffc19c5-d651-e6de-61ea-a0ea96f7fe0a.png)

|ガチャ画面|決算はカルーセルで実装|
|:-:|:-:|
|![momo_line_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/e99c3ad5-ef7d-4ec4-1f34-3a473417f54c.jpeg)|![momo_line_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/4950a40b-4215-1851-d0c6-b141c7ee55b1.jpeg)|

デザインセンスに欠けるので、表現力に乏しいLINE版のほうがかえって整っているように見える。
こちらでは社長名はユーザー名を元に自動で作られるので、決算で他者の目に留まってもバレにくいという特徴がある。
決算はデイリーランキングと通期（なにか適切な言葉、無いですかね）のランキングがのどちらかが**ランダムで**表示される。1回ずつ切り替わる仕様にしたかったのだが簡単な方法が思いつかなかったのでやめた。


# 目標と達成度

○　[家族見守りサービス](https://qiita.com/mo256man/items/13084c865bfbf512a691)ではGASを使ったので、今度はHerokuを使う
○　家族見守りサービスではGoogleスプレッドシートを使ったので、今度は一般的なデータベースを使う
○　家族見守りサービスではLINEにテキストメッセージを使ったので、今度はフレックスメッセージを使う
○　物件データは攻略サイトからスクレイピングする
○　サイゼリヤ1000円ガチャにはなかった、データを変更したり追加したりする処理の実装
○　cookieを使ったプレイヤー名登録
○　GitHubによるソースコード管理
△　桃鉄チックな演出…某所で新幹線が登場するのだが、登場してくれない場合がある。F5でリロードすると出てくる。

# ソースコード
内緒でお願いします。
見ていただきたい、より適切な実装方法を教えていただきたいという気持ちはあるが、それ以上に恥ずかしいので。


# 技術トピック
## Flask
### cookie
cookieについての解説はウェブ上に多数見られる。だが、自分の無能さを棚に上げて言うが、納得のいくものは非常に少なかった。
私が悩んだのはここ。

```Python:
@app.route("/hoge")
def hoge():
    # POSTやGETでvalueを取得する部分は省略
    resp = make_response(render_template("hoge.html"))
    resp.set_cookie(key="key", value=value)
    return resp
```

cookieが書き込まれるのは`set_cookie()`したときではなく`return`したとき。そして返すのはレスポンスなら何でもいいわけではなく`set_cookie()`したもの。それを明記している解説を見つけることはできなかった。
最終的に私が得心したのはこちら。

https://teratail.com/questions/149935

### url_for()
ということは、入力欄と送信ボタンで構成される`form`があるとして、cookieに書き込んだうえで別ページに遷移することはできないのだろうか。
`render_template()`で遷移先のhtmlを指定する手もあるが、これはよろしくない。遷移先のページをレンダリングするのに計算が必要だったら都度コードを書かなくてはならないからだ。あーしてこーしてそーして、その結果をレンダリングする。URLを指定するのではなく関数を指定する。そのときに使うのが**url_for()**だ。いくつかの解説サイトで目にしていたがスルーしていたぞ（それは自分が悪い）。

```Python:
# まず、hoge.htmlを作るこういう関数があるとする
@app.route("/hoge")
def hoge():
    # いろいろな計算をするが、省略
    resp = make_response(render_template("hoge.html", 変数1=値1, 変数2=値2))
    return resp
    # return render_template("hoge.html", 変数1=値1, 変数2=値2)  # これでもよい

# で、自分自身（hoge.html）でも別ページからでもいいが、cookieを書き込んだうえでhoge.htmlに遷移するにはこう書く
@app.route("/fuga")
def fuga():
    # 変数が必要なのでこれではエラーになる
    # resp = make_response(render_template("hoge.html"))
    # これならいいが、ここでも計算を書く必要が出てくる
    # resp = make_response(render_template("hoge.html", 変数1=値1, 変数2=値2))
    resp = redirect(url_for("hoge"))
    resp.set_cookie(key="key", value=value)
    return resp
```

今回のプロジェクトでは使っていないが、Flaskのwebアプリから他サイトに飛ぶ方法も覚えておくべき。

```Python:
from flask import redirect

@app.route("/goodbye")
def goodbye():
    return redirect("https://www.google.com") 
```

要するに、調べ物をする際は[こういう稚拙な記事](https://qiita.com/mo256man/items/2e9310dc41de657d069e)だけでなくちゃんとしたドキュメントを読むべきだということだな。


## データベース
### DB作成
スクレイピングは意外にも簡単にできた（攻略サイトの情報の正しさは未検証）が、csvをデータベースにするのに難儀した。
思えば私はSQLを叩いてデータの抽出をしたことはあってもデータベースを作るのは初めてだった。
多くのWebサイトを訪れたがダメで、結局は以下のYouTube動画のお世話になった。

　　[PythonのORM SQLAlchemyとは？ | 中学生でもわかるPython入門シリーズ（いまにゅのプログラミング塾）](https://www.youtube.com/watch?v=inEqP234Dpc)

PythonでDBを作る経験は得たが面倒なので最終的には SQLite Viewer with Google Drive を使った。


### DBのコミット（嘘）
このガチャはスマホゲーのガチャとは違い、一度出てきた物件は次からは出てこない。
品切れ判定のためにガチャの前に有効な物件の数をカウントしている。
だが、ガチャの前に有効物件数を表示させると、毎回初期値に戻ってしまっていた。VS Codeのブレークポイントで確認すると確かに減っているのに、次の瞬間には元に戻っている。`db.session.commit()`が働いていないのか。

これには悩んだ。1か月近く悩んだ。そしてとうとう、真因にたどり着いた。
物件が足りなくなると品切れで遊べなくなるが、日付が変わると品揃えは元に戻る。その日付変更判定が間違っていて、毎回毎回リセットされていただけだった。

日付判定といえば、タイムゾーンを考慮しなかったため本番環境に限って0時を過ぎても品揃えがリセットされないと悩んだこともあった。

## Webデザイン
### UI
元がコントローラーで遊ぶゲームなので、Webアプリにした際に「クリックできるボタン」と「クリックできないメッセージウィンドウ」を一目で区別できるようにするのに苦心した。苦心したといってもデザインセンスがない＋CSSが不得手なので大したことはしていない。

### アニメーション
JavaScriptは`innerHTML`を操作する程度ならやったことがあるが、CSSを操作してアニメーションができるなんて思ってもいなかった。
とはいえ今から新しい技術を勉強するのも億劫なのでとりあえず「JavaScript アニメーション」でググってみた。すると出てくるわ出てくるわ、私の知らない世界が。そんなわけで今回初めてjQueryを使った。
冗長になるのも覚悟のうえでアニメーションの演出を組み込んだ。自分で作っておいて自分でくどく感じたのでスキップ機能も実装している。

## LINE
### Flex Message
Flex Messageは[こちら](https://developers.line.biz/ja/docs/messaging-api/using-flex-messages/)の先にあるFlex Message Simulatorでレイアウトを作ることができる（ログイン要）。
JSONでの出力もできるので自分のコードにコピペするのも容易だ。

### JSONをプログラムで書く
コピペで得たJSONのFlex Messageをソースコードに織り込むのも苦労した。
物件を列挙するのに`for`を使いたい、だが複雑な入れ後構造になっている辞書の一部分だけを`for`で回すにはどうしたらいいのか。ここで役に立ったのが[辞書の復習](https://qiita.com/mo256man/items/b4567b0e0945b99f9cff)だった。
最終的には、1回1回のガチャ結果（ツイートボタン付き）とカルーセル形式の決算画面（ツイートボタンなし）でメインとなる部分を共通にすることができた。


## Twitter
### Twitterシェアボタン
[こちらのサイト](https://publish.twitter.com/)で対話式でボタンを作り、htmlを得ることができる。
作られたhtmlはこんな感じ。スクリプトを呼び出すことでアイコンの表示などもおこなっている。

```html:
<a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-text="テキスト" data-hashtags="ハッシュタグ">Tweet</a>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
```

独自の画像を使うときは、スクリプトを呼び出さずに

```html:
<a href="https://twitter.com/intent/tweet?text=テキスト&hashtags=ハッシュタグ">Tweet</a>
```

としてやればリンク部分のみを作ることができる。上と下でオプション名が微妙に異なるので注意が必要だ、

### Twitterカード
カードの情報は[公式サイト](https://developer.twitter.com/ja/docs/tweets/optimize-with-cards/guides/getting-started)に詳しい。メタデータを`<head>`タグの中に埋め込んで使う。
OGP（Open Graph Protcol）の構文とTwitter専用タグでは微妙に異なるのが厄介。
汎用的な書き方をしたつもりだが、[上にあるように](#Heroku版)Qiitaではうまく表示できていないようで残念。

```html:
<head prefix="og: http://ogp.me/ns# website: https://my-application.herokuapp.com/">
<meta property="og:url" content="https://my-application.herokuapp.com/">
<meta property="og:type" content="website">
<meta property="og:title" content="タイトル">
<meta property="og:site_name" content="サイト名">
<meta property="og:description" content="コメント">
<meta property="og:image" content="https://my-application.herokuapp.com/static/image.png">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@ユーザー名">
```

![momo_twitter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520216/78f797db-401b-e1f4-e46d-52197affccb2.png)

まあ、どんなにこだわったところで私はTwitterをほとんどやってないのだが。

## Heroku
湯婆婆をHerokuアプリにしたときはコツコツとコマンド入力していたが、GitHubと連携することで1クリックでデプロイできるようになった。これは便利！

# 終わりに
決算の関係で明らかに未完成なゲームソフトが世に出てしまうことがあるが、そのときの開発者の気持ちがわかった。
…は冗談にしても、成果物を衆目にさらすのは身が引き締まるものがあるな。