うしろのこの本ください

なんでもかきます

ミリシタのスコア計算をする簡易ツールつくった

つくった

millionlive-simple-score-calc.me

みりしたつーるさんのAPIと姫のAPIで成り立ってる。リポジトリはこれ。

github.com

使い方は楽曲選んで、5枚カード選んで、任意の総アピール値を入れて、計算を押す。そうすると1計算で1万回分のライブシミュレーションを行う。編成とアピ値は端末ごとに保存できて呼び出すことが出来る。

シミュ結果はこういう情報。

f:id:apple19940820:20190217040323p:plain

そのチーム編成での楽曲ベーススコアと理論値~50%の確率で達成できるスコア。あとアイコンはFantasiaへの個別カードリンクになってる。同僚は自分のリーダーと同じになるようにしてある。(手抜いた)

詳細はそのカードのスキルがその楽曲プレイ時に何回まで発動するかと、シミュ1万回で合計何回発動したかが書いてある。今思えばリザルトに総アピール値書いてないな。。。まあいいや

f:id:apple19940820:20190217034717p:plain

ほぼみりしたつーるのAPIで返って来る値まま。

情報量が少なくてさくっと動いて、自分の使ってる編成が使い物になるかどうか分かるだけで良かったから余計だと思った機能は全部削った。

スマホならホーム画面に追加をするとアプリ化できる。PWAってやつ。PCもChromeでメニューからインストールみたいなのするとデスクトップアプリ化できる。

f:id:apple19940820:20190217044615p:plain

f:id:apple19940820:20190217044632p:plain

f:id:apple19940820:20190217044728p:plain

オフラインでも動くと行きたいけどシミュ1万回をブラウザでやるのは結構色々準備がいるのでまだ。実装は試すかも、そしたらネットに繋がってなくてもシミュ回せるようになる。

技術的な方の話

Nuxt.js + Element UIで作ってデータは全部API経由で取得する。一部は自分で建てたFirebaseのcloud functionsを経由している。(モバイル対応)

ホストはNetlifyで、GitHubのmasterが更新されたら自動的にNuxt generateが走って静的なサイトがデプロイされる。

PWAモジュールを雑に突っ込んであって、1発目の読み込み以降はキャッシュが効いて早くなる。楽曲データに関してはlocalStorageに保存して、最後尾楽曲のidをリクエストで投げて更新があれば差分が戻って来るようにした。(API追加のissueを出して対応して貰った、ありがとうございました)

チーム編成もlocalStorageに保存してある。というか、vuex-persistedstateでストアと同期している。これめっちゃ便利ね。

github.com

これ作り始めたのが去年の年末で、ラウンジのメンバーにフィードバック貰いつつこの形に落ち着いた。今後はTSで置き換えられるところは置き換えていきたいのと、スクショからチームをセットできる機能を付けたい。やっぱポチポチ面倒だし総アピ値いちいち入れるのもだるい。

あとこのアプリに実装するかは分からないけど、手持ち一覧のスクショから強そうなチームを作って提案するとか。こっちは色々事情があって難しそうだけど。あれば便利。

要望あればリプでもDMでもissueでもなんでもいいので投げて貰えれば出来るときにやります。

おわり

Element UIのel-tableでslot-scopeを使っていた人はVue2.6.xにあげるのは待った方がいいかも

先日Vue2.6.0がリリースされてv-slotが追加されたので、ついでにNuxtと一緒にバージョンアップしようとしたら既存コンポーネントが動かなくなった。

Element UIのel-tableはel-table-column内にslotを差し込むことで任意の要素を表示できる。この部分で今まではslot-scopを使っていたがVue2.6.0での変更によって壊れてしまったみたいだ。

issue

github.com

自分が壊れたのを確認したのは以下のコードだ

<el-table :data="selectedCardList" target-order="push" max-height="960">
  <el-table-column label="カード">
      <template slot-scope="scope">
        <a :href="createImgUrl(scope.row.id)" target="_blunk"><img :src="scope.row.resourceId" style="max-width: 100px; width: 100%;"/></a>
      </template>
  </el-table-column>
  <el-table-column label="ボーカル値">
      <template slot-scope="scope">
        <span>{{ scope.row.vocalMaxAwakened }}<br>{{ "(" + `${scope.row.vocalMaxAwakened + scope.row.vocalMasterBonus * 4}` + ")" }}</span>
      </template>
  </el-table-column>
  <el-table-column label="ビジュアル値">
      <template slot-scope="scope">
        <span>{{ scope.row.visualMaxAwakened }}<br>{{ "(" + `${scope.row.visualMaxAwakened + scope.row.visualMasterBonus * 4}` + ")" }}</span>
      </template>
  </el-table-column>
  <el-table-column label="ダンス値">
      <template slot-scope="scope">
        <span>{{ scope.row.danceMaxAwakened }}<br>{{ "(" + `${scope.row.danceMaxAwakened + scope.row.danceMasterBonus * 4}` + ")" }}</span>
      </template>
  </el-table-column>
</el-table>

ミリシタのカードデータ表示部分で使っていた。特にURLを整形してPrincessへのリンクを貼るところとか他にどうしようもないし、一旦Nuxtごとバージョンを戻した。

対処法

scope.row ? scope.row.title : '' のようにv-ifでscope-rowの値を見ると通る。恐らくv-slotが親のレンダリングサイクルから分離し、子としてインライン関数に展開される最適化がされているため起こったもの。

v-slotと親テーブルのレンダリング処理が分離したので、v-slotより先に親テーブルがコンパイルされて、未定義の子を参照しようとして落ちるという話。

v-ifでv-slotがレンダリングされるまでは空を返してやれば良いということだ。

v-slotに合わせてslot-scopeも同様の動作になるよう変更されている。つまり現状v-ifを付けない限り解決はできない。(代替手段がない)

ただし、テーブルコンポーネントのような要素の多いものにv-ifを付けて回るのもアレなので余程急ぎでもない限りElementUI自体のアプデを待った方が賢明だろう。

Nuxtだけバージョン上げてNuxtが使うVueのバージョンだけ下げるみたいなこと出来ないのかな。いやできるだろうけど、出来るだけそういうので苦労はしたくない。

おわり

ミリシタライブシミュレーションAPI 「API for TD Score Calculation 」をNuxt.jsで使う

多分みんな使ってる編成とか出してくれるツールの計算部分だけAPIに切り出して公開してくれたもの。まだ試験運用らしい。

api.megmeg.work

ツールの方

megmeg.work

んで作った奴

github.com

使い方というか、GETで投げる時のパラメータが必要でそれの情報源としてmatsurihi.me使う前提な感じあるので前作った奴を拡張する形で進めた。

その時の記事

ushirock.hateblo.jp

megmeg.work が提供しているのは以下の3つ

  • 楽曲情報
  • 単体チーム編成で指定楽曲のライブシミュレーション
  • 複数チーム編成で指定楽曲のライブシミュレーション

