小游戏:扫雷 前端 H5 + CSS + JS

简介

很久以前刚接触前端的时候做的扫雷小游戏。虽然花了不少时间,但是整个做下来,还是很有收获的。

现在来看代码还是比较一般的,但也是一番心血。

特地分享出来,希望对需要的人有所帮助。适合初学者练习。

运行效果

运行效果

上方的表格左边格子表示剩余雷数,中间选择难度,右边计时。按下空格可以开始新游戏。


游戏中

游戏胜利

游戏失败

主体思路

随机生成雷的分布,用二维数组储存。同时遍历数组,生成数字,表示周围的雷的数目,同样储存在这个二维数组中。
生成div来表示格子,并且绑定一个坐标属性。与二维数组一一对应。
点击格子,根据坐标判断是否点到雷。是雷就游戏结束,不是雷就显示数字。

难点

1.点击到空白处的时候,会自动打开一大片格子。这个效果当时也是研究了很久。
最后的实现方法是:设置一个searched数组,储存已经点击或者查找过的坐标。然后点到空白处的时候,向上、下、左、右、右上、左上、右下、左下八个方向进行深度优先搜索,并将搜索过的格子的坐标储存到searched数组。搜索终止的条件是:遇到searched数组中的坐标,或者遇到数字,或者遇到边界。
(2020.10.3 更新:今天回顾这个代码,发现如果使用广度优先效果会更好,只用向上下左右四个方向搜索。)

2.玩过扫雷的都知道扫雷有一个操作是:在一个数字块上同时按下鼠标的左键和右键,如果操作目标周围插旗的格子数目等于数字的值,那么就能快速打开周围所有未标记的格子。
然而同时按下鼠标的操作js是没有的。
我的解决是方法是,计算鼠标点击的时间,如果2次点击时间间隔很小,那么就认定是同时按下左右键。调试之后,发现时间间隔设置为100毫秒的效果比较好。

代码

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>扫雷</title>
        <style type="text/css">
            .grid{
                width: 18px;
                height: 18px;
                background-color: #0066cc;
                float: left;
                border: 1px black solid;
            }
            .mine{
                width: 18px;
                height: 18px;
                float: left;
                background-image:url(mine.png);
                background-size: 18px 18px;
                border: 1px black solid;
            }
            .flag{
                width: 18px;
                height: 18px;
                float: left;
                background-image:url(flag.png);
                background-size: 18px 18px;
                border: 1px black solid;
            }
            .number{
                width: 18px;
                height: 18px;
                float:left;
                text-align: center;
                line-height: 20px;
                border: 1px black solid;
                background-color: palegoldenrod;
                font-weight: bold;
            }
            #box{
                height: 200px;
                width: 200px;
                border: black 5px solid;
                position: absolute;
                left:calc(50% - 100px);
                top: 100px;
                
            }
            #info{
                position: absolute;
                left:calc(50% - 185px);
                top: 30px;
                text-align: center;
            }
            #tip{
                position: absolute;
                bottom: 160px;
                right: calc(50% - 70px);
            }
        </style>
    </head>
    <!--屏蔽文字选中,屏蔽鼠标样式改变-->
    <body style="cursor: default; user-select: none;">
        <table id = 'info'  border="5px solid" height="40px" >
            <tr><td id = 'restMine' width="120px"></td>
                <td width="120px"><select name="difficulty" onchange='btnChange(this[selectedIndex].value);'>
                    <option value="0">初级</option>
                    <option value="1">中级</option>
                    <option value="2">高级</option>
                </select></td>
                <td id="time" width="120px">0:0</td>
            </tr>
        </table>
        <div id="box" ></div>
        <p1 id ='tip'>按下空格开始新游戏</p>
    </body>
</html>
<script type="text/javascript">
var mineMap = new Array();
var searched = new Array();
var clickX;
var clickY;
var boxHeight;
var boxWidth;
var mineNumber;
var intervalid;
var diffi = 0;
var around = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]];
var eleBox = document.getElementById('box');
var eleRestMine = document.getElementById('restMine');

//设置难度数值
diffiSet(diffi);
//设置Box
setBox();
//生成格子
createGrid();

//点击下拉框,选择不同难度
function btnChange(values){
    diffiSet(parseInt(values));
    clearGrid();
    setBox();
    createGrid();
}

//点击空格,开始新游戏
window.onkeydown = function (event){
    if(event.keyCode == 32){
        clearGrid();
        createGrid();
    }
}

