Click here to Skip to main content
15,867,834 members
Articles / Mobile Apps / Windows Phone 7

Hangman - a simple word game for Windows Phone 7

Rate me:
Please Sign up or sign in to vote.
4.74/5 (9 votes)
5 Mar 2012Ms-PL7 min read 41.5K   2.4K   18   5
Hangman is a very simple word game for Windows Phone 7

Sample Image - maximum width is 600 pixels

Introduction

This is a very simple game where, usually, a user is given with a word to guess. A user has typically 7-8 chances to guess the word. For each missed chance, a portion of the hanging of a man is drawn as shown above in the picture. The basic requirements of this game are very simple:

1. User should be able to select the difficulty level

2. User should be able to guess a letter by typing (or tapping on a keyboard button) on the Windows Phone

3. Keeping track of scores and to save/reload settings upon exiting/entering the game

4. Able to generate the word based on the difficulty level

Required software

Silverlight for Windows Phone toolkit

Windows Phone Developer Tools

Visual Studio 2010

Without these softwares, this application will not compile.

Basic design

The basic Windows Phone Application template has been used for this application. Some of the design and code were reused from my Stop Watch application.

The basic class diagram is given below:

Image 2

The AppSettings class persists data to/from isolated storage.

The WordList class reads data from an embedded text file (which is a dictionary) and saves into a list of strings. The word class manages the word shown to the user.

The GamePage is the main page which shows the letter blocks and keeps track of the game scores etc.

LetterBlock is a user control which shows each correct letter in a nice UI.

Logic of the game

The game picks a random word from a lit of words populated at the beginning of the game (see the WordList class). The picked word length is based on the level of the game. For easy level, the word length is 3, for medium level, the word length is 6 and for hard level, the word length is 8. Once the word is picked, based on the length of the word, empty letter blocks are created on the screen. Each letter block is actually a user control which consists of a rectangle (an outer boundary) and a text box. The letter blocks are added to a stack panel with horizontal and vertical alignment set to center so that that blocks appear on the center of the phone. To draw the hangman, I have 8 bitmaps. Each time a user taps a letter on keyboard, the letter is added to a list which keeps track of all the letter user has tapped. For each wrong letter, I changed bitmap based on the number of wrong letter user has tapped. Once user has pressed 8 wrong letters, the game is finished with the picture shown above. The score is updated once the game is over. For each win, the score is incremented by 1000 points. For each loss, the score is decremented by 1000 points. The entire score (win, loss) and game level are stored on isolated storage to track the history.

Dictionary

The dictionary is stored in a text file which is downloaded from http://www.mieliestronk.com/wordlist.html website. This is not a complete dictionary but for our purpose it is sufficient. A better and more complete dictionary would be the Unix words dictionary but the size is more than double the above dictionary. The file is used as embedded resource in the project for easy reading. I have a WordList class which reads the dictionary text file and saves data in a list. To see the complete implementations, please see WordList and Word classes.

Using the code

As described above, the application reads a dictionary file and then picks a random word from the dictionary. The problem is that Windows Phones don't have an application path so reading a file from a location was not possible. To overcome this problem, I used dictionary text as an embedded resource and then used the filename as "ApplicationName.Directoryname1....filename" as shown below.

/// <summary>
/// Read words from dictionary
/// </summary>
public void ReadWordFromDictionary()
{
    string fileName = "HangmanPhoneApp.Dictionary.corncob_lowercase.txt";
    System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
    string[] resources = assembly .GetManifestResourceNames();

    using (StreamReader reader = new StreamReader(assembly.GetManifestResourceStream(fileName), System.Text.Encoding.UTF8))
    {
        while (!reader.EndOfStream)
        {
            string s = reader.ReadLine().Trim();
            _wordList.Add(s);
        }
    }

}

To return a valid word and make game fair for all difficulty levels, the code returns a minimum number of letters for a word based on difficulty setting as returning a 3 letter word may be too easy for Hard difficulty setting. The code is shown below:

