FF11のルート探索プログラムをウェブアプリにする【AWS App Runner + Flask】

概要

実はSAAとSOAを最近取得できたのだが、構築経験に乏しすぎる耳年増なままDVAやらSAPを取っていくのは意味なさそうなので、色々経験を積んでいきたいと思う今日このごろ。

何かお題はないかと思っていたところ、こないだ作ったNetworkxでFF11のルート探索をするPythonプログラムがCLIベースだと微妙に使いにくいと思ったので、勉強がてらAWSパワーでウェブアプリにしてみることにした。

GitHub - bobfromjapan/FF11_route_finder

FF11のマップ移動が難しすぎるのでグラフ構造を使って解決してみる【NetworkX】 - 端の知識の備忘録

とりあえずまずは味気もなにもないHTMLテンプレートとFlaskを使ってローカルで動くようにしたところ、ちょうどAWSからApp Runnerなる新サービスが発表された。

aws.amazon.com

どうやらBeanstalkのようにEC2やらなんやらが立つスタックとしてではなく、HerokuみたいなPaaSとしてコードと簡単な設定ファイルを書くだけでお気軽にWebアプリを走らせられるというサービスらしい。

あまりにもタイムリーすぎて運命的な何かを感じてしまったので、兎に角一回Flaskで作ったFF11ルート探索Webアプリ、FF11_route_finder ~online~をApp runnerで動かしてみたというお話。

まとめ

  • Webアプリ化はFlaskでやってみた
    • これでフロントエンドも触ったことがあるということで、フルスタックエンジニアの仲間入りと言ってもいいかもしれない(冗談)
  • App Runnerは素人でも簡単にWebアプリをデプロイできる使いやすいサービス
    • GitHubにコードを上げておけばそこから勝手にPull→Buildしてくれるのはとても良い
    • ロードバランサーやオートスケーリングも勝手に作られるが、正直今回の用途じゃ過剰過ぎるのでこれならHerokuの無料枠で十分かも
    • お値段は最小構成(東京リージョン、1 vCPU $0.081 + 2GB RAM $0.009)で1時間$0.09。しかしリクエストを処理していないときはメモリ分の$0.009だけで済むらしいのでほぼ使われなければ月に700円程度
      • なんかのイタズラでオートスケーリングが発動するとデフォルトだと最大25台もインスタンスが動いてしまうぽいので、ちゃんと設定で減らしておくのをおすすめします。

App Runnerはお手軽で良いサービスなんだけども、やはり無料枠欲しいなあという感じ。誰も使わないであろうお遊びアプリで月700円取られるならビールを3本買ったほうがいいに決まっている。

ということで多分すぐ休止状態にしてしまうと思います。

こんな雑なアプリを公開状態にしておくのは怖いし。Flaskがレンダリングしてくれるものは対策してくれているらしいが、本当ならもうちょいちゃんとエスケープとか気にしなきゃならんのだろう。なにより、この辺の知識があやふやな私がお手軽PaaSで公開しているのは危ないだろうという判断である。

uh3dkqvvdz.ap-northeast-1.awsapprunner.com

FF11_route_finder ~online~使いたいという奇特な人はGitHubクローンして自分でローカルなりで動かしてください! コードはこちら

github.com

コードの更新部分

せっかくウェブアプリにするならば、ということで英語でも検索できる機能を追加してみた。

ヴァナ・ディールの地名の和英変換はFF11 Wikiのこちらの表から対訳表(auxfunc/dict.csv)を作成し、グラフを作成するためのyamlファイルの日本語地名を愚直に置換するスクリプトを書いて、英語用のyamlファイルを作成した。

FF11 Wikiは転載、改変自由というのはありがたいことである。

あとは出発地・目的地を入力するフォームや歩きのみで検索するかどうかの選択ボタン、和英選択のラジオボタンを配置しただけの簡単なHTMLテンプレートを作成し、Flaskの簡単なコーディングを行うだけ。その際こちらのサイトを参考にいたしました。

【Flask】formからサーバーサイドにテキストや各種コントロール、ファイルを渡す | たぬハック

ここまで作れば

cd path/to/app
python application.py

を実行するだけでローカルで検索はできるようになる。お金もかからないし、正直ここまでで十分なはずであったのだが……。

f:id:hashicco:20210525223535j:plain

App Runnerで動かしてみる

ほんとに前項のFlaskのコードを書き終えた日くらいにちょうどAWSからApp RunnerのGAがアナウンスされた。

いくつかApp Runner使ってみた系の記事はあったのだが、それらを見るまでもなくコンソールで言われたとおりにポチポチしていくだけで簡単にデプロイできてしまった。

書くほどのものではないのだが何枚かスクショを貼ってメモを書いておこう。


  • 今回はGitHubのコードから行うので、GitHubAWSの連携が必要である。AWS Connector for GitHubというアプリをGitHubにインストールする作業が発生した。

f:id:hashicco:20210525230351j:plain

  • デプロイトリガーは手動を選択。自動デプロイは追加でお金がかかるようである(1 USD/アプリケーション、月額)

  • 設定は別のファイルに記載しておくこともできるよう(App Runner configuration file examples - AWS App Runner)だが、今回はコンソールから設定することにした。

    • ランタイムはPython、構築コマンドはrequirements.txtを作っておいたので、pip install -r requirements.txtでOK

    • 開始コマンドは初めてだったので若干詰まった。公式ドキュメントより、今回application.pyがモジュール名なので、gunicorn application:appとすれば良いらしいことがわかった。

      • ちなみにうまくApp Runnerが起動しない場合、15分くらいでエラーでロールバックが行われる。ステータスはfailedになっていると設定項目の変更はできない(pauseまたはrunningでないとダメと言われる)ので、もうdeleteするしかなくなってしまった。

Running Gunicorn — Gunicorn 20.1.0 documentation

$ gunicorn [OPTIONS] [WSGI_APP] Where WSGI_APP is of the pattern $(MODULE_NAME):$(VARIABLE_NAME). The module name can be a full dotted path. The variable name refers to a WSGI callable that should be found in the specified module.

