C语言实现贪吃蛇

简介: 贪吃蛇游戏介绍

一、贪吃蛇游戏介绍


贪吃蛇是久负盛名的游戏,它也和俄罗斯方块,扫雷等游戏位列经典游戏的行列。用C语言来实现贪吃蛇游戏之前 要会 数据结构(链表)、枚举、结构体、动态内存管理、预处理指令、win32API

二、Win32API


1.Win32API

Windows这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是⼀个很大的服务中心,调用这个服务中心的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程式达到开启 视窗、描绘图形、使⽤周边设备等目的,由于这些函数服务的对象是应用程序(Application),所以便 称之为ApplicationProgrammingInterface,简称API函数。WIN32API也就是MicrosoftWindows 32位平台的应用程序编程接⼝。


2.控制台程序

平常我们运行起来的黑框程序其实就是控制台程序 我们可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小,30行,100列

//函数system来执行不过在使用时要包含
#include <windows.h>
mode con cols=100 lines=30
//也可以通过命令设置控制台窗⼝的名字
title 贪吃蛇

3.控制台屏幕上的坐标COORD

控制台屏幕上的坐标COORD

typedef struct _COORD {
 SHORT X;
 SHORT Y;
} COORD, *PCOORD;
//给坐标赋值
COORD pos = { 10, 15 };

4.GetStdHandle

 GetStdHandle是⼀个WindowsAPI函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(用来标识不同设备的数值),使⽤这个句柄可以操作设备。

 HANDLE hOutput = NULL;
 //获取标准输出的句柄(⽤来标识不同设备的数值) 
 hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
5.GetConsoleCursorInfo

检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;6 GetConsoleCursorInfo(hOutput, &CursorInfo);
//获取控制台光标信息
6.CONSOLE_CURSOR_INFO

dwSize,由光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会变化,范围从完 全填充单元格到单元底部的水平线条。 bVisible,游标的可见性。如果光标可见,则此成员为TRUE。

//这个结构体,包含有关控制台游标的信息
typedef struct _CONSOLE_CURSOR_INFO 
{   DWORD dwSize;
    BOOL  bVisible;
}CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
 
CursorInfo.bVisible = false; //隐藏控制台光标 
7.SetConsoleCursorInfo

设置指定控制台屏幕缓冲区的光标的大小和可见性

BOOL WINAPI SetConsoleCursorInfo
(
 HANDLE hConsoleOutput,
 const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
 
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作 
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 
CursorInfo.bVisible = false; //隐藏控制台光标 
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态 
8.SetConsoleCursorPosition

设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调 ⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。

BOOL WINAPI SetConsoleCursorPosition(
 HANDLE hConsoleOutput,
 COORD pos
);
 
COORD pos = { 10, 5};
 HANDLE hOutput = NULL;
 //获取标准输出的句柄(⽤来标识不同设备的数值) 
 hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
 //设置标准输出上光标的位置为pos 
 SetConsoleCursorPosition(hOutput, pos);

SetPos:封装⼀个设置光标位置的函数:

void SetPos(short x, short y)
{
 COORD pos = { x, y };
 HANDLE hOutput = NULL;
 //获取标准输出的句柄(⽤来标识不同设备的数值) 
 hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
 //设置标准输出上光标的位置为pos 
 SetConsoleCursorPosition(hOutput, pos);
}

9.GetAsyncKeyState

GetAsyncKeyState 的返回值是short类型,在上⼀次调用GetAsyncKeyState 函数后,如果 返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.

#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

三、贪吃蛇游戏设计与分析


1.地图

如果想在控制台的窗口中指定位置输出信息,我们得知道该位置的坐标,所以首先介绍⼀下控制台窗口的坐标知识。 控制台窗口的坐标如下所示,横向的是X轴,从左向右依次增长,纵向是Y轴,从上到下依次增长

LC_COLLATE
• LC_CTYPE
• LC_MONETARY
• LC_NUMERIC
• LC_TIME
• LC_ALL-针对所有类项修改

在游戏地图上,我们打印墙体使用宽字符:■,打印蛇使⽤宽字符●,打印食物使用宽字符★ 普通的字符是占⼀个字节的,这类宽字符是占用2个字节。简单的讲⼀下C语⾔的国际化特性相关的知识,过去C语言并不适合非英语国家(地区)使用。 C语言最初假定字符都是但自己的。但是这些假定并不是在世界的任何地方都适用。后来为了使C语言适应国家化,C语言的标准中不断加⼊了国际化的支持。比如:加入和宽字符的类型 wchar_t 和宽字符的输入和输出函数,加⼊和头文件,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语⾔的地理区域)调整程序行为的函数。

