Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Mastermind for Windows Mobile

0.00/5 (No votes)
15 Jul 2009 1  
Develop the Mastermind Game using the .NET Compact Framework.
mastermind/mastermind.png

Introduction

My daughter likes to play the Master Mind game. I don’t always have time to play the game with her, so I decided to write a program so that when I am not available she can play the game by herself using my HTC Touch.

In this article, I will briefly describe the design of this program.

Background

Master Mind is a code-breaking board game. The game is played using a decoding board, some code pegs of six (or more) different colors and some key pegs with black and white colors.

Two players play the game: code maker and code breaker. The code maker chooses a pattern of four code pegs and places them in the four holes covered by the shield, not visible to the code breaker.

The code breaker tries to guess the pattern, in both order and color. Each guess is made by placing a row of code pegs on the decoding board. Once placed, the code maker provides feedback by placing key pegs in the holes of the row with the guess. A black key peg is placed for each code peg from the guess which is correct in both color and position. A white peg indicates the existence of a correct color peg placed in the wrong position. Please refer to Wikipedia for details.

The Design

The design of the program basically follows the MVC design pattern. The model manages the state of a game, the guesses that have been made, and the answer-the pattern chosen by code maker. The view displays the state of the game and provides feedbacks. The controller handles the mouse events, updates the model and the view.

The Model

The Game class represents a session of the Master Mind game. The Pattern class can represents both the answer chosen and the guesses made by the code breaker. A Game object keeps a Pattern object as the Answer and a number of Pattern objects as the Guesses. When a new guess is made, the Guess() method is called on the Game object.

The Guess() method returns a GuessResult object indicating the result of the guess. The Succeeded property returns true if the guess is exactly the same as the answer. The Failed property returns true if the guess is different from the answer and the number of guesses has exceeded the limit.

The CodePeg and KeyPeg classes inherit from the Peg abstract class and override the Draw() method.

The View and the Controller

The GameBox class is responsible for displaying the state of a game, providing visual feedbacks to the user, and responding to the mouse events. In a sense, GameBox acts as the Controller of the MVC pattern. GameBox has a set of properties that define the layout of the View,including a Board and a PegBox. The Board is where the user places the code pegs and where the guess results are shown. The PegBox is where a code peg of a particular color is chosen to be placed in the Board.

In order to reduce the dependency of the Board and PegBox classes on the GameBox class, the IGameBox interface was introduced. The Board and PegBox classes depend on the IGameBox interface, which is implemented by the GameBox class.

To avoid flickering, we don’t draw directly onto the client area of the main form. Instead, we draw the GameBox on the image of a PictureBox that covers the entire client area of the main form, represented by the MainForm class, inheriting from the Form class.

As shown in the following sequence diagram, when the MainForm is being loaded, the InitializeGameBox() method is called, which creates an instance of the GameBox class and invokes the Initialize() method on it. The GameBox.Initialize() method first creates one instance each of Board and PegBox, and then creates an bitmap of the size of client area of the main form, gets the Graphics object from the Bitmap, and calls the Draw() method on the Board and PegBox objects with the Graphics object as a parameter. So effectively, the Board.Draw() and PegBox.Draw() methods draw graphics on the bitmap, which is subsequently set to the PictureBox object in the MainForm.

