邊做邊學jQuery系列08 - 益智拼圖遊戲

課程錄製時間: 2009年
益智拼圖遊戲

到目前為止,我們已學會了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行內完成,很不錯吧! 

【範例檔案下載】

歡迎推文分享:



 
RSS
【工商服務】
OrcsWeb: Windows Server Hosting
twMVC

關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。