广度优先搜索的应用——将一个图形切成多块

一、问题概述

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

二、解决思路

把组成图形的每个小方块全部存入数组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 }

 

四、运用场景设想

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

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

posted @ 2018-04-05 23:39  snsart  阅读(691)  评论(0编辑  收藏  举报