game
ClojureScript
processing
DDD
関数型プログラミング

ClojureScriptで配球予測ゲームを書いてます

前書き

 タイトル通り、ClojureScriptでバドミントンの配球を予測して遊ぶゲームを書いています。

 動機としては趣味のスポーツに何かITを生かせないか考えていて、一番現実的に役に立ちそうだと思ったからです。

 配球のシミュレーションは比較的論理的に組めるので、人間よりもコンピュータのほうが強くしやすいです。これを利用すれば、上達にだいぶ貢献してくれるのではという感覚があります。

 Systems of Recordのテーマ管理や練習管理は入力の手間が大きい割に、方針や手段が変動しやすく、開発コストと見合わなかったです。

進捗

 GitHub dexia2/readnext

  • とりあえず動くところまではできた
  • デプロイしておらずビルドが必要

技術セット

  • ClojureScript
    • 動機
      • 元々Lispが好きで、かつ関数型言語に触れてみたかった
        • Common Lisp、Emacs Lispは関数型とは言い難いかな。。
      • Webが専門なので、とっつきやすそうだった
  • Leiningen
    • ビルドやライブラリの依存関係を管理するツール
      • NPMに近いという認識
      • ほぼデファクトスタンダードらしい
  • quil
    • ProcessingのClojure(Script)版
      • アニメーションを比較的簡単に書けるライブラリ
      • 実態はcanvas
    • 動機
      • 比較的楽な方法で視覚化できるツールを探していて、ちょうどよかった
  • Emacs
    • 最初はIntelliJ IDEAで書いていたが、こちらのほうがLispらしい気がしたので移行
      • どちらでもそこまで問題はなかった

設計方針

 現時点(2018年1月)なので、また変わるかもしれません。

ドメイン言語で書く

 バドミントンというスポーツを扱う以上、できる限りバドミントンの言葉を使って書こうと思いました。今はやりのDDD(ドメイン駆動設計)に近い思想で開発しました。

;; ゲーム終了判定
(defn game-end?
  [{rallies :rallies
    score-limit :score-limit
    deuce-limit :deuce-limit
    }]
  (let [{npc :npc pc :pc} (group-by :won-by rallies )
        scores (list (count pc) (count npc))]
    (or (some (fn [s] (>= s deuce-limit)) scores)
        (and (some (fn [s] (= s score-limit)) scores)
             (>= (js/Math.abs (- (first scores) (second scores)))
                 2)))))

レイヤーをきちんと分割する

 ファイルは下記のような分割を意識しています。

  • 画面描画
  • ルール
  • ゲーム設定
  • AI
  • ユーティリティ

 また、副作用のある関数とそうでない関数は極力分けて、ルールとAIの中には一切副作用は持ち込まないようにしました。

 最終的には画面描画とゲーム設定に副作用を持ち込むようにしています。これによって、考えることを減らすことを狙いました。

;; ルールは副作用なし
(defn next-server
  [{:keys [rallies first-server]}]
  (get (last-decided-rally rallies) :won-by first-server))

;; データの変更も副作用なし
(defn record-service
  [context serve]
  (update-in context [:rallies] conj serve ))

;; ゲーム設定ではルール(=d)を利用しながら、副作用を扱う
(defn record-service! []
  (let [serve (d/next-serve @play-context)]
    (reset! play-context
      (d/record-service @play-context serve))))

lagénorhynqueさんにご指摘いただき、少し改善しました

感想

ClojureScriptは使いやすい

とにかく楽に書ける

 面倒なタスクは先述のLeiningenがこなしてくれるので、環境周りの苦痛はほぼないです。プログラミングを手早く書きたいならこれ以上ありがたいことはないです。

 また、Clojure(Script)自体もきめ細やかな関数や制御が多いです。
 例えば、マップから変数に簡単に展開する構文(分配束縛)や関数のチェインも簡単に書けます。