f:id:hashicco:20210525230812p:plain

  • 最後のサービスを設定ではAuto Scalingの設定だけ変更した。デフォルトだと最大25インスタンスまでスケールするようになっているので、万が一にでもDDoSを喰らおうものなら25倍もお金がかかってしまう。
    • Maxを1にしておけばリクエストが増えようがサービスは応答しなくなるだけで1台しか動かないはず。

ここまで設定すればあとは少し待つだけでWebアプリがAWSのどっかのサーバーで動いてインターネット経由でアクセスできるようになるわけである。

VPCもEC2もロードバランサーもRoute53も何も気にすることなくこれができるのはすごく魅力的。リクエストないときは最低限の課金というのもリーズナブルだし。App Runnerで費用・機能の要件を満たせるなら存分に使うべきサービスのような気がします。まあ会社じゃVPCでくくれない時点で使えなさそうなので私にとっては個人開発用ですね。

ただ、これだと楽すぎて正直なんの勉強にもならない!多分苦しみながらリソース建てたほうが何か得るものがあっただろうと思いました。

今後の展望

いつかは全体マップにルートの線書いたり、エリア別マップのリンクがついたりすると便利そうだなあと思っているがいつやるかはわからない。

あと、これならAmplifyとLambda、APIゲートウェイを合わせたほうがお安く済みそうだし、サーバーレスアーキテクチャの勉強になりそうなのでいつかはこっちも試してみたいと思います。

Arduino Leonardoでエアロバイクをコントローラーにしてヴァナ・ディールを駆け抜ける話

背景

田舎の引きこもり生活は健康に悪い。

東京で長距離通学をしていた頃は否応なしに人波を抜け、歩き、自転車に乗るトライアスロンをさせられていたのだが、田舎に来るとマジで動かない。

しかもコロナのせいで輪をかけて動かないものだからより健康に悪い。

流石にそれはまずいと思い筋トレとエアロバイクを日課にしていたのだが、最近はちょい残業が多かったせいでサボリ気味。すると元々痩せ型であった我が肉体にも徐々に余分な脂がつきはじめ……。

研究職もある程度は健康維持に務めねばならないということで、酒飲んでゲームしかしない休日に運動を無理やりブチ込む仕掛けはないものかと思案していたところ、1つの考えが湧いてくる。

今年一年は間違いなく現実世界よりもエオルゼアとヴァナ・ディールで歩いた量のほうが多いと断言できる。ならば、ゲームの世界をエアロバイクで移動できればよいのでは!?

……ということで、エアロバイクを漕げばW押して前進するようなものをArduino使って作ったろ!を実際にやってみたお話。

まとめ

注意:市販品に変な改造を施しているので、真似するときは自己責任で!

  • コードはこちら

GitHub - bobfromjapan/exercise_bike_as_a_controler: Arduino code for using an exercise bike to control video games.

  • アルインコのAFB4417というエアロバイクが今回犠牲になったブツ

    • こいつは速度・漕いだ距離とかを表示してくれるマイコン的なやつがついているので、ここから上手いこと漕いでいることを判定できる情報を取り出すことにした

    • 最初は出力の3.5mm プラグから何らか電圧とかで取れないかと思ったのだが、結局半田ごてを動員するはめになった

www.amazon.co.jp

  • 使うArduinoUSB入力をエミュレートできるArduino Leonardoを使用

参考URL

こちらのサイトを参考にさせていただきました。

Arduino電子工作の基本⑥ スイッチの状態を読み取る | Device Plus - デバプラ

Arduino Leonardo(Pro Micro)のHID(キーボード)機能を使う(ショートカットキー実行,コマンド実行) - Qiita

1. エアロバイクの改造

まずこのエアロバイク(AFB4417)では本体から3.5mmのプラグが伸びており、それを上部のサイクルコンピューター的な機械に差し込みペダルを漕ぐと、走行距離や速度が表示されるという機能がある。1万で買えた割には多機能である。

で、当初はこの3.5mmのプラグからペダル漕いだときにダイナモかなんかが動いて電圧が発生、それをサイクルコンピューターで読み取っているのかと思っていたのだが、テスターを当てながらいくら漕げども電圧表示は0Vのまま。

サイクルコンピューターには電池を2本入れて動作させるので、どうやら電圧をかけないと漕いでもなんの変化も現れないらしい。なんかこんなの高校の物理のときやった気がするが、今回の目的を達するために原理まで理解する必要はないのでとりあえず置いておくことにする(詳しい人いたら教えてください!)。要はサイクルコンピューターばらして、どっかの端子から漕いだときに何らかの変化を得られればいいわけである。

というわけで、まだ買ってから一年経っていないこいつを躊躇なくバラすことにした。実験には犠牲が不可欠!

中身はこんな感じ。非常にシンプルな基盤だが、私は別にこういうのに強いわけではないのでここもまた実験的手法でなんとかする。いくつかなんか取れそうな端子にテスターを当てながらエアロバイクを漕いで電圧変化が起こる組み合わせを探してみることに。

f:id:hashicco:20210509135355j:plain

すると、3.5mm端子の刺さっている部分の2極にテスターを当てながら漕いでると電圧が変化することがわかった。具体的には漕いでないときは電池由来の3V前後の電圧がかかっているのだが、ペダルを漕ぐと特定の場所を過ぎるときに電圧が下がる。ただ、下がる量は不安定だし漕いでいるときずっと下がるわけではない。

まあ一瞬でも下がってくれればそれをArduinoのAnalogピンに繋いで電圧が閾値を下がったことを検知できるはずなので、ここに無理やり導線をはんだ付けして信号を取り出すことにした。

で、出来上がったのはこんなもの。Amazonで30mも買ってしまって余っていたオーディオケーブルを導線として使ったが、太すぎて取り回しが悪かったので普通の27AWGくらいの導線を使うことをおすすめする。

酒飲みながらやったせいで、めちゃめちゃはんだ付けが雑……。気が向いたら直そうと思う。

f:id:hashicco:20210509141611j:plain