The Board.Draw() method draws the board, the rows, and the holes for placing code pegs and key pegs. The PegBox.Draw() method draws the 6 code pegs with different colors for the user to choose.

    public void Initialize()
    {
        // Set the selected color randomly.
        Random ran = new Random();
        CurrentColor = (PegColor)ran.Next(0, NumOfCodeColors);

        // Create the Board
        {
            int boardWidth =
                BoxBorderWidth * 2 +
                RowHeight * 5 +
                CellBorderWidth;

            int boardHeight =
                BoxBorderWidth * 2 +
                RowHeight * MaxGuesses +
                CellBorderWidth * (MaxGuesses - 1);

            int yMargin = (TotalHeight - boardHeight) / 2;
            int xMargin = LeftMargin + yMargin;

            Rectangle rcBoard = new Rectangle(xMargin, yMargin, 
                                              boardWidth, boardHeight);

            m_board = new Board(this, rcBoard);
        }

        // Create the code peg box.
        {
            int width = RowHeight * 2;
            int height = RowHeight * NumOfCodeColors / 2;

            Rectangle rect = new Rectangle
                (
                TotalWidth - width - RightMargin,
                TotalHeight - height - RightMargin,
                width,
                height
                );

            m_pegBox = new PegBox(this, rect);
        }

        DrawBitmap();
    }

    private void DrawBitmap()
    {
        Bitmap = new Bitmap(TotalWidth, TotalHeight);

        using (Graphics g = Graphics.FromImage(Bitmap))
        {
            Draw(g);
        }
    }

    public void Draw(Graphics g)
    {
        var rc = new Rectangle(0, 0, TotalWidth, TotalHeight);

        using (SolidBrush bgBrush = new SolidBrush(BackgroundColor))
        {
            g.FillRectangle(bgBrush, rc);
        }

        m_board.Draw(g);
        m_pegBox.Draw(g);
}

When the user taps on the PictureBox, the MouseUp event is triggered. The pbGameBox_MouseUp() method gets the Graphics object from the image of the PictureBox, and passes it together with position of the mouse to the OnMouseUp() method on the GameBox object. Notice that the Graphics object gotten from the bitmap of the PictureBox control is passed to the GameBox.OnMouseUp() method, to allow the GameBox object to update the screen.

private void pbGameBox_MouseUp(object sender, MouseEventArgs e)
{
    Status status = Status.Continue;

    using (Graphics g = Graphics.FromImage(pbGameBox.Image))
    {
        /* let the GameBox object handles the event and draw the image
         * of the pictuer box.
         */

        status = m_gameBox.OnMouseUp(g, e.X, e.Y);
    }

    // redraw the picture box to reflect of changes in the image.
pbGameBox.Refresh();

    // Check the status of the guess just made.
    switch (status)
    {
        case Status.Succeeded:
            MessageBox.Show("Correct!", "MasterMind");
            break;

        case Status.Failed:
            {
                Pattern answer = m_gameBox.Game.Answer;

                MessageBox.Show(string.Format(
                    "The correct answer is: {0} {1} {2} {3}",
                    answer.GetAt(0), answer.GetAt(1), answer.GetAt(2), 
                    answer.GetAt(3)),
                    "MasterMind");
            }
            break;
}
}

The GameBox.OnMouseUp() method calls the OnMouseUp() method on the Board object or the PegBox object depending on the position of the mouse.

    public Status OnMouseUp(Graphics g, int x, int y)
    {
        if (m_board.Rectangle.Contains(x, y))
        {
            // The user tapped on the board.
            return m_board.OnMouseUp(g, x, y);
        }
        else if (m_pegBox.Rectangle.Contains(x, y))
        {
            // The user tapped on the code peg box to select the current color.
            m_pegBox.OnMouseUp(g, x, y);
        }

        return Status.Continue;
    }

If the mouse is in the row of the current guess, the Board.OnMouseUp() method check puts the current selected code peg into the closest hole. If the current guess is completed, it displays the guess result and returns the status of the game.

    public Status OnMouseUp(Graphics g, int x, int y)
    {
        var rowRect = GetCurrentRowRect();

        if (rowRect.Contains(x, y))
        {
            // The user tapped in the current row.

            PutCodePeg(g, rowRect, x);

            if (m_gameBox.CurrentGuess.IsComplete)
            {
                /* The current guess is completed. Check if the guess is 
                 * or if the user has failed to get the correct answer within
                 * the limit.
                 */

                GuessResult r = m_gameBox.Game.Guess(m_gameBox.CurrentGuess);

                DrawGuessResult(g, r, y);
                DrawRowIndicator(g);

                m_gameBox.CurrentGuess.Clear();

                if (r.Failed)
                {
                    return Status.Failed;
                }
                else if (r.Succeeded)
                {
                    return Status.Succeeded;
                }
            }
        }

        return Status.Continue;
    }

