C实现贪吃蛇

一.基本知识

要实现这样的一个小游戏,需要掌握结构体,指针,链表,函数,分支循环语句,枚举等,绝大部分的C语言知识都运用到了。

还需要了解下Win32API的一些知识,使用这些知识时,需要包含一个Windows.h的头文件

Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外, 它同时也是⼀个很大的服务中心,调用这个服务中心的各种服务(每⼀种服务就是⼀个函数),可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序(Application), 所以便称之为 Application Programming Interface,简称 API 函数。WIN32 API也就是Microsoft Windows32位平台的应用程序编程接口。

下面就根据需要去学习一下。

1.窗口

游戏时,需要改变窗口的大小和标题
在这里插入图片描述
控制台窗口可以看成一个平面坐标轴,左上角是原点(0,0),上方是x轴,从左往右x值增加,左边是y轴,从上往下y值增加。

	system("mode con cols=100 lines=30");//改变窗口大小
	system("title 贪吃蛇");               //修改窗口标题

这两句代码可以修改窗口的大小和标题,大小改为30行和100列。

图中还会看到中间有一句:欢迎来到贪吃蛇小游戏!这句话使用printf函数就可以打印,但是打印是从光标的位置开始打印,光标默认在坐标(0,0)处,我们需要修改这个光标的位置。

COORD
这是一个结构体,它的值表示控制台屏幕上的坐标

typedef struct _COORD {
 SHORT X;
 SHORT Y;
} COORD, *PCOORD;

X,Y就表示屏幕上的坐标。
要修改还需要获得一个句柄,来操作我们的设备。

(1)GetStdHandle函数

这个函数可以获得一个句柄(标准输入,输出,错误)

在这里插入图片描述

(2)SetConsoleCurorInfo函数

在这里插入图片描述
函数的第一个参数是控制台屏幕缓冲区的句柄,第二个参数是一个指针,指针指向一个结构体类型的数据

typedef struct _CONSOLE_CURSOR_INFO {
  DWORD dwSize;
  BOOL  bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

成员变量的含义:

dwSize:由光标填充的字符单元的百分比。 该值介于 1 到 100 之间。 光标外观各不相同,范围从完全填充单元到显示为单元底部的横线。

bVisible:光标的可见性。(将值改为false后,光标可以隐藏)

通过以上两个函数我们可以改变光标的位置。

int main()
{
	printf("AB");

	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输出的句柄
	COORD pos = { 15,20 };                          //修改坐标
	SetConsoleCursorPosition(handle, pos);          //通过句柄来设置(改变) 光标的位置

	printf("AB");
	return 0;
}

结果如下:
在这里插入图片描述
游戏过程由于需要多次修改光标,因此将修改光标的代码封装成一个函数

//改变光标位置
void setPos(int x, int y)
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x,y };
	SetConsoleCursorPosition(handle, pos);
}

通过传入x和y,来改变光标位置

(3)GetConsoleCurInfo函数

在这里插入图片描述
如果程序处于运行状态,光标会一直闪烁,影响玩家的感受,我们可以将光标隐藏起来。

通过上面三个函数,实现以下代码来隐藏光标。

int main()
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输出的句柄
	CONSOLE_CURSOR_INFO cursorInfo;                 //创建一个CONSOLE_CURSOR_INFO类型的变量
	GetConsoleCursorInfo(handle, &cursorInfo);      //获得操作屏幕光标的权限
	cursorInfo.bVisible = false;                    //修改可见性
	SetConsoleCursorInfo(handle, &cursorInfo);      //设置属性

	getchar();
	return 0;
}

2.宽字符

我们平时打印的字符如26个英文字母等,这些属于窄字符,宽字符占两个字节,窄字符占一个字节,所以打印时窄字符会占用一个格子,而宽字符会占用两个格子。

打印时需要用wprintf函数。
打印格式为:

wprintf(L"%lc",L’…');

如下图中所示。
在这里插入图片描述

其次,在打印宽字符前,需要修改本地环境。

(1)setlocale函数

在这里插入图片描述
第一个参数是要修改的类项,第二个参数是修改环境,第二个参数只有两种"C"和" "。
在这里插入图片描述
如果第一个参数值为LC_ALL,那么会修改全部的类型。
使用这个函数前需要包含一个locale.h的头文件

setlocale(LC_ALL, “”); //修补适配本地环境。

(2)检测按键

GetAsyncKeyState函数可以实现检测键盘中的按键是否被按下
函数原型如下

SHORT GetAsyncKeyState(
  [in] int vKey
);

函数参数是被检测的按键的虚拟键值,返回值类型为short类型。

在上⼀次调用 GetAsyncKeyState 函数后,如果
返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;

如果最低位被置为1则说明,该按键被按过,否则为0
可以根据最低为的值来判断按键有没有被按下

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)

GetAsyncKeyState 函数的返回值和1按位与,如果结果为1,说明该键被按过,为0则没有被按过

通过定义这样一个宏函数来检测按键是否被按过。

二.贪吃蛇实现

有了上面的基础外加C语言知识,我们就可以开始实现贪吃蛇游戏了。

贪吃蛇游戏可以分为开始前,游戏中和结束后。

1.游戏前

(1)设置游戏窗⼝的⼤⼩

窗口大小设置为100列,30行

system(“mode con cols=100 lines=30”);

(2) 设置窗⼝的名字

窗口名字设置为贪吃蛇,大家也可以做修改

system(“title 贪吃蛇”);

(3) 隐藏屏幕光标

使用上面介绍的三个函数来隐藏光标

    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输出的句柄
	CONSOLE_CURSOR_INFO cursorInfo;                 //创建一个CONSOLE_CURSOR_INFO类型的变量
	GetConsoleCursorInfo(handle, &cursorInfo);      //获得操作屏幕光标的权限
	cursorInfo.bVisible = false;                    //修改可见性
	SetConsoleCursorInfo(handle, &cursorInfo);      //设置属性

(4)打印欢迎界⾯-WelcomeToGame

使用我们封装的setPos函数来修改光标位置,然后进行信息的打印。
使用system(“pause”);可以出现下方图中的:请按任意键继续…等字样
在这里插入图片描述

void WelcomeToGame()
{
	setPos(50, 15);
	printf("欢迎来到贪吃蛇小游戏!");
	setPos(53, 25);
	system("pause");//暂停
	system("cls");	//清屏

	setPos(40, 15);
	printf("游戏规则:用← → ↑ ↓ 控制蛇的移动,吃食物(¥)获得分数");
	setPos(49, 16);
	printf("空格暂停游戏,Esc退出游戏,数字0减速,数字1加速");
	setPos(49, 17);
	printf("改变速度可以改变食物分数");
	setPos(53, 25);
	system("pause"); 
	system("cls");   
}

按下任意键后跳到如下界面,在按下任意键后会跳到主界面
在这里插入图片描述

(5)创建地图-GetMap

创建一个58列,27行的一个地图,地图打印的是汉字’墙’。大家也可以选择自己喜欢的字符。

由于可能需要多次打印字符,宏定义了几个常量

#define WALL L'墙'   //表示墙
#define SNAKE L'@'  //表示蛇
#define FOOD L'¥'   //表示食物
//下面两个表示蛇身第一个结点的坐标
#define SNAKE_X 24          
#define SNAKE_Y 5    

创建地图代码如下

void GetMap()
{
	//地图上方
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//地图下方
	setPos(0, 26);
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//地图两边
	for (int i = 1; i < 27; i++)
	{
		setPos(0, i);
		wprintf(L"%lc", WALL);
		setPos(56, i);
		wprintf(L"%lc", WALL);
	}
}

绘制完地图后,接下来完成蛇的创建;

(6) 初始化蛇⾝-InitSnake

蛇用链表表示,那么需要一个蛇身的结构体类型

具体内容如下代码

//蛇身结点结构体
typedef struct snakeNode
{
	int x;
	int y;
	struct snakeNode* next;
}snakeNode,*psnakeNode;

snakeNode是这个结构体的别名,而psnakeNode是这个结构体指针的别名

除了蛇,还需要一些其他的信息,如食物分数,当前的总分数,休眠的时间(休眠时间越短速度越快),蛇运动的方向,指向蛇头的指针,指向食物的指针以及当前的游戏状态(正常,咬到自己,撞墙,按Esc退出)等

所以另外创建一个结构体类型,包括以上的全部成员。

具体代码如下

typedef struct snake
{
	psnakeNode pSnake;   //维护蛇的指针
	psnakeNode pFood;    //维护食物的指针
	int sum_sorce;       //总的分数
	int food_sorce;      //每个食物的分数
	int sleep;           //走一步休息的时间,决定速度 
	enum DIRECTION direct;    //蛇当前的方向
	enum GAME_STATUS status;  //游戏当前的状态
}snake,*psnake;

蛇的方向只有上下左右,而游戏状态也只有有限个,可以把它们枚举出来。

//枚举出所有方向
enum DIRECTION
{
	UP=1,
	DOWN,
	LEFT,
	RIGHT
};

//枚举出游戏的所有状态
enum GAME_STATUS
{
	OK=1,           //正常
	ESC,            //按Esc退出
	KILLED_BY_WALL, //撞墙退出
	KILLED_BY_SEIF  //咬到自己
};

完成以上任务后,就可以来初始化并打印蛇了。

接下来画图来分析如何实现代码。
在这里插入图片描述
蛇身长默认初始化为5代码如下

