C# WPF 实现五子棋&黑白棋小游戏
By
jerryxjr1220
at 2023-09-27 • 0人收藏 • 733人看过
用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的平面走格子的游戏应该都可以兼容,比如贪吃蛇、吃豆人等等,哪怕超级玛丽这种改造一下也可以实现