/// <summary>
/// Returns a word of given length. If length is 0 or less than 0 then word of any length is returned
/// </summary>
/// <param name="length">Lngth of word. To return word of any length, pass 0 or any number less than 0</param>
/// <returns></returns>
private string GetWordOfLength(int length)
{
    int minWordLength = (length <= 3) ? 3 : (length <= 6) ? 4 : 6;
    bool validWord = false;
    Random random = new Random();
    do
    {
        int index = random.Next(_wordList.Count);
        string s = _wordList[index];
        if ((s.Length <= length && s.Length >= minWordLength )|| length <= 0) return _wordList[index];
        validWord = false;
    }
    while (!validWord);

    // Default is to return en empty string
    return "";
}
To check if the typed letter is in the word, the code looks for the letter in the word and then returns an array of positions for that letter as same letter might be repeated many times in a word.
/// <summary>
 /// Checks the first character toCheck string with the generated word. If any of the position matches, it returns the position number
 /// </summary>
 /// <param name="toCheck"></param>
 /// <param name="position"></param>
 /// <returns></returns>
 public bool IsStringInWord(string toCheck, ref int [] position)
 {
     bool ret = false;
     for (int i = 0; i < position.Length; i++)
         position[i] = -1;

     if (CurrentWord.Contains(toCheck))
     {
         int pos = 0;
         for (int i = 0; i < CurrentWord.Length; i++)
         {
             if (Char.Equals(CurrentWord[i], toCheck[0]))
             {
                 position[pos++] = i;
                 ret = true;
             }
         }
     }

     return ret;
 }

The tricky part of this game is to show the keyboard to the user whenever the screen is tapped. To do this, I created a hidden text box as shown below:

C#
<TextBox x:Name="GuessLetter" 
    Grid.Row="3" HorizontalAlignment="Left" 
    KeyUp="GuessLetter_KeyUp" KeyDown="GuessLetter_KeyDown" 
    Opacity="0" Width="0" Height="0" 
    BorderThickness="0" FontSize="10.667" />

Now, on MouseLeftButtonDown event of this page, I set the focus on this text box which makes Windows Phone to show the keyboard. Every time, a user taps anywhere on phone, the focus is always set on this control.

The drawing of hangman is quite simple. There are eight pictures and each picture is replaced based on the number of wrong key taps. See the DrawHangman method in the class GamePage.

Background color

To set a consistent background color, I have used a linear gradient brush as shown below:

 <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="Black" Offset="0"/>
    <GradientStop Color="#FF29E7DA" Offset="1"/>
</LinearGradientBrush>

User interfaces

MainPage.xaml

This is the main page which guides a user to the requested page. By using this page, user can go to the game page, settings page, tutorial page .or about page.

The game page is where user plays the game.

Settings page has basic game settings like difficulty level and showing of the word when game ends.

The tutorial page explains in brief about the idea of the game and how to play it. The about page has information about the developer of this game. A user can send email to me by clicking the email address. Please see my previous article, Stop Watch, to find more details about this feature.

GamePage.xaml