つまり、計算に用いる楽曲の情報も提供している。(計算式の関係もあるだろうしそりゃそう)

楽曲情報にはそれぞれIDが振られていて指定すれば1つずつ取れるが、今回はページロード時に楽曲選択用コンポーネントのcreatedで全曲分取ってVuexストアに入れている。

components/songs/SongSelectionView.vue

  async created() {
    await this.$store
      .dispatch('fetchMusicData')
      .then(() => {
        this.$notify.success({
          title: '成功',
          message: '楽曲情報を更新しました',
          position: 'top-right',
          duration: '3000'
        })
      })
      .catch(err => {
        this.$notify.error({
          title: '失敗',
          message: `${err}`,
          position: 'top-right',
          duration: '3000'
        })
      })
  },

取得した楽曲から1つ選んで指定、チームをポチポチして計算開始ボタンを押すと単体チーム編成で指定楽曲のライブシミュレーションをするAPIに計算に必要なデータを整形して飛ばす。

これもストア経由だが返却値が3種あって扱いやすいように配列に詰めなおしている。fetchLiveSimulationData → setLiveSimulationData → liveSimulationData → getterから値取得の流れ。

store/index.js

export const state = () => ({
  //...省略
  liveSimulationData: []
})

export const getters = {
  //...省略
  liveSimulationData: state => state.liveSimulationData
}

export const mutations = {
  //...省略
  setLiveSimulationData(state, data) {
    data.forEach(x => {
      state.liveSimulationData.push(x)
    })
  }
}

export const actions = {
  //...省略
  async fetchLiveSimulationData({ commit }, payload) {
    const resultData = await this.$axios.$get(
      `https://api.megmeg.work/mltd/v1/score/singleunit/`,
      {
        params: payload
      }
    )
    commit('setLiveSimulationData', resultData)
  }
}

actions で引数として受け取るpayloadには単体チーム編成でのシミュに必要な情報が入っている。以下がmatsurihi.meとmegmeg.workから得たカード、楽曲情報を詰め替えてpayloadにする部分。

components/cards/CardTransfer.vue

const music = this.selectedMusic
const team = this.selectedCardList
const SongId = music[0].SongId
const Course = 4
const AppealValue = this.appealValue
const Unitlds = team.map(x => {
    return x.id
})
const GuestId = team[0].id
const SkillLvs = [10, 10, 10, 10, 10]
const TryNumber = 10000
const Prob = [0.1, 1, 50]
const Minimize = false

const requestParams = {
    SongId: SongId,
    Course: Course,
    AppealValue: AppealValue,
    UnitIds: Unitlds,
    GuestId: GuestId,
    SkillLvs: SkillLvs,
    TryNumber: TryNumber,
    p: Prob,
    Minimize: Minimize
}

そのままactionsにdispatchする。ここら辺の値についてはAPIドキュメントを読む方が早い。

サンプルコードがjQueryなのでaxiosとオプションの名前が違う点は注意。axiosではGETリクエストパラメータはdataではなくparams。それからチームのアピール値が必須なんだけど、ここを計算で出す場合matsurihi.meから得られる情報を使って求めることになる。面倒だし人によって変動が大きいため手動が無難。本家ミリシタツールも手入力。

動作画面はこんな感じ。

f:id:apple19940820:20190121002711g:plain

複数チームでのシミュレーションはまだ試してない。

matsurihi.meと同じく使う場合注意。また、サービスとして公開するならmegmeg.workの表記も必要。

追記

楽曲を毎回全件取得するのもあれなので、1回取ったらlocalStorageに突っ込んで楽曲更新があれば差分取得するようにした。みりしたつーるさんにincrementalUpdateの実装をお願いしたら1日で実装してくれた。ありがとうございました。

おわり

React+TypeScriptの勉強を兼ねてポートフォリオつくった

つくった

ushironoko2.me

TSのサポートのお陰でほぼ実行時エラーに悩まされることはなくなったけど、Material-UIのコンポーネントの型とかで結構苦労した。

今思えば新しくReactとTypeScriptとMaterial-UIとJSXを同時に始めるってのはカロリー高い。とくに+TypeScriptの面が辛かった。いやマジで。

あとOGPまでやる体力がなかった。上司に風邪うつされてかなりしんどい。。。

感想

完走した感想ですが、やっぱりVue.jsよりコスト高い。HTMLベースで直感的に書けるVue.jsとは違って全てがJavaScriptの世界だから、特にCSS周りで苦戦する。

最初はstyled-components使ってたんだけど一応ポートフォリオなんで素直にUIコンポーネントに頼る事にした。結果かなり時間使ってしまったが。

それと、Reactは現在も成長中なのでコンポーネントの書き方が複数あって混乱する。とりあえずコンポーネントのエンドポイントになる親のみクラスで書いて、子は全部FCにしたけど、FCに渡すpropsの型がMaterial-UIが絡むとおよよ…ってなる。

最初はMaterial-UIのWithStylesと一緒に渡すとsuper(props)って出来なくて、後から&で複数の型を指定できることを知った。

ググっても2015年あたりの古い記事が結構出てくる(日本語)。componentWillMountは廃止予定らしいが、Hooks使ってくれって事?

ライフサイクルメソッドもまだ勉強しきれてない。とりあえずFCには書けないので親が持つstateを書き換えてpropsを更新する感じになる。この辺はそれぞれのコンポーネントインスタンスを保持するVue.jsとは結構違う(FCだと)。

てか苦労の80%はMaterial-UI+TypeScript関連なんですけど。マジで辛かった。ドキュメントも多いとは言えない。これは続けていれば英語に強くなるタイプのアレだ。

総評

総じてやんちゃ出来ないなという印象。+TSで型を縛るとなるほど堅牢。フロントはゆるふわで行きたいんです~って人には向いてないと思った。書く前はJavaプログラマとかはむしろ向いてるんじゃない?って思ってたけど、フロントで消耗したくない適当に書きたいって人が出そうなくらいガチガチだったから微妙。個人的には印象は良かった半面Vue.jsの緩さが恋しくなった。

今後

書く優先度的には Nuxt > React = Vue くらいになりそう。Nuxtの考えなくてもある程度の設計を保証してくれる世界観は貴重っぽい(あと日本語)。Reactはフロントでサーバーサイドプログラミングしてる気分になる。

個人的な趣味でSPA作るうえでVue.jsの緩さだと危くなるような場面があまり思い浮かばないが、TSに慣れれば手なりで書けることも増えて今よりは苦労しなくなるだろう。多分。そこそこ書けるようになってからが型の恩恵の本領だから。(デザイン周りもうちょいなんとかならんか)

おわり

ミリシタのラウンジ名でTwitterエゴサしてDiscordに飛ばす奴

最近ミリシタ関連のものしか作ってねーな

github.com

前のocrの奴の流用。基盤自体が出来てるから検索ワードを変えてフィルタリング処理増やしただけ。

あとついでに全部TypeScriptで書き直した。VSCodeとの相性は評判通り物凄い良い。自製ライブラリもないので大概@typesに型定義が転がってるのも楽で良かった。

型推論バリバリ効くからElasticsearchのレスポンスの中身がしっかり予測変換で出てきて助かる。こういうのがJavaScriptで出来るだけで体験が全く変わって来るね。

今回のはdiscordに対してwebhookで投げてるだけだからいいけど、これをウェブアプリとかにするとなるとクライアント側からのリクエストのルーティングだとかページネーションだとかを実装しないといけない。

結構面倒だから先にディスコ版つくったのが経緯。ウェブ版需要あるなら作るけど今一ElasticsearchのScroll APIが分かってない。まあなんとかなるでしょ。

クライアント側はReact+TSで作るつもりだけどカロリー高そうだったらVueかNuxtでやっちゃうと思う。休み長い訳じゃないしね。

おわりだよ~

ミリシタライブレポートから実績情報をOCRで読み取るやつ

お試しでつくった

github.com

簡単な実装しかしてないけど、一応動く。技術スタックは以下

  • Elasticsearch
  • Logstash
  • Tesseract.js

詳しくはリポジトリに書いてある。Elasticsearch に Logstash が取得したライブレポートの Twitter 情報を送ってため込む。

Erasticsearch の REST API 叩いて必要な情報(主に画像URL)を取得したら、request で画像をダウンロードして Tesseract へ渡す。それだけ。

なんで Elasticsearch なのかっていうと別件で AWS に Logstash と一緒に建ててたから。結局そっちの方は色々あって駄目になったんだけど、Logstash の Twitter input plugin の検索ワードだけ変えれば流用出来る事に気づいたのでそのまま使った。

Tesseract の部分は本来 Cloud VisionAWS Rekognition になる予定だったけど手元で実行できればとりあえず良かったので見送り。

そもそも GCP でやるつもりだった。Amazon Elastic Service がかなり楽だったし、余ってる EC2 インスタンスもあったので AWS で完結させた。

画像は今はディレクトリに突っ込んでるけど本来は S3 に投げつけてアップロードイベントで Lambda を発火してその中で Rekognition 呼ぶ構成だった。色々妥協したし Node のコードもかなり雑。サーバーサイドjsの非同期なコード真面目に書くの初めてだったから結構大変だった。お陰で AWS と Node の知見はそこそこ溜まった。

Tesseract.js は出力結果を JSON で吐いてくれないし、得た結果は Elasticsearch に戻そうと思ってたからここは S3 に突っ込む部分と一緒に Rekognition で再実装すると思う。完成したらまた記事書く。

結局これで何がしたいかって言うと、ある程度データが溜まったらリザルトレポートの画像からプレイの評価をしてくれるTwitterBotが作りたかった。フォローしてるとリザルト投稿した時に自動でリプして評価してくれるみたいな。出来るか分からんけどとりあえずデータ収集から入った形。

あと白よりの情報源が欲しかったってのもある。解析とかしてそうなもの結構あるんだけど、それ本番サービスで使い辛いなって思って自前でデータ用意するなら自動化したいというお気持ちが出てきた。まあそんな感じ。

結果例。流石に精度悪い。座標も何も渡してないし仕方ない。途中でスコアの前に#が入って VSCode がカラーコード扱いで色付けてるの面白かった。

f:id:apple19940820:20190108232844p:plain

おわり

ミリシタチームアナライザー使用感メモ

作ってるツールで実装に迷ったら、その時点でベータ版にしてある程度使ってみると良い。というわけでミリシタイベントRtF中に今作りかけのチーム編成最適化ツールを使ってみた。

その使用感メモ。リポジトリはこれ

github.com

欲しくなった機能

  • カード能力の偏りで検索(VoDaViタイプ)
  • 総合アピ値表示
  • 単体アピ値表示
  • 曲との相性表示
  • 3人でのスキル発動被り
  • スコア/コンボ でスキル発動被り検知の分割

その内欲しくなりそうな機能

  • チーム保存
  • チーム単位での比較

issue に積んどく

おわり

Twitterに投稿されたミリシタのプレイリザルトからGCPのVision API OCRで情報を積む試み

っていうのができそう。画像はテキストでTwitterスクレイピングした奴を Cloud Storage にため込む。んで Storage のアップロードトリガーで Cloud Functions を蹴って、Functions の内部で Vision API を呼び出すって感じ。スクレイピングも Functions で書けば GCP で完結させることもできるね。別に他で実装して GAE でもいいけど。図にするとこんな感じ。

f:id:apple19940820:20190104043622j:plain

以下参考記事

Cloud Functions と Vision API を連携して画像解析を試してみる - Qiita

光学式文字認識(OCR)  |  Cloud Vision API ドキュメント  |  Google Cloud

Puppeteer + GCP Functionsでサーバレスなスクレイピング - Qiita

Twitterをスクレイピングするスクリプトをgithubに公開しました! - ブロックチェーンエンジニアとして生きる

ネックになりそうなのはやったことない Functions での Puppeteer 使ったスクレイピングVision API 回りだけど、あんまり心配してない。それよりも Twitter OAuth の使い方完全に忘れててそっちのが面倒な気がしてる。まあ大丈夫か。

これ上手くいけばスクショから情報を積み上げる基盤になるので結構需要ありそう。API の公開はちゃんとできる自信ないからやんないけどね。

明日(今日)から試しに書いてみる。使いすぎると GCP の無料枠超える事があるから気を付けないと。どうせ1円とかだけど。

おわり

Element の el-table-column に任意の要素を突っ込む方法

element ui の el-table にアイコンを表示したくて色々試した結果、死ぬほど時間を浪費したのでメモ残す。

こういうやつ

<el-table :data="filteredList" max-height="960">
  <el-table-column label="カード">
  </el-table-column>
  <el-table-column label="インターバル" prop="skill[0].interval">
  </el-table-column>
  <el-table-column label="スキル秒数" prop="skill[0].duration">
  </el-table-column>
  <el-table-column label="効果量" prop="skill[0].value">
  </el-table-column>
</el-table>

ミリシタのカードデータを使ってごにょごにょやろうと思ってたんだけど、このカードの部分でアイコン出したくなった。最初にそれっぽい issue を見つけたのでそれを頼りに頑張ったのがだめだった。

github.com

この issue にあるように slot-scope を使ってカラムに任意の要素を流し込むことが出来る。

jp.vuejs.org

<el-table :data="filteredList" max-height="960">
  <el-table-column label="カード">
    <template slot-scope="scope">
      <img :src="scope.resourceId" />
    </template>
  </el-table-column>
  <el-table-column label="インターバル" prop="skill[0].interval">
  </el-table-column>
  <el-table-column label="スキル秒数" prop="skill[0].duration">
  </el-table-column>
  <el-table-column label="効果量" prop="skill[0].value">
  </el-table-column>
</el-table>

resourceId にはその画像の url が入っていると思ってよい。ここも最初は動的にパスを変える方法を模索していて background-image にしてみたりもしたけど、結局 img の src属性を動的に変えるのに落ち着いた。

しかし、これだけでは動かない。なんで! issue にある通りに書いたのに!この後3時間程詰まってキレかけたが、神stackoverflowが見つかり事なきを得た。

stackoverflow.com

つまり、row が抜けているだけだった。

<el-table :data="filteredList" max-height="960">
  <el-table-column label="カード">
    <template slot-scope="scope">
      <img :src="scope.row.resourceId" />
    </template>
  </el-table-column>
  <el-table-column label="インターバル" prop="skill[0].interval">
  </el-table-column>
  <el-table-column label="スキル秒数" prop="skill[0].duration">
  </el-table-column>
  <el-table-column label="効果量" prop="skill[0].value">
  </el-table-column>
</el-table>

これで結果はこうなる

f:id:apple19940820:20190102044147p:plain

この結果に辿り着く前に、el-table-column が持つ formatter 属性でなんとかパース出来ないか試した時に実は row の存在には気づいていた。

<el-table-column label="カード" prop="resourceId" :formatter="formatter">
</el-table-column>

methods: {
  formatter(row, column) {
    return return '<img src="' + row.resourceId+ '">'
  }
}

なんかこんな感じで img 差し込めないかなとか思った。結果は文字列としてタグが表示されるだけだったけど。あと createElement('img') を返してみたけどだめでした。

この時点で気づけていれば2時間くらい無駄にせずに済んだな。つれぇ

頑張った成果ここだいぶ進んだから良し

github.com

おわり

ミリシタを褒めるだけ

アイドルマスターミリオンライブ シアターデイズというゲームをやっている。スマートフォン向けアプリで、音ゲー+3Dモデルを活用したアイドルとのコミュニケーションが基盤となったアイドル育成ゲームだ。

millionlive.idolmaster.jp

自分はP(プロデューサー:プレイヤー)となって今年で7年目で、アイマスのゲーム自体はアイマス2からプレイしているし、世代が世代なのでアイマス以外のアプリゲーもたくさんやっていた。今はミリシタとハースストーンくらいだけど。

なんでやらなくなったかっていうと目が肥えたとか飽きたとかそもそもスマホ向けのネイティブアプリゲームがそういう時期に来てるというのもあるけど、色々含めてやるに値しないと感じるようになったからだ。

急にそういう気分になったわけでもなく、例えばパズドラなんて200万DL記念から始めてまだ手元にアプリ自体は残ってたりする。

ただ、パズドラより後に始めて今手元に残ってるのは上記の2つのみだ。長い年月をかけてやっていたアプリほぼすべてに対して熱が冷めたというわけだ。

ミリシタには全く飽きが来ない。ゲーム的な魅力以外の部分から自分が勝手に色々と感動を受け取って一定のモチベをキープしている。自分は一応ソフトウェアエンジニアなので、主にそういう部分に魅力を感じているが、別にゲームが面白くないってわけじゃない。ゲーム自体も面白い。昔からこの手の音ゲーが結構好きだったしね。

年も明けたことだし?自分がミリシタに感じている魅力を書き出してみる。殆どのものはミリシタをプレイしているP達も感じ取っているか、気が付かなくとも恩恵を受けているものだ。褒めたら批判もセットにした方がバランスのいい記事になりそうな気もするけど今回は一切書かない。

UX に対しての貪欲さがすごい

ミリシタ運営チームは兎に角ゲームを遊ぶPのユーザー体験を第一に考えている(と思われる)。ミリシタ自体のプロデューサーである狭間和歌子さん(わかちこP)が一時期インタビューを受けまくっていたが一貫して「開発チームが協力をしてくれた」と語っている。

www.inside-games.jp

軽快にろくろを回していて面白い。ろくろを回してくださいって指示が出たりするのかな?

www.famitsu.com

わかちこPはプロモーションチームと開発チームの間を取り持ちつつ全体が上手く回るように調整を行うことが主な仕事のようだ。

当然プロモーションチームからはあんなことがしたいこんなことがしたいと要望が開発チームへ出てくると思うし、開発チームは出来ること出来ないことの取捨選択をしなければならない。

しかし、そこの判断基準は「いいと思う」や「技術的に難しい」といったものではなく、「プロデューサーがどう感じるか」が核になっているそうだ。実際、13人同時ライブ(リアルタイムレンダ)を実装した時には物凄い反響があったし、丁度今日THE IDOLM@STER 初星mix が 765Pro AllStars13人仕様で実装された。技術知見が溜まって実現可能になったタイミングで開発チーム側から提案があり、今に繋がっている。

www.youtube.com

gamebiz.jp


ライブ表現だけでなく、UIの更新も非常に多い。ミリシタ初期段階とのUIを比較すると一目瞭然で、多分ABテストやパフォーマンスの計測から現在の形に落ち着いている。

ミリシタはバックエンドに GCP を利用していて、ログを BigQuery に突っ込んで常に解析を行っているため、様々なユースケースと実際のPの傾向をすり合わせて的確なサービスリリースが出来ているんだと思う。

一番分かりやすいのはルーム間の移動で、初期実装時では劇場内を行き来するのに遷移だけで10秒弱待たされていた。現在は回線が貧弱でなければ3~4秒まで縮んでいる。初期ではレスポンスの遅さでストレスが溜まりまくった同僚へフラワースタンドを送る機能も現在ではまあまあ軽快に動作する。

とにかくユーザー体験を損ねない、それが大前提としてあって、あとはPが痒そうなところを探し出しては実装、探し出しては実装を繰り返しているのが他のアプリにはないユニークなポイントだ。

mltd.fun

上記記事にもあるように、初期はそこそこ雑な部分が多かったしデレステから来る人もいてよく比較されてやっぱやーめたってなる事がままあった。「ミリオンの音ゲーが出る」という事実だけで舞い上がっていた自分は全く気にしてなかったけど、実情としてこういう話はある。


これを後回しにしてただユーザーがやる事を増やし続けるだけのアプデが続くと、膨大でまあまあ良い質のコンテンツを貧弱なUI基盤でプレイするという構図になり、アップデート頑張ってるのに批判の声が増し続ける。

そういうゲームはそのうちコンテンツの更新に限界が来て、骨組みを再構築するべくセカンドシーズンをリリースしたり大型アップデートを実施したりする。最適化は優先されるべきアップデートだと強く感じる。


ライブレポートの画面が大きく変わったのは結構最近だけどすんごいびっくりした。そこ気合入れてもびた一文Pがお金落とすことに繋がらないよ?って思った。でも滅茶苦茶嬉しかったから普通にガシャでお布施しといた。課金ゲーのあるべき姿である。

