二、基类
这是两种棋子算法共有的部分。
-
public
abstract
class
IGame<
T>
-
{
-
/// <summary>
-
/// 伪无限大
-
/// </summary>
-
protected
const
int MAX_VALUE =
100000000;
-
/// <summary>
-
/// 伪无限小
-
/// </summary>
-
protected
const
int MIN_VALUE =
-100000000;
-
-
protected
int _row;
-
/// <summary>
-
/// 获得行数
-
/// </summary>
-
public
int Row
-
{
-
get
-
{
-
return _row;
-
}
-
}
-
-
protected
int _col;
-
/// <summary>
-
/// 获得列数
-
/// </summary>
-
public
int Col
-
{
-
get
-
{
-
return _col;
-
}
-
}
-
-
protected ChessColor wincolor;
-
/// <summary>
-
/// 胜利一方,必须在GameOver为true时调用
-
/// </summary>
-
public ChessColor WinColor
-
{
-
get
-
{
-
return wincolor;
-
}
-
}
-
-
protected Chess[,] board;
-
-
/// <summary>
-
/// 刚刚移动的棋子颜色
-
/// </summary>
-
public ChessColor MoveColor;
-
-
/// <summary>
-
/// 剩余棋子数
-
/// </summary>
-
public
int RemainChess;
-
-
/// <summary>
-
/// 算法深度
-
/// </summary>
-
protected
int AlgorithmDepth;
-
-
/// <summary>
-
/// 最优走步
-
/// </summary>
-
protected T BestStep;
-
-
/// <summary>
-
/// 历史走步分数
-
/// </summary>
-
protected Dictionary<
int,
int> history_score;
-
-
/// <summary>
-
/// 获得棋子
-
/// </summary>
-
public Chess Chess(int row, int col)
-
{
-
return board[row, col];
-
}
-
-
/// <summary>
-
/// 初始化
-
/// </summary>
-
public abstract void Init();
-
-
/// <summary>
-
/// 是否游戏结束
-
/// </summary>
-
/// <param name="pre_steps">历史走步</param>
-
public abstract bool GameOver(T[] pre_steps);
-
-
/// <summary>
-
/// 移动一步
-
/// </summary>
-
/// <returns>是否移动成功</returns>
-
public abstract bool Move(T step);
-
-
/// <summary>
-
/// 产生所有可以移动的步
-
/// </summary>
-
/// <param name="c">要移动的颜色</param>
-
/// <returns>所以可移动走步列表</returns>
-
protected abstract List<T> GenerateAllMove(ChessColor c);
-
-
#region 算法
-
-
/// <summary>
-
/// 计算最优走步
-
/// </summary>
-
/// <param name="c">要移动的颜色</param>
-
/// <param name="max_depth">最深深度</param>
-
/// <returns>最优走步</returns>
-
public T CalcBestMove(ChessColor c, int max_depth, bool clear_history)
-
{
-
if (clear_history)
-
{
-
history_score =
new Dictionary<
int,
int>();
-
}
-
else
-
{
-
if (history_score ==
null)
-
{
-
history_score =
new Dictionary<
int,
int>();
-
}
-
}
-
-
AlgorithmDepth = max_depth;
-
AlphaBeta(c, AlgorithmDepth, MIN_VALUE, MAX_VALUE,
null);
-
-
return BestStep;
-
}
-
-
/// <summary>
-
/// Alpha-Beta剪枝算法
-
/// </summary>
-
/// <param name="c">棋子颜色</param>
-
/// <param name="depth">深度</param>
-
/// <param name="alpha">alpha</param>
-
/// <param name="beta">beta</param>
-
/// <param name="pre_steps">历史走步</param>
-
/// <returns>当前节点的最高值</returns>
-
protected int AlphaBeta(ChessColor c, int depth, int alpha, int beta, T[] pre_steps)
-
{
-
if (depth ==
0 || GameOver(pre_steps))
-
{
-
return Evaluation(c, pre_steps);
-
}
-
-
List<T> steps = GenerateAllMove(c);
-
SortMove(steps);
-
T current_best_step =
default(T);
-
foreach (T step
in steps)
-
{
-
object obj = MakeMove(step);
-
-
T[] new_steps =
null;
-
if (pre_steps ==
null)
-
{
-
new_steps =
new T[
1];
-
}
-
else
-
{
-
new_steps =
new T[pre_steps.Length +
1];
-
Array.Copy(pre_steps, new_steps, pre_steps.Length);
-
}
-
new_steps[new_steps.Length -
1] = step;
-
-
int score = -AlphaBeta(SwitchColor(c), depth -
1, -beta, -alpha, new_steps);
-
UnmakeMove(step, obj);
-
-
if (score > alpha)
-
{
-
alpha = score;
-
-
if (depth == AlgorithmDepth)
-
{
-
BestStep = step;
-
}
-
-
current_best_step = step;
-
-
if (alpha >= beta)
-
{
-
break;
-
}
-
}
-
}
-
-
if (current_best_step !=
null)
-
{
-
int from_chess = GetStepNum(current_best_step);
-
if (history_score.ContainsKey(from_chess))
-
{
-
history_score[from_chess] += (
1 << depth);
-
}
-
else
-
{
-
history_score.Add(from_chess, (
1 << depth));
-
}
-
}
-
-
return alpha;
-
}
-
-
/// <summary>
-
/// 进走步进行排序
-
/// </summary>
-
protected abstract void SortMove(List<T> steps);
-
-
/// <summary>
-
/// 估值
-
/// </summary>
-
/// <param name="c">要估值的颜色</param>
-
/// <param name="pre_steps">历史走步</param>
-
/// <returns>分数</returns>
-
protected abstract int Evaluation(ChessColor c, T[] pre_steps);
-
-
/// <summary>
-
/// 虚拟走一步
-
/// </summary>
-
protected abstract object MakeMove(T step);
-
-
/// <summary>
-
/// 撤销走一步
-
/// </summary>
-
protected abstract void UnmakeMove(T step, object obj);
-
-
/// <summary>
-
/// 相反颜色
-
/// </summary>
-
protected abstract ChessColor SwitchColor(ChessColor c);
-
-
/// <summary>
-
/// 把走步转成一个数
-
/// </summary>
-
protected abstract int GetStepNum(T step);
-
-
#endregion
-
}
在基类里面,或者需要我们注意的只有CalcBestMove和AlphaBeta两个函数。这两个函数在《PC游戏编程(人机博弈)》一书中都有详细讲解,我这里只说明不一样的地方。
在说明历史启发这一算法时,《PC游戏编程(人机博弈)》认为只需要标记起始位置和结束位置。但我认为起始位置是什么棋子也是有必要记录的。历史记录存放在一个字典里面,在象棋里面,键值的计算如下:
-
protected override int GetStepNum(Step step)
-
{
-
return step.ToRow *
100000 + step.ToCol *
10000 + step.FromRow *
1000 + step.FromCol *
100 + (
int)board[step.FromRow, step.FromCol].color *
10 + (
int)board[step.FromRow, step.FromCol].type;
-
}
而在五子棋里面,键值计算如下:
-
protected override int GetStepNum(Put step)
-
{
-
return step.ToRow *
1000 + step.ToCol *
10 + (
int)step.Color;
-
}
我们可以看到,象棋的键值可能性明显更多,而且随着游戏的进展,某些移动位置的优势参考价值已不大。所以在象棋里面,每一次计算最优走步,历史启发数据都全部清空。
但五子棋就不一样了。五子棋的键值明显只有225种,而且越到后面会越少。所以我们在整个游戏里面,都不清空五子棋的历史启发数据。
在AlphaBeta函数里面,我加入了对整个走步过程的记录,这主要在打印路线和五子棋估值的时候使用。