デッキ構築ローグライクダンジョン の Slay the Spire が面白かったので紹介したい

デッキ構築 + ローグライク + ダンジョン。

アーリーアクセスで少々コンテンツが足りない感はあるものの、とても面白かった。日本語の wiki や攻略情報が現在ほぼ一切存在しないので、布教兼ねて紹介したい。

プレイアブルの2キャラでクリア済み。クリア = Stage 3 のボス撃破としている。

Slay the Spire on Steam

https://i.gyazo.com/419ab928c2cc0060598b624a9c0d2f93.jpg

概要

基本的にはドミニオンのようなデッキ構築ゲーム。最初のカードはキャラクターごとに固定、毎ターン +3 Energy で 5 ドロー、Attack|Power|Skill のアクションカードをコストを払って実行する。

カードは敵を倒したりショップで購入することで手に入れる。他のデッキ構築ゲーと一緒で圧縮が重要で、イベントマスのストアで金払って1枚廃棄したり、特定のイベントで廃棄したりできる。休憩ポイントで各カードは一回だけ強化できる。

英語か機械翻訳の日本語で、簡単な英語なので英語でやればいいのだが、カード説明の Exhausted が 「その戦闘内での廃棄」に気づくのに時間がかかった。

良さ

最近は自称ローグライクが氾濫しているが、プレー感覚はしっかりとローグライクで、ビルド 最初から理想のゴールを決めて突っ走るか、その場で決めていくか、思い通りにいかない感じが絶妙に歯痒くてよい。

自分はこの手のゲームだと Faster Then Light が一番好きなのだが、プレーのリズム感は似ている。馴れてくると1プレー1時間ぐらいでテンポが良い。

キャラクター

The Ironcrad

戦士系っぽいイメージ。 初期から持ってる、戦闘終了ごとに HP6 回復のレリックが強くて結構無茶できる。

最初にクリアしたときは Felix(そのターン中のみダメージ+4) Heavy Blade(固定ダメージ+ダメージ増加倍率が3倍の追加ダメージ) 以外をドロー系で固めて、Felix をブン回すことで高倍率の Heavy Blade で最大ダメージを叩き出す感じだったが、二回目のクリアは Barricade(ガード値がリセットされなくなる) と Heavy Slum(現在のガード値だけダメージ)のビルドでクリアして、やる度に別のプレー体験になるように多様なビルドが可能。たぶんいちばん調整されている。

Corruption(全ての Skill カードが Exhaust になり、コストが0になる) が何やるにしてもとにかく強い。ただし入手確率も低い。強すぎるので調整されそうな気がする。

The Silent

毒使いのアサシン。脳筋よりビルドがピーキーで上級者向け。 Ironcradと違って回復のレリックがないので、慎重な立ち回りが要求される。

敵の得手不得手がはっきりしてるので、序盤のユニークに手も足も出ずにやられることもしばしば。毒ビルドは強いのだがダメージ出力が出るのが遅いので、その間に防御値を稼げずに負けることも多い。

結局ダガービルドでクリアした。ダガービルドは結構回さないとカード自体がアンロックされないので、それまでが辛かったが、Shiv のダメージ上昇バフを使って Shiv のドローに全部を賭けるビルドが完成したときは強すぎて負ける気がしなかった。

今後にも期待

さすがに2キャラは少ないのだが、それでも30時間ぐらい遊べたし、まだまだやり込めそうではある。自分は30時間ぐらいやった。 追加予定の3キャラ目や、追加のボス、レリックなどに期待しつつ、正式版まで追加要素追うだけでもニヤニヤできそう。

サクサク出来るデッキ構築ローグライクでリプレイ性も高い。おすすめです。

読まれるテキストは読者へのおもてなしの構造を持っている

大学生だった当時、梅田望夫の本を読んではてなにやってきた僕は、ブログ論壇への憧れだけがあって、技術者にもなれず、時流のテーマに対して書くべきテーマを持たず、ただ実家の宗教に対する恨みだけを書き綴っていた。

もちろん、そんなものを好きこのんで読む人はいなくて、ただ虚無へとテキストを放り込んでいたのだけだど、いつからか、ある程度パターンを獲得して、その真似をするようになって、成功失敗を繰り返して、それなりにPDCAを回してきたと思う。思えば、その過程でいろんな人のヘイトを買った気がする。

人間のテキストの読み方、その反応、というのはパターンを、いくつか書き起こしてみる。

