JavaScript任意数量的拼图游戏

清华大佬耗费三个月吐血整理的几百G的资源,免费分享!....>>>

(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();