贪吃蛇大作战(C语言--实战项目)

2024-05-07 22:12

本文主要是介绍贪吃蛇大作战(C语言--实战项目),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

朋友们!好久不见。经过一段时间的沉淀,我这篇文章来和大家分享贪吃蛇大作战这个游戏是怎么实现的。

(一).贪吃蛇背景了解及效果展示

首先相信贪吃蛇游戏绝对称的上是我们00后的童年,不仅是贪吃蛇还有俄罗斯⽅块,扫雷等都是以前十分流行的游戏,下面我们就通过代码的形式进行贪吃蛇的实现,在进行代码实现之前我们先来看看实现的效果看看有些什么,相信大家一定会对它十分感兴趣的。

贪吃蛇游戏

上面这个就是关于贪吃蛇游戏的实现效果图,相信大家都十分的想了解到底它是怎样实现的,那么下面我们就来学习关于它的实现。

(二).预了解知识(相关Win32API介绍)

首先在实现贪吃蛇之前我们要先了解一些关于Win32API的知识。

2.1.控制台程序

在这里我们需要改变控制台,才能完成贪吃蛇游戏,如图:

这里我们首先要将默认终端应用程序改成如图所示,

mode命令:指的是改变控制台界面大小

如图所示:

title命令:改变控制台的名字

总的来说这两个命令实际运用如图所示:

改变的就是如图中的两个东西实际代码如下:

#include<stdio.h>
int main()
{system("mode con cols=100 lines=30");system("title 贪吃蛇");return 0;
}

 2.2.控制台屏幕上的坐标COORD

COORD也就是控制台屏幕上的坐标

也就是如图所示的坐标。

COORD类型的声明:

typedef struct _COORD {SHORT X;SHORT Y;
} COORD, *PCOORD;

 给坐标赋值:

COORD pos = { 10, 15 };

2.3.GetStdHandle

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

这里简而言之就是和我们鼠标相同的东西。

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

 这里运用GetStdHandle来获取句柄。

2.4.GetConsoleCursorInfo 函数

作用:检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息

 GetConsoleCursorInfo 函数的实际运用就是如图所示,用来获取控制台光标的信息。

2.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; //隐藏控制台光标

2.6.SetConsoleCursorInfo 

作用:设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。

HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

这里就是用 SetConsoleCursorInfo函数来设置控制台光标状态, 只有运用了这个函数光标才能够被隐藏。

2.7.SetConsoleCursorPosition

作用:设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调 ⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。简而言之就是将光标设置在自己想要它在的地方通过坐标的形式。

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

 SetPos:封装⼀个设置光标位置的函数

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

 2.8GetAsyncKeyState

获取按键情况,GetAsyncKeyState的函数原型如下:

SHORT GetAsyncKeyState(int vKey
);

 实例:检测数字键

#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
#include <stdio.h>
#include <windows.h>
int main()
{ while (1){if (KEY_PRESS(0x30)){printf("0\n");}else if (KEY_PRESS(0x31)){printf("1\n");}else if (KEY_PRESS(0x32)){printf("2\n");}else if (KEY_PRESS(0x33)){printf("3\n");}else if (KEY_PRESS(0x34)){printf("4\n");}else if (KEY_PRESS(0x35)){printf("5\n");}else if (KEY_PRESS(0x36))
{printf("6\n");}else if (KEY_PRESS(0x37)){printf("7\n");}else if (KEY_PRESS(0x38)){printf("8\n");}else if (KEY_PRESS(0x39)){printf("9\n");}}return 0;
}

这里就是用来检测键盘上面的键是否被按过。

2.9设置本地化环境

<locale.h>本地化

提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样⾏为的部分。 在标准中,依赖地区的部分有以下⼏项:

• 数字量的格式

• 货币量的格式

• 字符集

• ⽇期和时间的表⽰形式

2.10.setlocale

C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)。

在我们实现的时候通常我们会先把它设置为本地模式,以便进行环境的匹配。

setlocale(LC_ALL, "C"); 注意这里使用setlocale函数要有头文件<locale.h>。

