Make a hangman game in C#


[hangman]

Special thanks to Jeff Scarterfield for the skeleton drawing used by the program.

This example builds a simple hangman game that uses the dictionary created by the example Use LINQ to select words of certain lengths from a file in C#.

If you don’t know how hangman works, the program shows you blank spaces at the top where the letters in a word go and you have to guess the letters. Each time you make a wrong guess, the program displays another part of the hanged man, or in this example, a skeleton. If you build the entire skeleton, you lose. If you guess all of the letters first, you win.

This program isn’t too complicated but it is fairly long. The following code shows how the program initializes.

// PictureBoxes holding skeleton pictures.
private PictureBox[] PictureBoxes;

// The index of the current skeleton picture.
private int CurrentPictureIndex = 0;

// Words.
private string[] Words;

// The current word.
private string CurrentWord = "";

// Labels to show the current word's letters.
private List<Label> LetterLabels = new List<Label>();

// A list holding the letter buttons.
private List<Button> KeyboardButtons;

// Prepare to play.
private void Form1_Load(object sender, EventArgs e)
{
    // Save references to the PictureBoxes in the array.
    PictureBoxes = new PictureBox[]
    {
        picSkeleton0, picSkeleton1, picSkeleton2, picSkeleton3,
        picSkeleton4, picSkeleton5, picSkeleton6
    };

    // Load the words.
    Words = File.ReadAllLines("Words.txt");

    // Make button images.
    MakeButtonImages();
}

The program uses the following class-level variables to keep track of what’s happening:

  • PictureBoxes – An array that holds a series of 7 PictureBox controls that display skeleton images of varying completeness ranging from a blank white picture to the full skeleton.
  • CurrentPicture – The index of the currently visible PictureBox. When this equals the index of the last PictureBox, the user is seeing the full skeleton and therefore has lost the game.
  • Words – An array holding the word dictionary.
  • CurrentWord – The current word that the user is trying to guess.
  • LetterLabels – The Label controls that display the current word’s letters or the blank spots where the letters will go.
  • KeyboardButtons – The buttons that act as a keyboard.

The form’s Load event handler initializes the PictureBoxes array, uses File.ReadAllLines to load the Words array, and calls the following MakeButtonImages method to prepare the keyboard buttons.

// Make the button images.
private void MakeButtonImages()
{
    // Prepare the buttons.
    KeyboardButtons = new List<Button>();
    foreach (Control ctl in this.Controls)
    {
        if ((ctl is Button) && (!(ctl == btnNewGame)))
        {
            // Set the button's name.
            ctl.Name = "btn" + ctl.Text;

            // Attach the Click event handler.
            ctl.Click += btnKey_Click;

            // Make the button's image.
            MakeButtonImage(ctl as Button);

            // Save in the Buttons list for later use.
            KeyboardButtons.Add(ctl as Button);
        }
    }
}

When I first built the keyboard buttons, the letters didn’t center nicely over the buttons for some reason, possibly because the buttons were so small. To make them look nicer, I decided to make each button display an image holding its letter. The MakeButtonImages method creates the button’s images.

The MakeButtonImages method loops through the controls on the form. For every control that is a button except the New Game button, the program sets the button’s name to btn plus its letter. It adds the btnKey_Click event handler to the button’s Click event, and calls the MakeButtonImage method to make the button’s image. Finally it adds the button to the KeyboardButtons list.

The following code shows the MakeButtonImage method

// Give this button an image thet fits better than its letter.
private void MakeButtonImage(Button btn)
{
    Bitmap bm = new Bitmap(
        btn.ClientSize.Width,
        btn.ClientSize.Height);
    using (Graphics gr = Graphics.FromImage(bm))
    {
        gr.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
        using (StringFormat string_format = new StringFormat())
        {
            string_format.Alignment = StringAlignment.Center;
            string_format.LineAlignment = StringAlignment.Center;
            gr.DrawString(btn.Text, btn.Font, Brushes.Black,
                btn.ClientRectangle, string_format);
        }
    }
    btn.Tag = btn.Text;
    btn.Image = bm;
    btn.Text = "";
}

This method creates a Bitmap to fit the button’s client area. It makes an associated Graphics object and draws the button’s text centered on the Bitmap. It then saves the button’s letter in its Tag property, sets the button’s Image property to the Bitmap, and clears the button’s Text property so it displays only the image.

At this point the program is ready to run, although all of the keyboard buttons are initially disabled and the letter labels list is empty. When the user clicks the New Game button, the following code prepares the program for a new hangman game.

