五子棋工作文檔
1說明:
這個程序在創建初期的時候是有一個寫的比較亂的文檔的,但是很可惜回學校的時候沒有帶回來……所以現在趕緊整理一下,不然再過一段時間就忘干凈了。
最初這個程序是受老同學所托做的,一開始的時候要求要人人對戰和人機對戰,但是大家都很明白,所謂的人人對戰就是簡單那的GDI繪圖罷了,那些基礎函數用好了自然沒問題。而人機對戰則需要一定的棋盤分析能力,做起來還是很復雜的。當時受時間限制,第一個版本是我用了兩天時間做的一個人人對戰,直接就給她發過去了,用來應付她的實習,因為我當時也不確定人機對戰能不能做出來。不過之后我一直在做,畢竟之前沒做過,算是一次嘗試。之后貌似過了9天吧,才完成了核心函數:GetAIPoint。用這么長時間一個是因為沒做過另外當時在家里還要幫家里干活,刨去干活加上打游戲的時間,平均下來每天的編碼時間不到3個小時。不過去我還是用了不少的時間來思考棋盤的分析的。走了不少彎路,吸取了不少教訓,感覺收獲還是挺大的。但是比較悲劇的是,我后來發現這個程序有內存泄露問題,問題貌似處在DrawChess函數里,因為無棋子的重繪並不會增加內存總量,看官若有興趣就幫我找找看吧,我是沒找到到底哪里出了問題……
程序運行截圖演示:
2程序主要數據結構以及函數:
1 //使用結構體有利於以后的數據擴展 2 /* 3 status 參數是用來表示當前這個點的狀態的,0表示白子,1表示黑子 -1表示尚無子 4 后兩個參數是用來追蹤前一個點的,用於悔棋 5 */ 6 typedef struct 7 { 8 INT status; 9 //悔棋 專用 10 INT PrePointx; 11 INT PrePointy; 12 INT nVisit_flag; 13 }Chess; 14 typedef struct 15 { 16 POINT startpos;//起始地點 17 POINT endpos;//終止地點 18 INT length;//長度 19 INT ChessType;//黑白子的辨別 20 INT EffectLevel;//棋子線的影響力,這個值的優先級判定應該和長度相關聯進行判斷,可以考慮通過使用一個公式來計算 21 }ChessLine; 22 // Forward declarations of functions included in this code module: 23 ATOM MyRegisterClass(HINSTANCE hInstance); 24 BOOL InitInstance(HINSTANCE, int); 25 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 26 INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); 27 INT g_nbase_x = 300; 28 INT g_nbase_y = 10; 29 Chess g_ChessTable[CHESS_LINE_NUM][CHESS_LINE_NUM];//作為全局變量的數據表 30 BOOL w_b_turn = 0;//下棋順序的控制變量 31 INT nxPosForChessTable = -1;//悔棋專用 32 INT nyPosForChessTable = -1;//悔棋專用 33 INT nRestart_Flag;//默認初始化的值為0,應該是重啟游戲的標志位 34 ChessLine BestLine;//白黑的最長有效線即可 35 INT DrawMode = 0;//0常規模式 1調試模式 36 INT PlayMode = 0;//游戲模式,分為人人對戰0和人機對戰1 37 //使用vector等模板時,還需要注意命名空間的問題 38 std::vector<ChessLine> w_ChessLineBuffer;//這個變量用於存儲所有的棋子線,白色 39 std::vector<ChessLine> b_ChessLineBuffer;//黑色 40 41 void DrawTable(HDC hdc, int base_x = 0, int base_y = 0); 42 void WinRectConvert(RECT * rect); 43 void DrawChess(HDC hdc, int x, int y, int w_or_b = 0);//0為白子,1為黑子 44 void GlobalInitial();//全局初始化函數 45 void DrwaChessOnTable(HDC hdc); 46 INT IsWin(int x, int y); 47 INT TellWhoWin(HWND hWnd, INT n, RECT * rect); 48 void BkBitmap(HDC hdc, RECT * rect); 49 void DrawInfo(HDC hdc, ChessLine * cl, INT length); 50 void GetALLLine(INT w_or_b);//根據棋盤全局信息來獲取對應顏色的最大長度線 51 INT GetMaxValCLAddr(ChessLine * parray, INT N);//返回最大值的數字地址 52 ChessLine * GetChessMaxSubLine(INT x, INT y, INT nColor, BOOL IfRtnVal);//獲取單個點的最長線函數 53 void AddIntoBuf(ChessLine * pcl,INT w_or_b); 54 void ChessLineInitial(ChessLine * pcl, POINT * pstartpos, INT n, INT nColor); 55 inline void DeleteCL(ChessLine * pcl); 56 void DrawVecInfo(HDC hdc, std::vector<ChessLine> * pvcl); 57 ChessLine * GetBestLine(INT nColor); 58 INT GetValidSEDirection(POINT SP, POINT EP);//獲取有效的方向,返回值 0,1,2,3分別對應2-6, 3-7, 4-0,5-1, 59 POINT GetAIPoint();//根據GetBestLine返回的黑白兩棋子線情況來判斷棋子的位置 60 POINT GetSinglePoint(); 61 INT IsValidSinglePoint(int x, int y);
可以看到,好多好多的函數~我現在也覺得有點頭暈,不過這樣就更有必要對這個程序進行整理了。
3 程序調用層析如下:
繪圖消息:
鼠標左鍵消息:
剛才意外的發現了我在家的時候做的那個文檔,雖然比較亂,但是還是較好的體現了一部分我的設計思想歷程,等會貼在最后面的附錄中。
上面的函數層次圖主要還是為了表明函數之間的調用關系,這樣便於理清之間的功能關系。另外我發現在設計數據類型時使用結構體或者類是一個非常好的選擇,在數據擴充上有很大的優勢,我在這個工程中就因此受益。
4 AI部分核心函數設計部分思想
首先就是如何看待棋盤上的棋子,如何根據棋盤上的棋子來分析出一個比較合適點作為下一步的選擇。
我的程序中棋盤的大小是15*15的,這個還是那個同學和我說的,最初我設計的是19*19的,因為家里的顯示器分辨率比較高,放得下。X軸和Y軸各15條線,棋盤的棋子可以簡單的通過一個15*15的結構數組來表示,每個結構表示棋子的狀態。這個結構如下:
1 /* 2 3 status 參數是用來表示當前這個點的狀態的,0表示白子,1表示黑子 -1表示尚無子 4 5 后兩個參數是用來追蹤前一個點的,用於悔棋 6 7 */ 8 9 typedef struct 10 11 { 12 13 INT status; 14 15 //悔棋 專用 16 17 INT PrePointx; 18 19 INT PrePointy; 20 21 INT nVisit_flag; 22 23 }Chess;
上面的這個結構中,status的作用就不多說了,后面的參數用處還是挺有意思的。PrePoint如其意思一樣,就是之前的那個點。第一個點的這個參數的值均為-1,用來標識無效點。而第1個點之后的所有的點這個參數的值均為前一點的坐標值。我覺得我的這個設計除了有點浪費內存,別的嘛,效率和效果還都是挺不錯的。這樣只要根據這兩個值,就能按照原路找回之前的點,從而實現悔棋以及其一套連續的操作。
最后一個參數是廢棄的,之前想通過每個點做一個標記來實現點的方向的記錄,不過后來的代碼實現表明這個是比較困難的,有更好的方法來實現,也就是我的程序中現在所使用的方法。
1 typedef struct 2 3 { 4 5 POINT startpos;//起始地點 6 7 POINT endpos;//終止地點 8 9 INT length;//長度 10 11 INT ChessType;//黑白子的辨別 12 13 INT EffectLevel;//棋子線的影響力,這個值的優先級判定應該和長度相關聯進行判斷,可以考慮通過使用一個公式來計算 14 15 }ChessLine;
上面的這個結構是用來表示棋子線的結構,其組成包括:起點和終點,長度,棋子的顏色,以及棋子線兩端是否有效。
而棋子線的優先級也可以通過一個很簡單的公式計算出來,即優先級為length + EffectLevel的結果來表示。需要注意的是,EffectLevel的值是不會等於0的,這個在線檢查函數中專門進行了處理,因為EffectLeve==0,意味着這條線是一條廢線,兩端都被堵死了,直接拋棄。
由上面的兩個結構你可以初步了解我對於整個棋盤上信息的抽象方法。
5 人人對戰的核心函數(IsWin)
在進行人人對戰的時候,核心函數其實就是要對棋盤上的棋子進行分析,判斷是否存在已經大於或等於長度為5的棋子線。
棋盤上每一個點,都可以分為4個方向,或者8個小方向。
最簡單的想法就是對棋盤上每一個點都進行計算,如果存在這樣一個點,就獲取其顏色,然后返回就可以了,由此即可判斷出誰贏了。但是仔細想想,這樣完全沒必要,因為能贏與否,與剛下的點是必然有聯系的。所以在進行檢測的時候,只需要檢測當前剛剛下的這個點就足夠了。想明白了沒?這樣一來,效率非常高,完全避免了無謂的操作。
IsWin函數的參數是棋盤上的坐標,然后通過坐標值訪問全局變量棋盤二維數組,做四個方向的檢查,從-4到+4的坐標偏移。針對越界情況專門進行了處理。但是不排除存在bug。
人人對戰的核心函數就這樣,沒別的。在AI模式下,這個函數依舊能夠用來判斷輸贏結果。
6 人機對戰核心函數POINT GetAIPoint();
根據上面的函數層次圖,能夠知道在這個函數中調用了4個重要的功能函數,先看下GetAiPoint函數的部分源代碼:
1 POINT GetAIPoint()//根據GetBestLine返回的黑白兩棋子線情況來判斷棋子的位置 2 3 { 4 5 //先獲取全部的線。 6 7 GetALLLine(0); 8 9 GetALLLine(1); 10 11 //這里曾造成內存泄露,原因是返回路徑會切斷刪除函數的調用 12 13 ChessLine * pw_cl = GetBestLine(0);//白子 人方 14 15 ChessLine * pb_cl = GetBestLine(1);//黑子 AI 16 17 ChessLine * pfinal_cl; 18 19 POINT rtnpos = {-1, -1}; 20 21 if(pw_cl != NULL && pb_cl != NULL) 22 23 { 24 25 //防守優先 26 27 if(pw_cl->EffectLevel + pw_cl->length >= pb_cl->EffectLevel + pb_cl->length) 28 29 pfinal_cl = pw_cl; 30 31 else 32 33 pfinal_cl = pb_cl; 34 35 } 36 37 else if(pw_cl == NULL && pb_cl != NULL) 38 39 pfinal_cl = pb_cl; 40 41 else if(pb_cl == NULL && pw_cl != NULL) 42 43 pfinal_cl = pw_cl; 44 45 else //在上面的兩個ChessLine都獲取不到的時候,需要做的是,嘗試去獲取一個單獨的點。 46 47 { 48 49 POINT SingleFinalPoint = GetSinglePoint(); 50 51 return SingleFinalPoint; 52 53 }
最先調用的函數是GetAllLine函數。這個函數的功能是查找全部的有效的線並將其添加到棋子線的vector容器中。參數是棋子顏色,0表示白色,1表示黑色。
看下這個函數的代碼:
1 void GetALLLine(INT w_or_b)//這個函數應該只處理一個點 2 3 { 4 5 //現在看,不用進行8個方向的查詢,而是只需要做4個方向的查詢即可,比如:1234,剩下的0567用其他的點來檢測 6 7 //八個方向為上下左右以及其45度角 8 9 //8時鍾方向,上位0,順時針,從0 - 7 10 11 /* 12 13 7 0 1 14 15 6 2 16 17 5 4 3 18 19 */ 20 21 //這兩個變量都設計為數組,是因為8個方向的數據,都存儲處理還是可以的 22 23 //一種比較節約空間的方法是設置臨時變量,存儲當前結果,與上一結果相比,這樣就不需要8個變量,而僅僅是兩個了。 24 25 ChessLine * pCL; 26 27 28 29 INT MaxLength = 0; 30 31 POINT MaxStartPos = {0}; 32 33 POINT MaxEndPos = {0}; 34 35 //memset(ArrayLength, 0, sizeof(ArrayLength));//嘿,看代碼的,你應該知道我這么用是合法的吧? 36 37 //這段代碼中有一部分代碼應該函數化 38 39 if(0 == w_or_b) 40 41 w_ChessLineBuffer.clear(); 42 43 else 44 45 b_ChessLineBuffer.clear(); 46 47 for(int i = 0;i < CHESS_LINE_NUM;++ i) 48 49 { 50 51 for(int j = 0;j < CHESS_LINE_NUM; ++ j) 52 53 { 54 55 pCL = GetChessMaxSubLine(i, j, w_or_b, FALSE); 56 57 if(pCL == NULL) 58 59 continue; 60 61 if(pCL->length > MaxLength) 62 63 { 64 65 MaxLength = pCL->length; 66 67 MaxStartPos = pCL->startpos; 68 69 MaxEndPos = pCL->endpos; 70 71 } 72 73 DeleteCL(pCL); 74 75 } 76 77 } 78 79 }
代碼中的注釋可以好好的看一看,在方向的選擇上,8個小方向,只要每個點都能一次查找其中的4個方向,就已經足夠了。因為剩余4個方向的查找會被低地址點的查找覆蓋掉,代碼實際結果表明也是這樣的。
另外這個函數中,還調用了一個較為關鍵的函數:GetChessMaxSubLine。這個函數可以說是一個功能很強大的,實現承上啟下作用的一個函數。在這個函數中,完成了單個點的棋子線查找,避免重疊的棋子線,以及將合法的棋子線添加到容器的重要工作,這里每一步都很關鍵,直接決定棋盤數據抽象的效率和有效性。對於這個函數,我修改了數次,才最終確定下來。這個函數中使用了不少的技巧,讀的時候要好好看注釋。在GetAllLine函數中存在一定的代碼冗余,起因就是過多次的修改。
GetAllLine函數調用后,會將對應顏色的有效棋子線全部放到對應顏色棋子的vector容器中,確實做到了get all lines。
接下來調用的函數是GetBestLine函數。這個函數的功能就很簡單了,就是遍歷vector容器,獲取到最好的一條線。
那么此時你應該會有疑問了:如何判定一條線的好壞?
首先要說明的是,對於兩端都被堵住了的線,是不存在於vector容器中的。因為這樣的線一點用都沒有。從vector中讀出一條線的結構體之后,可以根據線長度和線影響力這兩個成員變量的和來進行衡量的。長度不用多解釋,線影響力就是棋子線兩端的可下點的數目。這個是五子棋中比較有趣的特點,根據這兩個值的和,就能很有效的得到一條線的優先級了。然后依此來獲取到整個容器中最好的線。這就是GetBestLine函數的功能。
在獲取到最佳線之后,需要對黑子最佳線和白字最佳線進行對比。這里我在AI設計中優先防守,所以只要黑子線不大於白字,就確定白子最佳線為要進行下一步處理的線。(白子為AI棋子)
在獲取了要進一步處理的線之后,只要根據這條線得到一個合法的點就可以了。這個沒太多可說的了,調用GetValidSEDirection后獲取到方向,然后根據始發點和終點進行相應的地址偏移就可以了。
其實這里有一個很有趣的地方,就是我根本就沒怎么關注最佳線到底是人方下的還是AI的,但是一樣能實現其功能。因為獲取到最佳線之后,如果是AI線,那么就能進一步擴大優勢;如果是人方的線,就能夠對其進行堵截。巧妙吧?
至此GetAiPoint函數的核心思想套路已經講差不多了,至少我這個發明人算是想起來了整體的構架~
7 GetSinglePoint是干什么用的?
兩點一線,如果只是單獨的一個點,是不能算成線的哦~所以對於單個且獨立的棋子點,並沒有作為線來計算並加入到容器中。但是在剛剛下棋的時候,毫無疑問只有一個點……這個時候用GetBestLine函數獲取到的指針都是空的,怎么辦?
為了應對這種情況,我專門設計了GetSinglePoint函數來解決問題。在GetAiPoint函數中可以看到,在兩個線指針都是空的時候,會調用GetSinglePoint函數從棋盤二維數組中專門找一個獨立的點,然后返回這個點周邊的一個有效的坐標值,而且需要注意的是,這個坐標是有效范圍內隨機的!為了實現這個我還頗為費了一點心思呢。看看GetSinglePoint函數:
1 POINT GetSinglePoint() 2 { 3 //所謂singlepoint,就是8個相鄰點中沒有任何一點是同色點。 4 //函數返回值為從0-7中的有效點中的一個隨機點 5 INT npos; 6 POINT rtnpoint = {-1, -1}; 7 for(int i = 0;i < CHESS_LINE_NUM;++ i) 8 { 9 for(int j = 0;j < CHESS_LINE_NUM;++ j) 10 { 11 if(g_ChessTable[i][j].status != -1) 12 { 13 npos = IsValidSinglePoint(i, j); 14 if(npos == -1) 15 continue; 16 switch(npos) 17 { 18 //這里的代碼直接return,就不用再break了 19 case 0: 20 rtnpoint.x = i - 1; 21 rtnpoint.y = j - 1; 22 break; 23 case 1: 24 rtnpoint.x = i; 25 rtnpoint.y = j - 1; 26 break; 27 case 2: 28 rtnpoint.x = i + 1; 29 rtnpoint.y = j - 1; 30 break; 31 case 3: 32 rtnpoint.x = i - 1; 33 rtnpoint.y = j; 34 break; 35 case 4: 36 rtnpoint.x = i + 1; 37 rtnpoint.y = j; 38 break; 39 case 5: 40 rtnpoint.x = i - 1; 41 rtnpoint.y = j + 1; 42 break; 43 case 6: 44 rtnpoint.x = i; 45 rtnpoint.y = j + 1; 46 break; 47 case 7: 48 rtnpoint.x = i + 1; 49 rtnpoint.y = j + 1; 50 break; 51 } 52 return rtnpoint; 53 } 54 } 55 } 56 return rtnpoint; 57 }
從中還能發現又調用了一個函數:IsValidSinglePoint。如果點合法,會返回一個隨機的方向值,0-7,即8個小方向。若非法,則返回-1。
接下來再看這個函數實現:
1 INT IsValidSinglePoint(int x, int y) 2 { 3 assert(x >= 0 && y >=0 && x < CHESS_LINE_NUM && y < CHESS_LINE_NUM); 4 char checkflag[8] = {0};//純標記位 5 if(x - 1 >= 0)//一次查三個點 6 { 7 if(y - 1 >= 0) 8 { 9 if(g_ChessTable[x - 1][y - 1].status == -1) 10 checkflag[0] = 1; 11 } 12 if(g_ChessTable[x - 1][y].status == -1) 13 checkflag[3] = 1; 14 if(y + 1 < CHESS_LINE_NUM) 15 { 16 if(g_ChessTable[x - 1][y + 1].status == -1) 17 checkflag[5] = 1; 18 } 19 } 20 if(y - 1 >= 0 && g_ChessTable[x][y - 1].status == -1) 21 checkflag[1] = 1; 22 if(y + 1 < CHESS_LINE_NUM && g_ChessTable[x][y + 1].status == -1) 23 24 { 25 checkflag[6] = 1; 26 } 27 if(x + 1 < CHESS_LINE_NUM) 28 { 29 if(g_ChessTable[x + 1][y].status == -1) 30 checkflag[4] = 1; 31 if(y + 1 < CHESS_LINE_NUM) 32 { 33 if(g_ChessTable[x + 1][y + 1].status == -1) 34 checkflag[7] = 1; 35 } 36 if(y - 1 >= 0) 37 { 38 if(g_ChessTable[x + 1][y - 1].status == -1) 39 checkflag[2] = 1; 40 } 41 } 42 /*調試部分 43 INT nrtn = 0; 44 for(int i = 0;i < 8;++ i) 45 { 46 if(checkflag[i] == 1) 47 { 48 nrtn |= 1 << (i * 4); 49 } 50 }*/ 51 INT nCounterofValidPoint = 0; 52 for(int i = 0;i < 8;++ i) 53 { 54 if(checkflag[i] == 1) 55 nCounterofValidPoint ++; 56 } 57 if(!nCounterofValidPoint) 58 return -1; 59 srand(time(0)); 60 INT nUpSection = rand() % 8;//在這倒是正好能用作地址上限 61 INT UpSearch = nUpSection; 62 INT DownSearch = nUpSection; 63 for(;UpSearch < 8 || DownSearch >= 0;) 64 { 65 if(UpSearch < 8) 66 { 67 if(checkflag[UpSearch] == 1) 68 return UpSearch; 69 else 70 UpSearch ++; 71 } 72 if(DownSearch >= 0) 73 { 74 if(checkflag[DownSearch] == 1) 75 return DownSearch; 76 else 77 DownSearch --; 78 } 79 } 80 }
看起來一個功能簡單的函數,其實要做的操作還是不少的。因為除了要將合法的點對號入座,還要以隨機的形式取出來,代碼並不是很簡單。
由此,整個工程AI的核心實現基本介紹完畢。
附錄A 比較雜亂的最初版工作日記
五子棋工作日記
20130716創建
棋盤布局:
初步估計為19*19的布局,這樣應該差不多。
每個棋子的大小尺寸暫時設計為30*30個像素,應該可以的。
期盼的網格大小為35*35,棋子放置在棋盤焦點上。
數據表示:
除了棋盤布局之外,還需要一個棋盤上的數據表示矩陣,-1表示可以下,0表示白子,1表示黑子。
並且需要處理
坐標轉換問題:
首先,畫出來的棋盤經過了基礎坐標的偏移。
目前的問題是,坐標對應不上。鼠標坐標的位置是基於表格的。
坐標對應很簡單,方案如下:
首先,按正常的思路去畫坐標,然后,在網格的范圍中來正常的畫出棋子,棋子的坐標為左上角,但是,要畫在網格中間。
鼠標點擊上,依舊要以網格作為確定范圍,點擊后在相應位置畫出棋子。
以上的任務完成之后呢,效果應該是:
用鼠標點擊網格,在對應的網格的中間畫出棋子。
上述完成后,只要簡單一步:將制表函數的頂點坐標向右下角方向偏移半個網格長度。
然后下棋的效果就出來了
Win32 SDK背景圖片的處理經驗
之前給程序貼圖片,用的都是MFC的類來進行操作。今天用了一把SDK,感覺,還是挺不錯的。代碼只有簡簡單單的這么幾行,具體如下:
HDC htmpdc = CreateCompatibleDC(hdc);
//HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rect->right - rect->right, rect->bottom - rect->top);
HBITMAP hPicBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BKBITMAP));
SelectObject(htmpdc, hPicBitmap);
BitBlt(hdc, 0, 0, rect->right - rect->left, rect->bottom - rect->top, htmpdc, 300, 200, SRCCOPY);
DeleteObject(hPicBitmap);
DeleteDC(htmpdc);
首先,先調用CreateCompatibleBitmap函數來創建一個memory DC。然后再調用LoadBitmap函數獲取資源中的一張圖片,這個函數調用完成后,會獲取到一個位圖句柄。
接下來將其選入內存DC中。
最后調用BitBlt函數,把數據復制到我們從beginpaint函數中得到的hdc里面。
最后清理工作。
接下來應該做一下我自己的AI了。
五子棋AI思路:
首先,遇到未堵塞的對方三連點要立刻進行封堵。
在自己有優勢不如對方的時候,對對方進行封堵。
在優勢相當或者大於對方的時候,進行進攻。
優勢的判斷問題:
如何確定自己是優勢還是劣勢?
優勢應該為自己方可用的多連節點數多於對方的可用多連節點數。
判斷可用多連節點
這個剛剛做完,其實對一個點的檢查,只要滿足其中8個方向里4個防線就可以了,方向如下:
//8時時鍾方向,上為0 順時針,從0 - 7
/*
7 0 1
6 2
5 4 3
*/
我在做的時候只做了其中的2 3 4 5其中的四個方向。
方向查找代碼:
萬惡的unicode……
//2方¤?向¨°
INT nRight = StartPos.x + 1;
while(nRight < CHESS_LINE_NUM && g_ChessTable[nRight][StartPos.y].status == w_or_b)
{
ArrayLength[0]++;
nRight++;//向¨°右®¨°查¨¦找¨°
}
//保À¡ê存ä?對?應®|的Ì?點Ì?
ArrayEndPos[0].x = nRight - 1;
ArrayEndPos[0].y = StartPos.y;
//3方¤?向¨°
INT nRightDownOffset = 1;//右®¨°下?方¤?向¨°的Ì?偏?移°?地Ì?址¡¤
while(StartPos.x + nRightDownOffset < CHESS_LINE_NUM && \
StartPos.y + nRightDownOffset < CHESS_LINE_NUM && \
g_ChessTable[StartPos.x + nRightDownOffset][StartPos.y + nRightDownOffset].status == w_or_b)
{
ArrayLength[1]++;
nRightDownOffset++;
}
//保À¡ê存ä?對?應®|的Ì?點Ì?
ArrayEndPos[1].x = StartPos.x + nRightDownOffset - 1;
ArrayEndPos[1].y = StartPos.y + nRightDownOffset - 1;
//4方¤?向¨°
INT nDown = StartPos.y + 1;
while(nDown < CHESS_LINE_NUM && g_ChessTable[StartPos.x][nDown].status == w_or_b)
{
ArrayLength[2]++;
nDown++;//向¨°下?查¨¦找¨°
}
//保À¡ê存ä?對?應®|的Ì?點Ì?
ArrayEndPos[2].x = StartPos.x;
ArrayEndPos[2].y = nDown - 1;
//5方¤?向¨°
INT nLeftDownOffset = 1;//左Á¨®下?方¤?向¨°偏?移°?地Ì?址¡¤,ê?x -;ê?y +
while(StartPos.x + nLeftDownOffset < CHESS_LINE_NUM && \
StartPos.y + nLeftDownOffset < CHESS_LINE_NUM && \
g_ChessTable[StartPos.x - nLeftDownOffset][StartPos.y + nLeftDownOffset].status == w_or_b)
{
ArrayLength[3]++;
nLeftDownOffset++;
}
ArrayEndPos[3].x = StartPos.x - (nLeftDownOffset - 1);//為a了¢?邏?輯-清?楚t,ê?就¨ª先¨¨這a么¡ä寫¡ä了¢?
ArrayEndPos[3].y = StartPos.y + nLeftDownOffset - 1;
INT MaxLengthAddr = GetMaxValnAddr(ArrayLength, 4);
if(MaxLengthAddr == -1)
return;
現在在棋盤數據掃描上,已經能夠按照要求獲取到最長的有效棋子線了,但是,還不能對最長棋子線的兩端是否封閉進行檢測。
初步估計要做的工作是在獲取當前點的最長棋子線后,根據其索引地址或者斜率計算的方式計算出來其可擴展方向,然后再判斷擴展方向上是否有對方的棋子或者己方的棋子占據,有點小復雜。
另外現在的棋子長度線檢測是針對所有的線全部進行半規模檢測,也就是只檢查幫個方向,由此,倒也可以在一定程度上提高效率。
之前的那種遞歸算法,也不是不可以,但是,那是另外一個思路了。我這個效率低一點,但是代碼還比較好寫。
2013-7-23 22:07
剛才遇到了一個溢出錯誤,但是中斷代碼中並沒有提示,給了我很大的困惑,因為在代碼中並沒有提示說異常出在了什么地方。
不過在調試信息的輸出欄中,我看到了有關於vector的異常信息,位置在932行處。我去看了之后,發現了如下的代碼:
#if _ITERATOR_DEBUG_LEVEL == 2
if (size() <= _Pos)
{ // report error
_DEBUG_ERROR("vector subscript out of range");
_SCL_SECURE_OUT_OF_RANGE;
}
_DEBUG_ERROR就是932行之所在。
第一次看到的時候並沒有很放在心上,但是后來我發現,這段代碼的意思,就是訪問越界的一個判斷。
常規數組並沒有提供這個功能,但是,作為泛型編程模板的vector,提供了這個能力。而我的代碼觸發這個異常的原因近乎可笑,是在復制代碼的時候,有一個數忘記了更改,也就是0和1之差別
就是這個數的差別,會在白子線少於黑子線的時候,導致對白子線數組的越界訪問。就這么簡單。
現在做AI,代碼漸漸的已經膨脹到了900行,但是,我還真是沒什么欣喜的感覺。代碼越多,越難維護。看着現在的這個代碼,感覺,別人估計是看不懂的。
附錄B程序代碼
1 // WuZiQi20130716.cpp : Defines the entry point for the application. 2 3 // 4 5 /* 6 7 曲敬原創建於2013年07月16日 8 9 */ 10 11 #include "stdafx.h" 12 13 #include "WuZiQi20130716.h" 14 15 #include <vector>//沒轍,容器還是C++好用,純SDK程序算是破產了 16 17 #include <assert.h> 18 19 #include <ctime> 20 21 #include <cstdlib> 22 23 #define MAX_LOADSTRING 100 24 25 #define TABLE_SQUARE_LENGTH 35 26 27 #define CHESS_LENGTH 30 28 29 #define CHESS_LINE_NUM 15 30 31 // Global Variables: 32 33 HINSTANCE hInst; // current instance 34 35 TCHAR szTitle[MAX_LOADSTRING]; // The title bar text 36 37 TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name 38 39 //使用結構體有利於以后的數據擴展 40 41 /* 42 43 status 參數是用來表示當前這個點的狀態的,0表示白子,1表示黑子 -1表示尚無子 44 45 后兩個參數是用來追蹤前一個點的,用於悔棋 46 47 */ 48 49 typedef struct 50 51 { 52 53 INT status; 54 55 //悔棋 專用 56 57 INT PrePointx; 58 59 INT PrePointy; 60 61 INT nVisit_flag; 62 63 }Chess; 64 65 typedef struct 66 67 { 68 69 POINT startpos;//起始地點 70 71 POINT endpos;//終止地點 72 73 INT length;//長度 74 75 INT ChessType;//黑白子的辨別 76 77 INT EffectLevel;//棋子線的影響力,這個值的優先級判定應該和長度相關聯進行判斷,可以考慮通過使用一個公式來計算 78 79 }ChessLine; 80 81 // Forward declarations of functions included in this code module: 82 83 ATOM MyRegisterClass(HINSTANCE hInstance); 84 85 BOOL InitInstance(HINSTANCE, int); 86 87 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 88 89 INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); 90 91 INT g_nbase_x = 300; 92 93 INT g_nbase_y = 10; 94 95 Chess g_ChessTable[CHESS_LINE_NUM][CHESS_LINE_NUM];//作為全局變量的數據表 96 97 BOOL w_b_turn = 0;//下棋順序的控制變量 98 99 INT nxPosForChessTable = -1;//悔棋專用 100 101 INT nyPosForChessTable = -1;//悔棋專用 102 103 INT nRestart_Flag;//默認初始化的值為0,應該是重啟游戲的標志位 104 105 ChessLine BestLine;//白黑的最長有效線即可 106 107 INT DrawMode = 0;//0常規模式 1調試模式 108 109 INT PlayMode = 0;//游戲模式,分為人人對戰0和人機對戰1 110 111 //使用vector等模板時,還需要注意命名空間的問題 112 113 std::vector<ChessLine> w_ChessLineBuffer;//這個變量用於存儲所有的棋子線,白色 114 115 std::vector<ChessLine> b_ChessLineBuffer;//黑色 116 117 118 119 void DrawTable(HDC hdc, int base_x = 0, int base_y = 0); 120 121 void WinRectConvert(RECT * rect); 122 123 void DrawChess(HDC hdc, int x, int y, int w_or_b = 0);//0為白子,1為黑子 124 125 void GlobalInitial();//全局初始化函數 126 127 void DrwaChessOnTable(HDC hdc); 128 129 INT IsWin(int x, int y); 130 131 INT TellWhoWin(HWND hWnd, INT n, RECT * rect); 132 133 void BkBitmap(HDC hdc, RECT * rect); 134 135 void DrawInfo(HDC hdc, ChessLine * cl, INT length); 136 137 void GetALLLine(INT w_or_b);//根據棋盤全局信息來獲取對應顏色的最大長度線 138 139 INT GetMaxValCLAddr(ChessLine * parray, INT N);//返回最大值的數字地址 140 141 ChessLine * GetChessMaxSubLine(INT x, INT y, INT nColor, BOOL IfRtnVal);//獲取單個點的最長線函數 142 143 void AddIntoBuf(ChessLine * pcl,INT w_or_b); 144 145 void ChessLineInitial(ChessLine * pcl, POINT * pstartpos, INT n, INT nColor); 146 147 inline void DeleteCL(ChessLine * pcl); 148 149 void DrawVecInfo(HDC hdc, std::vector<ChessLine> * pvcl); 150 151 ChessLine * GetBestLine(INT nColor); 152 153 INT GetValidSEDirection(POINT SP, POINT EP);//獲取有效的方向,返回值 0,1,2,3分別對應2-6, 3-7, 4-0,5-1, 154 155 POINT GetAIPoint();//根據GetBestLine返回的黑白兩棋子線情況來判斷棋子的位置 156 157 POINT GetSinglePoint(); 158 159 INT IsValidSinglePoint(int x, int y); 160 161 162 163 int APIENTRY _tWinMain(HINSTANCE hInstance, 164 165 HINSTANCE hPrevInstance, 166 167 LPTSTR lpCmdLine, 168 169 int nCmdShow) 170 171 { 172 173 UNREFERENCED_PARAMETER(hPrevInstance); 174 175 UNREFERENCED_PARAMETER(lpCmdLine); 176 177 178 179 // TODO: Place code here. 180 181 MSG msg; 182 183 HACCEL hAccelTable; 184 185 186 187 // Initialize global strings 188 189 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); 190 191 LoadString(hInstance, IDC_WUZIQI20130716, szWindowClass, MAX_LOADSTRING); 192 193 MyRegisterClass(hInstance); 194 195 196 197 // Perform application initialization: 198 199 if (!InitInstance (hInstance, nCmdShow)) 200 201 { 202 203 return FALSE; 204 205 } 206 207 208 209 hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WUZIQI20130716)); 210 211 212 213 // Main message loop: 214 215 while (GetMessage(&msg, NULL, 0, 0)) 216 217 { 218 219 if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) 220 221 { 222 223 TranslateMessage(&msg); 224 225 DispatchMessage(&msg); 226 227 } 228 229 } 230 231 232 233 return (int) msg.wParam; 234 235 } 236 237 238 239 240 241 242 243 // 244 245 // FUNCTION: MyRegisterClass() 246 247 // 248 249 // PURPOSE: Registers the window class. 250 251 // 252 253 // COMMENTS: 254 255 // 256 257 // This function and its usage are only necessary if you want this code 258 259 // to be compatible with Win32 systems prior to the 'RegisterClassEx' 260 261 // function that was added to Windows 95. It is important to call this function 262 263 // so that the application will get 'well formed' small icons associated 264 265 // with it. 266 267 // 268 269 ATOM MyRegisterClass(HINSTANCE hInstance) 270 271 { 272 273 WNDCLASSEX wcex; 274 275 276 277 wcex.cbSize = sizeof(WNDCLASSEX); 278 279 280 281 wcex.style = CS_HREDRAW | CS_VREDRAW; 282 283 wcex.lpfnWndProc = WndProc; 284 285 wcex.cbClsExtra = 0; 286 287 wcex.cbWndExtra = 0; 288 289 wcex.hInstance = hInstance; 290 291 wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WUZIQI20130716)); 292 293 wcex.hCursor = LoadCursor(NULL, IDC_ARROW); 294 295 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); 296 297 wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WUZIQI20130716); 298 299 wcex.lpszClassName = szWindowClass; 300 301 wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); 302 303 304 305 return RegisterClassEx(&wcex); 306 307 } 308 309 310 311 // 312 313 // FUNCTION: InitInstance(HINSTANCE, int) 314 315 // 316 317 // PURPOSE: Saves instance handle and creates main window 318 319 // 320 321 // COMMENTS: 322 323 // 324 325 // In this function, we save the instance handle in a global variable and 326 327 // create and display the main program window. 328 329 // 330 331 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) 332 333 { 334 335 HWND hWnd; 336 337 338 339 hInst = hInstance; // Store instance handle in our global variable 340 341 342 343 hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, 344 345 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); 346 347 348 349 if (!hWnd) 350 351 { 352 353 return FALSE; 354 355 } 356 357 358 359 ShowWindow(hWnd, nCmdShow); 360 361 UpdateWindow(hWnd); 362 363 364 365 return TRUE; 366 367 } 368 369 370 371 // 372 373 // FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) 374 375 // 376 377 // PURPOSE: Processes messages for the main window. 378 379 // 380 381 // WM_COMMAND - process the application menu 382 383 // WM_PAINT - Paint the main window 384 385 // WM_DESTROY - post a quit message and return 386 387 // 388 389 // 390 391 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 392 393 { 394 395 int wmId, wmEvent; 396 397 PAINTSTRUCT ps; 398 399 HDC hdc; 400 401 RECT winrect; 402 403 INT nlxPos; 404 405 INT nlyPos; 406 407 INT nDBPosx; 408 409 INT nDBPosy; 410 411 INT IORtmpx;//給IDM_OPTION_REGRET消息用的 412 413 INT IORtmpy; 414 415 POINT AIPoint; 416 417 HMENU SubMenu; 418 419 switch (message) 420 421 { 422 423 case WM_CREATE: 424 425 GlobalInitial(); 426 427 break; 428 429 case WM_COMMAND: 430 431 wmId = LOWORD(wParam); 432 433 wmEvent = HIWORD(wParam); 434 435 // Parse the menu selections: 436 437 switch (wmId) 438 439 { 440 441 case IDM_ABOUT: 442 443 DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); 444 445 break; 446 447 case IDM_EXIT: 448 449 DestroyWindow(hWnd); 450 451 break; 452 453 case IDM_OPTION_REGRET: 454 455 //這一步是專門給悔棋用的 456 457 //根據當前節點的指向,進行退解 458 459 if(nxPosForChessTable < 0 || nyPosForChessTable < 0) 460 461 break; 462 463 //下面這段代碼還挺好使的 464 465 IORtmpx = nxPosForChessTable; 466 467 IORtmpy = nyPosForChessTable; 468 469 g_ChessTable[IORtmpx][IORtmpy].status = -1; 470 471 nxPosForChessTable = g_ChessTable[IORtmpx][IORtmpy].PrePointx; 472 473 nyPosForChessTable = g_ChessTable[IORtmpx][IORtmpy].PrePointy; 474 475 //清理工作 476 477 g_ChessTable[IORtmpx][IORtmpy].PrePointx = -1; 478 479 g_ChessTable[IORtmpx][IORtmpy].PrePointy = -1; 480 481 (++w_b_turn) %= 2;//再次變成0或1 482 483 InvalidateRect(hWnd, NULL, TRUE); 484 485 break; 486 487 case ID_OPTION_PLAYMODE: 488 489 (++ PlayMode) %= 2; 490 491 SubMenu= GetSubMenu(GetMenu(hWnd), 1); 492 493 if(PlayMode == 1) 494 495 CheckMenuItem(SubMenu, 1, MF_CHECKED|MF_BYPOSITION); 496 497 else 498 499 CheckMenuItem(SubMenu, 1, MF_UNCHECKED|MF_BYPOSITION); 500 501 GlobalInitial(); 502 503 InvalidateRect(hWnd, NULL, TRUE); 504 505 break; 506 507 case ID_OPTION_AIWATCH: 508 509 SubMenu= GetSubMenu(GetMenu(hWnd), 1); 510 511 (++ DrawMode) %= 2; 512 513 if(DrawMode == 1) 514 515 CheckMenuItem(SubMenu, 2, MF_CHECKED|MF_BYPOSITION); 516 517 else 518 519 CheckMenuItem(SubMenu, 2, MF_UNCHECKED|MF_BYPOSITION); 520 521 InvalidateRect(hWnd, NULL, TRUE); 522 523 break; 524 525 default: 526 527 return DefWindowProc(hWnd, message, wParam, lParam); 528 529 } 530 531 break; 532 533 case WM_PAINT: 534 535 hdc = BeginPaint(hWnd, &ps); 536 537 GetWindowRect(hWnd, &winrect); 538 539 540 541 WinRectConvert(&winrect); 542 543 //防閃屏處理 544 545 //FillRect(hdc, &winrect, (HBRUSH)GetStockObject(WHITE_BRUSH)); 546 547 BkBitmap(hdc, &winrect); 548 549 550 551 //DrawChess(hdc, 10, 10, 0); 552 553 //根據棋盤對應數據來畫棋棋子 554 555 // TODO: Add any drawing code here... 556 557 EndPaint(hWnd, &ps); 558 559 break; 560 561 case WM_ERASEBKGND: 562 563 //這塊代碼就是為了進行消息攔截,因為我並不需要把屏幕背景重新刷一遍,那樣會導致閃屏 564 565 break; 566 567 case WM_DESTROY: 568 569 PostQuitMessage(0); 570 571 break; 572 573 case WM_LBUTTONDOWN: 574 575 nlxPos = LOWORD(lParam) - g_nbase_x; 576 577 nlyPos = HIWORD(lParam) - g_nbase_y; 578 579 //部分初始化 580 581 GetWindowRect(hWnd, &winrect); 582 583 WinRectConvert(&winrect); 584 585 //做完了減法,一定要判斷結果是否依舊大於0; 586 587 if(nlxPos <= 0 || nlyPos <= 0) 588 589 return 0; 590 591 //這兩個除法主要是獲取左上角的坐標,用來轉換到棋盤數據對應的地址,同時下棋 592 593 nDBPosx = nlxPos / TABLE_SQUARE_LENGTH; 594 595 nDBPosy = nlyPos / TABLE_SQUARE_LENGTH; 596 597 if(nDBPosx >= CHESS_LINE_NUM || nDBPosy >= CHESS_LINE_NUM) 598 599 return 0; 600 601 //坐標判定有效之后,還需要對當前點的數據否有效進行檢測 602 603 if(g_ChessTable[nDBPosx][nDBPosy].status != -1) 604 605 return 0; 606 607 else 608 609 { 610 611 g_ChessTable[nDBPosx][nDBPosy].status = w_b_turn; 612 613 g_ChessTable[nDBPosx][nDBPosy].PrePointx = nxPosForChessTable; 614 615 g_ChessTable[nDBPosx][nDBPosy].PrePointy = nyPosForChessTable; 616 617 //復制完成后,再更新前點坐標 618 619 nxPosForChessTable = nDBPosx; 620 621 nyPosForChessTable = nDBPosy; 622 623 DrawChess(GetDC(hWnd), nDBPosx * TABLE_SQUARE_LENGTH + g_nbase_x, nDBPosy * TABLE_SQUARE_LENGTH + g_nbase_y, w_b_turn); 624 625 TellWhoWin(hWnd, IsWin(nDBPosx, nDBPosy), &winrect); 626 627 } 628 629 //這里我打算改成GetAIPoint函數執行全部的AI函數調用,包括相關的數據顯示 630 631 if(PlayMode)//1的時候執行人機對戰 632 633 { 634 635 AIPoint = GetAIPoint(); 636 637 if(AIPoint.x != -1 && AIPoint.y != -1) 638 639 g_ChessTable[AIPoint.x][AIPoint.y].status = ((++w_b_turn) %= 2);//順便執行了 640 641 g_ChessTable[AIPoint.x][AIPoint.y].PrePointx = nxPosForChessTable; 642 643 g_ChessTable[AIPoint.x][AIPoint.y].PrePointy = nyPosForChessTable; 644 645 //前點坐標更新 646 647 nxPosForChessTable = AIPoint.x; 648 649 nyPosForChessTable = AIPoint.y; 650 651 if(DrawMode == 0) 652 653 DrawChess(GetDC(hWnd), AIPoint.x * TABLE_SQUARE_LENGTH + g_nbase_x, AIPoint.y * TABLE_SQUARE_LENGTH + g_nbase_y, w_b_turn); 654 655 else 656 657 InvalidateRect(hWnd, NULL, TRUE); 658 659 TellWhoWin(hWnd, IsWin(AIPoint.x, AIPoint.y), &winrect); 660 661 } 662 663 //繪圖部分 664 665 (++w_b_turn) %= 2;//再次變成0或1; 666 667 668 669 break; 670 671 default: 672 673 return DefWindowProc(hWnd, message, wParam, lParam); 674 675 } 676 677 return 0; 678 679 } 680 681 682 683 // Message handler for about box. 684 685 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 686 687 { 688 689 UNREFERENCED_PARAMETER(lParam); 690 691 switch (message) 692 693 { 694 695 case WM_INITDIALOG: 696 697 return (INT_PTR)TRUE; 698 699 700 701 case WM_COMMAND: 702 703 if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) 704 705 { 706 707 EndDialog(hDlg, LOWORD(wParam)); 708 709 return (INT_PTR)TRUE; 710 711 } 712 713 break; 714 715 } 716 717 return (INT_PTR)FALSE; 718 719 } 720 721 722 723 void DrawTable(HDC hdc, int base_x, int base_y) 724 725 { 726 727 int nsquarelength = TABLE_SQUARE_LENGTH; 728 729 int nTableOffset = TABLE_SQUARE_LENGTH / 2; 730 731 //畫豎表格 732 733 for(int i = 0;i < CHESS_LINE_NUM;++ i) 734 735 { 736 737 MoveToEx(hdc, i * nsquarelength + base_x + nTableOffset, base_y + nTableOffset, NULL); 738 739 LineTo(hdc, i * nsquarelength + base_x + nTableOffset, (CHESS_LINE_NUM - 1) * nsquarelength + base_y + nTableOffset); 740 741 } 742 743 //畫橫表格 744 745 for(int i = 0;i < CHESS_LINE_NUM;++ i) 746 747 { 748 749 MoveToEx(hdc, base_x + nTableOffset, i * nsquarelength + base_y + nTableOffset, NULL); 750 751 LineTo(hdc, (CHESS_LINE_NUM - 1) * nsquarelength + base_x + nTableOffset, i * nsquarelength + base_y + nTableOffset); 752 753 } 754 755 } 756 757 758 759 void DrwaChessOnTable(HDC hdc) 760 761 { 762 763 for(int i = 0;i < CHESS_LINE_NUM;++ i) 764 765 for(int j = 0;j < CHESS_LINE_NUM;++ j) 766 767 { 768 769 if(g_ChessTable[i][j].status != -1) 770 771 { 772 773 DrawChess(hdc, i * TABLE_SQUARE_LENGTH + g_nbase_x, j * TABLE_SQUARE_LENGTH + g_nbase_y, g_ChessTable[i][j].status); 774 775 } 776 777 } 778 779 } 780 781 void DrawChess(HDC hdc, int x, int y, int w_or_b)//0為白子,1為黑子 782 783 { 784 785 DWORD chesscolor; 786 787 if(w_or_b == 0) 788 789 { 790 791 chesscolor = RGB(255, 255, 255);//灰色,因為棋盤顏色背景還未選好 792 793 } 794 795 else 796 797 { 798 799 chesscolor = RGB(0, 0, 0); 800 801 } 802 803 HBRUSH ChessBrush = CreateSolidBrush(chesscolor); 804 805 HBRUSH OldBrush = (HBRUSH)SelectObject(hdc, ChessBrush); 806 807 //下面這兩行的+2是根據效果手動確定的,效果還不錯。 808 809 Ellipse(hdc, x + 2, y + 2, x + CHESS_LENGTH, y + CHESS_LENGTH); 810 811 ChessBrush = (HBRUSH)SelectObject(hdc, OldBrush); 812 813 assert(DeleteObject(ChessBrush) != 0); 814 815 } 816 817 818 819 void WinRectConvert(RECT * rect) 820 821 { 822 823 rect->bottom -= rect->top; 824 825 rect->right -= rect->left; 826 827 rect->left = 0; 828 829 rect->top = 0; 830 831 } 832 833 834 835 void GlobalInitial() 836 837 { 838 839 //初始化19*19的結構數組 840 841 for(int i = 0;i < CHESS_LINE_NUM;++ i) 842 843 for(int j = 0;j < CHESS_LINE_NUM;++ j) 844 845 { 846 847 g_ChessTable[i][j].status = -1; 848 849 //因為0 0 這個點是有效的坐標,因此初始化為-1用來表示無效點 850 851 g_ChessTable[i][j].PrePointx = -1; 852 853 g_ChessTable[i][j].PrePointy = -1; 854 855 856 857 g_ChessTable[i][j].nVisit_flag = 0;//該參數表明此節點節點是否已經訪問過。0未訪問 1訪問 858 859 } 860 861 w_ChessLineBuffer.clear(); 862 863 b_ChessLineBuffer.clear(); 864 865 ; 866 867 } 868 869 870 871 INT IsWin(int x, int y) 872 873 { 874 875 //這個邏輯要仔細的想一下 876 877 //首先 在這段代碼里 我很想說 如果每次都是對整個棋盤進行檢查,實在是太笨了。 878 879 //畢竟每次要做的,僅僅是檢查當前這點關聯單位是否滿足條件,而且,只關心上一點的顏色即可 880 881 882 883 int nTheColor = w_b_turn; 884 885 int CheckCounter = 0; 886 887 //行檢查 888 889 int xStartPos; 890 891 if(x - 4 >= 0) 892 893 xStartPos = x - 4; 894 895 else 896 897 xStartPos = 0; 898 899 int xEndPos; 900 901 if(x + 4 < CHESS_LINE_NUM) 902 903 xEndPos = x + 4; 904 905 else 906 907 xEndPos = (CHESS_LINE_NUM - 1); 908 909 CheckCounter = 0; 910 911 for(int i = xStartPos;i <= xEndPos;++ i) 912 913 { 914 915 if(g_ChessTable[i][y].status == nTheColor) 916 917 { 918 919 CheckCounter++; 920 921 if(CheckCounter >= 5) 922 923 { 924 925 CheckCounter = 0; 926 927 return nTheColor; 928 929 } 930 931 } 932 933 else 934 935 { 936 937 CheckCounter = 0; 938 939 } 940 941 } 942 943 //列檢查 944 945 int yStartPos; 946 947 if(y - 4 >= 0) 948 949 yStartPos = y - 4; 950 951 else 952 953 yStartPos = 0; 954 955 int yEndPos; 956 957 if(y + 4 < CHESS_LINE_NUM) 958 959 yEndPos = y + 4; 960 961 else 962 963 yEndPos = (CHESS_LINE_NUM - 1); 964 965 CheckCounter = 0; 966 967 for(int i = yStartPos;i <= yEndPos;++ i) 968 969 { 970 971 if(g_ChessTable[x][i].status == nTheColor) 972 973 { 974 975 CheckCounter++; 976 977 if(CheckCounter >= 5) 978 979 { 980 981 CheckCounter = 0; 982 983 return nTheColor; 984 985 } 986 987 } 988 989 else 990 991 { 992 993 CheckCounter = 0; 994 995 } 996 997 } 998 999 //左上角到右下角檢查 1000 1001 CheckCounter = 0; 1002 1003 for(int i = -4;i <= 4;++ i) 1004 1005 { 1006 1007 if(x + i < 0 || y + i < 0 || x + i >= CHESS_LINE_NUM || y + i >= CHESS_LINE_NUM) 1008 1009 { 1010 1011 continue; 1012 1013 } 1014 1015 else 1016 1017 { 1018 1019 if(g_ChessTable[x + i][y + i].status == nTheColor) 1020 1021 { 1022 1023 CheckCounter ++; 1024 1025 if(CheckCounter >= 5) 1026 1027 { 1028 1029 CheckCounter = 0; 1030 1031 return nTheColor; 1032 1033 } 1034 1035 } 1036 1037 else 1038 1039 { 1040 1041 CheckCounter = 0; 1042 1043 } 1044 1045 } 1046 1047 } 1048 1049 //右上角到左下角檢查 1050 1051 CheckCounter = 0; 1052 1053 for(int i = -4;i <= 4;++ i) 1054 1055 { 1056 1057 if(x - i < 0 || y + i < 0 || x - i >= CHESS_LINE_NUM || y + i >= CHESS_LINE_NUM) 1058 1059 { 1060 1061 continue; 1062 1063 } 1064 1065 else 1066 1067 { 1068 1069 if(g_ChessTable[x - i][y + i].status == nTheColor) 1070 1071 { 1072 1073 CheckCounter ++; 1074 1075 if(CheckCounter >= 5) 1076 1077 { 1078 1079 CheckCounter = 0; 1080 1081 return nTheColor; 1082 1083 } 1084 1085 } 1086 1087 else 1088 1089 { 1090 1091 CheckCounter = 0; 1092 1093 } 1094 1095 } 1096 1097 } 1098 1099 return -1; 1100 1101 } 1102 1103 1104 1105 INT TellWhoWin(HWND hWnd, INT n, RECT * rect) 1106 1107 { 1108 1109 //SetBkMode(hdc, TRANSPARENT);這個透明參數,想了想 還是算了,背景不透明更好一點。 1110 1111 /*把這段代碼注釋掉的原因是因為目前畫面做的還不夠好,這樣還不如直接使用messagebox函數 1112 1113 rect->top += rect->bottom / 2; 1114 1115 LOGFONT lf; 1116 1117 memset(&lf, 0, sizeof(lf)); 1118 1119 lf.lfHeight = 50; 1120 1121 HFONT hfont = CreateFontIndirect(&lf); 1122 1123 HFONT OldFont = (HFONT)SelectObject(hdc, hfont);*/ 1124 1125 switch(n) 1126 1127 { 1128 1129 case 0: 1130 1131 //打出來白方勝 1132 1133 //DrawText(hdc, _T("白方勝"), 3, rect, DT_CENTER); 1134 1135 MessageBeep(-1); 1136 1137 MessageBox(hWnd, _T("白方勝"), _T("Notice"), 0); 1138 1139 break; 1140 1141 case 1: 1142 1143 //DrawText(hdc, _T("黑方勝"), 3, rect, DT_CENTER); 1144 1145 MessageBeep(-1); 1146 1147 MessageBox(hWnd, _T("黑方勝"), _T("Notice"), 0); 1148 1149 //這個自然就是黑方勝了 1150 1151 break; 1152 1153 default: 1154 1155 //DeleteObject(SelectObject(hdc,OldFont)); 1156 1157 return 0; 1158 1159 break;//這個break雖然沒用,但是看着畢竟還是舒服點 1160 1161 } 1162 1163 //DeleteObject(SelectObject(hdc,OldFont)); 1164 1165 GlobalInitial(); 1166 1167 InvalidateRect(hWnd, NULL, TRUE);//擦寫屏幕 1168 1169 // 1170 1171 return 1; 1172 1173 } 1174 1175 1176 1177 void BkBitmap(HDC hdc, RECT * rect) 1178 1179 { 1180 1181 HDC htmpdc = CreateCompatibleDC(hdc); 1182 1183 //HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rect->right - rect->right, rect->bottom - rect->top); 1184 1185 HBITMAP hPicBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BKBITMAP)); 1186 1187 HBITMAP OldBitmap = (HBITMAP)SelectObject(htmpdc, hPicBitmap); 1188 1189 1190 1191 //代碼整合的嘗試 1192 1193 DrawTable(htmpdc, g_nbase_x, g_nbase_y); 1194 1195 DrwaChessOnTable(htmpdc); 1196 1197 1198 1199 //調試專用 1200 1201 SetBkMode(htmpdc, TRANSPARENT); 1202 1203 //DrawInfo(htmpdc, MaxOfw_bLine, 2); 1204 1205 if(DrawMode) 1206 1207 DrawVecInfo(htmpdc, &w_ChessLineBuffer); 1208 1209 1210 1211 BitBlt(hdc, 0, 0, rect->right - rect->left, rect->bottom - rect->top, htmpdc, 0, 0, SRCCOPY); 1212 1213 hPicBitmap = (HBITMAP)SelectObject(htmpdc, OldBitmap); 1214 1215 DeleteObject(hPicBitmap); 1216 1217 DeleteDC(htmpdc); 1218 1219 } 1220 1221 1222 1223 void DrawInfo(HDC hdc, ChessLine * cl, INT length) 1224 1225 { 1226 1227 TCHAR WORD[100]; 1228 1229 TCHAR TMPWORD[3];//三個應該就夠用了 1230 1231 for(int i = 0;i < length;++ i) 1232 1233 { 1234 1235 if(cl[i].ChessType == 0) 1236 1237 wcscpy(TMPWORD, _T("白方")); 1238 1239 else 1240 1241 wcscpy(TMPWORD, _T("黑方")); 1242 1243 wsprintf(WORD, _T("%s:StartPos x:%d y:%dEndPos x:%d y%d:%Length: %d"), 1244 1245 TMPWORD, 1246 1247 cl[i].startpos.x, 1248 1249 cl[i].startpos.y, 1250 1251 cl[i].endpos.x, 1252 1253 cl[i].endpos.y, 1254 1255 cl[i].length 1256 1257 ); 1258 1259 TextOut(hdc, 0,i * 100, WORD, 3); 1260 1261 TextOut(hdc, 0,i * 100 + 20, WORD + 3,wcslen(WORD) - 3); 1262 1263 } 1264 1265 } 1266 1267 POINT AIDeal(INT posx, INT posy)//因為大多數的變量都是全局變量,所以不需要很多參數。這兩個參數是剛剛按下的點 1268 1269 { 1270 1271 POINT tmppoint; 1272 1273 return tmppoint; 1274 1275 } 1276 1277 1278 1279 void GetALLLine(INT w_or_b)//這個函數應該只處理一個點 1280 1281 { 1282 1283 //現在看,不用進行8個方向的查詢,而是只需要做4個方向的查詢即可,比如:1234,剩下的0567用其他的點來檢測 1284 1285 //八個方向為上下左右以及其45度角 1286 1287 //8時鍾方向,上位0,順時針,從0 - 7 1288 1289 /* 1290 1291 7 0 1 1292 1293 6 2 1294 1295 5 4 3 1296 1297 */ 1298 1299 /*這個方法是有缺陷的,正常方法應該是對每個點都進行遍歷,換言之,應該對點使用遞歸函數*/ 1300 1301 //0方向的全部線查找 1302 1303 1304 1305 //這兩個變量都設計為數組,是因為8個方向的數據,都存儲處理還是可以的 1306 1307 //一種比較節約空間的方法是設置臨時變量,存儲當前結果,與上一結果相比,這樣就不需要8個變量,而僅僅是兩個了。 1308 1309 ChessLine * pCL; 1310 1311 1312 1313 INT MaxLength = 0; 1314 1315 POINT MaxStartPos = {0}; 1316 1317 POINT MaxEndPos = {0}; 1318 1319 //memset(ArrayLength, 0, sizeof(ArrayLength));//嘿,看代碼的,你應該知道我這么用是合法的吧? 1320 1321 //這段代碼中有一部分代碼應該函數化 1322 1323 if(0 == w_or_b) 1324 1325 w_ChessLineBuffer.clear(); 1326 1327 else 1328 1329 b_ChessLineBuffer.clear(); 1330 1331 for(int i = 0;i < CHESS_LINE_NUM;++ i) 1332 1333 { 1334 1335 for(int j = 0;j < CHESS_LINE_NUM; ++ j) 1336 1337 { 1338 1339 pCL = GetChessMaxSubLine(i, j, w_or_b, FALSE); 1340 1341 if(pCL == NULL) 1342 1343 continue; 1344 1345 if(pCL->length > MaxLength) 1346 1347 { 1348 1349 MaxLength = pCL->length; 1350 1351 MaxStartPos = pCL->startpos; 1352 1353 MaxEndPos = pCL->endpos; 1354 1355 } 1356 1357 DeleteCL(pCL); 1358 1359 } 1360 1361 } 1362 1363 } 1364 1365 INT GetMaxValCLAddr(ChessLine * parray, INT N) 1366 1367 { 1368 1369 if(parray == NULL && N <= 0) 1370 1371 return -1;//用來表示無效的數字 1372 1373 INT maxval = parray[0].length; 1374 1375 INT nrtnaddr = 0; 1376 1377 for(int i = 1;i < N;++ i) 1378 1379 { 1380 1381 if(maxval < parray[i].length) 1382 1383 { 1384 1385 maxval = parray[i].length; 1386 1387 nrtnaddr = i; 1388 1389 } 1390 1391 } 1392 1393 return nrtnaddr; 1394 1395 } 1396 1397 1398 1399 ChessLine * GetChessMaxSubLine(INT x, INT y, INT nColor, BOOL IfRtnVal) 1400 1401 { 1402 1403 INT CheckNum = 4; 1404 1405 POINT StartPos; 1406 1407 ChessLine JudgeLine[8]; 1408 1409 //判斷點是否合法 1410 1411 if(nColor != g_ChessTable[x][y].status) 1412 1413 return NULL;//放棄當前點 1414 1415 //當前點合法后,開始8個方向的遍歷 1416 1417 //初始化 1418 1419 StartPos.x = x; 1420 1421 StartPos.y = y; 1422 1423 //一旦這個點被選入,初始長度肯定至少是1 1424 1425 ChessLineInitial(JudgeLine, &StartPos, 8, nColor); 1426 1427 JudgeLine[0].endpos = StartPos; 1428 1429 //2方向 1430 1431 INT nRight = StartPos.x + 1; 1432 1433 while(nRight < CHESS_LINE_NUM && g_ChessTable[nRight][StartPos.y].status == nColor) 1434 1435 { 1436 1437 JudgeLine[0].length++; 1438 1439 nRight++;//向右查找 1440 1441 } 1442 1443 //保存對應的點 1444 1445 JudgeLine[0].endpos.x = nRight - 1; 1446 1447 JudgeLine[0].endpos.y = StartPos.y; 1448 1449 //檢測線兩端的情況,數據存儲在Effectivelevel中 1450 1451 //線左端方向的查找 1452 1453 if(JudgeLine[0].startpos.x - 1 >= 0)//邊界判斷的前提條件 1454 1455 { 1456 1457 if(g_ChessTable[JudgeLine[0].startpos.x - 1][JudgeLine[0].startpos.y].status == -1) 1458 1459 JudgeLine[0].EffectLevel ++; 1460 1461 else if(g_ChessTable[JudgeLine[0].startpos.x - 1][JudgeLine[0].startpos.y].status == nColor) 1462 1463 { 1464 1465 //線點存在重復的線將被拋棄 1466 1467 JudgeLine[0].length = 0;//這樣AddIntoBuf函數會自動拋棄該值 1468 1469 } 1470 1471 } 1472 1473 //線右端查找 1474 1475 if(JudgeLine[0].endpos.x + 1 < CHESS_LINE_NUM) 1476 1477 { 1478 1479 if(g_ChessTable[JudgeLine[0].endpos.x + 1][JudgeLine[0].endpos.y].status == -1) 1480 1481 JudgeLine[0].EffectLevel ++; 1482 1483 else if(g_ChessTable[JudgeLine[0].endpos.x + 1][JudgeLine[0].endpos.y].status == nColor) 1484 1485 { 1486 1487 JudgeLine[0].length = 0;//這樣AddIntoBuf函數會自動拋棄該值 1488 1489 } 1490 1491 } 1492 1493 if(JudgeLine[0].EffectLevel != 0) 1494 1495 AddIntoBuf(&JudgeLine[0], nColor); 1496 1497 1498 1499 //3方向 1500 1501 INT nRightDownOffset = 1;//右下方向的偏移地址 1502 1503 while(StartPos.x + nRightDownOffset < CHESS_LINE_NUM && \ 1504 1505 StartPos.y + nRightDownOffset < CHESS_LINE_NUM && \ 1506 1507 g_ChessTable[StartPos.x + nRightDownOffset][StartPos.y + nRightDownOffset].status == nColor) 1508 1509 { 1510 1511 JudgeLine[1].length++; 1512 1513 nRightDownOffset++; 1514 1515 } 1516 1517 //保存對應的點 1518 1519 JudgeLine[1].endpos.x = StartPos.x + nRightDownOffset - 1; 1520 1521 JudgeLine[1].endpos.y = StartPos.y + nRightDownOffset - 1; 1522 1523 //右下和左上方向查找 1524 1525 if(JudgeLine[1].startpos.x - 1 >= 0 && JudgeLine[1].startpos.y - 1 >= 0) 1526 1527 { 1528 1529 if(g_ChessTable[JudgeLine[1].startpos.x - 1][JudgeLine[1].startpos.y - 1].status == -1) 1530 1531 JudgeLine[1].EffectLevel ++; 1532 1533 else if(g_ChessTable[JudgeLine[1].startpos.x - 1][JudgeLine[1].startpos.y - 1].status == nColor) 1534 1535 { 1536 1537 JudgeLine[1].length = 0; 1538 1539 } 1540 1541 } 1542 1543 if(JudgeLine[1].startpos.x + 1 < CHESS_LINE_NUM && JudgeLine[1].startpos.y + 1 < CHESS_LINE_NUM) 1544 1545 { 1546 1547 if(g_ChessTable[JudgeLine[1].endpos.x + 1][JudgeLine[1].endpos.y + 1].status == -1) 1548 1549 JudgeLine[1].EffectLevel ++; 1550 1551 else if(g_ChessTable[JudgeLine[1].endpos.x + 1][JudgeLine[1].endpos.y + 1].status == nColor) 1552 1553 { 1554 1555 JudgeLine[1].length = 0; 1556 1557 } 1558 1559 } 1560 1561 if(JudgeLine[1].EffectLevel != 0) 1562 1563 AddIntoBuf(&JudgeLine[1], nColor); 1564 1565 1566 1567 //4方向 1568 1569 INT nDown = StartPos.y + 1; 1570 1571 while(nDown < CHESS_LINE_NUM && g_ChessTable[StartPos.x][nDown].status == nColor) 1572 1573 { 1574 1575 JudgeLine[2].length++; 1576 1577 nDown++;//向下查找 1578 1579 } 1580 1581 //保存對應的點 1582 1583 JudgeLine[2].endpos.x = StartPos.x; 1584 1585 JudgeLine[2].endpos.y = nDown - 1; 1586 1587 //上下兩個方向的查找 1588 1589 //上 - 1590 1591 if(JudgeLine[2].startpos.y - 1 >= 0) 1592 1593 { 1594 1595 if(g_ChessTable[JudgeLine[2].startpos.x][JudgeLine[2].startpos.y - 1].status == -1) 1596 1597 JudgeLine[2].EffectLevel ++; 1598 1599 else if(g_ChessTable[JudgeLine[2].startpos.x][JudgeLine[2].startpos.y - 1].status == nColor) 1600 1601 { 1602 1603 JudgeLine[2].length = 0; 1604 1605 } 1606 1607 } 1608 1609 //下 + 1610 1611 if(JudgeLine[2].endpos.y + 1 < CHESS_LINE_NUM) 1612 1613 { 1614 1615 if(g_ChessTable[JudgeLine[2].endpos.x][JudgeLine[2].endpos.y + 1].status == -1) 1616 1617 JudgeLine[2].EffectLevel ++; 1618 1619 else if(g_ChessTable[JudgeLine[2].endpos.x][JudgeLine[2].endpos.y + 1].status == nColor) 1620 1621 { 1622 1623 JudgeLine[2].length = 0; 1624 1625 } 1626 1627 } 1628 1629 if(JudgeLine[2].EffectLevel != 0) 1630 1631 AddIntoBuf(&JudgeLine[2], nColor); 1632 1633 1634 1635 //5方向 1636 1637 INT nLeftDownOffset = 1;//左下方向偏移地址,x -;y + 1638 1639 while(StartPos.x - nLeftDownOffset >= 0 && \ 1640 1641 StartPos.y + nLeftDownOffset < CHESS_LINE_NUM && \ 1642 1643 g_ChessTable[StartPos.x - nLeftDownOffset][StartPos.y + nLeftDownOffset].status == nColor) 1644 1645 { 1646 1647 JudgeLine[3].length++; 1648 1649 nLeftDownOffset++; 1650 1651 } 1652 1653 JudgeLine[3].endpos.x = StartPos.x - (nLeftDownOffset - 1);//為了邏輯清楚,就先這么寫了 1654 1655 JudgeLine[3].endpos.y = StartPos.y + nLeftDownOffset - 1; 1656 1657 //左下右上方向 1658 1659 //右上 1660 1661 if(JudgeLine[3].startpos.y - 1 >= 0 && JudgeLine[3].startpos.x + 1 < CHESS_LINE_NUM) 1662 1663 { 1664 1665 if(g_ChessTable[JudgeLine[3].startpos.x + 1][JudgeLine[3].startpos.y - 1].status == -1) 1666 1667 JudgeLine[3].EffectLevel ++; 1668 1669 else if(g_ChessTable[JudgeLine[3].startpos.x + 1][JudgeLine[3].startpos.y - 1].status == nColor) 1670 1671 { 1672 1673 JudgeLine[3].length = 0; 1674 1675 } 1676 1677 } 1678 1679 //左下 1680 1681 if(JudgeLine[3].endpos.y + 1 < CHESS_LINE_NUM && JudgeLine[3].endpos.x - 1 >= 0) 1682 1683 { 1684 1685 if(g_ChessTable[JudgeLine[3].endpos.x - 1][JudgeLine[3].endpos.y + 1].status == -1) 1686 1687 JudgeLine[3].EffectLevel ++; 1688 1689 else if(g_ChessTable[JudgeLine[3].endpos.x - 1][JudgeLine[3].endpos.y + 1].status == nColor) 1690 1691 { 1692 1693 JudgeLine[3].length = 0; 1694 1695 } 1696 1697 } 1698 1699 if(JudgeLine[3].EffectLevel != 0) 1700 1701 AddIntoBuf(&JudgeLine[3], nColor); 1702 1703 //這段代碼算是暫時廢棄的 1704 1705 if(IfRtnVal) 1706 1707 { 1708 1709 ChessLine * pFinalLine = new ChessLine; 1710 1711 if(pFinalLine == NULL) 1712 1713 return NULL; 1714 1715 INT MaxLengthAddr = GetMaxValCLAddr(JudgeLine, CheckNum); 1716 1717 if(MaxLengthAddr == -1) 1718 1719 { 1720 1721 delete pFinalLine; 1722 1723 return NULL; 1724 1725 } 1726 1727 *pFinalLine = JudgeLine[MaxLengthAddr]; 1728 1729 return pFinalLine; 1730 1731 } 1732 1733 return NULL; 1734 1735 } 1736 1737 void AddIntoBuf(ChessLine * pcl,INT w_or_b) 1738 1739 { 1740 1741 switch(w_or_b) 1742 1743 { 1744 1745 case 0://白色 1746 1747 if(pcl->length > 1) 1748 1749 w_ChessLineBuffer.push_back(*pcl); 1750 1751 break; 1752 1753 case 1: 1754 1755 if(pcl->length > 1) 1756 1757 b_ChessLineBuffer.push_back(*pcl); 1758 1759 break; 1760 1761 } 1762 1763 } 1764 1765 void ChessLineInitial(ChessLine * pcl, POINT * pstartpos, INT n, INT nColor) 1766 1767 { 1768 1769 if(pcl == NULL || pstartpos == NULL) 1770 1771 return; 1772 1773 for(int i = 0;i < n;++ i) 1774 1775 { 1776 1777 pcl[i].length = 1; 1778 1779 pcl[i].startpos = *pstartpos; 1780 1781 pcl[i].ChessType = nColor; 1782 1783 pcl[i].EffectLevel = 0;//最低級 1784 1785 } 1786 1787 } 1788 1789 void DeleteCL(ChessLine * pcl) 1790 1791 { 1792 1793 delete pcl; 1794 1795 } 1796 1797 1798 1799 void DrawVecInfo(HDC hdc, std::vector<ChessLine> * pvcl) 1800 1801 { 1802 1803 TCHAR wcBuf[100]; 1804 1805 TCHAR tmpbuf[100]; 1806 1807 POINT tmppoint = GetAIPoint(); 1808 1809 INT num_w = pvcl->size(); 1810 1811 assert(num_w >= 0); 1812 1813 INT num_b = (pvcl + 1)->size(); 1814 1815 assert(num_b >= 0); 1816 1817 wsprintf(tmpbuf, _T("SP x:%d y:%d;EP x:%d y:%d;Len:%d;EL:%d;HL:%d"), 1818 1819 BestLine.startpos.x, 1820 1821 BestLine.startpos.y, 1822 1823 BestLine.endpos.x, 1824 1825 BestLine.endpos.y, 1826 1827 BestLine.length, 1828 1829 BestLine.EffectLevel, 1830 1831 BestLine.length + BestLine.EffectLevel 1832 1833 ); 1834 1835 TextOut(hdc, 0, 0, tmpbuf, wcslen(tmpbuf)); 1836 1837 wsprintf(tmpbuf, _T("AI x:%d y:%d"), tmppoint.x, tmppoint.y); 1838 1839 TextOut(hdc, 900, 0, tmpbuf, wcslen(tmpbuf)); 1840 1841 for(int i = 0;i < num_w;++ i) 1842 1843 { 1844 1845 wsprintf(wcBuf, _T("SP x:%d y:%d;EP x:%d y:%d;Len:%d;EL:%d;HL:%d"), 1846 1847 pvcl[0][i].startpos.x, pvcl[0][i].startpos.y, 1848 1849 pvcl[0][i].endpos.x, pvcl[0][i].endpos.y, 1850 1851 pvcl[0][i].length, 1852 1853 pvcl[0][i].EffectLevel, 1854 1855 pvcl[0][i].length + pvcl[0][i].EffectLevel); 1856 1857 TextOut(hdc, 0, (i+1) * 15, wcBuf, wcslen(wcBuf)); 1858 1859 } 1860 1861 for(int i = 0;i < num_b;++ i) 1862 1863 { 1864 1865 wsprintf(wcBuf, _T("SP x:%d y:%d;EP x:%d y:%d;Len:%d;EL:%d;HL:%d"), 1866 1867 pvcl[1][i].startpos.x, pvcl[1][i].startpos.y, 1868 1869 pvcl[1][i].endpos.x, pvcl[1][i].endpos.y, 1870 1871 pvcl[1][i].length, 1872 1873 pvcl[1][i].EffectLevel, 1874 1875 pvcl[1][i].length + pvcl[1][i].EffectLevel); 1876 1877 TextOut(hdc, 900, (i+1) * 15, wcBuf, wcslen(wcBuf)); 1878 1879 } 1880 1881 } 1882 1883 1884 1885 ChessLine * GetBestLine(INT nColor) 1886 1887 { 1888 1889 ChessLine * pcl = new ChessLine; 1890 1891 if(pcl == NULL) 1892 1893 return NULL; 1894 1895 std::vector<ChessLine> * pvcl; 1896 1897 if(nColor == 0) 1898 1899 pvcl = &w_ChessLineBuffer; 1900 1901 else 1902 1903 pvcl = &b_ChessLineBuffer; 1904 1905 INT nsize = pvcl->size(); 1906 1907 if(nsize == 0) 1908 1909 return NULL; 1910 1911 //刪除沒用的線 1912 1913 //線還是先不刪了,擅自修改vector的大小會引發大量的越界問題 1914 1915 /* 1916 1917 std::vector<ChessLine>::iterator pvcl_itstart = pvcl->begin(); 1918 1919 std::vector<ChessLine>::iterator pvcl_itend = pvcl->end(); 1920 1921 for(int i = 0;i < nsize;) 1922 1923 { 1924 1925 if(pvcl_itstart[i].EffectLevel == 0) 1926 1927 { 1928 1929 pvcl->erase(pvcl_itstart + i); 1930 1931 nsize --; 1932 1933 continue; 1934 1935 } 1936 1937 i++; 1938 1939 }*/ 1940 1941 1942 1943 //然后使用優先級判斷公式 length + EffectLevel 1944 1945 //先獲取最大值 1946 1947 INT num_cl = pvcl->size(); 1948 1949 if(num_cl == 0) 1950 1951 return NULL; 1952 1953 INT nMax = 1; 1954 1955 INT nMaxAddr = 0; 1956 1957 for(int i = 0;i < num_cl;++ i) 1958 1959 { 1960 1961 if((*pvcl)[i].EffectLevel + (*pvcl)[i].length > nMax && (*pvcl)[i].EffectLevel != 0) 1962 1963 { 1964 1965 nMax = (*pvcl)[i].EffectLevel + (*pvcl)[i].length; 1966 1967 nMaxAddr = i; 1968 1969 } 1970 1971 } 1972 1973 1974 1975 *pcl = (*pvcl)[nMaxAddr]; 1976 1977 return pcl; 1978 1979 } 1980 1981 1982 1983 POINT GetAIPoint()//根據GetBestLine返回的黑白兩棋子線情況來判斷棋子的位置 1984 1985 { 1986 1987 //先獲取全部的線。 1988 1989 GetALLLine(0); 1990 1991 GetALLLine(1); 1992 1993 //這里曾造成內存泄露,原因是返回路徑會切斷刪除函數的調用 1994 1995 ChessLine * pw_cl = GetBestLine(0);//白子 人方 1996 1997 ChessLine * pb_cl = GetBestLine(1);//黑子 AI 1998 1999 ChessLine * pfinal_cl; 2000 2001 POINT rtnpos = {-1, -1}; 2002 2003 if(pw_cl != NULL && pb_cl != NULL) 2004 2005 { 2006 2007 //防守優先 2008 2009 if(pw_cl->EffectLevel + pw_cl->length >= pb_cl->EffectLevel + pb_cl->length) 2010 2011 pfinal_cl = pw_cl; 2012 2013 else 2014 2015 pfinal_cl = pb_cl; 2016 2017 } 2018 2019 else if(pw_cl == NULL && pb_cl != NULL) 2020 2021 pfinal_cl = pb_cl; 2022 2023 else if(pb_cl == NULL && pw_cl != NULL) 2024 2025 pfinal_cl = pw_cl; 2026 2027 else //在上面的兩個ChessLine都獲取不到的時候,需要做的是,嘗試去獲取一個單獨的點。 2028 2029 { 2030 2031 POINT SingleFinalPoint = GetSinglePoint(); 2032 2033 return SingleFinalPoint; 2034 2035 } 2036 2037 //這個是測試用數據,全局變量 2038 2039 BestLine = *pfinal_cl; 2040 2041 switch(GetValidSEDirection(pfinal_cl->startpos, pfinal_cl->endpos)) 2042 2043 { 2044 2045 case 0://2-6 2046 2047 //2 2048 2049 if(g_ChessTable[pfinal_cl->startpos.x - 1][pfinal_cl->startpos.y].status == -1 2050 2051 && pfinal_cl->startpos.x - 1 >= 0) 2052 2053 { 2054 2055 rtnpos.x = pfinal_cl->startpos.x - 1; 2056 2057 rtnpos.y = pfinal_cl->startpos.y; 2058 2059 } 2060 2061 else if(pfinal_cl->endpos.x + 1 < CHESS_LINE_NUM) 2062 2063 { 2064 2065 rtnpos.x = pfinal_cl->endpos.x + 1; 2066 2067 rtnpos.y = pfinal_cl->endpos.y; 2068 2069 } 2070 2071 break; 2072 2073 case 1://3-7 2074 2075 if(g_ChessTable[pfinal_cl->startpos.x - 1][pfinal_cl->startpos.y - 1].status == -1) 2076 2077 { 2078 2079 rtnpos.x = pfinal_cl->startpos.x - 1; 2080 2081 rtnpos.y = pfinal_cl->startpos.y - 1; 2082 2083 } 2084 2085 else 2086 2087 { 2088 2089 rtnpos.x = pfinal_cl->endpos.x + 1; 2090 2091 rtnpos.y = pfinal_cl->endpos.y + 1; 2092 2093 } 2094 2095 //return rtnpos; 2096 2097 break; 2098 2099 case 2://4-0 2100 2101 if(g_ChessTable[pfinal_cl->startpos.x][pfinal_cl->startpos.y - 1].status == -1 2102 2103 && pfinal_cl->startpos.y - 1>= 0 2104 2105 && pfinal_cl->endpos.y + 1 < CHESS_LINE_NUM) 2106 2107 { 2108 2109 rtnpos.x = pfinal_cl->startpos.x; 2110 2111 rtnpos.y = pfinal_cl->startpos.y - 1; 2112 2113 } 2114 2115 else 2116 2117 { 2118 2119 rtnpos.x = pfinal_cl->endpos.x; 2120 2121 rtnpos.y = pfinal_cl->endpos.y + 1; 2122 2123 } 2124 2125 //return rtnpos; 2126 2127 break; 2128 2129 case 3://5-1 2130 2131 if(g_ChessTable[pfinal_cl->startpos.x + 1][pfinal_cl->startpos.y - 1].status == -1 2132 2133 && pfinal_cl->startpos.x + 1 < CHESS_LINE_NUM 2134 2135 && pfinal_cl->startpos.y - 1 >= 0) 2136 2137 { 2138 2139 rtnpos.x = pfinal_cl->startpos.x + 1; 2140 2141 rtnpos.y = pfinal_cl->startpos.y - 1; 2142 2143 } 2144 2145 else 2146 2147 { 2148 2149 rtnpos.x = pfinal_cl->endpos.x - 1; 2150 2151 rtnpos.y = pfinal_cl->endpos.y + 1; 2152 2153 } 2154 2155 //return rtnpos; 2156 2157 break; 2158 2159 } 2160 2161 DeleteCL(pw_cl); 2162 2163 DeleteCL(pb_cl); 2164 2165 return rtnpos; 2166 2167 } 2168 2169 2170 2171 INT GetValidSEDirection(POINT SP, POINT EP)//獲取有效的方向,返回值 0,1,2,3分別對應2-6, 3-7, 4-0,5-1, 2172 2173 { 2174 2175 //終點減去起始點 2176 2177 INT ndirx = EP.x - SP.x; 2178 2179 INT ndiry = EP.y - SP.y; 2180 2181 /* 2182 2183 7(-1,1) 0(0,1) 1(1,1) 2184 2185 6(-1,0) 2(1,0) 2186 2187 5(-1,-1)4(0,-1) 3(1,-1) 2188 2189 */ 2190 2191 if(ndirx > 0) 2192 2193 { 2194 2195 if(ndiry == 0)//2(1,0) 2196 2197 return 0; 2198 2199 else//3(1,-1) 2200 2201 return 1; 2202 2203 } 2204 2205 else if(ndirx == 0) 2206 2207 return 2; 2208 2209 else 2210 2211 return 3; 2212 2213 } 2214 2215 POINT GetSinglePoint() 2216 2217 { 2218 2219 //所謂singlepoint,就是8個相鄰點中沒有任何一點是同色點。 2220 2221 //函數返回值為從0-7中的有效點中的一個隨機點 2222 2223 INT npos; 2224 2225 POINT rtnpoint = {-1, -1}; 2226 2227 for(int i = 0;i < CHESS_LINE_NUM;++ i) 2228 2229 { 2230 2231 for(int j = 0;j < CHESS_LINE_NUM;++ j) 2232 2233 { 2234 2235 if(g_ChessTable[i][j].status != -1) 2236 2237 { 2238 2239 npos = IsValidSinglePoint(i, j); 2240 2241 if(npos == -1) 2242 2243 continue; 2244 2245 switch(npos) 2246 2247 { 2248 2249 //這里的代碼直接return,就不用再break了 2250 2251 case 0: 2252 2253 rtnpoint.x = i - 1; 2254 2255 rtnpoint.y = j - 1; 2256 2257 break; 2258 2259 case 1: 2260 2261 rtnpoint.x = i; 2262 2263 rtnpoint.y = j - 1; 2264 2265 break; 2266 2267 case 2: 2268 2269 rtnpoint.x = i + 1; 2270 2271 rtnpoint.y = j - 1; 2272 2273 break; 2274 2275 case 3: 2276 2277 rtnpoint.x = i - 1; 2278 2279 rtnpoint.y = j; 2280 2281 break; 2282 2283 case 4: 2284 2285 rtnpoint.x = i + 1; 2286 2287 rtnpoint.y = j; 2288 2289 break; 2290 2291 case 5: 2292 2293 rtnpoint.x = i - 1; 2294 2295 rtnpoint.y = j + 1; 2296 2297 break; 2298 2299 case 6: 2300 2301 rtnpoint.x = i; 2302 2303 rtnpoint.y = j + 1; 2304 2305 break; 2306 2307 case 7: 2308 2309 rtnpoint.x = i + 1; 2310 2311 rtnpoint.y = j + 1; 2312 2313 break; 2314 2315 } 2316 2317 return rtnpoint; 2318 2319 } 2320 2321 } 2322 2323 } 2324 2325 return rtnpoint; 2326 2327 } 2328 2329 2330 2331 INT IsValidSinglePoint(int x, int y) 2332 2333 { 2334 2335 assert(x >= 0 && y >=0 && x < CHESS_LINE_NUM && y < CHESS_LINE_NUM); 2336 2337 char checkflag[8] = {0};//純標記位 2338 2339 if(x - 1 >= 0)//一次查三個點 2340 2341 { 2342 2343 if(y - 1 >= 0) 2344 2345 { 2346 2347 if(g_ChessTable[x - 1][y - 1].status == -1) 2348 2349 checkflag[0] = 1; 2350 2351 } 2352 2353 if(g_ChessTable[x - 1][y].status == -1) 2354 2355 checkflag[3] = 1; 2356 2357 if(y + 1 < CHESS_LINE_NUM) 2358 2359 { 2360 2361 if(g_ChessTable[x - 1][y + 1].status == -1) 2362 2363 checkflag[5] = 1; 2364 2365 } 2366 2367 } 2368 2369 if(y - 1 >= 0 && g_ChessTable[x][y - 1].status == -1) 2370 2371 checkflag[1] = 1; 2372 2373 if(y + 1 < CHESS_LINE_NUM && g_ChessTable[x][y + 1].status == -1) 2374 2375 2376 2377 { 2378 2379 checkflag[6] = 1; 2380 2381 } 2382 2383 if(x + 1 < CHESS_LINE_NUM) 2384 2385 { 2386 2387 if(g_ChessTable[x + 1][y].status == -1) 2388 2389 checkflag[4] = 1; 2390 2391 if(y + 1 < CHESS_LINE_NUM) 2392 2393 { 2394 2395 if(g_ChessTable[x + 1][y + 1].status == -1) 2396 2397 checkflag[7] = 1; 2398 2399 } 2400 2401 if(y - 1 >= 0) 2402 2403 { 2404 2405 if(g_ChessTable[x + 1][y - 1].status == -1) 2406 2407 checkflag[2] = 1; 2408 2409 } 2410 2411 } 2412 2413 /*調試部分 2414 2415 INT nrtn = 0; 2416 2417 for(int i = 0;i < 8;++ i) 2418 2419 { 2420 2421 if(checkflag[i] == 1) 2422 2423 { 2424 2425 nrtn |= 1 << (i * 4); 2426 2427 } 2428 2429 }*/ 2430 2431 INT nCounterofValidPoint = 0; 2432 2433 for(int i = 0;i < 8;++ i) 2434 2435 { 2436 2437 if(checkflag[i] == 1) 2438 2439 nCounterofValidPoint ++; 2440 2441 } 2442 2443 if(!nCounterofValidPoint) 2444 2445 return -1; 2446 2447 srand(time(0)); 2448 2449 INT nUpSection = rand() % 8;//在這倒是正好能用作地址上限 2450 2451 INT UpSearch = nUpSection; 2452 2453 INT DownSearch = nUpSection; 2454 2455 for(;UpSearch < 8 || DownSearch >= 0;) 2456 2457 { 2458 2459 if(UpSearch < 8) 2460 2461 { 2462 2463 if(checkflag[UpSearch] == 1) 2464 2465 return UpSearch; 2466 2467 else 2468 2469 UpSearch ++; 2470 2471 } 2472 2473 if(DownSearch >= 0) 2474 2475 { 2476 2477 if(checkflag[DownSearch] == 1) 2478 2479 return DownSearch; 2480 2481 else 2482 2483 DownSearch --; 2484 2485 } 2486 2487 } 2488 2489 }