当前位置:   article > 正文

贪吃蛇项目实践

贪吃蛇项目实践

本篇文章将讲解使用c语言实现贪吃蛇在控制台的实现

并将涉及到一些控制台使用的知识

效果展示

贪吃蛇

准备阶段

设置控制台

如果此时为终端鼠标右键->设置->windows控制台主机

修改控制台名称

使用system函数即可

  1. #include<Windows.h>
  2. int main()
  3. {
  4. //HANDLE h_out_put = GetStdHandle(STD_OUTPUT_HANDLE);
  5. system("title 贪吃蛇");
  6. system("pause");//暂停程序
  7. return 0;
  8. }

设置窗口大小

依然是system函数

"mode con cols=(数字)  lines=(数字));

  1. #include<Windows.h>
  2. int main()
  3. {
  4. //HANDLE h_out_put = GetStdHandle(STD_OUTPUT_HANDLE);
  5. //system("title 贪吃蛇");
  6. //system("pause");
  7. system("mode con cols=100 lines=30");
  8. return 0;
  9. }
获取句柄

句柄就像一个把手:我们通过GetStdHandle() API函数 获得标准输出句柄,通过这个句柄就可以对标准输出进行相关的操作

//使用句柄操作需要包含Windows.h头文件

  1. #include<Windows.h>
  2. int main()
  3. {
  4. HANDLE h_out_put = GetStdHandle(STD_OUTPUT_HANDLE);
  5. return 0;
  6. }

句柄的类型为HANDLE

STD_OUTPUT_HANDLE为从标准输出获取句柄

还有很多同类可以查询API函数

提示:(因为这里获取的是标出应该将运行界面换为控制台界面,而非终端)

操作光标

上文获取到标准输出的句柄之后, 就可以利用这个句柄来获取标准输出的光标

光标类型的结构体是: CONSOLE_CURSOR_INFO 

  1. #include<stdio.h>
  2. #include<Windows.h>
  3. int main()
  4. {
  5. system("title 贪吃蛇");
  6. system("pause");
  7. system("mode con cols=100 lines=30");
  8. HANDLE h_out_put = GetStdHandle(STD_OUTPUT_HANDLE);
  9. CONSOLE_CURSOR_INFO cscsif; //cscsif 就是一个光标变量
  10. return 0;
  11. }

GetConsoleCursorInfo()函数有两个参数, 第一个参数用来传送句柄, 第二个参数用来传送光标变量的地址。 然后就可以将句柄所指的标准设备里面的光标信息拷贝到光标变量之中。

  1. #include<Windows.h>
  2. #include<stdio.h>
  3. int main()
  4. {
  5. system("title 贪吃蛇");
  6. system("pause");
  7. system("mode con cols=100 lines=30");
  8. HANDLE h_out_put = GetStdHandle(STD_OUTPUT_HANDLE);
  9. CONSOLE_CURSOR_INFO cscsif;
  10. GetConsoleCursorInfo(h_out_put, &cscsif);//获取标准输出的光标信息
  11. return 0;
  12. }

光标变量里面的成员变量只有两个, 一个是DWORD类型的 dwSize, 一个是BOOL类型的bVisible

其中dwSize是控制光标的填充范围。这个数据是1 ~ 100,当dwSize == 100时, 光标是填满一个单位, 当dwSize == 1 时, 光标为一条横线。

注( 修改后应再利用SetConsoleCursorInfo函数设置光标)

bVisible是控制光标的隐藏或者显示, 这个数据是false或者true。

  bVisible的默认值是true, 也就是光标是显示的。 如果想要将光标设置为不显示, 那么就要让bVisible的值变成false。 

#include<stdbool.h>        true  false

  1. #include<Windows.h>
  2. #include<stdio.h>
  3. #include<stdbool.h>
  4. int main()
  5. {
  6. system("title 贪吃蛇");
  7. system("mode con cols=100 lines=30");
  8. HANDLE h_out_put = GetStdHandle(STD_OUTPUT_HANDLE);
  9. CONSOLE_CURSOR_INFO cscsif;
  10. GetConsoleCursorInfo(h_out_put, &cscsif);
  11. //将光标信息设置成false:
  12. cscsif.bVisible = false;
  13. SetConsoleCursorInfo(h_out_put, &cscsif);
  14. system("pause");
  15. return 0;
  16. }

