WPF开发连连看系列II-图片匹配算法的实现

来源:互联网 发布:java select数量 编辑:程序博客网 时间:2024/06/05 09:51

所谓“磨刀不误砍柴工”,我们先别急着用WPF把UI层给做好。当下的首要任务是先把这个项目里最难的一部分解决,之后我们基本上就可以开开心心地和WPF玩耍了。
我们目前要解决的问题就是:如何判断选中的两张图片是否可以被消除。
首先了解一下连连看的游戏规则:根据我以前玩过的连连看游戏(年代久远了),刚开始的简单级别是12X8的图片数量。选中的两张图片最多只能由三条直线连接起来,且连线中不能包含其它图片。
先贴贴我用C++写的代码,然后我再进行说明。(没错,就是用C++写的。很容易就能改成C#代码。至于我现在为什么是用C++实现的这部分代码,你猜。。。这份代码写得比较像ACM代码,不过我还是注意了可读性)

#include<cstdio>#include<cstdlib>#include<cstring>#include<cmath>#include<algorithm>#include<queue>#include<stack>using namespace std;struct Vertex{    int r;//row number    int c;//column number    Vertex* parent; };//Data structure of gridconst int ROW=14;const int COLUMN=10;int visited[ROW][COLUMN];int map[ROW][COLUMN];bool BFS(Vertex* u,Vertex* v,Vertex*& t){    memset(visited,0,sizeof(visited));    queue<Vertex*> qu;    int i;    int direction[4][2]={{-1,0},{0,1},{1,0},{0,-1}};//Four directions of BFS    visited[u->r][u->c]=1;    qu.push(u);    while(!qu.empty())    {        Vertex* tmp=qu.front();        t=tmp;        qu.pop();        for(i=0;i<4;i++)        {            int dr=tmp->r+direction[i][0];            int dc=tmp->c+direction[i][1];            if(dr>=0&&dr<ROW&&dc>=0&&dc<COLUMN)            {//coordinate valid                if(dr==v->r && dc==v->c)                {//Found the route                    return true;                }                if(visited[dr][dc]==0 && map[dr][dc]==0)                {//Vertex has not been visited and vertex has nothing                    visited[dr][dc]=1;                    Vertex* p=(Vertex*)malloc(sizeof(Vertex));                    p->r=dr;                    p->c=dc;                    p->parent=tmp;//Pointing to its pre-node so as to find route                    qu.push(p);                }            }        }    }    return false;}bool Solve(Vertex* u,Vertex* v,stack<Vertex*>& s){    if(u->r==v->r && u->c==v->c) return false;//Same Vertex    if(map[u->r][u->c]!=map[v->r][v->c]) return false;//Having different value    Vertex* t;    int cornerCount=0;    bool flag=true;    s.push(v);    //Two of the vertexces at same row    if(u->r==v->r)    {        int minc=min(u->c,v->c)+1;        int maxc=max(u->c,v->c)-1;        for(int i=minc;i<=maxc;i++)        {   //Between two of the vertexces, still have other vertexces            if(map[u->r][i]!=0)            {                flag=false;                break;            }        }        if(flag) return true;    }    //Two of the vertexces at same column    else if(u->c==v->c)    {        int minr=min(u->r,v->r)+1;        int maxr=max(u->r,v->r)-1;        for(int i=minr;i<=maxr;i++)        {   //Between two of the vertexces, still have other vertexces            if(map[i][u->c]!=0)            {                flag=false;                break;            }        }        if(flag) return true;    }    if(!BFS(u,v,t)) return false;    else    {        Vertex* post=v;        Vertex* pre=t->parent;        do        {   s.push(t);            int jr=(t->r-pre->r)*(post->r-t->r);            int jc=(t->c-pre->c)*(post->c-t->c);                //According to the mathematical knowledge learned at senior high school,            //line vectors vertical with each other when jr plus jc equals 0,            //pointing that we have met a corner-vertex            if(jr+jc==0) cornerCount++;             if(cornerCount==3) return false;//cornerCount must equals to or less than 3            //pointers move forward            post=t;            t=pre;            pre=pre->parent;        }while(pre!=NULL);    }    return true;}int main(){    //freopen("input.txt","r",stdin);    //freopen("output.txt","w",stdout);    for(int i=0;i<ROW;i++)    {        for(int j=0;j<COLUMN;j++)            scanf("%d",&map[i][j]);    }    int r1,c1,r2,c2;    while(scanf("%d%d%d%d",&r1,&c1,&r2,&c2)!=EOF)    {        stack<Vertex*> route;//store vertexces         Vertex* u=(Vertex*)malloc(sizeof(Vertex));        u->r=r1;        u->c=c1;        u->parent=NULL;        Vertex* v=(Vertex*)malloc(sizeof(Vertex));        v->r=r2;        v->c=c2;        v->parent=NULL;        if(Solve(u,v,route))        {            route.push(u);            map[r1][c1]=0;            map[r2][c2]=0;            puts("Succeed!");            while(!route.empty())            {//print route                Vertex* p=route.top();                route.pop();                printf("(%d,%d)\n",p->r,p->c);            }        }        else puts("Failed!");    }}

首先定义的格子的数据结构Vertex,r表示横坐标,c表示纵坐标。为了便于理解代码,所有坐标都是按照数组的格式来的(其实我半年前实现这部分代码时参考的算法格子的横纵坐标和实际二维数组的横纵坐标是反过来的,坑死我了。。。)。前面说了,简单级别的连连看游戏是12X8个格子的。不过我们这里定义的ROW=14,COLUMN=10。map[][]如果为0则说明该格子没有元素。为什么呢(自己思考)?
接着我们从Solve函数讲起。函数里的第一行和第二行分别判断是不是同一个格子(具有相同横纵坐标)和格子中的内容是否相同(这里的内容是指数字,连连看中则是指图片)。
然后我们要分别判断三种情况下的图片是否匹配(其实总的来说可以是一种,也可以是两种)。第一种是两张图片在同一行;第二种是两张图片在同一列;第三种是两张图片既不在同一行也不在同一列
先分析第一种情况。遇到两张在同一行的图片,先判断在这一行中的两张图片之间是否还有其它图片的存在。如果没有,说明是连通的,返回true;如果有,是不是就说明这两张图片不匹配呢?当然不是。上面所说的只是说明这两张图片不能用一条线连接起来,但是可能可以用两条或者三条其它路上的直线连接起来。如果同一行的图片不能用一条直线连接起来的话,我们接着就用BFS()函数判断两张图片是否连通(待会儿再讲BFS()函数)。也许你会说,为什么一开始不用BFS()函数来判连通呢?这里有两个目的。一个目的是提高算法的效率(虽然也提高不了多少)。BFS()函数的时间复杂度是O(V*E)的,V是顶点个数,E是边的数目。如果能够在线性时间内判断两张图片连通的话,就可以不用调用更慢的BFS()函数了。然后第二个目的嘛,你猜(其实真的想知道的话等讲解完后把70到99行的代码删掉自己运行一下程序就知道了)。在同一列的两张图片也是同样的原理。
然后到BFS()函数了。有一定图论基础的话这段函数看起来是相当简单的(没有的话建议先百度了解一下图论的广度优先搜索算法,即BFS)。这里要注意的就是direction[][]数组和visited[][]数组。direction[][]数组保存的是每次遍历的方向(我按顺时针方向是上右下左),visited[][]数组则用来表示该坐标是否访问过(为了避免出现原来访问过此路线,结果后边又往回走)。现将初始坐标顶点入队。当队列不空的时候循环:出队队首元素,每次访问其周围四个方向(即上右下左)的坐标,如果它们不是目的格子的坐标的话,就入队;否则返回true。注意到Vertex定义中还有一个Vertex*类型的parent成员,这个是为了保存当前坐标的前一个坐标(也就是父亲),然后我们后面可以根据parent指针寻找路线(别忘了连连看游戏里有一个效果,就是当两张图片匹配后会显示两张图片的连接线)。
BFS()函数讲完了,可是算法还没有完。我们回到Solve()函数。BFS()函数只是判断了两张图片能否连通,可是游戏规则是两张图片必须用三条以内的直线连通。接下来就是判断这一点的算法了:首先是一个post指针指向最后一个坐标点v,然后pre指向t的父亲坐标点(t作为参数传入BFS中,最后指向的其实就是v的前一个坐标点)。然后108、109、113行的代码以这三个指针指向的坐标点来进行判断。那么我们根据什么来统计直线的数目呢?如果有一条直线,中间突然拐了个弯(90度),是不是就多了一条直线呢。这两条直线就是垂直的。根据我们高中学的平面几何的知识,假设两条互相垂直直线的的方向向量分别为(x1,y1),(x2,y2),如果x1*x2+y1*y2=0,那么这两条直线垂直。于是统计直线的方法来了:每次取三个相邻的坐标点,根据上面的原理判断三个顶点是否在一条直线上,不在的话就将cornerCount加1。如果cornerCount等于3(出现了4条直线)的话返回false。然后将三个指针都向前移动一位。
最后要注意的是,为了能够打印连接线,我们用了一个stack

0 0 0 0 0 0 0 0 0 00 1 2 3 1 0 0 0 0 10 0 0 0 0 0 0 0 0 20 2 0 0 0 0 0 0 0 30 0 3 0 0 0 0 7 0 40 3 0 2 0 0 0 0 0 50 0 0 0 0 0 0 0 0 60 0 0 0 0 0 0 7 0 70 0 8 0 0 8 0 0 0 80 0 0 0 0 1 2 3 4 90 0 5 0 6 6 5 4 5 100 0 1 0 1 7 2 3 6 110 2 1 0 5 8 9 8 7 120 1 2 3 4 5 6 7 8 131 1 1 43 1 5 34 7 7 78 2 8 510 2 12 49 7 11 710 4 10 413 1 11 210 4 10 5

哦,对了,以上是我使用的数据。把这份数据保存到相同目录下的input.txt文件中,然后把126和127行中的注释去掉就可以在output.txt文件中看到运行结果了。看明白的话是不是很容易就能移植为C#代码呢?没看明白的话欢迎评论留言或者联系私聊(WPF开发连连看系列I有联系方式)。

0 0