C# WPF 自制扫雷小游戏

By jerryxjr1220 at 2023-10-08 • 0人收藏 • 385人看过

screenshots.gif

用C# WPF开发的练手小项目

<hc:GlowWindow x:Class="WPFMine.Views.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:vm="clr-namespace:WPFMine.ViewModels"
               xmlns:hc="https://handyorg.github.io/handycontrol"
               d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
               mc:Ignorable="d"
               Title="Mine Game" Height="490" Width="410" ResizeMode="NoResize"
               FontFamily="JetBrains Mono"
               FontSize="{StaticResource TextFontSize}">
    <Window.Resources>
    </Window.Resources>
    <hc:SimplePanel>
        <ScrollViewer x:Name="scrollViewer" VerticalScrollBarVisibility="Auto">
            <StackPanel>
                <WrapPanel HorizontalAlignment="Center" Margin="0,10, 0, 2">
                    <TextBox Text="{Binding TimeUsed, StringFormat={}{0:mm:ss}}" FontSize="20" Foreground="DarkRed"
                             Margin="10,0" />
                    <Button Style="{StaticResource ButtonIcon}"
                            hc:IconElement.Geometry="{StaticResource ClockGeometry}" Foreground="DarkBlue" />
                    <TextBox Text="{Binding RemainingMines}" FontSize="20" Foreground="DarkRed" Margin="10,0" />
                </WrapPanel>
                <Grid>
                    <ItemsControl x:Name="itemscontrol" Width="400" Height="400" Background="LightGray"
                                  ItemsSource="{Binding GameObjects}">
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <Canvas />
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                        <ItemsControl.ItemContainerStyle>
                            <Style TargetType="ContentPresenter">
                                <Setter Property="Canvas.Left" Value="{Binding XPosActual}" />
                                <Setter Property="Canvas.Top" Value="{Binding YPosActual}" />
                            </Style>
                        </ItemsControl.ItemContainerStyle>
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Border BorderBrush="DarkGray" BorderThickness="1" Width="40" Height="40"
                                        HorizontalAlignment="Center" VerticalAlignment="Center">
                                    <Border x:Name="OuterBorder" Background="WhiteSmoke" BorderThickness="1"
                                            BorderBrush="WhiteSmoke" Width="35" Height="35">
                                        <Border.RenderTransform>
                                            <TranslateTransform X="-2" Y="-1" />
                                        </Border.RenderTransform>
                                        <ToggleButton x:Name="MineCanvas" Style="{StaticResource ToggleButtonDefault}"
                                                      FontSize="12" BorderThickness="0"
                                                      Width="35" Height="35" IsChecked="False"
                                                      Click="MineCanvas_OnClick"
                                                      MouseWheel="MineCanvas_OnMouseWheel" />
                                    </Border>

                                </Border>
                                <DataTemplate.Triggers>
                                    <DataTrigger Binding="{Binding GameObjectType}" Value="0">
                                        <Setter TargetName="MineCanvas" Property="Background" Value="LightGray" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding GameObjectType}" Value="1">
                                        <Setter TargetName="MineCanvas" Property="Background" Value="LightGray" />
                                        <Setter TargetName="MineCanvas" Property="Foreground" Value="Blue" />
                                        <Setter TargetName="MineCanvas" Property="Content" Value="1" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding GameObjectType}" Value="2">
                                        <Setter TargetName="MineCanvas" Property="Background" Value="LightGray" />
                                        <Setter TargetName="MineCanvas" Property="Foreground" Value="Green" />
                                        <Setter TargetName="MineCanvas" Property="Content" Value="2" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding GameObjectType}" Value="3">
                                        <Setter TargetName="MineCanvas" Property="Background" Value="LightGray" />
                                        <Setter TargetName="MineCanvas" Property="Foreground" Value="Chocolate" />
                                        <Setter TargetName="MineCanvas" Property="Content" Value="3" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding GameObjectType}" Value="4">
                                        <Setter TargetName="MineCanvas" Property="Background" Value="LightGray" />
                                        <Setter TargetName="MineCanvas" Property="Foreground" Value="Red" />
                                        <Setter TargetName="MineCanvas" Property="Content" Value="4" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding GameObjectType}" Value="5">
                                        <Setter TargetName="MineCanvas" Property="Background" Value="LightGray" />
                                        <Setter TargetName="MineCanvas" Property="Foreground" Value="Cyan" />
                                        <Setter TargetName="MineCanvas" Property="Content" Value="5" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding GameObjectType}" Value="6">
                                        <Setter TargetName="MineCanvas" Property="Background" Value="LightGray" />
                                        <Setter TargetName="MineCanvas" Property="Foreground" Value="Brown" />
                                        <Setter TargetName="MineCanvas" Property="Content" Value="6" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding GameObjectType}" Value="7">
                                        <Setter TargetName="MineCanvas" Property="Background" Value="LightGray" />
                                        <Setter TargetName="MineCanvas" Property="Foreground" Value="NavajoWhite" />
                                        <Setter TargetName="MineCanvas" Property="Content" Value="7" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding GameObjectType}" Value="8">
                                        <Setter TargetName="MineCanvas" Property="Background" Value="LightGray" />
                                        <Setter TargetName="MineCanvas" Property="Foreground" Value="Black" />
                                        <Setter TargetName="MineCanvas" Property="Content" Value="8" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding GameObjectType}" Value="9">
                                        <Setter TargetName="MineCanvas" Property="Background" Value="LightGray" />
                                        <Setter TargetName="MineCanvas" Property="Foreground" Value="OrangeRed" />
                                        <Setter TargetName="MineCanvas" Property="Content" Value="X" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding GameObjectType}" Value="10">
                                        <Setter TargetName="MineCanvas" Property="Background" Value="LightGray" />
                                        <Setter TargetName="MineCanvas" Property="Foreground" Value="GreenYellow" />
                                        <Setter TargetName="MineCanvas" Property="Content" Value="V" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding IsClicked}" Value="false">
                                        <Setter TargetName="MineCanvas" Property="Opacity" Value="0" />
                                        <Setter TargetName="MineCanvas" Property="BorderThickness" Value="0" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding IsClicked}" Value="true">
                                        <Setter TargetName="MineCanvas" Property="Opacity" Value="1" />
                                        <Setter TargetName="MineCanvas" Property="BorderThickness" Value="0" />
                                        <Setter TargetName="MineCanvas" Property="Background" Value="FloralWhite" />
                                        <Setter TargetName="OuterBorder" Property="BorderThickness" Value="0" />
                                        <Setter TargetName="OuterBorder" Property="Background" Value="FloralWhite" />
                                    </DataTrigger>
                                </DataTemplate.Triggers>
                            </DataTemplate>

                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </Grid>

            </StackPanel>
        </ScrollViewer>
    </hc:SimplePanel>
