我有些在懷疑這是否會是自己發的最後一篇正經文章
最近的心情雖然平穩
也有玩 Sid Meier的文明六和Dungeon maker
但感覺自己對遊戲沒什麼積極、正面想法與靈感(靈感從以前就沒有啦)了
遊戲簡介
4*4的格子,有十五個可移動方塊(剩下的一格空出來用來挪動)
隨意打亂順序,然後透過移動把方塊順序組回來
可以買個象棋,把其中一層的象棋拿掉就可以玩了
不過這樣講可能蠻抽象的
遊戲做好的話大概就知道效果如何了
其實這題以前畢業專題有寫過
想說用來背單字
不過其實這是有些危險的
因為sliding puzzle 2*2跟3*3的狀況下,若任意打亂是有可能解不出來的
4*4確定可以解出來,但5*5以上就不清楚了
怎麼判斷sliding puzzle有沒有解應該是比實作sliding puzzle程式困難些
此外sliding puzzle也是可以用電腦來解的
當然現階段我還不確定自己做不做得出來
不過那時主要是看大陸一教學網站做的
唔,為何這程式需要看教學呢?
因為那時對Adobe Flash不熟
用javascript的話應該是不用看教學了
應該啦
我也不確定
那就開始吧!!
首先先來處理盤面顯示與盤面儲存
這是未打亂順序顯示的程式碼(網頁檔)
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<body>
<style>
canvas{
cursor:none;
}
</style>
<canvas id="myCanvas" width="640" height="480"></canvas>
<script>
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var img= new Image();
img.src="魂魄妖夢_big.png";
//等圖完整載入後再繪製,不然會有空白畫面,
//不寫的話重新整理也可以
//不過圖越大越有可能重新整理後還是沒圖片
//不寫的話要重新整理到圖載完時才會有畫面
img.onload =
function()
{
re_draw();
}
var board_width = 4;
var board_height = 4;
var board = [];
for(var i=0;i<board_width;i++)
{
board[i] = [];
for(var j=0;j<board_height;j++)
{
board[i][j] = i*board_width+j;
}
}
function re_draw()
{
for(var i=0;i<board_width;i++)
{
for(var j=0;j<board_height;j++)
{
//ctx.drawImage(img,0*640/4,1*480/4,640/4,480/4,2*640/4,2*480/4,640/4,480/4);
//不除4和取4的餘數的話,會超過畫布範圍變成一片空白
//不轉整數的話,執行畫面看起來會有些扭曲
ctx.drawImage(img,parseInt(board[i][j]/4)*c.width/4,board[i][j]%4*c.height/4,c.width/4,c.height/4,i*c.width/4,j*c.height/4,c.width/4,c.height/4);
}
}
}
</script>
</body>
</html>
執行結果如下(因為沒做框線,所以看不出來是分成十六格)
-------------------------------------第二步-------------------------------------
接著要打亂順序(透過陣列來做)
打亂的方式依舊是以前寫俄羅斯方塊期間曾提到的 楊式洗牌法()
也就是不做抽牌,先把牌依順序放好,然後再由左至右隨機交換牌的洗牌方式
(一陣子沒寫,我要看下過往網頁回憶下)
啊啊啊,我的記憶力衰退了,在這篇
應該是Knuth洗牌法(也就是Fisher–Yates shuffle)
不過在javascript中用這方法的話
對於二維陣列有麻煩
因此程式要修正下
由於洗牌通常都是一維陣列
然後javascript的二維陣列寫法特別麻煩
因此把原先程式的二維陣列轉一維陣列後
就能順利打亂了
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<body>
<canvas id="myCanvas" width="640" height="480"></canvas>
<script>
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var img= new Image();
img.src="魂魄妖夢_big.png";
//等圖完整載入後再繪製,不然會有空白畫面,
//不寫的話重新整理也可以
//不過圖越大越有可能重新整理後還是沒圖片
//不寫的話要重新整理到圖載完時才會有畫面
img.onload =
function()
{
re_draw();
}
var board_width = 4;
var board_height = 4;
var board = [];
for(var i=0;i<board_width*board_height;i++)
{
board[i]=i;
}
function re_draw()
{
shuffle(board);
for(var i=0;i<board_width*board_height;i++)
{
//不除4和取4的餘數的話,會超過畫布範圍變成一片空白
//不轉整數的話,執行畫面看起來會有些扭曲
ctx.drawImage(img,parseInt(board[i]/4)*c.width/4,board[i]%4*c.height/4,c.width/4,c.height/4,parseInt(i/4)*c.width/4,i%4*c.height/4,c.width/4,c.height/4);
}
}
function shuffle(array) {
var m = array.length, t, i;
while (m) {
i = Math.floor(Math.random() * m--);
t = array[m];
array[m] = array[i];
array[i] = t;
}
}
</script>
</body>
</html>
執行可能結果
-------------------------------------第三步 增加十六個感應滑鼠區塊-------------------------------------
然後是敲掉一格或者說拔空一格
(也就是陣列內容從1開始即可
若想隨機打掉一格的話會比較麻煩
但我想用Math.random一個陣列索引把值改成像-1的配合if-else應該是做得出來
不過這還是先等第一版出來再考慮好了
我是去掉最右下角啦,覺得這樣完成圖好看些)
並透過滑鼠來移動盤面
第三步相當於做16個按鈕或者說供滑鼠點擊的區塊
當然是可以用鍵盤玩法
不過那還是先等第一版出來再考慮吧
這部分可以參考 仿魔喚精靈小遊戲闇箏的程式碼
注意以下程式碼的畫圖順序有改過
改成先由左至右
然後這次要用到jQuery
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<body>
<script src="jquery-1.11.1.js"></script>
<canvas id="myCanvas" width="640" height="480"></canvas>
<script>
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var img= new Image();
img.src="魂魄妖夢_big.png";
//等圖完整載入後再繪製,不然會有空白畫面,
//不寫的話重新整理也可以
//不過圖越大越有可能重新整理後還是沒圖片
//不寫的話要重新整理到圖載完時才會有畫面
img.onload =
function()
{
re_draw();
}
var board_width = 4;
var board_height = 4;
var board = [];
for(var i=0;i<board_width*board_height-1;i++)
{
board[i]=i;
}
board[board_width*board_height-1]=-1;
function re_draw()
{
shuffle(board);
for(var i=0;i<board_width*board_height;i++)
{
//不除4和取4的餘數的話,會超過畫布範圍變成一片空白
//不轉整數的話,執行畫面看起來會有些扭曲
ctx.drawImage(img,board[i]%4*c.width/4,parseInt(board[i]/4)*c.height/4,c.width/4,c.height/4,i%4*c.width/4,parseInt(i/4)*c.height/4,c.width/4,c.height/4);
}
}
function shuffle(array) {
var m = array.length, t, i;
while (m) {
i = Math.floor(Math.random() * m--);
t = array[m];
array[m] = array[i];
array[i] = t;
}
}
function click_region(id,left,top,width,height)
{
this.id=id;
var self=this;
this.left=left;
this.top=top;
this.width=width;
this.height=height;
$(c).on('handleClick', function(e, mouse) {
if (self.left < mouse.x &&
self.left + self.width > mouse.x &&
self.top < mouse.y &&
self.top + self.height > mouse.y) {
alert("點擊到"+self.id);
}
});
}
var elements=[];
for (var i = 0; i < board_width*board_height; i++)
{
elements.push(new click_region(i,i%4*c.width/4,parseInt(i/4)*c.height/4,c.width/4,c.height/4));
}
$(c).on('click', function(e) {
var mouse= {
x: e.pageX - c.offsetLeft,
y: e.pageY - c.offsetTop
}
//fire off synthetic event containing mouse coordinate info
$(c).trigger('handleClick', [mouse]);
});
</script>
</body>
</html>
由於增加16個按鈕的程式碼有些多
雖然基本上是抄IBM範例的
第四步就改為點擊圖片後移動到空格的判斷好了
---------------------------第四步 點擊圖片後移動到空格--------------------------------
今天我累了
只列下第四步要修改的重點跟範例
明天再完善
首先洗牌要拉到re_draw()前(一局只洗一次)
然後re_draw要先上背景顏色再畫盤面內容
shuffle(board);
function re_draw()
{
ctx.fillRect(0,0,c.width,c.height);
for(var i=0;i<board_width*board_height;i++)
{
//不除4和取4的餘數的話,會超過畫布範圍變成一片空白
//不轉整數的話,執行畫面看起來會有些扭曲
//ctx.drawImage(img,board[i]%4*c.width/4,parseInt(board[i]/4)*c.height/4,c.width/4,c.height/4,parseInt(i/4)*c.width/4,i%4*c.height/4,c.width/4,c.height/4);
ctx.drawImage(img,board[i]%4*c.width/4,parseInt(board[i]/4)*c.height/4,c.width/4,c.height/4,i%4*c.width/4,parseInt(i/4)*c.height/4,c.width/4,c.height/4);
}
}
接著是在物件
也就是function click_region(.............)裡面的$(c).on('handleClick'.....
做點擊的該拼圖塊上下左右的檢查
這邊先只示範若空格在左邊的話
主要要注意的是要檢查邊界
javascript陣列索引不檢查問題較小
但左邊第一行跟右邊最後一行,有些是有相連的要排除
不然誤點的話滑動式拼圖就能瞬間移動了
最後記得重繪
$(c).on('handleClick', function(e, mouse) {
if (self.left < mouse.x &&
self.left + self.width > mouse.x &&
self.top < mouse.y &&
self.top + self.height > mouse.y) {
if((self.id-1) % board_width !=board_width-1 &&
board[self.id-1] == -1)
{
board[self.id-1] = board[self.id];
board[self.id] = -1;
re_draw();
}
}
});
-------------------滑鼠版滑動拼圖最後一步(第五步),檢查拼圖是否完成----------------------------------
這部份較為單純
每次移動
檢查是否board[i]=i
記得自己哪塊拔空那塊看是要跳過不檢查
還是board[拔空那塊]=拔空時的值
像我前面的程式的話就是 if(board[15] == -1)這樣
由於是最後一格其實不檢查也行
接著就等8/18完善
哪天想到的話再做鍵盤版的
(鍵盤版比較不需要格線或拼圖與拼圖間留白的輔助)
不過這篇大概就這樣了
接著再想看看要做什麼
但很可能下次寫新的要到天氣比較涼的時候才寫
最終完成程式檔
主要新增步驟用綠色表示
(我是在re_draw時檢查拼圖是否完成)
若說是遊戲其實可以記錄步數
不過我就懶得新增了
其實我這程式大概8/18 08:00就寫好了
但測試時花了很久時間
(雖然我是先把洗牌註解掉確認可行
但實測時我拼不出來)
但後來上網查了下
才發現不能4*4 sliding puzzleu也不能任意打亂順序
當拼到最後發現有奇數個要調換的話就代表是無解的
特別注意下
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<body>
<script src="jquery-1.11.1.js"></script>
<canvas id="myCanvas" width="640" height="480"></canvas>
<script>
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.fillStyle="#FFFFFF";
var img= new Image();
img.src="魂魄妖夢_big.png";
//等圖完整載入後再繪製,不然會有空白畫面,
//不寫的話重新整理也可以
//不過圖越大越有可能重新整理後還是沒圖片
//不寫的話要重新整理到圖載完時才會有畫面
img.onload =
function()
{
re_draw();
}
var finish_num=0;
var board_width = 4;
var board_height = 4;
var board = [];
for(var i=0;i<board_width*board_height-1;i++)
{
board[i]=i;
}
board[board_width*board_height-1]=-1;
shuffle(board);
function re_draw()
{
ctx.fillRect(0,0,c.width,c.height);
for(var i=0;i<board_width*board_height;i++)
{
//不除4和取4的餘數的話,會超過畫布範圍變成一片空白
//不轉整數的話,執行畫面看起來會有些扭曲
//ctx.drawImage(img,board[i]%4*c.width/4,parseInt(board[i]/4)*c.height/4,c.width/4,c.height/4,parseInt(i/4)*c.width/4,i%4*c.height/4,c.width/4,c.height/4);
ctx.drawImage(img,board[i]%4*c.width/4,parseInt(board[i]/4)*c.height/4,c.width/4,c.height/4,i%4*c.width/4,parseInt(i/4)*c.height/4,c.width/4,c.height/4);
}
finish_num=0;
for(var i=0;i<board_width*board_height-1;i++)
{
if(board[i]==i)
{
finish_num++;
}
}
if(finish_num==15)
{
alert("拼圖完成");
board[board_width*board_height-1]=15;
ctx.drawImage(img,board[board_width*board_height-1]%4*c.width/4,parseInt(board[board_width*board_height-1]/4)*c.height/4,c.width/4,c.height/4,i%4*c.width/4,parseInt(i/4)*c.height/4,c.width/4,c.height/4);
}
}
function shuffle(array) {
var m = array.length, t, i;
while (m) {
i = Math.floor(Math.random() * m--);
t = array[m];
array[m] = array[i];
array[i] = t;
}
}
function click_region(id,left,top,width,height)
{
this.id=id;
var self=this;
this.left=left;
this.top=top;
this.width=width;
this.height=height;
$(c).on('handleClick', function(e, mouse) {
if (self.left < mouse.x &&
self.left + self.width > mouse.x &&
self.top < mouse.y &&
self.top + self.height > mouse.y) {
if((self.id-1) % board_width !=board_width-1 &&
board[self.id-1] == -1)
{
board[self.id-1] = board[self.id];
board[self.id] = -1;
re_draw();
}
if(board[self.id-4] == -1)
{
board[self.id-4] = board[self.id];
board[self.id] = -1;
re_draw();
}
if((self.id+1) % board_width != 0 &&
board[self.id+1] == -1)
{
board[self.id+1] = board[self.id];
board[self.id] = -1;
re_draw();
}
if(board[self.id+4] == -1)
{
board[self.id+4] = board[self.id];
board[self.id] = -1;
re_draw();
}
if((self.id-1) % board_width !=board_width-1 &&
board[self.id-1] == -1)
{
board[self.id-1] = board[self.id];
board[self.id] = -1;
re_draw();
}
}
});
}
var elements=[];
for (var i = 0; i < board_width*board_height; i++)
{
elements.push(new click_region(i,i%4*c.width/4,parseInt(i/4)*c.height/4,c.width/4,c.height/4));
}
$(c).on('click', function(e) {
var mouse= {
x: e.pageX - c.offsetLeft,
y: e.pageY - c.offsetTop
}
//fire off synthetic event containing mouse coordinate info
$(c).trigger('handleClick', [mouse]);
});
</script>
</body>
</html>
檔案下載位置
注意要配合圖片檔 魂魄妖夢_big.png
或者是隨意個640*480的圖片
於網頁檔中改為相對應檔名