C语言项目:贪吃蛇游戏

引言

学习完C语言后,做一个简单的小项目无疑是加深理解、更快内化的最好途径

在这篇文章中。我会分享一个贪吃蛇游戏的项目案例,400+行的代码完全开源,附带win32API<locale.h>本地化的知识讲解和项目思路分析

喜欢的话别忘了三连哦,你的支持是我最大的动力~

目录

引言

一、项目展示

1.欢迎界面

2.规则介绍

3.游戏主体

4.游戏失败、游戏重新开始

二、基本功能

三、技术要点

 四、WIN32 API函数

五、本地化

1.定义

2.使用的原因

3.如何使用

setlocale()函数

定义

参数说明

返回值

示例

宽字符字面量

定义

表示方法

示例

六、编写思路

1.用本地化设置适配本地环境

2.测试逻辑

创建蛇

初始化游戏

运行游戏

结束游戏(善后工作)

七、代码 

1.游戏主函数所在文件:main_game.cpp

2.游戏函数等声明、宏定义文件:snack.h

3.游戏函数实现界面:snack.cpp

八、结束语

感谢观看!您的支持是我最大的动力!


一、项目展示

1.欢迎界面

2.规则介绍

3.游戏主体

4.游戏失败、游戏重新开始

二、基本功能

1.地图绘制

2.上下左右键盘控制蛇移动

3.蛇吃食物

4.蛇撞墙/撞到自己失败

5.计算、展示得分

6.蛇加速、减速及对应的食物的分值的变化

7.游戏暂停和退出

三、技术要点

 C语言函数、枚举类型、结构体、动态内存管理、预处理指令、链表

WIN32 API函数、<locale.h>本地化

接下来两点是比较重要的,但如果想先看代码或思路的可以用目录跳转到代码或思路处 ~

 四、WIN32 API函数

我已将整理在了这篇文章中,欢迎观看->WIN32 API函数-CSDN博客

五、<locale.h>本地化

1.定义

<locale.h> 是 C 语言标准库中的一个头文件,它提供了对本地化(Localization,简称 L10n)的支持。本地化是指将程序的界面和行为调整为适应不同国家和地区的语言、文化和法律要求的过程。

2.使用的原因

使用 <locale.h> 进行本地化的原因在于,不同的国家和地区有着不同的语言、文化习惯、货币格式、日期和时间表示、数字格式、排序规则等。为了使软件产品能够适应全球市场,开发者需要对应用程序进行本地化,以满足不同用户的特定需求。

而在本项目中,则是为了更好的显示一些特殊字符,以防出现打印出???的情况

3.如何使用

setlocale()函数

定义

setlocale() 函数是 C 和 C++ 语言中用于设置或获取程序的区域性(locale)的函数。区域性是指在特定地理区域或文化中使用的特定语言和文化习俗的集合,这包括字符的排序规则、货币格式、日期和时间的表示方式、数字的格式等。

参数说明
  1. category:指定要设置的区域性类别。它可以是以下值之一:

    • LC_ALL:所有区域性设置。
    • LC_COLLATE:字符串比较和排序规则。
    • LC_CTYPE:字符分类和转换。
    • LC_MONETARY:货币格式。
    • LC_NUMERIC:数字格式。
    • LC_TIME:时间和日期的格式。
  2. locale:指定要设置的区域性。如果这个参数是 NULLsetlocale() 将返回当前的区域性设置。如果它不是 NULL,函数将尝试将其设置为指定的区域性。

返回值

如果成功,setlocale() 返回一个指向由系统分配的字符串的指针,该字符串包含当前的区域性设置。如果 locale 参数不是 NULL 并且设置成功,它也返回相应的区域性字符串;如果设置失败,则返回 NULL

示例

以下是一个使用 setlocale() 的示例,它将程序的区域性设置为美式英语:

#include <stdio.h>
#include <locale.h>

