読者です 読者をやめる 読者になる 読者になる

GWの進捗としてRPG作った / redux-saga でメインループ処理、JSONSchemaからのコード生成

作った。GWの間、コンビニと近所のカフェ以外に外出してないし、ゲームもしてない。

https://mizchi-sandbox.github.io/rpg-prototype/ で触れる。デザインはしょぼい。Chrome以外で動いてる気がしない。

コードはここ https://github.com/mizchi-sandbox/rpg-prototype

仮素材はウディタに付いてくるサンプル素材をお借りした。

WOLF RPGエディター公式サイト 【RPG作成フリーソフト】

仕様

  • Spaceでポーズ&リスタート
  • クリックでスキルの使用
  • 一度スキルを使ったらクールダウンがある
  • Player1 だけ操作できる

あとはなんか察してほしい。

何故作ったか

前々から、ゲーム、とくにRPGを作りたいと思ってたのだけど、メインループがすんなり綺麗にかけたためしがない。趣味プロジェクトは技術的に辛いとやる気が無くなる。

ゲーム作りたい人は、そこを気合で乗り越えるんだろうけど、そこに関しては自分はエンジニア気質なのが勝つので、自分が納得できるまで綺麗なアーキテクチャが実現できるまで、なんども試作していて、今回やっと納得できそうなコードが書けた。

あと http://anond.hatelabo.jp/20170507200847 みたいなこと言われて、いや、揶揄じゃなくて、別にそう見えるのは仕方ないよなと思いつつ、いずれ金にしたいという思いはありつつも成果はある程度まで公開しとこうと思った。

技術

主な構成要素は flow/react/react-redux/redux-saga。 まず一番難しいであろう、戦闘画面だけ作った。

Immutable

コード、汚くはないつもりだが、かなり手癖が強い。頭のなかにある将来的な仕様も同時に作ってるので、デッドコードもたくさんある。テストもコメントも足りない。プロトタイピングなので、こんなもんで許して。コミットログは最悪。

特徴があるとすると、一部を除いて、オブジェクトへの副作用絶対ほぼ禁止、というルールで書いた。

たとえば入力されたコマンドを実行しながら、Resultの配列を作る処理はこうなってる。

export function processCommandPhase(
  state: BattleState,
  commandQueue: Command[]
): CommandApplicationProgress {
  return commandQueue.reduce(
    (next: CommandApplicationProgress, nextCmd: Command) => {
      const { state: nextState, results } = nextCmd(next.state)
      return {
        state: nextState,
        results: next.results.concat(results)
      }
    },
    { state, results: [] }
  )
}

また、 https://github.com/peterkhayes/eslint-plugin-mutation というルールを導入した。

やってみると、ほぼすべてImmutableにしたことで、コードの予測のしづらさがなくなり、心理的な安心が得られた。ただ、コードを書く量はどうしても増える。四則演算ごとに計算関数生やさないといけないから当然っちゃ当然だが。

このスタイル、おそらく盛大にメモリを使うと予想していたが、メモリプロファイラを見てもそこまで漏れてる様子がない。

GC負荷はそれなりにかかってそうなので、あとで調べる。

redux-saga でメインループ制御

1フレームの処理はこんな感じになる。

  • 入力処理
  • コマンド生成
  • コマンド処理
  • コマンド結果をビューに送信

で、二種類実装してみた。一つは某F○っぽく、常に時間が経過するアクティブなやつで、もう一つは入力可能なコマンドがあると一旦処理を止めて、入力があると再開する。

これを表現するために、 battleSaga.js というエモい名前で次のような処理を書いている。

