在控制台实现贪吃蛇

目录

1.游戏实现演示

2.实现功能

3.游戏架构

4.游戏预准备

5.游戏逻辑分析

6.参考代码


目录

目录

1.游戏实现演示

2.实现功能

3.游戏架构

4.游戏预准备

5.游戏逻辑分析

6.参考代码

1.游戏实现演示

2.实现功能

3.游戏架构

4.游戏预准备

4.1地图

4.1.1本地化

4.1.2类项

4.1.3setlocale函数

4.1.4宽字符的打印

4.1.5地图坐标

4.2蛇身和食物

4.3数据结构设计

5.游戏主逻辑

5.1GameStart

5.1.1打印欢迎页面

5.1.2创建地图

5.1.3初始化蛇身

5.1.4创建食物

5.2GameRun

5.2.1KEY_PRESS

5.2.2HelpInfo

5.2.3SnakeMove

5.2.3.1Next_is_Food

5.2.3.2EatFood

5.2.3.3NoFood

5.2.3.4Kill_by_Wall

5.2.3.5Kill_by_Self

5.3GameEnd

6.参考代码

snake_test.cpp

snake.h

snake.cpp

1.游戏实现演示

2.实现功能

●贪吃蛇地图绘制

●蛇吃食物的功能

●蛇撞墙死亡

●蛇吃到自己死亡

●计算得分

●蛇身加速、减速

●暂停、退出游戏

3.游戏架构

4.游戏预准备

在上一篇文章中我们讨论了win32API,可以利用win32API来实现对窗口的控制,像窗口大小、设置窗口名字、获取光标位置等信息由于在上一章中已讲到,因此本章不在提及。

4.1地图

我们打印地图使用宽字符,这样可以使地图更具美感。我们打印墙体使用‘▢’,打印蛇身使用‘●’,打印食物使用‘⨁’。宽字符类型为wchar_t,而要想使用宽字符,我们可以引入<locale.h>头文件,其中提供了了允许程序员针对特定地区调整程序行为的函数。

4.1.1<locale.h>本地化

<locale.h>提供的函数用于控制C标准库中对于不同地区会产生不一样行为的部分。

在标准中,依赖地区的部分有以下几项:

●数字量的格式

●货币量的格式

●字符集

●日期和时间的表示形式

4.1.2类项

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

●LC_COLLATE:影响字符串比较函数strcoll()和strxfrm()。

●LC_CTYPE:影响字符处理函数的行为。

●LC_MONETARY:影响货币格式。

●LC_NUMERIC:影响printf ()的数字格式。

●LC_TIME:影响时间格式strftime()和wcsftime()。

●LC_ALL:针对所有类项修改,将以上所有类别设置为给定的语言环境。

4.1.3setlocale函数

char* setlocale(int category,const char * locale);

setloacle函数用于修改当前地区,可以针对一个类项修改,也可以针对所有类项。

setlocale的第一个参数是前面的一个类项,第二个参数仅定义2种可能取值:“C”(正常模式)和“ ”(本地模式)。

在任意程序执行开始,都会隐式执行调用:

setlocale(LC_ALL,"C");

当我们切换到本地模式后就可以支持宽字符的输出

setlocale(LC_ALL," ")//切换到本地环境

4.1.4宽字符的打印

宽字符的字面量必须加上前缀“L”,否则c语言会把字面量当作窄字符类项处理。前缀“L”在单引号前面,表示宽字符,对应我printf()的占位为%lc;在双引号前面,表示宽字符串,对应wprintf()的占位符为%ls。

示例:

#include <stdio.h>

#include <locale.h>

int main(){

        setlocale(LC_ALL," ");

        wchar_t ch1=L"宽";

        wchar_t ch2=L"字";

        wchar_t ch3=L"符";

        wprintf(L"%lc"L"%lc"L"%lc",ch1,ch2,ch3);

        return 0;

}

输出“宽字符”,其中每个字符占两格(x轴方向)

4.1.5地图坐标

我们假设实现一个棋盘27行,58列棋盘,在地图中画出墙。

4.2蛇身和食物

初始化状态,假设蛇长为5,蛇身的每个节点是"●",在一固定位,入(24,25)处开始出现蛇,连续5个结点。

注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的一个节点出现在墙体中;食物在墙体内随机生成一个坐标(x坐标必须为2的倍数),且坐标不能和蛇的身体重合,然后打印“⨁”。

4.3数据结构设计

