I'm trying to program some AI for a game of checkers. My program is saying there are 0 moves for the white player, even though I know there are. The GetValidMoves()
function is tested, and works in other areas of the code.
To try and isolate the program I saved out the problematic board-state then loaded it back up to see if I would get the same problem:
using(Stream s = File.Open("board.dat", FileMode.Create))
{
var bf = new BinaryFormatter();
bf.Serialize(s, board);
}
Debug.WriteLine(board.GetValidMoves(Color.White).Count());
using (Stream s = File.Open("board.dat", FileMode.Open))
{
var bf = new BinaryFormatter();
board = (Board)bf.Deserialize(s);
}
Debug.WriteLine(board.GetValidMoves(Color.White).Count());
This prints:
0
7
When I would expect the output to be the same (7 is correct).
What could cause this to start working after deserialization? Both instances of the board appear to be exactly the same... I printed out all the properties and they all same correct. I'm not sure where to go from here?
The first instance of the board (before deserialization) is the result of a clone. Could I be cloning it wrong? Are there "dangling references"?
GetValidMoves:
public IEnumerable<Move> GetValidMoves(Color c)
{
var jumps = GetJumps(c);
if (jumps.Any())
foreach (var j in jumps)
yield return j;
else
foreach (var s in GetSlides(c))
yield return s;
}
public IEnumerable<Move> GetSlides(Color c)
{
foreach (int i in Enumerate(c))
foreach (var s in GetSlides(c, i))
yield return s;
}
public IEnumerable<Move> GetJumps(Color c)
{
foreach (int i in Enumerate(c))
foreach (var j in GetJumps(c, i))
yield return j;
}
public IEnumerable<Move> GetJumps(Color c, int i)
{
Checker checker = this[c, i] as Checker;
bool indentedRow = i % Width < rowWidth;
int column = i % rowWidth;
int offset = indentedRow ? 0 : -1;
bool againstLeft = column == 0;
bool againstRight = column == rowWidth - 1;
int moveSW = i + rowWidth + offset;
int moveSE = moveSW + 1;
int jumpSW = i + rowWidth * 2 - 1;
int jumpSE = jumpSW + 2;
if (!againstLeft && jumpSW < Count && IsEnemy(c, moveSW) && IsEmpty(c, jumpSW))
yield return new Move(c, i, jumpSW, jump: true, crown: IsCrowned(checker, jumpSW));
if (!againstRight && jumpSE < Count && IsEnemy(c, moveSE) && IsEmpty(c, jumpSE))
yield return new Move(c, i, jumpSE, jump: true, crown: IsCrowned(checker, jumpSE));
if (checker.Class == Class.King)
{
int moveNW = i - rowWidth + offset;
int moveNE = moveNW + 1;
int jumpNW = i - rowWidth * 2 - 1;
int jumpNE = jumpNW + 2;
if (!againstLeft && jumpNW >= 0 && IsEnemy(c, moveNW) && IsEmpty(c, jumpNW))
yield return new Move(c, i, jumpNW, jump: true);
if (!againstRight && jumpNE >= 0 && IsEnemy(c, moveNE) && IsEmpty(c, jumpNE))
yield return new Move(c, i, jumpNE, jump: true);
}
}
public IEnumerable<Move> GetSlides(Color c, int i)
{
Checker checker = this[c, i] as Checker;
bool indentedRow = i % Width < rowWidth;
int column = i % rowWidth;
int offset = indentedRow ? 0 : -1;
bool againstLeft = !indentedRow && column == 0;
bool againstRight = indentedRow && column == rowWidth - 1;
int moveSW = i + rowWidth + offset;
int moveSE = moveSW + 1;
if (!againstLeft && moveSW < Count && IsEmpty(c, moveSW))
yield return new Move(c, i, moveSW, crown: IsCrowned(checker, moveSW));
if (!againstRight && moveSE < Count && IsEmpty(c, moveSE))
yield return new Move(c, i, moveSE, crown: IsCrowned(checker, moveSE));
if (checker.Class == Class.King)
{
int moveNW = i - rowWidth + offset;
int moveNE = moveNW + 1;
if (!againstLeft && moveNW >= 0 && IsEmpty(c, moveNW))
yield return new Move(c, i, moveNW, crown: IsCrowned(checker, moveNW));
if (!againstRight && moveNE >= 0 && IsEmpty(c, moveNE))
yield return new Move(c, i, moveNE, crown: IsCrowned(checker, moveNE));
}
}
It shouldn't have side effects.
To answer your queries about whether valid moves is inadvertently changing the board state:
var board = new Board(8, 8);
board.SetUp();
foreach(var m in board.GetValidMoves(Color.White))
Console.WriteLine(m);
Console.WriteLine("---");
foreach(var m in board.GetValidMoves(Color.White))
Console.WriteLine(m);
Prints:
8-12
8-13
9-13
9-14
10-14
10-15
11-15
---
8-12
8-13
9-13
9-14
10-14
10-15
11-15
(The same output twice) As you'd expect.