mizdra's blog

ぽよぐらみんぐ

Google Play Music 向けの #NowPlaying 拡張機能を公開しました

はじめに

Google Play Music 向けの #NowPlaying 拡張機能, ありそうで無かったので作りました.

chrome.google.com

上記の拡張機能をインストールすると, 右下に曲のシェアボタンが表示されるようになります.

f:id:mizdra:20180523010803p:plainf:id:mizdra:20180523010800p:plain
右下の共有ボタンを押すと共有するSNSメッセージを編集する画面に遷移する.

サポートされるSNS

サポートしているSNSTwitterのみです. 現状他のSNSに対応するつもりは無いので, もし他のSNSにも共有したい人がいれば以下のGitHubリポジトリをForkするなりPullRequest送るなりしてもらえればと思います.

github.com

その他

拡張機能, シュッと作れると思っていたらChromeウェブストアで利用するプロモーション用の画像を用意する必要があったり, ロゴのデザインが指定されていたり, デベロッパー登録手数料として$5払ったりする必要があってちょっと面倒だった… コード書くのは大分速くなった気がするので, そろそろデザインもシュッとできるようになりたいですね.

おわりに

インストール!!! 今すぐ!!! シェアボタン連打!!!

WebAssembly 開発環境構築の本を公開しました

はじめに

Rust を用いて WebAssembly の開発環境を構築する手法を紹介する電子書籍を執筆・公開しました. WebAssembly へのコンパイルが可能な言語である Rust を用いて, WebAssembly の開発環境のテンプレートを作成する内容となっています.

wasm-dev-book.netlify.com

本のタイトルからは一見すると C/C++ を用いた開発環境の構築も扱うように受け取れますが, 本書では Rust のみしか扱っておりません. ご注意下さい.

書籍の執筆動機

著者が春休み中に WebAssembly を用いて Web アプリケーションを作成する機会があり, 本書はそこで得た知見を纏めたものとなっています *1. 元々大学のサークルで発行した部誌に同じテーマで記事を書いており, 本書ではそれを Web 向けに編集・加筆した内容から構成されています.

ここ最近 WebAssembly が急速に注目されており, 既に C / C++ / Rust などで利用することができるほか, 記事執筆時点でGolang でも WebAssembly のサポートが実装作業中となっています. また, 2017 年になって主要なブラウザ全てで WebAssembly がサポートされ, 2018 年に入って主要なモジュールバンドラで WebAssembly がサポートされるようになりました.

こうした需要の高まりに合わせて本書を公開することで, WebAssembly に興味を持っている人が WebAssembly を触り始めるきっかけになればと考えています.

書籍の目的

本書は Rust を用いて, WebAssembly の開発環境のテンプレートを作成する手法を学ぶことを目的としています. 本書の目的 の節には次のように書かれています.

  • Rust を用いた WebAssembly の開発環境を構築する手法を知ること
  • モジュールバンドラと連携する方法を知ること
  • TypeScript と連携する方法を知ること
  • WebAssembly を利用していて陥りやすい罠を知ること
  • WebAssembly に関する情報を調べる方法を身につけること

反対に, 次のような内容は本書では扱いません.

  • WebAssembly / Rust / JavaScript の文法や機能の解説が目的ではない
  • JavaScript のエコシステムの解説が目的ではない
  • C / C++ を用いた WebAssembly の開発環境を構築する手法の解説は目的ではない
  • WebAssembly や JavaScript の仕様変更の追従が目的ではない

本書の「Rust を用いた WebAssembly の開発環境を構築する手法を知る」というテーマに沿った内容を扱うため, テーマから外れた内容は扱わないことにしています. 読むにあたって足りない知識は本書の はじめに の節に書かれているサイトを参考に各自補完していって下さい.

また, WebAssembly は Web の最先端の技術の 1 つであり, 仕様や関連技術の変化が非常に活発です. したがって仕様変更など WebAssembly 関連の最新情報を本書に反映することは困難であり, これらの変更の追従は本書では行いません.

書籍の目次

