C# WPF 自制扫雷小游戏
By
jerryxjr1220
at 2023-10-08 • 0人收藏 • 356人看过
用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
#3
回复#1 @admin :
修改一个MakeTransparent函数,增加一个ContinueTransparent函数
实现了一开一大片功能。
//扫雷并判断是否触雷 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; } } }
登录后方可回帖
你这没一开一大片的,![](/static/editor/dialogs/emotion/images/pp/i_f02.png)