🏗️ Step 1: Project Structure & HTML Foundation
Let's start by creating the basic file structure and HTML skeleton for our solitaire game. We'll build a clean, semantic structure that's easy to style and manipulate with JavaScript.
📁 Project File Structure
solitaire-game/
├── index.html # Main HTML file
├── styles.css # CSS styling
├── script.js # JavaScript game logic
└── assets/
└── cards/ # Card images (optional)
├── hearts/
├── diamonds/
├── clubs/
└── spades/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Solitaire Game</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="game-container">
<header class="game-header">
<h1>🃏 Solitaire</h1>
<div class="game-stats">
<span>Moves: <span id="moves">0</span></span>
<span>Time: <span id="timer">00:00</span></span>
<button id="new-game">New Game</button>
</div>
</header>
<main class="game-board">
<!-- Foundation Piles (Aces to Kings) -->
<section class="foundation">
<div class="foundation-pile" data-suit="hearts"></div>
<div class="foundation-pile" data-suit="diamonds"></div>
<div class="foundation-pile" data-suit="clubs"></div>
<div class="foundation-pile" data-suit="spades"></div>
</section>
<!-- Stock and Waste Piles -->
<section class="stock-waste">
<div id="stock-pile" class="pile"></div>
<div id="waste-pile" class="pile"></div>
</section>
<!-- Tableau (7 columns) -->
<section class="tableau">
<div class="tableau-column" data-column="0"></div>
<div class="tableau-column" data-column="1"></div>
<div class="tableau-column" data-column="2"></div>
<div class="tableau-column" data-column="3"></div>
<div class="tableau-column" data-column="4"></div>
<div class="tableau-column" data-column="5"></div>
<div class="tableau-column" data-column="6"></div>
</section>
</main>
</div>
<script src="script.js"></script>
</body>
</html>
🎨 Step 2: CSS Styling - Making It Beautiful
Now let's add the CSS to make our solitaire game visually appealing. We'll create a dark theme with card-like elements and smooth animations.
/* Reset and Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
min-height: 100vh;
color: white;
}
.game-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* Header Styles */
.game-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 15px 20px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.game-header h1 {
font-size: 2rem;
font-weight: bold;
}
.game-stats {
display: flex;
gap: 20px;
align-items: center;
}
.game-stats span {
font-weight: 600;
}
#new-game {
background: linear-gradient(45deg, #ff6b6b, #ff8e8e);
border: none;
padding: 10px 20px;
border-radius: 8px;
color: white;
font-weight: bold;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
#new-game:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(255, 107, 107, 0.4);
}
/* Game Board Layout */
.game-board {
display: grid;
grid-template-areas:
"foundation foundation foundation foundation stock waste . ."
"tableau tableau tableau tableau tableau tableau tableau";
grid-template-columns: repeat(7, 1fr);
grid-gap: 15px;
min-height: 600px;
}
.foundation {
grid-area: foundation;
display: flex;
gap: 15px;
}
.stock-waste {
grid-area: stock;
display: flex;
gap: 15px;
justify-self: end;
}
.tableau {
grid-area: tableau;
display: flex;
gap: 15px;
}
/* Card and Pile Styles */
.pile, .foundation-pile, .tableau-column {
width: 100px;
min-height: 140px;
background: rgba(255, 255, 255, 0.1);
border: 2px dashed rgba(255, 255, 255, 0.3);
border-radius: 10px;
position: relative;
transition: all 0.3s ease;
}
.pile:hover, .foundation-pile:hover, .tableau-column:hover {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.5);
}
.card {
width: 100px;
height: 140px;
background: white;
border-radius: 8px;
border: 1px solid #333;
position: absolute;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 20px rgba(255, 255, 255, 0.3);
}
.card.dragging {
transform: rotate(5deg);
z-index: 1000;
opacity: 0.8;
}
.card.face-down {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.card.face-down::after {
content: "🃏";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 2rem;
}
.card.red {
color: #d63031;
}
.card.black {
color: #2d3436;
}
.card-rank {
font-size: 14px;
font-weight: bold;
}
.card-suit {
font-size: 20px;
}
/* Tableau specific styles */
.tableau-column {
min-height: 200px;
}
.tableau-column .card {
position: relative;
margin-bottom: -120px; /* Overlap cards */
}
.tableau-column .card:last-child {
margin-bottom: 0;
}
/* Drop zones */
.drop-zone {
background: rgba(76, 175, 80, 0.3) !important;
border-color: #4caf50 !important;
}
/* Animations */
@keyframes dealCard {
from {
transform: translateX(-200px) rotate(-90deg);
opacity: 0;
}
to {
transform: translateX(0) rotate(0deg);
opacity: 1;
}
}
.card.dealing {
animation: dealCard 0.5s ease-out;
}
@keyframes flipCard {
0% { transform: rotateY(0deg); }
50% { transform: rotateY(90deg); }
100% { transform: rotateY(0deg); }
}
.card.flipping {
animation: flipCard 0.6s ease-in-out;
}
/* Responsive Design */
@media (max-width: 768px) {
.game-board {
grid-template-columns: repeat(4, 1fr);
grid-template-areas:
"foundation foundation stock waste"
"tableau tableau tableau tableau";
}
.foundation {
flex-wrap: wrap;
}
.tableau {
flex-wrap: wrap;
}
.card, .pile, .foundation-pile, .tableau-column {
width: 80px;
min-height: 112px;
}
.card {
height: 112px;
}
}
⚙️ Step 3: JavaScript Game Logic - The Brain
Now comes the exciting part - implementing the game logic! We'll create classes to represent cards, deck, and the game state, then add interactivity.
// Card Class
class Card {
constructor(suit, rank) {
this.suit = suit; // 'hearts', 'diamonds', 'clubs', 'spades'
this.rank = rank; // 1-13 (Ace=1, Jack=11, Queen=12, King=13)
this.faceUp = false;
this.element = null;
}
get color() {
return (this.suit === 'hearts' || this.suit === 'diamonds') ? 'red' : 'black';
}
get rankName() {
const names = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
return names[this.rank];
}
get suitSymbol() {
const symbols = {
'hearts': '♥',
'diamonds': '♦',
'clubs': '♣',
'spades': '♠'
};
return symbols[this.suit];
}
createElement() {
const cardElement = document.createElement('div');
cardElement.className = `card ${this.color}`;
cardElement.draggable = true;
if (this.faceUp) {
cardElement.innerHTML = `
${this.rankName}
${this.suitSymbol}
${this.rankName}
`;
} else {
cardElement.classList.add('face-down');
}
this.element = cardElement;
return cardElement;
}
flip() {
this.faceUp = !this.faceUp;
if (this.element) {
this.element.classList.add('flipping');
setTimeout(() => {
this.element.classList.remove('flipping');
this.updateElement();
}, 300);
}
}
updateElement() {
if (!this.element) return;
if (this.faceUp) {
this.element.classList.remove('face-down');
this.element.innerHTML = `
${this.rankName}
${this.suitSymbol}
${this.rankName}
`;
} else {
this.element.classList.add('face-down');
this.element.innerHTML = '';
}
}
}
// Deck Class
class Deck {
constructor() {
this.cards = [];
this.initializeDeck();
}
initializeDeck() {
const suits = ['hearts', 'diamonds', 'clubs', 'spades'];
this.cards = [];
suits.forEach(suit => {
for (let rank = 1; rank <= 13; rank++) {
this.cards.push(new Card(suit, rank));
}
});
}
shuffle() {
// Fisher-Yates shuffle algorithm
for (let i = this.cards.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[this.cards[i], this.cards[j]] = [this.cards[j], this.cards[i]];
}
}
deal(count = 1) {
return this.cards.splice(0, count);
}
}
// Solitaire Game Class
class SolitaireGame {
constructor() {
this.deck = new Deck();
this.foundation = [[], [], [], []]; // Hearts, Diamonds, Clubs, Spades
this.tableau = [[], [], [], [], [], [], []]; // 7 columns
this.stock = [];
this.waste = [];
this.moves = 0;
this.startTime = Date.now();
this.timerInterval = null;
this.initializeGame();
this.setupEventListeners();
this.startTimer();
}
initializeGame() {
this.deck.shuffle();
// Deal tableau (1 card to first column, 2 to second, etc.)
for (let col = 0; col < 7; col++) {
for (let row = 0; row <= col; row++) {
const card = this.deck.deal(1)[0];
if (row === col) {
card.faceUp = true; // Top card face up
}
this.tableau[col].push(card);
}
}
// Remaining cards go to stock
this.stock = this.deck.cards;
this.renderGame();
}
renderGame() {
this.renderTableau();
this.renderFoundation();
this.renderStock();
this.renderWaste();
this.updateStats();
}
renderTableau() {
for (let col = 0; col < 7; col++) {
const column = document.querySelector(`[data-column="${col}"]`);
column.innerHTML = '';
this.tableau[col].forEach((card, index) => {
const cardElement = card.createElement();
cardElement.style.top = `${index * 20}px`;
cardElement.dataset.column = col;
cardElement.dataset.cardIndex = index;
column.appendChild(cardElement);
});
}
}
renderFoundation() {
const suits = ['hearts', 'diamonds', 'clubs', 'spades'];
suits.forEach((suit, index) => {
const pile = document.querySelector(`[data-suit="${suit}"]`);
pile.innerHTML = '';
if (this.foundation[index].length > 0) {
const topCard = this.foundation[index][this.foundation[index].length - 1];
pile.appendChild(topCard.createElement());
}
});
}
renderStock() {
const stockPile = document.getElementById('stock-pile');
stockPile.innerHTML = '';
if (this.stock.length > 0) {
const stockCard = document.createElement('div');
stockCard.className = 'card face-down';
stockCard.addEventListener('click', () => this.drawFromStock());
stockPile.appendChild(stockCard);
}
}
renderWaste() {
const wastePile = document.getElementById('waste-pile');
wastePile.innerHTML = '';
if (this.waste.length > 0) {
const topCard = this.waste[this.waste.length - 1];
wastePile.appendChild(topCard.createElement());
}
}
drawFromStock() {
if (this.stock.length > 0) {
const card = this.stock.pop();
card.faceUp = true;
this.waste.push(card);
this.moves++;
} else if (this.waste.length > 0) {
// Reset stock from waste
while (this.waste.length > 0) {
const card = this.waste.pop();
card.faceUp = false;
this.stock.push(card);
}
}
this.renderStock();
this.renderWaste();
this.updateStats();
}
updateStats() {
document.getElementById('moves').textContent = this.moves;
}
startTimer() {
this.timerInterval = setInterval(() => {
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0');
const seconds = (elapsed % 60).toString().padStart(2, '0');
document.getElementById('timer').textContent = `${minutes}:${seconds}`;
}, 1000);
}
setupEventListeners() {
// New game button
document.getElementById('new-game').addEventListener('click', () => {
this.newGame();
});
// Set up drag and drop (simplified version)
this.setupDragAndDrop();
}
setupDragAndDrop() {
// This is a simplified version - full implementation would be more complex
document.addEventListener('dragstart', (e) => {
if (e.target.classList.contains('card')) {
e.target.classList.add('dragging');
}
});
document.addEventListener('dragend', (e) => {
if (e.target.classList.contains('card')) {
e.target.classList.remove('dragging');
}
});
}
newGame() {
clearInterval(this.timerInterval);
this.deck = new Deck();
this.foundation = [[], [], [], []];
this.tableau = [[], [], [], [], [], [], []];
this.stock = [];
this.waste = [];
this.moves = 0;
this.startTime = Date.now();
this.initializeGame();
this.startTimer();
}
}
// Initialize the game when page loads
document.addEventListener('DOMContentLoaded', () => {
new SolitaireGame();
});
🎮 Step 4: Adding Game Logic & Validation
Let's add the core game mechanics: move validation, win detection, and drag-and-drop functionality. This is where your solitaire game comes to life!
🧠 Game Rules Implementation
Tableau Rules:
- • Cards must alternate colors (red on black, black on red)
- • Cards must be in descending order (King, Queen, Jack...)
- • Only Kings can be placed on empty columns
- • Multiple cards can be moved if they're in valid sequence
Foundation Rules:
- • Must start with Ace
- • Same suit only
- • Ascending order (A, 2, 3... K)
- • Win when all 4 foundations are complete