f:id:hashicco:20210509141618j:plain

この導線をArduinoのアナログピンに指すことで、エアロバイクを漕ぐことによる電圧変化を測れるわけである。

2. Arduinoのコードを書く

魔改造は前項までで、ここから先は至って普通のArduino工作です。

流石にペダル漕いで前にすすめるだけでは使い物になりませんので、横への方向転換用にハンドル部分に'A'と'D'のボタンもつけることにした。

配線はこんな感じ。'A'と'D'のボタンはfritzingの都合上ブレッドボードに挿してますが、実際には導線(オーディオケーブル)を2mくらいつけて、エアロバイクのハンドル部分に雑にマスキングテープで取り付けてます。実際の写真は後でのお楽しみということで。

f:id:hashicco:20210510205306p:plain

コードはこちら。'A'と'D'のボタンについてはシンプルに押されたかどうかをプルアップの変化で読み取ります。Keyboard.pressで'A'と'D'押したことにする。ピンで言うと7と8を利用。

エアロバイクの電圧変化はAnalogピンを利用。適当に閾値を3.3Vとしてそれを下回ったときにKeyboard.pressで'W'キーを入力するという感じ。更に、先程述べたように漕いでるとき常に電圧が下がる感じではないので、過去5回分の移動平均をとりそれが閾値を下回るか否かを判定基準とすることにした。

また単押しだと進む距離が短すぎて超漕がないとミリも進まないマゾ設定になってしまうので、press後Delayを入れて100ms押した状態にする。

#include <Keyboard.h>
const int A_PIN = 7;
const int D_PIN = 8;
double v_logs[5] = {3.3,3.3,3.3,3.3,3.3}; 
double thresh = 3.3;


void setup(){
    pinMode( A_PIN, INPUT_PULLUP );
    pinMode( D_PIN, INPUT_PULLUP );
    Serial.begin( 9600 );
}

void loop(){
    int value_a, value_d;
    double value_w = analogRead(A0);
    value_w *= 5;
    value_w /= 1024;

    int i;
    double sum = 0 ;
  
    for (i = 0; i < 4; i = i + 1) {
    v_logs[i] = v_logs[i+1];
    sum += v_logs[i];
    //Serial.println(v_logs[i]);
    }
    
    v_logs[4] = value_w; 
    sum += value_w;
  
    if(((sum/5) < thresh) && (sum > 10)){
      Keyboard.press( 0x77 );
      delay(100);
    } else {
      Keyboard.release( 0x77 );
    }
    
    value_a = digitalRead( A_PIN );
    if ( value_a == LOW ){
      Keyboard.press( 0x61 );
    } else {
      Keyboard.release( 0x61 );
    }

    value_d = digitalRead( D_PIN );
    if ( value_d == LOW ){
      Keyboard.press( 0x64 );
    } else {
      Keyboard.release( 0x64 );
    }

    
}

3. ヴァナ・ディールを走ってみる

てなわけでコーディングも配線も終わったのでいざ実物を!

f:id:hashicco:20210510214602j:plain

……はい汚い。こんなの部屋においてあったら長年の友達でも少し引く。後ろの自作のプラモ塗装乾かしスペースやらプチプチの巻きつけられたダンベルやら相まって怪しい部屋度が50%増し。独身寮だから許される何かが出来上がりました。

せめて導線にもっと目立たない細い配線を使ったり、フレームに這わせたり、なによりマスキングテープは使わないようにしたりすればもうちょいマシでしょうか。

で、肝心のゲーム内の移動に関しては問題なくできることを確認。アルテパ砂漠をラプトルに乗って走る冒険者をエアロバイクで操作するのはなかなか趣があって面白かったし、何よりしっかり運動になりました。

これからもこれ使って寿命を伸ばしつつ健康的にヴァナ・ディール生活を送っていこうと思いました。めでたしめでたし。

Kensingtonの人差し指トラックボール ExpertMouseとSlimBladeを比較する

概要

私は研究室・職場ではトラックボールをずっと使っている。かれこれ4年以上になろうか。

最初はOrbitTrackball with Scroll Ringを使い人差し指トラックボールにハマり、数カ月後には無線のExpertmouseを買っていた。

Amazon | Kensington ExpertMouse ワイヤレストラックボール K72359JP 【日本語パッケージ】 | Kensington | トラックボール 通販

Expertmouseは素晴らしく使いやすく、そこから3年以上ずっと使い続けていた。

Expertmouseには概ね満足であったが、Kensingtonのトラックボールの最上位といえばSlimBladeというイメージが頭について離れない。しかし無線バージョンがないということで購入は見送っていた。

まあ最近は職場でほとんどデスクトップしか使っておらず無線に拘る必要もないかと改心したのと、Paypay祭りで少し安く(実質7000円くらい)買えそうだったのでSlimBladeを衝動買いしたのであった。

Amazon | ケンジントン 【正規品・5年保証付き 日本語パッケージ】 SlimBlade Trackball 72327JP | Kensington | マウス 通販

使い始めて1ヶ月位経ったし、そろそろまともにレビューできそうなのでExpertmouseとSlimBlade2つのKensingtonトラックボールの比較を書いてみようと思う。

まとめ

ということで、1万近い価値は十分あるトラックボールであると断言できる。ちなみに4000円くらいで買えるOrbitTrackballも入門用としてとてもいいぞ。ぜひScroll ring付きをおすすめしたい。

Amazon | ケンジントン 【正規品・5年保証付き 日本語パッケージ】 OrbitTrackball with Scroll Ring 72337JP | Kensington | マウスパッド 通販

  • Expertmouseのいいところ

    • 無線版がある
    • クリック押しやすい
    • スクロールリングがある
  • SlimBladeのいいところ

    • 圧倒的に掃除がしやすい!
    • ボールをひねってスクロールするのは面白い(使いやすいとは言ってない)

必ずしもSlimBladeは上位版ではなく、正直単純な使いやすさならExpertmouseのほうが上だったというのが両方使ってみての感想。

しばらく使っていればなれる程度ではあるが、クリックに関してはSlimBladeが一枚板?的なデザインで作っているせいで、中心から離れた端の部分は可動域が少なく押しにくい。手の大きさや普段どんなポジションで指をおいているかによるが、Expertmouseのどこを押しても同じ感覚で押せるのに慣れていると最初のうちは押しにくさを感じると思う。気になる人は一度ヨドバシなどで実機を触ってから買ったほうが良いだろう。

SlimBladeの特徴であるボールをひねると音が出てそれでスクロールできるというのは面白いギミックだが、長いスクロールなどはやはり径が大きいリングのほうが使いやすい。

ただなんと言っても掃除がしやすいというのは大きなメリットである。多分EM使いの人なら皆わかると思うのだが、ExpertMouseはすごい汚れが溜まりやすいという欠点がある。掃除を怠り数日使ったあとのExpertmouseの支持球とか見ちゃうと正直他人の使うトラックボールとか汚くて触りたくないよね……もしくは私が手垢すごいだけ?

Expertmouseと違ってスクロールリングがない、支持球の構造が違う、ボール受けるところの下が空いているということで、ティッシュ1枚だけでらくらくお掃除できる。

掃除がしやすいというこの一点だけでも乗り換える価値があるということで、今はメインでSlimBladeを使っています!

ギャラリー

玉の大きさはおんなじ。というわけで今まで大事にボナンザ(フッ素コーティング剤)で磨き続けてきたExpertmouseのボールを使うことにした。

比較にトラックボールの中では一番売れているであろうLogicoolのM570も並べてみるとこんな感じ。フットプリントはExpertmouseとSlimBladeであまり違いはないのだが、傾斜がSlimBladeはだいぶ浅め。

なのでどちらかに慣れてからもう片方を触ると、それなりに違和感を感じることだろう。自分もSlimBlade触り始めて数日は左クリックがかなり押しにくく感じていたが、クリックする場所や力が調整できてくると特に問題なく使えるようになった。

f:id:hashicco:20210505151301j:plain

玉の入っているカップ部分はこんな感じ。Expertmouseとは人工ルビー支持球の構造が異なり、壁面に埋め込まれるような形になっているため、ここに手垢やホコリが溜まりにくい。

更にExpertmouseでは底面にあったレーザーの発光部がSlimBladeでは側面に存在するので、これもまたゴミが溜まりにくい構造になっている。

Expertmouseは分解してブラシを使わないと掃除ができなかったのがティッシュ1枚でさっと拭けるのはとても楽です(3回目)。

f:id:hashicco:20210505151233j:plainf:id:hashicco:20210505151250j:plain

【R】 三井住友・楽天のカード明細とUFJの口座明細をRで読み込んで社会人1年目の収支を見てみる

背景

早いもので働き始めて一年も経ってしまったようだ。

学生の時と違うことといえば、とにもかくにもお金が入ってくること。

生まれてきてから溜め込んできた分の欲望を無計画に吐き出すかのように、この1年はとにかく何も考えずにお金を使ったものである。グラボを買った月などはカードの引き落しがその月の給料より高かった気がする。

高い買い物は記憶に残るものの、あまり出費として気に留めない細かい買い物も明らかに増えたし、その「細かい買い物」に該当する金額の幅も広がった。飲むお酒もストロング系からちゃんとした生ビールに、ウイスキーもトリスから酒屋で適当なスコッチを選り取り見取り好きに買い、味の違いを楽しむなんて具合に。酒を趣味にするだなんて少し洒落た話以外、もっと卑近でナードなサムシングの話をするならば、DLSiteの買い物がマジで増えた

いくら使ったか怖くてちゃんと計算してなかったけれども、流石に2年目以降無計画というのは人生楽しけりゃどうにでもなれ思想の私としてもいずれ底しれぬ不安を感じ夜もおちおち寝てられない状態になるのは目に見えているので、Rを使って収支をしっかり可視化してみようというお話。

まとめ

  • 三井住友のアマゾンカード、楽天カード、あと三菱UFJの口座明細のRでの処理方法を書いています

  • 少なくとも私の使っている金融機関で出力できるCSVは基本的にクソUTF-8かと思ったらBOM付きだったり、ひとつのCSVに2つのカードの情報が入っていてヘッダー的な行が途中唐突に挿入されていたり。

    • あと金融機関ごとで全く列に格納される情報もフォーマットも異なるので、それぞれ別々に処理するコードを書かなければならない。標準化とかしないんですかね……。
  • パソコン&ゲームオタクは意外に安上がりな人種。新卒1年目で欲望のままに買い物をしてもちゃんと貯金ができるようだ。

    • なにより会社の寮にいるので光熱費食費住居費が合わせて5万以内というのがでかい。これがなければ貯金額は半分くらいになっただろう
    • 趣味に生きたい人間は、会社を選ぶときにきちんと住宅補助や寮の有無を確認したほうが良いだろうと思いました

CSVをとってくる

それぞれの会員ページからCSVが出力できる。UFJは期間指定で一括で落とせたが、三井住友のVpassと楽天カードはそれぞれ月ごと。

下記のようなフォルダ構成にして置いておきます。

---- rmdファイル
    |---- vpass
    |    L 202004.csv, 202005.csv... ..., 202104.csv
    |---- rakuten
    |    L 202004.csv, 202005.csv... ..., 202104.csv
    |---- UFJ
         L xxxxxxx.csv

CSVの読み込み・前処理

ここが一番大事。楽天カード三井住友カードは12ヶ月分を順繰りに読んでマージ、一つのdfにします。

使う libraryはtidyverseだけで良いので、library(tidyverse)しておく。

三井住友の場合

エンコーディングshift-jisなのでread.csvではfileEncoding="cp932"を指定します。

多分3つの中で一番イケてないcsv。ヘッダーがなく(!)各列になんの情報が入っているのかわからない上、idでの支払いもこんな感じで唐突に挟まる。

支払い者情報のある行だけカンマの数が違うので、そもそも列数が違うエラーが出て読み込みすらままならない。極めてクソである

