C语言实现贪吃蛇项目(2)

先来看看效果:

20240420_212115


3.项目实现

3.0宽字符的打印

3.01本地化操作

先要进行本地化,然后才能进行宽字符的打印
<locale.h>提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样⾏为的部分。
在标准中,依赖地区的部分有以下⼏项:
• 数字量的格式
• 货币量的格式
• 字符集
• ⽇期和时间的表⽰形式
在这里插入图片描述
每个类项的详细说明,请参考:https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/setlocale-wsetlocale?view=msvc-170

setlocale函数

在这里插入图片描述

setlocale 函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项
C标准给第⼆个参数仅定义了2种可能取值:“C”(正常模式)和""(本地模式)
我们来看看示例:
在这里插入图片描述
上面是C语言默认的环境,下面就是我们适配本地的环境了

宽字符的打印

宽字符的字⾯量必须加上前缀“L”,否则C语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引号前⾯,表⽰宽字符,对应wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应wprintf() 的占位符为%ls
在这里插入图片描述
所以在进行坐标判断是要注意是否为2的倍数

3.1贪吃蛇结构的创建和维护

3.11贪吃蛇结构的创建

我们的贪吃蛇使用链表进行维护我们先进行声明
在这里插入图片描述这里将结构体指针重命名为pSnakeNode方便后续书写

3.12贪吃蛇的维护

我们创建一个结构体来方便维护我们的程序(这里有枚举的方法)在这里插入图片描述

然后我们创建贪吃蛇
在这里插入图片描述

3.2初始化游戏

在这里插入图片描述
初始化游戏我们要做什么:
1.打印欢迎界面、隐藏光标和设置窗口大小
2.绘制地图
3.创建蛇
4.创建食物

3.21.打印欢迎界面、隐藏光标和设置窗口大小

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/0a07334e9292
这些上一篇文章已经写过了就不多赘述了在这里插入图片描述
光标定位也是前一篇写过的,直接拿过来使用即可
在这里插入图片描述
这里就是定位光标位置然后打印信息
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后就是清屏然后在打印
在这里插入图片描述

3.22.绘制地图

在这里插入图片描述

这里创建一个27行58列的地图,也可以根据自己的情况来
在这里插入图片描述
这里要注意光标的定位,通过循环就可以将地图绘制出来了
在这里插入图片描述

3.23.创建蛇

在这里插入图片描述
循环申请空间然后进行初始化,这里是从尾巴向头部申请空间的(蛇初始向右移动)
在这里插入图片描述
然后用头插法将其串起来
在这里插入图片描述
在这里插入图片描述
写成代码就是上面的方式
接着就是将蛇的身体打印出来,用循环遍历就行
在这里插入图片描述
在这里插入图片描述

并且将蛇的属性进行初始的设置
在这里插入图片描述

3.24.创建食物

由于食物要放到指针中,所以要传参数
在这里插入图片描述
要注意:
x坐标必须是2的倍数
⻝物的坐标不能和蛇⾝每个节点的坐标重复
在这里插入图片描述
在这里插入图片描述
接下来判断是否为2的倍数
在这里插入图片描述
判断x和y不能和蛇的身体冲突
在这里插入图片描述
申请食物的空间,并将其打印出来在这里插入图片描述

在这里插入图片描述

3.3 游戏运行逻辑

在这里插入图片描述

3.31打印游戏旁的提示

在这里插入图片描述
在这里插入图片描述

3.32按键检测

这里暂停为循环睡眠,然后在输入空格跳出循环
加减速要注意有判断,不能一直减速
在这里插入图片描述

3.33蛇的移动

要不断检测按键来移动蛇的位置
由于要改变游戏的状态所以也要传指针
在这里插入图片描述
我们的移动采用的方法是:将未接点释放,头节点进行头插
在这里插入图片描述
先创建节点,然后将移动的下一个坐标写出来,要注意x一次加2

检测下一个坐标是否是食物
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
然后是这两个函数的处理:
在这里插入图片描述
在这里插入图片描述
用头插法将食物节点插入,释放下一个位置的节点,打印蛇,最后创建食物
在这里插入图片描述
将下一个节点头插到蛇头,然后将尾接点用空格覆盖掉,最后将尾接点释放掉
在这里插入图片描述

3.34检测是否撞墙或自己

在这里插入图片描述
在这里插入图片描述
检测头的横纵坐标是否为墙
在这里插入图片描述
检测头的坐标是否为身体

3.4 游戏结束(善后工作)

打印结束信息:
在这里插入图片描述
在这里插入图片描述
然后释放节点
在这里插入图片描述
接下来就可以将游戏循环起来
在这里插入图片描述

这里要注意输入y后按回车getchar会读取\n,如果想将\n清理掉,就要在调用getchar
在更改一下:
在这里插入图片描述