再次注( 修改后应再利用SetConsoleCursorInfo函数设置光标)

光标定位

坐标轴是以左上角为中心原点。然后向右为x轴的正方向, 向下为y轴的正方向

 COORD类型里面右两个成员变量, 一个是short x, 一个是short y

  1. #include<Windows.h>
  2. #include<stdio.h>
  3. #include<stdbool.h>
  4. int main()
  5. {
  6. system("title 贪吃蛇");
  7. system("mode con cols=100 lines=30");
  8. HANDLE h_out_put = GetStdHandle(STD_OUTPUT_HANDLE);
  9. COORD pos = { 40, 15 };//坐标
  10. SetConsoleCursorPosition(h_out_put, pos);//设置坐标的函数
  11. system("pause");
  12. return 0;
  13. }

封装坐标定位函数

  1. //光标定位的函数, 只需要传x, y就可以
  2. void SetPos(int x, int y)
  3. {
  4. HANDLE h_out_put = GetStdHandle(STD_OUTPUT_HANDLE);
  5. //获取拒柄
  6. COORD pos = { x, y };
  7. //设立坐标.
  8. SetConsoleCursorPosition(h_out_put, pos);//定位光标.
  9. }
按键情况

GetAsyncKeyState( int vkey );  这个函数可以用来区分按键得状态

这个函数的返回值是一个short类型的数据:、

如果返回值的最高位是1, 那么说明按键的状态是按下;

如果最高位是0, 那么说明按键的状态是抬起;

如果最低位是1, 说明这个按键被按过;

如果最低位是0, 说明这个按键没有被按过;

键盘上的每一个建都有一个虚拟键值

按键检测代码

  1. #include<Windows.h>
  2. #include<stdio.h>
  3. #include<stdbool.h>
  4. int main()
  5. {
  6. while (1)
  7. {
  8. printf("1\n");
  9. if (GetAsyncKeyState(0x30))
  10. break;//这里的0x30是数字0的键值。
  11. }
  12. system("pause");
  13. return 0;
  14. }

 如果没有按0, 那么函数的返回值为0, 不会打破死循环, 一直打印1。 如果按0, 那么函数的返回值是1, 就会进入if判断中, 那么就会打破循环, 结束程序。 

附:在本节要实现的贪吃蛇中要用到的键值如下

上:VK_UP

下:VK_DOWN

左:VK_LEFT

右:VK_RIGHT

空格:VK_SPACE

ESC:VK_ESCAPE

F3:VK_F3

F4:VK_F4

因为后续会频繁判断是否按下某个健, 我们这里将它封装成一个宏。

  1. //判断是否按下某个健的宏
  2. #define KEY_PRESS(KV) ((GetAsyncKeyState(KV) & 1) ? 1 : 0)

这里的&1可以判断最低位是否为1

宽字符打印

使用宽字符, 需要用到locale.h头文件

setlocale函数有两个参数, 第一个参数是类项, 第二个参数是模式

在贪吃蛇的实现中也会用到这个函数,将所有的类项改编为本地模式,

所有类项:LC_ALL

本地模式 :""(注: 本地模式为双引号)

