Vue 快速开发之 - 网页版扫雷

前一阵子接触 Vue 和 MVVM 思想, 相比起 MVC 思想有质的改变, 前端开发不再深陷于繁琐的 DOM 操作,而是专注于数据(model层)和用户视图(view层), 将业务逻辑与 DOM 操作分离开来, 实现快速开发和高效代码复用. 另外作为扫雷骨灰级玩家, 闲来无事用周六下午的时间做了个扫雷自娱自乐

.

引入的库: 


        <!-- jQuery -->
        <script src="static/lib/jquery.min.js"></script>
        <!-- Vue -->
        <script src="static/lib/vue.min-2.6.10.js"></script>
        <!-- <script src="static/lib/vue-2.6.10.js"></script> -->
        <!-- bootstrap -->
        <link rel="stylesheet" href="static/lib/bootstrap-3.3.7-dist/css/bootstrap.min.css">

需要说明的是, 日常项目开发中, 使用 Vue 框架时并不提倡使用 jQuery, 这会使得项目开发重回 MVC 模式. 但在本例中 jQuery 只是为了对界面尺寸进行实时调整, 并没有参与到业务逻辑中, 纯 JavaScript 代码也可以实现, 这里使用 jQuery 只是为了省事(有些大材小用了), 即以下代码:


            $(document).ready(e => {
                // 界面尺寸调整
                $(window).resize(e => {
                    let w = $(".mine-field").outerWidth() / vm.m;
                    $(".mine-field span").css({
                        "font-size": w - 5,
                    });
                    $(".mine-field span,.mine-field img").css({
                        "width": w ,
                        "height": w ,
                    });
                });
            });
            // 禁用右键快捷菜单
            $(document).bind("contextmenu",false);

素材的准备, 有些选择困难症了, 这几张破图改来改去反而花费了不少时间:

样式:


            .mine-field {
                margin: 0 auto;
                padding: 0;
                line-height: 0;
            }
            .mine-field span {
                background-size: cover;
                background-repeat: no-repeat;
                margin: 0;
                padding: 0;
                line-height: 0;
            }
            .mine-field span.block {background-image: url("static/images/block.jpg") !important;}
            .mine-field span.mine {background-image: url("static/images/mine.jpg");}
            .mine-field span.flag {background-image: url("static/images/flag.jpg") !important;}
            .mine-field span.bflag {background-image: url("static/images/badflag.jpg") !important;}
            .mine-field span.boom {background-image: url("static/images/boom.jpg") !important;}

下面进入正题, 在 div#app 中放置扫雷界面和控制器:

<div class="mine-field" v-for="(mapi,i) in map">
    <span v-for="(mapij,j) in mapi" class="" :cnt="mapij.cnt" 
        @mouseup.stop="open($event,i,j)" @dblclick.stop="clear($event,i,j)"
        :style="{ 
            backgroundImage: 'url(\'static/images/' + mapij.cnt.mapijcnt() + '.jpg\')',
        }" 
        :class="{ 
            'empty': mapij.mine == 0, 
            'mine':  mapij.mine == 1, 
            'block': mapij.open == 0,
            'flag':  mapij.flag == 1,
            'boom':  mapij.boom == 1,
            'bflag': mapij.bflag == 1, 
        }">
        <img src="static/images/alpha.png">
    </span>
</div>
<div class="btn-group timer">
    <span class="btn btn-lg timer glyphicon glyphicon-time"></span>
    <span class="btn btn-lg timer">{{ timer }}</span>
</div>

<form class="form-inline btn-group controler">
    <label class="btn btn-default">m</label>
    <input class="btn btn-default" type="number" v-model="m">
    <label class="btn btn-default">n</label>
    <input class="btn btn-default" type="number" v-model="n">
    <label class="btn btn-default">x</label>
    <input class="btn btn-default" type="number" v-model="x">
    <label class="btn btn-default" @click="init">重置</label>
    <span class="btn btn-default glyphicon glyphicon-repeat" @click="init"></span>
</form>

<span class="btn-group pull-right timer">
    <span class="btn btn-lg timer glyphicon glyphicon-certificate"></span>
    <span class="btn btn-lg timer">{{ cnt }}</span>