在游戏运行过程中,蛇每次吃一个食物,蛇的身体会变长一节,如果我们使用链表存储蛇的信息,那么蛇的每一节就是链表中的每个节点。每个节点存储蛇身节点在地图上的坐标。

蛇节点链表:

typedef struct SnakeNode{

        int x;

        int y;

        struct SnakeNode *next ;

}SnakeNode,*pSnakeNode;

要管理整条贪吃蛇,我们继续封装一个Snake的结构来维护整条贪吃蛇:

typedef struct Snake{

    SnakeNode * _snake;//维护整条蛇的指针

    SnakeNode *_food;//维护食物的指针

    Dirction _dir;//蛇头方向,默认向右

    Status _sta;//游戏状态

    int _foodwight;//默认每个食物10分

    int _score;//游戏当前获得分数

    int _sleeptime;//每走一步休眠时间

}Snake,*pSnake;

蛇的方向,可以一一列举,使用枚举

//蛇的方向

enum Dirction{

    UP=1,

    DOWN,

    LEFT,

    RIGHT

};

游戏状态,可以一一列举,使用枚举

enum Status{

    OK,

    KILL_BY_WALL,

    KILL_BY_SELF,

    END_NORAML

};

5.游戏主逻辑

程序开始就设置程序支持本地模式,然后进入游戏主逻辑。

主逻辑分为3个过程:

●Gamestart:完成游戏的初始化

●GameRun:完成游戏运行逻辑的实现

●GameEnd:完成游戏结束的说明,实现资源释放

#include "snake.h"

void Game(){

    srand((unsigned int)time(NULL));  

    char ch;

    do{

        Snake ps{0};

        GameStart(&ps);

        GameRun(&ps);

        GameEnd(&ps);

        SetPos(20,15);

        printf("是否再来一局:Y/N");

        ch=getchar();

        getchar();//清理'\n'

    }while(ch=='Y'||ch=='y');

    SetPos(20,26);

}

int main(){

    setlocale(LC_ALL," ");

    SetConsoleOutputCP(CP_UTF8);//防止输出中文时乱码

    Game();

    return 0;

}

5.1GameStart

本模块功能:

●控制台窗口大小和名字

●光标的隐藏

●打印欢迎界面

●创建地图

●初始化蛇身

●创建食物

void GameStart(pSnake ps){

    //设置窗口大小

    system("mode con cols=100 lines=30");

    system("title snake");//设置窗口名字

    HANDLE hOutput=GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输出句柄

    //隐藏光标

    CONSOLE_CURSOR_INFO CursorInfo;

    GetConsoleCursorInfo(hOutput,&CursorInfo);//获取控制台光标信息

    CursorInfo.bVisible=false;//隐藏控制台光标

    SetConsoleCursorInfo(hOutput,&CursorInfo);//设置控制台光标状态

    Welcome();// 打印欢迎界面

    SetMap();

    InitSnake(ps);

    SetFood(ps);

}

5.1.1打印欢迎页面

定位光标位置的函数上文已讲过,文本不在赘述

void SetPos(short x,short y){

    COORD pos={x,y};

    HANDLE hOutput=NULL;

    hOutput=GetStdHandle(STD_OUTPUT_HANDLE);

    //设置标准输出光标坐标为pos

    SetConsoleCursorPosition(hOutput,pos);

}

游戏开始前的功能提醒:

void Welcome(){

    SetPos(34,13);

    printf("欢迎来到贪吃蛇小游戏");

    SetPos(30,20);

    system("pause");

    system("cls");

    SetPos(30,13);

    printf("用↑,ↆ,←,→,分别控制蛇的移动,F3加速,F4减速");

    SetPos(30,14);

    printf("加速能获得更高的分数");

    SetPos(30,20);

    system("pause");

    system("cls");

}

5.1.2创建地图

创建地图就是将墙打印出来,因为宽字符打印,使用wprintf函数,打印格式串前使用L。

打印地图的关键是要计算好坐标,才能在想要的位置打印墙体。

墙体打印的宽字符:

#define WALL L'■'

void SetMap(){

    SetPos(0,0);

    for(int i=0;i<58;i+=2) wprintf(L"%lc",WALL);

    SetPos(0,26);

    for(int i=0;i<58;i+=2) wprintf(L"%lc",WALL);

    for(int i=1;i<26;i++){SetPos(0,i);wprintf(L"%lc",WALL);}

    for(int i=1;i<26;i++){SetPos(56,i);wprintf(L"%lc",WALL);}

}

5.1.3初始化蛇身

蛇最开始长度为5节,每节对应链表的一个节点,蛇身的每个节点都有自己的坐标。

