使用Xamarin开发移动应用示例——数独游戏(二)创建游戏界面

时间:2024-03-19 12:54:41

在本系列第一部分,我们创建了程序框架,现在我们创建游戏的界面,项目代码可以从Github下载:https://github.com/zhenl/ZL.Shudu 。代码随项目进度更新。

首先在Views目录下添加一个内容页面,名称为Game.xaml:

然后,在AppShell.xaml中增加这个页面导航:

    <TabBar>
        <ShellContent Title="游戏" Icon="icon_about.png" Route="Game"  ContentTemplate="{DataTemplate local:Game}" />  
        <ShellContent Title="关于" Icon="icon_about.png" Route="AboutPage" ContentTemplate="{DataTemplate local:AboutPage}" />
        <ShellContent Title="Browse" Icon="icon_feed.png" ContentTemplate="{DataTemplate local:ItemsPage}" />
    </TabBar>

如果这时运行程序,会发现页面底部增加了一个“游戏”分页,并且初始页面也改为这个页面。现在我们修改这个页面,改造为数独游戏界面。数独的界面是9X9的格子,这些格子组成九个九宫格,每个格子中是1-9的数字,玩家需要将所有的格子填满,并且行、列和九宫格中的数据不能重复。我们可以使用Grid进行布局,构造九行九列,每个格子中放置一个按钮(Button),用户按下按钮,弹出数字输入框,输入数字后,数字显示在按钮上,完成一个输入。
下面是页面的布局XAML:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ZL.Shudu.Views.Game">
    <ContentPage.Content>
        <StackLayout x:Name="outerStack"  Orientation="Vertical">
            <!-- Place new controls here -->
            <Grid x:Name="myGrid" IsVisible="True" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>

                    <RowDefinition Height="25"  />
                    <RowDefinition Height="25" />
                    <RowDefinition Height="25" />
                    <RowDefinition Height="25" />
                    <RowDefinition Height="25" />
                    <RowDefinition Height="25" />
                    <RowDefinition Height="25" />
                    <RowDefinition Height="25" />
                    <RowDefinition Height="25" />
                    <RowDefinition Height="40" x:Name="rowButton" />
                    <RowDefinition Height="40" x:Name="rowResult" />
                </Grid.RowDefinitions>
                <Label x:Name="lbFinish" Text="完成" IsVisible="false" Grid.Row="10" Grid.Column="0"  Grid.ColumnSpan="2" />
                <Label x:Name="lbTime" Grid.Row="10" Grid.Column="3" Grid.ColumnSpan="2" Text="" IsVisible="False"></Label>
                <Label x:Name="lbMessage" Grid.Row="10" Grid.Column="5" Grid.ColumnSpan="4" Text="" IsVisible="False"></Label>
            </Grid>

            <Grid x:Name="grdNumber" IsVisible="false">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="*" />

                </Grid.RowDefinitions>
            </Grid>

        </StackLayout>
    </ContentPage.Content>
</ContentPage>

在页面初始化时,生成数字按钮、游戏界面中每个单元格按钮,并根据初始数据确定按钮的文本和使能。

        public Game()
        {
            try
            {
                InitializeComponent();

                SetNumButtons();
                SetLayout();
                SetNewGame();
            }
            catch (Exception ex)
            {
                lbMessage.IsVisible = true;
                rowResult.Height = 40;
                lbMessage.Text = ex.Message;
                //throw;
            }
        }

设置数字按钮的函数代码如下:

      private void SetNumButtons()
        {
            var num = 1;
            for (var i = 0; i < 2; i++)
            {
                for (var j = 0; j < 5; j++)
                {
                    var btn = new Button();
                    if (num == 10)
                    {
                        btn.Text = "清除";
                        btn.Clicked += btn_Clear_Clicked;
                        btn.FontSize = 15;
                    }
                    else
                    {
                        btn.Text = num.ToString();
                        btn.Clicked += btn_Num_Clicked;
                        btn.FontSize = 16;
                    }
                    btn.Padding = 0;
                    grdNumber.Children.Add(btn, j, i);
                    numbuttons[i, j] = btn;
                    num++;
                }
            }
        }

