Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>🍎 Apple Merge</title> | |
<style> | |
body { | |
font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
min-height: 100vh; | |
margin: 0; | |
background-color: #f0f0f0; | |
overflow: hidden; | |
user-select: none; | |
} | |
#header { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
width: 90vw; | |
max-width: 90vh; | |
padding: 10px; | |
margin-bottom: 10px; | |
} | |
#score-display { | |
font-size: 1.75rem; | |
font-weight: 600; | |
color: #333; | |
margin: 0; | |
} | |
#combinable-pairs { | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
font-size: 1.25rem; | |
font-weight: 600; | |
color: #555; | |
z-index: 20; | |
} | |
#game-container { | |
position: relative; | |
width: 90vw; | |
height: 90vw; | |
max-width: 90vh; | |
max-height: 90vh; | |
background-color: #a7d9ed; | |
border: 2px solid #333; | |
border-radius: 15px; | |
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); | |
touch-action: none; | |
display: grid; | |
grid-template-columns: repeat(10, 1fr); | |
grid-template-rows: repeat(10, 1fr); | |
gap: 3px; | |
} | |
.grid-cell { | |
width: 100%; | |
height: 100%; | |
box-sizing: border-box; | |
position: relative; | |
border-radius: 8px; | |
} | |
#selection-box { | |
position: absolute; | |
border: 2px dashed blue; | |
background-color: rgba(0, 0, 255, 0.2); | |
display: none; | |
z-index: 10; | |
pointer-events: none; | |
border-radius: 7px; | |
} | |
.apple { | |
width: 85%; | |
height: 85%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
transition: transform 0.1s ease-out, filter 0.1s ease-out; | |
background-color: rgba(255, 255, 255, 0.8); | |
border-radius: 50%; | |
box-sizing: border-box; | |
font-size: calc(2em + 1.5vw); | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
z-index: 2; | |
box-shadow: 0 1px 3px rgba(0,0,0,0.1); | |
} | |
.apple span { | |
font-size: 0.5em; | |
color: #000; | |
font-weight: bold; | |
z-index: 3; | |
pointer-events: none; | |
user-select: none; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
text-shadow: 0px 1px 3px #fff; | |
} | |
.apple.selected { | |
transform: translate(-50%, -50%) scale(1.1); | |
filter: brightness(1.2); | |
z-index: 4; | |
} | |
.apple.removing { | |
animation: confettiExplode 0.6s ease-out forwards; | |
} | |
@keyframes confettiExplode { | |
0% { opacity: 1; transform: scale(1) rotate(0); } | |
100% { | |
opacity: 0; | |
transform: scale(var(--random-scale)) | |
translateX(calc(var(--random-x) * 40px)) | |
translateY(calc(var(--random-y) * 40px)) | |
rotate(calc(var(--random-rotate) * 360deg)); | |
} | |
} | |
#game-over-screen { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0, 0, 0, 0.8); | |
display: none; /* Initially hidden */ | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
color: white; | |
font-size: 42px; | |
z-index: 30; | |
border-radius: 15px; | |
} | |
#game-over-screen button { | |
margin-top: 25px; | |
padding: 12px 24px; | |
font-size: 24px; | |
cursor: pointer; | |
background-color: #007aff; /* Apple blue */ | |
color: white; | |
border: none; | |
border-radius: 8px; | |
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); | |
transition: background-color: 0.2s ease; | |
} | |
#game-over-screen button:hover { | |
background-color: #0062cc; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="header"> | |
<div id="score-display">Score: 0</div> | |
</div> | |
<div id="combinable-pairs">Pairs: 0</div> | |
<div id="game-container"> | |
<div id="selection-box"></div> | |
<div id="game-over-screen"> | |
<div>No More Moves!</div> | |
<button onclick="restartGame()">Restart</button> | |
</div> | |
</div> | |
<script> | |
const gameContainer = document.getElementById('game-container'); | |
const selectionBox = document.getElementById('selection-box'); | |
const scoreDisplay = document.getElementById('score-display'); | |
const gameOverScreen = document.getElementById('game-over-screen'); | |
const combinablePairsDisplay = document.getElementById('combinable-pairs'); | |
const gridSize = 10; | |
let startCell = null; | |
let isSelecting = false; | |
let selectedApples = []; | |
let score = 0; | |
let isMouseDown = false; | |
let lastTouchEnd = 0; | |
// Helper function to get relative coordinates, handling both mouse and touch events. | |
function getRelativeCoordinates(event, element) { | |
const rect = element.getBoundingClientRect(); | |
const clientX = event.clientX || (event.touches && event.touches[0].clientX); | |
const clientY = event.clientY || (event.touches && event.touches[0].clientY); | |
if (clientX === undefined || clientY === undefined) { | |
return null; | |
} | |
return { x: clientX - rect.left, y: clientY - rect.top }; | |
} | |
// Helper function to get the row and column of a cell from coordinates. | |
function getCellFromCoordinates(coords) { | |
if (!coords) return null; | |
const cellSize = gameContainer.offsetWidth / gridSize; | |
const col = Math.floor(coords.x / cellSize); | |
const row = Math.floor(coords.y / cellSize); | |
if (row >= 0 && row < gridSize && col >= 0 && col < gridSize) { | |
return { row, col }; | |
} | |
return null; | |
} | |
// Creates an apple with a random number (1-9). | |
function createApple(row, col) { | |
const apple = document.createElement('div'); | |
apple.classList.add('apple'); | |
const randomNumber = Math.floor(Math.random() * 9) + 1; | |
apple.innerHTML = `🍎<span>${randomNumber}</span>`; | |
apple.dataset.value = randomNumber; | |
apple.dataset.row = row; | |
apple.dataset.col = col; | |
return apple; | |
} | |
// Populates the grid with apples. | |
function populateGrid() { | |
for (let row = 0; row < gridSize; row++) { | |
for (let col = 0; col < gridSize; col++) { | |
const cell = document.createElement('div'); | |
cell.classList.add('grid-cell'); | |
cell.dataset.row = row; | |
cell.dataset.col = col; | |
cell.addEventListener('touchstart', preventZoom, { passive: false }); // Prevent zoom on double-tap | |
const apple = createApple(row, col); | |
cell.appendChild(apple); | |
gameContainer.appendChild(cell); | |
} | |
} | |
updateCombinablePairs(); | |
} | |
// Prevent page zoom on touch devices | |
function preventZoom(e) { | |
if (e.touches && e.touches.length > 1) { | |
e.preventDefault(); return; | |
} | |
if (e.timeStamp - lastTouchEnd < 300) { e.preventDefault(); } | |
} | |
// Handles the start of a selection (mousedown or touchstart). | |
function handleSelectionStart(event) { | |
if (event.type === 'touchend') { lastTouchEnd = event.timeStamp; } | |
const coords = getRelativeCoordinates(event, gameContainer); | |
const cell = getCellFromCoordinates(coords); | |
if (!cell) return; | |
// Ensure we're starting on an apple. | |
const elementAtPoint = document.elementFromPoint( | |
event.clientX || (event.touches && event.touches[0].clientX), | |
event.clientY || (event.touches && event.touches[0].clientY) | |
); | |
if (!elementAtPoint || !elementAtPoint.classList.contains('apple')) { | |
return; | |
} | |
isMouseDown = true; | |
startCell = cell; | |
isSelecting = true; | |
deselectAllApples(); | |
// Set initial size and position of the selection box. | |
const cellSize = gameContainer.offsetWidth / gridSize; | |
selectionBox.style.left = `${startCell.col * cellSize}px`; | |
selectionBox.style.top = `${startCell.row * cellSize}px`; | |
selectionBox.style.width = `${cellSize}px`; | |
selectionBox.style.height = `${cellSize}px`; | |
selectionBox.style.display = 'block'; | |
selectApplesInBox(); | |
event.preventDefault(); // Prevent other events like scrolling. | |
} | |
// Handles mouse/touch movement during selection. | |
function handleSelectionMove(event) { | |
if (!isSelecting || !isMouseDown) return; | |
const coords = getRelativeCoordinates(event, gameContainer); | |
if (!coords) return; | |
const currentCell = getCellFromCoordinates(coords); | |
if (!currentCell) return; | |
const cellSize = gameContainer.offsetWidth / gridSize; | |
// Calculate top-left and bottom-right corners of the selection box. | |
const top = Math.min(startCell.row, currentCell.row); | |
const left = Math.min(startCell.col, currentCell.col); | |
const bottom = Math.max(startCell.row, currentCell.row); | |
const right = Math.max(startCell.col, currentCell.col); | |
// Update selection box size and position. | |
selectionBox.style.top = `${top * cellSize}px`; | |
selectionBox.style.left = `${left * cellSize}px`; | |
selectionBox.style.width = `${(right - left + 1) * cellSize}px`; | |
selectionBox.style.height = `${(bottom - top + 1) * cellSize}px`; | |
selectApplesInBox(); | |
event.preventDefault(); | |
} | |
// Handles the end of a selection (mouseup or touchend). | |
function handleSelectionEnd(event) { | |
if (event.type === 'touchend') { lastTouchEnd = event.timeStamp; } | |
if (!isSelecting) return; | |
isMouseDown = false; | |
isSelecting = false; | |
selectionBox.style.display = 'none'; // Hide selection box. | |
checkSum(); // Check the sum of selected apples. | |
startCell = null; // Reset startCell. | |
event.preventDefault(); | |
} | |
function selectApplesInBox() { | |
const apples = document.querySelectorAll('.apple'); | |
const selectionRect = selectionBox.getBoundingClientRect(); // Selection box rect | |
apples.forEach(apple => { | |
const appleRect = apple.getBoundingClientRect(); | |
// Calculate the center of the apple. | |
const appleCenterX = appleRect.left + appleRect.width / 2; | |
const appleCenterY = appleRect.top + appleRect.height / 2; | |
// Check if the apple's center is within the selection box. | |
const isInside = appleCenterX >= selectionRect.left && | |
appleCenterX <= selectionRect.right && | |
appleCenterY >= selectionRect.top && | |
appleCenterY <= selectionRect.bottom; | |
if (isInside) { | |
if (!selectedApples.includes(apple)) { | |
apple.classList.add('selected'); | |
selectedApples.push(apple); | |
} | |
} else { | |
apple.classList.remove('selected'); | |
selectedApples = selectedApples.filter(a => a !== apple); | |
} | |
}); | |
} | |
// Deselects all apples. | |
function deselectAllApples() { | |
selectedApples.forEach(apple => apple.classList.remove('selected')); | |
selectedApples = []; | |
} | |
// Checks if the sum of selected apples is 10. If so, removes them and updates the score. | |
function checkSum() { | |
let sum = 0; | |
selectedApples.forEach(apple => sum += parseInt(apple.dataset.value)); | |
if (sum === 10) { | |
score += selectedApples.length; | |
scoreDisplay.textContent = `Score: ${score}`; | |
selectedApples.forEach(apple => { | |
apple.classList.add('removing'); | |
// Add random animation properties | |
apple.style.setProperty('--random-x', Math.random() * 2 - 1); // -1 to 1 | |
apple.style.setProperty('--random-y', Math.random() * 2 - 1); | |
apple.style.setProperty('--random-rotate', Math.random() * 2 - 1); | |
apple.style.setProperty('--random-scale', Math.random() * 1 + 0.5); // 0.5 to 1.5 | |
// Remove the apple after the animation finishes. | |
apple.addEventListener('animationend', () => { | |
const parentCell = apple.parentElement; | |
if (parentCell && parentCell.classList.contains('grid-cell')) { | |
parentCell.removeChild(apple); | |
updateCombinablePairs(); // Check for game over after removing apples. | |
} | |
}); | |
}); | |
selectedApples = []; // Clear the selection. | |
} else { | |
deselectAllApples(); // Deselect if the sum is not 10. | |
} | |
} | |
// Checks if there are any combinable pairs left on the board. | |
function hasCombinablePairs() { | |
const apples = document.querySelectorAll('.apple'); | |
const values = Array.from(apples).map(apple => parseInt(apple.dataset.value)); | |
for (let i = 0; i < values.length; i++) { | |
for (let j = i + 1; j < values.length; j++) { | |
if (values[i] + values[j] === 10) { | |
return true; // Found a pair that sums to 10. | |
} | |
} | |
} | |
return false; // No pairs found. | |
} | |
// Updates the display of combinable pairs and checks for game over. | |
function updateCombinablePairs() { | |
const apples = document.querySelectorAll('.apple'); | |
const values = Array.from(apples).map(apple => parseInt(apple.dataset.value)); | |
let count = 0; | |
for (let i = 0; i < values.length; i++) { | |
for (let j = i + 1; j < values.length; j++) { | |
if (values[i] + values[j] === 10) { | |
count++; | |
} | |
} | |
} | |
combinablePairsDisplay.textContent = `Pairs: ${count}`; | |
// Check for game over (no apples left or no combinable pairs). | |
if (count === 0 && apples.length > 0) { | |
gameOverScreen.style.display = "flex"; | |
} | |
} | |
// Starts a new game. | |
function startGame() { | |
populateGrid(); | |
} | |
// Restarts the game. | |
function restartGame() { | |
score = 0; | |
scoreDisplay.textContent = "Score: 0"; | |
// Clear existing apples. | |
while (gameContainer.firstChild) { | |
gameContainer.removeChild(gameContainer.firstChild); | |
} | |
gameOverScreen.style.display = "none"; | |
startGame(); | |
} | |
// Event listeners for mouse and touch events. | |
gameContainer.addEventListener('mousedown', handleSelectionStart); | |
gameContainer.addEventListener('mousemove', handleSelectionMove); | |
gameContainer.addEventListener('mouseup', handleSelectionEnd); | |
gameContainer.addEventListener('touchstart', handleSelectionStart); | |
gameContainer.addEventListener('touchmove', handleSelectionMove); | |
gameContainer.addEventListener('touchend', handleSelectionEnd); | |
// Start the game when the page loads. | |
startGame(); | |
</script> | |
</body> | |
</html> |