贪吃蛇的代码和解析
到了本章,你已经学完了大部分C语言的基础知识,详细讲解贪吃蛇代码的条件就具备了。
本教程教你编写的贪吃蛇不依赖TC环境,不依赖任何第三方库,可以在VC 6.0、VS等常见IDE中编译通过,请看下图:
更多效果图请查看:游戏初始化、游戏进行中、游戏结束。 请大家先把贪吃蛇的源码下载下来浏览一下,我们再具体分析,这样将会有更好的效果。
贪吃蛇源代码下载:http://pan.baidu.com/s/1bnwJB8V 提取密码:81qm
贪吃蛇设计思路
上图中的红色空心方框(□)表示边框,是贪吃蛇的边界,贪吃蛇不能碰到它,否则就“死掉”,游戏结束。绿色实心方框(■)表示贪吃蛇的活动范围,贪吃蛇可以自由移动,食物(苹果)也会随机出现在这个区域。我们不妨将贪吃蛇的活动范围称为“贪吃蛇地图”,而加上边框就称为“全局地图”。
我们需要记录地图中每一个节点的信息,包括: 位置:也就是第几行几列;
类型:这个节点出现的是贪吃蛇、食物、边框,还是什么都没有(绿色的背景)。
索引:也就是数组下标,稍后会说明是什么意思。 所以需要定义一个结构体二维数组:
struct{
char type; int index;
}globalMap[MAXWIDTH][MAXHEIGHT];
用一维下标和二维下标表示位置;用
type
表示类型,不同的类
型用不同的数字代表;用
index
表示索引。
直观上讲,应该将
type
定义为
int
类型,不过
int
占用四个字
节,而节点类型的取值范围非常有限,一个字节就足够了,所以为了
节省内存才定义为
char
类型。
同时,再建立一个足够大的一维数组,让贪吃蛇在数组内活动:
struct{ int x; int y;
} snakeMap[ (MAXWIDTH-2)*(MAXHEIGHT-2) ]
x、y 表示行和列,也就是 globalMap 数组的两个下标。globalMap 数组中的索引 index 就是 snakeMap 数组的下标。
globalMap 表示了所有节点的信息,而 snakeMap 只表示了贪吃蛇的活动区域。通过 snakeMap 可以定位 globalMap 中的元素,反过来通过 globalMap 也可以找到 snakeMap 中的元素。请看下图:
图1:globalMap 和 snakeMap 的初始对应关系
贪吃蛇向左移动时,headerIndex 指向 404,tailIndex指向 406。
为什么设计的这么晦涩和复杂呢?因为这样设计有以下几个好处:
贪吃蛇移动时不用处理所有节点,只要添加蛇头、删除蛇尾、重建 globalMap 和 snakeMap 的对应关系就可以;
随机生成食物一次就可以成功,不用担心食物会占用边框或贪吃蛇的位置;
贪吃蛇移动时,不用遍历数组就可以知道是否与自身相撞。 这些优点,如果你自己尝试过其他方案就会深有体会。
源码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <math.h> 4 #include <conio.h> 5 #include <time.h> 6 #include <windows.h> 7 8 //MAXWIDTH、MAXHEIGHT、INITLEN 以字符记 9 #define MAXWIDTH (30) 10 #define MAXHEIGHT MAXWIDTH 11 #define INITLEN (3) //贪吃蛇的初始长度 12 13 //程序中用到的各种字符,以及它们的颜色和类型(以数字表示) 14 struct{ 15 char *ch; 16 int color; 17 char type; 18 } 19 charBorder = {"□", 4, 1}, //边框 20 charBg = {"■", 2, 2}, //背景 21 charSnake = {"★", 0xe, 3}, //贪吃蛇节点 22 charFood = {"●", 0xc, 4}; //食物 23 24 //用一个结构体数组保存地图中的各个点 25 struct{ 26 char type; 27 int index; 28 }globalMap[MAXWIDTH][MAXHEIGHT]; 29 30 //贪吃蛇有效活动范围地图的索引 31 struct{ 32 int x; 33 int y; 34 } snakeMap[ (MAXWIDTH-2)*(MAXHEIGHT-2) ], scoresPostion; 35 36 int scores = 0; //得分 37 int snakeMapLen = (MAXWIDTH-2)*(MAXHEIGHT-2); 38 int headerIndex, tailIndex; //蛇头蛇尾对应的snakeMap中的索引(下标) 39 HANDLE hStdin; //控制台句柄 40 41 // 设置光标位置,x为行,y为列 42 void setPosition(int x, int y){ 43 COORD coord; 44 coord.X = 2*y; 45 coord.Y = x; 46 SetConsoleCursorPosition(hStdin, coord); 47 } 48 49 // 设置颜色 50 void setColor(int color){ 51 SetConsoleTextAttribute(hStdin, color); 52 } 53 54 //创建食物 55 void createFood(){ 56 int index, rang, x, y; 57 //产生随机数,确定 snakeMap 数组的索引 58 srand((unsigned)time(NULL)); 59 if(tailIndex<headerIndex){ 60 rang = headerIndex-tailIndex-1; 61 index = rand()%rang + tailIndex + 1; 62 }else{ 63 rang = snakeMapLen - (tailIndex - headerIndex+1); 64 index = rand()%rang; 65 if(index>=headerIndex){ 66 index += (tailIndex-headerIndex+1); 67 } 68 } 69 70 x = snakeMap[index].x; 71 y = snakeMap[index].y; 72 setPosition(x, y); 73 setColor(charFood.color); 74 printf("%s", charFood.ch); 75 globalMap[x][y].type=charFood.type; 76 } 77 78 //死掉 79 void die(){ 80 int xCenter = MAXHEIGHT%2==0 ? MAXHEIGHT/2 : MAXHEIGHT/2+1; 81 int yCenter = MAXWIDTH%2==0 ? MAXWIDTH/2 : MAXWIDTH/2+1; 82 83 setPosition(xCenter, yCenter-5); 84 setColor(0xC); 85 86 printf("You die! Game Over!"); 87 getch(); 88 exit(0); 89 } 90 91 // 蛇移动 92 void move(char direction){ 93 int newHeaderX, newHeaderY; //新蛇头的坐标 94 int newHeaderPreIndex; //新蛇头坐标以前对应的索引 95 int newHeaderPreX, newHeaderPreY; //新蛇头的索引以前对应的坐标 96 int newHeaderPreType; //新蛇头以前的类型 97 int oldTailX, oldTailY; //老蛇尾坐标 98 // ----------------------------------------------- 99 //新蛇头的坐标 100 switch(direction){ 101 case 'w': 102 newHeaderX = snakeMap[headerIndex].x-1; 103 newHeaderY = snakeMap[headerIndex].y; 104 break; 105 case 's': 106 newHeaderX = snakeMap[headerIndex].x+1; 107 newHeaderY = snakeMap[headerIndex].y; 108 break; 109 case 'a': 110 newHeaderX = snakeMap[headerIndex].x; 111 newHeaderY = snakeMap[headerIndex].y-1; 112 break; 113 case 'd': 114 newHeaderX = snakeMap[headerIndex].x; 115 newHeaderY = snakeMap[headerIndex].y+1; 116 break; 117 } 118 //新蛇头的索引 119 headerIndex = headerIndex==0 ? snakeMapLen-1 : headerIndex-1; 120 // ----------------------------------------------- 121 //新蛇头坐标以前对应的索引 122 newHeaderPreIndex = globalMap[newHeaderX][newHeaderY].index; 123 //新蛇头的索引以前对应的坐标 124 newHeaderPreX = snakeMap[headerIndex].x; 125 newHeaderPreY = snakeMap[headerIndex].y; 126 127 //双向绑定新蛇头索引与坐标的对应关系 128 snakeMap[headerIndex].x = newHeaderX; 129 snakeMap[headerIndex].y = newHeaderY; 130 globalMap[newHeaderX][newHeaderY].index = headerIndex; 131 132 //双向绑定以前的索引与坐标的对应关系 133 snakeMap[newHeaderPreIndex].x = newHeaderPreX; 134 snakeMap[newHeaderPreIndex].y = newHeaderPreY; 135 globalMap[newHeaderPreX][newHeaderPreY].index = newHeaderPreIndex; 136 137 //新蛇头以前的类型 138 newHeaderPreType = globalMap[newHeaderX][newHeaderY].type; 139 //设置新蛇头类型 140 globalMap[newHeaderX][newHeaderY].type = charSnake.type; 141 // 判断是否出界或撞到自己 142 if(newHeaderPreType==charBorder.type || newHeaderPreType==charSnake.type ){ 143 die(); 144 } 145 //输出新蛇头 146 setPosition(newHeaderX, newHeaderY); 147 setColor(charSnake.color); 148 printf("%s", charSnake.ch); 149 //判断是否吃到食物 150 if(newHeaderPreType==charFood.type){ //吃到食物 151 createFood(); 152 //更改分数 153 setPosition(scoresPostion.x, scoresPostion.y); 154 printf("%d", ++scores); 155 }else{ 156 //老蛇尾坐标 157 oldTailX = snakeMap[tailIndex].x; 158 oldTailY = snakeMap[tailIndex].y; 159 //删除蛇尾 160 setPosition(oldTailX, oldTailY); 161 setColor(charBg.color); 162 printf("%s", charBg.ch); 163 globalMap[oldTailX][oldTailY].type = charBg.type; 164 tailIndex = (tailIndex==0) ? snakeMapLen-1 : tailIndex-1; 165 } 166 } 167 168 //下次移动的方向 169 char nextDirection(char ch, char directionOld){ 170 int sum = ch+directionOld; 171 ch = tolower(ch); 172 if( (ch=='w' || ch=='a' || ch=='s' || ch=='d') && sum!=197 && sum!=234 ){ 173 return ch; 174 }else{ 175 return directionOld; 176 } 177 } 178 179 //暂停 180 char pause(){ 181 return getch(); 182 } 183 184 // 初始化 185 void init(){ 186 // 设置相关变量 187 int x, y, i, index; 188 int xCenter = MAXHEIGHT%2==0 ? MAXHEIGHT/2 : MAXHEIGHT/2+1; 189 int yCenter = MAXWIDTH%2==0 ? MAXWIDTH/2 : MAXWIDTH/2+1; 190 CONSOLE_CURSOR_INFO cci; //控制台光标信息 191 192 //判断相关设置是否合理 193 if(MAXWIDTH<16){ 194 printf("'MAXWIDTH' is too small!"); 195 getch(); 196 exit(0); 197 } 198 199 //设置窗口大小 200 system("mode con: cols=96 lines=32"); 201 202 //隐藏光标 203 hStdin = GetStdHandle(STD_OUTPUT_HANDLE); 204 GetConsoleCursorInfo(hStdin, &cci); 205 cci.bVisible = 0; 206 SetConsoleCursorInfo(hStdin, &cci); 207 208 //打印背景 209 for(x=0; x<MAXHEIGHT; x++){ 210 for(y=0; y<MAXWIDTH; y++){ 211 if(y==0 || y==MAXWIDTH-1 || x==0 || x==MAXHEIGHT-1){ 212 globalMap[x][y].type = charBorder.type; 213 setColor(charBorder.color); 214 printf("%s", charBorder.ch); 215 }else{ 216 index = (x-1)*(MAXWIDTH-2)+(y-1); 217 snakeMap[index].x= x; 218 snakeMap[index].y= y; 219 globalMap[x][y].type = charBg.type; 220 globalMap[x][y].index = index; 221 222 setColor(charBg.color); 223 printf("%s", charBg.ch); 224 } 225 } 226 printf("\n"); 227 } 228 229 //初始化贪吃蛇 230 globalMap[xCenter][yCenter-1].type = globalMap[xCenter][yCenter].type = globalMap[xCenter][yCenter+1].type = charSnake.type; 231 232 headerIndex = (xCenter-1)*(MAXWIDTH-2)+(yCenter-1) - 1; 233 tailIndex = headerIndex+2; 234 setPosition(xCenter, yCenter-1); 235 setColor(charSnake.color); 236 for(y = yCenter-1; y<=yCenter+1; y++){ 237 printf("%s", charSnake.ch); 238 } 239 //生成食物 240 createFood(); 241 242 //设置程序信息 243 setPosition(xCenter-1, MAXWIDTH+2); 244 printf(" Apples : 0"); 245 setPosition(xCenter, MAXWIDTH+2); 246 printf(" Author : xiao p"); 247 setPosition(xCenter+1, MAXWIDTH+2); 248 printf("Copyright : c.biancheng.net"); 249 scoresPostion.x = xCenter-1; 250 scoresPostion.y = MAXWIDTH+8; 251 } 252 253 int main(){ 254 char charInput, direction = 'a'; 255 init(); 256 257 charInput = tolower(getch()); 258 direction = nextDirection(charInput, direction); 259 260 while(1){ 261 if(kbhit()){ 262 charInput = tolower(getch()); 263 if(charInput == ' '){ 264 charInput = pause(); 265 } 266 direction = nextDirection(charInput, direction); 267 } 268 move(direction); 269 Sleep(500); 270 } 271 272 getch(); 273 return 0; 274 }

浙公网安备 33010602011771号