C语言初学者项目:一个二维数组实现贪吃蛇游戏
本文章面向c语言初学者,所以我尽可能用最基本的语法实现整个游戏,并且只用到一个二维数组实现,下面将详细讲解实现过程。
一个二维数组可以直接打印成一个平面,就形成了整个游戏的背景,我们可以通过改变数组的内容来控制蛇的移动,食物的生成等。
先建立整个游戏框架
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
#define ROW 10//行数
#define COL 25//列数
#define SPEED 100//这里其实是刷新率,所以越低蛇速度越快,后边会引用到
int a[ROW][COL];
int main(void)
{
char play;
do
{
game();
printf("1:continue\n");
printf("2:exit\n");
play = getch();
} while (play=='1');
printf("exit\n");
return 0;
}
将二维数组建立为全局变量,使数组所有的数都初始化为0。之后可以用数组中不同的数字代表不同的事物,比如我们将数组的边框初始化为-2,-2就代表了墙
for (int i = 0; i < ROW; i++)//生成墙壁
for (int j = 0; j < COL; j++)
if (i == 0 || j == 0 || i == ROW - 1 || j == COL - 1)
a[i][j] = -2;
蛇的数字处理很重要,这涉及到蛇的移动问题,蛇的移动实际上可以理解为将尾巴那一块转移到将要移动的前方去,如果蛇要向上移动,就是把尾巴那一块转移到头的上边,同理向右移动即把尾巴移动到头的右边,理解了这个移动方式,就可以对蛇在数组中的值进行创建了,令头的值为1,之后身体块就是2,3、、、直到尾巴那个值,我们初始化蛇有三节,所以尾巴数为3(当然你也可以初始化其他的值)。
a[ROW-2][1] = 3;//生成蛇
a[ROW-2][2] = 2;//生成蛇
a[ROW-2][3] = 1;//生成蛇头
int tailmax = 3;//蛇长,之后蛇的变长要靠这个值
食物生成比较简单,取随机数在数组的范围内,如果是不恰当的位置就重新设置,设置食物的值为-1
void toeat(int a[ROW][COL])//生成食物
{
while (1)
{
int x = rand() % ROW;
int y = rand() % COL;
if (x != 0 && y != 0/*不能在边界上*/ && a[x][y] == 0/*只能在空的区域生成*/)
{
a[x][y] = -1;
break;
}
}
}
s
现在数组的基本处理完成,数组内一共有4种值 ,一种是-2,表示墙,一种是-1,表示食物,一种是0,表示空白区域,剩下的正数全都代表蛇,蛇头是1,那么就可以编写打印函数
void print(int a[ROW][COL])//打印
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL; j++)
{
if ((i == 0 || i == ROW - 1) && j != 0 && j != COL - 1)//上下边界
printf("-");
else if ((j == 0 || j == COL - 1) && i != 0 && i != ROW - 1)//左右边界
printf("|");
else if ((i == 0 || i == ROW - 1) && (j == 0 || j == COL - 1))//四个角
printf("+");
else if (a[i][j] == 0)//空白区域
printf(" ");
else if (a[i][j] == 1)//蛇头,我用字母‘O’表示
printf("O");
else if (a[i][j] == -1)//食物
printf("$");
else
printf("*");//剩下的都是正数,是蛇身
}
printf("\n");
}
}
前三个if选择式可能有点吓人,因为我想建立一个(稍微)好看的边界,如果你嫌麻烦,可以直接让边界都是一个字符,如下
void print(int a[ROW][COL])//打印
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL; j++)
{
if(a[i][j]==-2)
printf("*");
else if (a[i][j] == 0)
printf(" ");
else if (a[i][j] == 1)
printf("O");
else if (a[i][j] == -1)
printf("$");
else
printf("*");
}
printf("\n");
}
}
这些都处理好后,就可以进行蛇的移动操作了,我们习惯用wsad表示上下左右(不是)。首先思考,如何表示一只蛇向上移动一次呢?
从前文我们知道只要找到头尾坐标,将尾巴放到头上方就可以,但是蛇是一串连续的数字,在数组中必须要经过数据处理才能进行,得到头尾坐标后,我们可以先让尾巴消失,即坐标设置为0,然后把包括蛇头在内的蛇本体数字全部加1,这样原来蛇头的数字变成了2,现在没有蛇头,我们就可以把原来蛇头上面的数(如果头是a[i][j],头上面就是a[i-1][j])设置为1,新的蛇头就生成了,而且蛇新的尾巴的值没有改变,这样我们简单实现了蛇向上移动一格这个操作,转换为代码如下
int handx, handy;//蛇头坐标
int tailx, taily;//蛇尾坐标
for(int i=0;i<ROW;i++)
for(int j=0;j<COL;j++)
if (a[i][j] == 1)
{
handx = i;
handy = j;
}//找到头坐标
for(int i=0;i<ROW;i++)
for(int j=0;j<COL;j++)
if (a[i][j] == tailmax)
{
tailx = i;
taily = j;
}//找到尾坐标,这里就用到了之前的tailmax
if (a[handx - 1][handy] == 0)//判断前方是空白可行走
{
a[tailx][taily] = 0;//蛇尾设为0
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2)//排除墙壁、食物、空白
a[i][j]++;//蛇的身体和头都加1
a[handx - 1][handy] = 1;//设置新蛇头
}
运用这个逻辑,我们可以很容易实现吃食物这个过程,如果前进方向的值是-1即食物,就将整个蛇连头带尾所有值加1,然后把食物的值设为1,形成新的蛇头
else if (a[handx - 1][handy] == -1)//判断食物
{
for (int i = 0;i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2&&a[i][j]!=-5)
a[i][j]++;
a[handx - 1][handy] = 1;
tailmax++;//蛇变长了,所以尾巴的值要加1
toeat(a);.//生成一个新的食物
蛇的前方除了是空白和食物外就是墙和蛇身,如果继续前进就会游戏结束
else
gameover = 1;//之后用到
我们已经可以实现一次移动,稍微修改二维数组的参数就可以实现四个方向的移动,但是蛇的移动是一个连续的过程,我们应该用循环实现连续移动,但单纯的循环是非常快的,所以蛇会飞快移动,为了避免这种情况,我们要用到windows.h里边的Sleep函数即Sleep(m),m的值为停顿的毫秒,我们将之前定义的SPEED调用上去,Sleep(SPEED)就能控制蛇移动的速度。
另外,因为蛇一直在移动,程序一直在运行,我们要控制蛇必须要输入wsad的指令,不可能蛇动一次就输入一次,太麻烦,所以我们要学习一个新函数_kbhit(),它判断你的键盘有没有输入,没有就返回0,有就返回一个非0值。把他放在每次循环移动的后边,再用getct不回显直接读取输入的值,就可以操作蛇。
一次完整的移动如下
while(1)
{
if(move=='w')//以向上移动为例
{
if (a[handx - 1][handy] == 0)//判断可行走
{
a[tailx][taily] = 0;
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2)
a[i][j]++;
a[handx - 1][handy] = 1;
}
else if (a[handx - 1][handy] == -1)//判断食物
{
for (int i = 0;i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2&&a[i][j]!=-5)
a[i][j]++;
a[handx - 1][handy] = 1;
tailmax++;
toeat(a);
}
else gameover = 1;//判断游戏失败
break;
system("cls");//清除之前的屏幕
print(a);//打印新的局面,这样刷新基本实现的蛇的动态过程
Sleep(SPEED);//停一下
if (_kbhit())//判断是否有输入,没有直接跳过
{
char cao = getch();//用另一个变量来接受,避免接受到wsad以外的垃圾值
if (cao == 'w' || cao == 's' || cao == 'a' || cao == 'd')
move = cao;
}
}
}
因为要判断四个移动方向,我们用在while循环内部用switch语句来判断move的值决定移动的方向,
同时设置一个值int gameover=1来判断游戏是否结束。具体代码如下
while (!gameover)
{
for(int i=0;i<ROW;i++)
for(int j=0;j<COL;j++)
if (a[i][j] == 1)
{
handx = i;
handy = j;
}//找到头坐标
for(int i=0;i<ROW;i++)
for(int j=0;j<COL;j++)
if (a[i][j] == tailmax)
{
tailx = i;
taily = j;
}//找到尾坐标
switch (move)
{
case'w':
if (a[handx - 1][handy] == 0)//判断可行走
{
a[tailx][taily] = 0;
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2)
a[i][j]++;
a[handx - 1][handy] = 1;
}
else if (a[handx - 1][handy] == -1)//判断食物
{
for (int i = 0;i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2&&a[i][j]!=-5)
a[i][j]++;
a[handx - 1][handy] = 1;
tailmax++;
toeat(a);
}
else gameover = 1;
break;
case's':
if (a[handx + 1][handy] == 0)
{
a[tailx][taily] = 0;
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
a[i][j]++;
a[handx + 1][handy] = 1;
}
else if (a[handx + 1][handy] == -1)
{
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
a[i][j]++;
a[handx + 1][handy] = 1;
tailmax++;
toeat(a);
}
else gameover = 1;
break;
case'a':
if (a[handx][handy-1] == 0)
{
a[tailx][taily] = 0;
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
a[i][j]++;
a[handx][handy - 1] = 1;
}
else if (a[handx][handy-1] == -1)
{
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
a[i][j]++;
a[handx][handy - 1] = 1;
tailmax++;
toeat(a);
}
else gameover = 1;
break;
case'd':
if (a[handx][handy + 1] == 0)
{
a[tailx][taily] = 0;
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
a[i][j]++;
a[handx][handy + 1] = 1;
}
else if (a[handx][handy + 1] == -1)
{
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
a[i][j]++;
a[handx][handy + 1] = 1;
tailmax++;
toeat(a);
}
else gameover = 1;
break;
}
system("cls");
print(a);
Sleep(SPEED);
if (_kbhit())//判断是否有输入
{
char cao = getch();
if (cao == 'w' || cao == 's' || cao == 'a' || cao == 'd')
move = cao;
}
}
printf("YOU DIE!\n");
printf("score:%d", tailmax);//正好用tailmax的值判断蛇长表示得分
}
完整代码如下
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
#define ROW 10
#define COL 25
#define SPEED 100
int a[ROW][COL];
void toeat(int a[ROW][COL])//生成食物
{
while (1)
{
int x = rand() % ROW;
int y = rand() % COL;
if (x != 0 && y != 0 && a[x][y] == 0)
{
a[x][y] = -1;
break;
}
}
}
void print(int a[ROW][COL])//打印
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL; j++)
{
if ((i == 0 || i == ROW - 1) && j != 0 && j != COL - 1)
printf("-");
else if ((j == 0 || j == COL - 1) && i != 0 && i != ROW - 1)
printf("|");
else if ((i == 0 || i == ROW - 1) && (j == 0 || j == COL - 1))
printf("+");
else if (a[i][j] == 0)
printf(" ");
else if (a[i][j] == 1)
printf("O");
else if (a[i][j] == -1)
printf("$");
else
printf("*");
}
printf("\n");
}
}
void game()
{
system("cls");
for (int i = 0; i < ROW; i++)//重置数组,不然重开游戏会出现前一条蛇的尸体,甚至撞上去会死,而且食物数量会加1,莫名有意思,想体验可以注释这三行
for (int j = 0; j < COL; j++)
a[i][j] = 0;
srand((unsigned int)time(NULL));
for (int i = 0; i < ROW; i++)//生成墙壁
for (int j = 0; j < COL; j++)
if (i == 0 || j == 0 || i == ROW - 1 || j == COL - 1)
a[i][j] = -2;
a[ROW-2][1] = 3;//生成蛇
a[ROW-2][2] = 2;//生成蛇
a[ROW-2][3] = 1;//生成蛇头
print(a);
toeat(a);
char move='w';//默认为先向上移,你也可以改为其他方向
int handx, handy;
int tailx, taily;
int tailmax = 3;//蛇长
int gameover=0;//判断游戏是否结束
printf("\nPress any key to cintinue");
char flag = getch();//按任意键开始游戏
while (!gameover)
{
for(int i=0;i<ROW;i++)
for(int j=0;j<COL;j++)
if (a[i][j] == 1)
{
handx = i;
handy = j;
}//找到头坐标
for(int i=0;i<ROW;i++)
for(int j=0;j<COL;j++)
if (a[i][j] == tailmax)
{
tailx = i;
taily = j;
}//找到尾坐标
switch (move)
{
case'w':
if (a[handx - 1][handy] == 0)//判断可行走
{
a[tailx][taily] = 0;
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2)
a[i][j]++;
a[handx - 1][handy] = 1;
}
else if (a[handx - 1][handy] == -1)//判断食物
{
for (int i = 0;i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2&&a[i][j]!=-5)
a[i][j]++;
a[handx - 1][handy] = 1;
tailmax++;
toeat(a);
}
else gameover = 1;
break;
case's':
if (a[handx + 1][handy] == 0)
{
a[tailx][taily] = 0;
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
a[i][j]++;
a[handx + 1][handy] = 1;
}
else if (a[handx + 1][handy] == -1)
{
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
a[i][j]++;
a[handx + 1][handy] = 1;
tailmax++;
toeat(a);
}
else gameover = 1;
break;
case'a':
if (a[handx][handy-1] == 0)
{
a[tailx][taily] = 0;
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
a[i][j]++;
a[handx][handy - 1] = 1;
}
else if (a[handx][handy-1] == -1)
{
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
a[i][j]++;
a[handx][handy - 1] = 1;
tailmax++;
toeat(a);
}
else gameover = 1;
break;
case'd':
if (a[handx][handy + 1] == 0)
{
a[tailx][taily] = 0;
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
a[i][j]++;
a[handx][handy + 1] = 1;
}
else if (a[handx][handy + 1] == -1)
{
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COL; j++)
if (a[i][j] != 0 && a[i][j] != -1 && a[i][j] != -2 && a[i][j] != -5)
a[i][j]++;
a[handx][handy + 1] = 1;
tailmax++;
toeat(a);
}
else gameover = 1;
break;
}
system("cls");
print(a);
Sleep(SPEED);
if (_kbhit())//判断是否有输入
{
char cao = getch();
if (cao == 'w' || cao == 's' || cao == 'a' || cao == 'd')
move = cao;
}
}
printf("YOU DIE!\n");
printf("score:%d", tailmax);
}
int main(void)
{
char play;
do
{
game();
printf("\n1:continue");
printf("\n2:exit");
play = getch();
} while (play=='1');
printf("\nexit");
return 0;
}
最后存在的问题:因为是用这种刷新方式所以眼睛可能会不适,另外如果蛇往反方向走游戏会立即结束(应该没人会这样做吧)。