//初始化蛇
void InitSnake(psnake ps)
{
	psnakeNode cur = NULL;
	//默认蛇长为5
	for (int i = 0; i < 5; i++)
	{
		cur = (psnakeNode)malloc(sizeof(snakeNode));
		if (cur == NULL)
		{
			perror("InitSnake(psnake ps) malloc:");
			return;
		}
		//蛇身坐标
		cur->x = SNAKE_X + 2 * i;
		cur->y = SNAKE_Y;
		cur->next = NULL;

		//头插法创建蛇
		if (ps->pSnake == NULL)
		{
			ps->pSnake = cur;
		}
		else
		{
			cur->next = ps->pSnake;
			ps->pSnake = cur;
		}
	}

	cur = ps->pSnake; //cur指向蛇头

	//打印蛇身
	while (cur)
	{
		setPos(cur->x, cur->y);
		wprintf(L"%lc", SNAKE);
		cur = cur->next;
	}

	//初始化其他信息
	ps->direct = RIGHT;  //方向默认为右
	ps->pFood = NULL;    
	ps->food_sorce = 15; //食物分数为15
	ps->sleep = 100;     //休眠时间0.1s
	ps->sum_sorce = 0;   //初始总分为0分
	ps->status = OK;     //初始游戏状态正常
}

打印蛇身,实际上就是遍历链表,创建蛇,就是创建链表。

(7)创建⻝物-CreateFood

有了蛇,还需要食物,要将食物显示在屏幕上,需要坐标,因此创建一个snakeNode类型的变量,作为食物。

生成食物需要注意,食物是随机生成的,而且不能在墙外,因此坐标受到限制。其次,食物不能和蛇身重叠。

创建食物的代码如下

//创建食物
void CreateFood(psnake ps)
{    
	int x = 0;
	int y = 0;
	psnakeNode cur = NULL;
	do
	{
		psnakeNode cur = ps->pSnake;
		//随机生成食物坐标
        x = rand() % 54 + 2;
        y = rand() % 25 + 1;
		if (x % 2 == 1)
			x--;
		//食物不能和蛇身重叠
		while (cur&&(cur->x!=x&&cur->y!=y))
		{
			cur = cur->next;
		}
	} while (cur);

	//创建食物和打印食物
	psnakeNode food=(psnakeNode)malloc(sizeof(snakeNode));
	if (food == NULL)
	{
		perror("CreateFood(psnake ps) malloc:");
		return;
	}
	food->x = x;
	food->y = y;
	food->next = NULL;
	ps->pFood = food;
	//打印食物
	setPos(food->x, food->y);
	wprintf(L"%lc", FOOD);
}

由于墙面和蛇身是宽字符,占两个字节,因此食物的横坐标需要一定的限制,否则可能会出现问题。

程序要从主函数开始运行,主函数的代码很简短

void game()
{
	char ch;
	//循环可以多次进行游戏
	do
	{
		snake s = { 0 };
		GameStart(&s);//开始前
		GameRun(&s);  //进行中
		GameEnd(&s);  //结束后
		setPos(30, 17);
		Sleep(2000);  //休眠2s
		printf("再玩一次(Y/N)");
		ch = getchar();
		getchar();
	} while (ch=='Y'||ch=='y');
}

int main()
{
	setlocale(LC_ALL, "");  //修改适配本地环境
	game();
	setPos(0, 28);          
	return 0;
}

GameStart函数的实现如下

//游戏开始前
void GameStart(psnake ps)
{
	//设置窗口大小和窗口名
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");

	//隐藏光标
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursorInfo;
	GetConsoleCursorInfo(handle, &cursorInfo);
	cursorInfo.bVisible = false;
	SetConsoleCursorInfo(handle, &cursorInfo);

	WelcomeToGame();//基本游戏信息
	GetMap();//绘制地图
	InitSnake(ps);//初始化蛇
	CreateFood(ps);//创建食物
}

2.游戏中

游戏进行时,需要控制蛇的移动,需要打印一些帮助信息以及分数信息
当我们调整方向时,需要判断下一个位置处是不是食物,如果是就吃掉并生成新的食物,如果不是食物就从该位置开始打印蛇身,并释放掉尾节点。走一步就休眠0.2s,套上循环,就实现了蛇的运动。

在进行游戏时,需要检测按键是否被按下
实现一个宏函数来完整这一操作

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)

游戏进行时的完整代码如下

void GameRun(psnake ps)
{
	//打印帮助信息
	setPos(65, 15);
	printf("空格暂停游戏,Esc退出游戏");
	setPos(65, 16);
	printf("数字1加速,数字0减速");
	do
	{
		//打印分数
		setPos(65, 10);
		printf("总分:%d",ps->sum_sorce);
		setPos(65, 12);
		printf("食物分数:%02d", ps->food_sorce);
		//帮助信息

		//检测上下左右
		if (KEY_PRESS(VK_UP) && ps->direct != DOWN)
		{
			ps->direct = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && ps->direct != UP)
		{
			ps->direct = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->direct != RIGHT)
		{
			ps->direct = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && ps->direct != LEFT)
		{
			ps->direct = RIGHT;
		}
		//空格暂停游戏
		else if (KEY_PRESS(VK_SPACE))
		{
			while (1)
			{
				Sleep(100);
				if (KEY_PRESS(VK_SPACE))
				{
					break;
				}
			}
		}
		//Esc退出游戏
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->status = ESC;
			break;
		}
		//数字1加速
		else if (KEY_PRESS(VK_NUMPAD1))
		{
			if (ps->sleep > 25)
			{
				ps->sleep -= 25;
				ps->food_sorce += 5;
			}
		}
		//数字0减速
		else if(KEY_PRESS(VK_NUMPAD0))
		{
			if (ps->food_sorce > 5)
			{
				ps->food_sorce -= 5;
				ps->sleep += 25;
			}
		}
		//移动一步
		SnakeMove(ps);
		Sleep(ps->sleep);
	} while (ps->status == OK);
}