This is main page where user plays the game. The basic xaml of this page is given below:

 <!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" MouseLeftButtonDown="LayoutRoot_MouseLeftButtonDown">
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="#FF29E7DA" Offset="1"/>
</LinearGradientBrush>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0" HorizontalAlignment="Center" Margin="0,0,0,25">
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="#FF1BD9DE" Offset="1"/>
</LinearGradientBrush>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" HorizontalAlignment="Center" Text="Score" Margin="0,0,10,0" VerticalAlignment="Center" Opacity="0.5" />
<TextBlock Margin="5,0,10,0" TextWrapping="Wrap" Text="Level" d:LayoutOverrides="Width, Height" Grid.Column="3" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0.5"/>
<TextBlock Margin="0,0,1,0" TextWrapping="Wrap" d:LayoutOverrides="Width, Height" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Text="Lost" Opacity="0.5"/>
<TextBlock Margin="0,0,1,0" TextWrapping="Wrap" Text="Won" d:LayoutOverrides="Width, Height" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0.5"/>
<TextBlock x:Name="Score" Margin="0,0,1,0" TextWrapping="Wrap" Text="0" d:LayoutOverrides="Width, Height" Grid.Row="1" HorizontalAlignment="Center"/>
<TextBlock x:Name="LostGames" Margin="0,0,1,0" TextWrapping="Wrap" Text="0" d:LayoutOverrides="Width, Height" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="WonGames" Margin="0,0,2,0" TextWrapping="Wrap" Text="0" d:LayoutOverrides="Width, Height" Grid.Column="2" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock x:Name="Level" Margin="0,0,1,0" TextWrapping="Wrap" Text="Easy" d:LayoutOverrides="Width, Height" HorizontalAlignment="Center" Grid.Column="3" Grid.Row="1" Grid.ColumnSpan="2" VerticalAlignment="Center"/>
</Grid>
<StackPanel x:Name="BlockHolder" Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" />
<TextBlock Grid.Row="2" Grid.Column="0" HorizontalAlignment="Left" Text="Tap anywhere to show keyboard" Margin="0,0,0,0" />
<TextBox x:Name="GuessLetter" Grid.Row="3" HorizontalAlignment="Left" KeyUp="GuessLetter_KeyUp" KeyDown="GuessLetter_KeyDown" Opacity="0" Width="0" Height="0" BorderThickness="0" FontSize="10.667" />
<TextBlock x:Name="GameMessage" Grid.Row="4" Grid.Column="0" HorizontalAlignment="Left" Margin="8,20,0,5" FontSize="20" FontWeight="Normal" TextWrapping="Wrap" />
<Button x:Name="Play" Content="Play again" HorizontalAlignment="Center" Margin="0,0,0,10" d:LayoutOverrides="Height" Grid.Row="5" VerticalAlignment="Center" Click="Play_Click" Visibility="Collapsed">
<Button.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="#FF14C2EF" Offset="1"/>
</LinearGradientBrush>
</Button.Background>
<i:Interaction.Behaviors>
<el:FluidMoveBehavior Duration="0:0:5">
<el:FluidMoveBehavior.EaseY>
<ElasticEase EasingMode="EaseOut" Springiness="6"/>
</el:FluidMoveBehavior.EaseY>
<el:FluidMoveBehavior.EaseX>
<BounceEase EasingMode="EaseOut" Bounciness="3"/>
</el:FluidMoveBehavior.EaseX>
</el:FluidMoveBehavior>
</i:Interaction.Behaviors>
</Button>
<ScrollViewer Grid.ColumnSpan="8" Grid.Row="6" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image x:Name="Hangman" HorizontalAlignment="Center" Stretch="None" ImageFailed="Hangman_ImageFailed" />
</ScrollViewer>
</Grid>    
As can be seen above, the page is divided into many grid rows. The grid row zero shows the score, the total game won and lost and the game level. To give focus to score and other information, I made the text's opacity to 50% so that they blend into the background.

The grid row one contains a stack panel with horizontal and vertical alignment set to ceter. This row contains the actual letter block user controls. To add the letter blocks, the code looks like:

 /// <summary>
/// Add blocks
/// </summary>
void AddLettersBlocks()
{
    BlockHolder.Children.Clear();
    for (int i = 0; i < currentWord.WordLength; i++)
    {
        LetterBlock block = new LetterBlock();

        if (currentWord.WordLength > 6)
        {
            // Set the width
            block.Width = Application.Current.Host.Content.ActualWidth / currentWord.WordLength;
            // make the block square
            block.Height = block.Width;
            // Set rectangle width and height
            block.OuterEdge.Width = block.Width;
            block.OuterEdge.Height = block.Width;
            block.Letter.Width = block.Width * 0.53;
            block.Letter.Height = block.Width * 0.53;
        }

        BlockHolder.Children.Add(block);
        Grid.SetRow(block, 1);
        blocks.Add(block);
    }
}    
As you can see, it is very easy to add a children to a stack panel. I could have used other controls instead of stack panel like grid. It's just a personal choice to use the stack panel.