本書の内容をざっと確認できるように目次を貼っておきます.

  • WebAssembly 開発環境構築の本
    • 本書の目的
    • 本書でやらないこと
    • 本書の対象読者
    • 本書の読み方
  • 著者への寄付
    • 寄付先一覧
    • 著者の情報
  • はじめに
    • 本文章を読むにあたって
    • 開発環境について
  • WebAssembly 入門
    • WebAssembly を試す
    • WebAssembly から JavaScript の関数を呼び出す
    • Rust のサードパーティ製ライブラリの利用
    • 本節のまとめ
    • 参考文献
  • Parcel の利用
    • Parcel を試す
    • Rust のサードパーティ製ライブラリの利用
    • Parcel を採用する際のデメリット
    • 本節のまとめ
    • 参考文献
  • Webpack の利用
    • Webpack を試す
    • WebAssembly から JavaScript の関数を呼び出す
    • Rust のサードパーティ製ライブラリの利用
    • コレクション, 文字列の受け渡し
    • 本節のまとめ
    • 参考文献
  • TypeScript との連携
    • Webpack プロジェクトへの導入
    • 本節のまとめ
  • 関連ツール・サービスの紹介
    • alexcrichton/wasm-gc
    • ashleygwilliams/wasm-pack
    • WebAssembly Studio
    • koute/cargo-web
    • DenisKolodin/yew

書籍自体の開発について

本書は GitHub 上で管理されており, MIT License にて公開されています.

github.com

ここでは書籍自体の開発に使用したツール, サービスを紹介します.

電子書籍の作成に VuePress を使用しています. VuePress は技術文章向け静的サイトジェネレータです. 似たような技術文章向け静的サイトジェネレータに GitBook などがありますが, それらに対して VuePress は DOM のレンダリングVue.js を使用しているという特徴があります. そのためドキュメントの Single Page Application (SPA) 対応, ドキュメント内での Vue.js コンポーネントの埋め込み, デフォルトのコンポーネントの拡張などの機能が提供されています . また, Google Analytics の統合や rogressive Web Application (PWA) のサポート *2 などの機能もあります.

本書は VuePress により SPA が有効化されており, 画面遷移がこれでもかという程滑らかになっています. 是非体感してみて下さい. ちなみに本書では PWA は無効化しているのでご注意下さい.

vuepress.vuejs.org

qiita.com

本書は VuePress により拡張された Markdown 形式で書かれており, Prettier によるフォーマットが適用されています. 執筆中も エディタのプラグインと連携して Markdown を保存する度にフォーマットが適用されるため, 快適に執筆できました.

prettier.io

書籍のデプロイには Netlify を使用しており, git push する度に自動で Netlify のサーバ上で書籍がビルド・デプロイされます. npmYarn の強力なサポートのお陰で数クリックでデプロイ出来る上, SPA のサポート, コミット / ブランチ / PullRequest ごとの Deploy Previews の作成などの機能があります.

www.netlify.com

著者への寄付について

本書は著者の春休みの 1 ヶ月間とゴールデンウィークの 1 週間, 合わせて約 40 日間を費やして執筆されました. もしあなたが本書を読んで著者の活動に関心を持ち, 支援したいと思って頂けたのなら, 本書の 著者への寄付 の節にあるリンクから著者に寄付できます. 寄付は著者の今後の活動に有効に活用させて頂きます 😄 *3

おわりに

以上が WebAssembly 開発環境構築の本の紹介となります. いかがでしたでしょうか. 本書を読んで WebAssembly を触る人が 増えてくれると良いなと思っています. それと, Twitter などで #WASMの本ハッシュタグを付けて感想や意見などを書き込んでもらえると著者が喜びます.

それでは WebAssembly 開発環境構築の本 をよろしくお願いします.

*1:当該の Web アプリケーションはまだ完成していませんが, 完成次第別の記事で取り上げる予定です.

*2:技術文章ごときに PWA が必要なのかという疑問はありますが, オフライン環境下でタブレットなどを利用する場合は有用かもしれません.

*3:漫画や Blu-ray Disk も著者の今後の活動に活用されます 😛

遅刻可視化ツールの紹介

はじめに

個人開発で遅刻可視化ツールを作って Dentoo.LT #19 で話してきたのでその紹介です.

www.slideshare.net

遅刻がテーマということでアイコンはナマケモノをモチーフにしたポケモンである「ナマケロ」にしました. ちなみに「Slakoth」はナマケロの英名です.

モチベーション

  • 大学生活において1日の内最初の授業にどれくらい遅刻したかを可視化したい
    • 生活習慣という面で1年を気軽に振り返りたい
  • ちょっとしたデータ分析をしてみたかった

概要

Googleロケーション履歴*1」の生データを利用して通勤, 通学の遅刻をチャートで可視化できます. ツールにロケーション履歴をJSON形式で読み込み, 始業時間や通勤先/通学先を設定すれば, それを元に遅刻回数/遅刻時間/遅刻率をチャートで可視化してくれます.