//生成格子,并添加鼠标事件
function createGrid(){
//  生成格子
    for (var i = 0 ; i < boxHeight ; i++){
        for(var j = 0; j < boxWidth ; j++){
            eleImg = document.createElement('div');
            eleImg.className = 'grid';
            //添加坐标
            eleImg.attributes.coor = [i,j];
            document.getElementById('box').appendChild(eleImg);
            }
        }
    //剩余雷数
    eleRestMine.innerText = mineNumber;
    //添加鼠标事件
    var eleGrid = document.getElementsByTagName('div');
    var time1;
    var time2;
    for(var i = 1 ; i < eleGrid.length; i++){
//  阻止右键菜单
        eleGrid[i].oncontextmenu = function(e){
               e.preventDefault();
        }
//  获取左右键点击number类的时间,实现左右键同时点击的功能
        eleGrid[i].onmouseup = function(e){
            if(e.button == 0 && this.className == 'number'){
                time1 = new Date().getTime();
            }
            if(e.button == 2 && this.className == 'number'){
                time2 = new Date().getTime();
            }
    //      左键点击        
            if(e.button == 0 && this.className == 'grid'){
                clickX = this.attributes.coor[0];
                clickY = this.attributes.coor[1];
    //          如果searched为空,则生成扫雷图
                if(searched.length == 0){
                    mineMap = showNumber(createMine(clickX,clickY,boxWidth,boxHeight,mineNumber));
                    console.log(mineMap);
    //              启动计时器
                    var count = 0;
                    intervalid = setInterval(function(){
                        var second = ++count % 60;
                        var minute = parseInt(count / 60);
                        document.getElementById('time').innerText = minute + ':' + second;
                    },1000);                
                }
    //          点到雷,游戏失败
                if(mineMap[clickX][clickY] == '*'){
                    gameFail(eleGrid);
    //              红雷
                    this.style.backgroundImage = 'url(o.png)';
                }
    //          普通点击
                else{
                    this.className = 'number';
                    if (mineMap[clickX][clickY] != 0){
                        this.innerText = mineMap[clickX][clickY];
                    }
                    clickMap(mineMap,clickX,clickY);
                    showBlank(eleGrid);
                }
                console.log(searched);
    //          判断胜利
                if(searched.length == boxHeight * boxWidth - mineNumber){
                    eleRestMine.innerText = 0;
                    clearInterval(intervalid);
    //              自动插上所有旗子
                    for(var j = 1; j < eleGrid.length; j++){
                        if(eleGrid[j].className == 'grid'){
                            eleGrid[j].className = 'flag';
                        }
                    }
                    console.log('你赢了');
                }
            }
            //          点击滚轮 或者同时按下左右键
            if((e.button == 1 || Math.abs(time1 - time2) < 100) && this.className == 'number'){
                var flagNum = 0;
                var tempArr = new Array();
                clickX = this.attributes.coor[0];
                clickY = this.attributes.coor[1];
                for(var j = 0;j < 8; j++){
                    tempX = clickX + around[j][0];
                    tempY = clickY + around[j][1];
//                  判断是否越界
                    if(tempX >= 0 && tempX <= boxHeight && tempY >= 0 && tempY <= boxWidth ){
//                      查找周围格子
                        for(var k = 1; k < eleGrid.length; k++){
                            if(eleGrid[k].attributes.coor.toString() == [tempX,tempY].toString()){
//                              统计旗子数目
                                if(eleGrid[k].className == 'flag'){
                                    flagNum++;
                                }
//                              统计grid坐标
                                else if(eleGrid[k].className == 'grid'){
                                    tempArr.push([tempX,tempY]);
                                }
                                break;
                            }
                        }
                    }
                }
//                  如果旗数与数字相等,触发快捷操作
                if(flagNum == mineMap[clickX][clickY]){
                    for(var k = 0;k < tempArr.length;k++){
//                      引爆地雷,游戏失败
                        if(mineMap[tempArr[k][0]][tempArr[k][1]] == '*'){
                            gameFail(eleGrid);
                        }
                        clickMap(mineMap,tempArr[k][0],tempArr[k][1])
                    }
                    showBlank(eleGrid);
                }
            }
        }
//      设置右键点击插旗
        setRight(eleGrid[i]);
    }
}

function setRight(ele){
    ele.onmousedown = function(e){
        if(e.button == 2){
            if( this.className == 'grid'){
                this.className = 'flag';
                eleRestMine.innerText--;
            }
            else if(this.className == 'flag'){
                this.className = 'grid';
                eleRestMine.innerText++;
            }
        }
    }
}

