Just Monika

前評判はチラチラ聞いていたが、ドキドキ文芸部やってみた。 …すごかった

store.steampowered.com

無料。だが舐めてはいけない。これはギャルゲの皮を被った、それ以上の何か。

式日本語版はないが、有志の日本語翻訳の出来が良いので、それで。

https://i.gyazo.com/6f80a36c6a260333f3aadb4f3c256e74.png

Steamのゲームをそこそこやってる人なら、ただのギャップによる看板詐欺なだけでは、この「圧倒的に高評」を取るのがいかに難しいか、わかってくれると思う。

タグ、「精神的恐怖」は嘘偽りない看板。ただ、それを感じるところに辿り着くまでは結構長かった。可愛らしいキャラクターが徐々に豹変していくのは、ひぐらしを思い出させる。また、おそらく OFF、 Oneshot といった海外フリーゲームのメタ言及の文脈も組んでると思われる。

怖いだけではなく、考えさせられるゲームでもあって、最後にメインテーマのボーカル版が流れる演出で泣いてしまった。


メタ言及は創作の華だが、それにうまくいく作品は少ない。最近だとダンガンロンパV3が失敗していた。あれは失敗だったと思う。

創作の質が、読み手に与えるインパクトに比例するなら、これはここ最近では一番の傑作。

オブジェクト指向の呪いと、その避け方

このテーマで書く前に、まず、最初に自分に多少の偏りがあることを認めておかなくてはなりません。

階層化されたツリー構造(GUI/リレーショナルな参照構造)に埋め込まれる状態はコード品質を悪化させるので、できるだけ出現するべきではない。 ただし、状態は確実に存在する。だからこそ慎重に扱うべきだ、という派閥です

アンチパターン: 特に理由もないクラスメソッドへの所属

何かのバリデータを実装したいとします。

その関数がどこに所属するかについて、よく見るこれらの実装は全部アンチパターンといっていいと思います

export class Validator {
  static validate() {...}
}

export class Validator {
  validate() {...}
}

export default ({
  validate: () => ...
})

正解

export function validate() {
  ...
}

状態は割れ窓です。最終的な出力に関与される余地は、できるだけ減らしたほうがいいです。(詳しくは後述します)

また、名前空間がほしいだけのクラスも不要です。そもそもvalidateにもっと厳密な名前をつけるか、import 時にエイリアスを付ける、といった解決策はいくらでもあります。参照スコープが限定されているモジュールシステムの中ではあまり厳密ではありません。

new でヒープに積むから効率がどうこう、実際あまり問題じゃありません。ほとんどの場合、それを気にするのは早すぎる最適化です。パフォーマンスチューニングは良いコードだったらいくらでもやる余地はあって、今回は忘れるべきです。

古いJavaのような、クラスにしかメソッドが所属できないモジュールシステムばかりの時代じゃありません。 クラスは基本的に不要だと思います

状態は割れ窓

思いつき限り最悪のコードを書きます。

const validator = new Validator({defaultWithXXX: true})
validator.ignoreMethodOptions = true
const result = validator.validate({ withXXX: false })

オプションがたくさんあるから手続き的に組み立てたいんだ!という主張があるとしたら、オプションを組み立てる部分を別関数にするべきだと思います。 僕だったら高階関数でこうしますが…

const buildValidator = (options) => (input) => ...
const validate = buildValidator({...})

これは関数スコープにoptionsを保持するので不変であることは保証されます。とはいえ、カリー化のないJSで高階関数をやるのは型の支援がないと難しいので、 僕も Flow/TypeScript で型が保証されてる時にしかやりません。

アンチパターン: クラスと継承

現代では、継承は基本的に使うべきではない。ということは同意が取れることとします。基本的には、「継承よりコンポジション」です。

それでもやらないといけないとしたら、ストラテジーパターンを想定したライブラリから、一回だけ、です。それも、プラットフォームが提供するような練られた実装だけから、です。

class Foo extends View {
  render(){...}
}

