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
