十4、響應鼠標點擊事件
(1)設置對應座標位置爲相應的前景狀態canvas
/// <summary> /// 設置單元格圖樣 /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="state"></param> private void SetCellFore(int x, int y, ForeState state) { if (state > ForeState.QUESTION || state < ForeState.NONE) return; _foreData[y, x] = (int)state; if (ForeCanvas.Children.Contains(_foreImage[y, x])) ForeCanvas.Children.Remove(_foreImage[y, x]); if (state == ForeState.NONE) return; _foreImage[y, x].Source = ImageHelper.CutImage(_bmpForeground, new Int32Rect((_foreData[y, x] - 1) * _cellSize.Width, 0, _cellSize.Width, _cellSize.Height)); ForeCanvas.Children.Add(_foreImage[y, x]); }
若是當前座標位置設置的前景狀態爲容許值範圍,則將其賦給相應的_foreData元素,並刪除原來的圖形。若是設置狀態爲問號或小紅旗,則從新設置該圖形。
動畫
(2)鼠標點擊空白區域時,自動打開附近連片的空白區域。使用瞭如下遞歸方法。this
/// <summary> /// 自動打開附近空白區域 /// </summary> /// <param name="y"></param> /// <param name="x"></param> private void OpenNearToSpace(int y, int x) { if (y < 0 || y >= _gameLevel._colGrid || x < 0 || x >= _gameLevel._rowGrid)//越界 return; if (_backData[y, x] == (int)BackState.BLANK && _foreData[y, x] == (int)ForeState.NORMAL) { _foreData[y, x] = (int)ForeState.NONE; //已打開 if (ForeCanvas.Children.Contains(_foreImage[y, x])) ForeCanvas.Children.Remove(_foreImage[y, x]); if (y - 1 >= 0 && x - 1 >= 0) { OpenNearToSpace(y - 1, x - 1); } if (y - 1 >= 0) { OpenNearToSpace(y - 1, x); } if (y - 1 >= 0 && x + 1 <= _gameLevel._rowGrid - 1) { OpenNearToSpace(y - 1, x + 1); } if (y + 1 <= _gameLevel._colGrid - 1 && x - 1 >= 0) { OpenNearToSpace(y + 1, x - 1); } if (y + 1 <= _gameLevel._colGrid - 1) { OpenNearToSpace(y + 1, x); } if (y + 1 <= _gameLevel._colGrid - 1 && x + 1 <= _gameLevel._rowGrid - 1) { OpenNearToSpace(y + 1, x + 1); } if (x + 1 <= _gameLevel._rowGrid - 1) { OpenNearToSpace(y, x + 1); } if (x - 1 >= 0) { OpenNearToSpace(y, x - 1); } Open8Box(y, x); // 打開周圍8個方格 } return; } /// <summary> /// 打開周圍8個方格 /// </summary> /// <param name="y"></param> /// <param name="x"></param> private void Open8Box(int y, int x) { if (y - 1 >= 0 && x - 1 >= 0) { _foreData[y - 1, x - 1] = (int)ForeState.NONE; if (ForeCanvas.Children.Contains(_foreImage[y - 1, x - 1])) ForeCanvas.Children.Remove(_foreImage[y - 1, x - 1]); } if (y - 1 >= 0) { _foreData[y - 1, x] = (int)ForeState.NONE; if (ForeCanvas.Children.Contains(_foreImage[y - 1, x])) ForeCanvas.Children.Remove(_foreImage[y - 1, x]); } if (y - 1 >= 0 && x + 1 <= _gameLevel._rowGrid - 1) { _foreData[y - 1, x + 1] = (int)ForeState.NONE; if (ForeCanvas.Children.Contains(_foreImage[y - 1, x + 1])) ForeCanvas.Children.Remove(_foreImage[y - 1, x + 1]); } if (x - 1 >= 0) { _foreData[y, x - 1] = (int)ForeState.NONE; if (ForeCanvas.Children.Contains(_foreImage[y, x - 1])) ForeCanvas.Children.Remove(_foreImage[y, x - 1]); } if (x + 1 <= _gameLevel._rowGrid - 1) { _foreData[y, x + 1] = (int)ForeState.NONE; if (ForeCanvas.Children.Contains(_foreImage[y, x + 1])) ForeCanvas.Children.Remove(_foreImage[y, x + 1]); } if (y + 1 <= _gameLevel._colGrid - 1 && x - 1 >= 0) { _foreData[y + 1, x - 1] = (int)ForeState.NONE; if (ForeCanvas.Children.Contains(_foreImage[y + 1, x - 1])) ForeCanvas.Children.Remove(_foreImage[y + 1, x - 1]); } if (y + 1 <= _gameLevel._colGrid - 1) { _foreData[y + 1, x] = (int)ForeState.NONE; if (ForeCanvas.Children.Contains(_foreImage[y + 1, x])) ForeCanvas.Children.Remove(_foreImage[y + 1, x]); } if (y + 1 <= _gameLevel._colGrid - 1 && x + 1 <= _gameLevel._rowGrid - 1) { _foreData[y + 1, x + 1] = (int)ForeState.NONE; if (ForeCanvas.Children.Contains(_foreImage[y + 1, x + 1])) ForeCanvas.Children.Remove(_foreImage[y + 1, x + 1]); } }
(3)添加鼠標左鍵事件
編輯xaml文件,在ForeCanvas元素內添加MouseLeftButtonDown="ForeCanvas_MouseLeftButtonDown"。後臺代碼以下:spa
private void ForeCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (_gameState == GameState.NONE) return; // 獲取鼠標點擊的格子位置 Point mousePoint = e.MouseDevice.GetPosition(ForeCanvas); int dx = (int)(mousePoint.X / _cellSize.Width); int dy = (int)(mousePoint.Y / _cellSize.Height); // 已打開區域 if (_foreData[dy, dx] == (int)ForeState.NONE) return; if (_backData[dy, dx] > (int)BackState.BLANK) { if (ForeCanvas.Children.Contains(_foreImage[dy, dx])) { ForeCanvas.Children.Remove(_foreImage[dy, dx]); _foreData[dy, dx] = (int)ForeState.NONE; } } if (_backData[dy, dx] == (int)BackState.BLANK) { OpenNearToSpace(dy, dx); } if (_backData[dy, dx] == (int)BackState.MINE) { if (ForeCanvas.Children.Contains(_foreImage[dy, dx])) { ForeCanvas.Children.Remove(_foreImage[dy, dx]); _foreData[dy, dx] = (int)ForeState.NONE; } // FriedMine(dy, dx);等待後補 } // 是否勝利判斷 }
實現了主要狀態判斷和動做,踩雷和勝利判斷的狀況由於要使用動畫效果,因此這裏先留空,待後再作。
code
(4)添加鼠標右鍵功能
編輯xaml文件,在ForeCanvas元素內添加MouseRightButtonDown="ForeCanvas_MouseRightButtonDown",後臺代碼:orm
private void ForeCanvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e) { if (_gameState == GameState.NONE) return; // 獲取鼠標點擊的格子位置 Point mousePoint = e.MouseDevice.GetPosition(ForeCanvas); int dx = (int)(mousePoint.X / _cellSize.Width); int dy = (int)(mousePoint.Y / _cellSize.Height); // 已打開區域 if (_foreData[dy, dx] == (int)ForeState.NONE) return; if (ForeCanvas.Children.Contains(_foreImage[dy, dx])) { // 循環前景數據 _foreData[dy, dx]++; if (_foreData[dy, dx] > 3) { _foreData[dy, dx] = 1; } // 設置相應的圖片(原始、紅旗、問號) _foreImage[dy, dx].Source = ImageHelper.CutImage(_bmpForeground, new Int32Rect((_foreData[dy, dx]- 1) * _cellSize.Width, 0, _cellSize.Width, _cellSize.Height)); // 計算地雷數 int num = 0; for (int y=0; y<_gameLevel._colGrid; y++) { for (int x=0; x<_gameLevel._rowGrid; x++) { if (_foreData[y, x] == (int)ForeState.FLAG) num++; } } textBlockMineNum.Text = (_gameLevel._mineNum - num).ToString(); // 待加勝利檢測 } }
每單擊一次右鍵,將相應的單元格的圖片從原始-紅旗-問號-原始,循環遞增,並從新計算顯示的地雷數。
blog
十5、添加判斷是否勝利
看註釋的判斷。遞歸
private bool IsWin() { bool flag = true; for (int y = 0; y < _gameLevel._colGrid; y++) { for (int x = 0; x < _gameLevel._rowGrid; x++) { // 地雷未被紅旗標記 if (_backData[y, x] == (int)BackState.MINE && _foreData[y, x] != (int)ForeState.FLAG) { flag = false; break; } // 存在未打開格子或標記爲問號的格子 if (_foreData[y, x] == (int)ForeState.NORMAL || _foreData[y, x] == (int)ForeState.QUESTION) { flag = false; break; } } if (!flag) break; } return flag; }
將該方法添加到前景ForeCanvas控件的左、右鍵事件中進行調用。遊戲
if (IsWin()) { WinProcess(); }
這是勝利後的處理方法:先中止計時,而後從新覆蓋前景圖片,啓用計時動畫事件,從下往上逐消去前景圖片:
事件
private void WinProcess() { // 中止計時 _stopWatchGame.Stop(); _timerSetTimeText.Stop(); _gameState = GameState.NONE; // 從新覆蓋前景圖片 ForeCanvas.Children.Clear(); for (int y=0; y<_gameLevel._colGrid; y++) { for (int x=0; x<_gameLevel._rowGrid; x++) { _foreImage[y, x] = new Image(); _foreImage[y, x].Source = ImageHelper.CutImage(_bmpForeground, new Int32Rect(0, 0, _cellSize.Width, _cellSize.Height)); _foreImage[y, x].SetValue(Canvas.LeftProperty, x * (double)_cellSize.Width); _foreImage[y, x].SetValue(Canvas.TopProperty, y * (double)_cellSize.Height); ForeCanvas.Children.Add(_foreImage[y, x]); } } // 動畫行數 iCount = _gameLevel._colGrid - 1; _timerWinAnim.Start(); }
這是計時動畫事件方法:
private void _timerWinAnim_Tick(object sender, EventArgs e) { if (iCount >= 0) { Storyboard sb1 = new Storyboard(); DoubleAnimation daOpacity = null; for (int x = 0; x < _gameLevel._rowGrid; x++) { if (_backData[iCount, x] == (int)BackState.MINE) { SetCellFore(x, iCount, ForeState.FLAG); continue; } daOpacity = new DoubleAnimation(); daOpacity.From = 1d; daOpacity.To = 0d; daOpacity.Duration = new Duration(TimeSpan.FromMilliseconds(600)); Storyboard.SetTarget(daOpacity, _foreImage[iCount, x]); Storyboard.SetTargetProperty(daOpacity, new PropertyPath("(Image.Opacity)")); sb1.Children.Add(daOpacity); } sb1.Begin(); iCount--; } else { _timerWinAnim.Stop(); if (MessageBox.Show("你勝利了。要從新開始嗎?", "恭喜", MessageBoxButton.OKCancel, MessageBoxImage.Information) == MessageBoxResult.OK) { MenuGameStart_Click(null, null); } } }
十6、踩雷後的處理
爲了避免讓主程序複雜化,咱們另外建立一個Bomb的新類
public class Bomb { Image bombImg = new Image(); BitmapSource bmpBomb = null; const int FRAME_COUNT = 6; BitmapSource[] bmpSourceBomb = new BitmapSource[FRAME_COUNT]; int currFrame = 0; DispatcherTimer timerBomb; Canvas _canvas = new Canvas(); int dx, dy; public Bomb(Canvas canvas, int dx, int dy, BitmapSource bmpImgBomb) { _canvas = canvas; this.dx = dx; this.dy = dy; bmpBomb = bmpImgBomb; for (int i=0; i<FRAME_COUNT; i++) { bmpSourceBomb[i] = ImageHelper.CutImage(bmpBomb, new System.Windows.Int32Rect(i * 35, 0, 35, 35)); } timerBomb = new DispatcherTimer(); timerBomb.Interval = TimeSpan.FromMilliseconds(200); timerBomb.Tick += TimerBomb_Tick; } private void TimerBomb_Tick(object sender, EventArgs e) { bombImg.Source = bmpSourceBomb[currFrame]; currFrame++; if (currFrame == 5) { currFrame = 0; if (_canvas.Children.Contains(bombImg)) { _canvas.Children.Remove(bombImg); } timerBomb.Stop(); } } public void DrawBomb() { if (!_canvas.Children.Contains(bombImg)) _canvas.Children.Add(bombImg); Canvas.SetLeft(bombImg, dy * 35); Canvas.SetTop(bombImg, dx * 35); timerBomb.Start(); } public System.Drawing.Point GetPosition() { return new System.Drawing.Point(dx, dy); } }
建立實例時,一併將相關參數傳遞過去。而後調用DrawBomb在啓動內部計時器,該計時器順序更新前景圖片源,從而實現爆炸效果。
主程序添加兩個字段:
private Bomb[] bombs; private int bombCount = 0;
在InitGameData方法中進行初始化:
bombs = new Bomb[_gameLevel._mineNum];
踩雷動做方法:
private void FriedMines(int y, int x) { if (_backData[y, x] == (int)BackState.MINE) { _backImage[y, x].Source = ImageHelper.CutImage(_bmpBomb, new Int32Rect(1 * _cellSize.Width, 0, _cellSize.Width, _cellSize.Height)); } int bombCount = 0; for (int j=0; j<_gameLevel._colGrid; j++) { for (int i=0; i<_gameLevel._rowGrid; i++) { if (_backData[j,i] == (int)BackState.MINE && _foreData[j,i] != (int)ForeState.NONE && ForeCanvas.Children.Contains(_foreImage[j,i])) { bombs[bombCount++] = new Bomb(ForeCanvas, j, i, _bmpBomb); } } } _timerBomb.Start(); }
如今遍歷遊戲區,在有地雷的位置建立Bomb實例,而後啓動_timerBomb計時事件。
private void _timerBomb_Tick(object sender, EventArgs e) { bombs[bombCount].DrawBomb(); ForeCanvas.Children.Remove(_foreImage[bombs[bombCount].GetPosition().X, bombs[bombCount].GetPosition().Y]); bombCount++; if (bombCount == (bombs.Length - 1)) { bombCount = 0; _timerBomb.Stop(); } }
該計時器按順序調用Bomb類的DrawBomb方法,實現爆炸效果。
在ForeCanavas的左鍵事件中調用該方法,處理踩雷事件。
if (_backData[dy, dx] == (int)BackState.MINE) { if (ForeCanvas.Children.Contains(_foreImage[dy, dx])) { ForeCanvas.Children.Remove(_foreImage[dy, dx]); } FriedMines(dy, dx); _gameState = GameState.NONE; _stopWatchGame.Stop(); }
先到這裏了,其餘次要功能就再也不這裏囉嗦了。
另外:發現個小問題,若是困難難度踩着地雷的話,按原來設定的時間間隙,99個地雷爆炸須要比較長時間。這樣調整下:
在InitGameData方法中的switch裏,分別添加一行:
case Level.SIMPLE: ... _timerBomb.Interval = new TimeSpan(0, 0, 0, 0, 220); ... case Level.NORMAL: ... _timerBomb.Interval = new TimeSpan(0, 0, 0, 0, 120); ... case Level.HARD: ... _timerBomb.Interval = new TimeSpan(0, 0, 0, 0, 50); ...