模板匹配实现的(简易)五子棋单步估值算法

这个简陋的五子棋其实是去年底(12月)写的,一直没发出来,以至于现在算法都有些忘了。虽然那个五子棋已初具雏形(可在10秒内进行4层搜索,并具有GTK2写的界面),但很多地方都不满意:比如hash表不知为何一直弄不成功,GTK+学得半吊子以至于界面部分的代码自己都觉得丑陋,另外禁手规则也一直没有加上去。因此在这里就不分享自己完整的程序了,但说说我是如何对五子棋进行单步估分的吧~

首先分享一些本人在编写过程中找到的珍贵资料:

第一篇《十四步实现强大五子棋AI》:
www.cnblogs.com/goodness/archive/2010/05/27/1745756.html
这篇文章是我在写AI前看的第一篇完整的文章。虽然现在来看写的很简略,但整体框架相当完整,而且层次循序渐进,很适合初期对AI算法形成一个大体的把握。
第二篇《计算机博弈 象棋百科全书》:
www.xqbase.com/computer.htm
这个网站的内容非常全。虽然是讲象棋的,但是其中对搜索算法的介绍却是任何AI都通用的。里面不但给出了思路,也给出了c实现的源码。(其实我从alphabeta搜索开始基本就是照着网站上的介绍一点点写的,或者说照搬的- -)

接下来具体讲讲我当时写估分函数时每一步的想法:

一开始我判断棋型的思路就是简单的对每个棋子向两边延伸计数,比如数到3个而且还是被空格截断那么就算是活三,但很快发现这样能够识别的棋型范围太狭窄了;然后我又想找出棋子格数与棋型的规律,太麻烦了;最后参考了网上的几篇文章后我用的办法是,先设定好模板,然后获取盘面上的棋型,再逐个去匹配模板——这么做看似麻烦,但其实活的棋型也就十几个,一一列出模板是完全可以接受的;而在如何匹配模板上,我纠结了好久,最后借鉴的是另一篇博文,虽然那篇博文的代码我始终没看懂,但其中通过位操作来得到一个二进制数并以此表示模板的思想我觉得真是太妙了!总之最后我判断棋型的办法是:

举个例子,假设现在要判断某点的权重,该点水平方向所在行的情况是(以X表示当前棋子、以?表示空格):左边四格为?XX?,右边四格为XXX?(因为要构成五子连珠最多只需要再多判断4格,所以四格以外不用去管),左右汇总再加上该点本身,得出这个点所在的棋型为:?XX? X XXX?。现在可以用0来表示空格,用1来表示棋子,那么这个棋型用二进制表示就是:0110 1 1110,具体转化过程就是在循环体中用移位符<<来得到。这种办法获取的这个棋型数字就是我在代码中称为行棋型(line_model)的东西。

同样办法来表示活棋型模板。比如活四的棋型是:?XXXX?,二进制数表示为:011110,又如活三的一种棋型为:??XXX?,二进制就表示成:001110。以此得出活二及以上(活一就没必要判断了)活棋型的数值表示,并把这些数字存为数组作为模板。我在代码里写为model_awake[]。

因为活四的棋型是:?XXXX?,共6个子,而活三活二都是在活四中镂空子,所以活棋型都只需要6个子来判断就够了。但我前面获取的该行棋型是最多可能有9个子的,那么要匹配模板就先需要转化为6个子的棋型。这个还是用移位来完成,比如前面的:0110 1 1110这个行棋型,右移三位得到:011011,将这个数字与前面的活棋型模板匹配,也就是判断是否相等,相等则说明匹配出来了。

但这个011011这个棋型明显无法匹配出任何活棋型模板,因为它所代表的棋型是:?XX?XX,是眠四。所以一旦活棋模板匹配不成功那么接下来需要判断的就是这个棋型是眠棋型中的眠几。这个好办,因为011011中有四个棋子(四个1),所以就是眠四了!

判断完这个行棋型的前六格,接下来判断第二个六格棋型,那么就要将0110 1 1110右移二位,得到:0110111,但其实我这回需要的只是110111这后六个数位,于是我就用&操作符,将第六位以后屏蔽掉,得到该行中第二个六格棋型,然后再按上面的方法判断。

