C语言制作游戏——贪吃蛇

来源:互联网 发布:吉林国际软件 编辑:程序博客网 时间:2024/04/30 10:13

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:rfhklwt@163.com】

成品图

这里写图片描述

游戏模块组成

  • 开拓疆土——绘制游戏窗口
  • 上帝造蛇和食物——初始化蛇和食物
  • 自动裁判员——菜单信息
  • 食物从天而降——食物的随机生成
  • 让蛇扭起来——移动蛇
  • 大开吃戒——吃食物
  • 瞬间移动——穿墙
  • 生命凋零——蛇死亡
  • 一切的开始——游戏开始
  • 一切的结束——结束游戏
  • 上帝控制台——主函数

开拓疆土——绘制游戏窗口

大家都玩过游戏,游戏一定有一个限制框来限制我们游戏主人公的行动,说白了就是地图,这就是我们的游戏窗口。见如下代码

void MakeFrame(HANDLE hOut){    //打印边框    SetPosition(hOut, FRAMEX, FRAMEY);                                      //设置光标为左上角坐标处    printf("┏");    SetPosition(hOut, FRAMEX + 2 * FRAMEWIDTH - 2, FRAMEY);                 //设置光标为右上角坐标处    printf("┓");    SetPosition(hOut, FRAMEX, FRAMEY + FRAMEHEIGHT);                        //设置光标为左下角坐标处    printf("┗");    SetPosition(hOut, FRAMEX + 2 * FRAMEWIDTH - 2, FRAMEY + FRAMEHEIGHT);   //设置光标为右下角坐标处    printf("┛");    //水平-顶端    for (int i = 2; i < 2 * FRAMEWIDTH - 2; i+=2)    {        SetPosition(hOut, FRAMEX + i, FRAMEY);        printf("━");    }    //水平-底端    for (int i = 2; i < 2 * FRAMEWIDTH - 2; i += 2)    {        SetPosition(hOut, FRAMEX + i, FRAMEY + FRAMEHEIGHT);        printf("━");    }    //竖直-左端    for (int i = 1; i < FRAMEHEIGHT; i++)    {        SetPosition(hOut, FRAMEX, FRAMEY + i);        printf("|");    }    //竖直-右端    for (int i = 1; i < FRAMEHEIGHT; i++)    {        SetPosition(hOut, FRAMEX + 2 * FRAMEWIDTH - 2, FRAMEY + i);        printf("|");    }    //打印游戏名称    SetPosition(hOut, FRAMEX + FRAMEWIDTH - 5, FRAMEY - 2);    printf("贪吃蛇游戏");    //打印游戏操作    SetPosition(hOut, FRAMEX, FRAMEY + FRAMEHEIGHT + 2);    printf("游戏操作:   上: ↑    左: ←    右: →    下: ↓");    SetPosition(hOut, FRAMEX, FRAMEY + FRAMEHEIGHT + 4);    printf("加速: 长按方向键   退出: ESC 暂停:Space");}

以上代码需要说明的有
1. SetPosition(HANDLE hOut, int x, int y)函数:

void SetPosition(HANDLE hOut, int x, int y){    COORD pos;    pos.X = x;    pos.Y = y;    SetConsoleCursorPosition(hOut, pos);}

从上式代码可以看出,SetPosition(HANDLE hOut, int x, int y)函数是用于把光标放在我们需要的(x, y)上,从而能在(x, y)处打印我们需要的东西。

2. 常量:FRAMEX和FRAMEY和FRAMEHEIGHT和FRAMEWIDTH的含义
正如常量的名字一样,我们在主函数的前面定义了这四个常量,其含义和数值如下:

#define FRAMEX  4               //窗口左上角横坐标#define FRAMEY  4               //窗口左上角纵坐标#define FRAMEWIDTH  25          //游戏窗口宽度#define FRAMEHEIGHT 25          //游戏窗口高度

上帝造蛇和食物——初始化蛇和食物

根据蛇在本游戏中具有的性质,我们将这些性质打包到名为蛇的结构体中,同理也定义了一个食物的结构体。代码如下:
//蛇typedef struct SNAKE{    int x[100];                 //蛇的横坐标, x[0]蛇尾横坐标    int y[100];                 //蛇的纵坐标, y[0]蛇尾纵坐标    int nCount;                 //蛇吃食物总数    int nLength;                //蛇的长度    int nSpeed;                 //蛇的移动速度}Snake;//食物typedef struct FOOD{    int x;  //食物的横坐标    int y;  //食物的纵坐标}Food;//打印蛇void PrintfSnake(HANDLE hOut, Snake* pSnake){    for (int i = 0; i < pSnake->nLength; i++)    {        SetPosition(hOut, pSnake->x[i], pSnake->y[i]);        //打印蛇尾        if (i == 0)        {            SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_BLUE);            printf("○");            SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);        }        //打印蛇头        else if (i == pSnake->nLength - 1)        {            SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_GREEN);            printf("¤");            SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);        }        //打印蛇身        else        {            SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED);            printf("⊙");            SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);        }    }}

自动裁判员——菜单信息

作为一个心系玩家的开发者。我们需要将玩家在玩游戏时所产生的一些信息包括等级、得分等打印到屏幕上。这样可以让玩家在游戏得分中得到鼓舞,不断挑战自己。

void PrintfMenu(HANDLE hOut, Snake* pSnake, Food* pFood)    {    //游戏等级    SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 1);    printf("游戏等级:%d", pSnake->nCount / 5 + 1);          //每吃五个食物升一级    //游戏得分    SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 3);    printf("游戏得分:%d分", pSnake->nCount);                //每吃一个食物得一分    //食物个数    SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 5);    printf("所吃食物总个数:%d个", pSnake->nCount);           //玩家所吃的食物总数    //游戏速度    if (pSnake->nSpeed == 80)    {        SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);        printf("贪吃蛇的速度级别为:1");      }    else if (pSnake->nSpeed == 70)    {        SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);        printf("贪吃蛇的速度级别为:2");    }    else if (pSnake->nSpeed == 60)    {        SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);        printf("贪吃蛇的速度级别为:3");    }    else if (pSnake->nSpeed == 50)    {        SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);        printf("贪吃蛇的速度级别为:4");    }    else if (pSnake->nSpeed == 40)    {        SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);        printf("贪吃蛇的速度级别为:5");    }    else if (pSnake->nSpeed == 30)    {        SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);        printf("贪吃蛇的速度级别为:6");    }    else    {        SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 7);        printf("贪吃蛇的速度级别为:逆天级别");    }    //食物坐标    SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 9);    printf("食物的坐标:(%d, %d)", pFood->x, pFood->y);    //温馨提示:    SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 12);    SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_BLUE);    printf("游戏规则:吃到食物,碰到自身则蛇死亡。");    SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 14);    printf("PS:这不是普通的墙哦,可以穿过去的(*^▽^*)");    SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);}

食物从天而降——食物的随机生成

关于食物的生成,有一下几点需要注意的:

  1. 食物出现的位置不能超过我们界定的游戏窗口
  2. 食物不能产生在我们的蛇身上(不然我们就看不到食物了)

根据以上两点,可写出如下代码:

void MakeFood(HANDLE hOut, Snake* pSnake, Food* pFood){    srand((unsigned)time(NULL));    /*    1.在游戏框架内    2.不在蛇身上    */    while(1)     {        pFood->x = rand() % (FRAMEWIDTH - 1);       //0 - FRAMEWIDTH-1;        pFood->y = rand() % FRAMEHEIGHT;            //0 - FRAMEHEIGHT-1;        if (pFood->x == 0 || pFood->y == 0)         //食物不能再边界处            continue;        pFood->x = 2 * pFood->x + FRAMEX;        pFood->y += FRAMEY;        //判断食物是不是在蛇的身上        int temp;         for (temp = 0; temp < pSnake->nLength; temp++)        {            if (pFood->x == pSnake->x[temp] && pFood->y == pSnake->y[temp])                break;        }        if (temp == pSnake->nLength)        {            SetPosition(hOut, pFood->x, pFood->y);            SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_RED);            printf("◎");            SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);            break;        }    }}   

让蛇扭起来——移动蛇

看到这里,大家想想要如何让蛇移动起来呢?我们从前面的代码知道了整条蛇由众多的小蛇块组成,其中x[0]和y[0]是蛇尾的坐标。那么我们只要将让蛇尾去代替蛇尾前一块蛇的位置,以此不断推前,就能完成蛇的移动。在移动蛇的位置我们不考虑蛇头的位置,蛇头的运动由我们的玩家来控制,在StartGame()函数里面执行,现在可先不管。

这里写图片描述

如图,假设这条蛇的长度为6,x[5]为蛇头,x[0]为蛇尾。那么下一个时刻,除去蛇头,其余部位的位置应该如上图。x[0]所在的位置应该没有蛇的部位,反而,x[0]的位置挪到了x[1]的位置,以此类推,完成蛇的移动。至于蛇头的位置去哪了呢。不急~且看后文。看懂这个移动的原理后我们就可以写出下面的代码:

void MoveSnake(HANDLE hOut, Snake* pSnake){    SetPosition(hOut, pSnake->x[0], pSnake->y[0]);    printf("  ");    //后一个坐标代替前一个坐标    for (int i = 1; i < pSnake->nLength; i++)    {        pSnake->x[i - 1] = pSnake->x[i];        pSnake->y[i - 1] = pSnake->y[i];    }}

大开吃戒——吃食物

这部分没什么好讲的,大家看看代码就能理解了~

void EatFood(HANDLE hOut, Snake* pSnake, Food* pFood){    if (pSnake->x[pSnake->nLength - 1] == pFood->x && pSnake->y[pSnake->nLength - 1] == pFood->y)    {        //如果蛇头位置与食物位置相同,吃食物        pSnake->nLength++;        for (int i = pSnake->nLength - 1; i > 0; i--)        {            pSnake->x[i] = pSnake->x[i - 1];            pSnake->y[i] = pSnake->y[i - 1];        }        pSnake->x[0] = tail[0];     //得到蛇尾移动前的横坐标        pSnake->y[0] = tail[1];     //得到蛇尾移动前的纵坐标          //重新产生食物        MakeFood(hOut, pSnake, pFood);        pSnake->nCount++;           //食物的数量加一        //当蛇吃Up_level个食物时,速度加快Up_speed毫秒并且升一级        if (pSnake->nCount % 6 == 0)            pSnake->nSpeed -= 10;    }}

以上代码需要说明的有一点。
tail数组的含义
在主函数的前面,我们定义了一个全局变量tail[2]数组,来记录蛇尾移动前的位置

int tail[2];     //用于记住蛇尾坐标,其中tail[0]、tail[1]分别表示横、竖坐标

瞬间移动——穿墙

大家看成品图应该看到了,那一句蓝色的提示语,是的,在本游戏中,这条蛇是可以穿墙的,这穿墙不是指穿过墙跑到界外。而是,比如蛇撞到右边的墙壁中,则会从左边的墙壁出现。

void ThroughWall(HANDLE hOut, Snake* pSnake, char ch){    //如果蛇在上框且向上移动,穿墙    if (ch == 72 && pSnake->y[pSnake->nLength - 1] == FRAMEY)    {        pSnake->y[pSnake->nLength - 1] = FRAMEY + FRAMEHEIGHT - 1;    }    //如果蛇在下框且向下移动,穿墙    if (ch == 80 && pSnake->y[pSnake->nLength - 1] == FRAMEY + FRAMEHEIGHT)    {        pSnake->y[pSnake->nLength - 1] = FRAMEY + 1;    }    //如果蛇在右框且向右移动,穿墙    if (ch == 75 && pSnake->x[pSnake->nLength - 1] == FRAMEX)    {        pSnake->x[pSnake->nLength - 1] = FRAMEX + 2 * FRAMEWIDTH - 4;    }    //如果蛇在左框且向左移动,穿墙    if (ch == 77 && pSnake->x[pSnake->nLength - 1] == FRAMEX + 2 * FRAMEWIDTH - 2)    {        pSnake->x[pSnake->nLength - 1] = FRAMEX + 2;    }}

生命凋零——蛇死亡

在本游戏中,蛇死亡的条件很简单,碰到自身就死了。

bool IfSnakeDie(Snake* pSnake){    //当蛇头碰到自身时,蛇死,返回值为TRUE    for (int i = 0; i < pSnake->nLength - 1; i++)    {        if (pSnake->x[pSnake->nLength - 1] == pSnake->x[i] && pSnake->y[pSnake->nLength - 1] == pSnake->y[i])            return TRUE;    }    return FALSE;}

一切的开始——游戏开始

bool StartGame(){    unsigned char chOld = 77;    unsigned char chNow = 77;    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);    //定义蛇    Snake snake;    //定义食物    Food food;    //制作游戏窗口    MakeFrame(hOut);    //初始化蛇    InitSnake(&snake);    //产生食物    MakeFood(hOut, &snake, &food);    while (1)    {        //打印菜单信息        PrintfMenu(hOut, &snake, &food);        tail[0] = snake.x[0];       //记住蛇尾的横坐标        tail[1] = snake.y[0];       //记住蛇尾的纵坐标        bool speedUp = FALSE;        //判断键盘是否按下        if (kbhit())        {            chNow = getch();            //Sleep(10);                        if (kbhit())                speedUp = TRUE;        }        switch (chNow)        {            case 72:    //向上            {                            MoveSnake(hOut, &snake);                            if (chOld == 80)        //如果蛇调头,则无视调头继续走                            {                                snake.y[snake.nLength - 1] += 1;                                chNow = chOld;                                break;                            }                            chOld = chNow;                            snake.y[snake.nLength - 1] -= 1;                            break;            }            case 80:    //向下            {                            MoveSnake(hOut, &snake);                            if (chOld == 72)                            {                                snake.y[snake.nLength - 1] -= 1;                                chNow = chOld;                                break;                            }                            chOld = chNow;                            snake.y[snake.nLength - 1] += 1;                            break;            }            case 75:    //向左            {                            MoveSnake(hOut, &snake);                            if (chOld == 77)                            {                                snake.x[snake.nLength - 1] += 2;                                chNow = chOld;                                break;                            }                            chOld = chNow;                            snake.x[snake.nLength - 1] -= 2;                            break;            }            case 77:    //向右            {                            MoveSnake(hOut, &snake);                            if (chOld == 75)                            {                                snake.x[snake.nLength - 1] -= 2;                                chNow = chOld;                                break;                            }                            chOld = chNow;                            snake.x[snake.nLength - 1] += 2;                            break;            }            case 27:        //ESC键                                          break;            case 32:        //暂停            {                            break;            }            default:        //去掉其他按键的影响                            chNow = chOld;                            break;        }        //判断是否要出暂停提示        if (chNow != 32)    //清空暂停的提示        {            SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);            SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 16);            printf("                          ");            SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);        }        else//显示暂停指示        {            SetConsoleTextAttribute(hOut, FOREGROUND_INTENSITY | FOREGROUND_RED);            SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 16);            printf("已暂停,按方向键可继续游戏");            SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);        }        //穿墙        ThroughWall(hOut, &snake, chNow);        //判断有无吃食物        EatFood(hOut, &snake, &food);        //打印蛇        PrintfSnake(hOut, &snake);        /*        游戏结束条件:        1. 蛇碰到自身        2. 按ESC键        */        if (IfSnakeDie(&snake) == TRUE || chNow == 27)        {            SetPosition(hOut, FRAMEX + FRAMEWIDTH - 2, FRAMEY + FRAMEHEIGHT / 2 - 1);            bool State = OverGame(hOut, &snake);            return State;        }        PrintfMenu(hOut, &snake, &food);        if (speedUp == FALSE)        {            //SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 9);            //printf("          ");            Sleep(snake.nSpeed);        }                   else        {            //SetPosition(hOut, FRAMEX + FRAMEWIDTH * 2 + 5, FRAMEY + 9);            //printf("正在加速...");            Sleep(10);        }    }       }

一切的结束——结束游戏

bool OverGame(HANDLE hOut, Snake* pSnake){    system("cls");    SetPosition(hOut, FRAMEX + FRAMEWIDTH + 10, FRAMEY + FRAMEHEIGHT / 4);    printf("Game Over");    //打印得分    if (pSnake->nCount < 20)    {        SetPosition(hOut, FRAMEX + FRAMEWIDTH, FRAMEY + FRAMEHEIGHT / 4 + 2);        printf("好可惜哦!你的游戏得分为:%d分, 要不再来一盘突破自己?", pSnake->nCount);    }    else if (pSnake->nCount < 30)    {        SetPosition(hOut, FRAMEX + FRAMEWIDTH, FRAMEY + FRAMEHEIGHT / 4 + 2);        printf("不错哦!你的游戏得分为:%d分, 我猜你还没拿出实力吧", pSnake->nCount);    }    else    {        SetPosition(hOut, FRAMEX + FRAMEWIDTH, FRAMEY + FRAMEHEIGHT / 4 + 2);        printf("哇好腻害(⊙o⊙)!你的游戏得分为:%d分, 你的手速已经超越地球人了", pSnake->nCount);    }    SetPosition(hOut, FRAMEX + FRAMEWIDTH, FRAMEY + FRAMEHEIGHT/4 + 4);    printf("重新开始游戏:Enter    退出游戏:ESC");    int userKey = 0;    while (1)    {        if ((userKey = getch()) == 13)            return FALSE;        else if ((userKey = getch()) == 27)            return TRUE;        else            continue;    }}                           

上帝控制台——主函数

int main(){    while(1)     {        //开始游戏        system("cls");        bool State= StartGame();        if (State)            break;    }    return 0;}