A daily sudoku puzzle game built as a Discord Activity, letting you play the same puzzle as your friends and compete on leaderboards.
- Daily Puzzles — One unique sudoku per day, available in three difficulty levels (Easy, Medium, Hard). All players get the same puzzle on the same day.
- Discord Integration — Authenticate via Discord OAuth2, play inside Discord servers, and see server-specific leaderboards.
- Leaderboards — Compete on time-based rankings (with notes and mistakes as tiebreakers). Top 10 + your position shown when you rank outside it.
- Progress Persistence — Auto-saves game state (grid, notes, timer, mistakes) every 10 seconds and after key actions.
- Trophies — Earn badges for flawless runs: 💎 No mistakes and ✨ No notes.
- Responsive Design — Works in Discord's focused and picture-in-picture layouts, with CSS container queries for fluid sizing.
| Layer | Technology |
|---|---|
| Framework | Nuxt 4 (SSR disabled, SPA) |
| UI | Nuxt UI + Tailwind CSS v4 |
| State | Pinia |
| Database | PostgreSQL via Drizzle ORM |
| Auth | nuxt-auth-utils + Discord OAuth2 |
| Sudoku Engine | sudoku-core |
| Error Handling | neverthrow (Result types) |
| Validation | Zod v4 |
| Discord SDK | @discord/embedded-app-sdk |
| Fonts | Nuxt Fonts (Switzer, Alpino local; Outfit Google) |
| CI/CD | GitHub Actions → Railpack → GHCR → Dokploy |
app/ # Frontend (Vue 3 + Nuxt)
├── components/sudoku/ # Game UI: Board, Cell, NPad, Modals
├── stores/ # Pinia stores: game, leaderboard, discord, timer, homepage
├── composables/ # useGameSync, useCountdown, useDiscordSdk/Event
├── pages/ # / (homepage), /daily/[difficulty] (game page)
├── assets/css/ # Tailwind config, elevated design tokens, sudoku grid styles
└── plugins/ # Discord SDK client initialization
server/ # Backend (Nitro / Nuxt server)
├── api/ # REST endpoints
│ ├── auth/discord/ # OAuth2 token exchange
│ ├── games/daily/ # Puzzle fetch, progress, verification, status
│ └── leaderboard/ # Server-scoped rankings
├── services/ # Business logic (GameService, LeaderboardService, UserService)
├── db/ # Drizzle schema, migrations, relations
├── tasks/ # Nitro tasks (db:seed for dev data)
└── utils/ # DB client, leaderboard queries, time/date helpers
shared/ # Code shared between client and server
├── types/ # TypeScript interfaces (game, sudoku, discord, errors)
└── utils/ # Sudoku generation, parsing, verification, avatar URLs
| Table | Purpose |
|---|---|
users |
Discord user profiles (id, username, avatar, discriminator) |
user_guild_memberships |
Which users belong to which Discord servers |
daily_puzzles |
One sudoku puzzle per date + difficulty (81-char string, unique constraint) |
game_progress |
Per-user per-puzzle state (grid, moves, notes, hints, mistakes, time, completion) |
Leaderboard indexes are partial (WHERE is_completed = true) on timeSpent, notes, hints, moves, and mistakes.
All endpoints require Discord authentication via session.
| Method | Path | Description |
|---|---|---|
POST |
/api/auth/discord/token |
Exchange Discord OAuth2 code for session |
GET |
/api/games/daily/[difficulty] |
Get today's puzzle + user progress |
GET |
/api/games/daily |
Convenience: today's easy puzzle |
POST |
/api/games/progress |
Save game state (grid, timer, stats) |
POST |
/api/games/verify |
Verify completed board, mark as done |
GET |
/api/games/daily/status |
User's status across all 3 daily difficulties |
GET |
/api/games/daily/statuses/global |
Global completion counts per difficulty |
GET |
/api/games/daily/statuses/guild/[guildId] |
Guild-specific completion stats |
GET |
/api/leaderboard/daily/[scope]/[difficulty] |
Top 7 leaderboard entries + player context |
# Install dependencies
bun install
# Copy and configure environment
cp .env.example .env # (create .env with your values)| Variable | Description |
|---|---|
NUXT_DATABASE_URL |
PostgreSQL connection string |
NUXT_SESSION_PASSWORD |
Session encryption key (32+ chars) |
NUXT_DISCORD_CLIENT_ID |
Discord OAuth2 Client ID |
NUXT_DISCORD_CLIENT_SECRET |
Discord OAuth2 Client Secret |
NUXT_PUBLIC_DEV_MODE |
Set to true for mock Discord auth in dev |
bun dev # Start dev server (hosted, accessible on LAN)
bun dev:discord # Start dev server with Discord Activity mode
bun build # Production build
bun generate # Static generation
bun preview # Preview production build
bun lint # ESLint
bun ngrok # Expose dev server via ngrok# Run migrations
bun drizzle-kit generate # Generate migration from schema changes
bun drizzle-kit push # Push schema to database
bun drizzle-kit migrate # Run pending migrations
# Seed dev data (400 fake users, random progress)
npx nitro run db:seedCustom CSS variables create a tactile, layered aesthetic with hard box shadows (no blur):
.elevated {
box-shadow: 0 8px 0 4px var(--box-shadow-color), 0 0 0 4px var(--box-shadow-color);
}Buttons and cards "press down" on hover/active states by translating along the Y axis, maintaining the shadow offset.
- Built with CSS Grid (3×3 subgrids, each containing a 3×3 cell grid)
- Uses CSS container queries (
100cqmin) for responsive sizing - Related cells (same row/column/box) and same-number highlighting
- Notes rendered as a 3×3 sub-grid within each cell
The useGameSync composable provides:
- Debounce saves — 2s after player actions (insert/clear)
- Periodic autosave — Every 10 seconds with change detection
- Immediate saves — On pause and reset grid
- Final save — On component unmount
The app runs inside Discord as an Embedded App. Key integrations:
- OAuth2 flow — Discord SDK
authorize()→ code exchange → session creation - Layout modes — Adapts to
FOCUSED(full game) andPIP(minimal title) - Guild context — Server leaderboards show only members of the host server
- Dev mock mode — When
NUXT_PUBLIC_DEV_MODE=true, uses@discord/embedded-app-sdk'sDiscordSDKMockwith fake guild/user data
- TypeScript strict mode with
typeCheck: true - ESLint with @antfu/eslint-config
- neverthrow plugin for exhaustive error handling on
Resulttypes - Zod v4 for runtime API body validation
- Drizzle ORM for type-safe database queries with auto-generated relations
CI/CD pipeline (.github/workflows):
- Push to
mainorstaging→ GitHub Actions - Railpack builds the container image (BuildKit enabled)
- Image pushed to GHCR
- Webhook triggers Dokploy for deployment
Private project.