</hc:GlowWindow>
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;

namespace WPFMine.Models;

public partial class GameObject : ObservableObject
{
    public enum ObjectType
    {
        Blank,
        N1,
        N2,
        N3,
        N4,
        N5,
        N6,
        N7,
        N8,
        Mine,
        Flag
    }

    [ObservableProperty] private int _blockSize;

    [ObservableProperty] private ObjectType _gameObjectType;
    [ObservableProperty] private bool _isClicked;
    [ObservableProperty] private int _xPos;
    [ObservableProperty] private int _xPosActual;
    [ObservableProperty] private int _yPos;
    [ObservableProperty] private int _yPosActual;

    public GameObject()
    {
    }

    public GameObject(string _type, int _x, int _y, int _blockSize = 40)
    {
        switch (_type)
        {
            case "B":
                GameObjectType = ObjectType.Blank;
                break;
            case "1":
                GameObjectType = ObjectType.N1;
                break;
            case "2":
                GameObjectType = ObjectType.N2;
                break;
            case "3":
                GameObjectType = ObjectType.N3;
                break;
            case "4":
                GameObjectType = ObjectType.N4;
                break;
            case "5":
                GameObjectType = ObjectType.N5;
                break;
            case "6":
                GameObjectType = ObjectType.N6;
                break;
            case "7":
                GameObjectType = ObjectType.N7;
                break;
            case "8":
                GameObjectType = ObjectType.N8;
                break;
            case "M":
                GameObjectType = ObjectType.Mine;
                break;
            case "F":
                GameObjectType = ObjectType.Flag;
                break;
        }

        XPos = _x;
        YPos = _y;
        BlockSize = _blockSize;
        XPosActual = XPos * BlockSize;
        YPosActual = YPos * BlockSize;
        IsClicked = false;
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        if (e.PropertyName == "XPos") XPosActual = XPos * BlockSize;
        if (e.PropertyName == "YPos") YPosActual = YPos * BlockSize;
    }
}
using System;
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using WPFMine.Models;

namespace WPFMine.ViewModels;

public partial class MainViewModel : ObservableObject
{
    [ObservableProperty] private int _blockSize = 40;
    [ObservableProperty] private BindingList<GameObject> _gameObjects;
    [ObservableProperty] private int _remainingMines;
    [ObservableProperty] private DateTime _timeUsed;
    public int[,] MineCountMap;
    public string[,] MineMap;

