【游戏专区】贪吃蛇

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);//设置标准输出上光标的位置为posSetConsoleCursorPosition(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);//把倒数第二个节点的地址置为NULLcur->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;}//F3else if (KEY_PRESS(VK_F3)){//加速if (ps->_sleep_time > 80){ps->_sleep_time -= 30;ps->_food_weight += 2;}}//F4else 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);//把倒数第二个节点的地址置为NULLcur->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;}//F3else if (KEY_PRESS(VK_F3)){//加速if (ps->_sleep_time > 80){ps->_sleep_time -= 30;ps->_food_weight += 2;}}//F4else 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.mzph.cn/web/1458.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于Android绘制这一遍就够了

Android绘制基础 Android平台提供了一套完整的UI框架&#xff0c;其中包括了绘制组件和绘制API。在Android中&#xff0c;绘制主要涉及到两个核心概念&#xff1a;Canvas和Paint。 Canvas Canvas是Android中的一个类&#xff0c;它代表了绘图的画布。你可以在这个画布上进行…

Android Studio实现页面跳转

建立文件 temp.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"…

layui--table里使用switch

1. 项目需求 在layui.table上面渲染后的列表上面加一个switch开关&#xff0c;监听switch开关的动作&#xff0c;实现本列数据的状态切换&#xff01; 实现效果如下&#xff1a; 2. 实现方式 下面介绍的思路都是利用table的templet模板实现的&#xff0c;不同的在于模板代码…

Linux-内存文件

1. 基础IO操作 1.1 c语言的IO接口 fopen&#xff1a;打开一个文件&#xff0c;按照指定方式 参数&#xff1a;filename 文件名&#xff0c;也可以是路径&#xff0c;mode&#xff1a;打开方式 返回打开的文件指针 fread&#xff1a;从指定流中读数据 参数&#xff1a;从FIL…

Vuex 的原理

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store&#xff08;仓库&#xff09;。“store” 基本上就是一个容器&#xff0c;它包含着你的应用中大部分的状态 ( state )。 Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的…

没有理由不加倍努力

最近su7很火&#xff0c;各隐藏大佬都纷纷从后台来到前台&#xff0c;把整个网红界的网红等级提升了好几个档次。红衣大叔更是借此机会在疯狂地打造自己的网红IP。 千亿大佬都这还般努力&#xff0c;作为平民的自己哪还有不努力的理由。 加倍努力&#xff01;

29 共享内存

共享内存区是最快的IPC形式&#xff0c;一旦这样的内存映射到共享它的进程的地址空间&#xff0c;这些进程间数据传递不再涉及到内核&#xff0c;不再执行进入内核的系统调用来传递彼此的数据 原理 系统在内存中申请一段空间&#xff0c;通过页表映射挂接到进程的共享区&#…

Linux--链表 第二十五天

1. 链表 t1.next -> data t1.next->next->data .(点号)的优先级比->的大 所以 t1.next->data 就可以了 不用(t1.next)->data 2. 链表的静态增加和动态遍历 打印链表算法&#xff0c; void printLink(struct Test *head) { struct Te…

剑指Offer题目笔记32(拓扑排序)

面试题113&#xff1a; 解决方案&#xff1a; 将课程看成图中的节点&#xff0c;如果两门课程存在先修顺序那么它们在图中对应的节点之间存在一条从先修课程到后修课程的边&#xff0c;因此这是一个有向图。可行的修课序列实际上是图的拓扑排序序列。图中的每条边都是从先修课…

Web前端框架/库/工具

前言 前端从步枪&#xff08;原生js&#xff09;到了半自动武器&#xff08;jQuery&#xff09;并进化为全自动武器&#xff08;三大框架&#xff08;angular&#xff0c;react&#xff0c;vue及其生态链&#xff09;&#xff09;。 常说工欲善其事必先利其器。对于那些想要提…

【c++11】看完立马就懂--右值引用!!!

右值引用 一、什么是右值&#xff1f;什么是左值&#xff1f;二、右值引用三、右值引用的好处四、万能引用五、完美转发 一、什么是右值&#xff1f;什么是左值&#xff1f; 首先&#xff0c;当我们看到右值的时候&#xff0c;我们很自然的就会产生疑问&#xff1f; 什么的右边…

黑马鸿蒙学习5:LIST容器

LIST容器&#xff0c;其实就是如果FOREACH容器展示不全的话&#xff0c;会自动有滚动条了。要注意的是&#xff0c;LIST中必须有固定的listitem这个项&#xff0c;而且列表里面只能包含一个根组件。 必须把ROW容器放到listitem中&#xff0c;如下&#xff1a;

51、图论-岛屿数量

思路&#xff1a; 该问题要求在一个由 1&#xff08;表示陆地&#xff09;和 0&#xff08;表示水&#xff09;组成的二维网格中&#xff0c;计算岛屿的数量。岛屿被水包围&#xff0c;并且通过水平或垂直连接相邻的陆地可以形成。这个问题的核心是识别并计数网格中相连的陆地…

CSV解析

一直以为csv靠逗号&#xff08;,&#xff09;分割数据&#xff0c;那么只要用str.spilt(,,row)便可以将数据分割开。 事实证明想简单了&#xff0c;csv里还有这样的规定&#xff0c;如果数据内有双引号&#xff08;"&#xff09;和逗号&#xff08;,&#xff09;那么&…

车载电子电器架构 —— 售后诊断开发

车载电子电器架构 —— 售后诊断开发 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己…

在Postgres中,如何有效地管理大型数据库的大小和增长

文章目录 一、定期清理和维护1. VACUUM和ANALYZE2. 删除旧数据和归档 二、分区表三、压缩数据四、配置优化1. 调整维护工作负载2. 监控和日志 五、使用外部存储和扩展1. 外部表和FDW2. 扩展和插件 六、定期备份和恢复测试结论 管理大型数据库的大小和增长是数据库管理员&#x…

Java中的变量与常量

标识符 Java语言规定标识符由任意顺序的字母、下划线&#xff08;_&#xff09;、美元符号&#xff08;$&#xff09;和数字组成&#xff0c;并且第一个字符不能是数字。标识符也不能是Java中的关键字&#xff08;保留字&#xff09;。 在Java语言中&#xff0c;标识符的字母…

环境监测系统--------MQ系列气体检测模块驱动教程(保姆级教程)

⏩ 大家好哇&#xff01;我是小光&#xff0c;嵌入式爱好者&#xff0c;一个想要成为系统架构师的大三学生。 ⏩在环境检测中我们经常会用到检测气体的传感器&#xff0c;检测乙醇、甲烷、一氧化碳、氢气等等&#xff0c;博主呕心沥血对MQ系列传感器做一个史上最详细的使用教程…

网络安全产品---堡垒机

what 在网上搜索 运维审计与风险控制系统就是是堡垒机 我认为的堡垒机就是提供高效运维、认证管理、访问控制、安全审计和报表分析功能的云服务设备 实现高效运维的同时最大程度控制运维风险。 how 能够对运维人员维护过程进行全面跟踪、控制、记录、回放 支持细粒度配置…

政企版 WPS Pro 专业版注册安装教程

政企版 WPS Pro 专业版安装及激活步骤 第 1 步&#xff1a;下载压缩包&#xff08;内含注册码&#xff09;【无解压密码】。 第 2 步&#xff1a;解压缩后&#xff0c;运行 exe 文件&#xff0c;默认步骤安装即可。 第 3 步&#xff1a;安装完成后&#xff0c;新建一个 Word …