本人是大一学生,上半学期刚学完C语言。出于自己的兴趣同时也是哥哥的任务,在寒假这几天自学C#,利用WPF写了一个扫雷的小游戏。
自学教材:《C#入门经典(第六版)》,1月28日购入,1月29日到2月9日学习了前十六章,由于有C语言基础,在语法阶段学习起来比较轻松,不过在接触到面向对象的时候遇到了一些困难,对于一些概念的理解着实费了一些功夫,不过最后还是成功的理解了。整个程序的设计从2月10日开始,在2月12日程序基本完成。2月13日将已知的BUG都清除完毕。
扫雷很简单。一个程序的核心就是数据结构与算法,我选择的数据结构是二维数组,算法也很简单,就是很简单的利用bool属性做标记以及翻牌的递归。
首先是雷块类的实现,因为翻开雷块是利用单击,所以我在雷块类中继承了Button基类,使它具有Button类的所有特性。同时定义了几个bool属性,分别表示是不是雷(IsMine),有没有被翻开(IsOpened),有没有被标记(IsFlagged)。还有int属性代表周围雷数(MineAround),最大的行列数(MaxRow、MaxColumn),不是雷的个数(MaxNoMineNum),以及翻开之后显示的图片背景back,代码如下:
class Pane :Button { public Image back = new Image(); public int MaxNoMineNum { get; set; } public int MaxRow { get; set; } public int MaxColumn { get; set; } public bool IsMine { get; set; } public int MineAround { get; set; } public bool IsFlagged { get; set; } public bool IsOpened { get; set; } public Pane(bool isMine) { IsMine = isMine; } }
另外一个非常重要的类就是雷区类,为了方便,我将雷区做成了一个窗口,并根据用户选择难度的不同,向里面的Canvas控件动态加载我的雷块“Button”,同时在上面实现了计时器和重新开始按钮。
根据难度不同,在初始化函数里定义了int形参Level,传递参数0为初级,1为中级,2为高级,分别按不同的雷数以及雷区大小初始化。初始化函数比较简单故不将代码列出。
最重要的是单击事件,每次单击至少翻开一个雷块,如果该雷块周围没有雷(MineAround==0),则翻开他周围的雷块。这就要用到递归。同时应注意,一旦该雷块被翻开之后就不应该再次翻开他。否则就会无限递归下去直到栈溢出抛出*异常。所以应该在单击的事件处理程序中加入一个条件判断,在没有翻开(IsFlagged==false)的时候才执行。同时应注意边界上的雷块递归的处理,不能让数组越界。具体代码见下:
1 private void PaneField_Click(object sender, RoutedEventArgs e) 2 { 3 if(IsTimerStart==false) 4 { 5 IsTimerStart = true; 6 myTimer.Start(); 7 } 8 if(IsWining()&&WiningJudge==true&&IsGameOver==false) 9 { 10 IsGameOver = true; 11 WiningJudge = false; 12 for (int i = 0; i < paneField[0,0].MaxRow; i++) 13 for (int j = 0; j < paneField[0,0].MaxColumn; j++) 14 { 15 PaneField_Click(paneField[i, j], e); 16 } 17 System.Threading.Thread.Sleep(2000); 18 Wining w = new Wining(); 19 w.Show(); 20 System.Threading.Thread.Sleep(3000); 21 this.Close(); 22 } 23 if (!Field.Children.Contains((sender as Pane).back)) 24 { 25 int Row, Column; 26 Pane t = (sender as Pane); 27 (sender as Pane).IsOpened = true; 28 Field.Children.Remove((UIElement)sender); 29 Field.Children.Add((sender as Pane).back); 30 if(t.IsMine==true && LosingJudge==true&&IsGameOver==false) 31 { 32 IsGameOver = true; 33 LosingJudge = false; 34 for(int i=0;i<t.MaxRow;i++) 35 for(int j=0;j<t.MaxColumn;j++) 36 { 37 38 PaneField_Click(paneField[i, j], e); 39 } 40 Losing l = new Losing(); 41 l.Show(); 42 Thread.Sleep(3000); 43 this.Close(); 44 } 45 //递归 46 if (t.MineAround == 0) 47 { 48 GetRowAndColumn(t, out Row, out Column, t.MaxRow, t.MaxColumn); 49 #region 四个角 50 if (Row == 0 && Column == 0 && t.IsMine == false) //左上角 51 { 52 PaneField_Click(paneField[0, 1], e); 53 PaneField_Click(paneField[1, 0], e); 54 PaneField_Click(paneField[1, 1], e); 55 } 56 else if (Row == 0 && Column == t.MaxColumn - 1 && t.IsMine == false) //右上角 57 { 58 PaneField_Click(paneField[0, t.MaxColumn - 2], e); 59 PaneField_Click(paneField[1, t.MaxColumn - 2], e); 60 PaneField_Click(paneField[1, t.MaxColumn - 1], e); 61 } 62 else if (Row == t.MaxRow - 1 && Column == 0 && t.IsMine == false) //左下角 63 { 64 PaneField_Click(paneField[t.MaxRow - 2, 0], e); 65 PaneField_Click(paneField[t.MaxRow - 2, 1], e); 66 PaneField_Click(paneField[t.MaxRow - 1, 1], e); 67 } 68 else if (Row == t.MaxRow - 1 && Column == t.MaxColumn - 1 && t.IsMine == false) //右下角 69 { 70 PaneField_Click(paneField[t.MaxRow - 2, t.MaxColumn - 2], e); 71 PaneField_Click(paneField[t.MaxRow - 2, t.MaxColumn - 1], e); 72 PaneField_Click(paneField[t.MaxRow - 1, t.MaxColumn - 2], e); 73 } 74 #endregion 75 #region 四条边 76 else if (Row == 0 && Column != 0 && Column != t.MaxColumn - 1 && t.IsMine == false) //上边 77 { 78 for (int p = Row; p <= Row + 1; p++) 79 for (int q = Column - 1; q <= Column + 1; q++) 80 { 81 bool b = Field.Children.Contains(paneField[p, q].back); 82 if (!(p == Row && q == Column) && b == false) 83 PaneField_Click(paneField[p, q], e); 84 } 85 } 86 else if (Row == t.MaxRow - 1 && Column != 0 && Column != t.MaxColumn - 1 && t.IsMine == false) //下边 87 { 88 for (int p = Row - 1; p <= Row; p++) 89 for (int q = Column - 1; q <= Column + 1; q++) 90 { 91 bool b = Field.Children.Contains(paneField[p, q].back); 92 if (!(p == Row && q == Column) && b == false) 93 PaneField_Click(paneField[p, q], e); 94 } 95 } 96 else if (Column == 0 && Row != t.MaxRow - 1 && Row != 0 && t.IsMine == false) //左边 97 { 98 for (int p = Row - 1; p <= Row + 1; p++) 99 for (int q = Column; q <= Column + 1; q++) 100 { 101 bool b = Field.Children.Contains(paneField[p, q].back); 102 if (!(p == Row && q == Column) && b == false) 103 PaneField_Click(paneField[p, q], e); 104 } 105 } 106 else if (Column == t.MaxColumn - 1 && Row != t.MaxRow - 1 && Row != 0 && t.IsMine == false) //右边 107 { 108 for (int p = Row - 1; p <= Row + 1; p++) 109 for (int q = Column - 1; q <= Column; q++) 110 { 111 bool b = Field.Children.Contains(paneField[p, q].back); 112 if (!(p == Row && q == Column) && b == false) 113 PaneField_Click(paneField[p, q], e); 114 } 115 } 116 #endregion 117 #region 其他位置 118 else if (t.IsMine == false) 119 for (int p = Row - 1; p <= Row + 1; p++) 120 for (int q = Column - 1; q <= Column + 1; q++) 121 { 122 bool b = Field.Children.Contains(paneField[p, q].back); 123 if (!(p == Row && q == Column) && b == false) 124 PaneField_Click(paneField[p, q], e); 125 } 126 #endregion 127 } 128 } 129 }
其中的GetRowAndColumn方法在PaneField中定义,通过Pane对象的Equals方法来得到该元素在二维数组中的下标并利用out形参返回。IsWining方法也在类中定义为私有方法,通过统计翻开数与不是雷的个数是否相等来判断是否胜利。
至此,扫雷中比较复杂和重要的两个类就介绍完毕了。其他的类or窗口(难度选择,输赢提示)都非常简单,故不再赘述。通过编写这个程序,让我很好的应用了这十几天学习的.Net知识,自己的编程功夫也有了一些长进。希望开学之后能够继续进步,取得更大的成就!