The grid two two contains the message about the game like the tapped keys and other game messages like the outcome of the game etc.

The grid row three contains a button "Play again" which is only shown when the game is over.

The grid row four is for the hangman bitmaps as explained in previous section.

LetterBlock user control

To show each letter, a user control has been created to represent each letter in a word. The code is given below:

<UserControl.RenderTransform>
    <TransformGroup>
        <TranslateTransform x:Name="TranslateLetter" />
        <RotateTransform x:Name="RotateLetter" />
    </TransformGroup>
</UserControl.RenderTransform>

<Grid x:Name="LayoutRoot" Background="Transparent" Margin="0">
    <Rectangle x:Name="OuterEdge" Margin="0" Stroke="#FFE5DDDD" Width="65" Height="65" RenderTransformOrigin="0.867,0.467" StrokeThickness="3" RadiusX="10" RadiusY="10" HorizontalAlignment="Center" VerticalAlignment="Center">
    <Rectangle.Fill>
        <LinearGradientBrush EndPoint="0.5,1" MappingMode="RelativeToBoundingBox" StartPoint="0.5,0">
            <GradientStop Color="Black"/>
            <GradientStop Color="#FF11DBF5" Offset="1"/>
        </LinearGradientBrush>
    </Rectangle.Fill>
    </Rectangle>
    <TextBlock x:Name="Letter" Width="35" Height="35" 
        FontWeight="Bold" FontSize="28" Foreground="White" 
        HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center" />
</Grid>

Each user control is essentially a grid with a rectangel and a text block to hold a letter. To make rectangle's edges look smooth, the radius property of the rectangle is set to 10. The rectangle is set to fill as linear gradient brush with black and light greenish color to make it stand out as a text block.

Adding sound

There are two ways we can add sound in our application:

1. Using MediaElement tag of Silverlight

2. Using SoundEffect class of Xna framework

The problem using MediaElement is that it's kind of a heavyweight element and is a burden on a simple application like this as it will stop all other media playback on the phone. I didn't find a simple way to loop a sound file using MediaElement hence I decided to use the SoundEffect class of Xna Framework. To use this class, just add the reference of Microsoft.Xna.Framework.dll in your application. Below code adds music in this application:

        // For playing Sound files
        SoundEffect backgroundMusic;

...
            // Load the sound file
            StreamResourceInfo infoGamebackground = Application.GetResourceStream(
              new Uri("Audio/GameBackGround.wav", UriKind.Relative));

            // Create an XNA sound effect from the stream
            backgroundMusic = SoundEffect.FromStream(infoGamebackground.Stream);
            bgInstance = backgroundMusic.CreateInstance();
            bgInstance.IsLooped = true;

As can be seen above, just load the sound file using the StreamResourceInfo class and then load the sound effect. You don't need to create an instance of a sound effect class. I am using the instance of the sound effect class only for adding looping for this sound so that it plays continuously. Make sure that when the sound file is added, it adds as a "Content". To play the sound, call Play method of SoundEffect class.

History

V1.0 - Feb 2012

V2.0 - Mar 2012 - Added sound

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Architect
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Kanasz Robert21-Sep-12 1:35
professionalKanasz Robert21-Sep-12 1:35 
Questionerros Pin
oliass17-Aug-12 0:00
oliass17-Aug-12 0:00 
QuestionI like it Pin
Kanyungu7-Mar-12 21:15
Kanyungu7-Mar-12 21:15 
GeneralMy vote of 5 Pin
RaghuramanKan24-Feb-12 13:25
RaghuramanKan24-Feb-12 13:25 
GeneralMy vote of 5 Pin
Rahul Rajat Singh21-Feb-12 6:02
professionalRahul Rajat Singh21-Feb-12 6:02 

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.