1

I have been working on this program for a good hour or two now and every time I attempt to debug it visual studio is deciding to change the values in currentGuess.guessLine and I have no idea why. They're not being passed by reference any where and the only time they should change is under the comment 'Get guess from user'. Could someone please help as it's driving me crazy? I have provided the code below: (I have removed any completely irrelevant methods to make it shorter.

I am using visual studio professional and I have re-written this code 3 times now and I still cannot figure out why it is happening. Any help would be greatly appreciated.

struct History
    {
        public Guess[] previousGuesses;
        public int noOfPreviousGuesses;
    }
    struct Guess
    {
        public int[] guessLine;
        public Hint givenHint;
    }
    struct Hint
    {
        public int blackPegs;
        public int whitePegs;
    }

    static void Main(string[] args)
    {
        Mastermind(3, 3);
    }

    static void Mastermind(int N, int M)
    {
        bool gameFinished;
        int playAgain;
        History gameHistory;
        Guess currentGuess;
        int[] secretCode;

        do
        {
            //Game start
            gameFinished = false;

            //Reset history
            gameHistory.previousGuesses = new Guess[0];
            gameHistory.noOfPreviousGuesses = 0;

            //Generate secretCode
            secretCode = GenerateSecretCode(N, M);

            do
            {
                //Get guess from user
                currentGuess.guessLine = GetGuessLine(N, M);

                //Evaluate guess
                gameFinished = EvaluateGuess(currentGuess.guessLine, secretCode, N);

                if (gameFinished == false)
                {
                    //Generate hint
                    currentGuess.givenHint = GenerateHint(currentGuess.guessLine, secretCode, N);

                    //Add guess to history
                    gameHistory = AddGuessToHistoryQueue(currentGuess, gameHistory);

                    //Output history
                    OutputHistory(gameHistory, N);
                }

            } while (gameFinished == false);

            //Ask to play again
            playAgain = GetValueFromUser("Enter 0 or a positive value to play again, otherwise enter a negative value: ");

        } while (playAgain >= 0);
    }

    /// <summary>
    /// Gets a guess line from the user.
    /// Validates each value using above procedure.
    /// </summary>
    /// <param name="codeLength">The length of the code being used.</param>
    /// <param name="noOfColours">The number of colours allowed.</param>
    /// <returns>The line entered.</returns>
    static int[] GetGuessLine(int codeLength, int noOfColours)
    {
        int[] guessLine;

        guessLine = new int[codeLength];

        for (int count = 0; count < codeLength; count++)
        {
            //Get guessLine[count] from user
            guessLine[count] = GetValueFromUserInRange(1, noOfColours, "Please enter guess at position " + count + ": ");
        }

        return guessLine;
    }

    /// <summary>
    /// Compares the given guess to the given code.
    /// Returns true if guess and code match exactly otherwise
    /// returns false.
    /// </summary>
    /// <param name="guess">The guess being compared.</param>
    /// <param name="code">The code to be compared against.</param>
    /// <param name="codeLength">The length of the code and guess.</param>
    /// <returns></returns>
    static bool EvaluateGuess(int[] guess, int[] code, int codeLength)
    {
        bool correctGuess;

        correctGuess = true;

        for (int count = 0; count < codeLength; count++)
        {
            if (guess[count] != code[count])
            {
                //Found inconsistency
                correctGuess = false;
                break;
            }
        }

        return correctGuess;
    }

    /// <summary>
    /// Generates a hint through finding all matching values,
    /// changing their values and incrementing the black pegs total.
    /// Then calculates white pegs by checking for matching values again.
    /// </summary>
    /// <param name="guess">The guess requiring a hint.</param>
    /// <param name="code">The code for the guess to be compared to.</param>
    /// <param name="codeLength">The length of the code/guess.</param>
    /// <returns>The hint generated.</returns>
    static Hint GenerateHint(int[] guess, int[] code, int codeLength)
    {
        Hint newHint;

        newHint.blackPegs = 0;
        newHint.whitePegs = 0;

        //Calculate blackPegs
        for (int count = 0; count < codeLength; count++)
        {
            if (guess[count] == code[count])
            {
                newHint.blackPegs = newHint.blackPegs + 1;

                //Hide values
                guess[count] = 0;
                code[count] = 0;
            }
        }

        //Calculate white pegs
        for (int guessCount = 0; guessCount < codeLength; guessCount++)
        {
            //Ensure current guess value hasn't been used
            if (guess[guessCount] != 0)
            {

                //Check for matching value in code
                for (int codeCount = 0; codeCount < codeLength; codeCount++)
                {
                    if (guess[guessCount] == code[codeCount])
                    {
                        //Found match
                        newHint.whitePegs = newHint.whitePegs + 1;

                        //Hide values
                        guess[guessCount] = 0;
                        code[codeCount] = 0;
                    }
                }

            }
        }

        return newHint;
    }
Sciprios
  • 21
  • 1
  • 1
    `int[]` is a reference type, so passing `int[]` by value just passes a reference to the same array. It does not copy the elements of the array. –  Feb 22 '14 at 11:25
  • My guess is that since you only have a reference to one struct `currentGuess` which you modify the value of, this might be causing an issue. Can you explain exactly what you are experiencing because your explanation of the symptoms is a bit vague. Remember, that even though a struct is a value type, you are still holding a reference to the struct in your main game loop which you are modifying – Charleh Feb 22 '14 at 11:27
  • Forgive me if I'm wrong, my C# knowledge isn't great, but could it be that when you pass guess line to the GenerateHint function, it is being passed my reference, like passing an array pointer in C/C++? I notice you're setting guess[count] = 0 in that function. – Mrfence Feb 22 '14 at 11:29
  • @Charleh Hi, the problem is as described by Mrfence97. Do you know of anyway around this issue? – Sciprios Feb 22 '14 at 11:35
  • possible duplicate of [Passing Objects By Reference or Value in C#](http://stackoverflow.com/questions/8708632/passing-objects-by-reference-or-value-in-c-sharp) – Obsidian Phoenix Feb 22 '14 at 11:39

1 Answers1

4

This is a common mistake many developers make. Parameters can be passed as ByVal or ByRef (to use VB notation). But what this does is not exactly what most people assume it does.

For value types (e.g. int, etc - anything which resides in the stack). The value itself it copied into a new memory space, and passed into the method. Thus changes to the value do not affect the originating value.

For Reference types (e.g. objects, classes, etc - anything which resides in the heap). The pointer is copied and passed into the method. However, the pointer still points to the same object in memory. Thus changes to properties inside the object will still be reflected in the calling method. The only thing that wouldn't be reflected, is if you did this:

myObject = new Person();

At this point, the pointer passed into the method would be reset to point to a brand new object. But the original pointer from the calling method still points to the original object. Thus it wouldn't see these changes.

One of the easiest things to think about (and I'm not 100% sure if this is true, but it makes it easier to think about this). Is that the pointers to objects in the heap and stored in the stack. When you set byval or byref/ref this is acting on the object in the stack, not the object in the heap. i.e. Byval/ByRef only ever applies to the heap.

Edit Here is a supporting answer from Jon Skeet to back this up. He also links to this article

Update

Additional info based on your comment. Your structs contain reference types (int[] is an array of ints - arrays are reference types) inside them, so they are automatically moved to the heap (as far as I understand it, at least).

This answer tackles it from a slightly different angle (structs being part of a class), but I think it gets the point across

Community
  • 1
  • 1
Obsidian Phoenix
  • 4,083
  • 1
  • 22
  • 60
  • 1
    Hi, thanks, for your very fast response. Okay I see where you are coming from. Are there any ways around this? – Sciprios Feb 22 '14 at 11:40
  • You could modify your code to read the value out of the passed object into a local variable, then act upon that local variable. – Obsidian Phoenix Feb 22 '14 at 11:41
  • But according to [Microsoft:](http://msdn.microsoft.com/en-us/library/8b0bdca4.aspx) Structs are not passed by reference eventhough my code runs differently? – Sciprios Feb 22 '14 at 12:02