正文
01
思路
我没有选择专业的五子棋棋型,用我自己的逻辑(初高中玩五子棋的方法),去实现简单的人机对战。
首先因为要计算五子棋每一步的分数,那么你就要分析每一步形成的棋盘,以下图为例:
当你即将在红色方框区域内落子时,通过数据处理获取四个方向的棋子存储在数组里面,然后就是分析这个数组属于那种棋型。
以横向为例:
row相当于上图红色矩形区域的行坐标
col 相当于上图红色矩形区域的列坐标
player是这个位置即将落子的颜色,-1黑子 1 白子
int Game_score(int row, int col, int player) //player = -1黑子 player = 1 白子
{
char arr[4][9] = { 0 }; //存放四个方向的棋子
int grade[4]; //存放四个方向的总分
int begin = 0, end = 0;
/*========左右========*/
//从Map[row][col]处往左找四个棋子,遇到边界结束得到左边落子数量
//从Map[row][col]处往右找四个棋子,遇到边界结束得到右边落子数量
for (int index = 1; index <= 4; index++)
{
//往左找
if (col - index >= 0)
{
begin = index;
}
//往右找
if (col + index < 15)
{
end = index;
}
}
for (int temp = 0, index = col - begin; index <= col + end; index++, temp++)
{
arr[0][temp] = Map[row][index]; //将Map[row][col]附近九个位置的棋子放入到arr里面分析
}
arr[0][begin] = player; //将预测的落子位置由0置player
}
这个时候横向的棋子已经存储在arr这个数组的第一行这个时候只需要分析横向的棋型属于那种类型。
这里插句嘴,当时我处理完得到四个方向的棋型之后我觉得五子棋AI已经完成了一大半,然而在判断每个方向属于那种棋型的时候我慌了。
我觉得将所有棋型,也就是专业的连五,活四,冲四,活三,眠三,活二,眠二这些棋型枚举出来,然后和这个arr数组匹配就完事了。一枚举,十五种可能的棋型,还不是固定大小,下面是我当时枚举的情况
int g_score[15][6] = {
/*连五*/
{1,1,1,1,1},
/*活四*/
{0,1,1,1,1,0},
/*冲四*/
{0,1,1,1,1,-1},
{1,0,1,1,1},
{1,1,0,1,1},
/*活三*/
{0,1,1,1,0},
{1,0,1,1},
//{1,1,0,1},
/*眠三*/
{1,1,1,-1},
{1,0,1,1,-1},
{1,1,0,1,-1},
{1,0,0,1,1},
{1,0,1,0,1},
/*活二*/
{1,1},
{1,0,1},
{1,0,0,1},
};
先不管棋型的宽度有6有2,尝试匹配,一算算了近3s,时间复杂度太高了,而且还算错了,还是因为棋型宽度不一致,匹配起来很难,当时我还想要不然用C++里面的string类里面的find函数去匹配,后面忘记了还是咋,换成了最简单的方法,就是看下当前落子位置相连的棋子数量为多少。
int count = 1; //统计连子数量
/*从Map[row][col]往左找,统计player棋子的数量*/
for (int i = 1; i <= begin; i++)
{
if (arr[0][begin - i] == player)
{
count++;
}
elseif (arr[0][begin - i] == -player)
{
if (count < 5) //当最左边有一个异类棋子时权重降低 例如 xoooo 和 ooo 权重一样
count--;
break;
}
elsebreak;
}
/*从Map[row][col]往右找,统计player的数量*/
for (int i = 1; i <= end; i++)
{
if (arr[0][begin + i] == player)
{
count++;
}
elseif (arr[0][begin + i] == -player)
{
if (count < 5)
count--;
break;
}
elsebreak;
}
grade[0] = CountScore(count); //根据player棋子数量统计分数
在这里我将专业术语里面的冲四和活三的权重认为是等同的,所以当坐左边或最右边有异类棋子时权重减一。
本来就写的不是很好这个AI,所以一切从简。
02
评分
因为一切从简,所以评分规则也是异常的简单。
int CountScore(int num)
{
if (num >= 5) //五子
return50000;
if (num == 4) //四子
return4320;
if (num == 3) //三子
return720;
if (num == 2) //二子
return120;
if (num == 1) //一子
return20;
return0;
}
在分给出每一个方向的评分后我给出了一个综合得分,这个综合得分是分析四个方向给出最高分
/*给出综合得分*/
int max_score = 0;
for (int i = 0; i < 4; i++)
{
if (max_score < grade[i])
{
max_score = grade[i];
}
elseif (max_score == grade[i])
{
max_score += grade[i];
}
}
03
分析落子
分析黑棋落子点是在白棋下完之后,对每一个空白处评分。
//确保鼠标在棋盘区域
if (x >= 15 && x <= 465 && y >= 15 && y <= 465)
{
//此处无子
if (Map[(y - 30 + 15) / 30][(x - 30 + 15) / 30] == 0)
{
//下子,改变currentPos
currentPos.X = (x - 30 + 15) / 30;
currentPos.Y = (y - 30 + 15) / 30;
//map赋值
Map[currentPos.Y][currentPos.X] = 1; //玩家白棋
GameDraw();
if (Game_Juge())
{
return;
}
}
}
白棋下完之后,计算黑棋落子可能得分和白棋下一步落子可能评分,也就是进攻和防守。
struct GRADE
{
int m_x;
int m_y;
int m_score;
};
GRADE attack_grade[MAP_ROW][MAP_COL]; //攻击分数
GRADE define_grade[MAP_ROW][MAP_COL]; //防御分数
//计算得分
for (int row = 0; row < MAP_ROW; row++)
{
for (int col = 0; col < MAP_COL; col++)
{
if (Map[row][col] == 0)
{
define_grade[row][col].m_x = attack_grade[row][col].m_x = col;
define_grade[row][col].m_y = attack_grade[row][col].m_y = row;
attack_grade[row][col].m_score = Game_score(row, col, -1);
define_grade[row][col].m_score = Game_score(row, col, 1);
}
else
{
define_grade[row][col].m_x = attack_grade[row][col].m_x = col;
define_grade[row][col].m_y = attack_grade[row][col].m_y = row;
define_grade[row][col].m_score = attack_grade[row][col].m_score = 0;
}
}
}
给出评分后确定黑子的落子点,就是判断进攻还是防守
//计算黑白棋最佳落子点
for (int row = 0; row < MAP_ROW; row++)
{
for (int col = 0; col < MAP_COL; col++)
{
if (MaxAttackScore.m_score < attack_grade[row][col].m_score)
{
MaxAttackScore.m_score = attack_grade[row][col].m_score;
MaxAttackScore.m_x = attack_grade[row][col].m_x;
MaxAttackScore.m_y = attack_grade[row][col].m_y;
}
if (MaxDefineScore.m_score < define_grade[row][col].m_score)
{
MaxDefineScore.m_score = define_grade[row][col].m_score;
MaxDefineScore.m_x = define_grade[row][col].m_x;
MaxDefineScore.m_y = define_grade[row][col].m_y;
}
}
}
//选择进攻 还是 防守一波(放手一搏)
if (MaxAttackScore.m_score >= MaxDefineScore.m_score)
{
Map[MaxAttackScore.m_y][MaxAttackScore.m_x] = -1;
currentPos.X = MaxAttackScore.m_x;
currentPos.Y = MaxAttackScore.m_y;
}
elseif (MaxAttackScore.m_score < MaxDefineScore.m_score)
{
Map[MaxDefineScore.m_y][MaxDefineScore.m_x] = -1;
currentPos.X = MaxDefineScore.m_x;
currentPos.Y = MaxDefineScore.m_y;
}
GameDraw();
//判断输赢
if (Game_Juge())
{
return;
}
04
关于五子棋人机
这个AI只能说简单的实现了最基本的人机对战,很多方面因素我都没有考虑到,纯粹是自己看了几个AI实现的方法,再发挥下自己的想象力,七平八凑凑出来的,参考下我的思路即可,不要深究,毕竟失败是成功它妈。因为有些学校集训的时候会有五子棋AI的项目,我没有这个经历,考虑的方向或方法可能不对,我这就属于失败的产物,娱乐一下就好。如果以后有时间了我会改进下五子棋AI算法。