HTML5+JavaScript实现消消乐游戏

HTML5+JavaScript实现消消乐游戏

点击两个相邻的方块来交换它们位置。

如果交换后形成三个或更多相同图案的方块连成一线,这些方块会被消除。

消除后,上方的方块会下落填补空缺,顶部会生成新的方块。

每消除一个方块得10分。例如,如果一次消除了4个方块,玩家将得到40分。

运行效果如下图:

源码如下:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>基础消消乐游戏 - Emoji版</title>
    <style>
        body {
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
            font-family: Arial, sans-serif;
        }
        #gameContainer {
            display: flex;
            flex-direction: column;
            align-items: center;
        }
        canvas {
            border: 2px solid #000;
            margin-bottom: 10px;
        }
        #scoreDisplay {
            font-size: 24px;
            margin-bottom: 10px;
        }
    </style>
</head>
<body>
    <div id="gameContainer">
        <div id="scoreDisplay">分数: 0</div>
        <canvas id="gameCanvas" width="400" height="400"></canvas>
    </div>

    <script>
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const scoreDisplay = document.getElementById('scoreDisplay');

        const GRID_SIZE = 8;
        const CELL_SIZE = canvas.width / GRID_SIZE;
        const EMOJIS = ['☮', '⚜', '♾ ', '☯', '⚛', '✳'];

        let grid = [];
        let selectedCell = null;
        let score = 0;

        function initGrid() {
            for (let i = 0; i < GRID_SIZE; i++) {
                grid[i] = [];
                for (let j = 0; j < GRID_SIZE; j++) {
                    grid[i][j] = EMOJIS[Math.floor(Math.random() * EMOJIS.length)];
                }
            }
        }

        function drawGrid() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.font = `${CELL_SIZE * 0.8}px Arial`;
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';

            for (let i = 0; i < GRID_SIZE; i++) {
                for (let j = 0; j < GRID_SIZE; j++) {
                    ctx.fillText(
                        grid[i][j], 
                        i * CELL_SIZE + CELL_SIZE / 2, 
                        j * CELL_SIZE + CELL_SIZE / 2
                    );
                }
            }

            if (selectedCell) {
                ctx.strokeStyle = 'black';
                ctx.lineWidth = 2;
                ctx.strokeRect(
                    selectedCell.x * CELL_SIZE, 
                    selectedCell.y * CELL_SIZE, 
                    CELL_SIZE, 
                    CELL_SIZE
                );
            }
        }

        function checkMatches() {
            let matched = [];
            
            // 检查水平匹配
            for (let j = 0; j < GRID_SIZE; j++) {
                let streak = 1;
                for (let i = 1; i < GRID_SIZE; i++) {
                    if (grid[i][j] === grid[i-1][j]) {
                        streak++;
                    } else {
                        if (streak >= 3) {
                            for (let k = i - streak; k < i; k++) {
                                matched.push({x: k, y: j});
                            }
                        }
                        streak = 1;
                    }
                }
                if (streak >= 3) {
                    for (let k = GRID_SIZE - streak; k < GRID_SIZE; k++) {
                        matched.push({x: k, y: j});
                    }
                }
            }
            
            // 检查垂直匹配
            for (let i = 0; i < GRID_SIZE; i++) {
                let streak = 1;
                for (let j = 1; j < GRID_SIZE; j++) {
                    if (grid[i][j] === grid[i][j-1]) {
                        streak++;
                    } else {
                        if (streak >= 3) {
                            for (let k = j - streak; k < j; k++) {
                                matched.push({x: i, y: k});
                            }
                        }
                        streak = 1;
                    }
                }
                if (streak >= 3) {
                    for (let k = GRID_SIZE - streak; k < GRID_SIZE; k++) {
                        matched.push({x: i, y: k});
                    }
                }
            }
            
            return matched;
        }

        function removeMatches(matches) {
            matches.forEach(cell => {
                grid[cell.x][cell.y] = null;
            });
            //updateScore(matches.length);
        }

        function updateScore(matchCount) {
            score += matchCount * 10;
            scoreDisplay.textContent = `分数: ${score}`;
        }

        function fillBlanks() {
            for (let i = 0; i < GRID_SIZE; i++) {
                let blanks = 0;
                for (let j = GRID_SIZE - 1; j >= 0; j--) {
                    if (!grid[i][j]) {
                        blanks++;
                    } else if (blanks > 0) {
                        grid[i][j + blanks] = grid[i][j];
                        grid[i][j] = null;
                    }
                }
                for (let j = 0; j < blanks; j++) {
                    grid[i][j] = EMOJIS[Math.floor(Math.random() * EMOJIS.length)];
                }
            }
        }

        function swapCells(cell1, cell2) {
            const temp = grid[cell1.x][cell1.y];
            grid[cell1.x][cell1.y] = grid[cell2.x][cell2.y];
            grid[cell2.x][cell2.y] = temp;
        }

        canvas.addEventListener('click', (event) => {
            const rect = canvas.getBoundingClientRect();
            const x = Math.floor((event.clientX - rect.left) / CELL_SIZE);
            const y = Math.floor((event.clientY - rect.top) / CELL_SIZE);

            if (selectedCell) {
                if ((Math.abs(selectedCell.x - x) === 1 && selectedCell.y === y) ||
                    (Math.abs(selectedCell.y - y) === 1 && selectedCell.x === x)) {
                    swapCells(selectedCell, {x, y});
                    let matches = checkMatches();
                    if (matches.length === 0) {
                        swapCells(selectedCell, {x, y});
                    } else {
                        let totalMatches = 0;
                        while (matches.length > 0) {
                            totalMatches += matches.length;
                            removeMatches(matches);
                            fillBlanks();
                            matches = checkMatches();
                        }
                        updateScore(totalMatches);  // 在所有匹配处理完后更新分数
                    }
                }
                selectedCell = null;
            } else {
                selectedCell = {x, y};
            }
            drawGrid();
        });

        function gameLoop() {
            drawGrid();
            requestAnimationFrame(gameLoop);
        }

        initGrid();
        gameLoop();
    </script>
</body>
</html>
相关推荐
不好听61319 分钟前
JavaScript 的 this 到底指向谁?
javascript·面试
触底反弹22 分钟前
🔥 2026 年爆火的 Harness Engineering 到底是什么?从原理到实战一文讲透
javascript·人工智能·程序员
mONESY22 分钟前
一文搞定JavaScript不同场景中 this 的指向问题
javascript
用户2986985301432 分钟前
在 React 中使用 JavaScript 合并 Excel 文件
前端·javascript·react.js
大流星36 分钟前
LangChainJs之基础模型(一)
javascript·langchain
橘子星38 分钟前
JavaScript this 指向全解实战指南
前端·javascript
weedsfly40 分钟前
JS垃圾回收:从原理到项目实战,彻底根治内存泄漏
前端·javascript·面试
万少12 小时前
万少的博客 - 技术分享与解决方案
前端·javascript·后端
尘世中一位迷途小书童15 小时前
用 Cesium 撸了一个森林火情监控大屏,弧线、粒子、发光效果都齐了
前端·javascript
先吃饱再说16 小时前
JavaScript中`this` 的“千层套路”:从默认绑定到箭头函数的五种指向
javascript