C语言-链表实现贪吃蛇控制台游戏

使用C语言和链表实现贪吃蛇游戏

一、引言

贪吃蛇游戏是一个经典的游戏,它的玩法简单而富有挑战性。在这个博客中,我将分享如何使用C语言和链表数据结构来自主实现贪吃蛇游戏。我会详细介绍游戏的设计思路、编码过程、遇到的问题及解决方案,并分享我的心得体会。

二、游戏设计

需求分析

游戏界面:虽然C语言本身并不直接支持图形界面,但我们可以使用文本模式来模拟游戏界面。由于打印符号为宽字符消耗两个字符,所以应计划好行列的字符数,调整界面和游戏地图大小.
游戏逻辑:贪吃蛇的移动、食物的生成、碰撞检测等。
用户交互:通过键盘控制贪吃蛇的移动方向。

数据结构选择

使用链表来表示贪吃蛇,其中每个节点代表蛇身的一个部分。链表的头部代表蛇头,尾部代表蛇尾。为了简单实现选择头插方式延长蛇身。

算法设计
碰撞检测:检查蛇头是否碰到游戏边界或蛇身的其他部分。
食物生成:随机生成食物的位置,并检查是否与蛇身重叠。

涉及的头文件(.h)和宏定义

#pragma once
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#include <conio.h>#define POS_X 24
#define POS_Y 5#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'

三、编码实现(snake.c)

定义数据结构(.h)

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

其它声明和枚举类型(.h)

//蛇的方向
enum DIRECTION
{UP = 1,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 SnakeNode* pSnakeNode;

初始化游戏

//初始化
void GameStart(pSnake ps)
{//0.先设置窗口大小,再隐藏光标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.打印游戏环境界面+2.功能介绍WelcomeToGame();// 3.绘制地图CreateMap();// 4.创建蛇InitSnake(ps);// 5.创建食物CreateFood(ps);
}

列好框架,依次实现函数

//1.打印游戏环境界面+2.功能介绍
void WelcomeToGame()
{SetPos(40, 14);wprintf(L"欢迎来到贪吃蛇_小游戏\n");SetPos(42, 20);system("pause");system("cls");SetPos(25, 14);wprintf(L"用 ↑ . ↓ . ← . → 来控制蛇的移动,按F3加速,按F4减速\n");SetPos(42, 15);wprintf(L"加速能够得到更高的分数\n");SetPos(42, 20);system("pause");system("cls");
}//3.绘制地图
void CreateMap()
{//上for (int i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (int i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//左for (int i = 1; i < 26; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (int i = 1; i < 26; i++){SetPos(56, i); wprintf(L"%lc", WALL);}
}// 4.创建蛇
void InitSnake(pSnake ps)
{pSnakeNode cur = NULL;for (int i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));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->_status = OK;ps->_sleep_time = 200;//毫秒
}// 5.创建食物
void CreateFood(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("CreateFood()::malloc()");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;SetPos(x, y);wprintf(L"%lc", FOOD);ps->_pFood = pFood;
}

打印操作提示信息

//打印帮助信息
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(64, 19);wprintf(L"%ls", L"小志@Dreamboat 制作");
}

游戏进行逻辑

//游戏运行逻辑
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_ESCAPE)){ps->_status = END_NORMAL;}else if (KEY_PRESS(VK_SPACE)){Pause();}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;}}//蛇走一步的过程SnkaeMove(ps);Sleep(ps->_sleep_time);} while (ps->_status == OK);
}

依次实现函数内容

碰撞检测和食物生成

碰撞检测:遍历链表,检查蛇头是否与其他节点重叠或超出游戏边界。
食物生成:随机生成一个坐标,并检查是否与蛇身重叠,若重叠则重新生成。