int main() {
    // 设置区域性为美式英语
    setlocale(LC_ALL, "en_US.UTF-8");

    // 打印当前区域性设置
    printf("Current locale: %s\n", setlocale(LC_ALL, NULL));

    return 0;
}

而如果想直接设置成本地模式(中国简体中文),则setlocale()第二个参数可以填""

#include <stdio.h>
#include <locale.h>

int main() {
    // 设置区域性为本地区域(中国简体中文)
    setlocale(LC_ALL, "");

    // 打印当前区域性设置
    printf("Current locale: %s\n", setlocale(LC_ALL, NULL));

    return 0;
}

宽字符字面量

定义

在 C 和 C++ 语言中,宽字符字面量是一种表示 Unicode 或其他宽字符集中的字符的方法。宽字符字面量通常用于处理国际化的文本,它们可以表示多种语言的字符,包括那些在标准 ASCII 字符集中不存在的字符。

表示方法
  1. L 前缀:在字符字面量或字符串字面量前加上 L 前缀,可以将其转换为宽字符字面量或宽字符串字面量。

    • 宽字符:例如,L'a' 表示一个宽字符常量。
    • 宽字符串:例如,L"Hello" 表示一个宽字符串常量。
  2. 编码前缀:在 C++11 及以后的版本中,可以使用 u8 前缀来表示 UTF-8 编码的字符字面量,使用 U 前缀表示 UTF-16 编码的字符字面量,以及 U8U16U32 表示 UTF-8、UTF-16、UTF-32 编码的字符串字面量。

    • UTF-8 字符:例如,u8'a' 表示一个 UTF-8 编码的字符常量。
    • UTF-16 字符:例如,U'a' 表示一个 UTF-16 编码的字符常量。
    • UTF-8 字符串:例如,u8"Hello" 表示一个 UTF-8 编码的字符串常量
示例
#include <stdio.h>
#include <wchar.h>

int main() {
    // 宽字符字面量
    wchar_t wide_char = L'世'; // 使用 L 前缀表示宽字符
    wprintf(L"宽字符: %lc\n", wide_char); // 使用 wprintf 打印宽字符

    // 宽字符串字面量
    wchar_t wide_string[] = L"Hello, 世界"; // 使用 L 前缀表示宽字符串
    wprintf(L"宽字符串: %ls\n", wide_string); // 使用 wprintf 打印宽字符串

    return 0;
}

在这个示例中:

  1. L'世' 是一个宽字符字面量,它表示中文字符“世”。在 C 语言中,宽字符字面量前使用 L 前缀来表示。

  2. L"Hello, 世界" 是一个宽字符串字面量,包含了 ASCII 字符和中文字符。同样地,在宽字符串字面量前使用 L 前缀。

  3. wchar_t 是 C 语言中用于表示宽字符的数据类型。

  4. wprintf 是 C 语言中的宽字符版本的 printf 函数,用于格式化宽字符和宽字符串的输出。

注意,为了正确编译和运行这个示例,你的源代码文件需要以 UTF-8 或相应的宽字符编码格式保存,并且你的编译器需要支持宽字符和宽字符串字面量。

此外,C 语言标准库提供的本地化支持相对有限,因此在实际的本地化应用程序开发中,可能需要使用特定平台的 API 或第三方库来实现更完整的本地化功能。

六、编写思路

1.用<locale.h>本地化设置适配本地环境

2.测试逻辑

创建蛇

基于链表实现贪吃蛇

找到蛇头

蛇的属性:速度、方向(枚举)、食物、食物分数、总分、蛇的状态(枚举)

初始化游戏

设置窗口的大小和标题

打印欢迎界面

打印规则介绍

绘制地图

创建蛇、食物

设置相关信息

运行游戏

基于蛇的状态,判断蛇头下一个位置的属性,然后循环移动

结束游戏(善后工作)

回收蛇(free())

七、代码 

