邊做邊學 jQuery 系列 8- 益智拼圖盤


 

到目前為止,我們已學會了 jQuery 的基本運用技巧,可以開始做有一些有趣的東西。

不知道大家小時候有沒有玩過一種拼圖游戲,一個小盤子上有 8 塊或 15 塊的小圖塊,被放在 3x3 或 4x4 的空間中,由於存在一個空格,因此可以推動空格周圍的圖塊改變排列方式,最後的目標是要將圖塊組合成正確的排列。

沒有見過實體的話,不妨到 Amazon 上輸入 Slide Puzzle 查詢,可以看到一些照片。

這回,我們就來動手用 HTML+jQuery 實作一個 4x4 拼圖盤吧! 拼圖盤用網頁 HTML 實做,其實並不困難。從某個角度來看,HTML DOM 就是個物件導向的架構,一個個元素都是獨立的個體,能自行顯示,也可透過事件會對使用者的點選動作做出反應。圖塊移動的過程,不過就是座標位置的改變。

再進一步構思細節,我們可以想像,小圖塊就是一個個的 <div>,最外框再用一個 <div> 包起來當作拼圖盤,加上 CSS float:left 的設定,就能讓 <div> 排成 4x4 的配置。16 個 <div> 中有一個留白,其餘放入 1/16 大小的 <img> 圖塊。空白 <div> 周圍的 <div> 被點選時,就跟空白 <div> 交換位置,便可摸擬出將圖塊推到空白位置的效果。

經過這番講解,大家是不是覺得這個題材難易適中呢? 為了證明 jQuery 的簡潔,我們訂下一個目標,希望在 100 行內將這個網頁寫出來。

【基本排列】

第一步,我們要做出一個 <div> 內含 16 個 <div>,並讓它們排成 4x4 的配置。我們這裡運用了 append() 功能,在大 <div> 中新增 16 個小 <div>,將外層 div 設定寬高均為 480px,padding 0px,圖塊 div 則設成 118x118 的大小,加上四周各 1px 的框線,剛好變成 120x120,配合 float: left 的設定,480/120 整除為 4,就會變成 4x4 的排列。在框線設定上,我們將上 / 左設為白框線、下 / 右設為灰框線,讓圖塊呈現立體浮起的視覺效果。

<html xmlns="http://www.w3.org/1999/xhtml">   

  

<head>   

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />   

<title>jQuery Puzzle</title>   

<script type="text/javascript" src="js/jquery-1.3.1.js"></script>   

<style type="text/css">  

#dvPuzzle {   

     width: 480px; height: 480px;   

     border: solid 5px blue;   

     padding: 0px;   

}   

.PicCell {   

    width: 118px; height: 118px;   

    border-top: solid 1px white;   

    border-left: solid 1px white;   

    border-right: solid 1px gray;   

    border-bottom: solid 1px gray;   

    float: left;   

}   

</style>   

<script type="text/javascript">   

$(function() {   

    //填入 16 張圖   

    for (var i=0; i<16; i++)   

        $("#dvPuzzle").append("<div class='PicCell' id='Pic" + i + "'></div>");   

    //塗上顏色以強化展示效果   

    $(".PicCell").css("background-color", "yellow");   

});   

</script>   

</head>   

<body>   

<div id="dvPuzzle"></div>   

</body>   

</html>

【加上圖檔】

接著,我們準備一張 480x480 的照片,當作拼圖的對象。由於圖片要拆解成 16 個小圖塊,傳統做法可以利用修圖軟體分割照片,存成 16 個檔案。但在 CSS 中,還有其他解決方案。

我們先設定 <div>CSS 的 overflow 為 hidden 並限定 width / height,這樣子 <div> 內 <img> 尺寸較大超出範圍都會被隱藏。而對於 <img> 我們可以將 margin-top / margin-left 設定為負值,就可以自由控制圖檔案顯示的範圍。以下為裁切原則的解說;

因此我們在程式裡為圖塊 <div> 加入 <img> 並控制 margin-top / margin-left 顯示照片的不同部分。

$(function() {   

    //填入 16 張圖   

    for (var i=0; i<16; i++)   

    {   

        $("#dvPuzzle").append("<div class='PicCell' id='Pic" + i + "'><img src='Building.jpg' /></div>");    

        var row = parseInt(i / 4);   

        var col = i % 4;   

        $("#Pic" + i + " img").css("margin-left", col * -120 + 1).css("margin-top", row * -120 + 1);   

    }   

    //將左上角圖塊移除   

    $("#Pic0 img").remove();   

});

除了修改程式 .PicCell 要補上 overflow: hidden 設定,拼圖盤就大致成形了。

【演算邏輯】

接下來就是要加上圖塊點選後的反應,依設計,空格上、下、左、右的相鄰圖塊,在點選後會移到空格所在位置,而其原有位置會變成空格。換句話說,我們也可以想成,各圖塊被點選時,就去檢查其上、下、左、右是否為空格 <div>,若有則與其交換位置,否則不做任何動作。

歸納下來,我們需要預先算出每個圖塊的上、下、左、右的圖塊各是哪一個(如果圖塊位於邊或角,要找的的相鄰圖塊只有三塊或兩塊),於是決定將這部分提出來變成函數。我們將第一列依序編號為 0, 1、2、3、第二列則為 4、5、6、7、其餘類推,則我們用 $("#dvPuzzle div").index (任一圖塊),就可以算出圖塊的位置編號,進了算出所在的列、行數,當作找出上、下、左、右相鄰圖塊的依據。我們用一個物件 posConv 來保存由位置編號轉為列號、行號的速查表。另外,用一個函數 getNearPos(i),傳入任一位置編號,就可得到上、下、左、右相鄰圖塊位置編號組成的陣列。

$(function() {   

    //將位置轉成座標的換算表   

    var posConv = { };   

    //填入 16 張圖   

    for (var i=0; i<16; i++)   

    {   

        $("#dvPuzzle").append("<div class='PicCell' id='Pic" + i + "'><img src='Building.jpg' /></div>");    

        var row = parseInt(i / 4);   

        var col = i % 4;   

        $("#Pic" + i + " img").css("margin-left", col * -120 + 1).css("margin-top", row * -120 + 1);   

        //第 i 個換成第 row 列第 col 行   

        posConv[i] = { row:row, col:col };   

    }   

    //將左上角圖塊移除   

    $("#Pic0 img").remove();   

    //取得四周相鄰的位置   

    function getNearPos(i) {   

        var pool = [];   

        var row = posConv[i].row, col = posConv[i].col;   

        //toCheck 用來放入待比對的對象   

        if (row > 0) //上   

            pool.push((row - 1) *  4 + col);   

        if (row < 4) //下   

            pool.push((row + 1) * 4 + col);   

        if (col  >  0) //左   

            pool.push( i - 1);   

        if (col < 4) //右   

            pool.push(i + 1);   

        return pool;   

    }   

});

我們的準備功夫差不多了,接下來就可以處理圖塊的事件。我們為所有 ".PicCell" 掛上 Click 事件,其中的處理流程是先找出點選對象的位置編號,用 getNearPos() 找到相鄰的圖塊,逐一檢查它是不是空格。如果是 (id == "Pic0"),則利用 after() 進行位置對調。

//點選動作   

$(".PicCell").click(function() {   

    //找尋上下左右有沒有 Pic0,有則可以與它交換位置   

    //先找出元素是 16 個中第幾個?   

    var cells = $("#dvPuzzle div");   

    var i = cells.index(this);   

    var toCheck = getNearPos(i);   

    while (toCheck.length > 0) {   

        var j = toCheck.pop();   

        if (cells.eq(j).attr("id") == "Pic0") //為空白格,交換位子   

        {   

            //排序,必要時對調,讓 i < j   

            if (i > j) { var k = j; j = i; i = k; }   

            var ahead = cells.eq(i);   

            var behind = cells.eq(j);   

            var behindPrev = behind.prev();   

            //左右對調   

            if (Math.abs(i - j) == 1)   

                behind.after(ahead);   

            else //上下對調   

            {   

                ahead.after(behind);   

                behindPrev.after(ahead);   

            }   

            break;   

        }          

    }   

});

做到這裡,拼圖盤已經能依我們所想的透過點選將圖檔推至空格,基本操作已然成形。

最後,我們加上一個 <input type="button">,並在其點擊時觸發將拼圖盤弄亂的動作,拼圖盤就完成了!

$("input:button").click(function() {   

    for (var i = 0; i < 500; i++) {   

        var cells = $("#dvPuzzle div");   

        //找出空格所在位置,並取得其相鄰圖塊   

        var toMove = getNearPos(cells.index($("#Pic0")[0]));   

        cells.eq(toMove[ //由空格的相鄰圖塊擇一挪動   

            parseInt(Math.random() * toMove.length)    

        ]).click();   

    }   

});

檢視一下成果,最後可以玩的成品,HTML+CSS+ 程式,並包含程式註解,在 100 行內完成,很不錯吧!

【範例檔案下載】

我們在乎每一位開發人員的感受 : 到 (Twitter) 公開您的想法到 (論壇) 公開您的想法寫悄悄話告訴我們

回到首頁