什么会导致一段时间后开始计算错误?

发布于 2024-09-18 10:44:48 字数 3147 浏览 5 评论 0原文

我正在尝试为跳棋游戏实现 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是什么......也许它会提供更多线索!我感觉自己像个侦探。

I'm trying to implement NegaMax for a game of checkers. I'm just testing it with a depth of 0 right now, meaning the current player just evaluates all his moves without regard to what the other player might do next. It works perfectly for about half the game (computes the scores correctly), and then part way through it starts spitting out non-sense answers.

For example, White might have 1 piece left, and Black will have 5, but it will evaluate White's moves as a score of 7 for example, when they should all be negative because he's losing. Black might about to win on his next move, but it will evaluate the winning move as a -4 even though it should be 1000.

I can understand it consistently outputting garbage, but why would it work for the first few turns and then start messing up?

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));
}

I've isolated a board state that it doesn't like, on a 6x6 board:

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

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

It appears that this is not a time or number-of-moves played issue, it just doesn't like particular board states. I don't see anything peculiar about this state though.

In this state, it evaluates all 4 of Black's moves as -13. If you look at how I did the scoring, it says 2 pts per man, 3 pts per king, negative if owned by the other player. It looks as though it is treating all the pieces as white...that's the only way to get 13.


I found another clue. In the board score method I got it to print what it's seeing.. this is what it's telling me:

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

Where the board squares are numbered like this:

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

I think it is indeed saying the black piece is white...... now to figure out what's causing this.


So... now I know that the color is wrong, but only to the BoardScore function. My normal display routine never picked up on this otherwise I would have figured out the problem hours ago. I'm thinking it might be in the ApplyMove function that the color gets switched..

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;
}

But that doesn't make a lot of sense either... the piece is just copied from the start position to the end position. Need to investigate what m.Color is... perhaps it'll provide more clues! I feel like a detective.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

那请放手 2024-09-25 10:44:48

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

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

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

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

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

再次编辑:那里有两个调用——它是否正确克隆?

Given your description I find myself suspecting the piece color data. If it were somehow set to something wrong it would evaluate everything as negative.

I find myself not too happy with your BoardScore function--complex formulas like that are good at hiding errors and are hard to debug.

I would add a function Checker.Value(Color) to simplify BoardScore and allow you to view what was going on more easily.

You haven't shown the datatype of Color, if it permits more than black and white a corrupted value there would cause the behavior you are observing.

Given your latest update I would look at boardAfterMove and make sure it's being generated correctly.

Edit again: There are two calls there--is it cloning properly?

夏末 2024-09-25 10:44:48

发现问题了。

        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;
            }
        }

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

Found the problem.

        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;
            }
        }

Only happens with Black Kings. Stupid cloning!! Why couldn't deep-copying be easier?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文