十四、响应鼠标点击事件
    (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(三)——扫雷小游戏的简易实现(下)的更多相关文章

  1. WEBGL学习笔记(七):实践练手1-飞行类小游戏之游戏控制

    接上一节,游戏控制首先要解决的就是碰撞检测了 这里用到了学习笔记(三)射线检测的内容了 以鸟为射线原点,向前.上.下分别发射3个射线,射线的长度较短大概为10~30. 根据上一节场景的建设,我把y轴设 ...

  2. 练手WPF(三)——扫雷小游戏的简易实现(上)

    一.创建项目1.创建WPF项目,设置初始化窗口大小(初级难度):高x宽为430x350.2.添加文件夹Images,并添加相关图片. 3.xaml中引入图片资源. <Window.Resourc ...

  3. 练手WPF(三)——扫雷小游戏的简易实现(中)

    八.随机布雷 /// <summary> /// 随机布地雷 /// </summary> /// <param name="mineNum"> ...

  4. web版扫雷小游戏(一)

    作为一名程序猿,平时的爱好也不多,说起游戏,我不太喜欢大型的网游,因为太耗时间,偶尔玩玩经典的单机小游戏,比如windows下自带的游戏扫雷(秀一下,高级下最高纪录110s). 现阶段正在致力于web ...

  5. Angular4 扫雷小游戏

    扫雷小游戏,可以升级过关,难度随关卡增加.但是有很明显的bug,以后有时间会继续优化! HTML: <div class="mainContent"> <div ...

  6. 扫雷小游戏PyQt5开发【附源代码】

    也没啥可介绍哒,扫雷大家都玩过. 雷的分布算法也很简单,就是在雷地图(map:二维数组)中,随机放雷,然后这个雷的8个方位(上下左右.四个对角)的数字(非雷的标记.加一后不为雷的标记)都加一. 如何判 ...

  7. 练手WPF(二)——2048游戏的简易实现(上)

    1.创建游戏界面编辑MainWindow.xaml,修改代码如下: <Window.Resources> <Style TargetType="Label"> ...

  8. 练手WPF(一)——模拟时钟与数字时钟的制作(上)

    一.Visual Studio创建一个WPF项目. 简单调整一下MainWindow.xaml文件.主要使用了两个Canvas控件,分别用于显示模拟和数字时钟,命名为AnalogCanvas.digi ...

  9. C++扫雷小游戏(基于CMD命令行)

    这个小游戏是笔者在大一C语言课程设计的时候写的,基于命令行,为了显得漂亮一些,特别加上了彩色特效~~~ 注意:Win10系统须将命令行调为旧版命令行,否则有可能会显示乱码! 代码示例: #includ ...

随机推荐

  1. WSGI与uWSGI的应用场景与使用方法

    WSGI  /与/  uWSGI 在阿里云上部署项目时,在通信中我们都会用到wsgi与uWSGI,这此我就带大家来了解一下wsgi与uWSGI. 对了,上次有个朋友问我Django的生命周期是什么?我 ...

  2. Metasploit从文件中读取目标地址

    本文简单介绍如何使用Metasploit从文件中读取目标地址,来执行检测. 以检测MS17-010漏洞为例,在设定RHOSTS参数时,可设定目标地址范围和CIDR地址块,设定单个IP的目标也是可以的. ...

  3. springboot hikari 连接池 在启动时未初始化数据库连接问题

    在启动Springboot 项目时 2019-11-18 21:32:38.223 INFO 1080 --- [on(4)-127.0.0.1] o.s.web.servlet.Dispatcher ...

  4. tensorflow convert_variables_to_constants

    在使用tf.train.Saver函数保存模型文件的时候,是保存所有的参数信息,而有些时候我们并不需要所有的参数信息.我们只需要知道神经网络的输入层经过前向传播计算得到输出层即可,所以在保存的时候,我 ...

  5. NFS客户端挂载及永久生效

    1.NFS客户端挂载的命令格式: 挂载命令 挂载的格式类型 NFS服务器提供的共享目录 NFS客户端要挂载的目录mount -t nfs 服务器IP:/共享目录 /本地的挂载点(必须存在) 重启失效 ...

  6. Difference between JDK, JRE and JVM

    With Java programming language, the three terms i.e. JDK, JRE and JVM will always be there to unders ...

  7. Elasticsearch 6.x版本全文检索学习之数据建模

    1.什么是数据建模. 答:数据建模,英文为Data Modeling,为创建数据模型的过程.数据模型Data Mdel,对现实世界进行抽象描述的一种工具和方法,通过抽象的实体及实体之间联系的形式去描述 ...

  8. 阿里面试实战题2----ReentrantLock里面lock和tryLock的区别

    ReentrantLock ReentrantLock(轻量级锁)也可以叫对象锁,可重入锁,互斥锁.synchronized重量级锁,JDK前期的版本lock比synchronized更快,在JDK1 ...

  9. ASP.NET Core 2.2 WebApi 系列【二】使用EF CodeFirst创建数据库

    Code First模式 Code First是指"代码优先"或"代码先行". Code First模式将会基于编写的类和配置,自动创建模型和数据库. 一.准备 ...

  10. STL--标准模板库--简要概述

    STL--标准模板库 #include <vector>     //头文件 STL(Standared Template Library)即标准模板库,惠普实验室开发的一系列软件的统称. ...