C# WPF 实现五子棋&黑白棋小游戏
By
jerryxjr1220
at 2023-09-27 • 0人收藏 • 374人看过
用WPF画的界面,双击实现落子(黑白翻转)的效果。
加了一个小白点,瞬间有反光的立体效果了
<hc:GlowWindow x:Class="WPFBlackWhite.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:model="clr-namespace:WPFBlackWhite.Models" xmlns:vm="clr-namespace:WPFBlackWhite.ViewModels" xmlns:view="clr-namespace:WPFBlackWhite.Views" xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:local="clr-namespace:WPFBlackWhite" xmlns:hc="https://handyorg.github.io/handycontrol" d:DataContext="{d:DesignInstance Type=vm:MainViewModel}" mc:Ignorable="d" Title="Black & White Chess" Height="450" Width="400" ResizeMode="NoResize" FontFamily="JetBrains Mono" FontSize="{StaticResource TextFontSize}"> <Window.Resources> <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> <vm:BorderValueConverter Offset="2" x:Key="BorderValueConverter"/> </Window.Resources> <hc:SimplePanel> <ScrollViewer x:Name="scrollViewer" VerticalScrollBarVisibility="Auto"> <Grid Background="DarkGreen" Margin="0, 5"> <ItemsControl ItemsSource="{Binding Chesses}" MouseDoubleClick="Control_OnMouseDoubleClick"> <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"> <Border x:Name="ChessCanvas" Width="{Binding BlockSize, Converter={StaticResource BorderValueConverter}}" Height="{Binding BlockSize, Converter={StaticResource BorderValueConverter}}"/> </Border> <DataTemplate.Triggers> <DataTrigger Binding="{Binding ChessType}" Value="0"> <Setter TargetName="ChessCanvas" Property="Background" Value="DarkGreen"></Setter> </DataTrigger> <DataTrigger Binding="{Binding ChessType}" Value="1"> <Setter TargetName="ChessCanvas" Property="Background" Value="Black"></Setter> <Setter TargetName="ChessCanvas" Property="CornerRadius" Value="20"></Setter> </DataTrigger> <DataTrigger Binding="{Binding ChessType}" Value="2"> <Setter TargetName="ChessCanvas" Property="Background" Value="White"></Setter> <Setter TargetName="ChessCanvas" Property="CornerRadius" Value="20"></Setter> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid> </ScrollViewer> </hc:SimplePanel> </hc:GlowWindow>
GameModel:
using System; using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; namespace WPFBlackWhite.Models; public partial class GameModel: ObservableObject { public enum ChessTypes { NullChess, BlackChess, WhiteChess, } [ObservableProperty] private ChessTypes _chessType; [NotifyPropertyChangedFor("XPosActual")] [ObservableProperty] private int _xPos; [NotifyPropertyChangedFor("YPosActual")] [ObservableProperty] private int _yPos; [ObservableProperty] private int _xPosActual; [ObservableProperty] private int _yPosActual; protected override void OnPropertyChanged(PropertyChangedEventArgs e) { base.OnPropertyChanged(e); switch(e.PropertyName) { case "XPos": XPosActual = XPos * BlockSize; break; case "YPos": YPosActual = YPos * BlockSize; break; } } [ObservableProperty] private int _blockSize; public GameModel() { } public GameModel(string _type, int _x, int _y, int _blockSize=40) { switch (_type) { case "N": ChessType = ChessTypes.NullChess; break; case "B": ChessType = ChessTypes.BlackChess; break; case "W": ChessType = ChessTypes.WhiteChess; break; default: throw new ArgumentException(); } BlockSize = _blockSize; XPos = _x; YPos = _y; XPosActual = XPos * BlockSize; YPosActual = YPos * BlockSize; } public void ChangeToBlack() { ChessType = ChessTypes.BlackChess; } public void ChangeToWhite() { ChessType = ChessTypes.WhiteChess; } }
ViewModel:
using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Globalization; using System.Threading; using System.Windows.Data; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging.Messages; using WPFBlackWhite.Models; namespace WPFBlackWhite.ViewModels; public partial class MainViewModel: ObservableObject { [ObservableProperty] private BindingList<GameModel> _chesses; [ObservableProperty] private int _blockSize; public MainViewModel() { BlockSize = 40; Chesses = new BindingList<GameModel>(); for (int x = 0; x < 10; x++) { for (int y = 0; y < 10; y++) { var chess = new GameModel("N", x, y, BlockSize); if (x == 4 && y == 4 || x == 5 && y == 5) chess = new GameModel("B", x, y, BlockSize); else if (x == 4 && y == 5 || x == 5 && y == 4) chess = new GameModel("W", x, y, BlockSize); Chesses.Add(chess); } } //发送消息 //WeakReferenceMessenger.Default.Send(new CustomizedMessage(new MessageModel())); } public void FlipChess(int x, int y) { foreach (var chess in Chesses) { if (chess.XPos == x && chess.YPos == y) { switch (chess.ChessType) { case GameModel.ChessTypes.BlackChess: chess.ChangeToWhite(); break; default: chess.ChangeToBlack(); break; } break; } } } } public class BorderValueConverter : IValueConverter { public int Offset { get; set; } = 2; public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return int.Parse(value.ToString()) - Offset; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } //定义消息 // public class CustomizedMessage : ValueChangedMessage<MessageModel> // PropertyChangedMessage // RequestMessage ( Async + ..) // { // public CustomizedMessage(MessageModel value) : base(value) // { // } // }
主界面:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging.Messages; using HandyControl.Controls; using WPFBlackWhite.Models; using WPFBlackWhite.ViewModels; using MessageBox = HandyControl.Controls.MessageBox; namespace WPFBlackWhite.Views; /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : GlowWindow { private MainViewModel mainViewModel; public MainWindow() { InitializeComponent(); #region 注册并接受消息 //WeakReferenceMessenger.Default.Register<CustomizedMessage>(this, (o, m) => { MessageBox.Show("Received!"); }); #endregion mainViewModel = new MainViewModel(); this.DataContext = mainViewModel; } private void Control_OnMouseDoubleClick(object sender, MouseButtonEventArgs e) { var pos = e.GetPosition(sender as UIElement); //MessageBox.Show(pos.X.ToString() + " " + pos.Y.ToString()); var x = (int)(pos.X / mainViewModel.BlockSize); var y = (int)(pos.Y / mainViewModel.BlockSize); mainViewModel.FlipChess(x, y); } }
MVVM架构思维导图:
3 个回复 | 最后更新于 2023-09-27
2023-09-27
#2
上面示例中的黑白棋(五子棋)我是直接在WPF中用Border画的,但如果要用图像渲染的话,可以参考ChatGPT提供的方法:
// 加载图片到内存中 Bitmap bmp = new Bitmap("image_path"); // 定义切割后的小图片大小 int width = 100; int height = 100; // 遍历图片并切割为小图片 for (int x = 0; x < bmp.Width; x += width) { for (int y = 0; y < bmp.Height; y += height) { Rectangle cropArea = new Rectangle(x, y, width, height); Bitmap cropBmp = bmp.Clone(cropArea, bmp.PixelFormat); // 将小图片显示在WPF中的Image控件中 Image img = new Image(); img.Source = BitmapToImageSource(cropBmp); // 在Canvas中添加小图片 Canvas.SetLeft(img, x); Canvas.SetTop(img, y); canvas.Children.Add(img); } } // 将Bitmap对象转换为WPF中的ImageSource对象 private ImageSource BitmapToImageSource(Bitmap bmp) { MemoryStream ms = new MemoryStream(); bmp.Save(ms, ImageFormat.Bmp); BitmapImage bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.StreamSource = new MemoryStream(ms.ToArray()); bitmapImage.EndInit(); return bitmapImage; }
2023-09-27
#3
黑白棋逻辑
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Threading; using System.Windows.Data; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging.Messages; using WPFBlackWhite.Models; namespace WPFBlackWhite.ViewModels; public partial class MainViewModel: ObservableObject { [ObservableProperty] private BindingList<GameModel> _chesses; [ObservableProperty] private int _blockSize; [ObservableProperty] private int _countBlack = 2; [ObservableProperty] private int _countWhite = 2; public enum Who { User, System } public MainViewModel() { BlockSize = 40; Chesses = new BindingList<GameModel>(); for (int x = 0; x < 10; x++) { for (int y = 0; y < 10; y++) { var chess = new GameModel("N", x, y, BlockSize); if (x == 4 && y == 4 || x == 5 && y == 5) chess = new GameModel("B", x, y, BlockSize); else if (x == 4 && y == 5 || x == 5 && y == 4) chess = new GameModel("W", x, y, BlockSize); Chesses.Add(chess); } } //发送消息 //WeakReferenceMessenger.Default.Send(new CustomizedMessage(new MessageModel())); } public (int, int) CountChesses(BindingList<GameModel> Chesses) { var tCountBlack = 0; var tCountWhite = 0; foreach (var chess in Chesses) { if (chess.ChessType == GameModel.ChessTypes.BlackChess) tCountBlack += 1; if (chess.ChessType == GameModel.ChessTypes.WhiteChess) tCountWhite += 1; } return (tCountBlack, tCountWhite); } public void FlipChess(BindingList<GameModel> Chesses, int x, int y) { /*foreach (var chess in Chesses) { if (chess.XPos == x && chess.YPos == y) { switch (chess.ChessType) { case GameModel.ChessTypes.BlackChess: chess.ChangeToWhite(); break; default: chess.ChangeToBlack(); break; } break; } }*/ foreach (var chess in Chesses) { if (chess.XPos == x && chess.YPos == y) { chess.ChessType = chess.ChessType == GameModel.ChessTypes.NullChess ? GameModel.ChessTypes.BlackChess : (chess.ChessType == GameModel.ChessTypes.BlackChess ? GameModel.ChessTypes.WhiteChess : GameModel.ChessTypes.BlackChess); # region <- x // <- x var a = x; var b = y; bool flag = false; while (a > 1) { a--; var t = Chesses.ToList().First(c => c.XPos == a && c.YPos == y); if (t.ChessType == chess.ChessType) { flag = true; } if (t.ChessType == GameModel.ChessTypes.NullChess) { break; } if (flag) { DoFlipXY(a+1, x-1, y, y); break; } } #endregion #region x -> // x-> a = x; b = y; flag = false; while (a < 9) { a++; var t = Chesses.ToList().First(c => c.XPos == a && c.YPos == y); if (t.ChessType == chess.ChessType) { flag = true; } if (t.ChessType == GameModel.ChessTypes.NullChess) { break; } if (flag) { DoFlipXY(x+1, a-1, y, y); break; } } #endregion #region <- y // <-y a = x; b = y; flag = false; while (b > 1) { b--; var t = Chesses.ToList().First(c => c.YPos == b && c.XPos == x); if (t.ChessType == chess.ChessType) { flag = true; } if (t.ChessType == GameModel.ChessTypes.NullChess) { break; } if (flag) { DoFlipXY(x, x, b+1, y-1); break; } } #endregion #region y -> // y-> a = x; b = y; flag = false; while (b < 9) { b++; var t = Chesses.ToList().First(c => c.YPos == b && c.XPos == x); if (t.ChessType == chess.ChessType) { flag = true; } if (t.ChessType == GameModel.ChessTypes.NullChess) { break; } if (flag) { DoFlipXY(x,x, y + 1, b - 1); break; } } #endregion #region <- x <- y // <- x <- y a = x; b = y; flag = false; while (a > 1 && b > 1) { a--; b--; var t = Chesses.ToList().First(c => c.XPos == a && c.YPos == b); if (t.ChessType == chess.ChessType) { flag = true; } if (t.ChessType == GameModel.ChessTypes.NullChess) { break; } if (flag) { DoFlipXY(a+1, x-1, b+1, y-1); break; } } #endregion #region x -> y -> // x -> y -> a = x; b = y; flag = false; while (a <9 && b < 9) { a++; b++; var t = Chesses.ToList().First(c => c.XPos == a && c.YPos == b); if (t.ChessType == chess.ChessType) { flag = true; } if (t.ChessType == GameModel.ChessTypes.NullChess) { break; } if (flag) { DoFlipXY(x+1, a-1, y+1, b-1); break; } } #endregion #region <- x y -> // <- x y -> a = x; b = y; flag = false; while (a > 1 && b < 9) { a--; b++; var t = Chesses.ToList().First(c => c.XPos == a && c.YPos == b); if (t.ChessType == chess.ChessType) { flag = true; } if (t.ChessType == GameModel.ChessTypes.NullChess) { break; } if (flag) { DoFlipXY(a+1, x-1, y+1, b-1); break; } } #endregion #region x -> y <- // x -> y <- a = x; b = y; flag = false; while (a < 9 && b > 1) { a++; b--; var t = Chesses.ToList().First(c => c.XPos == a && c.YPos == b); if (t.ChessType == chess.ChessType) { flag = true; } if (t.ChessType == GameModel.ChessTypes.NullChess) { break; } if (flag) { DoFlipXY(x+1, a-1, b+1, y-1); break; } } #endregion } } } public void DoFlipXY(int xStart, int xEnd, int yStart, int yEnd) { for (int i = xStart; i <= xEnd; i++) { for (int j = yStart; j <= yEnd; j++) { foreach (var a in Chesses.ToList().Where(c=> c.XPos == i && c.YPos == j)) { if(a.ChessType==GameModel.ChessTypes.NullChess) continue; a.ChessType = a.ChessType == GameModel.ChessTypes.BlackChess ? GameModel.ChessTypes.WhiteChess : GameModel.ChessTypes.BlackChess; } } } } public bool IsGameOver { get { foreach (var chess in Chesses) { if (chess.ChessType == GameModel.ChessTypes.NullChess) return false; } return true; } } } public class BorderValueConverter : IValueConverter { public int Offset { get; set; } = 2; public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return int.Parse(value.ToString()) - Offset; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } //定义消息 // public class CustomizedMessage : ValueChangedMessage<MessageModel> // PropertyChangedMessage // RequestMessage ( Async + ..) // { // public CustomizedMessage(MessageModel value) : base(value) // { // } // }
登录后方可回帖
这个可以作为棋类游戏的通用底层模型,只要改一下行数、列数、调整一下颜色或者棋子的种类,就可以适合其他棋类游戏了,拓展性不错。
其实应该说不光是棋类游戏,2D的平面走格子的游戏应该都可以兼容,比如贪吃蛇、吃豆人等等,哪怕超级玛丽这种改造一下也可以实现![](/static/editor/dialogs/emotion/images/pp/i_f25.png)