贪吃蛇的实现(基于链表)

本文详细介绍了如何用C语言实现一个简单的贪吃蛇游戏,包括游戏测试、环境设置、蛇的初始化、运动控制、得分机制以及游戏结束处理。
摘要由CSDN通过智能技术生成

一.游戏介绍

二.游戏实现

1.test.c:游戏测试

1>设置适配本地的环境

2>在test()中完成游戏的测试逻辑

    1.创建贪吃蛇

    2.初始化游戏

    3.运行游戏

    4.结束游戏(善后工作)

3>代码实现

#include "snake.h"
#include <locale.h>


void test()
{
	system("cls");
	int op = 0;
	do
	{
		system("cls");
		Snake snake = { 0 };
		//初始化游戏
		GameStart(&snake);

		//运行游戏
		GameRun(&snake);

		//结束游戏
		GameEnd(&snake);
		SetPos(20, 14);
		printf("亲,还想再来一次吗(Y or N)?:");
		op = getchar();
		while(getchar()!='\n');//清理\n
	} while (op == 'Y' || op == 'y');
	SetPos(0, 27);
}
	
int main()
{
	//创建适配本地的环境
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));
	test();
	return 0;
}

2.snake.h:类型声明,函数声明

   snake.c:函数实现

1>创建贪吃蛇

    1.贪吃蛇节点的定义:包含坐标,指向下一个节点的坐标

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

    2.贪吃蛇的定义:蛇头位置,食物位置,

                                蛇的运动方向,蛇的运动状态,(枚举声明)

                                蛇的速度,(通过sleep来实现速度的改变)

                                一个食物的分数,总分数

     注:该步是为了便于维护贪吃蛇的信息

​
typedef struct Snake
{
	pSnakeNode pSnake;//指向蛇头的位置的指针
	pSnakeNode pfood;//指向食物的位置的指针
	enum DIRCTION dir;//蛇的运动方向
	enum GAME_STATE state;//蛇的运动状态
	int sleep_time;//睡眠时间,睡眠时间越短,速度越快
	int food_weight;//一个食物的分数
	int score;//总分数
}Snake,*pSnake;

​
//蛇的运动方向:上,下,左,右
enum DIRCTION
{
	UP=1,
	DOWN,
	LEFT,
	RIGHT
};

//蛇的运动状态:正常,撞到墙,撞到自己,正常结束
enum GAME_STATE
{
	OK,
	KILL_BY_WALL,
	KILL_BY_SELF,
	END_NORMAL
};

2>初始化游戏

    1.设置窗口大小,隐藏光标

system("mode con cols=100 lines=30");
system("title 贪吃蛇");
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false;//隐藏控制台光标
SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态

    2.打印环境界面,进行功能介绍

       1>封装一个函数实现光标的定位

void SetPos(short x, short y)
{
	//获得标准输出设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//定位光标位置
	COORD pos = { x,y };
	SetConsoleCursorPosition(houtput, pos);
}

       2>打印环境界面函数的实现

void WelcomeToGame()
{
	SetPos(40, 15);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");
	SetPos(40, 20);
	system("pause");
	system("cls");//清理屏幕
	SetPos(25, 14);
	wprintf(L"使用↑,↓,←,→控制蛇的移动,按F3加速,F4减速\n");
	SetPos(25, 15);
	wprintf(L"加速可以获得更高分数\n");
	SetPos(42, 20);
	system("pause");
	system("cls");
}

    3.绘制地图

       1>在地图中2x=y,所以在打印横行时,应该注意加个空行

       2>按上下左右的顺序实现墙体的打印,在左(右)打印时需要将光标定位在开头(结尾)处

       3>代码实现

void CreateMap()
{
	int i = 0;
	//上
	for (i = 0;i < 29; i++)
	{
		wprintf(L"%lc ", L'□'); 
	}
	//下
	SetPos(0, 26);
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc ",L'□');
	}
	//左
	for (i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", L'□');
	}
	//右
	for (i = 1; i <= 25; i++)
	{
		SetPos(56,i);
		wprintf(L"%lc", L'□');
	}
}

    4.创建蛇

       1>初始化蛇身:为贪吃蛇开辟5个节点作为初始长度,位置定位在(24,5)

       2>设置贪吃蛇最初的一些属性:

           默认运动方向:RIGHT

           默认速度大小:200毫秒

           游戏状态:OK

           最初成绩:0

            一个食物的分数:10

       3>代码实现

void InitSnake(pSnake ps)
{
	//初始化蛇身
	for (int i = 0; i < 5; i++)
	{
		//创建节点
		pSnakeNode psn = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (psn == NULL)
		{
			perror("malloc");
			exit(1);
		}

		//位置初始化
		psn->x = 24+2*i;
		psn->y = 5;
		psn->next = NULL;

		//头插串联5个节点
		if (ps->pSnake == NULL)
		{
			ps->pSnake = psn;
		}
		else
		{
			psn->next = ps->pSnake;
			ps->pSnake = psn;
		}
	}
	//打印蛇身
	pSnakeNode cur = ps->pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//设置参数
	ps->sleep_time = 200;
	ps->score = 0;
	ps->food_weight = 10;
	ps->state = OK;
	ps->dir = RIGHT;
}

    5.创建食物

       1>随机生成食物位置

       2>食物位置应该满足:

           不在墙体内,不在蛇体内

           食物位置x应该是2的倍数

       3>打印食物

       4>代码实现

void CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;
again:
	//判断不是2的倍数,不在墙体内
	do
	{
		//x:2-55
		//y: 1-26
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x%2 != 0);

	//判断不在蛇身内
	pSnakeNode cur = ps->pSnake;
	while (cur)
	{
		if (cur->x == x && cur->y == y)
		{
			goto again;
		}
		cur = cur->next;
	}

	//开辟食物位置
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL)
	{
		perror("malloc");
		exit(1);
	}
	else
	{
		pFood->x = x;
		pFood->y = y;
		pFood->next = NULL;
		SetPos(x, y);
		wprintf(L"%lc", FOOD);
		ps->pfood = pFood;
	}
}

3>游戏运行

    1.打印帮助信息

void PrintHelpInfo()
{
	SetPos(65, 14);
	wprintf(L"%ls", L"不能撞墙,不能咬到自己");
	SetPos(65, 15);
	wprintf(L"%ls", L"按↑,↓,←,→控制蛇的移动");
	SetPos(65, 16);
	wprintf(L"%ls",L"按F3加速,按F4减速");
	SetPos(65, 17);
	wprintf(L"%ls", L"按ESC退出游戏,按空格键暂停游戏");
}

    2.检测按键状态,以判断运动方向

       1>当按下的状态与当前运动状态相反时,其运动方向不变

       2>按键状态检测的宏

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

       3>代码实现

do
{
	SetPos(65, 10);
	printf("总分:%d\n", ps->score);
	SetPos(65, 11);
	printf("一个食物的分数:%d\n", ps->food_weight);
	if (KEY_PRESS(VK_UP) && ps->dir != DOWN)
	{
		ps->dir = UP;
	}
	else if (KEY_PRESS(VK_DOWN) && ps->dir != UP)
	{
		ps->dir = DOWN;
	}
	else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT)
	{
		ps->dir = LEFT;
	}
	else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT)
	{
		ps->dir = RIGHT;
	}
	else if (KEY_PRESS(VK_F3))
	{
		//加速
		if (ps->food_weight < 18)
		{
			ps->sleep_time -= 30;
			ps->food_weight += 2;
		}
		
	}
	else if (KEY_PRESS(VK_F4))
	{
		//减速
		if (ps->food_weight >2)
		{
			ps->sleep_time += 30;
			ps->food_weight -= 2;
		}
	}
	else if (KEY_PRESS(VK_SPACE))
	{
		//暂停
		Pause();
	}
	else if (KEY_PRESS(VK_ESCAPE))
	{
		//退出游戏
		ps->state = END_NORMAL;
	}
} while (ps->state == OK);

    3.蛇移动一步

      1>创建一个下一个节点

pSnakeNode NextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (NextNode == NULL)
{
	perror("malloc");
	exit(1);
}
//确定下一个节点的坐标
switch (ps->dir)
{
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;
	break;
}

      2>下一个节点是否是食物

int NextIsFood(pSnakeNode psn, pSnake ps)
{
	return ((psn->x == ps->pfood->x && psn->y == ps->pfood->y) ? 1 : 0);
}

          1.是食物,则吃食物

void EatFood(pSnakeNode psn, pSnake ps)
{
	//以头插法将食物节点连接在原链表上
	psn->next = ps->pSnake;
	ps->pSnake = psn;

	//打印蛇
	pSnakeNode cur = ps->pSnake;
	while (cur != NULL)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//显示总分
	ps->score += ps->food_weight;
	//释放食物位置,因为此时有两个节点指向食物所处位置
	free(ps->pfood);
	ps->pfood = NULL;
	//创建新的食物
	CreateFood(ps);
}

          2.不是食物

void NoFood(pSnakeNode psn, pSnake ps)
{
	//以头插法将食物节点连接在原链表上
	psn->next = ps->pSnake;
	ps->pSnake = psn;
	//打印蛇
	pSnakeNode cur = ps->pSnake;
	while (cur->next->next != NULL)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//在最后一个节点处打印两个空格,释放该节点
	SetPos(cur->x, cur->y);
	printf("  ");
	free(cur->next);
	cur->next = NULL;

}

      3>此时撞墙

          1.当x=0or56,ory=0or26时,表明蛇已经撞到墙了,此时将蛇的状态改为Kill_By_Wall

          2.代码实现

int KillByWall(pSnake ps)
{
	if (ps->pSnake->x == 0 || ps->pSnake->x == 56 || ps->pSnake->y == 0 || ps->pSnake->y == 26)
	{
		ps->state = KILL_BY_WALL;
		return 1;
	}
	return 0;
}

      4>此时撞到自己

          1.判断每个节点对应的坐标是否与蛇头坐标相等,若相等则撞到自己,此时更改蛇的状态为               Kill_By_Self

           2.代码实现

int KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->pSnake->next;
	while (cur)
	{
		if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
		{
			ps->state = KILL_BY_SELF;
			return 1;
		}
		cur = cur->next;
	}
	return 0;
}

    4.蛇休息

       1>蛇每一移动一步让它睡眠一段时间

4>结束游戏

    1.根据蛇的状态给出提示信息


//1.判断游戏状态,给出提示信息
SetPos(24, 12);
switch (ps->state)
{
case END_NORMAL:
	printf("您主动退出游戏!\n");
	break;
case KILL_BY_SELF:
	printf("您撞到自己了哟!游戏结束!\n");
	break;
case KILL_BY_WALL:
	printf("您撞到墙了哟!游戏结束!\n");
	break;
}

    2.释放链表

//2.释放链表
pSnakeNode cur = ps->pSnake;
pSnakeNode del = NULL;
while (cur)
{
	del = cur;
	cur = cur->next;
	free(del);
}

3.

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值