当前位置: 首页 > news >正文

C语言实战项目<贪吃蛇>

我们这篇会使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇 实现基本的功能:

结果如下:

1.一些Win32 API知识

本次实现呢我们会用到一些Win32 API的知识(WIN32 API也就是Microsoft Windows 32位平台的应用程序编程接口):

1)控制窗口大小

我们可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小,设置控制台窗口的名字:
mode con cols=100 lines=30//设置窗口大小为100列,30行
title 贪吃蛇//设置窗口名称为贪吃蛇

我们在C语言中可以用函数system来执行,入上面这两个,我们可以这么写:

system("mode con cols=100 lines=30");
system("title 贪吃蛇");

2)控制台屏幕上的坐标

Windows API中存在这么一个结构体,叫做COORD,表示⼀个字符在控制台屏幕上的坐标
typedef struct _COORD {SHORT X;SHORT Y;
} COORD, *PCOORD;
给坐标赋值:
COORD pos = { 10, 15 };

3)GetStdHandle

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

4)GetConsoleCursorInfo

检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息
BOOL WINAPI GetConsoleCursorInfo(HANDLE hConsoleOutput,PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
实例:
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息

5)CONSOLE_CURSOR_INFO

这个结构体,包含有关控制台光标的信息

typedef struct _CONSOLE_CURSOR_INFO {DWORD dwSize;BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
dwSize,由光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的水平线条。
bVisible,游标的可见性。 如果光标可见,则此成员为 TRUE。
CursorInfo.bVisible = false; //隐藏控制台光标

 

6)SetConsoleCursorInfo

设置指定控制台屏幕缓冲区的光标的大小和可见性。
BOOL WINAPI SetConsoleCursorInfo(HANDLE hConsoleOutput,const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
实例: 
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

7)SetConsoleCursorPosition

设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置
BOOL WINAPI SetConsoleCursorPosition(HANDLE hConsoleOutput,COORD pos
);

示例:

COORD pos = { 10, 5};HANDLE hOutput = NULL;//获取标准输出的句柄(⽤来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);

用上面的这些函数我们就可以封装⼀个设置光标位置的函数

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

8)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 )

2.游戏的逻辑

在做这个游戏前,我们应该先想好怎么做,游戏每个阶段该做什么.

这里我做了一个图:

3. 贪吃蛇的整体维护和地图绘制

整体维护,我们创建一个结构体来维护整体运行:

typedef struct Snake
{pSnakeNode pSnake;//维护整条蛇的指针,是指向蛇头pSnakeNode pFood;//指向食物的指针int Score;//当前累积的分数int FoodWeight;//一个食物的分数int SleepTime;//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,蛇的速度越慢enum GAME_STATUS status;//游戏当前的状态enum DIRECTION dir;//蛇当前走的方向
}Snake, * pSnake;

我们顺便将蛇,目标,墙用宽字符定义了

#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'

窗口大小,名字,以及隐藏光标,我们都解决了,现在我们来解决打印贪吃蛇地图的问题

这里不得不讲⼀下控制台窗口的⼀些知识,如果想在控制台的窗口中指定位置输出信息,我们得知道该位置的坐标,所以首先介绍⼀下控制台窗口的坐标知识.控制台窗口的坐标如下所示,横向的是X轴,从左向右依次增长,纵向是Y轴,从上到下依次增长。
在游戏地图上,我们打印墙使用宽字符:□,打印蛇使用宽字符●,打印食物使用宽字符★
普通的字符是占一个字节的,这类宽字符是占用2个字节。
过去C语言并不适合非英语国家(地区)使用。
C语言最初假定字符都是但自己的。但是这些假定并不是在世界的任何地方都适用。

C语言字符默认是采用ASCII编码的,ASCII字符集采用的是单字节编码,且只使用了单字节中的低7 位,最高位是没有使用的,可表示为0xxxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语国家中,128个字符是基本够用的,但是,在其他国家语言中,比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,⼀些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(⼆进制10000010)。这样⼀来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不⼀样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字⺟Gimel在俄语编码中又会代表另⼀个符号。但是不管怎样,所有这些编码方式中,0--127表⽰的符号是⼀样的,不⼀样的只是128--255的这⼀段。至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。⼀个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达⼀个符号。比如,简体中文常见的编码方式是 GB2312,使用两个字节表示⼀个汉字,所以理论上最多可以表示 256 x 256 = 65536 个符号。

