Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / C#
Tip/Trick

Hangman game in C#

Rate me:
Please Sign up or sign in to vote.
4.78/5 (8 votes)
22 Jun 2014CPOL2 min read 86.4K   5.5K   13   16
Windows Forms based hangman game in C#

Image 1

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.

C#
/// <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:

C#
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.

C#
/// <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).

C#
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.

C#
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).

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Nepal Nepal
I am a software developer practicing .NET stack and web technologies.

Comments and Discussions

 
Questioncode not properly run Pin
saif_ryan5-Dec-15 10:18
saif_ryan5-Dec-15 10:18 
AnswerRe: code not properly run Pin
Vishnu Rawal7-Dec-15 1:08
professionalVishnu Rawal7-Dec-15 1:08 
QuestionAfter Run Pin
Member 1208459931-Oct-15 10:10
Member 1208459931-Oct-15 10:10 
AnswerRe: After Run Pin
Vishnu Rawal4-Nov-15 17:47
professionalVishnu Rawal4-Nov-15 17:47 
QuestionMy vote of 5 Pin
Laxius7-Nov-14 12:29
Laxius7-Nov-14 12:29 
AnswerRe: My vote of 5 Pin
Vishnu Rawal7-Dec-14 2:52
professionalVishnu Rawal7-Dec-14 2:52 
GeneralBadhai Pin
Pradeep Joshi22-Jul-14 21:02
Pradeep Joshi22-Jul-14 21:02 
GeneralRe: Badhai Pin
Vishnu Rawal22-Jul-14 22:00
professionalVishnu Rawal22-Jul-14 22:00 
GeneralMy vote of 5 Pin
rumilal16-Jul-14 9:13
rumilal16-Jul-14 9:13 
GeneralRe: My vote of 5 Pin
Vishnu Rawal18-Jul-14 8:26
professionalVishnu Rawal18-Jul-14 8:26 
GeneralMy vote of 5 Pin
khanhamid92215-Jul-14 11:50
khanhamid92215-Jul-14 11:50 
GeneralRe: My vote of 5 Pin
Vishnu Rawal18-Jul-14 8:26
professionalVishnu Rawal18-Jul-14 8:26 
GeneralMy vote of 4 Pin
LostTheMarbles23-Jun-14 0:01
professionalLostTheMarbles23-Jun-14 0:01 
GeneralRe: My vote of 4 Pin
Vishnu Rawal23-Jun-14 2:12
professionalVishnu Rawal23-Jun-14 2:12 
GeneralRe: My vote of 4 Pin
LostTheMarbles23-Jun-14 2:19
professionalLostTheMarbles23-Jun-14 2:19 
GeneralRe: My vote of 4 Pin
Vishnu Rawal23-Jun-14 2:30
professionalVishnu Rawal23-Jun-14 2:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.