C#编写扫雷游戏

_路在远方 2015-11-02 11:27:23
我突发奇想,想用C#编写了一个扫雷游戏,在网上搜了半天,结果一无所获。
在图书馆翻了半天的书,硬着头皮把一本很厚很厚的书看完了,总之,还是觉得收获挺大。
为了能够学以致用,我开始通过C#来编写扫雷游戏。
废话少说,我们先来了解一下扫雷游戏的一些功能:
1.首先是界面设计:通过不同颜色大小的画刷画笔以及在窗体上绘制直线,将窗体分成两个区域。一个用来显示时间和雷数还有复位按钮,一个用来装载棋盘。

哎!完成这个东西其实很不容易,需要一些图片资源,要分析它们的尺寸,等等,图片上所有的资源都来自这个:

就是将它们的一部分截下来放在窗体的某一位置,呵呵,而你的工作就是以一种可视化有趣的方式不断的变换变量之间的次序。
想要代码吗?我觉得其实用处不大,关键得看懂,我也知道,一般在短时间内是不易看懂的,因为我做这个也花了不少时间。
本来想粘贴代码的,可是字数限制了只好算了
不管是什么游戏,产生随机数,这个少不了!我们需要编写一个类,内容如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 扫雷
{
class GetRandom
{
public static Random globalRandomGenerator= GenerateNewRandomGenerator();
public static Random GenerateNewRandomGenerator()
{
globalRandomGenerator= new Random((int)DateTime.Now.Ticks);
return globalRandomGenerator;
}
public static int GetRandomInt()//随机产生0至9的一个整数
{
return globalRandomGenerator.Next(10);
}
public static int GetRandomInt(int max)
{
return globalRandomGenerator.Next(max);
}
//产生一个随机浮点数组
public static float[] GenerateRandomOrder(int n)
{
float[] RandomOrder=new float[n];
for (int i = 0; i < n; i++)
{
RandomOrder[i] =GetRandom.GetRandomFloat(0.0f,1.0f);
}
return RandomOrder;
}
public static int GetRandomInt(int min, int max)
{
return globalRandomGenerator.Next(max - min) + min;
}
public static float GetRandomFloat(float min, float max)
{
return (float)globalRandomGenerator.NextDouble() * (max - min) + min;
}
//将一串随机数顺序打乱
public static int[] ArrRandom(int[] Arr)
{
int k = 0;
int strtmp ;
for (int i = 0; i < Arr.Length; i++)
{
k = GetRandomInt(Arr.Length);
if (k != i)
{
strtmp = Arr[i];
Arr[i] = Arr[k];
Arr[k] = strtmp;
}
}
return Arr;
}
}
}




当然这只是完成了边框的绘制,为了使游戏更有趣,更具可玩性,我们不妨来让地雷的布局由用户自己定义,呵呵,这就好玩多了!

允许用户自己定义地雷的布局,行列数,当然,一些简单的菜单控件的布置我就不说了,只需一拖。上图:

自定义布雷:

这个游戏的核心部分就是递归算法。还有许多辅助函数,由于内容太多,工作量比较大,我只好一股脑粘贴啦!
#region 几个重要的自定义函数
//放置雷的函数
private void LayMines()
//获得某个方格周围雷的个数的函数
private int GetAroundNums(MineGrid mineGrid)
/// <summary>
/// 当由于某种操作(可能是左键点击了一个恰好周围没有一个雷的方格,也可能是鼠标左右键点
/// 击了一个状态为数字的方格而触发了某个周围没有一个雷的方格被显示),一个周围没有一个
/// 雷的方格被显示,此时必须显示这个方格周围方格的周围雷的个数,而这可能发生一系列的连
/// 锁反应,导致一片方格被展开,这个过程的实现依靠递归函数Expand(MineGrid mineGrid)来实现:
/// </summary>
/// <param name="mineGrid"></param>
private void Expand(MineGrid mineGrid)
{
ExpandNums++;
int row = mineGrid.Row;
int col = mineGrid.Col;
int minRow = row == 0 ? 0 : row - 1;
int maxRow = row == RowsNum - 1 ? row : row + 1;
int minCol = col == 0 ? 0 : col - 1;
int maxCol = col == ColsNum - 1 ? col : col + 1;
int aroundNums = GetAroundNums(mineGrid);
mineGrid.State = 15 - aroundNums;
mineGrid.OldState = 15 - aroundNums;
if (aroundNums == 0)
{
for (int i = minRow; i <= maxRow; i++)
{
for (int j = minCol; j <= maxCol; j++)
{
if (!(i == row && j == col) &&mineGrids[i, j].State == (int)MineGridState.Normal&& (!mineGrids[i, j].IsMine))
{
Expand(mineGrids[i, j]);
}
}
}
}
}
/// <summary>
/// 当游戏失败时,必须显示还有哪些为雷的方格没有被确认,void Dead()函数:
/// </summary>
private void Dead()
//游戏胜利的判断函数:
private bool Victory()
#endregion