后来为了使C语言适应国际化,C语⾔的标准中不断加入了国际化的支持。比如:加入和宽字符的类型wchar_t 和宽字符的输入和输出函数,加入<locale.h>头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。

1)<locale.h>头文件

选自<clocale> (locale.h) - C++参考 (cplusplus.com)

2)setlocale 函数

setlocale 函数用于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
setlocale 的第一个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参数是LC_ALL,就会影响所有的类项。
C标准给第二个参数仅定义了2种可能取值:"C"和" "。
在任意程序执行开始,都会隐藏式执行调用:
setlocale(LC_ALL, "C");
当地区设置为"C"时,库函数按正常方式执行,小数点是⼀个点。
当程序运行起来后想改变地区,就只能显示调用setlocale函数。用" "作为第2个参数,调用setlocale 函数就可以切换到本地模式,这种模式下程序会适应本地环境。比如:切换到我们的本地模式后就支持宽字符(汉字)的输出等。
 setlocale(LC_ALL, " ");//切换到本地环境

在切换到我们的本地环境后,就可以打印宽字符了,

示例:

#include <stdio.h>
#include<locale.h>
int main()
{setlocale(LC_ALL, "");wchar_t ch1 = L'●';char ch2 = '&';wprintf(L"%c", ch1);printf("%c\n", ch2);printf("%c", ch2);return 0;
}

运行结果:

经过对比我们发现⼀个普通字符占⼀个字符的位置 但是打印⼀个宽字符,占用2个字符的位置,那么我们如果要在贪吃蛇中使用宽字符,就得处理好地图上坐标的计算。

3)地图坐标

我们假设实现⼀个棋盘27行,58列的棋盘(行和列可以根据自己的情况修改),再围绕地图画出墙, (图在上面)代码如下:
#define WALL L'□'
void createmap()
{//上SetPos(0, 0);for (int i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (int i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//左for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (int i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}

4.有关蛇的定义

我们创建一个结构体来维护蛇身的节点:

typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;

顺带我们枚举蛇的运动方向

enum DIRECTION
{UP,DOWN,LEFT,RIGHT
};

5.GameStart()

接下来我们开始按照流程来,现在是游戏前的准备:

1)设置窗口大小、设置窗口名字、隐藏光标

void setwindows()
{system("title 贪吃蛇");system("mode con cols=100 lines=30");
}
void hidcursor()
{//获得设备句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//定义控制台相关光标的结构体CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false;//光标可见性关闭SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态
}void SetPos(int x, int y)
{//获得设备句柄HANDLE hanlde = GetStdHandle(STD_OUTPUT_HANDLE);//根据句柄设置光标的位置COORD pos = { x, y };//设置控制台光标状态SetConsoleCursorPosition(hanlde, pos);
}

2)欢迎词

前面我们运行的时候都会有欢迎来到XXX,所以我们也做一个

void welcome()
{//欢迎信息SetPos(35, 10);printf("欢迎来到贪吃蛇小游戏\n");SetPos(38, 20);system("pause");system("cls");//功能介绍信息SetPos(15, 10);printf("用 ↑ . ↓ . ← . → 来控制蛇的移动,F3是加速,F4是减速");SetPos(15, 11);printf("加速能得到更高的分数");SetPos(38, 20);system("pause");system("cls");
}

(35,10)是我设计的打印时的光标位置,你也可以自行修改。cls是清屏

3)地图的创建与贪吃蛇的初始化

地图创建前面我讲过了这里就展示一下代码