The PegBox.OnMouseUp() methods gets and draws the color selected.

    public void OnMouseUp(Graphics g, int x, int y)
    {
        x -= Rectangle.Left;
        y -= Rectangle.Top;

        int i = y / m_gameBox.RowHeight; // row
        int j = x / m_gameBox.RowHeight; // column

        m_gameBox.CurrentColor = m_gameBox.Game.PegColors[i * 2 + j];

        DrawCurrentColor(g);
    }

Some Other Code

Two methods of the Pattern class are worth mentioning: Generate() and Compare(). The Generate() method generates a pattern randomly. In the current implementation, the 4 colors in the generated pattern must be unique. The m_random member variable was initialized in the constructor.

    public void Generate(int colors)
    {
        var usedColors = new Dictionary<int, int>();

        for (int i = 0; i < 4; i++)
        {
            int n = -1;

            // Make sure the pattern has 4 unique colors.
            do
            {
                n = m_random.Next(0, colors - 1);
            } while (usedColors.ContainsKey(n));

            usedColors[n] = n;
            m_pegs[i] = m_pegColors[n];
        }
    }

The Compare() method compares the given pattern with the answer. It first checks how many pegs are correct in both color and order, and then checks how many pegs are correct in color but in wrong position. The black variable indicates the code pegs that should be marked with black key pegs. The blackWhite variable indicates the code pegs that should be marked with either black or white key pegs.

    public GuessResult Compare(Pattern pegs)
    {
        int blackCount = 0;
        int whiteCount = 0;

        bool[] black = new bool[4] { false, false, false, false };

        /* Find all code pegs in the two patterns that have the same
         * color and position.
         */
        for (int i = 0; i < 4; i++)
        {
            if (m_pegs[i] == pegs.GetAt(i))
            {
                blackCount++;
                black[i] = true;
            }
        }

        if (blackCount < 4)
        {
            /* Find all remaining code pegs in the two patterns that have
             * the same color.
             */
            bool[] blackWhite = new bool[4];
            black.CopyTo(blackWhite, 0);

            for (int j = 0; j < 4; j++)
            {
                if (!black[j])
                {
                    for (int k = 0; k < 4; k++)
                    {
                        if (!blackWhite[k] && m_pegs[j] == pegs.GetAt(k))
                        {
                            whiteCount++;
                            blackWhite[k] = true;

                            break;
                        }
                    }
                }
            }
        }

        return new GuessResult(blackCount, whiteCount);
}

Unit Tests

A Unit Test project, named TestMasterMind, is created to unit test the classes that are unit-testable. Right now, only the Pattern and Game classes are unit-tested.

As an example, the GameTester.TestSucceeded() test method makes two guesses and checks if the result of the second guess is Succeded.

    [TestMethod]
    public void TestSucceeded()
    {
        m_game.NewGame();

        m_game.Answer.SetAt(0, PegColor.Green);
        m_game.Answer.SetAt(1, PegColor.Magenta);
        m_game.Answer.SetAt(2, PegColor.Orange);
        m_game.Answer.SetAt(3, PegColor.Red);

        Pattern p = new Pattern();
        p.SetAt(0, PegColor.Orange);
        p.SetAt(1, PegColor.Blue);
        p.SetAt(2, PegColor.Green);
        p.SetAt(3, PegColor.Yellow);

        GuessResult r = m_game.Guess(p);
        Assert.AreEqual(false, r.Succeeded);
        Assert.AreEqual(false, r.Failed);

        p.SetAt(0, PegColor.Green);
        p.SetAt(1, PegColor.Magenta);
        p.SetAt(2, PegColor.Orange);
        p.SetAt(3, PegColor.Red);

        r = m_game.Guess(p);
        Assert.AreEqual(true, r.Succeeded);
        Assert.AreEqual(false, r.Failed);
}

Note that since the TestMasterMind project depends on the MasterMind project, a Smart Device project, when you run the unit tests, you will be asked to deploy the MasterMind program to an emulator, which is a bit annoying.

Points of Interest

Drawing graphics in the bitmap of a PictureBox control helps avoid flickering.

Although this program is very small, defining the unit-testable domain classes helped make the design loose-coupled and shorten the development process.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here