使い方

  1. 事前に 自分のデータをダウンロード からJSON形式でGoogleロケーション履歴の生データをダウンロードしておく
  2. I Found Slakoth!!! にアクセス
  3. ロケーション履歴の読み込む からJSONファイルをツールに読み込ませる
  4. 始業時間を追加 から遅刻の基準となる1日の最初の授業開始時刻や始業時刻を入力する
  5. 必要に応じて 始業時刻一覧 から 3 で入力した時刻を編集, 削除する
  6. 通勤先/通学先の設定 から図形ツールを使って通勤先/通学先の領域をポリゴンで囲う
    • 検索ボックスから場所を検索してそこに飛べる
    • 頂点をドラッグで頂点を移動
    • 頂点と頂点の間にあるグレーアウトした丸をドラッグすると頂点を追加
    • 頂点を右クリックで頂点を削除
    • ポリゴンをダブルクリックでポリゴンを削除
    • ポリゴンは複数個作ることができる
    • 右上のボタンを押せば全画面で図形ツールを操作できる
  7. チャートチャートを出力 ボタンから入力したデータを元にチャートを生成
    • チャートで表示できるデータの種類は 遅刻回数/遅刻時間/遅刻率 の3つ
    • 月ごと, 週ごと, 曜日ごと の3つの分類方法が選択できる
    • 下に表示されているバーを弄ればチャートに表示するデータの期間を範囲指定できる

裏でロケーション履歴をどっかに転送して盗み見るなんてことは一切していませんが, 怖いなと思った方はオフライン環境下でやると良いです. まあそもそもツールを使わないというのが一番安全ですが.

f:id:mizdra:20180314232328g:plain
デモ用のロケーション履歴を用いたチャートのデモ

応用

  • 通勤先/通学先を職場や大学以外に設定してみる
    • 例えば電車通勤・通学者であれば最寄り駅の先の駅をポリゴンで囲っておけば寝過ごし回数を数えることができる
    • 北海道には何回行ったか, 海外には何回行ったかなどを調べることもできる

下の画像は僕のロケーション履歴を用いて可視化した「帰路における寝過ごし回数」です. この時期は試験で忙しかったんだなとか, この時期は余裕があったんだなとかが分かって便利です.

f:id:mizdra:20180314232849p:plain
帰路における寝過ごし回数

技術面

アプリケーションを作る上で利用したライブラリなどを書いておきます.

おわりに

*1:AndroidiOSGoogleロケーション履歴を有効化しておくと, 自動で位置情報が時刻と共にGoogleアカウントに記録できるサービスです

Vue.js+TypeScriptを試した際の雑感

Vue.js, 良いですよね. ドキュメントも充実しているし, 読みやすいし, 個人開発する分には素早く楽しく開発できてとっても良いライブラリだなと思っています.

ただ, 開発をしているとやっぱり「型が欲しい!」という気持ちが生まれてしまうものです. そういう経緯でVue.jsにTypeScriptを上手く結合しようと色々調べたので, ここではその時に出てきた雑感を箇条書きで纏めておきます. Vue.jsはダメだとかTypeScriptは悪とかではなくて, ただのメモだと思って読んでもらえればと思います. 「その問題, これで解決するよ」とか「こんな方法あるよ」とかあればコメントなり @mizdra へのリプライなりで教えて頂けるとありがたいです 🙏

前提

  • エディタ: VSCode
  • Webアプリケーションの個人開発をしたい
  • コンポーネント指向
  • 型が欲しい
    • クラッチになるのでFlowではなくTypeScriptで
  • 楽しく開発したい

雑感

要約

  • 問題が多く, まだ厳しい
  • 問題は沢山あるけど, 解決しようという動きはあるので継続して追っていきましょう

未解決の問題

解決済みの問題

代替手段

参考

2017年を振り返って

"今年"は2017年, "来年"は2018年のことを指します.


去年同様, 今年も1年の振り返りをします. 例年通り雑にやっていきます.

今年も年が明ける前に投稿出来なかった. 残念.*1

1月

この頃は去年の12月にやっていたReact+ReduxでSMの乱数調整補助ツールを作る作業の続きをしていました.

12月後半はReact+ReduxでSMの乱数調整補助ツールを作っていました. これは現在も製作中で, 暫くしたら公開できるかと思います. どうぞお楽しみに.

2016年を振り返って - mizdra's blog

