# 小諸そばのメニューをガチャで決めるサイトを作った

  仕事で書くコードは、当然ながらほぼ全部が業務のためです。それは嫌ではありません。ただ、業務のコードばかり書いていると、たまに「誰の役にも立たないものを全力で作る」という感覚が懐かしくなり、気分転換がしたくなります。

私が見ている YouTube チャンネルに[ラムダ技術部](https://youtu.be/LbFH3kWIxes?si=k-xqiRjRwsZ4nxF8)があります。技術系の動画を投稿しているチャンネルで、真面目な解説もあれば、冷蔵庫のセキュリティ装置やダジャレのおもしろさを判定して部屋を暖めるシステムといった、技術力をふんだんに使って「しょうもないもの」を全力で作る企画もあります。リンク先の動画はその後者の話で、見ていて自分も久々に「しょうもないもの」を作りたくなりました。

題材は、平日3回通っている小諸そばです。半年以上通っているのですが、たまには違うものにしようと思いながら、毎回同じメニューを選んでしまいます。だったら、メニューをガチャで決めてくれるアプリケーションを作ればよいのではないかと思いつきました。

PWA[^pwa] として組んで、スマートフォンのホーム画面から起動できる形にしました。サイトは[こちら](https://komoro-gacha.suntory-n-water.com/)にあります。

![小諸そばガチャのトップ画面](https://pub-151065dba8464e6982571edb9ce95445.r2.dev/images/f3f1b80ff49104f3c3fcc9080c40a9ad.png)

## ガチャに身を委ねたかった

そもそも、なぜ「ランダムで決める」という発想になったのか。

仕事で細かな判断を積み重ねていると、昼食のメニューを選ぶような些細なことまで考えるのが面倒になります。「今日何食べよう」を自分で決めたくなかったというのが正直なところです。

ガチャが出した結果には、素直に従う。指定したのに不満があるとモヤモヤしますが、ガチャに身を委ねた結果には文句が言えません。この「選ばされた感」がほしかったのです。

## 量の閾値をどう決めるか

実装で一番おもしろかったのは、抽選ロジックでも UI でもなく、「量の定義」でした。

このアプリケーションには「かるめ」「ふつう」「おおめ」「がっつり」という量の選択肢があります。ユーザーから見ればただのラベルですが、内部ではカロリーの閾値を持つ必要があります。「かるめを選んだら何 kcal 以下のメニューが出るのか」を決めないと、抽選プールが作れないのです。

カロリーは小諸そばが公開している[エネルギー・アレルギー一覧表](https://9d5817b7-89b0-4bd2-b400-a869f185ba70.filesusr.com/ugd/5b0732_aa653632e0b7441b82c3cb4f9d3098ad.pdf)から取得できます。ただ、表をそのまま型に落とそうとして詰まりました。

同じ「かけ」でも、「かけそば」は330kcal、「かけうどん」は282kcalと、麺種別ごとに別エントリで定義されています。さらに大盛りは加算カロリーで、麺種別ごとに別値です(かけそば +141 / かけうどん +87 / せいろ +140 / うどんせいろ +95)。型に落とすときに、品名 × 麺種別 × 温冷を別々のエントリとして展開する必要がありました。

```typescript
export const MENU_CATEGORIES = [
  "麺",
  "丼単品",
  "満腹セット",
  "ミニ丼セット",
] as const;
```

カテゴリも4種類に分けました。これは UI ではなく、抽選ロジックの分岐に効きます。たとえば「満腹セット」(大きい丼+麺)には大盛りを付与しません。丼がすでに大きいのに、さらに大盛りを乗せるのは現実の注文として不自然だからです。一方、ミニ丼セットや麺単品には大盛りを付与できます。

サイドメニューの「いなり」も同じ発想で、ガード条件を入れました。「ふつう」を選んだのに、いなりが付いた結果カロリーが「おおめ」相当になってしまうと、量の定義が壊れます。ベースカロリー + 160kcal が量カテゴリの上限を超えない場合だけ、25%の確率で付与するようにしました。

「量の感覚」という、本来あいまいなものを数値で定義する作業は、ドメイン理解の良い練習になりました。閾値の具体値はまだ調整中ですが、設計の枠組みとしては気に入っています。

## メニューの画像をどうするか

開発で一番悩んだのは、メニューの画像でした。

最初は公式ホームページの画像を使おうとしました。が、公式の画像は一部のメニューにしか存在せず、100件近い全メニューはカバーできません。

次にAI生成を試しました。メニュー × 麺種別の組み合わせが大量にあり、生成のたびに品質がぶれます。「かき揚げそば」と「かき揚げうどん」で別の見た目になるのは困ります。

自分で撮影する案も考えました。100件分を集めるために小諸そばに通い続けるのは現実的ではありません。他の人の画像は著作権の問題があります。

文字列だけで表現してみたところ、想像以上にインパクトがなくて没にしました。

最終的に、SVGでそれっぽいイラストを描くことに落ち着きました。SVGならAI生成のように出力がぶれず、メニューごとに同じ品質で表示できます。完璧に写実的である必要はなく、「そばっぽいもの」「丼っぽいもの」が伝われば十分です。

![ガチャを引いた後の結果画面。SVGで描いたメニューイラストが表示されている](https://pub-151065dba8464e6982571edb9ce95445.r2.dev/images/29baf3a66db5b42241879ae674f12610.png)

## まとめ

- 休日に「しょうもないもの」を作る時間は、開発者としての気分転換になる。仕事のコードでは出せない、自分の趣味全開のコードを書ける場として個人開発はちょうどよい
- 「量」のような感覚的な軸をコードに落とすには、カロリーという数値に置き換える必要があった。あいまいなものを定義する作業は、ドメインモデリングの練習としておもしろい
- カテゴリ設計が抽選ロジックの分岐と直結する。満腹セットに大盛りを付与しない、いなりで上限を超えさせない、といった現実の制約をカテゴリと型で表現できた
- 画像問題は公式・AI生成・自撮り・他人の画像・文字列とすべてボツになった末にSVGに落ち着いた。最初からSVGにすればよかったとも思うが、一通りつぶしてから納得できたのはよかった

[^pwa]: Progressive Web App の略。Webサイトをスマートフォンのホーム画面に追加して、アプリケーションのように全画面で起動できるようにする技術の総称。
    