3.用WPF实现2048
2019-03-29 本文已影响0人
半半百
终于来到了这里,废话不多说,开搞!
界面实现
想怎么画就怎么画,为所欲为之为所欲为。
<Window x:Class="WpfApp.MainWindow"
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"
xmlns:local="clr-namespace:WpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="550" Width="400" KeyDown="Window_KeyDown">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60*"/>
<RowDefinition Height="60*"/>
<RowDefinition Height="400*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Viewbox Grid.RowSpan="2" >
<Label Content="2048" />
</Viewbox>
<Viewbox Grid.Row="0" Grid.Column="1" VerticalAlignment="Bottom">
<TextBlock Text=" Score "/>
</Viewbox>
<Viewbox Grid.Row="1" Grid.Column="1" VerticalAlignment="Top">
<TextBlock x:Name="Score" Text=" 0 " />
</Viewbox>
<Viewbox Grid.Row="0" Grid.Column="2" VerticalAlignment="Bottom">
<TextBlock Text=" Best " />
</Viewbox>
<Viewbox Grid.Row="1" Grid.Column="2" VerticalAlignment="Top">
<TextBlock x:Name="Best" Text=" 0 " />
</Viewbox>
<Grid x:Name="grid" Grid.Row="2" Grid.ColumnSpan="3">
</Grid>
</Grid>
</Window>
有个小细节,因为中间使用了 Viewbox 来显示分数,Viewbox会根据文本长度自动调节字体的大小,如下。
这里需要将字符串统一设置成固定的长度,当小于这个长度时两边补充些空格,让分数显示更统一一些。
为String增加一个扩展方法,实现这个功能。
这时为Score赋值时可以直接写为
Score.Text = g.Score.ToString().FormatForLength(5);
,
public static string FormatForLength(this string _string,int len)
{
string res = _string.ToString();
int count = len - res.Length;
string temp = "";
for (int i = 0; i < count; i++)
{
temp += " ";
}
res = temp + res + temp;
return res;
}
动画逻辑
2048的游戏里,当玩家无操作的时候,无动画,玩家操作一次播一次动画,这种情况可以很简单的将动画部分抽离出来。
开始游戏
>>>显示当前游戏状态
>>>玩家操作
>>>生成动画并播放
>>>显示当前游戏状态
>>>玩家操作
>>>生成动画并播放
······
那么这里可以简单的将
当前游戏状态
,和生成动画
抽象出来,不封装太深,整个接口吧。
interface IWpf2048UI
{
//为了将每个方块和它的动画关联起来,建立一个NameScope,
//生成方块时将方块加进去,生成动画时从中拿出来关联
NameScope NameScope { get; set; }
//动画的持续时间
Duration Duration { get; set; }
//生成游戏画板(当前游戏状态)
Panel PanelFactory(G2048 g);
//生成游戏动画(生成动画并关联到画板中)
Storyboard StoryboardFactory(Dictionary<BizLogic.Point, BizLogic.Point> moved, Size blockSize);
}
交互逻辑
有了上面的接口,交互逻辑如下。
有个小细节,当游戏动画正在播放时,玩家又一次按下了方向键可能会导致此次动画播放有问题。
这里可以加上一把锁🔒,当开始播放动画时加锁,加锁期间不响应玩家操作,播放完毕后开锁。具体实现如下bool operationLock
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
init();
}
G2048 g;
IWpf2048UI iWpf2048UI;
bool operationLock = false;
int Row;
int Colum;
/// <summary>
/// 初始化
/// </summary>
private void init()
{
Row = 5;
Colum = 5;
g = new G2048(Row,Colum);
this.SetValue(NameScope.NameScopeProperty, iWpf2048UI.NameScope);
this.grid.Children.Add(iWpf2048UI.PanelFactory(g));
}
/// <summary>
/// 按键事件
/// </summary>
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (operationLock)
{
return;
}
G2048.Direction direction;
switch (e.Key)
{
case Key.Up: direction = G2048.Direction.Up; break;
case Key.Down: direction = G2048.Direction.Down; break;
case Key.Left: direction = G2048.Direction.Left; break;
case Key.Right: direction = G2048.Direction.Right; break;
default:
return;
}
if (!g.Operate(direction))
{
return;
}
playAnim();
}
/// <summary>
/// 播放动画
/// </summary>
private void playAnim()
{
Console.WriteLine(g.ToString());
operationLock = true;
Storyboard storyboard = iWpf2048UI.StoryboardFactory(g.Moved, new Size(grid.ActualWidth / Row, grid.ActualHeight / Colum));
storyboard.Completed += storyboard_Completed;
storyboard.Begin(this);
}
/// <summary>
/// 动画结束
/// </summary>
void storyboard_Completed(object sender, EventArgs e)
{
grid.Children.Clear();
grid.Children.Add(iWpf2048UI.PanelFactory(g));
Score.Text = g.Score.ToString().FormatForLength(5);
operationLock = false;
}
接口的实现
接下来实现一下动画接口
IWpf2048UI
,因为将生成方块和生成方块的动画分割开了,需要一个映射关系NameScope
,统一将生成的方块按坐标命名,存入NameScope
,生成动画时按命名附加。
这里统一下方块的命名方式,按其坐标命名,即
public static string Sign(this BizLogic.Point point)
{
return "X"+point.X +"Y"+ point.Y;
}
以下是默认的动画呈现方式
Wpf2048UIDefault
class Wpf2048UIDefault : IWpf2048UI
{
public NameScope NameScope { get; set; } = new NameScope();
public Duration Duration { get; set; } = new Duration(TimeSpan.FromMilliseconds(500));
public Panel PanelFactory(G2048 g)
{
Grid tempGrid = new Grid();
for (int i = 0; i < g.Row; i++)
{
tempGrid.RowDefinitions.Add(new RowDefinition());
}
for (int j = 0; j < g.Colum; j++)
{
tempGrid.ColumnDefinitions.Add(new ColumnDefinition());
}
for (int i = 0; i < g.Row; i++)
{
for (int j = 0; j < g.Colum; j++)
{
if (g.Map[i, j] == 0)
{
continue;
}
FrameworkElement element = this.FrameworkElementFactory(new BizLogic.Point(i, j), g.Map[i, j]);
element.SetValue(Grid.RowProperty, i);
element.SetValue(Grid.ColumnProperty, j);
tempGrid.Children.Add(element);
}
}
return tempGrid;
}
private FrameworkElement FrameworkElementFactory(BizLogic.Point point,int value)
{
Border border = new Border();
Transform transform = new TranslateTransform();
border.RenderTransform = transform;
NameScope.Add(point.Sign(),transform);
Viewbox viewbox = new Viewbox();
TextBlock block = new TextBlock();
if (value != 0)
{
block.Text = value.ToString().FormatForLength(3);
block.VerticalAlignment = VerticalAlignment.Center;
block.HorizontalAlignment = HorizontalAlignment.Center;
block.TextAlignment = TextAlignment.Center;
}
border.Margin = new Thickness(3);
viewbox.Child = block;
border.Child = viewbox;
border.Background = new SolidColorBrush(getColorByValue(value));
return border;
}
public virtual Storyboard StoryboardFactory(Dictionary<BizLogic.Point, BizLogic.Point> Moved,Size blockSize)
{
if (Moved.Count == 0)
{
return null;
}
Storyboard storyboard = new Storyboard();
DependencyProperty dp;
double len;
//如果是横向
if (Moved.First().Key.X== Moved.First().Value.X)
{
dp = TranslateTransform.XProperty;
len = blockSize.Width;
}
else
{
dp = TranslateTransform.YProperty;
len= blockSize.Height;
}
foreach (var move in Moved)
{
double lenTemp = (move.Value.X - move.Key.X + move.Value.Y - move.Key.Y) * len;
//获取一个移动动画
DoubleAnimation daTemp = DoubleAnimationFactory(lenTemp);
//使指定的动画的UI载体
Storyboard.SetTargetName(daTemp, move.Key.Sign());
//使动画与UI载体的属性相关联
Storyboard.SetTargetProperty(daTemp, new PropertyPath(dp));
//指定场景的时间,并把各个对像的动画添加到场景里面
storyboard.Children.Add(daTemp);
}
storyboard.Duration = Duration;
storyboard.Completed += storyboard_Completed;
return storyboard;
}
void storyboard_Completed(object sender, EventArgs e)
{
NameScope.Clear();
}
/// <summary>
/// 根据数值返回不同的显示内容
/// </summary>
protected virtual string getStringByValue(int value)
{
return value.ToString().FormatForLength(3);
}
/// <summary>
/// 根据数值返回不同的颜色
/// </summary>
protected virtual Color getColorByValue(int value)
{
Color color = Colors.BurlyWood;
switch (value)
{
//返回不同的颜色
}
return color;
}
测试下
将接口实现放进去。
接下来就是见证奇迹的时刻,F5走起
iWpf2048UI = new Wpf2048UIDefault
{
Duration = new Duration(TimeSpan.FromMilliseconds(500))
};