<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Alien Match Game</title>
    <style>
        canvas {
            display: block;
            margin: 0 auto;
            background-color: #000;
        }
        body {
            text-align: center;
            font-family: Arial, sans-serif;
            background-color: #222;
            color: white;
        }
    </style>
</head>
<body>

<h1>Alien Match Game</h1>
<canvas id="gameCanvas"></canvas>
<h2>Score: <span id="score">0</span></h2>
<h2>Level: <span id="level">1</span></h2>
<h2>Target Score: <span id="targetScore">100</span></h2>
<h2>Time Left: <span id="timer">60</span> seconds</h2>
<h2 id="gameOver" style="display:none;">Game Over!</h2>

<script>
    // Game variables
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');
    const tileSize = 60; // size of each tile
    const gridWidth = 8; // 8x8 grid
    const gridHeight = 8;
    const alienTypes = ['👽', '👾', '🛸', '🪐']; // Alien types
    const powerUpTypes = ['💣', '🚀', '🌈']; // Power-up types
    const aliens = [];
    let selectedTile = null; // Store the first selected tile
    let score = 0; // Keep track of the score
    let level = 1; // Current level
    let targetScore = 100; // Target score for the current level
    let timeLeft = 60; // Time per level in seconds
    let animating = false; // Tracks if an animation is running
    let timerInterval; // Store the timer interval

    // Resize canvas dynamically to fit screen
    function resizeCanvas() {
        canvas.width = gridWidth * tileSize;
        canvas.height = gridHeight * tileSize;
    }

    // Initialize the grid with random alien types
    function initGrid() {
        for (let y = 0; y < gridHeight; y++) {
            aliens[y] = [];
            for (let x = 0; x < gridWidth; x++) {
                aliens[y][x] = alienTypes[Math.floor(Math.random() * alienTypes.length)];
            }
        }
    }

    // Draw the grid with aliens and power-ups
    function drawGrid() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        for (let y = 0; y < gridHeight; y++) {
            for (let x = 0; x < gridWidth; x++) {
                if (aliens[y][x] !== null) { // Only draw non-null tiles
                    ctx.fillStyle = '#fff';
                    ctx.font = '40px Arial';
                    ctx.textAlign = 'center';
                    ctx.textBaseline = 'middle';
                    const alien = aliens[y][x];
                    const posX = x * tileSize + tileSize / 2;
                    const posY = y * tileSize + tileSize / 2;
                    ctx.fillText(alien, posX, posY); // Display either alien or power-up emoji
                }

                // Draw a border around the tile
                ctx.strokeStyle = "#fff";
                ctx.strokeRect(x * tileSize, y * tileSize, tileSize, tileSize);
            }
        }
    }

    // Get the tile (x, y) position based on mouse click
    function getTileCoordinates(mouseX, mouseY) {
        const tileX = Math.floor(mouseX / tileSize);
        const tileY = Math.floor(mouseY / tileSize);
        return { x: tileX, y: tileY };
    }

    // Handle mouse click on canvas for tile selection and swapping
    canvas.addEventListener('click', function(event) {
        if (animating) return; // Prevent interaction during animations

        const rect = canvas.getBoundingClientRect();
        const mouseX = event.clientX - rect.left;
        const mouseY = event.clientY - rect.top;

        const tile = getTileCoordinates(mouseX, mouseY);
        const selectedAlien = aliens[tile.y][tile.x];

        // Handle power-up activation
        if (powerUpTypes.includes(selectedAlien)) {
            handlePowerUp(tile.x, tile.y);
            return;
        }

        // Normal tile selection and swapping
        if (!selectedTile) {
            // Select the first tile
            selectedTile = tile;
            highlightTile(tile.x, tile.y);
        } else {
            // Select the second tile and try swapping
            swapTilesAnimated(selectedTile.x, selectedTile.y, tile.x, tile.y);
            selectedTile = null; // Reset selection
        }
    });

    // Highlight the selected tile
    function highlightTile(x, y) {
        ctx.strokeStyle = 'yellow';
        ctx.lineWidth = 4;
        ctx.strokeRect(x * tileSize, y * tileSize, tileSize, tileSize);
    }

    // Animate swapping of tiles
    function swapTilesAnimated(x1, y1, x2, y2) {
        if (animating) return; // Prevent simultaneous animations
        animating = true;

        const startTime = performance.now();
        const swapDuration = 300; // Duration of the animation in ms

        const startPos1 = { x: x1 * tileSize, y: y1 * tileSize };
        const startPos2 = { x: x2 * tileSize, y: y2 * tileSize };

        const alien1 = aliens[y1][x1];
        const alien2 = aliens[y2][x2];

        function animateSwap(time) {
            const elapsed = time - startTime;
            const progress = Math.min(elapsed / swapDuration, 1);

            // Clear the current grid section
            ctx.clearRect(startPos1.x, startPos1.y, tileSize, tileSize);
            ctx.clearRect(startPos2.x, startPos2.y, tileSize, tileSize);

            // Interpolating positions for animation
            const currentPos1 = {
                x: startPos1.x + (startPos2.x - startPos1.x) * progress,
                y: startPos1.y + (startPos2.y - startPos1.y) * progress
            };
            const currentPos2 = {
                x: startPos2.x + (startPos1.x - startPos2.x) * progress,
                y: startPos2.y + (startPos1.y - startPos2.y) * progress
            };

            // Draw the moving aliens
            ctx.font = '40px Arial';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillStyle = '#fff';

            ctx.fillText(alien1, currentPos1.x + tileSize / 2, currentPos1.y + tileSize / 2);
            ctx.fillText(alien2, currentPos2.x + tileSize / 2, currentPos2.y + tileSize / 2);

            ctx.strokeStyle = "#fff";
            ctx.strokeRect(startPos1.x, startPos1.y, tileSize, tileSize);
            ctx.strokeRect(startPos2.x, startPos2.y, tileSize, tileSize);

            if (progress < 1) {
                requestAnimationFrame(animateSwap);
            } else {
                // Swap is complete
                completeSwap(x1, y1, x2, y2);
                animating = false;
            }
        }

        requestAnimationFrame(animateSwap);
    }

    // Swap tiles without animation and check for matches
    function completeSwap(x1, y1, x2, y2) {
        const temp = aliens[y1][x1];
        aliens[y1][x1] = aliens[y2][x2];
        aliens[y2][x2] = temp;

        drawGrid();

        if (checkForMatches()) {
            setTimeout(() => handleMatches(), 500);
        } else {
            setTimeout(() => {
                swapTiles(x1, y1, x2, y2); // Swap back if no match
            }, 500);
        }
    }

    // Power-up creation logic based on match lengths (4: Bomb, 5: Rocket, 6: Rainbow)
    function handleMatches() {
        const tilesToRemove = [];

        // Horizontal matches
        for (let y = 0; y < gridHeight; y++) {
            for (let x = 0; x < gridWidth - 2; x++) {
                let matchLength = 1;
                while (x + matchLength < gridWidth && aliens[y][x] === aliens[y][x + matchLength]) {
                    matchLength++;
                }
                if (matchLength >= 3) {
                    for (let i = 0; i < matchLength; i++) {
                        tilesToRemove.push({ x: x + i, y });
                    }

                    // Create power-ups dynamically in the middle of the match
                    if (matchLength === 4) aliens[y][x + Math.floor(matchLength / 2)] = '💣'; // 4: Bomb
                    else if (matchLength === 5) aliens[y][x + Math.floor(matchLength / 2)] = '🚀'; // 5: Rocket
                    else if (matchLength >= 6) aliens[y][x + Math.floor(matchLength / 2)] = '🌈'; // 6: Rainbow
                    x += matchLength - 1;
                }
            }
        }

        // Vertical matches
        for (let x = 0; x < gridWidth; x++) {
            for (let y = 0; y < gridHeight - 2; y++) {
                let matchLength = 1;
                while (y + matchLength < gridHeight && aliens[y][x] === aliens[y + matchLength][x]) {
                    matchLength++;
                }
                if (matchLength >= 3) {
                    for (let i = 0; i < matchLength; i++) {
                        tilesToRemove.push({ x, y: y + i });
                    }

                    // Create power-ups dynamically in the middle of the match
                    if (matchLength === 4) aliens[y + Math.floor(matchLength / 2)][x] = '💣';
                    else if (matchLength === 5) aliens[y + Math.floor(matchLength / 2)][x] = '🚀';
                    else if (matchLength >= 6) aliens[y + Math.floor(matchLength / 2)][x] = '🌈';
                    y += matchLength - 1;
                }
            }
        }

        // Remove matched tiles and update score
        tilesToRemove.forEach(tile => {
            aliens[tile.y][tile.x] = null;
        });
        score += tilesToRemove.length * 10;
        document.getElementById('score').textContent = score;

        setTimeout(() => {
            refillGrid();
            drawGrid();
            checkForLevelCompletion(); // Check if the target score is reached after matches
            if (checkForMatches()) handleMatches();
        }, 500);
    }

    // Handle different power-ups
    function handlePowerUp(x, y) {
        const powerUp = aliens[y][x];

        if (powerUp === '💣') {
            clearSurroundingTiles(x, y); // Bomb: 3x3 area
        } else if (powerUp === '🌈') {
            const targetType = getRandomAlienType(); // Rainbow: Clear all of the same type
            clearAllTilesOfType(targetType);
        } else if (powerUp === '🚀') {
            clearRow(y); // Rocket: Clear the row
        }

        score += 30; // Increment score for power-up use
        document.getElementById('score').textContent = score;

        // Redraw and refill grid
        drawGrid();
        refillGrid();
        checkForLevelCompletion(); // Check if the target score is reached after using power-up
    }

    // Clears a 3x3 area
    function clearSurroundingTiles(x, y) {
        for (let i = -1; i <= 1; i++) {
            for (let j = -1; j <= 1; j++) {
                const newX = x + i;
                const newY = y + j;
                if (newX >= 0 && newX < gridWidth && newY >= 0 && newY < gridHeight) {
                    aliens[newY][newX] = null;
                }
            }
        }
    }

    // Clears all tiles of the same type
    function clearAllTilesOfType(type) {
        for (let y = 0; y < gridHeight; y++) {
            for (let x = 0; x < gridWidth; x++) {
                if (aliens[y][x] === type) {
                    aliens[y][x] = null;
                }
            }
        }
    }

    // Clears an entire row
    function clearRow(y) {
        for (let x = 0; x < gridWidth; x++) {
            aliens[y][x] = null;
        }
    }

    // Check for matches
    function checkForMatches() {
        let matchFound = false;

        // Horizontal matches
        for (let y = 0; y < gridHeight; y++) {
            for (let x = 0; x < gridWidth - 2; x++) {
                let matchLength = 1;
                while (x + matchLength < gridWidth && aliens[y][x] === aliens[y][x + matchLength]) {
                    matchLength++;
                }
                if (matchLength >= 3) matchFound = true;
            }
        }

        // Vertical matches
        for (let x = 0; x < gridWidth; x++) {
            for (let y = 0; y < gridHeight - 2; y++) {
                let matchLength = 1;
                while (y + matchLength < gridHeight && aliens[y][x] === aliens[y + matchLength][x]) {
                    matchLength++;
                }
                if (matchLength >= 3) matchFound = true;
            }
        }

        return matchFound;
    }

    // Refill the grid after clearing
    function refillGrid() {
        for (let x = 0; x < gridWidth; x++) {
            for (let y = gridHeight - 1; y >= 0; y--) {
                if (aliens[y][x] === null) {
                    for (let yAbove = y - 1; yAbove >= 0; yAbove--) {
                        if (aliens[yAbove][x] !== null) {
                            aliens[y][x] = aliens[yAbove][x];
                            aliens[yAbove][x] = null;
                            break;
                        }
                    }
                }
            }
        }

        // Generate new tiles at the top
        for (let x = 0; x < gridWidth; x++) {
            for (let y = 0; y < gridHeight; y++) {
                if (aliens[y][x] === null) {
                    aliens[y][x] = alienTypes[Math.floor(Math.random() * alienTypes.length)];
                }
            }
        }
    }

    // Utility: Get a random alien type (for color bomb)
    function getRandomAlienType() {
        return alienTypes[Math.floor(Math.random() * alienTypes.length)];
    }

    // Start the game timer
    function startTimer() {
        timerInterval = setInterval(() => {
            timeLeft--;
            document.getElementById('timer').textContent = timeLeft;

            if (timeLeft <= 0) {
                clearInterval(timerInterval);
                gameOver();
            }
        }, 1000);
    }

    // Check if the player has reached the target score
    function checkForLevelCompletion() {
        if (score >= targetScore) {
            clearInterval(timerInterval); // Stop the timer
            nextLevel(); // Move to the next level
        }
    }

    // Move to the next level
    function nextLevel() {
        level++;
        targetScore += 50; // Increase the target score for each level
        timeLeft = 60; // Reset the time for the next level
        document.getElementById('level').textContent = level;
        document.getElementById('targetScore').textContent = targetScore;
        startTimer(); // Start the timer again
    }

    // End the game and display "Game Over"
    function gameOver() {
        document.getElementById('gameOver').style.display = 'block';
        clearInterval(timerInterval); // Stop the timer
    }

    // Initialize the game
    function initGame() {
        resizeCanvas();
        initGrid();
        drawGrid();
        startTimer();
    }

    // Resize canvas on window resize
    window.addEventListener('resize', () => {
        resizeCanvas();
        drawGrid(); // Redraw grid on resize
    });

    // Start the game
    initGame();

</script>

</body>
</html>

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.