VB Walkthrough: Hang Man!
Introduction
In this tutorial we will create the game Hang Man. The project will be composed of two forms, the main application window and a “new game” dialog, and one class that will serve as the game engine. The project will also utilize three JPEG texture images in drawing the graphics.
This project will demonstrate drawing scalable images with the System.Drawing.Graphics object, passing data between forms, and creating a custom class complete with properties, methods, and events.
Starting the Project
To begin the project, start a new Visual Studio 2005 Windows Application and configure Form1 with the following properties:
· Size = 600, 600
· StartPosition = CenterScreen
· Text = Hang Man
Now add the following controls to the form and set each of their properties according to the information beneath each control:
· PictureBox
o Anchor = Top, Bottom, Left, Right
o BorderStyle = Fixed3D
o Location = 13, 13
o Size = 567, 432
· Label
o Anchor = Bottom, Left, Right
o BorderStyle = Fixed3D
o Font = Microsoft Sans Serif, 15.75pt, Bold
o Location = 13, 448
o Size = 568, 29
o TextAlign = MiddleCenter
· TextBox
o Anchor = Bottom, Left, Right
o Font = Microsoft Sans Serif, 12pt
o Location = 13, 480
o Size = 567, 26
o TextAlign = Center
· ListBox
o Anchor = Bottom, Left, Right
o BackColor = DarkRed
o ColumnWidth = 20
o Font = Microsoft Sans Serif, 12pt, Bold
o ForeColor = White
o HorizontalScrollbar = True
o ItemHeight = 20
o MultiColumn = True
o ScrollAlwaysVisible = True
o Size = 567, 44
Now we will create and layout the form for the “New Game” dialog box. Right-click on your project in the Solution Explorer, select Add and then New Item. Choose the Dialog template from the list, name it NewGameDialog, and then click the Add button. Set the form’s properties as follows:
· Size = 441, 165
· StartPosition = CenterScreen
· Text = New Game
Next, add the following controls to the form and set each of their properties according to the information beneath each control:
· Label
o Font = Microsoft Sans Serif, 12pt
o Location = 4,13
o Size = 430, 20
o Text = Enter a word to try and guess:
o TextAlign = MiddleCenter
· TextBox
o Location = 30, 48
o Size = 370, 20
o TextAlign = Center
That takes care of designing the forms that will be needed to play Hang Man. Now we will add a class that will be built into our game engine. Right-click on your project in the Solution Explorer, select Add and then New Item. Choose the Class template from the list, name it HangManEngine, and then click the Add button.
Finally, we need to add the texture image resources to the project. If you have not done so already, download the textures from http://rkimble.brinkster.net/files/hangmantextures.zip and extract them to a folder on your hard drive. Double-click My Project in the Solution Explorer to view the project properties. Click the Resources tab on the left. Click the down arrow next to Add Resource and select Add Existing Item. Browse to the location where you extracted the textures, select all three and add them to the project.
Now we’re ready to write some code!
Coding the Game Engine
The following code creates the Hang Man game engine. Place this code in the HangManEngine class we created earlier.
First we need to create some local variables to hold the word to be guessed, the total number of misses, and the incorrect letters already guessed. We’ll use an integer to hold the number of misses, a string for the word to be guessed and a list of characters for the incorrect letters guessed.
Private myMisses As Integer = 0
Private myWord As String
Private myLetters As New List(Of Char)
The next thing we will do is declare the events that will be raised by the engine as the Hang Man game plays out. These will include events to indicate that the player has either won or lost, that the player has guessed correctly and the word displayed needs to be updated, and an event to indicate that the player has guessed incorrectly and the image of the hanging man needs to be updated.
Public Event PlayerWins As EventHandler
Public Event PlayerLooses As EventHandler
Public Event ImageChanged As EventHandler
Public Event WordChanged As EventHandler
Now we’ll expose the Word() property that represents the word that the player is trying to guess. We need to do some work in the Get() and Set() methods of this property. When this property has it’s value set, not only will we set the private myWord variable to the value supplied (converted to all upper case), we also want to reset the game by setting the total misses to 0 and clearing the missed letters collection. Likewise, when the value is read we don’t want to just return the word; we only want to return the letters that were guessed correctly and display an underscore character for the letters that have not been guessed yet. We’ll also put a space between each character so that it reads better in the game.
Public Property Word() As String
Get
If myWord = String.Empty Then
Return String.Empty
Else
Dim mask As New System.Text.StringBuilder
For i As Integer = 0 To myWord.Length - 1
If myLetters.Contains(myWord.Chars(i)) Then
mask.Append(myWord.Chars(i))
Else
mask.Append("_")
End If
mask.Append(" ")
Next
Return mask.ToString.Trim.ToUpper
End If
End Get
Set(ByVal value As String)
If Not value = String.Empty Then
myWord = value.ToUpper
myMisses = 0
myLetters.Clear()
End If
End Set
End Property
The properties for Misses() and LettersGuessed() are more straightforward than the Word() property was. These properties will be read-only, as they get reset when a value is supplied to the Word() property. These properties will simply return the private values held by myMisses and myLetters.
Public ReadOnly Property Misses()
Get
Return myMisses
End Get
End Property
Public ReadOnly Property LettersGuessed() As List(Of Char)
Get
Return myLetters
End Get
End Property
Three methods will then finish the class. The first is a simple function that just returns the word being guessed at without any modification.
Public Function GetOriginalWord() As String
Return myWord.ToUpper
End Function
The next method will check to see if the letter that the player is guessing is correct. It will accept a character parameter and return a Boolean value. In addition, this function will raise the events that state whether the word changed or the picture changed, and if the player has won or lost the game.
Public Function CheckLetter(ByVal letter As Char) As Boolean
'Make sure we have a word to work with
If myWord = String.Empty Then
'If we have no word, return false
Return False
Else
'If we do have a word, see if it contains our letter
If myWord.Contains(Char.ToUpper(letter)) Then
'If so, see if we've already recorded it
If Not myLetters.Contains(Char.ToUpper(letter)) Then
'If not, add it to the collection
myLetters.Add(Char.ToUpper(letter))
'Raise the word chagned notification
RaiseEvent WordChanged(Me, New EventArgs)
'Test to see if the whole word has been guessed
Dim tst As New System.Text.StringBuilder
For i As Integer = 0 To myWord.Length - 1
If myLetters.Contains(myWord.Chars(i)) Then
tst.Append(myWord.Chars(i))
End If
Next
If tst.ToString.ToUpper.Trim = myWord.ToUpper Then
'If so, notify that the player wins
RaiseEvent PlayerWins(Me, New EventArgs)
End If
End If
Return True
'If our word does not contain the letter
Else
'Increment misses
myMisses += 1
'Notify that the image has changed
RaiseEvent ImageChanged(Me, New EventArgs)
'If this is ths sixth miss the player looses
If myMisses >= 6 Then
RaiseEvent PlayerLooses(Me, New EventArgs)
End If
Return False
End If
End If
End Function
Finally, we have a method that gets the image of the Hang Man game. This function takes the size of the desired image as a parameter and returns the image of the hangman’s noose with the appropriate body parts drawn.
Public Function GetImage(ByVal bounds As Size) As Image
Dim img As New Bitmap(bounds.Width, bounds.Height)
Dim gfx As Graphics = Graphics.FromImage(img)
'Draw Background
'Create a Rectangle for the sky and ground
Dim bkSky As New RectangleF(0, 0, bounds.Width, bounds.Height)
Dim bkGround As New RectangleF(0, 0.8 * bounds.Height, bounds.Width, 0.2 * bounds.Height)
'Draw the background sky and ground
gfx.FillRectangle(New TextureBrush(My.Resources.skytexture), bkSky)
gfx.FillRectangle(New TextureBrush(My.Resources.grasstexture), bkGround)
'Draw Frame
'To make the example cleaner, create a
'RectangleF object for each piece of
'the frame. All points will be calculate
'by using a percentage of the bounds to
'support a scalable image
Dim frBottom As New RectangleF(0.2 * bounds.Width, 0.9 * bounds.Height, 0.7 * bounds.Width, 0.1 * bounds.Height)
Dim frStand As New RectangleF(0.7 * bounds.Width, 0.1 * bounds.Height, 0.1 * bounds.Width, 0.8 * bounds.Height)
Dim frCross As New RectangleF(0.3 * bounds.Width, 0.1 * bounds.Height, 0.4 * bounds.Width, 0.1 * bounds.Height)
Dim frNoose As New RectangleF(0.3 * bounds.Width, 0.2 * bounds.Height, 0.1 * bounds.Width, 0.1 * bounds.Height)
'Now draw each RectangleF
gfx.FillRectangle(New TextureBrush(My.Resources.woodtexture), frBottom)
gfx.FillRectangle(New TextureBrush(My.Resources.woodtexture), frStand)
gfx.FillRectangle(New TextureBrush(My.Resources.woodtexture), frCross)
gfx.FillRectangle(New TextureBrush(My.Resources.woodtexture), frNoose)
'Draw Man
'Create a pen & brush to use for these shapes
Dim mnPen As New Pen(Color.Wheat, 0.05 * bounds.Width)
Dim mnHBrush As New SolidBrush(Color.Wheat)
Dim mnBPen As New Pen(Color.Maroon, 0.05 * bounds.Width)
Dim mnLPen As New Pen(Color.Navy, 0.05 * bounds.Width)
'Create a shapte for each body part
Dim mnHead As New RectangleF(0.275 * bounds.Width, 0.3 * bounds.Height, 0.15 * bounds.Width, 0.15 * bounds.Height)
Dim mnBody(2) As PointF
mnBody(0) = New PointF(0.35 * bounds.Width, 0.45 * bounds.Height)
mnBody(1) = New PointF(0.35 * bounds.Width, 0.675 * bounds.Height)
Dim mnLArm(2) As PointF
mnLArm(0) = New PointF(0.35 * bounds.Width, 0.45 * bounds.Height)
mnLArm(1) = New PointF(0.25 * bounds.Width, 0.6 * bounds.Height)
Dim mnRArm(2) As PointF
mnRArm(0) = New PointF(0.35 * bounds.Width, 0.45 * bounds.Height)
mnRArm(1) = New PointF(0.45 * bounds.Width, 0.6 * bounds.Height)
Dim mnLLeg(2) As PointF
mnLLeg(0) = New PointF(0.35 * bounds.Width, 0.675 * bounds.Height)
mnLLeg(1) = New PointF(0.25 * bounds.Width, 0.85 * bounds.Height)
Dim mnRLeg(2) As PointF
mnRLeg(0) = New PointF(0.35 * bounds.Width, 0.675 * bounds.Height)
mnRLeg(1) = New PointF(0.45 * bounds.Width, 0.85 * bounds.Height)
'Draw each piece of the man,
'as applicable
If myMisses > 1 Then
gfx.DrawLine(mnBPen, mnBody(0), mnBody(1))
End If
If myMisses > 2 Then
gfx.DrawLine(mnBPen, mnLArm(0), mnLArm(1))
End If
If myMisses > 3 Then
gfx.DrawLine(mnBPen, mnRArm(0), mnRArm(1))
End If
If myMisses > 4 Then
gfx.DrawLine(mnLPen, mnLLeg(0), mnLLeg(1))
End If
If myMisses > 5 Then
gfx.DrawLine(mnLPen, mnRLeg(0), mnRLeg(1))
End If
'The head is drawn last to go over the arms
If myMisses > 0 Then
gfx.FillEllipse(mnHBrush, mnHead)
End If
'Release the Graphics object
gfx.Dispose()
'Return the image
Return img
End Function
This provides all of the code for the game engine. Now we need to code the main game form.
Coding the Main Form
The following code will control the main application form. This code goes in the Form1 class. This form is pretty straight forward. We will create an instance of the HangManEngine, handle the events raised by the engine, and respond to key presses of the user. The comments in the code will explain the code’s purpose.
'Create an instance of our Hang Man engine
Dim WithEvents hme As New HangManEngine
Private Sub MainForm_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Call the NewGame routine on load
NewGame()
End Sub
Private Sub MainForm_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize
'Call the GetImage() method of the HangManEngine, passing
'in the Size of the PictureBox to get the initial image
PictureBox1.Image = hme.GetImage(PictureBox1.Size)
End Sub
'When the user presses a key in the Guess Entry TextBox
Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
'Make sure there is text to test
If TextBox1.Text.Trim.Length > 0 Then
'Make sure the first character of the text in the
'TextBox is a letter
If Char.IsLetter(TextBox1.Text.Trim.Chars(0)) Then
'Make sure we haven't already guessed the first
'letter in TextBox1.Text
If Not ListBox1.Items.Contains(Char.ToUpper(TextBox1.Text.Trim.Chars(0))) Then
'Check the letter
If Not hme.CheckLetter(TextBox1.Text.Trim.Chars(0)) Then
'If the letter is wrong, and we have 1 or
'more misses, play a sound and add the
'letter to the incorrect guesses list
If hme.Misses > 0 Then
My.Computer.Audio.PlaySystemSound(Media.SystemSounds.Asterisk)
ListBox1.Items.Add(Char.ToUpper(TextBox1.Text.Trim.Chars(0)))
End If
End If
End If
End If
End If
'Clear the text
TextBox1.Clear()
End Sub
'When the ImageChanged event is raised by the HangManEngine,
'get the new image
Private Sub hmd_ImageChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles hme.ImageChanged
PictureBox1.Image = hme.GetImage(PictureBox1.Size)
End Sub
'When the WordChanged event is raised by the HangManEngine,
'get the new masked word
Private Sub hmd_WordChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles hme.WordChanged
Label1.Text = hme.Word
End Sub
'If the PlayerLooses even is raised
Private Sub hmd_PlayerLooses(ByVal sender As Object, ByVal e As System.EventArgs) Handles hme.PlayerLooses
'Show a message to the player that they've lost
MessageBox.Show(String.Format("Sorry! The word was: {0}", hme.GetOriginalWord), "You Loose... :(", MessageBoxButtons.OK, MessageBoxIcon.Error)
'Start a new game
NewGame()
End Sub
'If the PlayerLooses event is raised
Private Sub hmd_PlayerWins(ByVal sender As Object, ByVal e As System.EventArgs) Handles hme.PlayerWins
'Show a message to the player that they've won
MessageBox.Show(String.Format("You've guessed the word! It was: {0}", hme.GetOriginalWord), "You Win!!!", MessageBoxButtons.OK, MessageBoxIcon.Information)
'Start a new game
NewGame()
End Sub
'This routine begins a new game
Private Function NewGame() As Boolean
'Create an instance of the NewGameDialog
Dim dlgWord As New NewGameDialog
'This code is so that we can give the
'TextBox focus when the dialog is shown
dlgWord.Show()
dlgWord.TextBox1.Focus()
dlgWord.Visible = False
'If the dialog is canceled, exit the app
If dlgWord.ShowDialog = Windows.Forms.DialogResult.Cancel Then
Application.Exit()
End If
'Clear the items in the incorrect
'guesses list
ListBox1.Items.Clear()
'Set the new word
hme.Word = dlgWord.NewWord
'Get the fresh image
PictureBox1.Image = hme.GetImage(PictureBox1.Size)
'Display the masked word
Label1.Text = hme.Word
End Function
Coding the New Game Dialog
Finally we need to add the code to the new game dialog that allows the user to specify a word to guess and resets the game engine. This code goes in the NewGameDialog class.
'Create a variable to hold the final
'text in the TextBox when the user
'clicks "OK"
Private myWord As String
'Create a flag that prevents the form
'from closing if the user clicks cancel
'after clicking cancel :)
Private NoClose As Boolean = False
'Return the word the user entered
Public ReadOnly Property NewWord() As String
Get
Return myWord
End Get
End Property
Private Sub OK_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OK_Button.Click
'Make sure the word is long enough
If TextBox1.Text.Length < 4 Then
MessageBox.Show("This word is too short. Please use at least 4 letters.", "Word Too Short", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
Else
'If it is, save it and return with OK
myWord = TextBox1.Text
Me.DialogResult = System.Windows.Forms.DialogResult.OK
Me.Close()
End If
End Sub
Private Sub Cancel_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Cancel_Button.Click
'If the user wants to cancel, close and return with CANCEL
If MessageBox.Show("Are you sure you want to quit ", "Exit Game", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) = Windows.Forms.DialogResult.Yes Then
Me.DialogResult = System.Windows.Forms.DialogResult.Cancel
Me.Close()
Else
'Otherwise, set the flag to not close the form
NoClose = True
End If
End Sub
'Whenever the text changes, remove any characters
'that are not letters and change the case to upper
Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
For i As Integer = TextBox1.Text.Length - 1 To 0 Step -1
If Not Char.IsLetter(TextBox1.Text.Chars(i)) Then
TextBox1.Text = TextBox1.Text.Remove(i, 1)
End If
TextBox1.Text = TextBox1.Text.ToUpper
TextBox1.SelectionStart = TextBox1.Text.Length
Next
End Sub
Private Sub NewGameDialog_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
'When the form closes, if the flag is set
'cancel the close and reset the flag
If NoClose Then
e.Cancel = True
NoClose = False
End If
End Sub
Conclusion
Well, there we have it – Hang Man! In the next version of Hang Man, we’ll learn to consume a Web Service that will allow us to randomly generate a word to guess. We can also create a networked version that allows two people to play each other by each supplying a word for the other.
If you have any questions or comments, please post them here, or visit my web space http://spaces.msn.com/reedkimble and post a comment on the Hang Man blog entry.
Have fun and happy coding!
(You can download this tutorial from http://rkimble.brinkster.net/files/hangman.doc and the finished project from http://rkimble.brinkster.net/files/hangmandemo.zip)

VB Tutorial - Hang Man!
Jonas Holm
Beginner's Challenge
I'd like the experienced coders to exclue themselves from this challenge.
For the beginners who may decide to walk through this project, can you find the following two errors and explain why they are errors:
First newbie to get it right wins the right to pat themselves on the back
Rohan_Misys
ReneeC, you are obviously not a beginner. If you have never seen Visual Basic before---that would be me---it is mind boggling. Let me put it this way. The last time I programmed anything in Basic it was actually called BASIC, Beginners All-purpose Symbolic Instruction Code. It used something called an Interpreter. And Visual meant something you saw in a Star Wars documentary.
RKimble, thanks for this starting point. I hope my reply will have enough key words to help others find this post. There is no real need to reply to some of the hypothetical questions I pose.
Where do I begin I have no clue why I have to jump through so many hoops just to print "Hello World" on the screen, but so be it. I have learned so many languages over the past 30+ years that I can remember mostly the names. No doubt there are lots of folks in the same boat as myself. What is a DLL, anyway Dynamic Link Library. That much I know. What is it for A repository of reusable subroutines. What are declarations Public or Private How to open a form
I guess VB is a text book example of event driven programming. Whatever happend to CPM
Thanks again, Rudedog
"Fooling computers since 1971."
PDP-8, ASR-33
MartinGreen
I think this is a terrific beginner project! The only difficulty I see is that you did the project for them.