【游戏专区】贪吃蛇

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)

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值