(1)根据蛇头的坐标和⽅向,计算下⼀个节点的坐标


    //ps:psnake类型的形参
	//创建下一个结点
	psnakeNode nextNode = (psnakeNode)malloc(sizeof(snakeNode));
	if (nextNode == NULL)
	{
		perror("SnakeMove(psnake ps) malloc");
		return;
	}
	nextNode->next = NULL;

	//确定下个结点坐标
	switch (ps->direct)
	{
	case UP:
		nextNode->x = ps->pSnake->x;
		nextNode->y = ps->pSnake->y - 1;
		break;
	case DOWN:
		nextNode->x = ps->pSnake->x;
		nextNode->y = ps->pSnake->y + 1;
		break;
	case LEFT:
		nextNode->x = ps->pSnake->x - 2;
		nextNode->y = ps->pSnake->y;
		break;
	case RIGHT:
		nextNode->x = ps->pSnake->x + 2;
		nextNode->y = ps->pSnake->y - 1;
		break;
	}

(2)下⼀个节点是⻝物

//下一个结点位置处是食物
	if (nextNode->x == ps->pFood->x && nextNode->y == ps->pFood->y)
	{
	    //头插到蛇身
		nextNode->next = ps->pSnake;
		ps->pSnake = nextNode;
		//修改分数
		ps->sum_sorce += ps->food_sorce;
		psnakeNode cur = ps->pSnake;
		//重新打印蛇身
		while (cur)
		{
			setPos(cur->x, cur->y);
			wprintf(L"%lc", SNAKE);
			cur = cur->next;
		}
		//释放旧的食物
		free(ps->pFood);
		//生成新的食物
		CreateFood(ps);
	}

(3)不是⻝物,尾巴删除⼀节

	//下一个结点处不是食物
	else
	{
	    //头插到蛇头
		nextNode->next = ps->pSnake;
		ps->pSnake = nextNode;

		psnakeNode cur = ps->pSnake;
		//找尾结点并同时打印蛇身
		while (cur->next->next)
		{
			setPos(cur->x, cur->y);
			wprintf(L"%lc", SNAKE);
			cur = cur->next;
		}
		//释放尾结点
		setPos(cur->next->x, cur->next->y);
		printf("  ");

		free(cur->next);
		cur->next = NULL;
	}

(4) 判断是否撞墙-Kill_By_Wall

//检测是否撞墙
void Kill_By_Wall(psnake ps)
{
    //满足其一就撞墙
	if (ps->pSnake->x == 0 ||
		ps->pSnake->x == 56 ||
		ps->pSnake->y == 0 ||
		ps->pSnake->y == 26)
	{
	    //修改游戏状态
		ps->status = KILLED_BY_WALL;
	}
}

(5)判断是否装上⾃⼰-Kill_By_Self

//检测是否咬到自己
void Kill_By_Self(psnake ps)
{
	psnakeNode cur = ps->pSnake->next;
	//从蛇第二个结点开始遍历
	while (cur)
	{
	    //坐标完全相同,即咬到自己了
		if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
		{
		    //修改游戏状态
			ps->status = KILLED_BY_SEIF;
			return;
		}
		cur = cur->next;
	}
}

当完成到这一步时,游戏已经能运行起来了,但是游戏结束后,玩家是如何结束游戏的(主动退出,撞墙,咬到自己)以及资源的释放等,这些任务还需要完成。

蛇的移动的完整代码如下

//蛇的移动
void SnakeMove(psnake ps)
{
	//创建下一个结点
	psnakeNode nextNode = (psnakeNode)malloc(sizeof(snakeNode));
	if (nextNode == NULL)
	{
		perror("SnakeMove(psnake ps) malloc");
		return;
	}
	nextNode->next = NULL;

	//确定下个结点坐标
	switch (ps->direct)
	{
	case UP:
		nextNode->x = ps->pSnake->x;
		nextNode->y = ps->pSnake->y - 1;
		break;
	case DOWN:
		nextNode->x = ps->pSnake->x;
		nextNode->y = ps->pSnake->y + 1;
		break;
	case LEFT:
		nextNode->x = ps->pSnake->x - 2;
		nextNode->y = ps->pSnake->y;
		break;
	case RIGHT:
		nextNode->x = ps->pSnake->x + 2;
		nextNode->y = ps->pSnake->y - 1;
		break;
	}

	//下一个结点位置处是食物
	if (nextNode->x == ps->pFood->x && nextNode->y == ps->pFood->y)
	{
		nextNode->next = ps->pSnake;
		ps->pSnake = nextNode;
		ps->sum_sorce += ps->food_sorce;
		psnakeNode cur = ps->pSnake;
		while (cur)
		{
			setPos(cur->x, cur->y);
			wprintf(L"%lc", SNAKE);
			cur = cur->next;
		}

		free(ps->pFood);
		CreateFood(ps);

	}
	//下一个结点处不是食物
	else
	{
		nextNode->next = ps->pSnake;
		ps->pSnake = nextNode;

		psnakeNode cur = ps->pSnake;
		while (cur->next->next)
		{
			setPos(cur->x, cur->y);
			wprintf(L"%lc", SNAKE);
			cur = cur->next;
		}
		setPos(cur->next->x, cur->next->y);
		printf("  ");

		free(cur->next);
		cur->next = NULL;
	}
	//检测是否撞墙
	Kill_By_Wall(ps);
	//检测是否咬到自己
	Kill_By_Self(ps);

}

