三、象棋AI算法
3.1 走法
相对于五子棋,象棋的走法是更复杂的。在走法里面,要说的东西不多,我直接贴代码:
protected override List<Step> GenerateAllMove(ChessColor c)
{
List<Step> moves = new List<Step>();
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
if (board[i, j].type != ChessType.空 && board[i, j].color == c)
{
switch (board[i, j].type)
{
case ChessType.卒:
GenerateMove卒(i, j, c, moves);
break;
case ChessType.士:
GenerateMove士(i, j, c, moves);
break;
case ChessType.将:
GenerateMove将(i, j, c, moves);
break;
case ChessType.象:
GenerateMove象(i, j, c, moves);
break;
case ChessType.马:
GenerateMove马(i, j, c, moves);
break;
case ChessType.车:
GenerateMove车(i, j, c, moves);
break;
case ChessType.炮:
GenerateMove炮(i, j, c, moves);
break;
}
}
}
}
return moves;
}
private void GenerateMove卒(int row, int col, ChessColor c, List<Step> moves)
{
if (c == ChessColor.红)
{
if (row < 5)
{
AddStep(moves, new Step(row, col, row + 1, col), c);
}
else
{
AddStep(moves, new Step(row, col, row + 1, col), c);
AddStep(moves, new Step(row, col, row, col + 1), c);
AddStep(moves, new Step(row, col, row, col - 1), c);
}
}
else
{
if (row >= 5)
{
AddStep(moves, new Step(row, col, row - 1, col), c);
}
else
{
AddStep(moves, new Step(row, col, row - 1, col), c);
AddStep(moves, new Step(row, col, row, col + 1), c);
AddStep(moves, new Step(row, col, row, col - 1), c);
}
}
}
private void GenerateMove士(int row, int col, ChessColor c, List<Step> moves)
{
int row1 = row + 1;
int row_1 = row - 1;
int col1 = col + 1;
int col_1 = col - 1;
Step[] steps = new Step[] {
new Step(row,col,row1,col1),
new Step(row,col,row1,col_1),
new Step(row,col,row_1,col1),
new Step(row,col,row_1,col_1)
};
if (c == ChessColor.红)
{
for (int i = 0; i < steps.Length; i++)
{
if (steps[i].ToRow < 0 || steps[i].ToRow > 2)
{
continue;
}
if (steps[i].ToCol < 3 || steps[i].ToCol > 5)
{
continue;
}
AddStep(moves, steps[i], c);
}
}
else
{
for (int i = 0; i < steps.Length; i++)
{
if (steps[i].ToRow < 7 || steps[i].ToRow > 9)
{
continue;
}
if (steps[i].ToCol < 3 || steps[i].ToCol > 5)
{
continue;
}
AddStep(moves, steps[i], c);
}
}
}
private void GenerateMove将(int row, int col, ChessColor c, List<Step> moves)
{
Step[] steps = new Step[] {
new Step(row,col, row + 1,col),
new Step(row,col, row - 1,col),
new Step(row,col,row, col + 1),
new Step(row,col,row, col - 1)
};
if (c == ChessColor.红)
{
for (int i = 0; i < steps.Length; i++)
{
if (steps[i].ToRow < 0 || steps[i].ToRow > 2)
{
continue;
}
if (steps[i].ToCol < 3 || steps[i].ToCol > 5)
{
continue;
}
AddStep(moves, steps[i], c);
}
bool face_to_face = false;
int start = row + 1;
for (; start < 10; start++)
{
if (board[start, col].type == ChessType.将)
{
face_to_face = true;
break;
}
else if (board[start, col].type != ChessType.空)
{
break;
}
}
if (face_to_face)
{
AddStep(moves, new Step(row, col, start, col), c);
}
}
else if (c == ChessColor.黑)
{
for (int i = 0; i < steps.Length; i++)
{
if (steps[i].ToRow < 7 || steps[i].ToRow > 9)
{
continue;
}
if (steps[i].ToCol < 3 || steps[i].ToCol > 5)
{
continue;
}
AddStep(moves, steps[i], c);
}
bool face_to_face = false;
int start = row - 1;
for (; start >= 0; start--)
{
if (board[start, col].type == ChessType.将)
{
face_to_face = true;
break;
}
else if (board[start, col].type != ChessType.空)
{
break;
}
}
if (face_to_face)
{
AddStep(moves, new Step(row, col, start, col), c);
}
}
}
private void GenerateMove象(int row, int col, ChessColor c, List<Step> moves)
{
int row1 = row + 1;
int row2 = row + 2;
int row_1 = row - 1;
int row_2 = row - 2;
int col1 = col + 1;
int col2 = col + 2;
int col_1 = col - 1;
int col_2 = col - 2;
if (c == ChessColor.红)
{
if (row_1 >= 0 && col_1 >= 0 && board[row_1, col_1].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row_2, col_2), c);
}
if (row_1 >= 0 && col1 <= 8 && board[row_1, col1].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row_2, col2), c);
}
if (row1 <= 4 && col_1 >= 0 && board[row1, col_1].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row2, col_2), c);
}
if (row1 <= 4 && col1 <= 8 && board[row1, col1].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row2, col2), c);
}
}
else
{
if (row_1 >= 5 && col_1 >= 0 && board[row_1, col_1].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row_2, col_2), c);
}
if (row_1 >= 5 && col1 <= 8 && board[row_1, col1].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row_2, col2), c);
}
if (row1 <= 9 && col_1 >= 0 && board[row1, col_1].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row2, col_2), c);
}
if (row1 <= 9 && col1 <= 8 && board[row1, col1].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row2, col2), c);
}
}
}
private void GenerateMove马(int row, int col, ChessColor c, List<Step> moves)
{
int row1 = row + 1;
int row2 = row + 2;
int row_1 = row - 1;
int row_2 = row - 2;
int col1 = col + 1;
int col2 = col + 2;
int col_1 = col - 1;
int col_2 = col - 2;
if (row_1 >= 0 && board[row_1, col].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row_2, col_1), c);
AddStep(moves, new Step(row, col, row_2, col1), c);
}
if (row1 < 10 && board[row1, col].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row2, col_1), c);
AddStep(moves, new Step(row, col, row2, col1), c);
}
if (col_1 >= 0 && board[row, col_1].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row_1, col_2), c);
AddStep(moves, new Step(row, col, row1, col_2), c);
}
if (col1 < 9 && board[row, col1].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row_1, col2), c);
AddStep(moves, new Step(row, col, row1, col2), c);
}
}
private void GenerateMove车(int row, int col, ChessColor c, List<Step> moves)
{
int temp_row = row;
while (temp_row > 0)
{
temp_row--;
if (board[temp_row, col].type == ChessType.空)
{
AddStep(moves, new Step(row, col, temp_row, col), c);
}
else if (board[temp_row, col].color == c)
{
break;
}
else
{
AddStep(moves, new Step(row, col, temp_row, col), c);
break;
}
}
temp_row = row;
while (temp_row < 9)
{
temp_row++;
if (board[temp_row, col].type == ChessType.空)
{
AddStep(moves, new Step(row, col, temp_row, col), c);
}
else if (board[temp_row, col].color == c)
{
break;
}
else
{
AddStep(moves, new Step(row, col, temp_row, col), c);
break;
}
}
int temp_col = col;
while (temp_col > 0)
{
temp_col--;
if (board[row, temp_col].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row, temp_col), c);
}
else if (board[row, temp_col].color == c)
{
break;
}
else
{
AddStep(moves, new Step(row, col, row, temp_col), c);
break;
}
}
temp_col = col;
while (temp_col < 8)
{
temp_col++;
if (board[row, temp_col].type == ChessType.空)
{
AddStep(moves, new Step(row, col, row, temp_col), c);
}
else if (board[row, temp_col].color == c)
{
break;
}
else
{
AddStep(moves, new Step(row, col, row, temp_col), c);
break;
}
}
}
private void GenerateMove炮(int row, int col, ChessColor c, List<Step> moves)
{
int temp_row = row;
bool hit = false;
while (temp_row > 0)
{
temp_row--;
if (board[temp_row, col].type == ChessType.空)
{
if (!hit)
{
AddStep(moves, new Step(row, col, temp_row, col), c);
}
}
else if (board[temp_row, col].color == c)
{
if (!hit)
{
hit = true;
}
else
{
break;
}
}
else
{
if (!hit)
{
hit = true;
}
else
{
AddStep(moves, new Step(row, col, temp_row, col), c);
break;
}
}
}
temp_row = row;
hit = false;
while (temp_row < 9)
{
temp_row++;
if (board[temp_row, col].type == ChessType.空)
{
if (!hit)
{
AddStep(moves, new Step(row, col, temp_row, col), c);
}
}
else if (board[temp_row, col].color == c)
{
if (!hit)
{
hit = true;
}
else
{
break;
}
}
else
{
if (!hit)
{
hit = true;
}
else
{
AddStep(moves, new Step(row, col, temp_row, col), c);
break;
}
}
}
int temp_col = col;
hit = false;
while (temp_col > 0)
{
temp_col--;
if (board[row, temp_col].type == ChessType.空)
{
if (!hit)
{
AddStep(moves, new Step(row, col, row, temp_col), c);
}
}
else if (board[row, temp_col].color == c)
{
if (!hit)
{
hit = true;
}
else
{
break;
}
}
else
{
if (!hit)
{
hit = true;
}
else
{
AddStep(moves, new Step(row, col, row, temp_col), c);
break;
}
}
}
temp_col = col;
hit = false;
while (temp_col < 8)
{
temp_col++;
if (board[row, temp_col].type == ChessType.空)
{
if (!hit)
{
AddStep(moves, new Step(row, col, row, temp_col), c);
}
}
else if (board[row, temp_col].color == c)
{
if (!hit)
{
hit = true;
}
else
{
break;
}
}
else
{
if (!hit)
{
hit = true;
}
else
{
AddStep(moves, new Step(row, col, row, temp_col), c);
break;
}
}
}
}
private int AdjustRow(ChessColor c, int row)
{
if (c == ChessColor.黑)
{
return Row - 1 - row;
}
return row;
}
private int AdjustCol(ChessColor c, int col)
{
if (c == ChessColor.黑)
{
return Col - 1 - col;
}
return col;
}
private int AdjustChange(ChessColor c)
{
if (c == ChessColor.黑)
{
return -1;
}
return 1;
}
private void AddStep(List<Step> moves, Step step, ChessColor c)
{
if (step.ToRow < 0 || step.ToRow >= Row)
{
return;
}
if (step.ToCol < 0 || step.ToCol >= Col)
{
return;
}
if (board[step.ToRow, step.ToCol].type != ChessType.空 && board[step.ToRow, step.ToCol].color == c)
{
return;
}
moves.Add(step);
}
3.2 走法排序
走法排序的目的是使用历史启发算法。我们先来看一下代码:
protected override void SortMove(List<Step> steps)
{
for (int i = steps.Count - 1; i >= 0; i--)
{
Step step = steps[i];
//杀子优先
switch (board[step.ToRow, step.ToCol].type)
{
case ChessType.卒:
step.value = VALUE卒 * (1 << AlgorithmDepth) / 10;
break;
case ChessType.士:
step.value = VALUE士 * (1 << AlgorithmDepth) / 10;
break;
case ChessType.将:
step.value = VALUE将 * (1 << AlgorithmDepth) / 10;
break;
case ChessType.炮:
step.value = VALUE炮 * (1 << AlgorithmDepth) / 10;
break;
case ChessType.象:
step.value = VALUE象 * (1 << AlgorithmDepth) / 10;
break;
case ChessType.车:
step.value = VALUE车 * (1 << AlgorithmDepth) / 10;
break;
case ChessType.马:
step.value = VALUE马 * (1 << AlgorithmDepth) / 10;
break;
default:
step.value = 0;
break;
}
//历史启发
int from_chess = GetStepNum(step);
if (history_score.ContainsKey(from_chess))
{
step.value += history_score[from_chess];
}
}
steps.Sort();
}
排在前面的走法会被优先考虑,我们一般使用之前出现剪枝的走法。根据本人的经验,杀子的走法一般都是比较好的走法,所以就同时考虑了杀子优先。特别是在开局的时候,杀子优先作用更大。
3.3 估值算法
先看代码:
protected override int Evaluation(ChessColor c, Step[] pre_steps)
{
int score = 0;
for (int i = 0; i < Row; i++)
{
for (int j = 0; j < Col; j++)
{
Chess chess = board[i, j];
if (chess.type != ChessType.空)
{
int temp_score = 0;
List<Step> moves = new List<Step>();
switch (chess.type)
{
case ChessType.卒:
temp_score = VALUE卒;
GenerateMove卒(i, j, chess.color, moves);
temp_score += moves.Count * STEP卒;
break;
case ChessType.炮:
temp_score = VALUE炮;
GenerateMove炮(i, j, chess.color, moves);
temp_score += moves.Count * STEP炮;
break;
case ChessType.马:
temp_score = VALUE马;
GenerateMove马(i, j, chess.color, moves);
temp_score += moves.Count * STEP马;
break;
case ChessType.士:
temp_score = VALUE士;
GenerateMove士(i, j, chess.color, moves);
temp_score += moves.Count * STEP士;
break;
case ChessType.象:
temp_score = VALUE象;
GenerateMove象(i, j, chess.color, moves);
temp_score += moves.Count * STEP象;
break;
case ChessType.车:
temp_score = VALUE车;
GenerateMove车(i, j, chess.color, moves);
temp_score += moves.Count * STEP车;
break;
case ChessType.将:
temp_score = VALUE将;
break;
}
if (chess.color == c)
{
score += temp_score;
}
else
{
score -= temp_score;
}
}
}
}
return score;
}
在《PC游戏编程(人机博弈)》一书里,估值考虑了三种情况。一是每种棋子的价值,二是每种棋子的灵活性,三是棋子是否受到了威胁。前面两种情况计算还是比较快的,第三种情况则较慢(测试得出)。我认为,其实这一局的收到威胁,就是下一局的杀子。多一层,其实就是考虑了威胁,所以不必考虑威胁这种情况。上面的代码就是只考虑棋子价值和灵活性。在这样的情况下,我的电脑可以运行6层,而如果加上威胁检测,就只能运行5层了。