2025年10月、agri-sim(AgriSim-R)でジャガイモ栽培シミュレーションを作った。ターミナルで動く農業シム。Ink(ターミナル用 React)+ Zustand という構成を試したかったのが主目的。
環境
- TypeScript + Node 22
- Ink 4(React for CLI)で画面描画
- Zustand で状態管理
seedrandomでシード固定の乱数- Vitest + ink-testing-library
モデル
シミュレーションは ドメインモデルが面白さの9割 だと思っているので、src/core/ にロジックを切り出してそこを厚くした。
- GDD — 生育度日。気温の積算で成長を進める
- 気象 — 確率イベントで日々の天候を生成
- 病害 — Smith 期間的な考え方で発病リスクを上げ下げ
- 土壌 — 水分モデル
- 市場 — マルコフ連鎖で価格が遷移
- scoring — 最後に決算してスコア化
100〜140日の1作期を、日次の意思決定(水やり・防除・収穫など)で回していく。難易度は config_easy/normal/hard.json で外から差し替えられるようにした。
作ってみて効いたこと
core の各モデルを純粋関数寄りに書いて、UI(Ink コンポーネント)と分けたのが大きかった。おかげで growth / disease / market / soil / scoring を単体でテストでき、最終的に 188 テスト / カバレッジ 97% くらいまで持っていけた。乱数を seedrandom でシード固定にしてあるので、「同じ種なら同じ展開」になって、テストもデバッグも安定する。
UI 側は Ink で十分で、Sparkline みたいな簡易グラフもターミナル文字だけでそれっぽく出せた。あとからコマンドライン引数での言語切り替え(ja/en のロケールファイル)も足した。
おわり
シミュレーションものは、UI を作り込むより モデルをテスタブルに切って、数式とパラメータを詰める 方が圧倒的に効く、というのを再確認した。Ink + Zustand + シード固定乱数の組み合わせは、この手の「ロジックが主役のターミナルアプリ」にかなり向いてそう。次は別ドメイン(在庫とか経済とか)でも同じ骨格を使い回せそうだと思っている。