2.8 数字拼图游戏设计
本节介绍一款数字拼图游戏的设计,如图2-21所示为该游戏运行时的情况。程序启动后,可以单击“Start”按钮,打乱数字的顺序,然后通过移动数字块来重新排列数字,当15个数字排列整齐后,游戏完成并会给出所用的时间与移动次数。
本游戏中应用了本章介绍的页面布局,如Grid面板的使用及元素的显示与隐藏。以下是程序的设计过程。
(1)启动Visual Studio Express 2010 for Windows Phone。在Windows操作系统中,单击“开始”→“所有程序”→“Microsoft Visual Studio 2010 Express” →“Microsoft Visual Studio 2010 Express for Windows Phone”。
图2-21 数字拼图(Number puzzle)运行情况
(2)新建应用程序项目。在“Start Page”页上,单击“New Project…”或者选择“File” →“New Project…”命令。在新建工程“New Project”窗口中,选择左侧项目模板为“Other Languages”→“Visual Basic”,然后在中间的项目模板列表中选择“Windows phoneApplication”,设置项目名称为“Puzzle”,指定项目文件存放的路径。单击“OK”按钮,创建新应用程序项目。
(3)选择“Windows phonePlatform”。选择应用程序运行的操作系统平台为Windows phoneos7.1。单击“OK”按钮,进入项目设计窗口。
(4)修改“MainPage.xaml”文件。将系统默认提供的MainPage.xaml,修改如下:
XAML代码:MainPage.xaml
<phone:PhoneApplicationPage x:Class="Puzzle.MainPage" … shell:SystemTray.IsVisible="True"> <!--LayoutRoot is the root grid where all page content is placed--> <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="489*" /> </Grid.RowDefinitions> <!--TitlePanel contains the name of the application and page title--> <StackPanel x:Name="DouDouSoft" Grid.Row="0" Margin="12,17,0,28"> <TextBlock x:Name="ApplicationTitle" Text="DouDouSoft" Style="{StaticResource PhoneTextNormalStyle}"/> <TextBlock x:Name="PageTitle" Text="Number puzzle" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}" FontSize="56" /> </StackPanel> <!--ContentPanel-place additional content here--> <Grid Name="GameContainer" Width="400" Height="400" Grid.Row="2" Margin="40" VerticalAlignment="Top"> <Grid.ColumnDefinitions> <ColumnDefinition Width="100" /> <ColumnDefinition Width="100" /> <ColumnDefinition Width="100" /> <ColumnDefinition Width="100" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="100" /> <RowDefinition Height="100" /> <RowDefinition Height="100" /> <RowDefinition Height="100" /> </Grid.RowDefinitions> </Grid> <Grid Grid.Row="1" Height="70" HorizontalAlignment="Center" Margin="2" Name="Grid1" VerticalAlignment="Center" Width="460"> <Grid.ColumnDefinitions> <ColumnDefinition Width="120*" /> <ColumnDefinition Width="157*" /> <ColumnDefinition Width="183*" /> </Grid.ColumnDefinitions> <Button Content="Start" Grid.Row="0" Height="76" HorizontalAlignment="Center" Margin="6" Name="Button1" VerticalAlignment="Center" Width="160" Grid.Column="1" /> <local:TimeDisplay x:Name="TotalTimeDisplay" Grid.Column="2" mdigitWidth="18" HorizontalAlignment="Right" Margin="0,0,12,0" FontSize="{StaticResource PhoneFontSizeLarge}" VerticalAlignment="Bottom" /> </Grid> </Grid> </phone:PhoneApplicationPage>
(5)添加程序引用的名称空间。本程序需要使用集合和Timer对象,因此,需要引用System.ComponentModel和System.Windows.Threading两个名称空间。在程序代码编辑窗口中打开MainPage.xaml.vb文件,在代码顶部加入如下代码:
Imports System.ComponentModel Imports System.Windows.Threading
(6)添加页面级公共变量。MainPage.xaml.vb文件代码中,用到多个页面级公共变量,在MainPage.xaml.vb文件的Public Sub New()之前,添加以下公共变量的定义代码:
Dim isstarting As Boolean=False '标记游戏是否开始,True表示开始,False表示未开始 Public TileList As New List(Of tile)(15) Dim timer As New DispatcherTimer()With {.Interval=TimeSpan.FromSeconds(0.1)} Dim TotalTime As New TimeSpan '记录总计用时 Dim Starttime As New DateTime '记录开始时间 Dim MoveCount As Integer=0 '移动次数
(7)定义页面载入事件。下面的代码首先通过两层For循环,往Grid面板(名称为“GameContainer”)中添加15个数字块tile,tile是一个用户自定义类(代码见tile.xaml和tile.xaml.vb),每个数字块标记值为1~15的数字,第16个数字块单独添加,其number值为-1。
Private Sub PhoneApplicationPage_Loaded(sender As System.Object,e As System.Windows.RoutedEventArgs)Handles MyBase.Loaded Dim tt As tile 'tile是一个用户自定义类,用于定义可移动的数字块 Dim x As Integer Dim y As Integer Dim i As Integer '通过循环,加入15个数字块,每行放4个 For x=0 To 14 tt=New tile tt.number=x+1 i=x \4 y=x Mod 4 tt.x=i tt.y=y Me.GameContainer.Children.Add(tt)'将数字方块添加到Grid面板中 Grid.SetRow(tt,i)'设置在Grid面板中的行位置 Grid.SetColumn(tt,y)'设置在Grid面板中的列位置 TileList.Add(tt)'将数字方块添加到集合中 Next '加入第十六数字块,与前面15个不同的是:这一块的数字是-1 tt=New tile tt.number=-1 tt.x=3 tt.y=3 Me.GameContainer.Children.Add(tt) Grid.SetRow(tt,3) Grid.SetColumn(tt,3) TileList.Add(tt) AddHandler timer.Tick,AddressOf Timer_Tick '绑定timer对象的事件句柄 End Sub
(8)定义“Start”按钮的事件代码。单击“Start”按钮,页面上的数字块顺序会被打乱,程序进入开始游戏状态,游戏状态记录在公共变量isstarting中。通过随机函数生成16以内的两个整数值,当这两个数不相等时,交换以这两个数字为Index值的对应数字块的number值,实现打乱。代码如下:
Private Sub Button1_Click(sender As System.Object,e As System.Windows.RoutedEventArgs)Handles Button1.Click Dim rnd As Random=New Random(System.DateTime.Now.Second) Dim n As Integer=0 For n=0 To 99 Dim n1 As Integer=rnd.Next(16) Dim n2 As Integer=rnd.Next(16) '随机取16以内的两整数值,当这两个数不相等时,交换这两个数字为索引的对应数'字块的number值,实现打乱 If(n1 <> n2)Then Dim tmp As Integer=TileList(n1).number TileList(n1).number=TileList(n2).number TileList(n2).number=tmp End If Next '调用refreshtile刷新页面数字块,使页面呈现打乱状态 refreshtile() '记录游戏状态为开始状态 Me.isstarting=True '记录游戏开始时的时间 Starttime=DateTime.UtcNow '显示用时记录器,用时记录器详细代码见TimeDisplay.xaml和'TimeDisplay.xaml.vb If Me.TotalTimeDisplay.Visibility=Windows.Visibility.Collapsed Then Me.TotalTimeDisplay.Visibility=Windows.Visibility.Visible End If '停止timer对象,清零总用时,然后重新开始,主要是为了在多次游戏中,用于清除上'次游戏的用时 Me.timer.Stop()reset() Me.timer.Start() End Sub
(9)定义reset()重置总用时子过程。该子过程非常简单,只完成将总用时清零操作,代码如下:
Private Sub reset() Me.TotalTime=TimeSpan.Zero End Sub
(10)定义触屏操作代码。用户在屏幕上触击数字块,以移动数字块。代码如下:
Private Sub PhoneApplicationPage_ManipulationStarted(sender As System.Object,e As System.Windows.Input.ManipulationStartedEventArgs)Handles MyBase.ManipulationStarted Dim tt As tile '判断触击的是否是数字块,数字块Tile外部用Border包裹,名称起始字符串为'Tborder,当这两条件满足时,执行移动代码 If(TypeOf e.ManipulationContainer Is Border And e.ManipulationContainer.GetValue(FrameworkElement.NameProperty).ToString().StartsWith("Tborder"))Then Dim b1 As Border=e.ManipulationContainer tt=b1.Parent '判断是否可移动和移动的方向,如果可移动,按可移动方向移动。 Dim ii As Integer=Me.CanMovePiece(tt.number) If ii=1 Then tmove(tt,TileList(4 * tt.x+tt.y-4)) End If If ii=2 Then tmove(tt,TileList(4 * tt.x+tt.y+1)) End If If ii=3 Then tmove(tt,TileList(4 *(tt.x+1)+tt.y)) End If If ii=4 Then tmove(tt,TileList(4 * tt.x+tt.y-1)) End If End If e.Complete() e.Handled=True '判断是否完成,如完成提示完成信息 If iscompleted()Then isstarting=False timer.Stop() MessageBox.Show("祝贺您!您成功了!" & Environment.NewLine & " 用时:" & Me.TotalTime.ToString & Environment.NewLine & " 移动次数:" & MoveCount & "下","游戏完成",MessageBoxButton.OK) Me.TotalTimeDisplay.Visibility=Windows.Visibility.Visible End If End Sub
(11)定义是否可移动判断函数。比较空白数字块(其number值为-1)与被触击数字块之间的位置,来判别是否可移动,以及移动的方向。代码如下:
Public Function CanMovePiece(ByVal num As Integer)As Integer 'num参数保存当前被触击数字块的数值 Dim totalPieces As Integer=15 Dim CurrentTileindex As Integer=-1 '当前被触击数字块的Index值 Dim emptyTileindex As Integer=-1 '空白数字块的Index值 Dim i As Integer=0 If Me.isstarting=False Then Return 0 End If For i=0 To totalPieces If(TileList(i).number=num)Then 'number=num,即为被触击块 CurrentTileindex=i ElseIf TileList(i).number=-1 Then 'number=-1,即为空白块 emptyTileindex=i End If Next If((CurrentTileindex=emptyTileindex+1)Or(CurrentTileindex=emptyTileindex-1)Or(CurrentTileindex=emptyTileindex+4)Or(CurrentTileindex=emptyTileindex-4))Then If(CurrentTileindex+1=emptyTileindex)Then Return 2 '可向右移动 ElseIf(CurrentTileindex-1=emptyTileindex)Then Return 4 '可向左移动 ElseIf(CurrentTileindex-4=emptyTileindex)Then Return 1 '可向上移动 ElseIf(CurrentTileindex+4=emptyTileindex)Then Return 3 '可向下移动 End If End If Return 0 '0表示不可移动 End Function
(12)定义移动函数。数字块移动函数通过将两个数字块的值交换,实现移动。参数t1、t2分别代表要互换的数字块。
Public Function tmove(ByVal t1 As tile,ByVal t2 As tile)As Boolean If t2.number=-1 Then Dim t3 As New Integer t3=t2.number t2.number=t1.number t1.number=t3 MoveCount=MoveCount+1 '统计移动次数 refreshtile() Return True Else Return False End If End Function
(13)定义游戏是否完成函数。当数字块集合中,所有数字块的Index(索引值)与number值(即显示的数值)相符时,即表示游戏完成。
Private Function iscompleted()As Boolean If isstarting Then '游戏处于开始状态时才进行判断 Dim iscompleted1 As Boolean=True Dim i As Integer=0 For i=0 To 14 '如果数字块集合中,Index与number不相等,则未完成,如果全部相符,则表示已排列完成 If i <>(TileList(i).number-1)Then iscompleted1=False End If Next Return(iscompleted1) Else Return False End If End Function
(14)定义timer执行函数。定时器timer每隔一定时间,更新总用时。代码如下(包括显示总用时的子过程):
Private Sub Timer_Tick(sender As Object,e As EventArgs) Dim Pasttime As TimeSpan=DateTime.UtcNow-Me.Starttime Me.Starttime=Me.Starttime+Pasttime Me.TotalTime=Me.TotalTime+Pasttime ShowCurrentTime() End Sub Private Sub ShowCurrentTime() Me.TotalTimeDisplay.mtime=Me.totalTime End Sub
(15)定义总用时显视器的可视特性。总用时显示在右上角,有时候变动的时间会干扰用户。因此,程序允许用户在需要时可以通过单击总用时显示器,达到隐藏总用时显示器的特性。代码如下:
Private Sub TotalTimeDisplay_MouseLeftButtonDown(sender As System.Object,e As System.Windows.Input.MouseButtonEventArgs)Handles TotalTimeDisplay.MouseLeftButtonDown Me.TotalTimeDisplay.Visibility=Windows.Visibility.Collapsed End Sub
上述完成的是MainPage.xaml.vb中的程序代码定义。接下来,需要创建两个程序用到的自定义控件。
(16)创建数字块控件。在解决方案管理器窗口中,用鼠标右键单击“Puzzle”项目,在弹出的快捷菜单中选择“Add…”→“New Item…”命令,在“New Item”对话框中,选择“Windows phoneUser Control”,Name为“tile.xaml”。双击打开“tile.xaml”文件,修改XAML代码如下。
XAML代码:tile.xaml
<UserControl x:Class="Puzzle.tile" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" d:DesignHeight="100" d:DesignWidth="100"> <Border Width="99" Height="99" Name="Tborder" Background="Blue"> <TextBlock Name="nu" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="36" /> </Border> </UserControl>
修改tile.xaml.vb程序代码如下。
VB.NET代码:tile.xaml.vb
Partial Public Class tile Inherits UserControl Private_x As Integer '记录在Grid面板中的行位置 Public Property x()As Integer Get Return_x End Get Set(ByVal value As Integer) _x=value End Set End Property Private_y As Integer '记录在Grid面板中的列位置 Public Property y()As Integer Get Return_y End Get Set(ByVal value As Integer) _y=value End Set End Property Private_number As Integer '数据块显示的数字 Public Property number()As Integer Get Return_number End Get Set(ByVal value As Integer) _number=value '如果不是空白块,设置数字块颜色为系统前景色,并显示数字 If number <>-1 Then Me.nu.Text=number.ToString Me.Tborder.Background=New SolidColorBrush(Application.Current.Resources("PhoneAccentColor")) Else '是空白块,不显示数字,且颜色为系统背景色 Me.nu.Text="" Dim clr As Color=Application.Current.Resources("PhoneBackgroundColor") Me.Tborder.Background=New SolidColorBrush(clr) End If End Set End Property Public Sub New() InitializeComponent() End Sub End Class
(17)创建时间显示器控件。与数字块控件类似,TimeDisplay.xaml代码如下。
XAML代码:TimeDisplay.xaml
<UserControl x:Class="Puzzle.TimeDisplay" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" VerticalAlignment="Center"> <StackPanel x:Name="LayoutRoot" Orientation="Horizontal"/> </UserControl>
修改TimeDisplay.xaml.vb代码如下:
Imports System Imports System.ComponentModel Imports System.Globalization Imports System.Windows Imports System.Windows.Controls Partial Public Class TimeDisplay Inherits UserControl Dim digitWidth As Integer Dim time As TimeSpan Public Sub New() InitializeComponent() If(DesignerProperties.IsInDesignTool)Then Dim textblock As New TextBlock textblock.Text="0:00.0" Me.LayoutRoot.Children.Add(textblock) End If End Sub Public Property mdigitWidth()As Integer Get Return digitWidth End Get Set(ByVal value As Integer) digitWidth=value mtime=time End Set End Property Public Property mtime()As TimeSpan Get Return time End Get Set(ByVal value As TimeSpan) Me.LayoutRoot.Children.Clear() Dim minutesString As String=value.Minutes.ToString() For i As Integer=0 To minutesString.Length-1 AddDigitString(minutesString(i).ToString()) Next Me.LayoutRoot.Children.Add(New TextBlock()With {.Text=":"}) AddDigitString((value.Seconds \10).ToString()) AddDigitString((value.Seconds Mod 10).ToString()) Me.LayoutRoot.Children.Add(New TextBlock()With {.Text=CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator}) AddDigitString((value.Milliseconds \100).ToString()) time=value End Set End Property Private Sub AddDigitString(ByVal digitString As String) Dim border As New Border()With {.Width=Me.digitWidth} border.Child=New TextBlock()With {.Text=digitString,.HorizontalAlignment=HorizontalAlignment.Center} Me.LayoutRoot.Children.Add(border) End Sub End Class
(18)更换程序图标。程序默认图标没有特色,可以根据需要更换为其他图标。本例中,ApplicationIcon.jpg和Background.jpg分别更换为如图2-22所示图标。
图2-22 ApplicationIcon.jpg和Background.jpg更换的图标