C语言练习-贪吃蛇(万字详解贪吃蛇)

目录

游戏说明

一.定义地图

二.设计蛇

三.让蛇动起来

 四.通过键盘控制蛇移动

五.生成食物

六.给墙、蛇、食物加颜色

 七.吃食物变长

 八.暂停功能

九.判断游戏会不会失败

十.计分


游戏说明

写一个贪吃蛇游戏,对这段时间的学习的复习,代码涉及到知识点

1.二维数组

2.函数

3.循环

4.全局变量

5.判断

超出知识点的只有光标移动,键盘的操作,颜色的设置,这部分内容可以直接照抄,先不需要理解原理。

实现的功能有游戏背景地图和小蛇展示,蛇的移动,通过键盘控制蛇转向,食物出现消失,游戏结束,计分,蛇吃食物变长,暂停。

现在让我们开始吧。

一.定义地图

1.首先定义一个贪吃蛇背景图,蛇只能在该地图中爬呀爬。

#define H 26
#define W 105

定义地图的长宽,使用的宏定义,方便后期维护。

2.接下来定义地图,地图障碍物为1,非障碍物为0,可以使用一个二维数组来存储,

a[i][j]=1就是障碍,a[i][j]=0就是非障碍物

void init()//程序开始时的初始化操作
{
	int i, j;
	for (i = 0; i < H; i++)
	{
		a[i][0] = 1;//让第一列为1
		a[i][W - 1] = 1;//让最后一列为1
	}
	

	for (j = 0; j < W; j++)
	{	
		a[0][j]=1;//让第一行为1
		a[H-1][j]=1;//让最后一行为1
	}
}

3.地图定义好了,接下来把地图输出出来,只需要循环嵌套打印即可,

a[i][j]=0是空格,=1是障碍物,用了#表示

void drawMap()//输出地图
{
	int i,j;
	for (i = 0; i < H; i++)
	{
		for (j = 0; j < W; j++)//两重for循环遍历数组
		{	
			if(a[i][j]==0)//为0输出空格
				printf(" ");
				
			else//为1输出+
				printf("#");
				

		}
		printf("\n");//别忘了换行
	}
}

打印出来的地图大小刚好合适,如果不喜欢可以修改宏定义的H/W,方便快捷^_^

二.设计蛇

1.蛇是由两个坐标i,j组成的,可以用二维数组把这些点的坐标存起来。

int s[H*W][2];

s[0][0]和s[0][1]就是蛇头的坐标,s[i][0]和s[j][0]组成蛇的身子坐标。

定义一个int,初始化蛇一开始的长度

int sLength = 3;

s[0][0] = H / 2;//蛇头在屏幕中间位置
s[0][1] = W / 2;

for (i = 1;i < sLength;i++) {//初始蛇是竖着的

	s[i][0] = s[0][0] + i;
	s[i][1] = s[0][1] ;
}

2.画出蛇

这里需要一个移动光标的函数

void gotoxy(int i, int j)//移动光标
{

	COORD position = { j,i };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);
}

把光标移动到蛇的位置

void drawSnake()//画蛇
{
	int i;

	for (i = 0; i < sLength; i++) {
		
		gotoxy(s[i][0], s[i][1]);//移动光标到蛇的坐标
		if(i==0)
			printf("0");//蛇头
		else
			printf("*");//蛇身子
	}
}

 看看小蛇的样子,还是蛮可爱的罒ω罒

这里蛇尾巴有个光标,如果不好看可以隐藏

CONSOLE_CURSOR_INFO cursor_info = { 1,0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);//隐藏光标

现在小蛇和背景框都出来了

三.让蛇动起来

小蛇的移动,蛇头往前移动一格,然后后面的身体移动到前一节身体原来的位置上。,比如长度3格的蛇,s[2][0]/s[2][1]会朝s[1][0]/s[1][1]移动一格

void move()
{
	int i;
	
	for (i = sLength - 1; i > 0; i--)//蛇身子每一个节点都朝前一个节点挪动一格
	{
		s[i][0] = s[i - 1][0];
		s[i][1] = s[i - 1][1];

	}
}

蛇身子可以移动了,蛇头要单独处理下,定义一个变量存储上下左右

#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3

int direction;//方向

void move()
{
	int i;

	for (i = sLength - 1; i > 0; i--)//蛇身子每一个节点都朝前一个节点挪动一格
	{
		s[i][0] = s[i - 1][0];
		s[i][1] = s[i - 1][1];

	}
	switch (direction) {//蛇头根据方向来加减,向上和左是减少,下和右是加
	case UP:
		s[i][0]--;
		break;
	case DOWN:
		s[i][0]++;
		break;
	case LEFT:
		s[i][1]--;
		break;
	case RIGHT:
		s[i][1]++;
		break;
	}

}