设置数独界面按钮的代码如下:

       private void SetLayout()
        {
            for (var i = 0; i < 9; i++)
            {
                for (var j = 0; j < 9; j++)
                {
                    int m = i / 3;
                    int n = j / 3;
                    var btn = new Button();
                    var c = new Color(0.9, 0.9, 0.9);
                    if ((m + n) % 2 == 0)
                    {
                        c = new Color(0.5,0.5, 0.5);
                    }
                    btn.BackgroundColor = c;
                    btn.Padding = 0;
                    btn.Margin = 0;
                    btn.FontSize = 20;
                    myGrid.Children.Add(btn, i, j);
                    btn.Clicked += Btn_Clicked;
                    buttons[i, j] = btn;
                }
            }
        }

根据初始化数据,设置按钮状态的代码如下:

      private void SetGame(int[,] inp)
        {

            for (var i = 0; i < 9; i++)
            {
                for (var j = 0; j < 9; j++)
                {
                    chess[i, j] = inp[i, j];
                }
            }

            for (var i = 0; i < 9; i++)
            {
                for (var j = 0; j < 9; j++)
                {
                    var btn = buttons[i, j];
                    if (chess[i, j] > 0)
                    {
                        btn.Text = chess[i, j].ToString();
                        btn.IsEnabled = false;
                    }
                    else
                    {
                        btn.Text = "";
                        btn.IsEnabled = true;
                    }
                }
            }
            this.lbFinish.IsVisible = false;
            this.lbTime.IsVisible = false;
            this.lbMessage.IsVisible = false;
            this.rowResult.Height = 1;
            lbTime.Text = "";
            lbMessage.Text = "";

        }

按钮的响应事件如下:

     private void Btn_Clicked(object sender, EventArgs e)
        {
            currentButton = sender as Button;
            rowResult.Height = 1;
            rowButton.Height = 1;
            grdNumber.IsVisible = true;
        }

        private void btn_Clear_Clicked(object sender, EventArgs e)
        {
            if (currentButton == null) return;
            currentButton.Text = "";
            grdNumber.IsVisible = false;
            myGrid.IsVisible = true;
            rowResult.Height = 40;
            rowButton.Height = 40;
        }

        private void btn_Num_Clicked(object sender, EventArgs e)
        {
            currentNumBtn = sender as Button;
            
            int x = -1, y = -1;
            for (var i = 0; i < 9; i++)
            {
                for (var j = 0; j < 9; j++)
                {
                    if (buttons[i, j] == currentButton)
                    {
                        x = i;
                        y = j;
                        break;
                    }

                }
            }
            var num = int.Parse(currentNumBtn.Text);

            if (!checkval(x, y, num))
            {
                return;
            }
            currentButton.Text = currentNumBtn.Text;
            myGrid.IsVisible = true;
            grdNumber.IsVisible = false;
            rowResult.Height = 40;
            rowButton.Height = 40;

            if (IsFinish())
            {
                lbFinish.IsVisible = true;
                rowResult.Height = 40;
            }
        }

判断输入状态和游戏结束的函数如下:


        private bool checkval(int x, int y, int num)
        {
            for (var i = 0; i < 9; i++)
            {
                var buttonnum = string.IsNullOrEmpty(buttons[x, i].Text) ? 0 : int.Parse(buttons[x, i].Text);
                if (i != y && buttonnum == num) return false;
            }

            for (var i = 0; i < 9; i++)
            {
                var buttonnum = string.IsNullOrEmpty(buttons[i, y].Text) ? 0 : int.Parse(buttons[i, y].Text);
                if (i != x && buttonnum == num) return false;
            }

            int m = x / 3;
            int n = y / 3;
            for (int i = m * 3; i < (m + 1) * 3; i++)
            {
                for (int j = n * 3; j < (n + 1) * 3; j++)
                {
                    var buttonnum = string.IsNullOrEmpty(buttons[i, j].Text) ? 0 : int.Parse(buttons[i, j].Text);
                    if (i != x && j != y && buttonnum == num) return false;
                }
            }

            return true;
        }

        private bool IsFinish()
        {
            for (var i = 0; i < 9; i++)
            {
                for (var j = 0; j < 9; j++)
                {
                    if (string.IsNullOrEmpty(buttons[i, j].Text)) return false;
                }
            }
            return true;
        }
    }

运行效果如下:

现在基本界面已经搭建完成,后续需要增加允许回退、历史记录、退出保存以及自动完成等功能。项目代码可以从Github下载:https://github.com/zhenl/ZL.Shudu 。代码随项目进度更新。