結局時間があまり取れなくてこのプロジェクトの開発は停止中です… スマホから乱数調整ツールを使いたいという要望が多いのでWebアプリでサクッと作ってしまいたいという気持ちはあるので, 今後何かしらやるかもしれません. 多分…

ReduxというかFlux, この時初めて触ったのですが得られるものも大きいが失うものも大きい…という印象を受けました. ボイラープレート辛い… 最近Flux周りは一切触ってないので来年は色々試してみたいですね.

2月

2月のある日, うっかりcd src/c src/typoして大事故が発生しました.

これは, 僕のzsh環境ではalias c="git checkout"という一文字エイリアスが登録されており, c src/git checkout src/に展開されてsrc/ディレクトリの内容がHEADへと巻き戻される==コミットしていないsrc/ディレクトリの変更が吹っ飛んだという話です. git checkoutが破壊的なコマンドだなんて知らなかったんです… うう… *2

それとyysk.herokuapp.comという超便利Webサービスを発見しました. 最近はこれを使ってゆゆ式ライフを満喫していて完全にオタクです. 皆さんも一緒にゆゆ式ライフを満喫しましょう*3.

3月

Nintendo Switchが届いたので「ゼルダの伝説 ブレス オブ ザ ワイルド」というげーむをやりました. とっても楽しかったです.

あと, Dentoo.LT #16 で登壇してVue.jsの話をしました.

4月, 5月, 6月

この頃はサークルで「Scala Collection Library Code Reading」というScalaのコレクションライブラリのコードをひたすら読む会に参加していました. 4月中はScalaの基礎を学び, 5月以降からコレクションライブラリのコードを読んでいました. 僕は今までScalaに触れたことはありませんでしたが, おかげで複雑でないScalaコードであれば読み書きできる程度にはなりました. 「ListStreamの構造はこうなっているのか〜」, 「implicitってこうやって使うのか…」, 「call by need, loop detection, 戻り値同型の原則, なるほど…なるほど…」などと言っていました. 普段JavaScriptしか書いていないので関数型言語に触れられたのは非常に良い体験でした.

7月

イカを買ってしまいました*4.

Switch, 結局最近はイカ専用機になってしまっているのですが, イカは楽しいので満足度は非常に高いです. ゲーム起動して数分でサクッと遊べるの最高ですよね. 無限に時間が溶ける.

8月

この頃からEmtimerの開発をしていました.

開発の動機は既存のFlash製のタイマーをリプレースしたい等色々あったのですが, 単純にVue.jsが良さそうだったのでそれを使って何か作ってみたいなというものがありました. この考え自体は3月にVue.jsでタイマーを作る話をした時からあって, やっと夏季休業で時間が取れたので開発を始めたという感じです.

Vue.jsを触ってみた感想としてはとりあえずVue.jsが提供してくれている機能だけでも十分アプリケーションは作れるんだなあという感じです. ただ, (最新のバージョンで多少マシにはなりましたが)TypeScriptのサポートが微妙だったり, ツール周りのサポートが不十分(特にLintなどの静的解析周り)だったりしたので, TypeScriptでガチガチにやりたいならVue.jsはちょっと辛いかも…とは思いました. まあ公式もこのあたりの問題を改善しようと努力しているようなのでいずれ時間が解決してくれると思います.

それと家のトイレが新しくなりました. 扉を開けたらパカーッと便蓋が開くすごいやつです. 便器の中が光ったりします. ピカー.

Dockerの勉強もしました.

mizdra.hatenablog.com

9月, 10月

Headless ChromeのラッパーライブラリであるGoogleChrome/puppeteerが公開されたので, それを使って自動車学校の技能教習の予約が空いたら通知するスクリプトを書きました.

mizdra.hatenablog.com

そう言えばウェブスプレイピングやるの初めてでしたね. 個人的に満足のいくものが出来たので良かったです.

11月

11月は8月からコツコツ作っていたEmtimerを公開しました.

RT: 170+, Like: 350+(記事執筆時点) と非常に多くの反響がありました. ありがとうございます🙇🙇🙇 お陰様でEmtimerは40日程で約4000ユーザ*5が利用しています. セッション数にすると約1.4万件です. 今後も開発を継続していく予定ですので, どうぞよろしくお願いします.

mizdra.hatenablog.com

12月

Pokémon RNG Advent Calendarの季節です!!! 今年もやりました!!!

adventar.org

今年も無事埋まったので本当に良かったです*6. めでたい 🎉🎉🎉 参加してくださった方々, ありがとうございました🙇🙇🙇