2.#include

本地化提供的函数用于控制C标准库中对于不同的地区会产生不⼀样行为的部分。在标准可以中,依赖地区的部分有以下几项:(1)数字量的格式 (2)货币量的格式 (3) 字符集 (4)日期期和时间的表示形式

3.类项

通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部 分,其中⼀部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改,下⾯的⼀个宏, 指定⼀个类项:

LC_COLLATE
 LC_CTYPE
 LC_MONETARY
 LC_NUMERIC
 LC_TIME
 LC_ALL 针对所有类项修改
4. setlocale函数:

setlocale函数用于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。setlocale的第⼀个参数可以是前面说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参 数是LC_ALL,就会影响所有的类项。C标准给第⼆个参数仅定义了2种可能取值:"C"和" "在任意程序执行开始,都会隐藏式执行调用


char* setlocale (int category, const char* locale);
setlocale(LC_ALL, "C");

当地区设置为"C"时,库函数按正常方式执行,小数点是⼀个点。当程序运行起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤" "作为第2个参数,调⽤setlocale 函数就可以切换到本地模式,这种模式下程序会适应本地环境。

5.宽字符的打印
#include <stdio.h>
#include<locale.h>
int main() {
 setlocale(LC_ALL, "");
 wchar_t ch1 = L'●'; 
 wchar_t ch2 = L'T';
 wchar_t ch3 = L'U';
 wchar_t ch4 = L'★';
 printf("%c%c\n", 'a', 'b');
 
 wprintf(L"%c\n", ch1);
 wprintf(L"%c\n", ch2);
 wprintf(L"%c\n", ch3);
 wprintf(L"%c\n", ch4);
 return 0;
}

从输出的结果来看,我们发现⼀个普通字符占⼀个字符的位置但是打印⼀个汉字字符,占用2个字符的位置,那么我们如果 要在贪吃蛇中使⽤宽字符,就得处理好地图上坐标的计算。普通字符和宽字符打印出宽度的展示如下:

6.地图坐标

假设实现⼀个棋盘27行,58列的棋盘,再围绕地图画出墙

7.蛇身和食物

初始化状态,假设蛇的⻓度是5,蛇⾝的每个节点是●,在固定的⼀个坐标处,⽐如(24,5)处开始出现 蛇,连续5个节点。注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半 ⼉出现在墙体中,另外⼀般在墙外的现象,坐标不好对⻬。 关于⻝物,就是在墙体内随机⽣成⼀个坐标(x坐标必须是2的倍数),坐标不能和蛇的⾝体重合,然 后打印★。

8.数据结构设计

在游戏运行的过程中,蛇每次吃⼀个⻝物,蛇的身体就会变长⼀节,如果我们使⽤链表存储蛇的信息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好舍⾝节点在地图上的坐标就⾏, 所以舍蛇节点结构如下:

