一个简单的控制台小游戏,用链表实现贪吃蛇。代码不到两百行,压缩一下可以在100行以内。通过链表,指针,光标控制,代码还是非常优雅的

很多同学刚学C语言,不知道能干嘛。其实C语言什么都能干,只不过可能比较复杂,但深入学习C语言,对于底层数据结构和算法原理学习极有好处。

以后再学习其他语言,学习速度跟坐飞机一样。

比如这个贪吃蛇小程序,实现方式有很多,数组也行,这里用链表,主要是为了练习链表和指针的使用。

一、控制光标

想让控制台成为游戏画面,printf作为输出,得先实现指定位置printf

头文件#include <conio.h>

代码:

    COORD coord;

    HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    //获取控制台缓冲区句柄
    coord.X = x; 
    coord.Y = y; 
    //设置光标位置
    SetConsoleCursorPosition(ConsoleHandle, coord);

    printf("*");

使用以上代码就可以在(x,y)位置输出,控制台左上角坐标为(0,0).

二、定时

贪吃蛇每走一步,需要间隔一段时间,所以需要定时。

        curTime = GetTickCount();
        if (curTime - lastTime > 300)
        {
            if (move(dire, &head, food, &tail) == -1)
                 break;
            lastTime = curTime;
        }

move函数是贪吃蛇的移动函数,GetTickCount()获取当前时间,当前时间和上次时间只差大于300ms就移动一次。

你也可以用一个宏定义,或者从键盘输出控制间隔时间也就是控制速度(难度)。

三、获取方向

头文件:#include <conio.h>

代码:

        if (_kbhit())
        {
            switch (_getch())
            {
            case 'w':
                dire = 'w'; break;
            case 's':
                dire = 's'; break;
            case 'a':
                dire = 'a'; break;
            case 'd':
                dire = 'd'; break;
            default:break;
            }
        }

_kbhit()函数检测键盘是否有按下,有则返回1,无则返回0;_getch()函数获取键盘值。

我们用w,s,a,d分别表示上,下,左,右。这样写是为了比较清晰,其实直接赋值就行。

四、构造贪吃蛇

typedef struct {
    int x;
    int y;
}POS;
typedef struct BODY {
    POS pos;
    struct BODY* next;
}BODY;
    BODY* head = malloc(sizeof(BODY));
    BODY* body = malloc(sizeof(BODY));
    BODY* tail = malloc(sizeof(BODY));
    BODY* food = malloc(sizeof(BODY));

    int lastTime;
    int curTime;
    head->pos.x = w_max / 2;
    head->pos.y = h_max / 2;
    body->pos.x = w_max / 2 + 1;
    body->pos.y = h_max / 2;
    tail->pos.x = w_max / 2 + 2;
    tail->pos.y = h_max / 2;

    head->next = body;
    body->next = tail;
    tail->next = NULL;

每一节身体,都有两部分内容,pos坐标,next指针,指向下一节的位置。初始化贪吃蛇为三节,head头,body身体,tail尾巴。

五、移动

没有吃到食物的移动:

移动位置根据方向确定。

首先检测新的位置在不在身体上,也就是链表的遍历,在这个遍历过程中,我们把尾巴的前一个位置pretail记录下来。

移动过程其实就是把原来尾巴储存的内容,变成新的头的坐标,

尾巴的前一节身体变成尾巴,

新的位置变为头,原来的头变成头的下一节。

pretail->next = NULL;//尾巴前一节指针赋空,即变成新的尾巴
(*tail)->next = *head;//原来的尾巴空间用来储存新的头结点位置
(*tail)->pos.x = newhead.pos.x;
(*tail)->pos.y = newhead.pos.y;
*head = *tail;//新的头结点指针,改为原来尾巴的储存位置
*tail = pretail;//新的尾巴结点指针,改为原来尾巴前一节的储存位置

吃到食物的移动:

其实就是食物的位置变成了头结点。

六、完整代码

如果编译不通过,只要将 malloc(sizeof(BODY))改为(BODY*)malloc(sizeof(BODY))。

自己可以加一下界面,比如开始和gameover,以及速度选择,地图大小选择。

tips:如果要做失败从头再来的功能记得释放malloc出来的空间。

#include <stdio.h>
#include <conio.h>
#include <time.h>
#include <windows.h>
#define H 52//长
#define W 102//宽
#define h_max (H-1)
#define w_max (W-1)
#define h_min 1
#define w_min 1

typedef struct {
    int x;
    int y;
}POS;
typedef struct BODY {
    POS pos;
    struct BODY* next;
}BODY;
void produceFood(BODY* food)
{
    food->pos.x = rand() % (W - 2) + 1;
    food->pos.y = rand() % (H - 2) + 1;
    show_point(food->pos.x, food->pos.y);
}
int show_point(int x, int y)
{
    COORD coord;
    HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    coord.X = x; 
    coord.Y = y;     //设置光标位置
    SetConsoleCursorPosition(ConsoleHandle, coord);
    printf("*");
    return 0;
}
int clear_point(int x, int y)
{
    COORD coord;
    HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);    //获取控制台缓冲区句柄
    coord.X = x;
    coord.Y = y;    //设置光标位置
    SetConsoleCursorPosition(ConsoleHandle, coord);
    printf(" ");
    return 0;
}