読者は、ファーストビューのレイアウトで、読む読まないを決める

  • タイトルは記事の印象の5割
  • 章タイトルが残りの半分
  • 本文はほとんど読み飛ばされる
    • 書き手としては単語の印象の連なりでイメージを形成することになる
  • 段落が均等に分割されていると、テンポがいいと認識される
    • スクロールせずに次の段落の見出しが覗いてると尚良い
  • 中身の濃さに関わらず、スクロールバーの威圧感で「読み応えがある」と判断される

読者が求めているのは、そのリンクを開いたことに対する納得感である

  • 「釣る」ならどれだけ納得させるかが勝負になる
  • 結果として、強い主張 => それに対する想定反論へのエクスキューズ、という反復構造になる
  • 脳内に想定読者をエミュレートする。それは訓練で得られる。はてブユーザーはそのイメージを既に持ってるだろう。
  • たとえば、この記事につくであろう反論は「あいつまた中身がない記事を書いてる」「使い古された文章論/ソーシャルマーケティング論」「技術以外のことに言及すると残念」「瞑想と野菜が足りない」などである
  • それに対する反論として、僕は自分自身の納得感のためにブログを書いている、上からコメントすることで気持ちよくなれるなら勝手にどうぞ、僕は気にしないけど、といったスタンスであることを表明しておく。別に言う必要もないのだが
  • 同意を取りたい人に訴求して、叩きたい人には気持ちよく叩かせて、シェアしたい人にはその人のTLを彩るオシャレタイトルを提供する、のが書き手の役割

テキストの質は、読まれるかどうかに影響しない

  • 複雑な暗喩や係り受けの構造、レトリックが含まれるかは、それが読まれるかにほとんど関係ない
    • 書き手の自己満足といってもいい
  • 先に述べた段落構造のテンポ感だけで判断されがち

説明してもしすぎるということはない

  • 同じことを何度も書いて嫌がる人は少ない。なぜならほとんどの読者は読んでいないから
  • 読者は余計なメタ構造を求めない。記事単独で主張が簡潔するのが望ましい。
  • 引用先は読まれない。引用するとしたら読者がその記事を読んでいることを前提に記事タイトルを作る

強い言葉、エモさの役割

  • 人が記事を開いてるのは、何かを言いたいからであり、同意の対象、批判の対象を探していて、その端的なターゲット、「的」として引用したくなる「強い言葉」を散りばめておくと、その記事の反応を見る時にメタ構造がはっきりしやすい
  • ただし、強い言葉は、書き手の認知をも歪める。読者に訴求したいがためのエモいフレーズに引っ張られてて、本来言いたかったことと違うゴールに着地してることもある
  • それはそれで酔狂だと思っていて、自分はあえてそのままにしている

闇に飲まれるな

  • 批判を受けて暗黒面に落ちていった人は多い
  • 心を折らないために、無言の言及/シェアは全て自分への賛同だと思い込む。それぐらいじゃないと釣り合いが取れない。
  • 強い言葉による批判は、センセーショナルで、目につきやすい
  • というメタ構造を知ることで自分へ向けられた敵意を相対化する必要がある
  • 人格批判されるのは辛いけど、人格批判した側の方が品性を疑われてるはずと開き直る

言いたかったこと

俺は絶対に暗黒面に落ちないからな

というのを僕が尊敬する技術者でありブロガーである rui314 さんのツイートを見て思った次第です。

GUI環境でWeb の UIを構築する開発ツールを作りたい

qiita.com

という記事を書いた。 要約としては、「今こそGUIでWebのUIを生成できるような開発環境を目指すべき」みたいな話。

概念

Web の GUIの概念を分解すると

  • JS はViewとしての実装を持つプレゼンテーションレイヤー、ロジック注入のコンテナーレイヤーに分割して考える
  • HTML/CSS は、レイアウト定義のレイヤー、そのレイアウト領域を占めるエレメントのレイヤーに分割して考える

この考え方に基づくと、GUI開発環境で作るべきものは、

  • テンプレートへのJSロジックのつなぎ込み
  • レイアウトエディタ
  • 個別のエレメントの装飾

となる。

JSロジックのつなぎこみはヘヴィにJSだからまだコードで書く方がいい。作るならノードベースのエディタになりそう。 エレメントの装飾は自力で頑張るより Sketch から生成できないか試したい。後回し。

レイアウトエディタは今でも作れそう。作った。

レイアウトエディタを作った

css grid layout を用いて、GUIでポチポチやることで、レイアウト生成をある程度自動化する。

https://mizchi-sandbox.github.io/grid-generator/ で試せる。

見ての通り、非常に雑だが、css grid layout としての表現力は試すには一通り機能は揃ってると思う。

https://i.gyazo.com/fd5f6f75b5f577b18ec276c1655ff80d.png

