目錄 |
程序佈局 |
估值算法 |
完整代碼 |
首先說明整個五子棋程序的總體佈局。(用Java實現)算法
class Chess{ //界面類 Player player1 ; Player player2; ChessBox box; //其他界面顯示相關函數; } class Player{
int code; //代號 1:選手1 2:選手2
ChessBox box;
abstract Point play(); //落子操做
int getLine(Point p, int i, int j) ;
}
class Person extends Player{
Point play(int x,int y );
}
class Robot extends Player{ //機器 int evaluate(Point, int, int); int Evaluate(Point); Point play(); } class ChessBox{ int chess_flag[15][15] //0:空 1:選手1 2:選手2 }
估值算法。要求給定棋盤上一個點,求出該點在當前棋局下的權值。若在該點落子後更容易接近勝利,則該點權值就高,越接近5子相連,權值越高。函數
則函數的形式爲 int Evaluate(Point p); 佈局
首先考慮每一個點有8個方向能夠連子,每一個方向上又有多種連子棋型,如活4、活3、死三等,而這些子又可能屬於己方或者對方。活四與活三的權值天然不一樣。而一樣是活三,己方的活三與對方的活三權值也不一樣,這樣才能實現攻守的策略。假如如今棋局上同時有己方的活三和對方的活三,此時輪到我方落子,則正常狀況下應當在己方活三上落子,使之成爲活四,從而獲勝。則計算機在判斷棋局時,遇到己方活三,權值應當較高,遇到對方活三,權值應當較低。優化
以上便是對於估值函數所應達到的要求的分析。lua
因爲着眼處在於對棋型的判斷,而不是方向,因此首先應該想個方法把方向問題先解決掉,這樣在棋型判斷時就可以對各個方向進行比較統一的處理,不至於棋型判斷時對每一個方向都寫一段代碼。spa
繼續分析,在判斷棋型時,着眼點在於棋子的相對位置,而常見棋型都呈線形排列,因此這個相對位置也就是順序。相對位置、順序,很容易想到要用一維的座標解決。若取某一斜列(行、列),假設當前點的座標爲0,取右下(下、右、右上)爲正方向,則在該斜列(行、列)上各點都能獲得相應的座標。以下圖。code
但如果一樣的一維座標,不一樣的方向,又會對應棋盤上不一樣的位置,也就是說,一維座標轉換到棋盤上的二維座標,還須要一個方向。(額,想到這裏,忽然發現本身的思路明明就是極座標啊。。。 ̄□ ̄||.........)orm
由此,咱們須要達到這麼一種要求:給定一個點、一個方向、一個相對座標值,就能獲得一個二維座標,對應棋盤上一個點,進而能夠得到任意一點的落子狀況。因此我寫了這麼一個函數:blog
int getLine(Point p,int i,int j);
其中p爲當前點,i爲方向,取值爲從1到8的整數,對應8個方向,j爲相對於p點的座標值。在函數體內要依據方向對p的x、y的值進行處理。返回該點的落子狀況,0表示無子,1或2分別表示兩個player,-1表示超出棋盤界。get
代碼以下:
1 int getLine(Point p, int i, int j) { // p:當前點 i:方向 j:座標相對值 2 int x = p.x, y = p.y; 3 switch (i) { //對8個方向的處理 4 case 1 : 5 x = x + j; 6 break; 7 case 2 : 8 x = x + j; 9 y = y + j; 10 break; 11 ... 12 ... 13 case 8 : 14 x = x + j; 15 y = y - j; 16 } 17 if (x < 0 || y < 0 || x > 14 || y > 14) { // 越界處理 返回-1 18 return -1; 19 } 20 return box.getFlag(x,y); 21 } 22 }
對於方向的處理完成後,就是棋型的判斷。判斷棋型時須要區分當前所判斷的棋型是哪一方的,假設當前所判斷的棋型所屬方的代號爲plyer,則它的值能夠是1或2,而要肯定這個plyer是本身仍是對方,就須要和本身的代號比對一下,假設本身的代號是me。則這個判斷棋型的函數應該知足如下要求:給出一個點p,本身的代號me,一個plyer,能得出當前點對應plyer的權值。因而函數形式以下:
int evaluate(Point p, int me,int plyer);
而後結合已有的算法結構,參考下圖(網上找到的)
將棋型分爲如下幾種:
/* *: 當前空位置; 0: 其餘空位置; 1: plyer(當前所計算的player的代號); 2: 3-plyer(對方的代號); */ 1.活四 :01111* 2.死四A :21111* 3.死四B :111*1 4.死四C :11*11 5.活三(近三位置) :111*0 6.活三(遠三位置) :1110* 7.死三 :11*1
此外因爲兩個或多個方向上都有活二的棋型較爲常見且勝率較高(見下圖)。因此又增長對此種棋型的判斷。
即在每個方向的棋型判斷中掃描011*0或111*0並計數,若最終計數值大於等於2,則權值增長一個較大的數值,不然不增長。
至此只要循環8次,每次循環中掃描各個棋型,並更新權值(設爲value)便可。
代碼以下:
1 int evaluate(Point p, int me,int plyer) { /* me:個人代號; plyer:當前計算的player的代號;*/ 2 int value = 0; 3 int numoftwo=0; 4 for (int i = 1; i <= 8; i++) { // 8個方向 5 // 活四 01111* *表明當前空位置 0表明其餘空位置 6 if (getLine(p, i, -1) == plyer && getLine(p, i, -2) == plyer 7 && getLine(p, i, -3) == plyer && getLine(p, i, -4) == plyer 8 && getLine(p, i, -5) == 0) { 9 value += 300000; 10 if(me!=plyer){value-=500;} 11 System.out.print("+ 300000"); 12 continue; 13 } 14 ... 15 //計算011*0或111*0的個數 16 if (getLine(p, i, -1) == plyer && getLine(p, i, -2) == plyer 17 && getLine(p, i, -3) != 3-plyer&&getLine(p,i,1)!=3-plyer) { 18 numoftwo++; 19 } 20 ... 21 } 22 if(numoftwo>=2){ 23 value+=3000; 24 if(me!=plyer){ 25 value-=100; 26 } 27 } 28 return value; 29 }
其中每種棋型對value值所作的貢獻要依據實際狀況不斷調整優化,優化不當就可能形成計算機放着活三不堵跑去堵活二了。。。
最終的估值函數 int Evaluate(Point p) 只要調用 int evaluate(Point p, int me,int plyer) 函數就能夠得到p點的權值。
代碼以下:
1 int Evaluate(Point p){ 2 return evaluate(p,code,1)+ evaluate(p,code,2); //code是調用者的代號 3 }
最終程序核心算法只運用該估值算法,沒有進行深度搜索。界面以下:
可見估值算法即使很是完美(固然這個算法離完美還差得遠 ̄□ ̄||),依然沒法作到立於不敗之地,由於每每會出現對方有多個接近連五,以致於堵都堵不住。因此博弈仍是必需要深度搜索的。
最後貼出本身寫的估值算法完整的代碼(僅供參考,正確性未經嚴格驗證):
1 int Evaluate(Point p){ 2 return evaluate(p, code,1) 3 + evaluate(p, code,2); 4 } 5 6 int evaluate(Point p, int me,int plyer) { // me:個人代號 plyer:當前計算的player的代號 7 int value = 0; 8 int numoftwo=0; 9 for (int i = 1; i <= 8; i++) { // 8個方向 10 // 活四 01111* *表明當前空位置 0表明其餘空位置 下同 11 if (getLine(p, i, -1) == plyer && getLine(p, i, -2) == plyer 12 && getLine(p, i, -3) == plyer && getLine(p, i, -4) == plyer 13 && getLine(p, i, -5) == 0) { 14 value += 300000; 15 if(me!=plyer){value-=500;} 16 continue; 17 } 18 // 死四A 21111* 19 if (getLine(p, i, -1) == plyer && getLine(p, i, -2) == plyer 20 && getLine(p, i, -3) == plyer && getLine(p, i, -4) == plyer 21 && (getLine(p, i, -5) == 3 - plyer||getLine(p, i, -5) == -1)) { 22 value += 250000; 23 if(me!=plyer){value-=500;} 24 continue; 25 } 26 // 死四B 111*1 27 if (getLine(p, i, -1) == plyer && getLine(p, i, -2) == plyer 28 && getLine(p, i, -3) == plyer && getLine(p, i, 1) == plyer) { 29 value += 240000; 30 if(me!=plyer){value-=500;} 31 continue; 32 } 33 // 死四C 11*11 34 if (getLine(p, i, -1) == plyer && getLine(p, i, -2) == plyer 35 && getLine(p, i, 1) == plyer && getLine(p, i, 2) == plyer) { 36 value += 230000; 37 if(me!=plyer){value-=500;} 38 continue; 39 } 40 // 活三 近3位置 111*0 41 if (getLine(p, i, -1) == plyer && getLine(p, i, -2) == plyer 42 && getLine(p, i, -3) == plyer) { 43 if (getLine(p, i, 1) == 0) { 44 value += 750; 45 if (getLine(p, i, -4) == 0) { 46 value += 3150; 47 if(me!=plyer){value-=300;} 48 } 49 } 50 if ((getLine(p, i, 1) == 3 - plyer||getLine(p, i, 1) == -1) && getLine(p, i, -4) == 0) { 51 value += 500; 52 } 53 continue; 54 } 55 // 活三 遠3位置 1110* 56 if (getLine(p, i, -1) == 0 && getLine(p, i, -2) == plyer 57 && getLine(p, i, -3) == plyer && getLine(p, i, -4) == plyer) { 58 value += 350; 59 continue; 60 } 61 // 死三 11*1 62 if (getLine(p, i, -1) == plyer && getLine(p, i, -2) == plyer 63 && getLine(p, i, 1) == plyer) { 64 value += 600; 65 if (getLine(p, i, -3) == 0 && getLine(p, i, 2) == 0) { 66 value += 3150; 67 continue; 68 } 69 if ((getLine(p, i, -3) == 3 - plyer||getLine(p, i, -3) == -1) && (getLine(p, i, 2) == 3 - plyer||getLine(p, i, 2) == -1)) { 70 continue; 71 } else { 72 value += 700; 73 continue; 74 } 75 } 76 //活二的個數 77 if (getLine(p, i, -1) == plyer && getLine(p, i, -2) == plyer 78 && getLine(p, i, -3) != 3-plyer&&getLine(p,i,1)!=3-plyer) { 79 numoftwo++; 80 } 81 //其他散棋 82 int numOfplyer = 0; // 由於方向會算兩次? 83 for (int k = -4; k <= 0; k++) { // ++++* +++*+ ++*++ +*+++ *++++ 84 int temp = 0; 85 for (int l = 0; l <= 4; l++) { 86 if (getLine(p, i, k + l) == plyer) { 87 temp++; 88 } else 89 if (getLine(p, i, k + l) == 3 - plyer 90 || getLine(p, i, k + l) == -1) { 91 temp = 0; 92 break; 93 } 94 } 95 numOfplyer += temp; 96 } 97 value += numOfplyer * 15; 98 if (numOfplyer != 0) { 99 } 100 } 101 if(numoftwo>=2){ 102 value+=3000; 103 if(me!=plyer){ 104 value-=100; 105 } 106 } 107 return value; 108 } 109 110 int getLine(Point p, int i, int j) { // i:方向 j:相對p的順序值(以p爲0) p:當前點 111 int x = p.x, y = p.y; 112 switch (i) { 113 case 1 : 114 x = x + j; 115 break; 116 case 2 : 117 x = x + j; 118 y = y + j; 119 break; 120 case 3 : 121 y = y + j; 122 break; 123 case 4 : 124 x = x - j; 125 y = y + j; 126 break; 127 case 5 : 128 x = x - j; 129 break; 130 case 6 : 131 x = x - j; 132 y = y - j; 133 break; 134 case 7 : 135 y = y - j; 136 break; 137 case 8 : 138 x = x + j; 139 y = y - j; 140 } 141 if (x < 0 || y < 0 || x > 14 || y > 14) { // 越界處理 142 return -1; 143 } 144 return box.getFlag(x,y); 145 }
2015.9.21 10:53