文章出處

  拼圖游戲的代碼400行, 有點多了, 在線DEMO的地址是:打開

  因為使用canvas,所以某些瀏覽器是不支持的: you know;  

  為什么要用canvas(⊙o⊙)?  因為圖片是一整張jpg或者png, 我們要用把圖片導入到canvas畫布, 然后再調用上下文contextgetImageData方法, 把圖片處理成小圖, 這些小圖就作為拼圖的基本單位;

  如何判斷游戲是否結束, 或者說如何判斷用戶拼成的大圖是正確的? 我們就要在剛剛生成的小圖上面添加自定義屬性, 后期在小圖被移動后再一個個判斷,如果順序是對的, 那么這張大圖就拼接成功, 允許進入下一關;

  

  游戲一共有四個關卡, 不會有人通關的,真的....因為第四關把圖片的寬高分別切成了6份, 看著都暈好吧(∩_∩);

    

  

  因為要考慮到移動端的效果, 所以主界面圖片是根據屏幕適配, 拼圖大圖的大小是屏幕寬度和屏幕高度之間最小值的一半, 都是為了不出現滾動條。 比如:用戶的手機是橫屏模式, 這個橫屏的寬度是1000px,高度是300px, 如果我們把主圖片的寬設置為屏幕1000px的一半500, 那么垂直方向就出滾動條了;

  用戶的事件只要考慮上下左右四個方向鍵即可, 要判斷圖片是否可以移動, 也要考慮到當圖片移動的時候的動畫效果 ,感興趣的話考慮我的實現, 和我寫的2048是一樣的道理;2048的DEMO

  如果用戶覺得這些圖片不好看, 甚至可以上傳自己手機的圖片, 瀏覽器要支持FileReader的API, 移動是基于webkit的內核,可以不用考慮兼容性;

 

  代碼包含工具方法和一些基本的配置, 比如, 圖片地址的配置圖片要切成的塊數加載圖片的工具方法等:

        //游戲關卡的圖片和游戲每一個關卡要切成的圖片快個數
        var levels = ["lake.jpg","cat.jpg","follower.jpg","view.jpg"];
        var numbers = [3,4,5,6];


        //工具方法
        var util = {
            /**
             * @desc 圖片加載成功的話就執行回調函數
             * @param 圖片地址 || 圖片的DataUrl數據;
             */
            loadImg : function(e, fn) {

                var img = new Image;
                if( typeof e !== "string" ) {
                    img.src = ( e.srcElement || e.target ).result;
                }else{
                    img.src = e;
                };

                img.onload = function() {//document.body.appendChild( canvas );
                    //document.getElementById("content").appendChild( canvas );
                    fn&&fn();
                };

            }
        };

 

 

  代碼是基于面向對象(oop), 包含了兩個類 :ClipImage 類, Block 類: 

  ClipImage類

        /**
         *  @desc 把圖片通過canvas切成一塊塊;
         */
        function ClipImage(canvas , number) {
        };

        $.extend(ClipImage.prototype, {
            /**
            * @desc 根據關卡把圖片canvas切成塊canvas
            * 然后渲染到DOM;
            * */
            clip :  function () {

            },
            /**
             * @param 把canvas塊混排, 打亂排序;
             * */
            //使用底線庫的方法shuffle打亂排序;
            random : function( ) {
            },
            /**
             * @desc 把canvas渲染到DOM;
             * */
            renderToDom : function () {
            },

            updataDom : function(cav, obj) {

                this.updataMap();
                $(cav).animate({top:obj.y*this.avH,left:obj.x*this.avW});

            },

            updataMap : function () {
            },

            testSuccess : function () {
            }
        });

 

 

  Block類

        /**
         * @desc 對每一個canvas進行包裝;
         * @param canvas
         * @param left
         * @param top
         * @param avW
         * @param avH
         * @constructor Block
         */
        var Block = function(canvas, left, top,avW, avH) {
        };

        $.extend(Block.prototype, {
            /**
             * @desc 對每一個canvas進行定位, 然后添加到界面中;
             * */
            init : function () {

            },

            /**
             * @desc 對每一個canvas進行定位
             * */
            setPosition : function() {

            },

            /**
             * @desc 向上移動會執行的函數  ,通過判斷maps下有沒有對應的key值判斷, 界面中的固定位置是否被占用;
             * */
            upF : function(maps,numbers,cb) {


                };

            },

            /**
             * @desc 同上
             * */
            rightF : function(maps, numbers, cb) {


            },

            /**
             * @desc 同上
             * */
            downF : function (maps,numbers,cb) {


            },

            /**
             * @desc 同上
             * */
            leftF : function(maps,numbers,cb) {


            }
        });

 

  為了考慮移動端,我們使用了zepto封裝的swipe系列事件, 默認并沒有這個模塊, 我們要通過script標簽引用進來, github的地址為 https://github.com/madrobby/zepto/blob/master/src/touch.js#files

            $(document).swipeLeft(function() {
                run(clipImage,"leftF")
            }).swipeUp(function() {
                run(clipImage,"upF")
            }).swipeRight(function() {
                run(clipImage,"rightF")
            }).swipeDown(function() {
                run(clipImage,"downF")
            });

 

  雖然是一個小游戲,都是要考慮的東西真的不少,包括動畫效果, 是否可以移動, 更改數據模型, 是否成功進入下一個關卡等, 包含挺多的判斷;

 

  全部代碼, 提供思路, 代碼可以作為參考:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"/>
    <script src="http://cdn.bootcss.com/zepto/1.0rc1/zepto.min.js"></script>
    <script src="http://cdn.bootcss.com/underscore.js/1.8.3/underscore.js"></script>
    <style>
        body{
            margin:0;
        }
        #content{
            position: relative;
            margin:40px auto;
        }
        canvas{
            border:1px solid #f0f0f0;
            box-shadow: 2px 2px 2px #eee;
        }
    </style>
