Hangman - Windows Phone 7的简单文字游戏






4.74/5 (9投票s)
Hangman是一款非常简单的Windows Phone 7文字游戏。
引言
这是一个非常简单的游戏,通常会给用户一个要猜测的单词。用户通常有 7-8 次机会来猜测单词。每次猜测错误,都会像上面图片中显示的那样,画出一部分绞刑架。这个游戏的基本要求非常简单
1. 用户应能够选择难度级别
2. 用户应能够在 Windows Phone 上通过输入(或点击键盘按钮)来猜测一个字母
3. 跟踪分数并在退出/进入游戏时保存/重新加载设置
4. 能够根据难度级别生成单词
所需软件
用于 Windows Phone 工具包的 Silverlight
Visual Studio 2010
没有这些 软件,此应用程序将无法编译。
基本设计
此应用程序使用了基本的 Windows Phone 应用程序模板。一些设计和代码是从我的 Stopwatch 应用程序中重新使用的。
基本类图如下所示
AppSettings 类将数据持久化到/从隔离存储中读取。
WordList 类从嵌入的文本文件(一个字典)读取数据并保存到字符串列表中。Word 类管理显示给用户的单词。
GamePage 是主页面,显示字母块并跟踪游戏分数等。
LetterBlock 是一个用户控件,以漂亮的 UI 显示每个正确的字母。
游戏逻辑
游戏在开始时从填充的单词列表中选取一个随机单词(请参阅 WordList 类)。选取的单词长度基于游戏级别。简单级别单词长度为 3,中等级别为 6,困难级别为 8。一旦选定了单词,就会根据单词的长度在屏幕上创建空的字母块。每个字母块实际上是一个用户控件,包含一个矩形(外边界)和一个文本框。字母块被添加到堆栈面板中,并将水平和垂直对齐方式设置为居中,以便块显示在手机的中心。为了绘制hangman,我有 8 张位图。每次用户点击键盘上的一个字母时,该字母都会被添加到列表中,该列表跟踪用户点击的所有字母。对于每个错误的字母,我会根据用户点击错误字母的数量更改位图。一旦用户按错了 8 个字母,游戏就结束了,并显示如上图所示的图片。游戏结束后更新分数。每次获胜,分数增加 1000 点。每次失败,分数减少 1000 点。将所有分数(获胜、失败)和游戏级别存储在隔离存储中以跟踪历史记录。
词典
词典存储在一个文本文件中,该文件从 http://www.mieliestronk.com/wordlist.html 网站下载。这不是一个完整的词典,但对我们来说已经足够了。一个更好、更完整的词典是 Unix 单词词典,但其大小是上述词典的两倍以上。该文件在项目中用作嵌入资源,便于读取。我有一个 WordList 类,它读取词典文本文件并将数据保存在列表中。要查看完整的实现,请参阅 WordList 和 Word 类。
使用代码
如上所述,该应用程序读取一个词典文件,然后从词典中选取一个随机单词。问题在于 Windows Phone 没有应用程序路径,因此无法从某个位置读取文件。为了解决这个问题,我使用了嵌入式资源的词典文本,然后使用文件名“ApplicationName.Directoryname1....filename”,如下所示。
/// <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); } } }
为了返回一个有效的单词并使所有难度级别的游戏公平,代码会根据难度设置返回单词的最小字母数,因为对于困难难度设置来说,返回一个 3 个字母的单词可能太容易了。代码如下所示
/// <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 ""; }要检查输入的字母是否在单词中,代码会在单词中查找该字母,然后返回该字母的所有位置数组,因为同一个字母可能在单词中重复多次。
/// <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; }
这个游戏棘手的部分是,每当屏幕被点击时,就向用户显示键盘。为了做到这一点,我创建了一个隐藏的文本框,如下所示
<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" />
现在,在该页面的 MouseLeftButtonDown 事件中,我将焦点设置在此文本框上,这将使 Windows Phone 显示键盘。每次用户点击手机上的任何位置,焦点都会一直设置在此控件上。
绘制hangman非常简单。有八张图片,每张图片会根据错误按键次数进行替换。请参阅 GamePage 类中的 **DrawHangman** 方法。
背景颜色
为了设置一致的背景颜色,我使用了如下所示的线性渐变画笔
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="Black" Offset="0"/> <GradientStop Color="#FF29E7DA" Offset="1"/> </LinearGradientBrush>
用户界面
MainPage.xaml
这是主页,它将用户引导到所需页面。通过使用此页面,用户可以进入游戏页面、设置页面、教程页面或关于页面。
游戏页面是用户玩游戏的地方。
设置页面包含基本游戏设置,例如难度级别以及游戏结束时是否显示单词。
教程页面简要解释了游戏的理念以及如何玩。关于页面包含有关此游戏开发者的信息。用户可以通过点击电子邮件地址给我发送电子邮件。有关此功能的更多详细信息,请参阅我之前的文章 Stopwatch 。
GamePage.xaml
这是用户玩游戏的主页面。此页面的基本 xaml 如下所示
<!--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>如上所示,页面分为许多网格行。第零行网格显示分数、游戏总胜负次数和游戏级别。为了突出分数和其他信息,我将文本的不透明度设置为 50%,使其与背景融为一体。
第一行网格包含一个堆栈面板,其水平和垂直对齐方式设置为居中。这一行包含实际的字母块用户控件。添加字母块的代码如下所示
/// <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); } }正如您所见,向堆栈面板添加子元素非常容易。我也可以使用其他控件而不是堆栈面板,比如网格。这只是我个人选择使用堆栈面板。
第二行网格包含游戏消息,例如已按的键和其他游戏消息,如游戏结果等。
第四行网格包含一个“再玩一次”按钮,该按钮仅在游戏结束后显示。
第五行网格用于显示hangman位图,如前一节所述。
LetterBlock 用户控件
为了显示每个字母,创建了一个用户控件来表示单词中的每个字母。代码如下所示
<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>
每个用户控件本质上是一个包含矩形和文本块的网格,用于容纳一个字母。为了使矩形的边缘看起来平滑,将矩形的半径属性设置为 10。矩形设置为使用黑色和浅绿色渐变填充,以使其作为文本块脱颖而出。
添加声音
我们可以在应用程序中添加声音的两种方式
1. 使用 Silverlight 的 MediaElement 标签
2. 使用 Xna 框架的 SoundEffect 类
使用 MediaElement 的问题是,它有点重量级,对于这样一个简单的应用程序来说是一种负担,因为它会停止手机上所有其他媒体播放。我找不到一种简单的方法使用 MediaElement 循环播放声音文件,因此我决定使用 Xna 框架的 SoundEffect 类。要使用此类,只需在您的应用程序中添加 Microsoft.Xna.Framework.dll 的引用。以下代码在此应用程序中添加音乐
// 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;
如上所示,只需使用 StreamResourceInfo 类加载声音文件,然后加载声音效果。您无需创建 SoundEffect 类的实例。我仅使用 SoundEffect 类的实例来为该声音添加循环,使其连续播放。确保在添加声音文件时,将其添加为“Content”。要播放声音,请调用 SoundEffect 类的 Play 方法。
历史
V1.0 - 2012 年 2 月
V2.0 - 2012 年 3 月 - 添加了声音