1.游戏主函数所在文件:main_game.cpp

//***
//File Name : GluttonousSnake						
//Author : Winter_snow
//Version : ver0_1
//Created : 2024 / 5 / 12
//Description : Main Function :实现贪吃蛇游戏
//History:
//<author>           <version>       <time>                 <Changes>
//Winter_snow         ver0_1         2024 / 5 / 12         实现贪吃蛇游戏
//*******************************************************************************

#define _CRT_SECURE_NO_WARNINGS

#include "snack.h"

void test() {
	while (1) {//循环游戏
		system("cls");
		//创建贪吃蛇
		Snack sc = { 0 };
		//初始化游戏
		SnackInit(&sc);
		//运行游戏
		GameStart(&sc);
		//结束游戏,善后工作
		pSnackNode p1 = sc._pSnack, p2 = p1->next;
		while (p1) {
			free(p1);
			p1 = p2;
			if (p1)
				p2 = p1->next;
		}
		//重新开始
		SetPos(27, 17);
		printf("再来一局?(Y/N):");
		switch (getchar()) {
		case 'Y':
		case'y':
			while (getchar() != '\n');
			continue;
		default:
			break;
		}
		break;
	}
	SetPos(0, 28);
	system("pause");
}

int main() {
	//设置适配本地环境
	setlocale(LC_ALL, "");
	srand(time(NULL));
	//测试逻辑
	test();
	return 0;
}

2.游戏函数等声明、宏定义文件:snack.h

#define _CRT_SECURE_NO_WARNINGS
#include<locale.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#include<stdbool.h>

#define COLS 60 //墙的长
#define LINES 27//墙的高
//蛇初始位置
#define STACOLS 22
#define STALINES 5

//接收键盘值
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK))&1?1:0)

//方向
enum DIRECTION {
	UP,
	DOWN,
	LEFT,
	RIGHT
};
enum STATUS {
	OK,
	KILL_BY_WALLS,
	KILL_BY_SELF,
	EXIT
};
//贪吃蛇节点
typedef struct SnackNode {
	short x;
	short y;
	struct SnackNode* next;
}SnackNode,*pSnackNode;
//贪吃蛇
typedef struct Snack {
	pSnackNode _pSnack;//蛇头
	int _sleepTime;//休眠时间
	DIRECTION _direction;//前进方向
	STATUS _status;//蛇的状态
	pSnackNode _food;//食物
	int _foodWeight;//食物的分数
	int _score;//总分
}Snack,*pSnack;

//移动光标
void SetPos(short x,short y);
//初始化游戏
void SnackInit(pSnack sc);
//打印欢迎界面,功能介绍
void Welcome();
//绘制地图
void CreateMap();
//创建蛇
void CreatSnack(pSnack sc);
//创建食物
void SetFood(pSnack sc);
//运行游戏
void GameStart(pSnack sc);
//蛇动起来
void Move(pSnack sc);
//是否吃到食物
bool IsFood(pSnackNode sn, pSnack sc);

3.游戏函数实现界面:snack.cpp

#define _CRT_SECURE_NO_WARNINGS
#include"snack.h"

//←→↑↓▢▨●◆□◉◎◇◈▼◬◻□◪◩▭▮▯▣◊▲

//移动光标
void SetPos(short x, short y) {
	COORD pos = {x , y};
	HANDLE hOutPut = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleCursorPosition(hOutPut,pos);
}

//打印欢迎界面,功能介绍
void Welcome() {
	SetPos(40, 12);
	wprintf(L"欢迎来到贪吃蛇游戏!");
	SetPos(42 , 23);
	system("pause");
	system("cls");
	SetPos(40, 10);
	wprintf(L"按 ↑ ↓ ← → 进行移动");
	SetPos(42, 12);
	wprintf(L"按F3加速,F4减速");
	SetPos(38, 14);
	wprintf(L"速度越大食物提供的分数越多");
	SetPos(42, 23);
	system("pause");
}