</span>

使用 Vue 提供的 class 和 style 绑定 将每个块的视图与属性绑定起来.

初始化:
cnt: 每个块周围的地雷数,
m,n: 行数, 列数
x: 地雷数的均值, 本例采用了不定地雷数的模式, 每个块有地雷的概率为 r = x / ( m × n )

init(){
    this.map = [];
    this.cnt = 0;
    this.stimer = -1;
    this.clock = 0;
    this.timer = 0;
    if(this.m <= 1 || this.n <= 1 || this.x <= 1 ) this.m = this.n = this.x = 10;
    if(this.m * this.n <= this.x) this.x = this.m * this.n - 1;
    r = this.x / (this.m * this.n);
    for( let i = 0 ; i < this.m ; i++ ){
        this.map.push([]);
        for( let j = 0 ; j < this.n ; j++ ){
            let res = r < Math.random();
            let mine = 0;
            if(!res) {
                mine = 1;
                this.cnt += 1;
            }
            this.map[i].push({
                mine: mine,
                open: 0,
                boom: 0,
                cnt: -1,
                flag: 0,
                bflag:0,
            });
        }
    }
    // 计算每个块周围地雷个数
    for( let i = 0 ; i < this.m ; i++ ){
        for( let j = 0 ; j < this.n ; j++ ){
            this.map[i][j].cnt = this.count(i,j);
        }
    }
    this.cntempty = this.m * this.n - this.cnt;
},
count(x,y){
    if(this.map[x][y]==1) return -1;
    let cnt = 0;
    for( let i = x-1 ; i <= x+1 ; i++ ){
        for( let j = y-1 ; j <= y+1 ; j++ ){
            if(i==x&&j==y) continue;
            if(i < this.m && i >= 0 && j < this.n && j >= 0){
                cnt += this.map[i][j].mine;
            }
        }
    }
    return cnt;
},

当单击块时执行open:
右键时执行插旗/取消插旗
左键时打开地雷块, 若周围8个都没有雷,则将周围的块全部打开
cntempty记录剩余地雷数
如果碰巧第一个打开的是地雷, 则会自动重置地图

open(e,x,y){
    let el = this.map[x][y];
    if(e.which == 3) return this.flag(e,x,y);// 右键
    if(e.which != 1) return;// 非左键
    if(el.flag == 1) return;// 已插旗
    if(el.mine == 1 && this.stimer == -1){
        this.init();
        return this.open(e,x,y);
    }
    this.settimer(this.stimer == -1);
    if(el.open == 0){
        el.open = 1;
        if(el.mine == 1){
            el.boom = 1;
            return this.over(false);
        }else{
            this.cntempty--;
        }
        if(el.cnt == 0){
            for( let i = x-1 ; i <= x+1 ; i++ ){
                for( let j = y-1 ; j <= y+1 ; j++ ){
                    if(i==x&&j==y) continue;
                    if(i < this.m && i >= 0 && j < this.n && j >= 0){
                        this.open(e,i,j);
                    }
                }
            }
        }
        if(this.cntempty==0) this.over(true);
    }
},
flag(e,x,y){
    let el = this.map[x][y];
    if(el.open == 0){       // 未开
        el.flag = (el.flag + 1) % 2;
    }
},

双击时执行clear: 当周围标记块数与地雷数相等时,自动清除周围未标记的块

clear(e,x,y){
    let el = this.map[x][y];
    let cntflag = 0;
    for( let i = x-1 ; i <= x+1 ; i++ ){
        for( let j = y-1 ; j <= y+1 ; j++ ){
            if(i==x&&j==y) continue;
            if(i < this.m && i >= 0 && j < this.n && j >= 0)
                cntflag += this.map[i][j].flag;
        }
    }
    if(cntflag == el.cnt)
        for( let i = x-1 ; i <= x+1 ; i++ ){
            for( let j = y-1 ; j <= y+1 ; j++ ){
                if(i==x&&j==y) continue;
                if(i < this.m && i >= 0 && j < this.n && j >= 0 && el.flag == 0){
                    this.open(e,i,j);
                }
            }
        }
},

下面看看效果图:

动图

失败演示: 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值