一、设计思路

蛇身本质上就是个结构数组,数组里存储了坐标x、y的值,再通过一个循环把它打印出来,蛇的移动则是不断地刷新重新打印。所以撞墙、咬到自己只是数组x、y值的简单比较。

二、用上的知识点

  1. 结构数组
  2. Windows API函数

三、具体实现

先来实现静态页面,把地图、初始蛇身、食物搞定。

这里需要用到Windows API的知识,也就是对控制台上坐标的修改

  1. //这段代码来自参考1
  2. void Pos(int x, int y)  
  3. {  
  4.     COORD pos;  
  5.     HANDLE hOutput;  
  6.     pos.X = x;  
  7.     pos.Y = y;  
  8.     hOutput = GetStdHandle(STD_OUTPUT_HANDLE);  
  9.     SetConsoleCursorPosition(hOutput, pos);  
  10. }  

    COORD是Windows API中定义的一种结构,表示在控制台上的坐标

  11. typedef struct _COORD {  
  12. SHORT X; // horizontal coordinate  
  13. SHORT Y; // vertical coordinate  
  14. } COORD;

    而代码中第七行则是获得屏幕缓冲区的句柄,第八行是直接修改光标位置的函数。

1.地图。

有了Pos()函数,打印一个框就不是问题了。假如我们用"-"作为上下边框,把"|"作为左右边框,这看起来没什么不妥,但其实我们已经掉进了坑里,直接上代码及实际效果图吧。

  1. //LONG==60
  2. //WIDTH==30
  3. void CreateMap()  
  4. {  
  5.     int i;  
  6.     for(i=0;i<LONG;i++)//上下两行   
  7.     {  
  8.         Pos(i,1);   
  9.         printf("-");  
  10.         Pos(i,WIDTH-1);  
  11.         printf("-");  
  12.     }  
  13.     for(i=2;i<WIDTH-1;i++)//左右两列   
  14.     {  
  15.         Pos(0,i);  
  16.         printf("|");   
  17.         Pos(LONG-1,i);  
  18.         printf("|");  
  19.     }  
  20. }  

    发现了问题吗?这是一条正常的蛇。。。那为什么看起来不正常呢?我们把边框都换成"#"来看看…

    这就清楚多了啊,要知道我们上下边框可是各有60个"#"的,长60宽30的长方形输出之后竟然成了个正方形。

    原因在这

    控制台上每个字符的长宽比例(像素点)是不同的,所以才会出现上图这种蛋疼的情况。

    解决方法其实也很简单,我们需要引入一些特殊符号,比如"●""■""⊙"等,这些字符的特点是它占据两个普通字符的位置

    所以上下边框就有60/2=30个符号,要让它仍然是个正方形的话,左右也可以设为30(28+2)个符号.

    代码及效果图如下

  21. void CreateMap()  
  22. {  
  23.     int i;  
  24.     for(i=0;i<LONG;i+=2)  
  25.     {  
  26.         Pos(i,0);  
  27.         printf("■");  
  28.         Pos(i,WIDTH-1);  
  29.         printf("■");  
  30.     }  
  31.     for(i=1;i<WIDTH-1;i++)  
  32.     {  
  33.         Pos(0,i);  
  34.         printf("■");  
  35.         Pos(LONG-2,i);  
  36.         printf("■");  
  37.     }  
  38. }  

    这样看就舒服多了,不过也让复杂度提升了一些,上边框每个符号的坐标分别是(0,0)(2,0)(4,0)…(2*n-2,0)这个在蛇的移动及食物的模块再提。

2.初始化一条蛇

因为蛇以及食物 本质上都是一个坐标,所以我们可以定义一个新的数据类型Node,每一个Node都是一个存储了两个变量(x、y)的结构体,再通过Node来定义蛇和食物。

  1. typedef struct node{  
  2.     int x;  
  3.     int y;  
  4. }Node;  
  5.     
  6.     
  7. Node snake[60];

    好了,我们现在定义了一条叫snake的蛇。为了这条蛇肥胖适中长宽比例一致,我们用"⊙"代表蛇的每一节。刚开始我们令蛇出现在地图中间位置,蛇头在右,共3个节点。所以我们需要求得每个节点的坐标。

  8.     void InitializeSnake()  
  9. {  
  10.     int i;  
  11.     for(i=0;i<3;i++)  
  12.     {  
  13.         snake[i].x = (LONG/2-i*2);//(30,15)(28,15)(26,15)  
  14.         snake[i].y = WIDTH/2;  
  15.         Pos(snake[i].x,snake[i].y);  
  16.         printf("⊙");  
  17.     }  
  18. }  

    这样我们就在(30,15)(28,15)(26,15)三个坐标处确定了一条蛇。X坐标之间减2是因为"⊙"在X轴占两个基本值。