;; letで分配束縛をしている
;; ->>が関数の連続適用
(defn record-stroke-to-context
  [context player direction]
  (let [{rallies :rallies} context]
    (->>
     (next-stroke rallies player direction prediction)
     (record-stroke-to-rally rallies)
     ((fn [r] (assoc-in context [:rallies] r))))))

 リテラルも多く、色んなことをシンプルに表現できるというところに強みがあるのではないかと思います。

 きれいに書く、手軽に書くという点をとれば、Clojure(Script)は他の言語に全く引けをとっていないと感じます。

バグ探しが比較的楽

 副作用がわかりやすかったり(!がついていたりする)、関数の連続適用を使うためにシグネチャをそろえないといけないという制約から、自然にきれいなプログラムになりやすい気がしました。

 インデントを初めとしたフォーマットも他の言語よりルールが明確で、そういう点でも違和感が見えやすいのかなという印象もあります。

 そして、関数型なので状態を持たないことを考えると、デバッグに必要以上に時間がかかるということはなかったです。関数を深くしていけばつらいこともありましたが、迷子になることは少ないです。

 そういったところで色んなものの見通しがよく、バグや問題になりそうな部分が発見しやすいという感想を持ちました。

最初は混乱するとは思う

 JavaScriptを想像して入ったので、そこがミスでした。

 基本的に状態は変更できませんし、オブジェクトの構造も工夫を凝らしてあるので、素のJavaScriptとは構造から違います。

 なので、JavaScriptの世界を忘れて頑張るのがいいかなという感じでした。

 逆にその2つを行き来し始めると、壁にぶつかるのかなぁという懸念はなくはないです。あとは、APIが多すぎて、覚えるのつらいです。書く分には好きなやつ使えばいいのですが。

シンプルに書くという感覚をつかめた

 関数型言語はモデリングが必要なドメインに向いているのかという懸念はありましたが、杞憂でした。

 DDD的な設計を持ち込んでも、全く問題なくコードをかけると思いますし、さらに関数型言語自体が宣言的な書き方をするのでむしろ相性がいいのではと思います。

 また、レイヤーやモデルを最初に切って固定するよりも組み換えがしやすいので、柔軟性を発揮できると思います。

 Processingも宣言的に描画を命令するので、それも設計思想とうまくマッチしたと感じます。技術セット全体でシンプルさを表現できた感覚があります。

スポーツをより楽しくとらえることができた

 やはり、運動の視覚化というのは直感的でスポーツそのものの本質を付いていると思いますね。

 また、配球を考えるという作業は自分自身の棚卸でもあるので、もっと役に立つんじゃないかという可能性は感じました。正直全然当たらないです。

 こういう切り口でシステムを作りながら、遊んでいけたら楽しいだろうなと思いました。

失敗したこと

  • 関数の入出力の設計を後からでも変えられるだろうと感じて、シグネチャを雑に決めた
    • シグネチャがそろっておらず、関数の連続適用ができなかったりした
    • あとから変更するのは簡単ではなかった
    • 絶対に変わる部分は柔軟に受ける設計が必要
      • そういう点ではマップは便利な感じはした
  • いきなり描画に手を付けた
    • ボトムアップに作るほうが楽だったし、関数型に近かった
    • 大きいものを分解するのは結局しんどい
  • 副作用を使うところの返り値を適当に決めた
    • あとの処理と連続して書くことがあるので、失敗だった
      • voidとは違う感覚を持たないといけなかった
    • なにを返すべきかと深く考えることが必要だった

 設計の重要性やプログラミング自体の課題がオブジェクト指向と関数型で根本的に違うとは感じなかったです。デザインパターンに近い問題は同様に起きます。

今後

  • デザイン周りとか見栄えはなんとかしないといけない
    • それが終われば、デプロイも視野に入れたい
  • AIの精度をひたすら向上させたい
    • できれば、機械学習と混ぜてAPIでつなげたりできたらうれしい
      • その際の技術セットは検討中 + perl、Scala、Clojureのどれかだとは思う
  • 最終的には3次元を表現しないと不完全かなという気はしている

まとめ

  • ClojureScriptは楽に書けるのでおすすめ
  • Processing、関数型、DDDは全部似ている概念だったような感じがした
    • シンプルにわかりやすく、宣言的に書く
    • とっかかりにはいいかなと思う
  • シミュレーションを視覚化することはスポーツに役立つのでぜひ