1

我正在尝试为跳棋游戏实现NegaMax。我现在只是在深度为 0 的情况下对其进行测试,这意味着当前玩家只是评估他的所有动作,而不考虑其他玩家接下来可能会做什么。它在大约一半的游戏中完美运行(正确计算分数),然后在它的中途开始吐出无意义的答案。

例如,白方可能还剩 1 件,而黑方将有 5 件,但它会将白方的走法评估为 7 分,例如,当白方的棋步都应该为负时,因为他输了。黑方下一步可能会获胜,但它会将获胜的一步评估为 -4,即使它应该是 1000。

我可以理解它一直在输出垃圾,但为什么它会在前几轮工作,然后开始搞砸?

private static Move GetBestMove(Color color, Board board, int depth)
{
    var bestMoves = new List<Move>();
    IEnumerable<Move> validMoves = board.GetValidMoves(color);
    int highestScore = int.MinValue;
    Board boardAfterMove;
    int tmpScore;
    var rand = new Random();

    Debug.WriteLine("{0}'s Moves:", color);

    foreach (Move move in validMoves)
    {
        boardAfterMove = board.Clone().ApplyMove(move);

        if (move.IsJump && !move.IsCrowned && boardAfterMove.GetJumps(color).Any())
            tmpScore = NegaMax(color, boardAfterMove, depth);
        else
            tmpScore = -NegaMax(Board.Opposite(color), boardAfterMove, depth);

        Debug.WriteLine("{0}: {1}", move, tmpScore);

        if (tmpScore > highestScore)
        {
            bestMoves.Clear();
            bestMoves.Add(move);
            highestScore = tmpScore;
        }
        else if (tmpScore == highestScore)
        {
            bestMoves.Add(move);
        }
    }

    return bestMoves[rand.Next(bestMoves.Count)];
}

private static int NegaMax(Color color, Board board, int depth)
{
    return BoardScore(color, board);
}

private static int BoardScore(Color color, Board board)
{
    if (!board.GetValidMoves(color).Any()) return -1000;
    return board.OfType<Checker>().Sum(c => (c.Color == color ? 1 : -1) * (c.Class == Class.Man ? 2 : 3));
}

我在 6x6 板上隔离了它不喜欢的板状态:

 . . .
. w B 
 W . .
. . . 
 . w .
. . W 

w = white, b = black, capital letter = king

看起来这不是时间或移动次数的问题,它只是不喜欢特定的棋盘状态。不过,我看不出这种状态有什么特别之处。

在这种状态下,它将黑方的所有 4 步棋评估为 -13。如果你看看我是如何得分的,它说每人 2 分,每个国王 3 分,如果由其他玩家拥有,则为负数。看起来好像它把所有的碎片都当作白色......这是获得 13 的唯一方法。


我发现了另一个线索。在棋盘评分方法中,我让它打印出它所看到的......这就是它告诉我的:

2: White 
4: White 
6: White 
13: White 
17: White 

棋盘格的编号如下:

  00  01  02
03  04  05
  06  07  08
09  10  11
  12  13  14
15  16  17

我认为这确实是在说黑色的部分是白色的……现在要弄清楚是什么原因造成的。


所以......现在我知道颜色是错误的,但仅限于BoardScore功能。我的正常显示程序从未对此有所了解,否则我会在数小时前发现问题。我在想它可能是在ApplyMove颜色被切换的功能中..

public Board ApplyMove(Move m)
{
    if (m.IsJump)
    {
        bool indented = m.Start % Width < _rowWidth;
        int offset = indented ? 1 : 0;
        int enemy = (m.Start + m.End) / 2 + offset;
        this[m.Color, enemy] = Tile.Empty;
    }

    this[m.Color, m.End] = this[m.Color, m.Start];
    this[m.Color, m.Start] = Tile.Empty;

    var checker = this[m.Color, m.End] as Checker;
    if (m.IsCrowned) checker.Class = Class.King;

    return this;
}

但这也没有多大意义……这件作品只是从开始位置复制到结束位置。需要调查什么m.Color是......也许它会提供更多线索!我觉得自己像个侦探。

4

2 回答 2

2

鉴于您的描述,我发现自己怀疑这件作品的颜色数据。如果它以某种方式设置为错误的,它会将所有内容评估为负面。

我发现自己对您的 BoardScore 函数不太满意——像这样的复杂公式擅长隐藏错误并且难以调试。

我会添加一个函数 Checker.Value(Color) 来简化 BoardScore 并让您更轻松地查看正在发生的事情。

您没有显示 Color 的数据类型,如果它允许超过黑色和白色的损坏值,则会导致您正在观察的行为。

鉴于您的最新更新,我会查看 boardAfterMove 并确保其生成正确。

再次编辑:那里有两个电话 - 是否正确克隆?

于 2010-09-06T19:22:03.677 回答
0

发现了问题。

        foreach (char ch in checkers)
        {
            switch (ch)
            {
                case 'w':
                    Add(new Checker(Color.White, Class.Man));
                    break;
                case 'W':
                    Add(new Checker(Color.White, Class.King));
                    break;
                case 'b':
                    Add(new Checker(Color.Black, Class.Man));
                    break;
                case 'B':
                    Add(new Checker(Color.White, Class.King));
                    break;
                default:
                    Add(Tile.Empty);
                    break;
            }
        }

只发生在黑王身上。愚蠢的克隆!!为什么深度复制不能更容易?

于 2010-09-06T19:36:28.600 回答