連連看演算法

whatday發表於2013-05-29
 程式的關鍵在於判斷使用者連續點選的兩個圖案能否消除。兩個圖片可以消除的條件有兩個:
  • 圖片相同
  • 圖片間連線的轉角數不得超過2 。

  而判斷能否通過小於兩個轉角的路徑連通的演算法有兩種:

  • 分類判斷法
  • 以轉角數為標準的廣度優先搜尋

 

  下面對兩種演算法分別討論。

 

  1. 分類判斷法

      這裡實質上是一種遞迴的思想,要判斷圖片A與圖片B能否通過一條有N個轉角的路徑相連,可以轉化為判斷能否找到圖片C,C與A能直線相連,且C與B能用一條有N-1個轉角的路徑相連。若這樣的圖片C存在,那麼A與B就可以通過一條有N個轉角的路徑相連。

      根據轉角數不得超過2個的規則,我們可以分為轉角數分別為0個、1個、2個這三種情況分別討論。

  (1)0轉角連通(直線連通):兩個圖片的縱座標或橫座標相等,且兩者連線間沒有其他圖案阻隔。

  (2)一個轉角連通:其實相當於兩個圖片劃出一個矩形,這兩個圖片是一對對角頂點,另外兩個頂點如果可以同時和這兩個棋子直連,那就說明可以"一折連通"。見下圖兩個紅色棋子的連通情況,右上角打叉的位置就是折點。

 

圖1.一個轉角連通 

  (3)兩個轉角連通: 判斷圖片A與圖片B能否經過有兩個轉角的路徑連通實質上可以轉化為判斷能否找到一個點C,這個C點與A可以直線連通,且C與B可以通過有一個轉角的路徑連通。若能找到這樣一個C點,那麼A與B就可以經過有兩個轉角的路徑連通 。

  判斷是否經兩個轉角連通的演算法需要做兩個方向上的掃描:水平掃描和垂直掃描。

  水平掃描。如下圖所示,為了判斷A,B能否通過2個轉角連通,則從A開始在水平方向上向左右掃描,並判斷經過的點能否與B點經過1個轉角連通。顯然C點能與B點經1個轉角連通,故A,B能經2個轉角連通。

 

圖2.兩個轉角連通的判斷

  垂直掃描。如下圖所示,為了判斷A,B能否通過2個轉角連通,則從A開始在垂直方向上下掃描,並判斷經過的點能否與B點經過1個轉角連通。顯然C點能與B點經1個轉角連通,故A,B能經2個轉角連通。

 

圖3 .兩個轉角連通的判斷

  詳細程式碼如下:

//棋盤基地址
PCHAR pBase=NULL;
//棋盤X軸數量 Y軸數量
const int nNumberX=19, nNumberY=11;
//得到陣列值的巨集
#define GETVALUE(point) (*(pBase + point.x +point.y * nNumberX))

//檢視兩個塊是否可以直連
BOOL MatchBlock(POINT st1, POINT st2)
{
	if(!(st1.x==st2.x || st1.y==st2.y))
	{
		return FALSE;
	}

	POINT stMin, stMax;

	//如果是豎線直連		
	if(st1.x==st2.x)
	{
		//由於這裡會被 一折連 遞迴呼叫 所以必須判斷大小
		stMin=st1.y<st2.y?st1:st2;
		stMax=st1.y>st2.y?st1:st2;

		for(stMin.y++; stMin.y<stMax.y; stMin.y++)
		{
			if(GETVALUE(stMin)!=0)
			{
				return FALSE;
			}
		}
	}
	//如果是橫線直連
	else
	{
		stMin=st1.x<st2.x?st1:st2;
		stMax=st1.x>st2.x?st1:st2;

		for(stMin.x++; stMin.x<stMax.x; stMin.x++)
		{
			if(GETVALUE(stMin)!=0)
			{
				return FALSE;
			}
		}
	}

	return TRUE;
}

//檢視兩個塊是否可以一折連
BOOL MatchBlockOne(POINT st1, POINT st2)
{
	//以st1 st2為矩形對角 尋找矩形另外2點
	POINT a1, a2;
	a1.x=st1.x;
	a1.y=st2.y;
	a2.x=st2.x;
	a2.y=st1.y;
	/*示意圖如下:
	st1			a2

	a1			st2
	*/

	//矩形另外2點都不會空 則跳過這種情況
	if((GETVALUE(a1)!=0) && (GETVALUE(a2)!=0))
	{
		return FALSE;
	}

	//如果a1為空
	if(GETVALUE(a1)==0)
	{
		//測試 a1-st1直連 a1-st2直連 
		if(!(MatchBlock(a1, st1) && MatchBlock(a1, st2)))
		{
			return FALSE;
		}
	}
	//如果a2為空
	else 
	{
		//測試 a2-st1直連 a2-st2直連 
		if(!(MatchBlock(a2, st1) && MatchBlock(a2, st2)))
		{
			return FALSE;
		}
	}

	return TRUE;
}

