象棋和五子棋AI的开发(二)

二、基类

这是两种棋子算法共有的部分。

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函数里面,我加入了对整个走步过程的记录,这主要在打印路线和五子棋估值的时候使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值