Checkered game mobile version

The mobile version of the grid game has been developed faster than I expected.

 

Game interface preview

 

Adaptation difficulties

1. Sliding monitoring in four directions of touch events.

Solution: Found a tool class written by a great god to solve the problem perfectly. Thanks here.

 

2. The width and height of the grid are not fixed, and the value is percentage. The percentage returned by the browser will discard the decimal point and round up, resulting in deviation in the offset position of the comparison dictionary.

Solution: Set the unit of measurement and synchronously modify the offset in the comparison dictionary. When calculating the score, the original all-equal judgment is changed to a comparative deviation value.

 

3. The touch event is triggered multiple times.

Solution: Set the Boolean value touchingScreen to limit the touch can only be triggered once at a time.

 

4. Page layout adjustment.

The screen window is limited, and the score list and the board no longer appear at the same time. Because of the width limitation, the checkerboard grid uses a percentage width instead of a fixed width. Width percentage, setting height equal to width requires css skills. Use the style touch-action: none; on the grid so that no touch event will have the default behavior, but the touch event will still be triggered, so as to solve the problem of not being able to passively listen to the event preventDefault.

 

undo

1. The display of the score value is not centered on the grid.

2. The problem of scrolling on mobile browsing pages has not been solved.

3. When the game starts, the little girl will report an error when she moves normally and then returns to the starting point. Error location: this.chess[i].usedScore Error message: Cannot read property'usedScore' of undefined.

 

Main code index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <title>方格游戏</title>
    <link rel="shortcut icon" href="favicon.ico">
    <meta name="keywords" content="方格游戏">
    <meta name="description" content="方格游戏">
    <script src="vue.js"></script>
    <style>
        body{
            width: 100%;
            height:100%;
            margin:0;
            background: #fff;
            overflow: hidden;
            box-sizing: border-box;
            color:#1a2a65;
        }
        div.container{
            width: 100%;
            margin-top:80px;
            text-align: center;
        }
        div.container div.chess{
            width: 98%;
            margin: 0 auto;
            box-sizing: border-box;
            border:1px solid #ccc;
            display: flex;
            justify-content: flex-start;
            flex-wrap: wrap;
        }
        div.chess-grid{
            width:14.2857%;
            height:0;
            box-sizing: border-box;
            padding-bottom: 14.2857%; /* 让div的高等于宽 */
            line-height: 100%;
            text-align: center;
            color: #fff;
            /*应用 CSS 属性 touch-action: none; 这样任何触摸事件都不会产生默认行为,但是 touch 事件照样触发,从而解决无法被动侦听事件preventDefault*/
            touch-action: none;
        }
        /*雪人*/
        div.snowman{
            width:100%;
            padding-bottom: 100%; /* 让div的高等于宽 */
            background: url("head.jpg") no-repeat;
            background-size: 100% 100%;
            cursor: pointer;
        }
        /*雪人的家*/
        div.home{
            width:100%;
            padding-bottom: 100%; /* 让div的高等于宽 */
            background: url("home.jpg") no-repeat;
            background-size: 100% 100%;
        }

        div.container div.score{
            width: 98%;
            margin: 0 auto;
            box-sizing: border-box;
            border:1px solid red;
            background: #f7f7f7;
            text-align: center;
        }
        div.container div.score .title{
            font-size:18px;
            background:red;
            color:#fff;
            padding:10px;
        }

        /*游戏计时器*/
        div.head{
            width:100%;
            height:64px;
            box-sizing: border-box;
            text-align: center;
            position: fixed;
            top: 0;
            background: #f7f7f7;
            padding:12px;
        }
        div.head span,div.score span{
            font-size: 24px;
            font-weight: bold;
            color:red;
            padding:0 4px
        }
        div.game_start_box{
            display: flex;
            justify-content: center;
        }
        div.game_time_box{
            margin-right:20px;
            line-height: 38px;
        }
        div.game_time_box input{
            height:30px;
            width:40px;
            box-sizing: border-box;
        }
        div.game_start_btn{
            width:100px;
            height:40px;
            line-height: 40px;
            box-sizing: border-box;
            border:1px solid #ccc;
            color: #fff;
            cursor: pointer;
            background: green;
        }

        /*控制器*/
        div.control{
            width:100%;
            height:60px;
            line-height:60px;
            text-align: center;
            display: flex;
            justify-content: center;
            position: fixed;
            bottom: 0;
            background: #f7f7f7;
        }
        div.btn{
            width:200px;
            height:40px;
            line-height: 40px;
            box-sizing: border-box;
            border:1px solid #ccc;
            margin:10px;
            color: #fff;
            cursor: pointer;
        }
        div.auto{
            background: green;
        }
        div.refresh{
            background: red;
        }
    </style>
