《連連看》算法c語言演示(自動連連看)



(圖片是游戲的示意圖,來自互聯網,與本文程序無關)

看題目就知道是寫給初學者的,沒需要的就別看了,自己都覺得怪無聊的。

很多游戲的耐玩性都來自精巧的算法,特別是人工智能的水平。比如前幾天看了著名的Alpha GO的算法,用了復雜的人工智能網絡。而最簡單的,可能就是連連看了,所以很多老師留作業,直接就是實現連連看。

連連看游戲的規則非常簡單:

  1. 兩個圖片相同。
  2. 兩個圖片之間,沿着相鄰的格子畫線,中間不能有障礙物。
  3. 畫線中間最多允許2個轉折。

所以算法主要是這樣幾部分:

  1. 用數據結構描述圖板。很簡單,一個2維的整數數組,數組的值就是圖片的標志,相同的數字表示相同的圖片。有一個小的重點就是,有些連連看的地圖中,允許在邊界的兩個圖片,從地圖外連線消除。這種情況一般需要建立的圖板尺寸,比實際顯示的圖板,周邊大一個格子,從而描述可以連線的空白外邊界。本例中只是簡單的使用完整的圖板,不允許利用邊界外連線。
  2. 生成圖板。通常用隨機數產生圖片ID來填充圖板就好。比較復雜的游戲,會有多種的布局方式,例如兩個三角形。這種一般要手工編輯圖板模板,在允許填充的區域事先用某個特定的整數值來標注,隨后的隨機數填充只填充允許填充的區域。本例中只是簡單的隨機填充。
  3. 檢查連線中的障礙物。確定有障礙物的關鍵在於確定什么樣的格子是空。通常定義格子的值為0就算空。要求所有的圖片ID從1開始順序編碼。復雜的游戲還會定義負數作為特定的標志,比如允許填充區之類的。
  4. 檢查直接連接:兩張圖片的坐標,必然x軸或者y軸有一項相同,表示兩張圖片在x軸或者y軸的同一條線上才可能出現直接連接。隨后循環檢查兩者之間是否有障礙物即可確定。
  5. 檢查一折連接:與檢查直接連接相反,兩個圖片必須不在一條直線上,才可能出現一折連接,也就是x/y必須都不相同。隨后以兩張圖片坐標,可以形成一個矩陣,矩陣的一對對角是兩張圖片,假設是A/B兩點。矩陣另外兩個對角分別是C1/C2,分別檢查A/C1和C1/B或者A/C2和C2/B能同時形成直線連接,則A圖片到B圖片的1折連接可以成立。描述比較蒼白,建議你自己畫張簡單的圖就容易理解了。在一折連接的檢查中,會調用上面的直線連接的檢測至少2次,這種調用的方式有點類似遞歸的調用。
  6. 檢查兩折連接:同樣假設兩張圖片分別為A/B兩點,在A點的X+/X-方向/Y+方向/Y-方向,共4個方向上循環查找是否存在一個點C,使得A到C為直線連接,C到B為1折連接,則兩折連接成立。這中間,會調用前面的直接連接檢測和一折連接檢測。

用到的算法基本就是這些,下面看程序。本程序使用GCC或者CLANG編譯的,可以在Linux或者Mac直接編譯執行。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>

//常量習慣定義在程序一開始,以便將來的修改,比如重新定義一個更大的地圖界限
//定義圖板尺寸
#define _width 20
#define _height 20
//定義數組矩陣中,0表示該格子為空
#define empty (0)
//定義共有20種圖片
#define _pics (20)
//定義在圖板中隨機產生100*2個圖片的填充
//使用100是為了每次產生2個相同的圖片,從而保證整個圖可以消除完
#define _datas (100)
//c語言沒有bool類型,為了方便自定義一個
typedef int bool;
#define TRUE (1)
#define FALSE (0)
//定義一個結構用來描述一個點坐標
typedef struct {
    int x;
    int y;
} _point;
//描述圖板的數組
int map[_width][_height];