let waitMode = false
function* start(_action: any) {
  // Use wait mode
  waitMode = location.search.indexOf('wait') > -1

  let state: BattleState = createBattleMock()

  // Sync first
  yield put(sync(state))

  // Start loop
  while (true) {
    // InputQueue buffer
    let takenInputQueue: Input[] = []

    // WaitMode: check executableSkill
    if (waitMode) {
      // Wait input on wait mode
      const executableSkill = findActiveSkill(state.battlers)
      if (executableSkill) {
        yield put(sync(state))
        yield put(battleActions.paused())
        const takenInputAction: { payload: Input } = yield take(
          battleActions.ADD_INPUT_TO_QUEUE
        )
        takenInputQueue = [takenInputAction.payload]
        yield put(battleActions.restarted())
        yield call(delay, 100)
      }
    }

    // ActiveMode: wait interval or intercept by pause request
    if (!waitMode) {
      const { paused } = yield race({
        waited: call(delay, 300),
        paused: take(battleActions.REQUEST_PAUSE)
      })
      // if user request pausing, wait for restart
      if (paused) {
        yield put(battleActions.paused())
        yield take(battleActions.REQUEST_RESTART)
        yield put(battleActions.restarted())
      }
      // Get input
      takenInputQueue = hydrateInputQueue()
      yield put(battleActions.updateInputQueue([]))
    }

    // Update state
    const processed = processTurn(state, takenInputQueue)
    state = processed.state
    for (const result of processed.results) {
      switch (result.type) {
        case ResultActions.LOG:
          yield put(battleActions.log(result.message))
          if (waitMode) {
            yield put(sync(state))
            yield call(delay, 100)
          }
          break
      }
    }

    // Check finished flag
    const finshed = isFinished(state)
    if (finshed) {
      yield put(sync(state))
      // yield put(battleActions.log(`${finshed.winner} win.`))
      yield put(battleActions.openResult(`${finshed.winner} win.`))
      break
    }

    // Sync state by each frame on active
    if (!waitMode) {
      yield put(sync(state))
    }

    // Clear inputQueue
    takenInputQueue = []
  }
}

generator関数の無限ループでyieldして一個ずつイベントを処理する。

put がビューへの dispatch, take が イベント待ちだと思えばよい。call は非同期関数呼び出し。

たぶん制御フロー的に面白いのはここ。

      const { paused } = yield race({
        waited: call(delay, 300),
        paused: take(battleActions.REQUEST_PAUSE)
      })
      // if user request pausing, wait for restart
      if (paused) {
        yield put(battleActions.paused())
        yield take(battleActions.REQUEST_RESTART)
        yield put(battleActions.restarted())
      }

saga の race コマンドで、「300ms 待つ」もしくは「ポーズ命令を受け取る」の速い方で処理を進めて、もしポーズ命令だったなら、リスタート命令が来るまで待機する。

saga、やってる処理が非常にRx的だが、このストップ&リスタートをRxで自然に書ける気がしない。いや、単に自分がRx詳しくないだけかもだが。

また、これをReducerからsaga側にゲームロジックの更新を全部追い出したことで、実際に副作用を起こす場所を、サーバー側に持っていけそう。オーバーエンジニアリングの成果だ。

ゲームの内部状態をビューに伝える箇所がここ。

    // Update state
    const processed = processTurn(state, takenInputQueue)
    state = processed.state
    for (const result of processed.results) {
      switch (result.type) {
        case ResultActions.LOG:
          yield put(battleActions.log(result.message))
          if (waitMode) {
            yield put(sync(state))
            yield call(delay, 100)
          }
          break
      }
    }

今はちょっと時間がなくて、LOG イベントしか実装してないんだけど、ここでアニメーションの再生命令などを書くと、ステップ処理をシュッと綺麗に書けそう。

JSONSchemaでコード生成

昔ポシャったやつから借りてきた。ので、ほとんど使われてないが…

masterdata/
├── data
│   ├── consume-item-data.yml
│   ├── dungeon-data.yml
│   ├── equip-data.yml
│   ├── job-data.yml
│   ├── material-data.yml
│   ├── monster-data.yml
│   ├── race-data.yml
│   ├── skill-data.yml
│   └── troop-data.yml
└── schema
    ├── consume-item-schema.yml
    ├── dungeon-schema.yml
    ├── job-schema.yml
    ├── material-schema.yml
    ├── monster-schema.yml
    ├── race-schema.yml
    ├── skill-schema.yml
    └── troop-schema.yml

2 directories, 17 files

script/gen-code.js が、こんな感じのコードを生成する。

/* @flow */
/* eslint-disable */
import data from './data'

// === ConsumeItem ===
export type ConsumeItemId = '$life-herb'
export type ConsumeItemData = {
  id: string;
  displayName: string;
  stackable?: boolean;
};
export function loadConsumeItemData(id: ConsumeItemId): ConsumeItemData  { return data['consume-item'].find(i => i.id === id) }

