HTML 简单拼图游戏

先不废话,请看演示。

公司要搞这么个微信活动,可现在没有前端开发,没办法,身为打杂总监只好临时顶下这个空缺了。先找了一些 JS 代码,试用了下都不太理想,好一点的写的又太复杂,改起来有难度,干脆撸起袖子自己干了。

基本需求

有一个固定区域,被拆分成 c*r 个同等大小的碎片,拿走其中一块,靠近缺口的块可以向缺口方向移动;当拼出原来的图样视为完成。

依照此需求,需要经历 加载图片-》拆分图片-》随机打散-》移动碎片-》判定完成 这些步骤。为了更有可玩性,能自行选择自己的图片就更妙了。

下面就重点说明下各个步骤,为编写方便,引入 jQuery 作为辅助库。

加载图片

首先当然是载入图片,计算宽高,对比拼图区域的尺寸进行缩放,如果比例不同,还得“裁剪”掉多余的部分。

/

  • 加载图片

  • cal 的回调参数为:

  • ox 横向偏移

  • oy 纵向偏移

  • this 指向载入的图片的 jQuery 对象

  • @param {String} src 图片路径

  • @param {int} w 额定宽

  • @param {int} h 额定高

  • @param {Fucntion} cal 加载完成后的回调方法
    /
    function loadr(src, w, h, cal) {
    var img = new Image();
    img.onload = function() {
    var xw = img.width ;
    var xh = img.height;
    var zw = xh
    w / h;
    if (zw > xw) {
    // 宽度优先
    img.width = w;
    img.height = xh w / xw;
    xh = (h - img.height) / 2;
    xw = 0;
    } else {
    // 高度优先
    img.height = h;
    img.width = xw
    h / xh;
    xw = (w - img.width ) / 2;
    xh = 0;
    }

     cal.call(img, xw, xh);

    };
    img.src = src ;
    }
    以上的“裁剪”仅仅是计算出偏移,然后将其传递给加载就绪的回调函数。

拆分图片

图有了,已缩放,现在需要“拆分”成碎片。这里自然不是真的切割了,而是将图片 clone 出 c*r 片,然后利用负的坐标定位,其实质是用一个块遮盖了“切除”的部分,仅显示需要的碎片部分。

/

  • 拆分图片

  • @param {jQuery} that 容器对象

  • @param {int} cols 行

  • @param {int} rows 列

  • @param {int} ew 板块宽度

  • @param {int} eh 板块高度

  • @param {int} ox 图片横向偏移

  • @param {int} oy 图片纵向偏移

  • @param {Image} im 图片对象
    */
    function split(that, cols, rows, ew, eh, ox, oy, im) {
    that.empty();

    for(var j = 0 ; j ');
    pic.attr("id", "pt-pic-"+k);
    pic.data("idx", k);
    pic.appendTo(that);
    pic.css ({
    "position": "relative",
    "overflow": "hidden",
    "border" : "0",
    "width" : ew + "px",
    "height" : eh + "px"
    });

         var img = $(im.cloneNode());     img.appendTo(pic);     img.css ({         "position": "absolute",         "z-index" : "88",         "border"  : "0",         "left"    : (0 - i * ew + ox) + "px",         "top"     : (0 - j * eh + oy) + "px"     });     // 因边框可能影响宽高计算, 故边框单独用一个块来放     var bor = $('');     bor.appendTo(pic);     bor.css ({         "position": "absolute",         "z-index" : "99",         "width"   : "100%",         "height"  : "100%"     });     // 由于样式宽高并不含边框, 故再次计算尺寸的偏移量     bor.css ({         "width"   : (2 * bor.width () - bor.outerWidth ()) + "px",         "height"  : (2 * bor.height() - bor.outerHeight()) + "px"     }); }

    }
    }
    稍微注意,为方便人眼分辨碎片,最好给碎片加个边框,但加边框必然影响坐标的计算,故在图片上再覆盖一层,边框设在他上面,就算加个撕裂效果的透明图做边框都没问题了。这样碎片内图片的偏移坐标的计算就少了些麻烦了。