サンプルで用意した HolyGrail レイアウトだと、こんな感じのCSSが生成される

.gridContainer {
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-columns: 120px 4fr 1fr;
  grid-template-rows: 60px 1fr 40px;
  grid-template-areas: 'header header header' 'left content right' 'footer footer footer';
}
.headerArea {
  grid-area: header;
}
.leftArea {
  grid-area: left;
}
.contentArea {
  grid-area: content;
}
.rightArea {
  grid-area: right;
}
.footerArea {
  grid-area: footer;
} 

この辺命名ルールがプロジェクトごとに違うので、うまい感じで表現するのが難しい。とりあえず適当。 注意点として、IE11 で動かすには postcss の autoprefixer などを通す必要がある。

作ってみた感触

まだPoCレベルの品質だが、レイアウト定義ツールとして発展性がある土台が作れたので、あとは欲しいものを気合で作っていくとどうにかなりそうな気がする。 2次元空間の連結処理を愚直に書いてると、まるでゲームプログラミングのようだった。こういうの辛いけど割と好き。

TODO

  • メディアクエリによるレスポンシブ対応
  • ドラッグで範囲指定で連結
  • 指定したgrid-areaをさらに子コンポーネントとして分割できるようにする
  • 指定したgrid-area へ React Component を埋め込んでプレビュー
  • 生成コードにpostcss通しておく
  • Atom プラグイン

思いつく限りだとこんな感じだろうか。 本気で作るなら patreon とかで gittip 使うべきか。あとで考える。

エンジニアのベンチャー企業の選び方/働き方/やめ方

この記事は退職者アドベントカレンダーの12日目です。

adventar.org

経歴としては、新卒で設立してすぐのゲーム会社 => 小規模教育系ベンチャー => Incements(Qiita) => フリーランス

今年で29歳、20代で3回退職しました。20代のうちは冒険してベンチャー企業で働いてみよう、と思ってたのですが、結局29を目前にフリーランスになってしまいました。

ベンチャーで働くこと

ベンチャーで働くのはリスクを取るということ。一番言いたいのは、ストックオプションもたずにベンチャーやるな、ストックオプションも確実に換金できるわけじゃない、ということ。上場するときに行使するか、バイアウト時に買い取ってもらわないといけません。

また、ストックオプションの期待だけ給与は下がるので、他の会社で同じことをやるのに比べて、 -100~-150万ぐらいの相場です。少数精鋭志向で最初からじゃんじゃん金がつぎ込まれてる会社はその限りではないですが、入るのも難しいですね。

つまりはストックオプションなしのベンチャーは、よっぽど給与が高くないと1つの搾取の形態です。若者の夢を食って成長する会社、残念なことにいっぱいあります。気をつけてください。

ただし、給与テーブルというものがないので、新卒でも即戦力ならお金は出るので、一律350万スタートみたいな大企業と比べると、最初のキャリアとしての給与は良いかもしれません。というか研修などはないので、扱いは中途です。

ビジョンとそのズレ

ベンチャー企業は、言ってしまうと創業者の夢を叶えるためのもので、ビジョンに共感して入社したとしても、些細な方向性の違いがどんどん大きくなるし、またそれが実現したところで実はお金にならなかった、みたいなことも往々にしてあります。

自分がプロダクトに思い入れを持つのはいいことなのですが、持ちすぎると会社が自分の願いに反する方向に舵をきったととき、裏切られたという感じてしまうこともあります。

共感は一つのモチベーションの作り方ですが、まったく共感をせずとも働けるのならそれは1つのやり方としてアリです。とくに技術者の場合は、目先で使う技術だけ意識してれば後はあんまり気にしない、というのもアリでしょう。ただし、その場合はその技術の使うアテがなくなったタイミングで辞めることになるでしょう。

ベンチャーの選び方

とくに小さいベンチャーでは、経営者は会社そのものです。経営者がやりたいことと、自分がやりたいことが合致するか、それが最初の確認ステップです。

自分は、会社に対して労働という形態で会社に投資をする形になります。その見返りはストックオプションです。

上場は簡単にはできません。ほとんどはそこにたどり着かないし、最短でも創業から4~5年、うまくいって10年かかるものです。自分の人生のうち10年をそこに賭けられるか?と考えてみてください。

幸いなことに、この業界は流動性が高いので、言い方が悪いですが「損切り」も簡単です。

自分がベンチャー企業をやめた理由

僕は結構プロダクトに思い入れるタイプで、作ったものに愛着とかを持ってしまうんですが、それが自分の願ってた方向と違う方向で使われたり、自分があるべきだと思った姿を歪められてしまうと萎えてしまう、ってのが1つ。

もう一つ、なんだかんだで技術そのものに興味があり、興味がある技術を常に追い求めていても、ベンチャーという小さい組織では使うアテが見つからない、ということです。次期社内フレームワークの選定のディスカッションでレガシーな方向にいって萎えたり、ということもありました。

言い方は悪いですが、それが競争力の源泉ではない限り、技術的にリスクをとらなくてもいい、というのがベンチャー企業の1つの在り方です。もちろん、あるに越したことはないので、みんないい技術者がほしい、といいますが、現場では必要とされなかったりします。ベンチャーに限らず、採用あるあるですね。

また、僕自身の属性として、若手から中堅どころになり、ベンチャーで給与テーブルをハックしなくても給与が上がる年齢になった、というのもあります。悪い慣習ですが。

ベンチャーを経験して、なぜ今フリーランスやってるのか

行きたい会社がないです。

1つに僕の専門としているフロントエンド技術が、長期的に活用できる会社がない、というのがあります。国内のウェブ業界で、技術寄りに倒して R&D に近いレイヤーでそれができる会社は、売上的に余裕がある必要があり、また日本のITベンチャーで金銭的な余裕があって競争力がある会社の選択肢があまりなく、それらとマッチしません。

国内で外資だとGoogleは多少興味があるんですが、どっちかというと中身を見てみたいという好奇心であって、僕がCSの基礎にスキル振ってないので、基礎教養だったり英語だったり、その辺が明らかに欠けてる認識あるんであんまりマッチしておらずお呼びでないという気がしていて、また自分としても今行くべき会社でもないという感じを察しており、今はあくまでGoogle技術の利用者という立場の方が気が楽です。

海外は…どうかなぁ。あまり国内から出たいという気持ちがないです。シリコンバレーの基準の給与はほしいですね。5000兆円ほしい。

今は、短期で小さい仕事を、難しい分ちょっと単価上げて取るほうが、僕自身のためにも、社会のためにも、全体効率としては良い、という時期だと認識しています。

いい話があったらお待ちしております。

フリーランスどうだったか

最初は不安だったけど、なんだかんだで仕事は断らないといけないほどあることがわかりました。

売上も結構立ったので余裕があって、来年は仕事減らして新しいことを勉強しようと思っていて、何を勉強するか考えてる最中です。ブロックチェーンとか気になってます。個人でスモールスタートなビジネスを、思いつきでやる分には一番近そうだし、まあ個人で何かやらなくても需要が高い技術になりそうなので。

まとめ

  • ビジョンはモチベーションの源泉だけど、それが過ぎると辞める理由にもなる
  • やる気ある若者はベンチャーで給与テーブルをハックできる
  • 僕は今行きたい会社がない

なんだかんだでコードを書くのが好きなので、それをやらせてくれる環境に居続けるために、色々やらないといけないですね。終わり。

当初の懸念どおりブラウザのプッシュ通知は邪悪に使われはじめている。実装側はクリックまで購読確認を待つべき。

追記: 2019/11/12

2年経ったけど体験が悪化し続けた結果、 Firefox がこの記事の通りになりましたね…

www.fxsitecompat.dev


プッシュ通知、ネイティブアプリの機能郡をWebに持ち込むPWA技術の売りの一つだが、当初から懸念されていたとおり、非常にノイジーなものとなってしまっている。自分も気づけばあらゆるサイトの購読確認を、無意識で拒否を押すようになってしまった。

hagex.hatenadiary.jp

少し前の記事。最近はどこかで wordpressプラグインになったのか、目にする機会が非常に多くなり、非常にストレスフル。最初は技術的な目新しさからか、ある程度容認していたが、さすがにこの状況が悪化する一方で、本気でやばいんじゃないかと思っている。とくに初見のブログの記事を読む前に、購読確認が出るのが最悪の体験となっている。

そもそもプッシュ配信とはそういうものであり、運営側はグロースハックの名目で何が何でもユーザのリテンションを獲得しようとしてくる。と考えるとプッシュ機能は性善説に立って設計して良いものではない。最近はユーザーの認知もある程度キャズムを越えたのか、悪評ばかりが集まるようになっている。また Chrome側の通知管理UIもよくわからないところにある。

実装の話

どこもかしこもこんな風に初期化時に購読を求めにいく。

const serviceWorkerRegistration = await navigator.serviceWorker.ready
const subscription = await serviceWorkerRegistration.pushManager.getSubscription() // ここで確認モーダルが出る

