SOURCE

console 命令行工具 X clear

                    
>
console
document.addEventListener('DOMContentLoaded', () => {
    const gridElement = document.getElementById('game-grid');
    const victoryRibbon = document.getElementById('victory-ribbon');
    const GRID_SIZE = 10;
    const CANDY_TYPES = ['��', '��', '��', '��', '��', '��', '��', '��'];
    const MIN_MATCH_LENGTH = 3;

    let grid = []; // 2D array representing the game board [row][col]
    let isAnimating = false; // Prevent clicks during animation/processing

    // --- Initialization ---

    function initGame() {
        gridElement.innerHTML = ''; // Clear previous grid
        grid = [];
        isAnimating = false;
        victoryRibbon.classList.add('hidden');
        victoryRibbon.classList.remove('visible');


        for (let r = 0; r < GRID_SIZE; r++) {
            grid[r] = [];
            for (let c = 0; c < GRID_SIZE; c++) {
                createCandyCell(r, c);
            }
        }
        // Ensure no initial matches (optional but good practice)
        // removeInitialMatches(); // Can be complex, skipping for simplicity here
        updateGridDOM(); // Initial draw
    }

    function getRandomCandy() {
        return CANDY_TYPES[Math.floor(Math.random() * CANDY_TYPES.length)];
    }

    function createCandyCell(r, c) {
        const cell = document.createElement('div');
        cell.classList.add('candy');
        cell.dataset.row = r;
        cell.dataset.col = c;
        grid[r][c] = getRandomCandy(); // Assign candy type to logical grid
        cell.addEventListener('click', handleCandyClick);
        gridElement.appendChild(cell);
    }

    // --- Core Gameplay Logic ---

    function handleCandyClick(event) {
        if (isAnimating) return; // Don't process clicks during animation

        const row = parseInt(event.target.dataset.row);
        const col = parseInt(event.target.dataset.col);
        const candyType = grid[row][col];

        if (!candyType) return; // Clicked on an empty space (shouldn't happen often)

        const connectedCandies = findConnectedCandies(row, col, candyType);

        if (connectedCandies.length >= MIN_MATCH_LENGTH) {
            isAnimating = true; // Start animation lock
            eliminateCandies(connectedCandies);

            // Use setTimeout to allow elimination animation to show
            setTimeout(() => {
                applyGravity();
                refillGrid();
                updateGridDOM(); // Update DOM after falling and refilling

                // Check for victory *after* everything has settled
                 if (checkVictory()) {
                     triggerVictory();
                 } else {
                    isAnimating = false; // Release lock if not victory
                 }
                // Note: Cascading matches are not implemented here for simplicity.
            }, 300); // Delay matching the elimination animation duration
        }
    }

    function findConnectedCandies(startRow, startCol, candyType) {
        const connected = [];
        const queue = [{ r: startRow, c: startCol }];
        const visited = new Set([`${startRow}-${startCol}`]); // Track visited cells

        while (queue.length > 0) {
            const { r, c } = queue.shift();
            connected.push({ r, c });

            // Check neighbors (Up, Down, Left, Right)
            const neighbors = [
                { r: r - 1, c: c }, { r: r + 1, c: c },
                { r: r, c: c - 1 }, { r: r, c: c + 1 }
            ];

            for (const neighbor of neighbors) {
                const { r: nr, c: nc } = neighbor;
                const key = `${nr}-${nc}`;

                // Check bounds, if same type, and not visited
                if (nr >= 0 && nr < GRID_SIZE && nc >= 0 && nc < GRID_SIZE &&
                    !visited.has(key) && grid[nr][nc] === candyType) {
                    visited.add(key);
                    queue.push({ r: nr, c: nc });
                }
            }
        }
        return connected;
    }

    function eliminateCandies(candiesToEliminate) {
        candiesToEliminate.forEach(({ r, c }) => {
            const cellElement = getCellElement(r, c);
            if (cellElement) {
                cellElement.classList.add('eliminating'); // Add class for visual effect
            }
            grid[r][c] = null; // Mark as empty in the logical grid
        });
    }

    // --- Gravity and Refill ---

    function applyGravity() {
        for (let c = 0; c < GRID_SIZE; c++) {
            let emptyRow = GRID_SIZE - 1; // Start checking from bottom
            for (let r = GRID_SIZE - 1; r >= 0; r--) {
                if (grid[r][c] !== null) {
                    // If this cell has a candy, move it down to the lowest empty spot
                    if (r !== emptyRow) {
                        grid[emptyRow][c] = grid[r][c];
                        grid[r][c] = null; // Clear the original spot
                    }
                    emptyRow--; // Move the target empty spot pointer up
                }
            }
        }
    }

    function refillGrid() {
        for (let r = 0; r < GRID_SIZE; r++) {
            for (let c = 0; c < GRID_SIZE; c++) {
                if (grid[r][c] === null) {
                    grid[r][c] = getRandomCandy();
                }
            }
        }
    }

    // --- DOM Updates ---

    function updateGridDOM() {
        for (let r = 0; r < GRID_SIZE; r++) {
            for (let c = 0; c < GRID_SIZE; c++) {
                const cellElement = getCellElement(r, c);
                if (cellElement) {
                    const candyType = grid[r][c];
                    if (candyType) {
                        cellElement.textContent = candyType;
                        cellElement.classList.remove('eliminating'); // Ensure class is removed
                        cellElement.style.opacity = 1; // Make sure it's visible
                    } else {
                        // Should ideally not happen after refill, but handles empty state visually
                        cellElement.textContent = '';
                        cellElement.style.opacity = 0; // Make empty cells invisible
                    }
                }
            }
        }
    }

    function getCellElement(r, c) {
        // Selector finds the element based on data attributes
        return gridElement.querySelector(`[data-row="${r}"][data-col="${c}"]`);
    }

    // --- Victory Condition ---

    function checkVictory() {
        const lastRow = GRID_SIZE - 1;
        for (let c = 0; c < GRID_SIZE; c++) {
            if (grid[lastRow][c] !== null) {
                return false; // Found a candy in the last row, not victory yet
            }
        }
        return true; // All cells in the last row are null (empty)
    }

    function triggerVictory() {
        console.log("Victory!");
        isAnimating = true; // Keep interaction locked
        victoryRibbon.classList.remove('hidden');
        // Force reflow to ensure transition plays
        void victoryRibbon.offsetWidth;
        victoryRibbon.classList.add('visible');

        setTimeout(() => {
            victoryRibbon.classList.remove('visible');
             // Optionally hide it again after animation + duration
            setTimeout(() => {
                victoryRibbon.classList.add('hidden');
                // Maybe restart game or show a "Play Again" button here
                // For now, just keep the lock
                 // isAnimating = false; // Or keep it locked until manual reset
            }, 500); // Corresponds to the transition duration in CSS
        }, 3000); // Display victory message for 3 seconds
    }

    // --- Start Game ---
    initGame();

});
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple Candy Elimination Fun</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>Simple Candy Elimination Fun</h1>
    <div id="game-container">
        <div id="game-grid">
            <!-- Candy cells will be generated by JavaScript -->
        </div>
        <div id="victory-ribbon" class="hidden">
            �� Victory! ��
        </div>
    </div>
    <div id="instructions">
        Click on 3 or more connected candies of the same type to eliminate them! Clear the bottom row to win!
    </div>

    <script src="script.js"></script>