按此方法判断该行的棋型。因为一行九格里不可能共存多个棋型(因为其中一种棋型被破坏后另一种棋型也会被同时破坏掉,因此只能取其中一个棋型。但少数情况下有例外),所以只要在一行中判断出来的棋型里取最重要的棋型即可。比如上面这个例子:0110 1 1110中,既存在上面判断过的眠四,又存在活四(后六位011110对应?XXXX?,活四棋型),所以这一行判断完后取最重要的活四作为该行的最终棋型,然后根据棋型打分(活棋和眠棋的分数我在代码里分别存为数组score_awake[]和score_asleep[],然后根据模板标号直接定值,避免用条件语句一一赋值使代码显得冗长)。本行就此判断结束。

判断了水平行的棋型后,接下来再循环判断45度、90度、135度四个方向的行棋型,并分别给出每个方向的棋型分值。分值加总后就是这个点的分数。

到此为止,一个简单的AI已经做出来了!只要让AI遍历整个棋盘上的空格按上述方法找出分值最大的点并在该点落子,那么AI就可以初步和人对下了。


下面是我的代码:

typedef struct _Point {
    int row,col;
}Point;

static Point dir[4] = {{0,1},{-1,1},{-1,0},{-1,-1}};
static int model_awake[] = {077,076,037,036,016,034,026,032,014,030,006,024,012,022};
static int score_awake[] = {INF,INF,INF,20000,6000,6000,5000,5000,600,500,500,400,400,300},score_asleep[] = {0,0,300,1500,6000,INF};

int EvaluateMove (char *board, int move_row, int move_col, int player, int opponent, int mode)
{
	int count_player,len1,len2,line_length,model,line_model,score,line_score,idir,ipoint,imodel,iawake,here_is_empty = 0;
	Point point,line_end;

	if (board[move_row][move_col] == NONE) {
		here_is_empty = 1;
		board[move_row][move_col] = player;
	}

	score = 0;
	for (idir=0; idir<4; ++idir) {
		count_player = 0;

		for (len1=0, point.row=move_row, point.col=move_col; len1<5 && point.col<15 && point.col>=0 && point.row>=0 && board[point.row][point.col] != opponent; ++len1, point.col+=dir[idir].col, point.row+=dir[idir].row)
			if (board[point.row][point.col] == player)
				++count_player;
		for (len2=0, point.row=move_row, point.col=move_col; len2<5 && point.col<15 && point.col>=0 && point.row<15 && board[point.row][point.col] != opponent; ++len2, point.col-=dir[idir].col, point.row-=dir[idir].row)
			if (board[point.row][point.col] == player)
				++count_player;
		--count_player;
		line_end.col = point.col + dir[idir].col, line_end.row = point.row + dir[idir].row;
		line_length = len1 + len2 - 1;

		if (line_length < 5)
			continue;

		if (line_length == 5) {
			score += ( mode == SUM ? score_asleep[count_player] : (score_asleep[count_player] / count_player) );
			continue;
		}

		for (ipoint=0, point=line_end, line_model=0; ipoint> 1;

		line_score = 0;
		for (imodel=0; imodel> imodel) & 077;

			count_player = 0;
			for (ipoint=0; ipoint<6; ++ipoint)
				if ((model >> ipoint) % 2)
					++count_player;

			for (iawake=0; iawake<14; ++iawake)
				if (model_awake[iawake] == model) {
					line_score = line_score > score_awake[iawake] ? line_score : score_awake[iawake];
					break;
				}

			if (iawake == 14) {
				if ( (model >> 0) % 2 && (model >> 5) % 2 )
					line_score = line_score > score_asleep[count_player-1] ? line_score : score_asleep[count_player-1];
				else
					line_score = line_score > score_asleep[count_player] ? line_score : score_asleep[count_player];
			}
		}

		score += ( mode == SUM ? line_score : (line_score / count_player) );
	}

	if (here_is_empty)
		board[move_row][move_col] = NONE;
	return score;
}
/**********未完待续***********//******另:代码里的几个又臭又长的for当时看着就很不舒服,现在回头来看更是如此,等到下次完善这个五子棋的时候再一并改掉了吧*****/

你可能感兴趣的:(模板匹配实现的(简易)五子棋单步估值算法)