int move(char dire, BODY** head, BODY* food, BODY** tail)
{
    BODY newhead;
    BODY* search;
    BODY* pretail;
    pretail = NULL;
    int pretail_flag = 0;

    newhead.pos.x = (*head)->pos.x;
    newhead.pos.y = (*head)->pos.y;
    switch (dire)
    {
    case 'w':newhead.pos.y--; break;
    case 's':newhead.pos.y++; break;
    case 'a':newhead.pos.x--; break;
    case 'd':newhead.pos.x++; break;
    default:break;
    }
    if (newhead.pos.y >= h_max || newhead.pos.y < h_min || newhead.pos.x < w_min || newhead.pos.x >= w_max)//越界则退出
        return -1;
    if (newhead.pos.x == food->pos.x && newhead.pos.y == food->pos.y)    //吃到食物
    {
        BODY* newHead = malloc(sizeof(BODY));//新申请一个空间并加入贪吃蛇头部
        newHead->pos.x = newhead.pos.x;
        newHead->pos.y = newhead.pos.y;
        newHead->next = *head;
        *head = newHead;
        produceFood(food);//吃了食物要产生一个新的食物
        return 0;
    }
    else//没吃到食物则按方向前进(其实就是尾巴变成新的头部,尾巴的前一个变尾巴),同时需看新的位置是否在身体上
    {
        search = *head;
        pretail_flag = 0;
        while (1)
        {
            if(pretail_flag==0)
            {
                pretail = search;
                if ((pretail->next)->next == NULL)
                    pretail_flag = 1;
            }
            if (search->pos.x == newhead.pos.x && search->pos.y == newhead.pos.y)
                return -1;
            if (search->next == NULL)
                break;
            search = search->next;
        }
        clear_point((*tail)->pos.x, (*tail)->pos.y);
        show_point(newhead.pos.x, newhead.pos.y);
        //没有返回-1说明未碰撞,尾巴变新的头,pretail变新尾巴
        pretail->next = NULL;
        (*tail)->next = *head;
        (*tail)->pos.x = newhead.pos.x;
        (*tail)->pos.y = newhead.pos.y;
        *head = *tail;
        *tail = pretail;
        return 0;
    }
}

void show()//画出背景
{
    COORD coord;
    //获取控制台缓冲区句柄
    HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    //设置光标位置

    for (int i = 0; i < W; i++)
    {
        coord.X = i;
        coord.Y = 0; 
        SetConsoleCursorPosition(ConsoleHandle, coord);
        printf("#");
        coord.Y = H - 1; 
        SetConsoleCursorPosition(ConsoleHandle, coord);
        printf("#");
    }
    for (int i = 0; i < H; i++)
    {
        coord.X = 0; 
        coord.Y = i; 
        SetConsoleCursorPosition(ConsoleHandle, coord);
        printf("#");
        coord.X = W - 1; 
        SetConsoleCursorPosition(ConsoleHandle, coord);
        printf("#");
    }
}
int main()
{
    srand((unsigned)time(NULL));

    //初始化
    BODY* head = malloc(sizeof(BODY));
    BODY* body = malloc(sizeof(BODY));
    BODY* tail = malloc(sizeof(BODY));
    BODY* food = malloc(sizeof(BODY));

    int lastTime;
    int curTime;
    head->pos.x = w_max / 2;
    head->pos.y = h_max / 2;
    body->pos.x = w_max / 2 + 1;
    body->pos.y = h_max / 2;
    tail->pos.x = w_max / 2 + 2;
    tail->pos.y = h_max / 2;

    head->next = body;
    body->next = tail;
    tail->next = NULL;

    lastTime = GetTickCount();
    //
    char dire;//0,1,2,3,上下左右
    dire = 'w';
    show();
    show_point(head->pos.x, head->pos.y);
    show_point(body->pos.x, body->pos.y);
    show_point(tail->pos.x, tail->pos.y);
    produceFood(food);

    while (1)
    {
        if (_kbhit())
        {
            switch (_getch())
            {
            case 'w':
                dire = 'w'; break;
            case 's':
                dire = 's'; break;
            case 'a':
                dire = 'a'; break;
            case 'd':
                dire = 'd'; break;
            default:break;
            }
        }
        curTime = GetTickCount();
        if (curTime - lastTime > 300)
        {
            if (move(dire, &head, food, &tail) == -1)
                 break;
            lastTime = curTime;
        }
    }
    return 0;
}

更多推荐

C语言新手入门贪吃蛇的链表实现-控制光标位置,流畅不闪屏