//-------------------------init map----------------------
//從圖板中獲取一個空白格子的坐標,這種方法隨着填充圖片的增加,
//效率會急劇降低,不過簡單實用,這么小的圖板對cpu來說也不算什么
_point getRndEmptyBox(){
    int x,y;
    while(TRUE){
        //gcc的隨機數跟windows的隨機數產生規則不同
        //linux是產生從0開始到RAND_MAX的一個正整數
        //如果移植到windows,這部分要修改
        int x=rand() % _width;
        int y=rand() % _height;
        if (map[x][y]==empty){
            _point r;
            r.x=x;
            r.y=y;
            return r;
        }
    }
}
//設置一對隨機圖片
void setRandPic(){
    _point p;
    //+1是為了防止出現隨機數為0的情況,那樣等於填充了空白
    int pic=rand() % _pics + 1;
    p = getRndEmptyBox();
    map[p.x][p.y]=pic;
    //printf("[%02d,%02d]=%02d\n",p.x,p.y,pic);
    p = getRndEmptyBox();
    map[p.x][p.y]=pic;
    return;
}
//用隨機圖片填充整個圖板
void setRndMap(){
    int i;
    for(i=0;i<_datas;i++){
        setRandPic();
    }
    return;
}
//-----------------------------show status --------------------
//顯示當前的圖板情況
void dumpMap(){
    int i,j;
    printf("--: ");
    for(i=0;i<_width;i++){
        printf("%02d ",i);
    }
    printf("\n");
    for(i=0;i<_height;i++){
        printf("%02d: ",i);
        for(j=0;j<_width;j++){
            printf("%02d ",map[j][i]);
        }
        printf("\n");
    }
}
//顯示當前的圖板情況,並且使用紅色標注上將要消除的2個點
//顯示部分使用了linux的終端控制專用方式,移植到windows時需要修改
void dumpMapWithHotPoint(_point c1,_point c2){
    int x,y;
    //為了方便計數,顯示x/y軸格子編號
    printf("--: ");
    for(x=0;x<_width;x++){
        printf("%02d ",x);
    }
    printf("\n");
    for(y=0;y<_height;y++){
        printf("%02d: ",y);
        for(x=0;x<_width;x++){
            if ((c1.x==x && c1.y==y) || (c2.x==x && c2.y==y))
                printf("\e[1;31m%02d\e[0m ",map[x][y]);
            else
                printf("%02d ",map[x][y]);
        }
        printf("\n");
    }
}
//-------------------------search path--------------------
//檢查直接連接,返回成功或者失敗
bool havePathCorner0(_point p1,_point p2){
    if (p1.x != p2.x && p1.y != p2.y)
        return FALSE; // not in the same line
    int min,max;
    if (p1.x == p2.x){
        min = p1.y < p2.y ? p1.y : p2.y;
        max = p1.y > p2.y ? p1.y : p2.y;
        for(min++;min < max;min++){
            if(map[p1.x][min] != empty)
                return FALSE;  //have block false
        }
    } else {
        min = p1.x < p2.x ? p1.x : p2.x;
        max = p1.x > p2.x ? p1.x : p2.x;
        for(min++;min < max;min++){
            if(map[min][p1.y] != empty)
                return FALSE; //have block false
        }
    }
    return TRUE;
}
//檢查1折連接,返回1個點,
//如果點的坐標為負表示不存在1折連接
_point havePathCorner1(_point p1,_point p2){
    _point nullPoint;
    nullPoint.x=nullPoint.y=-1;

    if (p1.x == p2.x || p1.y == p2.y)
        return nullPoint;
    _point c1,c2;
    c1.x=p1.x;
    c1.y=p2.y;
    c2.x=p2.x;
    c2.y=p1.y;
    if (map[c1.x][c1.y] ==  empty){
        bool b1=havePathCorner0(p1,c1);
        bool b2=havePathCorner0(c1,p2);
        if (b1 && b2)
            return c1;
    }
    if (map[c2.x][c2.y] ==  empty){
        bool b1=havePathCorner0(p1,c2);
        bool b2=havePathCorner0(c2,p2);
        if (b1 && b2)
            return c2;
    }
    return nullPoint;
}
//檢查兩折連接,返回兩個點,
//返回點坐標為負表示不存在兩折連接
//其中使用了4個方向的循環查找
_point result[2];
_point *havePathCorner2(_point p1,_point p2){
    int i;
    _point *r=result;
    //search direction 1
    for(i=p1.y+1;i<_height;i++){
        if (map[p1.x][i] == empty){
            _point c1;
            c1.x=p1.x;
            c1.y=i;
            _point d1=havePathCorner1(c1,p2);
            if (d1.x != -1){
                r[0].x=c1.x;
                r[0].y=c1.y;
                r[1].x=d1.x;
                r[1].y=d1.y;
                return r;
            }
        } else
        break;
    }
    //search direction 2
    for(i=p1.y-1;i>-1;i--){
        if (map[p1.x][i] == empty){
            _point c1;
            c1.x=p1.x;
            c1.y=i;
            _point d1=havePathCorner1(c1,p2);
            if (d1.x != -1){
                r[0].x=c1.x;
                r[0].y=c1.y;
                r[1].x=d1.x;
                r[1].y=d1.y;
                return r;
            }
        } else
        break;
    }
    //search direction 3
    for(i=p1.x+1;i<_width;i++){
        if (map[i][p1.y] == empty){
            _point c1;
            c1.x=i;
            c1.y=p1.y;
            _point d1=havePathCorner1(c1,p2);
            if (d1.x != -1){
                r[0].x=c1.x;
                r[0].y=c1.y;
                r[1].x=d1.x;
                r[1].y=d1.y;
                return r;
            }
        } else
        break;
    }
    //search direction 4
    for(i=p1.x-1;i>-1;i--){
        if (map[i][p1.y] == empty){
            _point c1;
            c1.x=i;
            c1.y=p1.y;
            _point d1=havePathCorner1(c1,p2);
            if (d1.x != -1){
                r[0].x=c1.x;
                r[0].y=c1.y;
                r[1].x=d1.x;
                r[1].y=d1.y;
                return r;
            }
        } else
        break;
    }
    r[1].x=r[0].x=r[0].y=r[1].y=-1;
    return r;
}
//匯總上面的3種情況,查找兩個點之間是否存在合法連接
bool havePath(_point p1,_point p2){
    if (havePathCorner0(p1,p2)){
        printf("[%d,%d] to [%d,%d] have a direct path.\n",p1.x,p1.y,p2.x,p2.y);
        return TRUE;
    } 
    _point r=havePathCorner1(p1,p2);
    if (r.x != -1){
        printf("[%d,%d] to [%d,%d] have a 1 cornor path throught [%d,%d].\n",
            p1.x,p1.y,p2.x,p2.y,r.x,r.y);
        return TRUE;
    } 
    _point *c=havePathCorner2(p1,p2);
    if (c[0].x != -1){
        printf("[%d,%d] to [%d,%d] have a 2 cornor path throught [%d,%d] and [%d,%d].\n",
            p1.x,p1.y,p2.x,p2.y,c[0].x,c[0].y,c[1].x,c[1].y);
        return TRUE;
    } 
    return FALSE;
}
//對於給定的起始點,查找在整個圖板中,起始點之后的所有點,
//是否存在相同圖片,並且兩張圖片之間可以合法連線
bool searchMap(_point p1){
    int ix,iy;
    bool inner1=TRUE;

    //printf("begin match:%d,%d\n",p1.x,p1.y);
    int c1=map[p1.x][p1.y];
    for (iy=p1.y;iy<_height;iy++){
        for(ix=0;ix<_width;ix++){
            //遍歷查找整個圖板的時候,圖板中,起始點之前的圖片實際已經查找過
            //所以應當從圖片之后的部分開始查找才有效率
            //遍歷的方式是逐行、每行中逐個遍歷
            //在第一次循環的時候,x坐標應當也是起始點的下一個,所以使用inner1來確認第一行循環
            if (inner1){
                ix=p1.x+1;
                inner1=FALSE;
            }
            if(map[ix][iy] != c1){
                //printf("skip:%d,%d\n",ix,iy);
                //continue;
            } else {
                _point p2;
                p2.x=ix;
                p2.y=iy;
                if (!havePath(p1,p2)){
                    //printf("No path from [%d,%d] to [%d,%d]\n",p1.x,p1.y,p2.x,p2.y);
                } else {
                    dumpMapWithHotPoint(p1,p2);
                    map[p1.x][p1.y]=empty;
                    map[p2.x][p2.y]=empty;
                    //dumpMap();
                    return TRUE;
                }
            }
        }
    };
    return FALSE;
}
//這個函數式掃描全圖板,自動連連看
bool searchAllMap(){
    int ix,iy;
    bool noPathLeft=FALSE;
    while(!noPathLeft){
        noPathLeft=TRUE;
        for (iy=0;iy<_height;iy++){
            for(ix=0;ix<_width;ix++){
                if(map[ix][iy] != empty){
                    _point p;
                    p.x=ix;
                    p.y=iy;
                    if(searchMap(p) && noPathLeft)
                        noPathLeft=FALSE;
                }
            }
        }
        printf("next loop...\n");
    };
    return TRUE;
}
//-----------------main-----------------------------
int main(int argc,char **argv){
    srand((unsigned)time(NULL));
    memset(map,0,sizeof(map));
    setRndMap();
    dumpMap();
    searchAllMap();
}