随机打散

这游戏当然是跟电脑玩了,总不能自己打散自己玩吧?但这个打散不能给每个图片一个随机位置,那很可能你永远也拼不回去了。小时拿那种拼图游戏板整人就干过这种事,故意抠下来把头和脚交换再打散,然后跟其他小朋友打赌。所以程序也得守规矩一块一块的移动。

/

  • 打散图片

  • @param {jQuery} that 容器对象

  • @param {int} cols 列

  • @param {int} rows 行

  • @param {int} rand 打散步数
    /
    function upset(that, cols, rows, rand) {
    var v ;
    var r = Math.floor(Math.random()
    cols * rows);
    var hole = that.children().eq(r).addClass("pt-pix");
    var part ;
    var step = [];
    var dbug = [];
    for(var i = 0, j = rand; i 0 && rx 0) {
    rv.push(r - 1); // 可向左移动
    } else
    {
    rv.push(r + 1); // 可向右移动
    }
    if (ry > 0 && ry 0) {
    rv.push(r - z); // 可向上移动
    } else
    {
    rv.push(r + z); // 可向下移动
    }

     // 排除来源位置 if (step.length > 0) {     v = step[step.length - 1];     v = $.inArray(v, rv);     if (v > -1) {         rv.splice(v, 1 );     } } // 排除回旋位置 if (step.length > 2 && rv.length > 1) {     v = step[step.length - 3];     v = $.inArray(v, rv);     if (v > -1) {         rv.splice(v, 1 );     } } // 随机方向 r = rv[Math.floor(Math.random()* rv.length)]; v = hole.index();     step.push(v); // 交换位置 part  = that.children().eq( r ); if (r  v) {     dbug.push("上"); } else if (r 

    把打散的步骤记录下来,然后反转数组,就是攻略啦。

不过随机时需要避免往回走,否则出现 左->右->左 这类情况就不好玩了;还得避免其他循环,如 上->右->下->左 这样的,这会回到原点,等于什么也没干;但更大的循环没想好怎么处理,暂时不去纠结了。

移动判定

移动碎片到缺口,也就是交换碎片与缺口的位置。左右移动很简单,序号大的 insertBefore 序号小的即可。上下移动有个小坑,开始自己没注意,我原本想不管横向还是纵向,没有两次 insertBefore 搞不定的,但是如果 3 和 7 交换位置(3x3, 0~8),3 移动到 7 前,7 再移动到 3 前,此时原来的 3 变成了 6。所以,确实是没有什么不能两次 insertBefore 解决的,但还得考虑让序号大的先动。

/

  • 移动板块

  • @param {jQuery} that 容器对象

  • @param {int} cols 列数

  • @param {int} rows 行数

  • @param {jQuery} hole 缺口对象

  • @param {jQuery} part 板块对象
    */
    function mover(that, cols, rows, hole, part) {
    var move = false ;
    var i = part.index();
    var j = hole.index();
    var ix = i % cols;
    var jx = j % cols;
    var iy = Math.floor(i / cols);
    var jy = Math.floor(j / cols);

    if (iy == jy) { // 在同一行
    move = ix == jx + 1 // 可向左边移动
    || ix == jx - 1; // 可向右边移动
    } else
    if (ix == jx) { // 在同一列
    move = iy == jy + 1 // 可向上移动
    || iy == jy - 1; // 可向下移动
    }

    // 互换位置
    if (move) {
    if (i
    判断是否完成就来个笨办法了,依次遍历所有版块,只要有一个没对上就是还没成功了。

未处理滑动事件,以后闲了再加吧。

整合游戏程序

上面分散的几个函数用起来还是不太方便,整合成一个 jQuery 插件。

/

  • 拼图游戏

  • @param {String} src 图片路径

  • @param {int} cols 列数

  • @param {int} rows 行数

  • @param {int} rand 打散步数
    */
    $.fn.hsPintu = function(src, cols, rows, rand) {
    var that = $(this);
    var srz = that.data("src");
    var img = that.data("img");

    var aw = that.width ();
    var ah = that.height();
    var ew = aw / rows;
    var eh = ah / cols;

    // 状态: 0 进行中, 1 成功, 2 结束
    that.data("hsPintuStatus", 2);
    that.data("cols", cols);
    that.data("rows", rows);

    /

    • img 存在且 src 没变化

    • 则不需要再次加载图片

    • 直接取出存储好的数据
      */
      if (img && srz === src) {
      var ox = that.data("pos_x");
      var oy = that.data("pos_y");
      console.log("Note: 图片无变化");

      split(that, cols, rows, ew, eh, ox, oy, img );

      // 未给 rand 则仅拆分而不打散
      if (rand === undefined) return;

      upset(that, cols, rows, rand);
      that.data("hsPintuStatus", 0);
      that.trigger("hsPintuLaunch");
      } else
      loadr(src, aw, ah, function(ox, oy) {
      that.data("src", src );
      that.data("img", this);
      that.data("pos_x", ox);
      that.data("pos_y", oy);
      console.log("Note: 载入新图片");

      split(that, cols, rows, ew, eh, ox, oy, this);

      // 未给 rand 则仅拆分而不打散
      if (rand === undefined) return;

      upset(that, cols, rows, rand);
      that.data("hsPintuStatus", 0);
      that.trigger("hsPintuLaunch");
      });

    // 已经初始化过就不要再绑定事件了
    if (! that.data("hsPintuInited")) {
    that.data("hsPintuInited", 1);

     that.on("click", ".pt-pic:not(.pt-pix)", function() {     if (that.data("hsPintuStatus") === 0) {         var cols =that.data("cols");         var rows =that.data("rows");         var hole =that.children(".pt-pix");         if (mover(that, cols, rows, hole, $(this))) {             that.data("hsPintuStatus", 1);             that.trigger("hsPintuFinish");         }     } });

    }

    return this;
    };
    用 $("# pt-box").hsPintu(图片URL, 列数, 行数[, 随机步数]); 即可初始化拼图游戏了, 拼图区域需要固定宽高;随机步数参数不提供时,仅拆解不打散。

图片没变化时没必要重新加载,避免下时间损耗。当然了,更好的办法是再判断行、列和区域尺寸,没变化则直接排列好碎片。懒得写了,就先这样了。

选择任意图片

上面都是固定的图片,参与感不好,让用户自行“上传”图片岂不更有意思。其实不必真的上传到服务器,既然“缩放”、“裁剪”上面都有了,直接加载本地图片不就好了嘛。

/

  • 预载文件
  • @param {Function} cal 回调函数
  • @returns {jQuery} 当前文件节点
    */
    $.fn.hsFileLoad = function(cal) {
    this.each(function() {
    var that = this;
    if (window.FileReader) {
    var fr = new FileReader( );
    fr.onloadend = function(e) {
    cal.call(that, e.target.result);
    }; cal.call(that);
    $.each( this.files, function(i, fo) {
    fr.readAsDataURL( fo );
    });
    } else
    if (this.getAsDataURL) {
    cal.call(that, that.getAsDataURL());
    } else {
    cal.call(that, that.value);
    }
    });
    return this;
    };
    这段代码也能从我的开源项目内找到 预载文件 方法,此工具包还有些其他的文件上传预览类的方法,这是我对 bootstrap-fileinput 没有图片裁剪功能(与最终服务端处理后的结果一致)而“一气之下”自己写的一点零散代码。

完整的代码及演示可在 这里 看到,有朋友说看不到图,但图片我用的百度图片搜索的缩略图,不清楚怎么回事,看不到可以自己从本地选择图片,只是那个“加载”(Image.onload)和“切片”(Image.cloneNode)比较耗时,比较大的图片等等就好。

当然了,也可以光顾我们的活动页玩一把 拼图抽奖。

关键字:html, html5, JavaScript, 小游戏

版权声明

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处。如若内容有涉嫌抄袭侵权/违法违规/事实不符,请点击 举报 进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部