C#带你玩扫雷(附源码)
程序员文章站
2024-02-11 21:04:46
扫雷游戏,大家都应该玩过吧!其实规则也很简单,可是我们想自己实现一个扫雷,我们应该怎么做呢?
step1: 知晓游戏原理
扫雷就是要把所有非地雷的格子揭开即...
扫雷游戏,大家都应该玩过吧!其实规则也很简单,可是我们想自己实现一个扫雷,我们应该怎么做呢?
step1: 知晓游戏原理
扫雷就是要把所有非地雷的格子揭开即胜利;踩到地雷格子就算失败。游戏主区域由很多个方格组成。使用鼠标左键随机点击一个方格,方格即被打开并显示出方格中的数字;方格中数字则表示其周围的8个方格隐藏了几颗雷;如果点开的格子为空白格,即其周围有0颗雷,则其周围格子自动打开;如果其周围还有空白格,则会引发连锁反应;在你认为有雷的格子上,点击右键即可标记雷;如果一个已打开格子周围所有的雷已经正确标出,则可以在此格上同时点击鼠标左右键以打开其周围剩余的无雷格。
1代表1的上下左右及斜角合计有一颗雷,依次轮推,2则有2颗,3则有3颗..
在确实是炸弹的方格上点了旗子,就安全了,不是炸弹的被点了旗子,后面会被炸死的..问号就先不确定这里有没有炸弹,不会存在点错了被炸死的状况..
step2: 由step1可知,游戏由格子组成,翻译成代码语言就叫做数组,也就是游戏地图就是一个二维数组。格子对象,格子的值即当前雷的数量,那么此时我们暂定雷的数字标识为-1。除此之外,格子对象还有是否被显示,显示当前雷数量等属性,那么我们大概可以定义这样一个类:
public class cellblockrole { /// <summary> /// 位于游戏地图中的坐标点x /// </summary> public int x { get; set; } /// <summary> /// 位于游戏地图中的坐标点y /// </summary> public int y { get; set; } /// <summary> /// 是否展示最后格子所代表的结果 /// </summary> public bool isshowresult { get; set; } = false; /// <summary> /// 是否计算数字结果 /// </summary> public bool iscomputeresult { get; set; } = false; /// <summary> /// 是否已经展示过计算结果了 /// </summary> public bool ishasshowcomputed { get; set; } = false; /// <summary> /// 当前的格子的角色数字, -1:地雷,其他当前雷的数量 /// </summary> public int number { set; get; } = 0; /// <summary> /// 是否被flag标识 /// </summary> public bool isflag { get; set; } = false; /// <summary> /// 是否是雷 /// </summary> public bool isboom => number == -1; }
绘制游戏ui画面,见代码:
using system; using system.collections.generic; using system.componentmodel; using system.drawing; using system.data; using system.linq; using system.text; using system.threading; using system.threading.tasks; using system.windows.forms; using sweeperlibrary.properties; using timer = system.threading.timer; namespace sweeperlibrary { public delegate void ongameoverdelegate(); public delegate void onshowanumberdelegate(); public delegate void onpublishgametimedelegate(string timedescription); public partial class gameview : usercontrol { /// <summary> /// 游戏结束事件 /// </summary> public event ongameoverdelegate ongameoverevent; /// <summary> /// 当一个格子被点击时,显示当前数字的事件 /// </summary> public event onshowanumberdelegate onshowanumberevent; /// <summary> /// 发布当前游戏的时间 /// </summary> public event onpublishgametimedelegate onpublishgametimeevent; /// <summary> /// 游戏绘制地图的每个格子的大小 /// </summary> public static readonly int cellsize = 40; /// <summary> /// 游戏规模n*n /// </summary> public static readonly int gamecellcount = 10; /// <summary> /// 移动方向坐标点改变的数组 /// </summary> public static readonly int[][] movedirectionpoints = { new[]{-1, -1}, new[] {0, -1}, new[] {1, -1}, new[] {1, 0}, new[] {1, 1}, new[] {0, 1}, new[] {-1, 1}, new[] {-1, 0} }; /// <summary> /// 随机数雷生成对象 /// </summary> private static readonly random random = new random(guid.newguid().gethashcode()); /// <summary> /// 游戏地图标识数组 /// </summary> private cellblockrole[][] gamemap = new cellblockrole[gamecellcount][]; /// <summary> /// 雷的数量,默认为10 /// </summary> public int boomcount { get; set; } = 10; /// <summary> /// 游戏开始时间 /// </summary> private datetime gamestarttime; /// <summary> /// 计时定时器 /// </summary> private system.windows.forms.timer gametimer = new system.windows.forms.timer(); public gameview() { initializecomponent(); setstyle(controlstyles.optimizeddoublebuffer, true); setstyle(controlstyles.allpaintinginwmpaint, true); initgame(); //默认游戏已经开始 setgametimer(); //设置游戏定时器 } private void gameview_paint(object sender, painteventargs e) { width = gamecellcount + 1 + gamecellcount * cellsize; height = gamecellcount + 1 + gamecellcount * cellsize; //绘制游戏界面 graphics graphics = e.graphics; graphics.clear(color.whitesmoke); if (gamemap != null && gamemap.length > 0 && gamemap[0] != null && gamemap[0].length > 0) { for (int y = 0; y < gamecellcount; y++) { for (int x = 0; x < gamecellcount; x++) { int dx = x + 1 + x * cellsize, dy = y + 1 + y * cellsize; cellblockrole cellblockrole = gamemap[y][x]; graphics.fillrectangle(new solidbrush(cellblockrole.isshowresult ? color.lightslategray : color.whitesmoke), dx, dy, cellsize, cellsize); graphics.drawrectangle(new pen(color.lightgray), dx, dy, cellsize, cellsize); if (cellblockrole.isshowresult && cellblockrole.number != 0) { switch (cellblockrole.number) { case -1: //雷 graphics.drawimage(image.fromhbitmap(resources.boom.gethbitmap()), new rectanglef(dx, dy, cellsize, cellsize)); break; default: //数字 string drawtext = cellblockrole.number.tostring(); font textfont = new font(fontfamily.genericsansserif, 12, fontstyle.bold); sizef textsize = graphics.measurestring(drawtext, textfont); graphics.drawstring(drawtext, textfont, new solidbrush(color.white), dx + (cellsize - textsize.width) / 2, dy + (cellsize - textsize.height) / 2); break; } } } } } } private void gameview_mousedown(object sender, mouseeventargs e) { int px = (e.x - 1) / (cellsize + 1), py = (e.y - 1) / (cellsize + 1); switch (e.button) { case mousebuttons.left: //鼠标左键 if (!gamemap[py][px].isshowresult) { if (gamemap[py][px].isboom) { new thread(() => { showallcellblockrolenumber(); if (this.invokerequired) { methodinvoker del = invalidate; this.invoke(del); } else { invalidate(); } }).start(); gametimer.stop(); ongameoverevent?.invoke(); } else { new thread(() => { showneiborhoodcellrolesbyposi(px, py); if (this.invokerequired) { methodinvoker del = invalidate; this.invoke(del); } else { invalidate(); } }).start(); onshowanumberevent?.invoke(); } } break; case mousebuttons.right: //鼠标右键 break; } } /// <summary> /// 初始化游戏 /// </summary> private void initgame() { new thread(() => { initgamemap(); generatebooms(); if (this.invokerequired) { methodinvoker del = invalidate; this.invoke(del); } else { invalidate(); } }).start(); } /// <summary> /// 设置游戏定时器 /// </summary> private void setgametimer() { gametimer.interval = 1000; gametimer.enabled = true; gametimer.tick += (sender, args) => { long dmillisecond = datetime.now.millisecond - gamestarttime.millisecond; long hour = dmillisecond / 60 / 60 / 1000; long minute = (dmillisecond - hour * (60 * 60 * 1000)) / (60 * 1000); long second = ((dmillisecond - hour * (60 * 60 * 1000)) % (60 * 1000)) / 1000; onpublishgametimeevent?.invoke((hour > 0 ? (hour > 9 ? hour.tostring() : "0" + hour) + ":" : "") + (minute > 9 ? minute.tostring() : "0" + minute) + ":" + (second > 9 ? second.tostring() : "0" + second)); }; } /// <summary> /// 初始化游戏地图 /// </summary> private void initgamemap() { for (int i = 0; i < gamecellcount; i++) { gamemap[i] = new cellblockrole[gamecellcount]; for (int j = 0; j < gamecellcount; j++) { gamemap[i][j] = new cellblockrole { x = j, y = i }; } } gamestarttime = datetime.now; gametimer.start(); } /// <summary> /// 重置游戏地图 /// </summary> public void resetgamemap() { new thread(() => { for (int i = 0; i < gamecellcount; i++) { for (int j = 0; j < gamecellcount; j++) { gamemap[i][j].x = j; gamemap[i][j].y = i; gamemap[i][j].number = 0; gamemap[i][j].isshowresult = false; gamemap[i][j].iscomputeresult = false; gamemap[i][j].ishasshowcomputed = false; } } generatebooms(); //生成一些雷 if (this.invokerequired) { methodinvoker del = invalidate; this.invoke(del); } else { invalidate(); } }).start(); gamestarttime = datetime.now; gametimer.start(); } /// <summary> /// 随机生成一些地雷 /// </summary> public void generatebooms() { for (int i = 0; i < boomcount; i++) { int boomnumberindex = random.next(0, gamecellcount * gamecellcount - 1); //生成随机数的范围 int boomx = boomnumberindex % gamecellcount, boomy = boomnumberindex / gamecellcount; if (gamemap[boomy][boomx].number == 0) gamemap[boomy][boomx].number = -1; //-1表示雷 else // 已经存在雷了,所以要重新处理 i--; } makeallnumbercomputeincellrole(0, 0); //默认从坐标(0,0)开始 } /// <summary> /// 显示所有的格子的信息 /// </summary> private void showallcellblockrolenumber() { for (int i = 0; i < gamecellcount; i++) { for (int j = 0; j < gamecellcount; j++) { gamemap[i][j].isshowresult = true; } } } /// <summary> /// 显示某点周边所有格子的数字 /// </summary> /// <param name="posix">x轴坐标</param> /// <param name="posiy">y轴坐标</param> private void showneiborhoodcellrolesbyposi(int posix, int posiy) { gamemap[posiy][posix].isshowresult = true; gamemap[posiy][posix].ishasshowcomputed = true; int boomcount = getboomcountinneiborhood(posix, posiy); if (boomcount == 0) //如果周围没有雷,则翻开所有8个方向的相关数字 { for (int i = 0; i < movedirectionpoints.length; i++) { int[] itemposi = movedirectionpoints[i]; int rx = posix + itemposi[0], ry = posiy + itemposi[1]; bool isnotoutindexrange = rx >= 0 && rx < gamecellcount && ry >= 0 && ry < gamecellcount; if (isnotoutindexrange) //防止坐标溢出 { gamemap[ry][rx].isshowresult = true; if (!gamemap[ry][rx].ishasshowcomputed && gamemap[ry][rx].number == 0) showneiborhoodcellrolesbyposi(rx, ry); } } } } /// <summary> /// 获取某点附近的雷数量 /// </summary> /// <param name="posix">x轴坐标点</param> /// <param name="posiy">y轴坐标点</param> /// <returns></returns> private int getboomcountinneiborhood(int posix, int posiy) { int boomcount = 0; for (int i = 0; i < movedirectionpoints.length; i++) { int[] itemposi = movedirectionpoints[i]; int rx = posix + itemposi[0], ry = posiy + itemposi[1]; bool isnotoutindexrange = rx >= 0 && rx < gamecellcount && ry >= 0 && ry < gamecellcount; if (isnotoutindexrange && gamemap[ry][rx].isboom) //防止坐标溢出 { boomcount++; } } return boomcount; } /// <summary> /// 计算每个格子的数字标识 /// </summary> /// <param name="posix">x轴坐标</param> /// <param name="posiy">y轴坐标</param> private void makeallnumbercomputeincellrole(int posix, int posiy) { int boomcount = getboomcountinneiborhood(posix, posiy); if (boomcount != 0) //如果周围没有雷,则计算周围的8个方向的格子 { gamemap[posiy][posix].number = boomcount; } else { if (!gamemap[posiy][posix].isboom) gamemap[posiy][posix].number = 0; } gamemap[posiy][posix].iscomputeresult = true; for (int i = 0; i < movedirectionpoints.length; i++) { int[] itemposi = movedirectionpoints[i]; int rx = posix + itemposi[0], ry = posiy + itemposi[1]; bool isnotoutindexrange = rx >= 0 && rx < gamecellcount && ry >= 0 && ry < gamecellcount; if (isnotoutindexrange && !gamemap[ry][rx].iscomputeresult && !gamemap[ry][rx].isboom) //防止坐标溢出 { makeallnumbercomputeincellrole(rx, ry); } } } } }
主要代码已经实现,现已知现有代码的定时器由问题,暂时不支持flag(旗子标识)。当然代码中还有其他不足的地方,游戏持续优化中。。。
源代码地址:minesweeper-cshape_jb51.rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
C#带你玩扫雷(附源码)
-
C# Winform调用百度接口实现人脸识别教程(附源码)
-
C#图书管理系统 附源码下载
-
基于Socket通讯(C#)和WebSocket协议(net)编写的两种聊天功能(文末附源码下载地址)
-
一篇文章带你入门Springboot整合微信登录与微信支付(附源码)
-
基于C#生成条形码操作知识汇总附源码下载
-
c#实现一个超实用的证件照换底色小工具(附源码)
-
Asp.net(C#)读取数据库并生成JS文件制作首页图片切换效果(附demo源码下载)
-
c#实现一个超实用的证件照换底色小工具(附源码)
-
Asp.net(C#)读取数据库并生成JS文件制作首页图片切换效果(附demo源码下载)