自學教材:《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),則翻開他周圍的雷塊。這就要用到遞歸。同時應注意,一旦該雷塊被翻開之后就不應該再次翻開他。否則就會無限遞歸下去直到棧溢出拋出StackOverFlow異常。所以應該在單擊的事件處理程序中加入一個條件判斷,在沒有翻開(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知識,自己的編程功夫也有了一些長進。希望開學之后能夠繼續進步,取得更大的成就!