僕が書いた記事は以下の3つです. 是非読んで下さい.

アニメを振り返る

今年からアニメの振り返りもしてみようと思います. 僕がこの1年で観たアニメで「良い」「良すぎる」などと感じたアニメを列挙するコーナーです. *7

良い << めっちゃ良い << 良すぎる << 最高 の順で評価が高いです.

秋は最高だった… 特に宝石の国は毎回最高って言っていた記憶があります. 早く2期が観たい…

GitHubを振り返る

去年

f:id:mizdra:20170101003302p:plain

今年

f:id:mizdra:20180101002819p:plain

去年から80 contributions程増えました. めでたいですね. 8月〜12月は大体Emtimer関連のcontributionsでした. この調子で来年も頑張っていきたいと思います.

おわりに

2017年は沢山頑張った気がします. 最近は自分の技術力が上がってきて開発スピードが以前よりかなり速くなったのを実感するようになりました. 良いことです. 2018年は2017年よりさらにやっていきを加速させていきたいと思います*8. 2018年もやっていきましょう.

*1:3年連続年内投稿失敗している気がしますが気のせいでしょう.

*2:この事故が発生して以降, 「alias c="git checkout"」は削除しました

*3:姉妹サービスに zoi.herokuapp.comがありますが, 記事執筆時点では落ちているようです. 残念…

*4:この行為が後にあんなことになるとはこの時はまだ誰も知らなかった― (イカのやり過ぎで無限に時間を消費するやつです)

*5:Google Analyticsより

*6:枠が予約されているものの書かれていない日がいくつかありますが, 気のせいです. 気のせいです.

*7:ちなみに僕が見たアニメはanimetickで管理しています.

*8:流石に雑すぎるので具体的な例を書いておくと, WebAssembly, Flux, サーバサイドあたりを触ってみたいと思ってます

Pokémon RNG Advent Calendar 2017 を振り返る

はじめに

この記事はPokémon RNG Advent Calendar 2017 25日目, 最終日の記事です. 今年はポケモンの最新作が完全新作ではなくマイナーチェンジだったこともあって「カレンダーが埋まりきらないのではないか」などの心配がありましたが, 無事埋まったのでひとまずホッとしています.*1 めでたい 🎉🎉🎉

adventar.org

この記事ではPokémon RNG Advent Calendar 2017を振り返り, 投稿された記事の中から, 個人的に興味を持ったものや面白いと思ったものをジャンルごとにピックアップします.

ツール

解析

乱数調整

数学

  • 64bit LCGの検索 - oupoの日記
    • 64bitLCGにおいて, 乱数値16bitとその10個先の乱数値16bitから高速にseedを求めるアルゴリズムの提案です
    • 普通64bitのseedを全探索で求めようとするとかなりの計算時間が必要ですが, この手法を使うとわずか20秒程で求めることができます

プログラミング

おわりに

以上が Pokémon RNG Advent Calendar 2017 25日目「Pokémon RNG Advent Calendar 2017 を振り返る」となります. いかがでしたでしょうか. 是非ここで紹介した記事を読んで今後の乱数調整活動「ランカツ!」の参考にして頂ければと思います.

それでは2017年も Pokémon RNG Advent Calendar を楽しんで頂き, ありがとうございました 😄 またいつかお会いしましょう!

adventar.org

様子

*1:埋まって入るものの記事が書かれていない日が多々あるようですが目を瞑りましょう.

Scalaで乱数ツールを書く話

はじめに

この記事はPokémon RNG Advent Calendar 2017 10日目の記事です.

adventar.org

乱数調整で楽しむ方々の間では乱数調整を支援するツールのことを乱数ツールと呼んでいます. 僕も乱数ツールを作成する内の1人であり, 時々ツールを作成しますがツールのソースコードはどうしても複雑になりがちです. たかが計算ツールといえども綺麗に書きたいですよね.

この記事ではScalaを使い, 乱数ツールを綺麗に書いてみる話をします.

…あれ?🤔

Adventarのコメント欄にはデザインパターンの話をするって書いてあったはずなのにScalaの話?🤔

デザインパターンは???🤔🤔🤔

f:id:mizdra:20171210234047p:plain
証拠です

…申し訳ありませんが今回はテーマを変えて記事を書いています🙇 本当はデザインパターンの話を書こうと思っていたのですが記事に出来るほど知見が溜まっていなかったのでボツ 🚮 となりました. デザインパターンについてはまたいつか別の機会に話そうと思います 🙏