運行結果會是類似這樣:

link> ./linktest 
--: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 
00: 14 00 02 16 18 06 00 00 00 00 00 12 13 04 00 00 00 19 18 00 
01: 00 10 00 12 00 05 00 00 00 00 00 00 00 15 09 00 00 00 18 00 
02: 00 00 03 00 00 13 16 00 05 17 00 17 00 00 07 05 00 00 05 16 
03: 02 00 00 00 00 13 00 17 15 00 00 00 00 00 00 02 00 11 15 08 
04: 05 11 00 08 05 00 06 00 00 00 07 06 00 00 06 00 15 17 00 00 
05: 17 18 16 11 01 04 00 16 18 00 04 01 00 02 19 18 00 11 16 00 
06: 00 01 00 11 00 00 00 12 03 00 02 17 01 00 00 19 00 13 07 03 
07: 06 10 00 10 10 00 00 02 00 00 11 15 09 18 00 00 00 00 07 00 
08: 09 14 06 19 00 09 00 00 09 18 00 00 00 12 18 05 00 11 00 18 
09: 01 00 00 07 06 00 15 00 00 00 00 00 00 02 11 00 00 00 08 00 
10: 00 00 02 03 00 15 00 00 19 00 00 07 00 12 00 00 10 00 19 00 
11: 12 11 14 09 10 00 00 00 19 18 00 13 05 11 05 00 00 18 00 07 
12: 11 00 09 00 00 00 00 10 03 00 00 00 00 00 00 00 16 05 12 00 
13: 02 17 00 05 00 00 00 00 04 00 07 00 01 00 09 00 00 00 19 00 
14: 07 00 00 17 00 00 06 00 00 14 00 00 05 00 09 00 08 00 18 00 
15: 00 02 19 00 04 16 00 00 14 00 00 15 16 14 00 00 00 00 00 12 
16: 00 02 00 16 09 00 00 00 00 00 00 09 13 01 19 15 00 17 00 15 
17: 00 18 00 00 08 00 00 00 10 00 00 00 00 06 00 09 02 06 00 01 
18: 00 00 15 00 00 02 08 00 09 07 00 18 06 00 09 00 11 00 00 15 
19: 06 18 00 00 00 02 17 00 00 00 19 00 19 00 00 00 00 04 00 03 
[18,0] to [18,1] have a direct path.
--: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 
00: 14 00 02 16 18 06 00 00 00 00 00 12 13 04 00 00 00 19 18 00 
01: 00 10 00 12 00 05 00 00 00 00 00 00 00 15 09 00 00 00 18 00 
02: 00 00 03 00 00 13 16 00 05 17 00 17 00 00 07 05 00 00 05 16 
03: 02 00 00 00 00 13 00 17 15 00 00 00 00 00 00 02 00 11 15 08 
04: 05 11 00 08 05 00 06 00 00 00 07 06 00 00 06 00 15 17 00 00 
05: 17 18 16 11 01 04 00 16 18 00 04 01 00 02 19 18 00 11 16 00 
06: 00 01 00 11 00 00 00 12 03 00 02 17 01 00 00 19 00 13 07 03 
07: 06 10 00 10 10 00 00 02 00 00 11 15 09 18 00 00 00 00 07 00 
08: 09 14 06 19 00 09 00 00 09 18 00 00 00 12 18 05 00 11 00 18 
09: 01 00 00 07 06 00 15 00 00 00 00 00 00 02 11 00 00 00 08 00 
10: 00 00 02 03 00 15 00 00 19 00 00 07 00 12 00 00 10 00 19 00 
11: 12 11 14 09 10 00 00 00 19 18 00 13 05 11 05 00 00 18 00 07 
12: 11 00 09 00 00 00 00 10 03 00 00 00 00 00 00 00 16 05 12 00 
13: 02 17 00 05 00 00 00 00 04 00 07 00 01 00 09 00 00 00 19 00 
14: 07 00 00 17 00 00 06 00 00 14 00 00 05 00 09 00 08 00 18 00 
15: 00 02 19 00 04 16 00 00 14 00 00 15 16 14 00 00 00 00 00 12 
16: 00 02 00 16 09 00 00 00 00 00 00 09 13 01 19 15 00 17 00 15 
17: 00 18 00 00 08 00 00 00 10 00 00 00 00 06 00 09 02 06 00 01 
18: 00 00 15 00 00 02 08 00 09 07 00 18 06 00 09 00 11 00 00 15 
19: 06 18 00 00 00 02 17 00 00 00 19 00 19 00 00 00 00 04 00 03 
......
[10,17] to [19,18] have a 1 cornor path throught [10,18].
--: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 
00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
01: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
02: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
03: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
04: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
05: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
06: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
07: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
08: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
09: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
12: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
13: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
14: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
15: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
16: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
17: 00 00 00 00 00 00 00 00 00 00 12 00 00 00 00 00 00 00 00 00 
18: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12 
19: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
next loop...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM