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

Hangman game in C#

0.00/5 (No votes)
22 Jun 2014 1  
Windows Forms based hangman game in C#

Introduction

Hangman is two-player word guessing game; a player guesses a hidden word character-by-character given by another player within a predefined numbers of moves (guesses). Later, player punishes (tries to hang) former if character-guess is wrong and if guess is right, he is rewarded revealing the letter positions within a word. More about Hangman is given here. This implementation tries to solve this scenario with technologies tagged. I hope this will help a beginner to play around.

Using the Code

This is a small Windows Forms application and all event-fireup logic is contained in a single file (That's what Winform is famous for :D ). For UI design part, some containers (GroupBox, Panels) are added statically and others (Buttons, Labels) are dynamic (added at runtime). I'm reading words from a text file for simplicity, contained in our assembly. 2D graphics APIs are used to draw graphics on a panel so as to indicate current hang state. All other steps in my implementation are given below:

1. Initialization and Public Declarations

Upon controls rendering, buttons are added at runtime with letters (A, B,.. Z) as text registering their click events.

/// <summary>
/// Adds buttons
/// </summary>
private void SelectWord()
{                      
      private void AddButtons()
        {
            for (int i = (int)'A'; i <= (int)'Z'; i++)
            {
                Button b = new Button();
                b.Text = ((char)i).ToString();
                b.Parent = flowLayoutPanel1;
                b.Font = new Font(FontFamily.GenericSansSerif, 20, FontStyle.Bold);
                b.Size = new Size(40, 40);
                b.BackColor = Color.LawnGreen;                
                b.Click += b_Click; // Event hook-up
            }
            // Initially disabling all buttons, requiring user to hit "Start"
            flowLayoutPanel1.Enabled = false;
}

enum to contain all hang states:

 enum HangState { None, Piller, Rope, Head, body, LeftHand, RightHand, LeftLeg, RightLeg }

Other declarations are in the code provided, viz. collections, graphics related variables, etc.

2. Read file containing words and select (randomize) a word to guess

File (Words.txt) containing some predefined words, is read from current directory (EXE location) and random word is chosen to start with.

/// <summary>
/// Randomizes a word reading text file
/// </summary>
private void SelectWord()
{                      
     string filePath = Path.Combine(Path.GetDirectoryName
     (System.Reflection.Assembly.GetExecutingAssembly().Location), "Words.txt");
     using (TextReader tr = new StreamReader(filePath, Encoding.ASCII))
     {
          Random r = new Random();
          var allWords = tr.ReadToEnd().Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);                
          currentWord = allWords[r.Next(0, allWords.Length - 1)]; // currentWord is public variable
     }
}

3. Adding labels for a word

Labels are added to groupbox container with some character (e.g. __) as text (so as to hide actual letter).

private void AddLabels()
{
    // Adding word to labels and labels to group Box
    groupBox1.Controls.Clear();
    labels.Clear();
    char[] wordChars = currentWord.ToCharArray();
    int len = wordChars.Length;
    int refer = groupBox1.Width / len;
    for (int i = 0; i < len; i++)
    {
         Label l = new Label();
         l.Text = DefaultChar;
         l.Location = new Point(10 + i * refer, groupBox1.Height - 30);
         l.Parent = groupBox1;
         l.BringToFront();
         labels.Add(l);
    }
    
    // Writting text boxes 
    txtWordLen.Text = len.ToString();
    txtGuessesLeft.Text = HangStateSize.ToString();
}

4. Implementing b_Click: handle letter clicks and adding graphics to a panel

We have to take all decisions (win, lose, wrong guess, right guess) upon button (containing letters) clicking, so the following code snippet is the heart.

void b_Click(object sender, EventArgs e)
        {
            Button b = (Button)sender;
            char charClicked = b.Text.ToCharArray()[0];
            b.Enabled = false;

            if ((currentWord = currentWord.ToUpper()).Contains(charClicked))
            {
                // char is there (right guess)
                lblInfo.Text = "Awesome!";
                lblInfo.ForeColor = Color.Green;
                char[] charArray = currentWord.ToCharArray();
                for (int i = 0; i < currentWord.Length; i++)
                {
                    if (charArray[i] == charClicked)
                        labels[i].Text = charClicked.ToString();
                }

                // Winning condition               
                if (labels.Where(x => x.Text.Equals(DefaultChar)).Any())
                    return;

                lblInfo.ForeColor = Color.Green;
                lblInfo.Text = "Hurray! You win.";
                flowLayoutPanel1.Enabled = false;
            }
            else
            {
                // Wrong guess
                lblInfo.Text = "Boo..";
                lblInfo.ForeColor = Color.Brown;
                if (CurrentHangState != HangState.RightLeg)
                    CurrentHangState++;
                txtGuessesLeft.Text = (HangStateSize - (int)CurrentHangState).ToString();
                txtWrongguesses.Text += string.IsNullOrWhiteSpace(txtWrongguesses.Text) 
                ? charClicked.ToString() : "," + charClicked;

                panel1.Invalidate(); // redrawing panel client area

                if (CurrentHangState == HangState.RightLeg)
                {
                    lblInfo.Text = "You lose!";
                    lblInfo.ForeColor = Color.Red;
                    flowLayoutPanel1.Enabled = false;

                    // Reveal the word
                    for (int i = 0; i < currentWord.Length; i++)
                    {
                        if (labels[i].Text.Equals(DefaultChar))
                        {
                            labels[i].Text = currentWord[i].ToString();
                            labels[i].ForeColor = Color.Blue;
                        }
                    }
                }
            }
        }

Now, we need to add hangman body parts (hand, leg, etc.) to panel graphics. This logic needs to be in panel Paint() event handler. This will keep our hangman drawing refreshed, otherwise graphics will vanish upon Paint() trigger (e.g. resize, minimize, etc).

private void panel1_Paint(object sender, PaintEventArgs e)
        {
            InitializeVars();
            var g = e.Graphics; //Graphic to draw on

            if (CurrentHangState >= HangState.Piller)
            {
                g.DrawLine(p, new Point(pillerVerBottomX, pillerVerBootomY), new Point(pillerVerTopX, pillerVerTopY));
                g.DrawLine(p, new Point(pillerHorRightX, pillerHorRightY), new Point(pillerHorLeftX, pillerHorLeftY));
            }

            if (CurrentHangState >= HangState.Rope)
            {
                g.DrawLine(pRope, new Point(ropeTopX, ropeTopY), new Point(ropeBottomX, ropeBottomY));
            }

            if (CurrentHangState >= HangState.Head)
            {
                g.DrawEllipse(pRope, new Rectangle(new Point(HeadBoundingRectX, ropeBottomY), new Size(diameter, diameter)));
                g.FillRectangles(new SolidBrush(Color.Crimson),
                    new[] {
                            new Rectangle( new Point(HeadBoundingRectX + 10, 
                            ropeBottomY + 10), new Size(6, 6)), // Left eye
                            new Rectangle( new Point(HeadBoundingRectX + 
                            diameter - 10 - 6, ropeBottomY + 10), new Size(6, 6)), // Right eye
                            new Rectangle(new Point(ropeBottomX - 5/2, 
                            ropeBottomY + diameter/2), new Size(5, 5)),  // Nose
                            new Rectangle(new Point(ropeBottomX - 10, 
                            ropeBottomY + diameter/2 + 10), new Size(20, 5))   // Mouth
                        });
            }

            if (CurrentHangState >= HangState.body)
            {
                g.DrawEllipse(pRope, new Rectangle(new Point
                (HeadBoundingRectX, bodyBoundingRectY), new Size(diameter, bodySize)));
            }

            if (CurrentHangState >= HangState.LeftHand)
            {
                g.DrawCurve(pRope,
                new[] { 
                            new Point(HeadBoundingRectX + 8, bodyBoundingRectY + 15), 
                            new Point(HeadBoundingRectX - 30, bodyBoundingRectY + 30),
                            new Point(HeadBoundingRectX - 30, bodyBoundingRectY + 20),
                            
                            new Point(HeadBoundingRectX + 5, bodyBoundingRectY + 25)
                        });
            }

            if (CurrentHangState >= HangState.RightHand)
            {
                g.DrawCurve(pRope,
                 new[] { 
                            new Point(HeadBoundingRectX + diameter - 8, bodyBoundingRectY + 15), 
                            new Point(HeadBoundingRectX + diameter + 30, bodyBoundingRectY + 30),
                            new Point(HeadBoundingRectX + diameter + 30, bodyBoundingRectY + 20),
                            
                            new Point(HeadBoundingRectX + diameter - 5, bodyBoundingRectY + 25)
                        });
            }

            if (CurrentHangState >= HangState.LeftLeg)
            {
                g.DrawCurve(pRope,
                new[] { 
                            new Point(HeadBoundingRectX + 8, bodyBoundingRectY + bodySize - 15), 
                            new Point(HeadBoundingRectX - 30, bodyBoundingRectY + bodySize - 5),
                            new Point(HeadBoundingRectX - 30, bodyBoundingRectY + bodySize),
                            new Point(HeadBoundingRectX + 5, bodyBoundingRectY + bodySize - 25)
                        });
            }

            if (CurrentHangState >= HangState.RightLeg)
            {
                g.DrawCurve(pRope,
                  new[] { 
                            new Point(HeadBoundingRectX + diameter - 8, bodyBoundingRectY + bodySize - 15), 
                            new Point(HeadBoundingRectX + diameter + 30, bodyBoundingRectY + bodySize - 5),
                            new Point(HeadBoundingRectX + diameter + 30, bodyBoundingRectY + bodySize),
                            new Point(HeadBoundingRectX + diameter - 5, bodyBoundingRectY + bodySize - 25)
                        });
            }
        }    

Each graphics drawing is relative to the previous one, so you can use it in your code while changing some width/height parameters.

Points of Interest

This game is very simple at this level. But we can add more vocabularies domain, meaning for hint. Doing so definitely carries some academic significance (vocabulary learning) with fun.

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