自分が言いたいのは、要はなにかしらのボタンをクリックして初めてモーダルを出してくれという話

document.querySelector('.subscription-button').addEventListener('click', async ev => {
  const serviceWorkerRegistration = await navigator.serviceWorker.ready
  const subscription = await serviceWorkerRegistration.pushManager.getSubscription()
  // ...
})

Googleの人やWeb標準に人にいいたいのは、そもそも仕様レベルでクリックをイベントのルートに持たないと購読できない、としたほうが良いのではないか。すでにそういう実装になってるAPIとして Fullscreen APIがある。

https://developer.mozilla.org/ja/docs/Web/Guide/DOM/Using_full_screen_mode

忖度

この機能の背景として、Webの相対的な立場の低下を踏まえ、モバイルアプリを倒すために今現在モバイルアプリが(提供側が)一番好んでいるであろうプッシュ通知をブラウザ側にもってきて、モバイルの体験をWebにもってきたい、というのはよくわかるが、ここでユーザーの体験のことは無視されがちだ。ちゃんと機能すれば良いもの、という言い訳もわかる。が、現状最悪な方向に向かっている。

この状況が続くと、2003年ごろjsをみんなオフにしていたように、ユーザー間でServiceWorker をオフにすることを推奨されたり、PWAというブランディングそのものを毀損してしまう恐れがある。

追記

Google の アドボケーターの agektmr さん曰く

自分でコードを書きながらブロックチェーンを勉強した

マネーゲームとしての仮想通貨は興味はないのだが、技術的に興味があって自分で簡単なコードを写経しながら勉強した。

定義

  • ブロックチェーンの実体はブロックを繋いだリスト構造
  • ブロックはいくつかの入力値(生成日時など)と、自分自身のハッシュを持っている
  • 前のブロックのハッシュ値と、入力値を元に自分自身のハッシュが決まる。その手順は公開されている。

要はハッシュ値とそのメタデータが連続するただの配列なりの LinkedList。面白いのはここから。

  • ネットワークに参加するそれぞれが任意に新しいブロックを追加することができる
  • ブロックチェーンは検証結果が正しく、より長いものが信頼される
    • なのでビットコインみたいな仮想通貨は、生成コストが重く、検証コストが軽いものが好まれる。
  • 他のネットワーク参加者からブロックチェーンの更新を受け取った時、手元のブロックチェーンとそれを比較し、より長いものを自分のブロックチェーンとして受け入れる
    • 同じ長さのときは何もしない。将来的に自分のブロックチェーンを信頼させるか、新しいブロックチェーンを受け入れて破棄するかが起こる。
    • 勝手に検証ロジックを変更したネットワーク参加者がいたとしても、それは他の参加者から受理されない。ただしネットワーク参加者間で合意がとれれば、新しいブロックチェーンに分岐することはありうる。

ここでいうネットワーク参加者は、P2Pでもなんでもいい。

https://github.com/lhartikk/naivechain/blob/master/main.js を参考にしながら、 JS (babel + flowtype) でアレンジしながら写経した。

/* @flow */
import SHA256 from 'crypto-js/sha256'

type Block = {
  index: number,
  previousHash: string,
  timestamp: number,
  data: string,
  hash: string
}

type BlockChain = Block[]

/* initial block chain */
export function getGenesisBlock(): Block {
  return createBlock(
    0,
    '0',
    1465154705,
    'my genesis block!!',
    '816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7'
  )
}

export function getLatestBlock(blockchain: BlockChain): Block {
  return blockchain[blockchain.length - 1]
}

/* implementation */

export function createBlock(
  index: number,
  previousHash: string,
  timestamp: number,
  data: any,
  hash: string
): Block {
  return {
    index,
    previousHash,
    timestamp,
    data,
    hash
  }
}

export function calculateHashForBlock(block: Block) {
  return calculateHash(
    block.index,
    block.previousHash,
    block.timestamp,
    block.data
  )
}

export function calculateHash(
  index: number,
  previousHash: string,
  timestamp: number,
  data: string = ''
): string {
  return SHA256(index + previousHash + timestamp + data).toString()
}

export function generateNextBlock(
  blockchain: BlockChain,
  blockData: string = ''
): Block {
  const previousBlock = getLatestBlock(blockchain)
  const nextIndex = previousBlock.index + 1
  const nextTimestamp = ~~(Date.now() / 1000)
  const nextHash = calculateHash(
    nextIndex,
    previousBlock.hash,
    nextTimestamp,
    blockData
  )
  return {
    index: nextIndex,
    previousHash: previousBlock.hash,
    timestamp: nextTimestamp,
    data: blockData,
    hash: nextHash
  }
}

