Modern TypeScript Edition โ React + Vite
A modern, type-safe implementation of Classic Klondike Solitaire, built with React, TypeScript, and Vite, featuring beautiful spritesheet-based card graphics, custom mouse-driven drag & drop, auto-move functionality, and full move validation logic.
โ ๏ธ Classic Klondike Rules โ Traditional solitaire gameplay- ๐จ Beautiful Card Graphics โ High-quality spritesheet rendering with subtle animations
- ๐ฑ๏ธ Custom Drag & Drop โ Precise mouse-driven card movement with real-time visual feedback
- ๐ฏ Multi-Card Stack Dragging โ Drag entire card sequences smoothly with proper offset
- ๐ Auto-Move to Foundation โ Double-click cards to automatically send them to foundations
- โจ Flying Animation โ Cards fly to their destination with beautiful arc motion
- โ Move Validation โ Only valid moves allowed
- ๐ Stock Recycling โ Draw through deck multiple times
- ๐ Win Detection โ Automatic celebration on completion
- โฑ๏ธ Timer & Move Counter โ Track your performance
- ๐ฑ Responsive Design โ Works on desktop, tablet, and mobile
- ๐ Crypto RNG โ Cryptographically secure card shuffling
- ๐งช Vitest Coverage โ Fast, comprehensive tests
- ๐งฉ TypeScript 5.7+ โ Strict type checking
- ๐ ESLint + Prettier โ Consistent code style
# Install dependencies
npm install
# Start dev server (auto-opens in browser)
npm run dev
# Build production bundle
npm run build
# Preview production build
npm run preview
# Run tests
npm testOpen http://localhost:10010/ to play.
.\dev.ps1 start # Start development mode
.\dev.ps1 stop # Stop dev server
.\dev.ps1 status # Check server statusnpm run build
npm run lint
npm run formatnpm run test
npm run test:watch
npm run test:coverage| Layer | Technology | Purpose |
|---|---|---|
| UI | React 18 | Game interface |
| Graphics | Spritesheet | Card rendering via canvas extraction |
| Drag System | Custom Mouse Events | Precise positioning & multi-card stacks |
| Build | Vite 5 | Lightning-fast dev + build |
| Language | TypeScript 5.7 | Type-safe logic |
| Tests | Vitest 2.1 | Unit testing |
| Quality | ESLint + Prettier | Formatting and linting |
| Runtime | Node.js 18+ | ES module environment |
cardserver/
โโโ index.html
โโโ vite.config.ts
โโโ vitest.config.ts
โโโ dev.ps1
โโโ src/
โ โโโ main.tsx # React entry point
โ โโโ App.tsx # Main game component
โ โโโ App.css # Styling
โ โโโ types.ts # Type definitions
โ โโโ cardSprites.ts # Spritesheet loader
โ โโโ gameLogic.ts # Klondike rules engine
โโโ public/
โ โโโ deck.png # Card spritesheet (4x13 grid)
โ โโโ back.jpg # Card back image
โ โโโ screenshot.png # Game screenshot
โโโ test/
โ โโโ *.test.ts # Unit tests
โโโ .github/
โโโ instructions.md
Move all 52 cards into four Foundations, building each suit from Ace to King.
- Deck: 52 standard cards (no jokers)
- Suits: โ Spades, โฅ Hearts, โฆ Diamonds, โฃ Clubs
- Colors: Red (โฅโฆ) / Black (โ โฃ)
- Tableau: 7 columns (1โ7 cards each)
- Stock: Remaining 24 cards (face-down)
- Waste: Discard pile (face-up)
- Foundations: 4 piles, one per suit (build AโK)
| Column | Cards | Face-up | Face-down |
|---|---|---|---|
| 1 | 1 | 1 | 0 |
| 2 | 2 | 1 | 1 |
| 3 | 3 | 1 | 2 |
| 4 | 4 | 1 | 3 |
| 5 | 5 | 1 | 4 |
| 6 | 6 | 1 | 5 |
| 7 | 7 | 1 | 6 |
- Draw 1 card from Stock onto Waste (classic mode)
- When Stock is empty, flip the Waste back to form a new Stock (preserve order)
- Move top Waste card to Foundation if next in suit order
- Or move to Tableau if opposite color and one rank lower
- Move sequences of descending, alternating-color cards (e.g., Red 6 on Black 7)
- Uncovering a face-down card flips it face-up automatically
- Empty columns can only be filled with a King (or sequence starting with King)
- Build by suit in ascending order (AโK)
- Only Aces can start a foundation pile
All four Foundations complete (AโK per suit). The game ends automatically with celebration animation.
| Parameter | Description | Default |
|---|---|---|
| draw_count | Cards drawn from Stock each turn | 1 |
| max_redeals | Number of Stock recycles | โ |
| auto_flip | Auto-turn face-down when uncovered | true |
| auto_move | Auto-move valid cards to Foundation | optional |
| scoring | Vegas / Standard / None | none |
type Suit = 'Spades' | 'Hearts' | 'Diamonds' | 'Clubs';
type Rank = 'A' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | 'J' | 'Q' | 'K';
interface Card {
suit: Suit;
rank: Rank;
faceUp: boolean;
}
interface GameState {
stock: Card[];
waste: Card[];
foundations: Record<Suit, Card[]>;
tableau: Card[][];
moves: number;
timer: number;
}The game uses a spritesheet-based card rendering system for optimal performance and visual quality:
- File:
public/deck.png(4 rows ร 13 columns) - Row mapping: 0=Clubs, 1=Hearts, 2=Spades, 3=Diamonds
- Column mapping: 0=A, 1=2, ..., 9=10, 10=J, 11=Q, 12=K
- Card back:
public/back.jpg
- Load
deck.pngspritesheet into memory - Calculate frame dimensions (width/13, height/4)
- Extract each card using canvas
drawImage()with precise coordinates - Convert to data URLs for React img elements
- Cache all 52 cards for instant rendering
This approach provides:
- โ High-quality card graphics with smooth edges
- โ Fast rendering (pre-extracted, cached data URLs)
- โ Single spritesheet download (better than 52 separate images)
- โ Subtle animations via CSS transforms
The game implements a custom mouse-driven drag system (not HTML5 drag API) for precise control:
- HTML5 drag API forces semi-transparency on drag images (browser limitation)
- Needed pixel-perfect positioning without "jump" on drag start
- Required multi-card stack dragging with proper visual offset
- Wanted full control over cursor states and visual feedback
- Mouse Down: Calculate offset from card's top-left to click position
- Mouse Move: Track global mouse position, update drag overlay position
- Drag Overlay: Fixed-position div at
mouseX - offsetX,mouseY - offsetY - Original Cards: Set to
opacity: 0during drag (fully invisible, no ghosting) - Drop Detection: Use
elementFromPoint()to find drop zone under cursor - Mouse Up: Validate move, update game state, reset cursor
- ๐ฏ Precise positioning โ Card stays under cursor at click point
- ๐ Multi-card stacks โ Drag sequences with 25px offset per card
- ๐๏ธ Clean visuals โ No ghostly images or transparency issues
- ๐ฑ๏ธ Cursor feedback โ grab โ grabbing โ normal states
- โก High performance โ No unnecessary re-renders
/* Shimmer effect on cards */
@keyframes cardShimmer {
0%, 100% { box-shadow: 0 2px 8px rgba(0,0,0,0.15); }
50% { box-shadow: 0 4px 16px rgba(0,0,0,0.25); }
}
/* Flying animation for auto-move */
@keyframes flyToFoundation {
0% { transform: scale(1) translateY(0); }
50% { transform: scale(0.9) translateY(-30px); }
100% { transform: scale(1) translateY(0); opacity: 0; }
}This project uses GitHub Actions for automatic deployment to GitHub Pages.
- Every push to
mainbranch triggers the deployment workflow - The workflow builds the app and publishes to
gh-pagesbranch - Live site updates automatically at: https://top-5.github.io/klondike/
npm run deploy # Build and deploy to GitHub Pages using gh-pagesCheck the Actions tab to see deployment status and history.
npm run build # Compile & bundle for production
npm run preview # Local preview of production buildEverything outputs into /dist โ ready to serve via any static host (Netlify, Vercel, GitHub Pages, etc.).
- Run tests via Vitest for cards, moves, and win detection
- Watch mode auto-reruns tests on file save
- Coverage reports generated under
/coverage
Non-Commercial License
This software is free for non-commercial use (personal, educational, research, AI training, etc).
Commercial use requires a separate license. Please contact @top-5 on GitHub for commercial licensing inquiries.
See LICENSE file for full terms.
ยฉ 2025 Top-5
