当前位置:   article > 正文

C语言 贪吃蛇_c语言贪吃蛇控制蛇移动用了啥函数

c语言贪吃蛇控制蛇移动用了啥函数

C语言 贪吃蛇

一、简述

       记--用C语言简单实现贪吃蛇小游戏。

二、效果

      使用上下左右方向键控制方向, 按ESC退出游戏。

       

       

三、代码结构

       

四、源代码

  1. #include <stdio.h> //标准输入输出函数库
  2. #include <stdlib.h> //包含system函数
  3. #include <windows.h>//包含Sleep函数,来控制速度
  4. #include <time.h> //设置食物时随机生成坐标用到time做种子
  5. #define DOWN_WALL 20 //下边框 (下面的墙) //使用宏定义 ,是方便以后调整边框大小
  6. #define RIGHT_WALL 58 //右边框 (右面的墙)
  7. //加上typedef 以后声明此类型的结构体不需要struct关键字,可以用这样声明 Snake s1;(原来:struct s_snake s1)
  8. typedef struct s_snake //用来存储每一段蛇身的坐标位置
  9. {
  10. int x; //x坐标
  11. int y; //y坐标
  12. struct s_snake *next; //下一段蛇身
  13. }Snake;
  14. /*函数声明*/
  15. void SetPos(int x,int y);//移动光标函数
  16. int IsHitWall();//判断撞墙函数
  17. int IsBiteYouself();//判断咬到自己的函数
  18. void DrawFrame();//画边框函数
  19. void InitSnake();//初始化蛇函数
  20. void CreateFood();//创建食物函数
  21. void PlayGame();//游戏移动循环函数
  22. int Move();//移动函数,方向控制
  23. void Welcome();//欢迎界面
  24. void free_snake(Snake *head);//释放资源
  25. Snake *head,*end;//蛇头、蛇尾
  26. Snake *p;//辅助指针
  27. int speed=310;//休眠时间,用来控制移动速度
  28. int level = 0,score = 0;//分数
  29. int foodx,foody;//食物的(x,y)坐标
  30. char key;//方向,暂停/继续 控制状态
  31. int main()
  32. {
  33. Welcome(); //欢迎界面
  34. DrawFrame();//画边框
  35. InitSnake();//初始化蛇身
  36. CreateFood();//创建食物
  37. //右侧提示信息
  38. SetPos(60,7);
  39. printf("得分:%d",score);
  40. SetPos(60,8);
  41. printf("当前速度:%d毫秒",speed);
  42. SetPos(60,9);
  43. printf("每+30分速度变快");
  44. SetPos(60,10);
  45. printf("不能撞墙/咬到自己");
  46. SetPos(60,11);
  47. printf("按空格暂停/继续");
  48. PlayGame();//按方向键控制蛇身进行游戏
  49. free_snake(head);
  50. return 0;
  51. }
  52. void SetPos(int x,int y)//设置光标位置(就是输出显示的开始位置)
  53. {
  54. /* COORD是Windows API中定义的一种结构体
  55. * typedef struct _COORD
  56. * {
  57. * SHORT X;
  58. * SHORT Y;
  59. * } COORD;
  60. *
  61. */
  62. COORD pos = {x,y};
  63. HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);//获得 标准输出的句柄
  64. SetConsoleCursorPosition(output,pos); //设置控制台光标位置
  65. }
  66. int IsHitWall()//判断是否撞墙
  67. {
  68. if (head->x==0||head->x==RIGHT_WALL||head->y==0||head->y==DOWN_WALL)
  69. { //因为蛇头最先动,并且蛇身后一段下一步会在前一段,所以只要蛇头不撞墙,那么整个蛇身就不会 撞墙
  70. SetPos(DOWN_WALL,14);
  71. printf("游戏结束!撞到墙了\n");
  72. SetPos(DOWN_WALL,15);//令 ‘按任意键继续...’居中显示
  73. //system("pause");//暂停
  74. return 1;
  75. }
  76. return 0;
  77. }
  78. int IsBiteYouself()//判断是否咬到自己
  79. {
  80. while(p->next!=NULL)
  81. {
  82. p=p->next;
  83. if (head->x==p->x&&head->y==p->y)//判断蛇头是否与其他蛇身重合
  84. {
  85. SetPos(DOWN_WALL,14);
  86. printf("游戏结束!你咬到自己了\n");
  87. SetPos(DOWN_WALL,15);//令 “请按任意键继续”居中显示
  88. //system("pause");//暂停
  89. return 1;
  90. }
  91. }
  92. return 0;
  93. }
  94. void DrawFrame()//画边框
  95. {
  96. int i = 0;
  97. for(i=0;i<60;i+=2)//打印上下边框 注意i 一段蛇身占用 x 2个单位, y 1个单位
  98. {
  99. SetPos(i,0); //上边框
  100. printf("■");
  101. SetPos(i,DOWN_WALL);//下边框
  102. printf("■");
  103. }
  104. for(i=1;i<DOWN_WALL;i++)//打印左右边框
  105. {
  106. SetPos(0,i); //左边框
  107. printf("■");
  108. SetPos(RIGHT_WALL,i); //右边框
  109. printf("■");
  110. }
  111. }
  112. void InitSnake()//初始化蛇身 头插法 初始化从(20,15)开始的四段蛇身 (横向排列)
  113. {
  114. int i = 0;
  115. //创建一个蛇身位置 蛇尾
  116. end=(Snake*)malloc(sizeof(Snake));
  117. end->x=20;
  118. end->y=15;
  119. end->next=NULL;
  120. //创建三个蛇身位置
  121. for (i=1;i<=3;i++)
  122. {
  123. head = (Snake*)malloc(sizeof(Snake));
  124. head->x = 20+2*i; //每个蛇身 x相差 2个单位
  125. head->y = 15;
  126. head->next=end; //头插法
  127. end = head;
  128. }
  129. //从蛇头开始画贪吃蛇
  130. while (end->next!=NULL)
  131. {
  132. SetPos(end->x,end->y);
  133. printf("■");
  134. end = end->next;
  135. }
  136. }
  137. void CreateFood()//设置食物
  138. {
  139. srand(time(0));//设置随机数种子
  140. flag:
  141. while(1)//由于food的x坐标必须为偶数,所以设置循环判断是否为偶数
  142. {
  143. //rand()%num产生 0~num-1
  144. //rand产生范围数公式rand()%(m+1-n)+n;有效范围在 [n,m]
  145. foody=rand()%(DOWN_WALL-1+1-1)+1;//foody的有效范围在 [1,DOWN_WALL-1 ]
  146. foodx=rand()%(RIGHT_WALL-2+1-3)+3;//foodx的有效范围在 [3,RIGHT_WALL-2] 注意x是以2为单位的
  147. if (foodx%2==0)
  148. {
  149. break;
  150. }
  151. }
  152. p=head;
  153. while(1)
  154. {
  155. if(p->x==foodx&&p->y==foody)//若生成坐标和蛇重叠了,回到生成坐标循环
  156. {
  157. goto flag;
  158. }
  159. if(p->next==NULL) //与每一段蛇身比较完毕,跳出循环
  160. {
  161. break;
  162. }
  163. p=p->next;
  164. }
  165. SetPos(foodx,foody);
  166. printf("■"); //显示食物
  167. }
  168. void PlayGame()//贪吃蛇移动,暂停
  169. {
  170. int mv_ret = 0;//移动后的返回值,如果撞墙、或就咬到自己设置为1
  171. key = 'd';//刚开始,贪吃蛇默认向右移动
  172. while (1)
  173. {
  174. //GetAsyncKeyState(VK_UP) 判断VK_UP按键的状态,若是被按下,则位15设为1;如抬起,则为0
  175. //所以要 与上0x8000 取出第15位 进行判断
  176. if ((GetAsyncKeyState(VK_UP)&& 0x8000)&& key != 's')//与key!='s',因为不能后退
  177. {
  178. key='w';//如果本来不是向下的,按下向上键,将key设置为w
  179. }
  180. else if ((GetAsyncKeyState(VK_DOWN)&& 0x8000)&& key != 'w')
  181. {
  182. key='s';
  183. }
  184. else if ((GetAsyncKeyState(VK_RIGHT) && 0x8000)&& key != 'a')
  185. {
  186. key='d';
  187. }
  188. else if ((GetAsyncKeyState(VK_LEFT)&& 0x8000) && key != 'd')
  189. {
  190. key='a';
  191. }
  192. else if (GetAsyncKeyState(VK_SPACE)&& 0x8000) //暂停/继续函数
  193. {
  194. //补上消隐的蛇尾(蛇尾还在) 原因未知
  195. while(p->next!=NULL) p=p->next;
  196. SetPos(p->x,p->y);
  197. printf("■");
  198. while(1)//暂停语句
  199. {
  200. Sleep(100); //必要延时(消抖) Sleep(毫秒)
  201. if (GetAsyncKeyState(VK_SPACE)&& 0x8000)
  202. {
  203. break;
  204. }
  205. }
  206. //擦掉补上的 蛇尾
  207. SetPos(p->x,p->y);
  208. printf(" ");
  209. }
  210. else if(GetAsyncKeyState(VK_ESCAPE)&& 0x8000)//按下ESC退出游戏,VK_ESCAPE == 27
  211. {
  212. return ;
  213. }
  214. //实时刷新速度 得分每+30分 移动速度变快
  215. if (score==level && speed > 10)//判断得分
  216. {
  217. speed -=10; //睡眠时间,改变移动速度,越少越快
  218. level +=30; //速度变快条件 变化
  219. SetPos(60,8);
  220. printf("当前速度:%d毫秒",speed);
  221. }
  222. mv_ret = Move();//移动蛇身
  223. if( mv_ret == 1)
  224. {
  225. break;
  226. }
  227. }
  228. }
  229. int Move()//移动函数,w前 s后 a左 d右,实现移动:头部增加一个,尾部减掉一个
  230. {
  231. int ret;
  232. //不是按下控制方向的 a,s,d,w 就
  233. if( (key != 'a') && (key != 's') && (key != 'd') && (key != 'w') )
  234. {
  235. return 0;
  236. }
  237. ret = IsHitWall(); //是否撞墙
  238. ret += IsBiteYouself(); //是否咬到自己
  239. if(ret == 1)
  240. {
  241. return 1;
  242. }
  243. p = (Snake*)malloc(sizeof(Snake));//头部增加的那个
  244. p->next=head;//添加到头部
  245. switch(key)
  246. {
  247. case 'd'://向右
  248. p->x = head->x+2;//右边
  249. p->y = head->y;
  250. break;
  251. case 'w'://向上
  252. p->x=head->x;
  253. p->y=head->y-1;//向上
  254. break;
  255. case 's'://向下
  256. p->x=head->x;
  257. p->y=head->y+1;//向下
  258. break;
  259. case 'a'://向左
  260. p->x=head->x-2;//向左
  261. p->y=head->y;
  262. break;
  263. }
  264. //画出新的头部
  265. SetPos(p->x,p->y);
  266. printf("■");
  267. head = p;//在贪吃蛇的头部添加一个称为新的头 ,相当于是贪吃蛇移动一格
  268. //如果 移动的一格刚好是食物的位置,新增的称为蛇头,不用去掉蛇尾
  269. //如果 移动的一格不是食物的位置,新增的称为蛇头,去掉蛇尾,就是贪吃蛇移动一格的效果
  270. Sleep(speed);//移动速度的控制
  271. if (p->x==foodx && p->y==foody)//移动的一格刚好是食物的位置
  272. {
  273. CreateFood();
  274. score +=10;
  275. SetPos(60,7);
  276. printf("得分:%d",score);
  277. }
  278. else //吃不到食物,头部添加一个,尾部去掉一个
  279. {
  280. //移动的一格刚好是食物的位置,新增的称为蛇头,不用去掉蛇尾
  281. while (p->next->next != NULL) p = p->next;//指向蛇尾前一格,然后释放蛇尾,并将原来的尾二置空。
  282. SetPos(p->x, p->y);//为什么不是setPos(p->next->x,p->next->y)?
  283. printf(" ");//擦掉蛇尾(蛇头加一,蛇尾减一,实现移动效果)
  284. free(p->next);//释放蛇尾
  285. p->next = NULL;//必须置空,不然原来的尾二还是指向原来的尾巴(但是原来的为尾巴已经释放了)
  286. p = head;//将p指向head
  287. }
  288. return 0;
  289. }
  290. void Welcome()//欢迎界面
  291. {
  292. SetPos(25,8);
  293. printf("【贪吃蛇】 作者:Genven_Liang");
  294. SetPos(25,11);
  295. printf("【游戏规则】");
  296. SetPos(25,12);
  297. printf("1、不能撞墙、咬到自己");
  298. SetPos(25,13);
  299. printf("2、按空格暂停/继续游戏");
  300. printf("\n");
  301. SetPos(30,15);
  302. system("pause");//暂停
  303. system("cls");//清屏
  304. }
  305. void free_snake(Snake *head)//释放资源,释放链表空间
  306. {
  307. if(head == NULL || head->next == NULL)
  308. {
  309. return ;
  310. }
  311. //从头部开始逐个节点释放
  312. while( (p=head) != NULL)
  313. {
  314. head = head->next;
  315. free(p);
  316. }
  317. }