来看看可以移动的蛇

 小蛇可以移动了,但是会留下移动痕迹,所以需要擦除掉移动的痕迹

gotoxy(s[sLength - 1][0], s[sLength - 1][1]);//尾巴擦除
printf(" ");

 四.通过键盘控制蛇移动

新函数key,用来处理键盘输入。

判断是否有键盘输入用kbhit函数。

C语言中kbhit()函数(conio.h):检查当前是否有键盘输入,若有则返回一个非0值,否则返回0。

输入不能在屏幕上有痕迹,并且不能输入一个按键就回车一下。。。所以用getch函数。

getch() 是 C 语言中的一个函数,用于在不回显到控制台的情况下从键盘读取单个字符。它读取用户按下的字符,但不将其显示在屏幕上。

getch() 的工作原理

  • 键盘缓冲区:当用户按下键盘上的键时,该键的 ASCII 码会存储在键盘缓冲区中。
  • getch() 读取缓冲区:getch() 函数读取键盘缓冲区中的第一个字符。
  • 字符不被回显:与 scanf() 等其他输入函数不同,getch() 不会将字符回显到控制台上,从而隐藏用户的输入。

void key()
{
	if (kbhit() != 0)//判断是否有键盘输入
	{
		char in;
		while (!kbhit() == 0)
			in = getch();//键盘输入的字符用in存储起来
		switch (in)//判断输入的字符是上下左右哪一个
		{
		case'w':
		case 'W':
			if (direction != DOWN)//判断一下如果是W,不能向下,蛇朝上移动不能突然向下吧。
				direction = UP;
			break;
		case'a':
		case 'A':
			if (direction != RIGHT)
				direction = LEFT;
			break;
		case's':
		case 'S':
			if (direction != UP)
				direction = DOWN;
			break;
		case'd':
		case 'D':
			if (direction != LEFT)
				direction = RIGHT;
			break;

		}
	}
}

现在通过wasd这四个按键控制蛇到处走了。

五.生成食物

食物在整个背景生成,所以用的是a[i][j]地图数组,生成之前要判断一下,不能生成到墙和蛇身上,

可以生产返回1,不能生成返回0,

int check(int ii, int jj)//判断这个坐标能不能生成食物,不能生成到墙和生成到蛇身上吧,如果可以返回1,不可以返回0
{
	if (a[ii][jj] == 1)//如果已经有食物了,不能在这个坐标点继续生成食物,
		return 0;
	int i;
	for (i = 0; i < sLength; i++)//判断是不是和蛇重合
	{
		if (ii == s[i][0] && jj == s[i][1])
			return 0;
	}
	if (ii == 0 || ii == H - 1 || jj == 0 || jj == W - 1)//判断是不是在墙上面
		return 0;
	return 1;//都不是可以生产了
}

判断坐标可以生成食物了,现在开始画出食物

void food()
{
	int i, j;
	do
	{
		i = rand() % H;//生成0-H-1之前的一个数,相当于食物的纵坐标
		j = rand() % W;//生成0-W-1之前的一个数,相当于食物的横坐标
	} 
	while (check(i, j) == 0);//找到一个点直到满足条件
	a[i][j] = -1; //标记为食物
	gotoxy(i, j);
	
	printf("*");//画出食物

}

六.给墙、蛇、食物加颜色

默认颜色不好看,可以用

SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //设置颜色

//颜色设置
void  color(int c)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
	//注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}

现在来看看实现的效果

注意选择方向的时候要切换到英文输入

 目前为止的代码

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include<time.h>
#define H 25
#define W 105
#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3
#define WAIT_TIME 200
#define true 1
#define false 0
void color(int c);

int a[H][W];//地图数组
int s[H * W][2];//蛇的定义长度
int sLength=3; //蛇身子
int direction;//方向

void init()//程序开始时的初始化操作
{
	
	CONSOLE_CURSOR_INFO cursor_info = { 1,0 };
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);//隐藏光标
	
	int i, j;
	for (i = 0; i < H; i++)
	{
		a[i][0] = 1;//让第一列为1
		a[i][W - 1] = 1;//让最后一列为1
	}
	

	for (j = 0; j < W; j++)
	{	
		a[0][j]=1;//让第一行为1
		a[H-1][j]=1;//让最后一行为1
	}
	
	s[0][0] = H / 2;
	s[0][1] = W / 2;

	for (i = 1;i < sLength;i++) {//初始蛇是竖着的

		s[i][0] = s[0][0] + i;
		s[i][1] = s[0][1] ;
	}
	direction = UP;
}



