C语言代码实现贪吃蛇(控制台程序)

本文详细描述了贪吃蛇游戏的结构体设计,包括蛇身节点、游戏状态管理、控制台初始化、键盘输入处理、地图绘制、蛇和食物的初始化以及游戏结束时的内存释放。展示了如何使用C语言实现一个基础的控制台贪吃蛇游戏。
摘要由CSDN通过智能技术生成

1.游戏结构体的定义

1.1蛇身节点的定义

贪吃蛇的身体我们需要用到链表的格式,结构体中存储横纵坐标x,y和指向自身的指针next。

控制台的整体坐标逻辑:

typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode,*pSnakeNode;

1.2游戏管理各项内容的结构体

游戏过程我们需要一个结构体来管理整个游戏的状态,它需要有:

①指向蛇头的指针

②指向食物的指针

③当前累计得分

④当前一个食物多少分

⑤蛇移动的速度

⑥游戏当前的状态

需要枚举常量来表示

enum GAME_STATUS
{
	OK = 1,
    ESC,
	KILL_BY_WALL,
	KILL_BY_SELF
};

⑦蛇移动的方向

需要枚举常量来表示

enum DIR
{
    UP=1,
    DOWN,
    LEFT,
    RIGHT
};

根据以上七点,我们得出总游戏的结构体:

typedef struct Snake
{
	pSnakeNode SHead;
	pSnakeNode Food;
	int Score;
	int FoodWeight;
	int SleepTime;
	enum GAME_STATUS Stauts;
	enum DIR dir;
}Snake,*pSnake;

2.游戏开始前的初始化

2.1本地地区化

因为打印地图,蛇身,食物需要用到宽字符,我们需要先进行本地化

头文件加#include<locale.h>,

setlocale(LC_ALL, "");

2.2设置控制台窗口大小,窗口名字,隐藏闪烁的光标

2.2.1窗口大小

我们设置横坐标0到150,纵坐标0到40

system("mode con cols=150 lines=40");

2.2.2窗口名字

system("title 贪吃蛇");

2.2.3隐藏闪烁的光标

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo = { 0 };
GetConsoleCursorInfo(handle, &CursorInfo);
CursorInfo.bVisible = false;
SetConsoleCursorInfo(handle, &CursorInfo);

2.3打印欢迎信息

2.3.1第一个,欢迎界面

我们要打印内容到控制台我们需要的坐标位置,就需要创建一个函数SetPos来帮助我们实现定位操作

void SetPos(int x, int y)
{
	HANDLE hOutput = NULL;
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x,y };
	SetConsoleCursorPosition(hOutput, pos);
}

完成以后再进行第一个界面的打印就会方便得多

SetPos(60, 17);
printf("欢迎来到贪吃蛇小游戏");
SetPos(63, 29);
system("pause");
system("cls");

2.3.2第二个,规则界面

SetPos(54, 16);
printf("用↑,↓,←,→控制蛇移动方向");
SetPos(54, 17);
printf("F3为加速,F4为减速,加速可以提高得分");
SetPos(63, 29);
system("pause");
system("cls");

2.4绘制地图

对于地图使用的符号,我们暂定

#define WALL L'□'

我们的目标是打印横坐标0到80,纵坐标0到30的地图

void CreatMap()
{
	int i = 0;
	//上
	SetPos(0, 0);
	for (i = 0; i <= 80; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 30);
	for (i = 0; i <= 80; i+=2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 0; i <= 30; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 0; i <= 30; i++)
	{
		SetPos(80, i);
		wprintf(L"%lc", WALL);
	}
}

2.5初始化蛇(同时完成游戏总结构体其他信息的初始化)

对于蛇身使用的符号,我们暂定

#define BODY L'●'

对于蛇头的起始坐标,我们暂定

#define POS_X 34
#define POS_Y 15

初始化:

过程中涉及到的影响速度的初始休眠时间,我们暂定

#define SLEEPTIME 200
void InitSnake(pSnake ps)
{
	int i = 0;

	for (i = 0; i < 5; i++)
	{
		pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake():malloc");
			return;
		}
		cur->x = POS_X + 2 * i;
		cur->y = POS_Y;
		cur->next = NULL;
		if (ps->SHead == NULL)
		{
			ps->SHead = cur;
		}
		else
		{
			cur->next = ps->SHead;
			ps->SHead = cur;
		}
	}
	//建立完成,开始打印蛇身
	pSnakeNode cur = NULL;
	for (cur = ps->SHead; cur; cur = cur->next)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
	}
	//顺便完成其他信息的初始化
	ps->dir = UP;
	ps->Food = NULL;
	ps->FoodWeight = 10;
	ps->Score = 0;
	ps->SleepTime = SLEEPTIME;
	ps->Stauts = OK;
}

2.6初始化食物

对于食物,我们暂定

#define FOOD L'¥'

过程中,我们需要完成两件事

①确定一个可以用的坐标

②向内存申请节点,存储到我们总结构体的FOOD中去

void InitFood(pSnake ps)
{
	//先确定一个可用的x,y坐标

	int x;
	int y;
again:
	x = 0;
	y = 0;
	//横坐标不能为偶数
	do
	{
		x = rand() % 77 + 2;
		y = rand() % 29 + 1;
	} while (x % 2 != 0);
	//不能出现在蛇身上
	pSnakeNode cur = ps->SHead;
	while (cur)
	{
		if ((cur->x) == x && (cur->y) == y)
			goto again;
		cur = cur->next;
	}
	pSnakeNode pfood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pfood == NULL)
	{
		perror("InitFood():malloc");
		return;
	}
	pfood->x = x;
	pfood->y = y;
	pfood->next = NULL;
	ps->Food = pfood;
	SetPos(pfood->x, pfood->y);
	wprintf(L"%lc", FOOD);

}

3.游戏过程中的操作

3.1在游戏过程中,始终显示规则信息

可在此处注明版权信息

void PrintHelpInfo()
{
    SetPos(100, 15);
    printf("不能穿墙,不能咬到自己");
    SetPos(100, 16);
    printf("用↑,↓,←,→控制蛇移动方向");
    SetPos(100, 17);
    printf("F3为加速,F4为减速,加速可以提高得分");
    SetPos(100, 18);
    printf("ESC为退出,space为暂停");
    SetPos(100, 20);
    printf("@版权归@九州~空城所有");

}

3.2游戏过程的循环

3.2.1每次更新总分信息和每个食物分值信息

SetPos(100, 10);
printf("当前总分是%5d", ps->Score);
SetPos(100, 11);
printf("当前每个食物的分值%2d", ps->FoodWeight);

3.2.2检测按键是否被按过,并做对应的修改

需要定义一个宏,来检测按键是否被按过

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

然后就可以进行操作了

if (KEY_PRESS(VK_UP) && (ps->dir != DOWN))
{
	ps->dir = UP;
}
if (KEY_PRESS(VK_DOWN) && (ps->dir != UP))
{
	ps->dir = DOWN;
}
if (KEY_PRESS(VK_LEFT) && (ps->dir != RIGHT))
{
	ps->dir = LEFT;
}
if (KEY_PRESS(VK_RIGHT) && (ps->dir != LEFT))
{
	ps->dir = RIGHT;
}
if (KEY_PRESS(VK_ESCAPE))
{
	ps->Stauts = ESC;
	break;
}
if (KEY_PRESS(VK_SPACE))
{
	GameSpace();
}
if (KEY_PRESS(VK_F3))
{
	if ((ps->SleepTime) >= 80)
	{
		ps->SleepTime -= 30;
		ps->FoodWeight += 2;
	}
}
if (KEY_PRESS(VK_F4))
{
	if ((ps->FoodWeight) >2)
	{
		ps->SleepTime += 30;
		ps->FoodWeight -= 2;
	}
}

