我正在尝试为跳棋游戏实现 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));}
我已经分离出一个它不喜欢的棋盘状态,在一个 6×6 的棋盘上:
. . .. 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 0203 04 05 06 07 0809 10 11 12 13 1415 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
是什么……也许它会提供更多线索!我感觉自己像个侦探。
回答:
根据您的描述,我怀疑是棋子的颜色数据出了问题。如果它以某种方式被设置为错误的值,它会将所有内容评估为负值。
我对您的 BoardScore 函数不太满意——像这样复杂的公式很擅长隐藏错误,而且很难调试。
我建议添加一个 Checker.Value(Color) 函数来简化 BoardScore,并允许您更轻松地查看正在发生的事情。
您没有展示 Color 的数据类型,如果它允许超过黑白两种颜色,那么那里的损坏值会导致您观察到的行为。
根据您最新的更新,我建议查看 boardAfterMove 并确保它被正确生成。
再次编辑:那里有两个调用——克隆是否正确?