void createmap()
{//上SetPos(0, 0);for (int i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (int i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//左for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (int i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}

贪吃蛇的初始化,包括蛇的出实话,以及各种信息的初始化。

我们使用单链表,将蛇的身体一节一节连起来,最后用头插把蛇头接上蛇身

代码示例:

	pSnakeNode cur = NULL;int i = 0;for (i = 0; i < 3; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc()");return;}cur->x = POS_X + 2 * i;cur->y = POS_Y;cur->next = NULL;//头插法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->FoodWeight = 10;ps->pFood = NULL;ps->Score = 0;ps->SleepTime = 200;ps->status = OK;

最后把这三段拼在一起就是贪吃蛇的初始化了:

//初始化贪吃蛇
void InitSnake(pSnake ps)
{pSnakeNode cur = NULL;int i = 0;for (i = 0; i < 3; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc()");return;}cur->x = POS_X + 2 * i;cur->y = POS_Y;cur->next = NULL;//头插法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->FoodWeight = 10;ps->pFood = NULL;ps->Score = 0;ps->SleepTime = 200;ps->status = OK;
}

4)食物的创建

既然我们完成了蛇的初始化,在游戏没开始的时候食物不也是创建好了吗?,所以我们接下来写事物的创建,但是我们食物的创建存在一定的条件,对吧。

存在以下两个条件:

1.食物不能与蛇重叠

2.食物不能生成在墙上

代码示例:

//创建食物
void Createfood(pSnake ps)
{int x = 0;int y = 0;again:do{x = rand() % 53 + 2;y = rand() % 24 + 1;} while (x % 2 != 0);//坐标和蛇的身体的每个节点的做坐标比较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("CreateFood()::malloc()");return;}pFood->x = x;pFood->y = y;ps->pFood = pFood;SetPos(x, y);wprintf(L"%lc", FOOD);
}

注意考虑空间开辟失败的情况。

4)打印帮助信息

我们在运行游戏时右边有一个帮助信息,这个也是提前打好的

代码示例:

//打印帮助信息
void PrintHelpInfo()
{SetPos(59, 15);printf("1.不要撞到墙");SetPos(59, 16);printf("2.不要撞到自己");SetPos(59, 17);printf("3.用 ↑ . ↓ . ← . → 来控制蛇的移动");SetPos(59, 18);printf("4.F3加速,F4减速,加速能得到更高的分数");SetPos(59, 19);printf("5.ESC退出,space暂停");SetPos(59, 20);printf("Dream_Snowar所作");
}

5)整合

最后我们将代码整合起来就是我们的要的函数

//游戏初始化
void GameStart(pSnake ps)
{setwindows();welcome();hidcursor();createmap();InitSnake(ps);Createfood(ps);PrintHelpInfo();
}

6.GameRun()

这里我们需要完成,蛇的移动,蛇吃食物,各个按键的使用

首先我们写一下函数的结构,以及判断各个按键

代码示例:

void GameRun(pSnake ps)
{		do{//当前的分数SetPos(59, 10);printf("总分:%d\n", ps->Score);SetPos(59, 11);printf("分数倍率:%02d\n", ps->FoodWeight);//检测按键,↑ . ↓ . ← . → . ESC . F3 . F4 . spaceif (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_ESCAPE)){ps->status = ESC;break;}else if (KEY_PRESS(VK_SPACE)){//游戏暂停pause();}else if (KEY_PRESS(VK_F3)){if (ps->SleepTime >= 80){ps->SleepTime -= 30;ps->FoodWeight += 2;}}else if (KEY_PRESS(VK_F4)){if (ps->FoodWeight > 2){ps->SleepTime += 30;ps->FoodWeight -= 2;}}//休眠Sleep(ps->SleepTime);//走一步Snakemove(ps);KillBySelf(ps);KillByWall(ps);} while (ps->status == OK);}

按键检测我们前面已经定义过了

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

1)暂停pause()

void pause()
{while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}}
}

2)判断下一个坐标是否是食物

int Nextblock(pSnake ps, pSnakeNode pNext)
{if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y){return 1;}else{return 0;}
}

1)是食物

void IsFood(pSnake ps, pSnakeNode pNext)
{pNext->next = ps->pSnake;ps->pSnake = pNext;pSnakeNode cur = ps->pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->Score += ps->FoodWeight;free(ps->pFood);Createfood(ps);
}

2)不是食物

void NotFood(pSnake ps, pSnakeNode pNext)
{pNext->next = ps->pSnake;ps->pSnake = pNext;pSnakeNode cur = ps->pSnake;while (cur->next->next){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;
}

3)是否撞到墙

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;}
}

4)是否撞到自己

//是否撞自己
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;return;}cur = cur->next;}
}

5)蛇移动