3.2.3睡眠一下(实现移动时的停顿)

Sleep(ps->SleepTime);

3.2.4走一步

需要注意走的下一步是不是食物

void SnakeMove(pSnake ps)
{
	pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNext == NULL)
	{
		perror("SnakeMove():malloc");
		return;
	}
	switch (ps->dir)
	{
	case UP:
		pNext->x = ps->SHead->x;
		pNext->y = ps->SHead->y - 1;
		break;
	case DOWN:
		pNext->x = ps->SHead->x;
		pNext->y = ps->SHead->y + 1;
		break;
	case LEFT:
		pNext->x = ps->SHead->x - 2;
		pNext->y = ps->SHead->y;
		break;
	case RIGHT:
		pNext->x = ps->SHead->x + 2;
		pNext->y = ps->SHead->y;
	}
	//下一个坐标是食物
	if ((pNext->x) == (ps->Food->x) && (pNext->y) == (ps->Food->y))
	{
		NextisFood(ps,pNext);
	}
	else
	{
		NextnoFood(ps, pNext);
	}
}
3.2.4.1走的下一步是食物

头插pNext,打印一次蛇,改变分数,再新建一个食物

void NextisFood(pSnake ps, pSnakeNode pNext)
{
	pNext->next = ps->SHead;
	ps->SHead = pNext;
	//再打印一次蛇
	pSnakeNode cur = ps->SHead;
	while(cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//分数改变
	ps->Score += ps->FoodWeight;
	free(ps->Food);
	//再新建一个食物
	InitFood(ps);
}
3.2.4.2走的下一步不是食物
void NextnoFood(pSnake ps, pSnakeNode pNext)
{
	pNext->next = ps->SHead;
	ps->SHead = pNext;
	pSnakeNode cur = ps->SHead;
	while (cur->next->next)
	{
		//顺带打印蛇身
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	SetPos(cur->next->x, cur->next->y);
	printf("  ");
	free(cur->next);
	cur->next = NULL;
}

3.2.5检测撞墙

只需要改变状态即可

void KillByWall(pSnake ps)
{
	if ((ps->SHead->x) == 0 || (ps->SHead->x == 80) || (ps->SHead->y == 0) || (ps->SHead->y == 30))
	{
		ps->Stauts = KILL_BY_WALL;
	}
}

3.2.6检测撞到自己

在while循环中,我们通过return可以直达函数外,避免直接改变状态可能导致的死循环

void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->SHead->next;
	while (cur)
	{
		if ((cur->x) == (ps->SHead->x) && (cur->y) == (ps->SHead->y))
		{
			ps->Stauts = KILL_BY_SELF;
			return;
		}
		cur = cur->next;
	}
}

4.游戏结束时内存的释放

释放时我们需要完成这三点

①注明蛇的死亡方式

②释放贪吃蛇节点

③释放食物节点

void GameEnd(pSnake ps)
{
	//说明怎么挂的
	SetPos(20, 32);
	switch (ps->Stauts)
	{
	case ESC:
		printf("游戏正常退出结束");
		break;
	case KILL_BY_SELF:
		printf("很遗憾,你咬到自己了,游戏结束");
		break;
	case KILL_BY_WALL:
		printf("很遗憾,你撞墙了,游戏结束");
	}
	//释放贪吃蛇节点
	pSnakeNode cur = ps->SHead;
	while (cur)
	{
		pSnakeNode tmp = cur;
		cur = cur->next;
		free(tmp);
	}
	ps->SHead = NULL;
	//释放食物节点
	free(ps->Food);
	ps = NULL;
}

5.源代码

注:需要将控制台程序-属性-终端中的默认终端应用程序更改为Windows控制台主机

有用的话不妨关注一下gitee

贪吃蛇/贪吃蛇 · 九州空城/c_code_yv - 码云 - 开源中国 (gitee.com)

  • 28
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值