JavaScript任意數量的拼圖游戲

程序猿123 9年前發布 | 6K 次閱讀 JavaScript

(function($) {
    var puzzleConfig = {
        sizeX: 3,
        sizeY: 3
    };

//全局常量
var Constants={
    //每一片拼圖透明度較低時候的透明度值
    fadeOpacity: 0.8,
    //放拼圖元素的水平方向padding+border的合計值,用于載入拼圖后控制容器尺寸
    puzzleContainerExtra: 42
};

//圖片相關變量
var puzzleImage=null,
imageURL="",
//圖片上傳標識,為true時表示相關設置合理,選擇圖片后將進入游戲
checkFlag=false,
imageWidth=0,
imageHeight=0;

//拼圖相關變量
var puzzleWidth=0,
puzzleHeight=0,
puzzleItemWidth=0,
puzzleItemHeight=0,
puzzleSizeX=0,
puzzleSizeY=0,
//拼圖數目
puzzleNumber=0,
//計數器,計算從開始到完成游戲用的步數
moveStepCount=0,
//拼圖步數以及是否完成的提示文字
puzzleNote=null,
//保存每一片拼圖的正確的坐標值的數組
validPosArrayX=[],
validPosArrayY=[],
//保存每一片拼圖的數組,索引順序和正確的拼圖順序相同
puzzleArray = [],
//整個拼圖元素本身
puzzle=null,
//最終放置該拼圖的父元素節點
puzzleSetElem=null;

//初始第一步,讀取拼圖設置和圖片源,包括對填寫內容的驗證*/
var puzzleConfigSet = function() {
    //類名常量
    var sizeInputClassName = "size_input",
        noteWarnClassName = "note_warn",
        currentProgressClassName = "current_progress",
        validImageSuffix = ".jpg|.jpeg|.gif|.bmp|.png";

    //放置拼圖的由外層變量保存的元素
    puzzleSetElem=$ ("puzzleSet");

    //取得對應元素
    var sizeXElem = $("sizeX"),
        sizeYElem = $("sizeY"),
        sizeSetNote = $("sizeSetNote"),
        uploadBtn = $("uploadBtn"),
        fileImage = $("fileImage"),
        uploadProgress = $("uploadProgress"),
        currentProgress = uploadProgress.getFirst("." + currentProgressClassName),
        uploadNote = $("uploadNote");

    //拼圖尺寸設定檢查
    var puzzleSizeCheck = function() {
        var sizeX = sizeXElem.value,
            sizeY = sizeYElem.value,
            numberReg = /^\d{1,2}$/;
        if (numberReg.test(sizeX) && numberReg.test(sizeY)) {
            if (sizeX >= 2 && sizeX <= 10 && sizeY >= 2 && sizeY <= 10) {
                puzzleConfig.sizeX = sizeX;
                puzzleConfig.sizeY = sizeY;
                checkFlag = true;
            } else {
                sizeSetNote.addClass(noteWarnClassName);
            }
        } else {
            sizeSetNote.addClass(noteWarnClassName);
        }
    };

    //圖片尺寸檢查
    var imageCheck = function(image) {
        var minWidth = 30,
            maxWidth = 850,
            minHeight = 30;
        if (image.width >= 30 && image.width <= 850 && image.height > 30) {
            checkFlag = checkFlag && true;
        } else {
            uploadNote.addClass(noteWarnClassName);
            checkFlag = false;
        }
    };

    //圖片格式檢查
    var formatCheck = function(image) {
        var fileURL = fileImage.value.toLowerCase();
        //獲取文件拓展名
        formatSuffix = fileURL.substring(fileURL.lastIndexOf("."));
        if (formatSuffix&&validImageSuffix.contains(formatSuffix)) {
            //如果是正確格式的圖片文件
            checkFlag = checkFlag && true;
        } else {
            alert("請上傳正確格式的圖片文件(" + validImageSuffix + ")");
            checkFlag = false;
        }
    };

    //拼圖尺寸輸入框的事件
    $$("." + sizeInputClassName).addEvent("focus", function() {
        sizeSetNote.removeClass(noteWarnClassName);
    });

    //讀取選擇上傳的圖片
    puzzleImage = new Image();
    puzzleImage.onload = function() {
        imageCheck(puzzleImage);
        if (checkFlag) {
            imageWidth = puzzleImage.width;
            //由于圖片尺寸不一定能被拼圖尺寸整除,因此做邊緣裁剪
            while(imageWidth % puzzleConfig.sizeX != 0){
                imageWidth--;
            }
            imageHeight = puzzleImage.height;
            while(imageHeight % puzzleConfig.sizeY != 0){
                imageHeight--;
            }
            imageURL= puzzleImage.src;
            puzzleSetElem.empty();
            var containerWidth = imageWidth+Constants.puzzleContainerExtra,
            properContainerWidth = containerWidth>120?containerWidth:120;
            puzzleSetElem.getParent().setStyles({
                width: properContainerWidth
            });
            createPuzzle(); //創建拼圖
        }
        else{
            //如果讀取后圖片尺寸不合適的話,重置圖片上傳
            uploadProgress.style.display = "none";
            currentProgress.setStyle("width", 0);
            uploadBtn.style.display = "";
        }
    };
    if (typeof FileReader == "undefined") {
        //如果是不支持File API的瀏覽器
        fileImage.onchange = function() {
            puzzleSizeCheck();
            if (checkFlag) {
                formatCheck();
            }
            if (checkFlag) {
                puzzleImage.src =  fileImage.value;
            }
        };
    } else {
        //如果支持File API,可以顯示讀取進度條
        var imageReader = new FileReader();

        //對象URL(blob URL),經測試新版Chrome也支持window.URL
        function createObjectURL(blob){
            if(window.URL){
                return window.URL.createObjectURL(blob);
            }else if(window.webkitURL){
                return window.webkitURL.createObjectURL(blob);
            }else{
                return null;
            }
        }
        //開始讀取
        imageReader.onloadstart = function() {
            puzzleSizeCheck();
            if(checkFlag){
                formatCheck();
            }
            if (checkFlag) {
                uploadBtn.style.display = "none";
                uploadProgress.style.display = "";
            }
        };
        //讀取中
        imageReader.onprogress = function(event) {
            if (checkFlag) {
                var percentage = 100 * parseInt(event.loaded / event.total) + "%";
                currentProgress.setStyle("width", percentage);
            }
        };
        imageReader.onload = function(event) {
            if (checkFlag) {
                    //IE10也支持blob URL
                    var url=createObjectURL(fileImage.files[0]);
                    puzzleImage.src = url;
            }
        };
        fileImage.onchange = function() {
            imageReader.readAsDataURL(fileImage.files[0]);
        };
    }
};

//用于創建拼圖
var createPuzzle = function() {
        //classNameSet表示生成的元素的class名
        var classNameSet = {
            listContainer: "puzzle_container",
            list: "puzzle_list",
            item: "puzzle_item"
        };
        //各類元素對應的基本樣式
        var puzzleStyle = {
            listContainer: {
                position: "relative",
                width: imageWidth,
                height: imageHeight,
                margin: "0 auto"
            },
            list: {

            },
            item: {
                position: "absolute"
            }
        };
        //計算得到每一塊拼圖的尺寸
        puzzleSizeX = puzzleConfig.sizeX;
        puzzleSizeY = puzzleConfig.sizeY;
        puzzleWidth = imageWidth;
        puzzleHeight = imageHeight;
        puzzleItemWidth = puzzleWidth / puzzleSizeX;
        puzzleItemHeight = puzzleHeight / puzzleSizeY;
        puzzleNumber = puzzleSizeX * puzzleSizeY;

        //建立一個臨時數組,用于生成隨機順序的拼圖塊
        var randomOrderPuzzleArray=[];

        //創建元素
        puzzle = elementsCreate();
        showAnime();

        //創建整個拼圖的dom,返回最外層的父級元素
        function elementsCreate() {
            var listContainer = new Element("div");
            listContainer.addClass(classNameSet.listContainer);
            listContainer.setStyles(puzzleStyle.listContainer);

            var list = new Element("ul");
            list.addClass(classNameSet.list);
            list.setStyles(puzzleStyle.list);

            //先通過循環,創建每一個拼圖塊,并按正確順序存入數組
            for(var i = 0, len = puzzleNumber; i < len; i++) {
                var item = new Element("li");
                //為每塊拼圖保存自身的正確索引
                var indexSet = i + 1;
                item.store("puzzleIndex", indexSet);
                item.addClass(classNameSet.item);
                //增加基本樣式
                item.setStyles(puzzleStyle.item);

                //以正確順序保存每一個拼圖塊到數組
                puzzleArray.push(item);
            }

            //建立一個正確順序數組的副本
            var puzzleArrayClone=puzzleArray.clone();

            //再次通過循環,創建一個亂序的拼圖數組,并把這個數組顯示到頁面中
            for (i = 0, len = puzzleNumber; i < len; i++) {
                var randomItem = puzzleArrayClone.getRandom();
                //為避免重復,需要把被取出來的元素在副本數組中刪除
                puzzleArrayClone.erase(randomItem);

                //為每一塊取出來的元素設置可變的位置索引
                var posIndex = i + 1;
                randomItem.posIndex = posIndex;

                //獲取取出來的元素的正確索引,用于接下來計算拼圖背景圖位置
                var correctIndex = randomItem.retrieve("puzzleIndex");

                //計算位置
                var topSet = Math.floor((posIndex - 1) / puzzleSizeX) * puzzleItemHeight,
                    leftSet = (posIndex - 1) % puzzleSizeX * puzzleItemWidth,

                    //計算符合正確索引的背景圖位置
                    backgroundSetX = -(correctIndex - 1) % puzzleSizeX * puzzleItemWidth,
                    backgroundSetY = -(Math.floor((correctIndex - 1) / puzzleSizeX) * puzzleItemHeight),
                    backgroundString = "url(" + imageURL + ") " + backgroundSetX + "px " + backgroundSetY + "px " + "no-repeat";

                //添加關鍵樣式
                randomItem.setStyles({
                    width: Math.ceil(puzzleItemWidth),
                    height: Math.ceil(puzzleItemHeight),
                    background: backgroundString,
                    left: leftSet,
                    top: topSet,
                    zIndex: posIndex
                });

                //生成合理的位置坐標數組
                validPosArrayX.push(leftSet);
                validPosArrayY.push(topSet);

                //存放亂序元素到亂序數組
                randomOrderPuzzleArray.push(randomItem);
            }

            //組合拼圖的各個元素
            list.adopt(randomOrderPuzzleArray);
            listContainer.adopt(list);

            return listContainer;
        }

        //為拼圖的初始化創建動畫
        function showAnime(){
            //一些動畫參數
            var timeSpace=50,
            //垂直移動的間距
            distance=30,
             //計數用
            count=0,
            timeFlag;        

            //所有拼圖先隱藏,透明度置為0
            for(var i=0,len=puzzleArray.length;i<len;i++){
                puzzleArray[i].setStyle("opacity",0);
            }

            //更新到頁面dom中,準備開始動畫
            puzzleSetElem.grab(puzzle);

            var enterFrameHandler=function(){
                var puzzleItem=randomOrderPuzzleArray[count++];
                var endTop=parseInt(puzzleItem.getStyle("top"));
                var startTop=endTop-distance;

                puzzleItem.set("morph",{
                    transition: Fx.Transitions.Quad.easeOut
                });
                puzzleItem.morph({
                    top:[startTop,endTop],
                    opacity:Constants.fadeOpacity
                });

                if(count<puzzleNumber){
                    //對最后一個拼圖塊的動畫結束做偵聽
                    if(count==puzzleNumber-1){
                        var lastMorph=puzzleItem.get("morph");
                        var showAnimeEnd=function(){
                            lastMorph.removeEvent("complete",showAnimeEnd);
                            puzzleEventBind();
                        }
                        lastMorph.addEvent("complete",showAnimeEnd);
                    }
                    timeFlag=setTimeout(enterFrameHandler,timeSpace);
                }
            };
            timeFlag=setTimeout(enterFrameHandler,timeSpace);
        }

    };

//拼圖的相關事件綁定,也是游戲的核心控制邏輯
var puzzleEventBind=function(){
    //拼圖游戲控制相關的變量
    var selectedItem=null,
    //當前選中的拼圖位置索引
    selectedIndex=0,
    //用于保存當前鼠標正在拖動的拼圖的zIndex值
    selectedItemZIndex=0,
    //每一次切換拼圖位置的時候,都涉及到2塊拼圖,鼠標拖動的這塊和交換位置的另外一塊,這個就是另外一塊
    relatedItem=null,
    //依照鼠標當前的位置,判斷得到的目標索引,如果鼠標此時放開,就是說把選中的拼圖移到現在鼠標所在的位置
    targetIndexNew=0,
    //通過new和old來區分鼠標從一個目標索引更換到另一個目標索引
    targetIndexOld=0,
    //判斷是否進行一次拼圖位置移動的邏輯值,只有當目標索引值有改變時,才允許進行拼圖位置移動
    isTargetIndexChanged=false,
    //判斷鼠標指針是否在拼圖的區域之內
    isInsidePuzzle=false,
    //鼠標點擊拼圖的某一個點的時候,距離拼圖的左上角定位點有的距離值
    disX=0,
    disY=0;

    //計算獲取整個拼圖的左上角點的坐標
    var puzzlePos=puzzle.getPosition();
    var puzzlePosX=puzzlePos.x,
    puzzlePosY=puzzlePos.y;

    //重新設置每一個元素的動畫速度
    (function(){
        for(var i=0,len=puzzleArray.length;i<len;i++){
            var puzzleItem=puzzleArray[i];
            puzzleItem.set("morph",{
                duration:250
            });
        }
    })();

    //計數函數準備
    var updateCount = (function(){
        var stepCount = $("stepCount");
        puzzleNote = stepCount.getParent();
        return function(){
            stepCount.set("text", moveStepCount);
        };
    })();

    //添加事件
    puzzle.addEvent("mouseover",mouseOverHandler);
    puzzle.addEvent("mouseout",mouseOutHandler);
    puzzle.addEvent("mousedown",mouseDownHandler);
    puzzle.addEvent("mouseup",mouseUpHandler);

    //鼠標經過
    function mouseOverHandler(event){
        var target=event.target;
        if(puzzleArray.contains(target)){
            target.setStyle("opacity",1);
        }
    }

    //鼠標移出
    function mouseOutHandler(event){
        var target=event.target;
        if(puzzleArray.contains(target)){
            target.setStyle("opacity",Constants.fadeOpacity);
        }
    }

    //鼠標按下
    function mouseDownHandler(event){
        var target=event.target;
        //race("[mouseDownHandler]selectedItem ="+selectedItem);
        //如果當前沒有其他目標選中,且鼠標選中的目標是拼圖塊
        if(!selectedItem&&puzzleArray.contains(target)){
            if(target.getStyle("opacity")<1){
                target.setStyle("opacity",1);
            }

            //設置當前選中的目標及索引
            selectedItemZIndex=target.getStyle("zIndex");
            target.setStyle("zIndex",5000);
            selectedItem=target;
            selectedIndex=target.posIndex;

            //設置初始目標索引
            targetIndexNew=targetIndexOld=selectedIndex;

            //計算出鼠標點擊的點和拼圖左上角定位點的偏差距離
            var targetPos=target.getPosition();
            disX=event.page.x-targetPos.x;
            disY=event.page.y-targetPos.y;

            //增加鼠標移動的事件偵聽,讓拼圖塊跟隨鼠標移動,并判斷當前位置
            document.addEvent("mousemove",mouseMoveHandler);
        }
    }

    //鼠標松開
    function mouseUpHandler(event){
        //如果有元素處于拖動狀態,取消
        if(selectedItem){
            selectedItem.setStyle("opacity",Constants.fadeOpacity);
            selectedItem.setStyle("zIndex",selectedItemZIndex);
            document.removeEvent("mousemove",mouseMoveHandler);

            //松開之后,根據目標索引和拖動元素的索引,移動拼圖,并更新dom結構
            if(isInsidePuzzle){
                //如果目標索引是一塊別的拼圖
                if(targetIndexNew!=selectedIndex){
                    puzzleItemMove(selectedItem,targetIndexNew,puzzleItemSwitch);
                }else{
                    //還原回原來的位置
                    puzzleItemMove(selectedItem,selectedIndex);
                    selectedItem=null;
                    relatedItem=null;
                }
            }else{
                //如果鼠標在拼圖之外的區域松開,則被拖動的拼圖還原回原來的位置
                puzzleItemMove(selectedItem,selectedIndex);
                selectedItem=null;
                relatedItem=null;
                targetIndexNew = targetIndexOld = selectedIndex;
            }
        }
    }

    //鼠標移動
    function mouseMoveHandler(event){
        var mouseX=event.page.x,
        mouseY=event.page.y;

        event.preventDefault();

        //設置選中元素的位置,跟隨鼠標
        selectedItem.setPosition({
            x:mouseX-disX-puzzlePosX,
            y:mouseY-disY-puzzlePosY
        })

        //計算鼠標當前位置是否在拼圖區域之內(拼圖邊緣也算在外)
        isInsidePuzzle=(function(){
            if(mouseX<=puzzlePosX||mouseX-puzzlePosX>=puzzleWidth){
                return false;
            }
            if(mouseY<=puzzlePosY||mouseY-puzzlePosY>=puzzleHeight){
                return false;
            }
            return true;
        })();

        //如果鼠標當前位置在拼圖區域之內,再做目標索引計算
        if(isInsidePuzzle){
            //race("[mouseMoveHandler]isInsidePuzzle = true");

            //計算目標索引,xIndex和yIndex分別表示當前位置所處的列序號和行序號
            var xIndex=Math.ceil((mouseX-puzzlePosX)/puzzleItemWidth),
            yIndex=Math.ceil((mouseY-puzzlePosY)/puzzleItemHeight);
            targetIndexNew=(yIndex-1)*puzzleSizeX+xIndex;

            if(targetIndexNew!=targetIndexOld){
                isTargetIndexChanged=true;
            }
            //只有當目標索引發生改變時,才移動拼圖做示意
            if(isTargetIndexChanged){
                //如果上一個目標索引的拼圖不是鼠標正在移動的這個,那么就需要恢復這張拼圖的位置到它原來的地方
                if(targetIndexOld!=selectedIndex){
                    var lastRelatedItemIndex=relatedItem.posIndex;
                    puzzleItemMove(relatedItem,lastRelatedItemIndex);
                }

                //更新相關元素,取得拼圖數組中posIndex等于當前的目標索引的元素
                relatedItem=puzzleArray.filter(function(item, index){
                    return item.posIndex == targetIndexNew;
                })[0];
                //如果下一個目標索引,不是被拖走的拼圖原來所在的位置,就移動新的目標索引的拼圖到被拖走的拼圖的位置
                if(targetIndexNew!=selectedIndex){
                    puzzleItemMove(relatedItem,selectedIndex);
                }

                //重置目標索引改變的邏輯值
                isTargetIndexChanged=false;

                //更新上一個目標索引
                targetIndexOld=targetIndexNew;
            }
        }else{
            //如果移到拼圖區域之外,則考慮還原上一個目標索引的拼圖
            if(targetIndexOld!=selectedIndex){
                    var lastRelatedItemIndex=relatedItem.posIndex;
                    puzzleItemMove(relatedItem,lastRelatedItemIndex);
            }
            //還原targetIndexOld的值,以處理移到拼圖外的情況。
            targetIndexOld = selectedIndex;
        }
    }

    //每一次拼圖交換的功能實現的函數,更改對應元素的posIndex,并更改zIndex
    function puzzleItemSwitch(){

        //交換元素的posIndex
        selectedItem.posIndex=targetIndexNew;
        relatedItem.posIndex=selectedIndex;

        //交換元素的zIndex,通過posIndex來賦值
        selectedItem.setStyle("zIndex",selectedItem.posIndex);
        relatedItem.setStyle("zIndex",relatedItem.posIndex);

        //清除對相關元素的引用
        selectedItem=null;
        relatedItem=null;

        //一次更換完成,計數器+1
        moveStepCount++;
        updateCount();

        //然后再判斷拼圖游戲是否完成
        clearJudgement();
    }

    //每一塊拼圖在游戲中的移動函數
    function puzzleItemMove(moveItem,moveToIndex,endFn){
        var moveToX=validPosArrayX[moveToIndex-1],
        moveToY=validPosArrayY[moveToIndex-1],
        originZIndex=moveItem.posIndex;
        moveItemMorph=moveItem.get("morph");
        moveItemMorph.addEvent("start",moveStartHandler);
        moveItemMorph.addEvent("complete",moveEndHandler);
        moveItem.morph({
                    left:moveToX,
                    top:moveToY
        });
        function moveStartHandler(){
            moveItem.setStyle("zIndex",1000);
        }
        function moveEndHandler(){
            moveItemMorph.removeEvent("start",moveStartHandler);
            moveItemMorph.removeEvent("complete",moveEndHandler);
            moveItem.setStyle("zIndex",originZIndex);

            //結尾執行的函數,如果需要的話
            if(typeOf(endFn)=="function"){
                endFn();
            }
        }
    }

    //完成拼圖游戲的判定函數
    function clearJudgement(){
        //檢查puzzleArray中的每一個元素的puzzleIndex和posIndex是否全部一致
        var isGameClear=puzzleArray.every(function(item, index){
            var puzzleIndex=item.retrieve("puzzleIndex");
            return item.posIndex==puzzleIndex;
        });

        if(isGameClear){
            clearShow();
        }
    }

    //確定完成拼圖游戲后,執行的函數
    function clearShow(){
         //清除所有事件偵聽
        puzzle.removeEvent("mouseover",mouseOverHandler);
        puzzle.removeEvent("mouseout",mouseOutHandler);
        puzzle.removeEvent("mousedown",mouseDownHandler);
        puzzle.removeEvent("mouseup",mouseUpHandler);

        var clearAnimeFlag=null,
        count=0;

        //按順序點亮所有拼圖的動畫
        var enterFrameHandler=function(){
            var item=puzzleArray[count++];
            item.fade(1);
            if(count<puzzleNumber){
                clearAnimeFlag=setTimeout(enterFrameHandler,50);
            }     
        };

         clearAnimeFlag=setTimeout(enterFrameHandler,50);

        //游戲完成后的信息~?
        puzzleNote.set('html','Congratulations ! Your final step count is <em class="step_count">'+moveStepCount+'</em>.');
    }
}

//創建全局變量puzzleGame
window.puzzleGame={};

//添加方法到全局變量puzzleGame中
puzzleGame.start = function() {
    puzzleConfigSet();
};

})(document.id);

puzzleGame.start();</pre>

 本文由用戶 程序猿123 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!