• C语言结课实战项目_贪吃蛇小游戏


    ✨✨所属专栏:C语言✨✨

    ✨✨作者主页:嶔某✨✨

    游戏源代码链接:function/贪吃蛇 · 钦某/c-language-learning - 码云 - 开源中国 (gitee.com)

    最终实现效果:

    实现基本的功能:

    1. void set_pos(short x, short y);//定位光标位置
    2. void Game_Start(pSnake ps);//初始化
    3. void WelcomeToGame(void);//打印欢迎界面
    4. void CreateMap(void);//创建地图
    5. void InitSnake(pSnake ps);//初始化蛇身
    6. void CreateFood(pSnake ps);//创建食物
    7. void Game_Run(pSnake ps);//游戏运行逻辑
    8. void SnakeMove(pSnake ps);//蛇的移动
    9. bool NextIsFood(pSnakeNode pn,pSnake ps);//判断下一位置是否为食物
    10. void EatFood(pSnakeNode pn, pSnake ps);//吃掉食物
    11. void NoFood(pSnakeNode pn, pSnake ps);//下一个位置不是食物
    12. void KillByWall(pSnake ps);//检测撞墙
    13. void KillBySelf(pSnake ps);//检测撞自己
    14. void Game_End(pSnake ps);//游戏善后

    贪吃蛇地图绘制
    • 蛇吃⻝物的功能(上、下、左、右⽅向键控制蛇的动作)
    • 蛇撞墙死亡
    • 蛇撞⾃⾝死亡
    • 计算得分
    • 蛇⾝加速、减速
    • 暂停游戏

    运用到的知识:C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等

    根据游戏进程解释代码:

    这里分为下面几个函数对游戏进行实现:

    1. system("cls");
    2. //创建贪吃蛇
    3. pSnakeNode pSnake = NULL;
    4. Snake snake = { 0 };
    5. //初始化游戏
    6. Game_Start(&snake);
    7. //运行游戏
    8. Game_Run(&snake);
    9. 结束游戏
    10. Game_End(&snake);

    游戏初始化:

    1. void Game_Start(pSnake ps)//初始化
    2. {
    3. //0.设置窗口大小/名字
    4. system("mode con cols=100 lines=30");
    5. system("title 贪吃蛇");
    6. //1.隐藏光标
    7. HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
    8. CONSOLE_CURSOR_INFO CursorInfo;
    9. GetConsoleCursorInfo(houtput,&CursorInfo);
    10. CursorInfo.bVisible = false;
    11. SetConsoleCursorInfo(houtput,&CursorInfo);
    12. //2.打印欢迎界面,介绍功能
    13. WelcomeToGame();
    14. //3.绘制地图
    15. CreateMap();
    16. //4.创建蛇
    17. InitSnake(ps);
    18. //5.创建食物
    19. CreateFood(ps);
    20. }

    首先进入游戏,我们应该将窗口名称改为 “贪吃蛇” 并将光标隐藏掉。再在中间打印游戏信息。

    这里用到的函数有:

    (1)system("mode con cols=100 lines=30");

    将窗口设置为100列,30行

    (2)system("title 贪吃蛇");

    将title设置为贪吃蛇

    (3)system("pause");

    暂停程序,按下任意键继续

    (4)system("cls");

    清理屏幕

    1. /*******************************************/
    2. //0.设置窗口大小/名字
    3. system("mode con cols=100 lines=30");
    4. system("title 贪吃蛇");
    5. /*******************************************/
    6. void set_pos(short x, short y)
    7. {
    8. //获得标准输出设备的句柄
    9. HANDLE houtput = NULL;
    10. houtput = GetStdHandle(STD_OUTPUT_HANDLE);
    11. //定位光标的位置
    12. COORD pos = { x,y };
    13. SetConsoleCursorPosition(houtput,pos);
    14. }
    15. /*******************************************/
    16. void WelcomeToGame()
    17. {
    18. set_pos(40, 12);
    19. wprintf(L"欢迎来到贪吃蛇小游戏");
    20. set_pos(42,18);
    21. system("pause");
    22. system("cls");
    23. set_pos(30, 12);
    24. wprintf(L"用上下左右控制蛇的移动,按 {[ 加速, ]} 减速\n");
    25. set_pos(38, 13);
    26. wprintf(L"加速可以得到更高的分数\n");
    27. set_pos(42, 18);
    28. system("pause");
    29. system("cls");
    30. }
    31. /********************************************/

     之后我们要把地图打印出来:

    分别打印上下左右的墙,将墙宏定义为WALL,字符为’□‘

    1. void CreateMap()
    2. {
    3. //上
    4. int i = 0;
    5. for (i = 0; i < 29; i++)
    6. {
    7. wprintf(L"%lc", WALL);
    8. }
    9. //下
    10. set_pos(0,25);
    11. for (i = 0; i < 29; i++)
    12. {
    13. wprintf(L"%lc", WALL);
    14. }
    15. //左
    16. for (i = 1; i < 25; i++)
    17. {
    18. set_pos(0, i);
    19. wprintf(L"%lc", WALL);
    20. }
    21. //右
    22. for (i = 1; i < 25; i++)
    23. {
    24. set_pos(56, i);
    25. wprintf(L"%lc", WALL);
    26. }
    27. }

    然后我们将贪吃蛇创建出来,将蛇有关的信息用结构体和枚举类型封装起来,将蛇身用链表维护。

    蛇头指针:指向链表头节点的指针,方便对蛇身进行维护

    食物指针:从开发角度来说,其实食物也是蛇的一个节点,当蛇头的下一个位置为食物时,将食物的节点头插到蛇身上面。

    方向:对蛇的方向进行枚举

    游戏状态:方便判断蛇的状态:(1)正常(2)撞墙(3)撞到自己(4)正常退出每一次while循环后判断游戏状态

    食物权重:每次加速食物权重+2,减速-2。

    总成绩:每吃掉一个食物,蛇身长度+1,分数+=食物权重。

    每走一步的缓冲时间:缓冲时间越短,蛇走得越快;反之越慢。

    1. #define POS_X 24
    2. #define POS_Y 5
    3. #define WALL L'□'
    4. #define BODY L'●'
    5. #define FOOD L'★'
    6. typedef struct SnakeNode
    7. {
    8. //坐标
    9. int x;
    10. int y;
    11. //下一个节点
    12. struct SnakeNode* next;
    13. }SnakeNode,*pSnakeNode;
    14. enum DRECCTION//方向
    15. {
    16. UP = 1,
    17. DOWN,
    18. LEFT,
    19. RIGHT
    20. };
    21. //蛇的状态
    22. enum GAME_STATUS
    23. {
    24. OK,//正常
    25. KILL_BY_WALL,//撞墙
    26. KILL_BY_SELF,//撞自己
    27. END_NORMAL//正常退出
    28. };
    29. typedef struct Snake
    30. {
    31. pSnakeNode _pSnake;//指向蛇头的指针
    32. pSnakeNode _pFood;//指向食物节点的指针
    33. enum DRECCTION _dir;//蛇的方向
    34. enum GAME_STAYUS _status;//游戏状态
    35. int _food_weight;//一个食物都分数
    36. int _score;//总成绩
    37. int _sleep_time;//休息时间
    38. }Snake,*pSnake;
    39. /************************************/
    40. void InitSnake(pSnake ps)//初始化蛇
    41. {
    42. int i = 0;
    43. pSnakeNode cur = NULL;
    44. for (int i = 0; i < 5; i++)
    45. {
    46. cur = (pSnakeNode)malloc(sizeof(SnakeNode));
    47. if (cur == NULL)
    48. {
    49. perror("InitSnake()::malloc()");
    50. return;
    51. }
    52. cur->next = NULL;
    53. cur->x = POS_X + 2 * i;
    54. cur->y = POS_Y;
    55. //头插插入
    56. if (ps->_pSnake == NULL)
    57. ps->_pSnake = cur;
    58. else
    59. {
    60. cur->next = ps->_pSnake;
    61. ps->_pSnake = cur;
    62. }
    63. }
    64. cur = ps->_pSnake;
    65. while (cur)
    66. {
    67. set_pos(cur->x,cur->y);
    68. wprintf(L"%lc", BODY);
    69. cur = cur->next;
    70. }
    71. //设置蛇的属性
    72. ps->_dir = RIGHT;//默认
    73. ps->_score = 0;
    74. ps->_food_weight = 10;
    75. ps->_sleep_time = 200;//单位为ms
    76. ps->_status = OK;
    77. }

    创建食物

    生成随机数,赋给x,y。

    这里x和y都有范围,不能超出地图边界,并且不能与蛇身重合。

    1. void CreateFood(pSnake ps)//创建食物
    2. {
    3. int x = 0;
    4. int y = 0;
    5. again:
    6. do
    7. {
    8. x = rand() % 52 + 2;//2~54
    9. y = rand() % 24 + 1;//1~25
    10. } while (x % 2 != 0);//x为2的倍数
    11. //不能和蛇身的坐标相同
    12. pSnakeNode cur = ps->_pSnake;
    13. while (cur)
    14. {
    15. if (x == cur->x && y == cur->y)
    16. goto again;
    17. cur = cur->next;
    18. }
    19. //创建食物节点
    20. pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
    21. if (pFood == NULL)
    22. {
    23. perror("CreateFood()::malloc");
    24. return;
    25. }
    26. pFood->x = x;
    27. pFood->y = y;
    28. pFood->next = NULL;
    29. set_pos(x, y);
    30. wprintf(L"%lc", FOOD);
    31. ps->_pFood = pFood;
    32. }

     游戏开始:

    1. void Game_Run(pSnake ps)//游戏运行逻辑
    2. {
    3. //打印帮助信息
    4. PrintHelpInfo();
    5. do
    6. {
    7. //打印分数,食物权重
    8. set_pos(64, 8);
    9. printf("总分数:%d\n",ps->_score);
    10. set_pos(64, 9);
    11. printf("当前食物权重:%2d\n", ps->_food_weight);
    12. //按键检测
    13. if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
    14. {
    15. ps->_dir = UP;
    16. }
    17. else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
    18. {
    19. ps->_dir = DOWN;
    20. }
    21. else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
    22. {
    23. ps->_dir = LEFT;
    24. }
    25. else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
    26. {
    27. ps->_dir = RIGHT;
    28. }
    29. else if (KEY_PRESS(VK_SPACE))
    30. {
    31. //暂停
    32. Pause();
    33. }
    34. else if (KEY_PRESS(VK_ESCAPE))
    35. {
    36. //正常退出
    37. ps->_status = END_NORMAL;
    38. }
    39. else if (KEY_PRESS(VK_OEM_6))
    40. {
    41. //加速
    42. if (ps->_sleep_time > 80)
    43. {
    44. ps->_sleep_time -= 30;
    45. ps->_food_weight += 2;
    46. }
    47. }
    48. else if (KEY_PRESS(VK_OEM_4))
    49. {
    50. //减速
    51. if (ps->_food_weight > 2)
    52. {
    53. ps->_sleep_time += 30;
    54. ps->_food_weight -= 2;
    55. }
    56. }
    57. SnakeMove(ps);//蛇走一步的过程
    58. Sleep(ps->_sleep_time);
    59. } while (ps->_status == OK);
    60. }

    用dowhile循环对主体进行不断刷新

    每次循环后让系统暂停一段时间(初始为200ms)

    打印相关信息

    蛇每走一步,分数都有可能变化,每次循环都打印一次。

    按键检测

    #define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)

    (1)用虚拟键值检测是否按下上下左右键,按下相应键并且蛇的当前方向不能与之相反。

    (2)检测是否按下空格,按下就进函数:

    1. void Pause()
    2. {
    3. while (1)
    4. {
    5. Sleep(200);
    6. if (KEY_PRESS(VK_SPACE))
    7. break;
    8. }
    9. }

     再次按下空格退出函数。

    (3)检测加速减速,按下加速键就将缓冲时间变短,食物权重增加;反之变长,食物权重减少。(这里也是有范围的,食物权重不能为负数,也不能过大)

    蛇的移动

    进入函数,创建蛇头的下一个位置所在的节点,并根据方向算出所在位置。

    判断下一位置是否为食物:
    1. bool NextIsFood(pSnakeNode pn, pSnake ps)//判断下一位置是否为食物
    2. {
    3. return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
    4. }
     下一位置是食物:

    先将新节点头插进蛇身,打印蛇身在屏幕上,总分数加上食物权重。

    再次创建食物。

    1. void EatFood(pSnakeNode pn, pSnake ps)
    2. {
    3. //头插
    4. ps->_pFood->next = ps->_pSnake;
    5. ps->_pSnake = ps->_pFood;
    6. //释放下一个位置的节点
    7. free(pn);
    8. pn = NULL;
    9. //打印
    10. pSnakeNode cur = ps->_pSnake;
    11. while (cur)
    12. {
    13. set_pos(cur->x, cur->y);
    14. wprintf(L"%lc",BODY);
    15. cur = cur->next;
    16. }
    17. ps->_score += ps->_food_weight;
    18. //重新创建食物
    19. CreateFood(ps);
    20. }
    下一位置不是食物:

    创建下一位置的节点,也是头插,但是在打印蛇身之后,将蛇尾位置打印两个空格(不打印空格蛇身就不会清除一直留在屏幕上:拖尾),将蛇尾的节点释放掉。(cur->next一定要置空,不能让它为野指针)

    1. void NoFood(pSnakeNode pn, pSnake ps)//下一个位置不是食物
    2. {
    3. //头插
    4. pn->next = ps->_pSnake;
    5. ps->_pSnake = pn;
    6. pSnakeNode cur = ps->_pSnake;
    7. while (cur->next->next)
    8. {
    9. set_pos(cur->x,cur->y);
    10. wprintf(L"%lc",BODY);
    11. cur = cur->next;
    12. }
    13. //把最后一个节点打印空格
    14. set_pos(cur->next->x,cur->next->y);
    15. printf(" ");
    16. //将最后一个节点释放
    17. free(cur->next);
    18. //将倒数第二个节点置为空
    19. cur->next = NULL;
    20. }
     检测是否撞墙:

    判断蛇头坐标位置是否超出范围,若超出范围,将蛇的状态改为KILL_BY_WALL

    1. void KillByWall(pSnake ps)//检测撞墙
    2. {
    3. if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 ||
    4. ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
    5. {
    6. ps->_status = KILL_BY_WALL;
    7. }
    8. }
    检测是否撞到自己:

    遍历蛇身链表,若坐标重合,将蛇的状态改为KILL_BY_SELF,并且跳出循环。

    1. void KillBySelf(pSnake ps)//检测撞自己
    2. {
    3. pSnakeNode cur = ps->_pSnake->next;
    4. while (cur)
    5. {
    6. if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
    7. {
    8. ps->_status = KILL_BY_SELF;
    9. break;
    10. }
    11. cur = cur->next;
    12. }
    13. }
     蛇移动一步的总函数:
    1. void SnakeMove(pSnake ps)//蛇的移动
    2. {
    3. //创建蛇头的下一个节点
    4. pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
    5. if (pNextNode == NULL)
    6. {
    7. perror("SnakeMove()::malloc()");
    8. return;
    9. }
    10. switch (ps->_dir)
    11. {
    12. case UP:
    13. pNextNode->x = ps->_pSnake->x;
    14. pNextNode->y = ps->_pSnake->y-1;
    15. break;
    16. case DOWN:
    17. pNextNode->x = ps->_pSnake->x;
    18. pNextNode->y = ps->_pSnake->y + 1;
    19. break;
    20. case LEFT:
    21. pNextNode->x = ps->_pSnake->x - 2;
    22. pNextNode->y = ps->_pSnake->y;
    23. break;
    24. case RIGHT:
    25. pNextNode->x = ps->_pSnake->x + 2;
    26. pNextNode->y = ps->_pSnake->y;
    27. break;
    28. }
    29. if (NextIsFood(pNextNode,ps))//检测下一个位置是否为食物
    30. {
    31. EatFood(pNextNode, ps);
    32. }
    33. else
    34. {
    35. NoFood(pNextNode, ps);
    36. }
    37. //检测是否撞墙
    38. KillByWall(ps);
    39. //检测是否撞自己
    40. KillBySelf(ps);
    41. }

     游戏结束:

    判断游戏结束的原因,并打印。

    释放蛇身链表

    1. void Game_End(pSnake ps)//游戏善后
    2. {
    3. set_pos(24,12);
    4. switch (ps->_status)
    5. {
    6. case END_NORMAL:
    7. printf("您主动结束游戏\n");
    8. break;
    9. case KILL_BY_WALL:
    10. printf("您被墙单杀了\n");
    11. break;
    12. case KILL_BY_SELF:
    13. printf("您被自己单杀了\n");
    14. break;
    15. }
    16. //释放蛇身链表
    17. pSnakeNode cur = ps->_pSnake;
    18. while (cur)
    19. {
    20. pSnakeNode del = cur;
    21. cur = cur->next;
    22. free(del);
    23. }
    24. }

    后续改进:

    1.穿墙

    2.食物分类

    3.多个食物

    4.双人游戏

    ……

     本期博客到这里就结束了,如果有什么错误,欢迎指出,如果对你有帮助,请点个赞,谢谢!

  • 相关阅读:
    【乐吾乐3D可视化组态编辑器】相机与视角
    sqlmap注入枚举参数详解
    RabbitMQ系列-Exchange介绍
    XXL-job-oracle 版本
    新车「智能化+安全」进入纵深区,艾拉比OTA成高频词
    深度学习Day-19:DenseNet算法实战与解析
    3. 项目立项三板斧
    Golang中make和new的区别
    Kotlin基础从入门到进阶系列讲解(入门篇)Activity的使用
    Vue2:Vue2 @vue/cli脚手架
  • 原文地址:https://blog.csdn.net/2301_80194476/article/details/137958506