说明:我是我2011年发表在IT168上面的一篇空当接龙文章的第一部分,后面几部分不好查找了。当然,我主要侧重学习微软ASP.NET及Silverlight+Windows Phone 7开发等技术。搬到此处,仅供学员参考。另外,注意中仅针对中高级玩家,而且我使用的是随机的发牌技术。
在本篇中,我们将讨论空当接龙游戏开发中的基础编程工作。
一、定义全局变量
本游戏中使用的关键数据结构列举如下:
private int nMaxMovingCards = 13;
DispatcherTimer timer;
private Card CurrentCard;
private Card InverseColorCard;
private Card SecondInverseColorCard;
private Card PreviousMatchedCardInSeryAtBottom;
private List topElementList;
Card PreviousMatchedCardInCells;
int iGlobalCellsIII = -1;
List PlaceHolder;
Card[] Cells=new Card[4];
List[] FoundationPiles ;
List[] TableauPiles;
DateTime timeStart;
int zIndexForAll = 0;
private Cursor originalCursor;
private GameOver gameoverDlg;
private MoveMultiCardsDlg MoveMultiCardsDlg;
InverseColorClickBehavior[] InverseBehaviorArray;
int iGlobalBottomColumn = -1;
int iGlobalBottomColumn2 = -1;
int iGlobalCellsColumn = -1;
下面分别给出这些变量的作用介绍。
nMaxMovingCards:用于限制底部可移动的最大扑克数。
timer:一个定时器控件,用于控制游戏总进程。
CurrentCard:用于存储当前扑克。当你单击左上方可用单元或下部发牌区的任意一张扑克(除去右上方的回收单元)时,当前扑克即被存储到此变量中。
InverseColorCard:用于存储当前的反色扑克。
SecondInverseColorCard:用于存储屏幕下部最近出现的反色扑克。
topElementList:用于记录屏幕下部可能存在的有效扑克序列,这个序列中的扑克将从一列移动到另一列上。
PreviousMatchedCardInSeryAtBottom:与变量topElementList联合应用,用于标记下部的有效序列中的第一张扑克。
Cells:一个Card数组,用于记录屏幕左上方右用单元区的4张扑克。
FoundationPiles:一个List数组,用于记录屏幕右上方回收单元中的四叠扑克。
TableauPiles:一个List数组,用于记录下部的8叠扑克。
InverseBehaviorArray:一个InverseColorClickBehavior数组,用于关联到52张扑克中以实现反色效果。
关于另外几个变量的作用,在此不再赘述。在接下来的文章中将结合代码介绍其作用。
二、选择移动多张扑克的对话框
在空当接龙游戏中,当你可能要移动多张扑克时将弹出一个对话框MoveMultiCardsDlg,如下图所示。你可以根据情况,选择移动一张还是多张。
注意到,在上图游戏主界面中,在我们刚刚单击过的下部栈区存在一组有序的扑克,而当前单击的这一列为空。此时将弹出一个相关对话框提示你想要把一张还是多张扑克移动到这个空栏。而移动扑克的实际张数依赖于可用的中介单元的总数而定。
三、初始化占位符
为了方便确定扑克的位置,我们在本游戏中也引入了一个称为PlaceHolder的List列表。我们共定义了16个占位符,它们的初始化是在游戏初始化的InitPlaceHolder方法中实现的。主要实现代码如下:
private void InitPlaceHolder(){
PlaceHolder = new List(16);
PlaceHolder.Add(new Rect(3 + PADDING, 0 + PADDING, WIDTH, border="1" Height1));//cell1
//……省略其他
PlaceHolder.Add(new Rect(343 + PADDING, 0 + PADDING, WIDTH, border="1" Height1));//topright1
//……省略其他
PlaceHolder.Add(new Rect(0 + PADDING, 114 + PADDING, WIDTH, border="1" Height2));//bottom1
//……省略其他
}
16个占位符中,4个相应于左上方的可用单元,4个相应于右上方的回收单元,剩下的8个对应于下部8列。
下面,我们来讨论如何使用上面定义好的数据结构来生成扑克与发牌的问题。
四、生成扑克与发牌
首先,发牌操作是在GenerateAndDealCards方法中实现的。
(1)生成52张随机顺序的扑克
首先,应当以随机的顺序生成52张扑克:
private void GenerateAndDealCards(){
Card[] arrPoker = new Card[52];
Card[] OrderPoker = new Card[52];
int[] RandomI = new int[52];
int curNum;
RandomKDiffer(0, 51, 52, RandomI);
for (int i = 0; i < 52; i++){
curNum = i < 13 ? i + 1 : ((i + 1) % 13 == 0 ? 13 : (i + 1) % 13);//range 1~13
if (i < 13) //0-12
OrderPoker[i] = new Card(curNum, Suits.Clubs);
else if (i < 26) //13-25
OrderPoker[i] = new Card(curNum, Suits.Diamonds);
else if (i < 39) //26-38
OrderPoker[i] = new Card(curNum, Suits.Hearts);
else
OrderPoker[i] = new Card(curNum, Suits.Spades);
arrPoker[RandomI[i]] = OrderPoker[i];//乱序
InverseBehaviorArray[i].Attach(arrPoker[RandomI[i]]);
//……省略其他
}
首先,我们定义一个Card数组arrPoker来存储52张随机顺序的扑克。另一个Card数组OrderPoker用于存储52张按自小到大顺序的扑克。
其次,借助于方法RandomKDiffer的帮助,我们生成序号从0到51的52张不同的扑克。
接下来,存储在数组arrPoker中的52张扑克都被初始化为正面向上(其实在空当接龙游戏中根本没有正面朝下的情况)。
最后,一旦生成一张扑克即把一个相关的InverseColorClickBehavior行为附加到其上。
(2)创建游戏主界面中的初始元素
当玩家按下菜单“Game”—“Start”后,界面底部的52张扑克准备就绪。下图给出了游戏开始时的运行时快照。
注意到,我们在上部添加了一些矩形控件用于修饰可用单元和回收单元。当然,你也可以采用其他的策略,但是我们必须注意由此产生的一些“影响”。
具体来说,本游戏中有两处涉及到这个“影响”。一处是前面讨论过的获取当前扑克牌的方法GetCurrentCard。另一处是我们应当何时及怎样添加上述矩形控件。
首先,我们选择在方法GenerateAndDealCards中添加这些矩形。不过,在单击“Start”菜单前,屏幕上是看不到这些矩形的。
其次,我们决定在正式加载扑克控件前以动态方法加载这些矩形相关的XAML代码。
那么,如何动态加载呢?下面讨论完成这项任务的LoadRectangleElementsFirst方法。此方法的关键代码如下:
private void LoadRectangleElementsFirst(){
XElement rectanglestr = XElement.Parse(
@"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Fill="Red" Stroke="Black" border="1" height="32" width="29" Canvas.Left="308" Canvas.Top="29" />");
cardContainer.Children.Add(XamlReader.Load(rectanglestr.ToString()) as UIElement);
XElement p3Str = XElement.Parse(
@"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Fill="White" Stretch="Fill" border="1" height="100" width="1"
UseLayoutRounding="False" Canvas.Left="75" Data="M319,100 L319,8"
Stroke="#FFBEFF00"/>");
cardContainer.Children.Add(XamlReader.Load(p3Str.ToString()) as UIElement);
XElement p2Str = XElement.Parse(
@"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Fill="White" Stretch="Fill" Stroke="Black" border="1" height="100"
width="1" UseLayoutRounding="False" Canvas.Left="0" Data="M319,100
L319,8"/>");
cardContainer.Children.Add(XamlReader.Load(p2Str.ToString()) as UIElement);
//……省略其他
cardContainer.Children.Add(XamlReader.Load(p0_Copy6Str.ToString()) as UIElement);
}
在上面代码中,有如下几点值得注意:
使用LINQ to XML技术动态创建XAML元素。
程序开始必须先添加对于程序集System.Xml.Linq.dll的引用。
创建了大量的XElement对象并填充以繁琐的XAML标记。
?在每一个XElement对象中,必须要声明两个默认的XML命名空间;否则,我们会遇到如下运行时错误:AG_E_PARSER_MISSING_DEFAULT_NAMESPACE。
五、把扑克控件添加到容器控件中
在游戏的开始,仅有屏幕下部存在扑克。其中,从左边起的前4列各有7张随机生成的扑克,后4列放置6张随机生成的扑克。相关代码如下:
for (int column = 0; column < 4; column++){
for (int i = 7 * column; i < 7 * (column + 1); i++) {
Canvas.SetLeft(arrPoker[i], (double)PlaceHolder[8 + column].X);
Canvas.SetTop(arrPoker[i], (double)PlaceHolder[8 + column].Y + LARGE_OFFSET * (i - 7 * column));
TableauPiles[column].Add(arrPoker[i]);
cardContainer.Children.Add(arrPoker[i]);
}
}
for (int column = 0; column < 4; column++){
for (int i = 6 * column + 28; i < 6 * column + 34; i++){
Canvas.SetLeft(arrPoker[i], (double)PlaceHolder[12 + column].X);
Canvas.SetTop(arrPoker[i], (double)PlaceHolder[12 + column].Y + LARGE_OFFSET * (i - 6 * column - 28));
TableauPiles[column + 4].Add(arrPoker[i]);
cardContainer.Children.Add(arrPoker[i]);
}
}
上面代码通过二个简单的二重foreach循环语句把生成的随机顺序的52张扑克依次添加到容器控件cardContainer中。
六、开始玩游戏
通过菜单控件来控制本游戏的总体流程不但简化了程序设计而且使程序操作类似于传统桌面应用。启动游戏的代码如下所示:
private void MenuItem_Click(object sender, RoutedEventArgs e){
string sText = (sender as MenuItem).MenuText.Trim();
switch (sText) {
case "Start":
InitAndStartGame();
bStart = true;
break;
//…省略其他
七、再次玩游戏
作为一个完整的游戏程序,提供重玩游戏是基本的要求。顾名思义,在重新开始下一场游戏前应当把所有数据结构恢复到其最初的数据状态。事实上,这里面还存
在一定的技巧。一方面,恢复工作依赖于你所使用的数据结构;另一方面,恢复工作还依赖于我们从何位置启动这个恢复操作。本游戏中使用的恢复操作相关代码如
下:
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
string sText = (sender as MenuItem).MenuText.Trim();
switch (sText) {
case "Start":
InitAndStartGame();
break;
//…省略其他
在此,通过调用一个助理方法InitAndStartGame,把所有内容准备就绪。
下面,我们看一下在纸牌游戏和空当接龙游戏初始化阶段的主要区别。
private void InitAndStartGame(){
if (timer != null) {
timer.Tick -= new EventHandler(timer_Tick);
timer.Stop();
timer = null;
}
zIndexForAll = 0;
PlaceHolder = null;
timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 1);
timer.Tick += new EventHandler(timer_Tick);
InitPlaceHolder();
TableauPiles = null;
FoundationPiles = null;
TableauPiles = new List[8];
for (int i = 0; i < 8; i++)
TableauPiles[i] = new List();
FoundationPiles = new List[4];
for (int i = 0; i < 4; i++)
FoundationPiles [i] = new List();
InverseBehaviorArray = null;
InverseBehaviorArray = new InverseColorClickBehavior[52];
for (int i = 0; i < 52; i++)
InverseBehaviorArray[i] = new InverseColorClickBehavior();
GenerateAndDealCards();
for (int i = 0; i < 4; i++)
Cells[i] = null;
nMaxMovingCards = 13;
iGlobalCellsIII = -1;
iGlobalBottomColumn = -1;
iGlobalBottomColumn2 = -1;
iGlobalCellsColumn = -1;
bStart = false;
timeStart = new DateTime();
timeStart = DateTime.Now;
timer.Start();
}
首先,初始化两个重要结构:TableauPiles和FoundationPiles。在此,我们使用数据结构TableauPiles存储游戏主界面底部的8列扑克,而数据结构FoundationPiles用于存储回收单元中的4列扑克。
接下来,我们创建52个InverseColorClickBehavior类型的行为,它们分别用于关联到52张不同的扑克上。再次强调,在Silverlight编程中,一个行为只能关联到一个UI元素上。
最后应当注意的是,鼠标相关事件的订阅是在MainPage控件的MainPage_Loaded方法中完成的,而不是在上面的InitAndStartGame方法中,恕不再赘举有关代码。