zoukankan      html  css  js  c++  java
  • 广度优先搜索的应用——将一个图形切成多块

    一、问题概述

    如何将下列的一个图形(下图左)用鼠标沿着白色格线切成多块(比如沿着黑色路径切割成下图右的两块)呢?

    二、解决思路

    把组成图形的每个小方块全部存入数组A中,关键要考虑的是如何根据切割路径将数组A分解为数组A和B,B用来存储切割下来的那块图形中的小方块。要解决此问题需要以下几步:

    1.为数组A中的小方块建立链接表,每个小方块有top、bottom、left、right四个属性,分别指向其上下左右的小方块,当小方块不存在时指向null;

    2.根据鼠标移动位置,获得图形的切割路径,存储在一数组中;

    3.根据切割路径,更新数组A中各小方块的链接关系,切割路径两侧的小方块将断开链接(left、right等属性指向null);

    4.从数组A中任意选择一个方块m,通过广度优先搜索,搜索所有与m有链接关系的方块,将其存入数组B,剩下的即为数组A;

    以下为具体实现细节:

    (注:本程序的实现借助了createjs框架来管理和绘制图形,并使用了我自己编写的createjsExtend工具库中的相关工具,在使用到这些工具时,我会做出必要的注解)

    三、问题实现

    一些变量说明:

    stage:用createjs构建的舞台

    canvas = document.getElementById(“canvas”);
    
    stage = new createjs.Stage(canvas);

    root: 一个容器,包含用flashcc创建的所有元素,此实例中所创建的所有图形也将加载到root中,首先需要将root加入stage中

    stage.addChild(root);

    用到的全局变量

    var rows=4,columns=9;//初始图形包含4行9列的小方块
    var rectHeight=rectWidth=50;//每个小方块的宽高
    var shapeConts=[];//存储舞台上切割出来的所有图形,初始只有一个
    var pathShape;//切割路径
    var cutState=false;//判断是否处于切割状态
    var mousedown=false;//判断鼠标是否按下

    1.创建初始图形并为图形中的小方块建立链接关系

    用createjs创建一个影片剪辑shapescont作为图形的容器,此容器包含此图形中的各个小方块,shapescont拥有的属性如下:

    shapescont.shapes,一个数组,用来存储此图形中的所有小方块;

    shapescont.linkpots,一个数组,存储图形中小方块之间链接点坐标,即所有小方块的顶点坐标;

    shapesCont.cutPath,一个数组,存储此图形被切割过的所有链接点;

    shapesCont.cutfinished,布尔值,在对图形切割过程中,此值为false,切割完成后为true;

    创建初始图形代码如下:

     1 function createInitShapes(){
     2     var initShapes=[];//一个二维数组,存储图形中的小方块
     3     var shapesCont=new createjs.MovieClip();
     4     shapesCont.shapes=[];//存储图形形状
     5     shapesCont.linkPots=[];//存储图形中各链接点坐标,相对于shapesCont
     6     shapesCont.cutPath=[];//存储图形中的切割路径,相对于shapesCont
     7     for(var row=0;row<rows;row++){
     8         var rowArr=[];
     9         for(var column=0;column<columns;column++){
    10             var shape=new createjs.Shape();
    11             shape.graphics.setStrokeStyle(2).beginStroke("#ffffff").beginFill("red").drawRect(0,0,rectWidth,rectHeight);//画一个小方块
    12             shape.x=rectWidth*column;
    13             shape.y=rectHeight*row;
    14             shapesCont.shapes.push(shape);
    15             shapesCont.addChild(shape);
    16             rowArr.push(shape);
    17         }
    18         initShapes.push(rowArr);
    19     }
    20     shapesCont.x=250;
    21     shapesCont.y=100;
    22     shapesCont.linkPots=getLinkPot(shapesCont);//得到shapesCont中的链接结点
    23     shapesCont.addDragAction(new createjs.Rectangle(0,0,1024,768),stage,false,false);//这里为shapesCont图形添加拖动效果
    24     shapeConts.push(shapesCont);
    25     root.addChild(shapesCont);
    26     
    27     //为每个方块设置上下左右的图形链接;
    28     (function setLinkShapes(){
    29         for(var row=0;row<rows;row++){
    30             for(var column=0;column<columns;column++){
    31                 initShapes[row][column].left=column-1<0?null:initShapes[row][column-1];
    32                 initShapes[row][column].top=row-1<0?null:initShapes[row-1][column];
    33                 initShapes[row][column].right=column+1>8?null:initShapes[row][column+1];
    34                 initShapes[row][column].buttom=row+1>3?null:initShapes[row+1][column];
    35             }
    36         }
    37     })();
    38 }

    获得图形中方块间各个链接结点的方法,求解思路:遍历图形中各方块的四个顶点,根据顶点位置判断两个方块的顶点是否重叠,防止重复加入

     1 //获得shapeCont中所有小方块之间的链接点
     2 function getLinkPot(shapeCont){
     3     var arr=new Array();
     4     for(var i=0;i<shapeCont.shapes.length;i++){
     5         
     6         var pot1={x:shapeCont.shapes[i].x,y:shapeCont.shapes[i].y};
     7         var pot2={x:shapeCont.shapes[i].x+rectWidth,y:shapeCont.shapes[i].y};
     8         var pot3={x:shapeCont.shapes[i].x,y:shapeCont.shapes[i].y+rectHeight};
     9         var pot4={x:shapeCont.shapes[i].x+rectWidth,y:shapeCont.shapes[i].y+rectHeight};
    10         if(myarrayIndexOf(arr,pot1)==-1){
    11             arr.push(pot1);
    12         }
    13         if(myarrayIndexOf(arr,pot2)==-1){
    14             arr.push(pot2);
    15         }
    16         
    17         if(myarrayIndexOf(arr,pot3)==-1){
    18             arr.push(pot3);
    19         }
    20         
    21         if(myarrayIndexOf(arr,pot4)==-1){
    22             arr.push(pot4);
    23         }
    24     }
    25     return arr;
    26     
    27     //根据坐标位置判断两个点是否一样
    28     function myarrayIndexOf(arr,pot){
    29         if(arr.length==0){
    30             return -1;
    31         }
    32         for(var i=0;i<arr.length;i++){
    33             if(arr[i].x==pot.x&&arr[i].y==pot.y){
    34                 return i;
    35             }
    36         }
    37         return -1;
    38     }
    39 }

    2.切割图形,获得shapescont的切割路径,存入shapescont的cutPath数组中

    鼠标按下并移动时开始切割图形,切割过程中会根据鼠标离shapescont各链接结点的距离,来不断的将链接结点加入shapescont的cutPath数组中,从而最终形成shapescont的切割路径,如下图,蓝色点为切割路径

     1 //添加鼠标移动事件
     2 stage.addEventListener("stagemousemove",function(e){
     3         if(mousedown&&cutState){
     4             for(var i=0;i<shapeConts.length;i++){
     5                 cuting(shapeConts[i]);
     6             }
     7         }
     8     });    
     9 
    10 //开始切割
    11 function cuting(shapeCont){
    12     shapeCont.cutfinish=false;
    13     //鼠标在图形上的位置
    14     var mouse={x:stage.mouseX-shapeCont.x,y:stage.mouseY-shapeCont.y};
    15     drawCutPath(shapeCont);
    16     for(var i=0;i<shapeCont.linkPots.length;i++){
    17         //根据鼠标离链接结点的距离,来判断将哪一个链接结点加入切割路径中
    18         if(createjsExtend.getDistance(mouse,shapeCont.linkPots[i])<10){
    19             if(shapeCont.cutPath.length==0){
    20                 arrayUtils.addSingleEleToArray(shapeCont.cutPath,shapeCont.linkPots[i]);
    21             }else if(Math.abs(createjsExtend.getDistance(shapeCont.linkPots[i],shapeCont.cutPath[shapeCont.cutPath.length-1])-50)<15){
    22                 arrayUtils.addSingleEleToArray(shapeCont.cutPath,shapeCont.linkPots[i]);
    23             }
    24         }
    25     }
    26 }

    说明:arrayUtils.addSingleEleToArray(arr,ele);自己写的一个数组工具,作用为将ele加入arr中,保证ele在arr中的唯一性;

     3.根据切割路径,更新shapescont中各小方块的链接关系

    这里首先需要根据每一段切割路经,获得其路经两侧的小方块(下图绿色部分),使两侧的小方块不再链接

    首先需要知道每一小段切割路经是水平还是垂直,当为水平时,需要得到路经上下两侧的方块,否则要得到左右两侧的方块。

    如下图,A、B为切割路径上相连的两点,可以根据A、B点x坐标是否相等来判断AB是否垂直,当垂直时,取得y坐标较小的点的坐标(B点)即为路径右侧即方块m点的坐标(小方块注册点在左上角,坐标系中y轴正方向朝下),m.left即为路径左侧的方块。同样的思路可以取得路径上下两侧的方块。

    切断路径两侧方块链接的代码如下:

     1 //根据shapeCont中的切割路径,更新形状之间的链接状态,被切开的两块形状将不再链接
     2 function updateLinkState(shapeCont){
     3     for(var i=0;i<shapeCont.cutPath.length-1;i++){
     4         //获得每一小段切割路径两端的坐标
     5         var startPos=shapeCont.cutPath[i];
     6         var endPos=shapeCont.cutPath[i+1];
     7         if(startPos.x==endPos.x&&Math.abs(Math.abs(startPos.y-endPos.y)-50)<15){
     8             var pos=startPos.y<endPos.y?startPos:endPos;
     9             for(var j=0;j<shapeCont.shapes.length;j++){
    10                 if(shapeCont.shapes[j].x==pos.x&&shapeCont.shapes[j].y==pos.y){
    11                     var rightShape=shapeCont.shapes[j];
    12                 }
    13             }
    14             if(rightShape!=null&&rightShape.left!=null){
    15                 rightShape.left.right=null;    
    16                 rightShape.left=null;
    17             }
    18         }
    19         
    20         if(startPos.y==endPos.y&&Math.abs(Math.abs(startPos.x-endPos.x)-50)<20){
    21             var pos=startPos.x<endPos.x?startPos:endPos;
    22             for(var j=0;j<shapeCont.shapes.length;j++){
    23                 if(shapeCont.shapes[j].x==pos.x&&shapeCont.shapes[j].y==pos.y){
    24                     var buttomShape=shapeCont.shapes[j]
    25                 }
    26             }
    27             if(buttomShape!=null&&buttomShape.top!=null){
    28                 buttomShape.top.buttom=null;
    29                 buttomShape.top=null;
    30             }
    31         }
    32     }
    33 }

    4.使用广度优先算法,将图形分成两块

    经过上面的处理,路径两侧的图形将不再拥有链接关系,接下来要如何将路径两侧的方块分在两个组呢,这里可用广度优先算法的思路求解,方法如下:

    1.从图形中任意选择一个方块比如m,定义一数组linkShapes,用来存储能与m连通的所有方块,定义队列checkShapes存储待检查的所有方块,将m加入checkShapes中

    2.从队列checkShapes的队头取一方块k(第一次取到m),遍历k四周的方块,将能查找到的方块加入待检查数组checkShapes,将k加入linkShapes

    3.重复第2步,直到checkShapes为空

    这样就将与m连通的所有方块分到了一个组,实现代码如下:

     1 //查找所有与shape能联通的方块
     2 function getAllLinksShape(shape){
     3     var linkShapes=[];
     4     var checkShapes=[];
     5     checkShapes.push(shape)
     6     while(checkShapes.length>0){
     7         var shape=checkShapes.shift();
     8         if(shape.right!=null&&linkShapes.indexOf(shape.right)==-1&&checkShapes.indexOf(shape.right)==-1){
     9             checkShapes.push(shape.right);
    10         }
    11         
    12         if(shape.buttom!=null&&linkShapes.indexOf(shape.buttom)==-1&&checkShapes.indexOf(shape.buttom)==-1){
    13             checkShapes.push(shape.buttom);
    14         }    
    15         
    16         if(shape.left!=null&&linkShapes.indexOf(shape.left)==-1&&checkShapes.indexOf(shape.left)==-1){
    17             checkShapes.push(shape.left);
    18         }    
    19         
    20         if(shape.top!=null&&linkShapes.indexOf(shape.top)==-1&&checkShapes.indexOf(shape.top)==-1){
    21             checkShapes.push(shape.top);
    22         }
    23         linkShapes.push(shape);    
    24     };    
    25     return linkShapes;    
    26 }

    四、运用场景设想

    运用本实例的制作思路,可以实现很多非常有趣的小游戏,比如可以用一张照片填充本实例的小方块,制作一款自己可以随意切割的拼图小游戏。

    (原创博文,转载请注明出处)

  • 相关阅读:
    layui的模块化和非模块化使用
    layui实现类似于bootstrap的模态框功能
    ajax下载文件
    【IDEA】IDEA中maven项目pom.xml依赖不生效解决
    主-主数据库系统架构
    MyEclipse x.x各版本终极优化配置指南
    Cactus入门
    有史以来最出彩的编程语言名字
    安卓开发20:动画之Animation 详细使用-主要通过java代码实现动画效果
    第一次讲课总结
  • 原文地址:https://www.cnblogs.com/snsart/p/8716835.html
Copyright © 2011-2022 走看看