import React, { useState, useEffect } from 'react'; import { Shuffle, RotateCcw, HelpCircle, X, Info, Trophy, Clock } from 'lucide-react'; export default function SolitairePlus() { // Game state const [deck, setDeck] = useState([]); const [tableau, setTableau] = useState([[], [], [], []]); const [foundation, setFoundation] = useState([[], [], [], []]); const [hand, setHand] = useState([]); const [score, setScore] = useState(0); const [moves, setMoves] = useState(0); const [gameOver, setGameOver] = useState(false); const [showRules, setShowRules] = useState(false); const [selectedCard, setSelectedCard] = useState(null); const [selectedSource, setSelectedSource] = useState(null); const [gameWon, setGameWon] = useState(false); const [showInfo, setShowInfo] = useState(true); const [gameTime, setGameTime] = useState(0); const [gameActive, setGameActive] = useState(false); // Initialize game useEffect(() => { newGame(); }, []); // Game timer useEffect(() => { let interval; if (gameActive && !gameOver) { interval = setInterval(() => { setGameTime(prevTime => prevTime + 1); }, 1000); } return () => clearInterval(interval); }, [gameActive, gameOver]); // Check for win condition useEffect(() => { if (foundation.every(pile => pile.length === 13)) { setGameWon(true); setGameOver(true); setGameActive(false); } }, [foundation]); const formatTime = (seconds) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }; const newGame = () => { // Create and shuffle deck const suits = ['hearts', 'diamonds', 'clubs', 'spades']; const values = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']; const newDeck = []; for (const suit of suits) { for (let i = 0; i < values.length; i++) { newDeck.push({ suit, value: values[i], numValue: i + 1, color: suit === 'hearts' || suit === 'diamonds' ? 'red' : 'black', id: `${values[i]}-${suit}` }); } } // Fisher-Yates shuffle for (let i = newDeck.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [newDeck[i], newDeck[j]] = [newDeck[j], newDeck[i]]; } // Set up initial tableau const newTableau = [[], [], [], []]; for (let i = 0; i < 4; i++) { for (let j = 0; j < 4; j++) { newTableau[i].push(newDeck.pop()); } } setDeck(newDeck); setTableau(newTableau); setFoundation([[], [], [], []]); setHand([]); setScore(0); setMoves(0); setGameOver(false); setGameWon(false); setSelectedCard(null); setSelectedSource(null); setGameTime(0); setGameActive(true); }; const drawCard = () => { if (deck.length === 0 || gameOver) return; const newHand = [...hand]; const newDeck = [...deck]; newHand.push(newDeck.pop()); setHand(newHand); setDeck(newDeck); setMoves(moves + 1); }; const selectCard = (card, source, index, pileIndex = null) => { if (gameOver) return; // If a card is already selected, try to move it if (selectedCard) { if (source === selectedSource && index === selectedCard.index && pileIndex === selectedCard.pileIndex) { // Deselect if clicking the same card setSelectedCard(null); setSelectedSource(null); return; } // Try to move the selected card to the new location moveCard(selectedSource, selectedCard.index, selectedCard.pileIndex, source, index, pileIndex); setSelectedCard(null); setSelectedSource(null); } else { // Select the card setSelectedCard({ card, index, pileIndex }); setSelectedSource(source); } }; const moveCard = (fromSource, fromIndex, fromPileIndex, toSource, toIndex, toPileIndex) => { // Handle different move scenarios if (fromSource === 'hand' && toSource === 'foundation') { moveFromHandToFoundation(fromIndex, toPileIndex); } else if (fromSource === 'tableau' && toSource === 'foundation') { moveFromTableauToFoundation(fromPileIndex, fromIndex, toPileIndex); } else if (fromSource === 'tableau' && toSource === 'tableau') { moveFromTableauToTableau(fromPileIndex, fromIndex, toPileIndex); } else if (fromSource === 'hand' && toSource === 'tableau') { moveFromHandToTableau(fromIndex, toPileIndex); } }; const moveFromHandToFoundation = (handIndex, foundationIndex) => { const card = hand[handIndex]; const foundationPile = foundation[foundationIndex]; // Check if valid move (A on empty or same suit and one higher value) if ( (foundationPile.length === 0 && card.value === 'A') || (foundationPile.length > 0 && foundationPile[foundationPile.length - 1].suit === card.suit && foundationPile[foundationPile.length - 1].numValue === card.numValue - 1) ) { const newHand = [...hand]; const newFoundation = [...foundation]; // Remove from hand newHand.splice(handIndex, 1); // Add to foundation newFoundation[foundationIndex] = [...foundationPile, card]; setHand(newHand); setFoundation(newFoundation); setScore(score + 10); setMoves(moves + 1); } }; const moveFromTableauToFoundation = (tableauIndex, cardIndex, foundationIndex) => { const tableauPile = tableau[tableauIndex]; if (cardIndex !== tableauPile.length - 1) return; // Only top card can be moved const card = tableauPile[cardIndex]; const foundationPile = foundation[foundationIndex]; // Check if valid move if ( (foundationPile.length === 0 && card.value === 'A') || (foundationPile.length > 0 && foundationPile[foundationPile.length - 1].suit === card.suit && foundationPile[foundationPile.length - 1].numValue === card.numValue - 1) ) { const newTableau = [...tableau]; const newFoundation = [...foundation]; // Remove from tableau newTableau[tableauIndex] = tableauPile.slice(0, -1); // Add to foundation newFoundation[foundationIndex] = [...foundationPile, card]; setTableau(newTableau); setFoundation(newFoundation); setScore(score + 10); setMoves(moves + 1); } }; const moveFromTableauToTableau = (fromPileIndex, cardIndex, toPileIndex) => { const fromPile = tableau[fromPileIndex]; const toPile = tableau[toPileIndex]; if (cardIndex !== fromPile.length - 1) return; // Only top card can be moved if (fromPileIndex === toPileIndex) return; // Can't move to same pile const card = fromPile[cardIndex]; // Check if valid move (K on empty or different color and one lower value) if ( (toPile.length === 0 && card.value === 'K') || (toPile.length > 0 && toPile[toPile.length - 1].color !== card.color && toPile[toPile.length - 1].numValue === card.numValue + 1) ) { const newTableau = [...tableau]; // Remove from source pile newTableau[fromPileIndex] = fromPile.slice(0, -1); // Add to destination pile newTableau[toPileIndex] = [...toPile, card]; setTableau(newTableau); setScore(score + 5); setMoves(moves + 1); } }; const moveFromHandToTableau = (handIndex, tableauIndex) => { const card = hand[handIndex]; const tableauPile = tableau[tableauIndex]; // Check if valid move if ( (tableauPile.length === 0 && card.value === 'K') || (tableauPile.length > 0 && tableauPile[tableauPile.length - 1].color !== card.color && tableauPile[tableauPile.length - 1].numValue === card.numValue + 1) ) { const newHand = [...hand]; const newTableau = [...tableau]; // Remove from hand newHand.splice(handIndex, 1); // Add to tableau newTableau[tableauIndex] = [...tableauPile, card]; setHand(newHand); setTableau(newTableau); setScore(score + 5); setMoves(moves + 1); } }; const renderCard = (card, source, index, pileIndex = null) => { if (!card) return null; const isSelected = selectedCard && selectedSource === source && selectedCard.index === index && selectedCard.pileIndex === pileIndex; const suitSymbol = { 'hearts': '♥', 'diamonds': '♦', 'clubs': '♣', 'spades': '♠' }; const suitColor = card.color === 'red' ? 'text-red-600' : 'text-gray-900'; return (
selectCard(card, source, index, pileIndex)} >
{card.value}
{suitSymbol[card.suit]}
{card.value}
); }; return (
{/* Header */}

Solitaire Plus

{score}
{formatTime(gameTime)}
Moves: {moves}
{/* Game area */}
{/* Foundation area */}
{foundation.map((pile, pileIndex) => (
0 ? 'border-transparent' : 'border-white/40'} flex items-center justify-center transition-all ${selectedCard && selectedSource !== 'foundation' ? 'hover:bg-white/10' : ''}`} onClick={() => { if (selectedCard && selectedSource !== 'foundation') { moveCard(selectedSource, selectedCard.index, selectedCard.pileIndex, 'foundation', 0, pileIndex); setSelectedCard(null); setSelectedSource(null); } }} > {pile.length > 0 ? ( renderCard(pile[pile.length - 1], 'foundation', pile.length - 1, pileIndex) ) : (
♠♥♦♣
Foundation {pileIndex + 1}
)}
))}
{/* Tableau area */}
{tableau.map((pile, pileIndex) => (
{pile.length > 0 ? ( pile.map((card, cardIndex) => (
0 ? '-mt-24' : ''}`} > {renderCard(card, 'tableau', cardIndex, pileIndex)}
)) ) : (
{ if (selectedCard) { moveCard(selectedSource, selectedCard.index, selectedCard.pileIndex, 'tableau', 0, pileIndex); setSelectedCard(null); setSelectedSource(null); } }} >
K
Empty
)}
))}
{/* Hand and deck area */}
0 ? 'hover:-translate-y-1' : ''}`} onClick={drawCard} > {deck.length > 0 ? (
{deck.length} cards left
) : (
Empty Deck
)}
{hand.map((card, index) => (
{renderCard(card, 'hand', index)}
))} {hand.length === 0 && (
Hand
Draw cards from the deck
)}
{/* Game over modal */} {gameOver && (
{gameWon ? (
) : (
)}

{gameWon ? 'You Won!' : 'Game Over'}

{gameWon ? 'Congratulations! You completed the game.' : 'No more possible moves.'}

Final Score: {score}
Moves Made: {moves}
Time: {formatTime(gameTime)}
)} {/* Rules modal */} {showRules && (

How to Play Solitaire Plus

Objective: Move all cards to the foundation piles, sorted by suit from Ace to King.

Game Elements:

  • 1
    Tableau: Four columns where you build descending sequences with alternating colors (red and black).
  • 2
    Foundation: Four piles at the top where you build ascending sequences by suit, starting with Aces.
  • 3
    Deck: Cards you can draw from when you need more options.
  • 4
    Hand: Cards drawn from the deck that you can play.

Rules:

  • In the tableau, cards must be placed in descending order with alternating colors (e.g., black 10 can be placed on red Jack).
  • Only Kings can be placed on empty tableau spots.
  • In the foundation, cards must be placed in ascending order by suit (e.g., 2 of hearts on Ace of hearts).
  • Only Aces can be placed on empty foundation spots.
  • You can move cards from your hand to either the tableau or foundation.
  • Only the top card of each tableau pile can be moved.

Scoring:

  • Moving a card to the foundation: 10 points
  • Moving a card within the tableau: 5 points
)} {/* Info tooltip */} {showInfo && (

Quick Tips

)} {/* Add some CSS animations */}
); }