练手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] - ) * _cellSize.Width, , _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 < || y >= _gameLevel._colGrid || x < || 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 - >= && x - >= )
{
OpenNearToSpace(y - , x - );
} if (y - >= )
{
OpenNearToSpace(y - , x);
} if (y - >= && x + <= _gameLevel._rowGrid - )
{
OpenNearToSpace(y - , x + );
} if (y + <= _gameLevel._colGrid - && x - >= )
{
OpenNearToSpace(y + , x - );
} if (y + <= _gameLevel._colGrid - )
{
OpenNearToSpace(y + , x);
} if (y + <= _gameLevel._colGrid - && x + <= _gameLevel._rowGrid - )
{
OpenNearToSpace(y + , x + );
}
if (x + <= _gameLevel._rowGrid - )
{
OpenNearToSpace(y, x + );
} if (x - >= )
{
OpenNearToSpace(y, x - );
} 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 - >= && x - >= )
{
_foreData[y - , x - ] = (int)ForeState.NONE;
if (ForeCanvas.Children.Contains(_foreImage[y - , x - ]))
ForeCanvas.Children.Remove(_foreImage[y - , x - ]);
} if (y - >= )
{
_foreData[y - , x] = (int)ForeState.NONE;
if (ForeCanvas.Children.Contains(_foreImage[y - , x]))
ForeCanvas.Children.Remove(_foreImage[y - , x]);
}
if (y - >= && x + <= _gameLevel._rowGrid - )
{
_foreData[y - , x + ] = (int)ForeState.NONE;
if (ForeCanvas.Children.Contains(_foreImage[y - , x + ]))
ForeCanvas.Children.Remove(_foreImage[y - , x + ]);
}
if (x - >= )
{
_foreData[y, x - ] = (int)ForeState.NONE;
if (ForeCanvas.Children.Contains(_foreImage[y, x - ]))
ForeCanvas.Children.Remove(_foreImage[y, x - ]);
}
if (x + <= _gameLevel._rowGrid - )
{
_foreData[y, x + ] = (int)ForeState.NONE;
if (ForeCanvas.Children.Contains(_foreImage[y, x + ]))
ForeCanvas.Children.Remove(_foreImage[y, x + ]);
}
if (y + <= _gameLevel._colGrid - && x - >= )
{
_foreData[y + , x - ] = (int)ForeState.NONE;
if (ForeCanvas.Children.Contains(_foreImage[y + , x - ]))
ForeCanvas.Children.Remove(_foreImage[y + , x - ]);
}
if (y + <= _gameLevel._colGrid - )
{
_foreData[y + , x] = (int)ForeState.NONE;
if (ForeCanvas.Children.Contains(_foreImage[y + , x]))
ForeCanvas.Children.Remove(_foreImage[y + , x]);
}
if (y + <= _gameLevel._colGrid - && x + <= _gameLevel._rowGrid - )
{
_foreData[y + , x + ] = (int)ForeState.NONE;
if (ForeCanvas.Children.Contains(_foreImage[y + , x + ]))
ForeCanvas.Children.Remove(_foreImage[y + , x + ]);
}
}
(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] > )
{
_foreData[dy, dx] = ;
} // 设置相应的图片(原始、红旗、问号)
_foreImage[dy, dx].Source = ImageHelper.CutImage(_bmpForeground,
new Int32Rect((_foreData[dy, dx]- ) * _cellSize.Width, , _cellSize.Width, _cellSize.Height)); // 计算地雷数
int num = ;
for (int y=; y<_gameLevel._colGrid; y++)
{
for (int x=; 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 = ; y < _gameLevel._colGrid; y++)
{
for (int x = ; 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=; y<_gameLevel._colGrid; y++)
{
for (int x=; x<_gameLevel._rowGrid; x++)
{
_foreImage[y, x] = new Image();
_foreImage[y, x].Source = ImageHelper.CutImage(_bmpForeground, new Int32Rect(, ,
_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 - ; _timerWinAnim.Start();
}
这是计时动画事件方法:
private void _timerWinAnim_Tick(object sender, EventArgs e)
{
if (iCount >= )
{
Storyboard sb1 = new Storyboard();
DoubleAnimation daOpacity = null; for (int x = ; 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()); 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 = ;
BitmapSource[] bmpSourceBomb = new BitmapSource[FRAME_COUNT];
int currFrame = ;
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=; i<FRAME_COUNT; i++)
{
bmpSourceBomb[i] = ImageHelper.CutImage(bmpBomb, new System.Windows.Int32Rect(i * , , , ));
} timerBomb = new DispatcherTimer();
timerBomb.Interval = TimeSpan.FromMilliseconds();
timerBomb.Tick += TimerBomb_Tick;
} private void TimerBomb_Tick(object sender, EventArgs e)
{
bombImg.Source = bmpSourceBomb[currFrame];
currFrame++;
if (currFrame == )
{
currFrame = ; 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 * );
Canvas.SetTop(bombImg, dx * ); timerBomb.Start();
} public System.Drawing.Point GetPosition()
{
return new System.Drawing.Point(dx, dy);
}
}
创建实例时,一并将相关参数传递过去。然后调用DrawBomb在启动内部计时器,该计时器顺序更新前景图片源,从而实现爆炸效果。
主程序添加两个字段:
private Bomb[] bombs;
private int bombCount = ;
在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( * _cellSize.Width, ,
_cellSize.Width, _cellSize.Height));
} int bombCount = ;
for (int j=; j<_gameLevel._colGrid; j++)
{
for (int i=; 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 - ))
{
bombCount = ;
_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(, , , , );
... case Level.NORMAL:
...
_timerBomb.Interval = new TimeSpan(, , , , );
... case Level.HARD:
...
_timerBomb.Interval = new TimeSpan(, , , , );
...
练手WPF(三)——扫雷小游戏的简易实现(下)的更多相关文章
- WEBGL学习笔记(七):实践练手1-飞行类小游戏之游戏控制
接上一节,游戏控制首先要解决的就是碰撞检测了 这里用到了学习笔记(三)射线检测的内容了 以鸟为射线原点,向前.上.下分别发射3个射线,射线的长度较短大概为10~30. 根据上一节场景的建设,我把y轴设 ...
- 练手WPF(三)——扫雷小游戏的简易实现(上)
一.创建项目1.创建WPF项目,设置初始化窗口大小(初级难度):高x宽为430x350.2.添加文件夹Images,并添加相关图片. 3.xaml中引入图片资源. <Window.Resourc ...
- 练手WPF(三)——扫雷小游戏的简易实现(中)
八.随机布雷 /// <summary> /// 随机布地雷 /// </summary> /// <param name="mineNum"> ...
- web版扫雷小游戏(一)
作为一名程序猿,平时的爱好也不多,说起游戏,我不太喜欢大型的网游,因为太耗时间,偶尔玩玩经典的单机小游戏,比如windows下自带的游戏扫雷(秀一下,高级下最高纪录110s). 现阶段正在致力于web ...
- Angular4 扫雷小游戏
扫雷小游戏,可以升级过关,难度随关卡增加.但是有很明显的bug,以后有时间会继续优化! HTML: <div class="mainContent"> <div ...
- 扫雷小游戏PyQt5开发【附源代码】
也没啥可介绍哒,扫雷大家都玩过. 雷的分布算法也很简单,就是在雷地图(map:二维数组)中,随机放雷,然后这个雷的8个方位(上下左右.四个对角)的数字(非雷的标记.加一后不为雷的标记)都加一. 如何判 ...
- 练手WPF(二)——2048游戏的简易实现(上)
1.创建游戏界面编辑MainWindow.xaml,修改代码如下: <Window.Resources> <Style TargetType="Label"> ...
- 练手WPF(一)——模拟时钟与数字时钟的制作(上)
一.Visual Studio创建一个WPF项目. 简单调整一下MainWindow.xaml文件.主要使用了两个Canvas控件,分别用于显示模拟和数字时钟,命名为AnalogCanvas.digi ...
- C++扫雷小游戏(基于CMD命令行)
这个小游戏是笔者在大一C语言课程设计的时候写的,基于命令行,为了显得漂亮一些,特别加上了彩色特效~~~ 注意:Win10系统须将命令行调为旧版命令行,否则有可能会显示乱码! 代码示例: #includ ...
随机推荐
- Django信号机制相关解释与示例
Django 信号# django自带一套信号机制来帮助我们在框架的不同位置之间传递信息.也就是说,当某一事件发生时,信号系统可以允许一个或多个发送者(senders)将通知或信号(signals)发 ...
- js中获取当前url路径
可以使用 window.location 获取当前页面url.以下是一些简单应用. <script> $(function(){ // 返回 web 主机的域名,如:http://127. ...
- 【RTOS】基于V7开发板的RTX5和FreeRTOS带CMSIS-RTOS V2封装层的模板例程下载,AC6和AC5两个版本
说明: 1.使用MDK的RTE环境开发RTX5和FreeRTOS,简单易移植,统一采用CMSIS-RTOS V2封装层. 2.DTCM是H7里面性能最高的RAM,主频400MHz,跟内核速度一样,所以 ...
- PAT 1007 Maximum Subsequence Sum 最大连续子序列和
Given a sequence of K integers { N1, N2, …, NK }. A continuous subsequence is defined to be { Ni, Ni ...
- 雪崩利器 hystrix-go 源码分析
阅读源码的过程,就像是在像武侠小说里阅读武功秘籍一样,分析高手的一招一式,提炼出精髓,来增强自己的内力. 之前的帖子说了一下微服务的雪崩效应和常见的解决方案,太水,没有上代码怎么叫解决方案.githu ...
- ABP开发框架前后端开发系列---(14)基于Winform的ABP快速开发框架
前面介绍了很多ABP系列的文章,一步一步的把我们日常开发中涉及到的Web API服务构建.登录日志和操作审计日志.字典管理模块.省份城市的信息维护.权限管理模块中的组织机构.用户.角色.权限.菜单等内 ...
- Spring Boot 2 快速教程:WebFlux 快速入门(二)
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 02:WebFlux 快速入门实践 文章工程: JDK 1.8 ...
- 怎么将CAD转PNG格式?这两种方法值得收藏
在从事相关CAD工作的小伙伴们都知道,CAD中不光需要绘制各种各样的图纸,还需要根据工作的需要来进行图纸格式的转换工作.那有的时候就需要将CAD转换成PNG格式的图片进行使用.那怎么将CAD转PNG格 ...
- SAP 不支持交货单中同一个物料多个行项目HU与序列号组合发货场景
SAP 不支持交货单中同一个物料多个行项目HU与序列号组合发货场景 笔者所在的项目里,后勤业务启用了序列号管理,Handling Unit Manager以及批次号管理.不同的物料分别启用序列号管理, ...
- OpenCV:图像的按位运算
首先导包: import numpy as np import cv2 import matplotlib.pyplot as plt def show(image): plt.imshow(imag ...