void gotoxy(int i, int j)//移动光标
{

	COORD position = { j,i };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);
}
	
void drawMap()//输出地图
{
	color(6);
	gotoxy(0, 0);
	int i,j;
	for (i = 0; i < H; i++)
	{
		for (j = 0; j < W; j++)//两重for循环遍历数组
		{	
			if(a[i][j]==0)//为0输出空格
				printf(" ");
				
			else//为1输出+
				printf("+");
			
		}
		printf("\n");//别忘了换行
	}
}
void drawSnake()//画蛇
{
	
	int i;

	for (i = 0; i < sLength; i++) {
		
		gotoxy(s[i][0], s[i][1]);//移动光标到蛇的坐标
		if(i==0)
		{
			color(12);
			printf("0");//蛇头
		}
		else
		{
			color(10);
			printf("*");//蛇身子 
		}
			
	}
}

void move()
{
	int i;
	gotoxy(s[sLength - 1][0], s[sLength - 1][1]);//尾巴擦除
	printf(" ");
	for (i = sLength - 1; i > 0; i--)//蛇身子每一个节点都朝前一个节点挪动一格
	{
		s[i][0] = s[i - 1][0];
		s[i][1] = s[i - 1][1];

	}
	switch (direction) {//蛇头根据方向来加减
	case UP:
		s[i][0]--;
		break;
	case DOWN:
		s[i][0]++;
		break;
	case LEFT:
		s[i][1]--;
		break;
	case RIGHT:
		s[i][1]++;
		break;
	}

}

void key()
{
	if (kbhit() != 0)//判断是否有键盘输入
	{
		char in;
		while (!kbhit() == 0)
			in = getch();//键盘输入的字符用in存储起来
		switch (in)//判断输入的字符是上下左右哪一个
		{
		case'w':
		case 'W':
			if (direction != DOWN)//判断一下如果是W,不能向下,蛇朝上移动不能突然向下吧。
				direction = UP;
			break;
		case'a':
		case 'A':
			if (direction != RIGHT)
				direction = LEFT;
			break;
		case's':
		case 'S':
			if (direction != UP)
				direction = DOWN;
			break;
		case'd':
		case 'D':
			if (direction != LEFT)
				direction = RIGHT;
			break;

		}
	}
}
int check(int ii, int jj)//判断这个坐标能不能生成食物,不能生成到墙和生成到蛇身上吧,如果可以返回1,不可以返回0
{
	if (a[ii][jj] == 1)//如果已经有食物了,不能在这个坐标点继续生成食物,
		return 0;
	int i;
	for (i = 0; i < sLength; i++)//判断是不是和蛇重合
	{
		if (ii == s[i][0] && jj == s[i][1])
			return 0;
	}
	if (ii == 0 || ii == H - 1 || jj == 0 || jj == W - 1)//判断是不是在墙上面
		return 0;
	return 1;//都不是可以生产了
}

void food()
{
	int i, j;
	do
	{
		i = rand() % H;//生成0-H-1之前的一个数,相当于食物的纵坐标
		j = rand() % W;//生成0-W-1之前的一个数,相当于食物的横坐标
	} 
	while (check(i, j) == 0);//生成点直到满足条件
	a[i][j] = -1; //标记为食物
	gotoxy(i, j);
	
	printf("*");//画出食物

}


//颜色设置
void  color(int c)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
	//注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}


int main()
{
	init();//程序开始时的初始化操作
	drawMap();//画地图
	
	while (1) {
		drawSnake();//画蛇
		food();//画食物
		Sleep(WAIT_TIME);//等待一段时间
		key();
		move();//移动蛇(主要是修改蛇身数组的数据)
		
	}
	getchar();
	return 0;
}

 七.吃食物变长

给个变量记录蛇吃到食物的状态

bool eated = false;//蛇吃到食物没,没迟到false,吃到true

main函数里面判断,如果吃到食物了,eated=true,然后重新投放食物,