// === Job ===
export type JobId = '$warrior' | '$mage' | '$rogue' | '$novice'
export type JobData = {
  id: string;
  displayName: string;
  life: string;
  mana: string;
};
export function loadJobData(id: JobId): JobData  { return data['job'].find(i => i.id === id) }
// ... 略

データ自体は仮なので、とくに意味はない。生成できてるというのが大事。

Storybook

試してみたら結構良かった。 なんとなくアトミックデザインっぽい分割をしてみている。

反省

仕事で使うReact/Reduxの知見を深める目的もあった。redux-sagaは元々半信半疑で、今回はユースケースに合うので使ってみたんだけど、大抵のケースではオーバーキルな気はする。ただ、今回はこれが生命線っていえるぐらいMVP。

前回の試作(1年半ぐらい前だったと思う。公開してない)と比較して、RPGツクールMVのエンジン部分のコードと、Game Programming Patterns を読んだ経験が生きた。

このGame Programming Patterns って本すごくよくて、ゲームの本というよりは、ゲームの視点でGoFデザパタを再考する、という感じで読むと、すごくいい。この手の本は筆者の視点がC++に偏ってることが多いが、関数型も動的型付の言語で実装する際のことも言及されていて、とてもバランスがよく、読みながら納得しかない。SPAのアーキテクチャGUIにとても近いので、実際に参考にできる技術も多い。

今後

  • マスターデータ増やす
  • 戦闘以外の画面を増やす
  • セーブデータの永続化層を作る
  • react-native に突っ込む

現状、まだ動くことがわかっただけで、ゲーム性と呼べるものが発生してない。

自分的には設計に納得したので、もう少し作り続けられそう。頑張る。

いかにしてJavaScriptを教えるか

経緯

ドワンゴ様から恵贈頂いた。

高校生からはじめる プログラミング

高校生からはじめる プログラミング

…読んでみたけど、HTML/CSS/JS の初歩的な部分を、初学者にやらせるとこうなる、という素朴な世界観で、CSSフレームワークもJSライブラリも出てこない。いや、出せと言ってるわけじゃない。理解せずにフレームワークを使う習慣がつくと、スクリプトキディ的な振る舞いによっていくし、教える側としても、変数が大きくなってコントロールできないのが問題だろう。

じゃあ基礎を抑えたとして、この先どう教えるといいんだろうな、というのは、たしかに自分も前から考えてはいて、それを書いてみる。

この文章のターゲット

JavaScriptを教える人、またはポインタがあれば自学できる中級者以上

追記: すべての初学者にJSを教えろという内容じゃなくて、ある程度コンテキストあってJSを学ぶ人がどういう経路で学ぶべきかというものを書いたつもりだけど、自分がマウントして気持ちよくなりたいがために意図してそこ無視してる人が多くて怒っています

提案: 初心者にNode.jsから教える

理由はいくつかある。

  • ES2015のES Module 以外はだいたい実装された環境
  • パッケージマネージャが最初から付いている
  • REPLが付いている
  • パッケージマネージャによってモジュールを分割する慣習が得られる
  • Node.jsツールチェインに抵抗がなくなる
  • サーバーエンジニアにもクライアントエンジニアにもモバイルにも派生できる
  • レガシーな jQuery 方面の慣習に引きずられていない

ただ、黒い画面に抵抗があったり、画面をゴリゴリ動かすのに最初から行きたい人は、Nodeから入るとモチベーションが削がれるかもしれない。そういう人には create-react-app から入って、しばらくそれ縛りでReactアプリを触らせるのがいいと思う。結局、そうやってると、これもあとからNodeツールチェインの理解が必要になる。段階的に、Babel、Webpackと触ることになるだろう。

TypeScriptとAngularは、最初からそれを使ってる会社での研修ではない限り、学習用途にはあまり勧めない。AngularはAngularの世界観が強くて、おそらくJSのルールなのかAngularなのかの区別がつかないし、TSも同じ。TSの慣習は、JSの雰囲気とやや距離がある。TSをやるのはJSやってからで遅くはない。

学習段階のブロッキング要素

  • 後方互換を切ることができずに残っている構文上のBadParts郡
  • 過去に書かれたES2015以前のドキュメント
  • 全体的にテストを書く強制力が弱く身につかない
  • プロジェクトごとにフレームワークが違う

テスト、まともなエンジニアなら、これだからJSは!と憤りがちなのは知ってるんだけど、GUI環境はそもそもテスト書く難易度が異常に高く、Unityなんかのゲームエンジン方面も同じ問題を抱えている。書けるけど難しい。TS/Flowを使って、型で強く固めるのも、結局テストを書かずにある程度カバーしたいという需要から来ている。

とはいえ、テストのターゲットがピュアなJSオブジェクトのうちはテストを書けるし、環境依存のDOMやその他のオブジェクトを、ピュアな部分から切り離す慣習を持つようにするのが、Nodeを使うと自然に身につく習慣なので、そこを頑張るように仕向けたい。これもNodeを勧めてる理由の一つ。

補足: サーバーエンジニアとしてのJavaScript

USだとそうでもないのだが、Node.jsを国内で運用してる会社が少ない。その為運用経験が足りなくて、採用されづらい。Nodeを使うような会社だったらRubyを使うだろう。とはいえ、npm の慣習は基本的に、rubygems の模倣だったので、LLでOSSのモジュールを組み合わせて、という体験が得られるので、学習段階の言語としては無駄にはならない。もちろんクライアントにも経験は生きる。

プログラミング言語としての JavaScript をどう捉えるか

雑に書くのは簡単だが、真っ当に運用しようとすると突然難しくなる。知識があれば、何を使っていいか、何を使っていけないかわかるが、それが身につくのは泥臭い経験の先である。ベストプラクティスは常に更新されていて、参考にして良い資料はここ3年に書かれたものに限る。

メインストリームは主に Babel/TypeScript だが、Babel も Flow が使われるようになってきているし、全体としては型ありきの固い環境を志向している。ただ、OOP周りでは、クラス記法は入ったが、あまり使われようとする気配はなく、Record + Pure Function による関数型的な方向性に寄っている雰囲気が強い。ライブラリが提供するAPIなどにその傾向が見られる。これは、JSが主に通信で使われていて、シリアライズ/デシリアライズを頻繁に行うためだと自分は考えている。

言語として物足りなくなった場合、Flowで型を拡張したり、Babelの構文を足したり、Lintツールで全く違う言語のような縛りにして遊ぶことが出来るので、長期的に物足りないということはないだろう。もうちょっとしたらWASMも来るし、そこからぜんぜん違う方面へ脱出できる環境を、これから自分は頑張りたい。

JavaScriptに投資して幸せになれるか

昔は片手間専用だったけど、今はこれ一本で一通りのことができるようになってはいる。ただ、フロントエンド以外で品質が高くなるかは、それ専用のプラットフォームに劣る。劣った分を技術で取り返したい場合、できなくはないが、そこそこのコストがかかる。

プログラミング初学者は、最初に見た言語を親だと思ってついていく習性がある。今のJavaScriptは、そこから脱出するきっかけを提供してくれない。これは勿論悪い意味で。

僕は雑に使える言語が、JSの他には RubyPythonScala という感じだが、やはりJSでなんでもなぎ倒してしまえるので、あまり触ることがない。よくないと思う。

とはいえ、目先の問題を解決するには良い言語だと思っているし、勉強した分の見返りはある。今一番プログラマの大統一言語に近いのは、「残念ながら」、JavaScriptだと思う。ここの「残念」のニュアンスは汲み取ってほしい。僕はそもそもJSという言語はそこまで好きではない。

あわせてよみたい / tagomoris.hatenablog.com

29歳の世界観

29歳になった。連休誕生日組はこうやってアピールしないと全く祝ってもらえないので…

例のリストです。

http://amzn.asia/3HzTsJb

去年からの変化

体は資本

20代のうちは大丈夫だろと高をくくっていたが、僕は作業時の姿勢が悪くて、首、肩、腰が悪くしがちだった。結局27歳ぐらいから肩こり、首の痛さ、腰の痛さで集中力を乱されることが多くなり、改善を決意した。

