Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Game Hub</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| /* Custom styles */ | |
| .game-card { | |
| transition: all 0.3s ease; | |
| transform-style: preserve-3d; | |
| } | |
| .game-card:hover { | |
| transform: translateY(-5px) scale(1.02); | |
| box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| } | |
| /* Pong game styles */ | |
| #pongCanvas { | |
| background-color: #111; | |
| border-radius: 8px; | |
| } | |
| /* Blackjack table */ | |
| .blackjack-table { | |
| background: linear-gradient(135deg, #1a5f1a, #0d3b0d); | |
| border-radius: 16px; | |
| box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3); | |
| } | |
| .card { | |
| width: 80px; | |
| height: 120px; | |
| background: white; | |
| border-radius: 8px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin: 0 5px; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); | |
| font-size: 24px; | |
| font-weight: bold; | |
| position: relative; | |
| user-select: none; | |
| } | |
| .card.red { | |
| color: red; | |
| } | |
| .card.black { | |
| color: black; | |
| } | |
| .card-back { | |
| background: linear-gradient(135deg, #d10000, #8b0000); | |
| color: white; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| /* Memory game */ | |
| .memory-card { | |
| width: 80px; | |
| height: 120px; | |
| perspective: 1000px; | |
| margin: 5px; | |
| } | |
| .memory-card-inner { | |
| position: relative; | |
| width: 100%; | |
| height: 100%; | |
| transition: transform 0.6s; | |
| transform-style: preserve-3d; | |
| } | |
| .memory-card.flipped .memory-card-inner { | |
| transform: rotateY(180deg); | |
| } | |
| .memory-card-front, .memory-card-back { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| backface-visibility: hidden; | |
| border-radius: 8px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| font-size: 36px; | |
| font-weight: bold; | |
| } | |
| .memory-card-front { | |
| background: linear-gradient(135deg, #d10000, #8b0000); | |
| color: white; | |
| } | |
| .memory-card-back { | |
| background: white; | |
| transform: rotateY(180deg); | |
| color: #333; | |
| } | |
| /* Hide all game screens initially */ | |
| .game-screen { | |
| display: none; | |
| } | |
| /* Solitaire styles */ | |
| .solitaire-card { | |
| width: 80px; | |
| height: 120px; | |
| background: white; | |
| border-radius: 8px; | |
| position: absolute; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); | |
| font-size: 24px; | |
| font-weight: bold; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: space-between; | |
| padding: 5px; | |
| user-select: none; | |
| cursor: pointer; | |
| transition: transform 0.2s; | |
| } | |
| .solitaire-card.red { | |
| color: red; | |
| } | |
| .solitaire-card.black { | |
| color: black; | |
| } | |
| .solitaire-card.dragging { | |
| z-index: 1000; | |
| transform: scale(1.05); | |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); | |
| } | |
| .solitaire-card .card-value { | |
| font-size: 20px; | |
| align-self: flex-start; | |
| } | |
| .solitaire-card .card-suit { | |
| font-size: 16px; | |
| align-self: flex-end; | |
| } | |
| .solitaire-pile { | |
| position: relative; | |
| min-width: 80px; | |
| min-height: 120px; | |
| margin-bottom: 20px; | |
| } | |
| .solitaire-foundation { | |
| width: 80px; | |
| height: 120px; | |
| border: 2px dashed #444; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| } | |
| .solitaire-stock { | |
| width: 80px; | |
| height: 120px; | |
| background: linear-gradient(135deg, #d10000, #8b0000); | |
| border-radius: 8px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| color: white; | |
| cursor: pointer; | |
| margin-bottom: 20px; | |
| } | |
| .solitaire-waste { | |
| width: 80px; | |
| height: 120px; | |
| position: relative; | |
| margin-bottom: 20px; | |
| } | |
| .solitaire-tableau { | |
| display: grid; | |
| grid-template-columns: repeat(7, 1fr); | |
| gap: 20px; | |
| margin-top: 20px; | |
| } | |
| .solitaire-top-area { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-bottom: 20px; | |
| } | |
| .solitaire-foundations { | |
| display: flex; | |
| gap: 20px; | |
| } | |
| .drop-target { | |
| border: 2px dashed transparent; | |
| } | |
| .drop-target.highlight { | |
| border-color: gold; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-white min-h-screen"> | |
| <!-- Main Menu --> | |
| <div id="mainMenu" class="container mx-auto px-4 py-8"> | |
| <header class="flex justify-between items-center mb-12"> | |
| <h1 class="text-4xl font-bold bg-gradient-to-r from-purple-500 to-blue-500 bg-clip-text text-transparent">Game Hub</h1> | |
| <div class="flex space-x-4"> | |
| <button id="soundToggle" class="p-2 rounded-full bg-gray-800 hover:bg-gray-700 transition"> | |
| <i class="fas fa-volume-up"></i> | |
| </button> | |
| </div> | |
| </header> | |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> | |
| <!-- Pong Card --> | |
| <div class="game-card bg-gray-800 rounded-xl p-6 cursor-pointer" onclick="loadGame('pong')"> | |
| <div class="flex justify-between items-start mb-4"> | |
| <h2 class="text-2xl font-bold text-purple-400">Pong</h2> | |
| <div class="bg-purple-500 text-white px-3 py-1 rounded-full text-xs font-bold">ARCADE</div> | |
| </div> | |
| <p class="text-gray-300 mb-4">The classic arcade game. Control your paddle and defeat your opponent!</p> | |
| <div class="bg-gray-700 rounded-lg h-32 flex items-center justify-center"> | |
| <i class="fas fa-table-tennis text-4xl text-purple-400"></i> | |
| </div> | |
| </div> | |
| <!-- Blackjack Card --> | |
| <div class="game-card bg-gray-800 rounded-xl p-6 cursor-pointer" onclick="loadGame('blackjack')"> | |
| <div class="flex justify-between items-start mb-4"> | |
| <h2 class="text-2xl font-bold text-blue-400">Blackjack</h2> | |
| <div class="bg-blue-500 text-white px-3 py-1 rounded-full text-xs font-bold">CARD</div> | |
| </div> | |
| <p class="text-gray-300 mb-4">Try to beat the dealer without going over 21. Double down and split pairs!</p> | |
| <div class="bg-gray-700 rounded-lg h-32 flex items-center justify-center"> | |
| <i class="fas fa-club text-4xl text-blue-400"></i> | |
| <i class="fas fa-heart text-4xl text-red-500 ml-2"></i> | |
| </div> | |
| </div> | |
| <!-- Memory Match Card --> | |
| <div class="game-card bg-gray-800 rounded-xl p-6 cursor-pointer" onclick="loadGame('memory')"> | |
| <div class="flex justify-between items-start mb-4"> | |
| <h2 class="text-2xl font-bold text-green-400">Memory Match</h2> | |
| <div class="bg-green-500 text-white px-3 py-1 rounded-full text-xs font-bold">PUZZLE</div> | |
| </div> | |
| <p class="text-gray-300 mb-4">Test your memory by matching pairs of cards. Find all matches to win!</p> | |
| <div class="bg-gray-700 rounded-lg h-32 flex items-center justify-center"> | |
| <i class="fas fa-brain text-4xl text-green-400"></i> | |
| </div> | |
| </div> | |
| <!-- War Card --> | |
| <div class="game-card bg-gray-800 rounded-xl p-6 cursor-pointer" onclick="loadGame('war')"> | |
| <div class="flex justify-between items-start mb-4"> | |
| <h2 class="text-2xl font-bold text-yellow-400">War</h2> | |
| <div class="bg-yellow-500 text-white px-3 py-1 rounded-full text-xs font-bold">CARD</div> | |
| </div> | |
| <p class="text-gray-300 mb-4">The classic card game of War. Highest card wins each round!</p> | |
| <div class="bg-gray-700 rounded-lg h-32 flex items-center justify-center"> | |
| <i class="fas fa-flag text-4xl text-yellow-400"></i> | |
| </div> | |
| </div> | |
| <!-- Solitaire Card --> | |
| <div class="game-card bg-gray-800 rounded-xl p-6 cursor-pointer" onclick="loadGame('solitaire')"> | |
| <div class="flex justify-between items-start mb-4"> | |
| <h2 class="text-2xl font-bold text-red-400">Solitaire</h2> | |
| <div class="bg-red-500 text-white px-3 py-1 rounded-full text-xs font-bold">CLASSIC</div> | |
| </div> | |
| <p class="text-gray-300 mb-4">The timeless single-player card game. Sort all cards by suit in ascending order.</p> | |
| <div class="bg-gray-700 rounded-lg h-32 flex items-center justify-center"> | |
| <i class="fas fa-crown text-4xl text-red-400"></i> | |
| </div> | |
| </div> | |
| <!-- Coming Soon Card --> | |
| <div class="game-card bg-gray-800 rounded-xl p-6"> | |
| <div class="flex justify-between items-start mb-4"> | |
| <h2 class="text-2xl font-bold text-pink-400">More Games</h2> | |
| <div class="bg-pink-500 text-white px-3 py-1 rounded-full text-xs font-bold">COMING SOON</div> | |
| </div> | |
| <p class="text-gray-300 mb-4">We're working on adding more exciting games to our collection!</p> | |
| <div class="bg-gray-700 rounded-lg h-32 flex items-center justify-center"> | |
| <i class="fas fa-gamepad text-4xl text-pink-400"></i> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Pong Game Screen --> | |
| <div id="pongScreen" class="game-screen container mx-auto px-4 py-8"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <button onclick="returnToMenu()" class="flex items-center text-white hover:text-purple-400 transition"> | |
| <i class="fas fa-arrow-left mr-2"></i> Back to Menu | |
| </button> | |
| <h2 class="text-2xl font-bold text-purple-400">Pong</h2> | |
| <div class="text-xl font-mono"> | |
| Player: <span id="pongPlayerScore">0</span> - <span id="pongComputerScore">0</span> :Computer | |
| </div> | |
| </div> | |
| <div class="flex justify-center"> | |
| <canvas id="pongCanvas" width="800" height="500" class="border-2 border-purple-500"></canvas> | |
| </div> | |
| <div class="mt-6 text-center"> | |
| <p class="text-gray-400 mb-4">Use <span class="font-bold">W/S</span> keys to move your paddle up and down</p> | |
| <button id="pongPauseBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-6 py-2 rounded-full font-bold mr-4"> | |
| <i class="fas fa-pause mr-2"></i>Pause | |
| </button> | |
| <button id="pongResetBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-6 py-2 rounded-full font-bold"> | |
| <i class="fas fa-redo mr-2"></i>Reset | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Blackjack Game Screen --> | |
| <div id="blackjackScreen" class="game-screen container mx-auto px-4 py-8"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <button onclick="returnToMenu()" class="flex items-center text-white hover:text-blue-400 transition"> | |
| <i class="fas fa-arrow-left mr-2"></i> Back to Menu | |
| </button> | |
| <h2 class="text-2xl font-bold text-blue-400">Blackjack</h2> | |
| <div class="text-xl font-mono"> | |
| Chips: $<span id="blackjackChips">1000</span> | |
| </div> | |
| </div> | |
| <div class="blackjack-table p-8 mb-8"> | |
| <div class="text-center mb-8"> | |
| <h3 class="text-xl font-bold text-white mb-2">Dealer</h3> | |
| <div id="dealerHand" class="flex justify-center mb-4"> | |
| <!-- Dealer cards will be added here --> | |
| </div> | |
| <div id="dealerTotal" class="text-white font-bold">Total: ?</div> | |
| </div> | |
| <div class="text-center"> | |
| <h3 class="text-xl font-bold text-white mb-2">Player</h3> | |
| <div id="playerHand" class="flex justify-center mb-4"> | |
| <!-- Player cards will be added here --> | |
| </div> | |
| <div id="playerTotal" class="text-white font-bold">Total: 0</div> | |
| </div> | |
| </div> | |
| <div id="blackjackBetControls" class="bg-gray-800 p-6 rounded-xl mb-6"> | |
| <h3 class="text-lg font-bold mb-4">Place Your Bet</h3> | |
| <div class="flex items-center mb-4"> | |
| <span class="mr-4">Bet Amount:</span> | |
| <input type="range" id="betSlider" min="10" max="500" step="10" value="50" class="flex-1 mr-4"> | |
| <span id="betAmount" class="font-bold">$50</span> | |
| </div> | |
| <button id="dealBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-full font-bold"> | |
| <i class="fas fa-play mr-2"></i>Deal | |
| </button> | |
| </div> | |
| <div id="blackjackGameControls" class="bg-gray-800 p-6 rounded-xl hidden"> | |
| <div class="flex justify-center space-x-4"> | |
| <button id="hitBtn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-full font-bold"> | |
| <i class="fas fa-plus mr-2"></i>Hit | |
| </button> | |
| <button id="standBtn" class="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded-full font-bold"> | |
| <i class="fas fa-hand-paper mr-2"></i>Stand | |
| </button> | |
| <button id="doubleBtn" class="bg-yellow-600 hover:bg-yellow-700 text-white px-6 py-2 rounded-full font-bold"> | |
| <i class="fas fa-times mr-2"></i>Double | |
| </button> | |
| </div> | |
| </div> | |
| <div id="blackjackMessage" class="text-center text-xl font-bold mt-6 hidden"></div> | |
| </div> | |
| <!-- Memory Match Game Screen --> | |
| <div id="memoryScreen" class="game-screen container mx-auto px-4 py-8"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <button onclick="returnToMenu()" class="flex items-center text-white hover:text-green-400 transition"> | |
| <i class="fas fa-arrow-left mr-2"></i> Back to Menu | |
| </button> | |
| <h2 class="text-2xl font-bold text-green-400">Memory Match</h2> | |
| <div class="text-xl font-mono"> | |
| Moves: <span id="memoryMoves">0</span> | Pairs: <span id="memoryPairs">0</span>/8 | |
| </div> | |
| </div> | |
| <div class="flex flex-col items-center"> | |
| <div id="memoryBoard" class="grid grid-cols-4 gap-4 mb-6"> | |
| <!-- Memory cards will be added here --> | |
| </div> | |
| <button id="memoryResetBtn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-full font-bold"> | |
| <i class="fas fa-redo mr-2"></i>New Game | |
| </button> | |
| </div> | |
| </div> | |
| <!-- War Game Screen --> | |
| <div id="warScreen" class="game-screen container mx-auto px-4 py-8"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <button onclick="returnToMenu()" class="flex items-center text-white hover:text-yellow-400 transition"> | |
| <i class="fas fa-arrow-left mr-2"></i> Back to Menu | |
| </button> | |
| <h2 class="text-2xl font-bold text-yellow-400">War</h2> | |
| <div class="text-xl font-mono"> | |
| Rounds: <span id="warRounds">0</span> | |
| </div> | |
| </div> | |
| <div class="flex flex-col items-center"> | |
| <div class="flex justify-center space-x-16 mb-8"> | |
| <div class="text-center"> | |
| <h3 class="text-xl font-bold mb-4">Player</h3> | |
| <div id="playerWarCard" class="card flex items-center justify-center text-4xl"> | |
| <!-- Player's war card will be shown here --> | |
| </div> | |
| <div class="mt-4">Cards: <span id="playerWarCount">26</span></div> | |
| </div> | |
| <div class="text-center"> | |
| <h3 class="text-xl font-bold mb-4">Computer</h3> | |
| <div id="computerWarCard" class="card flex items-center justify-center text-4xl"> | |
| <!-- Computer's war card will be shown here --> | |
| </div> | |
| <div class="mt-4">Cards: <span id="computerWarCount">26</span></div> | |
| </div> | |
| </div> | |
| <div id="warMessage" class="text-center text-xl font-bold mb-6"></div> | |
| <div class="flex space-x-4"> | |
| <button id="warPlayBtn" class="bg-yellow-600 hover:bg-yellow-700 text-white px-6 py-2 rounded-full font-bold"> | |
| <i class="fas fa-play mr-2"></i>Play Round | |
| </button> | |
| <button id="warResetBtn" class="bg-gray-700 hover:bg-gray-600 text-white px-6 py-2 rounded-full font-bold"> | |
| <i class="fas fa-redo mr-2"></i>New Game | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Solitaire Game Screen --> | |
| <div id="solitaireScreen" class="game-screen container mx-auto px-4 py-8"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <button onclick="returnToMenu()" class="flex items-center text-white hover:text-red-400 transition"> | |
| <i class="fas fa-arrow-left mr-2"></i> Back to Menu | |
| </button> | |
| <h2 class="text-2xl font-bold text-red-400">Solitaire</h2> | |
| <div class="text-xl font-mono"> | |
| Time: <span id="solitaireTime">00:00</span> | |
| </div> | |
| </div> | |
| <div class="bg-green-800 p-6 rounded-xl"> | |
| <div class="solitaire-top-area"> | |
| <div class="flex space-x-4"> | |
| <div id="stockPile" class="solitaire-stock"> | |
| <i class="fas fa-crown"></i> | |
| </div> | |
| <div id="wastePile" class="solitaire-waste"></div> | |
| </div> | |
| <div class="solitaire-foundations"> | |
| <div id="foundationHeart" class="solitaire-foundation drop-target" data-suit="hearts"></div> | |
| <div id="foundationDiamond" class="solitaire-foundation drop-target" data-suit="diamonds"></div> | |
| <div id="foundationClub" class="solitaire-foundation drop-target" data-suit="clubs"></div> | |
| <div id="foundationSpade" class="solitaire-foundation drop-target" data-suit="spades"></div> | |
| </div> | |
| </div> | |
| <div id="tableau" class="solitaire-tableau"> | |
| <div id="pile1" class="solitaire-pile drop-target" data-pile="1"></div> | |
| <div id="pile2" class="solitaire-pile drop-target" data-pile="2"></div> | |
| <div id="pile3" class="solitaire-pile drop-target" data-pile="3"></div> | |
| <div id="pile4" class="solitaire-pile drop-target" data-pile="4"></div> | |
| <div id="pile5" class="solitaire-pile drop-target" data-pile="5"></div> | |
| <div id="pile6" class="solitaire-pile drop-target" data-pile="6"></div> | |
| <div id="pile7" class="solitaire-pile drop-target" data-pile="7"></div> | |
| </div> | |
| </div> | |
| <div class="mt-6 text-center"> | |
| <button id="solitaireResetBtn" class="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded-full font-bold"> | |
| <i class="fas fa-redo mr-2"></i>New Game | |
| </button> | |
| </div> | |
| </div> | |
| <script> | |
| // Game selector functionality | |
| function loadGame(game) { | |
| document.getElementById('mainMenu').style.display = 'none'; | |
| document.getElementById(game + 'Screen').style.display = 'block'; | |
| // Initialize the selected game | |
| switch(game) { | |
| case 'pong': | |
| initPong(); | |
| break; | |
| case 'blackjack': | |
| initBlackjack(); | |
| break; | |
| case 'memory': | |
| initMemory(); | |
| break; | |
| case 'war': | |
| initWar(); | |
| break; | |
| case 'solitaire': | |
| initSolitaire(); | |
| break; | |
| } | |
| } | |
| function returnToMenu() { | |
| // Hide all game screens | |
| document.querySelectorAll('.game-screen').forEach(screen => { | |
| screen.style.display = 'none'; | |
| }); | |
| // Show main menu | |
| document.getElementById('mainMenu').style.display = 'block'; | |
| } | |
| // Sound toggle | |
| document.getElementById('soundToggle').addEventListener('click', function() { | |
| const icon = this.querySelector('i'); | |
| if (icon.classList.contains('fa-volume-up')) { | |
| icon.classList.remove('fa-volume-up'); | |
| icon.classList.add('fa-volume-mute'); | |
| // Mute all game sounds | |
| } else { | |
| icon.classList.remove('fa-volume-mute'); | |
| icon.classList.add('fa-volume-up'); | |
| // Unmute all game sounds | |
| } | |
| }); | |
| // Pong Game | |
| function initPong() { | |
| const canvas = document.getElementById('pongCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const playerScoreEl = document.getElementById('pongPlayerScore'); | |
| const computerScoreEl = document.getElementById('pongComputerScore'); | |
| const pauseBtn = document.getElementById('pongPauseBtn'); | |
| const resetBtn = document.getElementById('pongResetBtn'); | |
| // Game state | |
| let gameRunning = true; | |
| let playerScore = 0; | |
| let computerScore = 0; | |
| // Paddle and ball properties | |
| const paddleWidth = 15; | |
| const paddleHeight = 100; | |
| const ballSize = 15; | |
| let playerPaddle = { | |
| x: 10, | |
| y: canvas.height / 2 - paddleHeight / 2, | |
| width: paddleWidth, | |
| height: paddleHeight, | |
| speed: 8 | |
| }; | |
| let computerPaddle = { | |
| x: canvas.width - 10 - paddleWidth, | |
| y: canvas.height / 2 - paddleHeight / 2, | |
| width: paddleWidth, | |
| height: paddleHeight, | |
| speed: 5 | |
| }; | |
| let ball = { | |
| x: canvas.width / 2, | |
| y: canvas.height / 2, | |
| size: ballSize, | |
| speedX: 5, | |
| speedY: 5 | |
| }; | |
| // Keyboard controls | |
| let upPressed = false; | |
| let downPressed = false; | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === 'w' || e.key === 'W') { | |
| upPressed = true; | |
| } else if (e.key === 's' || e.key === 'S') { | |
| downPressed = true; | |
| } | |
| }); | |
| document.addEventListener('keyup', (e) => { | |
| if (e.key === 'w' || e.key === 'W') { | |
| upPressed = false; | |
| } else if (e.key === 's' || e.key === 'S') { | |
| downPressed = false; | |
| } | |
| }); | |
| // Pause/Resume game | |
| pauseBtn.addEventListener('click', () => { | |
| gameRunning = !gameRunning; | |
| if (gameRunning) { | |
| pauseBtn.innerHTML = '<i class="fas fa-pause mr-2"></i>Pause'; | |
| gameLoop(); | |
| } else { | |
| pauseBtn.innerHTML = '<i class="fas fa-play mr-2"></i>Resume'; | |
| } | |
| }); | |
| // Reset game | |
| resetBtn.addEventListener('click', resetPong); | |
| function resetPong() { | |
| playerScore = 0; | |
| computerScore = 0; | |
| playerScoreEl.textContent = '0'; | |
| computerScoreEl.textContent = '0'; | |
| playerPaddle.y = canvas.height / 2 - paddleHeight / 2; | |
| computerPaddle.y = canvas.height / 2 - paddleHeight / 2; | |
| ball.x = canvas.width / 2; | |
| ball.y = canvas.height / 2; | |
| ball.speedX = 5 * (Math.random() > 0.5 ? 1 : -1); | |
| ball.speedY = 5 * (Math.random() > 0.5 ? 1 : -1); | |
| if (!gameRunning) { | |
| gameRunning = true; | |
| pauseBtn.innerHTML = '<i class="fas fa-pause mr-2"></i>Pause'; | |
| gameLoop(); | |
| } | |
| } | |
| // Game loop | |
| function gameLoop() { | |
| if (!gameRunning) return; | |
| // Clear canvas | |
| ctx.fillStyle = '#111'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Draw center line | |
| ctx.strokeStyle = '#333'; | |
| ctx.setLineDash([10, 10]); | |
| ctx.beginPath(); | |
| ctx.moveTo(canvas.width / 2, 0); | |
| ctx.lineTo(canvas.width / 2, canvas.height); | |
| ctx.stroke(); | |
| ctx.setLineDash([]); | |
| // Draw paddles | |
| ctx.fillStyle = '#fff'; | |
| ctx.fillRect(playerPaddle.x, playerPaddle.y, playerPaddle.width, playerPaddle.height); | |
| ctx.fillRect(computerPaddle.x, computerPaddle.y, computerPaddle.width, computerPaddle.height); | |
| // Draw ball | |
| ctx.beginPath(); | |
| ctx.arc(ball.x, ball.y, ball.size, 0, Math.PI * 2); | |
| ctx.fillStyle = '#fff'; | |
| ctx.fill(); | |
| // Move player paddle | |
| if (upPressed && playerPaddle.y > 0) { | |
| playerPaddle.y -= playerPaddle.speed; | |
| } | |
| if (downPressed && playerPaddle.y < canvas.height - playerPaddle.height) { | |
| playerPaddle.y += playerPaddle.speed; | |
| } | |
| // Simple AI for computer paddle | |
| if (computerPaddle.y + computerPaddle.height / 2 < ball.y - 10) { | |
| computerPaddle.y += computerPaddle.speed; | |
| } else if (computerPaddle.y + computerPaddle.height / 2 > ball.y + 10) { | |
| computerPaddle.y -= computerPaddle.speed; | |
| } | |
| // Keep computer paddle in bounds | |
| if (computerPaddle.y < 0) { | |
| computerPaddle.y = 0; | |
| } else if (computerPaddle.y > canvas.height - computerPaddle.height) { | |
| computerPaddle.y = canvas.height - computerPaddle.height; | |
| } | |
| // Move ball | |
| ball.x += ball.speedX; | |
| ball.y += ball.speedY; | |
| // Ball collision with top and bottom walls | |
| if (ball.y - ball.size <= 0 || ball.y + ball.size >= canvas.height) { | |
| ball.speedY = -ball.speedY; | |
| } | |
| // Ball collision with paddles | |
| if ( | |
| ball.x - ball.size <= playerPaddle.x + playerPaddle.width && | |
| ball.y >= playerPaddle.y && | |
| ball.y <= playerPaddle.y + playerPaddle.height | |
| ) { | |
| ball.speedX = -ball.speedX * 1.05; // Increase speed slightly | |
| // Add some angle based on where ball hits paddle | |
| const hitPos = (ball.y - (playerPaddle.y + playerPaddle.height / 2)) / (playerPaddle.height / 2); | |
| ball.speedY = hitPos * 5; | |
| } | |
| if ( | |
| ball.x + ball.size >= computerPaddle.x && | |
| ball.y >= computerPaddle.y && | |
| ball.y <= computerPaddle.y + computerPaddle.height | |
| ) { | |
| ball.speedX = -ball.speedX * 1.05; // Increase speed slightly | |
| // Add some angle based on where ball hits paddle | |
| const hitPos = (ball.y - (computerPaddle.y + computerPaddle.height / 2)) / (computerPaddle.height / 2); | |
| ball.speedY = hitPos * 5; | |
| } | |
| // Ball out of bounds - score points | |
| if (ball.x - ball.size <= 0) { | |
| computerScore++; | |
| computerScoreEl.textContent = computerScore; | |
| resetBall(); | |
| } | |
| if (ball.x + ball.size >= canvas.width) { | |
| playerScore++; | |
| playerScoreEl.textContent = playerScore; | |
| resetBall(); | |
| } | |
| requestAnimationFrame(gameLoop); | |
| } | |
| function resetBall() { | |
| ball.x = canvas.width / 2; | |
| ball.y = canvas.height / 2; | |
| ball.speedX = 5 * (Math.random() > 0.5 ? 1 : -1); | |
| ball.speedY = 5 * (Math.random() > 0.5 ? 1 : -1); | |
| // Pause briefly before resuming | |
| gameRunning = false; | |
| pauseBtn.innerHTML = '<i class="fas fa-play mr-2"></i>Resume'; | |
| setTimeout(() => { | |
| gameRunning = true; | |
| pauseBtn.innerHTML = '<i class="fas fa-pause mr-2"></i>Pause'; | |
| gameLoop(); | |
| }, 1000); | |
| } | |
| // Start the game | |
| gameLoop(); | |
| } | |
| // Blackjack Game | |
| function initBlackjack() { | |
| const dealerHandEl = document.getElementById('dealerHand'); | |
| const playerHandEl = document.getElementById('playerHand'); | |
| const dealerTotalEl = document.getElementById('dealerTotal'); | |
| const playerTotalEl = document.getElementById('playerTotal'); | |
| const chipsEl = document.getElementById('blackjackChips'); | |
| const betSlider = document.getElementById('betSlider'); | |
| const betAmountEl = document.getElementById('betAmount'); | |
| const dealBtn = document.getElementById('dealBtn'); | |
| const hitBtn = document.getElementById('hitBtn'); | |
| const standBtn = document.getElementById('standBtn'); | |
| const doubleBtn = document.getElementById('doubleBtn'); | |
| const gameControls = document.getElementById('blackjackGameControls'); | |
| const betControls = document.getElementById('blackjackBetControls'); | |
| const messageEl = document.getElementById('blackjackMessage'); | |
| // Game state | |
| let deck = []; | |
| let dealerHand = []; | |
| let playerHand = []; | |
| let chips = 1000; | |
| let currentBet = 50; | |
| let gameInProgress = false; | |
| let canDouble = true; | |
| // Update bet amount display | |
| betSlider.addEventListener('input', () => { | |
| currentBet = parseInt(betSlider.value); | |
| betAmountEl.textContent = currentBet; | |
| }); | |
| // Deal button | |
| dealBtn.addEventListener('click', deal); | |
| // Game action buttons | |
| hitBtn.addEventListener('click', hit); | |
| standBtn.addEventListener('click', stand); | |
| doubleBtn.addEventListener('click', doubleDown); | |
| // Initialize game | |
| function newDeck() { | |
| const suits = ['hearts', 'diamonds', 'clubs', 'spades']; | |
| const values = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']; | |
| deck = []; | |
| for (let suit of suits) { | |
| for (let value of values) { | |
| deck.push({ suit, value }); | |
| } | |
| } | |
| // Shuffle deck | |
| for (let i = deck.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [deck[i], deck[j]] = [deck[j], deck[i]]; | |
| } | |
| } | |
| function deal() { | |
| if (gameInProgress) return; | |
| if (currentBet > chips) { | |
| messageEl.textContent = "You don't have enough chips!"; | |
| messageEl.classList.remove('hidden'); | |
| return; | |
| } | |
| // Reset game state | |
| dealerHand = []; | |
| playerHand = []; | |
| gameInProgress = true; | |
| canDouble = true; | |
| messageEl.classList.add('hidden'); | |
| // Update UI | |
| chips -= currentBet; | |
| chipsEl.textContent = chips; | |
| // Hide bet controls, show game controls | |
| betControls.classList.add('hidden'); | |
| gameControls.classList.remove('hidden'); | |
| // Create new deck and deal cards | |
| newDeck(); | |
| // Deal initial cards (player first, then dealer, then player, then dealer) | |
| playerHand.push(deck.pop()); | |
| dealerHand.push(deck.pop()); | |
| playerHand.push(deck.pop()); | |
| dealerHand.push(deck.pop()); | |
| // Update display | |
| updateDisplay(); | |
| // Check for blackjack | |
| if (calculateHandValue(playerHand) === 21) { | |
| // Player has blackjack | |
| endGame(true); | |
| } | |
| } | |
| function hit() { | |
| if (!gameInProgress) return; | |
| // Add card to player's hand | |
| playerHand.push(deck.pop()); | |
| canDouble = false; | |
| // Update display | |
| updateDisplay(); | |
| // Check if player busted | |
| if (calculateHandValue(playerHand) > 21) { | |
| endGame(false); | |
| } | |
| } | |
| function stand() { | |
| if (!gameInProgress) return; | |
| // Dealer draws until they have at least 17 | |
| while (calculateHandValue(dealerHand) < 17) { | |
| dealerHand.push(deck.pop()); | |
| } | |
| // Update display (show all dealer cards) | |
| updateDisplay(true); | |
| // Determine winner | |
| const playerValue = calculateHandValue(playerHand); | |
| const dealerValue = calculateHandValue(dealerHand); | |
| if (dealerValue > 21 || playerValue > dealerValue) { | |
| // Player wins | |
| endGame(true); | |
| } else if (playerValue < dealerValue) { | |
| // Dealer wins | |
| endGame(false); | |
| } else { | |
| // Push | |
| endGame(null); | |
| } | |
| } | |
| function doubleDown() { | |
| if (!gameInProgress || !canDouble) return; | |
| if (currentBet > chips) { | |
| messageEl.textContent = "You don't have enough chips to double!"; | |
| messageEl.classList.remove('hidden'); | |
| return; | |
| } | |
| // Double the bet | |
| chips -= currentBet; | |
| currentBet *= 2; | |
| chipsEl.textContent = chips; | |
| betAmountEl.textContent = currentBet; | |
| // Take one more card | |
| hit(); | |
| // Automatically stand | |
| stand(); | |
| } | |
| function endGame(playerWins) { | |
| gameInProgress = false; | |
| // Show all dealer cards | |
| updateDisplay(true); | |
| // Payout | |
| if (playerWins === true) { | |
| const isBlackjack = calculateHandValue(playerHand) === 21 && playerHand.length === 2; | |
| const payout = isBlackjack ? Math.floor(currentBet * 2.5) : currentBet * 2; | |
| chips += payout; | |
| messageEl.textContent = isBlackjack ? "Blackjack! You win $" + payout : "You win $" + payout; | |
| } else if (playerWins === false) { | |
| messageEl.textContent = "Dealer wins!"; | |
| } else { | |
| // Push | |
| chips += currentBet; | |
| messageEl.textContent = "Push! Bet returned"; | |
| } | |
| chipsEl.textContent = chips; | |
| messageEl.classList.remove('hidden'); | |
| // Reset bet to minimum | |
| currentBet = 50; | |
| betSlider.value = currentBet; | |
| betAmountEl.textContent = currentBet; | |
| // Show bet controls, hide game controls | |
| gameControls.classList.add('hidden'); | |
| betControls.classList.remove('hidden'); | |
| } | |
| function updateDisplay(showAllDealerCards = false) { | |
| // Clear hands | |
| dealerHandEl.innerHTML = ''; | |
| playerHandEl.innerHTML = ''; | |
| // Display dealer's hand | |
| dealerHand.forEach((card, index) => { | |
| if (index === 0 && !showAllDealerCards) { | |
| // First card is hidden | |
| const cardEl = document.createElement('div'); | |
| cardEl.className = 'card card-back'; | |
| dealerHandEl.appendChild(cardEl); | |
| } else { | |
| // Show card | |
| const cardEl = document.createElement('div'); | |
| cardEl.className = `card ${card.suit === 'hearts' || card.suit === 'diamonds' ? 'red' : 'black'}`; | |
| cardEl.textContent = card.value; | |
| // Add suit symbol | |
| const suitSymbol = document.createElement('div'); | |
| suitSymbol.className = 'absolute bottom-1 right-1 text-xs'; | |
| switch(card.suit) { | |
| case 'hearts': | |
| suitSymbol.innerHTML = '♥'; | |
| break; | |
| case 'diamonds': | |
| suitSymbol.innerHTML = '♦'; | |
| break; | |
| case 'clubs': | |
| suitSymbol.innerHTML = '♣'; | |
| break; | |
| case 'spades': | |
| suitSymbol.innerHTML = '♠'; | |
| break; | |
| } | |
| cardEl.appendChild(suitSymbol); | |
| dealerHandEl.appendChild(cardEl); | |
| } | |
| }); | |
| // Display player's hand | |
| playerHand.forEach(card => { | |
| const cardEl = document.createElement('div'); | |
| cardEl.className = `card ${card.suit === 'hearts' || card.suit === 'diamonds' ? 'red' : 'black'}`; | |
| cardEl.textContent = card.value; | |
| // Add suit symbol | |
| const suitSymbol = document.createElement('div'); | |
| suitSymbol.className = 'absolute bottom-1 right-1 text-xs'; | |
| switch(card.suit) { | |
| case 'hearts': | |
| suitSymbol.innerHTML = '♥'; | |
| break; | |
| case 'diamonds': | |
| suitSymbol.innerHTML = '♦'; | |
| break; | |
| case 'clubs': | |
| suitSymbol.innerHTML = '♣'; | |
| break; | |
| case 'spades': | |
| suitSymbol.innerHTML = '♠'; | |
| break; | |
| } | |
| cardEl.appendChild(suitSymbol); | |
| playerHandEl.appendChild(cardEl); | |
| }); | |
| // Update totals | |
| if (showAllDealerCards) { | |
| dealerTotalEl.textContent = 'Total: ' + calculateHandValue(dealerHand); | |
| } else { | |
| // Only show value of dealer's up card | |
| const upCardValue = dealerHand[1].value; | |
| dealerTotalEl.textContent = 'Total: ?'; | |
| } | |
| playerTotalEl.textContent = 'Total: ' + calculateHandValue(playerHand); | |
| } | |
| function calculateHandValue(hand) { | |
| let value = 0; | |
| let aces = 0; | |
| for (let card of hand) { | |
| if (card.value === 'A') { | |
| aces++; | |
| value += 11; | |
| } else if (['K', 'Q', 'J'].includes(card.value)) { | |
| value += 10; | |
| } else { | |
| value += parseInt(card.value); | |
| } | |
| } | |
| // Adjust for aces if needed | |
| while (value > 21 && aces > 0) { | |
| value -= 10; | |
| aces--; | |
| } | |
| return value; | |
| } | |
| } | |
| // Memory Match Game | |
| function initMemory() { | |
| const memoryBoard = document.getElementById('memoryBoard'); | |
| const movesEl = document.getElementById('memoryMoves'); | |
| const pairsEl = document.getElementById('memoryPairs'); | |
| const resetBtn = document.getElementById('memoryResetBtn'); | |
| // Game state | |
| let cards = []; | |
| let flippedCards = []; | |
| let moves = 0; | |
| let pairsFound = 0; | |
| let canFlip = true; | |
| // Card values (8 pairs of letters) | |
| const cardValues = ['A', 'A', 'B', 'B', 'C', 'C', 'D', 'D', 'E', 'E', 'F', 'F', 'G', 'G', 'H', 'H']; | |
| // Initialize game | |
| resetBtn.addEventListener('click', newGame); | |
| newGame(); | |
| function newGame() { | |
| // Reset game state | |
| moves = 0; | |
| pairsFound = 0; | |
| flippedCards = []; | |
| canFlip = true; | |
| // Update UI | |
| movesEl.textContent = moves; | |
| pairsEl.textContent = pairsFound; | |
| // Create shuffled deck | |
| cards = [...cardValues]; | |
| for (let i = cards.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [cards[i], cards[j]] = [cards[j], cards[i]]; | |
| } | |
| // Create card elements | |
| memoryBoard.innerHTML = ''; | |
| cards.forEach((value, index) => { | |
| const cardEl = document.createElement('div'); | |
| cardEl.className = 'memory-card'; | |
| cardEl.dataset.index = index; | |
| cardEl.innerHTML = ` | |
| <div class="memory-card-inner"> | |
| <div class="memory-card-front"></div> | |
| <div class="memory-card-back">${value}</div> | |
| </div> | |
| `; | |
| cardEl.addEventListener('click', flipCard); | |
| memoryBoard.appendChild(cardEl); | |
| }); | |
| } | |
| function flipCard() { | |
| if (!canFlip || this.classList.contains('flipped')) return; | |
| const index = parseInt(this.dataset.index); | |
| // Don't allow flipping more than 2 cards | |
| if (flippedCards.length >= 2) return; | |
| // Flip the card | |
| this.classList.add('flipped'); | |
| flippedCards.push({ element: this, value: cards[index] }); | |
| // If two cards are flipped, check for a match | |
| if (flippedCards.length === 2) { | |
| moves++; | |
| movesEl.textContent = moves; | |
| canFlip = false; | |
| if (flippedCards[0].value === flippedCards[1].value) { | |
| // Match found | |
| pairsFound++; | |
| pairsEl.textContent = pairsFound; | |
| // Remove event listeners from matched cards | |
| flippedCards[0].element.removeEventListener('click', flipCard); | |
| flippedCards[1].element.removeEventListener('click', flipCard); | |
| // Check if game is won | |
| if (pairsFound === 8) { | |
| setTimeout(() => { | |
| alert(`Congratulations! You won in ${moves} moves!`); | |
| }, 500); | |
| } | |
| flippedCards = []; | |
| canFlip = true; | |
| } else { | |
| // No match - flip cards back after delay | |
| setTimeout(() => { | |
| flippedCards.forEach(card => { | |
| card.element.classList.remove('flipped'); | |
| }); | |
| flippedCards = []; | |
| canFlip = true; | |
| }, 1000); | |
| } | |
| } | |
| } | |
| } | |
| // War Game | |
| function initWar() { | |
| const playerCardEl = document.getElementById('playerWarCard'); | |
| const computerCardEl = document.getElementById('computerWarCard'); | |
| const playerCountEl = document.getElementById('playerWarCount'); | |
| const computerCountEl = document.getElementById('computerWarCount'); | |
| const roundsEl = document.getElementById('warRounds'); | |
| const messageEl = document.getElementById('warMessage'); | |
| const playBtn = document.getElementById('warPlayBtn'); | |
| const resetBtn = document.getElementById('warResetBtn'); | |
| // Game state | |
| let deck = []; | |
| let playerDeck = []; | |
| let computerDeck = []; | |
| let rounds = 0; | |
| let gameOver = false; | |
| // Initialize game | |
| resetBtn.addEventListener('click', newGame); | |
| playBtn.addEventListener('click', playRound); | |
| newGame(); | |
| function newGame() { | |
| // Reset game state | |
| rounds = 0; | |
| gameOver = false; | |
| messageEl.textContent = ''; | |
| // Create and shuffle deck | |
| const suits = ['hearts', 'diamonds', 'clubs', 'spades']; | |
| const values = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']; | |
| deck = []; | |
| for (let suit of suits) { | |
| for (let value of values) { | |
| deck.push({ suit, value }); | |
| } | |
| } | |
| // Shuffle deck | |
| for (let i = deck.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [deck[i], deck[j]] = [deck[j], deck[i]]; | |
| } | |
| // Deal cards to players | |
| playerDeck = deck.slice(0, 26); | |
| computerDeck = deck.slice(26); | |
| // Update UI | |
| updateUI(); | |
| } | |
| function playRound() { | |
| if (gameOver) return; | |
| // Check if either player is out of cards | |
| if (playerDeck.length === 0 || computerDeck.length === 0) { | |
| endGame(); | |
| return; | |
| } | |
| // Increment round | |
| rounds++; | |
| roundsEl.textContent = rounds; | |
| // Draw cards | |
| const playerCard = playerDeck.shift(); | |
| const computerCard = computerDeck.shift(); | |
| // Display cards | |
| displayCard(playerCard, playerCardEl); | |
| displayCard(computerCard, computerCardEl); | |
| // Compare cards | |
| const playerValue = getCardValue(playerCard.value); | |
| const computerValue = getCardValue(computerCard.value); | |
| if (playerValue > computerValue) { | |
| // Player wins the round | |
| playerDeck.push(playerCard, computerCard); | |
| messageEl.textContent = "You win this round!"; | |
| } else if (computerValue > playerValue) { | |
| // Computer wins the round | |
| computerDeck.push(playerCard, computerCard); | |
| messageEl.textContent = "Computer wins this round!"; | |
| } else { | |
| // War! | |
| messageEl.textContent = "War!"; | |
| // Check if players have enough cards for war | |
| if (playerDeck.length < 4 || computerDeck.length < 4) { | |
| endGame(); | |
| return; | |
| } | |
| // Each player puts 3 cards face down and 1 face up | |
| const playerFaceDown = playerDeck.splice(0, 3); | |
| const computerFaceDown = computerDeck.splice(0, 3); | |
| const playerWarCard = playerDeck.shift(); | |
| const computerWarCard = computerDeck.shift(); | |
| // Display war cards after a delay | |
| setTimeout(() => { | |
| displayCard(playerWarCard, playerCardEl); | |
| displayCard(computerWarCard, computerCardEl); | |
| const playerWarValue = getCardValue(playerWarCard.value); | |
| const computerWarValue = getCardValue(computerWarCard.value); | |
| if (playerWarValue > computerWarValue) { | |
| // Player wins the war | |
| playerDeck.push( | |
| playerCard, computerCard, | |
| ...playerFaceDown, ...computerFaceDown, | |
| playerWarCard, computerWarCard | |
| ); | |
| messageEl.textContent = "You win the war!"; | |
| } else if (computerWarValue > playerWarValue) { | |
| // Computer wins the war | |
| computerDeck.push( | |
| playerCard, computerCard, | |
| ...playerFaceDown, ...computerFaceDown, | |
| playerWarCard, computerWarCard | |
| ); | |
| messageEl.textContent = "Computer wins the war!"; | |
| } else { | |
| // Another war - for simplicity, we'll just split the pot | |
| playerDeck.push(...playerFaceDown, playerWarCard); | |
| computerDeck.push(...computerFaceDown, computerWarCard); | |
| messageEl.textContent = "Another war! Cards split."; | |
| } | |
| // Update UI | |
| updateUI(); | |
| }, 1000); | |
| } | |
| // Update UI | |
| updateUI(); | |
| } | |
| function displayCard(card, element) { | |
| element.className = `card ${card.suit === 'hearts' || card.suit === 'diamonds' ? 'red' : 'black'}`; | |
| element.textContent = card.value; | |
| // Add suit symbol | |
| const suitSymbol = document.createElement('div'); | |
| suitSymbol.className = 'absolute bottom-1 right-1 text-xs'; | |
| switch(card.suit) { | |
| case 'hearts': | |
| suitSymbol.innerHTML = '♥'; | |
| break; | |
| case 'diamonds': | |
| suitSymbol.innerHTML = '♦'; | |
| break; | |
| case 'clubs': | |
| suitSymbol.innerHTML = '♣'; | |
| break; | |
| case 'spades': | |
| suitSymbol.innerHTML = '♠'; | |
| break; | |
| } | |
| element.appendChild(suitSymbol); | |
| } | |
| function getCardValue(value) { | |
| const values = { | |
| '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, | |
| '9': 9, '10': 10, 'J': 11, 'Q': 12, 'K': 13, 'A': 14 | |
| }; | |
| return values[value]; | |
| } | |
| function updateUI() { | |
| playerCountEl.textContent = playerDeck.length; | |
| computerCountEl.textContent = computerDeck.length; | |
| } | |
| function endGame() { | |
| gameOver = true; | |
| if (playerDeck.length > computerDeck.length) { | |
| messageEl.textContent = "You win the game!"; | |
| } else if (computerDeck.length > playerDeck.length) { | |
| messageEl.textContent = "Computer wins the game!"; | |
| } else { | |
| messageEl.textContent = "The game is a tie!"; | |
| } | |
| } | |
| } | |
| // Solitaire Game | |
| function initSolitaire() { | |
| const stockPileEl = document.getElementById('stockPile'); | |
| const wastePileEl = document.getElementById('wastePile'); | |
| const foundationPiles = { | |
| 'hearts': document.getElementById('foundationHeart'), | |
| 'diamonds': document.getElementById('foundationDiamond'), | |
| 'clubs': document.getElementById('foundationClub'), | |
| 'spades': document.getElementById('foundationSpade') | |
| }; | |
| const tableauPiles = [ | |
| document.getElementById('pile1'), | |
| document.getElementById('pile2'), | |
| document.getElementById('pile3'), | |
| document.getElementById('pile4'), | |
| document.getElementById('pile5'), | |
| document.getElementById('pile6'), | |
| document.getElementById('pile7') | |
| ]; | |
| const timeEl = document.getElementById('solitaireTime'); | |
| const resetBtn = document.getElementById('solitaireResetBtn'); | |
| // Game state | |
| let deck = []; | |
| let stock = []; | |
| let waste = []; | |
| let foundations = { | |
| 'hearts': [], | |
| 'diamonds': [], | |
| 'clubs': [], | |
| 'spades': [] | |
| }; | |
| let tableau = [[], [], [], [], [], [], []]; | |
| let timer = null; | |
| let seconds = 0; | |
| let draggedCard = null; | |
| let draggedCards = []; | |
| let offsetX = 0; | |
| let offsetY = 0; | |
| // Initialize game | |
| resetBtn.addEventListener('click', newGame); | |
| stockPileEl.addEventListener('click', drawFromStock); | |
| newGame(); | |
| // Set up drag and drop | |
| document.addEventListener('mousedown', startDrag); | |
| document.addEventListener('mousemove', dragCard); | |
| document.addEventListener('mouseup', endDrag); | |
| function newGame() { | |
| // Reset timer | |
| if (timer) clearInterval(timer); | |
| seconds = 0; | |
| updateTimer(); | |
| timer = setInterval(updateTimer, 1000); | |
| // 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']; | |
| deck = []; | |
| for (let suit of suits) { | |
| for (let value of values) { | |
| deck.push({ suit, value, faceUp: false }); | |
| } | |
| } | |
| // Shuffle deck | |
| for (let i = deck.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [deck[i], deck[j]] = [deck[j], deck[i]]; | |
| } | |
| // Reset game state | |
| stock = []; | |
| waste = []; | |
| foundations = { | |
| 'hearts': [], | |
| 'diamonds': [], | |
| 'clubs': [], | |
| 'spades': [] | |
| }; | |
| tableau = [[], [], [], [], [], [], []]; | |
| // Deal to tableau | |
| for (let i = 0; i < 7; i++) { | |
| for (let j = 0; j <= i; j++) { | |
| const card = deck.pop(); | |
| if (j === i) { | |
| // Last card in pile is face up | |
| card.faceUp = true; | |
| } | |
| tableau[i].push(card); | |
| } | |
| } | |
| // Remaining cards go to stock | |
| stock = [...deck]; | |
| // Update UI | |
| updateUI(); | |
| } | |
| function drawFromStock() { | |
| if (stock.length === 0) { | |
| // If stock is empty, recycle waste back to stock | |
| stock = [...waste.reverse()]; | |
| waste = []; | |
| stock.forEach(card => card.faceUp = false); | |
| } else { | |
| // Draw 1 card from stock to waste | |
| const card = stock.pop(); | |
| card.faceUp = true; | |
| waste.push(card); | |
| } | |
| updateUI(); | |
| } | |
| function updateTimer() { | |
| seconds++; | |
| const minutes = Math.floor(seconds / 60); | |
| const remainingSeconds = seconds % 60; | |
| timeEl.textContent = `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; | |
| } | |
| function updateUI() { | |
| // Clear all piles | |
| wastePileEl.innerHTML = ''; | |
| tableauPiles.forEach(pile => pile.innerHTML = ''); | |
| for (let suit in foundationPiles) { | |
| foundationPiles[suit].innerHTML = ''; | |
| } | |
| // Update stock | |
| stockPileEl.innerHTML = stock.length > 0 ? '<i class="fas fa-crown"></i>' : ''; | |
| // Update waste | |
| if (waste.length > 0) { | |
| const topCard = waste[waste.length - 1]; | |
| createCardElement(topCard, wastePileEl, true); | |
| } | |
| // Update foundations | |
| for (let suit in foundations) { | |
| const pile = foundations[suit]; | |
| if (pile.length > 0) { | |
| const topCard = pile[pile.length - 1]; | |
| createCardElement(topCard, foundationPiles[suit], true); | |
| } | |
| } | |
| // Update tableau | |
| for (let i = 0; i < 7; i++) { | |
| const pile = tableau[i]; | |
| const pileEl = tableauPiles[i]; | |
| for (let j = 0; j < pile.length; j++) { | |
| const card = pile[j]; | |
| createCardElement(card, pileEl, false, j * 20); | |
| } | |
| } | |
| } | |
| function createCardElement(card, parent, isTopCard, offset = 0) { | |
| const cardEl = document.createElement('div'); | |
| cardEl.className = `solitaire-card ${card.suit === 'hearts' || card.suit === 'diamonds' ? 'red' : 'black'}`; | |
| cardEl.style.top = `${offset}px`; | |
| cardEl.dataset.suit = card.suit; | |
| cardEl.dataset.value = card.value; | |
| cardEl.dataset.faceUp = card.faceUp; | |
| if (card.faceUp) { | |
| cardEl.innerHTML = ` | |
| <div class="card-value">${card.value}</div> | |
| <div class="card-suit">${getSuitSymbol(card.suit)}</div> | |
| `; | |
| } else { | |
| cardEl.className += ' card-back'; | |
| } | |
| if (isTopCard) { | |
| cardEl.dataset.topCard = 'true'; | |
| } | |
| parent.appendChild(cardEl); | |
| return cardEl; | |
| } | |
| function getSuitSymbol(suit) { | |
| switch(suit) { | |
| case 'hearts': return '♥'; | |
| case 'diamonds': return '♦'; | |
| case 'clubs': return '♣'; | |
| case 'spades': return '♠'; | |
| } | |
| } | |
| function startDrag(e) { | |
| if (!e.target.classList.contains('solitaire-card') || e.target.classList.contains('card-back')) return; | |
| draggedCard = e.target; | |
| draggedCards = [draggedCard]; | |
| // Calculate offset | |
| const rect = draggedCard.getBoundingClientRect(); | |
| offsetX = e.clientX - rect.left; | |
| offsetY = e.clientY - rect.top; | |
| // If this is a tableau card, check if there are cards below it | |
| const parentPile = draggedCard.parentElement; | |
| if (parentPile.classList.contains('solitaire-pile')) { | |
| const cards = Array.from(parentPile.querySelectorAll('.solitaire-card')); | |
| const cardIndex = cards.indexOf(draggedCard); | |
| // Add all cards below this one to draggedCards | |
| for (let i = cardIndex + 1; i < cards.length; i++) { | |
| draggedCards.push(cards[i]); | |
| } | |
| } | |
| // Style the dragged cards | |
| draggedCards.forEach((card, index) => { | |
| card.style.position = 'absolute'; | |
| card.style.zIndex = '1000'; | |
| card.style.transform = `translate(${e.clientX - offsetX}px, ${e.clientY - offsetY + (index * 20)}px)`; | |
| card.classList.add('dragging'); | |
| }); | |
| // Highlight valid drop targets | |
| highlightDropTargets(true); | |
| } | |
| function dragCard(e) { | |
| if (!draggedCard) return; | |
| // Move all dragged cards | |
| draggedCards.forEach((card, index) => { | |
| card.style.transform = `translate(${e.clientX - offsetX}px, ${e.clientY - offsetY + (index * 20)}px)`; | |
| }); | |
| } | |
| function endDrag(e) { | |
| if (!draggedCard) return; | |
| // Remove highlight from drop targets | |
| highlightDropTargets(false); | |
| // Find the drop target | |
| const dropTarget = document.elementFromPoint(e.clientX, e.clientY); | |
| const validDrop = dropTarget && dropTarget.classList.contains('drop-target'); | |
| if (validDrop) { | |
| // Check if the move is valid | |
| const sourcePile = draggedCard.parentElement; | |
| const sourceType = sourcePile.id.startsWith('pile') ? 'tableau' : | |
| sourcePile.id.startsWith('foundation') ? 'foundation' : | |
| sourcePile.id === 'wastePile' ? 'waste' : null; | |
| const targetType = dropTarget.id.startsWith('pile') ? 'tableau' : | |
| dropTarget.id.startsWith('foundation') ? 'foundation' : null; | |
| // Get the card data | |
| const cardData = { | |
| suit: draggedCard.dataset.suit, | |
| value: draggedCard.dataset.value, | |
| faceUp: true | |
| }; | |
| // Check if the move is valid | |
| if (targetType === 'foundation') { | |
| const targetSuit = dropTarget.dataset.suit; | |
| const foundation = foundations[targetSuit]; | |
| // Check if the card can be placed on the foundation | |
| if (canMoveToFoundation(cardData, foundation)) { | |
| // Remove card from source | |
| if (sourceType === 'tableau') { | |
| const pileIndex = parseInt(sourcePile.dataset.pile) - 1; | |
| const cardIndex = tableau[pileIndex].findIndex(c => | |
| c.suit === cardData.suit && c.value === cardData.value && c.faceUp | |
| ); | |
| if (cardIndex !== -1) { | |
| // Remove this card and all below it | |
| const removedCards = tableau[pileIndex].splice(cardIndex); | |
| foundations[targetSuit].push(removedCards[0]); | |
| // Flip the next card if there is one | |
| if (tableau[pileIndex].length > 0 && !tableau[pileIndex][tableau[pileIndex].length - 1].faceUp) { | |
| tableau[pileIndex][tableau[pileIndex].length - 1].faceUp = true; | |
| } | |
| } | |
| } else if (sourceType === 'waste') { | |
| const wasteCard = waste.pop(); | |
| foundations[targetSuit].push(wasteCard); | |
| } | |
| } | |
| } else if (targetType === 'tableau') { | |
| const pileIndex = parseInt(dropTarget.dataset.pile) - 1; | |
| const targetPile = tableau[pileIndex]; | |
| // Check if the card can be placed on the tableau pile | |
| if (canMoveToTableau(cardData, targetPile)) { | |
| // Remove card from source | |
| if (sourceType === 'tableau') { | |
| const sourcePileIndex = parseInt(sourcePile.dataset.pile) - 1; | |
| const cardIndex = tableau[sourcePileIndex].findIndex(c => | |
| c.suit === cardData.suit && c.value === cardData.value && c.faceUp | |
| ); | |
| if (cardIndex !== -1) { | |
| // Remove this card and all below it | |
| const removedCards = tableau[sourcePileIndex].splice(cardIndex); | |
| tableau[pileIndex].push(...removedCards); | |
| // Flip the next card if there is one | |
| if (tableau[sourcePileIndex].length > 0 && !tableau[sourcePileIndex][tableau[sourcePileIndex].length - 1].faceUp) { | |
| tableau[sourcePileIndex][tableau[sourcePileIndex].length - 1].faceUp = true; | |
| } | |
| } | |
| } else if (sourceType === 'waste') { | |
| const wasteCard = waste.pop(); | |
| tableau[pileIndex].push(wasteCard); | |
| } else if (sourceType === 'foundation') { | |
| const sourceSuit = sourcePile.dataset.suit; | |
| const foundationCard = foundations[sourceSuit].pop(); | |
| tableau[pileIndex].push(foundationCard); | |
| } | |
| } | |
| } | |
| } | |
| // Reset dragged card | |
| draggedCards.forEach(card => { | |
| card.style.position = ''; | |
| card.style.zIndex = ''; | |
| card.style.transform = ''; | |
| card.classList.remove('dragging'); | |
| }); | |
| draggedCard = null; | |
| draggedCards = []; | |
| // Update UI | |
| updateUI(); | |
| // Check for win | |
| checkWin(); | |
| } | |
| function highlightDropTargets(highlight) { | |
| document.querySelectorAll('.drop-target').forEach(target => { | |
| const targetType = target.id.startsWith('pile') ? 'tableau' : | |
| target.id.startsWith('foundation') ? 'foundation' : null; | |
| if (!draggedCard) return; | |
| const cardData = { | |
| suit: draggedCard.dataset.suit, | |
| value: draggedCard.dataset.value, | |
| faceUp: true | |
| }; | |
| let isValid = false; | |
| if (targetType === 'foundation') { | |
| const targetSuit = target.dataset.suit; | |
| isValid = canMoveToFoundation(cardData, foundations[targetSuit]); | |
| } else if (targetType === 'tableau') { | |
| const pileIndex = parseInt(target.dataset.pile) - 1; | |
| isValid = canMoveToTableau(cardData, tableau[pileIndex]); | |
| } | |
| if (highlight && isValid) { | |
| target.classList.add('highlight'); | |
| } else { | |
| target.classList.remove('highlight'); | |
| } | |
| }); | |
| } | |
| function canMoveToFoundation(card, foundation) { | |
| if (foundation.length === 0) { | |
| return card.value === 'A'; | |
| } else { | |
| const topCard = foundation[foundation.length - 1]; | |
| const cardValues = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']; | |
| const currentIndex = cardValues.indexOf(topCard.value); | |
| return card.suit === topCard.suit && cardValues.indexOf(card.value) === currentIndex + 1; | |
| } | |
| } | |
| function canMoveToTableau(card, pile) { | |
| if (pile.length === 0) { | |
| return card.value === 'K'; | |
| } else { | |
| const topCard = pile[pile.length - 1]; | |
| const cardValues = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']; | |
| const currentIndex = cardValues.indexOf(topCard.value); | |
| const newIndex = cardValues.indexOf(card.value); | |
| const differentColor = | |
| (topCard.suit === 'hearts' || topCard.suit === 'diamonds') !== | |
| (card.suit === 'hearts' || card.suit === 'diamonds'); | |
| return differentColor && newIndex === currentIndex - 1; | |
| } | |
| } | |
| function checkWin() { | |
| for (let suit in foundations) { | |
| if (foundations[suit].length !== 13) return false; | |
| } | |
| // All foundations are complete | |
| alert('Congratulations! You won the game!'); | |
| return true; | |
| } | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=CroissantWhyNot/game-hub" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |