3

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.

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 1
    Could you show the Board class along with the `GetValidMoves` function so that we can reproduce the behavior? – Darin Dimitrov Sep 05 '10 at 21:46
  • Does `board.GetValidMoves` have side effects? That is, if you call it, does it change the state of `board`? – Oded Sep 05 '10 at 21:47
  • 1
    What happens if you call the `GetValidMoves` consecutively, leaving the serialization/deserialization aside? As other have suggested it seems that this function modifies the state of the object. – Darin Dimitrov Sep 05 '10 at 21:51
  • I think the state part of `Board` might be relevant as well. Also, can you try Darins suggestion? 2 calls w/o the serizalization. – H H Sep 05 '10 at 22:16
  • As this suggestion is a wild guess - it looks as if the serializing of the board clobbered some internal state between the serializing and the deserializing... have you tried changing the name of variables in the serializing and deserializing to be distinctive? – t0mm13b Sep 05 '10 at 23:18

2 Answers2

1

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
  • 33,034
  • 36
  • 130
  • 210
  • I don't see anything in there that modifies the board state... it just yields a bunch of `Move` objects. – mpen Sep 05 '10 at 22:00
  • 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 Sep 05 '10 at 22:06
-1

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 How to clone an inherited object? for details.

Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • I've downvoted you here because, for the code shown, it doesn't make sense that object identity is an issue, especially given that what is serialized _is_ deserialized "correctly". The one piece of code you're not showing us that could cause the issues you've seen is `Enumerate`. – Mark Hurd Sep 26 '16 at 02:14
  • @MarkHurd You don't think it was a deep-cloning issue? You might be right but I can't say for sure; this was 6 years ago now. – mpen Sep 26 '16 at 17:20
  • None of the code you've shown us in this question actually cares about object identity, it only compares values. – Mark Hurd Sep 27 '16 at 01:34