第6天——评价的准才能胜券在握
在博弈类游戏中,都可以使用博弈搜索求解最优解,但是最优解的质量如何取决于评估器的质量,也就是说AI对棋局态势的感知能力。
在不考虑时间时,我们可以让AI前瞻若干步直到终局,终局评估器设计起来就很简单,AI胜利给个正1,AI败北给个负1,平局给个0。完全博弈树的叶节点的数量很大,因此产生终局的时间代价是不能容忍的,我们只能让AI前瞻有限步。如何对中间局进行评价,可以理解成AI对于中间局的感知能力。
之前我们介绍了阳线评估器和阴线评估器,并说明了每个评估器的应用场合。对于阳线评估器的设计相对简单,只要讨论几种模式就可以了,每种模式和对应的得分见表1。
模式 |
五连 |
四连 |
双三连 |
三连 |
双二连 |
二连 |
一连 |
分数 |
100000 |
25000 |
15000 |
10000 |
250 |
100 |
10 |
表1 阳线模式评分表
阴线评估器的设计相对复杂一些,不仅要考虑一个方向还有考虑两个方向,模式及得分见表2、3。
模式 |
评分 |
四连 |
100000 |
三连两端为空 |
100000 |
三连两端非空 |
10000 |
二连 |
10 |
一连 |
1 |
表2 阴线一个方向的模式评分表
相隔45、90 135度 |
相隔180度 |
||
模式 |
评分 |
模式 |
评分 |
双四连 |
100000 |
>=4 |
100000 |
双三连一端为空 |
100000 |
=3两端空 |
100000 |
三连和二连一端为空 |
10000 |
=3两端非空 |
1 |
三连和二连一端非空 |
100 |
=3一端空 |
10 |
三连和一连 |
10 |
=2两端空 |
10000 |
双二连两端空 |
100000 |
=2两端非空 |
1 |
双二连一端空 |
10000 |
=2一端空 |
10 |
双二连两端非空 |
100 |
- |
- |
二连一连一端空 |
10000 |
- |
- |
其他 |
- |
- |
- |
表3 阴线两个方向的模式评分表
表1、2和3的模式仅仅是我能够想到的模式中的一部分,能够简单表示的一部分模式,还有很多其他的模式。因此,另外一种设计评估器的方法就是记棋谱。说到这里,我们不难发现,AI的最高水平要略低于开发者玩该游戏的水平,因此我设计的AI目前仅仅能算是个初学者。同时,每种模式的给分情况也是我根据自己的感觉随便给的,如何设计每种模式的得分又是一个问题。在阅读别人的论文时,主流的评分方法是使用神经网络进行训练,然后利用训练的网络作为评估器每种模式的得分。
讨论了半天,我们还是在原地打转,最最根本的问题并没有得到解决。按照这种技术路线继续走下去,我们设计的AI的水平最好能够达到我们自己的水平,而不能够更高。细细想想,其实min-max搜索,加上alpha-beta剪枝并不是AI的关键,关键是评估器的设计,而评估器的设计实质上是将人的经验形式化转换成计算机的数据结构或者代码。如何让计算机自己学习这一过程,也许才是人工智能的发展方向,目前这种方法充其量还是在模拟人的智能模式。如果能够创造出一种新的智能模式,那么我们又何必挖空心思复现人的智能模式呢,就像地外生命形式一样。如果存在非碳基的生命形式,我们就不会显得如此狭隘,如此孤独,如此自大。如果智能能够被界定成生命的话,那么非人类模式的智能会是什么样,硅基智能吗,计算智能吗,路漫漫啊。
--------------------分割线--------------------
int CWuZiQi::MaxMinValuationOnNonempty( const int oldp[16][16], bool first ) { int i, j, k, t, s, cnt; int tx, ty; int dx[4] = {1, 1, 0, -1}; int dy[4] = {0, 1, 1, 1}; int tiju[16][16][8][2]; int mark[16][16][8][2]; int res = 0; int typeq[8][2] = {0}; int type[8] = {0}; int score[8] = {100000, 25000, 15000, 10000, 250, 100, 10, 1}; //count FillMatrix( tiju, 0 ); FillMatrix( mark, 0 ); /****************为双方填写棋型表************/ for ( i = 1; i <= 15; i++ ) // col { for ( j = 1; j <= 15; j++ ) // row { if ( oldp[i][j] != 0 ) { for ( k = 0; k < 4; k++ ) // direction { if ( oldp[i][j] == 1 ) { if ( mark[i][j][k][0] == 0 ) { cnt = 1; tx = i; ty = j; mark[tx][ty][k][0] = 1; for ( t = 0; t < 5; t++ ) { tx += dx[k]; ty += dy[k]; if ( tx > 15 || tx < 1 || ty > 15 || ty < 1 ) break; if( oldp[tx][ty] == 1 ) { cnt++; mark[tx][ty][k][0] = 1; } else break; } tiju[i][j][k][0] = cnt; if ( cnt == 5 ) typeq[0][0]++; else if ( cnt == 4 ) typeq[1][0]++; else if ( cnt == 3 ) typeq[3][0]++; else if ( cnt == 2 ) typeq[5][0]++; else if ( cnt == 1 ) { typeq[6][0]++; for ( int ii = 0; ii < 4; ii++ ) mark[i][j][ii][0] = 1; } } } else if ( oldp[i][j] == 2 ) { if ( mark[i][j][k][1] == 0 ) { cnt = 1; tx = i; ty = j; mark[tx][ty][k][1] = 1; for ( t = 0; t < 5; t++ ) { tx += dx[k]; ty += dy[k]; if ( tx > 15 || tx < 1 || ty > 15 || ty < 1 ) break; if( oldp[tx][ty] == 2 ) { cnt++; mark[tx][ty][k][1] = 1; } else break; } tiju[i][j][k][1] = cnt; if ( cnt == 5 ) typeq[0][1]++; else if ( cnt == 4 ) typeq[1][1]++; else if ( cnt == 3 ) typeq[3][1]++; else if ( cnt == 2 ) typeq[5][1]++; else if ( cnt == 1 ) { typeq[6][1]++; for ( int ii = 0; ii < 4; ii++ ) mark[i][j][ii][1] = 1; } } } } for ( s = 0; s < 2; s++ ) { t = 0; for ( k = 0; k < 4; k++ ) if ( tiju[i][j][k][s] == 3 ) t++; if ( t == 2 ) typeq[2][s] += 1; else if ( t == 3 ) typeq[2][s] += 3; else if ( t == 4 ) typeq[2][s] += 6; t = 0; for ( k = 0; k < 4; k++ ) if ( tiju[i][j][k][s] == 2 ) t++; if ( t == 2 ) typeq[4][s] += 1; else if ( t == 3 ) typeq[4][s] += 3; else if ( t == 4 ) typeq[4][s] += 6; } } } } //PrintMatrix( tiju ); //PrintMatrix( oldp ); //value for ( i = 0; i < 7; i++ ) { if ( first ) type[i] = typeq[i][0] - typeq[i][1]; else type[i] = typeq[i][0] - typeq[i][1]; res += type[i] * score[i]; } // if ( first ) return -res; else return res; } void CWuZiQi::MaxMinValuationOnEmpty( const int oldp[16][16], int a1[16][16], int a2[16][16] ) { int i, j, k, t, s, cnt, ii, jj; int tx, ty; int barrier; int win[2]; int dx[8] = {0, 1, 1, 1, 0, -1, -1, -1}; int dy[8] = {-1, -1, 0, 1, 1, 1, 0, -1}; int type[8][2]; int p[16][16]; for ( i = 0; i < 16; i++ ) for ( j = 0; j < 16; j ++ ) p[i][j] = oldp[i][j]; //couny /****************为双方填写棋型表************/ for ( i = 1; i <= 15; i++ ) // col { for ( j = 1; j <= 15; j++ ) // row { if ( oldp[i][j] == 0 ) { for ( ii = 0; ii < 8; ii++ ) for ( jj = 0; jj < 2; jj++ ) type[ii][jj] = 0; // count for ( k = 0; k < 8; k++ ) // direction { // cnt = 0; tx = i; ty = j; barrier = 0; for ( t = 0; t < 5; t++ ) { tx += dx[k]; ty += dy[k]; if ( tx > 15 || tx < 1 || ty > 15 || ty < 1 ) {barrier = 1; break;} if( oldp[tx][ty] == 1 ) cnt++; else { if ( oldp[tx][ty] == 2 ) barrier = 1; break; } } type[k][0] = cnt * 2 + barrier; // cnt = 0; tx = i; ty = j; barrier = 0; for ( t = 0; t < 5; t++ ) { tx += dx[k]; ty += dy[k]; if ( tx > 15 || tx < 1 || ty > 15 || ty < 1 ) {barrier = 1; break;} if( oldp[tx][ty] == 2 ) cnt++; else { if ( oldp[tx][ty] == 1 ) barrier = 1; break; } } type[k][1] = cnt * 2 + barrier; } // value for ( t = 0; t < 2; t++ ) { win[t] = 0; for ( k = 0; k < 8; k++ ) // single line { int t1, s1; if ( type[k][t] % 2 == 0 ) {t1 = type[k][t] / 2; s1 = 0;} else {t1 = ( type[k][t] - 1 ) / 2; s1 = 1;} if ( t1 >= 4 ) win[t] += 100000; else if ( t1 == 3 && s1 == 0 ) win[t] += 100000; else if ( t1 == 3 && s1 == 1 ) win[t] += 10000; else if ( t1 == 2 ) win[t] += 10; else win[t] += 1; } for ( k = 0; k < 8; k++ ) // two line combination, 45 degree, 90 degree and 135 degree for ( s = 1; s <= 3; s++ ) { int t1, t2, s1, s2; if ( type[k][t] % 2 == 0 ) {t1 = type[k][t] / 2; s1 = 0;} else {t1 = ( type[k][t] - 1 ) / 2; s1 = 1;} if ( type[( k + s ) % 8][0] % 2 == 0 ) {t2 = type[( k + s ) % 8][t] / 2; s2 = 0;} else {t2 = ( type[( k + s ) % 8][t] - 1 ) / 2; s2 = 1;} int tmp = t1 + t2; // if ( t1 == 0 || t2 == 0 ) continue; if ( t1 >= 4 || t2 >= 4 ) {win[t] += 100000; continue;} else if ( t1 == 3 || t2 == 3 ) { if (( t1 == 3 && s1 == 0 ) || ( t2 == 3 && s2 == 0 )) {win[t] += 100000; continue;} else if (( t1 == 2 && s1 == 0 ) || ( t2 == 2 && s2 == 0 )) {win[t] += 10000; continue;} else if (( t1 == 2 && s1 == 1 ) || ( t2 == 2 && s2 == 1 )) {win[t] += 100; continue;} else if (( t1 == 1 ) || ( t2 == 1 )) {win[t] += 10; continue;} else if (( t1 == 0 ) || ( t2 == 0 )) {win[t] += 1; continue;} } else if ( t1 == 2 || t2 == 2 ) { if (( t1 == 2 && s1 == 0 ) && ( t2 == 2 && s2 == 0 )) {win[t] += 100000; continue;} else if (( t1 == 2 && s1 == 1 ) && ( t2 == 2 && s2 == 1 )) {win[t] += 100; continue;} else if (( t1 == 2 && s1 == 1 ) && ( t2 == 2 && s2 == 0 ) || ( t1 == 2 && s1 == 0 ) && ( t2 == 2 && s2 == 1 )) {win[t] += 10000; continue;} if (( t1 == 2 && s1 == 0 ) || ( t2 == 2 && s2 == 0 )) {win[t] += 10000; continue;} else {win[t] += 100; continue;} } else if ( t1 == 1 || t2 == 1 ) {win[t] += 1; continue;} } for ( k = 0; k < 4; k++ ) // two line combination 180 degree { int t1, t2, s1, s2; if ( type[k][t] % 2 == 0 ) {t1 = type[k][t] / 2; s1 = 0;} else {t1 = ( type[k][t] - 1 ) / 2; s1 = 1;} if ( type[( k + 4 ) % 8][0] % 2 == 0 ) {t2 = type[( k + 4 ) % 8][t] / 2; s2 = 0;} else {t2 = ( type[( k + s ) % 8][t] - 1 ) / 2; s2 = 1;} int tmp = t1 + t2; // if ( t1 == 0 || t2 == 0 ) continue; if ( tmp >= 4 ) {win[t] += 100000;continue;} else if ( tmp == 3 ) { if ( s1 == 0 && s2 == 0 ) {win[t] += 100000;continue;} else if ( s1 == 1 && s2 == 1 ) {win[t] += 1;continue;} else {win[t] += 10;continue;} } else if ( tmp == 2 ) { if ( s1 == 0 && s2 == 0 ) {win[t] += 10000;continue;} else if ( s1 == 1 && s2 == 1 ) {win[t] += 1;continue;} else {win[t] += 10;continue;} } } } a1[i][j] = win[0]; a2[i][j] = win[1]; } } } }