利用js+canvas实现扫雷游戏

本文实例为大家分享了用js+canvas实现扫雷游戏的具体代码,供大家参考,具体内容如下

记录js学习后制作的第一关小游戏。

这里的代码还不够精简,许多地方偷懒没有封装,逻辑也有许多可以优化。

<body>
 
    胜利条件,找出所有地雷并标记
    <form action="javaScript:createContent()">
        <div id="message" style="color: red; display: none;">地雷数量必须小于地图大小xy的平方</div>
        <br /> 
        地图大小xy :<input id="xyNum" type="number" required="true" name="points" min="1" max="50"  /> 
        booNum:<input id="booNum" type="number" required="true" name="points" min="1" max="2500"/>
        <input type="submit" value="OK" : />
        <br /> 1. 输入宽度 <br />2. 输入地雷数(地雷数小于宽*宽) <br /> 3. 单击确定  <br />
        鼠标右键:<br />
        第一次:标记您的猜测<br />
        第二次: 取消标签<br />
    </form>
    <div id= 'game'>
 
    </div>
    <script src="./js/MarkObs.js"></script>
    <script src="./js/Isboo.js"></script>
    <script src="./js/lei.js"></script>
    <script>
    let xy = document.getElementById('xyNum');
    let boo = document.getElementById('booNum');
    let meg = document.getElementById("message");
    let div = document.getElementById('game');
 
    //获取输入的宽高和地雷数
    createContent = function (){
            // console.log(xy.value);
            // console.log(boo.value);
            let xyNum = xy.value; 
            let booNum = boo.value; 
            // console.log(Math.pow(xyNum,2));
            
            //判断输入是否合法
            if(Math.pow(xyNum,2)<boo.value){
                meg.style.display = 'block';
            }
            else {//绘制地图
                div.innerHTML = '';//清除上次div里的地图
                let game = new Game('game',xyNum,booNum);
            }
        }
    </script>
</body>

lei.js

/* 一个自定义原型数组方法  可以放到html里
二维数组查找
arr:要找数组第一第二项 找到返回下标,没有返回-1
PS:只要this数组和arr数组第一第二项的值相等,即为找到。
*/
Array.prototype.myindexOf = function(arr){
    for(let i=0;i<this.length;i++){
        
        if((this[i][0] == arr[0]) &&(this[i][1]==arr[1])){
            return i;
        }
    }
    return -1;
}
/*
初始化地雷图
id:传入绘制地图的容器id
xyNum:长||宽的格子数(地图固定正方形)
booNum:地雷数
*/ 
class Game {
    constructor(id,xyNum,booNum){
        this.xyNum = xyNum;
        this.booNum = booNum;
        this.id = id;
 
        this.booArrs = [];//保存雷的位置
        this.boox = -1;//地雷在x轴第几个块
        this.booy = -1;//地雷在x轴第几个块
 
        this.numArrs = [];//保存提醒数字的位置以及数字
        this.num = 0;//保存找到的提醒数字的个数
 
        this.markArrs = [];//保存标记位置的数组
 
        //单个块的宽高
        this.divw = 20;
 
        // 初始化画布
        this.initCanvas(xyNum);
        // 初始化地雷位置(地雷用-1代替,图片绘制麻烦)
        this.initBooxy(xyNum,booNum);
        // 初始化遮挡物
        this.initObs(xyNum);
 
        //判断是否胜利
        this.win();
    
 
    }
    /*初始化画布(包括网格)
    xyNum:传入需要绘制的宽格子数
    */ 
    initCanvas(xyNum){
        this.canvas = document.createElement('canvas');
        this.ctx = this.canvas.getContext('2d');
        
        //1为border
        this.canvas.width = (this.divw+1)*xyNum;
        this.canvas.height = (this.divw+1)*xyNum;
 
        // 绘制网格坐标
                // 获取canvas的宽高;
                let w = this.canvas.width;
                let h = this.canvas.height;
                // 绘制水平线
                for (let i = 1; i < h / 21; i++) {
                    this.ctx.beginPath();
                    this.ctx.lineTo(0,  21 * i) //起点
                    this.ctx.lineTo(w, 21 * i); //重点
                    this.ctx.stroke();
        
                }
                // h绘制垂直线
                for (let i = 1; i < w / 21; i++) {
                    this.ctx.beginPath();
                    this.ctx.lineTo(21 * i,0) //起点
                    this.ctx.lineTo(21 * i,h); //重点
                    this.ctx.stroke();
        
                }
                // ctx.stroke();
 
        // 放入容器
        this.div = document.getElementById(this.id);
        this.div.appendChild(this.canvas);
        
        // 绑定点击事件!!!
        this.canvas.addEventListener('mousedown',this.mouseDown.bind(this))//!!!!注意需要更改this指向,用bind
        
        // 清除鼠标右键的默认事件 “contextmenu“
        this.canvas.addEventListener("contextmenu",function(event){
            event.preventDefault()
        })
    }
 