y\x

26

27

28

29

30

31

15

3.随机出现食物

先创建一个变量来存储食物的坐标

Node food;

得到它的坐标其实就是用随机值对长、宽取余,使值在区间(地图)范围内。

  1. void CreateFood()  
  2. {  
  3.     int i;  
  4.     srand((unsigned int)time(0));  
  5.     while(1)  
  6.     {  
  7.         do{  
  8.             food.x = rand()%(LONG-6)+2;  
  9.         }while(food.x%2!=0);  
  10.         food.y = rand()%(WIDTH-2)+1;  
  11.         for(i=0;i<3+length;i++)  
  12.             if(food.x==snake[i].x && food.y==snake[i].y)  
  13.             {  
  14.                 i=-1;  
  15.                 break;  
  16.             }  
  17.         if(i>=0)  
  18.         {  
  19.             Pos(food.x,food.y);  
  20.             printf("●");  
  21.             break;   
  22.         }  
  23.     }  
  24.     //AfterEatFood();   
  25. }  

    X的坐标值求法为rand()%(LONG-6)+2,因为食物"●"也是两个字符的位置,所以它可能的取值为(2,y)(4,y)…(56,y)上下变宽共30个字符,从0开始,每个+2,所以最后一个为(58,y)

    Rand()%(LONG)的取值范围为0~59而x=1,x=2,x=58,x=59是地图范围,所以得对LONG-6(60-6=54)取余,这样取值范围就是0~54,再加2,就成了2~56.又因为蛇的各节坐标及移动x坐标都是+2,所以食物的x坐标必须是偶数,这可以用一个do(…)while()搞定,先取值,再判断,不行就再取值

    Y的坐标稍微简单些,只要保证坐标值在1~28就行。

    另外求出了坐标之后要判断食物是否与蛇身重合,重合的话重新赋值。

搞完上面的,我们就有了一个基本的(静态)效果了,现在我们要让它动起来

注:第86行是设置控制台窗口长、宽的系统函数。

4.让蛇动起来

蛇每次移动背后发生的事就是数组里的值改变,再在每个坐标位置打印蛇身。