</head>
<body>
<input type="file" name="file" id="file"/>
<div class="container">
    <div class="row">
        <div class="progress">
            <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: 25%">
                <span class>當前是第<span id="now">1</span>關,共4關</span>
            </div>
        </div>
    </div>
    <div class="row">
        <div id="content" class="clearfix ">

        </div>
    </div>
</div>
<script>
    (function(fn) {

        fn($);

    })(function($) {

        //這個canvas是緩存圖片用的;
        var canvas = document.createElement("canvas");
        var minScreenWidth = Math.min( document.documentElement.clientWidth/ 2, document.documentElement.clientHeight/2 );
        canvas.width = minScreenWidth;
        canvas.height = minScreenWidth;
        document.getElementById("content").style.width =  minScreenWidth + "px";
        //保存了所有的block;
        var blocks = [];

        //工具方法
        var util = {
            /**
             * @desc 圖片加載成功的話就執行回調函數
             * @param 圖片地址 || 圖片的DataUrl數據;
             */
            loadImg : function(e, fn) {

                var img = new Image;
                if( typeof e !== "string" ) {
                    img.src = ( e.srcElement || e.target ).result;
                }else{
                    img.src = e;
                };

                img.onload = function() {
                    //canvas.width = img.width;
                    //canvas.height = img.height;
                    canvas.getContext("2d").drawImage( img, 0, 0 ,canvas.width, canvas.height);
                    //document.body.appendChild( canvas );
                    //document.getElementById("content").appendChild( canvas );
                    fn&&fn();
                };

            }
        };

        //綁定事件;
        function bindEvents () {

            var file = $("#file");

            file.bind("change", function(ev) {
                var reader = new FileReader;
                reader.onload = function(e) {
                    util.loadImg(e, function() {
                        window.clipImage = new ClipImage(canvas, numbers[window.lev]);
                        window.clipImage.random();
                        Controller( window.clipImage, numbers[window.lev]);
                    });
                };
                reader.readAsDataURL(this.files[0]);
            });

        };

        //游戲關卡的圖片和游戲每一個關卡要切成的圖片快個數
        var levels = ["http://sqqihao.github.io/games/jigsaw/lake.jpg","http://sqqihao.github.io/games/jigsaw/cat.jpg","http://sqqihao.github.io/games/jigsaw/follower.jpg","http://sqqihao.github.io/games/jigsaw/view.jpg"];
        var numbers = [3,4,5,6];

        /**
         *  @desc 把圖片通過canvas切成一塊塊;
         */
        function ClipImage(canvas , number) {
            //blocks是一個二維數組,保存的是所有的canvas方塊;
            this.blocks = [];
            //instances是一維數組,保存的是實例化的數組;
            this.instances = [];
            this.maps = {};
            this.canvas = canvas;
            this.context = this.canvas.getContext("2d");
            this.number = number;
            this.clip();

        };

        $.extend(ClipImage.prototype, {
            /**
            * @desc 根據關卡把圖片canvas切成塊canvas
            * 然后渲染到DOM;
            * */
            clip :  function () {

                var avW = this.avW = this.canvas.width/this.number;
                var avH = this.avH = this.canvas.height/this.number;

                for(var i=0; i< this.number; i++ ) {
                    for(var j=0; j<this.number; j++ ) {
                        this.blocks[i] = this.blocks[i] || [];
                        var canvas = document.createElement("canvas");
                        canvas.width = avW;
                        canvas.height = avH;
                        canvas.x = j;
                        canvas.y = i;
                        canvas.map = i+"_"+j;
                        canvas.correctMap = i+"_"+j;
                        var imageData = this.context.getImageData(j*avW, i*avH, avW, avH);
                        canvas.getContext("2d").putImageData( imageData, 0, 0 );
                        if( i === j && j=== (this.number-1) )break;
                        // 把canvas放到二維數組blocks中;
                        this.blocks[i][j] =  canvas;
                    };
                };
                this.renderToDom();

            },
            /**
             * @param 把canvas塊混排, 打亂排序;
             * */
            random : function( ) {
                var len = this.instances.length;
                while(len--) {
                    $(this.instances[len].canvas).remove();
                };
                //使用底線庫的方法shuffle打亂排序;
                this.blocks = _.shuffle(this.blocks);
                for(var i=0 ;i <this.blocks.length; i++) {
                    this.blocks[i] = _.shuffle(this.blocks[i]);
                }
                this.renderToDom();
            },
            /**
             * @desc 把canvas渲染到DOM;
             * */
            renderToDom : function () {
                document.getElementById("content").innerHTML = "";
                this.maps = {};
                this.doms = [];
                this.instances = [];
                for(var i=0; i<this.blocks.length; i++ ) {
                    for(var j=0; j<this.blocks[i].length; j++) {
                        var instance = new Block( this.blocks[i][j], j, i ,this.avW, this.avH);
                        //把實例化的數據保存到instances
                        this.instances.push( instance );
                        this.maps[i+"_"+j] = true;
                    };
                };
            },

            updataDom : function(cav, obj) {

                this.updataMap();
                $(cav).animate({top:obj.y*this.avH,left:obj.x*this.avW});

            },

            updataMap : function () {
                this.maps = {};

                var len = this.instances.length;
                while(len--) {
                    this.maps[this.instances[len].canvas.y + "_" + this.instances[len].canvas.x] = true;
                    this.instances[len].canvas.map = this.instances[len].canvas.y + "_" + this.instances[len].canvas.x;
                };
                /*
                for(var i=0; i<this.blocks.length; i++ ) {
                    for (var j = 0; j < this.blocks[i].length; j++) {
                        this.maps[this.blocks[i][j].y + "_" + this.blocks[i][j].x] = true;
                    }
                }*/
            },

            testSuccess : function () {

                var len = this.instances.length;
                while(len--) {
                    //只要有一個不等就無法成功;
                    if(this.instances[len].canvas.correctMap !== this.instances[len].canvas.map) {
                       return ;
                    };
                };
                console.log("成功");
                if( ++window.lev >=4 ) {
                    alert("已經通關");
                    return ;
                } ;
                $("#now").html( window.lev + 1 );
                $(".progress-bar").width( (window.lev+ 1) * 25 + "%" );
                init(window.lev);
            }
        });

        /**
         * @desc 對每一個canvas進行包裝;
         * @param canvas
         * @param left
         * @param top
         * @param avW
         * @param avH
         * @constructor Block
         */
        var Block = function(canvas, left, top,avW, avH) {
            this.canvas = canvas;
            this.left = left;
            this.top = top;
            this.avW = avW;
            this.avH = avH;
            this.init();
        };

        $.extend(Block.prototype, {
            /**
             * @desc 對每一個canvas進行定位, 然后添加到界面中;
             * */
            init : function () {

                this.canvas.style.position = "absolute";
                this.canvas.style.left = this.avW*this.left +"px";
                this.canvas.style.top = this.avH*this.top +"px";
                this.canvas.x = this.left;
                this.canvas.y = this.top;
                document.getElementById("content").appendChild( this.canvas );

            },

            /**
             * @desc 對每一個canvas進行定位
             * */
            setPosition : function() {

                this.canvas.style.left = this.avW*this.canvas.x +"px";
                this.canvas.style.top = this.avH*this.canvas.y +"px";

            },

            /**
             * @desc 向上移動會執行的函數  ,通過判斷maps下有沒有對應的key值判斷, 界面中的固定位置是否被占用;
             * */
            upF : function(maps,numbers,cb) {

                //如果目標有
                var temp = (this.canvas.y>0 ? (this.canvas.y-1) : this.canvas.y);
                var targetXY =  temp+"_"+this.canvas.x;
                if( !maps[targetXY] ) {
                    this.canvas.y = temp;
                    this.canvas.map = targetXY;
                    //alert("可以走")
                    cb(this.canvas, {
                        x : this.canvas.x,
                        y : this.canvas.y
                    });
                    return true;
                };

            },

            /**
             * @desc 同上
             * */
            rightF : function(maps, numbers, cb) {

                var temp = ((this.canvas.x+1>numbers-1) ? this.canvas.x : this.canvas.x+1);
                var targetXY = this.canvas.y+"_"+temp;
                if( !maps[targetXY] ) {
                    this.canvas.x = temp;
                    this.canvas.map = targetXY;
                    //alert("可以走")
                    cb(this.canvas, {
                        x : this.canvas.x,
                        y : this.canvas.y
                    });
                    return true;
                };

            },

            /**
             * @desc 同上
             * */
            downF : function (maps,numbers,cb) {

                var temp = ((this.canvas.y+1>numbers-1) ? this.canvas.y : this.canvas.y+1);
                var targetXY = temp+"_"+this.canvas.x
                if( !maps[targetXY] ) {
                    this.canvas.y = temp;
                    this.canvas.map = targetXY;
                    cb(this.canvas, {
                        x : this.canvas.x,
                        y : this.canvas.y
                    });
                    //alert("可以走");
                    return true;
                };

            },

            /**
             * @desc 同上
             * */
            leftF : function(maps,numbers,cb) {

                var temp = ( (this.canvas.x-1)>=0 ? this.canvas.x-1 : this.canvas.x );
                var targetXY = this.canvas.y+"_"+temp;
                if( !maps[targetXY] ) {
                    this.canvas.x = temp;
                    this.canvas.map = targetXY;
                    //alert("可以走")
                    cb(this.canvas, {
                        x : this.canvas.x,
                        y : this.canvas.y
                    });
                    return true;
                };

            }
        });

        /**
         * @desc 主要控制器;
         *
         * */
        function Controller( clipImage, number) {
            var run = function( clipImage, name ) {
                //window.clipImage.doms ,window.clipImage.maps, numbers[level], window.clipImage.updataDom.bind(window.clipImage)
                for(var i=0; i<clipImage.instances.length; i++ ) {
                    var instance = clipImage.instances[i];
                    if( instance[name].bind(instance)(clipImage.maps, number, clipImage.updataDom.bind(clipImage)) ) {
                        clipImage.testSuccess();
                        return
                    };
                };
            }

            $(window).unbind("keydown");

            $(window).bind("keydown", function(ev) {
                var name;
                switch(ev.keyCode) {
                    case 37 :
                        name = "leftF";
                        break;
                    case 38 :
                        name = "upF";
                        break;
                    case 39 :
                        name = "rightF";
                        break;
                    case 40 :
                        name = "downF";
                        break;
                    default :
                        ev.preventDefault();
                        return false
                };
                run( clipImage, name );
                ev.preventDefault();
            });

            $(document).swipeLeft(function() {
                run(clipImage,"leftF")
            }).swipeUp(function() {
                run(clipImage,"upF")
            }).swipeRight(function() {
                run(clipImage,"rightF")
            }).swipeDown(function() {
                run(clipImage,"downF")
            });


        };

        function init(level) {

            util.loadImg( levels[level] ,function() {

                window.clipImage = new ClipImage(canvas, numbers[level]);
                window.clipImage.random();
                Controller( window.clipImage, numbers[level] || 3);

            });

        };

        $(function() {

            window.lev = 0;
            init(lev);
            bindEvents();

        });
    });
</script>
</body>
</html>
View Code

 

        如果有bug直接評論, 我會修正, 提git的issue也行,

  DEMO地址查看:打開

 

作者: NONO
出處:http://www.cnblogs.com/diligenceday/
QQ:287101329 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()