void Snakemove(pSnake ps)
{pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext == NULL){perror("CreateFood()::malloc()");return;}pNext->next = NULL;switch (ps->dir){case UP:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y - 1;break;case DOWN:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y + 1;break;case LEFT:pNext->x = ps->pSnake->x - 2;pNext->y = ps->pSnake->y;break;case RIGHT:pNext->x = ps->pSnake->x + 2;pNext->y = ps->pSnake->y;break;}//判断下一个坐标是都是食物if (Nextblock(ps, pNext)){IsFood(ps, pNext);}else{NotFood(ps, pNext);}
}

7.GameEnd()

完成上面的内容游戏已经可以游玩了,只是缺少了一些结束的提示,但是我们要记得最后将开辟的空间释放。

代码示例:

void GameEnd(pSnake ps)
{SetPos(15, 12);switch (ps->status){case ESC:printf("主动退出游戏,正常退出\n");break;case KILL_BY_WALL:printf("撞墙了,游戏结束\n");break;case KILL_BY_SELF:printf("咬到自己了,游戏结束\n");break;}//释放贪吃蛇的链表资源pSnakeNode cur = ps->pSnake;pSnakeNode del = NULL;while (cur){del = cur;cur = cur->next;free(del);}free(ps->pFood);ps = NULL;
}

8.整合程序

void test()
{//创建贪吃蛇Snake snake = { 0 };GameStart(&snake);//游戏前的初始化GameRun(&snake);GameEnd(&snake);
}
int main()
{setlocale(LC_ALL, "");test();//贪吃蛇游戏的测试SetPos(0, 27);return 0;
}

这样贪吃蛇就完成了

9.代码展示:

//snake.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include <locale.h>
#include<stdlib.h>
#include<stdbool.h>
#include<Windows.h>
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
//蛇默认的起始坐标
#define POS_X 24
#define POS_Y 5
//检测按键是否按了
#define KEY_PRESS(VK)  ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )//设置控制台窗口
void setwindows();
//隐藏光标
void hidcursor();
//欢迎内容
void welcome();
//创建地图
void createmap();
//光标位置
void SetPos(int x, int y);//游戏的状态
enum GAME_STATUS
{OK = 1,//正常运行ESC, //按了ESC键退出,正常退出KILL_BY_WALL,//撞墙KILL_BY_SELF //撞到自身
};//蛇行走的方向
enum DIRECTION
{UP,DOWN,LEFT,RIGHT
};//蛇身结点的定义
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;// 贪吃蛇
typedef struct Snake
{pSnakeNode pSnake;//维护整条蛇的指针,是指向蛇头pSnakeNode pFood;//指向食物的指针int Score;//当前累积的分数int FoodWeight;//一个食物的分数int SleepTime;//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,蛇的速度越慢enum GAME_STATUS status;//游戏当前的状态enum DIRECTION dir;//蛇当前走的方向
}Snake, * pSnake;//初始化贪吃蛇
void InitSnake(pSnake ps);
//创建食物  
void Createfood(pSnake ps);
//打印帮助信息
void PrintHelpInfo();
//游戏初始化
void GameStart(pSnake ps);
//蛇移动
void Snakemove(pSnake ps);
//打印蛇
void PrintShake(pSnake ps);
//判断下一个坐标是否是食物
int Nextblock(pSnake ps, pSnakeNode pNext);
//是食物
void IsFood(pSnake ps,pSnakeNode pNext);
// 不是食物
void NotFood(pSnake ps, pSnakeNode pNext);
//是否撞自己
void KillBySelf(pSnake ps);
//是否撞墙
void KillByWall(pSnake ps);
//游戏进程
void GameRun(pSnake ps);
//游戏结束的资源释放
void GameEnd(pSnake ps);
//snake.c
#include"shake.h"
void setwindows()
{system("title 贪吃蛇");system("mode con cols=100 lines=30");
}
void hidcursor()
{//获得设备句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//定义控制台相关光标的结构体CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false;//光标可见性关闭SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态
}void SetPos(int x, int y)
{//获得设备句柄HANDLE hanlde = GetStdHandle(STD_OUTPUT_HANDLE);//根据句柄设置光标的位置COORD pos = { x, y };//设置控制台光标状态SetConsoleCursorPosition(hanlde, pos);
}void welcome()
{//欢迎信息SetPos(35, 10);printf("欢迎来到贪吃蛇小游戏\n");SetPos(38, 20);system("pause");system("cls");//功能介绍信息SetPos(15, 10);printf("用 ↑ . ↓ . ← . → 来控制蛇的移动,F3是加速,F4是减速");SetPos(15, 11);printf("加速能得到更高的分数");SetPos(38, 20);system("pause");system("cls");
}
//创建地图
void createmap()
{//上SetPos(0, 0);for (int i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (int i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//左for (int i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (int i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}//初始化贪吃蛇
void InitSnake(pSnake ps)
{pSnakeNode cur = NULL;int i = 0;for (i = 0; i < 3; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc()");return;}cur->x = POS_X + 2 * i;cur->y = POS_Y;cur->next = NULL;//头插法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->FoodWeight = 10;ps->pFood = NULL;ps->Score = 0;ps->SleepTime = 200;ps->status = OK;
}
//创建食物
void Createfood(pSnake ps)
{int x = 0;int y = 0;again:do{x = rand() % 53 + 2;y = rand() % 24 + 1;} while (x % 2 != 0);//坐标和蛇的身体的每个节点的做坐标比较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("CreateFood()::malloc()");return;}pFood->x = x;pFood->y = y;ps->pFood = pFood;SetPos(x, y);wprintf(L"%lc", FOOD);
}//打印帮助信息
void PrintHelpInfo()
{SetPos(59, 15);printf("1.不要撞到墙");SetPos(59, 16);printf("2.不要撞到自己");SetPos(59, 17);printf("3.用 ↑ . ↓ . ← . → 来控制蛇的移动");SetPos(59, 18);printf("4.F3加速,F4减速,加速能得到更高的分数");SetPos(59, 19);printf("5.ESC退出,space暂停");SetPos(59, 20);printf("Dream_Snowar所作");
}//游戏初始化
void GameStart(pSnake ps)
{setwindows();welcome();hidcursor();createmap();InitSnake(ps);Createfood(ps);PrintHelpInfo();
}//判断下一个坐标是否是食物
int Nextblock(pSnake ps, pSnakeNode pNext)
{if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y){return 1;}else{return 0;}
}//space暂停
void pause()
{while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}}
}
//打印蛇
void PrintShake(pSnake ps)
{pSnakeNode cur = ps->pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}
}//是食物
void IsFood(pSnake ps, pSnakeNode pNext)
{pNext->next = ps->pSnake;ps->pSnake = pNext;PrintShake(ps);ps->Score += ps->FoodWeight;free(ps->pFood);Createfood(ps);
}
//不是食物
void NotFood(pSnake ps, pSnakeNode pNext)
{pNext->next = ps->pSnake;ps->pSnake = pNext;pSnakeNode cur = ps->pSnake;while (cur->next->next){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 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;return;}cur = cur->next;}
}
//是否撞墙
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 Snakemove(pSnake ps)
{pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext == NULL){perror("CreateFood()::malloc()");return;}pNext->next = NULL;switch (ps->dir){case UP:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y - 1;break;case DOWN:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y + 1;break;case LEFT:pNext->x = ps->pSnake->x - 2;pNext->y = ps->pSnake->y;break;case RIGHT:pNext->x = ps->pSnake->x + 2;pNext->y = ps->pSnake->y;break;}//判断下一个坐标是都是食物if (Nextblock(ps, pNext)){IsFood(ps, pNext);}else{NotFood(ps, pNext);}
}
//游戏运行
void GameRun(pSnake ps)
{		do{//当前的分数SetPos(59, 10);printf("总分:%d\n", ps->Score);SetPos(59, 11);printf("分数倍率:%02d\n", ps->FoodWeight);//检测按键,↑ . ↓ . ← . → . ESC . F3 . F4 . spaceif (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_ESCAPE)){ps->status = ESC;break;}else if (KEY_PRESS(VK_SPACE)){//游戏暂停pause();}else if (KEY_PRESS(VK_F3)){if (ps->SleepTime >= 80){ps->SleepTime -= 30;ps->FoodWeight += 2;}}else if (KEY_PRESS(VK_F4)){if (ps->FoodWeight > 2){ps->SleepTime += 30;ps->FoodWeight -= 2;}}//休眠Sleep(ps->SleepTime);//走一步Snakemove(ps);KillBySelf(ps);KillByWall(ps);} while (ps->status == OK);}
void GameEnd(pSnake ps)
{SetPos(15, 12);switch (ps->status){case ESC:printf("主动退出游戏,正常退出\n");break;case KILL_BY_WALL:printf("撞墙了,游戏结束\n");break;case KILL_BY_SELF:printf("咬到自己了,游戏结束\n");break;}//释放贪吃蛇的链表资源pSnakeNode cur = ps->pSnake;pSnakeNode del = NULL;while (cur){del = cur;cur = cur->next;free(del);}free(ps->pFood);ps = NULL;
}
//test.c
#include"shake.h"
void test()
{//创建贪吃蛇Snake snake = { 0 };GameStart(&snake);//游戏前的初始化GameRun(&snake);GameEnd(&snake);
}
int main()
{setlocale(LC_ALL, "");test();//贪吃蛇游戏的测试SetPos(0, 27);return 0;
}

C语言的学习结束了,把贪吃蛇作为最后的收官之作是我目前的想法,接下来我重点完成,C语言实践和数据结构的博客的撰写,希望大家多多支持.

相关文章:

C语言实战项目<贪吃蛇>

我们这篇会使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇 实现基本的功能&#xff1a; 结果如下: 1.一些Win32 API知识 本次实现呢我们会用到一些Win32 API的知识(WIN32 API也就是Microsoft Windows 32位平台的应用程序编程接口): 1)控制窗口大小 我们可以使用…...

人工智能时代:AI提示工程的奥秘 —— 驾驭大语言模型的秘密武器

文章目录 一、引言二、提示工程与大语言模型三、大语言模型的应用实践四、策略与技巧五、结语《AI提示工程实战&#xff1a;从零开始利用提示工程学习应用大语言模型》亮点内容简介作者简介目录获取方式 一、引言 随着人工智能技术的飞速发展&#xff0c;大语言模型作为一种新…...

Idea编写mapper.xml文件提示表名和字段

一、连接database 二、setting- > language -> sql Dialects中 的选项设为 mysql就可以了 三、测试...

解密人工智能:探索机器学习奥秘

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 机器学习的定义二. 机器学习的发展历程三. 机器学习的原理四. 机器学习的分类…...

C语言第十四弹---函数递归

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 函数递归 1、递归是什么&#xff1f; 1.1、递归的思想 1.2、递归的限制条件 2、递归举例 2.1、举例1&#xff1a;求n的阶乘 2.1.1、分析和代码实现 2.1.2、…...

etcd自动化安装配置教程

文章目录 前言一、简介1. 简介2. 特点3. 端口介绍 二、etcd安装教程&#xff08;单机版&#xff09;1. 复制脚本2. 增加执行权限3. 执行脚本4. 查看启动状态5. 卸载etcd 三、etcd安装教程&#xff08;集群版&#xff09;1. 复制脚本2. 增加执行权限3. 分发脚本4. 执行脚本5. 启…...

时间序列预测——GRU模型

时间序列预测——GRU模型 在深度学习领域&#xff0c;循环神经网络&#xff08;RNN&#xff09;是处理时间序列数据的一种常见选择。上期已介绍了LSTM的单步和多步预测。本文将深入介绍一种LSTM变体——门控循环单元&#xff08;GRU&#xff09;模型&#xff0c;包括其理论基础…...

通用CI/CD软件平台TeamCity全新发布v2023.11——增强Git托管平台的集成

TeamCity是一个通用的 CI/CD 软件平台&#xff0c;可以实现灵活的工作流、协作和开发做法。我们的解决方案将帮助在您的 DevOps 流程中成功实现持续集成、持续交付和持续部署。 TeamCity 2023.11正式版下载 TeamCity 2023.11 带来了矩阵构建和构建缓存等多项备受期待的功能&a…...

C语言:register类型变量

register—— 寄存器存储 register 是 C 语言中的一种存储类别&#xff08;Storage Class&#xff09;&#xff0c;它用于告诉编译器将变量存储在寄存器中。在 C 语言中&#xff0c;变量的存储位置可以是寄存器、堆栈或静态存储区&#xff0c;使用 register 存储类别可以帮助我…...

android 自定义下拉框

一、 简介&#xff1a; 原生Android 提供的spinner下拉框不怎么方便&#xff0c;样式有点丑。修改起来麻烦&#xff0c;于是就自己动手写了一下拉列表。 实现原理使用的是&#xff0c;popwindow弹框&#xff0c;可实现宽高自定义&#xff0c;下拉列表使用listview. 二、pop弹框…...

揭开时间序列的神秘面纱:特征工程的力量

目录 写在开头1. 什么是特征工程?1.1 特征工程的定义和基本概念1.2 特征工程在传统机器学习中的应用1.3 时间序列领域中特征工程的独特挑战和需求3. 时间序列数据的特征工程技术2.1 数据清洗和预处理2.1.1 缺失值处理2.1.2 异常值检测与处理2.2 时间特征的提取2.2.1 时间戳解析…...

vue3 源码解析(5)— patch 函数源码的实现

什么是 patch 在 vue 中 patch 函数的作用是在渲染的过程中&#xff0c;比较新旧节点的变化&#xff0c;通过打补丁的形式&#xff0c;进行新增、删除、移动或替换操作&#xff0c;此过程避免了大量的 dom 操作&#xff0c;提升了运行的性能。 patch 执行流程 patch 函数整体…...

蓝桥杯2024/1/28----十二届省赛题笔记

题目要求&#xff1a; 2、 竞赛板配置要求 2.1将 IAP15F2K61S2 单片机内部振荡器频率设定为 12MHz。 2.2键盘工作模式跳线 J5 配置为 KBD 键盘模式。 2.3扩展方式跳线 J13 配置为 IO 模式。 2.4 请注意 &#xff1a; 选手需严格按照以上要求配置竞赛板&#xff0c;编写和调…...

STM32+ESP8266 实现物联网设备节点

目录 一、硬件准备 二、编译环境 三、源代码地址 四、说明 五、测试方法 六、所有测试工具和文档 本项目使用stm32F103ZEesp8266实现一个物联网的通信节点&#xff0c;目前支持的协议有mqtt&#xff0c;tcp。后续会持续更新&#xff0c;增加JSON&#xff0c;传感器&#…...

免费的ChatGPT网站(7个)

还在为找免费的chatGPT网站或者应用而烦恼吗&#xff1f;博主归纳总结了7个国内非常好用&#xff0c;而且免费的chatGPT网站&#xff0c;AI语言大模型&#xff0c;我们都来接触一下吧。 免费&#xff01;免费&#xff01;免费&#xff01;...&#xff0c;建议收藏保存。 1&…...

Go语言基础之单元测试

1.go test工具 Go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的&#xff0c;并不需要学习新的语法、规则或工具。 go test命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内&#xff0c;所有以_test.go为后缀名的源代码文件都是go …...

C++ easyX小程序(介绍几个函数的使用)

本小程序通过代码和注释&#xff0c;介绍了easyX窗口及控制台窗口的设置方法&#xff1b;还介绍了easyX中关于颜色、线型、画圆、画方、显示文字以及鼠标消息处理等函数的使用方法。为便于理解&#xff0c;本程序同时使用控制台和easyX窗口&#xff0c;由控制台控制程序运行、由…...

配置nginx以成功代理websocket

配置nginx以成功代理websocket 在使用socket.io的时候遇到这样一个问题&#xff1a;websocket接收的消息的顺序错位了&#xff0c;然后看了一下浏览器的console的报错&#xff0c;提示连接到ws失败&#xff0c;然后在浏览器的开发者工具的网络中看了一下ws对应的消息里面报错&…...

代码随想录算法训练营第二十二天|235.二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点

文档讲解&#xff1a; BST&#xff0c;各种插入删除操作 235.二叉搜索树的最近公共祖先 思路&#xff1a;昨天练习了二叉树的搜索&#xff0c;今天这道题是二叉搜索树的搜索&#xff0c;其具有有序这个特点&#xff0c;其能决定我们每次搜索是进入该节点的左子树还是右子树&…...

collection、ofType、select的联合用法(Mybatis实现树状结构查询)

需求 得到树结构数据也可以用lambda表达式也行&#xff0c;也可以直接循环递归也行&#xff0c;本文采用的是直接在Mybatis层得到结果&#xff0c;各有各的优势。 代码 1、实体类 Data public class CourseChapterVO implements Serializable {private static final long s…...

FLUENT Meshing Watertight Geometry工作流入门 - 4 局部加密区域

本视频中学到的内容&#xff1a; 使用Watertight Geometry Workflow 的 Create Local Refinement Regions 任务来创建细化的网格区域 视频链接&#xff1a; FLUENT Meshing入门教程-4创建局部加密区域_哔哩哔哩_bilibili 可以通过使用 Watertight Geometry Workflow 的 Create…...

前端添加富文本/Web 富文本编辑器wangeditor

官网wangEditor 需要引入两个文件 <link href"https://unpkg.com/wangeditor/editorlatest/dist/css/style.css" rel"stylesheet"> <script src"https://unpkg.com/wangeditor/editorlatest/dist/index.js"></script> 前端…...

软件价值2-贪吃蛇游戏

贪吃蛇游戏虽然很多&#xff0c;不过它可以作为软件创作的开端&#xff0c;用python来实现&#xff0c;然后dist成windows系统可执行文件。 import pygame import sys import random# 初始化 pygame.init()# 游戏设置 width, height 640, 480 cell_size 20 snake_speed 15# …...

应用案例 | 基于三维机器视觉的汽车副车架在线测量解决方案

在汽车制造领域中&#xff0c;精确的测量是确保产品质量和生产效率的关键。随着科技的不断进步&#xff0c;测量技术也在不断精进。 副车架是汽车底盘的重要组成部分&#xff0c;负责支撑引擎&#xff0c;是车辆结构中至关重要的组成部分之一&#xff0c;其制造质量直接关系到汽…...

线程的创建和使用threading.Thread()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 线程的创建和使用 threading.Thread() [太阳]选择题 关于以下代码的输出是&#xff1f; import threading import time def f(name): print(name) for i in range(3): print…...

大数据学习之Redis,十大数据类型的具体应用(四)

3.8 Redis基数统计&#xff08;HyperLogLog&#xff09; 需求 统计某个网站的UV、统计某个文章的UV 什么是UV unique Visitor &#xff0c;独立访客&#xff0c;一般理解为客户端IP 大规模的防止作弊&#xff0c;需要去重复统计独立访客 比如IP同样就认为是同一个客户 需要去…...

哪个牌子的头戴式耳机好?推荐性价比高的头戴式耳机品牌

随着科技的不断发展&#xff0c;耳机市场也呈现出百花齐放的态势&#xff0c;从高端的奢侈品牌到亲民的平价品牌&#xff0c;各种款式、功能的耳机层出不穷&#xff0c;而头戴式耳机作为其中的一员&#xff0c;凭借其优秀的音质和降噪功能&#xff0c;受到了广大用户的喜爱&…...

Java EE 5 SDK架构

Java EE 5 SDK架构 大型组织每天都要处理大量数据和多用户的相关事务。为管理该组织如此大型而又复杂的系统,开发了企业应用程序。企业应用程序是在服务器上托管的应用程序,通过计算机网络同时向大量用户提供服务。这种应用程序可采用各种技术开发,如Java EE 5。Java EE 5平…...

nop-entropy可逆计算入门(1)

第1步&#xff1a;从大佬的gitee&#xff1a;https://gitee.com/canonical-entropy/nop-entropy下载源码&#xff0c;进行本地编译&#xff0c;具体编译看项目下的readme,想偷懒的可以下载我编译后的jar&#xff0c;放到自己的maven仓库 https://pan.baidu.com/s/15qANnrCh5RV…...

C++(9) 虚函数

文章目录 虚函数1. 虚函数1.1 虚函数案例11.2 虚函数案例21.2 纯虚函数1.3 纯虚函数语法要求总环1.4 纯虚函数应用1.4.1 生活案例1.4.2 虚函数引用代码 虚函数 1. 虚函数 1.1 虚函数案例1 #include <iostream>using namespace std;class Animal { public:// Animal 类…...