document.addEventListener('DOMContentLoaded', () => { const gameBoard = document.getElementById('game-board'); const scoreDisplay = document.getElementById('score'); const boardSize = 8; // 8x8 grid const candies = []; const candyColors = ['red', 'blue', 'green', 'yellow', 'purple', 'orange']; let score = 0; let selectedCandy = null; let targetCandy = null; // --- Game Initialization --- function createBoard() { gameBoard.style.gridTemplateColumns = `repeat(${boardSize}, 1fr)`; gameBoard.innerHTML = ''; // Clear existing board for (let i = 0; i < boardSize * boardSize; i++) { const candy = document.createElement('div'); candy.classList.add('candy'); candy.setAttribute('data-id', i); let randomColor = Math.floor(Math.random() * candyColors.length); candy.classList.add(candyColors[randomColor]); gameBoard.appendChild(candy); candies.push(candy); } // Ensure no immediate matches on board creation checkAllMatches(true); } // --- Candy Swapping --- function setupEventListeners() { gameBoard.addEventListener('mousedown', handleMouseDown); gameBoard.addEventListener('mouseup', handleMouseUp); gameBoard.addEventListener('touchstart', handleTouchStart, { passive: true }); gameBoard.addEventListener('touchend', handleTouchEnd, { passive: true }); } function handleMouseDown(e) { if (e.target.classList.contains('candy')) { selectedCandy = e.target; } } function handleMouseUp(e) { if (selectedCandy && e.target.classList.contains('candy') && selectedCandy !== e.target) { targetCandy = e.target; swapCandies(); } selectedCandy = null; targetCandy = null; } function handleTouchStart(e) { if (e.target.classList.contains('candy')) { selectedCandy = e.target; } } function handleTouchEnd(e) { // Find the candy element at the touch end coordinates const touch = e.changedTouches[0]; const targetElement = document.elementFromPoint(touch.clientX, touch.clientY); if (selectedCandy && targetElement && targetElement.classList.contains('candy') && selectedCandy !== targetElement) { targetCandy = targetElement; swapCandies(); } selectedCandy = null; targetCandy = null; } function swapCandies() { if (!selectedCandy || !targetCandy) return; const selectedId = parseInt(selectedCandy.dataset.id); const targetId = parseInt(targetCandy.dataset.id); const rowOfSelected = Math.floor(selectedId / boardSize); const colOfSelected = selectedId % boardSize; const rowOfTarget = Math.floor(targetId / boardSize); const colOfTarget = targetId % boardSize; // Check if candies are adjacent (horizontally or vertically) const isAdjacent = ( (Math.abs(rowOfSelected - rowOfTarget) === 1 && colOfSelected === colOfTarget) || (Math.abs(colOfSelected - colOfTarget) === 1 && rowOfSelected === rowOfTarget) ); if (isAdjacent) { const tempClassList = Array.from(selectedCandy.classList).filter(c => c !== 'candy' && c !== 'removing'); selectedCandy.className = 'candy ' + Array.from(targetCandy.classList).filter(c => c !== 'candy' && c !== 'removing').join(' '); targetCandy.className = 'candy ' + tempClassList.join(' '); // Reassign classes based on new positions const selectedClass = selectedCandy.classList[1]; const targetClass = targetCandy.classList[1]; candies[selectedId].classList.remove(selectedClass); candies[selectedId].classList.add(targetClass); candies[targetId].classList.remove(targetClass); candies[targetId].classList.add(selectedClass); // Check for matches after swap setTimeout(() => { const matchesFound = checkAllMatches(); if (!matchesFound) { // If no match, swap back const tempClassListBack = Array.from(selectedCandy.classList).filter(c => c !== 'candy' && c !== 'removing'); selectedCandy.className = 'candy ' + Array.from(targetCandy.classList).filter(c => c !== 'candy' && c !== 'removing').join(' '); targetCandy.className = 'candy ' + tempClassListBack.join(' '); candies[selectedId].classList.remove(selectedClass); candies[selectedId].classList.add(targetClass); candies[targetId].classList.remove(targetClass); candies[targetId].classList.add(selectedClass); } }, 300); // Give a slight delay for visual feedback } } // --- Candy Matching Logic --- function checkAllMatches(initialCheck = false) { let matchesFound = false; // Horizontal Matches for (let r = 0; r < boardSize; r++) { for (let c = 0; c <= boardSize - 3; c++) { const index = r * boardSize + c; const candy1 = candies[index]; const candy2 = candies[index + 1]; const candy3 = candies[index + 2]; if (candy1.classList[1] && candy1.classList[1] === candy2.classList[1] && candy1.classList[1] === candy3.classList[1]) { const matchedCandies = [candy1, candy2, candy3]; let currentC = c + 3; while (currentC < boardSize && candies[r * boardSize + currentC].classList[1] === candy1.classList[1]) { matchedCandies.push(candies[r * boardSize + currentC]); currentC++; } if (matchedCandies.length >= 3) { if (!initialCheck) { removeCandies(matchedCandies); matchesFound = true; } else { // If initial check, regenerate candies to avoid immediate matches matchedCandies.forEach(candy => { let newColor = candyColors[Math.floor(Math.random() * candyColors.length)]; while (newColor === candy.classList[1]) { // Ensure new color is different newColor = candyColors[Math.floor(Math.random() * candyColors.length)]; } candy.classList.remove(candy.classList[1]); candy.classList.add(newColor); }); // Recursively check until no initial matches are found return checkAllMatches(true); } } } } } // Vertical Matches for (let c = 0; c < boardSize; c++) { for (let r = 0; r <= boardSize - 3; r++) { const index = r * boardSize + c; const candy1 = candies[index]; const candy2 = candies[index + boardSize]; const candy3 = candies[index + 2 * boardSize]; if (candy1.classList[1] && candy1.classList[1] === candy2.classList[1] && candy1.classList[1] === candy3.classList[1]) { const matchedCandies = [candy1, candy2, candy3]; let currentRow = r + 3; while (currentRow < boardSize && candies[currentRow * boardSize + c].classList[1] === candy1.classList[1]) { matchedCandies.push(candies[currentRow * boardSize + c]); currentRow++; } if (matchedCandies.length >= 3) { if (!initialCheck) { removeCandies(matchedCandies); matchesFound = true; } else { matchedCandies.forEach(candy => { let newColor = candyColors[Math.floor(Math.random() * candyColors.length)]; while (newColor === candy.classList[1]) { newColor = candyColors[Math.floor(Math.random() * candyColors.length)]; } candy.classList.remove(candy.classList[1]); candy.classList.add(newColor); }); return checkAllMatches(true); } } } } } return matchesFound; } // --- Candy Removal and Refilling --- function removeCandies(matchedCandies) { matchedCandies.forEach(candy => { candy.classList.add('removing'); }); // Update score score += matchedCandies.length * 10; scoreDisplay.textContent = score; setTimeout(() => { matchedCandies.forEach(candy => { candy.classList.remove(candy.classList[1], 'removing'); }); dropCandies(); }, 300); // Match with CSS animation duration } function dropCandies() { for (let c = 0; c < boardSize; c++) { let emptyCells = []; for (let r = boardSize - 1; r >= 0; r--) { const index = r * boardSize + c; if (!candies[index].classList[1]) { // If the cell is empty (no color class) emptyCells.push(index); } else if (emptyCells.length > 0) { // Move the candy down to the lowest empty cell const lowestEmptyIndex = emptyCells.shift(); const currentColor = candies[index].classList[1]; candies[lowestEmptyIndex].classList.add(currentColor); candies[index].classList.remove(currentColor); emptyCells.push(index); // The current cell is now empty } } // Fill the top empty cells with new candies emptyCells.forEach(index => { let randomColor = candyColors[Math.floor(Math.random() * candyColors.length)]; candies[index].classList.add(randomColor); }); } // After dropping and refilling, check for new matches (cascading effect) setTimeout(() => { checkAllMatches(); }, 300); // Give time for candies to drop } // Initial setup createBoard(); setupEventListeners(); });