f:id:apple19940820:20190101063032p:plain

f:id:apple19940820:20190101062257p:plain

リザルトにリザルト以上の意味を持たせようとする試み。凄くない?


多分、ゲームプロデューサーであるわかちこP自身が一貫した視点で各運営チームへ調整を行っている成果だ。それを実現するチーム自体の力も非常に大きいが、トップがへたをうっていると良いサービスは作れないものだ。軽快なEDMに乗せて動画サイトの広告でアプリ紹介してる暇があったら少しでも高速化、最適化を進めた方がずっと売り上げは伸びるだろうな、と思った。

サービスが止まらない

ミリシタは今日2019/1/1の時点で1年と185日の間、自身のやらかし範疇では一切アプリをメンテに入れていない。GCP のグローバルロードバランサの設定をとちって数時間止めた奴 みたいなのを除けば、実質完全無停止状態を維持し続けている。

バックエンドに GCP を使っているとはいえ、ミリオンライブのようなBotの標的になりやすいでかい看板を背負ったアプリでこれ程の高可用性を保っているのは、システム設計がうまくいっている証だと思う。

少し前にバックエンドについてのスライドショーが公開されたので、それを優し目にした記事を書いたことがあるから詳しく知りたい人はこっちを読んで欲しい。

ushirock.hateblo.jp

UX の話にも繋がるけど、アプリのダウンタイムはそのままPを損失する機会となるって認識があるみたいだ。バンナム自慢のアイドルマスターも遊んでもらえなければ意味がないし。

アプリを止めない事には多くの意味と価値が生まれている。簡単に書いてるけどめっちゃむずいからね。世の中にはDRDoSみたいなとんでもないF5アタックとかあるし。

初心者に対して常に優しい

優しいというより、初心者と熟練者の差があまり生まれないようにしているという方が正しい。ミリシタには楽曲プレイと衣装収集、PLv以外にいわゆるやりこみ要素と呼ばれるものがない。

過去に実施されたイベントで手に入ったものは、後からでも引き換えできるようになっている。また、楽曲に関してもイベント時に難易度ではポイント効率に差がでない。

これが滅茶苦茶大事で、ユーザー間に競技性がない癖に古参プレイヤーが有利になるような仕組みにするとそのゲームは死に向かう。新規プレイヤーにのみ見える壁は低くなければならないからだ。

常識のように思えるが、長く続けていくうちに増えたコンテンツがその副作用として簡易的な競技要素を持ってしまい、弄りたくても弄れないまま新規ユーザーの流入が減っていくのは結構ある話だ。


ミリシタはラウンジのような交流場所や、TBTCのような他Pとの競い合いはあれどそれが直接ゲームプレイに影響を及ぼすことはない。どこのラウンジにも所属せずソロを貫き通そうが、いろんなラウンジを転々として常に多くの同僚に囲まれてようが、ゲームの内容が変わる事はないし、そこで有利不利もつかない。

イベントではランキング形式で他Pと戦うので競技要素自体はある。ただ、本質的な部分以外で差がつかないようになっているだけだ。そこがいい。「プロデューサーはどう思うか」が曲げられずにゲームへ反映されていると思う。

あ、楽曲スコアは例外で、良いカードを持っている人が当然有利になる。これは音ゲー+ガチャのシステムを採用した時点で逃れられない奴だから、大目に見てくれよな。

ミリシタから学ぶこと

結局ミリシタがやってるのは「アプリの品質を追求する」、「ユーザーの体験を常に考える」の2点だけだった。でもコンテンツが育ってくると後回しにされたり蔑ろになりやすい部分でもある。

最悪なのは、最初はユーザーのためだったものがいつしか「俺たちが面白いと思うもの」にすり替わってしまう事で、自分は色んなゲームでそういうのを見ているのでこれをやられるとだいぶ萎える。お前の好き嫌いは二の次にして欲しい。

現状ミリシタがそうなる様子はない。まだ2年目だけどむしろどんどんユーザーに寄り添ってきてる気がする。もう隣に居るんじゃないの?痒い所を発見するのが上手すぎる。

ミリシタから学ぶことは沢山あるけど、やっぱり誰が為に働くのかを忘れないことだなって思った。


おわり あけおめ!

開発ブログをまともにした

ushironoko-dev-blog.me

まともというのは見ても目が腐り落ちない程度という意味です。

リポジトリ

github.com

色々試せていい。やっぱり自作ブログは経験しとくとがっつり経験値貯まると思う。

このブログには余計なものは一切乗せるつもりはなくて、高速化の方向にもっていく。というかその実験場みたいな感じになる予定。

そのうちマウスオーバーの prefetch とか入れる。BFF 建ててページキャッシュしてもいいかもしれない。結構しんどいけどやってみたい。

あとミリシタのツールはこれに統合してくかも。そうしたらここより開発ブログの方がメインになるかも。正直めんどいのでやらないかも。

おわり

【一般P向け暇つぶし用】お手持ちのChromeでミリシタのデータを回収してみよう

世の中にはWikiって奴が存在して、その中にはたくさんのゲームデータが詰まっている。非公式だけど。

これ手元に欲しくない?欲しいよね!お手持ちのChromeには超絶強力な開発者ツールが備わってます。しかも誰でも使えて完全無料!いいの?いいよ

情報を回収してみよう

Fantasia というミリシタの 非公式情報ポータルがある。ボーダーBotとかで皆お世話になっているはずだ。

mltd.matsurihi.me


まずはこのサイトから情報を引っ張り出してみよう。プログラムは全部コピペで動くから安心してほしい。では早速、カード一覧のページを開こう。

f:id:apple19940820:20181224093533p:plain

余計な情報がなくていいレイアウト。じゃあそれぞれのカード名とそのカードのページURLをセットで取ってみる。


ChromeWindows なら F12 、Mac なら Cmd + Opt + I でデベロッパーツールと呼ばれる開発者用のウィンドウが開くようになっている。

f:id:apple19940820:20181224093844p:plain

最初に見えるのはそのページの中身だ。この情報を元にしてブラウザは UI を配置する。それぞれが Element とか言われたりするのでこのタブの名称は Elements になっている。


ここでやる事もあるが、まずはどうやって情報を手に入れるかを先に説明しよう。隣の Console タブへ移動。

f:id:apple19940820:20181224094624p:plain

ここでは実際にプログラムを書いて実行することができる。いわゆるエディタ実行環境がセットになっているのが Console だ。ここで Chrome に備わっている機能を使って、各カード情報ページのURLとカード名称を1:1に紐づけてデータとして抽出するのが今回の目標ということにしよう。

手始めに全てのカード名をクリップボードへコピーすることからやってみる。

難しそうに見えるけど、実際はたった1行のコードで出来てしまう。以下をコピーして Console に貼り付けて Enter キーを押してみよう。

