不多废话,直接进入正题——用C编写简易贪吃蛇。附上拙劣的源码 * c-snake *
首先说明使画面动起来的原理:通过 system("cls"); 清除当前控制台的显示,再printf下一步画面进行显示,以此循环形成动画效果(类似手翻书)。因为这是在控制台,难免会有晃眼的感觉,并且不存在美工一说,所以就将就下吧。
有了使画面动起来的方法,接下来就是贪吃蛇游戏本体的制作思路了,可将游戏本体分解为以下3个部分:
1、边界,即墙:圈定一个范围,蛇只能在墙内移动,食物只能生成在墙内
2、蛇:游戏主角,可视为2部分组成——蛇头和蛇身。蛇头引导了蛇身前行的方向,也用于判定蛇是否吃到食物
3、食物:蛇的猎物,被蛇吃掉后会触发2个事件——蛇身加长、重新生成一个食物
将本体分解完后,就是解析游戏方式:蛇会自动向蛇头朝向进行移动,玩家需要控制蛇头的方向进行移动,抛却理论不说(理论上可以将蛇身占满整个边界内部),游戏结束的情况有2种:撞墙和撞身
用人话将贪吃蛇游戏理了一遍,接下来就是将人话变成真正的设计思路:
1、边界的描述:可看作一个二维数组,四周为墙,用“*”表示;内部为空,用“ ”表示
2、蛇的描述:由蛇头和蛇身组成,而蛇头可看作蛇身的特殊部分,因此实际并无蛇头一说,仅仅是表示方式不同罢了(蛇头用“@”表示,蛇身用“#”表示),从而将蛇头和蛇身的描述问题转为了蛇身的描述。而蛇身应当如何描述?蛇身由一个又一个的、连续的点组成,因此,该问题实质便是如何去描述一个点。在二维坐标中,点便是用x和y描述,所以蛇的每个蛇身(点)都需要储存x和y这2个”标识符”,同时又需要储存多个xy对,显而易见,依旧是用一个二维数组去描述蛇
3、食物的描述:食物本质上依旧是一个点,但和蛇身不同,食物有且仅有1个,所以用一个一维数组即可描述食物
4、蛇的移动:什么是移动?移动即为位置的改变,所以在确定了方向后,我们要做的仅仅是将蛇头向该方向移动后,后面的蛇身依次覆盖前一个蛇身/头,即拷贝前一个蛇身所储存的xy坐标。当吃到食物时,应当将长度+1
5、蛇的移动方向控制:那么如何控制蛇头所指向的方向呢?虽然方向是蛇头的属性之一,但可将其从蛇中分离出去,定义一个单独的变量去描述方向。当有键盘敲击输入事件发生时,获取输入值并判断输入值与方向的关系,更新方向即可
6、食物的消失与生成:当蛇头与食物的坐标相同时,食物应当消失(被吃),消失后应当生成一个新的食物,而生成食物的x和y应为随机数,且需在墙内,不能生成在蛇上
7、游戏的结束:判断蛇头的位置即可(撞墙:当蛇头的位置处于墙上时;撞身:当蛇头的位置处于蛇身上时)
有了思路,代码其实就自然而然的写出来了:
定义部分:
1 /* 2 若内部场地大小为x*y: 3 墙定义为char wall[y+2][x+2],+2是因为2边的边界 4 蛇定义为int snake[x*y][3],snake[i][0]表示x坐标,snake[i][1]表示y坐标,snake[i][2]表示该蛇身是否存在。当然亦可定义为snake[x*y][2],初始化为0,判断x和y值是否为0去确定该蛇身是否存在 5 食物定义为int food[3],food[0]表示x坐标,food[1]表示y坐标,food[2]表示食物是否已经存在 6 移动的方向定义为int direction,1/2/3/4分别对应4个方向 7 */ 8 9 char wall[22][42]; 10 int snake[800][3]; 11 int food[3]; 12 int direction = 2; //方向:1-上 2-右 3-下 4-左
功能模块部分:
1 //初始化围墙属性 2 void init_wall() 3 { 4 extern char wall[22][42]; 5 int i,j; 6 for(i = 0; i < 22; i++) 7 { 8 for(j = 0; j < 42; j++) 9 { 10 if(i == 0 || i == 21) 11 wall[i][j] = '*'; 12 else if(j == 0 || j == 41) 13 wall[i][j] = '*'; 14 else 15 wall[i][j] = ' '; 16 } 17 } 18 }
1 //初始化蛇属性 2 void init_snake() 3 { 4 /* 5 snake[i][0]为x坐标 6 snake[i][1]为y坐标 7 snake[i][2]值为1或0 1表示存在 0表示不存在 8 */ 9 extern int snake[800][3]; 10 snake[0][0] = 11; 11 snake[0][1] = 11; 12 snake[0][2] = 1; 13 snake[1][0] = 10; 14 snake[1][1] = 11; 15 snake[1][2] = 1; 16 snake[2][0] = 9; 17 snake[2][1] = 11; 18 snake[2][2] = 1; 19 }
1 //初始化食物属性 2 void init_food() 3 { 4 /* 5 food[0]为x坐标 6 food[1]为y坐标 7 food[2]值为1或0 1表示不存在 0表示存在 8 */ 9 extern int food[3]; 10 extern int snake[800][3]; 11 12 int x,y; 13 int flag = 1; //是否在蛇身的标志 1表示在 0表示不在 14 food[2] = 0; //生成食物 置0 15 16 while(flag) 17 { 18 srand(time(0)); 19 x = rand()%40+1; 20 y = rand()%20+1; 21 int i; 22 for(i = 0; i < 800; i++) 23 { 24 if(snake[i][2]) 25 { 26 if(snake[i][0] == x && snake[i][1] == y) 27 break; 28 } 29 else 30 flag = 0; 31 } 32 } 33 food[0] = x; 34 food[1] = y; 35 }
1 //在墙中生成蛇和食物 2 void change() 3 { 4 extern char wall[22][42]; 5 extern int snake[800][3]; 6 extern int food[3]; 7 int i; 8 9 if(food[2]) 10 { 11 init_food(); 12 } 13 14 wall[food[1]][food[0]] = 'O'; //食物 15 16 for(i = 0; i < 800; i++) 17 { 18 if(snake[i][2]) 19 { 20 int x = snake[i][0]; 21 int y = snake[i][1]; 22 if(i == 0) 23 wall[y][x] = '@'; //蛇头 24 else 25 wall[y][x] = '#'; //蛇身 26 } 27 } 28 }
1 //判断是否吃到食物 2 int ifEat() 3 { 4 extern int snake[800][3]; 5 extern int food[3]; 6 extern int score; 7 8 if(snake[0][0] == food[0] && snake[0][1] == food[1]) 9 { 10 food[2] = 1; 11 return 1; //吃到返回1 12 } 13 return 0; //没吃到返回0 14 }
1 //判断是否撞墙或自身 2 int ifBreak() 3 { 4 extern int snake[800][3]; 5 int i; 6 7 if(snake[0][0] == 0 || snake[0][0] == 41 || snake[0][1] == 0 || snake[0][1] == 21) 8 return 1; 9 for(i = 1; i < 800; i++) 10 { 11 if(snake[0][0] == snake[i][0] && snake[0][1] == snake[i][1]) 12 return 1; 13 } 14 return 0; 15 }
1 //获取输入的方向 2 void getKey() 3 { 4 char ch; 5 if(_kbhit()) 6 { 7 ch = _getch(); 8 } 9 switch(ch) 10 { 11 case 'w': 12 case 'W': 13 if(direction != 3) 14 direction = 1; 15 break; 16 case 'd': 17 case 'D': 18 if(direction != 4) 19 direction = 2; 20 break; 21 case 's': 22 case 'S': 23 if(direction != 1) 24 direction = 3; 25 break; 26 case 'a': 27 case 'A': 28 if(direction != 2) 29 direction = 4; 30 break; 31 default: 32 break; 33 } 34 }
1 //移动 2 void move() 3 { 4 extern int direction; 5 extern int snake[800][3]; 6 extern int score; 7 int i = score - 1; 8 int x,y; 9 10 switch(direction) 11 { 12 case 1: 13 x = 0; 14 y = -1; 15 break; 16 case 2: 17 x = 1; 18 y = 0; 19 break; 20 case 3: 21 x = 0; 22 y = 1; 23 break; 24 case 4: 25 x = -1; 26 y = 0; 27 break; 28 default: 29 break; 30 } 31 32 if(ifEat()) 33 { 34 snake[score][0] = snake[score-1][0]; 35 snake[score][1] = snake[score-1][1]; 36 snake[score][2] = 1; 37 score++; 38 } 39 40 while(i > 0) 41 { 42 if(snake[i][2] != 0) 43 { 44 snake[i][0] = snake[i-1][0]; 45 snake[i][1] = snake[i-1][1]; 46 } 47 i--; 48 } 49 50 snake[0][0] += x; 51 snake[0][1] += y; 52 }
1 //打印 2 void print_all() 3 { 4 extern char wall[22][42]; 5 int i,j; 6 for(i = 0; i < 22; i++) 7 { 8 for(j = 0; j < 42; j++) 9 { 10 printf("%c", wall[i][j]); 11 } 12 printf("\n"); 13 } 14 }
最后只需在main中按照顺序将各个模块进行调用即可
1 void main() 2 { 3 extern char wall[22][42]; 4 extern int snake[800][3]; 5 extern int score; 6 int speed = 400; 7 int spot = 8; 8 9 init_snake(); 10 init_food(); 11 12 while(1) 13 { 14 getKey(); 15 system("cls"); 16 init_wall(); 17 move(); 18 change(); 19 print_all(wall[22][42]); 20 if(ifBreak()) 21 break; 22 Sleep(speed); 23 if(score == spot) 24 { 25 speed = 0.9*speed; 26 spot += 5; 27 } 28 } 29 printf("Game Over\n"); 30 printf("score:%d\n",score); 31 system("pause"); 32 }