C语言贪吃蛇(有详细注释)

简介: C语言贪吃蛇(有详细注释)

这个贪吃蛇是在比特特训营里学到的,同时我还写了用EasyX图形库实现的图形化贪吃蛇,含有每个函数的实现以及游戏中各种细节的讲解,感兴趣的可以去看一看。

贪吃蛇小游戏

实现效果

以下就是源码,感兴趣的小伙伴可以cv自己玩一玩改造改造,每个函数都有相应功能细节的注释,有用的话欢迎大家点赞

snake.h

#pragma once
#include <locale.h>
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#define wall L'¤'
#define body L'◎'
#define food L'★'
#define POS_X 24
#define POS_Y 5
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) &0x1)?1:0)//今天继续写,写完删除该行注释
enum DIRECTION//四个方向
{
  UP = 1,
  DOWN,
  LEFT,
  RIGHT
};
enum GAME_STATUS//游戏状态:正常,退出,撞墙,吃到自己
{
  OK=1,
  END_NOMAL,//正常退出
  KILL_BY_WALL,
  KILL_BY_SELF
};
typedef struct SnakeNode
{
  int x;
  int y;
  struct SnakeNode* next;
}SnakeNode,* pSnakeNode;
//相当于
//typedef struct SnakeNode* pSnakeNode;//结构体指针的重命名
//描述蛇的结构体
typedef struct snake
{
  pSnakeNode _psnake;//指向贪吃蛇头结点的指针。
  pSnakeNode _fFood;//假设食物也是蛇节点的指针,吃掉时改变其状态即可。
  int _Score;//分数,到时候要打印
  int _Foodweight;
  int SleepTime;//每走一步休息的时间,时间越短,速度越快
  enum DIRECTION _Dir;//方向,用枚举常量给出
  enum GAME_STATUS _status;
}Snake,*psnake;
//游戏开始
void GameStart(psnake);
//欢迎界面
WecomeGame();//打印游戏界面
//创建地图
void CreatMap();
void InitSnake(psnake ps);
void CreateFood(psnake ps);
//游戏的正常运行
void GameRun(psnake ps);
//打印帮助信息
void SetPos(short x, short y);
int KillBySelf(psnake ps);
void GameOver(psnake ps);

===============================

snake.c

#define _CRT_SECURE_NO_WARNINGS
#include "snake.h"
//设置光标位置
void SetPos(short x, short y)
{
  COORD pos = { x,y };
  HANDLE hOutput = NULL;
  //获取标准输出的句柄
  hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  //设置标准输出上光标的位置为pos
  SetConsoleCursorPosition(hOutput, pos);
}
WecomeGame()//打印游戏界面
{
  //定位光标,打印欢迎语句
  SetPos(40,15);
  printf("欢迎来到贪吃蛇游戏");
  
  SetPos(37, 27);
  //printf("按任意键继续");
  system("pause");//暂停程序,库函数的暂停命令
  //清空屏幕
  system("cls");
  SetPos(20, 15);
  printf("上下左右为↑↓←→,F3为加速,F4为减速  ");
  system("pause");//暂停程序,库函数的暂停命令
  system("cls");
  CreatMap();
}
void CreatMap()
{
  int i = 0;
  //通过创建的终端大小打印地图
  SetPos(0, 0);
  for (i = 0; i <= 56; i+=2)
  {
    wprintf(L"%c",wall);
  }
  SetPos(0, 26);
  for (i = 0; i <= 56; i += 2)
  {
    wprintf(L"%c", wall);
  }
  for (i = 1; i <= 25; i++)
  {
    SetPos(0, i);
    wprintf(L"%c", wall); 
  }
  for (i = 1; i <= 25; i++)
  {
    SetPos(56, i);
    wprintf(L"%c", wall);
  }
}
void GameStart(psnake ps)
{
  //初始化控制台窗体及初始化信息
  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;//将状态设置为fasle,隐藏
  SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
  //打印欢迎界面,提示
  WecomeGame();//打印游戏界面
  //初始化贪吃蛇
  InitSnake(ps);
  //创建食物
  CreateFood(ps);
}
void CreateFood(psnake ps)//创建食物
{
  //坐标范围内随机生成,且不可以生成在蛇身上。
  int x = 0;
  int y = 0;
  again:
  do
  {
    x = rand() % 53 + 2;
    y = rand() % 25 + 1;
  } while (x % 2 != 0);//横坐标为2的倍数,因为宽字符的原因
  //坐标不可以和蛇的节点坐标冲突
  pSnakeNode cur = ps->_psnake;
  while (cur)
  {
    //比较坐标
    if (cur->x == x && cur->y == y)
    {
      goto again;//也可以利用循环解决
    }
    cur = cur->next;
  }
  pSnakeNode pfood = (pSnakeNode)malloc(sizeof(SnakeNode));
  if (pfood == NULL)
  {
    perror("malloc fail");
    return;
  }
  pfood->x = x;
  pfood->y = y;
  ps->_fFood = pfood;
  //打印食物
  SetPos(x, y);
  wprintf(L"%lc", food);
  //getchar();随时阻塞,判断效果
}
void InitSnake(psnake ps)//初始化蛇
{
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    pSnakeNode snk = (pSnakeNode)malloc(sizeof(SnakeNode));
    if (snk == NULL)//问题检查
    {
      perror("malloc fail");
      return;
    }
    snk->x = POS_X + 2*i;//节点位置不同
    snk->y = POS_Y;
    snk->next = NULL;
    if (ps->_psnake == NULL)
    {
      ps->_psnake = snk;
    }
    else//此时_psnake修饰的就是蛇节点的头结点
    {
      snk->next = ps->_psnake;
      ps->_psnake = snk;
    }
  }
  //打印蛇的身体
  pSnakeNode cur = ps->_psnake;
  while (cur)
  {
    SetPos(cur->x, cur->y);
    wprintf(L"% c", body);
    cur = cur->next;
  }
  //蛇的相关数据
  ps->_status = OK;
  ps->_Score = 0;
  ps->_Foodweight = 10;
  ps->_fFood = NULL;
  ps->SleepTime = 200;//休眠时间关乎蛇移动的速度
  ps->_Dir = RIGHT;
}
void PrintInform()
{
  SetPos(60, 15);
  printf("1:不能穿墙,不能咬到自己");
  SetPos(60, 17);
  printf("2:上下左右为↑↓←→,F3为加速,F4为减速");
  SetPos(60, 19);
  printf("3:F3加速,F4减速,Esc退出,空格暂停");
  //getchar();
}
void Pause()//暂停游戏或者继续游戏
{
  while (1)
  {
    Sleep(100);
    if (KEY_PRESS(VK_SPACE))
    {
      break;
    }
  }
}
//判断是否吃掉食物
int NextIsFood(psnake ps,pSnakeNode pnext)
{
  if (ps->_fFood->x == pnext->x && ps->_fFood->y == pnext->y)
  {
    return 1;
  }
  else
  {
    return 0;
  }
}
void EatFood(psnake ps, pSnakeNode pnext)//吃掉
{
  pnext->next = ps->_psnake;
  ps->_psnake = pnext;//把蛇头换一换
  //打印蛇
  pSnakeNode cur = ps->_psnake;
  while (cur)
  {
    SetPos(cur->x, cur->y);
    wprintf(L"%lc", body);
    cur = cur->next;
  }
  //清理食物节点(食物节点是malloc出来的,所以要清理,不然会造成内存浪费),加分
  free(ps->_fFood);
  ps->_Score += ps->_Foodweight;
  //继续创建食物
  CreateFood(ps);
}
void NotEatFood(psnake ps, pSnakeNode pnext)//没有吃掉
{
  //如果不是食物,生成新的节点,删掉最后一个节点
  //头插
  pnext->next = ps->_psnake;
  ps->_psnake = pnext;//把蛇头换一换
  //打印社蛇的身体
  pSnakeNode cur = ps->_psnake;
  while (cur->next->next)//生成了新的头节点,只打印五个
  {
    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;
}
//蛇是否撞墙
int 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;
    return 1;
  }
  return 0;
}
//是否吃到自己
int KillBySelf(psnake ps)
{
  pSnakeNode cur = ps->_psnake->next;
  while (cur)
  {
    if (ps->_psnake->x == cur->x && ps->_psnake->y == cur->y)
    {
      ps->_status = KILL_BY_SELF;//改变状态
      return 1;
    }
    cur = cur->next;
  }
  return 0;
}
//值得学习的地方,修改整个数组
void SnakeMove(psnake ps)
{
  pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
  if (pnext == NULL)
  {
    perror("malloc fail");
    return;
  }
  //pnext->next = NULL;
  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 RIGHT:
    pnext->x = ps->_psnake->x+2;//减2,因为宽度为2.
    pnext->y = ps->_psnake->y;
    break;
  case LEFT:
    pnext->x = ps->_psnake->x-2;
    pnext->y = ps->_psnake->y;
    break;
  }
  //判断是否吃掉食物,如果吃掉食物,最后一个节点不清理,如果没有吃掉食物,就将其清理、
  //蛇头坐标和食物坐标
  if (NextIsFood(ps, pnext))
  {
    //吃掉食物
    EatFood(ps,pnext);
  }
  else
  {
    //没吃食物
    NotEatFood(ps,pnext);
  }
  //蛇是否撞墙
  KillByWall(ps);
  //是否吃到自己
  KillBySelf(ps);
}
void GameRun(psnake ps)
{
  PrintInform();
  do
  {
    SetPos(64, 10);
    printf("得分:%0.5d", ps->_Score);
    SetPos(64, 12);
    printf("每个食物10分");
    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_NOMAL;//正常退出
      break;
    }
    else if (KEY_PRESS(VK_SPACE))
    {
      //暂停函数
      Pause();//封装一个函数,按下一次暂停,再按就继续
    }
    else if (KEY_PRESS(VK_F3))//加速
    {
      //ps->SleepTime = 100;//如果一直按着,那就一直二倍加速
      //ps->_Foodweight = 15;
      if (ps->SleepTime >= 80)//也可以逐渐加速
      {
        ps->SleepTime -= 30;
        ps->_Foodweight += 2;
      }
    }
    else if (KEY_PRESS(VK_F4))//减速
    {
      if (ps->SleepTime <= 300)//逐渐减速
      {
        ps->SleepTime += 30;
        ps->_Foodweight -= 2;
      }
    }
    Sleep(ps->SleepTime);
    //蛇的移动
    SnakeMove(ps);//继续封装成函数
  } while (ps->_status==OK);
}
//善后处理,打印分数,清理贪吃蛇
void GameOver(psnake ps)
{
  SetPos(20, 12);
  switch (ps->_status)
  {
  case END_NOMAL:
    printf("您主动退出游戏");
    break;
  case KILL_BY_SELF:
    printf("自杀成功");
    break;
  case KILL_BY_WALL:
    printf("撞墙啦");
    break;
  }
  SetPos(0, 27);
  //释放蛇的节点
  pSnakeNode cur = ps->_psnake;
  while (cur)//循环全部释放
  {
    pSnakeNode Next = cur->next;
    free(cur);
    cur = Next;
  }
  ps->_psnake = NULL;
}

