在Windows 8中,Metro风格的应用程序以其简洁、直观的界面而受到用户的喜爱。本文将介绍如何在Windows 8中开发一个经典的15拼图游戏,包括游戏的基础结构、查找方法、打乱方法、检查胜利条件、移动物品以及检查游戏是否可解等步骤。
15拼图(也称为宝石拼图、老板拼图、十五游戏、神秘方块等)是一种滑动拼图游戏,由一个框架内的编号方块组成,方块顺序随机,且有一个方块缺失。游戏的目标是通过滑动操作,将方块按顺序排列。
构建拼图层有多种方法,本文选择使用Canvas和StackPanel的组合。首先,创建一个带有棋盘图像的底层Canvas,然后在其上放置另一个Canvas,包含16个StackPanel,每个Panel的大小为100x100。接着,为每个StackPanel添加一个95x95大小的图像,并设置Tag属性为图像的编号。同时,添加一个计时器来计算解决拼图所需的时间,以及一个整数属性来记录用户移动的次数。
在代码中,查找功能是常用的。例如,通过图像ID查找StackPanel,找到空的Panel,通过位置查找值,以及找到具有特定Tag值的图像的父级Panel。以下是C#代码示例:
StackPanel FindStackPanelByTagId(int tag) {
if (tag == 16) {
return (from stackPanel in ContentPanel.Children.OfType()
where stackPanel.Children.Count == 0
select stackPanel).First();
} else {
return (from stackPanel in ContentPanel.Children.OfType()
from img in stackPanel.Children.OfType ()
where Convert.ToInt32(img.Tag) == tag
select stackPanel).First();
}
}
接下来,找到没有子元素的StackPanel的位置,以及通过StackPanel位置获取Tag值。
有了拼图结构和查找方法后,下一步是打乱拼图。编写一个方法,运行n次,生成1到16之间的随机数,对于每个数字,找到当前持有它的StackPanel。如果第一个和第二个数字都小于16,则交换图像和Tag值。如果其中一个值为16,则清除一个StackPanel中的项目。
用户每次移动后,执行循环并检查1到16的值。如果数字不在正确的顺序中,则不发生任何事情。否则,停止游戏计时器并显示胜利消息。
在应用物品移动之前,需要检查几件事情。首先,检查物品是否可以移动,检查特定物品周围的所有Panel(-1、+1、-4、+4),如果其中一个是空的,则可以移动。
StackPanel CanMove(UIElement itemToMove) {
var count = ContentPanel.Children.Count;
for (var i = 0; i < count; i++) {
if (!(ContentPanel.Children[i] is StackPanel)) continue;
var stackPanel = (StackPanel)ContentPanel.Children[i];
if (!stackPanel.Children.Contains(itemToMove)) continue;
if (!IsBorderSwich(i, i + 1) && i + 1 <= 15 && ContentPanel.Children[i + 1] != null &&
((StackPanel)ContentPanel.Children[i + 1]).Children.Count == 0)
return ((StackPanel)ContentPanel.Children[i + 1]);
if (!IsBorderSwich(i, i - 1) && i - 1 > -1 && ContentPanel.Children[i - 1] != null &&
((StackPanel)ContentPanel.Children[i - 1]).Children.Count == 0)
return ((StackPanel)ContentPanel.Children[i - 1]);
if (i + 4 <= 15 && ContentPanel.Children[i + 4] != null &&
((StackPanel)ContentPanel.Children[i + 4]).Children.Count == 0)
return ((StackPanel)ContentPanel.Children[i + 4]);
if (i - 4 > -1 && ContentPanel.Children[i - 4] != null &&
((StackPanel)ContentPanel.Children[i - 4]).Children.Count == 0)
return ((StackPanel)ContentPanel.Children[i - 4]);
}
return null;
}
接下来,如果两个要交换的物品都在棋盘边界内,则不执行任何操作。然后,根据用户的点击移动物品。
这部分非常重要,因为n-puzzle的一半起始位置是无法解决的。Johnson & Story (1879) 使用奇偶性论证来展示n-puzzle的一半起始位置是无法解决的,无论进行多少次移动。这是通过考虑在任何有效移动下不变的瓷砖配置函数来实现的,然后使用这个来将所有可能的标记状态空间划分为两个等价类:可达状态和不可达状态。
bool CheckIfSolvable() {
var n = 0;
for (var i = 1; i <= 16; i++) {
if (!(ContentPanel.Children[i] is StackPanel)) continue;
var num1 = FindItemValueByPosition(i);
var num2 = FindItemValueByPosition(i - 1);
if (num1 > num2) {
n++;
}
}
var emptyPos = FindEmptyItemPosition();
return n % 2 == (emptyPos + emptyPos / 4) % 2 ? true : false;
}
public void NewGame() {
_moves = 0;
txtMoves.Text = "0";
txtTime.Text = Const.DefaultTimeValue;
Scrambles();
while (!CheckIfSolvable()) {
Scrambles();
}
_startTime = DateTime.Now.AddSeconds(1);
_timer.Start();
GridScrambling.Visibility = System.Windows.Visibility.Collapsed;
}