4.项目代码

snake.h

#pragma once
#include <locale.h>
#include <stdio.h>
#include <Windows.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
//wchar_t ch1 = L'●';
//wchar_t ch2 = L'★';

void SetPos(short x, short y);
//类型的声明
//蛇身的节点类型
typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode;
typedef struct SnakeNode* pSnakeNode;

enum DIRECTION //记录方向(枚举)
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};
//蛇的状态(正常,撞墙,撞到自己,正常退出)
enum GAME_STATUS
{
	OK, //正常
	KILL_BY_WALL, //撞墙
	KILL_BY_SELF, //撞到自己
	END_NORMAL  //正常退出
};
typedef struct Snake
{
	pSnakeNode _pSnake;//指向蛇头变量
	pSnakeNode _pFood;//指向食物的指针
	enum DIRECTION _dir;//记录方向(枚举)
	enum GAME_STATUS _status;//游戏的状态
	int _food_weight; //食物的分数
	int _score;//总成绩
	int _sleep_time;//休息时间(时间越短,速度越快)
}Snake;
typedef Snake* pSnake;

void GameStart(pSnake ps);//初始化游戏

void WelcomeToGame();//欢迎界面

void GreateMap();//绘制地图

void InitSnake(pSnake ps);//创建蛇

void CreateFood(pSnake ps); //创建食物

void GameRun(pSnake ps);//游戏运行逻辑

void SnackMove(pSnake ps);//蛇的移动

int NextIsFood(pSnakeNode pn, pSnake ps);//检测下一个坐标是否是食物

void EatFood(pSnakeNode pn, pSnake ps);
void NoFood(pSnakeNode pn, pSnake ps);

//检测是否撞墙或自己
void KillByWall(pSnake ps);
void KillBySelf(pSnake ps);

void GameEend(pSnake ps);

snake.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "snake.h"

void SetPos(short x, short y)
{
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	COORD pos = { x,y };
	SetConsoleCursorPosition(houtput, pos);
}

void WelcomeToGame()
{
	SetPos(40, 13);
	printf("欢迎来到贪吃蛇小游戏\n");
	SetPos(42, 20);
	system("pause");
	system("cls");
	SetPos(27, 13);
	wprintf(L"用↑.↓.←.→分别控制蛇的移动,F3为加速,F4为减速\n");
	SetPos(27, 14);
	printf("加速能获得更高的分数\n");
	SetPos(40, 20);
	system("pause");
	system("cls");
}

#define WALL L'□'
#define BODY L'●'
void GreateMap()
{
	//上
	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 < 26; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (int i = 1; i < 26; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	
}

#define POS_X 24
#define POS_Y 5
void InitSnake(pSnake ps)
{
	pSnakeNode cur = NULL;
	for (int i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("malloc");
			exit(1);
		}
		cur->next = NULL;
		cur->x = POS_X + i * 2;
		cur->y = POS_Y;

		//头插法将其串起来
		if (ps->_pSnake == NULL)
		{
			ps->_pSnake = cur;
		}
		else
		{
			cur->next = ps->_pSnake;
			ps->_pSnake = cur;
		}
	}

	cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	
	//设置蛇的属性
	ps->_dir = RIGHT;
	ps->_score = 0;
	ps->_food_weight = 10;
	ps->_sleep_time = 200;//毫秒
	ps->_status = OK;
}

#define FOOD L'★'
void CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;
again:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 24 + 1;
	} while (x % 2 != 0);

	//x和y不能和蛇的身体冲突
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}
	//创建食物
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL)
	{
		perror ("pFood::malloc");
		exit(1);
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;
	
	SetPos(x, y);
	wprintf(L"%lc", FOOD);
	ps->_pFood = pFood;
}

void GameStart(pSnake ps)
{
	//1.打印欢迎界面、隐藏光标和设置窗口大小

	//设置窗口大小
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	//隐藏光标
	//获得标准输出设备
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	//创建光标信息结构体
	CONSOLE_CURSOR_INFO cursor_info = { 0 };

	//获取光标的信息,放在cursor_info中
	GetConsoleCursorInfo(houtput, &cursor_info);

	//修改光标占比
	//cursor_info.dwSize = 100;
	cursor_info.bVisible = false;

	//设置光标信息
	SetConsoleCursorInfo(houtput, &cursor_info);

	//1.打印欢迎界面
	WelcomeToGame();
	//2.绘制地图
	GreateMap();
	//3.创建蛇
	InitSnake(ps);
	//4.创建食物
	CreateFood(ps);
}