</head>
<body>
<div id="root">
    <div class="head" v-show="!showControl">
        <div v-show="!showTime" class="game_start_box">
            <div class="game_time_box">倒计时设置(10 ~ 60s):<input type="number" v-model="userSetGameTime" min="10" max="60"/></div>
            <div class="game_start_btn" @click="game_start">开始游戏</div>
        </div>

        <div v-show="showTime">倒计时<span>{
   
   {gameTime}}</span>秒 </div>
    </div>

    <div class="container">
        <div class="chess" v-show="!showControl">
            <div class="chess-grid"><div ref="snowman" class="snowman" :style="moveStyle" title="按键盘方向键移动我哦" @touchend="dealTouchEvent($event)"></div></div>
            <div class="chess-grid" v-for="(item,index) in chess" :key="index" :style="{background:item.background}">{
   
   {item.usedScore == 0 ? item.usedScore : item.score}}</div>
            <div class="chess-grid"><div class="home"></div></div>
        </div>

        <div class="score" v-show="showControl">
            <div class="title">战绩</div>
            <p v-for="(score,index) in gameScores" :key="index">第{
   
   {index+1}}战得分<span>{
   
   {score}}</span>分</p>
        </div>
    </div>

    <div class="control" v-show="showControl">
        <div class="btn auto" @click="game_review">本局复盘</div>
        <div class="btn refresh" @click="game_update">再来一局</div>
    </div>
</div>