typedef struct SnakeNode
{
 int x;
 int y;
 struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
//要管理整条贪吃蛇,我们再封装⼀个Snake的结构来维护整条贪吃蛇
typedef struct Snake
{
 pSnakeNode _pSnake;//维护整条蛇的指针 
 pSnakeNode _pFood;//维护⻝物的指针 
 enum DIRECTION _Dir;//蛇头的⽅向默认是向右 
 enum GAME_STATUS _Status;//游戏状态 
 int _Socre;//当前获得分数 
 int _Add;//默认每个⻝物10分 
 int _SleepTime;//每⾛⼀步休眠时间 
}Snake, * pSnake;
//蛇的⽅向,可以⼀⼀列举,使⽤枚举
//⽅向 
enum DIRECTION
{
 UP = 1,
 DOWN,
 LEFT,
 RIGHT
};
//游戏状态 可以⼀⼀列举,使⽤枚举 
enum GAME_STATUS
{
 OK,//正常运⾏ 
 KILL_BY_WALL,//撞墙 
 KILL_BY_SELF,//咬到⾃⼰ 
 END_NOMAL//正常结束 
};
9.游戏流程设计

四、核心逻辑实现


1.游戏主要部分
#include <locale.h>
void test()
{
  int ch = 0;
  srand((unsigned int)time(NULL));
  do
  {
  Snake snake = { 0 };
  GameStart(&snake);
  GameRun(&snake);
  GameEnd(&snake);
  SetPos(20, 15);
  printf("再来⼀局吗?(X/T):");
  ch = getchar();
  getchar();//清理\n 
  } while (ch == 'X');
  SetPos(0, 27);
}
int main()
{
//修改当前地区为本地模式,为了⽀持中⽂宽字符的打印 
  setlocale(LC_ALL, "");
  //测试
  test();
  return 0;
}
2.游戏开始
void GameStart(pSnake ps)
{
 //设置控制台窗⼝的⼤⼩,30⾏,100列 
 //mode 为DOS命令 
 system("mode con cols=100 lines=30");
 //设置cmd窗⼝名称 
 system("title 贪吃蛇"); 
 //获取标准输出的句柄(⽤来标识不同设备的数值) 
 HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
 //影藏光标操作 
 CONSOLE_CURSOR_INFO CursorInfo;
 GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 
 CursorInfo.bVisible = false; //隐藏控制台光标 
 SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态 
 //打印欢迎界⾯ 
 WelcomeToGame();
 //打印地图 
 CreateMap();
 //初始化蛇 
 InitSnake(ps);
 //创造第⼀个⻝物 
 CreateFood(ps);
}

2.1打印欢迎界面:

void WelComeToGame()
{
  //定位光标
  SetPos(40,14);
  printf("欢迎来到贪吃蛇游戏");
  SetPos(40, 25);
  system("pause");//pause是暂停
  system("cls");//清理屏幕
  SetPos(20, 14);
  printf("使用↑,↓,←,→,分别控制蛇的移动,F3是加速,F4是减速");
  SetPos(40, 25);
  system("pause");
  system("cls");
}

2.2创建地图:创建地图就是将强打印出来,因为是宽字符打印,所有使用wprintf函数,打印格式串前使⽤L■打印地图的关键是要算好坐标,才能在想要的位置打印墙体。 墙体打印的宽字符:

#define WALL L'■'

创建地图函数CreateMap:

void CreateMap()
{
  SetPos(0, 0);
  int i = 0;
  for (i = 0; i <= 56; i += 2)//上
  {
    wprintf(L"%lc", WALL);
  }
  SetPos(0, 26);
  for (i = 0; i <= 56; i += 2)//下
  {
    wprintf(L"%lc", WALL);
  }
  for (i = 0; i <= 25; i++)//左
  {
    SetPos(0,i);
    wprintf(L"%lc",WALL);
  }
  for (i=1;i<=25;i++)//右
  {
    SetPos(56,i);
    wprintf(L"%lc",WALL);
  }
}

2.3初始化蛇身:蛇最开始长度为5节,每节对应链表的⼀个节点,蛇⾝的每⼀个节点都有⾃⼰的坐标。创建5个节点,然后将每个节点存放在链表中进行管理。创建完蛇⾝后,将蛇的每⼀节打印在屏幕上再设置当前游戏的状态,蛇移动的速度,默认的⽅向,初始成绩,蛇的状态,每个⻝物的分数。 蛇⾝打印的宽字符

#define BODY L'●'

初始化蛇函数:InitSnake

void InitSnake(pSnake ps)
{
  pSnakeNode cur = NULL;
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    cur =(pSnakeNode)malloc(sizeof(SnakeNode));
    if (cur == NULL)
    {
      perror("InitSnake()::malloc()");
      return;
    }
    cur->x = POS_X + 2*i;
    cur->y = POS_Y;
    cur->next = NULL;
    //头插
    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->_Status = OK;
  ps->_Score = 0;
  ps->_pFood = NULL;
  ps->_SleepTime = 200;
  ps->_FoodWeight = 10;
  ps->_Dir = RIGHT;
}

2.4创建第⼀个食物:先随机生成食物的坐标,x坐标必须是2的倍数,食物的坐标不能和蛇身每个节点的坐标重复。创建食物节点,打印食物。

//食物打印的宽字符
#define FOOD L'★'

创建食物的函数:CreateFood

void CreateFood(pSnake ps)
{
  int x = 0;
  int y = 0;
again:
  do
  {
    x = rand() % 53 + 2;
    y = rand() % 25 + 1;
  } while (x % 2 != 0);//x坐标必须是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("CreateFood()::malloc()");
    return;
  }
  pFood->x = x;
  pFood->y = y;
  ps->_pFood = pFood;
  //打印食物
  SetPos(x,y);
  wprintf(L"%lc",FOOD);
}

