目标:游戏界面显示 拼图面板 、 提示面板(拼好的样子)、移动次数。
通过拖动小拼图片 进行拼图 ,最终拼成提示面板的样子。提示面板中的蓝色块表示空白块。
成功后提示成功!可通过点击重玩按钮,在任何时候选择重玩!
大致如图:
主要考虑四个问题:
1、界面的绘制 2、随机生成初始状态 3、拼图片移动的判断和实现 4、成功状态的判断
1、绘制界面
考虑到landscape的情况 ,还是用StackPanel爽点,将整个content放在一个StackPanel内,当方向改变的时候,只要改变StackPanel的Orientation就Ok了
然后将拼图面板用Grid 表示,主要是使用行列很方便,下面的 提示面板 、重玩按钮、 以及移动次数TextBlock放在一个StackPanel中
xaml中如下:
<StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" Orientation="Vertical" Loaded="ContentPanel_Loaded">
<Grid Name="puzzlePanel" Margin="0 0" ></Grid>
<StackPanel Name="bottomStackPanel" Orientation="Horizontal" >
<Grid Name="TargetPanel" Grid.Column="0" Margin="20 20 20 20" Width="180" Height="180">
</Grid>
<Grid >
<TextBlock Text="移动次数: 0" Name="moveTimesTextBlock" Grid.Column="1" HorizontalAlignment="Center" Margin="0 30 0 0" VerticalAlignment="Top"></TextBlock>
<Button Name="replayButton" Content="重玩" Margin="40,70,23,70" Click="replayButton_Click" Width="128"></Button>
</Grid> </StackPanel>
</StackPanel>
最重要的是拼图面板的绘制,我的思路是 根据需要的小拼图片数 给puzzlePanel添加相应数量的行和列,创建拼图面板的CreatePuzzlePanel如下:
//创建拼图面板
//获取puzzlePanel 并为其设置宽高,背景色
//然后为其定义指定的行列,用来存放 小拼图片
public void CreatePuzzlePanel()
{
puzzlePanel.Height = puzzlePanelHeight;
puzzlePanel.Width = puzzlePanelWidth;
puzzlePanel.Background = new SolidColorBrush(backgroundColor) ;
for (int i = 0; i < rowNum; i++)
{
puzzlePanel.RowDefinitions.Add(new RowDefinition());
}
for (int j = 0; j < columNum; j++)
{
puzzlePanel.ColumnDefinitions.Add(new ColumnDefinition());
}
Grid.SetRow(puzzlePanel,0);
}
每个拼图片使用一个Grid 然后在Grid中放一个TextBlock 用来显示数字,拼图片的大小根据拼图面板的大小进行计算
最重要的还有需要设置一个空白的拼图片,这里通过随机生成一个空白的位置来实现,添加的时候这个空白的位置就设为空,就不会显示拼图片了,InitPuzzle方法会记录下该片对应的值,供创建提示面板使用。
然后将Grid添加到puzzlePanel容器中,这里通过设置Grid在puzzlePanel中的Row,Column来实现同时将Grid添加到一个List中,以便移懂拼图片的时候使用。这里设置Grid的Row和Column
通过Grid.SetRow和 Grid.SetColumn实现,当然这里显示的数字需要随机生成,是使用CreateRandNumArry方法实现的,这个后面再说。初始化拼图的 InitPuzzle方法如下
//初始化拼图面板
//首先创建拼图片数组,数组大小等于rowNum*columNum
//然后生成生成一个 大小 小于 拼图片数组大小 的随机数,该位置不显示拼图片
//然后就是依次生成所需要的拼图片,在拼图片上显示 随机的数字
public int InitPuzzle()
{
int emptyPieceValue = 0;
int[] pieceValueArry = CreateRandNumArry();
//int[] pieceValueArry = new int[] { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
int emptyValue = rand.Next(rowNum*columNum)+1;
double pieceWidth=puzzlePanelWidth/columNum;
double pieceHeight=puzzlePanelHeight/rowNum;
int index=0;
//重玩的时候需要先清空拼图面板,然后重新添加
puzzlePanel.Children.Clear();
for (int i = 0; i < rowNum; i++)
{
for (int j = 0; j < columNum; j++)
{
//判断该位置是否需要显示拼图片
if (getCorrectNum(i, j) != emptyValue)
{
Grid puzzlePiece = new Grid();
Grid container = new Grid();
container.Width = pieceWidth;
container.Height = pieceHeight;
container.Margin = new Thickness(1, 1, 1, 1);
container.Name = (pieceValueArry[index] + 1).ToString();
container.ManipulationDelta += new EventHandler<ManipulationDeltaEventArgs>(container_ManipulationDelta);
container.ManipulationCompleted += new EventHandler<ManipulationCompletedEventArgs>(container_ManipulationCompleted);
container.Background = new SolidColorBrush(piecesColor);
puzzlePanel.Children.Add(container);
TextBlock content = new TextBlock();
content.Text = (pieceValueArry[index] + 1).ToString();
content.VerticalAlignment = VerticalAlignment.Center;
content.HorizontalAlignment = HorizontalAlignment.Center;
content.FontSize = 50;
container.Children.Add(content);
Grid.SetRow(container, i);
Grid.SetColumn(container, j);
puzzlePiecesArry.Add(puzzlePiece);
}
else
{
//否则的话这个 拼图片肯定是空白的了,所以在数组中存储空
//以标志此拼图片
puzzlePiecesArry.Add(null);
emptyPieceValue = pieceValueArry[index] + 1;
}
index++;//获取下一个 拼图片上的 值
}
}
//记录空白拼图片的 值是为了targetPanel中将空白拼图片显示为特别的颜色
return emptyPieceValue;
}
完了之后还要绘制 提示面板 其绘制方法和绘制拼图面板差不多,不过我把小片都放在Border元素中了,这里用到了InitPuzzle返回的空白片对应的值,创建提示面板的CreateTargetPanel如下:
//创建 目标面板 用来提示最终效果
public void CreateTargetPanel(int emptyPieceValue)
{
TargetPanel.Children.Clear();
TargetPanel.RowDefinitions.Clear();
TargetPanel.ColumnDefinitions.Clear();
TargetPanel.Background = new SolidColorBrush(backgroundColor);
for (int i = 0; i < rowNum; i++)
{
TargetPanel.RowDefinitions.Add(new RowDefinition());
}
for (int j = 0; j < columNum; j++)
{
TargetPanel.ColumnDefinitions.Add(new ColumnDefinition());
}
double targetPieceWidth = TargetPanel.Width / columNum;
double targetPieceHeight = TargetPanel.Height / rowNum;
int index = 0;
for (int i = 0; i < rowNum; i++)
{
for (int j = 0; j < columNum; j++)
{
Border border = new Border();
border.Width = targetPieceWidth;
border.Height = targetPieceHeight;
border.Margin = new Thickness(1, 1, 1, 1);
if (getCorrectNum(i, j) != emptyPieceValue)
{
border.Background = new SolidColorBrush(piecesColor);
}
else
{
border.Background = new SolidColorBrush(Color.FromArgb(10, 100, 100, 0));
}
TextBlock content = new TextBlock();
content.Text = (++index).ToString();
content.VerticalAlignment = VerticalAlignment.Center;
content.HorizontalAlignment = HorizontalAlignment.Center;
content.FontSize = 20;
border.Child = content;
Grid.SetRow(border, i);
Grid.SetColumn(border, j);
TargetPanel.Children.Add(border);
}
}
}
2、随机生成初始状态
这里我的思路是根据 拼图片的数目num,生成一个num大小的随机数组
我先定义一个 List<int> temp 依次存储0-(num-1)的整数,然后每次随机从该List中选择一个数添加到随机数组,同时将该数从List中删除,这样就生成了一个随机数组。
将随机数组交给上面的InitPuzzle处理,InitPuzzle 依次读取数组中的数 作为其显示的内容。
这样界面上显示的数字就是随机的了,并且有一个是空白 拼图片。
生成随机数组的 CreateRandNumArry 方法如下:
//生成一个 随机的 拼图片值 数组,这些是拼图片上显示的数字
//首先建立一个List 存储num个顺序的数字,
//然后从中随机取出一个数 给randArry 并将该数从List中移除
//重复此动作,直到生成所需的随机数组
public int[] CreateRandNumArry()
{
int num = columNum * rowNum;
int []randArry=new int[num];
List<int> temp = new List<int>();
for (int i = 0; i < num; i++)
{
temp.Add(i);
}
for (int j = 0; j < num; j++)
{
int randValue=temp[rand.Next(temp.Count)];
randArry[j] =randValue;
temp.Remove(randValue);
}
return randArry;
}
3、拼图片移动的判断和实现
这个应该是最重要的部分了,只有拼图片 相邻位置为空白片的时候 他才可以移动,所以可以通过判断元素的相邻位置是否为空,该判断可通过puzzlePiecesArry(存储当前所有拼图块Grid,通过获取Grid的Row和Column 很容易找到相邻元素)中存储的值来进行比较
若为空,将其与空元素交换位置,并且交换puzzlePiecesArry数组中的值,以便下一次继续比较
该比较应该在拼图片的 ManipulationDelta 方法中实现 该方法和ManipulationCompleted已经在每个拼图块创建的时候注册过, container_ManipulationDelta方法实现如下:
void container_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
Grid grid = sender as Grid;
int row=Grid.GetRow(grid);
int colum=Grid.GetColumn(grid);
CompositeTransform transform = new CompositeTransform();
grid.RenderTransform = transform;
//因为puzzlePiecesArry中保存有各个 拼图片 且空拼图片对应的值为null
// 所以可以直接判断要移动元素的相邻元素是否为空即可判断是否可以移动
//若不为空,则不执行任何操作
//若为空,将其与空元素交换位置,并且交换puzzlePiecesArry数组中的值,以便下一次继续比较
Grid puzzlepiece=puzzlePiecesArry[getCorrectNum(row, colum) - 1];
double moveX = e.DeltaManipulation.Translation.X;
double moveY = e.DeltaManipulation.Translation.Y;
//向右
if (moveX > 0 && colum < (columNum - 1) && puzzlePiecesArry[getCorrectNum(row, colum + 1) - 1] == null)
{
Grid.SetRow(grid, row);
Grid.SetColumn(grid, colum + 1);
puzzlePiecesArry[getCorrectNum(row, colum + 1) - 1] = puzzlePiecesArry[getCorrectNum(row, colum) - 1];
puzzlePiecesArry[getCorrectNum(row, colum) - 1] = null;
moveTimes++;
}
//向左
else if (moveX < 0 && colum > 0 && puzzlePiecesArry[getCorrectNum(row, colum-1) - 1] == null)
{
Grid.SetRow(grid, row);
Grid.SetColumn(grid, colum - 1);
puzzlePiecesArry[getCorrectNum(row, colum -1) - 1] = puzzlePiecesArry[getCorrectNum(row, colum) - 1];
puzzlePiecesArry[getCorrectNum(row, colum) - 1] = null;
moveTimes++;
}
//向下
else if (moveY > 0 &&row < (rowNum - 1) && puzzlePiecesArry[getCorrectNum(row+1, colum) - 1] == null)
{
Grid.SetRow(grid, row + 1);
Grid.SetColumn(grid, colum);
puzzlePiecesArry[getCorrectNum(row+1, colum) - 1] = puzzlePiecesArry[getCorrectNum(row, colum) - 1];
puzzlePiecesArry[getCorrectNum(row, colum) - 1] = null;
moveTimes++;
}
//向上
else if (moveY < 0 && row > 0 && puzzlePiecesArry[getCorrectNum(row-1, colum ) - 1] == null)
{
Grid.SetRow(grid, row - 1);
Grid.SetColumn(grid, colum);
puzzlePiecesArry[getCorrectNum(row-1, colum) - 1] = puzzlePiecesArry[getCorrectNum(row, colum) - 1];
puzzlePiecesArry[getCorrectNum(row, colum) - 1] = null;
moveTimes++;
}
//触发一次后立即结束,否则的话 拼图片会一直移动,
//故此处一定要调用Complete方法
e.Complete();
e.Handled = true;
}
这里和上面用到的getCorrectNum方法是获取 row行colum列 应该对应的数字,即第0行0列应该对应1,0行1列对应2 等等,getCorrectNum如下:
//获取 row行colum列 应该对应的数字
private int getCorrectNum(int row, int colum)
{
return row * columNum + colum + 1;
}
当然在移动拼图块的时候还要修改 移动次数值,这个可以在 ManipulationCompleted方法中实现
container_ManipulationCompleted方法如下:
void container_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
//更新移动次数
this.moveTimesTextBlock.Text = "移动次数: "+moveTimes.ToString();
if (IsSuccess())
{
MessageBoxResult result= MessageBox.Show("恭喜你,成功啦!再来一局?", "Congratulate", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
replayButton_Click(sender, e);
}
}
}
4、成功状态的判断
每次操作完成后都要判断是否成功, 这个其实很简单了,根据拼图块所在的行和列可以很容易计算出该位置应该显示的数字 (通过getCorrectNum方法),和事实上显示的数字进行对比就可以
了,若除了空白块外都一样了,就成功了。判断成功的方法如下:
//判断是否成功
//分别判断每个PazzlePiece的数字是否与他应该对应的数字相等
//他所对应的数字 可以通过getCorrectNum(int row, int colum) 获得
public bool IsSuccess()
{
int row=0;
int colum=0;
foreach (Grid grid in puzzlePanel.Children)
{
if (grid != null)
{
row = Grid.GetRow(grid);
colum = Grid.GetColumn(grid);
//如果显示文本不等于 他应该对应的数字
//且不是空白拼图片
if (grid.Name != getCorrectNum(row, colum).ToString())
{
return false;
}
}
}
return true;
}
然后需要在 页面加载的时候 和 重玩按钮点击的时候 调用这些方法就可以了。
此外还有就是当屏幕方向改变的时候,需要调整这几部分显示的位置,这个我放在了PhoneApplicationPage_OrientationChanged 事件中:
private void PhoneApplicationPage_OrientationChanged(object sender, OrientationChangedEventArgs e)
{
if (e.Orientation == PageOrientation.PortraitUp)
{
this.ContentPanel.Orientation = System.Windows.Controls.Orientation.Vertical;
this.bottomStackPanel.Orientation = System.Windows.Controls.Orientation.Horizontal;
}
else
{this.ContentPanel.Orientation = System.Windows.Controls.Orientation.Horizontal;
this.bottomStackPanel.Orientation = System.Windows.Controls.Orientation.Vertical;
}
}
就Ok了,当屏幕方向改变成landscape时候显示效果如下:
成功时候显示如下:
这个小程序我还要再进行修改 ,
就是实现图片拼图 、行列的选择、大小的选择、计算机解决等
代码下载:NumberPuzzle.rar