C程序设计 贪吃蛇分析

这是第一次做小游戏,

下面对别人写的一个非常简单的基本功能的贪吃蛇进行分析

 

VC下可以运行。。先看下源代码,

基本上注释我都加在里面了,同时保留了作者的版权。

  1 //*******************************************************
  2 //**************版权所有***2011.9.20***咸鱼**************
  3 //*******************************************************
  4 //*友情提示:如想速度快点,请改小_sleep(500)函数中参数*****
  5 //*******************************************************
  6 //*****************如写的不好,请见谅*********************
  7 //*******************************************************
  8 
  9 #include <stdio.h>
 10 #include <stdlib.h>
 11 #include <conio.h>
 12 #include <string.h>
 13 #include <time.h>
 14 /*
 15 con就是console,控制台
 16 io就是输入输出
 17 连起来就是用来声明控制台输入输出所需函数的头文件
 18 如果你要用到像
 19 getch()
 20 cprintf()
 21 cputs()
 22 kbhit()
 23 之类的函数,那就需要这个头文件了
 24 */
 25 
 26 
 27 const int H = 8;   //地图的高
 28 const int L = 16;  //地图的长
 29 
 30 char GameMap[H][L];   //游戏地图
 31 int  key;  //用来保存按键
 32 int  sum = 1, over = 0;  //蛇的长度, 游戏结束(自吃或碰墙)
 33 int  dx[4] = {0, 0, -1, 1};  //左、右、上、下的方向
 34 int  dy[4] = {-1, 1, 0, 0};  //左、右、上、下的方向
 35 
 36 struct Snake   //蛇的每个节点的数据类型
 37 {
 38     int x, y;  //左边位置
 39     int now;   //保存当前节点的方向, 0,1,2,3分别为左右上下
 40 }Snake[H*L];        //理论上蛇的长度,因为地图的高和长已确定,所以直接用H*L
 41 
 42 char Shead = 'Y';  //蛇头
 43 char Sbody = '#';  //蛇身
 44 char Sfood = '*';  //食物
 45 char Snode = '.';  //'.'在地图上标示为空
 46 
 47 void Initial();  //地图的初始化
 48 void Create_Food(); //在地图上随机产生食物
 49 void Show();   //刷新显示地图
 50 void Button();  //取出按键,并判断方向
 51 void Move();   //蛇的移动
 52 void Check_Border();  //检查蛇头是否越界
 53 void Check_Head(int x, int y);   //检查蛇头移动后的位置情况
 54 
 55 //主函数
 56 int main() 
 57 {
 58     Initial();
 59     Show();
 60     
 61     return 0;
 62 }
 63 
 64 
 65 
 66 void Initial()  //地图的初始化
 67 {
 68     int i, j;
 69     int hx, hy;
 70     
 71     system("title 贪吃蛇");  //控制台的标题
 72     memset(GameMap, '.', sizeof(GameMap));  //初始化地图全部为空'.'(即将GameMap数组中所有值改为".")
 73 
 74     system("cls"); //清屏
 75     
 76     srand(time(0));   //随机种子,需要头文件time.h
 77     hx = rand()%H;    //产生蛇头
 78     hy = rand()%L;
 79     GameMap[hx][hy] = Shead;  //将蛇头的值(也就是用来表示蛇头的字符,赋给GameMap,以便后面打印)
 80     Snake[0].x = hx;  Snake[0].y = hy; //蛇头的坐标确定
 81     Snake[0].now = -1;
 82     
 83     Create_Food();   //随机产生食物
 84     
 85     for(i = 0; i < H; i++)   //地图显示
 86     { 
 87         for(j = 0; j < L; j++)
 88             printf("%c", GameMap[i][j]);    //将蛇头 和 其他 字符 打印出来
 89         printf("\n");
 90     }
 91     
 92     printf("\n小小C语言贪吃蛇\n");
 93     printf("按任意方向键开始游戏\n");
 94     
 95     getch();   //先接受一个按键,使蛇开始往该方向走
 96     Button();  //取出按键,并判断方向
 97 }
 98 
 99 void Create_Food()  //在地图上随机产生食物
