VB Tutorial - Hang Man!

VB Walkthrough: Hang Man!

VB Walkthrough: Hang Man! 1

Introduction.. 1

Starting the Project.. 1

Coding the Game Engine. 3

Coding the Main Form... 7

Coding the New Game Dialog.. 9

Conclusion.. 11

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)




Answer this question

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:

    1. There is a method call that is used several times unnecessarily.

    2. A specific piece of functionality is being extracted from the UI when it should be wrapped up in a new method on the game engine.

    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.



  • VB Tutorial - Hang Man!