3.游戏后

当游戏结束后,需要进行以下的一些操作。

游戏结束后的完整代码

//游戏结束
void GameEnd(psnake ps)
{
	setPos(30, 15);
	switch (ps->status)
	{
	case ESC:
		printf("正常退出游戏!");
		break;
	case KILLED_BY_WALL:
		printf("撞墙,游戏结束!");
		break;
	case KILLED_BY_SEIF:
		printf("咬到自己,游戏结束!");
		break;
	}

	psnakeNode del = ps->pSnake;
	psnakeNode cur = NULL;
	while (del)
	{
		cur = del->next;
		free(del);
		del = cur;
	}
	ps->pSnake = NULL;
	free(ps->pFood);
	ps->pFood = NULL;
}

(1)告知结束原因

switch-case语句就可以解决这个问题

    setPos(30, 15);
	switch (ps->status)
	{
	case ESC:
		printf("正常退出游戏!");
		break;
	case KILLED_BY_WALL:
		printf("撞墙,游戏结束!");
		break;
	case KILLED_BY_SEIF:
		printf("咬到自己,游戏结束!");
		break;
	}

(2)资源释放

由于结点是从堆上开辟的,所以游戏结束后需要主动去free掉这些内存并将指针置NULL。

	psnakeNode del = ps->pSnake;
	psnakeNode cur = NULL;
	while (del)
	{
		cur = del->next;
		free(del);
		del = cur;
	}
	ps->pSnake = NULL;
	free(ps->pFood);
	ps->pFood = NULL;

4.源代码

分为三个文件

snake.h

#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<Windows.h>
#include<locale.h>
//←→↑↓¥@

#define WALL L'墙'
#define SNAKE L'@'
#define FOOD L'¥'
#define SNAKE_X 24         
#define SNAKE_Y 5         

//宏函数,检测键盘某按键是否被按下
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)

//枚举出所有方向
enum DIRECTION
{
	UP=1,
	DOWN,
	LEFT,
	RIGHT
};

//枚举出游戏的所有状态
enum GAME_STATUS
{
	OK=1,           //正常
	ESC,            //按Esc退出
	KILLED_BY_WALL, //撞墙退出
	KILLED_BY_SEIF  //咬到自己
};

//蛇身结点结构体
typedef struct snakeNode
{
	int x;
	int y;
	struct snakeNode* next;
}snakeNode,*psnakeNode;


//维护蛇
typedef struct snake
{
	psnakeNode pSnake;   //维护蛇的指针
	psnakeNode pFood;    //维护食物的指针
	int sum_sorce;       //总的分数
	int food_sorce;      //每个食物的分数
	int sleep;           //走一步休息的时间,决定速度 
	enum DIRECTION direct;    //蛇当前的方向
	enum GAME_STATUS status;  //游戏当前的状态
}snake,*psnake;

//游戏开始前
void GameStart(psnake ps);
//欢迎界面
void WelcomeToGame();
//绘制地图
void GetMap();
//初始化蛇
void InitSnake(psnake ps);
//创建食物
void CreateFood(psnake ps);
//游戏进行中
void GameRun(psnake ps);
//蛇的移动
void SnakeMove(psnake ps);
//修改光标位置
void setPos(int x, int y);
//检测是否撞墙
void Kill_By_Wall(psnake ps);
//检测是否咬到自己
void Kill_By_Self(psnake ps);
//游戏结束
void GameEnd(psnake ps);

snake.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"

