HTML5+JavaScript实现连连看游戏之二

HTML5+JavaScript实现连连看游戏之二

以前一篇,见 https://blog.csdn.net/cnds123/article/details/144220548

连连看游戏连接规则:

只能连接相同图案(或图标、字符)的方块。

连线路径必须是由直线段组成的,最多可以有两个拐角。

连线路径必须是空的,不能穿过其他方块。

当两个相同图案的方块被有效连接时,它们会从游戏板上消失。当所有方块都被消除时,玩家获胜。

现在再发布一个,可以自定义 行列数、可用图片数(实际是使用Font Awesome图标库以便跨平台兼容,通过CDN加载图标库)

同一局游戏中所有图标使用相同颜色,不同局颜色会变化。

游戏就可以在行数和列数乘积为偶数时才允许开始,如果行数和列数的乘积为奇数,则不允许游戏开始,并且可以通过弹出提示信息来告知用户。

点击"开始"按钮后立即生成布局并开始计时。

运行界面

下面代码若要在手机上玩,可以在 <meta charset="UTF-8"> 之后添加一行

<meta name="viewport" content="width=device-width, initial-scale=1.0">

源码如下:

html 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>连连看游戏(跨平台版)</title>
    <!-- 引入Font Awesome图标库 -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            background-color: #f0f0f0;
        }
        #controls {
            margin: 20px 0;
            text-align: center;
        }
        input, button {
            margin: 0 10px;
            padding: 5px;
        }
        button {
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        button:hover {
            background-color: #45a049;
        }
        #timebar {
            height: 30px;
            width: 600px;
            background-color: #ddd;
            margin-bottom: 20px;
            position: relative;
        }
        #timebar-inner {
            height: 100%;
            width: 100%;
            background-color: green;
            transition: width 1s linear;
        }
        #timeleft {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-weight: bold;
            z-index: 1;
        }
        #container {
            background-color: white;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
            display: flex;
            justify-content: center;
            align-items: center;
        }
        table {
            border-collapse: collapse;
        }
        td {
            border: 1px solid #ddd;
            text-align: center;
            vertical-align: middle;
            cursor: pointer;
            transition: background-color 0.3s;
            width: 40px;
            height: 40px;
            font-family: "Font Awesome 5 Free";
            font-weight: 900;
        }
        td:hover {
            background-color: #f5f5f5;
        }
        td i {
            font-size: 24px;
        }
    </style>
