apple-game / index.html
kimhyunwoo's picture
Update index.html
37913c3 verified
<!DOCTYPE html>
<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>