DEV Community

Cover image for Building "Customer Escape Room": A Voice-Powered Game That Teaches Customer Experience by Making You Live Through Support Hell
Harish Kotra (he/him)
Harish Kotra (he/him)

Posted on

Building "Customer Escape Room": A Voice-Powered Game That Teaches Customer Experience by Making You Live Through Support Hell

"Can you survive being a customer?"

At every hackathon, teams build the same thing: chatbots, AI agents, customer support copilots. All of them try to fix customer experience. None of them force you to feel what broken customer experience is actually like.

So I built the opposite. A game.


The Idea

Customer Escape Room is a mobile-first, voice-powered game where players must escape increasingly absurd customer service nightmares. Think Portal meets The Stanley Parable meets your worst call center experience.

Six levels. Six NPCs. Six unique mechanics. All connected by one question: can you survive being a customer?


The Tech Stack

Technology Purpose
Next.js 16 (App Router) Full-stack framework
TypeScript Type safety
Tailwind CSS v4 Styling
Framer Motion Animations
shadcn/ui Component library
MediaRecorder API Voice recording
OpenAI Whisper Speech-to-text
React Context + useReducer State management

Architecture

┌──────────────────────────────────────────┐
│               Page Layer                  │
│  /  (Game Hub)      /game/judge (Demo)   │
├──────────────────────────────────────────┤
│            Game Engine                    │
│  ┌────────────────────────────────────┐  │
│  │  Level 1  │  Level 2  │  Level 3  │  │
│  │  Level 4  │  Level 5  │  Level 6  │  │
│  └────────────────────────────────────┘  │
├──────────────────────────────────────────┤
│        Shared Components                 │
│  VoiceInput  NPCDialogue  ScoreDisplay   │
│  Achievements  ShareCard  LevelComplete  │
├──────────────────────────────────────────┤
│              State Layer                 │
│  GameProvider → useReducer → Context     │
├──────────────────────────────────────────┤
│               API Layer                  │
│  /api/ai  →  OpenAI / Featherless       │
│  /api/ai  →  Whisper Transcription      │
└──────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The 6 Levels

Level 1: The Refund Maze

The player is trapped in a corporate maze. The Refund Robot NPC obsesses over "Policy 42(a)(3)(b)(ii)" — a fictional policy that keeps getting more nested the deeper you go. The player must ask for Form R-1, fill it out, then demand supervisor escalation to escape.

Mechanic: Bureaucratic layer navigation. Each stage requires specific keywords to advance.

Level 2: IVR Hell

A timed level. The player is stuck in an infinite phone menu system that loops back on itself. Press 1 for Billing. Press 2 for Billing Questions. Press 3 for Billing Questions About Billing. The only escape is saying "operator" or "representative" — the hidden human path.

Mechanic: Countdown timer + menu depth tracking. The hint system activates after 3+ failed attempts.

Level 3: Transfer Dungeon

The Transfer Goblin enthusiastically transfers the player to random departments, forgetting everything each time. The player must track their own story consistency — repeating yourself gets penalized. Escape by asking for "Escalations" after 5+ transfers.

Mechanic: Transfer counter + repetition detection (word-level matching against previous responses).

Level 4: Subscription Prison

Manager Dragon uses every retention tactic in the book. "Are you sure?" asked 4 times. Discount offers. Guilt trips. The player must stay firm through multiple "sure?" checkpoints without accepting any offers.

Mechanic: Persuasion gauntlet. Saying "no" or accepting offers resets progress. Only firm persistence wins.

Level 5: Chatbot Labyrinth

The Chatbot Wizard answers every question wrong with extreme confidence. The player must discover 3 hidden keywords ("human", "agent", "representative") to prove they're not a bot. Every regular input is met with creatively unhelpful responses.

Mechanic: Keyword collection. Misunderstanding counter unlocks progressive hints.

Level 6: Karen Boss Fight

The final boss. Karen Queen interrupts, contradicts herself, escalates unreasonably, and demands everything for free. The player must use empathy words ("understand", "sorry", "help") to de-escalate without triggering more rage.

Mechanic: De-escalation score. Empathy words reduce anger; demanding or fighting back triggers interruptions.


Level Design Pattern

Every level follows the same architecture:

type Stage = "intro" | "stage1" | "stage2" | "escaped"

// Stage data with deterministic responses
const STAGE_DATA: Record<Stage, StageConfig> = {
  intro: {
    npc: "...NPC dialogue...",
    advanceOn: ["keyword1", "keyword2"],
    advanceTo: "stage1",
    hint: "Hint for confused players"
  },
  // ...
}