    /*初始化地雷(包括提醒数字)
    xyNum:传入地图的宽的格子数
    booNum:传入地雷数
    */ 
    initBooxy (xyNum,booNum){
 
        // 随机地雷位置 并保存起来
        for(let i=0;i<booNum;i++){
 
            // x,y为地雷所在格子坐标,从0开始
            this.boox = parseInt(Math.random()*xyNum);
            this.booy = parseInt(Math.random()*xyNum);
 
            //避免雷的位置重复
            while(this.booArrs.myindexOf([this.boox,this.booy])!=-1){
                this.boox = parseInt(Math.random()*xyNum);
                this.booy = parseInt(Math.random()*xyNum);
            }
 
            this.booArrs.push([this.boox,this.booy])//!!!保存地雷的位置
            console.log(i,'x:'+this.boox,'y:'+this.booy);
 
            //绘制地雷
            this.ctx.beginPath();//不清楚可不可以删
            this.ctx.rect(this.boox*21,this.booy*21,20,20);
            this.ctx.fillStyle = 'red';
            this.ctx.fill();
        }
 
        // 绘制地雷位置周围提醒数字
            // 这里的逻辑可以优化,不提前绘制数字,在点击清除障碍物后再判断绘制。  
 
        /*
            想法一:在每个雷周围添加数字1,如果在多个雷交集处累加
            想法二:所有块依次判断周围是否有雷,有几个雷,就fillText()多少
            想法三:(一二结合)先找每个雷,该雷周围的8个块依次 判断周围有几个雷
        */ 
            // 这里为法二
       for(let i=0;i<xyNum;i++){
           for(let j=0;j<xyNum;j++){
               let num = 0;//提醒数字 ,每次重置为0
            
                if(this.booArrs.myindexOf([i-1,j-1]) !=-1){
                    num++;
                }
                if(this.booArrs.myindexOf([i-1,j]) !=-1){
                    num++;
                }
                if(this.booArrs.myindexOf([i-1,j+1]) !=-1){
                    num++;
                }
                if(this.booArrs.myindexOf([i,j-1]) !=-1){
                    num++;
                }
                if(this.booArrs.myindexOf([i,j+1]) !=-1){
                    num++;
                }
                if(this.booArrs.myindexOf([i+1,j-1]) !=-1){
                    num++;
                }
                if(this.booArrs.myindexOf([i+1,j]) !=-1){
                    num++;
                }
                if(this.booArrs.myindexOf([i+1,j+1]) !=-1){
                    num++;
                }
               
                //绘制提醒数字  
                if(num!=0 && (this.booArrs.myindexOf([i,j]) ==-1 )){//(this.booArrs.myindexOf([i,j]) ==-1)地雷不标注提示数字若。要标注需要+1(本身)
 
                this.ctx.font = '18px fasdg'
                this.ctx.fillStyle = '#000'
                this.ctx.fillText(num,i*(this.divw+1)+2,(j+1)*(this.divw+1)-2);//加1和j+1为测试结果,-+2是为了文本在格子里居中//y为文本中线坐标
                
                this.numArrs.push([i,j,num]);//i,j为提醒数字的块坐标,num为装数组里的值(myindexOf来判断)
                }
                // this.NUM = num;
           }
       }
        
 
        
    }
 