…さて話を戻します. なぜScalaを使って乱数ツールを書くかというと, Scalaには非常に充実したコレクションライブラリが備わっており, これを用いることで乱数列に対する複雑な操作を簡単に記述できるからです*1. 乱数ツールを記述していく中で, このコレクションライブラリがいかに力を発揮していくかを感じでもらえればと思います.

前提

Iteratorを継承したLCGを作成する

まずは乱数生成器(LCG)を作成しましょう. LCGクラスにIteratorトレイトを継承させることで, 乱数列をコレクションとして扱うことができます. そうすることで, コレクションライブラリで提供される非常に多くの便利なメソッドがLCGクラスで利用できるようになります.

class LCG(seed: Int, a: Int, b: Int) extends Iterator[Int] {
  var state: Int = seed

  override def hasNext: Boolean = true
  override def next(): Int = {
    state = state * a + b
    state
  }
  def next(n: Int): Int = java.lang.Integer.remainderUnsigned(this.next(), n)
}

object Wandbox {
  def main(args: Array[String]): Unit = {
    val lcg = new LCG(0x00000000, 0x41c64e6d, 0x6073)
    println(lcg.take(5).map(_.toHexString).toList)
    // stdout: List(6073, e97e7b6a, 52713895, 31b0dde4, 8e425287)
  }
}

サンプルコードの例ではコレクションのメソッド take(), map(), toList() を使用して先頭5つの乱数を16進数表記で出力しています.

調律された乱数列を取得する

通常ではLCGで得られる生の乱数列は乱数性に問題があるため, 下半分のbitを切り落として利用されます. これを先程作成したLCGクラスを用いて書くと以下のようになります.

object Wandbox {
  def temper(state: Int): Int = state >>> 16
  
  def main(args: Array[String]): Unit = {
    val lcg = new LCG(0x00000000, 0x41c64e6d, 0x6073)
    val temperedLcg = (new LCG(0x00000000, 0x41c64e6d, 0x6073)).map(temper(_))
    
    println(lcg.take(5).map(_.toHexString).toList)
    // stdout: List(6073, e97e7b6a, 52713895, 31b0dde4, 8e425287)
    println(temperedLcg.take(5).map(_.toHexString).toList)
    // stdout: List(0, e97e, 5271, 31b0, 8e42)
  }
}

写像を表わす map() メソッドを用いて生の乱数を調律後の値へと変換しています. たったこれだけで, 生の乱数列を調律された乱数列に変換できます.

ただし, これでは map() によって返ってくる型が Iterator[Int] となってしまい, LCGクラスで実装したメソッド (def next(n: Int): Int など) を呼び出せなくなってしまいます. これは後々困ったことになるのでここではLCGクラスを継承して調律された乱数列を生成するTemperedLCGクラスを作成することにしましょう.

class TemperedLCG(seed: Int, a: Int, b: Int) extends LCG(seed, a, b) {
  override def next(): Int = super.next() >>> 16
}

object Wandbox {
  def main(args: Array[String]): Unit = {
    val lcg = new LCG(0x00000000, 0x41c64e6d, 0x6073)
    val temperedLcg = new TemperedLCG(0x00000000, 0x41c64e6d, 0x6073)
    
    println(lcg.take(5).map(_.toHexString).toList)
    // stdout: List(6073, e97e7b6a, 52713895, 31b0dde4, 8e425287)
    println(temperedLcg.take(5).map(_.toHexString).toList)
    // stdout: List(0, e97e, 5271, 31b0, 8e42)
  }
}

fork() メソッドを実装する

LCGインスタンスはmutableな操作をするので, そのまま複数のスレッドに渡すと破滅します. 次は乱数列上の5つの乱数を出力する処理を, 1消費ずつずらしながら各々のスレッドで実行する例です*2.

// 破滅する例
object Wandbox {
  def printRands(lcg: LCG, n: Int) = println(for (i <- 1 to n) yield lcg.next())
  
  def main(args: Array[String]): Unit = {
    val lcg = new LCG(0x00000000, 1, 1)
    
    (1 to 3).map(_ => {
      val f = Future {
        printRands(lcg, 5)
      }
      lcg.drop(1) // 1つずらす
      f
    }).foreach(Await.ready(_, Duration.Inf))
    // stdout:
    // Vector(4, 5, 6, 7, 8)
    // Vector(9, 10, 11, 12, 13)
    // Vector(14, 15, 16, 17, 18)
    
    // 本当は次のようになって欲しい(行については順不同):
    // Vector(1, 2, 3, 4, 5)
    // Vector(2, 3, 4, 5, 6)
    // Vector(3, 4, 5, 6, 7)
  }
}

