鱼C论坛

 找回密码
 立即注册
查看: 2060|回复: 0

[技术交流] C控制台项目第一篇(扫雷)

[复制链接]
发表于 2016-1-28 10:13:16 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 Praw2NS 于 2016-1-28 11:34 编辑

扫雷程序的实现


这应该是我本人在鱼C上面发的第一篇主题帖,写了一个小项目的系列,在这里献丑了!毕竟我是15PB的招生老师,我也不搞技术,这个纯属兴趣使然。15PB的任何一个学习超过1个月的学员都要比我写的好,所以大家要是有兴趣的话就早点来15PB吧,相信会有很大的提升。

一直说写个几百行的小项目,于是我写了一个控制台的扫雷,没有想到精简完了代码才200行左右,不过考虑到这是我精简过后的,浓缩才是精华嘛,我就发出来大家一起学习啦,看到程序跑起来能玩,感觉还是蛮有成就感的~哈哈
扫雷应该属于一款大众游戏,从我初中使用计算机开始,它就被集成到了windows系统中,虽然他是这么经典,我还是要介绍一下他的玩法,然后再考虑在控制台中怎么实现它。(扫雷界面 如图1)



1 游戏的主界面,是一个一个小方格,在小方格上单击左键,可以翻开小方格看看后面有什么。
2 在这些小方格的背后隐藏着雷,如果不幸点中了雷,那么就GameOver了。


2 如果点中的不是一个雷,那么就是一块空地,这个时候会出现两种情况:
   1)用鼠标点中的空地周围八个点内有雷,那么就显示雷的个数


   2)用鼠标点中的空地周围没有雷,这个时候就将周围的空地全部显示出来,遇到该显示数字的空地,就将数字显示出来。(仔细观察你会发现,数字会将空地围起来,这是一句废话,但是也值得想一想这是为什么)

3 在小方格上,点击鼠标的右键,可以将一个空地标记为雷,当然这个功能只是为了方便你记忆你之前确定是雷的地方。(还有左右键都点,和点击右键出现?标记,这里就不谈啦)
4 当空地上剩余的格子数和雷的个数一样多,那么这个时候就应该算是胜利啦。
OK~游戏流程说完了,这个时候该谈谈如何实现了。
1 首先需要一张地图,一般情况下我们都可以用一个二维数组表示一个地图,每一个元素代表着扫雷中的一个小方格。相应元素存储0,那么地图上的这个位置就是空地,相应元素存储1,那么就代表这个位置就一颗雷。
2 在控制台上依照二维数组长度和宽度,打印相应的小方块。
3 然后就用鼠标点击那些小方块,对于控制台来讲,在黑框框的区域中是有坐标的,可以使用一些函数捕获到你点击了屏幕的哪一个坐标。
4 对于控制台来说,打印一个字符,有的字符横向占一个位置比如普通的字母数字,有的字符横向占两个位置比如一些图形字符: ①②③■◆等等,这点在控制台编程的时候要注意。
5 当点击屏幕的时候,获取到点击的坐标后,去二维数组中查看相应的位置是雷还是空地,从而做相应的处理。
1)假如点击到了雷,那么就控制游戏结束
2)假如点击到了空地有两种情况
1)点击的空地周围有雷,那么就将雷的个数显示出来
2)假如点击的空地周围没有雷,那么就使用递归的方法去探测周围的点,探测出与其相连的所有周围有雷的点。


这个是我实现的效果:(吐槽,每天只能上传3个附件,来来小甲鱼你过来,我保证不打死你。这个就上传不了了,可能后面的PDF也上传不了!!!)