    /*初始化遮挡物
     xyNum:传入地图的宽的格子数
    */ 
   initObs(xyNum){
    for(let i=0;i<xyNum;i++){
        for(let j=0;j<xyNum;j++){
 
            this.ctx.beginPath();
            this.ctx.rect(i*21,j*21,20,20);
            // this.ctx.fillStyle = 'rgb(155,25,205,0.7)';//设置障碍物透明度可以方便查看雷的位置
            this.ctx.fillStyle = 'rgb(155,25,205,1)';//正常游戏时透明度为'1‘
            this.ctx.fill();
        }
    }
   }
 
 
/*点击事件在initCanvas中绑定*/  
   mouseDown(){
 
    //这里使用preventDefault,默认事件被没有消除,是因为触发鼠标右键的默认事件的事件类型不是mousedown,是contextmenu
    // event.preventDefault(); //ie9以下不兼容 
    
    this.clix = Math.floor(event.layerX/( this.divw+1));//this.divw为20是块的宽
    this.cliy = Math.floor(event.layerY/( this.divw+1)); 
 
    // 鼠标左键
    if(event.button==0){
        this.clearObs(this.clix,this.cliy);
 
 
    }
    
    // 鼠标右键
    else if(event.button==2){
        
        
        this.markObs(this.clix,this.cliy);
    }
   
   }
 
 
   /*扫雷*/  //这里的代码可以封装一下 为了方便此处没有封装
   clearObs(x,y){
    // console.log(x,y);点击坐标
 
    this.ctx.clearRect(x*21,y*21,20,20);//清除指定块
    
    // 点击到标记,点击到提醒数字,点击到地雷,点击到空白,
    if(this.markArrs.myindexOf([x,y])!=-1){  //点击到标记,重新覆盖
        this.ctx.rect(x*21,y*21,20,20);
        this.ctx.fillStyle = 'rgb(155,25,205,1)';
        this.ctx.fill();
        
        this.ctx.beginPath();
        this.ctx.fillStyle = 'red';
        this.ctx.fillText('?',x*(this.divw+1)+2,(y+1)*(this.divw+1)-2);
        this.ctx.fill();
 
    }
    else if(this.numArrs.myindexOf([x,y])!=-1){//点击到提醒数字
        let index = this.numArrs.myindexOf([x,y]);//下标
        let num = this.numArrs[index][2];//提醒数字
        this.ctx.fillText(num,x*(this.divw+1)+2,(y+1)*(this.divw+1)-2);//加1和j+1为测试结果,-+2是为了文本在格子里居中//y为文本中线坐标
        this.num++;
    }
    else if(this.booArrs.myindexOf([x,y])!=-1){//,点击到地雷,全部绘制
        console.log(this.booArrs.myindexOf([x,y]));
            //绘制全图
            // 绘制提醒数字
            for(let i=0;i<this.xyNum;i++){
                for(let j=0;j<this.xyNum;j++){
                    let num = 0;//提醒数字 ,每次重置为0
                     // if(booArrs.indexof([i-1,j-1]) != -1){//数组是对象这样永远-1
                     this.ctx.clearRect(i*21,j*21,20,20);
                     if(this.booArrs.myindexOf([i-1,j-1]) !=-1){
                         num++;
                     }
                     if(this.booArrs.myindexOf([i-1,j]) !=-1){
                         num++;
                     }
                     if(this.booArrs.myindexOf([i-1,j+1]) !=-1){
                         num++;
                     }
                     if(this.booArrs.myindexOf([i,j-1]) !=-1){
                         num++;
                     }
                     if(this.booArrs.myindexOf([i,j+1]) !=-1){
                         num++;
                     }
                     if(this.booArrs.myindexOf([i+1,j-1]) !=-1){
                         num++;
                     }
                     if(this.booArrs.myindexOf([i+1,j]) !=-1){
                         num++;
                     }
                     if(this.booArrs.myindexOf([i+1,j+1]) !=-1){
                         num++;
                     }
                     
     
                    
                     //绘制提醒数字
                     if(num!=0 && (this.booArrs.myindexOf([i,j]) ==-1 )){//(this.booArrs.myindexOf([i,j]) ==-1)地雷不标注提示数字若要标注需要+1(本身)
     
                     this.ctx.font = '18px fasdg'
                     this.ctx.fillStyle = '#000'
                     this.ctx.fillText(num,i*(this.divw+1)+2,(j+1)*(this.divw+1)-2);//加1和j+1为测试结果,-+2是为了文本在格子里居中//y为文本中线坐标
                     
                     this.numArrs.push([i,j,num]);//i,j为提醒数字的块坐标,num为装数组里的值(myindexOf来判断)
                     }
                     // this.NUM = num;
                }
            }
            // 绘制地雷
            for(let i=0;i<this.booArrs.length;i++){
                this.ctx.fillStyle = 'red';
                this.ctx.rect(this.booArrs[i][0]*21,this.booArrs[i][1]*21,20,20);
                this.ctx.fill(); 
            }
            this.ctx.clearRect((this.xyNum-1)*21,(this.xyNum-1)*21,20,20);//每次最后一个都会变红,不知道原因,此处专门删除。
           
            alert('你惊动了雷雷');
 
            
    }
 
    else {
 
        this.isboo(this.ctx,x,y,this.booArrs,this.numArrs,this.markArrs,this.xyNum);
 
 
    } 
   }
 