    public MainViewModel()
    {
        MineMap = new string[10, 10];
        MineCountMap = new int[10, 10];
        GameObjects = new BindingList<GameObject>();
        GameInit();

        //发送消息
        //WeakReferenceMessenger.Default.Send(new CustomizedMessage(new MessageModel()));
    }

    //游戏初始化
    public void GameInit()
    {
        MineMap = new string[10, 10];
        MineCountMap = new int[10, 10];
        for (var x = 0; x < 10; x++)
        for (var y = 0; y < 10; y++)
        {
            MineMap[x, y] = "B";
            MineCountMap[x, y] = 0;
        }

        //设置地雷数
        RemainingMines = 10;
        for (var i = 0; i < RemainingMines; i++)
        {
            (var x, var y) = GenerateMine();
            MineMap[x, y] = "M";

            if (x > 0 && y > 0) MineCountMap[x - 1, y - 1]++;
            if (x > 0 && y < 9) MineCountMap[x - 1, y + 1]++;
            if (x < 9 && y > 0) MineCountMap[x + 1, y - 1]++;
            if (x < 9 && y < 9) MineCountMap[x + 1, y + 1]++;
            if (x > 0) MineCountMap[x - 1, y]++;
            if (x < 9) MineCountMap[x + 1, y]++;
            if (y > 0) MineCountMap[x, y - 1]++;
            if (y < 9) MineCountMap[x, y + 1]++;
        }

        TimeUsed = new DateTime(3000, 1, 1, 0, 0, 0);
        GameObjects = new BindingList<GameObject>();

        for (var x = 0; x < 10; x++)
        for (var y = 0; y < 10; y++)
            if (MineMap[x, y] == "M")
                GameObjects.Add(new GameObject("M", x, y, BlockSize));
            else if (MineMap[x, y] == "B" && MineCountMap[x, y] > 0)
                GameObjects.Add(new GameObject(MineCountMap[x, y].ToString(), x, y, BlockSize));
            else
                GameObjects.Add(new GameObject("B", x, y, BlockSize));
    }

    //生成随机地雷
    public (int, int) GenerateMine()
    {
        var random = new Random(DateTime.Now.Microsecond);
        var x = random.Next(0, 10);
        var y = random.Next(0, 10);
        return (x, y);
    }

    //扫雷并判断是否触雷
    public bool MakeTransparent(int x, int y)
    {
        var isover = false;
        foreach (var go in GameObjects)
        {
            if (go.XPos == x && go.YPos == y && go.GameObjectType == GameObject.ObjectType.Mine)
            {
                go.IsClicked = true;
                isover = true;
                return isover;
            }

            if (go.XPos == x && go.YPos == y) go.IsClicked = true;
        }

        return isover;
    }
}

//定义消息
// public class CustomizedMessage : ValueChangedMessage<MessageModel> // PropertyChangedMessage // RequestMessage ( Async + ..)
// {
//     public CustomizedMessage(MessageModel value) : base(value)
//     {
//     }
// }
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using HandyControl.Controls;
using WPFMine.Models;
using WPFMine.ViewModels;
using MessageBox = HandyControl.Controls.MessageBox;

namespace WPFMine.Views;

/// <summary>
///     Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : GlowWindow
{
    private readonly MainViewModel mainViewModel;
    public DispatcherTimer timer;

    public MainWindow()
    {
        InitializeComponent();

        #region 注册并接受消息

        //WeakReferenceMessenger.Default.Register<CustomizedMessage>(this, (o, m) => { MessageBox.Show("Received!"); });

        #endregion


        mainViewModel = new MainViewModel();
        DataContext = mainViewModel;
        //计时器设置
        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromSeconds(1);
        timer.Tick += (sender, args) => { mainViewModel.TimeUsed += TimeSpan.FromSeconds(1); };
        timer.Start();
    }

    //点击扫雷
    private void MineCanvas_OnClick(object sender, RoutedEventArgs e)
    {
        var point = Mouse.GetPosition(itemscontrol);
        var xpos = (int)(point.X / mainViewModel.BlockSize);
        var ypos = (int)(point.Y / mainViewModel.BlockSize);

        foreach (var go in mainViewModel.GameObjects)
            //如果已经扫过,则跳过
            if (go.XPos == xpos && go.YPos == ypos && go.IsClicked)
                return;
        //扫雷,并判断是否游戏结束
        var isover = mainViewModel.MakeTransparent(xpos, ypos);
        if (isover)
        {
            timer.Stop();
            MessageBox.Show(
                $"You lose!\r\nTime used: {mainViewModel.TimeUsed.Minute} min {mainViewModel.TimeUsed.Second} sec",
                "Game Over");
            mainViewModel.GameInit();
            timer.Start();
        }
    }

    //标记地雷
    private void MineCanvas_OnMouseWheel(object sender, MouseWheelEventArgs e)
    {
        var point = Mouse.GetPosition(itemscontrol);
        var xpos = (int)(point.X / mainViewModel.BlockSize);
        var ypos = (int)(point.Y / mainViewModel.BlockSize);
        foreach (var go in mainViewModel.GameObjects)
        {
            //如果是地雷,且未被扫过,则标记,地雷数-1
            if (go.XPos == xpos && go.YPos == ypos && go.GameObjectType == GameObject.ObjectType.Mine && !go.IsClicked)
            {
                go.GameObjectType = GameObject.ObjectType.Flag;
                go.IsClicked = true;
                mainViewModel.RemainingMines--;
                break;
            }

            //如果不是地雷,则游戏结束
            if (go.XPos == xpos && go.YPos == ypos && go.GameObjectType != GameObject.ObjectType.Mine && !go.IsClicked)
            {
                timer.Stop();
                MessageBox.Show(
                    $"You lose!\r\nTime used: {mainViewModel.TimeUsed.Minute} min {mainViewModel.TimeUsed.Second} sec",
                    "Game Over");
                mainViewModel.GameInit();
                timer.Start();
            }
        }

        //剩余地雷数为0,游戏胜利
        if (mainViewModel.RemainingMines == 0)
        {
            timer.Stop();
            MessageBox.Show(
                $"You win!\r\nTime used: {mainViewModel.TimeUsed.Minute} min {mainViewModel.TimeUsed.Second} sec",
                "Game Over");
            mainViewModel.GameInit();
        }
    }
}