ダイエットはその一環で、糖質制限で14kg痩せたことで、腰への負担はかなり改善し、腰痛が減った。

肩は、周辺の筋肉を付けて改善しようと思い、今トレーニングの数をかなり増やしている。

首は未だに悪く、手足のしびれ、呼吸の辛さなど、頚椎ヘルニアのような症状が出ることがある。懸垂バーを買った。

Twitter

Twitter始めたのが20歳になる直前だったので、人生の1/3程度Twitterしてることになる。もはや生活の一部になってるので、Twitterをしてるという感覚すらない。

世代的な話

結婚しそうな人はだいたい結婚してしまった。早い人は第二子とか家建てたとかそういう話をしている。

自分がそれらに全く興味がない、わけじゃないけど、趣味に生きるタイプだし、それを他人と共有しなくても平気で、そもそも他人にあまり興味がなく、誰かと生きることをあまり学ばずに大人になってしまったので、そういうこと人並みにやろうとしてもうまくいきそうにない、という感覚だけがある。経験がないわけじゃないけど、無理にやろうとすると、自分が飽きるだろう。相手に失礼だと思う。

とはいえあと数年がまともにそういうチャレンジできる数年だし、ちょっとした葛藤はある。とはいえ、たとえば結婚したいというモチベーションのほとんどが、祖母を安心させたい、というところに尽きるので、いやーそんな気持ちで婚活しても相手に失礼では?という…

genba.fm

https://genba.fm/

まだ 第0回しかやってないのだが、第0回、好評で嬉しかった。音質が悪かったので、その辺反省して、今後も続きをやっていきたい。

今後もやる気はあるのだが、ゲストの予定が中々合わなくて、収録タイミングがない。

1回やるとCloudfrontの転送量が40GB で $5 ぐらいかかるのがわかって、ちょっとオエってなってる。

技術的な方向性

技術選択的には、最近はもっぱらFacebook党になってしまった。React, Flow, Babel その辺。

ReactNative がすごく盛り上がってるのを感じる。フリーランス的発想だと、これは間違いなく金になる。結局Titanium、RubyMotion、Cordovaと一緒やんけ、みたいなのもわかるが、これらが越えられなかった、周辺エコシステム構築の壁を超えるだけの勢いを感じる。

ただ、自分はフロントエンドエンジニア的なことより、nodeで書かれたツールチェインや、BFFのサーバーをいじってることが多く、nodeエンジニアとして環境やミドルウェアを作ることを期待されてることが多いと感じている。ReactもReactNativeも下回りのツールであって、下回りが整ったからこそ、Redux的なアーキテクチャの世界観が生まれていて、ここが自分の主戦場だという認識をしている。

30までに頑張ること

  • 税務関係の手続きを覚える
  • AWS/Terraform周りを覚える
  • 今年こそ趣味プロジェクトをリリースする

趣味開発、リリースできたことなくて、無茶な構成して、アレは駄目、コレはいける、みたいな経験値貯めて、仕事にフィードバックして終わり、ってなりがち。

じゃあ今年もこんな感じで。

67kg、筋肉、骨盤

67kg

去年の5月から10月にかけて 81kg から 67kg まで減らして、しばらく維持していたのだが、寝正月をしていた正月太りと、フリーランスになる準備として毎日誰かしらと会って飲んでたら 69kg 近くまで増えてしまった。

ちょっとヤバイなと思って、3月末から再び少しずつダイエットして、今67kgに戻った。誤差の範囲といえば誤差だが、一瞬70kg見えてたので、体重計に乗って67kg前後だと安心感がある。

そして筋トレしまくってる。

筋肉

最初は腕立て10回でヒーヒー言ってたが、今は30回で物足りないと感じる程度には筋肉がついた。腕周りが明らかに太くなった。筋肉が増えて体重は変わらず、なので、相対的に体脂肪率が減ってる、はず。

あと、リモートワークしていた弊害だと思うが、下半身の筋肉が上半身に比べると落ちていて、大腿はでかい筋肉なので鍛えて代謝を上げておくと、体重の維持に役立ちそうな気がする。今後はスクワットを増やしたい。

本来は、肩こり解消のために筋トレを始めたのだが、残念ながらそこに影響はない。