copy($$('.card-wrapper .card-name-wrapper').map(e => ({cardName :e.textContent})))

すると undefined と返って来るはずだ。

f:id:apple19940820:20181224100355p:plain


既にクリップボードに結果がコピーされている。

適当な場所に ctrl + v で貼り付けると{ cardName:カード名 } の形式で全てのカード情報が取れている。

[
  {
    "cardName": "りるきゃん 篠宮可憐"
  },
  {
    "cardName": "りるきゃん 野々原茜"
  },
  {
    "cardName": "フローズン・クリスタル 三浦あずさ"
  },
  {
    "cardName": "雪解けブレイクダンス! 舞浜歩"
  },
  {
    "cardName": "一生懸命の音色 中谷育"
  },
  {
    "cardName": "早朝お寝惚け温泉 真壁瑞希"
  },
  {
    "cardName": "ステージ後のひととき 豊川風花"
  },
  {
    "cardName": "ゴシックホワイトドール 七尾百合子"
  },
  {
    "cardName": "ほわほわステップ 高槻やよい"
  },
  {
    "cardName": "幻想の光の中で 二階堂千鶴"
  },
//以下省略
]

あなたは今 JavaScript というプログラミング言語のコードと、 Chrome コマンドライン API を実行して目的の情報を手に入れました!おめでとう!君もプログラマーだね。


何が起こったのかは後に説明するとして、今のままでは情報が不足しているので少し変えてみる。

copy($$('.a-card-select').map(e => ({cardName : e.childNodes[1].childNodes[3].textContent, url : e.href})))


同じように Console に貼り付けて Enter で実行。すると先ほどのリストにそれぞれのページへのリンクが追加されている。

   {
      "cardName": "りるきゃん 篠宮可憐",
      "url": "https://mltd.matsurihi.me/cards/459"
    },
    {
      "cardName": "りるきゃん 野々原茜",
      "url": "https://mltd.matsurihi.me/cards/460"
    },
    {
      "cardName": "フローズン・クリスタル 三浦あずさ",
      "url": "https://mltd.matsurihi.me/cards/456"
    },
    {
      "cardName": "雪解けブレイクダンス! 舞浜歩",
      "url": "https://mltd.matsurihi.me/cards/457"
    },
    {
      "cardName": "一生懸命の音色 中谷育",
      "url": "https://mltd.matsurihi.me/cards/458"
    },
    {
      "cardName": "早朝お寝惚け温泉 真壁瑞希",
      "url": "https://mltd.matsurihi.me/cards/453"
    },
    {
      "cardName": "ステージ後のひととき 豊川風花",
      "url": "https://mltd.matsurihi.me/cards/454"
    },
    {
      "cardName": "ゴシックホワイトドール 七尾百合子",
      "url": "https://mltd.matsurihi.me/cards/455"
    },
    {
      "cardName": "ほわほわステップ 高槻やよい",
      "url": "https://mltd.matsurihi.me/cards/449"
    },
    {
      "cardName": "幻想の光の中で 二階堂千鶴",
      "url": "https://mltd.matsurihi.me/cards/450"
    },
    //以下省略

そこそこ便利なデータになった。プログラマにこれを引き渡せばカード名にリンクを付けてテーブル一覧にするくらいは10秒でやってくれるだろう。

【おまけ】他のサイトでやってみる

※ネタバレ、失敗しました。

もしかしたら別の Wiki を愛用していてそっちでやりたいって人がいるかもしれない。さっきのコードは少し変えるだけでどんな Web サイトでも同じようにデータを抽出できる。 in が合っていれば out が同じものになる仕組みをインターフェースと言う。コンソール API の I はインターフェースの I だ。


ミリシタ wiki でググったらここが出てきた。ので、SSR のページで同じことが出来るか試してみる。

imasml-theater-wiki.gamerch.com

F12 で デベロッパーツールを開いて、 Console に移動。とりあえずさっきのコードをコピペして実行してみる。undefind と返ってきたらとりあえずエラーなしにコードが通った証だ。

ではいざ ctrl + v!

f:id:apple19940820:20181224114424p:plain

どう見ても空。さっき貼り付けたコードを確認してみよう。

copy($$('.a-card-select').map(e => ({cardName : e.childNodes[1].childNodes[3].textContent, url : e.href})))

なんで空だったかっていうと、対象がなかったから。サイトによって作りが違うから当たり前なんだけどね。


じゃあさっき言ってた API ってなんなんだよ!ってなるじゃない?変えなくていいのは実はこの部分だけなんです。

copy($$('サイトによって変える').map(e => ({cardName : e.サイトによって変える, url : e.サイトによって変える})))

「サイトによって変える」の部分はサイトによって変えなければならない。冒頭で触れた Elements の構造が違うからだ。このコードは Elements を順番に辿って行って目的のデータがあればそれを手に入れるというもの。

では Console タブから Elements タブに戻って、このサイトの構造を調べてみよう。作りが良ければアイコンの URL も回収できるかもしれない。


f:id:apple19940820:20181224115131p:plain

見づれぇ。。。何かしらの Wiki ジェネレーターで作ってるはずだし仕方ない。目的の Element へのあたりを付けるために近くまで移動する必要がある。

デベロッパーツールの左上にななめ矢印のアイコンがある、それをクリック。すると実際のページ上の各種 Element (というかDOM)を範囲選択のように触ることができるようになる。


とりあえず、春香のアイコンをクリックしてみる。Elements 側もそれに対応して Element ツリーを開いてくれる。

f:id:apple19940820:20181224115746p:plain

なるほど、このサイトは Fantasia とは違い table タグで整列させているようだ。見れば分かるが。

ここからさっきのコードにいい感じに当てはめる要素を探っていく。まずはこの部分

copy($$('サイトによって変える')

ここには欲しい情報のすべての親になっている Element を指定するのが良い。Element ツリーは階層構造になっているのが見て取れるが、上へ行くほど親となり下へ行くほどその子となる。


つまり、実際に欲しいデータ(子)を持つ親を探せばいいってわけだ。「カード名を持つ子」と「そのページへの URL を持つ子」の親となる要素はどれだろう。

f:id:apple19940820:20181224120332p:plain

table > tbody > tr がそうっぽい。tr は子要素として複数の td を持つ。table の基本的な構造だ。つまりこの SSR 一覧はこういう作りになっている。

table > tbody > tr(SSR1枚の行) > td(画像|名前|アイドル|タイプ|限定) > a(画像、リンク等)


欲しいデータはこれの内 a にあたるので、その親となる td を全て含む tr 'サイトによって変える' に指定する。右クリックから Copy > Copy selector でそこまでのパスをコピーできる。

f:id:apple19940820:20181224121145p:plain


まず単体で実行してみよう。

copy($$('#ui_wikidb_table_913361 > tbody > tr:nth-child(1)'))

f:id:apple19940820:20181224122152p:plain

まあ置いておこう。残りを埋めればいい結果が得られるはず。

.map(e => ({cardName : e.サイトによって変える, url : e.サイトによって変える})))