   win (){//标记数组==地雷数组
    this.tim = setInterval(()=>{
        if(this.booArrs.length ==this.markArrs.length){
            for(let i=0;i<this.booNum;i++){
                
                if( true == this.booArrs.some(()=>{
                    return this.markArrs.myindexOf(this.booArrs[i])!=-1;
                })){
                   this.booNum--;
                }
                if(this.booNum==0){
                    clearInterval(this.tim);
                    alert('you are win');
                    }
            }
        }
    },10)
 
   }
   isboo(ctx,x,y,booArrs,numArrs,markArrs,xyNum){
       new Isboo(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
   }
 
 
    /*标记 
    */
   markObs(x,y){  
       console.log(x,y);
    new MarkObs(this.ctx,x,y,this.booArrs,this.divw,this.markArrs);
    
 
   }
 
   
}

isboo.js

Array.prototype.myindexOf = function(arr){
    for(let i=0;i<this.length;i++){
        
        if((this[i][0] == arr[0]) &&(this[i][1]==arr[1])){
            return i;
        }
    }
    return -1;
}
/*
这里解决点击到空白格子时,把周围的空白格一起显示。此处的逻辑可以再优化.
ctx:布局
x,点击位置
y,点击位置
booArrs:炸弹的位置数组
numArrs:提示数的位置
markArrs:标记的位置
*/ 
class Isboo {
    constructor(ctx,x,y,booArrs,numArrs,markArrs,xyNum){
        this.x = x;
        this.y = y;
        
        // 判断有没有提醒数字
        this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
        this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
        this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
        this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
    }
    isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum){
        if((numArrs.myindexOf([x,y])==-1)&&(x<xyNum)&&(markArrs.myindexOf([x,y])==-1)){
            ctx.clearRect(x*21,y*21,20,20);
            x+=1;
            this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
            // this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
            this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
            this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
        }else {
            return ;
        }
    }
    isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum){
        if((numArrs.myindexOf([x,y])==-1)&&(x>=0)&&(markArrs.myindexOf([x,y])==-1)){
            ctx.clearRect(x*21,y*21,20,20);
            x-=1;
            // this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
            this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
            this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
            this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
        }else {
            return ;
        }
    }
    isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum){
        if((numArrs.myindexOf([x,y])==-1)&&(y<xyNum)&&(markArrs.myindexOf([x,y])==-1)){
            ctx.clearRect(x*21,y*21,20,20);
            y+=1;
            // this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
            // this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
            this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
            // this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
 
        }else {
            return ;
        }
    }
    isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum){
        if((numArrs.myindexOf([x,y])==-1)&&(y>=0)&&(markArrs.myindexOf([x,y])==-1)){
            ctx.clearRect(x*21,y*21,20,20);
            y-=1;
            // this.isbool(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
            // this.isboor(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
            // this.isboot(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
            this.isboob(ctx,x,y,booArrs,numArrs,markArrs,xyNum);
        }else {
            return ;
        }
    }
 
}

MarkObs.js

Array.prototype.myindexOf = function(arr){
    for(let i=0;i<this.length;i++){
        
        if((this[i][0] == arr[0]) &&(this[i][1]==arr[1])){
            return i;
        }
    }
    return -1;
}
/*
ctx:布局
x,点击位置
y,点击位置
booArrs:炸弹的位置数组
divw:各自宽度
markarrs:标记数组
*/ 
class MarkObs{
    constructor(ctx,x,y,booArrs,divw,markarrs){
        this.markObs(ctx,x,y,booArrs,divw,markarrs);
    }
 
    markObs(ctx,x,y,booArrs,divw,markarrs){
 
        if(markarrs.myindexOf([x,y])==-1){//如果标记数组里没有该地址,则标记,并添加进数组
        ctx.beginPath();
        ctx.fillStyle = 'red';
        ctx.fillText('?',x*(divw+1)+2,(y+1)*(divw+1)-2);
        markarrs.push([x,y]);
        }else {//如果标记数组里有该地址,则取消标记,并从数组中删除
            ctx.clearRect(x*(divw+1),y*(divw+1),divw,divw);
            ctx.beginPath();
            ctx.rect(x*21,y*21,20,20);
            ctx.fillStyle = 'rgb(155,25,205,1)';
            ctx.fill();
            markarrs.splice((markarrs.myindexOf([x,y])),1);
        }
    }
 
}

页面效果

初始化障碍物设置了透明度时

正常游戏时