xx xx 様,xxxx-xxx-xxxx-x***,Amazonマスタークラシック
2020/03/26,xxxx,1628,1,1,1628,
2020/03/30,xxxx,2192,1,1,2192,
xx xx 様,xxxx-xxx-xxxx-x***,三井住友カードiD
2020/03/19,xxxx/iD,267,1,1,267,
2020/03/23,xxxx /iD,3448,1,1,3448,

そのため、読み込みのときは利用日の列が数字で始まるか否かで判定して、filterで唐突に現れる支払い者情報行を弾くことにする。

てなわけで、こんな感じで読み込んでみた。いらない列はselectで省きます。

df_smbc <- data.frame()

file_list <- list.files("smbc/", pattern="csv", full.names=T)

for( i in file_list){
  df_tmp <- read.csv(i,col.names = c("利用日","利用名","利用金額", "item1", "item2","支払総額","note"),skip=1,sep=',', fileEncoding = "cp932") %>% 
            filter(str_detect(利用日, "^[0-9]" )) %>% 
            select(-item1, -item2, -note)

  df_smbc <- rbind(df_smbc, df_tmp)
}

このdf_smbcから

#総額
sum(as.integer(df_smbc$利用金額))
#アマゾンだけ
sum(as.integer(filter(df_smbc, 利用名=="AMAZON.CO.JP")$利用金額))

とかすれば使った金額がわかる。

ちなみに手取り給料6.5ヶ月分くらい使っててビビったのは内緒。やはりちゃんと可視化するのは大事

楽天カードの場合

こちらもなかなかクソなCSVエンコーディングは何ということでしょうUTF-8 with BOM。使いにくいからやめてほしい。

でもまだヘッダーがあるぶんSMBCよりも随分マシである。まあ他のdfと列名の整合性を取るため、自分で列名はつけますが。

ただ分割払いに対応するためなのか、"X月支払金額","X月繰越残高"という列があり、これが月によって変わるのがめんどい。

"利用日","利用店名・商品名","利用者","支払方法","利用金額","支払手数料","支払総額","X月支払金額","X月繰越残高","新規サイン"
"2020/05/27","xxxx","本人","1回払い","40000","0","40000","40000","0","*"
"2020/05/13","xxxx","本人","1回払い","5200","0","5200","5200","0","*"

また、ebayの買い物とかで為替が発生すると、下記のような列が挿入されることがあり邪魔なので、先程同様 利用日の列が数字で始まるか否かのfilterを行います。

"2020/11/20","xxxx利用国USA","本人","1回払い","1000","0","1000","1000","0","*"
"","現地利用額      1000.000変換レート   1.000円","","","","","","","",""

てなわけでエンコーディングと削る列以外はほぼSMBCとおんなじ感じ。

df_rakuten <- data.frame()

file_list <- list.files("rakuten/", pattern="csv", full.names=T)

for( i in file_list){
  df_tmp <- read.csv(i,col.names = c("利用日","利用名","利用者","支払方法","利用金額","支払手数料","支払総額","支払金額","繰越残高","新規サイン"),sep=',', fileEncoding = "UTF-8-BOM") %>% 
            filter(str_detect(利用日, "^[0-9]" )) %>% 
            select(-支払方法, -利用者, -支払手数料, -支払金額, -繰越残高, -新規サイン)  

  df_rakuten <- rbind(df_rakuten, df_tmp)
}
sum(as.integer(df_rakuten$利用金額))

で、楽天カードの利用額もわかる。

こちらが給料2.5ヶ月分でしたので、カードで合わせて給料9ヶ月分も使っていたらしい。怖いね!

三菱UFJの場合

これは12ヶ月分まとめて出せるので、for使わなくて良くて楽ちん。エンコーディングshift-jis。ちゃんとヘッダーもある。

しかし、こいつのめんどくさいところは金額に3桁ごとにカンマが入っているところである。仕方がないのでgsubでカンマを空白に置換し、as.numericで数値化した新規の列を作成することにした。

また、給料や賞与の摘要内容が空になってしまっていたので、if_elseを使って空のときは摘要の内容を入力することにしてみた。

さらに、時系列で残高を折れ線で書いてみたかったので、 gsubで /-にした後as.DateでRで日付として扱えるようにした日付列をdate列として追加した。

df_ufj <- read.csv("ufj/0208339_20210414213549.csv",col.names = c("日付","摘要","摘要内容","支払い金額","預かり金額","差引残高","メモ","未資金化区分","入払区分"), skip=1,sep=',', fileEncoding = "cp932") %>% 
          mutate(payment=as.numeric(sub(",","",支払い金額)), 
                 income=as.numeric(gsub(",","",預かり金額)),
                 balance=as.numeric(gsub(",","",差引残高)),
                 date=as.Date(gsub("/","-",日付)), 
                 item=as.character(if_else(摘要内容=="", 摘要, 摘要内容))
                 )

可視化!

金額わかるのは微妙なので適宜目盛は消していますが、ggplotで可視化。

  • カードの支払いを積み上げ棒グラフで

最初そのままsmbc_dfをグラフにしたら項目数が多すぎて何が何やらわからないグラフになってしまったので、総額のうち占める割合が3%以下の利用名の項目をis_samatsu=Trueとして判定、sonotaという項目に全部ひとまとめにすることに。

ちょっと列名つけるのめんどかったのでperとかperperとかいう適当な列が途中生成されていますが、そこはご愛嬌。

tmp <- df_smbc %>% mutate(per = round(as.integer(利用金額)/sum(as.integer(df_smbc$利用金額))*100, 3)) %>%
                                    group_by(利用名) %>% 
                                    summarise(perper = sum(per))  %>% 
                                    mutate(is_samatsu = if_else(perper<3, TRUE, FALSE))

new_df_smbc <- merge(df_smbc, tmp) %>% filter(is_samatsu==FALSE)

new_df_smbc <- rbind(new_df_smbc, c("others", "9999/99/99", sum(as.integer(filter(merge(df_smbc, tmp), is_samatsu==TRUE)$利用金額)), sum(as.integer(filter(merge(df_smbc, tmp), is_samatsu==TRUE)$利用金額)),"sonota" ))

