练手WPF(三)——扫雷小游戏的简易实现(下)
十四、响应鼠标点击事件
(1)设置对应坐标位置为相应的前景状态
/// <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)鼠标点击空白区域时,自动打开附近连片的空白区域。使用了以下递归方法。
/// <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"。后台代码如下:
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);等待后补 } // 是否胜利判断 }
实现了主要状态判断和动作,踩雷和胜利判断的情况因为要使用动画效果,所以这里先留空,待后再做。
(4)添加鼠标右键功能
编辑xaml文件,在forecanvas元素内添加mouserightbuttondown="forecanvas_mouserightbuttondown",后台代码:
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(); // 待加胜利检测 } }
每单击一次右键,将相应的单元格的图片从原始-红旗-问号-原始,循环递增,并重新计算显示的地雷数。
十五、添加判断是否胜利
看注释的判断。
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); } } }
十六、踩雷后的处理
为了不让主程序复杂化,我们另外创建一个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 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(); }
我们一开始在初始化阶段根据地雷数创建了bombs数组,现在遍历游戏区,在有地雷的位置创建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(); }
先到这里了,其他次要功能就不再这里啰嗦了。
上一篇: 媒体类型和响应式设计