3.游戏运行

游戏运行期间,右侧打印帮助信息,提示玩家 根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。如果游戏继续,就是检测按键情况,确定蛇下⼀步的⽅向,或者是否加速减速,是否暂停或者退出游戏。确定了蛇的方向和速度,蛇就可以移动了

void GameRun(pSnake ps)
{
  PrintHelpInfo();
  do
  {
    SetPos(64,10);
    printf("得分:%04d",ps->_Score);
    SetPos(64,11);
    printf("每个食物:%2d",ps->_FoodWeight);
 
    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;
      break;
    }
    else if (KEY_PRESS(VK_SPACE))
    {
      Pause();
    }
    else if (KEY_PRESS(VK_F3))//加速
    {
      if (ps->_SleepTime >= 80)
      {
        ps->_SleepTime -= 30;
        ps->_FoodWeight += 2;
      }
    }
    else if (KEY_PRESS(VK_F4))//减速
    {
      if (ps->_SleepTime < 320)
      {
        ps->_SleepTime += 30;
        ps->_FoodWeight -= 2;
      }
    }
    Sleep(ps->_SleepTime);
    SnakeMove(ps);
  } while (ps->_Status == OK);
 
}

3.1:KEY_PRESS

//检测按键状态,封装了⼀个宏
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)

PrintHelpInfo

void PrintHelpInfo()
{
  SetPos(64, 15);
  printf("1.不能穿墙,不能咬自己");
  SetPos(64, 16);
  printf("2.使用↑,↓,←,→,分别控制蛇的移动");
  SetPos(64, 17);
  printf("3.F3加速,F4减速");
  SetPos(64, 18);
  printf("4.ESC-退出,空格暂停游戏");
}

3.2 蛇身移动:先创建下⼀个节点,根据移动方向和蛇头的坐标,蛇移动到下⼀个位置的坐标。 确定了下⼀个位置后,看下⼀个位置是否是⻝物(NextIsFood),是食物就做吃食物处理 (EatFood),如果不是食物则做前进⼀步的处理(NoFood)。蛇身移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上⾃⼰蛇⾝(KillBySelf),从而影响游戏的状态

void SnakeMove(pSnake ps)
{
  pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
  if (pNext == NULL)
  {
    perror("SnakeMove()::malloc()");
    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 LEFT:
    pNext->x = ps->_pSnake->x - 2;
    pNext->y = ps->_pSnake->y;
    break;
  case RIGHT:
    pNext->x = ps->_pSnake->x + 2;
    pNext->y = ps->_pSnake->y;
    break;
  }
  //判断蛇头到达的坐标处是否是食物
  if (NextIsFood(ps,pNext))
  {
    //吃掉食物
    EatFood(ps, pNext);
  }
  else
  {
    //不是食物
    NoFood(ps, pNext);
  }
 
  //蛇是否撞墙
  KillByWall(ps);
  //蛇是否自杀
  KillBySelf(ps);
}

