我查找了大量的网上资料,结合自己编程实践,走了很多弯路,总结了一些关于五子棋ai的经验以供大家参考借鉴。
一、贪心算法(相当于ai只思考一步的情况)
对于五子棋ai,大部分人想到的是做一个评估函数。这里的评估函数网上大概有两种,但是很多人会弄混淆。
第一种我称为K函数(只是一个代号,与第二种相区别),是对一个可走的空位子进行打分,如果ai白子落在这个空位置的分数越高,说明这个位置就越好,每次ai走棋就找到一个最好的空位置就行了。
第二种我称为F函数,是对现在的棋盘局面进行打分。 ai白子首先找所有可以走的空位置,模拟走了这个位置以后,用f函数进行局面评分,如果走了这样的一个空位置的得分越高,说明这个位置就越好,每次ai走棋就找这样一个分数最高的位置。
那么你可能就要问了,这两个评估函数有什么区别呢?区别非常大!如果你只是想实现一个只看一步的ai,那么你可以用K函数也可以用F函数。但是如果你想要实现基于博弈树的极大极小搜索和α-β剪枝算法的“聪明”ai,就只能用F函数,因为博弈树必须要对局面打分,而不是对位置打分。如果你明白了这一点,那么接下来就好办了。
二、评估函数(对白子走了一步的局面评估)
怎么对局势有一个比较准确的估计呢?我查阅了大量资料。五子棋的局势无非就是对棋型个数和权重的统计。(对某一方而言的)
五子棋棋型
我分为8种:
1.连5或者长连
2.活4(有两个位置可以形成连5)
3.冲4(有一个位置可以形成连5)
还有其他情况,比如边界,总之再下一步(只能是1步而不是2步)就能连5的就是冲4。
4.活3(走一步可以形成活4)
5.眠3(走一步可以形成冲4)
6.活2(走一步可以形成活3):形状自行脑补
7.眠2(走一步可以形成眠3)
8.活1(走一步可以形成活2)
具体评分规则
我用六元组(一条直线上连续六个位置的状态)来表示棋型,通过检查棋盘上所有六元组来得分。最容易漏掉的地方是边界,比如如果一个斜线只有5个位置也有可能连5、但六元组没考虑进来等等问题,办法是用一个更大的、包括了边界的数组保存棋盘和边界信息。
1.六元组所有棋型辨识
用一个棋型辨识数组保存所有棋型。
#define C_NONE 0//棋子:黑子,白子,无子
#define C_BLACK 1
#define C_WHITE 2
//棋型代号 下标 权重
#define OTHER 0//0,其他棋型不考虑
#define WIN 1//100000,白赢
#define LOSE 2//-10000000
#define FLEX4 3//50000,白活4
#define flex4 4//-80000
#define BLOCK4 5//400
#define block4 6//-80000
#define FLEX3 7//400
#define flex3 8//-8000
#define BLOCK3 9//20
#define block3 10//-40
#define FLEX2 11//20
#define flex2 12//-40
#define BLOCK2 13//1
#define block2 14//-2
#define FLEX1 15//1
#define flex1 16//-2
int tuple6type[4][4][4][4][4][4];//棋型辨识数组,0无子,1黑子,2白子,3边界
需要对这个数组进行初始化。
void chessAi::init_tuple6type(){
memset(tuple6type,0,sizeof (tuple6type));//全部设为0
//白连5,ai赢
tuple6type[2][2][2][2][2][2]=WIN;
tuple6type[2][2][2][2][2][0]=WIN;
tuple6type[0][2][2][2][2][2]=WIN;
tuple6type[2][2][2][2][2][1]=WIN;
tuple6type[1][2][2][2][2][2]=WIN;
tuple6type[3][2][2][2][2][2]=WIN;//边界考虑
tuple6type[2][2][2][2][2][3]=WIN;
//黑连5,ai输
tuple6type[1][1][1][1][1][1]=LOSE;
tuple6type[1][1][1][1][1][0]=LOSE;
tuple6type[0][1][1][1][1][1]=LOSE;
tuple6type[1][1][1][1][1][2]=LOSE;
tuple6type[2][1][1][1][1][1]=LOSE;
tuple6type[3][1][1][1][1][1]=LOSE;
tuple6type[1][1][1][1][1][3]=LOSE;
//白活4
tuple6type[0][2][2][2][2][0]=FLEX4;
//黑活4
tuple6type[0][1][1][1][1][0]=flex4;
//白活3
tuple6type[0][2][2][2][0][0]=FLEX3;
tuple6type[0][0][2][2][2][0]=FLEX3;
tuple6type[0][2][0][2][2][0]=FLEX3;
tuple6type[0][2][2][0][2][0]=FLEX3;
//黑活3
tuple6type[0][1][1][1][0][0]=flex3;
tuple6type[0][0][1][1][