</body>
</html>
body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background: linear-gradient(135deg, #ffdde1, #ee9ca7); /* Cute pink gradient */
    font-family: 'Arial Rounded MT Bold', 'Helvetica Rounded', Arial, sans-serif;
    overflow: hidden; /* Hide scrollbars if content slightly overflows */
}

h1 {
    color: #d13a5a;
    text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
    margin-bottom: 15px;
}

#game-container {
    position: relative; /* Needed for absolute positioning of victory ribbon */
    border: 5px solid #fff;
    border-radius: 15px;
    box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
    background-color: rgba(255, 255, 255, 0.7); /* Semi-transparent white */
    padding: 10px;
}

#game-grid {
    display: grid;
    grid-template-columns: repeat(10, 40px); /* 10 columns */
    grid-template-rows: repeat(10, 40px);    /* 10 rows */
    gap: 3px; /* Small gap between candies */
    width: 430px;  /* (10 * 40px) + (9 * 3px) */
    height: 430px; /* (10 * 40px) + (9 * 3px) */
}

.candy {
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 28px; /* Make emojis larger */
    border-radius: 8px; /* Slightly rounded corners */
    background-color: rgba(255, 255, 255, 0.5); /* Slight background for cells */
    cursor: pointer;
    transition: transform 0.1s ease-out, background-color 0.2s;
    user-select: none; /* Prevent text selection */
    box-shadow: inset 0 0 5px rgba(0,0,0,0.05);
}

.candy:hover {
    transform: scale(1.1);
    background-color: rgba(255, 255, 255, 0.8);
}

/* Style for eliminated candies (briefly) */
.eliminating {
    transform: scale(0);
    transition: transform 0.3s ease-in;
    background-color: transparent !important; /* Override hover */
}

#instructions {
    margin-top: 20px;
    color: #555;
    font-size: 0.9em;
    text-align: center;
}

#victory-ribbon {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0.8); /* Start slightly smaller */
    background: linear-gradient(45deg, #ffd700, #ffa500); /* Gold gradient */
    color: white;
    padding: 25px 50px;
    border-radius: 10px;
    font-size: 2.5em;
    font-weight: bold;
    text-align: center;
    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
    opacity: 0;
    transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
    pointer-events: none; /* Don't interfere with clicks */
    z-index: 10;
}

#victory-ribbon.visible {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1); /* Scale to full size */
}

.hidden {
    display: none; /* Use display: none initially */
}

/* Override hidden for animation start */
#victory-ribbon:not(.hidden) {
    display: block; /* Make it block to allow transitions */
}