五、关于看不见“游戏结束”

           例子使用的是C-Free,这个IDE在程序结束后会暂停的;如果使用的是Visual Studio,程序结束后不会暂停;需要在IsHitWall()函数和IsBiteYouself()函数里面加上一句system(pause);暂停一下(或者是加上一句getchar()),这样就能看见"游戏结束"了。

六、问题记录

   6.1 残留问题

         目前在Win10-64bit家庭中文版 发现残留问题,现象如下:       

         ps:在Win7-32bit 系统,Win7-64bit系统,Win10专业版未发现该问题。

         产生原因:空格的宽度与■的宽度不一致,导致用空格不能完全擦除一段完整的蛇身--■

         解决办法:使用两个空格来擦除一段蛇身         

 

===========================以下回复 qq_45936030 这位老铁===================

使用Dev-C++ IDE 

使用VC6.0 IDE     (需要暂停一下才能看到最后的"游戏结束")

 

====================================以下回复  钢钢钢不爽 这位老铁 ======================

在字符映射表charmap中可以搜索 实心 就可以找到实心方形--就是黑色的那个正方形

可以使用快捷键Win键+R唤出运行窗口  (Win键就是这个按键

ps:还可以使用系统自带的造字程序进行自定义,搜索:专用字符编辑程序 或在运行窗口直接输入 eudcedit.exe 然后回车。

 

======================以下回复  xmx666 这位老铁 ===============

1 p->next->next是对的,p->next是不对的。以下是p->next->next和p->next的对比:

原因:在没有吃到食物情况,我们需要在前面增加一个方块,并将最后的方块A去掉,以实现移动效果。

         去掉最后一个方块有2个操作:1)将最后的方块A释放内存,擦除方块A;2)断开联系,方块A的前面的方块B不要再指向方块A。

         (如果不断开的话,B还是指向A,但是A已经被释放了,程序已经没有合法权限去的操作A的内存空间了。)

         其中while (p->next->next != NULL) p = p->next;是找到倒数第二个方块,

         释放方块A的空间不管是找到A还是A的前一个都是可以的,但是单项链表不能操作A的前一个。

         将A的前面一个的next置空,如果是使用p->next找到A并释放A,那么是不能直接操作A的前面一个的(但是可以增加一个变量用来指向A的前面一个)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/626115
推荐阅读
相关标签
  

闽ICP备14008679号