用JS写一个拼图游戏

思路:

  1. 上传一张自己的本地的图片
  2. 将图片分割成若干行若干列
+ 给每一个小格子,添加上自己的索引
+ 把索引存入一个数组中,方便最后判断是否完成
  1. 分割的每一个小格子上都显示完整图片对应的一部分
  2. 点击开始游戏按钮,打乱图片的位置
+ 创建一个新数组,将原数组打乱之后存到新数组中
+ 让每一个小格子根据打乱后的数组进行定位
  1. 点击两个不同的小格,让其交换位置。
+ 小格子位置交换完成后,需要将打乱的数组对应的两个值也交换位置。
  1. 判断交换后的乱序数组和原来的数组是否相等,如果相等即表示游戏完成。

难点:

  1. 如何上传本地图片让其显示的
  2. 如何将一张图片切割成多个小格子
  3. 如果根据乱序的数组来计算当前的行数和列数

解决办法:

  1. 针对于如何上传一张本体图片,看之前的一篇关于浏览器如何在页面上显示准备上传的图片的问题,下面是传送门。
+ <a target = "_blank" href="http://www.jianshu.com/p/9e4a1ee03089">关于浏览器如何在页面上显示准备上传的图片的问题</a>
  1. 针对如何切割一张图片的问题,事实上也并不能说是切割,且听我细细道来。
+ 首先我们利用两个for循环,外层代表行数,里面代表列数,然后动态创建div来表示每一个小方格。
+ 然后我们分隔好了小方格,我们也知道小方格的宽和高,并且知道每行每列各有多少个小方格。
+ 之后我们给每一个小方格都设置一个`background-image`属性,让每一个方格都有一个完整的图。所以说这里并不是一张背景图,而是有多少个小方格就有多少张图片。
+ 再之后根据行数和列数的数量来设置`background-size`,比如只有一个格子,`background-size`的值就是 100% ,同理,两个值就是200%
+ 最后根据当前第几行,第几列,每一个小格子的宽度和高度来计算出偏移量,即left值和top值。再利用`background-position`来进行定位即可实现。
  1. 针对根据乱序数组来计算当前的行数和列数。这里其实也并不难,只是我个人卡在这里卡了一会儿。。。。
+ 首先我们的乱序数组是根据原来的数组打乱顺序而来,所以里面的数值除了顺序应该就是一样的。
+ 当我们点击图片的时候能够获取到图片所在的div的索引。
+ 因为原数组是按照0,1,2,3,4.....这样排列的,所以这个索引在乱序数组中对应的值就是打乱后的图片是从0开始数的第多少张图片。
+ 知道了这张图片此时是属于第几张,我们就能根据这个值来计算出这张图片此时对应的行数和列数。   
  - 当前是第几张图片 / 一行多少列 然后取整 就是当前属于第几行
  - 当前是第几张图片 % 一行多少列 然后取余 就是当前属于第几列

这里我用一张动图来表示如果切割图片的

如何把一张图切割成多块.gif

注意点:

  1. 打乱索引 的时候要注意,由于打乱的数组可能跟原数组还是一样,所以需要控制一下。
  2. 上传的本地的图片不要太大,不然读取的速度那还真有点慢。。。
  3. 上传的图片需要是个正方形的,实在没有正方向的话,高度大于宽度的图片也行,无非是下半部分被干掉了。。
  4. 因为个人比较懒。很多地方没有写兼容,所以打开小游戏请使用chrome浏览器

我们来看看关键代码

  1. HTML部分
  <!-- 获取图片 -->
  <input type="file" id="file">
  <!-- 字体图标 -->
  <div id="btn">
      <i class="iconfont icon-xiangji2"></i>      
  </div>
  <div id="gameArea">
      <!-- 游戏开始按钮 -->
      <div id="gameStart">点击开始</div>
      <!-- 图片存放的区域 -->
      <div id="imgArea"></div>
  </div>
  1. CSS部分