void PrintHelpInfo()
{
	SetPos(64, 15);
	wprintf(L"%ls\n", L"不能穿墙,不能咬到自己");
	SetPos(64, 16);
	wprintf(L"%ls\n", L"用↑.↓.←.→分别控制蛇的移动.");
	SetPos(64, 17);
	wprintf(L"%ls\n", L"F3 为加速,F4为减速");
	SetPos(64, 18);
	wprintf(L"%ls\n", L"ESC :退出游戏.空格:暂停游戏.");
}

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

void Pause()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

int NextIsFood(pSnakeNode pn, pSnake ps)
{
	assert(ps->_pFood);
	return (pn->x == ps->_pFood->x && pn->y == ps->_pFood->y);
}

void EatFood(pSnakeNode pn, pSnake ps)
{
	//用头插法将食物节点插入
	ps->_pFood->next = ps->_pSnake;
	ps->_pSnake = ps->_pFood;

	//释放下一个位置的节点
	free(pn);
	pn = NULL;

	//打印蛇
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->_score += ps->_food_weight;
	CreateFood(ps);
}

void NoFood(pSnakeNode pn, pSnake ps)
{
	//将下一个节点头插到蛇头
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;
	
	//最后将尾接点用空格覆盖掉
	pSnakeNode cur = ps->_pSnake;
	while (cur->next->next != NULL)
	{
		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;
}

void KillByWall(pSnake ps)
{
	if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 ||
		ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
	{
		ps->_status = KILL_BY_WALL;
	}
}

void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_status = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}
void SnackMove(pSnake ps)
{
	//创建一个节点来表示蛇即将到的下一个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("SnackMove()::malloc()");
		exit(1);
	}
	switch (ps->_dir)
	{
	case UP:
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y - 1;
		break;
	case DOWN:
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y + 1;
		break;
	case LEFT:
		pNextNode->x = ps->_pSnake->x - 2;
		pNextNode->y = ps->_pSnake->y;
		break;
	case RIGHT:
		pNextNode->x = ps->_pSnake->x + 2;
		pNextNode->y = ps->_pSnake->y;
		break;
	}
	//检测下一个坐标是否是食物
	if (NextIsFood(pNextNode,ps))
	{
		EatFood(pNextNode,ps);
	}
	else
	{
		NoFood(pNextNode, ps);
	}

	//检测蛇是否撞到墙或自己
	KillByWall(ps);
	KillBySelf(ps);
}


void GameRun(pSnake ps)
{
	//打印帮助信息
	PrintHelpInfo();
	do
	{
		SetPos(64, 10);
		printf("总分数:%d", ps->_score);
		SetPos(64, 11);
		printf("当前食物分数:%2d", 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_RIGHT) && ps->_dir != LEFT)
		{
			ps->_dir = RIGHT;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
		{
			ps->_dir = LEFT;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			//暂停
			Pause();
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//正常退出
			ps->_status = END_NORMAL;
		}
		else if (KEY_PRESS(VK_F4))
		{
			//加速
			if (ps->_sleep_time > 80)
			{
				ps->_sleep_time -= 30;
				ps->_food_weight += 2;
			}
		}
		else if (KEY_PRESS(VK_F3))
		{
			//减速
			if (ps->_food_weight > 2)
			{
				ps->_sleep_time += 30;
				ps->_food_weight -= 2;
			}
		}
		SnackMove(ps);//蛇的移动
		Sleep(ps->_sleep_time);
	} while (ps->_status == OK);

}

void GameEend(pSnake ps)
{
	SetPos(24, 12);
	switch (ps->_status)
	{
	case END_NORMAL:
		printf("您主动结束游戏\n");
		break;
	case KILL_BY_WALL:
		printf("您撞到了墙上,游戏结束\n");
		break;
	case KILL_BY_SELF:
		printf("您撞到了自己,游戏结束\n");
		break;
	}
	
	//释放节点
	pSnakeNode cur = ps->_pSnake;
	pSnakeNode del = ps->_pSnake;
	while (cur)
	{
		del = cur;
		cur = cur->next;
		free(del);
	}
	SetPos(0,27);
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "snake.h"

void Test()
{
	int ch = 0;
	do
	{
		system("cls");
		//创建贪吃蛇
		Snake snake = { 0 };
		//初始化游戏
		GameStart(&snake);

		运行游戏
		GameRun(&snake);
		结束游戏(善后工作)
		GameEend(&snake);
		SetPos(20, 15);
		printf("再来一局吗?(y/n):");
		ch = getchar();
		while (getchar() != '\n');
	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 27);
}

int main()
{
	setlocale(LC_ALL, "");//适配本地化
	srand((unsigned int)time(NULL));
	Test();//游戏的测试逻辑
	return 0;
}


好了本篇文章就结束了,希望大家可以动手自己去写一下,还是会有一些收获的

到这里C语言的语法部分就差不多了,后续我会继续写C语言数据结构的文章了,C语言语法部分看看可能会补充一部分,还是看看时间吧,大家加油!!!

  • 30
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值