//檢視兩個塊是否可以兩折連
BOOL MatchBlockTwo(POINT st1, POINT st2)
{
    /*同樣把st1,st2想成一個矩形的兩個對角, 依次從st1的X軸 Y軸每次+1/-1尋找為空的點C/D
    再由C/D和st2進行一折連判斷 示意圖如下:
    st1            C        

    D                st2
    
    由於st1和st2的相對位置不確定所以 需要把st1分4種情況討論
    */
	POINT stTmp;

	//判斷st1的X軸+1
	if(st1.x!=nNumberX-1)
	{
		stTmp.x=st1.x+1;
		stTmp.y=st1.y;

		for(; stTmp.x<=nNumberX-1; stTmp.x++)
		{
			if(GETVALUE(stTmp)==0)
			{
				if(MatchBlockOne(stTmp, st2))
				{
					return TRUE;
				}
			}
			else
			{
				break;
			}
		}
	}	

	//判斷st1的X軸-1
	if(st1.x!=0)
	{
		stTmp.x=st1.x-1;
		stTmp.y=st1.y;

		for(; stTmp.x>=0; stTmp.x--)
		{
			if(GETVALUE(stTmp)==0)
			{
				if(MatchBlockOne(stTmp, st2))
				{
					return TRUE;
				}
			}
			else
			{
				break;
			}
		}
	}


	//判斷st1的Y軸+1
	if(st1.y!=nNumberY-1)
	{
		stTmp.x=st1.x;
		stTmp.y=st1.y+1;

		for(; stTmp.y<=nNumberY-1; stTmp.y++)
		{
			if(GETVALUE(stTmp)==0)
			{
				if(MatchBlockOne(stTmp, st2))
				{
					return TRUE;
				}
			}
			else
			{
				break;
			}
		}
	}


	//判斷st1的Y軸-1
	if(st1.y!=0)
	{
		stTmp.x=st1.x;
		stTmp.y=st1.y-1;

		for(; stTmp.y>=0; stTmp.y--)
		{
			if(GETVALUE(stTmp)==0)
			{
				if(MatchBlockOne(stTmp, st2))
				{
					return TRUE;
				}
			}
			else
			{
				break;
			}
		}
	}



	return FALSE;
}

2.以轉角數為基準的廣度優先搜尋法

  這種演算法參考《程式設計之美》。

  這種演算法的動機:若能將所有與圖片A經過不多於2個轉角的路徑相連的圖片找出來,加入一個集合S中。那麼判斷B與A能否相連只需判斷B是否存在於集合S中即可。採用廣度優先搜尋演算法可以方便的實現這一構想。演算法的思路如下:

  (1)定義空集S與T,將A加入集合S

  (2)找出所有與A能直接相連的點,將其加入集合S

  (3)找出與集合S中的點能直接相連的點,加入集合T,然後將T中所有元素加入到集合S中,清空集合T

  (4)找出與集合S中的點能直接相連的點,加入集合T,然後將T中所有元素加入到集合S中

  (5)若B在集合S中,則A,B可以相連。否則A,B不能相連

 

  模仿圖論中廣度優先搜尋的演算法,可以寫出以轉角為基準的廣度優先搜尋的虛擬碼如下:

//判斷圖片A與圖片B能否經過不多於2個轉角的路徑相連的演算法
bool Match( Picture A,Picture B )
{
    Set< picture > S ;//已經搜尋到的點的集合,集合S中每個元素與A都可以通過不多於個轉角的路徑連通
    Set< picture > T ;//臨時儲存搜尋到的點
    將A加入到S中
 
    int crossNum = 0 ;//用於記錄當前搜尋到節點的最大轉角數
 
    While( B 不在S 中&& crossNum < 3 )
    {
        for( S 中每個元素e )
        {
            將所有與e能直線連通的點加入到集合T中
        }
        T中的所有元素加入到S中
        crossNum ++ ;
    }
    if( B 在S中)
        returntrue ;
    else
        returnfalse ;
}

    實際程式設計實現這一演算法時可以採取優化措施,不一定要搜尋出所有與A轉角不超過2的點。

相關文章