CTable::Win中的情况,对于每一个落子坐标,获胜的组合一共有15 * 11 * 2 + 11 * 11 * 2 = 572种。而对于每个坐标的获胜组合,应该设置一个[15][15][572]大小的三维数组。在拥有了这些获胜组合之后,就可以参照每个坐标的572种组合给自己的局面和玩家的局面进行打分,也就是根据当前盘面中某一方所拥有的获胜组合多少进行权值的估算,给出最有利于自己的一步落子坐标。由于是双方对弈,所以游戏的双方都需要一份获胜组合,也就是:
bool m_Computer[15][15][572]; // 电脑获胜组合 bool m_Player[15][15][572]; // 玩家获胜组合
在每次游戏初始化(COneGame::Init)的时候,需要将每个坐标下可能的获胜组合都置为true。此外,还需要设置计算机和玩家在各个获胜组合中所填入的棋子数定义数组为int m_Win[2][572];在初始化的时候,将每个棋子数置为0。 8.2.2 落子后处理
每当一方落子后,都需要作如下处理:
(1) 如果己方此坐标的获胜组合仍为true,且仍有可能在此获胜组合处添加棋子,则将此获胜组合添加棋子数加1。
(2) 如果对方此坐标的获胜组合仍为true,则将对方此坐标的获胜组合置为false,并将对方此获胜组合添加棋子数置为-1(不可能靠此组合获胜)。 以玩家落子为例,代码为: for ( i = 0; i < 572; i++ ) {
// 修改状态变化
if ( m_Player[stepPut.x][stepPut.y][i] &&m_Win[0][i] != -1 ) m_Win[0][i]++;
if ( m_Computer[stepPut.x][stepPut.y][i] ) {
m_Computer[stepPut.x][stepPut.y][i] = false; m_Win[1][i] = -1; } }
8.2.3 查找棋盘空位
在计算机落子之前,需要查找棋盘的空位,所以需要一个SearchBlank成员函数
- 18 -
完成此项工作,此函数需要进行不重复查找,也就是说,对已查找过的空位进行标记,并返回找到空位的坐标,其代码如下:
bool COneGame::SearchBlank( int &i, int &j,int nowTable[][15] ) {
int x, y;
for ( x = 0; x < 15; x++ ) {
for ( y = 0; y < 15; y++ ) {
if ( nowTable[x][y] == -1 && nowTable[x][y] != 2 ) { i = x; j = y; return true; } } }
return false; }
8.2.4 落子打分
找到空位后,需要对这个点的落子进行打分,这个分数是这个坐标重要性的体现,代码如下:
int COneGame::GiveScore( const STEP& stepPut ) {
int i, nScore = 0; for ( i = 0; i < 572; i++ ) {
if ( m_pTable->GetColor() == stepPut.color ) {
if ( m_Player[stepPut.x][stepPut.y][i] ) // 玩家下 {
- 19 -
switch ( m_Win[0][i] ) { case 1:
nScore -= 5; break; case 2:
nScore -= 50; break; case 3:
nScore -= 500; break; case 4:
nScore -= 5000; break; default: break; } } } else {
// 计算机下
if ( m_Computer[stepPut.x][stepPut.y][i] ) {
switch ( m_Win[1][i] ) { case 1:
nScore += 5; break; case 2:
nScore += 50;
- 20 -
break; case 3:
nScore += 100; break; case 4:
nScore += 10000; break; default: break; } } } }
return nScore; }
如代码所示,考虑到攻守两方面的需要,所以将玩家落子给的分数置为负值。 8.2.5 防守策略
落子的考虑不单单要从进攻考虑,还要从防守考虑。这一细节的实现是让计算机从玩家棋盘布局分析战况,然后找出对玩家最有利的落子位置。整个过程如下: for ( m = 0; m < 572; m++ ) {
if ( m_Player[i][j][m] ) // 暂时更改玩家信息 {
temp1[n] = m;
m_Player[i][j][m] = false; temp2[n] = m_Win[0][m]; m_Win[0][m] = -1; n++; } }
ptempTable[i][j] = 0;
- 21 -
pi = i; pj = j;
while ( SearchBlank( i, j, ptempTable ) ) {
ptempTable[i][j] = 2; // 标记已被查找 step.color = m_pTable->GetColor(); step.x = i; step.y = j;
ptemp = GiveScore( step );
// 此时为玩家下子,运用极小极大法时应选取最小值 if ( pscore > ptemp ) pscore = ptemp; }
for ( m = 0; m < n; m++ ) {
m_Player[pi][pj][temp1[m]] = true; // 恢复玩家信息 m_Win[0][temp1[m]] = temp2[m]; }
8.2.6 选取最佳落子
在循环结束的时候,就可以根据攻、守两方面的打分综合地考虑落子位置。代码如下:
if ( ctemp + pscore > cscore ) {
cscore = ctemp + pscore; bestx = pi; besty = pj; }
在这之后,重新改变一下棋盘的状态(6.2.2)即可。
9 总结
本设计有关网络Socket编程、博弈树算法的知识都参考了Internet上的相关源代码,系统的架构、程序窗口设计、功能模块设计完全靠自己独立完成,几千上万行的代
- 22 -