[EZoApp] 打造九宮格拼圖遊戲 App

  • 7121
  • 0

摘要:[EZoApp] 打造九宮格拼圖遊戲 App

 

這篇要來介紹如何利用 EZoApp ,打造一個九宮格的拼圖遊戲,完成範例 ( 點選 preview 可以預覽 ):http://jqmdesigner.appspot.com/designer.html#&ref=5767590991364096

 

在製作遊戲之前,要先來規劃一下拼圖遊戲大概的流程:
 

  1. 一開始選擇想要拼圖的圖片

  2. 選擇後,先看一下大圖 ( 拼圖一定要先知道圖片長怎樣 )

  3. 點選圖片開始遊戲

  4. 遊戲時可以暫停或顯示數字提示

  5. 隨時可以重新開始遊戲

  6. 遊戲過程記錄「步數」與「經過時間」

 

有了流程之後,就按照步驟開始進行,由流程中可以發現,總共會經過三個頁面:「選擇 點選開始 遊戲」,因此我們先在 page 面板建立三個頁面,分別叫做 index、ready、home

 

先來編輯 index 這個頁面,在上方放入 header,然後在下方利用三個 grid 元件放入圖片,這裡我的圖片已經預先都用成正方形,避免長方形的圖片會變形,不過當你放上圖片之後,會發現長相差很大,因為還沒有套用 CSS。( 範例:http://jqmdesigner.appspot.com/designer.html#&ref=5686390339665920 )

 

 

 

 

套用樣式之後整體就會好看許多 ( 範例:http://jqmdesigner.appspot.com/designer.html#&ref=4694843586510848  )


CSS:

html,
body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
}
#index-menu img{
}
.menu-img img{
  margin:5px;
  width:90%;
}

 

接下來編輯一下 javascript,最主要是要讓點選的時候,會把這張圖片的網址傳給第二頁,這樣後面就可以直接抓取這張圖,首先建立一個全域變數 bgUrl,目的來儲存點選後的網址,這樣在第二第三頁都可以使用,然後設定點選 tap 事件動作,點選時將 img 的 src 記錄到 bgUrl 裡頭。( 範例:http://jqmdesigner.appspot.com/designer.html#&ref=6607042886238208 )

 

JS:


var bgUrl;
/*  
 ** #index
 */
(function () {
  $(document).on('pageshow', '#index', function () {

    var $menuImg = $('.menu-img');

    $menuImg.on('tap', function () {
      bgUrl = $(this).find('img').attr('src');
    });

  });
})();

 

完成第一頁之後就來看看第二頁,與第一頁相同的做法,先放上 header,然後再 ui-content 裡頭放入文字,同時預留 img 的位置出來,img 由 a 包著,目的在於點選圖片之後,會進入遊戲頁面,同樣的加上轉場的效果 data-transition="slide"


    <h2 is="gk-text" style="text-align:center;">準備好了嗎?</h2>
    <h3 is="gk-text" style="text-align:center;">點選圖片開始遊戲</h3>
    <h3 is="gk-text" style="text-align:center;"><a href="#home" data-transition="slide"><img src style="width:70%;" id="ready-img"></a></h3>

 

到這邊還沒完成,再來要補上第二頁的 javascript,因為要讀取剛剛第一頁傳進來的 bgUrl,為了避免每次進去出來,都還會看到上一張圖片,所以新增了 $readyImg.removeAttr('src'); 這一段來讓進入頁面時圖片是空的,可以看範例然後點選 preview 看看效果:http://jqmdesigner.appspot.com/designer.html#&ref=6279215682945024

JS:


(function () {
  $(document).on('pageshow', '#ready', function () {

    var $readyImg = $('#ready-img');

    $readyImg.removeAttr('src');
    $readyImg.attr({
      'src': bgUrl
    });

  });
})();

 

就這樣,前兩頁都完成了,要來最重點的第三頁,一如往常,先編輯第三頁的 HTML,上方放置 header,中間放九個 div,最下面放個 footer 並且塞入四個按鈕,並設計一下 CSS 樣式。( 範例:http://jqmdesigner.appspot.com/designer.html#&ref=4871840799391744 )

 

HTML:


<div id="home" data-role="page">
  <div data-role="header" data-position="fixed" data-theme="b" data-tap-toggle="false" id="boxheader">
    <h3>九宮格拼圖遊戲</h3>
  </div>
  <!-- 拼圖內容  -->
  <div role="main" class="ui-content">
    <div id="end-image"></div>
    <div id="boxArea">
      <div class="box"></div>
      <div class="box"></div>
      <div class="box"></div>
      <div class="box"></div>
      <div class="box"></div>
      <div class="box"></div>
      <div class="box"></div>
      <div class="box"></div>
      <div id="empty"></div>
    </div>
    <h3 id="completed">恭喜過關</h3>
    <h3 id="tap-num">你走了:<i>0</i> 步</h3>
    <h3 id="play-time">經過時間:<i>0</i> 秒</h3>
  </div>
  <div data-position="fixed" data-role="footer" data-theme="b" data-tap-toggle="false" style="padding:0; height:60px;">
    <div class="ui-grid-c" style="height:100%; margin:0; padding:0">
      <div class="ui-block-a" style="height:100%; margin:0; padding:0;">
        <a id="back-index" class="ui-btn ui-btn-icon-top ui-icon-bars" style="height:100%; width:100%; padding:0; line-height:80px; margin:0;" href="#index" data-transition="flip">回主選單</a>
      </div>
      <div class="ui-block-b" style="height:100%; margin:0;padding:0;">
        <a id="show-num" class="ui-btn ui-btn-icon-top ui-icon-eye" style="height:100%; width:100%; padding:0; line-height:80px; margin:0;">顯示數字</a>
      </div>
      <div class="ui-block-c" style="height:100%">
        <a id="pause" class="ui-btn ui-icon-clock ui-btn-icon-top" style="height:100%; width:100%; padding:0; line-height:80px; margin:0;">暫停</a>
      </div>
      <div class="ui-block-d" style="height:100%">
        <a id="restart" class="ui-btn ui-icon-refresh ui-btn-icon-top" style="height:100%; width:100%; padding:0; line-height:80px; margin:0;">重新開始</a>
      </div>
    </div>
  </div>
</div>

 

CSS:


#home {
  padding: 0;
  margin: 0;
  height: 75%;
  width: 100%;
}
.ui-content {
  padding: 0;
  margin: 0;
  height: 100%;
  width: 100%;
  background:#fff;
}
#boxArea {
  position: relative;
  margin-top: 15px;
  margin-bottom:20px;
}
#end-image{
  position: absolute;
  z-index: 3;
  background-repeat: no-repeat;
  border: 1px solid #000;
  margin-top: 15px;
  margin-bottom:20px;
  display:none;
}
.box {
  position: absolute;
  z-index: 2;
  background-repeat: no-repeat;
  border: 1px solid #000;
  cursor: pointer;
}
.box h2{
  margin:0;
  padding:0;
  background:rgba(0,0,0,.5);
  color:#fff;
  max-width:15%;
  font-size:12px;
  font-weight:normal;
  text-shadow:none;
  text-align:center;
  display:none;
}
#empty {
  position: absolute;
  background-color: #fff;
  z-index: 1;
  border: 1px solid #fff;
}
#tap-num,#play-time{
  text-align:center;
  padding:0;
  margin:0;
  font-size:16px;
}
#completed{
  color:#f00;
  text-align:center;
  display:none;
  padding:0;
  margin:0;
}
.redcolor{
  color:#f60;
}

 

完成之後應該可以看到大致的長相已經出來了:

 

接著要來寫最關鍵的 javascript,完整的 javascript 請看範例 ( http://jqmdesigner.appspot.com/designer.html#&ref=6248894153359360 ),以下將一步步介紹。


第一段主要用來定義會用到的變數,$開頭的是把 jquery 選擇的 DOM 做成變數,就不用再選取一次,windowWidth 和 windowHeight 目的在抓取螢幕長寬,讓九宮格的格子可以根據不同尺寸縮放,a 是一個亂數陣列,記錄圖片打散的順序,boxSize 是每個格子的尺寸,a1、a2、random1、random2 是用來打亂陣列使用,x0、y0、x1、y1 是記錄方格的位置,i、j 是 for 迴圈使用,$boxArea_x、$boxArea_y 是讓九宮格置中的計算位置變數,aIndex,、aEmpty 是用來判斷是否過關的變數,timer、 moveNum、 playtime 計算步數與時間的變數。


    var $box = $('.box'),
      $empty = $('#empty'),
      $boxArea = $('#boxArea'),
      $tapNum = $('#tap-num'),
      $playTime = $('#play-time'),
      $endImage = $('#end-image'),
      windowWidth = $('#home').width(),
      windowHeight = $('#home').height(),
      a = [],
      boxSize, a1, a2, random1, random2, x0, y0, x1, y1, i, j,
      $boxArea_x, $boxArea_y, aIndex, aEmpty, timer, moveNum, playtime;

 

變數定義完,接著就是遊戲主程式,這裡用一個 _home 把主程式包起來,因為我們有設計一個重新開始的按鈕,點選之後再度執行就會重來一次,在程式一開始要進行初始化的動作,也就是把步數與時間歸零,然後把陣列初始化,停止 timer 計時器,然後把按鈕狀態恢復,把過關畫面移除...等


    function _home() {
      //初始化
      moveNum = 0;
      playtime = 0;
      a = [0, 1, 2, 3, 4, 5, 6, 7];
      clearTimeout(timer);
      $('#pause').removeClass('ui-state-disabled');
      $('#show-num,#pause').removeClass('ui-btn-active').off('tap');
      $('#restart,#back-index').off('tap');
      $box.off('tap');
      $tapNum.find('i').text('0');
      $playTime.find('i').text('0');
      $('#completed').hide();
      $endImage.hide();

 

初始化完成後就是把剛剛圖片 bgUrl 傳給方塊的背景圖


      $endImage.css({
        'background': 'url(' + bgUrl + ')'
      });
      $box.css({
        'background': 'url(' + bgUrl + ')'
      });

 

設定計時器的 function , 待會在後面會介紹到


      //設定計時器
      _time();

 

根據 window 的長寬設置位置以及方塊的長寬,為了避免太大,所以有做了一個最小邊寬的判斷


      //設定 box 的位置
      if (windowWidth < windowHeight) {
        if (windowWidth >= 420) {
          boxSize = (420 - 40) / 3;
        } else {
          boxSize = (windowWidth - 40) / 3;
        }
        _boxPos(boxSize);
      } else {
        if (windowHeight >= 420) {
          boxSize = (420 - 40) / 3;
        } else {
          boxSize = (windowHeight - 40) / 3;
        }
        _boxPos(boxSize);
      }

 

把陣列做亂數排列,為什麼不直接使用 random 呢?因為若使用 random ,則很有可能出來的陣列會變成 1,2,3,4,5,6,8,7 而不是 1,2,3,4,5,6,7,8 ,若最後是 8,7 不是 7,8,不管如何玩都不可能過關,_addID 的 function 是用來加上 id,後面會介紹。

 


      //把原本的陣列打散
      for (i = 0; i < 20; i++) {
        random1 = Math.floor(Math.random(10) * 8);
        _randomNum(random1);
        a1 = a[random1];
        a2 = a[random2];
        a[random1] = a2;
        a[random2] = a1;
        if (i == 19) {
          _addID(); //加 id
        }
      }

 

設置按鈕的點選事件,後面會介紹。


      //點選事件
      $box.on('tap', _play);

 

設置 footer 按鈕的點選事件,最主要是要讓暫停按鈕會變色,同時會停止 timer 計時器,然後點選顯示數字可以顯示提示數字,這樣會比較容易過關。

 


      //footer按鈕事件
      $('#pause').on('tap', function () {
        if ($(this).hasClass('ui-btn-active')) {
          $(this).removeClass('ui-btn-active');
          _time();
          $box.on('tap', _play);
        } else {
          $(this).addClass('ui-btn-active');
          clearTimeout(timer);
          $box.off('tap');
        }
      });

      $('#show-num').on('tap', function () {
        if ($(this).hasClass('ui-btn-active')) {
          $(this).removeClass('ui-btn-active');
          $box.find('h2').hide();
        } else {
          $(this).addClass('ui-btn-active');
          $box.find('h2').show();
        }
      });

      $('#restart').on('tap', function () {
        _home();
      });

      $('#back-index').on('tap', function () {
        bgUrl = '';
        clearTimeout(timer);
      });
    }

 

看完了 _home() ,再來看看剛剛提到的一些 function 行為在做什麼,先看到 _play(),為什麼點選時知道旁邊是空的,要換過去呢?這裡我使用位置進行判斷,當旁邊的空格位置相差大於方格邊長,就表示方格旁邊不是空白,就不能移動,只是一個很簡單的判斷而以。


  function _play() {
      x0 = $(this).offset().left;
      y0 = $(this).offset().top;
      x1 = $empty.offset().left;
      y1 = $empty.offset().top;

      d = Math.abs(x0 - x1) + Math.abs(y0 - y1);

      if (d < (boxSize + 20)) {
        $(this).animate({
          "left": (x1 - $boxArea_x) + "px",
          "top": (y1 - $boxArea_y) + "px"
        }, 100);
        $empty.css({
          "left": (x0 - $boxArea_x) + "px",
          "top": (y0 - $boxArea_y) + "px"
        });

        //計算步數
        moveNum = moveNum + 1;
        $tapNum.find('i').text(moveNum);

 

利用 indexOf 可以知道陣列的哪個地方是哪些值,每次移動都會判斷一次陣列是否等於 1,2,3,4,5,6,7,8 ,如果是,就過關了。

 


        //判斷是否完成
        aIndex = $(this).attr('num') * 1;
        aEmpty = a.indexOf(8);
        a[a.indexOf(aIndex)] = 8;
        a[aEmpty] = aIndex;
        _checkCompleted(a);
      }
    };

 

設定方塊的位置,也是用視窗的長寬來做判斷。


    function _boxPos(s) {
      $boxArea.css({
        'width': (s * 3) + 'px',
        'height': (s * 3) + 'px',
        'margin-left': (windowWidth - (s * 3) - 4) / 2 + 'px'
      });
      $endImage.css({
        'width': (s * 3 + 4) + 'px',
        'height': (s * 3 + 4) + 'px',
        'margin-left': (windowWidth - (s * 3) - 4) / 2 + 'px',
        'background-size': (s * 3 + 4) + 'px ' + (s * 3 + 4) + 'px'
      })

      $boxArea_x = $boxArea.offset().left;
      $boxArea_y = $boxArea.offset().top;

      $box.css({
        'width': s + 'px',
        'height': s + 'px',
        'background-size': (s * 3) + 'px ' + (s * 3) + 'px'
      });
      $empty.css({
        'width': s + 'px',
        'height': s + 'px'
      })

 

先判斷 y 坐標 1~3、4~6、7~9

 

      $box.slice(0, 3).css({
        'top': '0'
      });
      $box.slice(3, 6).css({
        'top': s + 1 + 'px'
      });
      $box.slice(6, 9).css({
        'top': s * 2 + 2 + 'px'
      });

 

再判斷 x 坐標


      $box.eq(0).css({
        'left': '0'
      });
      $box.eq(3).css({
        'left': '0'
      });
      $box.eq(6).css({
        'left': '0'
      });
      $box.eq(1).css({
        'left': s + 1 + 'px'
      });
      $box.eq(4).css({
        'left': s + 1 + 'px'
      });
      $box.eq(7).css({
        'left': s + 1 + 'px'
      });
      $box.eq(2).css({
        'left': s * 2 + 2 + 'px'
      });
      $box.eq(5).css({
        'left': s * 2 + 2 + 'px'
      });
      $empty.css({
        'left': s * 2 + 2 + 'px',
        'top': s * 2 + 2 + 'px'
      });
    }

 

剛剛亂數的公式,目的不要讓 random1 等於 random2,如果相等就再亂數一次


    function _randomNum(r) {
      random2 = Math.floor(Math.random(10) * 8);
      if (random2 == r) {
        _randomNum(r);
      } else {
        return random2;
      }
    }

 

增加 id 與數字提示


    function _addID() {
      for (j = 0; j < a.length; j++) {
        $box.eq(j).attr({
          'id': 'box' + a[j],
          'num': a[j]
        }).html(
          '<h2>' + (a[j] + 1) + '</h2>'
        );
        if (j == (a.length - 1)) {
          return _bgPos(boxSize); //放入背景圖
        }
      }
    }

 

設定背景圖片位置,確保 1,2,3,4,5,6,7 組合起來是剛剛好完整的圖。

 

    function _bgPos(s) {

      a.push(8);

      $('#box0').css({
        'background-position': '0 0'
      });
      $('#box1').css({
        'background-position': (-s) + 'px 0'
      });
      $('#box2').css({
        'background-position': (-s * 2) + 'px 0'
      });
      $('#box3').css({
        'background-position': '0 ' + (-s) + 'px'
      });
      $('#box4').css({
        'background-position': (-s) + 'px ' + (-s) + 'px'
      });
      $('#box5').css({
        'background-position': (-s) * 2 + 'px ' + (-s) + 'px'
      });
      $('#box6').css({
        'background-position': '0 ' + (-s * 2) + 'px'
      });
      $('#box7').css({
        'background-position': (-s) + 'px ' + (-s * 2) + 'px'
      });
    }

 

計時器,每一秒會跑一次。


    function _time() {
      timer = setTimeout(function () {
        playtime = playtime + 1;
        $playTime.find('i').text(playtime);
        clearTimeout(timer);
        _time();
      }, 1000);
    }

 

判斷是否完成,就是由陣列去做判斷,完成之後會鎖住步數以及時間,同時出現原本的圖片和恭喜文字。


    function _checkCompleted(c) {
      if (c[0] == 0 && c[1] == 1 && c[2] == 2 && c[3] == 3 && c[4] == 4 && c[5] == 5 && c[6] == 6 && c[7] == 7) {
        clearTimeout(timer);
        $box.off('tap');
        $('#pause').off('tap').addClass('ui-state-disabled');
        $('#completed').show();
        $endImage.fadeIn(500);
        $tapNum.find('i').css({
          'color': '#f80'
        });
        $playTime.find('i').css({
          'color': '#f80'
        });
      }
    }

 

完成範例:http://jqmdesigner.appspot.com/designer.html#&ref=6248894153359360

就這樣,就完成了一個九宮格的遊戲囉!