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倍
目录
相关文章
|
6天前
|
API 定位技术 C语言
贪吃蛇---C语言---详解
贪吃蛇---C语言---详解
|
6天前
|
API 定位技术 C语言
C语言项目实战——贪吃蛇
贪吃蛇是久负盛名的游戏,它也和俄罗斯方块,扫雷等游戏位列经典游戏的行列。 在编程语言的学习中,我将以贪吃蛇为例,从设计到代码来展示一个C语言项目实战,进而再一步提升读者对C语言的理解和认知。
90 0
|
6天前
|
存储 定位技术 C语言
【c语言】简单贪吃蛇的实现
【c语言】简单贪吃蛇的实现
|
6天前
|
C语言
C语言之实现贪吃蛇小游戏篇(2)
C语言之实现贪吃蛇小游戏篇(2)
26 1
|
6天前
|
C语言 C++
每天一道C语言编程:(去掉:双斜杠注释,去掉空格)
每天一道C语言编程:(去掉:双斜杠注释,去掉空格)
4 0
|
6天前
|
C语言
|
6天前
|
存储 定位技术 API
C语言实现贪吃蛇【完整版】
C语言实现贪吃蛇【完整版】
|
6天前
|
C语言
【C语言】第三回 关于字符串,语句和注释的使用2
【C语言】第三回 关于字符串,语句和注释的使用
|
6天前
|
编译器 C语言
【C语言】第三回 关于字符串,语句和注释的使用1
【C语言】第三回 关于字符串,语句和注释的使用
|
6天前
|
算法 程序员 编译器
【C语言】注释
【C语言】注释
58 2