</head>
<body>
    <div id="controls">
        行数<input id="setrow" type="number" value="9" min="2" max="20">
        列数<input id="setcol" type="number" value="16" min="2" max="40">
        图片数<input id="setpic" type="number" value="12" min="1" max="26">
        时间<input id="settime" type="number" value="120" min="10" max="1200">秒
        <button onclick="SetupGame();">设置</button>
        <button onclick="StartGame();" id="startButton">开始</button>
    </div>
    <div id="timebar">
        <div id="timebar-inner"></div>
        <div id="timeleft"></div>
    </div>
    <div id="container"></div>

    <script>
    // 图标配置(扩展更多图标请访问:https://fontawesome.com/icons)
    const icons = [
        'fa-heart',    'fa-star',    'fa-circle',  'fa-square',
        'fa-check',    'fa-times',   'fa-bell',    'fa-flag',
        'fa-cloud',    'fa-sun',     'fa-moon',    'fa-leaf',
        'fa-gem',      'fa-car',     'fa-tree',    'fa-coffee',
        'fa-bolt',     'fa-rocket',  'fa-key',     'fa-bug',
        'fa-cat',      'fa-dog',     'fa-fish',    'fa-horse'
    ];

    // 图标配置(仅定义类型)
    const iconClasses = [
        'fa-heart', 'fa-star', 'fa-circle', 'fa-square',
        'fa-check', 'fa-times', 'fa-bell', 'fa-flag',
        'fa-cloud', 'fa-sun', 'fa-moon', 'fa-leaf'
    ];
    
    let currentColor = ""; // 全局当前颜色
    
    let RowMax = 11;
    let ColMax = 18;
    let PicMax = 12;
    let TimeMax = 120;
    
    let st;
    let TmpStr = "";
    let TmpObj = null;
    let TmpTime = 0;
    let TmpInt = 0;
    let gameMatrix;
    let gameStarted = false;

    let P = new Array(4);
    for(let i=0; i<4; i++) P[i] = {x:0, y:0};

    function SetupGame(){
        clearInterval(st);
        
        // 参数校验
        const rowVal = Math.min(20, Math.max(2, parseInt(document.getElementById("setrow").value)));
        const colVal = Math.min(40, Math.max(2, parseInt(document.getElementById("setcol").value)));
        RowMax = rowVal + 2;
        ColMax = colVal + 2;

        // 检查行数与列数的乘积是否为偶数
        if ((rowVal * colVal) % 2 !== 0) {
            alert("行数和列数的乘积必须为偶数,请调整行数或列数。");
            return; // 终止函数执行
        }
        
        PicMax = Math.min(icons.length, Math.max(1, 
            parseInt(document.getElementById("setpic").value)));
        
        TimeMax = Math.min(1200, Math.max(10, 
            parseInt(document.getElementById("settime").value)));

        generateNewGame();
        
        document.getElementById("timeleft").innerHTML = "点击开始按钮开始游戏";
        document.getElementById("timebar-inner").style.width = "100%";
        document.getElementById("timebar-inner").style.backgroundColor = "#4CAF50";
        document.getElementById("startButton").disabled = false;
        gameStarted = false;
    }

    function getRandomColor() {
        return `hsl(${Math.random() * 360}, 70%, 60%)`; // 随机HSL颜色
    }
 
    function generateNewGame() {
        gameMatrix = Array(RowMax).fill().map(() => Array(ColMax).fill(0));
        
        // 生成全局统一颜色
        currentColor = `hsl(${Math.random()*360}, 70%, 60%)`; // 随机HSL颜色
    
        // 生成配对逻辑(仅存储图标类型索引)
        const totalCells = (RowMax-2)*(ColMax-2);
        const pairs = [];
        for(let i=0; i<totalCells/2; i++){
            const type = i % PicMax; // 确保成对生成
            pairs.push(type, type);
        }
        
        // 洗牌算法
        for(let i = pairs.length -1; i > 0; i--){
            const j = Math.floor(Math.random() * (i+1));
            [pairs[i], pairs[j]] = [pairs[j], pairs[i]];
        }
    
        // 构建表格(所有图标使用相同颜色)
        TmpStr = "<table>";
        let pairIndex = 0;
        for(let i=0; i<RowMax; i++){
            TmpStr += "<tr>";
            for(let j=0; j<ColMax; j++){
                TmpStr += `<td onclick="CheckP(this,${i},${j});">`;
                
                if(i === 0 || j === 0 || i === RowMax-1 || j === ColMax-1){
                    gameMatrix[i][j] = null; // 边界保持为空
                } else {
                    const type = pairs[pairIndex++];
                    gameMatrix[i][j] = type; // 存储类型索引
                    // 所有图标使用同一颜色
                    TmpStr += `<i class="fas ${iconClasses[type]}" 
                               style="color:${currentColor}"></i>`;
                }
                TmpStr += "</td>";
            }
            TmpStr += "</tr>";
        }
        TmpStr += "</table>";
        document.getElementById("container").innerHTML = TmpStr;
        TmpInt = totalCells/2;
    }

    // 开始游戏
    function StartGame(){

        gameStarted = true; // 标记游戏已开始
        TmpTime = TimeMax; // 重置时间
        document.getElementById("timeleft").innerHTML = TmpTime; // 显示剩余时间
        document.getElementById("timebar-inner").style.width = "100%";
        document.getElementById("timebar-inner").style.backgroundColor = "green";

        st = setInterval(ShowTime, 1000); // 开始倒计时

        // 禁用"开始"按钮
        document.getElementById("startButton").disabled = true;
    }

    // X方向连线。(有起点,无终点)
    function LineX(x, y, xt){
        for(let i=x; i!=xt; (x<xt? i++: i--) ){
            if(gameMatrix[i][y]){
                return false;
            }
        }
        return true;
    }

    // Y方向连线。(有起点,无终点)
    function LineY(x, y, yt){
        for(let i=y; i!=yt; (y<yt? i++: i--) ){
            if(gameMatrix[x][i]){
                return false;
            }
        }    
        return true;
    }

    // 2个点被3条线连接
    function LinkP(P1,P2){
        // P1在P2下方,交换P1、P2
        if(P1.x>P2.x){
            [P1, P2] = [P2, P1];
        }
        // P1下方1点(x+1)先横向再纵向是否可连接。(因为起点P1不为空,所以检测其下方一点)
        if( LineX((P1.x+1), P1.y, P2.x) && LineY(P2.x, P1.y, P2.y) ) return true;
        // P1先向上侧连接,再检测该点再横向再纵向是否可连接P2。
        for(let j=(P1.y-1); j>=0; j--){
            if(gameMatrix[P1.x][j]) break;
            if( LineX((P1.x+1), j, P2.x) && LineY(P2.x, j, P2.y) ) return true;
        }
        // P1先向下侧连接,再检测该点再横向再纵向是否可连接P2。
        for(let j=(P1.y+1); j<ColMax; j++){
            if(gameMatrix[P1.x][j]) break;
            if( LineX((P1.x+1), j, P2.x) && LineY(P2.x, j, P2.y) ) return true;
        }

        // P1在P2左侧,交换P1、P2
        if(P1.y>P2.y){
            [P1, P2] = [P2, P1];
        }
        if( LineY(P1.x, (P1.y+1), P2.y) && LineX(P1.x, P2.y, P2.x) ) return true;
        for(let j=(P1.x-1); j>=0; j--){
            if(gameMatrix[j][P1.y]) break;
            if( LineY(j, (P1.y+1), P2.y) && LineX(j, P2.y, P2.x) ) return true;
        }
        for(let j=(P1.x+1); j<RowMax; j++){
            if(gameMatrix[j][P1.y]) break;
            if( LineY(j, (P1.y+1), P2.y) && LineX(j, P2.y, P2.x) ) return true;
        }
        return false;
    }

    // 单击检测该点(仅需比较类型)
    function CheckP(o,x,y){
        if (!gameStarted) return;
    
        if(gameMatrix[x][y] !== null){ 
            if(null==TmpObj){ 
                TmpObj = o;
                TmpObj.style.border = "2px solid blue";
                P[0].x = x;
                P[0].y = y;
            }
            else if(o!=TmpObj){
                TmpObj.style.border = "";
                P[1].x = x;
                P[1].y = y;
                
                // 仅比较类型索引
                if(gameMatrix[P[0].x][P[0].y] === gameMatrix[P[1].x][P[1].y]){
                    if(LinkP(P[0],P[1])){
                        gameMatrix[P[0].x][P[0].y] = null;
                        gameMatrix[P[1].x][P[1].y] = null;
                        TmpObj.innerHTML = "";
                        o.innerHTML = "";
                        TmpTime++;
                        
                        TmpInt--;
                        if(!TmpInt){
                            clearInterval(st);
                            document.getElementById("timeleft").innerHTML = "";
                            document.getElementById("timebar-inner").style.backgroundColor = "white";
                            alert("恭喜完成!");
                            document.getElementById("startButton").disabled = false;
                            gameStarted = false;
                        }
                    }
                }
                TmpObj = null;
            }
        } else {
            if(TmpObj) TmpObj.style.border = "";
            TmpObj = null;
        }
    }

    function ShowTime(){
        TmpTime--; // 时间减1
        // 更新时间显示
        document.getElementById("timeleft").innerHTML = TmpTime;
        let percentage = Math.floor(100*TmpTime/TimeMax);
        document.getElementById("timebar-inner").style.width = percentage + "%";
        if(percentage <= 25){
            document.getElementById("timebar-inner").style.backgroundColor = "red";
        }
        else if(percentage <= 50){
            document.getElementById("timebar-inner").style.backgroundColor = "yellow";
        }

        if(!TmpTime){ // 剩余时间为0
            clearInterval(st); // 清除倒计时
            document.getElementById("timeleft").innerHTML = "";
            document.getElementById("timebar-inner").style.backgroundColor = "white";
            alert("时间到!游戏结束");
            // 启用"开始"按钮
            document.getElementById("startButton").disabled = false;
            gameStarted = false; // 重置游戏状态
        }
    }

    // 页面加载完成后,显示初始提示信息
    window.onload = function() {
        document.getElementById("timeleft").innerHTML = "请设置游戏参数并点击设置按钮";
        document.getElementById("timebar-inner").style.backgroundColor = "#ddd";
        document.getElementById("startButton").disabled = true;
    }
    </script>
</body>
</html>
相关推荐
Jedi Hongbin2 小时前
echarts自定义图表--柱状图-横向
前端·javascript·echarts
3D虚拟工厂3 小时前
1️⃣7️⃣three.js_OrbitControls相机控制器
javascript·3d·vue·blender·three.js·uv
sunly_4 小时前
Flutter:组件10、倒计时
开发语言·javascript·flutter
徐白11774 小时前
Node.js 事件循环和线程池任务完整指南
开发语言·javascript·node.js
哈希茶馆4 小时前
前端工程化利器:Node.js 文件匹配库 fast-glob 完全指南——比传统方案快 350% 的「文件搜索神器」
运维·前端·javascript·npm·node.js·全文检索·运维开发
zhangguo20025 小时前
react18基础速成
前端·javascript·react.js
牧杉-惊蛰13 小时前
Vue3中到达可视区域后执行
前端·javascript·vue.js
GISer_Jing13 小时前
ByteMD详解
前端·javascript
IoOozZzzz14 小时前
ES6异步编程中Promise与Proxy对象
前端·javascript·es6