Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ZigZag Puzzle</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <style> | |
| @keyframes pulse { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| } | |
| .pulse-animation { | |
| animation: pulse 1.5s infinite; | |
| } | |
| .tile { | |
| transition: all 0.3s ease; | |
| } | |
| .tile:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| } | |
| #game-board { | |
| perspective: 1000px; | |
| } | |
| .win-effect { | |
| animation: confetti 3s ease-out; | |
| } | |
| @keyframes confetti { | |
| 0% { transform: translateY(0) rotate(0deg); opacity: 1; } | |
| 100% { transform: translateY(-100vh) rotate(360deg); opacity: 0; } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gradient-to-br from-indigo-900 to-purple-800 min-h-screen font-sans text-white"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="text-center mb-8"> | |
| <h1 class="text-4xl md:text-5xl font-bold mb-2">ZigZag Puzzle</h1> | |
| <p class="text-xl text-indigo-200">Slide the tiles to solve the puzzle!</p> | |
| </header> | |
| <div class="flex flex-col md:flex-row gap-8 items-center justify-center"> | |
| <div id="game-board" class="bg-white/10 backdrop-blur-md rounded-xl p-4 shadow-2xl"> | |
| <div class="grid grid-cols-4 gap-2" id="puzzle-container"></div> | |
| </div> | |
| <div class="bg-white/10 backdrop-blur-md rounded-xl p-6 shadow-2xl max-w-md w-full"> | |
| <div class="flex items-center justify-between mb-6"> | |
| <div> | |
| <h2 class="text-2xl font-bold">Game Stats</h2> | |
| <p class="text-indigo-200">Complete the puzzle!</p> | |
| </div> | |
| <div class="bg-indigo-600 rounded-full p-3"> | |
| <i data-feather="clock" class="w-6 h-6"></i> | |
| </div> | |
| </div> | |
| <div class="space-y-4"> | |
| <div class="flex justify-between items-center bg-white/5 rounded-lg p-4"> | |
| <div> | |
| <p class="text-indigo-200">Moves</p> | |
| <p class="text-2xl font-bold" id="move-counter">0</p> | |
| </div> | |
| <i data-feather="repeat" class="w-6 h-6 text-indigo-300"></i> | |
| </div> | |
| <div class="flex justify-between items-center bg-white/5 rounded-lg p-4"> | |
| <div> | |
| <p class="text-indigo-200">Time</p> | |
| <p class="text-2xl font-bold" id="time-counter">00:00</p> | |
| </div> | |
| <i data-feather="clock" class="w-6 h-6 text-indigo-300"></i> | |
| </div> | |
| <div class="flex justify-between items-center bg-white/5 rounded-lg p-4"> | |
| <div> | |
| <p class="text-indigo-200">Difficulty</p> | |
| <p class="text-2xl font-bold" id="difficulty">Easy</p> | |
| </div> | |
| <i data-feather="sliders" class="w-6 h-6 text-indigo-300"></i> | |
| </div> | |
| </div> | |
| <div class="mt-6 space-y-3"> | |
| <button id="shuffle-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded-lg flex items-center justify-center gap-2 transition-colors"> | |
| <i data-feather="refresh-cw" class="w-5 h-5"></i> | |
| <span>Shuffle</span> | |
| </button> | |
| <button id="new-game-btn" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-4 rounded-lg flex items-center justify-center gap-2 transition-colors"> | |
| <i data-feather="plus-circle" class="w-5 h-5"></i> | |
| <span>New Game</span> | |
| </button> | |
| <div class="grid grid-cols-2 gap-3"> | |
| <button id="hint-btn" class="bg-amber-600 hover:bg-amber-700 text-white font-bold py-3 px-4 rounded-lg flex items-center justify-center gap-2 transition-colors"> | |
| <i data-feather="help-circle" class="w-5 h-5"></i> | |
| <span>Hint</span> | |
| </button> | |
| <button id="solve-btn" class="bg-emerald-600 hover:bg-emerald-700 text-white font-bold py-3 px-4 rounded-lg flex items-center justify-center gap-2 transition-colors"> | |
| <i data-feather="check-circle" class="w-5 h-5"></i> | |
| <span>Solve</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-12 text-center"> | |
| <h2 class="text-2xl font-bold mb-4">How to Play</h2> | |
| <div class="grid md:grid-cols-3 gap-6 max-w-4xl mx-auto"> | |
| <div class="bg-white/5 rounded-xl p-6"> | |
| <div class="bg-indigo-600 rounded-full w-12 h-12 flex items-center justify-center mb-4 mx-auto"> | |
| <i data-feather="move" class="w-6 h-6"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold mb-2">Slide Tiles</h3> | |
| <p class="text-indigo-200">Click or tap adjacent tiles to swap them with the empty space.</p> | |
| </div> | |
| <div class="bg-white/5 rounded-xl p-6"> | |
| <div class="bg-indigo-600 rounded-full w-12 h-12 flex items-center justify-center mb-4 mx-auto"> | |
| <i data-feather="target" class="w-6 h-6"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold mb-2">Complete the Pattern</h3> | |
| <p class="text-indigo-200">Arrange all tiles to form the correct zigzag pattern.</p> | |
| </div> | |
| <div class="bg-white/5 rounded-xl p-6"> | |
| <div class="bg-indigo-600 rounded-full w-12 h-12 flex items-center justify-center mb-4 mx-auto"> | |
| <i data-feather="award" class="w-6 h-6"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold mb-2">Beat Your Score</h3> | |
| <p class="text-indigo-200">Complete the puzzle in as few moves and as quickly as possible.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="win-modal" class="fixed inset-0 bg-black/80 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-gradient-to-br from-indigo-600 to-purple-700 rounded-2xl p-8 max-w-md w-full mx-4 text-center transform transition-all duration-500 scale-95 opacity-0"> | |
| <div class="bg-white/20 rounded-full w-20 h-20 flex items-center justify-center mb-6 mx-auto pulse-animation"> | |
| <i data-feather="award" class="w-10 h-10 text-white"></i> | |
| </div> | |
| <h2 class="text-3xl font-bold mb-2">Puzzle Solved!</h2> | |
| <p class="text-indigo-100 mb-6">Congratulations on completing the puzzle!</p> | |
| <div class="bg-white/10 rounded-xl p-4 mb-6"> | |
| <div class="grid grid-cols-2 gap-4"> | |
| <div> | |
| <p class="text-indigo-200">Moves</p> | |
| <p class="text-2xl font-bold" id="final-moves">0</p> | |
| </div> | |
| <div> | |
| <p class="text-indigo-200">Time</p> | |
| <p class="text-2xl font-bold" id="final-time">00:00</p> | |
| </div> | |
| </div> | |
| </div> | |
| <button id="play-again-btn" class="w-full bg-white text-indigo-900 hover:bg-indigo-100 font-bold py-3 px-4 rounded-lg flex items-center justify-center gap-2 transition-colors"> | |
| <i data-feather="play" class="w-5 h-5"></i> | |
| <span>Play Again</span> | |
| </button> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| feather.replace(); | |
| // Game state | |
| const state = { | |
| board: [], | |
| size: 4, | |
| emptyPos: { row: 3, col: 3 }, | |
| moves: 0, | |
| time: 0, | |
| timer: null, | |
| isPlaying: false, | |
| difficulty: 'easy' | |
| }; | |
| // DOM elements | |
| const puzzleContainer = document.getElementById('puzzle-container'); | |
| const moveCounter = document.getElementById('move-counter'); | |
| const timeCounter = document.getElementById('time-counter'); | |
| const difficultyDisplay = document.getElementById('difficulty'); | |
| const shuffleBtn = document.getElementById('shuffle-btn'); | |
| const newGameBtn = document.getElementById('new-game-btn'); | |
| const hintBtn = document.getElementById('hint-btn'); | |
| const solveBtn = document.getElementById('solve-btn'); | |
| const winModal = document.getElementById('win-modal'); | |
| const finalMoves = document.getElementById('final-moves'); | |
| const finalTime = document.getElementById('final-time'); | |
| const playAgainBtn = document.getElementById('play-again-btn'); | |
| // Initialize the game | |
| function initGame() { | |
| createBoard(); | |
| renderBoard(); | |
| setupEventListeners(); | |
| } | |
| // Create the board | |
| function createBoard() { | |
| state.board = []; | |
| let counter = 1; | |
| for (let i = 0; i < state.size; i++) { | |
| state.board[i] = []; | |
| for (let j = 0; j < state.size; j++) { | |
| if (i === state.size - 1 && j === state.size - 1) { | |
| state.board[i][j] = 0; // Empty tile | |
| } else { | |
| state.board[i][j] = counter++; | |
| } | |
| } | |
| } | |
| // Set empty position | |
| state.emptyPos = { row: state.size - 1, col: state.size - 1 }; | |
| state.moves = 0; | |
| moveCounter.textContent = state.moves; | |
| // Reset timer | |
| clearInterval(state.timer); | |
| state.time = 0; | |
| updateTimeDisplay(); | |
| } | |
| // Render the board | |
| function renderBoard() { | |
| puzzleContainer.innerHTML = ''; | |
| puzzleContainer.style.gridTemplateColumns = `repeat(${state.size}, 1fr)`; | |
| for (let i = 0; i < state.size; i++) { | |
| for (let j = 0; j < state.size; j++) { | |
| const value = state.board[i][j]; | |
| const tile = document.createElement('div'); | |
| if (value === 0) { | |
| tile.className = 'tile bg-transparent'; | |
| } else { | |
| tile.className = `tile bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center text-white font-bold text-xl md:text-2xl rounded-lg cursor-pointer hover:shadow-lg transition-all duration-200 h-16 md:h-20`; | |
| tile.textContent = value; | |
| // Zigzag pattern colors | |
| if ((i + j) % 2 === 0) { | |
| tile.classList.add('from-indigo-500', 'to-purple-600'); | |
| } else { | |
| tile.classList.add('from-purple-500', 'to-indigo-600'); | |
| } | |
| tile.dataset.row = i; | |
| tile.dataset.col = j; | |
| } | |
| puzzleContainer.appendChild(tile); | |
| } | |
| } | |
| } | |
| // Shuffle the board | |
| function shuffleBoard() { | |
| let shuffleCount = 0; | |
| const maxShuffles = 100; | |
| const shuffleInterval = setInterval(() => { | |
| if (shuffleCount >= maxShuffles) { | |
| clearInterval(shuffleInterval); | |
| state.isPlaying = true; | |
| startTimer(); | |
| return; | |
| } | |
| const directions = []; | |
| const { row, col } = state.emptyPos; | |
| if (row > 0) directions.push('up'); | |
| if (row < state.size - 1) directions.push('down'); | |
| if (col > 0) directions.push('left'); | |
| if (col < state.size - 1) directions.push('right'); | |
| const randomDir = directions[Math.floor(Math.random() * directions.length)]; | |
| moveTile(randomDir, false); | |
| shuffleCount++; | |
| }, 50); | |
| } | |
| // Move tile | |
| function moveTile(direction, countMove = true) { | |
| const { row, col } = state.emptyPos; | |
| let newRow = row; | |
| let newCol = col; | |
| switch (direction) { | |
| case 'up': | |
| if (row === 0) return false; | |
| newRow = row - 1; | |
| break; | |
| case 'down': | |
| if (row === state.size - 1) return false; | |
| newRow = row + 1; | |
| break; | |
| case 'left': | |
| if (col === 0) return false; | |
| newCol = col - 1; | |
| break; | |
| case 'right': | |
| if (col === state.size - 1) return false; | |
| newCol = col + 1; | |
| break; | |
| default: | |
| return false; | |
| } | |
| // Swap tiles | |
| state.board[row][col] = state.board[newRow][newCol]; | |
| state.board[newRow][newCol] = 0; | |
| state.emptyPos = { row: newRow, col: newCol }; | |
| if (countMove) { | |
| state.moves++; | |
| moveCounter.textContent = state.moves; | |
| } | |
| renderBoard(); | |
| // Check if puzzle is solved | |
| if (isSolved()) { | |
| clearInterval(state.timer); | |
| showWinModal(); | |
| } | |
| return true; | |
| } | |
| // Check if puzzle is solved | |
| function isSolved() { | |
| let counter = 1; | |
| for (let i = 0; i < state.size; i++) { | |
| for (let j = 0; j < state.size; j++) { | |
| if (i === state.size - 1 && j === state.size - 1) { | |
| if (state.board[i][j] !== 0) return false; | |
| } else { | |
| if (state.board[i][j] !== counter++) return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| // Timer functions | |
| function startTimer() { | |
| clearInterval(state.timer); | |
| state.time = 0; | |
| updateTimeDisplay(); | |
| state.timer = setInterval(() => { | |
| state.time++; | |
| updateTimeDisplay(); | |
| }, 1000); | |
| } | |
| function updateTimeDisplay() { | |
| const minutes = Math.floor(state.time / 60).toString().padStart(2, '0'); | |
| const seconds = (state.time % 60).toString().padStart(2, '0'); | |
| timeCounter.textContent = `${minutes}:${seconds}`; | |
| } | |
| // Show win modal | |
| function showWinModal() { | |
| finalMoves.textContent = state.moves; | |
| finalTime.textContent = timeCounter.textContent; | |
| const modalContent = winModal.querySelector('div'); | |
| modalContent.classList.remove('scale-95', 'opacity-0'); | |
| modalContent.classList.add('scale-100', 'opacity-100'); | |
| winModal.classList.remove('hidden'); | |
| // Add confetti effect | |
| for (let i = 0; i < 50; i++) { | |
| const confetti = document.createElement('div'); | |
| confetti.className = 'absolute w-2 h-2 rounded-full bg-yellow-400 win-effect'; | |
| confetti.style.left = `${Math.random() * 100}%`; | |
| confetti.style.top = `${Math.random() * 100}%`; | |
| confetti.style.animationDelay = `${Math.random() * 0.5}s`; | |
| winModal.appendChild(confetti); | |
| setTimeout(() => { | |
| confetti.remove(); | |
| }, 3000); | |
| } | |
| } | |
| // Setup event listeners | |
| function setupEventListeners() { | |
| // Tile click | |
| puzzleContainer.addEventListener('click', (e) => { | |
| if (!state.isPlaying) return; | |
| const tile = e.target.closest('.tile'); | |
| if (!tile || tile.textContent === '') return; | |
| const row = parseInt(tile.dataset.row); | |
| const col = parseInt(tile.dataset.col); | |
| const emptyRow = state.emptyPos.row; | |
| const emptyCol = state.emptyPos.col; | |
| // Check if tile is adjacent to empty space | |
| if ((Math.abs(row - emptyRow) === 1 && col === emptyCol) || | |
| (Math.abs(col - emptyCol) === 1 && row === emptyRow)) { | |
| // Determine direction | |
| let direction; | |
| if (row < emptyRow) direction = 'up'; | |
| else if (row > emptyRow) direction = 'down'; | |
| else if (col < emptyCol) direction = 'left'; | |
| else direction = 'right'; | |
| moveTile(direction); | |
| } | |
| }); | |
| // Keyboard controls | |
| document.addEventListener('keydown', (e) => { | |
| if (!state.isPlaying) return; | |
| switch (e.key) { | |
| case 'ArrowUp': | |
| moveTile('down'); | |
| break; | |
| case 'ArrowDown': | |
| moveTile('up'); | |
| break; | |
| case 'ArrowLeft': | |
| moveTile('right'); | |
| break; | |
| case 'ArrowRight': | |
| moveTile('left'); | |
| break; | |
| } | |
| }); | |
| // Shuffle button | |
| shuffleBtn.addEventListener('click', () => { | |
| createBoard(); | |
| shuffleBoard(); | |
| }); | |
| // New game button | |
| newGameBtn.addEventListener('click', () => { | |
| createBoard(); | |
| shuffleBoard(); | |
| }); | |
| // Hint button | |
| hintBtn.addEventListener('click', () => { | |
| if (!state.isPlaying) return; | |
| // Simple hint: highlight possible moves | |
| const { row, col } = state.emptyPos; | |
| const tiles = puzzleContainer.querySelectorAll('.tile'); | |
| tiles.forEach(tile => { | |
| if (tile.textContent === '') return; | |
| const tileRow = parseInt(tile.dataset.row); | |
| const tileCol = parseInt(tile.dataset.col); | |
| if ((Math.abs(tileRow - row) === 1 && tileCol === col) || | |
| (Math.abs(tileCol - col) === 1 && tileRow === row)) { | |
| tile.classList.add('ring-4', 'ring-yellow-400'); | |
| setTimeout(() => { | |
| tile.classList.remove('ring-4', 'ring-yellow-400'); | |
| }, 1000); | |
| } | |
| }); | |
| }); | |
| // Solve button | |
| solveBtn.addEventListener('click', () => { | |
| createBoard(); | |
| state.isPlaying = false; | |
| clearInterval(state.timer); | |
| }); | |
| // Play again button | |
| playAgainBtn.addEventListener('click', () => { | |
| winModal.classList.add('hidden'); | |
| createBoard(); | |
| shuffleBoard(); | |
| }); | |
| } | |
| // Initialize the game | |
| initGame(); | |
| }); | |
| </script> | |
| <script>feather.replace();</script> | |
| </body> | |
| </html> | |