//绘制地图
void CreateMap() {
	system("cls");
	int i = 0;
	SetPos(0, 0);
	for (i = 0; i < COLS+1; i += 2) {
		wprintf(L"□");
	}
	SetPos(0, LINES);
	for (i = 0; i < COLS + 1; i += 2) {
		wprintf(L"□");
	}
	for (i = 1; i < LINES; i ++) {
		SetPos(0, i);
		wprintf(L"□");
		SetPos(COLS ,i);
		wprintf(L"□");
	}
}

//创建食物
void SetFood(pSnack sc) {
	int x;
	int y;
again:
	do {
		x = rand() % 57 + 2;
		y = rand() % 26 + 1;
	} while (x % 2 != 0);
	//不能与蛇身重合
	pSnackNode cur = sc->_pSnack;
	while (cur) {
		if (cur->x == x && cur->y == y)
			goto again;
		cur = cur->next;
	}
	sc->_food = (pSnackNode)malloc(sizeof(SnackNode));
	sc->_food->x = x;
	sc->_food->y = y;
	sc->_food->next = NULL;
	
	//打印食物 
	SetPos(sc->_food->x, sc->_food->y);
	wprintf(L"▲");
}

//创建蛇,食物
void CreatSnack(pSnack sc) {
	pSnackNode cur=NULL;
	//创建蛇
	for (int i = STACOLS; i < STACOLS+11; i += 2) {
		cur = (pSnackNode)malloc(sizeof(SnackNode));
		cur->x = i;
		cur->y = STALINES;
		cur->next = NULL;
		if (sc->_pSnack == NULL) {
			sc->_pSnack = cur;
		}
		else {
			cur->next = sc->_pSnack;
			sc->_pSnack = cur;
		}
	}
	//遍历打印蛇
	while (cur) {
		SetPos(cur->x, cur->y);
		wprintf(L"●");
		cur = cur->next;
	}
}

//初始化游戏
void SnackInit(pSnack sc) {
	//设置窗口大小、名字
	system("mode con cols=100 lines=31");
	system("title 贪吃蛇");
	// 隐藏光标
	HANDLE hOutPut = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursorInfo;
	GetConsoleCursorInfo(hOutPut,&cursorInfo);
	cursorInfo.bVisible = false;
	SetConsoleCursorInfo(hOutPut, &cursorInfo);
	//打印欢迎界面,功能介绍
	Welcome();
	//绘制地图
	CreateMap();
	//创建蛇
	CreatSnack(sc);
	//创建食物
	SetFood(sc);
	//设置相关信息
	sc->_direction = RIGHT;
	sc->_foodWeight = 10;
	sc->_score = 0;
	sc->_sleepTime = 200;
	sc->_status = OK;
}

//打印辅助游戏规则
void PrintHelp() {
	SetPos(66, 20);
	wprintf(L"不能撞墙  不能撞自己");
	SetPos(66, 21);
	wprintf(L"Esc退出  空格暂停");
	SetPos(66, 22);
	wprintf(L"↑ ↓ ← → 移动");
	SetPos(66, 23);
	wprintf(L"F3加速  F4减速");
	SetPos(66, 24);
	wprintf(L"速度越大食物的分数越多");
}

//是否吃到食物
bool IsFood(pSnackNode sn, pSnack sc) {
	if (sn->x == sc->_food->x && sn->y == sc->_food->y) {
		return true;
	}
	else return false;
}

