3.用WPF實現(xiàn)2048

終于來到了這里,廢話不多說需忿,開搞诅炉!

界面實現(xiàn)

想怎么畫就怎么畫蜡歹,為所欲為之為所欲為。


<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>

有個小細節(jié)涕烧,因為中間使用了 Viewbox 來顯示分數(shù)月而,Viewbox會根據(jù)文本長度自動調(diào)節(jié)字體的大小,如下议纯。


這里需要將字符串統(tǒng)一設置成固定的長度父款,當小于這個長度時兩邊補充些空格,讓分數(shù)顯示更統(tǒng)一一些瞻凤。
為String增加一個擴展方法憨攒,實現(xiàn)這個功能。
這時為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的游戲里肝集,當玩家無操作的時候,無動畫结笨,玩家操作一次播一次動畫包晰,這種情況可以很簡單的將動畫部分抽離出來。

開始游戲 >>> 顯示當前游戲狀態(tài) >>> 玩家操作 >>> 生成動畫并播放 >>> 顯示當前游戲狀態(tài) >>> 玩家操作 >>> 生成動畫并播放······

那么這里可以簡單的將當前游戲狀態(tài)炕吸,和生成動畫 抽象出來伐憾,不封裝太深,整個接口吧赫模。

    interface IWpf2048UI
    {       
        //為了將每個方塊和它的動畫關(guān)聯(lián)起來树肃,建立一個NameScope,
        //生成方塊時將方塊加進去瀑罗,生成動畫時從中拿出來關(guān)聯(lián)
        NameScope NameScope { get; set; }
        //動畫的持續(xù)時間
        Duration Duration { get; set; }
        //生成游戲畫板(當前游戲狀態(tài))
        Panel PanelFactory(G2048 g);
        //生成游戲動畫(生成動畫并關(guān)聯(lián)到畫板中)
        Storyboard StoryboardFactory(Dictionary<BizLogic.Point, BizLogic.Point> moved, Size blockSize);
    }
交互邏輯

有了上面的接口胸嘴,交互邏輯如下。

有個小細節(jié)斩祭,當游戲動畫正在播放時劣像,玩家又一次按下了方向鍵可能會導致此次動畫播放有問題。
這里可以加上一把鎖??摧玫,當開始播放動畫時加鎖耳奕,加鎖期間不響應玩家操作,播放完畢后開鎖诬像。具體實現(xiàn)如下 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>
        /// 動畫結(jié)束
        /// </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;
        }
接口的實現(xiàn)

接下來實現(xiàn)一下動畫接口IWpf2048UI屋群,因為將生成方塊和生成方塊的動畫分割開了,需要一個映射關(guān)系NameScope坏挠,統(tǒng)一將生成的方塊按坐標命名芍躏,存入NameScope,生成動畫時按命名附加降狠。

這里統(tǒng)一下方塊的命名方式对竣,按其坐標命名庇楞,即

        public static string Sign(this BizLogic.Point point)
        {
            return "X"+point.X +"Y"+ point.Y;
        }

以下是默認的動畫呈現(xiàn)方式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載體的屬性相關(guān)聯(lián)
                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>
        /// 根據(jù)數(shù)值返回不同的顯示內(nèi)容
        /// </summary>
        protected virtual string getStringByValue(int value)
        {
            return value.ToString().FormatForLength(3);
        }
        /// <summary>
        /// 根據(jù)數(shù)值返回不同的顏色
        /// </summary>
        protected virtual Color getColorByValue(int value)
        {
            Color color = Colors.BurlyWood;
            switch (value)
            {
                //返回不同的顏色
            }
            return color;
        }
測試下

將接口實現(xiàn)放進去。
接下來就是見證奇跡的時刻柏肪,F(xiàn)5走起

            iWpf2048UI = new Wpf2048UIDefault
            {
                Duration = new Duration(TimeSpan.FromMilliseconds(500))
            };
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末姐刁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子烦味,更是在濱河造成了極大的恐慌聂使,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,865評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谬俄,死亡現(xiàn)場離奇詭異柏靶,居然都是意外死亡,警方通過查閱死者的電腦和手機溃论,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評論 3 399
  • 文/潘曉璐 我一進店門屎蜓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钥勋,你說我怎么就攤上這事炬转。” “怎么了算灸?”我有些...
    開封第一講書人閱讀 169,631評論 0 364
  • 文/不壞的土叔 我叫張陵扼劈,是天一觀的道長。 經(jīng)常有香客問我菲驴,道長荐吵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,199評論 1 300
  • 正文 為了忘掉前任赊瞬,我火速辦了婚禮先煎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘巧涧。我一直安慰自己薯蝎,他們只是感情好,可當我...
    茶點故事閱讀 69,196評論 6 398
  • 文/花漫 我一把揭開白布谤绳。 她就那樣靜靜地躺著占锯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闷供。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,793評論 1 314
  • 那天统诺,我揣著相機與錄音歪脏,去河邊找鬼。 笑死粮呢,一個胖子當著我的面吹牛婿失,可吹牛的內(nèi)容都是我干的钞艇。 我是一名探鬼主播,決...
    沈念sama閱讀 41,221評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼豪硅,長吁一口氣:“原來是場噩夢啊……” “哼哩照!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起懒浮,我...
    開封第一講書人閱讀 40,174評論 0 277
  • 序言:老撾萬榮一對情侶失蹤飘弧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后砚著,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體次伶,經(jīng)...
    沈念sama閱讀 46,699評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,770評論 3 343
  • 正文 我和宋清朗相戀三年稽穆,在試婚紗的時候發(fā)現(xiàn)自己被綠了冠王。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,918評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡舌镶,死狀恐怖柱彻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情餐胀,我是刑警寧澤哟楷,帶...
    沈念sama閱讀 36,573評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站骂澄,受9級特大地震影響吓蘑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坟冲,卻給世界環(huán)境...
    茶點故事閱讀 42,255評論 3 336
  • 文/蒙蒙 一磨镶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧健提,春花似錦琳猫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至紊遵,卻和暖如春账千,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背暗膜。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評論 1 274
  • 我被黑心中介騙來泰國打工匀奏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人学搜。 一個月前我還...
    沈念sama閱讀 49,364評論 3 379
  • 正文 我出身青樓娃善,卻偏偏與公主長得像论衍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子聚磺,可洞房花燭夜當晚...
    茶點故事閱讀 45,926評論 2 361