为了让蛇一直动,我们就需要一个循环

  1. while(1)  
  2. {  
  3.     //获得输入,改变坐标  
  4.     //在每个坐标处输出   

首先,我们需要确定方向,而这需要两个变量,一个是输入值(可能是任意值),另一个则是确定方向的变量。

这里介绍一个函数

  1. int kbhit(void);  
  2. 值,否则返回0

这是一个非阻塞函数,有键按下时返回非0,但此时按键码仍然在键盘缓冲队列中。所以在确定键盘有响应之后,再用一个char变量将输入从缓冲区中调出来。

  1. if(kbhit())  
  2.     ch = getch();  

再对ch做判断,如果是符合情况(不能往后走等)的输入,则开始执行switch改变坐标

  1. if(ch=='w'&&direction!='s')  
  2.     direction = ch;  
  3. else if(ch=='s'&&direction!='w')  
  4.     direction = ch;  
  5. else if(ch=='a'&&direction!='d')  
  6.     direction = ch;  
  7. else if(ch=='d'&&direction!='a')  
  8.     direction = ch;  
  9. else if(ch==' ')  
  10.     continue

这里设置空格是暂停,而为了让蛇一开始就移动,我们把direction设置为d(往右)。

在方向确定了之后,再用一个switch语句进行坐标判断

  1. switch(direction)  
  2. {  
  3.     case 'w':  
  4.         if(snake[0].x==food.x && snake[0].y-1==food.y)  
  5.         {  
  6.             length++;  
  7.             score+=10;  
  8.             snake[2+length].x = snake[2+length-1].x;  
  9.             snake[2+length].y = snake[2+length-1].y;  
  10.             for(i=length+3-2;i>0;i--)  
  11.             {  
  12.                 snake[i].x = snake[i-1].x;  
  13.                 snake[i].y = snake[i-1].y;  
  14.             }  
  15.             CreateFood();  
  16.         }  
  17.         else  
  18.         {  
  19.             Pos(snake[2+length].x,snake[2+length].y);  
  20.             printf(" ");  
  21.             for(i=length+3-1;i>0;i--)  
  22.             {  
  23.                 snake[i].x = snake[i-1].x;  
  24.                 snake[i].y = snake[i-1].y;  
  25.             }  
  26.         }  
  27.         snake[0].y -=1;  
  28.         break;  
  29.     case 's':  
  30.         //。。。   
  31.     case 'a':  
  32.         //。。。   
  33.     case 'd':  
  34.         //。。。   
  35. }  

    对蛇头的下一步做判断,如果吃到了食物的话,则先对分数等全局变量进行处理,再把snake[2+length-1](吃到食物后的倒数第二个变量)的值赋值给snake[2+length](此时新加的尾节)。

再从倒数第二节开始,把前一节的坐标值赋给后一节,直到第二节得到了之前蛇头坐标。在食物被吃了之后,再调用随机出现食物函数。

如果没有吃到食物的话,先到之前最后一节的坐标处,输入空格,算是销毁它再对各节重新赋值。在蛇头后每节都赋值完成之后,根据输入值单独对蛇头赋值,如输入是'w',则往上,所以蛇头纵坐标减一。

对其余输入也是同样的道理,在snake数组各值都更新之后,再用一个函数把它打印出来。

这样移动部分就实现了,现在只需处理一些小模块就行。

5.移动后的处理。

这一部分相对简单,即对判断蛇是否撞墙、是否咬到自身,再对这种情况做处理,我们用两个函数搞定它

  1. int ThroughWall()  
  2. {  
  3.     if(snake[0].x==0 || snake[0].x==58 ||  
  4.         snake[0].y==0 || snake[0].y==29)  
  5.         {  
  6.             Pos(25,15);  
  7.             printf("撞墙 游戏结束");  
  8.             return 1;  
  9.         }  
  10.         Pos(0,WIDTH);  
  11.         printf(" ");  
    1. int BiteItself()  
    2. {  
    3.     int i;  
    4.     for(i=3;i<=2+length;i++)  
    5.         if((snake[0].x==snake[i].x) && (snake[0].y==snake[i].y))  
    6.         {  
    7.             Pos(25,15);  
    8.             printf("咬到自己 游戏结束");  
    9.             return 1;  
    10.         }  
    11. }  

当返回值为1时,游戏也就GG了。

  1. if(ThroughWall()==1)  
  2. {  
  3.     Pos(25,WIDTH);  
  4.     system("pause");   
  5.     exit(0);   
  6. }  
  7. if(BiteItself()==1)  
  8. {  
  9.     Pos(25,WIDTH);  
  10.     system("pause");  
  11.     exit(0);   
  12. }

最后再加一行Sleep()函数,对刷新时间(每次重新打印的时间间隔)做处理。speed是一个变量,在每次吃到食物后递减。

Sleep(speed);

源代码在这:结构数组实现_贪吃蛇源码

四、总结与反思。

首先从蛇的结构上来说,结构数组的实现直接无视了"效率"这个词,数组占用大量空间且有容量限制,并不是一种好办法。

其次是BUG的问题,在ThroughWall()函数中,在对蛇头坐标进行判断时在蛇头移动到(x,1)位置时,游戏直接结束,且没有任何提示。

但诡异的是,在判断后加入 Pos(0,WIDTH);printf(" "); 这两行不相干的语句后,这个问题解决了,而我对这两行语句的原有目的则只是想把闪烁不停光标放到地图外面去。

还有就是while()循环里代码行太多,特别是switch-case 里各项,蛇身的移动(结构数组个元素坐标值的变换)应该抽象成一个move()函数。

五、其他。

这是对我第一份代码(snakeV1.0)的重构,程序结构上有较大变化

重构期间研究了链表实现_贪吃蛇源码,在结构上采用了里面的部分思想。

个人空空如也的github:MagicXyxxx的github,今后会不定期更新一些乱七八糟的玩意儿,开心就好。

[C语言]贪吃蛇_结构数组实现的更多相关文章

  1. c语言贪吃蛇详解1.画出地图

    c语言贪吃蛇详解-1.画出地图 前几天的实验室培训课后作业我布置了贪吃蛇,今天有时间就来写一下题解.我将分几步来教大家写一个贪吃蛇小游戏.由于大家c语言未学完,这个教程只涉及数组和函数等知识点. 首先 ...

  2. c语言贪吃蛇详解3.让蛇动起来

    c语言贪吃蛇详解3.让蛇动起来 前几天的实验室培训课后作业我布置了贪吃蛇,今天有时间就来写一下题解.我将分几步来教大家写一个贪吃蛇小游戏.由于大家c语言未学完,这个教程只涉及数组和函数等知识点. 上次 ...

  3. c语言贪吃蛇详解-2.画出蛇

    c语言贪吃蛇详解-2.画出蛇 前几天的实验室培训课后作业我布置了贪吃蛇,今天有时间就来写一下题解.我将分几步来教大家写一个贪吃蛇小游戏.由于大家c语言未学完,这个教程只涉及数组和函数等知识点. 蛇的身 ...

  4. c语言贪吃蛇详解5.GameOver功能与显示成绩

    c语言贪吃蛇详解5.GameOver功能与显示成绩 以前我们已经做出来了一个能吃东西变长的蛇.不过它好像不会死... 现在就来实现一下game over的功能吧. 写个函数判断蛇是否撞到自己或者撞到墙 ...

  5. c语言贪吃蛇详解4.食物的投放与蛇的变长

    c语言贪吃蛇详解4.食物的投放与蛇的变长 前几天的实验室培训课后作业我布置了贪吃蛇,今天有时间就来写一下题解.我将分几步来教大家写一个贪吃蛇小游戏.由于大家c语言未学完,这个教程只涉及数组和函数等知识 ...

  6. 程序游戏推荐(C语言贪吃蛇,python天天酷跑(需要安装pygame),js是狠人就坚持30s)

    下面是下载位置,我把他们上传到我的文件下了. C语言贪吃蛇:https://files.cnblogs.com/files/ITXiaoAng/%E8%B4%AA%E5%90%83%E8%9B%87. ...

  7. C/C++编程笔记:C语言贪吃蛇源代码控制台(二),分数和食物!

    接上文<C/C++编程笔记:C语言贪吃蛇源代码控制台(一),会动的那种哦!>如果你在学习C语言开发贪吃蛇的话,零基础建议从上一篇开始哦!接下来正式开始吧! 三.蛇的运动 上次我已经教大家画 ...

  8. C/C++编程笔记:C语言贪吃蛇源代码控制台(一),会动的那种哦!

    前几天有个同学加我QQ私聊我说他们老师布置了一个贪吃蛇,他不知道怎么写所以来找我求解,我给他简单讲解了思路和一些难点之后他也能够自己独立将项目完成了!考虑到更多同学可能有贪吃蛇上的问题,今天有时间就来 ...

  9. C语言 贪吃蛇

    贪吃蛇(单人版): 本人先来介绍一个函数 -- bioskey函数: int bioskey (int cmd) 参数 (cmd) 基本功能 0 返回下一个从键盘键入的值(若不键入任何值,则将等下一个 ...

随机推荐

  1. 最近完成的AndroidStudio项目实现思路及应用技术

    主要内容: Android Studio的介绍 AS中个Gradle及Groovy介绍 AS中的依赖管理 Maven以及Nexus私库管理依赖 Gradle对变种代码的管理以及多渠道打包 eclips ...

  2. Keil报错failed to execute 'd:\Keil\C51\BIN\C51.EXE'

    关于老师发的keil软件报错如下: --- Error: failed to execute 'd:\Keil\C51\BIN\C51.EXE' 错误是因为老师直接拷贝的安装目录,里面的文件路径设置仍 ...

  3. Centos 6 PXE安装

    author:JevonWei 版权声明:原创作品 192.168.198.134作为安装服务器,由httpd服务共享安装程序 192.168.198.134作为dhcp服务器,客户机获取IP 一.安 ...

  4. 打开safari开发者选项

    1.点击Safari启动浏览器 2.点击左上Safari标志,选择偏好设置 3.选择高级,勾选下方的在菜单栏显示开发菜单. 如此,Safari就出现了开发菜单,右键网页元素也会出现查看元素功能了.

  5. 201521123108 《Java程序设计》第7周学习总结

    1. 本周学习总结 2. 书面作业 Q1.ArrayList代码分析 Q1.1 解释ArrayList的contains源代码 答:源代码如下: public boolean contains(Obj ...

  6. 201521123109《java程序设计》第四周学习总结

    1. 本周学习总结 #1.1 尝试使用思维导图总结有关继承的知识点. #1.2 使用常规方法总结其他上课内容. - 了解了有关类的继承的知识 - 了解继承和多态的关系以及一些关键字内容 -  学习了O ...

  7. 201521123015《Java程序设计》第1周学习总结

    1.本周学习总结 知道了JAVA语言的发展历史和目前使用的版本,还有什么是JDK(Java Development Kit).JRE (Java Runtime Environment).JVM(Ja ...

  8. 201521123034《Java程序设计》第十三周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...

  9. JAVA课程设计个人博客 学生成绩管理 201521145048 林健

    1. 团队课程设计博客链接 http://www.cnblogs.com/kawajiang/p/7062407.html 2.个人负责模块或任务说明 本人主要负责支持用户登录.验证操作,显示设计界面 ...

  10. 201521123030 《Java程序设计》 第9周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 2. 书面作业 本次PTA作业题集异常 常用异常 1.题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自己 ...