美文网首页
3.用WPF实现2048

3.用WPF实现2048

作者: 半半百 | 来源:发表于2019-03-29 15:10 被阅读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))
                };
    

    相关文章

      网友评论

          本文标题:3.用WPF实现2048

          本文链接:https://www.haomeiwen.com/subject/intcbqtx.html