export function isValidNewBlock(
  newBlock: Block,
  previousBlock: Block
): boolean {
  if (previousBlock.index + 1 !== newBlock.index) {
    console.log('invalid index')
    return false
  } else if (previousBlock.hash !== newBlock.previousHash) {
    console.log('invalid previoushash')
    return false
  } else if (calculateHashForBlock(newBlock) !== newBlock.hash) {
    console.log(
      'invalid hash: ' + calculateHashForBlock(newBlock) + ' ' + newBlock.hash
    )
    return false
  }
  return true
}

export function isValidChain(blockchainToValidate: BlockChain): boolean {
  // check genesis block
  if (
    JSON.stringify(blockchainToValidate[0]) !==
    JSON.stringify(getGenesisBlock())
  ) {
    return false
  }

  const tempBlocks = [blockchainToValidate[0]]
  for (var i = 1; i < blockchainToValidate.length; i++) {
    if (isValidNewBlock(blockchainToValidate[i], tempBlocks[i - 1])) {
      tempBlocks.push(blockchainToValidate[i])
    } else {
      return false
    }
  }
  return true
}

export function addBlock(blockchain: BlockChain, newBlock: Block): BlockChain {
  if (isValidNewBlock(newBlock, getLatestBlock(blockchain))) {
    return blockchain.concat([newBlock])
  }
  return blockchain
}

(こういうのこそテストコード書いたほうが良さそう…)

とくに難しいコードはない。 見るべきは検証部分の calculateHashForBlock(newBlock) !== newBlock.hash だろうか。その中身は今回はSHA256を計算しているだけ。

このコードを簡単に動かす。

/* @flow */
import {
  getGenesisBlock,
  generateNextBlock,
  getLatestBlock,
  addBlock,
  isValidChain,
  isValidNewBlock
} from './index'

// 初期ブロックチェーンを生成
const blockchain = [getGenesisBlock()]

// ブロックを一個生成
const prev = getLatestBlock(blockchain)
const next1 = generateNextBlock(blockchain, 'foo')
const newBlockchain1 = addBlock(blockchain, next1)

// もう一個生成
const next2 = generateNextBlock(newBlockchain1, 'bar')
const newBlockchain2 = addBlock(newBlockchain1, next2)

