前言
贪吃蛇相信大家小时候都玩过,那么你有没有想过有一天你也能自己编程实现它呢?今天我就在这里教大家用C语言,通过自顶向下逐步求精的方法,实现一个简易的贪吃蛇。
准备工作
(首先心里要有点X数)既然是简易的,那么没有高大上的画面,我们实现的一定是字符版。为了方便我们之后的思考,我们先把最基础的几个参数和初始参数定下来。
#include<stdio.h>
#include<conio.h>
#define SNAKE_MAX_LENGTH 20
#define SNAKE_HEAD 'H'
#define SNAKE_BODY 'X'
#define BLANK_CELL ' '
#define WALL_CELL '*'
#define SNAKE_FOOD '$'
#define stay 'S'
char map[12][12] =
{ "************",
"*XXXXH *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"************"
};
int snakeX[SNAKE_MAX_LENGTH] = { 5,4,3,2,1 };
int snakeY[SNAKE_MAX_LENGTH] = { 1,1,1,1,1 };
int snake_length = 5;
对没错,那个二元字符组就是游戏界面,第二行那一条就是我们的蛇了。
一条会动、会死的蛇
秉着由简入繁的原则,我们先来实现最基本的功能,就是一条能够接受我们命令(WASD)动起来,而且撞到墙或者自己会死的蛇。让我们先敲下伪代码。
输出字符矩阵
WHILE not 游戏结束 DO
ch=输入的字符
move(ch);
输出字符矩阵
END WHILE
输出 Game Over!!!
然后按照伪代码敲下main函数,以及我们秉着“自顶向下、逐步求精”的原则自己写的move函数以及判断死亡的函数。这里用到了两个小技巧,一个是getch()这个函数,可以让程序不需要每次输入后再敲个回车;另一个是p这个值(其实就是布尔值)来帮助记录蛇的死活。
void print(char map[12][12]) {
int i;
for (i = 0; i < 12; i++) {
int j;
for (j = 0; j < 12; j++) {
printf("%c", map[i][j]);
}
printf("\n");
}
}
int hit_it_self(int X[], int Y[]) {
int i;
for(i = 2; i < snake_length; i++)
if (X[0] == X[i] && Y[0] == Y[i]) return 0;
return 1;
}
int hit_the_wall(int X[], int Y[]) {
if (X[0] == 0 || X[0] == 11) return 0;
if (Y[0] == 0 || Y[0] == 11) return 0;
return 1;
}
void move(int x) {
map[snakeY[snake_length - 1]][snakeX[snake_length - 1]] = ' ';
map[snakeY[0]][snakeX[0]] = 'X';
int i;
tempX = snakeX[snake_length - 1];
tempY = snakeY[snake_length - 1];
for (i = 1; i < snake_length; i++) {
snakeX[snake_length - i] = snakeX[snake_length - i - 1];
snakeY[snake_length - i] = snakeY[snake_length - i - 1];
}
switch (x) {
case 'A':
snakeX[0] = snakeX[0] - 1;
break;
case 'W':
snakeY[0] = snakeY[0] - 1;
break;
case 'D':
snakeX[0] = snakeX[0] + 1;
break;
case 'S':
snakeY[0] = snakeY[0] + 1;
break;
}
map[snakeY[0]][snakeX[0]] = 'H';
}
int main() {
print(map);
int p = 1;
char movement;
while (p>0) {
movement = getch();
int x = movement;
move(x);
int p1 = hit_it_self(snakeX, snakeY);
int p2 = hit_the_wall(snakeX, snakeY);
if (p1 == 0 || p2 == 0) p = 0;
else {
system("cls");//清屏
print(map);
}
}
printf("GAME OVER");
}
放食物,吃食物
贪吃蛇的另一功能就是吃食物,然后变长。这里有两个难点:一个就是放置食物需要随机,而C语言的随机是随机数表法生成的伪随机,所以我们这里就需要一点小技巧,根据启动程序的时间来决定随机的seed;另一个则是吃食物后我要实现立刻再生成一个食物,以及蛇的身体长度加一。下面就是相关的函数。
int food = 0;
int foodX, foodY;
int tempX, tempY;
void set_food(char map[12][12]){
srand((int)time(0));
foodX = (int)(rand()%10) + 1;
foodY = (int)(rand()%10) + 1;
int judge = 1;
int i;
for(i =0; i < snake_length; i++){
if(snakeX[i] == foodX && snakeY[i] == foodY){
judge = 0;
break;
}
}
if(judge == 1){
map[foodY][foodX] = '$';
food = 1;
//return;
}
//if(judge == 0) set_food(map);如果迭代就会偶尔导致程序崩溃。
}
void eat(char map[12][12]){
snake_length++;
snakeX[snake_length - 1] = tempX;
snakeY[snake_length - 1] = tempY;
map[tempY][tempX] = 'X';
food = 0;
}
自己会动
接下来就是最后的部分,我需要蛇能够自己动起来。就是说,当我没有输入的时候,蛇要自己沿着原方向继续走。这里我们用了两个函数,一个是kbhit()来解决输入的问题,另一个则是clock()或者sleep()来解决自己动的问题,我这里选择的是前者。其中kbhit的功能是有输入则返回输入值,没有则返回0;clock()则是返回程序执行的时间。
具体代码如下:(这里笔者就将整个main函数再重新敲一遍了。)
char my_kbhit(char movement){
if(kbhit()){
movement = getch();
return movement;
}
else return movement;
}
int main() {
int game_time;
print(map);
int p = 1;
char movement = stay;
while (p>0) {
game_time = clock();
if(game_time % (1000 - 50 * snake_length) == 0) {
movement = my_kbhit(movement);
while(food == 0){
if(snake_length <= 20){
set_food(map);
}
}
int x = movement;
move(x);
int p1 = hit_it_self(snakeX, snakeY);
int p2 = hit_the_wall(snakeX, snakeY);
if (p1 == 0 || p2 == 0) p = 0;
else {
if(snakeX[0] == foodX && snakeY[0] == foodY) eat(map);
system("cls");
print(map);
}
}}
if(snake_length == 20){
system("cls");
printf("YOU WIN");
return;
}
printf("GAME OVER");
}
这里为了增加难度,我让被模的数随蛇的长度增加而减少,从而实现了蛇越长动得越快来给游戏增加乐趣。
后记
学会了贪吃蛇,其实很多小游戏的字符版就都可以实现了,比如 flappy bird、 或者一个简单的“雷电”。当然读者也可以自如发挥来美化这个程序,比如利用 system 的种种函数来设计边框,背景颜色,亦或者想个办法让打印的时候不那么闪,这些就留给读者进一步自由发挥了。
总之目前的效果就是这样啦。