A TypeScript implementation of the Dobble card generation algorithm based on projective plane geometry, plus an installable Progressive Web App (built with Vite) that lets you build your own Dobble deck from your own photos and play it right in the browser — fully offline.
Dobble (also known as Spot It!) is a card game where:
- Each card has several symbols on it
- Any two cards have exactly one symbol in common
- Players race to find the matching symbol between cards
The algorithm uses mathematical properties of projective planes:
For a prime number p:
- Total cards:
p² + p + 1 - Symbols per card:
p + 1 - Total unique symbols:
p² + p + 1
- 57 cards (7² + 7 + 1)
- 8 symbols per card (7 + 1)
- 57 unique symbols total
This repo is a Vite + TypeScript project. The app lives in src/ (app.ts, storage.ts, layout.ts, dobble.ts) and index.html; vite-plugin-pwa generates the web manifest and service worker at build time.
npm installnpm run dev
# opens a dev server (usually http://localhost:5173) with instant hot-reloadnpm run build # type-checks with tsc, then bundles to dist/
npm run preview # serves dist/ locally, including the real service worker/manifestDeploying is just publishing the contents of dist/ (e.g. GitHub Pages, Netlify, any static host) - see the project's CI/CD docs for wiring a build step into your deploy of choice.
- Choose a deck size - pick a prime
p; the app shows how many cards and how many photos you'll need (p² + p + 1photos,p + 1per card). - Add your photos - tap to choose files, or drag-and-drop / paste images. Photos are downscaled and stored locally in IndexedDB on your device - nothing is uploaded anywhere.
- Generate My Dobble Deck - the app randomly assigns your photos to symbol slots and runs the same projective-plane algorithm from
src/dobble.tsto build a deck where any two cards share exactly one photo. - View Deck - see every card rendered as a circular Dobble card with your photos packed into a randomized, non-overlapping layout (
src/layout.ts) - shuffle the layout for a new look, or print physical cards. - Play - a "tower" style single-player mode: spot the one photo shared between the center card and the top card of the pile, tap it, and race to clear the whole deck. Tracks correct taps, mistakes, and cards remaining.
Your photos and generated deck persist in IndexedDB, so closing the tab (or installing the app to your home screen and going offline) keeps everything intact until you hit Reset Everything.
Visit the page in a supported browser (Chrome, Edge, Safari on iOS via "Add to Home Screen", etc.) and use the install prompt/banner, or your browser's "Install app" menu option. Once installed, the service worker caches the app shell so it keeps working with no network connection - only the initial photo upload needs the device to render/store your images, which also happens fully offline.
import { generateDobbleCards, verifyDobbleCards } from './src/dobble.ts';
// Generate cards using prime number 7
const cards = generateDobbleCards(7);
console.log(`Generated ${cards.length} cards`);
// Verify that all cards follow the Dobble rule
const verification = verifyDobbleCards(cards);
console.log('Valid:', verification.success);import { generateDobbleCards, convertToSymbols } from './src/dobble.ts';
const cards = generateDobbleCards(3); // Smaller set: 13 cards
const symbols = ['🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻',
'🐼', '🐨', '🐯', '🦁', '🐮', '🐷'];
const emojiCards = convertToSymbols(cards, symbols);
console.log(emojiCards);npm run demoRuns src/cli.ts (via tsx, no compile step needed) to print a sample deck and verification results to the console, same as the original node dobble.js demo.
npm testGenerates a complete Dobble card set.
- Parameters:
p(number) - A prime number - Returns: Array of cards, where each card is an array of symbol IDs
- Throws: Error if
pis not prime
Verifies that all cards follow the Dobble rule.
- Parameters:
cards(Array) - Array of cards to verify - Returns: Object with verification results
Converts numeric symbol IDs to custom labels.
- Parameters:
cards(Array) - Cards with numeric IDssymbols(Array) - Array of symbol labels
- Returns: Cards with custom labels
Checks if a number is prime.
- Parameters:
n(number) - Number to check - Returns: Boolean
| Prime (p) | Cards | Symbols per Card | Total Symbols |
|---|---|---|---|
| 2 | 7 | 3 | 7 |
| 3 | 13 | 4 | 13 |
| 5 | 31 | 6 | 31 |
| 7 | 57 | 8 | 57 |
| 11 | 133 | 12 | 133 |
| 13 | 183 | 14 | 183 |
The original Dobble game uses p=7, resulting in 55 cards with 8 symbols each (with 2 cards removed for manufacturing reasons).
The algorithm uses finite projective plane geometry, specifically an affine plane extended with a "line at infinity":
- Direction symbols (0 to p): Represent "points at infinity" where parallel lines meet
- Symbol 0: Where all vertical lines meet
- Symbols 1 to p: Where lines of each slope (0 to p-1) meet
- Point symbols (p+1 to p²+p): Represent points in the affine plane, arranged as (p+1) + row×p + col
-
Card 0 - Line at Infinity
- Contains all direction symbols: [0, 1, 2, ..., p]
- This card shares exactly one "direction" with every other card
-
Cards 1 to p² - Affine Lines (y = mx + b)
- For each slope m (0 to p-1) and y-intercept b (0 to p-1):
- Contains: direction symbol (m+1) + p points on the line y ≡ mx + b (mod p)
- Each line has a unique slope-intercept combination
-
Cards p²+1 to p²+p - Vertical Lines (x = c)
- For each column c (0 to p-1):
- Contains: direction symbol 0 + p points where x = c
- All vertical lines are parallel and meet at symbol 0
The construction ensures that:
- Any two non-parallel affine lines intersect at exactly one affine point
- Any two lines with the same slope (including verticals) share their direction symbol
- The line at infinity intersects each affine/vertical line at its direction symbol
- Every pair of cards shares exactly one symbol - either a direction or an affine point
MIT