// 今のブロックチェーンが妥当か検証
console.log('current', newBlockchain2)
console.log('isValidChain', isValidChain(newBlockchain2)

これだけだと面白くないので、ローカルに競合する仮想的なネットワーク参加者を作って走らせてみる。

/* @flow */
import {
  getGenesisBlock,
  generateNextBlock,
  getLatestBlock,
  addBlock,
  isValidChain,
  isValidNewBlock
} from './index'
import range from 'lodash.range'

const blockchain = [getGenesisBlock()]

const wait = n => new Promise(resolve => setTimeout(resolve, n))

// これは全員がシングルトンのバッファを持つのは実装的によくないのだが、簡単なので…
let receivedBlockchain = [getGenesisBlock()]

// receivedBlockchain を更新するブロードキャスト関数
const bloadcast = (name, next) => {
  console.log(`${name} bloadcasted`)
  receivedBlockchain = next
}

const createMiner = (name: string) => {
  // Miner自身のブロックチェーンを用意
  let myBlockchain = blockchain.slice()

  // receivedBlockchain を信用するかどうか
  const accept = () => {
    console.log(
      `${name} checks receivedBlockchain. Size: ${receivedBlockchain.length}`
    )

    if (receivedBlockchain.length > myBlockchain.length) {
      if (isValidChain(receivedBlockchain)) {
        console.log(`${name} accepted received blockchain`)
        myBlockchain = receivedBlockchain
      } else {
        console.log('Received blockchain invalid')
      }
    }
  }

  // 自分自身でブロックを追加
  const addNewBlock = () => {
    const next = generateNextBlock(myBlockchain)
    myBlockchain = addBlock(myBlockchain, next)
    console.log(`${name} add new block: ${next.hash}`)
  }

  // ランダムな時間 wait しながら 0~2 個のブロックを追加
  // ランダムなのでどこかで競合して巻き戻ったりする
  return async () => {
    while (true) {
      await wait(Math.random() * 3000)
      accept()
      range(~~(Math.random() * 3)).forEach(_ => addNewBlock())
      bloadcast(name, myBlockchain)
    }
  }
}

// 3人ほど走らせてみる
createMiner('miner1')()
createMiner('miner2')()
createMiner('miner3')()

実行結果

...
miner2 checks receivedBlockchain. Size: 15
miner2 add new block: 5f9f37ce0a84dd148138e733ea63630c4c8482f99362aac7c23463b7594e02f9
miner2 bloadcasted
miner1 checks receivedBlockchain. Size: 16
miner1 accepted received blockchain
miner1 add new block: 2b2594d6cc92e3a25f5f567a645b81c0894e5271482440a4b8cdb2655f195553
miner1 add new block: 1e1018d769aa5a47b0b62e9176c8332e2e475869b43b062efcc31ace9fb1d1fb
miner1 bloadcasted
miner1 checks receivedBlockchain. Size: 18
miner1 bloadcasted
miner1 checks receivedBlockchain. Size: 18
miner1 add new block: 861633faa0478234004d0557ddabc1946892f2ad1f0474a90334a118842d708d
miner1 add new block: b5ba3acce6d6dc09c26808ddee6ad8ee82ad99bcb0bae148e41a786d03d157e9
miner1 bloadcasted
miner3 checks receivedBlockchain. Size: 20
miner3 accepted received blockchain
miner3 add new block: dc5d8959934dfe7843a5d263eb8d34dca8bd2a301d8471d06315aa667ca6db2f
miner3 bloadcasted
miner3 checks receivedBlockchain. Size: 21
miner3 add new block: 18fd2d81753d1b12c8ee4af1ef7623810cc9ed33d7e20353f4577c8ba9def2e5
miner3 bloadcasted
miner2 checks receivedBlockchain. Size: 22
miner2 accepted received blockchain
miner2 add new block: 304f91a4ee6b10f022988a22a07b5724139a4023dfcd7cd72f50be0452962ccf
miner2 bloadcasted
miner2 checks receivedBlockchain. Size: 23
miner2 add new block: d91a1ca6137dce1b6cb2fd5c2a087e75193568d9a6d5f675c37cf85542edeaca
miner2 add new block: 0fe784699ff2b63b3affaccee27d962637fbd1aedbb50dcf2892bbddcd00d154
miner2 bloadcasted
miner3 checks receivedBlockchain. Size: 25
miner3 accepted received blockchain
miner3 add new block: 577f63f1a5093cffe4e8ce086a8977105bb5d457f7b9424160a03883921365e3
miner3 add new block: ccf166e561e75cf865e7314105f098b022116977061ad131c253a1fb03766d15
miner3 bloadcasted
miner2 checks receivedBlockchain. Size: 27
miner2 accepted received blockchain
miner2 add new block: 9b69c13ecc83b5d2216fbede77e0d1ceeb634f12ec237fe7b38d2c363b4091ff
miner2 bloadcasted
miner1 checks receivedBlockchain. Size: 28
miner1 accepted received blockchain
miner1 add new block: 953cdde0848bb5ec9a727638383f680d5507759ed4496cab908d34e154d9676f
miner1 add new block: 37b295ad1b9ccd1de70eeb0292a84dfa67263cbf1513e21f2aaec2b8ec59fc08
miner1 bloadcasted
miner3 checks receivedBlockchain. Size: 30
miner3 accepted received blockchain
miner3 add new block: fe47c3f3c892b4cfa6954322d0203751d7d9dea4eb8431513c259165772a2284
miner3 bloadcasted
miner2 checks receivedBlockchain. Size: 31
miner2 accepted received blockchain
miner2 bloadcasted
miner2 checks receivedBlockchain. Size: 31
miner2 add new block: 44a718dc29699a4599b79b5b6cc21eef335699dd8fc1439696402725e3d5cedb
miner2 bloadcasted

まあなんか動いてそう。 本当はちゃんと p2p で動かしたり、敵対的な嘘の検証を行ったり publish する miner とかを混ぜてみたかったけど面倒なので略。あとで気が向いたらやる。

60FPSへの夢、そしていかにキャッシュを従えるか / 書評「超速! Webページ速度改善ガイド 使いやすさは「速さ」はじまる」

CAの ahomu/1000ch さんからご恵贈頂き、レビュー書いてくれと頼まれたので、自分のパフォーマンスというものへの思いの丈と共に書評を書こうと思う。

自分も6年ぐらいフロントエンドぐらいやってきたけど、あんまりちゃんと用語定義をしないまま「勘」で調査しがちだったのと、彼ら二人がどの数値見てるんだろう、という視点で読んだ。

gihyo.jp

要約: これはフロントエンドエンジニアの勘を体系化/言語化したものだ

フロントエンドエンジニアにとって Chrome DevTools は日常的にお世話になる、「これナシでは生きていけない」という類のツールだ。だが、これの読み方は癖があり、経験的に蓄積してきた積み重ねがなければやや難しい側面があった。

この本は、フロントエンドエンジニアが今まで経験的に蓄積してきたでろう「勘」を、Webの仕様や各社のUXガイドラインの明確な定義を通して再整理したものだ。

歴史的な話をすると、発想のオリジナルとなった Firebug, Webkitのデバッガの進化とともに一つ一つ機能が拡充されてきたが、今では膨大な機能を有していて、キャッチアップが難しくなってしまっていた。

それらにキャッチアップするマイルストーンとして、このタイミングで本を出す企画のタイミングが適切でうまいなーとも思った。

なぜ速度が大事か

  • モバイルの台頭
  • コンテンツのリッチによる圧迫
  • 得られるモノ
    • ユーザーによるエンゲージメント
    • UX

最近、なぜ dev.to がこんなにも速く、こんなにも自分にとって感動的なのか

って記事を書いたんだけど、ただ速いのがいいというより、そもそも速度が担保されなければ、他の機能を足す余地もそもそもない、というのが自分の持論。

DevTool に詳しくなることで得られること

数値は嘘をつかないが、数値を読み解く知識はまた別途必要だ。そして、数値の意味を知ることで、開発者のメタ認知が変わっていくことで、実際に書くコードも変わってくる。

  • CPU の気持ちになる: メモリプロファイラの読み方を通して、CPUの状態、GCが何を掴んで、何を捨てるかを意識してコードを書けるようになる。またOSSAPIインターフェイスをみてメモリのどこにインパクトを及ぼすか、だいたい察することができるようになる。
  • GPU の気持ちになる: CSS属性の変更が及ぼす影響を通して、フレームレートに悪影響がある属性の扱いに慎重になる。

たとえば will-change 属性はパフォーマンスを意識するのにわかりやすい属性だろう。事前に変化する値を宣言しておくことで、「温めておく」わけだ。

本書もその点を詳しく解説している。

[レイアウト再算出が必要なプロパティ]

透過の合成のアルファブレンドも重いことで有名だが、スクロール時にcss属性を再計算するときと組み合わさって酷いことになりがち。これを検出するには事前に重くなりうることを意識しておく他にない。

ただ、一応気をつけておくべき点として、 Chrome DevTools は Chrome の内部状態の一種のダンプであって、他のブラウザでも おそらくブラウザでCSSGPU命令になる場所はだいぶ異なっているだろう。ただし、同じWebの共通仕様で実装している以上、そこまで乖離はないはず。

昔は Skia Debugger を直接見てたんだけど、いい時代になったなーと思う。

本書の良かった点

  • 世代別GCの解説と、メモリスナップショットを比較して、ヒープ領域の調査でMajor GCに負荷をかけてる部分の特定手法
  • Minor GC の調査
  • 不要なイベントリスナの特定と排除
  • picture 要素でデバイスに応じてレスポンシブに必要な画像を読み込む
  • Service Workerの初歩的な実装パターン。ネットにはコード断片はあるけどまとまった資料は初かも
  • リソースヒントの dis-fetch, preconnect, prefetch, preload, prerender。これらはpreload ぐらいしか現在使えるものはないが、書き得なんで知っておくと良さそう。
  • WebAnimation の解説、日本語だとはじめてみた
  • will-change 命令による compositing 最適化
  • スクロールイベント下のForced Reflow の対策
  • FP - FCP - FMP - TTI がRAILモデルとの対応で用語整理できた
  • サンプルコードがしれっと ES2015
  • 現代でのドメインシャーディングはアンチパターンだよって話

自分が読んでよかった点なんで、初歩的な分は飛ばして読んでいた。

違和感あった点

  • Nodeのインストールについては最小限で勝手にやってくれって感じ
  • 突然 webpack commonchunk の設定例が出てきて気持ちはわかるけど何もかも前提ふっ飛ばしてさすがに唐突で笑った
  • サーバーサイドでの処理の重さは度外視します、現代ではフロントエンドが主です!と宣言してしまってるけど、要件次第なんでここはポジショントークだと言ってほしかった

最後に

「推測するな、計測せよ」を地で行く本。「属人性の高い高度に発達した勘」から体系的な知識への転換。

フロントエンドに関してはネットワークのレイテンシで制御できずYAGNI原則が通じる箇所と通じない箇所がある。それらをこの本で学べると思う。

あと DevTools の UI すぐ変わるから、早いとこ読んだほうがいい。今すぐ買おう!(あとで読んでもいいけど)

この本の知識を皆が知ってたら「なんか重いんですけど〜」って自分に振られることが減っていい世界が来そうだと思いました。完。