下面就是代码啦:
  1. // saolei.cpp : 定义控制台应用程序的入口点。
  2. //

  3. #include "stdafx.h"
  4. #include <windows.h>
  5. #include <stdlib.h>
  6. #include <time.h>
  7. #define Boom   10
  8. int a[10][10] = {0};
  9. COORD TempPos[100] ={0};
  10. int nSign = 0;


  11. /************************************
  12. 函数名  :  WriteWchar
  13. 函数作用:  在控制台相应的坐标上显示一串字符
  14. 返回值  :  void     
  15. 参数    :  int x 横坐标
  16. 参数    :  int y 纵坐标
  17. 参数    :  char szString[]  要显示的字符串
  18. 说明    :
  19. ************************************/
  20. void  WriteWchar(int x,int y,char szString[])
  21. {
  22. HANDLE hOut= GetStdHandle(STD_OUTPUT_HANDLE);
  23. COORD pos  = {x*2,y};
  24. SetConsoleCursorPosition(hOut,pos);
  25. printf("%s",szString);
  26. }


  27. /************************************
  28. 函数名  :  DrawNumber
  29. 函数作用:  在相应的坐标上,根据传入的数字,打印相应的数字字符
  30. 返回值  :  void     
  31. 参数    :  COORD pos    要打印的位置
  32. 参数    :  int nNumber  要打印的数字
  33. 说明    :   
  34. ************************************/
  35. void DrawNumber(COORD pos,int nNumber)
  36. {
  37. switch (nNumber)
  38. {
  39. case 1:
  40. WriteWchar(pos.X,pos.Y,"①");
  41. break;
  42. case 2:
  43. WriteWchar(pos.X,pos.Y,"②");
  44. break;
  45. case 3:
  46. WriteWchar(pos.X,pos.Y,"③");
  47. break;
  48. case 4:
  49. WriteWchar(pos.X,pos.Y,"④");
  50. break;
  51. case 5:
  52. WriteWchar(pos.X,pos.Y,"⑤");
  53. break;
  54. case 6:
  55. WriteWchar(pos.X,pos.Y,"⑥");
  56. break;
  57. case 7:
  58. WriteWchar(pos.X,pos.Y,"⑦");
  59. break;
  60. case 8:
  61. WriteWchar(pos.X,pos.Y,"⑧");
  62. break;
  63. default:
  64. break;
  65. }
  66. }

  67. /************************************
  68. 函数名  :  GetNumber
  69. 函数作用:  获取一个点的四周有几颗雷
  70. 返回值  :  int     
  71. 参数    :  COORD pos   要探测的点的坐标
  72. 说明    :   
  73. ************************************/
  74. int  GetNumber(COORD pos)
  75. {
  76. int nCount = 0;
  77. for(int i = pos.X-1;i<=pos.X+1;i++)
  78. for (int j = pos.Y-1;j<=pos.Y+1;j++)
  79. {
  80. if (a[j][i] == Boom)
  81. {
  82. nCount++;
  83. }
  84. }
  85. return nCount;
  86. }

  87. /************************************
  88. 函数名  :  Drawmap
  89. 函数作用:  打印一下地图
  90. 返回值  :  void     
  91. 说明    :   
  92. ************************************/
  93. void Drawmap()
  94. {
  95. for (int i =0;i<10;i++)
  96. {
  97. for (int j =0;j<10;j++)
  98. {
  99. WriteWchar(j,i,"");
  100. }
  101. }
  102. }


  103. /************************************
  104. 函数名  :  Init
  105. 函数作用:  随机生成10个地雷,然后存到数组中
  106. 返回值  :  void     
  107. 说明    :   
  108. ************************************/
  109. void Init()
  110. {
  111. srand(time(NULL));
  112. for (int i =0;i<10;i++)
  113. {
  114. int Temp_x = rand()%10;
  115. int Temp_y = rand()%10;
  116. //判断这个地方是不是已经生成一个雷了,如果没有,赋值为雷
  117. if (a[Temp_x][Temp_y]!=Boom)
  118. {
  119. a[Temp_x][Temp_y] = Boom;
  120. }
  121. //如果是雷,就相当于本次生成没有发生过。。。。。
  122. else
  123. {
  124. i--;
  125. }
  126. }
  127. Drawmap();        
  128. }

  129. /************************************
  130. 函数名  :  IsClose
  131. 函数作用:  判断是不是已经探测过的点,由于使用的8方向递归的探测,这样避免重复
  132. 返回值  :  bool     
  133. 参数    :  COORD posTemp
  134. 说明    :   
  135. ************************************/
  136. bool IsClose(COORD posTemp)
  137. {
  138. for (int i =0;i<nSign;i++)
  139. {
  140. if(TempPos[i].X == posTemp.X&&TempPos[i].Y == posTemp.Y)
  141. return true;
  142. }
  143. return false;
  144. }

  145. /************************************
  146. 函数名  :  IsKongdi
  147. 函数作用:  判断一个点是空地,还是雷,如果是空地,需要做其他处理
  148. 返回值  :  void     
  149. 参数    :  COORD pos
  150. 说明    :   
  151. ************************************/
  152. bool IsKongdi(COORD pos)
  153. {
  154. int nNumber = 0;
  155. //1 如果是雷,就直接返回一个false说明要挂了
  156. if (a[pos.Y][pos.X] == Boom)
  157. {
  158. return false;
  159. }
  160. //2 如果不是雷,那么就做后续处理
  161. else
  162. {
  163. //2.1先判断一下周围有几颗雷
  164. nNumber = GetNumber(pos);
  165. if (nNumber!=0){
  166. //有几颗雷,就打印这个数字
  167. DrawNumber(pos,nNumber);
  168. return true;
  169. }
  170. else
  171. {
  172. //如果没有雷,那就先画空地出来,然后向周围扩散去探测其他点
  173. WriteWchar(pos.X,pos.Y," ");
  174. }
  175. }
  176. //2.2点到了空地,但是周围没有雷的情况的处理,继续去探测周围8个点
  177. for(int i = -1;i<=1;i++)
  178. for (int j = -1;j<=1;j++)
  179. {
  180. COORD posTemp = {pos.X+i,pos.Y+j};
  181. //是不是越界了
  182. if (i==0&&j==0||posTemp.X==-1||posTemp.Y==-1||posTemp.X==10||posTemp.Y==10)
  183. continue;
  184. //这个点是不是已经探测过了
  185. if (IsClose(posTemp))
  186. continue;
  187. //这个点没有探测过,就将其加入到数组中,然后使其在以后的探测中,存入
  188. TempPos[nSign++] =posTemp;
  189. IsKongdi(posTemp);
  190. }
  191. return true;
  192. }

  193. /************************************
  194. 函数名  :  GetMousePosition
  195. 函数作用:  获取鼠标点击的位置,假如没有获取到,就返回(-1,-1)
  196. 返回值  :  COORD    鼠标点击的坐标
  197. 说明    :   
  198. ************************************/
  199. COORD GetMousePosition()
  200. {
  201. HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
  202. INPUT_RECORD stcInput = {0};
  203. DWORD  buffer;
  204. COORD pos = {-1,-1};
  205. ReadConsoleInput(hIn,&stcInput,1,&buffer);
  206. if (stcInput.EventType == MOUSE_EVENT)
  207. {
  208. MOUSE_EVENT_RECORD stcMouseEnent = stcInput.Event.MouseEvent;

  209. if (stcMouseEnent.dwButtonState ==FROM_LEFT_1ST_BUTTON_PRESSED )
  210. {
  211. pos = stcMouseEnent.dwMousePosition;
  212. pos.X/=2;
  213. }
  214. }
  215. return pos;
  216. }
  217. int _tmain(int argc, _TCHAR* argv[])
  218. {
  219. Init();
  220. COORD pos;
  221. //开始游戏
  222. while(1)
  223. {
  224. //获取鼠标点击位置
  225. pos = GetMousePosition();
  226. if (pos.X!=-1)
  227. {
  228. //如果鼠标点击的位置被探测过了,就开始下一次循环
  229. if (IsClose(pos))
  230. {
  231. continue;
  232. }
  233. TempPos[nSign++] =pos;
  234. bool bIskongdi = IsKongdi(pos);
  235. //点到雷了,就直接退出游戏了。
  236. if (false ==bIskongdi)
  237. {

  238. system("cls");
  239. WriteWchar(20,10,"you lose");
  240. getchar();
  241. break;
  242. }
  243. //检测是不是赢了,赢的条件就是没有被探测的点的个数和雷的个数相等
  244. if (nSign ==90)
  245. {
  246. system("cls");
  247. WriteWchar(20,10,"you win");
  248. }
  249. }
  250. }
  251. return 0;
  252. }
复制代码

扫雷小项目到此结束,希望对于大家有所帮助。
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-6-21 19:53

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表