-2

I am trying to write AI for computer player using MinMax in checkers game and i have strange problem. I have that dictionary:

Dictionary<int, Field> gameBoard;

Look at the methods ApplyMove() and RevertMove()

public int MinMax(Dictionary<int, Field> gameBoard, string color, Boolean maximizingPlayer, int depth)
{
    int bestValue;
    if (0 == depth)
        return ((color == myColor) ? 1 : -1) * evaluateGameBoard(gameBoard, color);

    int val;
    if (maximizingPlayer)
    {
        bestValue = int.MinValue;
        foreach (Move move in GetPossibleMoves(gameBoard, color))
        {
            gameBoard = ApplyMove(gameBoard, move);
            val = MinMax(gameBoard, color, false, depth - 1);
            bestValue = Math.Max(bestValue, val);
            gameBoard = RevertMove(gameBoard, move);
        }
        return bestValue;
    }
    else
    {
        bestValue = int.MaxValue;
        foreach (Move move in GetPossibleMoves(gameBoard, Extend.GetEnemyPlayerColor(color)))
        {
            gameBoard = ApplyMove(gameBoard, move);
            val = MinMax(gameBoard, color, true, depth - 1);
            bestValue = Math.Min(bestValue, val);
            gameBoard = RevertMove(gameBoard, move);
        }
        return bestValue;
    }
}

Method ApplyMove() working well, ApplyMove() and RevertMove() both of theme returning gameBoard.

ApplyMove()

public Dictionary<int, Field> ApplyMove(Dictionary<int, Field> gameBoard, Move move)
{
    gameBoard_old = Extend.CloneGameBoard(gameBoard);

    gameBoard = UpdateFieldTo(gameBoard, move);
    if (move.indexThrough == 0)
        gameBoard = UpdateFieldThrough(gameBoard, move);
    gameBoard = UpdateFieldFrom(gameBoard, move);
    return gameBoard;
}

RevertMove()

public Dictionary<int, Field> RevertMove(Dictionary<int, Field> gameBoard, Move move)
{
    gameBoard[move.indexFrom] = gameBoard_old[move.indexFrom];
    if (move.indexThrough != 0)
    {
        gameBoard[move.indexThrough] = gameBoard_old[move.indexThrough];
    }
    gameBoard[move.indexTo] = gameBoard_old[move.indexTo];
    return gameBoard;
}

The problem is here in RevertMove():

return gameBoard; ---> value is correct

but when that lane is executed in MinMax():

gameBoard = RevertMove(gameBoard, move);

there is 0 effect... GameBoard ignore that value from RevertMove() and keep holding old values.
I dont know what i am doing wrong... I suspect some problems with the reference. Is that operator "=" is bad way to change dictionary values? I have no idea whats going on... Once time this works well, another don't. Please don't blame me, i am newbie in c#


edit(1)