//改变光标位置
void setPos(int x, int y)
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x,y };
	SetConsoleCursorPosition(handle, pos);
}
//欢迎界面
void WelcomeToGame()
{
	setPos(50, 15);
	printf("欢迎来到贪吃蛇小游戏!");
	setPos(53, 25);
	system("pause");//暂停
	system("cls");	//清屏

	setPos(40, 15);
	printf("游戏规则:用← → ↑ ↓ 控制蛇的移动,吃食物(¥)获得分数");
	setPos(49, 16);
	printf("空格暂停游戏,Esc退出游戏,数字0减速,数字1加速");
	setPos(49, 17);
	printf("改变速度可以改变食物分数");
	setPos(53, 25);
	system("pause"); 
	system("cls");   
}
//绘制地图
void GetMap()
{
	//地图上方
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//地图下方
	setPos(0, 26);
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//地图两边
	for (int i = 1; i < 27; i++)
	{
		setPos(0, i);
		wprintf(L"%lc", WALL);
		setPos(56, i);
		wprintf(L"%lc", WALL);
	}
}
//初始化蛇
void InitSnake(psnake ps)
{
	psnakeNode cur = NULL;
	//默认蛇长为5
	for (int i = 0; i < 5; i++)
	{
		cur = (psnakeNode)malloc(sizeof(snakeNode));
		if (cur == NULL)
		{
			perror("InitSnake(psnake ps) malloc:");
			return;
		}
		cur->x = SNAKE_X + 2 * i;
		cur->y = SNAKE_Y;
		cur->next = NULL;

		//头插法创建蛇
		if (ps->pSnake == NULL)
		{
			ps->pSnake = cur;
		}
		else
		{
			cur->next = ps->pSnake;
			ps->pSnake = cur;
		}
	}

	cur = ps->pSnake; //cur指向蛇头

	//打印蛇身
	while (cur)
	{
		setPos(cur->x, cur->y);
		wprintf(L"%lc", SNAKE);
		cur = cur->next;
	}

	//初始化其他信息
	ps->direct = RIGHT;  //方向默认为右
	ps->pFood = NULL;    
	ps->food_sorce = 15; //食物分数为15
	ps->sleep = 100;     //休眠时间0.1s
	ps->sum_sorce = 0;   //初始总分为0分
	ps->status = OK;     //初始游戏状态正常
}
//创建食物
void CreateFood(psnake ps)
{    
	int x = 0;
	int y = 0;
	psnakeNode cur = NULL;
	do
	{
		psnakeNode cur = ps->pSnake;
		//随机生成食物坐标
        x = rand() % 54 + 2;
        y = rand() % 25 + 1;
		if (x % 2 == 1)
			x--;
		//食物不能和蛇身重叠
		while (cur&&(cur->x!=x&&cur->y!=y))
		{
			cur = cur->next;
		}
	} while (cur);

	//创建食物和打印食物
	psnakeNode food=(psnakeNode)malloc(sizeof(snakeNode));
	if (food == NULL)
	{
		perror("CreateFood(psnake ps) malloc:");
		return;
	}
	food->x = x;
	food->y = y;
	food->next = NULL;
	ps->pFood = food;
	setPos(food->x, food->y);
	wprintf(L"%lc", FOOD);
}
//游戏开始前
void GameStart(psnake ps)
{
	//设置窗口大小和窗口名
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");

	//隐藏光标
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursorInfo;
	GetConsoleCursorInfo(handle, &cursorInfo);
	cursorInfo.bVisible = false;
	SetConsoleCursorInfo(handle, &cursorInfo);

	WelcomeToGame();//基本游戏信息
	GetMap();//绘制地图
	InitSnake(ps);//初始化蛇
	CreateFood(ps);//创建食物
}
//检测是否撞墙
void Kill_By_Wall(psnake ps)
{
	if (ps->pSnake->x == 0 ||
		ps->pSnake->x == 56 ||
		ps->pSnake->y == 0 ||
		ps->pSnake->y == 26)
	{
		ps->status = KILLED_BY_WALL;
	}
}
//检测是否咬到自己
void Kill_By_Self(psnake ps)
{
	psnakeNode cur = ps->pSnake->next;
	while (cur)
	{
		if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
		{
			ps->status = KILLED_BY_SEIF;
			return;
		}
		cur = cur->next;
	}
}
//蛇的移动
void SnakeMove(psnake ps)
{
	//创建下一个结点
	psnakeNode nextNode = (psnakeNode)malloc(sizeof(snakeNode));
	if (nextNode == NULL)
	{
		perror("SnakeMove(psnake ps) malloc");
		return;
	}
	nextNode->next = NULL;

	//确定下个结点坐标
	switch (ps->direct)
	{
	case UP:
		nextNode->x = ps->pSnake->x;
		nextNode->y = ps->pSnake->y - 1;
		break;
	case DOWN:
		nextNode->x = ps->pSnake->x;
		nextNode->y = ps->pSnake->y + 1;
		break;
	case LEFT:
		nextNode->x = ps->pSnake->x - 2;
		nextNode->y = ps->pSnake->y;
		break;
	case RIGHT:
		nextNode->x = ps->pSnake->x + 2;
		nextNode->y = ps->pSnake->y - 1;
		break;
	}

	//下一个结点位置处是食物
	if (nextNode->x == ps->pFood->x && nextNode->y == ps->pFood->y)
	{
		nextNode->next = ps->pSnake;
		ps->pSnake = nextNode;
		ps->sum_sorce += ps->food_sorce;
		psnakeNode cur = ps->pSnake;
		while (cur)
		{
			setPos(cur->x, cur->y);
			wprintf(L"%lc", SNAKE);
			cur = cur->next;
		}

		free(ps->pFood);
		CreateFood(ps);

	}
	//下一个结点处不是食物
	else
	{
		nextNode->next = ps->pSnake;
		ps->pSnake = nextNode;

		psnakeNode cur = ps->pSnake;
		while (cur->next->next)
		{
			setPos(cur->x, cur->y);
			wprintf(L"%lc", SNAKE);
			cur = cur->next;
		}
		setPos(cur->next->x, cur->next->y);
		printf("  ");

		free(cur->next);
		cur->next = NULL;
	}
	//检测是否撞墙
	Kill_By_Wall(ps);
	//检测是否咬到自己
	Kill_By_Self(ps);

}
//游戏进行中
void GameRun(psnake ps)
{
	//打印帮助信息
	setPos(65, 15);
	printf("空格暂停游戏,Esc退出游戏");
	setPos(65, 16);
	printf("数字1加速,数字0减速");
	do
	{
		//打印分数
		setPos(65, 10);
		printf("总分:%d",ps->sum_sorce);
		setPos(65, 12);
		printf("食物分数:%02d", ps->food_sorce);
		//帮助信息

		//检测上下左右
		if (KEY_PRESS(VK_UP) && ps->direct != DOWN)
		{
			ps->direct = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && ps->direct != UP)
		{
			ps->direct = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->direct != RIGHT)
		{
			ps->direct = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && ps->direct != LEFT)
		{
			ps->direct = RIGHT;
		}
		//空格暂停游戏
		else if (KEY_PRESS(VK_SPACE))
		{
			while (1)
			{
				Sleep(100);
				if (KEY_PRESS(VK_SPACE))
				{
					break;
				}
			}
		}
		//Esc退出游戏
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->status = ESC;
			break;
		}
		//数字1加速
		else if (KEY_PRESS(VK_NUMPAD1))
		{
			if (ps->sleep > 25)
			{
				ps->sleep -= 25;
				ps->food_sorce += 5;
			}
		}
		//数字0减速
		else if(KEY_PRESS(VK_NUMPAD0))
		{
			if (ps->food_sorce > 5)
			{
				ps->food_sorce -= 5;
				ps->sleep += 25;
			}
		}
		//移动一步
		SnakeMove(ps);
		Sleep(ps->sleep);
	} while (ps->status == OK);
}
//游戏结束
void GameEnd(psnake ps)
{
	setPos(30, 15);
	switch (ps->status)
	{
	case ESC:
		printf("正常退出游戏!");
		break;
	case KILLED_BY_WALL:
		printf("撞墙,游戏结束!");
		break;
	case KILLED_BY_SEIF:
		printf("咬到自己,游戏结束!");
		break;
	}

	psnakeNode del = ps->pSnake;
	psnakeNode cur = NULL;
	while (del)
	{
		cur = del->next;
		free(del);
		del = cur;
	}
	ps->pSnake = NULL;
	free(ps->pFood);
	ps->pFood = NULL;
}



