mizdra's blog

ぽよぐらみんぐ

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:AndroidやiOSでGoogleロケーション履歴を有効化しておくと, 自動で位置情報が時刻と共に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で****ツールを作る作業の続きをしていました.

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

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

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

  • 2017冬
    • リトルウィッチアカデミア(TVシリーズ) 1クール目: 最高
    • けものフレンズ: 良すぎる
  • 2017春
    • リトルウィッチアカデミア(TVシリーズ) 2クール目: 最高
    • 冴えない彼女の育てかた♭(まだ半分くらいしか観てない…): めっちゃ良い
    • エロマンガ先生: 良い
  • 2017夏
    • NEW GAME!!: 良すぎる
    • 徒然チルドレン: 良すぎる
    • メイドインアビス: 最高
  • 2017秋
    • Just Because!: 最高
    • 少女終末旅行: 最高
    • 宝石の国: 最高

秋は最高だった… 特に宝石の国は毎回最高って言っていた記憶があります. 早く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:申し訳程度のデザインパターン要素です

Emtimerの紹介

はじめに

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

adventar.org

今年もPokémon RNG Advent Calendarの季節がやって来ました! ポケモン最新作も発売したことですし, 皆さんの乱数調整への意気込みもいっそう高まっていると思います. 今回は皆さんの乱数調整を支援するためのツールを開発したのでその紹介をします.

Emtimerの紹介

乱数調整では, 事象を発生させるタイミングを合わせるためにカウントダウンタイマーが頻繁に使われます. 既に乱数調整のためのタイマーはいくつか公開されていますが, 多くは次のような問題点を含んでいます.

  • Adobe Flashへの依存
    • 今後数年を掛けてAdobe Flashはサポート終了される予定*1であり, Adobe Flashに依存するツールを利用するのは不適切です
    • メジャーなモバイル向けブラウザではAdobe Flashに対応していないものが殆どです
  • モバイルフレンドリーでない
    • スマートフォンタブレットといった, 画面の小さな端末のことを考慮して設計されておらず, 画面の乱れや操作への支障が生じます
    • モバイル端末を利用するユーザが急増する今, モバイル対応はとても重要です
  • マルチプラットフォーム非対応
    • Adobe Flash や特定のOSに依存するツールは様々な環境で動作させることを考慮していません
    • 例えば, Adobe Flashに依存しているツールはAdobe Flashをサポートしていない環境では動作せず, Windows向けに開発されたツールは MacOS 上では動作しません
  • 新機能の導入が困難
    • 多くのツールのソースコードは公開されておらず, ユーザが新機能を開発して追加することは困難です

これらの問題を解決するために, 新たにEmtimerというツールを開発しました.

emtimer.mizdra.net

Emtimerは乱数調整のために開発されたシンプルで扱いやすい高機能なカウントダウンタイマーです. Emtimerには次のような特徴があります.

機能の紹介

Emtimerは乱数調整で最も使われているポケモンの館のエメループ *2を参考に作られました. Emtimerにはエメループから引き継いだ機能と, より乱数調整を快適にするために追加された新機能があります.

  • エメループから引き継いだ機能
    • キーバインド
    • サウンド
    • 開始までの猶予
    • 切り上げ
    • 残り時間特大表示
  • 新機能
    • ループ
    • モバイルに最適化されたコントローラ
    • カウントダウン時間の単位切り替え
    • ハイライト
    • Progressive Web Apps

エメループから引き継いだ機能

キーバインド

カウントダウンの開始/停止をスペースキーで操作することができます. スペースキー押下で停止, 押上で開始です. この機能により簡単にタイマーを再起動できます.

サウンド

秒の桁が更新される時に「ピッ」という音を鳴らします. サウンドを有効化するか無効化するか, カウントダウン終了の何秒前から音を鳴らすかを設定できます. タイミングを合わせたいときに便利です.

開始までの猶予

「待機時間」のカウントダウンを開始する前に別途カウントダウンを設けます. 両手が塞がっていてカウントダウン開始のボタンが押せない時に便利です.

切り上げ

値を設定すると, 「待機時間」のカウントダウンを指定秒数だけ早く切り上げることができます. 甘い香りを使って乱数調整する際は甘い香りを使ったときに発生する鳴き声の長さがポケモンによって異なるので, その調整に利用すると便利でしょう.

残り時間特大表示

エメタイマーBIGから引き継いだ機能です. 残り時間の秒未満の値を赤丸の位置で表現します. 赤丸が中央に来たときが秒の桁が更新される瞬間です.

f:id:mizdra:20171130221504p:plain:w600
残り時間表示. カウントダウン中は1秒ごとに赤丸が左から右へと流れていく.

新機能

ループ

タイマーの1サイクル (開始までの猶予のカウントダウン+待機時間のカウントダウン) をループします. ループ回数を直接指定, もしくは無限ループできます. 一定間隔でタイマーを起動したい時に便利です.

モバイルに最適化されたコントローラ

再生/一時停止/再開/停止ボタンを搭載したコントローラを設置しました. モバイルでも扱いやすいよう画面下部に固定し, ボタンを大きくしています.

f:id:mizdra:20171130221641p:plain:w300
画面下にコントローラが表示される

カウントダウン時間の単位切り替え