2.11.宽字符的打印

那如果想在屏幕上打印宽字符,怎么打印呢?

宽字符的字⾯量必须加上前缀“L”,否则 C 语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引 号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应 wprintf() 的占位符为 %ls 。

这三个内容其实是连在一起用的,要先进行本地环境的设置再来进行宽字符的打印。

总结:上面的全部都是我们在完成贪吃蛇之前需要了解的知识,可能我们就这样直接来看可能不容易理解下面我们进行贪吃蛇的实现将这些知识带进去我们大家可能更容易理解。 

(三).项目实践

 项目进行之前我们要先进行准备工作,首先定义一个结构体,来包装贪吃蛇

typedef struct Snake
{pSnakeNode _psnake;//指向蛇头的指针pSnakeNode _pfood;//指向食物节点的指针enum DIRECTION _dir;//蛇的方向enum GAME_STATUS _status;//游戏的状态int _food_weight;//一个食物的分数int _score;//总分数int _sleep_time;//休息时间,休息时间越短,速度越快
}Snake,* pSnake;

这里面就是贪吃蛇的全部东西我们这样将它的七个内容定义成一个结构体的好处是有利于后期的管理和实现,同样里面蛇的方向有多个,游戏状态也有多种,这两个我们就可以利用枚举的方式来包装起来,蛇的节点类型其实就是一个链表同样这里我们也需要定义一个结构体来表示蛇的节点,这三个实现的代码我总结到下面:

//枚举蛇的方向
enum DIRECTION
{UP,DOWN,LEFT,RIGHT
};
//枚举游戏的状态
enum GAME_STATUS {OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自己END_NORMAL //正常退出
};//蛇身节点类型
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;}SnakeNode,* pSnakeNode;

好了当我们把这些准备工作做好以后我们就可以来进行贪吃蛇的实现了,首先我们来看游戏开始部分。

3.1.游戏开始(GameStart)

游戏开始阶段一共有七个内容我们来逐一学习。 

3.1.1.设置游戏窗⼝的⼤⼩

设置游戏窗口的大小,就是我们上面说的mode命令 

system("mode con cols=100 lines=30");就是有system函数来设置控制台窗口的大小。

3.1.2.设置窗⼝的名字

设置窗口的名字就是通过title命令来设置窗口的名字

 system("title 贪吃蛇");就是运用system函数来设置窗口的名字

当这两个程序完成之后的窗口样子就是如下:

3.1.3.隐藏屏幕光标

隐藏光标实际上就是把如图所示的东西给隐藏掉

 那么这个程序我们该如何去写呢?接下来紧跟我们脚步我们一起来学习

通过上面我们了解的一些知识我们先看看下面这段代码:


//设置光标的坐标
void SetPos(short x, short y)
{HANDLE hOutput = NULL;//获取标准输出的句柄(⽤来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posCOORD pos = { x, y };SetConsoleCursorPosition(hOutput, pos);
}//获取输出设备的句柄
HANDLE hOutput = NULL;
hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo = {0};//定义一个光标信息的结构体
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false;//隐藏控制台光标  
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置光标信息

 这里面就解释了我们如何来隐藏光标,先获取句柄,定义一个houtput来接收,再通过定义一个结构体,在获取控制台光标信息,最后再来隐藏控制台光标。

3.1.4.打印欢迎界⾯-WelcomeToGame

 

欢迎界面就是如图所示,同样我们的代码程序如下:

void WelcomeToGame()
{SetPos(38,14);wprintf(L"欢迎来到贪吃蛇小游戏");SetPos(40, 18);system("pause");system("cls");//清理界面SetPos(20, 14);wprintf(L"用↑ . ↓ . ← . →来控制蛇的移动,按F3加速,按F4减速\n");SetPos(20, 15);wprintf(L"加速能够获得更高的分数\n");SetPos(40, 20);system("pause");system("cls");
}

 

3.1.5.创建地图-CreateMap

地图创建我们需要我们来进行创建以下的界面

 

 

创建一个x轴为58y轴为26的一个游戏地图,这里我们就可以运用循环语句来打印□从而创建游戏窗口。

实际代码如图所示:

void CreatMap()
{//上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 < 26; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i < 26; i++){SetPos(56, i);wprintf(L"%lc", WALL);}}

上面的SetPos是指的是定位光标的位置。

3.1.6.初始化蛇⾝-InitSnake

//创建蛇身
void InitSnack(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;for (i; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnack()::malloc");return;}cur->next = NULL;cur->x = POS_X + 2 * i;cur->y = POS_Y;//采用头插法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->_score = 0;ps->_food_weight = 10;ps->_sleep_time = 200;//单位是毫秒ps->_status = OK;}

3.1.7.创建⻝物

//创建食物
void CreatFood(pSnake ps)
{int x = 0;int y = 0;//生成x必须是2的倍数//x:2--54//y:1--25
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//x和y不能与蛇身体的位置冲突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("pFood::malloc");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;SetPos(x, y);wprintf(L"%lc", FOOD);ps->_pfood = pFood;
}

3.2.游戏进行(GameRun)

3.2.1.右侧打印帮助信息-PrintHelpInfo

//打印运行游戏旁边的提示信息
void printfhelpInfo()
{SetPos(64, 12);wprintf(L"%ls", L"不能撞墙,不能咬到自己");SetPos(64, 13);wprintf(L"%ls", L"用↑ . ↓ . ← . →来控制蛇的移动,");SetPos(64, 14);wprintf(L"%ls", L"按F3加速,按F4减速");SetPos(64, 15);wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");}

3.2.2.打印当前已获得分数和每个⻝物的分数 

//打印总分数和食物的分值SetPos(64, 9);printf("总分数:%d\n", ps->_score);SetPos(64, 10);printf("当前食物的分数:%2d\n", ps->_food_weight);

3.2.3.获取按键情况-KEY_PRESS

//按键的设置
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();
}
else if (KEY_PRESS(VK_ESCAPE))
{//正常退出ps->_status = END_NORMAL;
}
else if (KEY_PRESS(VK_F3))
{if(ps->_sleep_time>80){ps->_sleep_time -= 30;ps->_food_weight += 2;}}
else if(KEY_PRESS(VK_F4))
{if (ps->_food_weight > 2){ps->_sleep_time += 30;ps->_food_weight -= 2;}
}

3.2.4.根据按键情况移动蛇-SnakeMove 2~4循环,直到游戏是结束状态

void SnakeMove(pSnake ps)
{//创建一个节点表示蛇即将要到的下一个节点pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("pNextNode()::malloc!");return;}switch (ps->_dir){case UP:pNextNode->x = ps->_psnake->x;pNextNode->y = ps->_psnake->y-1;break;case DOWN:pNextNode->x = ps->_psnake->x;pNextNode->y = ps->_psnake->y + 1;break;case LEFT:pNextNode->x = ps->_psnake->x-2;pNextNode->y = ps->_psnake->y;break;case RIGHT:pNextNode->x = ps->_psnake->x+2;pNextNode->y = ps->_psnake->y;break;}//检测下一个坐标是否是食物if (NextIsFood(pNextNode,ps)){EatFood(pNextNode, ps);}else{NoFood(pNextNode, ps);}//检测是否撞墙KillByWall(ps);//检测是否撞到自己KillBySelf(ps);}
3.2.4.1. 根据蛇头的坐标和⽅向,计算下⼀个节点的坐标
void SnakeMove(pSnake ps)
{//创建一个节点表示蛇即将要到的下一个节点pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("pNextNode()::malloc!");return;}switch (ps->_dir){case UP:pNextNode->x = ps->_psnake->x;pNextNode->y = ps->_psnake->y-1;break;case DOWN:pNextNode->x = ps->_psnake->x;pNextNode->y = ps->_psnake->y + 1;break;case LEFT:pNextNode->x = ps->_psnake->x-2;pNextNode->y = ps->_psnake->y;break;case RIGHT:pNextNode->x = ps->_psnake->x+2;pNextNode->y = ps->_psnake->y;break;}//检测下一个坐标是否是食物if (NextIsFood(pNextNode,ps)){EatFood(pNextNode, ps);}else{NoFood(pNextNode, ps);}//检测是否撞墙KillByWall(ps);//检测是否撞到自己KillBySelf(ps);}
3.2.4.2. 判断下⼀个节点是否是⻝物-NextIsFood
int NextIsFood(pSnakeNode pn, pSnake ps)
{return (ps->_pfood->x == pn->x && ps->_pfood->y == pn->y);
}
3.2.4.3.是⻝物就吃掉-EatFood
void EatFood(pSnakeNode pn, pSnake ps)
{//采用头插法将食物放上去ps->_pfood->next = ps->_psnake;ps->_psnake = ps->_pfood;free(pn);pn = NULL;pSnakeNode cur = ps->_psnake;//打印while (cur){SetPos(cur->x,cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->_score += ps->_food_weight;//重新创建食物CreatFood(ps);
}
3.2.4.4.不是⻝物,吃掉⻝物,尾巴删除⼀节-NoFood
void NoFood(pSnakeNode pn, pSnake ps)
{pn->next = ps->_psnake;ps->_psnake = pn;pSnakeNode cur = ps->_psnake;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);cur->next = NULL;
}
3.2.4.5.判断是否撞墙-KillByWall
//检测是否撞墙
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;}
}
3.2.4.6.判断是否撞上⾃⼰-KillBySelf
//检测是否撞到自己
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;}cur = cur->next;}
}

3.3游戏结束(GameEnd)

3.3.1.告知游戏结束的原因

3.3.2.释放蛇⾝节点

//结束游戏--善后工作
void GameEnd(pSnake ps)
{switch (ps->_status){case END_NORMAL:printf("主动结束游戏\n");break;case KILL_BY_SELF:printf("撞到了自己,游戏结束\n");break;case KILL_BY_WALL:printf("撞到了墙游戏结束\n");break;}//释放链表pSnakeNode cur = ps->_psnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

(四).代码汇总

4.1.snake.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<locale.h>
#include<windows.h>
#include<stdbool.h>
#include<stdlib.h>
#include<time.h>#define POS_X 24
#define POS_Y 5#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
//枚举蛇的方向
enum DIRECTION
{UP,DOWN,LEFT,RIGHT
};
//枚举游戏的状态
enum GAME_STATUS {OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自己END_NORMAL //正常退出
};//蛇身节点类型
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;}SnakeNode,* pSnakeNode;//贪吃蛇
typedef struct Snake
{pSnakeNode _psnake;//指向蛇头的指针pSnakeNode _pfood;//指向食物节点的指针enum DIRECTION _dir;//蛇的方向enum GAME_STATUS _status;//游戏的状态int _food_weight;//一个食物的分数int _score;//总分数int _sleep_time;//休息时间,休息时间越短,速度越快
}Snake,* pSnake;
//定位光标位置
void SetPos(short x, short y);//初始化游戏
void GameStart(pSnake ps);//打印欢迎界面和功能介绍
void WelcomeToGame(pSnake ps);//2.绘制地图
void CreatMap();//初始化蛇
void InitSnack(pSnake ps);//创建食物
void CreatFood(pSnake ps);//运行游戏
void GameRun(pSnake ps);//蛇的移动
void SnakeMove(pSnake ps);//实现暂停逻辑
void pause();//检测下一个坐标是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps);//下一个位置是食物吃掉食物
void EatFood(pSnakeNode pn, pSnake ps);//如果下一个坐标不是食物
void NoFood(pSnakeNode pn, pSnake ps);//检测是否撞墙
void KillByWall(pSnake ps);//检测是否撞到自己
void KillBySelf(pSnake ps);//结束游戏--善后工作
void GameEnd(pSnake ps);

4.2.snake.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"//设置光标的坐标
void SetPos(short x, short y)
{HANDLE hOutput = NULL;//获取标准输出的句柄(⽤来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posCOORD pos = { x, y };SetConsoleCursorPosition(hOutput, pos);
}
//1.打印欢迎界面和功能介绍
void WelcomeToGame()
{SetPos(38,14);wprintf(L"欢迎来到贪吃蛇小游戏");SetPos(40, 18);system("pause");system("cls");//清理界面SetPos(20, 14);wprintf(L"用↑ . ↓ . ← . →来控制蛇的移动,按F3加速,按F4减速\n");SetPos(20, 15);wprintf(L"加速能够获得更高的分数\n");SetPos(40, 20);system("pause");system("cls");
}//2.绘制地图
void CreatMap()
{//上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 < 26; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i < 26; i++){SetPos(56, i);wprintf(L"%lc", WALL);}}//创建蛇身
void InitSnack(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;for (i; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnack()::malloc");return;}cur->next = NULL;cur->x = POS_X + 2 * i;cur->y = POS_Y;//采用头插法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->_score = 0;ps->_food_weight = 10;ps->_sleep_time = 200;//单位是毫秒ps->_status = OK;}//创建食物
void CreatFood(pSnake ps)
{int x = 0;int y = 0;//生成x必须是2的倍数//x:2--54//y:1--25
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//x和y不能与蛇身体的位置冲突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("pFood::malloc");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;SetPos(x, y);wprintf(L"%lc", FOOD);ps->_pfood = pFood;
}void GameStart(pSnake ps)
{//首先设置窗口的大小,进行光标隐藏system("mode con cols=100 lines=30");system("title 贪吃蛇");//获取输出设备的句柄HANDLE hOutput = NULL;hOutput=GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo = {0};//定义一个光标信息的结构体GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false;//隐藏控制台光标  SetConsoleCursorInfo(hOutput, &CursorInfo);//设置光标信息//1.打印欢迎界面和功能介绍WelcomeToGame();//2.绘制地图CreatMap();//3.创建蛇InitSnack(ps);//4.创建食物CreatFood(ps);
}//打印运行游戏旁边的提示信息
void printfhelpInfo()
{SetPos(64, 12);wprintf(L"%ls", L"不能撞墙,不能咬到自己");SetPos(64, 13);wprintf(L"%ls", L"用↑ . ↓ . ← . →来控制蛇的移动,");SetPos(64, 14);wprintf(L"%ls", L"按F3加速,按F4减速");SetPos(64, 15);wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");}void pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}int NextIsFood(pSnakeNode pn, pSnake ps)
{return (ps->_pfood->x == pn->x && ps->_pfood->y == pn->y);
}void EatFood(pSnakeNode pn, pSnake ps)
{//采用头插法将食物放上去ps->_pfood->next = ps->_psnake;ps->_psnake = ps->_pfood;free(pn);pn = NULL;pSnakeNode cur = ps->_psnake;//打印while (cur){SetPos(cur->x,cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->_score += ps->_food_weight;//重新创建食物CreatFood(ps);
}void NoFood(pSnakeNode pn, pSnake ps)
{pn->next = ps->_psnake;ps->_psnake = pn;pSnakeNode cur = ps->_psnake;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);cur->next = NULL;
}//检测是否撞墙
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 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;}cur = cur->next;}
}
void SnakeMove(pSnake ps)
{//创建一个节点表示蛇即将要到的下一个节点pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("pNextNode()::malloc!");return;}switch (ps->_dir){case UP:pNextNode->x = ps->_psnake->x;pNextNode->y = ps->_psnake->y-1;break;case DOWN:pNextNode->x = ps->_psnake->x;pNextNode->y = ps->_psnake->y + 1;break;case LEFT:pNextNode->x = ps->_psnake->x-2;pNextNode->y = ps->_psnake->y;break;case RIGHT:pNextNode->x = ps->_psnake->x+2;pNextNode->y = ps->_psnake->y;break;}//检测下一个坐标是否是食物if (NextIsFood(pNextNode,ps)){EatFood(pNextNode, ps);}else{NoFood(pNextNode, ps);}//检测是否撞墙KillByWall(ps);//检测是否撞到自己KillBySelf(ps);}//运行游戏
void GameRun(pSnake ps)
{printfhelpInfo();do {//打印总分数和食物的分值SetPos(64, 9);printf("总分数:%d\n", ps->_score);SetPos(64, 10);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();}else if (KEY_PRESS(VK_ESCAPE)){//正常退出ps->_status = END_NORMAL;}else if (KEY_PRESS(VK_F3)){if(ps->_sleep_time>80){ps->_sleep_time -= 30;ps->_food_weight += 2;}}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);} while (ps->_status==OK);
}//结束游戏--善后工作
void GameEnd(pSnake ps)
{switch (ps->_status){case END_NORMAL:printf("主动结束游戏\n");break;case KILL_BY_SELF:printf("撞到了自己,游戏结束\n");break;case KILL_BY_WALL:printf("撞到了墙游戏结束\n");break;}//释放链表pSnakeNode cur = ps->_psnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

4.3.test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"//完成游戏的测试逻辑
void test()
{int ch = 0;do {system("cls");//在每次开始前先清理屏幕//创建贪吃蛇Snake snake = { 0 };//初始化游戏//1.打印欢迎界面//2.功能介绍//3.绘制地图//4.创建蛇//5.创建食物//6.设置游戏相关信息GameStart(&snake);//运行游戏GameRun(&snake);//结束游戏--善后工作GameEnd(&snake);SetPos(20,15);printf("只来一局?(Y/N):");ch=getchar();getchar();while (getchar() != '\n');} while (ch == 'Y' || ch == 'y');SetPos(0, 27);
}int main()
{//设置适配环境(本地环境)setlocale(LC_ALL, "");srand((unsigned int)time(NULL));//生成随机数值test();return 0;
}

好了,兄弟们以上就是贪吃蛇项目的全部内容,通过去写这个项目我自己反正是感悟挺大的,因为从学习到自己实现这个过程虽然很艰苦,但当自己写出来能实现的时候觉得一切都值得,以后的编程之路一定会使我越来越牛掰,一起加油,兄弟们!

这篇关于贪吃蛇大作战(C语言--实战项目)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/968535

相关文章

部署Vue项目到服务器后404错误的原因及解决方案

《部署Vue项目到服务器后404错误的原因及解决方案》文章介绍了Vue项目部署步骤以及404错误的解决方案,部署步骤包括构建项目、上传文件、配置Web服务器、重启Nginx和访问域名,404错误通常是... 目录一、vue项目部署步骤二、404错误原因及解决方案错误场景原因分析解决方案一、Vue项目部署步骤

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

配置springboot项目动静分离打包分离lib方式

《配置springboot项目动静分离打包分离lib方式》本文介绍了如何将SpringBoot工程中的静态资源和配置文件分离出来,以减少jar包大小,方便修改配置文件,通过在jar包同级目录创建co... 目录前言1、分离配置文件原理2、pom文件配置3、使用package命令打包4、总结前言默认情况下,

在Java中使用ModelMapper简化Shapefile属性转JavaBean实战过程

《在Java中使用ModelMapper简化Shapefile属性转JavaBean实战过程》本文介绍了在Java中使用ModelMapper库简化Shapefile属性转JavaBean的过程,对比... 目录前言一、原始的处理办法1、使用Set方法来转换2、使用构造方法转换二、基于ModelMapper

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

Java实战之自助进行多张图片合成拼接

《Java实战之自助进行多张图片合成拼接》在当今数字化时代,图像处理技术在各个领域都发挥着至关重要的作用,本文为大家详细介绍了如何使用Java实现多张图片合成拼接,需要的可以了解下... 目录前言一、图片合成需求描述二、图片合成设计与实现1、编程语言2、基础数据准备3、图片合成流程4、图片合成实现三、总结前

python实现简易SSL的项目实践

《python实现简易SSL的项目实践》本文主要介绍了python实现简易SSL的项目实践,包括CA.py、server.py和client.py三个模块,文中通过示例代码介绍的非常详细,对大家的学习... 目录运行环境运行前准备程序实现与流程说明运行截图代码CA.pyclient.pyserver.py参