はじめに
友達とのご飯、毎回こうなる。
「今日どこ行く?」
「どこでもいい」
「じゃあA店は?」
「うーん、まあいいけど…」
「じゃあB店は?」
「どこでもいいよ」
30分後、なんとなくA店に決まる。でも後から「実はB店の方が良かった」と思っていた人がいたりする。
多数決でも解決しない。「どこでもいい」と言いながら本当はどこでもよくない人が、なんとなく多数派に流れるだけだから。
この問題をロジックで解決しようとして、**kimeru(キメル)**を作りました。
アルゴリズムの設計
多数決の問題:消極的な賛成が見えない
多数決の最大の欠点は、「積極的に賛成」と「まあいいか」が同じ1票に見えることです。
全員が「まあいいか」で選ばれた案と、半数が「ぜひここ!」で選んだ案は、結果が同じでも意味が全然違います。
4段階評価にした理由
最初は◎◯×の三択で作ろうとしていました。でも三択だと真ん中の◯に逃げやすい。「まあいいか」も「ぜひここ!」も「できれば嫌」も全
部◯になってしまう。
そこで◯と×の間に△を加えて四択にしました。◯か△かを選ばないといけないので、賛成寄りか反対寄りかを必ず表明することになる。「
どちらかといえば」が見えるようになる設計です。
kimeruでは◎◯△×の4段階で評価します。
| 記号 | 意味 | スコア |
|---|---|---|
| ◎ | 最高! | +3pt |
| ◯ | いいね | +1pt |
| △ | うーん… | -3pt |
| × | それはない | -100pt |
「×」の拒否権
×は-100ptという強烈なペナルティを持っています。誰か一人でも×をつけた候補は事実上最下位になります。
「言い出しにくい『絶対嫌』をシステムが中立に処理する」という設計で、角を立てずに意思決定できます。
技術スタック
- Frontend: Next.js 16(App Router)+ TypeScript + Tailwind CSS
- Backend/DB: Supabase(PostgreSQL + Realtime)
- Hosting: Vercel
スタック選定の基準は「月額維持費ほぼゼロ」です。Vercel・Supabaseともに無料枠で十分動きます。
リアルタイム投票の実装
一番面白かった実装がリアルタイムの投票状況更新です。
Supabaseの postgres_changes を使って voters テーブルを購読し、誰かが投票するたびに待機画面のカウントが更新されます。全員
が投票完了した瞬間に、全員の画面が自動で結果ページへ遷移します。
const channel = supabase
.channel(`waiting-${roomId}`)
.on(
"postgres_changes",
{
event: "UPDATE",
schema: "public",
table: "voters",
filter: `room_id=eq.${roomId}`,
},
async () => {
const updated = await getVoters(roomId);
setVoters(updated);
if (updated.every((v) => v.hasVoted)) {
router.push(`/room/${roomId}/results`);
}
}
)
.subscribe();
Supabase Realtimeはこういうユースケースにすごく相性が良く、WebSocketの設定を自分で書かずに済むのが楽でした。
作ってみて
正直なところ、「どこでもいい問題」に本当に困っている人がどれくらいいるのかはまだわかっていません。課題があるんじゃないかとい
う仮説で作り始めたプロダクトです。
ただ、△という選択肢を置いたことで「消極的な賛成を可視化する」という設計は、意思決定の場面に広く使えると思っています。ご飯だけ
じゃなく、旅行先・プレゼント選び・会議の議題など。
実際に使ってみた感想・フィードバックをもらえると嬉しいです。