现在就可以开玩啦!

不好意思,把我的个人信息粘上来了。
再来一张,我玩过的:

哈哈!有意思吧!我居然用了942秒就通过了!
偶!忘了说了,还需要加载音乐,不然就太无趣了!
添加一个命名空间:
using System.Runtime.InteropServices;

向定义全局变量一样放入以下语句(具体是什么原理,?我也不太清楚)

[DllImport("winmm")]
public static extern bool PlaySound(string szSound, int hMod, int i);
为了使编程更简单,还需要编写一些播放声音的函数。当然如果你比较牛逼,可以不写,不过你要让你多年后还能看得懂。
private void Play(string waveName)
{
PlaySound(Application.StartupPath + "\\GameSounds\\" + waveName + ".wav", 0, 1);
}

在可执行目录下放置几个音乐文件,命名规则遵循见名知意,当然其他变量和函数的命名都应该遵循这个潜规则(难道你老湿没告诉你吗?)。


还有,我们一时兴奋,完成了一项伟大的扫雷任务,想要把它保存起来,该怎么办!和其他游戏一样,我们可以来个截图,留下精彩瞬间,也可以保存一下游戏记录,以便以后翻给学弟学妹门看,看,这是我创下了丰碑!当然你也有了装逼的资本,虐小狗还是挺容易的,呵呵!

保存截图这项功能是我通过网络了解到的,其实对于编程这种数据量繁重的工作对人脑的载荷能力是很有挑战性的。一个很好的应对办法是通过网络不断的学习和发展自我。分享自己的智慧,也吸收别人的精华。我不可能手把手的帮助到你,只能交给你怎样学习的方法。 这些对你在今后的学习工作中都是有用的。
private void 保存截图ToolStripMenuItem_Click(object sender, EventArgs e)
{
Bitmap bit = new Bitmap(this.Width - 16, this.Height - 7);//实例化一个和窗体一样大的bitmap
Graphics g = Graphics.FromImage(bit);
g.CompositingQuality = CompositingQuality.HighQuality;//质量设为最高
//g.CopyFromScreen(this.Left+8, this.Top, 0, 0, new Size(this.Width-12, this.Height-7));//保存整个窗体为图片
g.CopyFromScreen(new Point(this.Left + 8, this.Top), new Point(0, 0), new Size(this.Width - 16, this.Height - 7));
//g.CopyFromScreen(panel游戏区 .PointToScreen(Point.Empty), Point.Empty, panel游戏区.Size);//只保存某个控件(这里是panel游戏区)
bit.Save(Application.StartupPath + "\\扫雷游戏"+DateTime.Now.Year.ToString() + DateTime.Now.Month.ToString() + DateTime.Now.Year.ToString()
+ DateTime.Now.Day.ToString() + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString()+
DateTime.Now.Second.ToString()+DateTime.Now.Millisecond.ToString()+".png");//默认保存格式为PNG,保存成jpg格式质量不是很好
}

图片保存的名字以“扫雷游戏+日期+时间”命名
如:

保存记录需要按照一定的格式,方便其他程序调用。

private void 保存记录ToolStripMenuItem_Click(object sender, EventArgs e)
{
GameTimer.Stop();
FormGetName Fgname = new FormGetName();
if (Fgname.ShowDialog()==DialogResult.OK)
{
string LevelInfo = "";
for (int i = 0; i < RowsNum; i++)
{
for (int j = 0; j < ColsNum; j++)
{

LevelInfo += (mineGrids[i, j].IsMine == true) ? "1" : "0";
}
}
LevelInfo = LevelInfo + "," + RowsNum.ToString() + "," + ColsNum.ToString() + "," + RowsColsNums[3 * Difficulty + 2].ToString()
+ "," + SpendSeconds.ToString() + "," + System.Enum.GetName(typeof(GameState), gameState) + ","+DateTime.Now.ToLongDateString()
+ "," + DateTime.Now.ToLongTimeString() + ",";
GameConfig.AddRecord(LevelInfo, Fgname.GetUserName);
}
if (gameState==(int)GameState.Run)
{
GameTimer.Start();
}
}