100 {
101     int fx, fy;
102     
103     while(1)     
104     {
105         fx = rand()%H;
106         fy = rand()%L;
107         
108         if(GameMap[fx][fy] == '.')  //食物不能出现在蛇所占有的位置
109         { 
110             GameMap[fx][fy] = Sfood;
111             break;                  //如果成功产生了食物,就while跳出循环,否则继续执行,直到成功.
112         }
113     }
114 }
115 
116 void Show()  //刷新显示地图
117 {
118     int i, j;
119     
120     while(1)
121     {  
122         _sleep(200); //延迟半秒(1000为1s),即每半秒刷新一次地图
123         
124         Button();   //先判断按键在移动
125         Move();
126         
127         if(over)  //自吃或碰墙即游戏结束
128         { 
129             printf("\n**游戏结束**\n");
130             printf("     >_<\n");
131             getchar();
132             break;
133         }
134         
135         system("cls");   //清空地图再显示刷新后的地图
136         for(i = 0; i < H; i++) 
137         { 
138             for(j = 0; j < L; j++)
139                 printf("%c", GameMap[i][j]);
140             printf("\n");
141         }
142         
143         printf("\n小小C语言贪吃蛇\n");
144         printf("按任意方向键开始游戏\n");
145     }
146     
147 }
148 
149 void Button()  //取出按键,并判断方向
150 {
151     if(kbhit() != 0) //需要头文件,检查当前是否有键盘输入,若有则返回一个非0值,否则返回0
152     { 
153         while(kbhit() != 0)  //可能存在多个按键,要全部取完,以最后一个为主
154             key = getch(); //将按键从控制台中取出并保存到key中
155         
156         switch(key)
157         {   //
158         case 75:  Snake[0].now = 0;
159             break;
160             //
161         case 77:  Snake[0].now = 1;     
162             break;
163             //
164         case 72:  Snake[0].now = 2;
165             break;
166             //
167         case 80:  Snake[0].now = 3;
168             break;
169         }
170     }
171 }
172 
173 void Move()   //蛇的移动
174 {
175     int i, x, y;
176     int t = sum;  //保存当前蛇的长度
177     
178     //记录当前蛇头的位置,并设置为空,蛇头先移动
179     x = Snake[0].x;  y = Snake[0].y;  GameMap[x][y] = '.';
180     Snake[0].x = Snake[0].x + dx[ Snake[0].now ];
181     Snake[0].y = Snake[0].y + dy[ Snake[0].now ];
182     
183     Check_Border();   //蛇头是否越界
184     Check_Head(x, y);  //蛇头移动后的位置情况,参数为: 蛇头的开始位置
185 
186     if(sum == t)  //未吃到食物即蛇身移动哦
187         for(i = 1; i < sum; i++)  //要从蛇尾节点向前移动哦,前一个节点作为参照
188         {
189             if(i == 1)   //尾节点设置为空再移动
190                 GameMap[ Snake[i].x ][ Snake[i].y ] = '.';
191             
192             if(i == sum-1)  //为蛇头后面的蛇身节点,特殊处理
193             {
194                 Snake[i].x = x;
195                 Snake[i].y = y;
196                 Snake[i].now = Snake[0].now;
197             }
198             else   //其他蛇身即走到前一个蛇身位置
199             {
200                 Snake[i].x = Snake[i+1].x;
201                 Snake[i].y = Snake[i+1].y;
202                 Snake[i].now = Snake[i+1].now;
203             }
204             
205             GameMap[ Snake[i].x ][ Snake[i].y ] = '#'; //移动后要置为'#'蛇身 
206         }
207         
208 }
209 
210 void Check_Border()  //检查蛇头是否越界
211 {
212     if(Snake[0].x < 0 || Snake[0].x >= H
213         || Snake[0].y < 0 || Snake[0].y >= L)
214         over = 1;
215 }
216 
217 void Check_Head(int x, int y)  //检查蛇头移动后的位置情况
218 {
219     
220     if(GameMap[ Snake[0].x ][ Snake[0].y ] == '.')  //为空
221         GameMap[ Snake[0].x ][ Snake[0].y ] = 'Y';
222     else
223         if(GameMap[ Snake[0].x ][ Snake[0].y ] == '*')  //为食物
224         {
225             GameMap[ Snake[0].x ][ Snake[0].y ] = 'Y';  
226             
227             Snake[sum].x = x;   //新增加的蛇身为蛇头后面的那个
228             Snake[sum].y = y;
229             Snake[sum].now = Snake[0].now;
230             
231             GameMap[ Snake[sum].x ][ Snake[sum].y ] = '#'; 
232             sum++;
233             
234             Create_Food();  //食物吃完了马上再产生一个食物
235         }
236         else
237             over = 1;
238 }

接下来主要分析下 贪吃蛇的算法部分,

首先,主函数

//主函数
int main()
{
  Initial();
  Show();

  return 0;
}

仅仅只有两个函数,其中Initial()用来初始化界面,并在函数最后读入一个键盘操作,就结束了;

完全靠Show函数来实现贪吃蛇的功能,

 

show函数中,用一个while(1)来实现无限循环,

其中,最核心的就是Move();

而函数Move()中有一个关键函数

Check_Head(x, y); //蛇头移动后的位置情况,参数为: 蛇头的开始位置

基本上搞清楚这两个函数,就能够掌握这个简单的贪吃蛇了。

 

(必须将两个函数一起看,才能看懂)

 