5 个回复 | 最后更新于 2023-10-08
2023-10-08   #1

你这没一开一大片的,

2023-10-08   #2

回复#1 @admin :

对的,最老的扫雷版本其实是不支持一开一大片的,win98以后的扫雷开始支持一开一大片了

这个功能其实很简单,只要把点开的相邻格子如果是空白的都把IsChecked属性设置为true就可以了(每开启一个空白格用递归实现一次)

2023-10-08   #3

回复#1 @admin :

修改一个MakeTransparent函数,增加一个ContinueTransparent函数

实现了一开一大片功能。

screenshots.gif

//扫雷并判断是否触雷
public bool MakeTransparent(int x, int y)
{
    var isover = false;
    foreach (var go in GameObjects)
    {
        if (go.XPos == x && go.YPos == y && go.GameObjectType == GameObject.ObjectType.Mine)
        {
            go.IsClicked = true;
            isover = true;
            return isover;
        }

        if (go.XPos == x && go.YPos == y && go.GameObjectType == GameObject.ObjectType.Blank && !go.IsClicked)
        {
            go.IsClicked = true;
            ContinueTransparent(x, y);
        }

        if (go.XPos == x && go.YPos == y) go.IsClicked = true;
    }

    return isover;
}

public void ContinueTransparent(int x, int y)
{
    foreach (var go in GameObjects)
    {
        if (Math.Abs(go.XPos - x) <= 1 && Math.Abs(go.YPos - y) <= 1 && go.GameObjectType == GameObject.ObjectType.Blank && !go.IsClicked)
        {
            go.IsClicked = true;
            ContinueTransparent(go.XPos,  go.YPos);
        }
    }
}


2023-10-08   #4

一开一大片连同旁边的数字一起开

//判断相邻格是否为空白,连续开启空白格及数字格
public void ContinueTransparent(int x, int y)
{
    foreach (var go in GameObjects)
    {
        if (Math.Abs(go.XPos - x) <= 1 && Math.Abs(go.YPos - y) <= 1 && go.GameObjectType == GameObject.ObjectType.Blank && !go.IsClicked)
        {
            go.IsClicked = true;
            ContinueTransparent(go.XPos,  go.YPos);
        }
        if (Math.Abs(go.XPos - x) <= 1 && Math.Abs(go.YPos - y) <= 1 && go.GameObjectType != GameObject.ObjectType.Blank 
            && go.GameObjectType != GameObject.ObjectType.Mine && go.GameObjectType != GameObject.ObjectType.Flag && !go.IsClicked)
        {
            go.IsClicked = true;
        }
        
    }
}


2023-10-08   #5

可以用图标字体替换地雷和旗子

这里贴不上来,不过只要用输入法里的图标字体替换即可。

登录后方可回帖

登 录
信息栏
 私人小站

本站域名

ChengXu.XYZ

投诉联系:  popdes@126.com



快速上位机开发学习,本站主要记录了学习过程中遇到的问题和解决办法及上位机代码分享

这里主要专注于学习交流和经验分享.
纯私人站,当笔记本用的,学到哪写到哪.
如果侵权,联系 Popdes@126.com

友情链接
Aardio官方
Aardio资源网


才仁机械


网站地图SiteMap

Loading...