NextIsFood:

int NextIsFood(pSnake ps,pSnakeNode pnext)
{
  if (ps->_pFood->x == pnext->x && ps->_pFood->y == pnext->y)
  {
    return 1;
  }
  else
  {
    return 0;
  }
}

EatFood:

//吃掉食物
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;
  }
  free(ps->_pFood);
  ps->_Score += ps->_FoodWeight;
 
  CreateFood(ps);//新创建食物
}

NoFood:将下⼀个节点头插⼊蛇的⾝体,并将之前蛇身最后⼀个节点打印为空格,放弃掉蛇⾝的最后⼀个节点

//不吃食物
void NoFood(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;
     
}

KillByWall:判断蛇头的坐标是否和强的坐标冲突

//蛇是否撞墙
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;
}

KillBySelf: 判断蛇头的坐标是否和蛇身体的坐标冲突

//蛇是否自杀
void 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;
    cur = cur->next;
  }
}

4.游戏结束

游戏状态不再是OK(游戏继续)的时候,要告知游戏技术的原因,并且释放蛇身节点

void GameEnd(pSnake ps)
{
  SetPos(20,12);
  switch (ps->_Status)
  {
  case END_NORMAL:
    printf("你主动退出游戏\n");
    break;
  case KILL_BY_SELF:
    printf("游戏自杀,游戏结束\n");
    break;
  case KILL_BY_WALL:
    printf("撞墙了,游戏结束\n");
    break;
  }
  //释放蛇身的结点
  pSnakeNode cur = ps->_pSnake;
  while (cur)
  {
    pSnakeNode del = cur;
    cur = cur->next;
    free(del);
  }
  ps->_pSnake = NULL;
}

五、完整代码


https://gitee.com/jjawei/tu-data-structure/tree/master/Gluttonous%20snake

Snake.c
#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);
}
 
void WelComeToGame()
{
  //定位光标
  SetPos(40,14);
  printf("欢迎来到贪吃蛇游戏");
  SetPos(40, 25);
  system("pause");//pause是暂停
  system("cls");//清理屏幕
  SetPos(20, 14);
  printf("使用↑,↓,←,→,分别控制蛇的移动,F3是加速,F4是减速");
  SetPos(40, 25);
  system("pause");
  system("cls");
}
 
void CreateMap()
{
  SetPos(0, 0);
  int i = 0;
  for (i = 0; i <= 56; i += 2)//上
  {
    wprintf(L"%lc", WALL);
  }
  SetPos(0, 26);
  for (i = 0; i <= 56; i += 2)//下
  {
    wprintf(L"%lc", WALL);
  }
  for (i = 0; i <= 25; i++)//左
  {
    SetPos(0,i);
    wprintf(L"%lc",WALL);
  }
  for (i=1;i<=25;i++)//右
  {
    SetPos(56,i);
    wprintf(L"%lc",WALL);
  }
}
 
void InitSnake(pSnake ps)
{
  pSnakeNode cur = NULL;
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    cur =(pSnakeNode)malloc(sizeof(SnakeNode));
    if (cur == NULL)
    {
      perror("InitSnake()::malloc()");
      return;
    }
    cur->x = POS_X + 2*i;
    cur->y = POS_Y;
    cur->next = NULL;
    //头插
    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->_Status = OK;
  ps->_Score = 0;
  ps->_pFood = NULL;
  ps->_SleepTime = 200;
  ps->_FoodWeight = 10;
  ps->_Dir = RIGHT;
}
 