//蛇动起来
void Move(pSnack sc) {
	pSnackNode sn = (pSnackNode)malloc(sizeof(SnackNode));
	pSnackNode cur = NULL;
	switch (sc->_direction) {
	case UP:
		sn->x = sc->_pSnack->x;
		sn->y = sc->_pSnack->y - 1;
		break;
	case DOWN:
		sn->x = sc->_pSnack->x;
		sn->y = sc->_pSnack->y + 1;
		break;
	case LEFT:
		sn->x = sc->_pSnack->x - 2;
		sn->y = sc->_pSnack->y;
		break;
	case RIGHT:
		sn->x = sc->_pSnack->x + 2;
		sn->y = sc->_pSnack->y;
		break;
	}
	//是否吃到食物
	if (IsFood(sn, sc)) {//吃到
		sn->next = sc->_pSnack;
		sc->_pSnack = sn;
		SetPos(sn->x, sn->y);
		wprintf(L"●");
		sc->_score += sc->_foodWeight;
		SetFood(sc);
	}
	else {//未吃到
		sn->next = sc->_pSnack;
		sc->_pSnack = sn;
		cur = sn;
		while (cur->next->next) {
			SetPos(cur->x, cur->y);
			wprintf(L"●");
			cur = cur->next;
		}
		SetPos(cur->next->x, cur->next->y);
		wprintf(L"  ");
		free(cur->next);
		cur->next = NULL;
	}
	if (sn->x == 0 || sn->x == 60 || sn->y == 0 || sn->y == 27) {
		sc->_status = KILL_BY_WALLS;
	}
	cur = sn->next;
	while (cur) {
		if (sn->x == cur->x && sn->y == cur->y) {
			sc->_status = KILL_BY_SELF;
		}
		cur = cur->next;
	}
	Sleep(sc->_sleepTime);
}

//运行游戏
void GameStart(pSnack sc) {
	//打印辅助游戏规则
	PrintHelp();
	do{
		//打印分数等信息
		SetPos(66, 10);
		printf("总分:%3d", sc->_score);
		SetPos(66, 12);
		printf("食物分值:%2d", sc->_foodWeight );
		SetPos(66, 26);
		//接受键盘信息
		if (KEY_PRESS(VK_UP) && sc->_direction != DOWN) //上
			sc->_direction = UP;
		else if (KEY_PRESS(VK_DOWN) && sc->_direction != UP) //下
			sc->_direction = DOWN;
		else if (KEY_PRESS(VK_LEFT) && sc->_direction != RIGHT) //左
			sc->_direction = LEFT;
		else if (KEY_PRESS(VK_RIGHT) && sc->_direction != LEFT) //右
			sc->_direction = RIGHT;
		else if (KEY_PRESS(VK_F3)) //F3
			if (sc->_sleepTime > 80) {
				sc->_sleepTime -= 20;
				sc->_foodWeight++;
			}
			else;
		else if (KEY_PRESS(VK_F4)) //F4
			if (sc->_foodWeight > 5) {
				sc->_sleepTime += 20;
				sc->_foodWeight--;
			}
			else;
		else if (KEY_PRESS(VK_SPACE)) {//暂停
				while (1) {
					Sleep(200);
					if (KEY_PRESS(VK_SPACE))
						break;
				}
		}
		else if (KEY_PRESS(VK_ESCAPE)) {//退出
				sc->_status = EXIT;
		}
		//蛇动起来
		Move(sc);
	} while (sc->_status == OK);
	if (sc->_status == EXIT) {
		SetPos(25, 15);
		wprintf(L"您已主动退出");
		SetPos(25, 28);
	}
	else if (sc->_status == KILL_BY_WALLS) {
		SetPos(25, 15);
		wprintf(L"撞到墙了,游戏退出");
		SetPos(25, 28);
	}
	else if (sc->_status == KILL_BY_SELF) {
		SetPos(25, 15);
		wprintf(L"撞到自己,游戏退出");
		SetPos(25, 28);
	}
}

八、结束语

阅读完后请记得自己动手尝试一下,将知识内化于己

也欢迎在评论区交作业与讨论

感谢观看!您的支持是我最大的动力!

>>喜欢请三连加关注,冬码农持续创作优质作品中<<

希望与您一同成长!

  • 28
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冬有雪的学习之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值