//设置难度数值
function diffiSet(diffi){
    switch (diffi){
        case 0:
        boxWidth = 9;
        boxHeight = 9;
        mineNumber = 10;
        break;
        case 1:
        boxWidth = 16;
        boxHeight = 16;
        mineNumber = 40;
        break;
        case 2: 
        boxWidth = 30;
        boxHeight = 16;
        mineNumber = 99;
        break;
        default:
        break;
    }
}

//设置box
function setBox(){
    eleBox.style.height = 20 * boxHeight + 'px';
    eleBox.style.width = 20 * boxWidth + 'px';
    eleBox.style.left = 'calc(50% - ' + (20 * boxWidth / 2) + 'px)';
}

//生成雷的分布
function createMine(x,y,width,height,mineNum){
    var mineArr = new Array()
    for(var i=0 ; i < height ; i++){          
        mineArr[i]=new Array(i);   
        for(var j=0 ; j < width ; j++){     
            mineArr[i][j] = 0;
        }
   }
    
    var count = 0;
    while(count != mineNum){
        randX = parseInt(Math.random()*height);
        randY = parseInt(Math.random()*width);
        if(randX != x && randY != y && mineArr[randX][randY] != '*'){
            mineArr[randX][randY] = '*';
            count++;
        }
    }
    return mineArr;
}

//生成数字
function showNumber(mineArr){
    for (var i = 0; i < mineArr.length ; i++){
        for (var j = 0 ; j < mineArr[i].length ; j++){
            if(mineArr[i][j] == 0){
                var mineCount = 0;
                for (var k = 0; k < 8; k++){
                    var aroundI = i + around[k][0];
                    var aroundJ = j + around[k][1];
                    if(aroundI >= 0 && aroundI < mineArr.length && aroundJ >= 0 && aroundJ < mineArr[0].length && mineArr[aroundI][aroundJ] == '*'){
                        mineCount++;
                    }
                }
                mineArr[i][j] = mineCount;
            }
        }
    }
    return mineArr;
}

//显示数字和空白区
function showBlank(eleGrid){
    for (var i = 0; i < searched.length; i++){
        for(var j = 1;j < eleGrid.length; j++){
            if(searched[i].toString() == eleGrid[j].attributes.coor.toString()){
                if(eleGrid[j].className == 'flag'){
                    searched.splice(i,1);
                    i--;
                }
                else{
                    eleGrid[j].className = 'number';
                    var temp = mineMap[eleGrid[j].attributes.coor[0]][eleGrid[j].attributes.coor[1]];
                    if(temp != 0){
                        eleGrid[j].innerText = temp ;
                    }
                }
            }
        }
    }
}

//点击
function clickMap(mineArr,x,y){
    if(x < 0 || x >= boxHeight || y < 0 || y >= boxWidth ){
        return;
    }
    for (var i = 0; i < searched.length; i++){
        if(x+','+y == searched[i].toString()){
            return;
        }
    }
    if(mineArr[x][y] == '*'){
        return ;
    }
    searched.push([x,y]);
    if(mineArr[x][y] == 0){
        for(var i = 0; i < 8; i++){
            clickMap(mineArr,x + around[i][0],y + around[i][1]);
        }
    }
    return ;
}
function gameFail(eleGrid){
    clearInterval(intervalid);
//              所有雷都显示出来
    for(var j = 0; j < boxHeight; j++){
        for(var k = 0;k < boxWidth ;k++){
            if( mineMap[j][k] == '*'){
                for(var m = 1;m < eleGrid.length; m++){
                    if(j+','+k == eleGrid[m].attributes.coor.toString()){
                        eleGrid[m].className = 'mine';
                    }
//                  将鼠标事件取消
                    eleGrid[m].onmouseup = "return false";
                    eleGrid[m].onmousedown = 'return false';
                }
            }
//                      如果不是雷,但是插了旗子,则显示X
            else{
                for(var m = 1;m < eleGrid.length; m++){
                    if(j+','+k == eleGrid[m].attributes.coor.toString()){
                        if(eleGrid[m].className == 'flag'){
                            eleGrid[m].style.backgroundImage = 'url(X.png)';
                        }
                        break;
                    }
                }
            }
        }
    }
}

//清空searched数组、所有格子并重置计时器
function clearGrid(){
    //重置时间
    clearInterval(intervalid);
    document.getElementById('time').innerText = '0:0';
//  清空搜索数组
    searched.splice(0,searched.length);
//清空原有的格子
    eleBox = document.getElementById('box');
    eleBox.innerHTML = '';
}
</script>

需要用到的图片

mine.png

flag.png

o.png

X.png

图片均来自网络,如果侵权,请联系本人进行删除

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354