そこで自身のクローンを作成する fork() メソッドを実装してクローンをスレッドに渡すようにしてみましょう.

import scala.concurrent._
import ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration

class LCG(seed: Int, a: Int, b: Int) extends Iterator[Int] {
  // ...
  def fork(): LCG = new LCG(state, a, b)
}

class TemperedLCG(seed: Int, a: Int, b: Int) extends LCG(seed, a, b) {
  // ...
  override def fork(): TemperedLCG = new TemperedLCG(state, a, b)
}

// 破滅しない例
object Wandbox {
  def printRands(lcg: LCG, n: Int) = println(for (i <- 1 to n) yield lcg.next())
  
  def main(args: Array[String]): Unit = {
    val lcg = new LCG(0x00000000, 1, 1)
    
    (1 to 3).map(_ => {
      val forkedLcg = lcg.fork()
      val f = Future {
        printRands(forkedLcg, 5)
      }
      lcg.drop(1) // 1つずらす
      f
    }).foreach(Await.ready(_, Duration.Inf))
    // stdout:
    // Vector(1, 2, 3, 4, 5)
    // Vector(3, 4, 5, 6, 7)
    // Vector(2, 3, 4, 5, 6)
  }
}

これで並列処理でも安全にLCGインスタンスを扱うことができます.

作成したLCGをエンカウント処理で使う

ここまで作成したLCGクラスを使って3世代の野生乱数の処理を書いてみましょう. 問題を簡単にするために次のような仕様で処理を書くことにします.

  • seedは 0x00000000
  • 検索する消費数の範囲は 1 〜 100000
  • CDS個体値がVのものを検索
  • 並列処理する

…と簡単にしたと言ってもなかなか複雑そうなプログラムになりそうですが, ここでもScalaのコレクションライブラリの力が発揮されます. コードを見てみましょう*3.

class LCG(seed: Int, a: Int, b: Int) extends Iterator[Int] { ... }

class TemperedLCG(seed: Int, a: Int, b: Int) extends LCG(seed, a, b) { ... }

// エンカウントデータ
case class Encounter(frame: Int, slot: Int, level: Int, nature: Int, pid: Int, ivs: Seq[Int], item: Int) {
  override def toString(): String = {
    s"frame: $frame, slot: $slot, level: $level, nature: $nature, pid: ${pid.toHexString}, ivs: ${ivs.mkString("(", ", ", ")")}, item: $item"
  }
}

// エンカウントデータを生成するBuilder
class EncounterBuilder(wantedIvs: Seq[Range]) {
  var frame: Int = 0
  var slot: Int = 0
  var level: Int = 0
  var nature: Int = 0
  var pid: Int = 0
  var ivs: Seq[Int] = Nil
  var item: Int = 0
  def frame(frame: Int): Unit = this.frame = frame
  def slot(slot: Int): Unit = this.slot = slot
  def level(level: Int): Unit = this.level = level
  def nature(nature: Int): Unit = this.nature = nature
  def pid(pid: Int): Unit = this.pid = pid
  def ivs(ivs: Seq[Int]): Unit = this.ivs = ivs
  def item(item: Int): Unit = this.item = item

  def result(): Option[Encounter] = {
    // 個体値が条件を満たしていなければ None を返す
    val isValidIvs = (wantedIvs zip ivs).forall({ case (range, iv) => range.contains(iv) })
    if (isValidIvs) Some(Encounter(frame, slot, level, nature, pid, ivs, item))
    else None
  }
}