ggplot(new_df_smbc, aes(x="", y=利用金額, fill=利用名)) + geom_col()

最初めちゃめちゃ使ってたと思っていた例のサイトは、せいぜいヨドバシと同じくらいしか使ってなかったらしい。よかったよかった!

いやでも少なくともRTX3080とRyzen5 5600X買った淀と同じということは、150000近くもいかがわしい何かに使っていたということか?……いや深くは考えないことにしておこう。

f:id:hashicco:20210420222409p:plain

  • 残高の変化を折れ線で

普通にdf_ufjdatebalance列を使って描くだけ。

ggplot(filter(df_ufj, balance!=""), aes(date, balance))  + geom_line() + geom_point()

時々散財の影が見え落ち込みを見せるものの、全体的には右肩上がりの楽しげなグラフである。

f:id:hashicco:20210420221615p:plain

一年頑張った証拠として、皆様もRで収支をグラフ化してみてはいかがでしょうか?

FF11のマップ移動が難しすぎるのでグラフ構造を使って解決してみる【NetworkX】

概要

FF11のマップ、多すぎて覚えられない!

移動手段も経験も豊富なベテラン冒険者の方々にはきっとなんの障害にもならないのであろうが、初心者には

白魔道士の限界突破のために『偉大な白魔道士の証』を取りに行きたいけど、テリガン岬?ソ・ジヤ?ってどこだよ!」

とか

「闇の王を倒しにズヴァール城?たしかザルカバード?ってことは更に隣は……どこだっけ?」

みたいな感じで本筋ではない、その場所への行き方という壁がそびえ立っている。

ウィキもFF11 DBも非常に内容が充実しておりこの上ない情報源ではあるのだが、流石にピンポイントにウィンダスからボスディン氷河に行きたい!みたいな疑問には答えてくれない。

結局地図を辿っていくしかないのだが、土地勘のない場所はデータベースのマップを追うのも難しい。

どうにかならないものかと思っていたところ、丁度仕事で化合物のグラフ構造表現の話をしていたのを思い出した。

マップって正にノードとエッジで表せそうだよね!という考えから、PythonのNetworkxというパッケージを使ってFF11のマップ移動をグラフ構造の最短経路問題として解いてみるというお話です。

似たようなことやっている人はもういそうだけど、勉強がてらということで。

結論

  • ちゃんとFF11のマップはグラフ構造で表現できる
    • とりあえずどのマップを辿れば目的地に行き着くかは探索できるようになった
    • 本当は移動距離の情報をエッジに埋め込めればよいのだが、うまいデータの形が思いつかず断念

f:id:hashicco:20210323213729p:plain

グラフにしてもさっぱりわけがわからない。が、nx.shortest_path(graph, source='ジュノ', target='ズヴァール城')で探索すれば、

['ジュノ', 'バタリア丘陵', 'ボスディン氷河', 'ザルカバード', 'ズヴァール城']

とかnx.shortest_path(graph, source='ジュノ', target='テリガン岬')とやれば

['ジュノ', 'バタリア丘陵', 'ジャグナー森林', 'ラテーヌ高原', 'バルクルム砂丘', 'グスタフの洞門', 'テリガン岬']

みたいな結果が得られるものができた。素晴らしい!

  • データの入力がきつすぎた。ひたすらFF11DBの地図を見ながら繋がっているマップを入力し、計2時間かかった。マップ多すぎ!

    • 入力データをYamlファイルで記述したのは我ながらいい考えであったと思うが、手入力はきつい
    • Pythonコード20行に対してマップ定義のyamlファイルが1262行という有様である。ほんとはある程度座標も実際のマップに即して書きたいが、流石に面倒すぎる。
  • このデータ使ってElastic BeanstalkでWebアプリとか作ったらいい自由研究になりそう。……だけど今更利用者がいない気がする。

コード

GitHub - bobfromjapan/FF11_route_finder

Githubにも置いておいたので、なんかミスがあったらPR投げてください。

こんな感じのYamlファイルとしてそれぞれのエリアから行き先を記載していき、これをパースしてあるエリア→目的地というノード-エッジ-ノードをひたすら作って行く感じ。

あと、飛空艇や船のノードの重みを増やしておくことで、グラフで表示するとき重要都市を真ん中あたりに持ってこようというちょっとした工夫もしている。

areas:
  - name: サンドリア
    survival_guide: True
    home_point: True
    distination:
      - name: 東ロンフォール
        weight: 1
        transportation: walk
      - name: 西ロンフォール
        weight: 1
        transportation: walk
      - name: ドラギーユ城
        weight: 1
        transportation: walk
      - name: ジュノ
        weight: 100
        transportation: airship
  - name: バストゥーク
    survival_guide: True
    home_point: True
    distination:
      - name: ツェールン鉱山
        weight: 1
        transportation: walk
      - name: 北グスタベルグ
        weight: 1
        transportation: walk
      - name: 大工房
        weight: 1
        transportation: walk
      - name: ジュノ
        weight: 100
        transportation: airship
  - name: ウィンダス
    survival_guide: True
    home_point: True
    distination:
      - name: 東サルタバルタ
        weight: 1
        transportation: walk
      - name: 西サルタバルタ
        weight: 1
        transportation: walk
      - name: 天の塔
        weight: 1
        transportation: walk
      - name: ジュノ
        weight: 100
        transportation: airship

Pythonコードは適当だけどこんな感じ。

工夫としては日本語で書いたyamlを読むためにcodecsを使ってファイルオープンしたこと、パースしたyamlを上手いことfor文で回してエッジを追加したことでしょうか。

import networkx as nx
import codecs
import matplotlib.pyplot as plt
import yaml

walk_only = True

graph = nx.DiGraph()

with codecs.open("areas.yaml", "r", 'utf-8') as yml:
    net = yaml.load(yml, Loader=yaml.SafeLoader)

