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;
})();

    

    經過跟小夥伴們討論本身感受思路更加開闊了,也但願各位大神不吝賜教,不勝感激~
相關文章
相關標籤/搜索