美文网首页
连连看

连连看

作者: 我爱吃花牛 | 来源:发表于2017-06-12 20:42 被阅读0次

    哇,终于写完了,算是初步实现连连看的基本功能吧,其他的等以后在慢慢完善吧~

    记录一下,这次代码实现中遇到的一些问题和想法吧,要不然时间久了,自己怎么写的也没印象了

    言归正传,开始:

    功能的实现主要是:javascript + vue2.js + jquery 来实现的

    文件目录如下图所示:

    文件目录
    功能实现图片

    1、第一步,是界面的实现,准备工作总要做好的,先来放一张目标图片,然后照着图片一步一步来

    根据上图可以看到,界面的实现其实和 table 差不多,行(row) 里 包含 列(col)  ,每个人写代码的风格不一样,我是通过ul 来实现。 第一个 li 是 行(row) ,里面的 ul 下所包含的 li 是 列(col) 。html代码如下图所示:

    html代码

    2、第二步,构造假数据 ,界面基于数据来实现,通过数据让页面丰富起来

    定义所需数据变量

    绘制界面不可或缺的需要用到 各种图片、行、列,其实开始这三种(type、row、col)是单独定义的,在写 createData 这个函数时,将这三个做为参数传进去,根据传进来的值创建 行 和 列。不过为了防止后期增加其他的参数或者参数过多造成混乱 所以直接定义成一个 json ,也就是上面定义的 pGroup数组 来存储这些 json数据 。包括后面要实现的增加游戏的难易程度都可以通过这组数据来实现。

    下面来写 创建数据函数 (createData) ,函数调用默认 createData(pGroup[0]) ,(相当于关卡1)

    循环 count ;用 i % 总数据的一半, 得到 一组相同 的数据;用得到的数据  % 传参进来的图片类型数量 ;然后添加到数组中。这样能保证数组中的 数据都是成对 出现,且范围都在传参进来的类型数量以内。

    将 arr 里的同类型的数据进行随机排序,这样,每次获取数据时,图片的排列顺序都不一样,展示效果也不一样。

    打乱类型数据

    循环传参进来的 parameter.row 和 parameter.col 并分别 加2 。界面上格子数量如下图所示。在循环 col 里 声明 定义 json ,每一个json代表一个格子所包含的信息,当两个格子链接时,起始格子通过自己本身的上,下,左,右四个方向的格子 json 下的 isRemove 的值是 true 或者 false 来判断线路是否可以到达

    而最外层(上,下,左,右)四个方向的格子是缺少一个或两个相邻的格子的,所以在循环时给行和列分别 加2 ,相当于在外面包上一层,这样处于 上部,下部,左部,右部的格子就可以不受没有相邻格子的方向的限制且默认设置为 isRemove = true ( 也就是隐藏 )。这样四边的格子在链接时,也有四个方向用来判断,而因为默认是隐藏,而不会影响正常的显示。效果如下图。(为了看的清晰,下图中的空格子设置是false,使用时改成true即可)

    所以在定义每个格子下的 json 信息时,做判断。如果是最外面的四个边,只需要存 x , y , isRemove 三个参数信息,且 isRemove = true 因为其他的参数是用不到的所以也无需设置。

    用到的数据参数进行一下说明,将他存到 json = {

        x , y : 格子分别的横纵坐标

        type : 图片的类型,后期根据类型去获取图片

        ID : �每个格子单独的标识,代号

        imgSrc : 通过 type 去 imgData 里找到相对应的路径信息,并记录

        isRemove : 通过赋值 true 或者 false 来定义格子是否显示或者隐藏,用来实现消除后的效果

        active : 通过 true 或者 false 来定义是否为选中状态

    }

    第一层循环是行,第二层循环是列,arr 里存的是一行的格子的信息,arrData里存的是多行的格子的信息,所以我们现在界面需要展示的所有信息都存在 arrData 里。


    3、第三步,将数据放到界面中进行展示

    不得不说,vue真是好用啊!将之前定义好存储所有格子信息的 arrData ,传给 实例的 msg 属性。

    msg : 格子信息数组

    o1 : 点击的第一个格子

    o2 : 点击的第二个格子

    level : 当前的关卡

    line : 存放路线

    在接下来的代码中通过指令,对 msg 进行操作。不进行过多的说明,具体可以看文档,

    v-show : 根据表达式之真假值,切换元素的displayCSS 属性。

    v-for : 基于源数据多次渲染元素或模板块。此指令之值,必须使用特定语法alias in expression,为当前遍历的元素提供别名;另外也可以为数组索引指定别名(或者用于对象的键):(row , i1) in msg

    @click : 监听事件,触发点击事件时,将 当前元素的 索引(i1,i2) 传参到函数中

    :class : 绑定属性,通过格子的 isRemove 和 active 来控制 class : hide 和 active 的使用


    第四步,来看当格子连接时触发的 add() 函数 

    点击时,判断 this.o1 下真或者假,如果是假(没有值) ,用传参进来的 行索引 和 列索引 去 存放所有格子信息的 arrData 里找到对应的 json数据,赋值给 this.o1 ,因为当前应该是选中状态,所以 this.o1.active = true ;

    this.o1 的选中状态

    如果 this.o1 已经存在,那么当前将值赋给 this.o2,同样是存放格子对应的 json数据。

    接下来进行判断,除去连接时的是否有障碍物的规则,最基本的要求是:

             1、两次点击不能是同一个格子,格子所对应的 json数据 下的 ID 是独一无二的哦;

            2、两个格子的类型必须是相同的(举个例子:蚊子和鱼是没法配对消除的哦)。而我们之前定义的图片是根据不同的 type 来定义的相对应的图片,

    所以当 ID相同,类型不同 ,是没法消除格子的,这时调用 clearOther(this) 函数。

    clearOther(this)

    因为以上条件,现在无法完成消除,这时 this.o1 对于我们来说,作用已经不大了,而 this.o2 将变成新的 this.o1 。所以先将两个的 active 状态都定义为 false 。 在取代完成后 ,赋予最新的 this.o1 的 active 状态 为 true 。因为后面需要常用到,所以将它封装成函数,在接下来的代码中,如果不满足格子配对消除的规则都将调用这个函数。


    反之如果不满足以上两个条件,进入 else,即 ID不同,类型相同,则正好满足我们的基本条件。

    ID 和 类型 的判断,只是连接成功的基本判断,接下来我们在满足这种基本条件的情况下,进行深入判断:

            1、两个挨在一起的相同的格子;

            两个挨在一起,在保证基本条件的情况下,必然是能够连接成功的,下面来写符合两个挨           在一起的规则,首先是知道每个格子的索引的 ( json.x 和 json.y ) ,也就是下图中所出现的         数字。例: 1,1  -->  2,1   两者分别相减,得到 x = -1 || 1 和 y = 0,然后 x + y = 1 ||               -1,因为两者只要相差是1就好,所以用绝对值来处理,总结规则如下:

            Math.abs(this.o1.x - this.o2.x) + Math.abs(this.o1.y-this.o2.y) == 1

            如果满足以上条件则调用 sameHide(this) 函数,drawLine(this,arr) 作用是画两个格子连接时的线,具体稍后在说,现在只要知道里面传两个参数 this 和 一个数组,就可以。

    sameHide(this)函数

          �首先让目标盒子也有选中效果 active = true。接下来声明变量并赋值 _this.o1 和 _this.o2 ;成功后,清空 _this.o1 和 _this.o2。因为在选中状态的时候有一个0.3秒放大的效果,所以用定时器,让数据在0.3秒后让o1,o2在 arrData 相对应的数据的状态 isRemove = true 。之所以用 a1,a2 来代替 _this.o1,_this.o2,是因为当 点完 o1 在点 o2 ,接着快速点击 其他同类型格子的时候,o2 会变成 o1,而最后点的格子会相继变成 o2 ,进而在满足条件下再次配对消除,所以找其他的变量储存信息后,就将 o1 和 o2 清空。 

    _this.line = [ ] ,配对连接完成后,因为连接时线的位置和展示是根据数组的值和length 决定的 所以在配对成功后,让线隐藏,将其清空。关于连线,后面会详细说明 。

            2、双方处于外层四条边上且在同一排的格子;

            接下来定义规则,单独把他抽出来,因为它本身比较特殊,如上图所示,拿底部的格子来举例,除去线路2 和 线路3 是需要判断中间是否有障碍物,线路1是直接就可以进行的,所以当满足这些条件后(上边 || 下边 || 左边 || 右边)来接着进行下一步。调用 route(this) 函数,因为第3种情况也需要调用此函数,所以在描述完第三种情况后一起说。

            上边 :_this.o1.x == 1 && _this.o2.x == 1

            下边 :_this.o1.x == pGroup[this.level].row && _this.o2.x == pGroup[this.level].row

            左边 :_this.o1.y == 1 && _this.o2.y == 1

            右边 :_this.o1.y == pGroup[this.level].col && _this.o2.y == pGroup[this.level].col

            3、其他情况:一种复杂的连接 和 连接失败,因为还要做更复杂的判断,所以我们将两个放在一起

    第三种情况

    很明显除去上面2种不用通过特别复杂的路线就可以实现配对消除的情况之外,第三种包含了两种情况。一种是通过复杂路线实现配对,一种是抛开基本要求(ID相同或类型不同),经过复杂的路线后依然不成功的情况。而这两种情况都需要在 调用 router(this) 函数之后才可以知道结果。


    第五步,调用 router(this) 。在说这个函数之前,要先说两个已经封装好的函数。 

    1、direction(o1,o2)      作用:判断方向 。 o1代表点击的第一个格子,o2代表第二个格子。 如果 : o1.x > o2.x ,是 h = -1 路线向左走 。两者 x 相等 ,h = 0 水平不动。除去前两种只剩下一种情况  o1.x < o2.x ,是 h = 1 路线向右走。y 同理,是垂直方向。

    direction(o1,o2)

    2、direction2(o1,o2)      作用:优先路线排序。在做判断的时候,我是以左右来做大方向,在大方向里在进行二次条件判断,这个根据个人的想法来这写,第一个判断 ,如果 o1.x > o2.x ,优先方向为 左,接着进行二次判断 如果 o1.y > o2.y ,那就是目标格子(o2)在 起始格子(o1)的左上角,所以优先路线定义为 arr = [ '左','上','下','右' ] 。 后面的判断同理,不一一做解释。判断全部结束后将数组做为函数的返回值。

    direction(o1,o2)

    其实除去两者出现 x 相同或 y 相同是有三种路线以外,其他的都是四种路线,如下图所示,所以需要封装四种路线的函数

    四种连接路线

    接下来来看 route(this) ,将之前 direction 和 direction2 的返回值接收,循环 direction2 的优先路线返回值 arr , 并做判断,上面已经说过,分别封装了四个方向的函数,这时根据循环数组的值来调用相对应的方向函数。

    而调用方向函数本身也是有返回值 ,如果配对成功,返回true,配对不成功则返回false,所以依次做判断,将返回值 pusharr里,如果当前路线配对成功,则跳出循环,如果当前配对不成功,则继续判断直到最后,并在结束后将 arr 做为函数的返回值。

    route(this)

    拿左 routeLeft(_this,x,y) 来举例,需要将之前判断方向(direction)中的值也就是后面声明定义的 x , y 传参进去。

    声明定义变量

    声明定义变量

    switchBtn :相当于一个开关,默认为false ,当路线没有障碍物是 设置为 true。

    mainLine(主干路线) , childLine(第一次转弯后路线) , subBranch(第二次转弯后路线) :记录路线经过哪些格子所对应的索引,最后由sum 进行数组整合,记录配对成功的完整路线。开始 push 初始路线(this.o1的x , y)的值到mainLine 。


    a)、如果 y = 0 , 说明两个格子的 y 值相同 ,都在 x 轴上,从 o1 起始,循环 o1 到 o2  的区间,将路线存到 mainLine 下,并对区间格子当前的 isRemove(隐藏或显示) 状态,进行判断,如果状态为 false ,则证明两者之前依然有障碍,无法连接成功,那 switchBtn 依然是 false ,将mainLine 清空,并结束整个循环。如果直到 目标格子的前一个,则证明区间状态都为 true (隐藏),配对格子之间已无障碍,连接配对成功,switchBtn = true 。

    接下来判断 swithcBtn ,这个判断代码后面会经常出现,对其进行下 说明。如果是真,push 目标格子索引,并调用以下函数

    sameHide(_this) 配对成功,改变状态,格子消失。refresh(_this)

    drawLine(_this , sum) : 之前已经说过是用来画线的函数,�稍后做详细说明

    sameHide(_this) : 已描述,配对成功,改变状态,格子消失

    refresh(_this) : 当没有满足配对要求的情况下,刷新界面,重新排序绘制

    如果是假 return false ;


    注:因为在开始存储格子的json信息的时候是 json.x = j ,json.y = i 方便按照正常的逻辑来展示,所以在取的时候同样需要向开始存储那样�获取 j 和 i 的位置下相对应的信息。




    b)、反之,则不在同一个轴上,如下图所示,只要和起始格子处于同一轴,那么这条轴相当于主干。如果主干上有障碍(格子 isRemove = false),那么这条线路将无法进行下去,如下图左,如果主干线上没有障碍(格子 isRemove = true), 那么可继续下去,再对支干 和 支干下的子支干 进行判断,如下图右,所有路线开始的前提是主干可以进行下去,否则白搭。所以先对主干线进行判断

    路线图

    循环 x 轴,直到最左侧,x = 0 , 循环判断如果主干线上格子当前的状态,如果 isRemove = false ,则证明障碍物的存在,也没有必要继续下去,将记录主干线路的数组清空,结束整个循环。配对连接失败 (如上图左所示)。

    (如图右所示)如果没有障碍物,则进行下一步,通过之前传参进来的 x , y ,因为 y > 0 得到所处的方向(左上),而支干路线又分为两种,一种是拐弯后直接到达。如上面路线图 5 线路,还有两种比较特别的是 4 和 6 线路,因为在路线过程中,只会去判断两个格子区间的状态,并不包含要配对的格子本身,所以无障碍到达目标格子高度时,即配对成功。而1 ,2 ,3 线路是当到达和目标格子高度一致的时候,再经过一个拐弯后才可到达。

    左上直达

    就像 5 线路那样,拐弯向上后直达就可到达目标格子。

    但是也有一种情况就是,假如现在连接的是(6,3)  -->  (1,2) , 这种情况的特点就是,在主干线格子的x = o2.x 的情况下,目标格子 y 坐标 - 初始格子 y 坐标 = 1 就行,因为现在是左上,所以是1,如果是左下就是 -1 ,所以直接给绝对值,避免因为数字导致判断错误。格子走到这一步,相当于配对成功,swichBtn = true  ,sum 整合 主干上的路线和支干上的路线。

    在说另一种,两个配对格子如上路线图 红框所示。主干上拐弯后,依然要向上走,直到目标格子的前一个,说明期间没有跳出循环,所有格子状态均为 isRemove = true ,sum 整合主干路线和支干上的路线。 最后通过 switchBtn 进行判断。

    非左上直达

    接着说 1,2,3,4,6 路线 。 第一次拐弯后,y 一直走,如果期间有格子 isRemove == false,则结束该循环。

    这时,4 和 6 路线比较特别,因为拐弯后就到达目标格子,所以同样规则:o2.x - i = 1 或者 -1 同样用绝对值来处理,配对成功,swichBtn = true  ,sum 整合 主干上的路线和支干上的路线。

    1,2,3 路线,当 y == o2.y 时,又该拐弯了,如果当前 x > o2.x ,往左拐,如果 x < o2.x ,往右拐,判断和之前大同小异,不一一做解释,如果到达 o2.x的前一个,依然没有结束循环,则证明 格子状态为 (isRemove = true), sum 整合数组,通过 swithBtn 做判断。

    push 目标格子的位置索引添加到 sum 中。调用函数。

    支干下的路线

    最后的最后 ,switchBtn 在做最后一次判断 ,函数中需要注意的是,全程只有在同一条轴上直达和代码的结尾 switchBtn 的判断有 else { return false } , 因为�情况只有直达和非直达,如果直达中有障碍物,那么代码可以结束。而非直达,要等到所有的路线确定完毕后,在反馈结果。

    routeLeft函数

    每个方向分别有一个函数,代码内容大同小异,现在回到 第四步 下所描写的第三种情况,复杂路线配对成功或者配对失败,因为在调用route(this)时,已经得到一个数组。里面是路线是否连接成功的状态,

    所以在这做代码判断,循环数组,如果有一个为true,则表示格子配对成功。如果都是false,则说明通过 上,下,左,右 都配对失败。所以调用 clearOther(this) 函数。

    第六步,关卡难度升级,界面重绘

    当界面所有的格子的 isRemove状态 等于 true 的时候,说明界面中所有的格子都已经消除成功。因为格子在消除时有0.3秒的动画效果,所以在0.3秒之后,格子的 isRemove 状态才会更改,所以在定时器里去循环判断每个格子的状态。如果还有格子的存在都 return 。

    如果 当前的关卡(从0开始计数) 已经 等于 数组的leng - 1 ,则说明已经到了最后一关,更改界面友情提示。如果不是最后一关,则进入下一关。给 this.leval + 1 ,并且去调用 �createData函数并将最新关卡数据传参进去 createData(pGroup[_this.level]) ,并将格子状态 isRemove 改成 false 。然后将同步数据 _this.msg = arrData 。

    难度升级,界面重绘

    第七步,画线

    现在配对成功后可以消除两个格子,但是当页面中的所有格子消除完成后,要进行下一个关卡,也就是之前定义好的 存储关卡的数据 pGrout  。但是连接线还没有画,接下来,画线。

    这需要用到之前已经整合好路线的数组 sum,循环数组,用当前数组 和 下一个做对比,如果 x有变化就是横线,如是是 y 有变化 就是竖线。前提条件 i < arr.length - 1 ,是因为最后一组数据是目标格子的数据,只做参考,不画线。

    当 arr[i].x < arr[i+1].x 时,从左往右连,直接设置left 即可,反之,则需要做针对位置进行调整,y 轴同理,将数值进行调整即可。

    第八步,无可链接数据时,刷新界面重新排序。点击按钮调用 repaint_page() 函数。

    点击调用

    循环 行 和 列 如果,当前的格子已经隐藏(isRemove = true),则跳过当前循环,如果依然显示在界面上,则将其位置 push 到 arr数组中。 全部添加完成后,将数组随机排序。再次循环 行 和 列 ,已经消失的依然消失,没消失的位置 重新根据 数组存储 下来的 type 进行设置,展示相对应的图片。

    repaint_page函数

    以上,是全部内容,感觉总结比写代码还累,不过也算再整理一遍思路,好外还是有的。。。希望下次自己忘了的时候,看到这些记录能回忆起来

    github地址:https://github.com/juanjuanGit/llk

    相关文章

      网友评论

          本文标题:连连看

          本文链接:https://www.haomeiwen.com/subject/tnptqxtx.html