骨盤

健康のためにダイエットしてBMI30からBMI22に減って、多少期待していたことだが、腰回りは全く細くならかなった。痩せて筋肉が多少ついたとは言え、標準よりはまだ重く、腹の肉がまだ削れるのはわかりつつ、ウエストのサイズが変わらないのは、そもそも骨盤がでかい、ということが分かった。骨なので、頑張ってもあんまり変わらない。そういえば58kgだった高校の頃にも「安産体型じゃん」とからかわれてたの思い出した。

結局、一番でかいサイズから一つ小さいサイズでやや苦しいぐらいに落ち着いてしまっている。サイズで言うとLLになる。体重減ってその辺期待してたが、多分これ以上落としても無理そう。

明日の準備

明日はアレの日なのでちょっと仕込みをやる

現場.fm というフロントエンドの現場について話すラジオを始めた

現場.fm

mizchi(主にReactの人) と armorik83 (主にAngularの人) でフロントエンドで現場の肌感などを話すラジオです。混沌としたフロントエンドの雰囲気などでみなさんのやっていく気持ちなどをサポートしたいという意図。

Jxck さんの mozaic.fm が未来の仕様とかを話すのに対して、こっちは現場の愚直な話がメインとしています。

Podcast は死ぬほど雑なアイコン(Atomのスクショ)にしたので怒られて再審査かも。

このメンバーの意図

React vs Angular、みんながこのタイトルで求めてるのはプロレスなんだろうけど、React の人と Angular の人でやろうとなったのは、どっちかの立場で喋ると偏るから、バイアスを中和するための対立軸という設定です。

第0回の反省

  • 生なら大丈夫だろうと対面でとったが、録音環境があまりよくなかった
  • 生配信やろうとしたが準備不足で失敗した
  • そもそも生配信やるんだったら22時ぐらいからやらないと人が集まらなさそうだった

編集後記

やってみようとしたログであって、本編と無関係

  • ホスティングサイト、 hexo 使ってみるか
  • どうせなら AMP 縛りでやってみるか
  • どうせならドメインとるか
  • この辺の構成しばらくやってないから、復習しておくか
  • Route53 で ドメイン購入
  • Route53 で CNAME 設定
  • => ドメイン取ったらSSL証明書取り直しでAMPで効かないじゃん
  • ACM で証明書取得
  • => AWSバージニアのリージョンでとらないと有効にならず
  • バージニアで再発行
  • cloudfront に CNAME と証明書を設定
  • 証明書が動かない
  • => *.example.comexample.com を含まない
  • 証明書再発行
  • cloudfront に証明書を再設定
  • 動いた
  • ホスティングサイトに戻る
  • organization の公開に失敗し続ける
  • => organization の公開は gh-pages ブランチではなく master 縛りがあった
  • hexo がローカルにインストールしたプラグインを認識できない
    • yarn global add hexo したのを消して yarn global add hexo-cli したら治った
  • 記事更新してもなんか動かない
  • => hexo のローカルキャッシュ更新のタイミングを理解してなかった
  • 成功
  • atom フィード生成
  • S3 直受け配信がまずそうなので、cloudfront をかます
  • cloudfront の設定ミスってキャッシュ同士でリダイレクトループ発生
  • => invalidate 教えてもらってキャッシュ捨てさせた
  • cloudfront がS3バケットの相対パスを解決してるのに気付かず
  • => ホスト側と同じ raw-assets フォルダ切って相対パス設定
  • cloudfront 周りが落ち着いて podcast フィードを生成できるようになる
  • podcast フィード生成
  • podcast 審査中

マストドンについて所感

鉄は熱いうちに叩け。

  • 表面上はただの Twitter、というかTweetDeck
  • フェデレーションの抽象は一般ユーザーには理解が難しすぎる
  • せめて Yammer ぐらいは倒してほしい

フェデレーション(連邦)

ユーザーレベルだとTwitterと同じサーバー抽象にみえるが、その上流にサーバーレベルP2Pとでもいうのだろうか、サーバー同士が接続して大きなストリームを形成する。

fshinさんが指摘するように、リモートの削除に難があるが、炎上するような連中はそこまで考えないので、普及するとした場合、それが普及のボトルネックになることはないと考えている。これは善悪でかくあるべしという話ではなく、そうならざるをえないという話。