// Prepare for a new game.
private void btnNewGame_Click(object sender, EventArgs e)
{
    // Delete old letter labels.
    foreach (Label lbl in LetterLabels)
    {
        flpLetters.Controls.Remove(lbl);
        lbl.Dispose();
    }

    // Pick a new word.
    Random rand = new Random();
    CurrentWord = Words[rand.Next(Words.Length)].ToUpper();
    // Console.WriteLine(CurrentWord);

    // Create new letter labels.
    LetterLabels = new List<Label>();
    foreach (char ch in CurrentWord)
    {
        Label lbl = new Label();
        flpLetters.Controls.Add(lbl);
        lbl.Tag = ch.ToString();
        lbl.Size = btnQ.Size;
        lbl.TextAlign = ContentAlignment.MiddleCenter; 
        lbl.BackColor = Color.White;
        lbl.BorderStyle = BorderStyle.Fixed3D;
        LetterLabels.Add(lbl);
    }

    // Hide the won/lost labels.
    lblWon.Visible = false;
    lblLost.Visible = false;

    // Enable the letter buttons.
    foreach (Button letter_btn in KeyboardButtons)
        letter_btn.Enabled = true;

    // Display the first picture.
    PictureBoxes[CurrentPictureIndex].Visible = false;
    CurrentPictureIndex = 0;
    PictureBoxes[CurrentPictureIndex].Visible = true;
}

The code starts by deleting any letter labels that were created for the previous word. Those labels were contained in a FlowLayoutPanel named flpLetters so the code removes them from that control’s Controls collection and then disposes of each label.

The code then picks a random word from the Words list for the next game. It creates a new LetterLabels list and builds a Label for each of the new word’s letters. For each letter, the code:

  • Creates the label
  • Adds the label to the FlowLayoutPanel
  • Sets the label’s Tag property to the word’s corresponding letter
  • Centers the label’s text
  • Sets the label’s foreground and background colors
  • Adds the label to the LetterLabels list

Note that the code does not set the new labels’ Text properties.

The btnNewGame_Click event handler continues by hiding the labels that indicate that the user won or lost. It then enables the keyboard buttons.

Finally the code hides the previously displayed skeleton picture, sets CurrentPictureIndex to 0 (the index of the blank picture), and displays the blank picture.

The last piece of the program is the following btnKey_Click event handler, which executes when the user clicks a keyboard button.

// A key was clicked.
private void btnKey_Click(object sender, EventArgs e)
{
    // Disable this button so the user can't click it again.
    Button btn = sender as Button;
    btn.Enabled = false;

    // See if this letter is in the current word.
    string ch = btn.Tag.ToString();
    if (CurrentWord.Contains(ch))
    {
        // Good guess. Display matching letters.
        bool has_won = true;
        foreach (Label lbl in LetterLabels)
        {
            // See if this letter matches the current guess.
            if (lbl.Tag.ToString() == ch) lbl.Text = ch.ToString();

            // See if the user has found this letter.
            if (lbl.Text == "") has_won = false;
        }
        
        // See if the user has won.
        if (has_won)
        {
            lblWon.Visible = true;
            foreach (Button letter_btn in KeyboardButtons)
                letter_btn.Enabled = false;
        }
    }
    else
    {
        // Bad guess. Show the next picture.
        PictureBoxes[CurrentPictureIndex].Visible = false;
        CurrentPictureIndex++;
        PictureBoxes[CurrentPictureIndex].Visible = true;

        // See if the user has lost.
        if (CurrentPictureIndex == PictureBoxes.Length - 1)
        {
            lblLost.Visible = true;
            foreach (Button letter_btn in KeyboardButtons)
                letter_btn.Enabled = false;
            foreach (Label lbl in LetterLabels)
                lbl.Text = lbl.Tag.ToString();
        }
    }
}

The event handler first disables the clicked button so the user can’t click it again. It then gets the button’s Tag property, converts it to the button’s letter, and determines whether the letter is in the current word.

If the letter is in the word, the program loops through the letters labels. If a label corresponds to the letter, the code displays the letter in the label. If any label’s text is still blank, then the user has not guessed all of the letters yet and the game continues. If the user has guessed all of the letters, the program displays the “you won” message and disables all of the keyboard buttons.

If the guessed letter is not in the word, the program hides the currently displayed skeleton image, increments CurrentPictureIndex to show the next skeleton image, and displays that image. Then if the current image is the last one, the program displays the “you lost” message, disables all of the keyboard buttons, and makes each label letter display its letter.

If you like you can invent a scoring system for the game and save statistics such as high scores and win/loss percentages in the registry or in program settings.

Also note that the dictionary that the program uses comes from words that are common to several dictionaries. That means a lot of the words are unusual so the game is fairly hard. You can change the dictionary if you like to make the game easier. For example, if you want to give the game to third graders, you might want to use easier words. It might also be interesting to use a domain-specific dictionary containing words about a particular topic such as programming.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in algorithms, files, games, graphics, strings and tagged , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *