问题
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.
回答1:
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?
回答2:
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?
来源:https://stackoverflow.com/questions/3653670/what-could-cause-this-to-start-miscalculating-after-awhile