这里加载了一个计时器,这些次要的东西我都忽略不讲了。
当然,游戏保存了以后还需要浏览哈!需要再新建一个子窗体。

它的程序如下:
在主类中调用这个类
private void 浏览记录ToolStripMenuItem_Click(object sender, EventArgs e)
{
FormScanRecord FSR = new FormScanRecord();
FSR.Show();
}
窗体类中构造显示函数,读取保存的信息:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 扫雷
{
public partial class FormScanRecord : Form
{
string showInfo;
public FormScanRecord()
{
InitializeComponent();
}

private void FormScanRecord_Load(object sender, EventArgs e)
{
string[] RecordInfo=GameConfig.GetFileLines();

if (RecordInfo!=null)
{
if (RecordInfo[0]=="0")
{
MessageBox.Show("还没有游戏记录");
this.Close();
}
else
{
showInfo = "一共有" + RecordInfo[0] + "次记录,按时间先后顺序分别是:\r\n";
for (int i = 1; i < int.Parse(RecordInfo[0])+1; i++)
{
string[] strSplit = RecordInfo[i].Split(',');
showInfo += "\r\n第" + i.ToString() + "位扫雷员:\r\n";
showInfo += " 姓名:" + strSplit[8] + "\r\n";
showInfo += " 扫雷行数:" + strSplit[1] + "\r\n";
showInfo += " 扫雷列数:" + strSplit[2] + "\r\n";
showInfo += " 雷数:" + strSplit[3] + "\r\n";
showInfo += " 地雷分布密度:" + (int.Parse(strSplit[3]) / (float)((int.Parse(strSplit[1]) * int.Parse(strSplit[2])))).ToString() + "\r\n";
showInfo += " 扫雷用时:" + (int.Parse(strSplit[4]) / 60).ToString() + "分" + (int.Parse(strSplit[4]) % 60).ToString() + "秒\r\n";
showInfo += " 扫雷记录状态:" + strSplit[5] + "\r\n";
showInfo += " 扫雷记录日期:" + strSplit[6] + "\r\n";
showInfo += " 扫雷记录时间:" + strSplit[7] + "\r\n";
}
this.RecordtextBox.Text = showInfo;
}
}
}

private void close_Click(object sender, EventArgs e)
{
this.Close();
}
}
}
最后,也是你最迫不及待想要的东西,我还是把可执行文件发给你们吧。
点击下面的链接:
http://pan.baidu.com/s/1c0uhFCO
需要源代码的,可以向我发送QQ邮件,我会不定时查看
1596058469@qq.com

我也不知道为啥,不能上传图片了。

...全文
427 7 打赏 收藏 转发到动态 举报
写回复
用AI写文章
7 条回复
切换为时间正序
请发表友善的回复…
发表回复
_路在远方 2015-11-04
  • 打赏
  • 举报
回复
递归算法,巧用标识参数。
john_QQ:2335298917 2015-11-04
  • 打赏
  • 举报
回复
分享一下代码吧,反正也不能卖钱
_路在远方 2015-11-03
  • 打赏
  • 举报
回复
咋了?我写了qq密码吗?在哪里?
runerback 2015-11-03
  • 打赏
  • 举报
回复
我也写了一个,还是WinForm的。。不过最后不知道怎么做出那种点一个出来一大片的那种效果,或者说不知道怎么设置可以被点出来的一大片的“封闭”区域,所以放弃了,不知道你是怎么实现的
Poopaye 2015-11-02
  • 打赏
  • 举报
回复
你这样直接写着qq密码真的好吗?
  • 打赏
  • 举报
回复
写了这么多,为啥不能上传源代码呢?
crystal_lz 2015-11-02
  • 打赏
  • 举报
回复
http://bbs.csdn.net/topics/390606908 以前做过一个 demo

110,552

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