for i in net['areas']:
    for j in i['distination']:
        if walk_only:
            if j['transportation']=="walk":
                graph.add_edges_from([(i['name'], j['name'], {"transportation" : j['transportation'], "weight":j['weight']})])
        else:
            graph.add_edges_from([(i['name'], j['name'], {"transportation" : j['transportation'], "weight":j['weight']})])


# pos = nx.spring_layout(graph, k=1.2)
# nx.draw_networkx_nodes(graph, pos, alpha=0.6, node_size=500)
# nx.draw_networkx_labels(graph, pos, font_size=4, font_family="MS Gothic")
# nx.draw_networkx_edges(graph, pos, alpha=0.4)
# plt.show()

print(nx.shortest_path(graph, source='ウィンダス', target='ムバルポロス新市街'))

一番下のSourceとTargetを書き換えれば、任意のルートの探索ができる。

また、walk_onlyをFalseにすれば、汽船や飛空艇を使ったルートも探索できるはず。

例えばwalk_only=Trueで source='ウィンダス', target='ムバルポロス新市街' なら

['ウィンダス', '東サルタバルタ', 'タロンギ大峡谷', 'メリファト山地', 'ソロムグ原野', 'ジュノ', 'バタリア丘陵', 'ジャグナー森林', 'ラテーヌ高原', 'バルクルム砂丘', 'コンシュタット高地', '北グスタベルグ', 'ムバルポロス旧市街', '2716号採石場', 'ムバルポロス新市街']

てな感じ。ちなみにムバルポロスとか行ったことないのでこの道が正しいのかわからない笑

また、walk_only=Falseにすればウィンダス→ジュノ間とジュノ→グスタベルグを飛空艇でショートカットしてくれるので、

['ウィンダス', 'ジュノ', 'バストゥーク', '北グスタベルグ', 'ムバルポロス旧市街', '2716号採石場', 'ムバルポロス新市街']

となるよう。まあなんとなくあってそうな感じですね!めでたしめでたし。

Ryzen5 5600XとRX6900XTでFF14 4K 最高品質の(ほぼ)60FPS張り付きプレイが実現できる

概要

こないだRTX 3080とRyzen5 5600XでFF14 4K 最高品質の60FPS張り付きプレイが実現できるという記事を書いた。

hashicco.hatenablog.com

RTX 3080からRX6900XTに買い替えてからすでに2ヶ月もたったようなので、使用感を記す。

最近の週末は割とずっとFF14をやっており、このRX 6900XTとRyzen5 5600XというAMD製最新CPUとGPUの組み合わせでどれだけ快適にゲームができるかを、FPSの計測で確かめたというお話。

結論

  • RX 6900XTとRyzen5 5600Xの組み合わせによって、4K 最高品質でもほぼずっと60FPS張り付きが実現可能!個人的には満足な性能。

    • 3080よりもFPS出ない……。まあNvidia寄りのゲームだししゃーないか
    • でも5600Xも6900XTも発熱が少ないのが素晴らしい!どんなに上がっても60度以内で収まる今回のTSMC7nm AMDは良い。
  • しかしなによりも割と頻繁に「DirectXで致命的なエラーが発生しました」というエラーが出て落ちるのがつらみ……。フォーラムでも報告されているように、Radeon特有の問題のようである。

    • 仕方無しに4K最高品質から4Kデスクトップ高品質に落としてみたらなんとなく安定しているような気がする。最高品質で入る処理の一部で起きやすい問題なのかもしれない。

forum.square-enix.com

測定環境

  • CPU: Ryzen5 5600X
  • GPU: Biostar Reference RX 6900XT
  • MB: MSI B550 MPG Gaming Plus
  • SSD: HP EX950 500GB
  • Driver: 20.12.2
  • monitor: Acer ET322QK(Adaptive Sync有効)

使用ソフトウェア: Fraps

fraps.com

これでプレイ中ずっとFPSを記録してみました。

結果

プレイ中ほぼ常時60FPSに張り付いていました。戦闘中でも街中でも

ただ、若干RTX3080よりもFPSは低めで、50-60FPSを行き来している感じ。

今後FF14を4Kで快適にプレイしたい人は(エラーさえ出なければ)RX6900XTも十分通用するが、正直RTX3080をおすすめします。

不定期FF11日記① 新米冒険者、2回洋上で死す

概要

私は小2のときからFFをやってきた生粋のFFプレイヤー。ナンバリングはもちろん、割とほとんどの外伝シリーズもプレイ済みである。

そんな私でも唯一やったことの無いタイトル、それがFF11であった。

中高生のときはお金がなくて月々金を払ってゲームするというのはできなかったし、大学生になってからはFF14始めてしまったので流石に2つも同時にMMOをすすめる余裕はなかった。

隙あらばFF11をやってやろうと思っていたのだが、FF14の情報発表会で次の大型パッチが今年9月からという案内があった。

丁度ボズヤも青魔も終わり凪に入ったところだし、やるなら今しかない!ということでFF11の無料体験を先日から始めたのであった。

ヴァナ・ディールに降り立ち15時間、FF11の洗礼を浴び続ける

登録の仕方やスクエニアカウントとの結びつけかたが分からなかったり、いざ初めて見ると解像度が荒すぎて4KにするもHUD小さすぎて見えなくなったり、箱コンさしても動かなかったりと、始めるまでも様々困難があった。

そしていざ始めてみたもののしばらくはマップの見方がわからずどこ行っていいんだがわからなかったり、ウィンダスの街が広すぎて迷子になったり、Seleleさんのミッション終わったあとどこからメインミッション進めればいいのか分からなかったり……。

最近ユーザーフレンドリーなぬるいゲームをやりすぎて緩んでいた私は、本格MMOの洗礼を浴び続けるのであった。いや、でも始めたばかりでミッション見ても

・ヴァナ・ディールの星唄

冒険者は知る、己の運命を……。

ヴァナ・ディールの運命を……。

しか書いてないんだからね!マジでちゃんと住人の話を聞いていかないと、次何していいのか分からなさすぎる。

FF14のメテオマーク追っていくだけでメインが進むのは今思えば凄い親切だったんだなあ。