void CreateFood(pSnake ps)
{
  int x = 0;
  int y = 0;
again:
  do
  {
    x = rand() % 53 + 2;
    y = rand() % 25 + 1;
  } while (x % 2 != 0);//x坐标必须是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("CreateFood()::malloc()");
    return;
  }
  pFood->x = x;
  pFood->y = y;
  ps->_pFood = pFood;
  //打印食物
  SetPos(x,y);
  wprintf(L"%lc",FOOD);
}
 
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;//隐藏控制台光表
  SetConsoleCursorInfo(hOutput,&CursorInfo);//设置控制台光标状态
  //打印欢迎界面
  WelComeToGame();
  //创建地图
  CreateMap();
  //初始化贪吃蛇
  InitSnake(ps);
  //创建食物
  CreateFood(ps);
}
//游戏正常运行
void PrintHelpInfo()
{
  SetPos(64, 15);
  printf("1.不能穿墙,不能咬自己");
  SetPos(64, 16);
  printf("2.使用↑,↓,←,→,分别控制蛇的移动");
  SetPos(64, 17);
  printf("3.F3加速,F4减速");
  SetPos(64, 18);
  printf("4.ESC-退出,空格暂停游戏");
  SetPos(64, 20);
  printf("游戏版权归@TU^");
 
}
 
void Pause()
{
  while (1)
  {
    Sleep(100);
    if (KEY_PRESS(VK_SPACE))
    {
      break;
    }
  }
}
 
int NextIsFood(pSnake ps,pSnakeNode pnext)
{
  if (ps->_pFood->x == pnext->x && ps->_pFood->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;
  }
  free(ps->_pFood);
  ps->_Score += ps->_FoodWeight;
 
  CreateFood(ps);//新创建食物
}
 
//不吃食物
void NoFood(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;
     
}
 
//蛇是否撞墙
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 (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y)
      ps->_Status = KILL_BY_SELF;
    cur = cur->next;
  }
}
 
void SnakeMove(pSnake ps)
{
  pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
  if (pNext == NULL)
  {
    perror("SnakeMove()::malloc()");
    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 LEFT:
    pNext->x = ps->_pSnake->x - 2;
    pNext->y = ps->_pSnake->y;
    break;
  case RIGHT:
    pNext->x = ps->_pSnake->x + 2;
    pNext->y = ps->_pSnake->y;
    break;
  }
  //判断蛇头到达的坐标处是否是食物
  if (NextIsFood(ps,pNext))
  {
    //吃掉食物
    EatFood(ps, pNext);
  }
  else
  {
    //不是食物
    NoFood(ps, pNext);
  }
 
  //蛇是否撞墙
  KillByWall(ps);
  //蛇是否自杀
  KillBySelf(ps);
 
}
void GameRun(pSnake ps)
{
  PrintHelpInfo();
  do
  {
    SetPos(64,10);
    printf("得分:%04d",ps->_Score);
    SetPos(64,11);
    printf("每个食物:%2d",ps->_FoodWeight);
 
    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;
      break;
    }
    else if (KEY_PRESS(VK_SPACE))
    {
      Pause();
    }
    else if (KEY_PRESS(VK_F3))//加速
    {
      if (ps->_SleepTime >= 80)
      {
        ps->_SleepTime -= 30;
        ps->_FoodWeight += 2;
      }
    }
    else if (KEY_PRESS(VK_F4))//减速
    {
      if (ps->_SleepTime < 320)
      {
        ps->_SleepTime += 30;
        ps->_FoodWeight -= 2;
      }
    }
    Sleep(ps->_SleepTime);
    SnakeMove(ps);
  } while (ps->_Status == OK);
 
}
void GameEnd(pSnake ps)
{
  SetPos(20,12);
  switch (ps->_Status)
  {
  case END_NORMAL:
    printf("你主动退出游戏\n");
    break;
  case KILL_BY_SELF:
    printf("游戏自杀,游戏结束\n");
    break;
  case KILL_BY_WALL:
    printf("撞墙了,游戏结束\n");
    break;
  }
  //释放蛇身的结点
  pSnakeNode cur = ps->_pSnake;
  while (cur)
  {
    pSnakeNode del = cur;
    cur = cur->next;
    free(del);
  }
  ps->_pSnake = NULL;
}
 