たしかに継承は、ライブラリなどのよく練られたAPIの一回目の継承は規約として強烈なのですが、それが多段に継承されると protected が乱用され閉鎖原則が破綻しがちで、経験上この先は最悪なコードしか見ません。リスコフの置換原則が守られないのは、歴史が明らかにしています。GUIでの標準コンポーネントを多段継承するのは最悪で、React / Backbone / Android / Flash / Unity で地獄を見ました。

(Rails の Controller 継承は悩ましくて、認証漏れを起こすぐらいだったら規約で縛るのもアリな気がするんですが、読み解くのオーバーヘッド大きくてあんまり好きじゃなくて、それこそ mixin とかでどうにかなるような…)

Go や Rust など、最近の言語ではそもそも継承は実装されないことも増えてきました。コンポジションの手段として Scala では trait, Swift では protocol が提供されているので、基本的に避けられると思います。完全コンストラクタ制約があれば実質イミュータブルみたいなもんでしょう。

イミュータブルだと思いこむ

副作用ではなくGCに頼り切ってイミュータブルなオブジェクトを返す、という実装のが最近は推奨されると思います。

function setA(obj, a) {
  return {...obj, a}
}

基本的にすべてをイミュータブルだと思って使って、古い参照はGCに落としてもらうことが前提です。これである時点でのその参照へのアクセスは保証されます。GC負荷が…と気にするのも早すぎる最適化ですね。

イミュータブル参照を守っていると、リスナーの関数クロージャでアクセスするオブジェクトがアクセスするたびに値が変わる、といったことは避けられます。redux の reducer なんて、それを実現するだけの関数ですからね。

POJOJSONのような、薄いオブジェクトを扱っていると、シリアライズしやすいというのあります。JSはJSONがあるので特殊な環境といえばそうなんですが、他の言語でも ORM にマップするときや、通信のためにシリアライズするときなんかも有用でしょう。クラスのインスタンスから関数以外のプロパティを落としてシリアライズするのは簡単ですが、その逆のデシリアライザを常に用意するのは大変なので、そもそも切り離す、という感じです。

データと実装を切り離して、常にデータだけをシリアライズする、というアプローチは、データの可用性を大きく高めてくれます。批判があるとしたら、それは「ドメインモデル貧血症」じゃないか?というのがありそうですが、それは名前空間の所属だけの問題だと、自分は思います。

ただ、これも型がある環境じゃないとやりづらいとは思います。

最後に

もっといろんな言語の立場を書きたかったけど、JSの立場により過ぎた気します。

書き始めた理由としては、以下の2つの記事が念頭にあります。

qiita.com

ubiteku.oinker.me

この2つの記事に同意してるわけじゃないですが、そういう意見が出る時代だろうという認識です。

最近読んだ本の中では、オブジェクト指向を振り返るにあたって、「Game Programming Patterns ソフトウェア開発の問題解決メニュー」 が最高の本でした。

ゲームプログラミングと銘打ってますが(実際にゲーム特有のパターンはあるものの)、単にゲームを題材にした、ステートフルな対象をどう扱うか、という本で、ゲームでよくあるC++の実装例だけではなく、筆者が関数型や動的型付けならこうなるからこのパターンは不要と切り捨てたりするので、非常にバランスが良いです。とくに継承批判とシングルトン批判が強烈です。

日頃思ってたことを書いてみたけど、すべての立場の想定反論を用意できたわけではないので、反論がたくさんありそう。ファイッ

IPFSについて勉強した

ipfs.io

IPFS とは / BitTorrent とどう違うのか

P2P分散ファイルシステム

BitTorrentがトラッカーファイルと呼ばれる単位でファイル共有を行うのに対し、IPFS は内部が別のオブジェクトを指し示すポインタ(ディレクトリ相当) or またはバイナリ(ファイル相当)のものがDAG構造をとっていて、それぞれにユニークなキーが振られている。

IPFSの内部構造は Git の内部オブジェクトと非常に似ている。分散ファイルシステムとしての Git との概念的な差はほとんどなくて、両者ともにコンテンツアドレッシング方式の Merkle DAG 構造ということができる。調べた感じ各種ツリーフォーマットの表現の差や、オブジェクトの圧縮方法が違うだけ、といっていってよさそう。

