(圖片是游戲的示意圖,來自互聯網,與本文程序無關)
看題目就知道是寫給初學者的,沒需要的就別看了,自己都覺得怪無聊的。
很多游戲的耐玩性都來自精巧的算法,特別是人工智能的水平。比如前幾天看了著名的Alpha GO的算法,用了復雜的人工智能網絡。而最簡單的,可能就是連連看了,所以很多老師留作業,直接就是實現連連看。
連連看游戲的規則非常簡單:
- 兩個圖片相同。
- 兩個圖片之間,沿着相鄰的格子畫線,中間不能有障礙物。
- 畫線中間最多允許2個轉折。
所以算法主要是這樣幾部分:
- 用數據結構描述圖板。很簡單,一個2維的整數數組,數組的值就是圖片的標志,相同的數字表示相同的圖片。有一個小的重點就是,有些連連看的地圖中,允許在邊界的兩個圖片,從地圖外連線消除。這種情況一般需要建立的圖板尺寸,比實際顯示的圖板,周邊大一個格子,從而描述可以連線的空白外邊界。本例中只是簡單的使用完整的圖板,不允許利用邊界外連線。
- 生成圖板。通常用隨機數產生圖片ID來填充圖板就好。比較復雜的游戲,會有多種的布局方式,例如兩個三角形。這種一般要手工編輯圖板模板,在允許填充的區域事先用某個特定的整數值來標注,隨后的隨機數填充只填充允許填充的區域。本例中只是簡單的隨機填充。
- 檢查連線中的障礙物。確定有障礙物的關鍵在於確定什么樣的格子是空。通常定義格子的值為0就算空。要求所有的圖片ID從1開始順序編碼。復雜的游戲還會定義負數作為特定的標志,比如允許填充區之類的。
- 檢查直接連接:兩張圖片的坐標,必然x軸或者y軸有一項相同,表示兩張圖片在x軸或者y軸的同一條線上才可能出現直接連接。隨后循環檢查兩者之間是否有障礙物即可確定。
- 檢查一折連接:與檢查直接連接相反,兩個圖片必須不在一條直線上,才可能出現一折連接,也就是x/y必須都不相同。隨后以兩張圖片坐標,可以形成一個矩陣,矩陣的一對對角是兩張圖片,假設是A/B兩點。矩陣另外兩個對角分別是C1/C2,分別檢查A/C1和C1/B或者A/C2和C2/B能同時形成直線連接,則A圖片到B圖片的1折連接可以成立。描述比較蒼白,建議你自己畫張簡單的圖就容易理解了。在一折連接的檢查中,會調用上面的直線連接的檢測至少2次,這種調用的方式有點類似遞歸的調用。
- 檢查兩折連接:同樣假設兩張圖片分別為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...