function handleTranscript(text: string) {
  // 1. Detect confused input ("i don't know", "help")
  if (isConfused(text)) return showHint()

  // 2. Check for advancement keywords
  if (matchesKeyword(text, currentStage.advanceOn)) {
    return advanceStage(currentStage.advanceTo)
  }

  // 3. Redirect to objective
  return showRedirectMessage()
}
Enter fullscreen mode Exit fullscreen mode

This pattern ensures:

  • No random responses — every NPC line is stage-appropriate
  • Confused players get help — "i don't know" always shows a hint
  • Clear progression — keywords gate advancement
  • Graceful failure — off-script input redirects to the objective

Voice System

The voice system uses the native MediaRecorder API:

// src/lib/voice.ts
export function useVoice() {
  const startRecording = async () => {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
    const mediaRecorder = new MediaRecorder(stream)
    // ... record audio chunks
  }

  const stopRecording = async () => {
    // ... create blob, send to /api/ai with X-Transcribe
    const text = await transcribeAudio(blob)
    return text
  }
}
Enter fullscreen mode Exit fullscreen mode

The VoiceInput component wraps this with a toggle:

<VoiceInput onTranscript={handleTranscript} />
// Shows mic button + "⌨️ Type instead" toggle
Enter fullscreen mode Exit fullscreen mode

Falls back gracefully:

  • No microphone → shows text input only
  • No API key → simulates transcription with context-aware phrases
  • Error → shows text input as fallback

AI Abstraction Layer

The API route handles both dialogue generation and transcription through a unified endpoint:

// /api/ai handles:
// 1. Chat completions (dialogue)
// 2. Audio transcriptions (Whisper)

const provider = process.env.AI_PROVIDER || "openai"
const baseUrl = provider === "featherless"
  ? "https://api.featherless.ai/v1"
  : "https://api.openai.com/v1"
Enter fullscreen mode Exit fullscreen mode

Provider switching via env vars:

AI_PROVIDER=openai           # Uses gpt-4o-mini
AI_PROVIDER=featherless      # Uses Llama 3.1 8B
Enter fullscreen mode Exit fullscreen mode

State Management

Game state uses React Context with useReducer:

type Action =
  | { type: "SET_PHASE"; phase: GamePhase }
  | { type: "SET_LEVEL"; levelId: LevelId }
  | { type: "ADD_SCORE"; levelId: LevelId; category: ScoreCategory; amount: number }
  | { type: "ADD_ACHIEVEMENT"; achievementId: string }
  | { type: "RESET_GAME" }

// Scores are calculated cumulatively
// Achievements are checked and dispatched by level logic
// Game phase controls which screen renders
Enter fullscreen mode Exit fullscreen mode

Scoring & Ranks

6 Categories × 6 Levels × 100 points = 3,600 max

Categories:
  patience       - Staying calm under absurdity
  problemSolving - Finding the escape path
  persistence    - Not giving up
  empathy        - Showing understanding (critical for Level 6)
  escapeSpeed    - Completing efficiently
  negotiation    - Arguing effectively

Ranks:
  🥉 Casual Complainer    (0+ pts)
  🥈 Support Veteran      (300+ pts)
  🥇 Customer Champion    (600+ pts)
  👑 Escape Legend        (900+ pts)
Enter fullscreen mode Exit fullscreen mode

Judge Demo Mode

One of the most important features: /game/judge drops judges directly into Level 2 (IVR Hell) with zero friction.

// /game/judge/page.tsx
<GameEngine initialLevel={2} />
Enter fullscreen mode Exit fullscreen mode

No signup. No onboarding. Ten seconds from scanning a QR code to playing.


What I'd Add Next

  1. Multiplayer — Socket.io is installed. 4-8 players in a shared escape room with voice cooperation.
  2. Dynamic AI scenarios — Use the AI provider to generate unique company names and NPC dialogue per playthrough.
  3. Supabase persistence — Leaderboards, saved scores, daily challenges.
  4. Sound design — Hold music, menu beeps, NPC voice lines.
  5. More levels — "The Data Deletion Request," "The Warranty Voidening," "The Return Policy Paradox."
  6. Share cards — Generate shareable SVG images with scores and rank.

Try It

npm install
npm run dev
# → http://localhost:3000
# → http://localhost:3000/game/judge (judge demo)
Enter fullscreen mode Exit fullscreen mode

Screenshots

How it works - demo 1

How it works - demo 2

How it works - demo 3

How it works - demo 4

Code & more: https://www.dailybuild.xyz/project/153-escape-velocity

Top comments (0)