这里点击右键标记后忘了把填充颜色设置回来。所以后面变红。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • js实现简单扫雷

    本文实例为大家分享了js实现简单扫雷的具体代码,供大家参考,具体内容如下 实现效果: 总体思想:这个扫雷难点主要就两点:第一点就是雷的随机分布,我这里使用js动态生成了10X10的table表格,然后设定生成20个雷,然后在生成每个格子代表的数字,雷我们用9表示,因为0-8要被其他非雷格子使用,然后我们只要一格是雷区那么周围的格子都加一,这样我们的雷区就生成好了,然后玩法的编写主要难点就是在当点击数字为0时的格子时要把它周围的0区也显示出来,因此这里我们使用递归来实现 1.界面的生成 //生成界

  • HTML+JavaScript实现扫雷小游戏

    本文实例为大家分享了JavaScript实现扫雷小游戏的具体代码,供大家参考,具体内容如下 工具:Sublime Text / Dreamweaver /Hbuilder <!doctype html> <html> <head> <meta charset="utf-8"> <title>SaoLei</title> <style type="text/css"> table {

  • js实现扫雷小程序的示例代码

    初学javascript,写了一个扫雷程序练练手! 扫雷规则及功能 扫雷想必大家都不陌生,就是windows上点击排雷的小游戏,它的主要规则有 1.左键点击显示当前格子是否为雷,如果为雷的话,GameOver啦,如果不是雷的话,这个格子会显示周围八个格子内的雷数量. 2.鼠标右键标记,标记可能的雷,标记了之后取消需要再次右键点击该格子,左键无效果. 3.鼠标中键(滚轮)按下后,快捷扫雷(如果周围雷数和未被标记且未被翻开的格子相等,会将这些格子一并翻开) 主要功能基本完全复刻了windows7扫雷

  • JavaScript版经典游戏之扫雷游戏完整示例【附demo源码下载】

    本文实例讲述了JavaScript扫雷游戏.分享给大家供大家参考,具体如下: 翻出年初写的游戏贴上来,扫雷相信大家都玩过,先上图: 源码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.or

  • js实现扫雷源代码

    经过一段时间学习,对javascript有了一个初步的了解自己制作了一个扫雷,源代码+详细注释放在后面,先看下效果图. 初始化界面: 游戏界面: 难易程度切换: 游戏结束: 思路 采用构造函数的形式进行全局开发 生成游戏棋盘 利用双层for循环创建设定的棋盘大小 为每个单元格的dom元素创建一个属性,该属性用于保存单元格的所有信息,如x,y坐标,value,是否为雷等 随机生成炸弹 利用随机数,随机生成炸弹x,y坐标,并将符合该坐标信息的单元格的属性更改为雷 炸弹是在用户第一次点击的时候生成,防

  • js版扫雷实现代码 原理不错

    效果图:以下代码复制粘贴到记事本后保存为.html文件,打开后会出现提示,右击允许即可: 寻雷-----by 魅月 var data=new Array( [0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0

  • 使用纯javascript实现经典扫雷游戏

    很久以前写的 当时都没写注释的 刚加上了 (尼玛,好多自己都不认识了 ... ) 不足的地方就是本来想写个游戏排名的统计的,等有空了再加上(好像每次都这么说 然后就等好久好久...) 还有就是没有实现:点击第一个格子不能是雷的功能 <style> ul{padding:0;list-style:none;} #mine{overflow:hidden;width:30px;height:30px;border:1px solid #966;} #mine li{float:left;width

  • javascript 扫雷游戏

    "); for (var i = 0; i "); for(var j = 0; j   "); buffer.append(" "); } buffer.append(" "); var workarea = document.getElementById("workarea"); workarea.innerHTML = buffer.toString(); mine(); // lay mines at the

  • 分享自己用JS做的扫雷小游戏

    引用了jQuery,节省了很多鼠标点击上的判断.界面显然都是照搬Windows的扫雷啦,详细的内容注释里都有,我就不啰嗦啦~ 先上截图~ 引用了jQuery,节省了很多鼠标点击上的判断 界面显然都是照搬Windows的扫雷啦 详细的内容注释里都有,我就不啰嗦啦~ JS部分 var mineArray, //地雷数组 lastNum, //剩余雷数 countNum, //未被揭开的方块数 inGame = 0, //游戏状态,0为结束,1为进行中,2为初始化完毕但未开始 startTime; /

  • JavaScript制作windows经典扫雷小游戏

    代码其实很简单,这里就不多废话了 <html> <head> <meta http-equiv="Content-Language" content="zh-cn"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>扫雷-JavaScript Mine Sweeper

随机推荐