判断蛇吃到没有,用蛇头的坐标和食物坐标判断,如果等于-1(投放食物的时候给食物赋值-1,1是墙,0是空),就是蛇头和食物在同一个坐标内

	if (a[s[0][0]][s[0][1]] == -1)//蛇头碰到食物,继续投放食物,把吃到的食物变为0
	{
		eated = true;//吃到食物 了
		food();
		a[s[0][0]][s[0][1]] = 0;//食物消失

	}
	Sleep(WAIT_TIME);//等待一段时间
	key();
	move();//移动蛇(主要是修改蛇身数组的数据)
	

然后move函数判断,如果吃到食物,蛇身子+1,同时蛇又变成没吃到食物状态,比如蛇会一直变长

	if (eated)
	{
		sLength++;//蛇变长一格
		eated = false;//然后蛇又变成没吃到食物状态
	}

现在看看效果,现在蛇还可以从自己身上和墙传过

 八.暂停功能

如果突然有事要暂停游戏,需要输入一个字符来暂停游戏。

在key函数里面加入一个case,用来判断是否暂停,

case '1'://输入1暂停游戏
	gotoxy(H, 0);//光标移动到下面
	system("pause");
	gotoxy(H, 0);
	printf("                        ");//按任意键继续
	break;

目前的游戏不会失败和记录积分

九.判断游戏会不会失败

gameover函数

bool gameOver()
{
	bool isGameOver = false;
	int sX = s[0][0], sY = s[0][1];//蛇头的坐标
	if (sX == 0 || sX == H - 1 || sY == 0 || sY == W - 1)//判断蛇头碰到墙壁没有
	{
		isGameOver = true;
		
	}
	for (int i = 1; i < sLength; i++)
	{
		if (s[i][0] == sX && s[i][1] == sY)//判断蛇头碰到自己身体没有
		{
			isGameOver = true;//判断了状态变为true 游戏结束
			break;//如果蛇头坐标和身子坐标一致,就跳出循环
		}
		
	}
	return isGameOver;
}

主函数里面加入如果游戏结束了,打印结束语

if(gameOver())
{
	system("cls");//清屏
	printf("Game Over!!!\n");
	system("pause");
	break;
}

十.计分

声明一个变量来记录分数

int score = 0;//得分

写一个printScore函数来记录分数

void printfScore()
{
	
	gotoxy(0, W + 2);
	printf("  贪吃蛇的小游戏\n");
	gotoxy(1, W + 2);
	printf("  by yumibaobao\n");
	gotoxy(4, W + 2);
	printf("  得分:%d", score);
}

最终展示效果

 所有代码展示

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include<time.h>
#define H 25
#define W 65
#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3
#define WAIT_TIME 100
#define true 1
#define false 0
void color(int c);
typedef int bool;

int a[H][W];//地图数组
int s[H * W][2];//蛇的定义长度
int sLength=6; //蛇身子
int direction;//方向
bool eated = false;//蛇吃到食物没
int score = 0;//得分

void init()//程序开始时的初始化操作
{
	
	CONSOLE_CURSOR_INFO cursor_info = { 1,0 };
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);//隐藏光标
	
	int i, j;
	for (i = 0; i < H; i++)
	{
		a[i][0] = 1;//让第一列为1
		a[i][W - 1] = 1;//让最后一列为1
	}
	

	for (j = 0; j < W; j++)
	{	
		a[0][j]=1;//让第一行为1
		a[H-1][j]=1;//让最后一行为1
	}
	
	s[0][0] = H / 2;
	s[0][1] = W / 2;

	for (i = 1;i < sLength;i++) {//初始蛇是竖着的

		s[i][0] = s[0][0] + i;
		s[i][1] = s[0][1] ;
	}
	direction = UP;
}



void gotoxy(int i, int j)//移动光标
{

	COORD position = { j,i };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);
}
	
void drawMap()//输出地图
{
	color(6);
	gotoxy(0, 0);
	int i,j;
	for (i = 0; i < H; i++)
	{
		for (j = 0; j < W; j++)//两重for循环遍历数组
		{	
			if(a[i][j]==0)//为0输出空格
				printf(" ");
				
			else//为1输出+
				printf("+");
			
		}
		printf("\n");//别忘了换行
	}
}
void drawSnake()//画蛇
{
	
	int i;

	for (i = 0; i < sLength; i++) {
		
		gotoxy(s[i][0], s[i][1]);//移动光标到蛇的坐标
		if(i==0)
		{
			color(12);
			printf("0");//蛇头
		}
		else
		{
			color(10);
			printf("*");//蛇身子 
		}
			
	}
}