创建5个节点,然后将每个节点存放在链表中进行管理。创建完蛇身后,将蛇的每一节打印在屏幕上。

●蛇的初始位置从(24,5)开始

●游戏状态是:OK

●蛇的移动速度:200毫秒

●蛇的默认方向:RIGHT

●初始成绩:0

●每个食物的分数:10

蛇身打印宽字符:

#define BODY L'●'

效果:

5.1.4创建食物

●先随机生成食物的坐标

        x坐标必须是2的倍数

        食物的坐标不能和蛇身的每个节点坐标重复

●创建食物节点,打印食物

食物打印的宽字符:

#define FOOD L'★'

5.2GameRun

游戏运行期间,右侧打印帮助信息,提示玩家,坐标开始位置(64,15)

根据游戏状态检查游戏是否继续,若状态为OK,游戏继续,否则游戏结束。

若游戏继续,检查按键情况,确定蛇的下一步方向和加减速情况,是否暂停或退出游戏。

需要用到的按键虚拟值:

●上:VK_UP

●下:VK_DOWN

●左:VK_LEFT

●右:VK_RIGHT

●空格:VK_SPACE

●ESC:VK_ESCAPE

●F3:VK_F3

●F4:VK_F4

5.2.1KEY_PRESS

为了方便,检测按键状态的函数我们封装了一个宏:

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

5.2.2HelpInfo

void HelpInfo(){

    SetPos(64,15);

    printf("不能穿墙,不能咬到自己");

    SetPos(64,16);

    printf("用用↑,ↆ,←,→,分别控制蛇的移动");

    SetPos(64,17);

    printf("F3为加速,F4为减速");

    SetPos(64,18);

    printf("ESC:退出游戏,space:暂停游戏");

}

5.2.3SnakeMove

先创建下一个节点,根据移动方向和蛇头坐标,判断蛇移到下一个位置的坐标。

确定了下一个位置后,看下一个位置是否是食物(Nest_is_Food),是食物就吃掉食物(EatFood),若不是食物,则做相应处理(NoFood)

 而在蛇移动后,判断此次移动是否会造成撞墙(Kill_by_Wall)或吃到自己  (Kill_by_Self),影响游戏状态。

5.2.3.1Next_is_Food

bool Next_is_Food(pSnakeNode pNextNode,pSnake ps){

    return (pNextNode->x==ps->_food->x&&pNextNode->y==ps->_food->y);

}

5.2.3.2EatFood

void EatFood(pSnakeNode pNextNode,pSnake ps){

    pNextNode->next=ps->_snake;

    ps->_snake=pNextNode;

    pSnakeNode cur=ps->_snake;

    //打印蛇

    while(cur){

        SetPos(cur->x,cur->y);

        wprintf(L'%c',BODY);

        cur=cur->next;

    }

    ps->_score+=ps->_foodwight;

    free(ps->_food);//释放掉此节点

    SetFood(ps);//重新生成食物

}

5.2.3.3NoFood

将下一个节点头插进蛇身,并将蛇身最后一个节点打印为空格,释放掉最后一个节点。

void NoFood(pSnakeNode pNextNode,pSnake ps){

    pNextNode->next=ps->_snake;

    ps->_snake=pNextNode;

    pSnakeNode cur=ps->_snake;

    while(cur->next->next!=NULL){

        SetPos(cur->x,cur->y);

        wprintf(L'%c',BODY);

        cur=cur->next;

    }

    SetPos(cur->next->x,cur->next->y);

    printf(" ");

    free(cur->next);

    cur->next=NULL;

}

注:一定要将最后一个节点释放并置为NULL,否则肯能会发生越界

5.2.3.4Kill_by_Wall

只需检查蛇头是否和墙坐标重合

void Kill_by_Wall(pSnake ps){

    if(ps->_snake->x==0||ps->_snake->x==56

        ||ps->_snake->y==0||ps->_snake->y==30){

            ps->_sta=KILL_BY_WALL;

        }

}

5.2.3.5Kill_by_Self

只需检查蛇头是否和蛇身坐标重合

void Kill_by_Self(pSnake ps){

    pSnakeNode cur=ps->_snake;

    while(cur){

        if(cur->x=ps->_snake->x&&cur->y==ps->_snake->y){

            ps->_sta=KILL_BY_SELF;

            break;

        }

    }

}

5.3GameEnd

当游戏状态不再是OK时,打印游戏死亡原因,并释放节点

6.参考代码

完整代码,三个文件实现

snake_test.cpp

snake.h

snake.cpp

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值