setlocale(LC_ALL,'''');

应使用新的打印函数wprintf();

wprintf(L"深海时光鱼\n");

宽字符有自己的类型 wchar_t

wchar_t ch1=L'深海';

wprintf(L"%lc\n",ch1);

蛇的实现

创建蛇
 
  1. typedef struct Snake
  2. {
  3.     pSnakeNode _pSnake;//指向蛇头的指针
  4.     pSnakeNode _pFood;//指向食物节点的指针
  5.     enum DIRECTION _dir;//蛇的方向
  6.     enum GAME_STATUS _status;//游戏的状态
  7.     int _food_weight;//一个食物的分数
  8.     int _score;      //总成绩
  9.     int _sleep_time; //休息时间,时间越短,速度越快,时间越长,速度越慢
  10. }Snake, * pSnake;

Sanke sanke={0}; 

初始化游戏


//1. 打印环境界面
//2. 功能介绍
//3. 绘制地图
//4. 创建蛇
//5. 创建食物
//6. 设置游戏的相关信息

此图设置了地图和光标的相关信息

  1. void GameStart(pSnake ps)
  2. {
  3.     //0. 先设置窗口的大小,再光标隐藏
  4.     system("mode con cols=100 lines=30");
  5.     system("title 贪吃蛇");
  6.     HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
  7.     //影藏光标操作
  8.     CONSOLE_CURSOR_INFO CursorInfo;
  9.     GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息
  10.     CursorInfo.bVisible = false; //隐藏控制台光标
  11.     SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态
  12.     //1. 打印环境界面和功能介绍
  13.     WelcomeToGame();
  14.     //2. 绘制地图
  15.     CreateMap();
  16.     //3. 创建蛇
  17.     InitSnake(ps);
  18.     //4. 创建食物
  19.     CreateFood(ps);
  20. }

 

 此图打印了介绍信息

  1. void WelcomeToGame()
  2. {
  3. SetPos(40, 14);
  4. wprintf(L"欢迎来到贪吃蛇小游戏\n");
  5. SetPos(42, 20);
  6. system("pause");
  7. system("cls");
  8. SetPos(25, 14);
  9. wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");
  10. SetPos(25, 15);
  11. wprintf(L"加速能够得到更高的分数\n");
  12. SetPos(42, 20);
  13. system("pause");
  14. system("cls");
  15. }

上图中包含了的Setpos函数如下

 

  1. void SetPos(short x, short y)
  2. {
  3. //获得标准输出设备的句柄
  4. HANDLE houtput = NULL;
  5. houtput = GetStdHandle(STD_OUTPUT_HANDLE);
  6. //定位光标的位置
  7. COORD pos = { x, y };
  8. SetConsoleCursorPosition(houtput, pos);
  9. }

 此图封装了光标位置定位等操作

接下来就是绘制地图

  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. SetPos(0, 26);
  11. for (i = 0; i < 29; i++)
  12. {
  13. wprintf(L"%lc", WALL);
  14. }
  15. //左
  16. for (i = 1; i <= 25; i++)
  17. {
  18. SetPos(0, i);
  19. wprintf(L"%lc", WALL);
  20. }
  21. //右
  22. for (i = 1; i <= 25; i++)
  23. {
  24. SetPos(56, i);
  25. wprintf(L"%lc", WALL);
  26. }
  27. }

这里的WALL需要提前

#define WALL  L'#' 

初始化蛇

  1. void InitSnake(pSnake ps)
  2. {
  3. int i = 0;
  4. pSnakeNode cur = NULL;
  5. for (i = 0; i < 5; i++)
  6. {
  7. cur = (pSnakeNode)malloc(sizeof(SnakeNode));
  8. if (cur == NULL)
  9. {
  10. perror("InitSnake()::malloc()");
  11. return;
  12. }
  13. cur->next = NULL;
  14. cur->x = POS_X + 2 * i;
  15. cur->y = POS_Y;
  16. //头插法插入链表
  17. if (ps->_pSnake == NULL) //空链表
  18. {
  19. ps->_pSnake = cur;
  20. }
  21. else //非空
  22. {
  23. cur->next = ps->_pSnake;
  24. ps->_pSnake = cur;
  25. }
  26. }
  27. cur = ps->_pSnake;
  28. while (cur)
  29. {
  30. SetPos(cur->x, cur->y);
  31. wprintf(L"%lc", BODY);
  32. cur = cur->next;
  33. }
  34. //设置贪吃蛇的属性
  35. ps->_dir = RIGHT;//默认向右
  36. ps->_score = 0;
  37. ps->_food_weight = 10;
  38. ps->_sleep_time = 200;//单位是毫秒
  39. ps->_status = OK;
  40. }

创建食物

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

 

运行游戏
  1. void GameRun(pSnake ps)
  2. {
  3. //打印帮助信息
  4. PrintHelpInfo();
  5. do
  6. {
  7. //打印总分数和食物的分值
  8. SetPos(64, 10);
  9. printf("总分数:%d\n", ps->_score);
  10. SetPos(64, 11);
  11. printf("当前食物的分数:%2d\n", ps->_food_weight);
  12. if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
  13. {
  14. ps->_dir = UP;
  15. }
  16. else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
  17. {
  18. ps->_dir = DOWN;
  19. }
  20. else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
  21. {
  22. ps->_dir = LEFT;
  23. }
  24. else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
  25. {
  26. ps->_dir = RIGHT;
  27. }
  28. else if (KEY_PRESS(VK_SPACE))
  29. {
  30. Pause();
  31. }
  32. else if (KEY_PRESS(VK_ESCAPE))
  33. {
  34. //正常退出游戏
  35. ps->_status = END_NORMAL;
  36. }
  37. else if (KEY_PRESS(VK_F3))
  38. {
  39. //加速
  40. if (ps->_sleep_time > 80)
  41. {
  42. ps->_sleep_time -= 30;
  43. ps->_food_weight += 2;
  44. }
  45. }
  46. else if (KEY_PRESS(VK_F4))
  47. {
  48. //减速
  49. if (ps->_food_weight > 2)
  50. {
  51. ps->_sleep_time += 30;
  52. ps->_food_weight -= 2;
  53. }
  54. }
  55. SnakeMove(ps);//蛇走一步的过程
  56. Sleep(ps->_sleep_time);
  57. } while (ps->_status == OK);
  58. }
  1. void PrintHelpInfo()
  2. {
  3. SetPos(64, 14);
  4. wprintf(L"%ls", L"不能穿墙,不能咬到自己");
  5. SetPos(64, 15);
  6. wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动");
  7. SetPos(64, 16);
  8. wprintf(L"%ls", L"按F3加速,F4减速");
  9. SetPos(64, 17);
  10. wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
  11. }

 蛇走一步

  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. //检测下一个坐标处是否是食物
  30. if (NextIsFood(pNextNode, ps))
  31. {
  32. EatFood(pNextNode, ps);
  33. }
  34. else
  35. {
  36. NoFood(pNextNode, ps);
  37. }
  38. //检测蛇是否撞墙
  39. KillByWall(ps);
  40. //检测蛇是否撞到自己
  41. KillBySelf(ps);
  42. }

检测下一步是不是食物

  1. int 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. pSnakeNode cur = ps->_pSnake;
  10. //打印蛇
  11. while (cur)
  12. {
  13. SetPos(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. }
  21. void NoFood(pSnakeNode pn, pSnake ps)
  22. {
  23. //头插法
  24. pn->next = ps->_pSnake;
  25. ps->_pSnake = pn;
  26. pSnakeNode cur = ps->_pSnake;
  27. while (cur->next->next != NULL)
  28. {
  29. SetPos(cur->x, cur->y);
  30. wprintf(L"%lc", BODY);
  31. cur = cur->next;
  32. }
  33. //把最后一个结点打印成空格
  34. SetPos(cur->next->x, cur->next->y);
  35. printf(" ");
  36. //释放最后一个结点
  37. free(cur->next);
  38. //把倒数第二个节点的地址置为NULL
  39. cur->next = NULL;
  40. }

 检测状态

  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. }
  9. void KillBySelf(pSnake ps)
  10. {
  11. pSnakeNode cur = ps->_pSnake->next;
  12. while (cur)
  13. {
  14. if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
  15. {
  16. ps->_status = KILL_BY_SELF;
  17. break;
  18. }
  19. cur = cur->next;
  20. }
  21. }

 

结束游戏 - 善后工作
  1. void GameEnd(pSnake ps)
  2. {
  3. SetPos(24, 12);
  4. switch (ps->_status)
  5. {
  6. case END_NORMAL:
  7. wprintf(L"您主动结束游戏\n");
  8. break;
  9. case KILL_BY_WALL:
  10. wprintf(L"您撞到墙上,游戏结束\n");
  11. break;
  12. case KILL_BY_SELF:
  13. wprintf(L"您撞到了自己,游戏结束\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. del = NULL;
  24. }
  25. }

到这里就完成了!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/802940
推荐阅读
相关标签
  

闽ICP备14008679号