目录
- 布局
- 生成雷区
- 左键扫雷和右键标记
- 翻面功能
布局
布局效果如下,下面每个“网格”都是一个按钮,点击按钮,就会有相应的事件发生。
由于UniformGrid
中每个Grid的尺寸相等,所以作为雷区的容器。
<DockPanel> | |
<DockPanel.Resources> | |
<Style TargetType="TextBlock"> | |
<Setter Property="Margin" Value="5"/> | |
</Style> | |
<Style TargetType="TextBox"> | |
<Setter Property="Margin" Value="5"/> | |
<Setter Property="InputMethod.IsInputMethodEnabled" Value="False"/> | |
</Style> | |
</DockPanel.Resources> | |
<ToolBar DockPanel.Dock="Top"> | |
<TextBlock Text="雷区尺寸"/> | |
<TextBox Width="40" Text="20" x:Name="txtNumX"/> | |
<TextBlock Text="×"/> | |
<TextBox Width="40" Text="20" x:Name="txtNumY"/> | |
<TextBlock Text="雷数"/> | |
<TextBox Width="40" Text="20" x:Name="txtNumMine"/> | |
<Button Content="🔄" Click="btnNewGame_Click"/> | |
</ToolBar> | |
<UniformGrid Name="ugMine"> | |
</UniformGrid> |
生成雷区
值得一提的是,由于随机数可能在生成过程中产生重复的值,所以这里通过概率的方式来生成雷。
假设按钮数为N,雷数为n,那么在][0,N]之间随机生成一个数x,如果x<n,则判定当前按钮为雷。按钮是否为雷的标志作为布尔型存放在btn.tag中。
由于通过遍历的方法生成雷,所以一旦剩余雷的个数和剩余按钮的个数相等,就说明剩余的按钮全都是雷。这种情况发生,则不必进行随机数的判定。
private void newGame() | |
{ | |
x = int.Parse(txtNumX.Text); | |
y = int.Parse(txtNumY.Text); | |
var nBtns = x * y; | |
nMark = 0; | |
nRes = int.Parse(txtNumMine.Text); | |
if (nRes > nBtns) | |
nRes = nBtns; | |
pMine = new List<int>(); | |
ugMine.Rows = y; | |
ugMine.Columns = x; | |
ugMine.Children.Clear(); | |
Random rd = new Random(); | |
int numSetMine = 0; //已经布置的雷的个数 | |
for (int i = 0; i < nBtns; i++) | |
{ | |
var btn = new Button(); | |
ugMine.Children.Add(btn); | |
btn.Click += Btn_Click; | |
btn.MouseRightButtonDown += Btn_MouseRightButtonDown; | |
btn.Content = ""; | |
btn.Tag = false; | |
if ((nRes - numSetMine) == (nBtns - i) || //如果剩余的雷数刚好等于剩余的按钮数,则剩下的按钮都是雷 | |
(numSetMine < nRes && rd.Next(0, nBtns) < nRes)) | |
{ | |
btn.Tag = true; | |
numSetMine += 1; | |
pMine.Add(i); | |
} | |
} | |
} |
左键扫雷和右键标记
左键点击,则类似于一个翻面的动作;右键点击,则相当于是标记,而且在标记之后,不能再通过左键进行翻面。
//左键单击 | |
private void Btn_Click(object sender, RoutedEventArgs e) | |
{ | |
var btn = sender as Button; | |
int index = ugMine.Children.IndexOf(btn); | |
flipButton(index); | |
if(nMark == pMine.Count || nRes == pMine.Count) | |
MessageBox.Show("您赢了!"); | |
} | |
//右键单击 | |
private void Btn_MouseRightButtonDown(object sender, MouseButtonEventArgs e) | |
{ | |
var btn = sender as Button; | |
var flag = btn.Content.ToString() != "🚩"; | |
if (flag) | |
btn.Click -= Btn_Click; //如果已经标记,则卸载左键的功能 | |
else | |
btn.Click += Btn_Click; //如果取消标记,则重新挂载左键的功能 | |
btn.Content = flag ? "🚩" : ""; | |
btn.Foreground = flag ? Brushes.Red : Brushes.Gray; | |
nMark += flag ? 1 : -1; | |
} |
右键单击效果如下
翻面功能
在翻面的时候,如果当前按钮为雷,则雷炸了,游戏结束。
如果当前按钮不是雷,那么判断该按钮周围是否有雷。如果有雷,则当前按钮显示周围雷的个数;如果没雷,则将周围的雷全部翻面——需要调用自身。
private void flipButton(int index) | |
{ | |
var btn = ugMine.Children[index] as Button; | |
if (!btn.IsEnabled) | |
return; | |
if ((bool)btn.Tag) | |
{ | |
foreach (var i in pMine) | |
{ | |
var mine = ugMine.Children[i] as Button; | |
mine.Content = "💥"; | |
mine.Foreground = Brushes.Red; | |
} | |
MessageBox.Show("您输了"); | |
return; | |
} | |
nRes -= 1; | |
btn.IsEnabled = false; | |
int numMines = 0; | |
var nears = setNear(index); | |
foreach (var i in nears) | |
{ | |
var near = ugMine.Children[i] as Button; | |
if ((bool)near.Tag) | |
numMines += 1; | |
} | |
if (numMines != 0) | |
btn.Content = numMines; | |
else | |
foreach (var i in nears) | |
flipButton(i); | |
} |
其中setNear
是用于获取当前按周围按钮的序号,这里分别需要考虑四个角、四个边以及中间区域。
private int[] setNear(int index) | |
{ | |
if (index == 0) | |
return new int[3] { 1, x, x + 1 }; | |
if (index == x * y - 1) | |
return new int[3] { index - 1, index - x, index - x - 1 }; | |
if (index == x - 1) | |
return new int[3] { x - 2, 2 * x - 1, 2 * x - 2 }; | |
if (index == x * y - x) | |
return new int[3] { index + 1, index - x, index - x + 1 }; | |
if (index % x == 0) | |
return new int[5] { index - x, index - x + 1, index + 1, index + x, index + x + 1 }; | |
if (index % x == (x - 1)) | |
return new int[5] { index - x - 1, index - x, index - 1, index + x - 1, index + x }; | |
if (index < x) | |
return new int[5] { index - 1, index + 1, index + x - 1, index + x, index + x + 1 }; | |
if (index > x * (y - 1)) | |
return new int[5] { index - x - 1, index - x, index - x + 1, index - 1, index + 1 }; | |
return new int[8] { index - 1, index + 1, index - x, index-x-1, | |
index-x+1,index + x,index+x-1,index+x+1 }; | |
} |
效果如下