views:

108

answers:

2

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.

+1  A: 

Just a wild guess, but did you make sure there are absolutely no side effects from calling GetValidMoves?

Since you are serializing and deserializing the board after calling GetValidMoves, it appears that GetValidMoves changes the board in some way (which seems a bit odd to me given the function's name). So perhaps there are also other side-effects you did not take into consideration.

Adrian Grigore
I don't see anything in there that modifies the board state... it just yields a bunch of `Move` objects.
Mark
In that case, I see no reason to serialize / deserialize the board. Try calling GetValidMoves twice, one call immediately after another and see what output that gives you. If it's the same output, then the problem is in your serialization / deserialization code. Otherwise you have some side-effects.
Adrian Grigore
A: 

Pretty sure the bug was in the Board.Clone method actually. I think serializing/deserializing created brand new objects whereas my clone method wasn't cloning everything correctly, but rather returning references.

See http://stackoverflow.com/questions/3647983/how-to-clone-an-inherited-object for details.

Mark