さてこの部分。そろそろ何を入れればいいのか分かってきたかもしれない。e => の e というのはさっき指定した tr だと思っていい。そこから目的のデータまで辿っていけば良い。

Fantasia の時はこうだった。

e => ({cardName : e.childNodes[1].childNodes[3].textContent, url : e.href

e.ChildNodes というのが子要素を指定する奴で、[1] とかは子が複数ある時にどれを辿っていくか選ぶ奴だ。これを参考にしよう。

そうそう、さっき Copy selector で取った奴は春香の tr 要素だから、それより一個上の tbody から始めないとカード一覧全てを対象にできない。それも踏まえてコードを修正しよう。


どこから始めれば良いのか分かったら、次はどのデータを使うかを調べる。 Fantasia 版で言う .textContent とか .href の部分。これは基本的にどんな Web サイトでも変わらないように出来ている。開発者がオリジナルの属性を作らない限り、同じように分類分けされる。

デベロッパーツールの右側にまだ使っていなかったパネルがある。現在は Styles となっているはず。tbody を選択後、これを Properties タブに変更すると、このようになる。

f:id:apple19940820:20181224123952p:plain

▶tbody をクリックすると tbody が抱えているもの全てが展開される。childrenNodes も持っていることが分かる。


どうすればいい感じに tbody からスタートして各カード情報を得られるだろうか。実はこれを書いているうちに tr の1つ目の子(td)が、欲しい情報を全て持っていることに気が付いてしまった。

カード名は tbody > tr > td > a > img.alt 、ページ URL は tbody > tr > td > a.href 、ついでにサムネURLは tbody > tr > td > a > img.src だ。


調べて得た情報を childNodes を辿ってコードに当てはめてみる。長いから改行する。

copy($$('#ui_wikidb_table_913361 > tbody')
.map(e => ({
    cardName : e.childNodes[0].childNodes[0].childNodes[0].alt, 
    url : e.childNodes[0].childNodes[0].href, 
    img : e.childNodes[0].childNodes[0].childNodes[0].src
})))


説明してなかったけど、.map というのは付けた対象が持っているもの全てを1つずつ取り出して、() の中身を実行するという JavaScript のコードだ。ここは本当のコードを書いている。俗にいう配列操作と言う奴。

これを Console に戻って貼り付けて Enter !

f:id:apple19940820:20181224130637p:plain

普通に間違えた。 tbody 以下の tr.map で取り出していくので、 childNodes[0] はまずい。1つずつ取り出せるようにスタート位置を tbody.childNodes に変えて見よう。

copy($$('#ui_wikidb_table_913361 > tbody.childNodes')
.map(e => ({
    cardName : e.childNodes[0].childNodes[0].alt, 
    url : e.childNodes[0].href, 
    img : e.childNodes[0].childNodes[0].src
})))


こんどこそいけ!

f:id:apple19940820:20181224131600p:plain


f:id:apple19940820:20181224133226p:plain

OK、どうやらこのサイトでは Console から値を取るのは無理みたいだ。多分動的なサイトなんだな。


ムカつくから番外編始めます。

【番外】pandas で table の値をスクレイピングしてくる

お使いのパソコンに python をインストールします。Anaconda 使おう。間違っても 2.x 版を入れないように。

f:id:apple19940820:20181224133557p:plain

この記事が分かりやすかった。

qiita.com

インストールが終わったら Anaconda Prompt を開き、Python の実行準備をする。プログラムはデスクトップに置いておくので、ディレクトリの移動も忘れずに。


適当にファイル名決めて拡張子.pyで保存。できたらこんな感じで pandas から url 指定して table 情報を根こそぎ攫ってくる。

f:id:apple19940820:20181224140759p:plain


Anaconda Prompt 上でこのスクリプトを実行。えい!

f:id:apple19940820:20181224140906p:plain

勝ったな。(うrl取られへん)

おわり

すいませんでした

聖夜なので課金と共にミリシタを振り返る

自分宛ての戒めのつもりだったけど、俺こんなに使ったんだぜっていう自己満足の自慢記事になってる。

自分が稼いだお金を自分で使いまくっただけなので文句もなし。良いコンテンツ、技術に貢献できる一番簡単な方法は金を払う事だ。


というわけでミリシタを金で振り返る。昔真面目に書いた記事あるからそっち読んでくれてもいいよ。

ushirock.hateblo.jp

集計しましょう

iOS はゲームに課金した時無事に決済出来ればその旨のメールが App Store に紐づいたメールアドレス宛てに届くようになっている。

今までの課金額を知ろうと思ったらこれを集計するか、iTunes を見るか、運営またはApp Storeへ問い合わせが必要だ。

自分の場合やはり、エンジニアの端くれとしては集計の自動化でささっとやってしまいたいと思ったため問い合わせるのはやめておいた。レスポンスも遅いだろうし。

しかし App Store に紐づいているのが Yahoo! メールだったので API の叩き方が分からない。これから課金を始める人は必ず各種 G Suite と連携できる Gmail にするべきだ。


というわけでエンジニア裏三種の神器の一つである Excel に手動で打ち込んでミリシタへの課金額を集計した。From Apple でフィルタリングした後 3200 とか 9800 とかで絞り込んで結果を加算すれば集計なんて一瞬なんだよな。

だいたい1時間半程度で結果が出た。ミリシタが始まった 2017/6/30 ~ 2018/11/30 までの間で自分がどの程度課金したか、どのプランを多く使ったかを表にした。

課金プラン 利用回数 合計金額
120 7 840
480 24 11520
960 23 22080
1600 34 54400
3200 158 505600
5000 30 150000
9800 95 931000
- 371 1674600

塵も積もればとはこのことか、と思った。

お遊びの記事なのでソースとかは晒しません。嘘や!と思ったら嘘で良いのです。

成果を見ましょう

自身の年収の半分以上を消費しているので納得のいく買い物が出来ていないと悲しい。

ガシャなんて引く前から期待するものでもないが、一年半通して常に運が良かったわけでもないし悪かったわけでもないアカウントなのでそこそこ強い状態になっているはずだ。

カード状況

SSR の状況はこんな感じ

f:id:apple19940820:20181223233243p:plain

f:id:apple19940820:20181223233314p:plain

f:id:apple19940820:20181223233336p:plain


そういえばプロフィールから活動記録が見られる。Markdown Table でがちゃがちゃ書くよりスクショを貼った方が1億倍分かりやすい。集計を書いていてそう思った。

活動記録

f:id:apple19940820:20181223233847p:plain

カード枚数

f:id:apple19940820:20181223233741p:plain

マスターレッスン

f:id:apple19940820:20181223233914p:plain


余談だけどこのプレーンなテキストのみで構成されたプロデュース情報が好きだ。ごちゃごちゃ整形する必要なんてねえんだぜ!

ゲーム上から得られる情報はこんなもんだろうか。自分は結構最近までラウンジに所属することなくイベントも走って5000位程度の遊び方をしていたため、そちらへお金を盗られることは少なかったはずだ。

ではデータが出揃ったところで振り返りをしよう。

振り返りましょう

ミリシタが始まって暫くの間、性能厨の自分は恒常だろうが限定だろうがガシャ更新されたら出るまで引いていた。当時は天井がなかったがあまり考えずに課金していた。

しかし限定美奈子だけは違った。現在の天井は9万だが、それ以上で殴ってもピクリともしなかったためやむなく撤退した。10万円使って買い物失敗する事がこの世の中にはあるんだな、と思った。

f:id:apple19940820:20181224001038p:plain

それ以降も基本出るまで引くスタイルは貫いていたが、突如現れたアイススケーターと金沢の冷やし金魚に足元を掬われてしまう。今思い返せばミリフェスって大体4日しかなくカスかよって感じがある。

この時期の社会人と言えば賞与だが、満額貰うのは初めてだったため加減が分からず全部使った。

この頃は確か5ターン目に着地するヴォイドロードとか動員からの回廊漁り蟲にキレまくってた気がする。よく覚えていない。あとステラステージとかEDFやってた。

可哀想。。。


そしてミリフェス2回目。ゆうちゃがひもじい生活に入ったり矢吹健太郎がミリラジを聴いていたりする情報が入りつつ無事4凸を終える。

なんかこの時は前月に結構お金使ってたみたいで生活がヤバかった。

f:id:apple19940820:20181224005631p:plain

タダ飯食わせてくれるおかんに頭が下がる。

ここら辺で恒常を追い続ける事に疑問を感じ、職場の先輩にも恒常追うのは自分を金持ちだと錯覚した精神異常者と責め立てられ、遂に所持枠に穴が空き始める。おっしゃる通りです。

f:id:apple19940820:20181224010248p:plain

と言いつつも諦めきれてない感じがあって馬鹿が極まっている。業務では客の無理な要望やら偉い人の思い付きをドヤ顔で弾き飛ばして必要な事だけまずは進めましょうとか言ってる癖に金払いが絡むと頭より手が賢くなってしまう。中高生の頃は小遣いが尽きるまで遊戯王のパックを買っていた。何も成長してないな。

そして1周年記念信号機フェス。この時は死ぬほど運が良くて、過去フェス限も出るし前回のフェス石も余っている事から天井を迎える前に3枚とも4凸を達成してしまった。

あっけなく終わったため UNION を楽しみつつ Go に入門したり React に入門したりしてた。んで問題はこの後で、

f:id:apple19940820:20181224011528p:plain

f:id:apple19940820:20181224011632p:plain

単純な話給料と課金額の釣り合いのしわ寄せが限界に来た。丁度各アイドル SSR の2周目が始まった辺りから引かなきゃいけない限定を悉く逃しまくり、結果 Pr のスコアタはほぼ参加できない状況になっている。

さっきも書いたけど自分は性能厨のきらいがあって、性能厨の行きつく先はスコアアタックなのでそこに参加できないとなるとどんどんモチベも落ちていく。金もないし、まあ引かんでもええかって感じで穴あきリストを眺めているのが直近の自分である。

しかしやよいおりはしっかり4凸した。これも天井へ行く前に引ききってゴールすることが出来た。やよいおりからステータスのインフレが顕著になって今では対応するフェス限は引かなきゃ死みたいな状態になってるよね、スコアタ界隈。


f:id:apple19940820:20181224013748p:plain

Pr が伸びないのが厳しい。 LP4400 の壁はそこにある。

今後を考えました

全然課金を辞めるつもりはない。何故かというと払ってもいいかなという品質を維持してくれているからだ。

自分は金払いの良い方なのは確かだが、現状課金しているのはミリシタと各種技術系サービスと、たまにハースストーンだけ。エッチな広告を挟んで来てそれがいい感じだったらお礼に課金してしまうかもしれないけど、基本アプリの品質評価と自身が利用しているサービス継続のための従量課金くらいだ。

本当はこんな自己満足の課金ひけらかし記事よりもただミリシタのいい所を褒めるだけの記事を書きたかった。いち技術者として見た時、ミリシタはアプデのスタンスやマーケティング、運用管理、UIUX 等すげーな尊敬するなと感じる部分が多い。

ただ褒めたたえてもいいけど顔をスマホにかざすだけで貢献出来るならその方が良い、というのが今の自分の考えだ。


というわけで多分今月末のミリフェスもしっかり回します。ただのデータに馬鹿だなと思ったそこのあなたは間違いなく正常。でも自分が沢山お金払っとくからその分無料で沢山遊んで欲しい。そういうプロデュースも多分ありだと思う。

おわり

エゴサ?それIFTTTで自動化できます

皆さんエゴサしてますか?僕はしません。著名な活動してる訳でもないし、巨大なミリシタラウンジのラウマスしてるわけでもないので。

でもエゴサじゃなくても定期的に検索する事はあります。イベント終わりの反応とかね。

そういうの、一々検索するのだるくないですか?IFTTTならそれ全部自動化できます。

ifttt.com

英語だけど逃げないでね。

IFTTTは様々なサービス間で通知をしあえるっていうサービスで、WebHookに対応してるアプリならどれでも使えます。

WebHookは任意の通知(POST送信)をイベント発生時に飛ばせるって仕組み。これを使えば例えばTwitterに特定キーワードが投稿されたら、そのイベントでWebHookが飛んでLINEの指定した部屋に内容が送られるみたいなことができます。

僕はミリシタの公式アカウントを監視対象にしてツイートがあればLINEに飛ばすっていうのをやってます。普段AppleWatch付けてるのでミリシタ公式アカウントが呟いたら腕に振動が来る状態になってます。

f:id:apple19940820:20181209220012p:plain

使い方も簡単で、有名どころはデフォで色んな機能が用意されてます。

僕のは今こんな感じ。

f:id:apple19940820:20181209220434p:plain

左上と中上がTwitterのつぶやきをLINEに通知するアプレットです。自分が普段LINE使っているのでLINEになっていますが、通知先をWebHookに対応している別アプリにすることもできます。(Slack、discordなど)

iOS/Android アプリ版もあるのでスマホから初めてみるのもいいでしょう。

IFTTT

IFTTT

  • IFTTT
  • 仕事効率化
  • 無料

play.google.com

特にキーワードプッシュは特定キーワードを自分で決めてTwitterで呟けば備忘録のように使えて便利です。([log]みたいに書いてIFTTTで[log]を監視する)

雑ですがIFTTTの紹介でした。それ自動化できるよって事で。

おわり