zoukankan      html  css  js  c++  java
  • JavaScript写一个拼图游戏

      拼图游戏的代码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=" 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 

  • 相关阅读:
    Spark系列文章(三):搭建Spark开发环境IDEA
    MAC下搭建Hadoop运行环境
    Spark系列文章(二):Spark运行环境构建
    Spark系列文章(一):Spark初识
    Mac配置Maven及IntelliJ IDEA Maven配置
    《VC++深入详解》学习笔记 第十八章 ActiveX控件
    《VC++深入详解》学习笔记 第十七章 进程间通信
    Git 常用指令
    BAT脚本
    让Git的输出更友好: 多种颜色和自定义log格式
  • 原文地址:https://www.cnblogs.com/diligenceday/p/4659832.html
Copyright © 2011-2022 走看看