void move()
{
	int i;
	gotoxy(s[sLength - 1][0], s[sLength - 1][1]);//尾巴擦除
	printf(" ");
	for (i = sLength - 1; i > 0; i--)//蛇身子每一个节点都朝前一个节点挪动一格
	{
		s[i][0] = s[i - 1][0];
		s[i][1] = s[i - 1][1];

	}
	switch (direction) //蛇头根据方向来加减
	{
	case UP:
		s[0][0]--;
		break;
	case DOWN:
		s[0][0]++;
		break;
	case LEFT:
		s[0][1]--;
		break;
	case RIGHT:
		s[0][1]++;
		break;
	}
	if (eated)
	{
		sLength++;//蛇变长一格
		eated = false;//然后蛇又变成没吃到食物状态
	}
}

void key()
{
	if (kbhit() != 0)//判断是否有键盘输入
	{
		char in;
		while (!kbhit() == 0)
			in = getch();//键盘输入的字符用in存储起来
		switch (in)//判断输入的字符是上下左右哪一个
		{
		case'w':
		case 'W':
			if (direction != DOWN)//判断一下如果是W,不能向下,蛇朝上移动不能突然向下吧。
				direction = UP;
			break;
		case'a':
		case 'A':
			if (direction != RIGHT)
				direction = LEFT;
			break;
		case's':
		case 'S':
			if (direction != UP)
				direction = DOWN;
			break;
		case'd':
		case 'D':
			if (direction != LEFT)
				direction = RIGHT;
			break;
		case '1':
			gotoxy(H, 0);//光标移动到下面
			system("pause");
			gotoxy(H, 0);
			printf("                        ");//按任意键继续
			break;

		}
	}
}
int check(int ii, int jj)//判断这个坐标能不能生成食物,不能生成到墙和生成到蛇身上吧,如果可以返回1,不可以返回0
{
	if (a[ii][jj] == 1)//如果已经有食物了,不能在这个坐标点继续生成食物,
		return 0;
	int i;
	for (i = 0; i < sLength; i++)//判断是不是和蛇重合
	{
		if (ii == s[i][0] && jj == s[i][1])
			return 0;
	}
	if (ii == 0 || ii == H - 1 || jj == 0 || jj == W - 1)//判断是不是在墙上面
		return 0;
	return 1;//都不是可以生产了
}

void food()
{
	int i, j;
	do
	{
		i = rand() % H;//生成0-H-1之前的一个数,相当于食物的纵坐标
		j = rand() % W;//生成0-W-1之前的一个数,相当于食物的横坐标
	} 
	while (check(i, j) == 0);//生成点直到满足条件
	a[i][j] = -1; //标记为食物
	gotoxy(i, j);
	
	printf("*");//画出食物

}

bool gameOver()
{
	bool isGameOver = false;
	int sX = s[0][0], sY = s[0][1];//蛇头的坐标
	if (sX == 0 || sX == H - 1 || sY == 0 || sY == W - 1)//判断蛇头碰到墙壁没有
	{
		isGameOver = true;
		
	}
	for (int i = 1; i < sLength; i++)
	{
		if (s[i][0] == sX && s[i][1] == sY)//判断蛇头碰到自己身体没有
		{
			isGameOver = true;//判断了状态变为true 游戏结束
			break;
		}
		
	}
	return isGameOver;
}

void printfScore()
{
	
	gotoxy(0, W + 2);
	printf("  贪吃蛇的小游戏\n");
	gotoxy(1, W + 2);
	printf("  by yumibaobao\n");
	gotoxy(4, W + 2);
	printf("  得分:%d", score);
}

//颜色设置
void  color(int c)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
	//注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}


int main()
{
	init();//程序开始时的初始化操作
	drawMap();//画地图
	food();
	while (1) {
		
		drawSnake();//画蛇
			
		Sleep(WAIT_TIME);//等待一段时间
		key();
		move();//移动蛇(主要是修改蛇身数组的数据)
		
		if(gameOver())
		{
			system("cls");//清屏
			printf("Game Over!!!\n");
			system("pause");
			break;
		}

		if (a[s[0][0]][s[0][1]] == -1)//蛇头碰到食物,继续投放食物,把吃到的食物变为0
		{
			eated = true;//吃到食物了
			food();
			score+= 10;
			a[s[0][0]][s[0][1]] = 0;//食物消失

		}
		printfScore();
		
	}

	return 0;
}

 整个贪吃蛇游戏,涉及到二维数组,循环,判断,函数,宏定义,全局变量等基础知识,

通过这个游戏,又复习了这些基础知识的使用,把一个复杂的问题拆分成一个个小的问题去解决,希望初学C的同学都可以用它来练练手。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值