65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (9投票s)

2012 年 2 月 21 日

Ms-PL

7分钟阅读

viewsIcon

42812

downloadIcon

2435

Hangman是一款非常简单的Windows Phone 7文字游戏。

Sample Image - maximum width is 600 pixels

引言

这是一个非常简单的游戏,通常会给用户一个要猜测的单词。用户通常有 7-8 次机会来猜测单词。每次猜测错误,都会像上面图片中显示的那样,画出一部分绞刑架。这个游戏的基本要求非常简单

1. 用户应能够选择难度级别

2. 用户应能够在 Windows Phone 上通过输入(或点击键盘按钮)来猜测一个字母

3. 跟踪分数并在退出/进入游戏时保存/重新加载设置

4. 能够根据难度级别生成单词

所需软件

用于 Windows Phone 工具包的 Silverlight

Windows Phone 开发人员工具

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 月 - 添加了声音

© . All rights reserved.