<script src="touch.js"></script>
<script>
    new Vue({
        el: "#root",
        data: {
            touchingScreen:false,//是否正在触屏 限制 touch 触发频率
            showControl:false,//是否显示本局复盘、再来一局
            showTime:false,//是否显示倒计时 不显示倒计时的时候会显示开始游戏按钮
            is_get_home:false,
            gameTime:30,//系统设置的游戏时间 30秒一局游戏
            userSetGameTime:30,//用户设置的游戏时间
            gameScores:[],//单机多人游戏 游戏复盘,存储历史得分
            currentScore:0,//当前一局游戏的得分
            moveDown:0,
            moveRitht:0,
            chess:[],//存储随机的分数和背景色数组
            moveStyle:{transform:"translate(0,0)"},//偏移样式
            unit:0,//记量单位 移动端 移动的距离不再是固定的
            isDictionaryUpdate:false,//字典中一部分数据只用更新一次,因此设置flag,isDictionaryUpdate 是否已经同步更新数据,默认否。
            dictionary:[//棋盘各块偏移量对照字典 最后一格为终点格
                {"score":0," i":0, "r":1, "d":0},
                {"score":0, "i":1, "r":2, "d":0},
                {"score":0,"i":2, "r":3, "d":0},
                {"score":0,"i":3, "r":4, "d":0},
                {"score":0,"i":4, "r":5, "d":0},
                {"score":0,"i":5, "r":6, "d":0},
                {"score":0,"i":6, "r":0, "d":1},
                {"score":0,"i":7, "r":1, "d":1},
                {"score":0,"i":8, "r":2, "d":1},
                {"score":0,"i":9, "r":3, "d":1},
                {"score":0,"i":10, "r":4, "d":1},
                {"score":0,"i":11, "r":5, "d":1},
                {"score":0,"i":12, "r":6, "d":1},
                {"score":0,"i":13, "r":0, "d":2},
                {"score":0,"i":14, "r":1, "d":2},
                {"score":0,"i":15, "r":2, "d":2},
                {"score":0,"i":16, "r":3, "d":2},
                {"score":0,"i":17, "r":4, "d":2},
                {"score":0,"i":18, "r":5, "d":2},
                {"score":0,"i":19, "r":6, "d":2},
                {"score":0,"i":20, "r":0, "d":3},
                {"score":0,"i":21, "r":1, "d":3},
                {"score":0,"i":22, "r":2, "d":3},
                {"score":0,"i":23, "r":3, "d":3},
                {"score":0,"i":24, "r":4, "d":3},
                {"score":0,"i":25, "r":5, "d":3},
                {"score":0,"i":26, "r":6, "d":3},
                {"score":0,"i":27, "r":0, "d":4},
                {"score":0,"i":28, "r":1, "d":4},
                {"score":0,"i":29, "r":2, "d":4},
                {"score":0,"i":30, "r":3, "d":4},
                {"score":0,"i":31, "r":4, "d":4},
                {"score":0,"i":32, "r":5, "d":4},
                {"score":0,"i":33, "r":6, "d":4},
                {"score":0,"i":34, "r":0, "d":5},
                {"score":0,"i":35, "r":1, "d":5},
                {"score":0,"i":36, "r":2, "d":5},
                {"score":0,"i":37, "r":3, "d":5},
                {"score":0,"i":38, "r":4, "d":5},
                {"score":0,"i":39, "r":5, "d":5},
                {"score":0,"i":40, "r":6, "d":5},
                {"score":0,"i":41, "r":0, "d":6},
                {"score":0,"i":42, "r":1, "d":6},
                {"score":0,"i":43, "r":2, "d":6},
                {"score":0,"i":44, "r":3, "d":6},
                {"score":0,"i":45, "r":4, "d":6},
                {"score":0,"i":46, "r":5, "d":6},
                {"score":0,"i":47, "r":6, "d":6}
            ]
        },
        methods: {
            //生成随机分数棋格
            createChess:function(){
                //7*7方格,掐头去尾,需要生成47个随机数。
                var score;
                var bgColor;
                if(this.isDictionaryUpdate){
                    for(var i=0;i<47;i++){
                        // 按奇数偶数对应正负分值
                        if(i%2 ==0){
                            //正数 加分
                            score =Math.round(Math.random()*10)+2;
                            bgColor="#16a05d";
                        }else{
                            //负数 减分
                            score =-Math.round(Math.random()*6)-1;
                            bgColor="#e21918";
                        }
                        this.chess.push({
                            "score":score,
                            "usedScore":100,//随便指定一个现今规则不可能有的一个分数即可
                            "background":bgColor
                        });
                        //同步更新对照字典,存储分值。
                        this.dictionary[i].score = score;
                    }
                }else{
                    for(var i=0;i<47;i++){
                        // 按奇数偶数对应正负分值
                        if(i%2 ==0){
                            //正数 加分
                            score =Math.round(Math.random()*10)+2;
                            bgColor="#16a05d";
                        }else{
                            //负数 减分
                            score =-Math.round(Math.random()*6)-1;
                            bgColor="#e21918";
                        }
                        this.chess.push({
                            "score":score,
                            "usedScore":100,//随便指定一个现今规则不可能有的一个分数即可
                            "background":bgColor
                        });
                        //同步更新对照字典,存储分值。
                        this.dictionary[i].score = score;
                        //同步更新对照字典,计算准确坐标偏移量
                        this.dictionary[i].r = this.dictionary[i].r * this.unit;
                        this.dictionary[i].d = this.dictionary[i].d * this.unit;
                    }
                    this.isDictionaryUpdate=true;
                }

            },

            //处理手机触摸事件
            dealTouchEvent:function(e){
                e||event;
                this.touchingScreen = true;
                //使用的时候很简单,只需要像下面这样调用即可 up, right, down, left为四个回调函数,分别处理上下左右的滑动事件
                EventUtil.listenTouchDirection(e.target,true,this.upCallback, this.rightCallback, this.downCallback, this.leftCallback);
            },

            //touch的回调事件
            upCallback:function(){
                //当游戏倒计时显示时,即游戏还未结束,才能触发键盘事件,开始移动。
                if(this.showTime && this.touchingScreen){
                    this.touchingScreen = false;//触屏只触发一次
                    //向上移动
                    this.moveDown -=this.unit;
                    this.moveDown < 0 ? this.moveDown = 0 : this.moveDown;
                    this.moveStyle.transform = "translate("+this.moveRitht+"px,"+this.moveDown+"px)";
                    //根据偏移的位置,统计得分。
                    this.countScore(this.moveRitht,this.moveDown);
                }
            },
            rightCallback:function(){
                if(this.showTime && this.touchingScreen){
                    this.touchingScreen = false;//触屏只触发一次
                    //向右移动
                    this.moveRitht +=this.unit;
                    //判断界限值 不能超出棋盘活动
                    this.moveRitht > 6*this.unit ? this.moveRitht = 6*this.unit : this.moveRitht;
                    this.moveStyle.transform = "translate("+this.moveRitht+"px,"+this.moveDown+"px)";
                    //根据偏移的位置,统计得分。
                    this.countScore(this.moveRitht,this.moveDown);
                }
            },
            downCallback:function(){
                if(this.showTime && this.touchingScreen){
                    this.touchingScreen = false;//触屏只触发一次
                    //向下移动
                    this.moveDown +=this.unit;
                    this.moveDown > 6*this.unit ? this.moveDown = 6*this.unit : this.moveDown;
                    this.moveStyle.transform = "translate("+this.moveRitht+"px,"+this.moveDown+"px)";
                    //根据偏移的位置,统计得分。
                    this.countScore(this.moveRitht,this.moveDown);
                }
            },
            leftCallback:function(){
                if(this.showTime && this.touchingScreen){
                    this.touchingScreen = false;//触屏只触发一次
                    //向左移动
                    this.moveRitht -=this.unit;
                    //判断界限值 不能超出棋盘活动
                    this.moveRitht < 0 ? this.moveRitht = 0 : this.moveRitht;
                    this.moveStyle.transform = "translate("+this.moveRitht+"px,"+this.moveDown+"px)";
                    //根据偏移的位置,统计得分。
                    this.countScore(this.moveRitht,this.moveDown);
                }
            },

            //计算得分
            countScore:function(r,d){
                //遍历偏移量字典,根据当前所在的位置,获取对应的分值。
                //偏移量字典(len=48)比棋格(len=47)多了一个终点的位置信息。
                if(!(Math.abs(r - this.unit*6) < 7 && Math.abs(d - this.unit*6) < 7)){
                    //不在家,赋值false 防止回家后再离开的情形
                    this.is_get_home = false;
                    for(var i=0;i<48;i++){
                        //因为浏览器将数值取整后返回,所以,与实际值差值小于1.累积偏移差值小于1*7. Math.abs 取绝对值.
                        if(Math.abs(r - this.dictionary[i].r) < 7 && Math.abs(d - this.dictionary[i].d) < 7){
                            //游戏开始时,小女孩正常移动后再回到起点会报这个错误
                            //undo: this.chess[i].usedScore  有时会报错 Cannot read property 'usedScore' of undefined
                            if(this.chess[i].usedScore == 100){
                                this.currentScore += this.dictionary[i].score;
                                //分数一次性有效 走过的分数变为0.
                                //为了复盘,不直接改变分数,新分数存储到 usedScore
                                this.chess[i].usedScore = 0;
                            }
                        }
                    }
                }else{
                    //雪人到家
                    this.is_get_home = true;
                }

            },

            //计时器,每次时间-1,时间单位秒。
            timer:function(){
                this.gameTime -= 1
            },

            //用户点击游戏开始 创建定时器 显示倒计时
            game_start:function(){
                if(this.gameTime != this.userSetGameTime){
                    //系统设置的游戏时长和用户设置的游戏时长冲突,则使用用户设置的时长
                    this.gameTime = this.userSetGameTime;
                }
                this.showTime=true;
                timer1=setInterval(this.timer, 1000);
            },

            game_review:function(){
                this.parameter_reset();
                this.resetUsedScore();
            },

            //恢复棋盘 使用过的分数初始化
            resetUsedScore:function(){
                var len = 47;
                while(len--){
                    this.chess[len].usedScore = 100;
                }
            },

            //本局重玩,只需要重置参数。
            parameter_reset:function(){
                this.showControl=false;
                this.is_get_home = false;
                this.showTime=false;//先不显示倒计时,显示开始游戏按钮。
                this.moveRitht=0;
                this.moveDown=0;
                this.moveStyle.transform = "translate(0,0)";
                this.currentScore=0;
            },

            //页面初始化 游戏重新开始
            game_update:function(){
                this.parameter_reset();
                this.gameScores=[];
                this.chess=[];
                this.createChess();
            }
        },
        watch:{//监测游戏时间
            gameTime(){
                if(this.gameTime == 0){
                    clearInterval(timer1);
                    this.showControl=true;
                    this.showTime=false;//倒计时结束,关闭倒计时结果显示
                    if(this.is_get_home){
                        //游戏结束:倒计时结束,雪人进入小屋。当前得分计入。
                        this.gameScores.push(this.currentScore);
                    }else{
                        //游戏失败: 倒计时结束,但雪人未进入小屋。本局得分为0。
                        this.currentScore=0;
                        this.gameScores.push(0);
                    }
                }
            }
        },
        mounted(){
            //计量单位等于小方格的宽或高
            //获取的高度值约等于实际值,存在差值。获取的值取了实际值的近似整数。
            this.unit = this.$refs.snowman.offsetHeight;
            this.game_update();
        }
    })
