首先说明整个五子棋程序的整体布局。(用Java实现)
class Chess{ //界面类 Player player1 ; Player player2; ChessBox box; //其余界面显示相关函数; } class Player{ 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个方向可以连子,每个方向上又有多种连子棋型,如活四、活三、死三等,而所连的子又可能属于己方或者对方。活四与活三的权值自然不同。而同样是活三,己方的活三与对方的活三权值也不同,这样才能实现攻守的策略。假如现在棋局上同时有己方的活三和对方的活三,此时轮到我方落子,则正常情况下应当在己方活三上落子,使之成为活四,从而获胜。则计算机在判断棋局时,遇到己方活三,权值应当较高,遇到对方活三,权值应当较低。
以上即是对于估值函数所应达到的要求的分析。
由于着眼处在于对棋型的判断,而不是方向,所以对于各个方向应当能够进行比较统一的处理,所以在判断棋子的相对位置时,应当与方向无关,而只与相互之间的顺序有关。若取某一行(列、斜列),假设当前点的坐标为0,取右(下、右下、右上)为正方向,则在该行(列、斜列)上各点都能得到相应的坐标。如下图。
由此,只要三个元素即可确定棋盘上任意一点,即当前点、方向、相对坐标值。进而可以获得任意一点的落子情况。函数即为
int getLine(Point p,int i,int j);
其中p即为当前点,i为方向,取值为从1到8的整数,j为相对于p点的坐标值。在函数体内要依据方向对x、y的值进行处理。返回值为该点的落子情况,0表示无子,1或2分别表示两个player,-1表示超出棋盘界。
代码如下:
1 int getLine(Point p, int i, int j) { // p:当前点 i:方向 j:坐标相对值 2 int x = p.x, y = p.y; 3 switch (i) { 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 }
对于方向的处理完成后,就是棋型的判断。
结合已有的算法结构,参考下图
将棋型分为以下几种:
/* *: 当前空位置; 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 }
而 int Evaluate(Point p) 函数则只要调用 int evaluate(Point p, int me,int plyer) 函数就可以获得p点的权值。
代码如下:
1 int Evaluate(Point p){ 2 return evaluate(p, 1,1)+ evaluate(p, 1,2); 3 }
最终程序核心算法只运用该估值算法,没有进行深度搜索。界面如下:
可见估值算法即便非常完美(当然这个算法离完美还差得远 ̄□ ̄||),依然无法做到立于不败之地,因为往往会出现对方有多个接近连五,以至于堵都堵不住。所以博弈还是必须要深度搜索的。
最后贴出自己写的估值算法完整的代码(仅供参考,正确性未经严格验证):
1 int Evaluate(Point p){ 2 return evaluate(p, 1,1) 3 + evaluate(p, 1,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