下面先分析void Check_Head(int x, int y)

 2 {
 3     
 4     if(GameMap[ Snake[0].x ][ Snake[0].y ] == '.')  //           这里要注意的是 第4行和第6行的一个大的if else.如果if语句为ture,含义为没有吃到食物。那么执行的只有第5行,
                                        下面的else将直接跳过了。

//第四,五行表示如果蛇头将要移动到的下一个坐标处是空的(没有食物的),就将地图的该坐标处变为蛇头。后面的后续步骤,就要结合move()函数来看了。
5 GameMap[ Snake[0].x ][ Snake[0].y ] = 'Y';               6 else 7 if(GameMap[ Snake[0].x ][ Snake[0].y ] == '*') //为食物            //这个大的else中,要注意的是sum,也就是蛇头后面那个部分,
                                                     这里先记住一定是紧跟着蛇头的那个部分才是sum,而1表示的是蛇尾。
                                                         也就是实际上Snake[sum-1].x 和Snake[sum-1].y 表示的为蛇头后面第一个蛇节的坐标
                                                         而Snake[1].x 和 Snake[1].y 表示的是蛇尾的坐标。
                                             

 8         {
 9             GameMap[ Snake[0].x ][ Snake[0].y ] = 'Y';  
10             
11             Snake[sum].x = x;   //新增加的蛇身为蛇头后面的那个,其实也就是原来蛇头所在的坐标,记住,是原来蛇头所在的坐标。
12             Snake[sum].y = y;
13             Snake[sum].now = Snake[0].now;
14             
15             GameMap[ Snake[sum].x ][ Snake[sum].y ] = '#'; 
16             sum++;
17             
18             Create_Food();  //食物吃完了马上再产生一个食物
19         }
20         else
21             over = 1;
22 }



接下来就开始结合最重要的函数Move()函数来看。

 

 

 1 void Move()   //蛇的移动
 2 {
 3     int i, x, y;      
 4     int t = sum;  //保存当前蛇的长度   后面需要用到
 5     
 6     //记录当前蛇头的位置,并设置为空,蛇头先移动
 7     x = Snake[0].x;  y = Snake[0].y;  GameMap[x][y] = '.';    //x,y用来存放原来的蛇头的位置,也就是即将成为蛇身第一个节点的位置。
                                      //看这个程序的时候,请务必要搞清楚x,y 和 Snake[i].x,Snake[i].y 是两码事。不能搞混
                                      //
8 Snake[0].x = Snake[0].x + dx[ Snake[0].now ];        //这里就和上面那个函数呼应了,因为这里把蛇头所用的结构体Snake[0]中的x,y,now,都进行了修改,
                                      //所以在调用Check_head函数的时候,蛇头的位置。或者是坐标,已经改变了,所以在上一个函数的分析中才会有
                                第四,五行表示如果蛇头将要移动到的下一个坐标处是空的(没有食物的),就将地图的该坐标处变为蛇头。就要结合move()函数来看了
 9     Snake[0].y = Snake[0].y + dy[ Snake[0].now ];
10     
11     Check_Border();   //蛇头是否越界                //这个函数比较简单,可以跳过不看,知道是用来判断是否越界的就可以了。
12     Check_Head(x, y);  //蛇头移动后的位置情况,参数为: 蛇头的开始位置
13                                           //到这里的话,其实上面的函数都已经执行完毕。
                                              执行完毕后,会发生一下几个结果,
                                                        1.Snake[0].x 和 Snake[0].y ,也就是蛇头表示的位置,实际上已经发生变化,
                                                              变为蛇头下一步要去的那个位置。
                                                        2.x,y,保存的是原来蛇头的位置。
                                                        3.如果吃到了食物,就执行了Check_Head()函数中的 大if语句,前面已经提到,就是第四行那个
                                                         如果没吃到食物, 就执行了  大esle中的语句,那么,sum的值就会+1;
                                                        至此,再往下,就是程序的关键了。
                                                        4. 如果吃到了食物,执行力Check_Head()函数中的 大if语句的话,sum的值会等于t,
                                                          那么14行到31行的语句将不会被执行。(即吃到食物不执行14到31行)
                                                          如果没吃到食物,则会执行14到31行。
                                                        5.前面已经讲过,了,Snake[sum-1]代表的是蛇头后面紧跟着的那个蛇节,
                                                                  Snake[1] 代表的是蛇尾的位置,下面的第28到31行,
                                                                  我们就可以清楚的知道为什么是i和i+1的关系了。
14 if(sum == t) //未吃到食物即蛇身移动哦 15 for(i = 1; i < sum; i++) //要从蛇尾节点向前移动哦,前一个节点作为参照 16 { 17 if(i == 1) //蛇神最后一个节点的情况单独考虑    //将蛇尾变为空,因为蛇头多一节,所以蛇尾必定少一节。 18 GameMap[ Snake[i].x ][ Snake[i].y ] = '.'; 19 20 if(i == sum-1) //为蛇头后面的蛇身节点,特殊处理(蛇身第一个节点的情况也单独考虑) 21 { 22 Snake[i].x = x; 23 Snake[i].y = y; 24 Snake[i].now = Snake[0].now; 25 } 26 else //其他蛇身即走到前一个蛇身位置 (中间部分往前移动的考虑) 27 { 28 Snake[i].x = Snake[i+1].x; 29 Snake[i].y = Snake[i+1].y; 30 Snake[i].now = Snake[i+1].now; 31 } 32 33 GameMap[ Snake[i].x ][ Snake[i].y ] = '#'; //移动后要置为'#'蛇身 , 34 } 35 36 }

 

posted @ 2013-06-17 14:10  Geekers  阅读(878)  评论(0编辑  收藏  举报