マストドンに関する現時点での解釈と感想 | F's Garage

中のコード

https://github.com/tootsuite/mastodon 読んだ。

Rails / Postgres / Redis / React / Redux / Node Websocket

素直。愚直。という印象。ISUCONの教材として殴り合うといったインフラエンジニアのおもちゃとしていいのではないか。

希望

Twitterはマイクロブロギングという文脈では今までこれといった競合がおらず*1、UIが商業的にどんどん邪悪に、サードパーティ開発者を締め出してどんどん傲慢になった上で、収益化に失敗という、割とどうしようもない落とし穴にハマっている。サードパーティ開発者の信頼を取り戻すことに取り組むという声明はでているが、信用されてはいない。正直言って、今が頂点で、落ち目のサービスになってしまっていたと思う。

マストドンに期待するのは、邪悪で傲慢になり続けるTwitterに対する競争相手になってくれることで、理想的には、Twitter側が GNU Social に対応せざるを得なくなるぐらい落ちぶれて、マストドンの1つのサーバーというレベルにまで蹴落とされるのが望ましい。UIとインフラで差別化してほしい。そうするとGNU Socialというマイクロブロギングの1プロトコルという立場で、オープンなプロトコルとして、互換サービス郡のエコシステムが花開くだろう。

昔はTumblrがそうなると予想していたが、そうならなかった。

で、流行るの

これが未来とか言うつもりはない。結局SNSは生まれては飽きられる運命にあるし、僕らは常に最新の棺桶に乗り換える永遠の流民であり、アーリーアダプターの居心地の良さはすぐ失われれる。もう失われつつある。一瞬流行ったけど、そんなものもあったね、で終わる可能性は高い。

自分が使うかというと、微妙。自分は2回データ吹っ飛んだので、だるい。とりあえず定点観測する。Twitter への圧力としてがんばってほしいですね、おわり。

https://mstdn.jp/@mizchi

*1:Facebookはちょっとずれてる

筋トレ、スカイクロラ

肩こりは治らないのは筋力が足りないからだ、という仮説のもと、最近毎日腕立てをするようにした。左肩だけ異常に凝ってる。右利きなので、左の筋力が足りないのでは?という仮説がある。

最初は12x2で死にそうになっていたが、徐々に回数を増やして、今日は 20x5 やった。やりすぎたと思った。が、全然肩が痛くない。筋肉は増えたと思う。肩こりが治ったかというと、多少良くなった気がしないでもないが、あまり関係はないのではないか。

どちらかというと、必要なのは背筋・広背筋で、ラットプルダウンを家に買おうと思って調べたが、それなりのやつが4万円する。場所も取るし、もっと別のトレーニングから入ったほうがいいかもしれない。家でできるトレーニングを調べている。

読書

スカイ・クロラの一巻を読み終わった。森博嗣はあんまり読んでなくて、この前「すべてがFになる」を読んだぐらいだった。自分の読書スタイルは、SFのオールタイムベストから入って、その周辺に及ぶという感じなのだが、読み終わって、面白いとは思ったのだけど、ちょっと引っかかる部分があった。

ドッグファイトのスピード感を演出するために、短文で改行するスタイルが、とてもライトノベル的演出だと感じてしまって、ラベリングで忌避するのは良くないとわかりつつ、苦手なジャンル的演出だという意識が先行して、その部分だけ冷静に読むことができなかった。たぶん逆で、ライトノベルが部分的に森博嗣的なのだろう。

そこでふと思ったのだけど、戦闘機モノは他にも、トップガンとか戦闘妖精雪風あるわけだけど、あれにロマンを感じる筆者の熱意は伝わってくるんだけど、自分には、それをイメージする力が足りてなくて、読んでいて多少歯がゆく感じる。想像力を補うのに、映像作品から入ったほうが良かったのだろうか。自分はミリタリーには疎くて、ミリタリーとしての戦闘機モノにはあまり興味を惹かれないのだが、SFとしてのロマンがそこに生まれるのはわかる。わかるが、及んでいない。歯痒いなぁ。

とりあえず今はスカイ・クロラの2巻を読んでいる。