IPFSで配布する際は、URLのような名前空間に対して保存するのではなく、オブジェクトまたはその集合による Merkle によって一意な sha256 のキーが決まり、それが一意性を示すポインタとなる。なのでsha256一致させる高コストな攻撃をされない限り、コンテンツに対してユニークなキーといっていい。

IPFSのP2Pノードとしての振る舞いは BitTorrentに似ており、各ノードがほしいブロックリストと、提供できるブロックリストを公開し、その希少度や見返りのインセンティブによってスコアが付けられる(BitSwapプロトコル)

なので、要約すると Git の分散ファイルシステム + BitTorrentインセンティブ設計に基づいたファイルブロック交換システムといって良さそう。

P2Pなので、アップロードしたファイルはネットワーク参加者全員の同意を取らないと消せない(=ほぼ消せない)。

libp2p(-js) というミドルウェア層があり、p2pネットワーク内での webrtc/websocket/http を抽象している。IPを利用しているにすぎず、よりメタなプロトコルを採用することも、理論上は可能。

公開してみた

まず、 brew install ipfs などして ipfs コマンドを入れるとする。これで入るのはGoのリファレンス実装で、他にJS実装がそこそこ。CとPythonが In Progress.

こんな感じでアップロードして、ノードに参加し広めることができる

mkdir pub-to-ipfs
echo hello > hello.txt
ipfs add -r .
ipfs daemon # IPFSネットワークの参加ノードとなり、ファイルを配布する

https://ipfs.io/ がデフォルトのゲートウェイで、 https://ipfs.io/ipfs/<ObjectID> という風にアクセスするとその中身が取れる。ipfs daemonlocalhost:8080 にローカルのゲートウェイを立ててくれる。

試しに自分のツイッターのアイコンを投げてみた。アクセスできると思う https://ipfs.io/ipfs/QmdqSc9ZKcdzrdfjRmMLms4jH3JtFQCBALyJNTSaEwi59m/saboten.png

Git をホスティングする

https://kotet.github.io/2018/06/04/ipfs-git-readonly.html が非常に参考になった

手元にあった next-editor のソースを投げてみた。 .git オブジェクトをそのまま投げる。

cd .git
ipfs add . -r

Git オブジェクトは 5MB程度なのを確認した。

git clone https://ipfs.io/ipfs/QmQFVzbiueHb6CtuN7aa6W67Uzr9kaeVKsVYLHoFLWDcxz --single-branch

僕の環境で ipfs daemon のノードのPeerが800程度で、Twitter で頼んでこれを clone してもらったところ、およそ1分~6分という結果になった。使い物になるかどうか、微妙なラインでなんとも言えない…。

(アクセス絞るのに --depth 1 しようとしたが、素朴なファイルホスティングだと smart http プロトコルにならず、 dumb http プロトコルだと --depth が使えなかった)

動的なコンテンツを追跡するには ipns というものを使うらしい。まだ使ってないので後で調べる。

わからなかったこと / 次に調べること

やりたかったこととして、 https://next-editor.app は完全な静的サイトで、pure js な Git 実装を積んでて、PWAでオフラインで動くように作っているのだが、 GitHub にアップロードするにはアクセストークンなりCORS迂回のゲートウェイなりの準備が大変。なので、手軽にGitを共有する対象として IPFS へのアップロードする選択肢を用意できるかどうかを検証していた。というか Git って本来分散システムだからGitHubが権威サーバーである必要もないと思っているのだが…。

クライアント単独で git clone し publish までいけたら夢があるし、たぶんいける。検証中。

上記の方法で、サーバーを立ててゲートウェイを通してアップロードできるのはわかった。しかしもっというとブラウザで完全に完結させたくて js-ipfs でIPFSのオブジェクトを作ってブラウザで単独のノードとしてそれを配布したかったが、そのやり方が分からなかった。概念上 WebRTC でP2P への参加ノードになれるはずだが…

共有するためのオブジェクトを作るまではできてて、こんな感じ。

import pify from "pify";
import IPFS from "ipfs";