=====================================

test.c

#define _CRT_SECURE_NO_WARNINGS
#include "snake.h"
//Win32,API,Windows 32位接口。
//宽字符类型,一个汉字占用两个字符。
// 一个字母宽一个字符,一个汉字占两个字符
//wchar_t宽字符类型□☆★¤◎㊣
//setlocale(LC_ALL,"");//适应中文环境
//宽字符的打印,前缀加上L
//int main()
//{
//  SetPos(10, 10);
//  setlocale(LC_ALL, "");
//  wchar_t ch1 = L'●';
//  wprintf(L"%lc\n", ch1);//打印时printf前边加w,打印时前边大写L,类型为lc=
//  return 0;
//}
void test()
{
  char ch = 0;
  do
  {
    Snake snake = { 0 };//创建贪吃蛇
    //1,游戏开始——初始化游戏
    GameStart(&snake);
    //getchar();//设置光标状态是否成功可以检查一下,用getchar阻塞程序运行
    //2,游戏运行——正常运行
    GameRun(&snake);
    //3,游戏结束——如何结束,释放资源
    GameOver(&snake);
    SetPos(20, 15);
    printf("是否想再来一把?(Y/N):");
    ch = getchar();
    getchar();//清理‘/n’。
  } while (ch == 'Y' || ch == 'y');
}
int main()
{
  srand((unsigned int)time(NULL));
  //设置程序适应本地化
  setlocale(LC_ALL, "");
  test();
  return 0;
}
//地图,长为宽的2倍
目录
相关文章
|
2月前
|
定位技术 API C语言
C语言——实现贪吃蛇小游戏
本文介绍了一个基于Windows控制台的贪吃蛇游戏的实现方法。首先,需调整控制台界面以便更好地显示游戏。接着,文章详细描述了如何使用Win32 API函数如`COORD`、`GetStdHandle`、`GetConsoleCursorInfo`等来控制控制台的光标和窗口属性。此外,还介绍了如何利用`GetAsyncKeyState`函数实现键盘监听功能。文中还涉及了`&lt;locale.h&gt;`库的使用,以支持本地化字符显示。
59 1
C语言——实现贪吃蛇小游戏
|
1月前
|
C语言 定位技术 API
【C语言】实践:贪吃蛇小游戏(附源码)(二)
【C语言】实践:贪吃蛇小游戏(附源码)
【C语言】实践:贪吃蛇小游戏(附源码)(二)
|
1月前
|
存储 定位技术 API
C语言项目实战:贪吃蛇
C语言项目实战:贪吃蛇
|
1月前
|
C语言
【C语言】实践:贪吃蛇小游戏(附源码)(三)
【C语言】实践:贪吃蛇小游戏(附源码)
|
1月前
|
存储 API C语言
【C语言】实践:贪吃蛇小游戏(附源码)(一)
【C语言】实践:贪吃蛇小游戏(附源码)
|
1月前
|
C语言
C语言贪吃蛇小游戏来啦!
C语言贪吃蛇小游戏来啦!
31 0
|
2月前
|
存储 算法 C语言
数据结构基础详解(C语言):单链表_定义_初始化_插入_删除_查找_建立操作_纯c语言代码注释讲解
本文详细介绍了单链表的理论知识,涵盖单链表的定义、优点与缺点,并通过示例代码讲解了单链表的初始化、插入、删除、查找等核心操作。文中还具体分析了按位序插入、指定节点前后插入、按位序删除及按值查找等算法实现,并提供了尾插法和头插法建立单链表的方法,帮助读者深入理解单链表的基本原理与应用技巧。
560 6
|
3月前
|
存储 数据可视化 C语言
【C语言】C语言-身份证管理系统(源码+注释)【独一无二】
【C语言】C语言-身份证管理系统(源码+注释)【独一无二】
|
4月前
|
存储 编译器 C语言
C语言实战 | “贪吃蛇”游戏
【7月更文挑战第5天】在C语言实战中,本文档介绍了如何构建一个简单的“贪吃蛇”游戏。游戏的核心是控制蛇移动并增长,当吃掉食物时,蛇的身体变长。数据结构使用固定大小的数组表示蛇的位置,变量存储食物位置和蛇的长度。初始化后,利用非阻塞式`getKey()`函数实现WASD键盘控制蛇的运动方向。虽然蛇的边界检测和吃食物后的增长尚未详细说明,但提到了这些问题作为练习留给读者解决,并预告将在后续章节讨论模块化编程以简化复杂代码。
95 0
C语言实战 | “贪吃蛇”游戏
|
4月前
|
存储 数据管理 C语言
C语言实战 | 使用链表完成“贪吃蛇”游戏
【7月更文挑战第1天】整体思维,即系统思维,强调以整体视角理解事物。在编程中,结构体体现这种思想,将相关变量打包处理。示例展示了如何用链表而非数组实现“贪吃蛇”游戏,链表提供了更灵活的动态数据管理。一系列代码图片详细描绘了链表结构体在游戏中的应用,包括节点定义、移动、碰撞检测等,凸显了使用链表的优势和代码的清晰组织。
46 0
C语言实战 | 使用链表完成“贪吃蛇”游戏