zoukankan      html  css  js  c++  java
  • javascrpit开发连连看记录-小游戏

        工作之余,总想做点什么有意思的东西。但是苦于不知道做什么,也就一直没有什么动作。在一个午饭后,跟@jedmeng和@墨尘聊天过程中,发现可以写一些小东西来练练手,有以下几点好处:
        1. 加强巩固前端相关知识
        2. 可以用一些平时项目中想用但没用的新东西
        3. 一块儿做相同的东西,可以分享各自不同的想法
     
        先来一张效果图,也可以来这里玩玩~
            
     
        接下来就介绍一下做这个小游戏,自己的一些步骤和思路:
     
        首先就是熟悉连连看的规则,为此还专门下载了一个app感受了一下,规则简单的说就是:找到两个相同的图标,但是之前的连线不能大于条线;
        其次就要准备棋盘的UI设计和结构的成型,这块儿我直接使用了@墨尘的设计;
        再次就看是思考js里需要哪些功能:
    • 初始化棋盘
    • 绑定连线事件
    • 处理连线的核心逻辑:判断是否可以连线
    • 成功、失败提示等其他功能
        最后完善细节基本上就搞定了。
     
        下面详细介绍一下实现连连看核心功能的一些想法:
        第一:初始化棋盘
            初始化棋盘的时候,需要一些原始数据,页面才能知道哪里需要展现什么。我这里一共有45个不同的图标,那么所有的数据都是从这45个数据中获取,这个地方遇到的问题是:怎么能保证每个图标出现是偶数个?我的做法是随机去除总个数一般的数据,然后每个数据存入两次,这样就能保证每个图标都是偶数个。
           
     /**
       * 获取源数据
       * @param {Number} total 获取个数,默认整个棋盘需要的棋子数
       * @returns {Array} 源数据
       */
      getRandomData: function(total) {
       total = total || this.cols * this.rows;
       var halfTotal = total/2;
       var pieces = [];
       for(var i = 0; i < halfTotal; i++) {
        //保证每个元素都是成双的
        var num = Math.floor(Math.random() * 45);
        pieces.push(num);
        pieces.push(num);
       }
       return pieces;
      }
    

      

            在展示数据的时候,需要时随机布局,这个时候我想到了数组的sort方法,可以创建规则进行排序,如果我的排序规则是随机的呢?是不是这样展示的时候就是随机的呢。当然可能还有更加好的随机做法,也希望可以分享给我。
          
      /**
       * 随机排序算法
       * @returns {number} -1或1
       */
      randomSort: function() {
       return Math.random() > 0.5 ? -1 : 1;
      }
    

      

            这样基本上就可以初始化一个随机排序的棋盘了。
     
        第二:绑定连线事件
            这个相对比较简单,需要注意的是,只有在点击两个点的时候才能进行判断是否可以连接,并且保证同时处于选中状态的不能超过两个。
     
        第三:连线的核心逻辑
                经过分析,所有的连线情况无非就是三种情况:连线没有拐点、连线只有一个拐点、连线有两个拐点
                连线没有拐点:
                这个很简单,这种情况就是两个点(x,y)和(m,n),两点必须同行或者同列,再通过一个循环就能知道两个能不能连起来了,伪代码大概是这样:
                
    //假设方法名是 dealOneLine(x,y,m,n)
                if(x == m || y == n) {
                    // y == n 是从x轴方向开始遍历
                    if(x ==m) {
                        for(i = mixY; i<=maxY;i++) {
                            //判断是否能连通
                        }
                    }
                }
    

      

                一个拐点:
                这种情况,必须得保证两点不是同行或者同列,这个时候只要检测要使得两点连通并且只能有一个拐点的话,这个拐点只有两个(x,n)和(m, y),所以就变成了带上这两个虚拟的点,能不能成功连通,伪代码是这样的:
                
    //假设方法名是  oneFoldPoint(x,y,m,n)
                if(this.dealOneLine(x, y, x, n) && this.dealOneLine(x, n, m, n)) {
    // console.log('纵向一个折点');
       }else if(this.dealOneLine( m, y, m, n) && this.dealOneLine( x, y, m, y)) {
    // console.log('横向一个折点');
        return this.isSamePic();
       }
    

      

                两个拐点:
                这种情况,可以理解为出发地能不能到达目的地,将一个点作为不动点,另外一点寻找正确的路径找到不动点。所以这个动点就得通过遍历上线左右四个方向找到适合自己路径。每移动一次,就得使用oneFoldPoint判断是否可以通过一个拐点连通,如果可以直接返回,如果不能连通就再移动。
                基本上这些就是连连看比较核心的算法了。在一块儿我们的讨论的过程中,@宁宁提到了两外一个思路,就是分别遍历(x,y)和(m,n)四个方向,找到可以连通的路径。这个我没有去实现,但是感觉可以行的通。(做这个小游戏的一好处已经体现出来了,感觉非常好)
     
           最后就是一些辅助功能,如:连接成功或是失败怎么表现、过关成功和失败处理、倒计时处理等等,这些都比较常规,也比较简单,这里就不再赘述了~
     
            交流过程中,@尊杰也提到了一些安全方面的问题,如:通过提供的接口,机器进行过关;我这里没有进行处理,倒是需要考虑的地方。@jedmeng提到,可以将逻辑、数据和UI进行分离,各自完成各自的逻辑,相互直接不能有太多影响,这个想法会在以后的开发中多多考虑这方面的问题。还存在一个问题就是:我可以记录路径,但是不能顺序记录路径,导致不能进行画线,我初步分析了一下,应该是我的算法没有办法准确的记录路径~这个@墨尘实现了,他跟我的算法不太一样,想了解的可以看这里
     
            这里是实现连连看的源码:
           
     /**
     * LinkGame 360产品连连看
     * @author 黑MAO
     * @time 2014-7-24
     * @desription 描述
     */
    (function() {
     'use strict';
     
     var isError = false;
     var clickTimes = 0;
     var timer = null;
     var pointOne = {
      x: 0,
      y: 0
     };
     var pointTwo = {
      x: 0,
      y: 0
     };
     var levelSum = 1;
     
     /**
      * LinkGame构造函数
      * @param {Number} rows 行数
      * @param {Number} cols 列数
      */
     function LinkGame(rows, cols) {
      if((rows && rows%2 != 0) && (rows && rows%2 != 0)) {
       isError = true;
      }
      this.rows = rows || 6;
      this.cols = cols || 7;
     }
     
     $.extend(LinkGame.prototype, {
      /**
       * 初始化函数
       * @param {Boolean} restart 是否是重新初始化
       * @param {Number} total 总倒计时时间,默认值200s
       * @param {Number} level 等级,默认是levelSum
       */
      init: function(restart, total, level) {
       total = total || 200;
       this.total = total;
       levelSum = level || levelSum;
       if(isError) return;
       this.restart = restart || false;
       this.initGameBoard();
       this.initEvent();
       //初始化时间条
       this.startTime(total, 300);
       this.setLevel(levelSum);
      },
     
      //初始化棋盘
      initGameBoard: function() {
       var $gameBoard = $('.game-board');
       this.restart && $gameBoard.html('');
       for(var y = 1; y <= this.rows; y++) {
        for(var x = 1; x <= this.cols; x++) {
         this.renderHtml(x, y);
        }
       }
       this.fillBoardData();
      },
     
      //重新初始化棋盘
      rebuildGameBoard: function() {
       var $items = $('.game-board').find('.item');
       this.fillBoardData($items.length);
      },
     
      //初始化事件
      initEvent: function() {
       var self = this;
       var $gameBoard = $('.game-board');
     
       $gameBoard.find('.item').on('click', function(e) {
        e.preventDefault();
        var pos = $(this).data('pos').split('_');
        clickTimes++;
        if(clickTimes > 2) return;
        if(pointOne.x > 0 || pointOne.y > 0) {
         pointTwo.x = pos[0];
         pointTwo.y = pos[1];
         $(this).addClass('current-one');
        }else {
         pointOne.x = pos[0];
         pointOne.y = pos[1];
         $(this).addClass('current-two');
        }
        self.dealGame(this);
       });
      },
     
      /**
       * 渲染棋子
       * @param {Number} x 横坐标
       * @param {Number} y 纵坐标
       */
      renderHtml: function(x, y) {
       var $gameBoard = $('.game-board');
       var left= 15+(x-1)*79;
       var top = 15+(y-1)*79;
       var pos = x + '_' + y;
       var $item = $('<div data-pos="' + pos + '" class="item pos' + pos + '" data-status="1"><div class="u"></div></div>');
       $gameBoard.append($item);
       $item.css({
        top: top,
        left: left
       });
      },
     
      /**
       * 填充数据
       * @param {Number} total 棋子总个数
       */
      fillBoardData: function(total) {
       var sourceArr = this.getRandomData(total).sort(this.randomSort);
       var $gameBoard = $('.game-board');
       $.each(sourceArr, function(i, value) {
        var className = 'p'+value;
        var pos = $gameBoard.find('.item').eq(i).data('pos')
        $gameBoard.find('.item').eq(i).data('pic', className).removeClass()
         .addClass(className)
         .addClass('item')
         .addClass('pos' + pos);
       });
      },
     
      /**
       * 获取源数据
       * @param {Number} total 获取个数,默认整个棋盘需要的棋子数
       * @returns {Array} 源数据
       */
      getRandomData: function(total) {
       total = total || this.cols * this.rows;
       var halfTotal = total/2;
       var pieces = [];
       for(var i = 0; i < halfTotal; i++) {
        //保证每个元素都是成双的
        var num = Math.floor(Math.random() * 45);
        pieces.push(num);
        pieces.push(num);
       }
       return pieces;
      },
     
      /**
       * 随机排序算法
       * @returns {number} -1或1
       */
      randomSort: function() {
       return Math.random() > 0.5 ? -1 : 1;
      },
     
      /**
       * 开始计时
       * @param {Number} total 总时间,单位秒
       * @param {Number} interval 间隔时间
       */
      startTime: function(total, interval) {
       interval = interval || 1000;
       var self = this;
       var $bar = $('.time-line').find('.bar');
       var remain = total;
       this.reset();
       clearInterval(timer);
       $('.remain-time').html(total);
       $bar.css('background-color', '#40b60f');
       timer = setInterval(function() {
        if(remain < 0.1) {
         clearInterval(timer);
         self.fail();
         return;
        }
        remain = remain-interval/1000;
        $bar.css('width', remain*100/total + '%');
        if(remain*100/total > 25 && remain*100/total < 50) {
         $bar.css('background-color', 'orange');
        }else if(remain*100/total <= 25){
         $bar.css('background-color', 'red');
        }
        $('.remain-time').html(Math.round(remain));
       },interval)
      },
     
      /**
       * 处理游戏逻辑
       * @param {Object} el 需要处理的元素
       */
      dealGame: function(el) {
       var x = pointOne.x * 1;
       var y = pointOne.y * 1;
       var m = pointTwo.x * 1;
       var n = pointTwo.y * 1;
       var self = this;
       if(clickTimes > 2) return;
       this.dealSelectStatus(el);
     
       //点击同一个点
       if(x == m && y == n) {
        self.showError();
        return;
       }
     
       if(pointTwo.x > 0 || pointTwo.y > 0) {
        if(self.isOk(x, y, m, n)) {
         self.showRight();
        }else {
         self.showError();
        }
       }
      },
     
      /**
       * 连线没有折点的处理
       * @param {Number} x 第一个点的横坐标
       * @param {Number} y 第一个点的纵坐标
       * @param {Number} m 第二个点的横坐标
       * @param {Number} n 第二个点的纵坐标
       * @returns {Boolean} 是否可以连通
       */
      noFoldPoint: function(x, y, m, n) {
     
       if(this.isOneLine(x, y, m, n)) {
        if(this.dealOneLine(x, y, m, n)) {
    // console.log('同行或同列');
         return this.isSamePic();
        }
       }
     
       return false;
      },
     
      /**
       * 处理在一条线上的情况
       * @param {Number} x 第一个点的横坐标
       * @param {Number} y 第一个点的纵坐标
       * @param {Number} m 第二个点的横坐标
       * @param {Number} n 第二个点的纵坐标
       * @returns {boolean} 是否可以连通
       */
      dealOneLine: function(x, y, m, n) {
       var $gameBoard = $('.game-board');
       var currentOnePos = $gameBoard.find('.current-one').data('pos');
       var currentTwoPos = $gameBoard.find('.current-two').data('pos');
       var minX = Math.min(x, m);
       var maxX = Math.max(x, m);
       var minY = Math.min(y, n);
       var maxY = Math.max(y, n);
       var isSuccess = true;
       if(x == m) {
        for(var b = minY; b <= maxY; b++) {
         var pos = x + '_' + b;
         var $item = $gameBoard.find('.pos'+ pos);
         var status = $item.data('status');
         if(status == 1 && pos != currentOnePos && pos != currentTwoPos) {
          isSuccess = false;
          break;
         }
        }
       }else if(y == n) {
        for(var a = minX; a <= maxX; a++) {
         var pos = a + '_' + y;
         var $item = $gameBoard.find('.pos'+ pos);
         var status = $item.data('status');
         if(status == 1 && pos != currentOnePos && pos != currentTwoPos) {
          isSuccess = false;
          break;
         }
        }
       }
     
       return isSuccess;
      },
     
      /**
       * 处理一个折点的情况
       * @param {Number} x 第一个点的横坐标
       * @param {Number} y 第一个点的纵坐标
       * @param {Number} m 第二个点的横坐标
       * @param {Number} n 第二个点的纵坐标
       * @returns {Boolean} 是否可以连通
       */
      oneFoldPoint: function(x, y, m, n) {
       //两种情况
       if(this.dealOneLine(x, y, x, n) && this.dealOneLine(x, n, m, n)) {
    // console.log('纵向一个折点');
        return this.isSamePic();
       }else if(this.dealOneLine( m, y, m, n) && this.dealOneLine( x, y, m, y)) {
    // console.log('横向一个折点');
        return this.isSamePic();
       }
       return false;
      },
     
      /**
       * 处理两个折点的情况
       * @param {Number} x 第一个点的横坐标
       * @param {Number} y 第一个点的纵坐标
       * @param {Number} m 第二个点的横坐标
       * @param {Number} n 第二个点的纵坐标
       * @returns {boolean} 是否可以连通
       */
      twoFoldPoint: function(x, y, m, n) {
       if(this.deepSearch(x, y, m, n, 'right')
        || this.deepSearch(x, y, m, n, 'down')
        || this.deepSearch(x, y, m, n, 'left')
        || this.deepSearch(x, y, m, n, 'up')) {
    // console.log('两个折点');
        return true;
       }
       return false;
      },
     
      /**
       * 深度搜索
       * @param {Number} x 第一个点的横坐标
       * @param {Number} y 第一个点的纵坐标
       * @param {Number} m 第二个点的横坐标
       * @param {Number} n 第二个点的纵坐标
       * @param {String} direction 搜索方向
       * @returns {Boolean} 是否可以连通
       */
      deepSearch: function(x, y, m, n, direction) {
       switch (direction) {
        case 'up':
         for(var i = y - 1; i >= 0; i--) {
          if(!this.isHasPic(x, i)) {
           var result = this.oneFoldPoint(x, i, m, n);
           if(!result) continue;
           return result;
          }else {
           return false;
          }
         }
        case 'down':
         for(var i = y + 1; i <= this.rows + 1; i++) {
          if(!this.isHasPic(x, i)) {
           var result = this.oneFoldPoint(x, i, m, n);
           if(!result) continue;
           return result;
          }else {
           return false;
          }
         }
        case 'left':
         for(var i = x - 1; i >= 0; i--) {
          if(!this.isHasPic(i, y)) {
           var result = this.oneFoldPoint(i, y, m, n);
           if(!result) continue;
           return result;
          }else {
           return false;
          }
         }
        case 'right':
         for(var i = x + 1; i <= this.cols + 1; i++) {
          if(!this.isHasPic(i, y)) {
           var result = this.oneFoldPoint(i, y, m, n);
           if(!result) continue;
           return result;
          }else {
           return false;
          }
         }
       }
      },
     
      /**
       * 检测是否存在棋子
       * @param {Number} x 横坐标
       * @param {Number} y 纵坐标
       * @returns {boolean} 是否存在
       */
      isHasPic: function(x, y) {
       var status = $('.game-board').find('.pos' + x + '_' + y).data('status');
       return status == 1 ? true : false;
      },
     
      /**
       * 检测两点是否同行或同列
       * @param {Number} x 第一个点的横坐标
       * @param {Number} y 第一个点的纵坐标
       * @param {Number} m 第二个点的横坐标
       * @param {Number} n 第二个点的纵坐标
       * @returns {boolean} 是否同行或同列
       */
      isOneLine: function(x, y, m, n) {
       if(x == m || y == n){
        return true;
       }
       return false;
      },
     
      /**
       * 检测选中两点是否相同棋子
       * @returns {boolean}
       */
      isSamePic: function() {
       var $gameBoard = $('.game-board');
       var $currentOne = $gameBoard.find('.current-one');
       var $currentTwo = $gameBoard.find('.current-two');
     
       if($currentOne.data('pic') == $currentTwo.data('pic')) {
        return true;
       }else {
        return false;
       }
      },
     
      /**
       * 检测两点是否可以连通
       * @param {Number} x 第一个点的横坐标
       * @param {Number} y 第一个点的纵坐标
       * @param {Number} m 第二个点的横坐标
       * @param {Number} n 第二个点的纵坐标
       * @returns {boolean}
       */
      isOk: function(x, y, m, n) {
       if(this.noFoldPoint(x, y, m, n) || this.oneFoldPoint(x, y, m, n) || this.twoFoldPoint(x, y, m, n)) {
        return true;
       }
       return false;
      },
     
      /**
       * 处理选中状态
       * @param {Object} el 选中元素
       */
      dealSelectStatus: function(el) {
       $(el).addClass('selected');
      },
     
      //提示功能
      hints: function() {
       var self = this;
       var switchFlag = false;
       var $items = $('.game-board').find('.item');
       $items.each(function(i, sourceItem) {
        var $sourceItem = $(sourceItem);
        var $remainItems = $sourceItem.siblings('.item');
        var sourcePos = $sourceItem.data('pos').split('_');
        $remainItems.each(function(i, item) {
         var $targetItem = $(item);
         var targetPos = $targetItem.data('pos').split('_');
         $sourceItem.addClass('current-one');
         $targetItem.addClass('current-two');
         if(self.isSamePic()) {
          pointOne = {
           x: sourcePos[0],
           y: sourcePos[1]
          };
          pointTwo = {
           x: targetPos[0],
           y: targetPos[1]
          };
          if(self.isOk(pointOne.x, pointOne.y, pointTwo.x, pointTwo.y)) {
           $targetItem.addClass('tip');
           $sourceItem.addClass('tip');
           self.reset();
           switchFlag = true;
           $sourceItem.removeClass('current-one');
           $targetItem.removeClass('current-two');
           return false;
          }
         }
         $sourceItem.removeClass('current-one');
         $targetItem.removeClass('current-two');
        });
     
        if(switchFlag) return false;
       });
       //没有可以提示的情况下,重新排序
       if(!switchFlag) {
        self.rebuildGameBoard();
        self.hints();
       }
      },
     
      //可正确连线
      showRight: function() {
       var self = this;
       var $gameBoard = $('.game-board');
       var $currentOne = $gameBoard.find('.current-one');
       var $currentTwo = $gameBoard.find('.current-two');
       var posOne = $currentOne.data('pos');
       var posTwo = $currentTwo.data('pos');
       $currentOne.addClass('remove').data('status', 0);
       $currentTwo.addClass('remove').data('status', 0);
       setTimeout(function() {
        $currentOne.removeClass().addClass('item-remove').addClass('pos'+posOne);
        $currentTwo.removeClass().addClass('item-remove').addClass('pos'+posTwo);
        self.reset();
        self.checkSuccess();
       }, 300);
      },
     
      //不可正确连线
      showError: function() {
       var self = this;
       var $gameBoard = $('.game-board');
       var $currentOne = $gameBoard.find('.current-one');
       var $currentTwo = $gameBoard.find('.current-two');
       $currentOne.addClass('error');
       $currentTwo.addClass('error');
       setTimeout(function() {
        $currentOne.removeClass('error').removeClass('current-one').removeClass('selected').remove('tip');
        $currentTwo.removeClass('error').removeClass('current-two').removeClass('selected').remove('tip');
        self.reset();
       }, 300);
      },
     
      //检测是否过关
      checkSuccess: function() {
       var $item = $('.game-board').find('div').filter('.item');
       if(!$item[0]) {
        clearInterval(timer);
        this.win();
       }
      },
     
      //过关成功通知
      win: function() {
                var self = this;
       var total = this.total;
                var newTotal = 0;
                clearInterval(timer);
       if(total > 100) {
                    this.showMsg('成功提示', '看来你很厉害,要不要再来一局!点击确定减少10秒钟');
                    newTotal = total - 10;
       }else if(total > 50) {
                    this.showMsg('成功提示', '果然不同反响!点击确定减少5秒钟');
                    newTotal = total - 5;
       }else if(total > 10){
                    this.showMsg('成功提示', '你要超神啊!点击确定减少1秒钟');
                    newTotal = total - 1;
       }
     
                $('.sure-btn').off('click').on('click', function(e) {
                    e.preventDefault();
                    self.init(true, newTotal);
                 levelSum++;
                    self.hideMsg();
                });
     
                $('.unsure-btn').off('click').on('click', function(e) {
                    e.preventDefault();
                    self.hideMsg();
                });
      },
     
      //过关失败通知
      fail:function () {
                var self = this;
                this.showMsg('失败提示', '不要气馁,点击确定重新开始,点击取消关闭');
                $('.sure-btn').off('click').on('click', function(e) {
                    e.preventDefault();
                    $('.restart').trigger('click');
                 levelSum = 1;
                    self.hideMsg();
                });
     
                $('.unsure-btn').off('click').on('click', function(e) {
                    e.preventDefault();
                    self.hideMsg();
                    $('.item').off('click').on('click', function() {
                        e.preventDefault();
                        self.showMsg('温馨提示', '您可以点击确定重新开始按钮');
                    });
                });
      },
     
      //设置关数
      setLevel: function(level) {
       $('.level').find('.level-num').html(level);
      },
     
      /**
       * 显示提示信息
       * @param {String} title 标题
       * @param {String} msg 信息内容
       */
            showMsg: function(title, msg) {
                $('.panel-title').html(title);
                $('.panel-msg').html(msg);
                $('.mask').show();
                $('.panel').show();
            },
     
      //隐藏提示信息
            hideMsg: function() {
                $('.mask').hide();
                $('.panel').hide();
             this.setLevel(levelSum);
            },
     
      //重置坐标、点击状态
      reset: function() {
       pointOne.x = pointOne.y = pointTwo.x = pointTwo.y = 0;
       clickTimes = 0;
      }
     });
     
     window.LinkGame = LinkGame;
    })();

        

        通过跟小伙伴们讨论自己感觉思路更加开阔了,也希望各位大神不吝赐教,不胜感激~
     
     
  • 相关阅读:
    排序之快速排序
    希尔排序
    大数据的乘法
    大数据的乘法实现——C语言
    js函数纪实
    【转】js中$含义及用法
    python基础操作
    git 常用指令
    Django框架学习记录
    【转】Java 字符串拼接 五种方法的性能比较分析 从执行100次到90万次
  • 原文地址:https://www.cnblogs.com/xiaoheimiaoer/p/3868571.html
Copyright © 2011-2022 走看看