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:
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.
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:
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);
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.
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:
<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:
<!---->
<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:
void AddLettersBlocks()
{
BlockHolder.Children.Clear();
for (int i = 0; i < currentWord.WordLength; i++)
{
LetterBlock block = new LetterBlock();
if (currentWord.WordLength > 6)
{
block.Width = Application.Current.Host.Content.ActualWidth / currentWord.WordLength;
block.Height = block.Width;
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:
SoundEffect backgroundMusic;
...
StreamResourceInfo infoGamebackground = Application.GetResourceStream(
new Uri("Audio/GameBackGround.wav", UriKind.Relative));
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