#define KEY_PRESS(vk)  ((GetAsyncKeyState(vk)&1)?1:0)
//暂停
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;//重新创建食物CreateFood(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;break;}cur = cur->next;}
}//蛇走一步的过程
void SnkaeMove(pSnake ps)
{//创建节点,表示蛇即将就到的下一个节点pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("SnakeMove()::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 GameEnd(pSnake ps)
{SetPos(24, 12);switch (ps->_status){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->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

四、test.c

#define _CRT_SECURE_NO_WARNINGS 1#include <locale.h>
#include "snake.h"//完成的是游戏的测试逻辑
void test()
{int ch = 0;do{system("cls");//创建贪吃蛇Snake snake = { 0 };//初始化游戏//1. 打印环境界面//2. 功能介绍//3. 绘制地图//4. 创建蛇//5. 创建食物//6. 设置游戏的相关信息GameStart(&snake);//运行游戏GameRun(&snake);//检测是否有按键被按下while (_kbhit()){// 使用 _getch() 获取按下的键,不阻塞程序_getch();// 处理按键事件,可以根据需要进行相应的操作}//结束游戏 - 善后工作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();return 0;
}

别忘记函数在头文件中的声明哦.

五、收获与心得体会

通过编写贪吃蛇游戏,我深入了解了链表数据结构的操作和应用,提高了自己的编程能力。同时,我也学会了如何在限制条件下(如文本模式)设计和实现游戏。在解决问题的过程中,我体会到了编程的乐趣和挑战性。

效果如下

六、总结

使用C语言和链表实现贪吃蛇游戏是一个有趣且富有挑战性的项目。通过这个项目,我不仅提高了自己的编程能力,还加深了对链表数据结构的理解。希望这篇博客能对想要编写贪吃蛇游戏的朋友们有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/6627.shtml

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

相关文章

翻译《The Old New Thing》 - Why does the CreateProcess function do autocorrection?

Why does the CreateProcess function do autocorrection? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20050623-03/?p35213 Raymond Chen 在 2005 年 6 月 23 日 为什么 CreateProcess 函数会进行自动更正&#xff1f; 译注&#xff…

【华为OD机试C卷D卷】符号运算(C++/Java/Python)

【华为OD机试】-(A卷+B卷+C卷+D卷)-2024真题合集目录 【华为OD机试】-(C卷+D卷)-2024最新真题目录 题目描述 给定一个表达式,求其分数计算结果。 表达式的限制如下: 所有的输入数字皆为正整数(包括0) 仅支持四则运算(+-*,/)和括号 结果为整数或分数,分数必须化为最简格…

13_Scala面向对象编程_伴生对象

文章目录 1.伴生对象1.1 scala的一个性质&#xff0c;scala文件中的类都是公共的&#xff1b;1.2 scala使用object关键字也可以声明对象&#xff1b; 3.关于伴生对象和类4.权限修饰符&#xff0c;scala仅有private;5.伴生对象可以访问伴生类中的私有属性&#xff1b;6.案例7.伴…

世界十大最具影响力人物颜廷利:真正的高人,靠谱的行为

对于真正的‘高人’&#xff0c; 在面对‘狗洞’时&#xff0c; 他们都比较理智&#xff0c; 从来都不趾高气扬&#xff0c; 因为他们晓得&#xff0c; 倘若说不能弯下腰&#xff0c; 并而直立着身子走路的话&#xff0c; 那么&#xff0c; 他们就不是纯粹的‘高人’&#xff0…

华为OD机试题:字符串变换最小字符串

题目描述&#xff1a; 给定一个字符串s&#xff0c;最多只能进行一次变换&#xff0c;返回变换后能得到的最小字符串&#xff08;按照字典序进行比较&#xff09;。 变换规则&#xff1a;交换字符串中任意两个不同位置的字符。 一串小写字母组成的字符串s 输出描述 按照要求进…

启程Python机器学习之旅:从JupyterLab到神经网络初探

引言 在数据科学和人工智能的浪潮中&#xff0c;Python已经成为最受欢迎的编程语言之一。其简洁的语法和强大的库支持&#xff0c;使得从科研到商业的各个领域都能见到Python的身影。今天&#xff0c;我们将通过JupyterLab——一个交互式的开发环境&#xff0c;开始我们的Pyth…

1850H-The Third Letter

题目链接&#xff1a;The Third Letter 本道题目就是带权并查集的模板题&#xff0c;但又好久没学忘了&#xff0c;再复习一遍。。。 路径压缩函数模板&#xff1a; int root(int x){if(pre[x]!x){int troot(pre[x]);d[x]d[pre[x]];pre[x]t;}return pre[x]; } 之后就模拟一…

c++ 红黑树学习及简单实现

1. 了解红黑树 1.1. 概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个节点增加一个存储位表示节点的颜色&#xff0c;可以是红色&#xff0c;或是黑色&#xff0c;通过对任何一条从根到叶子的路径上各个节点的着色方式进行限制&#xff0c;红黑树确保没有一条路…

初识webpack项目

新建一个空的工程 -> % mkdir webpack-project 为了方便追踪执行每一个命令&#xff0c;最终产生了哪些变更&#xff0c;将这个空工程初始化成git项目 -> % cd webpack-project/-> % git init Initialized empty Git repository in /Users/lixiang/frontworkspace/…

selenium自动化,Chrome 启动参数

常用参数 常用参数请参考下表。 序号参数说明1--allow-outdated-plugins不停用过期的插件。2--allow-running-insecure-content默认情况下&#xff0c;https 页面不允许从 http 链接引用 javascript/css/plug-ins。添加这一参数会放行这些内容。3--allow-scripting-gallery允许…

R语言 统计篇之T-test 检验

《Cookbook for R》 Statistical Analysis 数据统计篇之 T-test 检验 当你想测试两个样本是否来自均值不同的群体 或者测试一个样本是否来自均值不同于某个理论均值的群体。 示例数据&#xff1a;R内置数据集 sleep sleep #> extra group ID #> 1 0.7 1 1 #…

AI文章框架分析

大家在文章写作的时候结构难免会有点凌乱&#xff0c;但是自己可能无法发现问题所在&#xff0c;那么有没有一款工具可以帮你自动分析你写的文章框架存在的问题&#xff0c;然后并给你详细的分析报告呢&#xff1f;今天给大家介绍一下文件框架分析助手&#xff01; 使用说明 打…

如何配置X86应用程序启用大地址模式(将用户态虚拟内存从2GB扩充到3GB),以解决用户态虚拟内存不够用问题?(项目实战案例解析)

目录 1、概述 2、为什么不直接将程序做成64位的&#xff1f; 3、进程内存不足导致程序发生闪退的案例分析 3.1、问题说明 3.2、将Windbg附加到程序进程上进行动态调试 3.3、动态调试的Windbg感知到了中断&#xff0c;中断在DebugBreak函数调用上 3.4、malloc或new失败的…

IoTDB 入门教程 问题篇②——RPC远程连接IoTDB服务器失败

文章目录 一、前文二、发现问题三、分析问题四、检查6667端口是否监听所有IP五、检查ECS云服务器的安全组是否允许六、检查Linux防火墙是否允许 一、前文 IoTDB入门教程——导读 二、发现问题 使用本地IP127.0.0.1可以连接IoTDB服务器使用远程IPxx.xx.xx.xx却连接不到。提示你…

【Python】回溯法解全排列问题

题目 给定一个不含重复数字的数组&#xff0c;返回其所有可能的全排列。 分析 要实现全排列&#xff0c;就有一个长度与原数组相等的数组&#xff0c;数组的第一位可能是原数组中的任意一位&#xff0c;第二位是除了第一位的原数组的任意一位&#xff0c;第三位则是除了前两位…

C++中的reverse_iterator迭代器结构设计

目录 reverse_iterator迭代器结构设计 reverse_iterator迭代器基本结构设计 operator*()函数 operator()函数 operator->()函数 operator!()函数 rbegin()函数 rend()函数 operator--()函数 operator()函数 测试代码 const_reverse_iterator迭代器设计 reverse…

【蓝桥2025备赛】容斥原理

容斥原理 背景&#xff1a;两个集合相交 高中的韦恩图&#xff0c;我们知道两个集合相交时我们可以通过简单的计算来认识相关的性质 集合相交的区域是 A ∩ B A\cap B A∩B ,集合的并集是 A ∪ B A\cup B A∪B ,那怎么用集合表示 A ∪ B A\cup B A∪B 我们可以看作是A集合…

华为OD试题之第k长子串

第k长子串 题目描述 给定一个字符串 只包含大写字母 求在包含同一字母的子串中 长度第K长的子串 相同字母只取最长的子串 输入描述 第一行 一个子串 1 < len < 100 只包含大写字母 第二行为k的值 输出描述 输出连续出现次数第k多的字母的次数 如果子串中只包含同一字母…

探索 CSS 动画中的 steps() 函数:精准控制动画节奏

在CSS动画的世界里&#xff0c;steps() 函数是一个强大而灵活的工具&#xff0c;它允许开发者精确地控制动画的播放方式&#xff0c;尤其适用于创建逐帧动画效果或实现类似幻灯片的过渡效果。本文将深入探讨steps()函数的工作原理、语法、应用场景及实战代码示例&#xff0c;帮…

分布式与一致性协议之ZAB协议(一)

ZAB协议 概述 很多人应该都使用过ZooKeeper&#xff0c; 它是一个开源的分布式协调服务&#xff0c;比如你可以用它进行配置管理、名字服务等。在ZooKeeper中&#xff0c;数据是以节点的形式存储的。如果你要用ZooKeeper做配置管理&#xff0c;那么就需要在里面创建指定配置&…