Snake.h
#pragma once
#include<locale.h>
#include<stdlib.h>
#include<windows.h>
#include<stdbool.h>
#include<stdio.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 DIREDCTION //
{
  UP = 1,
  DOWN,
  LEFT,
  RIGHT
};
enum GAME_STATUS
{
  OK,//正常运行
  END_NORMAL,//按ESC退出
  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 _pFood;//指向食物结点的指针
  int _Score;//贪吃蛇累计的总分
  int _FoodWeight;//一个食物的分数
  int _SleepTime;//每一步休息的时间,时间越短,速度越快,时间越长,速度越慢
  enum DIREDCTION _Dir;//描述蛇的方向
  enum GAME_STATUS _Status;//游戏的状态:正常,退出,撞墙,吃到自己
}Snake,*pSnake;
 
 
//游戏开始-完成游戏的初始化动作
void GameStart(pSnake ps);
//定位坐标
void SetPos(short x, short y);
//游戏开始的欢迎界面
void WelComeToGame();
//打印地图
void CreateMap();
//初始化贪吃蛇
void InitSnake(pSnake ps);
//创建食物
void CreateFood(pSnake ps);
 
//游戏正常运行
void GameRun(pSnake ps);
 
//打印帮助信息
void PrintHelpInfo();
 
//游戏暂停和恢复
void Pause();
 
//蛇的移动
void SnakeMove(pSnake ps);
 
//判断蛇头到达的坐标处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pnext);
 
//吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext);
 
//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext);
 
//蛇是否撞墙
void KillByWall(pSnake ps);
 
//蛇是否自杀
void KillBySelf(pSnake ps);
 
//游戏的善后处理
void GameEnd(pSnake ps);
test.c
#include"snake.h"
int main()
{
    //设置程序适应本地环境
  setlocale(LC_ALL,"");
  srand((unsigned int )time(NULL));
  //test();
  int ch = 0;
  do
  {
    Snake snake = { 0 };//创建了贪吃蛇
    //1,游戏开始——初始化游戏
    GameStart(&snake);
    //2,游戏运行——游戏的正常运行过程
    GameRun(&snake);
    //3,游戏结束——游戏善后(释放资源)
    GameEnd(&snake);
    SetPos(20, 15);
    printf("再来一局吗?(X/T)");
    ch = getchar();
    getchar();//清理掉\n
 
  } while (ch == 'x' || ch == 'X');
  SetPos(0, 27);
  return 0;
}

以上就是贪吃蛇的基本内容啦

目录
相关文章
|
1月前
|
API 定位技术 C语言
贪吃蛇---C语言---详解
贪吃蛇---C语言---详解
|
1月前
|
存储 定位技术 C语言
【c语言】简单贪吃蛇的实现
【c语言】简单贪吃蛇的实现
|
1月前
|
C语言
C语言之实现贪吃蛇小游戏篇(2)
C语言之实现贪吃蛇小游戏篇(2)
35 1
|
1月前
|
API 定位技术 C语言
C语言项目实战——贪吃蛇
贪吃蛇是久负盛名的游戏,它也和俄罗斯方块,扫雷等游戏位列经典游戏的行列。 在编程语言的学习中,我将以贪吃蛇为例,从设计到代码来展示一个C语言项目实战,进而再一步提升读者对C语言的理解和认知。
104 0
|
8月前
|
C语言 C++
【c语言】贪吃蛇
【c语言】贪吃蛇
77 0
|
30天前
|
定位技术 API C语言
贪吃蛇小游戏(c语言)
贪吃蛇小游戏(c语言)
33 0
|
1月前
|
存储 定位技术 API
C语言实现贪吃蛇【完整版】
C语言实现贪吃蛇【完整版】
|
1月前
|
C语言
|
1月前
|
C语言 计算机视觉
C语言贪吃蛇(有详细注释)
C语言贪吃蛇(有详细注释)
16 0
|
1月前
|
C语言
C语言实战演练之贪吃蛇游戏
C语言实战演练之贪吃蛇游戏