五子棋估值算法

  首先说明整个五子棋程序的整体布局。(用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

时间: 2024-10-27 13:33:40

五子棋估值算法的相关文章

五子棋AI算法

之前说想写一些比较大型的算法,想了半天,还是觉得写五子棋的AI比较合适.一则因为自己研究过这个,有一些基础,二则尽管现在网上有很多五子棋AI算法的资料,但是确实都有些晦涩难懂.就想着借这个机会,凭自己的理解,尽量的讲通俗一些.然而,这个算法确实有些复杂,想要通俗一些需要较大的篇幅,一篇博客难以讲完,这里就分很多个章节,一点一点的深入探讨.为了让文章更加通俗一些,我会略去一部分很简单但是占用篇幅很长的代码,改为用几行注释说明. 框架的搭建 首先,我们计划是做一个五子棋AI,也就是说让玩家和这个AI

五子棋AI算法第二篇-极大极小值搜索算法

AI实现的基本思路-极大极小值搜索算法 五子棋看起来有各种各样的走法,而实际上把每一步的走法展开,就是一颗巨大的博弈树.在这个树中,从根节点为0开始,奇数层表示电脑可能的走法,偶数层表示玩家可能的走法. 假设电脑先手,那么第一层就是电脑的所有可能的走法,第二层就是玩家的所有可能走法,以此类推. 我们假设平均每一步有50种可能的走法,那么从根节点开始,往下面每一层的节点数量是上一层的 50被,假设我们进行4层思考,也就是电脑和玩家各走两步,那么这颗博弈树的最后一层的节点数为 50^4 = 625W

五子棋AI算法-算杀

关于剪枝问题 前面讲到的通过Alpha-Beta剪枝和启发式搜索可以将4层搜索的平均时间降低到1秒以下.只有这两个优化方式其实目前最多可以做到6层搜索,就是把AI和玩家各向后推算三步. 6层搜索的棋力其实相当弱,碰到经常玩五子棋的人基本都会输,更不要说对五子棋有研究的玩家.以目前的平均一个节点有50个子节点的搜索方式,把搜索效率提高50倍则可以增加一层搜索深度.而除了前面讲到的AlphaBeta剪枝和启发式搜索,其他的剪枝算法基本都是非安全剪枝.也就是说后序我们会使用一些非安全的剪枝算法来提升搜

五子棋AI算法-Zobrist

这个博客不是把五子棋算法研究透彻之后再写的,而是一边研究算法一边写代码,同时一边写博客,所以有些博文的顺序不太对,比如 Zobrist 其实应该放在算杀之前就讲的.不过这并没有大的影响,总体上的顺序是OK的. 另外,这一系列博客讲的五子棋代码其实是一个开源的项目,源码地址:https://github.com/lihongxun945/gobang 由于是边写代码边写博客,所以博客中的代码不是最新的,甚至是有bug的,所以源码请尽量参考上述开源项目中的代码.比如之前讲极大极小值搜索改为负极大值的

五子棋AI算法第三篇-Alpha Beta剪枝

剪枝是必须的 上一篇讲了极大极小值搜索,其实单纯的极大极小值搜索算法并没有实际意义. 可以做一个简单的计算,平均一步考虑 50 种可能性的话,思考到第四层,那么搜索的节点数就是 50^4 = 6250000,在我的酷睿I7的电脑上一秒钟能计算的节点不超过 5W 个,那么 625W 个节点需要的时间在 100 秒以上.电脑一步思考 100秒肯定是不能接受的,实际上最好一步能控制在 5 秒 以内. 顺便说一下层数的问题,首先思考层数必须是偶数.因为奇数节点是AI,偶数节点是玩家,如果AI下一个子不考

人机ai五子棋 ——五子棋AI算法之Java实现

人机ai五子棋 下载:chess.jar (可直接运行) 源码:https://github.com/xcr1234/chess 其实机器博弈最重要的就是打分,分数也就是权重,把棋子下到分数大的地方,我获胜的概率就更大. 而在下棋过程中,大部分的点的得分都很小,或者接近,因此无需对每一个点都打分,只需要在我方附近(进攻)或者敌方附近(防守)的几个点进行打分. 具体原理大家可以看源码中的注释,说明的很清楚. 参考 http://blog.csdn.net/pi9nc/article/details

五子棋AI算法-迭代加深

前面讲到了算杀,其实在算杀之前应该讲一下迭代加深.因为这些文章是我边做边写的一些笔记,所以顺序上可能不是那么严谨. 按照前面的所有算法实现之后(当然不包括算杀),会发现一个比较严重的问题,就是电脑在自己已经胜券在握的情况下(有双三之类的棋可以走),竟然会走一些冲四之类的棋来调戏玩家.这种走法出现的本质就是因为现在的AI只比较最终结果,并没有考虑到路径长短.所以很容易出现在6层搜索到一个双三,其实在4层的时候也有一个双三,因为分数一样,AI会随机选择一个走法.就导致了明明可以两步赢的棋,AI非要走

五子棋AI算法-重构代码

为什么需要重构 之前的代码有很多松散的模块组合在一起.在把 Zobrist 集成进去时,会发现全部需要走棋的操作其实都需要进行一次 Zobrist 异或操作.另外在逻辑上,其实很多模块都是可以合并到同一个类的,所以这次把代码进行了一次大的重构.所以如果发现博客说的一些模块找不到了也是很正常的,因为大部分模块都被移到了 Board 类中. 这次重构主要的工作就是 把AI相关的代码分成了四个模块: Board ,所有和棋子相关的操作都在这里,包括打分,判断胜负,zobrist缓存,启发函数等. ne

高通8X16电池BMS算法(一)【转】

本文转载自:http://www.voidcn.com/blog/yanleizhouqing/article/p-6037399.html 最近一直在搞电源管理相关内容,之前是8610的bms,现在8916的bms,发现两者还是有点区别的,8916把对last_ocv_uv的估值算法分装成执行文件,作为服务一直运行. 电源管理方面,应该是android驱动开发的一大难点,主要涉及的方面多,如充.放电.休眠唤醒等.这一部分主要讲BMS相关的一些基本概念.电池这一块刚开始入手时,感觉很难,很复杂,