贪吃蛇——C语言实现(详细代码讲解)

贪吃蛇——C语言实现

贪吃蛇是非常经典的一款游戏,本次我们模拟在控制台实现贪吃蛇游戏,也就是实现贪吃蛇的基本功能,比如在地图中,用“↑↓←→”控制移动蛇的方向,吃掉食物之后,蛇身体会变长等等。。。。

首先我们得分析,游戏中我们会碰见的一些情况。

①蛇的部分,蛇的身子是一节一节的,此时最容易联想到的数据结构就是顺序表,链表,如果把蛇比做顺序表或者链表,在之后吃到食物的时候,身子肯定会变长,这就涉及到插入的操作,所以为了更高的效率,我们用链表实现我们的蛇的部分,最初我们把蛇身子按照四个结点打印在屏幕。

②蛇的移动,在屏幕上面蛇的移动看起来是整个身子向前方平移一个单位,但是其原理是我们在屏幕的另一个地方把蛇从新打印一遍,又把之前的蛇身子去除掉。

③食物的产生,随机的在地图中产生一个节点,在蛇的头坐标和食物的坐标重复的时候,食物消失,蛇的身子加长,也就是蛇的节点数增加一个。

④蛇在其中的几种状态,正常状态:蛇头节点的坐标没有和墙的坐标以及自己身子的坐标重合,

被自己杀死:蛇头的坐标和蛇身子的坐标重合,

撞墙:蛇头的坐标和墙的坐标重合。

下面我们给出代码,在代码中解释整个游戏的简单运行

 

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <memory.h>
#include <string.h>
#define  FOOD  "█"//我们把蛇身子和食物,以及地图用黑框框表示,定义为宏
//蛇初始位置的坐标
#define  INIT_X 24
#define  INIT_Y 2
//设置光标位置,之后会使用这个函数来进行打印蛇身子,食物,地图等
void SetPos(int x,int y){
	HANDLE handle =  GetStdHandle(STD_OUTPUT_HANDLE);//把标准输出的句柄得到,并交给变量handle,
	COORD pos = {0};
	pos.X = x;
	pos.Y = y;
	SetConsoleCursorPosition(handle,pos);//设置handle的输出位置,
}
//画地图
void DrawMap(void){
	int i;
	for (i=0;i<58;i+=2)//画出地图的上边界,在控制界面每一个汉字,占两位,所以每次i+2

	{
		SetPos(i,0);
		printf(FOOD);
	}
	for (i=0;i<58;i+=2)//画出地图的下边界
	{
		SetPos(i,26);
		printf(FOOD);
	}
	for (i=0;i<26;i++)//画出地图的左边界
	{
		SetPos(0,i);
		printf(FOOD);
	}
	for (i=0;i<=26;i++)//画出地图的右边界
	{
		SetPos(58,i);
		printf(FOOD);
	}
} 
// 蛇身子节点,因为我们要打印蛇身子的每一个节点在地图上显示,所以结构体里面要有蛇身子结点的x,y坐标,
 typedef struct node{
 	int x;
	int y;
	struct node* next;//因为使用链表的结构,所以我们要储存每一个结点下一个结点的地址,构成一条链表
}SnakeNode,*pSnakeNode;
// 蛇的行走方向
enum DIRECTION{ UP=1,DOWN,LEFT,RIGHT};
//蛇的状态
enum  Status{OK,KILL_BY_SELF,KILL_BY_WALL,ESC};

// 蛇本身
typedef struct snake{
	pSnakeNode _pSnake;//蛇头指针
	pSnakeNode _pFood;//食物
	enum DIRECTION _Dir;//蛇行走的方向
	enum Status _Status;//蛇的当前状态
	int _SleepTime;//每走一步停留的时间
}Snake,*pSnake;

