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();
});