我是你們的索兒呀,很幸運我的文章能與你相見
願萌新能直觀的感受到Javascript的趣味性,願有一定基礎者有所收穫,願大佬不吝賜教
拼圖遊戲是一張圖片分爲若干塊,打亂次序,將其中一塊變爲空白塊,其只能與相鄰塊互換,發揮你的聰明才智來將其復原
而我本篇文章的目的是,介紹拼圖遊戲的玩法、技巧,使用JavaScript語言實現拼圖遊戲
就算是萌新,也是可以閱讀前半篇文章的,若能勾起你的興趣將整篇文章看完那就更棒了
效果展覽
- 原圖、2 × 2、3 × 3、6 × 6
、、、 - 甚至還可以4 × 3、3 × 6
、 - 圖片也是可以更換的,其原圖、4 × 4
、
思路及代碼實現
若你對於JavaScript才初窺門徑,我建議你使用電腦下載代碼(文末提供的核心代碼),然後配合本篇文章食用更佳
我先以 3 × 3 的九宮格來講解拼圖遊戲的思路,實際上我提供的代碼是很靈活的,可以更換圖片和改變分割方式
補充一嘴:本人沒有對打亂拼圖的算法做邏輯處理,顯示出來的拼圖不一定可以復原哦~~
html代碼只需要一行即可:
<div id="game"></div>
其餘內容皆由JavaScript完成
一:遊戲整體配置
此時,擺在我們眼前的有兩種做法:
- div中放九個img作爲拼圖塊(這個做法太死板了,若我要改變圖片、分割方式,改動起來會非常麻煩瑣碎)
- div只需要一個背景圖片,裏面生成九個div作爲拼圖塊,即將其分割爲3 × 3 的九宮格(將圖片看作spirit圖,然後配合background-position屬性即可做到)
- 根據圖片的分辨率( 900 × 900 ),確定
<div id="game"></div>
的寬高 - 拼圖遊戲( 3 × 3 ),確定每一行、每一列有多少個拼圖塊
- 圖片路徑,相對的是頁面路徑
以上三個方面的配置是最基本的,可以通過更改以上配置來更改拼圖遊戲的圖片或者分割方式
/**
* 遊戲配置
*/
var config = {
dom: document.getElementById("game"), //遊戲的dom對象
width: 900,
height: 900,
rows: 3, //行數
cols: 3, //列數
url: "img/Ahri.png", //圖片路徑
};
- 每一塊拼圖塊的寬高取決於
<div id="game"></div>
的寬高和行列數 - 拼圖塊的數量取決於div的行列數
//每一個拼圖塊的寬高
config.blockWidth = config.width / config.cols;
config.blockHeight = config.height / config.rows;
//拼圖塊的數量
config.blockNumber = config.rows * config.cols;
二:初始化遊戲
1. 初始化遊戲面板
- config.dom獲取的是
<div id="game"></div>
的dom對象 - 然後使用這個dom對象設置其css屬性(寬高、邊框)
- 爲什麼要設置爲相對定位呢?因爲拼圖遊戲中的拼圖是可以移動的,它們的位置需要使用絕對定位
/**
* 初始化遊戲容器
*/
function initGameDom(){
config.dom.style.width = config.width + "px";
config.dom.style.height = config.height + "px";
config.dom.style.border = "2px solid #ccc";
config.dom.style.position = "relative";
}
2. 初始化拼圖塊
一個拼圖塊就是一個div,
需要在<div id="game"></div>
中創建九個div(都設置爲絕對定位),
此時需要一個數組,數組的每一項是一個對象,存放每一個div(拼圖塊)的信息
//存放拼圖塊信息
var blocks = [];
- i 對應行號,j 對應列號,left、top爲定位元素
- left = j × blockWidth
- top = i × blockHeight
此時,<div id="game"></div>
中的九個div(拼圖塊)的位置就安排好了
/**
* 初始化拼圖塊的數組
*/
function initBlocks(){
for(var i = 0; i < config.rows; i++){
for(var j = 0; j < config.cols; j++){
var isVisible = true;
if(i === config.rows-1 && j === config.cols-1){
isVisible = false;
}
blocks.push(new Block(j * config.blockWidth, i * config.blockHeight, isVisible));
}
}
}
同時,每個div(拼圖塊)對應的正確的背景圖片的位置correctBgX、correctBgY與left、top其實是一一對應的
- correctBgX = -left;
- correctBgY = -top;
Block構造函數有三個參數:;
以下函數的第三個參數isVisible控制拼圖塊是否可見(最後一個拼圖塊是不可見的)
關鍵參數爲前兩個參數:定位元素left、right,
在構造函數Block中先生成div的dom元素,然後定義其必須要的屬性:width、height、border、background、position,
box-sizing: border-box
決定div的寬高包括邊框的寬度,這樣不會影響到頁面佈局
cursor: pointer
鼠標移動到該標籤上時變爲手指
transition : .5s
css屬性變化的時候,在0.5秒內完成(達到拼圖塊交互時的拖拽效果)
/**
* 拼圖塊的構造函數
* @param {*} left
* @param {*} top
* @param {*} isVisible 是否可見
*/
function Block(left, top, isVisible){
this.left = left;
this.top = top;
this.correctBgX = -this.left;
this.correctBgY = -this.top;
this.isVisible = isVisible;//是否可見
this.dom = document.createElement("div");
this.dom.style.width = config.blockWidth + "px";
this.dom.style.height = config.blockHeight + "px";
this.dom.style.boxSizing = "border-box";
this.dom.style.border = "2px solid #fff";
this.dom.style.background = 'url("'+ config.url + '") ' + this.correctBgX + "px " + this.correctBgY + "px";
this.dom.style.cursor = "pointer";
this.dom.style.transition = ".5s";//css屬性變化的時候,在0.5秒內完成
if(!isVisible){
this.dom.style.display = "none";
}
this.dom.style.position = "absolute";
/**
* 根據當前的left、top,顯示div的位置
*/
this.show = function(){
this.dom.style.left = this.left + "px";
this.dom.style.top = this.top + "px";
}
this.show();
config.dom.appendChild(this.dom);
}
此時,拼圖的大概樣子是做出來了
3. 打亂拼圖塊
將存放拼圖塊的數組blocks重新排序,需要循環數組重新給其left、top值賦值,
最後一個拼圖塊是空白的,它的位置是確定的(不參與重新洗牌)
/**
* 給blocks數組從新排序
*/
function shuffle(){
for(var i = 0; i < blocks.length-1; i++){
//隨機產生一個下標
var index = getRandom(0, blocks.length-2);
//交換left、top
exchangeBlock(blocks[i], blocks[index]);
}
}
/**
* 生成[min, max]範圍內的隨機數
* @param {*} min
* @param {*} max
*/
function getRandom(min, max){
return Math.floor(Math.random() * (max +1 -min) + min);
}
/**
* 交換兩個拼圖塊的top、left,並在頁面上重新顯示
* 參數都爲拼圖塊對象
* @param {*} x
* @param {*} y
*/
function exchangeBlock(x, y){
//交換left
var temp = x.left;
x.left = y.left;
y.left = temp;
//交換top
var temp = x.top;
x.top = y.top;
y.top = temp;
//在頁面重新顯示
x.show();
y.show();
}
4. 給拼圖塊註冊點擊事件
交換看的見的拼圖塊與空白拼圖塊的座標位置,
並且它們必須要挨着一起
- 同一行,它們的left的差值的絕對值,等於拼圖塊的寬度
- 同一列,它們的top的差值的絕對值,等於拼圖塊的高度
/**
* 給拼圖塊註冊點擊事件
*/
function regEvent(){
//找到空白拼圖塊
var inVisibleBlock = blocks.find(function(b){
return !b.isVisible;
});
blocks.forEach(function(b){
b.dom.onclick = function(){
if((isEqual(b.top, inVisibleBlock.top) &&
isEqual(Math.abs(b.left - inVisibleBlock.left), config.blockWidth))
||
(isEqual(b.left, inVisibleBlock.left) &&
isEqual(Math.abs(b.top - inVisibleBlock.top), config.blockHeight))){
//交換看的見的拼圖塊與空白拼圖塊的座標位置
exchangeBlock(b, inVisibleBlock);
//遊戲結束判定
此處有一個函數
}
}
});
}
/**
* 避免小數的不精確,將兩數化整比較
* @param {*} x
* @param {*} y
*/
function isEqual(x, y){
return parseInt(x) === parseInt(y);
}
5. 遊戲結束判定
關鍵:判斷每一個拼圖塊是否都在正確的位置上
/**
* 遊戲結束判定
*/
function isWin(){
//過濾不在正確位置上的拼圖塊
var mistakenBlocks = blocks.filter(function(b){
return !(isEqual(b.left, -b.correctBgX) && isEqual(b.top, -b.correctBgY));
});
if(mistakenBlocks.length === 0){
//遊戲結束
blocks.forEach(function(b){
b.dom.style.border = "none";
b.dom.style.display = "block";//將最後一個拼圖塊也顯示在頁面上
});
}
}
此時還沒有完成哦~
拼圖完成後,要將每一個拼圖塊設置爲都不能點擊,有興趣的可以閱讀文末提供的完整核心代碼喲
部分JS知識點陳列
爲了閱讀的流暢性,我先陳列一些後文我將用到的JS函數、dom元素的玩法(大致瞟一眼,留個印象就好了,閱讀到後面可以返回來看)
一:dom元素的玩法
- document.getElementById(“id名”)
通過id獲取對應id的元素 - document.createElement(“元素名”)
創建一個dom元素對象(此時不影響文本結構) - this關鍵字在事件處理程序中指代當前發生的事件元素
- dom.style.樣式名 = 值
設置該dom元素的行內樣式 - dom.appendChild(dom元素)
在某個元素末尾加入一個子元素 - dom.onclick() = function(){函數體}
爲dom元素註冊點擊事件,當鼠標在頁面上點擊該dom元素時,執行函數體內容
二:JS函數
- push()
可向數組的末尾添加一個或多個元素,並返回新的長度 - parseInt(浮點數)
返回整數,即將浮點數的小數點即其後的數去掉 - Math.floor(數)
向下取整,比如12.1~12.9都返回12 - Math.random()
返回介於 0 ~ 1 之間的一個隨機數(包括0,不包括1) - Math.abs(數)
返回該數的絕對值 - 數組.find()
爲數組中的每個元素都調用一次函數執行:當數組中的元素在測試條件時返回 true 時, find() 返回符合條件的元素,之後的值不會再調用執行函數。 - 數組.filter()
創建一個新的數組,新數組中的元素是通過調用數組中的每個元素並檢查,返回滿足條件的元素到新數組中 - 數組.forEach()
用於調用數組的每個元素,並將元素傳遞給回調函數。
個人所慮,必有不足。若有疑問,歡迎在評論區提問。
遊戲玩法及技巧
既然決定寫這篇文章,我花了一個上午研究拼圖遊戲,也某度、某乎查看了一些技巧,遂整體成文
本人建議,先到文末將核心代碼下載下來,邊玩邊看玩法技巧,食用更佳
如果你是萌新,推薦使用百度網盤下載代碼、查看運行喲
下載到電腦上後,只需要鼠標左鍵雙擊game.html文件,即可用瀏覽器打開,可以嘗試玩一下喲(圖片尺寸較大,縮放頁面視覺效果更佳)
查看代碼,就使用編輯器(或者電腦自帶的記事本:鼠標右鍵該文件,打開方式,記事本)打開script文件夾下的game.js文件,就可以開始玩了(重新開一局也很簡單:刷新頁面)
若拼圖是完全隨機排序的,不一定可以復原,
拼圖的復原在於旋轉,旋轉是受限制的(可以這麼理解:把復原好的任意階拼圖,任意互換兩塊的位置,都不可能復原)
- 2 × 2 比如下圖是復原不了的:
- 3 × 3 是拼圖遊戲的基礎,因爲它大概率可以復原(只有一種情況是無法復原的,後文會講到);可以引申爲一句話,無論是?× ?的拼圖,只要包含3 × 3,就可以簡化爲3 × 3的拼圖
只要會復原3 × 3,就可以玩轉拼圖遊戲了(若它能夠復原)
以可復原爲基礎,某乎高贊答案 給的復原3 × 3技巧,我發現非常好用:
- 復原順序分爲兩種:縱向和橫向
假設以一列一列的方式復原 - 準則一:當你復原一列的時候,你的眼中只有這一列的塊,其他列都不屬於你的考慮範圍。
- 準則二:當你復原完一列的時候,就再不要管它,不要動它,把它給忘掉。
- 注意:不要被僞復原迷惑(復原每一列的時候,一定也要遵循從上往下或從下往上一塊塊復原,不連貫就是僞復原)
除了以上技巧,還要會借位技巧(你就已經是拼圖大神了)
我將情況儘量簡化,來講解借位技巧,給下圖(1、4、7已經排好了,這一列不看了):
而且上圖恰好是僞復原,2、8看似復原了,實際上這樣想會進入死衚衕的,實際上只復原了2或8
我就以2復原了作爲基礎,來講解怎麼將第二列(2、5、8)復原:
先將2、5復原(不用思考就做得到),
此時擺在我們面前的問題是,如何將8復原,此處需要借位技巧了
先將2移到上圖3處,5移到上圖2處(眼中只做到這兩步即可)
然後就是最後一個難點了,將8移到上圖6處,空白格再移回上圖空白格處
接位的目標已達成,將空白格上移、右移,第二列復原完成
於是,你發現了一個問題…嘿嘿…我給的這個3 × 3 拼圖是不可能復原的
代碼獲取
拼圖遊戲的完整核心代碼,有兩個方式獲取
所謂核心代碼,即本篇文章帶你實現的拼圖遊戲的完整代碼,沒有加入許許多多花裏胡哨的東西,以免文章無法連貫講解
- 百度網盤
文件夾鏈接:https://pan.baidu.com/s/1bFiosDk2uXjvOHuVYsI2IQ
提取碼:r1ua
壓縮包鏈接:https://pan.baidu.com/s/1nvYilW7vYErJZWQzXER6DQ
提取碼:zr7k - github:https://github.com/Zhangguohao666/demo-front-end/tree/master/jigsaw
拼圖遊戲的完整提高版代碼
未完待續…
本文代碼思路參考自:渡一,袁進老師