待機時間, 開始までの猶予, 切り上げの値の単位を「秒」および「フレーム」から選択できます.

ポケモンの館のエメループにもフレームを秒に変換するツールが結合されていますが, 実際のカウントダウンで使われる値の単位は「秒」です. これはカウントダウンしたい時間を「秒」と「フレーム」どちらを基準にしたのかの判断が困難になるという問題があります.

本ツールではカウントダウンしたい時間をどちらの単位の値として扱うかを直接していする設計にすることで, どの単位を基準にしたのかを判断しやすくしています. 多くの場合, こちらのほうが直感的でしょう.

f:id:mizdra:20171130221556p:plain:w500
それぞれの入力欄に異なる単位でカウントダウン時間を入力できる

ハイライト

秒の桁が更新される時に「残り時間特大表示」の枠内がハイライト (発光) します. タイミングを合わせたいときに便利です.

youtu.be

Progressive Web Apps

これはほんの先程追加した新機能です! Progressive Web Apps (PWA) とは簡単に言うと, 「モバイル向けWebアプリを (Google PlayApp Storeでインストールするような) ネイティブアプリに近づける」技術です. WebアプリをPWAに対応させると様々な便利な機能が有効になりますが, ここでは本ツールで活用されているほんの一部の機能を紹介します.

(残念ながらPWAはSafariなどの一部のブラウザには対応していません😢 今後SafariでもPWAの対応が進んでいくと思われますが*3, 今すぐにこの機能を利用したい場合は Google Chrome の使用を推奨します👍)

ウェブアプリ マニフェスト

ウェブアプリ マニフェストはアプリを(スマートフォンタブレット, パソコンなどの)ホーム画面に追加したり, ホーム画面からアプリを起動した時の外観をカスタマイズする機能です.

youtu.be

動画では次の機能を確認できます.

  • ホーム画面にWebアプリを追加する
  • ホーム画面からアプリをタップすると初めにスプラッシュ画面が表示され, ロードが終わってからアプリ画面が表示される
  • ブラウザのUI (アドレスバーなど) が隠されている
  • ブラウザとは別のウインドウとして扱われている

キャッシング

ツールを構成するコンテンツは全てローカルにキャッシュされ, 初回以降のアクセスが高速になります. コンテンツの取得はキャッシュを優先的に使用し, 必要なコンテンツだけがネットワークから取得されます.

オフラインで動作可能

なんとEmtimerはWebアプリにも関わらず, オフラインでも動作します!!! これは先程紹介したキャッシングを用いて, オフライン時は全てキャッシュからコンテンツを取得することで実現しています. お使いの端末が機内モードであっても動作します. 飛行機の中でも乱数調整し放題です 💪💪💪

youtu.be

自動更新

PWAは一見するとネイティブアプリのようですが, 実際にはWebアプリであり普通のWebアプリと同じようにアクセスする度にコンテンツは更新されます. ユーザは更新ボタンを押さずに, ツールにアクセスするだけで新機能を利用することができます.

新機能/改善のリクエストについて

ここまで本ツールに搭載されている機能を紹介してきました. もしかしたら🔥熱心な乱数勢🔥*4の方々は「あの機能が欲しいのに無い…!」, 「UIを改善して欲しい!」などと思っているかもしれません. そういった場合は思っていることを開発者に伝えて (フィードバックして) 頂けると今後の開発の支援になります. フィードバックはこの記事のコメントや, 開発者 @mizdra へのリプライ, またはGitHubリポジトリのIssueまでお気軽にどうぞ 😃

今後の開発の予定

Emtimerの開発は🚀今現在も進行中🚀です. 今後の開発の予定はまだ不透明な部分が多いですが, ちょっとだけ紹介します.

  • UI/UXの改善
    • モバイルでより使いやすいようUI/UXを改善します
  • カウントダウン処理の改善
    • カウントダウンがより正確に行われるよう改善します
  • 新しいタイマーの追加
    • 今あるSimpleTimerとは別に, 新しいタイマーを追加します
    • 8秒間のカウントダウン -> 16秒間のカウントダウン -> 17秒間のカウントダウン のように, 異なるカウントダウン時間のタイマーを組み合わせられるプログラマブルなタイマーを追加予定です

おわりに

以上が Pokémon RNG Advent Calendar 2017 1日目「Emtimerの紹介」となります. ここまで読んでくださった方々, ありがとうございました! 😄

それでは今日からクリスマスまでの間, Pokémon RNG Advent Calendar 2017 を楽しんでいきましょう! 良いクリスマスを!

adventar.org

2日目は @Blastoise_X さんの担当です!

参考

*1:http://blogs.adobe.com/japan-conversations/201707adobe-flash-update/

*2:一般的にエメタイマーと呼ばれています

*3:PWAの基礎となっているService WorkerがSafariに実装されることが決まっている (参考: http://trac.webkit.org/changeset/220220/webkit/trunk/Source/WebCore/features.json)

*4:乱数調整に携わる人々のことをそう呼びます

ポケットモンスター・ポケモン・Pokémon・は任天堂・クリーチャーズ・ゲームフリークの登録商標です.

当ブログは @mizdra 個人により運営されており, 株式会社ポケモン及びその関連会社とは一切関係ありません.