とはいえ何も指針無しでこれを進めていくのはしんどい。ということでググってみると素晴らしい記事が!

note.com

丁度ウィンダススタートで序盤の流れを説明してくれている!これ幸いとこの記事をなぞりながらエミネンス・レコードを開放し、フェイスを開放し、どうやらこうやらウィンダスミッションを進めていくことができたのであった。

順調と思われた旅の矢先……

FF11始めたけどほぼ歩いてしかいない……。戦闘ほぼフェイス任せで戦ってる実感ない……。でも冒険してる感楽しい!位な感じでいつの間にかプレイ時間が15時間を突破。

次のミッションはウィンダス2-3「三大強国」。どうやら、船に乗って他の国に行くらしい。14で言うところの3国回るミッションみたいなものだろうか?

ウィンダスより東サルタバルタを駆け抜け、タロンギ大峡谷を超え、ブブリム半島へ。敵は同レベル帯なんでそんなに苦戦せずマウア入りまでは行けました。そう、マウラ入りまでは……。

マウラは予想外に人の多い街だった。人の密度が高いからだろうか、ウィンダスよりもずっと賑わっているように感じる。流れてくるログを見れば、補助魔法を掛け合う人や、謎の本を開き続ける人。

意気揚々と船着き場に向かう。ヴァナ・ディール時間で1時間でセルビナ行きの船が来るとのこと。おー船に乗るにもリアルに時間がかかるのかーと感心しながら、受付に話しかけて乗船!

……まさか移動手段たる船の上が死地となろうとは、ヌルゲーに慣れきった新米冒険者は予想だにしていなかった。

洋上の死闘!群がるとてもつよい骸骨たち!

セルビナまでは約10分で着くらしい。おー船に乗ってからもリアルに時間がかかるのかーと感心しながら(2回目)暇つぶしに船内を散策するLv25の白魔道士

客室には何もなし。甲板に上がってみると魚類のモンスターがいた。調べてみると「同じくらいの強さ」とのこと。暇なのでこいつを倒してレベリングしておこうと、フェイスでいつものクピピ・ヴァレンラール・テンゼンという心強い仲間たちを召喚。

サクサクと魚を2-3匹狩ったところだっただろうか、リポップしてきたモンスターの様子がおかしい。時間が夜になったからなのか、甲板はいつの間にか魚ではなく骸骨に支配されていたのであった。

強さを調べてみると「とても強そうだ」。うーん流石に手を出すのはやめた方が……?と思いつつも到着まであと8分以上残っているのでこの時間を無駄にしたくない。

ということで、少し離れたところからケアルで釣って1体ずつ倒してみることにした。船長室横に陣取りケアルをスケルトンへ詠唱。戦闘開始!

流石にとても強いだけあって一撃が痛い。さっさとヴァレンラールに釣ってもらって、クピピに回復してもらいつつ、テンゼンが削る。私は軽く棍で殴りながら応援していただけだが、順調に半分くらいまでスケルトンの体力は減っていった。

しかし、このまま押しきれるかと思った矢先事件が起こる。スケルトンが範囲攻撃を連打してきたのである。それだけなら別に問題ない。私も仮にも白魔道士なので、クピピの3分の1くらいのHPSで回復ができるのである。体力も半分以下になってしまったので、いそいそと覚えたてのケアルⅡを詠唱し始めるが……、何故か詠唱が妨害される

タゲ取ってないはずなのになんで?と思って焦っていると、何故か甲板真ん中くらいにいたはずの2匹目のスケルトンが船長室横まで来て私を殴りかかってきているではないか! なんで!?と再度思ったところ、見る見る間に減るHP。クピピさんがケアルⅢを詠唱してくれているが、一瞬赤くなったHP。するとなんともう1体遠ーくの方からスケルトンが来ているではないか!

もう勝てない!と思い急いで船長室に駆け込んでみるも船長はこちらに目を配ることすらなく気にせず舵を握り続けるし、スケルトンの魔法は扉を貫通して私に襲いかかる。

そして、そのままハゲタトゥーヒュームの白魔道士は、3体のとても強い骸骨に蹂躙されるのであった

……あとから知ったのだが、骸骨たちは生命感知なんだそう。HPの減り具合に応じて感知範囲が広がるらしい。このように生命感知骸骨に嬲り殺されることはよくあるらしく、その恐ろしさを身を持って体験したのであった。

リベンジ!再びの定期船

更に、ここで私はミスをしていた。新しい街についたというのにマウラでホームポイントに触れていなかったのである。

絶望……。ウィンダスより東サルタバルタを駆け抜け、タロンギ大峡谷を超え、ブブリム半島へと、さっき書いたのと全く同じ文章が使い回せます。

また片道30分の道程を駆け抜ける羽目になった後、再びマウラに入り今度はすかさずホームポイントに触れる。そして迷うことなく船着き場へ向かい2度目のセルビナ行きの定期船を待つ冒険者

少し待って再度の乗船。今度は骸骨には手を出さん!と心に決め、またレベル上げのために甲板へ躍り出る。えいやとプギルに殴りかかる白魔道士!……しかしまたも悲劇が。なんとフェイスを召喚し忘れていたのである!

そして、装備を更新していなかったせいなのか……。普通に同レベル帯のプギルにタイマンで殴り負けたのであった。

……タイトル通り2回洋上で死んだことにより3国に行く前に心が折れかけたものの、今度はマウラのホームポイントへテレポができる!ということで、迷うことなく500ギルを支払う。そして今、この日記を3度目のセルビナ行きの航路で大人しく客室に籠もりながら書き始めるのであった。

今回の反省

  • 生命感知には気をつけろ

  • 戦う前にフェイスは絶対出すべし

ちなみにこのフェイス出し忘れミスは何回もやることになるのだがそれはまた別のお話。

今更のFF11はこういった若干の理不尽さといい意味での面倒くささを思う存分味わわせてくれる。昔ながらの骨太RPGをやっている感じで大変懐かし楽しい。完全ソロでやってるのでMMO感は全然ないのだが、せめてメインシナリオクリアするくらいまではやろうかなと思います!