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

永无止境的记忆游戏

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (4投票s)

2009 年 4 月 30 日

CPOL

8分钟阅读

viewsIcon

38461

downloadIcon

1435

学习如何使用 XBap 编写一个小游戏

引言

2009年初,微软举办了一场编程竞赛,旨在激发大家对WPF/XAML技术的兴趣。挑战很简单:用10k代码创造出令人惊叹的东西。程序必须以ClickOnce、Silverlight或XBap的形式利用WPF。我一直在研究XBaps,并认为这种模式听起来很酷,所以我选择尝试为比赛创建一个小型XBap应用程序。

背景

如果您不熟悉,XBap是XAML浏览器应用程序的缩写。它是ClickOnce部署的下一个伟大飞跃;用户只需点击网页上的超链接即可启动应用程序。此链接指向已部署到托管应用程序的Web服务器上的.xbap文件。这与ClickOnce部署的发生方式完全相同,但对于XBap,客户端浏览器会下载应用程序并在浏览器空间内启动它。

Silverlight应用程序也托管在浏览器中。虽然XBap和Silverlight应用程序都在沙箱中运行,但XBap提供完整的WPF编程模型,而Silverlight只能使用.NET Framework的子集。下面是一个简短的表格来比较两者

XBap Silverlight
编程 完整的.NET/WPF框架 框架和WPF的子集
客户端计算机 需要完整的.NET Framework 无需安装框架,只需Silverlight客户端(约5MB下载)
平台 客户端计算机必须是Windows计算机或模拟的Windows 可以在支持Silverlight客户端的任何平台上运行(Windows、MacOS、即将推出Linux)
浏览器 可以在Internet Explorer或Firefox中运行 任何支持Silverlight客户端的浏览器
需要的客户端软件 .NET 2.0/3.5可再发行组件包(约30 MB) Silverlight运行时(约5 MB)
沙盒 两者都在沙箱中运行;即保护客户端计算机免受本地磁盘访问、注册表访问等。两者都可以使用服务(XBap必须使用源服务器)

游戏

我为比赛构思的游戏是创建一个记忆匹配游戏。这是一个简单的游戏,玩家“翻开”棋盘上的卡片,翻开第二张卡片后,检查玩家是否选择了匹配的卡片。如果没有,卡片会再次翻开以进行下一轮。诀窍在于记住错过的匹配,如果玩家能记住原始匹配的位置,则在其他地方找到匹配的卡片。

这款游戏的有趣之处在于它理论上永无止境。原始棋盘有4张卡片,需要玩家找到2个匹配项。然后玩家进入下一关,进行4个匹配项,然后是8个,然后是12个,以此类推,直到方块变得太小而无法点击!;o)

Using the Code

有两个源代码zip文件:一个是比赛的原始提交版本,另一个是“未压缩”版本。未压缩版本已重新格式化,并更改了一些对象和变量名称,以使代码更清晰。请记住,这是一项10k挑战,因此每个空格都计入总字节数。如果您查看压缩版本(为比赛提交的原始版本),您会发现我非常小心地利用了每一个字节!从现在开始,我将引用未压缩版本源代码中的变量和方法名称。

棋盘布局和卡片构建

整个游戏是使用一个Grid创建的,该Grid根据当前级别动态生成行和列。如果Grid包含ColumnDefinitionsRowDefinitions,但没有为它们分配高度或宽度,WPF的布局引擎会自动将它们均匀分布。发现这个小技巧就是我开始编写整个程序的原因。

主级别变量(_currentLevel)跟踪玩家在游戏中的进度,并用作每个棋盘的主要驱动值。棋盘本身是通过BuildGrid()方法构建的,该方法将行和列添加到Grid中。

下一步是使用BuildCards()方法创建“卡片”。有一个名为PlayerCard的自定义对象,它保存了我们将在本文稍后介绍的各种信息。

class PlayerCard
{
    public int myXCoordinate { get; set; }
    public int myYCoordinate { get; set; }
    public int partnerXCoordinate { get; set; }
    public int partnerYCoordinate { get; set; }
    public bool firstGuess { get; set; }
    public bool secondGuess { get; set; }
}

