A multiplayer card game inspired by Cards Against Humanity — where AI personas with distinct personalities compete to create the funniest responses to hilarious prompts. Watch AI models battle each other, vote on winners, or jump in and compete alongside them in real-time.
- AI Battle Royale — Watch AI models go head-to-head with zero human intervention
- Human vs AI — Compete directly against AI opponents
- AI Judge — Submit your responses and let an AI pick the winner
- Collaborative — Team up with an AI partner against other teams
Each AI opponent has a unique personality and humor style:
| Persona | Style | Creativity |
|---|---|---|
| Chaotic Carl | Absurd, random, surreal humor | 1.0 (High) |
| Sophisticated Sophie | Witty, intellectual wordplay | 0.7 (Medium) |
| Edgy Eddie | Dark humor, boundary-pushing | 0.9 (High) |
| Wholesome Wendy | Clean, family-friendly fun | 0.5 (Medium) |
| Literal Larry | Misses the joke, accidentally funny | 0.3 (Low) |
- React 19 + React Router 7 — Server-rendered UI with nested routing
- Convex — Real-time backend with automatic subscriptions (no WebSocket boilerplate)
- OpenAI API — AI response generation with per-persona temperature tuning
- Tailwind CSS 4 — Neon-themed dark mode styling
- Upstash Redis — Rate limiting (optional)
- Playwright — End-to-end testing
- TypeScript — Throughout
- Node.js 20+
- A Convex account
- An OpenAI API key
npm installCreate a .env file:
VITE_CONVEX_URL=https://your-project.convex.cloud
Set backend secrets in the Convex Dashboard:
| Variable | Required | Description |
|---|---|---|
OPENAI_API_KEY |
Yes | Default OpenAI key for AI responses |
UPSTASH_REDIS_URL |
No | Enables rate limiting |
UPSTASH_REDIS_TOKEN |
No | Enables rate limiting |
ENCRYPTION_KEY |
For BYOK | 32-byte hex key for encrypting user API keys |
Generate an encryption key:
openssl rand -hex 32npm run devThe app will be available at http://localhost:5173.
Users can bring their own OpenAI API key to unlock premium features:
- More AI players per game — Up to 5 AI players (vs. 3 with the default key)
- No rate limits — Bypass the 10/min and 100/day per-game limits
- Automatic fallback — If a user's key fails, the system falls back to the default key
- User enters their API key on the Settings page (
/settings) - The key is validated with a lightweight API call (listing models)
- The key is encrypted with AES-256-GCM using the
ENCRYPTION_KEYenv var - The encrypted key is stored in the
userApiKeystable in Convex - When the user hosts a game, the AI service decrypts and uses their key
- The key is never sent to or stored on the frontend — encryption/decryption happens server-side in Convex actions
- Keys are encrypted at rest with AES-256-GCM (same standard as Vercel and AWS)
- The
ENCRYPTION_KEYis stored in the Convex Dashboard, never in code - Decryption only happens inside Convex actions at the moment of API call
- The frontend only sees a masked hint (e.g.,
...7xQ2) - If a key is found to be invalid during gameplay, it's automatically marked as invalid
Users can create custom AI personalities:
- Go to Settings (
/settings) - Click Create New Persona
- Configure:
- Name — Display name (e.g., "Sarcastic Steve")
- Personality — Short description shown in the game lobby
- Behavior Instructions — Detailed prompt describing the AI's humor style
- Creativity — Temperature slider from Focused (0.1) to Chaotic (1.2)
- Public/Private — Public personas can be used by any player
- Use your persona in the Create Game lobby
Custom persona prompts are wrapped with a system-level instruction that:
- Enforces the Cards Against Humanity game format
- Limits responses to short phrases
- Prevents prompt injection by always prefixing with game rules
- Bounds temperature to 0.1–1.2
npm run build
npm run startOr with Docker:
docker build -t ai-against-humanity .
docker run -p 3000:3000 ai-against-humanitynpm test # Run all E2E tests
npm run test:unit # Run unit tests (encryption, etc.)
npm run test:ui # Playwright UI mode
npm run test:headed # Tests with visible browser
npm run test:debug # Debug modeapp/
├── components/ # Reusable UI (GameBoard, PlayerList, Card, etc.)
├── routes/ # Pages — home, game creation, lobby, active game
│ ├── home.tsx
│ ├── games._index.tsx
│ ├── games.new.tsx
│ ├── games.$gameId.tsx
│ └── settings.tsx # API keys + custom persona management
└── lib/ # Utilities, constants, helpers
convex/
├── schema.ts # Database schema (users, games, cards, apiKeys, customPersonas)
├── games.ts # Game logic (mutations & queries)
├── rounds.ts # Round management
├── ai.ts # AI response generation & caching (Node.js actions)
├── aiQueries.ts # AI-related queries & mutations (V8 runtime)
├── apiKeys.ts # API key CRUD (encrypt, validate, store)
├── customPersonas.ts # Custom persona CRUD
├── encryption.ts # AES-256-GCM encryption utilities
├── users.ts # User management
├── rateLimit.ts # Rate limiting helpers
└── seed.ts # Database seeding
e2e/ # Playwright test specs