</script>
</body>
</html>

The referenced code tool touch.js

var EventUtil = {
    addHandler: function (element, type, handler) {
        if (element.addEventListener)
            element.addEventListener(type, handler, false);
        else if (element.attachEvent)
            element.attachEvent("on" + type, handler);
        else
            element["on" + type] = handler;
    },
    removeHandler: function (element, type, handler) {
        if(element.removeEventListener)
            element.removeEventListener(type, handler, false);
        else if(element.detachEvent)
            element.detachEvent("on" + type, handler);
        else
            element["on" + type] = handler;
    },
    /**
     * 监听触摸的方向
     * @param target            要绑定监听的目标元素
     * @param isPreventDefault  是否屏蔽掉触摸滑动的默认行为(例如页面的上下滚动,缩放等)
     * @param upCallback        向上滑动的监听回调(若不关心,可以不传,或传false)
     * @param rightCallback     向右滑动的监听回调(若不关心,可以不传,或传false)
     * @param downCallback      向下滑动的监听回调(若不关心,可以不传,或传false)
     * @param leftCallback      向左滑动的监听回调(若不关心,可以不传,或传false)
     */
    listenTouchDirection: function (target, isPreventDefault, upCallback, rightCallback, downCallback, leftCallback) {
        this.addHandler(target, "touchstart", handleTouchEvent);
        this.addHandler(target, "touchend", handleTouchEvent);
        this.addHandler(target, "touchmove", handleTouchEvent);
        var startX;
        var startY;
        function handleTouchEvent(event) {
            switch (event.type){
                case "touchstart":
                    startX = event.touches[0].pageX;
                    startY = event.touches[0].pageY;
                    break;
                case "touchend":
                    var spanX = event.changedTouches[0].pageX - startX;
                    var spanY = event.changedTouches[0].pageY - startY;

                    if(Math.abs(spanX) > Math.abs(spanY)){      //认定为水平方向滑动
                        if(spanX > 30){         //向右
                            if(rightCallback)
                                rightCallback();
                        } else if(spanX < -30){ //向左
                            if(leftCallback)
                                leftCallback();
                        }
                    } else {                                    //认定为垂直方向滑动
                        if(spanY > 30){         //向下
                            if(downCallback)
                                downCallback();
                        } else if (spanY < -30) {//向上
                            if(upCallback)
                                upCallback();
                        }
                    }

                    break;
                case "touchmove":
                    //阻止默认行为
                    if(isPreventDefault)
                        event.preventDefault();
                    break;
            }
        }
    }
};

 

 

Guess you like

Origin blog.csdn.net/Irene1991/article/details/105067174