隨著計算機技術(shù)的發(fā)展,人工智能(Artificial intelligence,下文簡稱"AI")已經(jīng)成為世界各國一個熱門的研究方向。對于這一領(lǐng)域的內(nèi)容,國內(nèi)起步較晚,目前雖然網(wǎng)絡(luò)上各種編程文章很多,但是關(guān)于如何編程解決人工智能問題的文章和資料少之又少。近日,筆者有幸在國外網(wǎng)站上發(fā)現(xiàn)了這一篇精彩文章,該文通過VC實例來說明如何解決及實現(xiàn)人工智能問題,例子程序雖然相對來說比較簡單,但有一定的代表性,對有興趣研究這一領(lǐng)域的朋友們有借鑒意義。
一、"八迷宮"游戲簡介
在大學(xué)進行AI模塊開發(fā)的時候,我就開始著手寫這篇文章,目的是介紹我所了解到的一些讓人感興趣的問題。對于什么是人工智能,我想沒有任何人愿意花費大量時間進行爭論。
當(dāng)你讀到這里的時候,推薦你下源代碼,因為粘貼和拷貝的代碼與源代碼的作用相比是遠遠不及的。在本文中,我將使用一個單人游戲"八迷宮"作為一個例子,這個游戲的規(guī)則如下:
有一個3X3的網(wǎng)格,包含1到8這幾個數(shù)(因此,有一個格子是空的),最初這8個數(shù)的排列是隨機的,例如,下面的排列是一種可能情況:
6 | 1 | 7 |
3 | 4 | |
5 | 8 | 2 |
游戲玩家可以向東、南、西、北等方向移動這個空格,移動空格時,空格所占位置的數(shù)字將填補到原來空格的位置。例如,對于上圖來說,如果向北移動空格,游戲的狀態(tài)將如下圖所示:
6 | 1 | |
3 | 4 | 7 |
5 | 8 | 2 |
當(dāng)游戲狀態(tài)實現(xiàn)如下所示的狀態(tài)時,游戲結(jié)束。
1 | 2 | 3 |
8 | 4 | |
7 | 6 | 5 |
二、解決"八迷宮"問題的方法
解決上述問題的方法主要有以下幾種:
1、Breadth First Search(按照"橫向"原則搜索).
2、Depth First Search(按照"深度"原則搜索).
3、Heuristic Search(啟發(fā)式搜索).
4、A* search.
實現(xiàn)這些方法的代碼非常相似,但是它們工作的方式是截然不同的。所有的上述方法都要使用一個鏈表,而且最初的鏈表都只有一個成員,它對應(yīng)著游戲網(wǎng)格的最初狀態(tài)。這個成員向它所有可能的子狀態(tài)進行膨脹(每種移動所產(chǎn)生的結(jié)果都被考慮了進去),然后所有這些子狀態(tài)都被添加到鏈表中。這種處理將在一個循環(huán)中進行持續(xù)處理,直到實現(xiàn)目標狀態(tài),也就是說直到游戲結(jié)束為止。
實現(xiàn)"八迷宮"單人游戲的偽代碼如下:
Do
Current_Item = Remove_Item_From_List
Child_State_Array = Expand (Current_State)
Add_to_List(Child_State_Array)
If child_State_Array Holds the solution then IsGameOver = true
Loop while (IsGameOver = false)
這個結(jié)構(gòu)的代碼可以在上述所有的人工智能方法中看到,各種方法的區(qū)別僅僅在于它們是如何在鏈表中進行排序。
1、在 breadth first search方法中,各個成員都是按照某一固定的方向被添加到鏈表中,刪除時按相反方向進行。
2、在 depth first search方法中,各個成員的添加和刪除都是在鏈表的同一個方向進行。
3、在heuristic search方法中,各個成員可以在鏈表的任意方向進行添加,然而,在從鏈表中刪除一個成員時,每一個成員被"heuristic"函數(shù)進行打分(對于狀態(tài)來說,分數(shù)越高越接近于最終狀態(tài)),得分最高的項目將被從鏈表中刪除。
4、A*方法使用兩個heuristic函數(shù),并將最終的兩個得分加在一起。使用兩個heuristics函數(shù)極大地提高了程序發(fā)現(xiàn)下步最佳狀態(tài)的能力,但是卻降低了程序的運行速度。
其實,三、四方法是第二種方法的延伸和變種,區(qū)別僅在于啟發(fā)式搜索的方式不同罷了,這一點讀者朋友們可以在后面的代碼中細細體味。實際上"八迷宮"這種游戲的heuristic函數(shù)的可以是這么一個函數(shù),它計算擁有正確位置的網(wǎng)格個數(shù),或是100-狀態(tài)深度。
使用啟發(fā)式的搜索有利有弊,在這個例子中,使用啟發(fā)式可能使程序的主循環(huán)降低10倍速度,然而,它降低了循環(huán)的次數(shù),可能從6百萬次降低到了4000次,這節(jié)省了內(nèi)存的負荷,以及例子程序的運行時間。(這提醒了我,我使用的最初的狀態(tài)使用了25步解決了問題),這看起來不是很多,但如果沒有啟發(fā)式搜索,搜索將1秒鐘處理300兆的內(nèi)存,除非你使用功能強大的處理機器,(我使用的是1.3G,256兆的機器),否則你可能需要減少結(jié)束游戲所需要的移動次數(shù),要低于10。
三、程序代碼
首先,要聲明的是,游戲網(wǎng)格在程序中對應(yīng)著一個擁有10個成員變量的數(shù)組,網(wǎng)格每個位置按照從左到右、從上到下的位置排序。對于網(wǎng)格狀態(tài),可實現(xiàn)一個狀態(tài)類,它對應(yīng)著網(wǎng)格的各種狀態(tài),并擁有所有操作網(wǎng)格時所需要的代碼和變量,這個類命名為CState,代碼如下:
/* 聲明網(wǎng)格中空格所有可能移動的方向,至于為什么要包括"NONE"將在后面的代碼中顯現(xiàn)出來;*/
enum DIRECTION { NONE, NORTH, EAST, SOUTH, WEST };
// 聲明CState類
class CState {
private:
// 使用1 to 9號索引來對應(yīng)網(wǎng)格的每個位置, 定義為 char類型是為了節(jié)省內(nèi)存;
char Grid[10];
char Depth; //Depth變量對游戲的最初原始狀態(tài)演變到當(dāng)前狀態(tài)所經(jīng)歷的步數(shù);
//我們需要記錄下父狀態(tài)是如何進行移動而得到當(dāng)前狀態(tài)的;
DIRECTION OperatorApplyed;
// 父狀態(tài)指針,當(dāng)程序結(jié)束時,我們可以利用它跟蹤所經(jīng)歷的狀態(tài),從而給出答案;
CState *PrevState;
// 尋找當(dāng)前網(wǎng)格中的空格位置或某一個具體數(shù)字的位置,默認狀態(tài)是尋找空格位置;
inline int FindBlank(char Search=0) {
int Blank=1;
while (Grid[Blank] != Search) {
Blank++;
}
return Blank;
}
// 按照規(guī)定的方向移動空格;
void MoveBlank(DIRECTION Direction) {
int Blank = FindBlank();
// 我們需要記住本次操作所移動的方向;
OperatorApplyed = Direction;
switch (Direction) {
case NORTH:
Grid[Blank] = Grid[Blank - 3];
Grid[Blank - 3] = 0;
break;
case EAST:
Grid[Blank] = Grid[Blank + 1];
Grid[Blank + 1] = 0;
break;
case SOUTH:
Grid[Blank] = Grid[Blank + 3];
Grid[Blank + 3] = 0;
break;
case WEST:
Grid[Blank] = Grid[Blank - 1];
Grid[Blank - 1] = 0;
break;
}
}
/* 下面的函數(shù)將被用于第一種方法的heuristics函數(shù)的一部分,它得到兩個索引位置的直角距離,它還用于確定一個數(shù)字當(dāng)前位置與其應(yīng)該所在位置的直角距離;
int GetDistanceBetween(int Tile1, int Tile2) {
int X1, X2;
int Y1, Y2;
int temp=0;
// 將grid位置轉(zhuǎn)換為X,Y坐標;
Y1 = 1;
Y2 = 2;
X1 = Tile1;
X2 = Tile2;
if(Tile1 > 3) { Y1 = 2; X1 = Tile1 - 3; }
if(Tile1 > 6) { Y1 = 3; X1 = Tile1 - 6; }
if(Tile2 > 3) { Y2 = 2; X2 = Tile2 - 3; }
if(Tile2 > 6) { Y2 = 3; X2 = Tile2 - 6; }
//為了確保距離值為正說,進行必要的換位處理;
if(Y1 - Y2 < 0) {
temp = Y1;
Y1 = Y2;
Y2 = temp;
}
if(X1 - X2 < 0) {
temp = X1;
X1 = X2;
X2 = temp;
}
return ((Y1 - Y2) + (X1 - X2));
}
public:
// 異常處理;
class ERROR_ILLEGAL_MOVE{};
class ERROR_NO_MORE_DIRECTIONS{};
class ERROR_OUT_OF_BOUNDS{};
//用于heuristic函數(shù);它代表了當(dāng)前狀態(tài)與前一狀態(tài)的距離;這個數(shù)值越小越好。
int GetDepth() {
return Depth;
}
// CState類構(gòu)造函數(shù);
CState() {
Depth = 0;
Grid[1] = 6; // for slower machines use 4
Grid[2] = 1; // for slower machines use 1
Grid[3] = 7; // for slower machines use 3
Grid[4] = 3; // for slower machines use 2
Grid[5] = 0; // for slower machines use 0
Grid[6] = 4; // for slower machines use 5
Grid[7] = 5; // for slower machines use 8
Grid[8] = 8; // for slower machines use 7
Grid[9] = 2; // for slower machines use 6
PrevState = 0;
/*由于還沒有以前移動的操作,所以這就是為什么我們需要聲明NONE 變量的原因。*/
OperatorApplyed = NONE;
}
void SetPrevState(CState *Set) {
PrevState = Set;
}
CState* GetPrevState() {
return PrevState;
}
// 用于確定移動操作是否合法
bool CanBlankMove(DIRECTION Direction) {
int Blank = FindBlank();
switch (Direction) {
case NORTH:
if (Blank > 3) {
return true;
}
else {
return false;
}
break;
case EAST:
if (Blank != 3 && Blank != 6 && Blank != 9) {
return true;
}
else {
return false;
}
break;
case SOUTH:
if (Blank < 7) {
return true;
}
else {
return false;
}
break;
case WEST:
if (Blank != 1 && Blank != 4 && Blank != 7) {
return true;
}
else {
return false;
}
break;
default:
return false;
break;
}
}
void setgrid(int index, int value) {
Grid[index] = value;
}
/*該函數(shù)如果返回"true", 當(dāng)前狀態(tài)將是最終狀態(tài),以前的狀態(tài)指針可以用來回退,從而得到解決問題的答案。*/
bool Solution() {
if (Grid[1] == 1 && Grid[2] == 2 && Grid[3] == 3 && Grid[4] == 8 && Grid[5]
== 0 && Grid[6] == 4 && Grid[7] == 7 && Grid[8] == 6 && Grid[9] == 5)
{
return true;
}
else {
return false;
}
}
char GetGridValue(int Index) {
if (Index < 1 || Index > 9) {
throw ERROR_OUT_OF_BOUNDS();
}
else {
return Grid[Index];
}
}
// 含一個參數(shù)的構(gòu)造函數(shù);
CState(CState* Init) {
Depth = (Init->GetDepth());
OperatorApplyed = Init->GetLastOperator();
PrevState = Init->GetPrevState();
for (int n=1; n<=9; n++) {
Grid[n] = Init->GetGridValue(n);
}
}
DIRECTION GetLastOperator() {
return OperatorApplyed;
}
// 兩個參數(shù)的構(gòu)造 函數(shù);
CState(CState* Init, DIRECTION Direction) {
int n;
PrevState = Init;
Depth = (Init->GetDepth() + 1);
for (n=1; n<=9; n++) {
Grid[n] = Init->GetGridValue(n);
}
MoveBlank(Direction);
}
// 另外一個heuristic函數(shù),它計算錯誤網(wǎng)格的數(shù)量;
int GetWrongTiles() {
return ((Grid[1] != 1) +
(Grid[2] != 2) +
(Grid[3] != 3) +
(Grid[4] != 3) +
(Grid[5] != 3) +
(Grid[6] != 3) +
(Grid[7] != 3) +
(Grid[8] != 3) +
(Grid[9] != 3) +
(Depth )); // 也用于第二種heuristic (A*)的depth
}
/* ManhattanDistance是一個技術(shù)術(shù)語,它代表了所有當(dāng)前位置數(shù)字與其應(yīng)該處于的位置的直角距離之和*/
int GetManhattanDistance() {
int Result=0;
Result = GetDistanceBetween(1, FindBlank(1));
Result = Result + GetDistanceBetween(2, FindBlank(2));
Result = Result + GetDistanceBetween(3, FindBlank(3));
Result = Result + GetDistanceBetween(4, FindBlank(8));
Result = Result + GetDistanceBetween(5, FindBlank(0));
Result = Result + GetDistanceBetween(6, FindBlank(4));
Result = Result + GetDistanceBetween(7, FindBlank(7));
Result = Result + GetDistanceBetween(8, FindBlank(6));
Result = Result + GetDistanceBetween(9, FindBlank(5));
Result = Result + Depth;// 也用于第二種heuristic (A*)的depth;
return Result;
}
};
正如你所看到的,這是一個很"巨大"的類,但是,它一點都不復(fù)雜,僅僅是一些簡單的數(shù)據(jù)操作。比較難的部分是跟蹤各種狀態(tài),并按照正確的順序操作它們,這將是我們下面要做的。
這部分與人工智能關(guān)系不大,但是人工智能程序不能沒有它。如果你不了解為什么我們使用鏈表而不用數(shù)組,那末這里告訴你原因,數(shù)組在設(shè)計時有固定的尺寸,在運行時不能改變,所以如果程序一開始,你就擁有一個含有6 百萬個Cstates類對象的數(shù)組的話,而且這時你還不需要它,那將浪費大量的內(nèi)存。
鏈表僅在需要時才為新的對象申請空間,至于它是如何工作的就不詳細介紹了,到處都有此類的例子,你只要知道它確實在工作,它是一個可變尺寸的數(shù)組。鏈表實際上并不保存狀態(tài),而保存指向各個狀態(tài)的指針,這是因為,鏈表將是一個等待膨脹的狀態(tài)隊列,當(dāng)一個狀態(tài)膨脹時,它將從鏈表中刪除,然而,我們需要在內(nèi)存中保存這個狀態(tài),用于可能發(fā)生的回退或是用于報告從最初狀態(tài)到解決問題時的解決路徑。
當(dāng)我們實際編寫主循環(huán)代碼時,我們將把膨脹狀態(tài)放入到第二個隊列中,這樣我們才能跟蹤它們在那里,什么時候從內(nèi)存中刪除狀態(tài)(防止內(nèi)存泄露) 。
好了,從現(xiàn)在起回到C++代碼,生成一個新的頭文件"Llist.h",輸入如下代碼:
//列舉鏈表的兩個末端;
enum SIDE { LEFT, RIGHT };
// 鏈表由下面的Link結(jié)構(gòu)對象組成;
struct Link {
Link *LeftLink; // 指向鏈表中左端LINK對象的指針;
Link *RightLink; //指向鏈表中右端LINK對象的指針;
CState *Data; // 指向狀態(tài)數(shù)據(jù)的指針;
};
// 鏈表類;
class CLList {
private:
Link* LeftPointer; // 指向一個永遠是空的,并且是末端的link對象;
Link* RightPointer; //與上面的指針一樣,但方向相反;
double ListLen; // 鏈表的長度;
// 清空內(nèi)存;
void EmptyUsedMemory() {
CState *temp;
while(!IsListEmpty()) {
temp = Pop(LEFT);
delete temp;
}
}
public:
class ERROR_CANT_POP_EMPTY_LIST{}; // Error Exception
CLList() {
// initialise all private variables
LeftPointer = new Link;
RightPointer = new Link;
ListLen = 0;
LeftPointer->LeftLink = 0;
LeftPointer->RightLink = RightPointer;
RightPointer->RightLink = 0;
RightPointer->LeftLink = LeftPointer;
}
~CLList() {
EmptyUsedMemory();
}
inline double GetListLen() {
return ListLen;
}
inline bool IsListEmpty() {
return (LeftPointer->RightLink == RightPointer);
}
//從鏈表中彈出數(shù)據(jù);
CState* Pop(SIDE Side) {
Link ForReturn;
Link* ForDelete;
if (!IsListEmpty()) {
ListLen--;
if (Side == LEFT) {
ForReturn = *(LeftPointer->RightLink);
ForDelete = LeftPointer->RightLink;
LeftPointer->RightLink = ForReturn.RightLink;
ForReturn.RightLink->LeftLink = LeftPointer;
}
else {
ForReturn = *(RightPointer->LeftLink);
ForDelete = RightPointer->LeftLink;
RightPointer->LeftLink = ForReturn.LeftLink;
ForReturn.LeftLink->RightLink = RightPointer;
}
delete ForDelete;
return ForReturn.Data;
}
return 0;
}
//向鏈表中壓入數(shù)據(jù)
void Push(SIDE Side, CState* What) {
Link* NewLink = new Link;
NewLink->Data = What;
ListLen++;
if (Side == LEFT) {
NewLink->RightLink = LeftPointer->RightLink;
NewLink->LeftLink = LeftPointer;
LeftPointer->RightLink = NewLink;
NewLink->RightLink->LeftLink = NewLink;
}
else {
NewLink->RightLink = RightPointer;
NewLink->LeftLink = RightPointer->LeftLink;
RightPointer->LeftLink = NewLink;
NewLink->LeftLink->RightLink = NewLink;
}
}
//啟發(fā)式搜索過程中,從鏈表中搜尋最佳狀態(tài);
CState* PopBestByHeuristics(HEURISTIC heuristic) {
int BestValue=9999;
int Temp=0;
Link* BestState = 0;
Link* Current = LeftPointer;
CState* ForReturn = 0;
if(!IsListEmpty()) {
//啟發(fā)式搜索可以使用MANHATTAN_DISTANCE或WrongTile方式來搜尋最佳狀態(tài)
while(Current->RightLink != RightPointer) {
Current = Current->RightLink;
if(heuristic == MANHATTAN_DISTANCE) {
Temp = Current->Data->GetManhattanDistance();
}
else {
Temp = Current->Data->GetWrongTiles();
}
if(Temp < BestValue) {
BestValue = Temp;
BestState = Current;
}
}
//從鏈表中刪除最佳狀態(tài);
BestState->RightLink->LeftLink = BestState->LeftLink;
BestState->LeftLink->RightLink = BestState->RightLink;
ForReturn = BestState->Data;
delete BestState;
return ForReturn;
}
return 0;
}
CState* PeekByBestHueristics(HEURISTIC heuristic) {
int BestValue=9999;
int Temp=0;
Link* BestState = 0;
Link* Current = LeftPointer;
CState* ForReturn = 0;
if(!IsListEmpty()) {
// Find BEST State By Wrong tile number heuristic
while(Current->RightLink != RightPointer) {
Current = Current->RightLink;
if(heuristic == MANHATTAN_DISTANCE) {
Temp = Current->Data->GetManhattanDistance();
}
else {
Temp = Current->Data->GetWrongTiles();
}
if(Temp < BestValue) {
BestValue = Temp;
BestState = Current;
}
}
ForReturn = BestState->Data;
return ForReturn;
}
return 0;
}
接下來,創(chuàng)建另外一個頭文件"Eightpuzz.h",這里,除了main(),我將放入所有的東西,這些代碼閱讀起來有一定的難度,所以你要堅持住:
void gotoxy(int x, int y); //函數(shù)原型,使用光標的xy坐標;
// 枚舉搜索的類型;
enum TYPE {BREADTH_FIRST, DEPTH_FIRST };
// 類舉可能的A* 啟發(fā)式方法,第二種啟發(fā)式使用深度;
enum HEURISTIC {NOT_USED, MANHATTAN_DISTANCE, WRONG_TILES};
// include the header files we are going to need
#include<iostream.h> // for console i/o
#include<windows.h> // for sleep() and gotoxy()
#include"State.h" // for game state class
#include"Llist.h" // for a linked list class
CLList PrevStates; /* 用于跟蹤以前的狀態(tài),程序結(jié)束時要及時刪除,以免內(nèi)存泄露*/
CLList MainQue; // 等待主隊列;
SIDE PushSide;
SIDE PopSide;
// 膨脹函數(shù);
CState* GeneralExpand(int DepthLimit, HEURISTIC heuristic);
// 膨脹的步數(shù);
double Expanded = 0;
// 當(dāng)發(fā)現(xiàn)解決方法后,該函數(shù)將結(jié)果顯示在屏幕上;
void ShowSolution(CState* Solution) {
// 結(jié)果是回退得到的,所以實際結(jié)果應(yīng)該是與之反向的,這時候可以使用后入先出的方法;
CLList Reverse;
bool EndLoop=false;
while(!EndLoop) {
Reverse.Push(LEFT, Solution);
if(Solution->GetPrevState() != 0) {
Solution = Solution->GetPrevState();
}
else {
EndLoop = true;
}
}
int NewLine = 0;
CState *Temp;
cout << "\n";
while(!Reverse.IsListEmpty()) {
Temp = Reverse.Pop(LEFT);
NewLine++;
if(NewLine > 10) { cout << "\n"; NewLine=0;}
switch(Temp->GetLastOperator()) {
case NORTH:
cout << "North, ";
break;
case EAST:
cout << "East, ";
break;
case SOUTH:
cout << "South, ";
break;
case WEST:
cout << "West, ";
break;
}
}
cout << "\n\n" << "Expanded: " << Expanded << endl;
}
}
// 設(shè)置控制臺i/o x,y坐標
void gotoxy(int x, int y) {
// SET CONSOLE CURSOR POSITION.
COORD coord = {x,y};
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(handle,coord);
}
// 為主循環(huán)做準備;
void GeneralSearch(TYPE Type, HEURISTIC heuristic) {
CState *Solution;
int DepthLimit=0;
switch(heuristic) {
case NOT_USED:
// Breadth First Queing Type
if(Type == BREADTH_FIRST) {
PushSide = RIGHT;
PopSide = LEFT;
}
else {
// Depth First Search
cout << "Enter a Depth Limit :";
cin >> DepthLimit;
PushSide = RIGHT;
PopSide = RIGHT;
}
break;
case MANHATTAN_DISTANCE:
PushSide = RIGHT;
PopSide = RIGHT;
break;
case WRONG_TILES:
PushSide = RIGHT;
PopSide = RIGHT;
}
//將最初的狀態(tài)放入鏈表中;
MainQue.Push(PushSide, new CState());
//調(diào)用主循環(huán)
Solution = GeneralExpand(DepthLimit, heuristic);
// returns pointer to soution.
// or null pointer (failure)
if(Solution != 0) {
cout << "FOUND SOLUTION!" << endl;
ShowSolution(Solution);
cout << "DONE" << endl;
}
else {
//Fail
cout << "FAIL" << endl;
}
}
// 主循環(huán)函數(shù)(YEP, it BITES !)
CState* GeneralExpand(int DepthLimit, HEURISTIC heuristic) {
CState *CurrentState = 0;
CState *TempState = 0;
int LastDepth=-1;
if(PushSide == PopSide) {cout << "THINKING...please wait." << endl;}
// Main loop
while(!MainQue.IsListEmpty()) {
if(heuristic == NOT_USED) {
CurrentState = MainQue.Pop(PopSide);
}
else {
CurrentState = MainQue.PopBestByHeuristics(heuristic);
}
PrevStates.Push(RIGHT, CurrentState);
//對取出的狀態(tài)保持跟蹤(需要防止內(nèi)存)
/*可以讓用戶規(guī)定結(jié)束游戲所需要移動的最大步數(shù),這僅限于"breadth first only"方法*/
if(LastDepth < CurrentState->GetDepth() && PushSide != PopSide) {
LastDepth = CurrentState->GetDepth();
cout << "EXPANDING LEVEL " << LastDepth << endl;
}
// 如果當(dāng)前節(jié)點沒有超出depth限制
if((CurrentState->GetDepth() < DepthLimit) || (DepthLimit == 0 )) {
if((CurrentState->CanBlankMove(NORTH)) && (CurrentState->GetLastOperator() != SOUTH)) {
TempState = new CState(CurrentState, NORTH);
MainQue.Push(PushSide, TempState);
Expanded++;
if(TempState->Solution()) {
return TempState;
}
}
if((CurrentState->CanBlankMove(EAST)) && (CurrentState->GetLastOperator() != WEST)) {
TempState = new CState(CurrentState, EAST);
MainQue.Push(PushSide, TempState);
Expanded++;
if(TempState->Solution()) {
return TempState;
}
}
if((CurrentState->CanBlankMove(SOUTH)) && (CurrentState->GetLastOperator() != NORTH)) {
TempState = new CState(CurrentState, SOUTH);
MainQue.Push(PushSide, TempState);
Expanded++;
if(TempState->Solution()) {
return TempState;
}
}
if((CurrentState->CanBlankMove(WEST)) && (CurrentState->GetLastOperator() != EAST)) {
TempState = new CState(CurrentState, WEST);
MainQue.Push(PushSide, TempState);
Expanded++;
if(TempState->Solution()) {
return TempState;
}
}
}
}
return 0;
}
下面是主文件"EightPuzz.cpp" 的代碼:
#include"Eightpuzz.h"
int main() {
char Choice=0;
cout << "Choose Search Method...\n"
<< " [1] Blind Breadth First Search\n"
<< " [2] Blind Depth First Search\n"
<< " [3] A*(Tiles in the wrong position)\n"
<< " [4] A*(Manhattan Distance)\n" << endl;
cin >> Choice;
switch(Choice) {
case ’1’:
// call Blind Breadth First Search
GeneralSearch(BREADTH_FIRST, NOT_USED);
break;
case ’2’:
// call Blind Depth First Search
GeneralSearch(DEPTH_FIRST, NOT_USED);
break;
case ’3’:
// call A* with wrong tiles heuristic
GeneralSearch(DEPTH_FIRST, WRONG_TILES);
break;
case ’4’:
// call A* with manhatten heuristic
GeneralSearch(DEPTH_FIRST, MANHATTAN_DISTANCE);
break;
}
return 0;
}
正如我前面所說的,還是推薦你看完本文中的代碼,下載程序的源代碼,并用VC打開,仔細看一看整個程序的結(jié)構(gòu)及流程。通過這個例子程序,你應(yīng)該明白,編寫一個AI程序,所需要做的是:
1、 一個存儲狀態(tài)的方法;
2、 一個膨脹狀態(tài)的方法;
3、 一個回退到原始狀態(tài)的方法;
4、 一個排序狀態(tài)的方法;
5、 一個給狀態(tài)評分的函數(shù);
6、 一個主循環(huán),將一個狀態(tài)從鏈表中移出,并將其所有的子狀態(tài)添加到鏈表中;
上面的每個步驟都很簡單,但將它們組合在一起,并使它易讀易懂卻不那么容易了。