|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Sliding Puzzle Game</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> |
|
.tile { |
|
transition: all 0.3s ease; |
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2); |
|
} |
|
.tile:hover { |
|
transform: scale(1.02); |
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); |
|
} |
|
.win-message { |
|
animation: fadeIn 0.5s ease-in-out; |
|
} |
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(20px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
.puzzle-container { |
|
perspective: 1000px; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gradient-to-br from-blue-50 to-purple-100 min-h-screen flex flex-col items-center justify-center p-4"> |
|
<div class="max-w-4xl w-full"> |
|
<h1 class="text-4xl font-bold text-center text-purple-800 mb-2">Sliding Puzzle Challenge</h1> |
|
<p class="text-center text-gray-600 mb-8">Slide the tiles to reconstruct the image. Click on adjacent tiles to swap with the empty space.</p> |
|
|
|
<div class="flex flex-col md:flex-row gap-8 items-center justify-center"> |
|
<div class="puzzle-container bg-white rounded-xl p-4 shadow-xl"> |
|
<div class="grid grid-cols-3 gap-2 mb-4" id="puzzle-grid"> |
|
|
|
</div> |
|
|
|
<div class="flex justify-between items-center mt-4"> |
|
<div class="text-lg font-semibold text-purple-700"> |
|
Moves: <span id="move-counter">0</span> |
|
</div> |
|
<div class="flex gap-2"> |
|
<button id="shuffle-btn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg transition flex items-center gap-2"> |
|
<i class="fas fa-random"></i> Shuffle |
|
</button> |
|
<button id="new-game-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition flex items-center gap-2"> |
|
<i class="fas fa-redo"></i> New Game |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="bg-white rounded-xl p-6 shadow-xl max-w-xs w-full"> |
|
<h2 class="text-xl font-bold text-purple-800 mb-4">How to Play</h2> |
|
<ul class="space-y-3 text-gray-700"> |
|
<li class="flex items-start gap-2"> |
|
<i class="fas fa-arrow-right text-purple-500 mt-1"></i> |
|
<span>Click on a tile adjacent to the empty space to move it</span> |
|
</li> |
|
<li class="flex items-start gap-2"> |
|
<i class="fas fa-arrow-right text-purple-500 mt-1"></i> |
|
<span>Rearrange all tiles to complete the image</span> |
|
</li> |
|
<li class="flex items-start gap-2"> |
|
<i class="fas fa-arrow-right text-purple-500 mt-1"></i> |
|
<span>Try to solve it in the fewest moves possible</span> |
|
</li> |
|
</ul> |
|
|
|
<div class="mt-6"> |
|
<h3 class="font-semibold text-gray-800 mb-2">Completed Image:</h3> |
|
<img src="https://source.unsplash.com/random/300x300/?nature" alt="Target image" class="rounded-lg border border-gray-200 w-full" id="target-image"> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="win-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden"> |
|
<div class="bg-white rounded-xl p-8 max-w-md w-full mx-4 win-message"> |
|
<div class="text-center"> |
|
<i class="fas fa-trophy text-6xl text-yellow-500 mb-4"></i> |
|
<h2 class="text-3xl font-bold text-purple-800 mb-2">Congratulations!</h2> |
|
<p class="text-gray-600 mb-6">You solved the puzzle in <span id="final-moves" class="font-bold">0</span> moves!</p> |
|
<button id="play-again-btn" class="bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-lg text-lg font-medium w-full"> |
|
Play Again |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
const gridSize = 3; |
|
let puzzleState = []; |
|
let emptyPos = { row: gridSize - 1, col: gridSize - 1 }; |
|
let moveCount = 0; |
|
let isGameWon = false; |
|
|
|
|
|
const puzzleGrid = document.getElementById('puzzle-grid'); |
|
const moveCounter = document.getElementById('move-counter'); |
|
const shuffleBtn = document.getElementById('shuffle-btn'); |
|
const newGameBtn = document.getElementById('new-game-btn'); |
|
const winModal = document.getElementById('win-modal'); |
|
const finalMoves = document.getElementById('final-moves'); |
|
const playAgainBtn = document.getElementById('play-again-btn'); |
|
|
|
|
|
initGame(); |
|
|
|
|
|
shuffleBtn.addEventListener('click', shufflePuzzle); |
|
newGameBtn.addEventListener('click', initGame); |
|
playAgainBtn.addEventListener('click', initGame); |
|
|
|
|
|
function initGame() { |
|
|
|
moveCount = 0; |
|
isGameWon = false; |
|
moveCounter.textContent = moveCount; |
|
winModal.classList.add('hidden'); |
|
|
|
|
|
puzzleState = []; |
|
for (let i = 0; i < gridSize * gridSize - 1; i++) { |
|
puzzleState.push(i + 1); |
|
} |
|
puzzleState.push(0); |
|
|
|
|
|
shufflePuzzle(); |
|
|
|
|
|
renderPuzzle(); |
|
} |
|
|
|
|
|
function shufflePuzzle() { |
|
if (isGameWon) { |
|
isGameWon = false; |
|
winModal.classList.add('hidden'); |
|
} |
|
|
|
|
|
moveCount = 0; |
|
moveCounter.textContent = moveCount; |
|
|
|
|
|
for (let i = puzzleState.length - 1; i > 0; i--) { |
|
const j = Math.floor(Math.random() * (i + 1)); |
|
[puzzleState[i], puzzleState[j]] = [puzzleState[j], puzzleState[i]]; |
|
} |
|
|
|
|
|
const emptyIndex = puzzleState.indexOf(0); |
|
emptyPos = { |
|
row: Math.floor(emptyIndex / gridSize), |
|
col: emptyIndex % gridSize |
|
}; |
|
|
|
|
|
if (!isSolvable()) { |
|
|
|
if (puzzleState[0] === 0 || puzzleState[1] === 0) { |
|
[puzzleState[2], puzzleState[3]] = [puzzleState[3], puzzleState[2]]; |
|
} else { |
|
[puzzleState[0], puzzleState[1]] = [puzzleState[1], puzzleState[0]]; |
|
} |
|
} |
|
|
|
renderPuzzle(); |
|
} |
|
|
|
|
|
function isSolvable() { |
|
let inversions = 0; |
|
const flattened = puzzleState.filter(num => num !== 0); |
|
|
|
for (let i = 0; i < flattened.length - 1; i++) { |
|
for (let j = i + 1; j < flattened.length; j++) { |
|
if (flattened[i] > flattened[j]) { |
|
inversions++; |
|
} |
|
} |
|
} |
|
|
|
|
|
return inversions % 2 === 0; |
|
} |
|
|
|
|
|
function renderPuzzle() { |
|
puzzleGrid.innerHTML = ''; |
|
|
|
for (let row = 0; row < gridSize; row++) { |
|
for (let col = 0; col < gridSize; col++) { |
|
const index = row * gridSize + col; |
|
const tileValue = puzzleState[index]; |
|
|
|
if (tileValue === 0) { |
|
|
|
const emptyTile = document.createElement('div'); |
|
emptyTile.className = 'bg-gray-200 rounded-lg aspect-square flex items-center justify-center opacity-70'; |
|
emptyTile.dataset.row = row; |
|
emptyTile.dataset.col = col; |
|
puzzleGrid.appendChild(emptyTile); |
|
} else { |
|
|
|
const tile = document.createElement('div'); |
|
tile.className = 'tile bg-gradient-to-br from-purple-100 to-blue-100 rounded-lg aspect-square flex items-center justify-center text-2xl font-bold text-purple-800 cursor-pointer'; |
|
tile.textContent = tileValue; |
|
tile.dataset.row = row; |
|
tile.dataset.col = col; |
|
tile.dataset.value = tileValue; |
|
|
|
|
|
tile.style.backgroundImage = `url(https://source.unsplash.com/random/300x300/?nature)`; |
|
tile.style.backgroundSize = `${gridSize * 100}% ${gridSize * 100}%`; |
|
|
|
|
|
const targetRow = Math.floor((tileValue - 1) / gridSize); |
|
const targetCol = (tileValue - 1) % gridSize; |
|
const xPos = (targetCol / (gridSize - 1)) * 100; |
|
const yPos = (targetRow / (gridSize - 1)) * 100; |
|
tile.style.backgroundPosition = `${xPos}% ${yPos}%`; |
|
|
|
tile.addEventListener('click', () => handleTileClick(row, col)); |
|
puzzleGrid.appendChild(tile); |
|
} |
|
} |
|
} |
|
|
|
|
|
const emptyIndex = puzzleState.indexOf(0); |
|
emptyPos = { |
|
row: Math.floor(emptyIndex / gridSize), |
|
col: emptyIndex % gridSize |
|
}; |
|
} |
|
|
|
|
|
function handleTileClick(row, col) { |
|
if (isGameWon) return; |
|
|
|
|
|
const isAdjacent = |
|
(Math.abs(row - emptyPos.row) === 1 && col === emptyPos.col) || |
|
(Math.abs(col - emptyPos.col) === 1 && row === emptyPos.row); |
|
|
|
if (isAdjacent) { |
|
|
|
const clickedIndex = row * gridSize + col; |
|
const emptyIndex = emptyPos.row * gridSize + emptyPos.col; |
|
|
|
|
|
[puzzleState[clickedIndex], puzzleState[emptyIndex]] = |
|
[puzzleState[emptyIndex], puzzleState[clickedIndex]]; |
|
|
|
|
|
moveCount++; |
|
moveCounter.textContent = moveCount; |
|
|
|
|
|
renderPuzzle(); |
|
|
|
|
|
checkWinCondition(); |
|
} |
|
} |
|
|
|
|
|
function checkWinCondition() { |
|
for (let i = 0; i < puzzleState.length - 1; i++) { |
|
if (puzzleState[i] !== i + 1) { |
|
return false; |
|
} |
|
} |
|
|
|
if (puzzleState[puzzleState.length - 1] === 0) { |
|
|
|
isGameWon = true; |
|
finalMoves.textContent = moveCount; |
|
winModal.classList.remove('hidden'); |
|
|
|
|
|
const confettiSettings = { target: 'confetti-canvas', max: 150 }; |
|
const confetti = new ConfettiGenerator(confettiSettings); |
|
confetti.render(); |
|
|
|
setTimeout(() => { |
|
confetti.clear(); |
|
}, 3000); |
|
} |
|
} |
|
}); |
|
</script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/index.min.js"></script> |
|
<canvas id="confetti-canvas" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1000;"></canvas> |
|
<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=madansa7/sliding-puzzle-challenge-game" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |