这是第一次做小游戏,
下面对别人写的一个非常简单的基本功能的贪吃蛇进行分析
VC下可以运行。。先看下源代码,
基本上注释我都加在里面了,同时保留了作者的版权。
1 //******************************************************* 2 //**************版权所有***2011.9.20***咸鱼************** 3 //******************************************************* 4 //*友情提示:如想速度快点,请改小_sleep(500)函数中参数***** 5 //******************************************************* 6 //*****************如写的不好,请见谅********************* 7 //******************************************************* 8 9 #include10 #include 11 #include 12 #include <string.h> 13 #include 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 }