test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"

void game()
{
	char ch;
	do
	{
		snake s = { 0 };
		GameStart(&s);
		GameRun(&s);
		GameEnd(&s);
		setPos(30, 17);
		Sleep(2000);
		printf("再玩一次(Y/N)");
		ch = getchar();
		getchar();
	} while (ch=='Y'||ch=='y');
}

int main()
{
	setlocale(LC_ALL, "");
	game();
	setPos(0, 28);
	return 0;
}

以上就是C实现贪吃蛇的全过程,有适合修改或者错误的地方,欢迎大家发现并指出!

  • 11
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
用windows api 做的贪吃蛇 #include #include"resource.h" #include"Node.h" #include #include TCHAR szAppname[] = TEXT("Snack_eat"); #define SIDE (x_Client/80) #define x_Client 800 #define y_Client 800 #define X_MAX 800-20-SIDE //点x的范围 #define Y_MAX 800-60-SIDE //点y的范围 #define TIME_ID 1 #define SECOND 100 #define NUM_POINT 10 //点的总个数 #define ADD_SCORE 10 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwnd; //窗口句柄 MSG msg; //消息 WNDCLASS wndclass; //窗口类 HACCEL hAccel;//加速键句柄 wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口的水平和垂直尺寸被改变时,窗口被重绘 wndclass.lpfnWndProc = WndProc; //窗口过程为WndProc函数 wndclass.cbClsExtra = 0; //预留额外空间 wndclass.cbWndExtra = 0; //预留额外空间 wndclass.hInstance = hInstance; //应用程序的实例句柄,WinMain的第一个参数 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //设置图标 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //载入预定义的鼠标指针 wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //设置画刷 wndclass.lpszMenuName = szAppname; //设置菜单 wndclass.lpszClassName = szAppname; //设置窗口类的名字 if (!RegisterClass(&wndclass))//注册窗口类 { MessageBox(NULL, TEXT("这个程序需要windows NT!"), szAppname, MB_ICONERROR); return 0; } hwnd = CreateWindow(szAppname, TEXT("Snack_eat"),//CreateWindow函数调用时,WndProc将受到WM_CREATE WS_OVERLAPPEDWINDOW&~WS_THICKFRAME& ~WS_MAXIMIZEBOX,//普通的层叠窗口&禁止改变大小&禁止最大化 CW_USEDEFAULT, //初始x坐标(默认) CW_USEDEFAULT, //初始y坐标 x_Client, //初始x方向尺寸 770 y_Client, //初始y方向尺寸 750 NULL, //父窗口句柄 NULL, //窗口菜单句柄 hInstance, //程序实例句柄 WinMain函数中第二个参数 NULL); //创建参数 ShowWindow(hwnd, iCmdShow);//显示窗口,iCmdShow是WinMain的第四个参数,决定窗口在屏幕中的初始化显示形式,例:SW_SHOWNORMAL表示正常显示 UpdateWindow(hwnd);//使窗口客户区重绘,通过向WndProc发送一条WM_PAINT消息而完成的 hAccel = LoadAccelerators(hInstance, szAppname);//加载加速键 while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(hwnd, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }/* while (GetMessage(&msg, NULL, 0, 0))//GetMessage函数从消息队列中得到消息,填充msg。如果msg.message等于WM_QUIT,返回0,否则返回非0 { TranslateMessage(&msg);//将msg返回给windows已进行某些键盘消息的转换 DispatchMessage(&msg);//将msg再次返回给windows }*/ return msg.wParam;//msg.wParam是PostQuitMessage函数的参数值,通常是0 } ...
以下是Linux C语言实现贪吃蛇的基本步骤: 1. 定义蛇的结构体,包括蛇头、蛇身和蛇尾等信息。 2. 初始化蛇的位置和长度。 3. 定义食物的结构体,包括食物的位置和分值等信息。 4. 随机生成食物的位置。 5. 定义蛇的移动函数,包括蛇头的移动、蛇身的移动和蛇尾的删除等操作。 6. 定义蛇的死亡判断函数,包括蛇头是否撞墙和蛇头是否撞到蛇身等情况。 7. 定义游戏主循环,包括监听用户输入、移动蛇、判断蛇是否死亡、更新分数和重新生成食物等操作。 8. 在游戏结束后,清理蛇和食物的数据。 以下是一个简单的Linux C语言实现贪吃蛇的代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #include <termios.h> #define ROW 20 #define COL 20 struct snake { int row; int col; struct snake *next; }; struct food { int row; int col; int score; }; struct snake *head = NULL; struct food food; void init_snake() { struct snake *p1 = (struct snake *)malloc(sizeof(struct snake)); p1->row = 5; p1->col = 5; p1->next = NULL; head = p1; struct snake *p2 = (struct snake *)malloc(sizeof(struct snake)); p2->row = 5; p2->col = 6; p2->next = NULL; p1->next = p2; } void init_food() { srand(time(NULL)); food.row = rand() % ROW; food.col = rand() % COL; food.score = 10; } void draw() { system("clear"); printf("score: %d\n", food.score); for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { if (i == food.row && j == food.col) { printf("*"); } else { struct snake *p = head; int flag = 0; while (p != NULL) { if (p->row == i && p->col == j) { printf("#"); flag = 1; break; } p = p->next; } if (flag == 0) { printf(" "); } } } printf("\n"); } } void add_snake_node(int row, int col) { struct snake *p = (struct snake *)malloc(sizeof(struct snake)); p->row = row; p->col = col; p->next = head; head = p; } void delete_snake_node() { struct snake *p = head; while (p->next->next != NULL) { p = p->next; } free(p->next); p->next = NULL; } void move_snake(int direction) { int row = head->row; int col = head->col; switch (direction) { case 1: row--; break; case 2: row++; break; case 3: col--; break; case 4: col++; break; default: break; } add_snake_node(row, col); if (row == food.row && col == food.col) { food.score += 10; init_food(); } else { delete_snake_node(); } } int snake_die() { if (head->row < 0 || head->row >= ROW || head->col < 0 || head->col >= COL) { return 1; } struct snake *p = head->next; while (p != NULL) { if (p->row == head->row && p->col == head->col) { return 1; } p = p->next; } return 0; } int getch() { struct termios tm, tm_old; int fd = STDIN_FILENO, c; if (tcgetattr(fd, &tm) < 0) { return -1; } tm_old = tm; cfmakeraw(&tm); if (tcsetattr(fd, TCSANOW, &tm) < 0) { return -1; } c = getchar(); if (tcsetattr(fd, TCSANOW, &tm_old) < 0) { return -1; } return c; } int main() { init_snake(); init_food(); int direction = 2; while (1) { draw(); int c = getch(); if (c == 'w' && direction != 2) { direction = 1; } else if (c == 's' && direction != 1) { direction = 2; } else if (c == 'a' && direction != 4) { direction = 3; } else if (c == 'd' && direction != 3) { direction = 4; } move_snake(direction); if (snake_die()) { printf("Game Over!\n"); break; } usleep(200000); } return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值