拼圖遊戲 玩法介紹及其代碼實現(有意思的JS 一)

我是你們的索兒呀,很幸運我的文章能與你相見
願萌新能直觀的感受到Javascript的趣味性,願有一定基礎者有所收穫,願大佬不吝賜教

拼圖遊戲是一張圖片分爲若干塊,打亂次序,將其中一塊變爲空白塊,其只能與相鄰塊互換,發揮你的聰明才智來將其復原

而我本篇文章的目的是,介紹拼圖遊戲的玩法、技巧使用JavaScript語言實現拼圖遊戲

就算是萌新,也是可以閱讀前半篇文章的,若能勾起你的興趣將整篇文章看完那就更棒了

效果展覽

  • 原圖、2 × 2、3 × 3、6 × 6
  • 甚至還可以4 × 3、3 × 6
  • 圖片也是可以更換的,其原圖、4 × 4

思路及代碼實現

若你對於JavaScript才初窺門徑,我建議你使用電腦下載代碼(文末提供的核心代碼),然後配合本篇文章食用更佳

我先以 3 × 3 的九宮格來講解拼圖遊戲的思路,實際上我提供的代碼是很靈活的,可以更換圖片和改變分割方式

補充一嘴:本人沒有對打亂拼圖的算法做邏輯處理,顯示出來的拼圖不一定可以復原哦~~

html代碼只需要一行即可:

<div id="game"></div>

其餘內容皆由JavaScript完成

一:遊戲整體配置

在這裏插入圖片描述

此時,擺在我們眼前的有兩種做法:

  1. div中放九個img作爲拼圖塊(這個做法太死板了,若我要改變圖片、分割方式,改動起來會非常麻煩瑣碎)
  2. 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 拼圖是不可能復原的

代碼獲取

拼圖遊戲的完整核心代碼,有兩個方式獲取

所謂核心代碼,即本篇文章帶你實現的拼圖遊戲的完整代碼,沒有加入許許多多花裏胡哨的東西,以免文章無法連貫講解

  1. 百度網盤
    文件夾鏈接:https://pan.baidu.com/s/1bFiosDk2uXjvOHuVYsI2IQ
    提取碼:r1ua
    壓縮包鏈接:https://pan.baidu.com/s/1nvYilW7vYErJZWQzXER6DQ
    提取碼:zr7k
  2. github:https://github.com/Zhangguohao666/demo-front-end/tree/master/jigsaw

拼圖遊戲的完整提高版代碼

未完待續…

本文代碼思路參考自:渡一,袁進老師


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章