📄 gobangai.java
字号:
*@return 返回新的列数
*/
protected int jMove(int j,int dir)
{
switch (dir)
{
case LEFT:
case UP_LEFT:
case DOWN_LEFT:
j--; //向左
break;
case RIGHT:
case UP_RIGHT:
case DOWN_RIGHT:
j++; //向右
break;
default:
}
return j;
}
/*======================2.对棋局评分的各个方法=======================*/
/**
*对于当前角色记录棋局中每一个空格的每个方向情况。即是求situation
*@param player 当前角色
*@param i 棋的行数
*@param j 棋的列数
*return situation 每一个空格的情况。
*其中,前两个索引是横纵坐标,第三个索引是方向
*/
public void getSituation(int player, int i, int j)
{
int step; //距离空格的步数
int iTest, jTest; //要检验的格
dirContinue:
for (int dir = 0; dir < DIR; dir++) //对于每一个方向
{
//iTest,jTest分别指向i,j
iTest = i;
jTest = j;
for (step = 1; true; step ++)
{
if (step == 5)
//找到连株
{
situation[player][i][j][dir] = FINISH_BANG;
continue dirContinue; //下一个方向
}
//向指定的方向移动一格
iTest = iMove(iTest, dir);
jTest = jMove(jTest, dir);
if (! ifIn(iTest,jTest) ||
tableAI[iTest][jTest] == antiPlayer(player))
//若iTest,jTest越界,或是对手的棋
{
situation[player][i][j][dir] = DEAD_ZERO + step - 1;
continue dirContinue; //下一个方向
}
else if (tableAI[iTest][jTest] == NOTHING)
//若iTest,jTest是空位
{
situation[player][i][j][dir] = LIFE_ZERO + step - 1;
continue dirContinue; //下一个方向
}
else if (tableAI[iTest][jTest] == player);
//若iTest,jTest是自己的棋,不需操作
}
}
}
/**
*获得棋局中每一个空格的四个方向(即在一条直线上)的评分。即是求MarkLink
*@param player 当前角色
*@param i 棋的行数
*@param j 棋的列数
*/
public void getLinkMark(int player, int i, int j)
{
int tempMark; //临时记分
int tempMarkLink = 0;
dirContinue:
for (int dir = 0; dir < DIR/2; dir ++) //对于四条直线的方向
{
//把(一条直线上)两个方向的情况记到临时记分
tempMark = situation[player][i][j][dir] +
situation[player][i][j][dir + 4];
if (tempMark >= 100) //找到连珠
{
tempMarkLink = LINK_BANG; //连珠
markLink[player][i][j][dir] = tempMarkLink;
continue dirContinue; //下一个方向
}
switch (tempMark)
{
case 0: //活一
tempMarkLink = LINK_LIFE_ONE;
break;
case 1: //活二
tempMarkLink = LINK_LIFE_TWO;
break;
case 2: //活三
tempMarkLink = LINK_LIFE_THREE;
break;
case 3: //活四
tempMarkLink = LINK_LIFE_FOUR;
break;
case 4:
case 5:
case 6: //连珠
tempMarkLink = LINK_BANG;
break;
case 10: //死一
tempMarkLink = LINK_DEAD_ONE;
break;
case 11: //死二
tempMarkLink = LINK_DEAD_TWO;
break;
case 12: //死三
tempMarkLink = LINK_DEAD_THREE;
break;
case 13: //死四
tempMarkLink = LINK_DEAD_FOUR;
break;
case 14:
case 15:
case 16: //连珠
tempMarkLink = LINK_BANG;
break;
case 20:
case 21:
case 22:
case 23:
tempMarkLink = LINK_DEAD;
break;
case 24:
case 25:
case 26: //连珠
tempMarkLink = LINK_BANG;
break;
default:
tempMarkLink = LINK_DEAD;
}
markLink[player][i][j][dir] = tempMarkLink;
}
}
/**
*获得棋局中每一个空格的总评分。即是求mark
*@param player 当前角色
*@param i 棋的行数
*@param j 棋的列数
*/
public void getTotleMark(int player, int i, int j)
{
int tempMark = 0;
int lastLinkMark = 0; //记录上一次的评分,采用markLink的值
for (int dir = 0; dir < DIR/2; dir ++)
{
switch (markLink[player][i][j][dir])
{
case LINK_DEAD_TWO: //死2
if (lastLinkMark < markLink[player][i][j][dir])
//前面的评分少于当前的markLink[player][i][j][dir]
{
lastLinkMark = markLink[player][i][j][dir];
tempMark = MARK_D2;
}
break;
case LINK_LIFE_TWO: //活2
if (lastLinkMark < markLink[player][i][j][dir])
{
lastLinkMark = markLink[player][i][j][dir];
tempMark = MARK_L2;
}
else if (lastLinkMark == LINK_LIFE_TWO)
//若前面的评分与当前markLink[player][i][j][dir]都是活2
{
tempMark = MARK_L2L2; //双活2
}
break;
case LINK_DEAD_THREE: //死3
if (lastLinkMark < markLink[player][i][j][dir] &&
tempMark != MARK_L2L2)
//前面的评分不是双活2
{
lastLinkMark = markLink[player][i][j][dir];
tempMark = MARK_D3; //单死3
}
if (lastLinkMark == LINK_LIFE_THREE)
//前面已经有一个活3
{
tempMark = MARK_D3L3; //死3活3
}
break;
case LINK_LIFE_THREE: //活3
if (lastLinkMark == LINK_DEAD_THREE)
//前面已经有一个死3
{
lastLinkMark = markLink[player][i][j][dir];
tempMark = MARK_D3L3; //死3活3
}
else if (lastLinkMark < markLink[player][i][j][dir])
{
lastLinkMark = markLink[player][i][j][dir];
tempMark = MARK_L3; //单活3
}
else if (lastLinkMark == LINK_LIFE_THREE)
//若前面的评分与当前markLink[player][i][j][dir]都是活3
{
tempMark = MARK_L3L3; //双活3
}
break;
case LINK_DEAD_FOUR:
if (lastLinkMark == LINK_DEAD_FOUR ||
lastLinkMark == LINK_LIFE_THREE)
//前面已经有一个死4 或 活3
{
tempMark = MARK_L4_D4D4_D4L3; //双死4 或 死4活3
}
else if (lastLinkMark < markLink[player][i][j][dir])
{
lastLinkMark = markLink[player][i][j][dir];
tempMark = MARK_D4; //单死4
}
break;
case LINK_LIFE_FOUR: //活4
if (lastLinkMark < markLink[player][i][j][dir])
{
lastLinkMark = markLink[player][i][j][dir];
tempMark = MARK_L4_D4D4_D4L3;
}
break;
case LINK_BANG: //连珠
tempMark = MARK_BANG;
lastLinkMark = markLink[player][i][j][dir];
//System.out.println(i + " " + j + "find bang " + tempMark); //测试
break;
default:
}
}
mark[player][i][j] = tempMark; //作记录
//System.out.println(i +"," + j +"tempMark " + mark[player][i][j]);
}
/**
*搜索出最高利益的COUNT个空格的坐标(连接所有关于评分的方法)
*@param player 当前角色
*@return 返回最高分的COUNT个空格的坐标,用二维数组表示
*/
public int[][] getMaxProfitPoint(int player)
{
//遍历每一个坐标,分三步进行评分
for (int i = 0 ; i < BOARD_SIZE ; i++)
{
for (int j = 0 ; j < BOARD_SIZE ; j++)
{
if (tableAI[i][j] == NOTHING) //若是空格
{
getSituation(COMPUTER, i, j);
getLinkMark(COMPUTER, i, j);
getTotleMark(COMPUTER, i, j);
getSituation(PLAYER, i, j);
getLinkMark(PLAYER, i, j);
getTotleMark(PLAYER, i, j);
}
}
}
//maxPoints记录最高利益的COUNT个空格的坐标:
//第一个索引是表示第几个最高分点;
//第二个索引有三个元素,分别是iMax,jMax,profitMax。
int[][] maxPoints = new int[COUNT][3];
int iMax, jMax, profitMax, profitTemp;
for (int count = 0; count < COUNT; count ++) //第count个最高利益点
{
iMax = 0;
jMax = 0;
profitMax = 0; //最大利益为两个角色在空格得分的加权平均值
//遍历每一个坐标
for (int i = 0 ; i < BOARD_SIZE ; i++)
{
for (int j = 0 ; j < BOARD_SIZE ; j++)
{
//利益=权*本角色的分数 + (10-权)*对手的分数
profitTemp = WEIGHT * mark[player][i][j] +
(10 - WEIGHT) * mark[antiPlayer(player)][i][j];
if (profitMax < profitTemp)
{
iMax = i;
jMax = j;
profitMax = profitTemp;
}
}
}
//把第count个最高利益点记录在maxPoints
maxPoints[count][I_MAX] = iMax;
maxPoints[count][J_MAX] = jMax;
maxPoints[count][PROFIT_MAX] = profitMax;
//把这个最高利益点的分数改为零,以便找出下一个最高分点
mark[COMPUTER][iMax][jMax] = 0;
mark[PLAYER][iMax][jMax] = 0;
}
return maxPoints;
}
/*========================3.求AI的主要的方法=======================*/
/**
*用AI求出电脑下一步的坐标
*@return 返回电脑下一步的坐标的数组
*/
public int[] GobangAImain()
{
//先把table的内容记录到tableAI
for (int i = 0 ; i < BOARD_SIZE ; i++)
{
for (int j = 0 ; j < BOARD_SIZE ; j++)
{
tableAI[i][j] = table[i][j];
}
}
//定义各个临时变量
int iBest = 0; //最好的空格的行数
int jBest = 0; //最好的空格的列数
int profit = 0; //当前考虑的棋的利益
int profitMax = -9999; //最大的利益初始化
//找出最高利益的COUNT个空格的坐标
int[][] bestProfit = getMaxProfitPoint(COMPUTER);
int i; //当前考虑的行数
int j; //当前考虑的列数
for (int count = 0; count < COUNT; count ++) //第count个最高分点
{
i = bestProfit[count][I_MAX]; //这个最高分点的行数
j = bestProfit[count][J_MAX]; //这个最高分点的列数
//利益 = 获取本角色利益值() - GobangAImain(角色=相反角色,层次-1);
profit = bestProfit[count][PROFIT_MAX] -
GobangAI(PLAYER, DEPTH-1, i, j);
//System.out.println("i = " + i + "; j = " + j +"; profit = " + profit); //测试用
if (profitMax < profit)
{
profitMax = profit;
iBest = i; //纪录这个空格的行数
jBest = j; //纪录这个空格的列数
//System.out.println("profit = " + profit); //测试用
}
}
//遍历每一个坐标
if (tableAI[iBest][jBest] != NOTHING) //若所求的位置有棋
{
System.out.println("Error!");
System.exit(0);
}
return new int[]{iBest, jBest};
}
/**
*考虑往后几步的递归方法
*@param player 当前角色
*@param depth 考虑剩下depth步
*@param iLast 上一步的棋的行数
*@param jLast 上一步的棋的列数
*@return 返回这个空格的利益(来源于mark的值)
*/
public int GobangAI(int player, int depth, int iLast, int jLast)
{
int profit = 0; //当前考虑的棋的利益
int profitMax = -9999; //最大的利益初始化
if (depth == 0)
//考虑到最低层了,返回0利益
{
return 0;
}
//先记录上一步,对手下的棋
tableAI[iLast][jLast] = antiPlayer(player);
//找出最高分的COUNT个空格的坐标
int[][] bestProfit = getMaxProfitPoint(player);
//System.out.println("mark = " + bestProfit[0][PROFIT_MAX]); //测试用
int i; //当前考虑的行数
int j; //当前考虑的列数
for (int count = 0; count < COUNT; count ++) //第count个最高分点
{
i = bestProfit[count][I_MAX]; //这个最高分点的行数
j = bestProfit[count][J_MAX]; //这个最高分点的列数
//利益 = 获取本角色利益值() - GobangAImain(角色=相反角色,层次-1);
profit = bestProfit[count][PROFIT_MAX] -
GobangAI(antiPlayer(player), depth-1, i, j);
if (profitMax < profit)
{
profitMax = profit;
//System.out.println("profit = " + profit); //测试用
}
}
tableAI[iLast][jLast] = NOTHING; //退回这步棋
return profitMax;
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -