Click here to Skip to main content
Click here to Skip to main content

Hangman game in C#

, 22 Jun 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
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 Big Grin | :-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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Vishnu Rawal
Software Developer
Nepal Nepal
I am a software developer practicing .NET stack and web technologies.
Follow on   LinkedIn

Comments and Discussions

 
QuestionMy vote of 5 PinmemberLaxius7-Nov-14 13:29 
AnswerRe: My vote of 5 PinprofessionalVishnu Rawal7-Dec-14 3:52 
GeneralBadhai PinmemberPradeep Joshi22-Jul-14 22:02 
GeneralRe: Badhai PinprofessionalVishnu Rawal22-Jul-14 23:00 
GeneralMy vote of 5 Pinmemberrumilal16-Jul-14 10:13 
GeneralRe: My vote of 5 PinprofessionalVishnu Rawal18-Jul-14 9:26 
GeneralMy vote of 5 Pinmemberkhanhamid92215-Jul-14 12:50 
GeneralRe: My vote of 5 PinprofessionalVishnu Rawal18-Jul-14 9:26 
GeneralMy vote of 4 PinprofessionalLostTheMarbles23-Jun-14 1:01 
GeneralRe: My vote of 4 PinprofessionalVishnu Rawal23-Jun-14 3:12 
GeneralRe: My vote of 4 PinprofessionalLostTheMarbles23-Jun-14 3:19 
GeneralRe: My vote of 4 PinprofessionalVishnu Rawal23-Jun-14 3:30 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150128.1 | Last Updated 23 Jun 2014
Article Copyright 2014 by Vishnu Rawal
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid