电子商务网站开发报价,零件加工网,杭州网站设计手机,html指令代码大全目录 前言一 . 游戏背景1. 背景介绍2. 项目目标3. 技术要点 二 . 效果演示三 . 游戏的设计与分析1. 核心逻辑2. 设计与分析游戏开始Gamestart()函数游戏运行Gamerun()函数游戏结束Gameend()函数 四 . 参考代码五 . 总结 前言
本文旨在使用C语言和基础数据结构链表来实现贪吃蛇… 目录 前言一 . 游戏背景1. 背景介绍2. 项目目标3. 技术要点 二 . 效果演示三 . 游戏的设计与分析1. 核心逻辑2. 设计与分析游戏开始Gamestart()函数游戏运行Gamerun()函数游戏结束Gameend()函数 四 . 参考代码五 . 总结 前言
本文旨在使用C语言和基础数据结构链表来实现贪吃蛇经典小游戏
更多精彩 点击个人主页: 酷酷学!!!
代码仓库Gitee: 爱马仕 正文开始
一 . 游戏背景
1. 背景介绍
贪吃蛇是久负盛名的游戏, 它和俄罗斯方块, 扫雷等游戏位列经典游戏的行列. 在编程语言的学习中, 以贪吃蛇为例, 来提高编程能力和逻辑能力. 2. 项目目标
使用C语言在windows环境下的控制台模拟实现经典小游戏贪吃蛇
实现基本功能:
贪吃蛇地图绘制蛇吃食物的功能(上, 下, 左, 右方向键控制蛇的动作)蛇撞墙死亡蛇自身死亡计算得分蛇身加速, 减速暂停游戏 3. 技术要点
C语言函数, 枚举, 结构体, 动态内存管理, 预处理指令, 链表, Win32API等. 二 . 效果演示 三 . 游戏的设计与分析
1. 核心逻辑
地图: 首先控制台窗口在windows操作平台是有坐标的 x轴向右增长 y轴向下增长 需要使用后到宽字符 , 宽字符和窄字符所占的比例如下,并且需要使用setlocale函数设置本地化,并且包含对应头文件#includelocale.h
详情点击: setlocale 我们这里可以设置为⼀个棋盘27⾏58列的棋盘⾏和列可以根据⾃⼰的情况修改再围绕地图画出墙 蛇身和食物
初始化状态假设蛇的长度是5蛇身的每个节点是●在固定的⼀个坐标处比如(24,5)处开始出现蛇连续5个节点。
注意蛇的每个节点的x坐标必须是2个倍数否则可能会出现蛇的⼀个节点有⼀半出现在墙体中另外⼀般在墙外的现象坐标不好对齐。
关于食物就是在墙体内随机生成⼀个坐标x坐标必须是2的倍数坐标不能和蛇的⾝体重合然后打印★。 数据结构设计
在游戏运行的过程中蛇每次吃⼀个⻝物蛇的⾝体就会变⻓⼀节如果我们使⽤链表存储蛇的信息那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇⾝节点在地图上的坐标就⾏所以蛇节点结构如下
//一个节点
typedef struct Snode
{int x;int y;struct Snode* next;
}Snode ,* pSnode;要管理整条贪吃蛇我们再封装⼀个Snake的结构来维护整条贪吃蛇
//蛇的维护
typedef struct Snake
{pSnode _pSnake;//指向蛇头的指针pSnode _pFood;//指向食物节点的指针enum DIRECTION _dir;//蛇的方向enum GAME_STATUS _status;//游戏的状态int _food_weight;//一个食物的分数int _score; //总成绩int _sleep_time; //休息时间时间越短速度越快时间越长速度越慢
}Snake, * pSnake;蛇的⽅向可以⼀⼀列举使⽤枚举
//蛇的方向
enum DIRECTION
{UP 1,DOWN,LEFT,RIGHT
};游戏状态可以⼀⼀列举使⽤枚举
enum GAME_STATUS
{OK, //正常KILL_BY_WALL, //撞墙KILL_BY_SELF, //撞到自己END_NORMAL //正常退出
};流程设计 程序开始就设置程序支持本地模式然后进⼊游戏的主逻辑。 主逻辑分为3个过程 • 游戏开始GameStart完成游戏的初始化 • 游戏运行GameRun完成游戏运行逻辑的实现 • 游戏结束GameEnd完成游戏结束的说明实现资源释放 2. 设计与分析
游戏开始Gamestart()函数
第一步:
设置在windows系统下控制台窗口大小, 使用windows的系统命令, 以及控制台窗口的名字 然后我们需要隐藏掉光标的显示, 首先使用GetStdHandle函数来获取标准输出的光标, 这里需要用到win32API的一些知识, 接着使用定义一个CONSOLE_CURSOR_INFO类型的结构体变量用来存放光标信息的, GetConsoleCursorInfo和SetConsoleCursorInfo分别是用来获取和设置光标信息的
具体了解可以点击查看官方文档: getstdhandle getconsolecursorinfo setconsolecursorinfo
在游戏开始函数中我们主要需要实现初始化的功能 //1.打印环境界面//2.功能介绍//3.地图//4.创建蛇//5.创建食物//6.设置相关属性void gamestart(pSnake ps)
{//设置windows下窗口大小system(mode con cols100 lines30);//100列30行system(title 贪吃蛇);HANDLE hOutput GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO info;GetConsoleCursorInfo(hOutput, info);info.bVisible false;SetConsoleCursorInfo(hOutput, info);//打印环境界面和功能介绍WelcomeToGame();//绘制地图CreateMap();//创建蛇InitSnake(ps);//创建食物CreateFood(ps);}第二步:
打印环境界面和功能介绍
游戏开始之前, 先打印环境界面和功能的介绍, 单独封装为一个函数 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);
}这里我们可以把光标定位单独封装成一个函数,这也需要用到win32API提供的函数
用来设置光标位置 点击详情: setconsolecursorposition
void SetPos(short x,short y)
{HANDLE hOutput GetStdHandle(STD_OUTPUT_HANDLE);COORD pos { x,y };SetConsoleCursorPosition(hOutput, pos);
}第三步:
地图绘制: 主要定位光标位置
void CreateMap()
{//27⾏58列//上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);}//getchar();
}第四步 蛇身的初始化, 使用链表进行初始化
蛇最开始⻓度为5节每节对应链表的⼀个节点蛇⾝的每⼀个节点都有⾃⼰的坐标。 创建5个节点然后将每个节点存放在链表中进⾏管理。创建完蛇⾝后将蛇的每⼀节打印在屏幕上。 • 蛇的初始位置从(24,5)开始。 再设置当前游戏的状态蛇移动的速度默认的⽅向初始成绩每个⻝物的分数。 • 游戏状态是OK • 蛇的移动速度200毫秒 • 蛇的默认⽅向RIGHT • 初始成绩0 • 每个⻝物的分数10
void InitSnake(pSnake ps)
{int i 0;pSnode cur NULL;for (i 0; i 5; i){cur (pSnode)malloc(sizeof(Snode));if (cur NULL){perror(InitSnake()::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;
}第五步: 进行食物的创建,
• 先随机⽣成⻝物的坐标 ◦ x坐标必须是2的倍数 ◦ ⻝物的坐标不能和蛇⾝每个节点的坐标重复 • 创建⻝物节点打印⻝物
⻝物打印的宽字符:
#define FOOD L★void CreateFood(pSnake ps)
{int x 0;int y 0;//生成x是2的倍数//x2~54//y: 1~25
again:do{x rand() % 53 2;y rand() % 25 1;} while (x % 2 ! 0);//x和y的坐标不能和蛇的身体坐标冲突pSnode cur ps-_pSnake;while (cur){if (x cur-x y cur-y){goto again;}cur cur-next;}//创建食物的节点pSnode pFood (pSnode)malloc(sizeof(Snode));if (pFood NULL){perror(CreateFood()::malloc());return;}pFood-x x;pFood-y y;pFood-next NULL;SetPos(x, y);//定位位置wprintf(L%lc, FOOD);ps-_pFood pFood;
}游戏运行Gamerun()函数
第一步:
游戏运⾏期间右侧打印帮助信息提⽰玩家坐标开始位置(64,15) 根据游戏状态检查游戏是否继续如果是状态是OK游戏继续否则游戏结束。 如果游戏继续就是检测按键情况确定蛇下⼀步的⽅向或者是否加速减速是否暂停或者退出游戏。 需要的虚拟按键的罗列 • 上VK_UP • 下VK_DOWN • 左VK_LEFT • 右VK_RIGHT • 空格VK_SPACE • ESCVK_ESCAPE • F3VK_F3 • F4VK_F4 确定了蛇的⽅向和速度蛇就可以移动了。
检测按键状态我们封装了⼀个宏
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)0x1) ? 1 : 0)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_RIGHT) ps-_dir ! LEFT){ps-_dir RIGHT;}else if (KEY_PRESS(VK_LEFT) ps-_dir ! RIGHT){ps-_dir LEFT;}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 PrintHelpInfo()
{SetPos(64, 14);wprintf(L不能穿墙,不能咬到自己);SetPos(64, 15);wprintf(L用 ↑. ↓ . ← . → 来控制蛇的移动);SetPos(64, 16);wprintf(L按ESC退出游戏按空格暂停游戏);SetPos(64, 17);wprintf(L按F3加速F4减速);
}第三步 检测虚拟按键, 然后来修改蛇的方向
第四步: 蛇身移动SnakeMove函数
先创建下⼀个节点根据移动⽅向和蛇头的坐标蛇移动到下⼀个位置的坐标。 确定了下⼀个位置后看下⼀个位置是否是⻝物NextIsFood是⻝物就做吃⻝物处理EatFood如果不是⻝物则做前进⼀步的处理NoFood。 蛇⾝移动后判断此次移动是否会造成撞墙KillByWall或者撞上⾃⼰蛇⾝KillBySelf从⽽影响游戏的状态。 具体代码见四
游戏结束Gameend()函数
游戏状态不再是OK游戏继续的时候要告知游戏结束的原因并且释放蛇⾝节点。我们可以使用一个while循环来判断
具体代码见下 四 . 参考代码
snake.h
#pragma once#includestdio.h
#includetime.h
#includestdlib.h
#includelocale.h
#includeWindows.h
#includestdbool.h
#includestring.h#define POS_X 24
#define POS_Y 5#define WALL L□
#define BODY L●
#define FOOD L★//一个节点
typedef struct Snode
{int x;int y;struct Snode* next;
}Snode ,* pSnode;//蛇的方向
enum DIRECTION
{UP 1,DOWN,LEFT,RIGHT
};enum GAME_STATUS
{OK, //正常KILL_BY_WALL, //撞墙KILL_BY_SELF, //撞到自己END_NORMAL //正常退出
};//蛇的维护
typedef struct Snake
{pSnode _pSnake;//指向蛇头的指针pSnode _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 GameRun(pSnake ps);void GameEnd(pSnake ps);
snake.c
#define _CRT_SECURE_NO_WARNINGS 1#includesnake.hvoid SetPos(short x,short y)
{HANDLE 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()
{//27⾏58列//上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);}//getchar();
}void InitSnake(pSnake ps)
{int i 0;pSnode cur NULL;for (i 0; i 5; i){cur (pSnode)malloc(sizeof(Snode));if (cur NULL){perror(InitSnake()::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 CreateFood(pSnake ps)
{int x 0;int y 0;//生成x是2的倍数//x2~54//y: 1~25
again:do{x rand() % 53 2;y rand() % 25 1;} while (x % 2 ! 0);//x和y的坐标不能和蛇的身体坐标冲突pSnode cur ps-_pSnake;while (cur){if (x cur-x y cur-y){goto again;}cur cur-next;}//创建食物的节点pSnode pFood (pSnode)malloc(sizeof(Snode));if (pFood NULL){perror(CreateFood()::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)
{//设置windows下窗口大小system(mode con cols100 lines30);//100列30行system(title 贪吃蛇);HANDLE hOutput GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO info;GetConsoleCursorInfo(hOutput, info);info.bVisible false;SetConsoleCursorInfo(hOutput, info);//打印环境界面和功能介绍WelcomeToGame();//绘制地图CreateMap();//创建蛇InitSnake(ps);//创建食物CreateFood(ps);}void PrintHelpInfo()
{SetPos(64, 14);wprintf(L不能穿墙,不能咬到自己);SetPos(64, 15);wprintf(L用 ↑. ↓ . ← . → 来控制蛇的移动);SetPos(64, 16);wprintf(L按ESC退出游戏按空格暂停游戏);SetPos(64, 17);wprintf(L按F3加速F4减速);
}#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)1)?1:0) //检测虚拟按键void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}int NextIsFood(pSnode pNext, pSnake ps)
{return (ps-_pFood-x pNext-x ps-_pFood-y pNext-y);
}void EatFood(pSnode pn, pSnake ps)
{//头插法ps-_pFood-next ps-_pSnake;ps-_pSnake ps-_pFood;free(pn);pn NULL;pSnode cur ps-_pSnake;//打印蛇while (cur){SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}ps-_score ps-_food_weight;//重新创建食物CreateFood(ps);}void NoFood(pSnode pn, pSnake ps)
{pn-next ps-_pSnake;ps-_pSnake pn;pSnode 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)
{pSnode cur ps-_pSnake-next;while (cur){if (cur-x ps-_pSnake-x cur-y ps-_pSnake-y){ps-_status KILL_BY_SELF;break;}cur cur-next;}
}void SnakeMove(pSnake ps)
{pSnode pNext (pSnode)malloc(sizeof(Snode));if (pNext NULL){perror(SnakeMove()::malloc());return;}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;}pNext-next NULL;if (NextIsFood(pNext, ps)){EatFood(pNext, ps);}else{NoFood(pNext, ps);}KillByWall(ps);KillBySelf(ps);
}
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_RIGHT) ps-_dir ! LEFT){ps-_dir RIGHT;}else if (KEY_PRESS(VK_LEFT) ps-_dir ! RIGHT){ps-_dir LEFT;}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)
{SetPos(24,12);switch (ps-_status){case END_NORMAL:printf(您主动结束游戏\n);break;case KILL_BY_WALL:printf(您撞到墙上游戏结束\n);break;case KILL_BY_SELF:printf(您撞到了自己游戏结束\n);break;}pSnode cur ps-_pSnake;while (cur){pSnode del cur;cur cur-next;free(del);del NULL;}
}test.c
#define _CRT_SECURE_NO_WARNINGS 1#includesnake.hvoid test01()
{int ch 0;do {system(cls);Snake ps { 0 };gamestart(ps);//1.打印环境界面//2.功能介绍//3.地图//4.创建蛇//5.创建食物//6.设置相关属性GameRun(ps);//运行游戏GameEnd(ps);//结束游戏SetPos(20, 15);printf(再来一局吗?(Y/N):);ch getchar();getchar();}while (ch Y || ch y);SetPos(0, 27);
}int main()
{setlocale(LC_ALL, );srand((unsigned int)time(NULL));test01();return 0;
}五 . 总结
以上是一个简单的贪吃蛇游戏的代码总结具体的实现方式可能会有所不同但是核心的思路是相同的。如果对你有帮助 别忘了点赞 关注 感谢!!!