public static Dictionary<int, Field> CloneGameBoard(Dictionary<int, Field> gameBoard)
{
    string json = Newtonsoft.Json.JsonConvert.SerializeObject(gameBoard);
    return Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<int, Field>>(json);
}
Brarord
  • 611
  • 5
  • 18
  • 1
    Are you doing a deep copy or a shallow copy in CloneGameBoard? – Shaiar Jul 18 '20 at 21:41
  • I am not sure, i just copy paste that from stack. Check last edit – Brarord Jul 18 '20 at 21:42
  • Ciao, may I suggest you to change CloneGameBoard in this way: `return new Dictionary(gameBoard)`? Just to be sure that you are doing a deep copy of the dictionary. – Giovanni Esposito Jul 18 '20 at 21:48
  • Could you show me a little example? I am not sure what you mean. – Brarord Jul 18 '20 at 21:50
  • 1
    It's fine, the serialize/deserialize is a deep copy. However your assign operations in RevertMode are not, so when you do gameBoard[move.indexFrom] = gameBoard_old[move.indexFrom], you copy the reference and now both gameboard and gameboard_old point to the same thing. Initially that would work, but if you change something to one, you modify the other. That might be what you're seeing here. – Shaiar Jul 18 '20 at 21:56
  • Wow it is totally strange to me. Why is this that complicated... So should i deep copy fields from gameBoard too? – Brarord Jul 18 '20 at 22:00
  • @GiovanniEsposito Careful, your example does a shallow copy – Shaiar Jul 18 '20 at 22:02
  • Ciao @Shaiar, I don't think so. I made my deep copy by using my code. Note: I'm on .NET 4.6.1 – Giovanni Esposito Jul 18 '20 at 22:06
  • @Brarord I'm not sure what indexThrough means in your context, but you revert when it's not 0 while you applied when it was 0, shouldn't they both share the same condition ? – Shaiar Jul 18 '20 at 22:16
  • indexThrough mean "a piece that was beaten", sometimes move is not a beat so i dont need to revert that parameter all the time. – Brarord Jul 18 '20 at 22:20
  • But then in ApplyMove, shouldn't if (move.indexThrough == 0) be instead != 0 ? – Shaiar Jul 18 '20 at 22:36
  • Unrelated, you do not need a return value in your methods. As you return the same instance that was passed, you could as well write public void ApplyMove(Dictionary gameBoard, Move move) and use it as ApplyMove(gameboard, Move) – Shaiar Jul 18 '20 at 22:37
  • 1
    @GiovanniEsposito https://dotnetfiddle.net/EZEcli – Shaiar Jul 18 '20 at 22:46
  • 1
    Yeee that Shaiar, that was wrong :D Thanks for that! – Brarord Jul 18 '20 at 22:56
  • 1
    Now, you do not need to revert at all though, I'm going to post a code example where you create temporary board for evaluation but do not keep it outside of your evaluation loop. That's simpler and will help you go deeper than one level without confusing yourself :) – Shaiar Jul 18 '20 at 23:00
  • Glad you're back on track. Sorry it took so long : when you wrote you suspected a reference issue, I went on the wrong hypothesis of checking deep/shallow copy. It was actually not related at all – Shaiar Jul 18 '20 at 23:19
  • 1
    Well well well, you're right @Shaiar! It's always good to learn something new. Thanks! – Giovanni Esposito Jul 18 '20 at 23:31
  • You've already asked the same question [How to properly copy Dictionary and do NOT copy the reference?](https://stackoverflow.com/questions/62973088/how-to-properly-copy-dictionary-and-do-not-copy-the-reference) – Pavel Anikhouski Jul 19 '20 at 08:53

1 Answers1

1

Example of how to not need a revert at all:

    public int MinMax(Dictionary<int, Field> gameBoard, string color, Boolean maximizingPlayer, int depth)
{
    int bestValue;
    if (0 == depth)
        return ((color == myColor) ? 1 : -1) * evaluateGameBoard(gameBoard, color);

    int val;
    if (maximizingPlayer)
    {
        bestValue = int.MinValue;
        foreach (Move move in GetPossibleMoves(gameBoard, color))
        {
            var movedGameBoard = ApplyTentativeMove(gameBoard, move);
            val = MinMax(movedGameBoard, color, false, depth - 1);
            bestValue = Math.Max(bestValue, val);
        }
        return bestValue;
    }
    else
    {
        bestValue = int.MaxValue;
        foreach (Move move in GetPossibleMoves(gameBoard, Extend.GetEnemyPlayerColor(color)))
        {
            var movedGameBoard = ApplyTentativeMove(gameBoard, move);
            val = MinMax(movedGameBoard, color, true, depth - 1);
            bestValue = Math.Min(bestValue, val);
        }
        return bestValue;
    }
}

public void ApplyMove(Dictionary<int, Field> gameBoard, Move move)
{
    UpdateFieldTo(gameBoard, move);
    if (move.indexThrough != 0)
        UpdateFieldThrough(gameBoard, move);
    UpdateFieldFrom(gameBoard, move);
}

public Dictionary<int, Field> ApplyTentativeMove(Dictionary<int, Field> gameboard, Move move)
{
    var gameBoardAfterMove = Extend.CloneGameBoard(gameBoard);
    ApplyMove(gameBoardAfterMove, move);
    return gameBoardAfterMove;
}

public void ApplyMove is the function to modify the actual board and this is the one you would use in the game itself.

public Dictionary<int, Field> ApplyTentativeMove is a function to create a new board with that move. This temporary board will be discarded after you move on to the next loop step.

Shaiar
  • 91
  • 7
  • 1
    Edited to fix the wrong variable in the else clause, sorry about that, I typed that code outside of compiler as I do not have your functions. – Shaiar Jul 18 '20 at 23:16
  • Thanks a lot of man :D You saved me. I will try to refactor my code to your tommorow in order to NOT reverting that – Brarord Jul 18 '20 at 23:22