【游戏专区】贪吃蛇

1,游戏背景

贪吃蛇(Snake)是一款经典的电子游戏,最初在1976年由 Gremlin 公司开发。它的游戏背景相对简单,但具有高度的成瘾性。

1. **游戏场景**:通常在一个有界的矩形区域内进行,可以是一个正方形或长方形。这个区域被分割成一个个小方格,称为“像素”或“点”。

2. **主角**:玩家控制一条由一连串方块组成的“蛇”,最初通常只有一个小方块。蛇会在游戏区域内移动。

3. **目标**:游戏的目标通常是控制蛇吃到食物,每吃到一个食物蛇的长度就会增加一格。

4. **障碍物**:在一些版本的贪吃蛇中,会有障碍物阻碍蛇的移动,或者一些区域是不可通过的。

5. **游戏规则**:玩家通过控制蛇的移动方向来使蛇吃到食物。蛇可以向上、向下、向左、向右移动,但不能穿过自己的身体或者游戏区域的边界。当蛇碰到自己的身体或者边界时,游戏结束。

6. **难度提升**:随着蛇不断吃到食物,蛇的长度会增加,使得游戏变得更加困难。有些版本的游戏会在蛇吃到食物后增加蛇的移动速度,增加游戏的挑战性。

7. **分数计算**:游戏通常会记录玩家的得分,得分的计算方式可以是蛇吃到食物的数量,也可以是蛇移动的步数等。

总的来说,贪吃蛇游戏的背景非常简单,但由于其简单易懂的玩法和高度成瘾性,成为了一款经典的游戏,受到了广泛的欢迎。

2,技术要求

C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等

3,Win32API介绍

Windows 这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外, 它同时也是⼀个很⼤ 的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启 视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(Application), 所以便 称之为 Application Programming Interface,简称 API 函数。WIN32 API也就是Microsoft Windows 32位平台的应⽤程序编程接⼝。

4,所使用到的Win32API

/* 设置windows窗口大小 */
system("mode con cols=100 lines=30");
/* 设置窗口名称 */
system("title 贪吃蛇");

system 执行这行windows的命令

有些小伙伴可能在这里会遇到问题

COORD 是Windows API中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系
(0,0) 的原点位于缓冲区的顶部左侧单元格。
COORD类型声明
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
我们可以通过COORD pos(10,20);进行坐标赋值

GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标
准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。
HANDLE GetStdHandle(DWORD nStdHandle);

实例:

 GetConsoleCursorInfo
检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息
BOOL WINAPI GetConsoleCursorInfo(
 HANDLE hConsoleOutput,
 PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标
(光标)的信息


typedef struct _CONSOLE_CURSOR_INFO {
 DWORD dwSize;
 BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
• dwSize,由光标填充的字符单元格的百分⽐。 此值介于1到100之间。 光标外观会变化,范围从完
全填充单元格到单元底部的⽔平线条。
• bVisible,游标的可⻅性。 如果光标可⻅,则此成员为 true。

SetConsoleCursorInfo
设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。
BOOL WINAPI SetConsoleCursorInfo(
 HANDLE hConsoleOutput,
 const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调
⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。
BOOL WINAPI SetConsoleCursorPosition(
 HANDLE hConsoleOutput,
 COORD pos
);

实例:

我们写贪吃蛇需要大量的获取位置,所以我们最好分装一个函数去实现它

void SetPos(short x, shoet y)
{
     COORD pos = { x, y };
     HANDLE hOutput = NULL;
     //获取标准输出的句柄(⽤来标识不同设备的数值)
     hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
     //设置标准输出上光标的位置为pos
     SetConsoleCursorPosition(hOutput, pos);
}

这样我们只需调用这个函数就可以了

GetAsyncKeyState
获取按键情况,GetAsyncKeyState的函数原型如下:
SHORT GetAsyncKeyState(
 int vKey
);
将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,
如果 返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬 起;
如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.
就像这样,我们可以定义一个宏去判断

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

到这里,我们所有所要使用到的Win32API都已经介绍完毕了

5,游戏的设计与分析

到这里我们还需知道一种字符——宽字符

宽字符是指在计算机中用来表示字符的一种编码方式,其中每个字符占据多个字节的存储空间。它的由来可以追溯到对字符集进行扩展和标准化的需求。

在计算机发展初期,字符编码通常采用单字节编码,比如ASCII编码,它只能表示英文字符和一些特殊符号,因为只使用了7位二进制来表示字符,最多只能表示128个字符。

随着计算机技术的发展和国际间信息交流的增加,对字符集进行扩展以支持更多语言和符号的需求也日益显现。为了解决这个问题,人们开始引入了多字节字符编码,其中一种重要的编码方式就是宽字符编码。

宽字符编码通过使用16位或更多位来表示一个字符,从而可以支持更多的字符和符号,包括国际化字符、表情符号等。常见的宽字符编码包括Unicode和UTF-16编码。Unicode是一种字符集,定义了每个字符对应的唯一编码,而UTF-16是Unicode的一种实现方式,它使用16位编码表示大部分字符,但对于一些辅助平面的字符需要使用32位编码。

总的来说,宽字符的由来是为了满足对字符集扩展和国际化的需求,通过使用多字节来表示一个字符,从而支持更多的语言和符号。

宽字符的类型
wchar_t 和 头⽂件<locale.h>

`locale.h` 是 C/C++ 标准库中的一个头文件,它提供了对程序本地化(Localization)的支持。本地化是指根据用户的地理位置、语言、文化习惯等因素,使程序能够以符合用户习惯的方式展示信息和处理数据。

在 `locale.h` 中,提供了一系列函数和宏,用于设置和查询当前程序的本地化环境,包括以下主要功能:

1. **设置本地化环境**:通过函数 `setlocale()` 可以设置程序的本地化环境,包括语言、地区、货币等信息。

2. **查询本地化环境**:通过函数 `localeconv()` 可以查询当前本地化环境下的货币、日期、时间等格式信息。

3. **本地化化字符串处理**:提供了一系列本地化化字符串处理函数,如 `strcoll()` 用于字符串的比较、`strxfrm()` 用于字符串的转换等,以适应不同语言的字符串处理规则。

4. **本地化化输入输出**:提供了一系列本地化化的输入输出函数,如 `printf()`、`scanf()` 等的本地化版本,以便根据本地化环境输出或输入不同格式的数据。

通过 `locale.h` 中提供的这些功能,程序可以根据用户的本地化环境进行适当的调整,使得程序在不同的语言和地区下能够以最符合用户习惯的方式展示信息和处理数据。

通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部
分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏,
指定⼀个类项:
• LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm() 。
• LC_CTYPE:影响字符处理函数的⾏为。
• LC_MONETARY:影响货币格式。
• LC_NUMERIC:影响 printf() 的数字格式。
• LC_TIME:影响时间格式 strftime() 和 wcsftime() 。
• LC_ALL - 针对所有类项修改,将以上所有类别设置为给定的语⾔环境。

向更加详细的了解,可以点击下面的网址去进一步了解

setlocale,_wsetlocale | Microsoft Learn

今天我们只会使用到一个函数“setlocale()`”

`setlocale()` 函数用于设置程序的本地化环境,以适应用户的语言、地区和文化习惯。它的原型通常定义在 `<locale.h>` 头文件中,其基本形式如下:

```c
char *setlocale(int category, const char *locale);
```

其中,`category` 参数指定了要设置的本地化类别,而 `locale` 参数指定了新的本地化环境设置。常见的本地化类别包括:

- `LC_ALL`:设置所有本地化类别。
- `LC_COLLATE`:设置字符串比较和排序规则。
- `LC_CTYPE`:设置字符分类和转换规则。
- `LC_MONETARY`:设置货币格式。
- `LC_NUMERIC`:设置数字格式。
- `LC_TIME`:设置日期和时间格式。

`locale` 参数通常是一个字符串,表示要设置的本地化环境,可以采用特定的命名约定,例如 `"en_US"` 表示英语(美国),`"zh_CN"` 表示中文(中国)等。另外,也可以使用特殊值 `"C"` 或 `NULL` 来表示默认的本地化环境。

`setlocale()` 函数的返回值是一个指向表示当前本地化环境的字符串的指针,如果设置成功,则返回指向新的本地化环境字符串的指针;如果设置失败,则返回 `NULL`。

使用 `setlocale()` 函数可以在程序运行时动态地设置本地化环境,从而使程序能够根据用户的习惯进行适当的本地化处理。例如,可以根据用户的语言设置界面语言、日期格式等,以提高用户体验。

我们只需要setlocale(LC_ALL, "");就可以设置为本地

实例:

  1. 默认字体宽度和高度不同:某些字体在命令行窗口中的宽度和高度可能不相等。如果选择的字体在水平方向上比垂直方向上更宽,那么 x 坐标可能会比 y 坐标小。

6,游戏开始界面

我们将要写的其实很简单,只需要这样然后,那样就可以了,你懂了吗!


7,游戏地图

所以当我们想要产生这样一个地图也是非常简单的,

#define POS_X 24
#define POS_Y 5

#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define KEY_PRESS(vk)  ((GetAsyncKeyState(vk)&1)?1:0)

8,蛇身和食物

我这里使用链表来控制蛇身,所以我们还需要定义蛇的结构和节点

typedef struct SnakeNode
{
	int x;//坐标
	int y;
	struct SnakeNode* next;//下一个节点的位置
}SnakeNode, * pSnakeNode;

typedef struct Snake
{
	pSnakeNode _PSnakeHead;//头
	pSnakeNode _PFood;//食物
	enum DIRECTION//方向
	{
		UP = 1,//移动的方向
		DOWN,
		LEFT,
		RIGHT
	}_Dir;
	enum GAME_STATUS//蛇的状态
	{
		OK, //正常
		KILL_BY_WALL, //撞墙
		KILL_BY_SELF, //撞到自己
		END_NORMAL //正常退出
	}_Game;
	int _food_weight;//一个食物的分数
	int _score;//总分数
	int _sleep_time;//休眠的毫秒数
}Snake, * PSnake;

有了结构体,我们先来进行初始化

void CreateSnake(PSnake ps)
{
	pSnakeNode cur = NULL;
	//初始时我们有五个节点
	for (int i = 0; i < 5; i++)
	{
		//申请节点
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("CreateSnake::malloc fail");
		}
		//初始位置是(24, 5),每次改变x的位置
		cur->x = POS_X + 2 * i;
		cur->y = POS_Y;
		cur->next = NULL;
		//如果结构体PSnake中_PSnakeHead为空,蛇头没有节点,则将创建好的节点给它
		if (ps->_PSnakeHead == NULL)
		{
			ps->_PSnakeHead = cur;
		}
		//更新蛇头
		else
		{
			cur->next = ps->_PSnakeHead;
			ps->_PSnakeHead = cur;
		}
	}
	//遍历创建好的蛇头,打印蛇身
	cur = ps->_PSnakeHead;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//初始化其余变量,不包括食物
	ps->_sleep_time = 200;
	ps->_food_weight = 10;
	ps->_score = 0;
	ps->_Dir = RIGHT;
	ps->_Game = OK;
}

初始化食物

void CreateFood(PSnake ps)
{
	int x = 0;
	int y = 0;
again:
	/*
	  do-while循环随机创建位置,但因为宽字符占两个位置,
	  为了使我们的食物能被我们的蛇吃到,确定x的位置不能为单数
	  */
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);
	//遍历蛇身,创建的食物不能与蛇身重合
	pSnakeNode cur = ps->_PSnakeHead;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}
	//申请食物的节点,定位并打印食物
	cur = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (NULL == cur)
	{
		perror("CreateFood :: malloc fail");
	}
	cur->x = x;
	cur->y = y;
	cur->next = NULL;
	SetPos(x, y);
	wprintf(L"%lc", FOOD);
	ps->_PFood = cur;
}

//初始化函数
void GameStart(PSnake ps)
{
	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);//设置控制台光标状态
	//1. 打印环境界面和功能介绍
	WelcomeToGame();
	//2. 地图
	CreateMap();
	//3. 蛇
	CreateSnake(ps);
	//4. 食物
	CreateFood(ps);

}

9,核心逻辑实现

也就是蛇的移动

//下一格是否为食物
int NextIsFood(pSnakeNode snake, PSnake ps)
{
	//直接返回结果
	return (snake->x == ps->_PFood->x && snake->y == ps->_PFood->y);
}
//吃食物
void EatFood(pSnakeNode snake, PSnake ps)
{
	//让食物的下一节点指向蛇头
	ps->_PFood->next = ps->_PSnakeHead;
	//更新蛇头
	ps->_PSnakeHead = ps->_PFood;
	//释放申请到的下一格节点
	free(snake);
	snake = NULL;
	//重新打印蛇身
	pSnakeNode cur = ps->_PSnakeHead;
	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->_PSnakeHead;
	ps->_PSnakeHead = pn;
	//遍历打印蛇身
	pSnakeNode cur = ps->_PSnakeHead;
	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);

	//把倒数第二个节点的地址置为NULL
	cur->next = NULL;
}
void KillByWall(PSnake ps)
{
	//判断蛇头是否碰到墙体
	if (ps->_PSnakeHead->x == 0 || ps->_PSnakeHead->x == 56 ||
		ps->_PSnakeHead->y == 0 || ps->_PSnakeHead->y == 26)
	{
		//更新蛇的状态
		ps->_Game = KILL_BY_WALL;
	}
}

void KillBySelf(PSnake ps)
{
	//循环遍历蛇身是否接触到蛇头
	pSnakeNode cur = ps->_PSnakeHead->next;
	while (cur)
	{
		if (cur->x == ps->_PSnakeHead->x && cur->y == ps->_PSnakeHead->y)
		{
			//碰到更新蛇的状态
			ps->_Game  = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}
//蛇的移动
void SnakeMove(PSnake ps)
{
	//申请节点
	pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (cur == NULL)
	{
		perror("SnakeMove :: malloc");
	}
	//根据按键按下的运动状态做出相应的处理
	switch (ps->_Dir)
	{
	case UP:
		cur->x = ps->_PSnakeHead->x;
		cur->y = ps->_PSnakeHead->y - 1;
		break;
	case DOWN:
		cur->x = ps->_PSnakeHead->x;
		cur->y = ps->_PSnakeHead->y + 1;
		break;
	case LEFT:
		cur->x = ps->_PSnakeHead->x - 2;
		cur->y = ps->_PSnakeHead->y;
		break;
	case RIGHT:
		cur->x = ps->_PSnakeHead->x + 2;
		cur->y = ps->_PSnakeHead->y;
		break;
	}
	//检测前方是否为食物
	if (NextIsFood(cur, ps))
	{
		EatFood(cur, ps);
	}
	else
	{
		NoFood(cur, ps);
	}
	//碰到墙面
	KillByWall(ps);
	//碰到蛇身
	KillBySelf(ps);
}

void Pause()
{
	//睡眠,再次按下退出
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

void GameRun(PSnake ps)
{
	PrintHelpInfo();
	do
	{
		
		SetPos(64, 10);
		printf("总分数:%d\n", ps->_score);
		SetPos(64, 11);
		printf("当前食物的分数:%2d\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_SPACE))
		{
			Pause();
		}
		//ESC退出
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->_Game = END_NORMAL;
		}
		//F3
		else if (KEY_PRESS(VK_F3))
		{
			//加速
			if (ps->_sleep_time > 80)
			{
				ps->_sleep_time -= 30;
				ps->_food_weight += 2;
			}
		}
		//F4
		else if (KEY_PRESS(VK_F4))
		{
			//减速
			if (ps->_food_weight > 2)
			{
				ps->_sleep_time += 30;
				ps->_food_weight -= 2;
			}
		}

	SnakeMove(ps);//蛇走一步的过程
	//睡眠函数
	Sleep(ps->_sleep_time);
	//检测游戏运行状态是否为OK
	} while (ps->_Game == OK);
}

现在我们的蛇虽然可以走了,也可以吃到食物了,但我们还差一步,如何结束游戏。

10,游戏结束

我们只需要根据蛇的状态,去做出相应的操作即可

void GameEnd(PSnake ps)
{
	SetPos(24, 12);
	switch (ps->_Game)
	{
	case END_NORMAL:
		wprintf(L"您主动结束游戏\n");
		break;
	case KILL_BY_WALL:
		wprintf(L"您撞到墙上,游戏结束\n");
		break;
	case KILL_BY_SELF:
		wprintf(L"您撞到了自己,游戏结束\n");
		break;
	}

	//释放蛇身的链表

	pSnakeNode cur = ps->_PSnakeHead;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

11,游戏总代码

snake.h

#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>

#define POS_X 24
#define POS_Y 5

#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define KEY_PRESS(vk)  ((GetAsyncKeyState(vk)&1)?1:0)

typedef struct SnakeNode
{
	int x;//坐标
	int y;
	struct SnakeNode* next;//下一个节点的位置
}SnakeNode, * pSnakeNode;

typedef struct Snake
{
	pSnakeNode _PSnakeHead;//头
	pSnakeNode _PFood;//食物
	enum DIRECTION//方向
	{
		UP = 1,//移动的方向
		DOWN,
		LEFT,
		RIGHT
	}_Dir;
	enum GAME_STATUS//蛇的状态
	{
		OK, //正常
		KILL_BY_WALL, //撞墙
		KILL_BY_SELF, //撞到自己
		END_NORMAL //正常退出
	}_Game;
	int _food_weight;//一个食物的分数
	int _score;//总分数
	int _sleep_time;//休眠的毫秒数
}Snake, * PSnake;





void GameStart(PSnake ps);

void GameRun(PSnake ps);

void GameEnd(PSnake ps);

snake.c

#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, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");
	SetPos(42, 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");
}



void CreateMap()
{
	//上
	int i = 0;
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}

	//下
	SetPos(0, 26);
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}

	//右
	for (i = 1; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}

}

void CreateSnake(PSnake ps)
{
	pSnakeNode cur = NULL;
	//初始时我们有五个节点
	for (int i = 0; i < 5; i++)
	{
		//申请节点
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("CreateSnake::malloc fail");
		}
		//初始位置是(24, 5),每次改变x的位置
		cur->x = POS_X + 2 * i;
		cur->y = POS_Y;
		cur->next = NULL;
		//如果结构体PSnake中_PSnakeHead为空,蛇头没有节点,则将创建好的节点给它
		if (ps->_PSnakeHead == NULL)
		{
			ps->_PSnakeHead = cur;
		}
		//更新蛇头
		else
		{
			cur->next = ps->_PSnakeHead;
			ps->_PSnakeHead = cur;
		}
	}
	//遍历创建好的蛇头,打印蛇身
	cur = ps->_PSnakeHead;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//初始化其余变量,不包括食物
	ps->_sleep_time = 200;
	ps->_food_weight = 10;
	ps->_score = 0;
	ps->_Dir = RIGHT;
	ps->_Game = OK;
}

void CreateFood(PSnake ps)
{
	int x = 0;
	int y = 0;
again:
	/*
	  do-while循环随机创建位置,但因为宽字符占两个位置,
	  为了使我们的食物能被我们的蛇吃到,确定x的位置不能为单数
	  */
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);
	//遍历蛇身,创建的食物不能与蛇身重合
	pSnakeNode cur = ps->_PSnakeHead;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}
	//申请食物的节点,定位并打印食物
	cur = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (NULL == cur)
	{
		perror("CreateFood :: malloc fail");
	}
	cur->x = x;
	cur->y = y;
	cur->next = NULL;
	SetPos(x, y);
	wprintf(L"%lc", FOOD);
	ps->_PFood = cur;
}
void GameStart(PSnake ps)
{
	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);//设置控制台光标状态
	//1. 打印环境界面和功能介绍
	WelcomeToGame();
	//2. 地图
	CreateMap();
	//3. 蛇
	CreateSnake(ps);
	//4. 食物
	CreateFood(ps);

}

void PrintHelpInfo()
{
	//给一些提示
	SetPos(64, 14);
	wprintf(L"%ls", L"不能穿墙,不能咬到自己");
	SetPos(64, 15);
	wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动");
	SetPos(64, 16);
	wprintf(L"%ls", L"按F3加速,F4减速");
	SetPos(64, 17);
	wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");

	SetPos(68, 19);
	wprintf(L"工作顺利,家人幸福安康\n");
	SetPos(68, 20);
	wprintf(L"@灰灰\n");

	//wprintf(L"%lc", L'');
}



//下一格是否为食物
int NextIsFood(pSnakeNode snake, PSnake ps)
{
	//直接返回结果
	return (snake->x == ps->_PFood->x && snake->y == ps->_PFood->y);
}
//吃食物
void EatFood(pSnakeNode snake, PSnake ps)
{
	//让食物的下一节点指向蛇头
	ps->_PFood->next = ps->_PSnakeHead;
	//更新蛇头
	ps->_PSnakeHead = ps->_PFood;
	//释放申请到的下一格节点
	free(snake);
	snake = NULL;
	//重新打印蛇身
	pSnakeNode cur = ps->_PSnakeHead;
	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->_PSnakeHead;
	ps->_PSnakeHead = pn;
	//遍历打印蛇身
	pSnakeNode cur = ps->_PSnakeHead;
	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);

	//把倒数第二个节点的地址置为NULL
	cur->next = NULL;
}
void KillByWall(PSnake ps)
{
	//判断蛇头是否碰到墙体
	if (ps->_PSnakeHead->x == 0 || ps->_PSnakeHead->x == 56 ||
		ps->_PSnakeHead->y == 0 || ps->_PSnakeHead->y == 26)
	{
		//更新蛇的状态
		ps->_Game = KILL_BY_WALL;
	}
}

void KillBySelf(PSnake ps)
{
	//循环遍历蛇身是否接触到蛇头
	pSnakeNode cur = ps->_PSnakeHead->next;
	while (cur)
	{
		if (cur->x == ps->_PSnakeHead->x && cur->y == ps->_PSnakeHead->y)
		{
			//碰到更新蛇的状态
			ps->_Game  = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}
//蛇的移动
void SnakeMove(PSnake ps)
{
	//申请节点
	pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (cur == NULL)
	{
		perror("SnakeMove :: malloc");
	}
	//根据按键按下的运动状态做出相应的处理
	switch (ps->_Dir)
	{
	case UP:
		cur->x = ps->_PSnakeHead->x;
		cur->y = ps->_PSnakeHead->y - 1;
		break;
	case DOWN:
		cur->x = ps->_PSnakeHead->x;
		cur->y = ps->_PSnakeHead->y + 1;
		break;
	case LEFT:
		cur->x = ps->_PSnakeHead->x - 2;
		cur->y = ps->_PSnakeHead->y;
		break;
	case RIGHT:
		cur->x = ps->_PSnakeHead->x + 2;
		cur->y = ps->_PSnakeHead->y;
		break;
	}
	//检测前方是否为食物
	if (NextIsFood(cur, ps))
	{
		EatFood(cur, ps);
	}
	else
	{
		NoFood(cur, ps);
	}
	//碰到墙面
	KillByWall(ps);
	//碰到蛇身
	KillBySelf(ps);
}

void Pause()
{
	//睡眠,再次按下退出
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

void GameRun(PSnake ps)
{
	PrintHelpInfo();
	do
	{
		
		SetPos(64, 10);
		printf("总分数:%d\n", ps->_score);
		SetPos(64, 11);
		printf("当前食物的分数:%2d\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_SPACE))
		{
			Pause();
		}
		//ESC退出
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->_Game = END_NORMAL;
		}
		//F3
		else if (KEY_PRESS(VK_F3))
		{
			//加速
			if (ps->_sleep_time > 80)
			{
				ps->_sleep_time -= 30;
				ps->_food_weight += 2;
			}
		}
		//F4
		else if (KEY_PRESS(VK_F4))
		{
			//减速
			if (ps->_food_weight > 2)
			{
				ps->_sleep_time += 30;
				ps->_food_weight -= 2;
			}
		}

	SnakeMove(ps);//蛇走一步的过程
	//睡眠函数
	Sleep(ps->_sleep_time);
	//检测游戏运行状态是否为OK
	} while (ps->_Game == OK);
}

void GameEnd(PSnake ps)
{
	SetPos(24, 12);
	switch (ps->_Game)
	{
	case END_NORMAL:
		wprintf(L"您主动结束游戏\n");
		break;
	case KILL_BY_WALL:
		wprintf(L"您撞到墙上,游戏结束\n");
		break;
	case KILL_BY_SELF:
		wprintf(L"您撞到了自己,游戏结束\n");
		break;
	}

	//释放蛇身的链表

	pSnakeNode cur = ps->_PSnakeHead;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

/*
* 碰到墙壁
* 蛇神太长碰到自身
* 食物随机,无法确定位置
* 食物在同一个位置出现两次的几率为
*/

main.c

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




void test_gamer()
{
 //   //1 初始化数据
 //   Snake snake = { 0 };
 //   GameStart(&snake);
	//GameRun(&snake);
	//getchar();
	int ch = 0;
	do
	{
		system("cls");

		Snake snake = { 0 };

		GameStart(&snake);

		GameRun(&snake);
		GameEnd(&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_gamer();
    return 0;
}

12,每期一问

上期答案

 typedef struct ListNode ListNode;
 //创建链表节点
 ListNode* ApplyList(int x)
 {
    ListNode* node = (ListNode*)malloc(sizeof(ListNode));
    node->next = NULL;
    node->val = x;
    return node;
 }
 //创建循环链表
 ListNode* circulationList(int n)
 {
    ListNode* head ,*tail;
    head = tail = ApplyList(1);
    for(int i = 2;i <= n; i++)
    {
        tail->next =  ApplyList(i);
        tail = tail->next;
    }
    tail->next = head;
    return tail;
 }
//约瑟夫问题
int ysf(int n, int m ) {
    ListNode* prev, *pcur = NULL;
    //接收返回的尾结点
    prev = circulationList(n);
    //找到头结点
    pcur = prev->next;
    int count = 1;
    //当头结点与尾结点相等的时候就说明只剩最后一个节点,结束循环
    while(pcur != prev)
    {
        //计数,喊道相应的数,就释放掉节点
        if(count == m)
        {
            //更新上一个节点的next指针的指向
            prev->next = pcur->next;
            //释放掉节点
            free(pcur);
            //更新pcur的节点
            pcur = prev->next;
            //重新开始计数
            count = 1;
        }
        //没有喊到相应的数据
        else {
            //更新prev的值
            prev = pcur;
            //让pcur走一步
            pcur = pcur->next;
            //累加,直到喊道相应的数值为止           
            count++;
        }
    }
    int ret = pcur->val;
    free(pcur);
    return ret;
}

本期问题

. - 力扣(LeetCode)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/559571.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设计模式—门面模式

定义: 门面模式,也称为外观模式&#xff0c;是一种结构型设计模式。它的主要目的是提供统一的接口来访问子系统中的多个接口&#xff0c;从而简化客户端与复杂子系统之间的交互。 在门面模式中&#xff0c;一个门面类充当中介&#xff0c;为客户端提供一个简化了的访问方式&…

【Gradio】Could not create share link

【Gradio】Could not create share link 写在最前面在服务器端一直运行一个Python脚本解决&#xff1a;下载frpc_linux_amd64文件&#xff0c;并添加权限原理 完整过程&#xff1a;先找gradio库位置&#xff0c;然后发现缺失文件1. 打开终端2. 使用 find 命令查找 gradio 目录3…

Codigger GT模块:GUI融合Terminal,重塑开发体验

在信息技术日新月异的今天&#xff0c;开发者与计算机系统进行交互的界面&#xff0c;其体验的优化与升级显得尤为关键。Codigger G&T正是应这一需求而生&#xff0c;它巧妙地将现代图形用户界面&#xff08;GUI&#xff09;的优势融入传统的Terminal中&#xff0c;为开发者…

基于Java SpringBoot+Vue的体育用品库存管理系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

链路加密技术:保护数据传输的盾牌

在数字时代&#xff0c;数据安全已经成为我们日常生活和工作中的重要议题。随着网络技术的飞速发展和信息量的爆炸式增长&#xff0c;数据的安全传输变得尤为重要。链路加密技术作为一种重要的网络安全措施&#xff0c;为数据的传输提供了强有力的保障。本文将深入探讨链路加密…

2024第十五届蓝桥杯 Java B组 填空题

声明&#xff1a;博主比较菜&#xff0c;以下均为个人想法。解决方法仅供参考。欢迎大家一起讨论交流&#xff01; 编程题在文末链接 第一题&#xff1a; 题目&#xff1a; &#xff08;简洁版&#xff09;从小到大排列是20或24倍数的正整数&#xff0c;前10个数依次是&…

C语言结课实战项目_贪吃蛇小游戏

目录 最终实现效果&#xff1a; 实现基本的功能&#xff1a; 根据游戏进程解释代码&#xff1a; 游戏初始化&#xff1a; 首先进入游戏&#xff0c;我们应该将窗口名称改为 “贪吃蛇” 并将光标隐藏掉。再在中间打印游戏信息。 之后我们要把地图打印出来&#xff1a; 然后…

【动态规划】C++简单多状态dp问题(打家劫舍、粉刷房子、买卖股票的最佳时机...)

文章目录 前言1. 前言 - 理解动态规划算法2. 关于 简单多状态的dp问题2.5 例题按摩师/打家劫舍 3. 算法题3.1_打家劫舍II3.2_删除并获得点数3.3_粉刷房子3.4_买卖股票的最佳时机含冷冻期3.5_买卖股票的最佳时机含手续费3.6_买卖股票的最佳时机III3.7_买卖股票的最佳时机IV 前言…

开源模型应用落地-chatglm3-6b-gradio-入门篇(七)

一、前言 早前的文章&#xff0c;我们都是通过输入命令的方式来使用Chatglm3-6b模型。现在&#xff0c;我们可以通过使用gradio&#xff0c;通过一个界面与模型进行交互。这样做可以减少重复加载模型和修改代码的麻烦&#xff0c; 让我们更方便地体验模型的效果。 二、术语 2.…

oracle 清空回收站

参考官方文档 select * from user_recyclebin; select * from dba_recyclebin; ---清除回收站中当前用户下的对象 purge recyclebin; ---清除回收站中所有的对象 purge dba_recyclebin; ---清除回收站中指定用户的表 PURGE TABLE owner.table_name; ---清除回收站中指…

精通MongoDB聚合操作API:深入探索高级技巧与实践

MongoDB 聚合操作API提供了强大的数据处理能力&#xff0c;能够对数据进行筛选、变换、分组、统计等复杂操作。本文介绍了MongoDB的基本用法和高级用法&#xff0c;高级用法涵盖了setWindowFields、merge、facet、expr、accumulator窗口函数、结果合并、多面聚合、查询表达式在…

Spring Boot | Spring Boot 应用的 “打包” 和 “部署”

目录: Spring Boot 应用的 “打包” 和 “部署” :一、Jar包方式打包部署 ( SpringBoot默认以 "Jar包" 形式进行 “打包部署” ) :1.1 "Jar包" 方式 “打包” :① 添加Maven “打包插件”② 使用IDEA开发工具进行 "打包" 1.2 "Jar包" …

构建Python中的分布式日志系统:ELK与Fluentd的结合

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在现代软件开发中&#xff0c;日志系统是至关重要的组成部分。它们不仅用于故障排查和性能监…

户外运动用什么耳机?五款主流运动耳机推荐!

城市的喧嚣和繁忙&#xff0c;常常让我们渴望逃离&#xff0c;去寻找一片属于自己的宁静天地。大自然&#xff0c;便是那个能够抚慰我们心灵、让我们重新找回宁静与美好的地方。对于热爱自然、钟情户外的你&#xff0c;一款合适的运动耳机&#xff0c;无疑是探索自然、享受运动…

贪吃蛇游戏源码(VS编译环境)

贪吃蛇游戏源码&#xff08;VS编译环境&#xff09; &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;C语言&#x1f353; &#x1f33c;文章目录&#x1f33c; 1. Snake.h 头文件 2. Snake.c 源文件 3. Test.c 头文件 1. Snake.h 头…

只需几步,即可享有笔记小程序

本示例是一个简单的外卖查看店铺点菜的外卖微信小程序&#xff0c;小程序后端服务使用了MemFire Cloud&#xff0c;其中使用到的MemFire Cloud功能包括&#xff1a; 其中使用到的MemFire Cloud功能包括&#xff1a; 云数据库&#xff1a;存储外卖微信小程序所有数据表的信息。…

二进制OpenStack

二进制搭建OpenStack 1.环境准备 1.1机器的准备 主机名服务器配置操作系统IP地址controller-node4C8Gcentos7.9172.17.1.117computer-node4C8Gcentos7.9172.17.1.118 1.2网络架构 [rootcotroller-node ~]# ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noque…

dy号转uid和sec_uid

如何将抖dy号转换为uid和sec_uid&#xff1f; 摘要&#xff1a;本文将介绍如何实dy号与uid、sec_uid之间的转换过程&#xff0c;并提供相关的代码示例。 正文&#xff1a; dy作为一款热门的短视频社交平台&#xff0c;每个用户都有着唯一的用户ID&#xff08;uid&#xff09…

VisualGLM-6B的部署步骤

对于如下命令&#xff0c;你将完全删除环境和环境中的所有软件包 conda remove -n env_name --all 一、VisualGLM-6B环境安装 1、硬件配置 操作系统&#xff1a;Ubuntu_64&#xff08;ubuntu22.04.3&#xff09; GPU&#xff1a;4050 显存&#xff1a;16G 2、配置环境 建…

如何在Windows 11上退出安全模式?这里提供详细步骤

序言 安全模式是对电脑进行故障排除的强大工具。通过仅使用关键和必要的软件和服务启动电脑,它可以帮助你确定后台进程是否干扰了你的正常日常使用,或者是否有任何第三方软件导致电脑出现问题并使其难以使用。 如果你想退出安全模式,最简单的方法是重新启动你的电脑。只要…
最新文章