BuildCards()遵循以下基本算法

  1. 计算填充当前级别所需的卡片数量
    1. 每隔一个级别有一个奇数个空格,所以这些级别会有一个空白格
  2. 创建一张卡片
    1. 随机选择棋盘上的一个方格
    2. 如果此方格尚未被卡片填充,则用当前卡片填充它
    3. 如果此方格已填充,则随机选择棋盘上的另一个方格,直到找到一个空方格
      1. 这可能看起来并不那么优化。确实不是。此代码的重点是最小化。但我很惊讶C#能处理得这么好……在开发过程中,我可以观看它构建级别34、35……65、66……而速度没有太大的减慢!;o)
  3. 创建下一张卡片(这将是第一张卡片的匹配项)
    1. 遵循与第一张卡片相同的算法
    2. 找到一个方格并用这张卡片填充后,将这张卡片的网格坐标分配给第一张卡片
    3. 回到创建的第一张卡片,并将第二张卡片的坐标分配给它
      1. 这就是PlayerCard的……坐标变量的用途
  4. 重复此过程,直到所有卡片都创建完毕

每张“卡片”实际上只是一个Border对象。在所有Border对象创建完成后,每对都会使用RadialGradientBrushBackerGradient()方法分配一个随机化的背景。此方法会打乱GradientStop对象的颜色和偏移量,为每对创建不同的图案。作为一个“永无止境”的记忆游戏,随着玩家在级别中越来越高,这种区分效果越来越小。但这正是游戏的挑战所在!

算法最后为每张卡片创建一个Button对象。每个按钮的大小与Border完全相同,并位于Border之上。这些按钮都分配了CardClickCheckForMatch(...)来处理单击,以及CardMouseMoveAnimation(...)来处理漂亮的鼠标悬停淡入效果。因此,“翻卡”动作是通过在单击卡片时将按钮的Opacity动画设置为零来实现的。然后,如果没有找到匹配项,则通过将按钮的Opacity重新动画化回100来“翻回”两张卡片。

匹配检测和计分

通过使用匹配状态变量(_turnCount)和CardClickCheckForMatch(...)方法来实现匹配检测。如果当前单击是匹配检查中的第一次单击,则该方法会根据匹配的网格坐标在卡片集合(_PlayerCards)中查找被单击的卡片,并将其firstGuess成员标记为true并返回。如果这是本轮的第二次单击,则在网格中找到第二张卡片,并将其secondGuess设置为true。然后调用MatchCheck(...)方法。

MatchCheck(...)方法通过首先使用LINQ to Objects提取firstGuesssecondGuess已标记的卡片来确定是否找到匹配项。如果第一张卡片的myXCoordinate / myYCoordinate与第二张卡片的partnerXCoordinate / partnerYCoordinate相等,那么我们就找到了匹配项!

然后使用ReportScore(...)方法计算得分。分数由以下因素决定

  • 当前级别编号
  • 自上次匹配以来或自级别开始以来的已用时间
    • 匹配越快,分数越高
  • 连击序列
    • 每一次连续匹配而不失误都会使分数乘以10
  • 游戏开始时选择的难度设置

如果这是一次失误,则根据当前级别编号和难度设置,以类似的方式扣除分数。无论计算出多少分数,都会添加到总游戏分数中,然后在屏幕上显示在第二次单击的卡片上方,并带有漂亮的淡入淡出效果和TranslateTransform,以增加游戏的“吸引力”。

ScorePoint(...)(其中包含对ReportScore(...)的调用)还会确定这是否是游戏中的最终匹配,并奖励玩家他们的表现统计数据。然后,玩家有机会进入下一级别并继续享受乐趣!

难度设置

初次启动游戏时,玩家可以使用Slider选择难度设置。难度会以以下方式影响游戏……

  • 向易 ←
    • 错过的匹配翻得更慢,以便有更多时间记住错过的卡片模式
    • 找到匹配项的计算中获得的奖励分数较低
    • 失误时扣除的分数较少
  • 向难 →
    • 失误时卡片翻得更快
    • 匹配时获得的奖励分数较高
    • 失误时扣除的分数较多

关注点

这是一次关于学习游戏编程和WPF/XAML的有趣冒险。我学到了很多WPF动画的新技巧。

历史

  • 2009 年 4 月 30 日:初始发布
© . All rights reserved.