const streamFiles = (root, files, cb) => {
  const stream = node.files.addReadableStream();
  stream.on("data", data => {
    console.log(`Added ${data.path} hash: ${data.hash}`);
    // The last data event will contain the directory hash
    if (data.path === root) {
      cb(null, data.hash);
    }
  });
  files.forEach(file => stream.write(file));
  stream.end();
};

const repoPath = "ipfs-" + Math.random();
const node = new IPFS({ repo: repoPath });
global.ipfs = node; // DEBUG

node.on("ready", async () => {
  const root = "xxx";
  const files = [
    {
      path: `${root}/file1.txt`,
      content: node.types.Buffer.from("one", "utf8")
    },
    {
      path: `${root}/file2.txt`,
      content: node.types.Buffer.from("two", "utf8")
    }
  ];

  const directoryHash = await pify(streamFiles)(root, files);
  const newFiles = await pify(node.ls)(directoryHash);

  console.log(`${root} - ${directoryHash}`);
  newFiles.forEach(f => console.log(f.name, f.path, f.hash));
});

もうちょい調べる。

オクトパストラベラー

体験版で感じたとおり、河津ゲーの同人ゲームという印象だった。たぶん今の技術でロマサガ3完全版を作ろうとしたらこんなゲームになるんだろうという感じ。

毒にも薬にもならないシナリオを、やたらウェットなカット演出で見せられる。仲間4人目ぐらいから全部スキップするようになった。会話内容自体はサガシリーズと同じぐらいの(中身がない)情報量なんだけど、声優のフルボイス+ウェイトが挟まる演出で死ぬほどテンポが悪いし、それを全員分見せるためのメタな進行管理が行われていて(飛ばすことはできるが)、とにかくだるかった。

サガのあの無味乾燥な、だけど妙に味のあるテキストは、容量の問題や技術制約もあったんだろうけど、ゲームのテンポを損なわないために必要だったんだな、という学びがあった。

JRPGとしての戦闘システムの出来は悪くないと思っていて、サポジョブでシナジー出して、バフデバフを過不足なく揃えてバーストダメージ出すという現代的な設計で、それは悪くないと思うんだけど、キャラクターやシナリオが全然味気ないのにそれに引っ張られたレベルデザインで、とにかく先が読めてしまう。フィールドのデザインがかなり酷くて、3Dマップの奥行きを使って隠し道に宝箱をおいておく、というパターンしかない。

攻略情報縛ってて、意図的なレベル上げを可能な限り避けてても、かなりぬるく感じた。緊張感出すのに推奨Lv-7ぐらいの進行をしていたが、自分が楽しむには特殊なレギュレーションが必要という感じだった。

スイッチの入れ方

自己分析

どうやったらスイッチが入るか

  • コーヒー飲む
  • 作業机に着席する
  • エディタが開いてある
  • 次にやることが自明 => やる

集中継続の仕方

  • 取り組んでる対象が面白い
  • いい音楽がある
  • 通すべきテストがあったり、タスクが明確だったりで、なんらかのリズムがある
  • 課題が小さい(小さく分割してあるという状態)

スイッチの切れ方

  • コンテキストスイッチのタイミングで開発環境の再セットアップしてると萎えてくる
  • 対象がそもそも気が重くて逃げる(タスクが分割されていない)​
  • Twitter で気になる話題が流れてきて別のスイッチが入ってしまう
  • 定期的に腰の調子が気になる
  • 定期的に肩の調子が気になる
  • 定期的に首の調子が気になる

音楽

  • 飽きっぽいので常に新しい音楽がほしい
  • 昔はメタルやプログレッシブ・ロックが好きだったが、最近は作業を害さない程度のエレクトロニカに寄ってる
  • Spotify はいい感じなのだが、たまに自動再生でやたら癇に障る曲が来て、集中が途切れる
  • 思考停止で選ぶ Radiohead / Kid A の一曲目 Everything in its right place の再生数が 1000 回を超えてるがさすがに飽きてる
  • https://musicforprogramming.net たまに使う

音楽があると集中してるのは結果的にわかっているが、おそらく集中している結果であって、理由ではない。 しかし因果が逆転しているとしても音楽を流すと自然とコードを書いてることがある。不思議。

戦略

とりあえず何もやる気がでないときは、コンビニでコーヒーを買いにいく。

事前に細かくタスクを分割する。GitHubのイシューを眺める。自分で次にやりそうな内容を思いつき次第メモる。作業中は、作業ディレクトリに TODO.md おいて、CURRENT / ICEBOX に思いつきをひたすら書いてメモしている。こんな感じ https://github.com/mizchi/next-editor/blob/master/TODO.md

t-wada さんには「間があくときは、テストを一件だけわざと落としておくとよい」と言われた。とっかりりになるので。ただそのときはテスト書けるような作業内容ではなかった。要は、最終状態をどう復元するかが大事なんだと思う。最近思うけどTDDやれるテーマってDBとかAPIとかのデータ中心の作業で、UIとかインフラとか副作用がデータの奥にあるものは非常にだるい。タスク管理みたいな別の戦略が必要。

タスクを消化する順番は、モチベーションが上がる順。モチベーションが高まったら気が進まない大ボスに挑む。

数値でゲーミフィケーションされてる作業はモチベが上がりやすい。テストカバレッジとか。イシューの消化数とか、スプリントのポイントとか。ただスプリントのポイントは結局自分で作るのでもやっと感がある。イシューの大きさも

フリーランスでだいたい2〜3社の取引があるので、コンテキストスイッチ対策に、docker-compose で全部立ち上がるという体験が結構大事。開発環境構築ツールとしてのdocker は本当に大事。デプロイ環境はわからん。

あるといいもの

自分でなにか作るとしたらのメモ

  • 無理やりやる気をキックするタイマー
  • 重くないタスク管理ツール。最近のは全部重い
  • エディタに GitHub Issue / Project を組み込む

Chrome に PWA for Desktop が来ていたので next-editor で試した

追記: Canary じゃなくてもいいらしいのでタイトル修正した。が…実装具合はよくわからない

今年中に来るとは聞いていたやつ。要はウェブアプリを デスクトップアプリ化する。Electron と違って Chrome の Sandbox と同じ権限で動いている

試した

Chrome Canary 落として有効化する

https://i.gyazo.com/82888deb2ee0fce09a23c245c70d0dbc.png

最近作ってる PWA のGit 組み込みエディタの next-editor に試してみた。特に理由がないが PWA 対応していた。オフラインでも Git が動くエディタ。

Next Editor を開いて 「Next Editor で開く」を選択

https://gyazo.com/f6654e1900831b8f83f429fa3b4d1e04.gif

https://i.gyazo.com/cf00e83c14101b2bd7fea2cd897d1981.gif

最高です。

感想

デスクトップPCという世界は業務以外でシュリンクすると思っているのだが、業務やオーサリング系はおそらくいまと同じPCか、それと同じ横長の画面幅のタブレットが長く生き残るだろう。next-editor はそこを狙って作っている。

Electronでアプリを作っていた経験としては、人はそれがどういう実体であろうとも、アプリを起動するメタファとしてのデスクトップアイコンに惹かれるところがある。その点でDock に入るのはすごく嬉しいし、それだけで価値がある。Electronのプロセスモデルの万能性(や、それが引き起こす脆弱性)なんかより、ただ開けるという事実のが大事なんだと思う。

最近まで PCでPWA化するメリットを具体的に説明できていなかったが、とくにツール類はデスクトップ化できるというのが売りになる時代が来ると思っている。

Slay the Spire: 3キャラ目

3キャラ目追加されてたので久しぶりにやった。 たぶん4回目ぐらいの挑戦でクリア。

Pyramid + Cursed Key と引けたので理想的な展開。最終的なビルドどんな感じになったかはこの動画の通り

youtu.be

だいぶ前に紹介記事を書いたけど、売れてるみたいでよかった。

デッキ構築型ローグライクRPG『Slay the Spire』100万プレイヤー突破。わかりやすく奥深いゲーム性が人気を呼び、Steamの定番タイトルに | AUTOMATON