object Wandbox {
  def printRands(lcg: LCG, n: Int) = println(for (i <- 1 to n) yield lcg.next())
  def getPID(lid: Int, hid: Int): Int = lid.toInt | (hid.toInt << 16)
  def isValidPID(pid: Int, nature: Int): Boolean = java.lang.Integer.remainderUnsigned(pid, 25) == nature
  def getIVs(rand: Int): (Int, Int, Int) = ((rand >> 10) & 0x1F, (rand >> 5) & 0x1F, rand & 0x1F)
  def searchEncounter(lcg: LCG, builder: EncounterBuilder, frame: Int) = {
    builder.frame(frame)
    
    // 出現スロット, レベル, 性格の決定
    builder.slot(lcg.next(100))
    builder.level(lcg.next())
    val nature = lcg.next(25)
    builder.nature(nature)
    
    // (pid % 25) == nature となるような PID を探す
    val pid: Int = lcg
      .grouped(2) // List(LID, HID) の組にする
      .map(iter => getPID(iter.head, iter.tail.head))
      .find(pid => isValidPID(pid, nature))
      .get // 必ずPIDが見つかることが保証されているので getOrElse の代わりに get を使っている
    builder.pid(pid)
    
    // 個体値決定
    val (b, a, h) = getIVs(lcg.next())
    val (d, c, s) = getIVs(lcg.next())
    builder.ivs(Vector(h, a, b, c, d, s))
    
    lcg.drop(5) // 5つの乱数をスキップ
    
    // 持ち物の決定
    builder.item(lcg.next(100))
    
    // 個体が条件を満たしていれば Some(Encounter), そうでなければ None が返る
    builder.result()
  }
  
  def main(args: Array[String]): Unit = {
    val lcg = new TemperedLCG(0x00000000, 0x41c64e6d, 0x6073)
    val wantedIvs = Vector( // 検索する個体値の範囲
      0 to 31,  // h
      0 to 31,  // a
      0 to 31, // b
      31 to 31, // c
      31 to 31, // d
      31 to 31  // s
    )
    val maxFrame = 100000
    
    val results: Seq[Encounter] = (1 to maxFrame).map(frame => {
      val forkedLcg = lcg.fork()
      val builder = new EncounterBuilder(wantedIvs)
      val f: Future[Option[Encounter]] = Future {
        searchEncounter(forkedLcg, builder, frame)
      }
      lcg.drop(1) // 1つずらす
      f
    }).map(Await.result(_, Duration.Inf)).flatten
    
    results.foreach(println _)
  }
}

出力

frame: 15506, slot: 40, level: 7292, nature: 0, pid: d000755f, ivs: (6, 27, 31, 31, 31, 31), item: 38
frame: 15530, slot: 86, level: 27437, nature: 0, pid: d000755f, ivs: (6, 27, 31, 31, 31, 31), item: 38
frame: 15540, slot: 72, level: 47192, nature: 0, pid: d000755f, ivs: (6, 27, 31, 31, 31, 31), item: 38
frame: 31693, slot: 21, level: 43592, nature: 1, pid: 44b35699, ivs: (2, 19, 18, 31, 31, 31), item: 64
frame: 36963, slot: 73, level: 6376, nature: 9, pid: 8e20683d, ivs: (13, 7, 24, 31, 31, 31), item: 43
frame: 36983, slot: 85, level: 41274, nature: 9, pid: 8e20683d, ivs: (13, 7, 24, 31, 31, 31), item: 43
frame: 36993, slot: 90, level: 26931, nature: 9, pid: 8e20683d, ivs: (13, 7, 24, 31, 31, 31), item: 43

乱数列からエンカウントデータを生成する searchEncounter() では, Builderパターンを用いています*4. PIDの決定では grouped() メソッドを使い, コレクションを (LID, HID) の組にすることで2つずつ乱数を消費しながら条件を満たすPIDの検索をしています.

Builderに注目すると, Builder自身に検索する個体の条件を持たせていることがわかります. これは result() で使用していて, 条件を満たさなければエンカウントデータの代わりに条件を満たす個体が見つからなかったことを表わす None を返しています. また条件が満たされているかの判定には zip, forall を使っています. Builderパターンを採用することで個体の生成, および個体のフィルタリング処理が searchEncounter() からBuilderに切り離されることになります.

このように, Scalaのコレクションライブラリの力を借りることで乱数ツールのコードを綺麗に記述することができます 👍

おわりに

以上が Pokémon RNG Advent Calendar 2017 10日目「Scalaで乱数ツールを書く話」となります. いかがでしたでしょうか. この記事を読んでちょっとしたツールの作成でも綺麗なコードを意識して取り組むきっかけになれればと思います 😃

adventar.org

11日目は oupo さんの担当です!

参考

*1:他にも「型システムが強力だから」, 「関数型言語であるから」などの理由があります.

*2:この後すぐに出てきますが, このようなテクニックは乱数ツールの中でよく使われています.

*3:何の関係もない話ですが最初にこのコードをwandboxで書き上げた時にタブがフリーズし, 1時間の成果が水の泡になる出来事が発生しました

*4:申し訳程度のデザインパターン要素です