css部分真没啥好说的,有兴趣的话大家下载源码,一看就知道了

  1. JS部分
  • 切割图片
      // 切割图片
    imgSplit:function(){
        // 清空图片存放区域
        this.imgArea.innerHTML = '';
        // 用来存方动态创建的div元素
        var _cell = '';
    
        // 行数
        for (var i = 0, l =this.leverArr[0]; i<l ;i++) {
            // 列数
            for (var j = 0,l =this.leverArr[1]; j<l ;j++) {
                // 给每张图片一个索引值
                // 索引递增规则为  从左到右  从上到下
                this.imgOrigArr.push(i*this.leverArr[0] + j);
                // 创建div
                _cell = document.createElement('div');
                // 给div添加id
                _cell.className = "imgCell";
                // 给每张一个索引,方便后面点击的时候进行判断
                _cell.index = i*this.leverArr[0] + j;
                // 给div添加样式
                _cell.style.width = this.cellWidth+'px';
                _cell.style.height = this.cellHeight+'px';
                _cell.style.left =  j*this.cellWidth+'px';
                _cell.style.top = i*this.cellHeight + 'px';
                _cell.style.backgroundImage= "url("+this.imgUrl+")";
                // 这里因为100%就让背景图的大小等于了一个小格子的大小
                // 而我们只需要原始图的一部分,并不是想缩小原图
                // 所以根据小格子的个数来放大图片
                _cell.style.backgroundSize = this.leverArr[1]+'00%';
                // 移动背景图,行成最后切成的一块块的效果
                _cell.style.backgroundPosition = (-j)*this.cellWidth + 'px ' + (-i)*this.cellHeight+'px';
                // 让背景图从边框开始平铺
                _cell.style.backgroundOrigin = "border-box";
                this.imgArea.appendChild(_cell);
            }
        }
    
        // 获取小格子的dom元素
        this.imgCells = document.querySelectorAll('.imgCell');
    
        //将选择图片的按钮移动到可视区域外
        this.btnObj.style.left= -this.btnObj.offsetWidth+'px';
        // 将图片移入可视区域
        // this.gameAreaObj.style.background = 'url('+this.imgUrl +')';
        this.gameAreaObj.style.left = '50%';
        this.gameAreaObj.style.transform= 'translate(-50%,-50%)';
    
        // 使按钮绑定事件,点击按钮开始整个游戏
        this.gameStartBtnObj.onclick = this.clickHandle();
    }
    
  • 打乱图片索引
      // 打乱图片索引
    randomArr:function(){
        // 清空乱序数组
        this.imgRandomArr = [];
        // 判断原来的数组是否和乱序数组一样
        var _flag = true;
        // 遍历原始索引
        for(var i=0,l=this.imgOrigArr.length;i<l;i++){
            // 获取从0到数组长度之间的一个索引值
            var order = Math.floor(Math.random()*this.imgOrigArr.length);
            // 如果乱序数组中没有值就直接添加
            // 否则就在这个乱序数组中找对应的随机数的索引,找不到就添加,找到就继续随机
            if(this.imgRandomArr.length>0){
                while(this.imgRandomArr.indexOf(order) >-1){
                    order = Math.floor(Math.random()*this.imgOrigArr.length);
                }
            }
            this.imgRandomArr.push(order);
        }
    
        // 判断乱序数组和原始数组是否一样
        if(this.imgRandomArr.length === this.imgOrigArr.length){
            // 遍历数组
            for(var i=0,l=this.imgOrigArr.length;i<l;i++){
                if(this.imgRandomArr[i] != this.imgOrigArr[i]){
                    _flag = false;
                    break;
                }else{
                    _flag = true;
                }
            }
        }else{
            _flag = true;
        }
    
        // 返回值为true的话 就代表原始数组和乱序数组一致,重新打乱数组
        if(_flag){
            this.randomArr();
        }
    }
    
  • 交换两次点击的图片位置
      // 交换两次点击的图片位置
    cellExchange:function(from,to){
        // 因为图片此时的排序是根据 以图片的索引值为索引
        // 在乱序的数组中根据相对应的索引取出的值作为当前图片的排序位置的
        // 因此根据from to这两个值作为索引,就能在乱序数组中得到当前图片是第几张
    
        // 求出from的图片 是第几行第几列
        // 当前是第几张图片 / 一行多少列 然后取整 就是当前属于第几行
        var _fromRow = Math.floor(this.imgRandomArr[from] / this.leverArr[1]);
        // 当前是第几张图片 % 一行多少列 然后取余 就是当前属于第几列
        var _fromCol = this.imgRandomArr[from] % this.leverArr[1];
        // 求出to的图片 是第几张第几列
        var _toRow = Math.floor(this.imgRandomArr[to] / this.leverArr[1]);
        var _toCol = this.imgRandomArr[to] % this.leverArr[1];
    
        // 移动两张图片
        this.imgCells[from].style.left = _toCol*this.cellWidth + 'px';
        this.imgCells[from].style.top = _toRow*this.cellHeight + 'px';
    
        this.imgCells[to].style.left = _fromCol*this.cellWidth + 'px';
        this.imgCells[to].style.top = _fromRow*this.cellHeight + 'px';
    
        // 将乱序数组中的两个值交换位置
        // 定义一个临时变量来实现交换顺序
        var _temp = this.imgRandomArr[from];
        this.imgRandomArr[from] = this.imgRandomArr[to];
        this.imgRandomArr[to] = _temp;
    
        //如果乱序数组和原数组一致,则表示拼图已完成
        if(this.imgOrigArr.toString() === this.imgRandomArr.toString()){
            // 调用成功方法
            this.success();
        }
        
    }
    
源码保存在了<a target = "_blank" href="https://github.com/MagicianShiro/gamePractice">github</a>上,有爱点击github自取。
在线地址可以直接测试使用,<a target = "_blank" href="http://139.199.6.43/puzzle/puzzle.html">拼图小游戏</a>←点击跳转即可

总结:

  1. 最近不知道怎么了,老是不在状态,脑袋反应比较慢,一个简单问题总是要想好久。感觉整个人都魔怔了。。。。
  2. 本来我是想自动获取图片宽高来切成行列的,不过行数列数不相等的话,好像有点麻烦。而且不是正方形也不好看啊。想了想还是不弄了。。想兴趣的朋友自己弄弄吧。。
  3. 顺便一提,上面的动图所用到的图片,画师是P站的 METO@三日目東ミ24a
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容

  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,706评论 2 17
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,046评论 1 10
  • 《ijs》速成开发手册3.0 官方用户交流:iApp开发交流(1) 239547050iApp开发交流(2) 10...
    叶染柒丶阅读 5,024评论 0 7
  • 以下是常用的代码收集,学习用。转自豪情博客园 1. PC - js 返回指定范围的随机数(m-n之间)的公式 re...
    自由加咖啡阅读 989评论 0 1
  • There are six kinds of coins in Canadian dollar, they are...
    悬崖上的小树阅读 343评论 3 6