創作內容

2 GP

8/17~8/18,程式復健計劃─滑動式拼圖(sliding puzzle),上次更新於8/18 09:52,完成滑鼠版

作者:李兒諳│2018-08-17 12:09:12│巴幣:4│人氣:341
我有些在懷疑這是否會是自己發的最後一篇正經文章
最近的心情雖然平穩
也有玩 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的圖片
於網頁檔中改為相對應檔名
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=4097182
All rights reserved. 版權所有,保留一切權利

相關創作

同標籤作品搜尋:sliding puzzle|滑動式拼圖

留言共 3 篇留言

哥布林鎖鍊旋轉者
有買文明五,但玩過幾局就放置很久
會推薦文明六嗎

08-21 03:06

李兒諳
那應該是不推薦
文明六比文明五貴多了
可以先下載試玩版玩看看08-21 05:01
哥布林鎖鍊旋轉者
原來有試玩版!!
我是看文明六有一些不一樣的機制
覺得很有意思
但要時常坐下來玩好幾個小時我辦不太到XD

08-21 09:30

李兒諳
文明六(其它我沒接觸過)可以自訂地圖的高級設置調整
可以鎖定勝利方式,地圖大小,回合數、關閉戰鬥動畫、自動下回合之類
不過回合制的類似世紀帝國遊戲確實蠻花時間的
但試玩版似乎就不能調整了08-21 10:44
哥布林鎖鍊旋轉者
可以調整那些倒是五代也有,所以試玩版沒有應該還好

08-21 11:00

我要留言提醒:您尚未登入,請先登入再留言

2喜歡★y541258 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:7/29,近況,我覺得... 後一篇:8/18,思考下一步,沒...

追蹤私訊切換新版閱覽

作品資料夾

zzz54872qw所有人
【敬啟:無法重來的你。】第一集終於完成!謝謝一直以來持續閱讀的讀者們!看更多我要大聲說3小時前


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】