void Welcome();//我们在游戏开始之前做一个欢迎界面
void InitSnake(pSnake ps){//因为蛇是一条链表,所以在开始的时候我们要初始化它
	pSnakeNode cur = NULL;//这是我们设置的蛇身子结点
	int i = 0;
	cur = malloc(sizeof(SnakeNode));
	memset(cur,0x00,sizeof(SnakeNode));
	cur->next = NULL;
	cur->x = INIT_X;//设置蛇身子第一个人结点的位置
	cur->y = INIT_Y;
	for (i=1;i<=4;i++)//这个循环使用头插的方法最后一次出循环的时候,cur就指向第一个结点,也就是蛇头结点
	{
		ps->_pSnake = malloc(sizeof(SnakeNode));
		ps->_pSnake->next = cur;
		ps->_pSnake->x =INIT_X+i*2;
		ps->_pSnake->y = INIT_Y;
		cur = ps->_pSnake;
	}
	while (cur!=NULL)//按照每一个蛇身子结点里面的x,y坐标打印出整条蛇
	{
		SetPos(cur->x,cur->y);
		printf(FOOD);
		cur = cur->next;
	}
	ps->_Dir = RIGHT;//设置初始蛇的朝向向右
	ps->_SleepTime = 500;
	ps->_Status = OK;//设置蛇的初始状态是OK,不然就没法玩了
}
void CreatFood(pSnake ps){//食物是蛇结构体的其中一种状态,我们把食物拿到单独函数中初始化
	pSnakeNode cur = NULL;
	pSnakeNode food = NULL;//食物也是一个结点,所以用蛇结点的结构体定义
	food = malloc(sizeof(SnakeNode));
again:
	memset(food,0x00,sizeof(SnakeNode));
	do{
		food->x = rand()%56+2;//因为我们横着的墙的最后一个设置在58的位置,一个食物的标致占两位,所以我们%56+2控制食物的x坐标不会越界
	}while(food->x %2 != 0);
	food->y = rand()%25+1;//因为我们竖着的墙的最下的位置设置在26,所以%25+1
	cur = ps->_pSnake;
	while (cur!=NULL)//这个循环判断随机产生的食物有没有和蛇的位置重叠,如果有重叠,那就使用goto语句返回,重新产生一个
	{
		if(cur->x == food->x && cur->y == food->y)
		{
			goto again;
		}
		cur = cur->next;
	}
	ps->_pFood = food;
	SetPos(food->x,food->y);//设置好食物的坐标之后,再在屏幕的相应位置输出
	printf(FOOD);
}
void EatFood(pSnakeNode nNode,pSnake ps){
	pSnakeNode cur = ps->_pSnake;//创建一个结点,当作食物,蛇吃掉之后把它当作结点插入到蛇身子里面
	nNode->next = cur;
	ps->_pSnake = nNode;
	cur = ps->_pSnake;
	while (cur!=NULL)//结点插入之后在把整个链表,也就是蛇打印一遍
	{
		SetPos(cur->x,cur->y);
		printf(FOOD);
		cur = cur->next;
	}
	CreatFood(ps);//食物吃掉之后,在产生一个新食物
}
void NoFood(pSnakeNode nNode,pSnake ps){//如果移动的下一步没有食物,那么就把蛇在新的位置打印一遍
	pSnakeNode cur = ps->_pSnake;
	nNode->next = cur;
	ps->_pSnake = nNode;
	cur = ps->_pSnake;
	while (cur->next->next!=NULL)//因为蛇的长度是一定的,所以在新的位置打印之后,最后一个结点用空格代替,就产生了一条新蛇
	{
		SetPos(cur->x,cur->y);
		printf(FOOD);
		cur = cur->next;
	}
	SetPos(cur->x,cur->y);
	printf(FOOD);
	SetPos(cur->next->x,cur->next->y);//设置好最后一个位置的结点
	printf("  ");//用空格代替,那么在屏幕显示就是蛇向前走了一步
	free(cur->next);
	cur->next = NULL;
}
int NextHasFood(pSnakeNode nNode,pSnake ps){
	return ps->_pFood->x == nNode->x  && ps->_pFood->y == nNode->y;
}
void SnakeMove(pSnake ps){
	pSnakeNode  nNode = malloc(sizeof(SnakeNode));// 定义一个结点,赋给他蛇头结点的值,再根据下一步要走的方向,确定结点真正的值
	memset(nNode,0x00,sizeof(SnakeNode));
	nNode->x = ps->_pSnake->x;
	nNode->y = ps->_pSnake->y;
	switch (ps->_Dir)
	{
	case UP:
		nNode->y-=1;
		break;
	case DOWN:
		nNode->y+=1;
		break;
	case LEFT:
		nNode->x-=2;
		break;
	case RIGHT:
		nNode->x+=2;
		break;
	default:
		break;
	}
	if (NextHasFood(nNode,ps))//判断下一步有没有食物,
	{
		EatFood(nNode,ps);//有的话,就进入吃食物的操作函数
	} 
	else
	{
		NoFood(nNode,ps);//没有的话,就进入没有食物的操作
	}
}
int KillBySelf(pSnake ps){//用遍历判断蛇头是否和蛇身子的结点的坐标重合,重合就是吃到了自己
	pSnakeNode cur= ps->_pSnake->next;
	while (cur!=NULL)
	{
		if (cur->x == ps->_pSnake->x  && cur->y == ps->_pSnake->y)
		{
			return 1;
		}
		cur = cur->next;
	}
	return 0;
}
int KillByWall(pSnake ps){//如果蛇头结点的坐标和墙重合了,那就是撞墙了
	if(ps->_pSnake->x == 0  || ps->_pSnake->x == 58 || ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
		return 1;
	return 0;
}
void SnakeRun(pSnake ps){
	do 
	{
		if(GetAsyncKeyState(VK_UP) && ps->_Dir != DOWN){//判断键盘输入的如果是↑键,且蛇的方向没有向下,那就进入循环,把蛇的方向的状态改成向上
			ps->_Dir = UP;
		}
		if(GetAsyncKeyState(VK_DOWN) && ps->_Dir != UP){//判断键盘输入的如果是↓键,且蛇的方向没有向上,那就进入循环,把蛇的方向的状态改成向下

			ps->_Dir = DOWN;
		}
		if(GetAsyncKeyState(VK_LEFT) && ps->_Dir != RIGHT){//判断键盘输入的如果是左键,且蛇的方向没有向右,那就进入循环,把蛇的方向的状态改成向左

			ps->_Dir = LEFT;
		}
		if(GetAsyncKeyState(VK_RIGHT) && ps->_Dir != LEFT){//判断键盘输入的如果是右键,且蛇的方向没有向左,那就进入循环,把蛇的方向的状态改成向右

			ps->_Dir = RIGHT;
		}
		if(GetAsyncKeyState(VK_ESCAPE)){//如果键盘输入ESC键,那就状态改成退出
			ps->_Status = ESC;
		}
		SnakeMove(ps);//蛇的方向改了之后,那就向当前方向行走一步
		if (KillBySelf(ps))//判断当前是否会被自己咬死
		{
			ps->_Status = KILL_BY_SELF;
			SetPos(30,12);
			printf("自己你都想吃,你咋不上天!\n");
		} 
		if(KillByWall(ps))//判断是否会被墙撞死
		{
			ps->_Status = KILL_BY_WALL;
			SetPos(30,12);
			printf("不是吧!看见墙还往上撞!\n");
		}
		if(ps->_Status == ESC){
			printf("你竟然退出?\n");
		}
		Sleep(ps->_SleepTime);
	} while (ps->_Status == OK);//如果蛇的状态是OK那就一直进入判断,
}
void SnakeStart(pSnake ps){//开始之前的准备工作
	srand(time(NULL));
	system("mode con cols=100 lines=30");
	system("cls");
	DrawMap();//画地图
	InitSnake(ps);//初始化蛇。并画出
	CreatFood(ps);//创建一个食物
}
void Welcome(void){//欢迎界面
	system("mode con cols=100 lines=30");
	system("cls");
	SetPos(38,6);
	printf("welcome come to SnakeGame\n");
	SetPos(38,8);
	printf("↑↓←→control direction\n");
	SetPos(45,10);
	printf("ESC For Exit\n");
	SetPos(42,12);
	printf("宇哥科技倾情奉献\n");
	getchar();
	system("cls");
}
int main(){
	Snake s;
	memset(&s,0x00,sizeof(Snake));
	Welcome();
	SnakeStart(&s);
	SnakeRun(&s);
	return 0;
}

 

 

限于编者水平,有很多不足之处,欢迎来指正。

如需转载,请注明出处~!
 

 

  • 47
    点赞
  • 243
    收藏
    觉得还不错? 一键收藏
  • 21
    评论
用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 } ...
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值