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

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

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

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

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

7e5bb0d3a00d48f6ab9c6f5796648576.gif

一、控制光标

想让控制台成为游戏画面,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;
}

  • 4
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
生成字母链表可以使用C语言的随机数生成函数rand(),然后将生成的数字转换成字母即可。以下是生成长度为n的字母链表的示例代码: ``` #include <stdio.h> #include <stdlib.h> #include <time.h> typedef struct Node { char data; struct Node *next; } Node; Node *createList(int n) { Node *head = NULL; Node *tail = NULL; srand(time(NULL)); for (int i = 0; i < n; i++) { Node *node = (Node *) malloc(sizeof(Node)); node->data = rand() % 26 + 'a'; node->next = NULL; if (head == NULL) { head = node; tail = node; } else { tail->next = node; tail = node; } } return head; } void printList(Node *head) { while (head != NULL) { printf("%c ", head->data); head = head->next; } printf("\n"); } int main() { Node *list = createList(10); printList(list); return 0; } ``` 接下来是实现链表贪吃蛇的示例代码。链表贪吃蛇可以通过一个链表来表示蛇的身体,每个节点表示一个身体部位,头节点表示蛇头,尾节点表示蛇尾。蛇的运动可以通过在链表头部添加一个节点来实现。以下是示例代码: ``` #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <windows.h> #define WIDTH 30 #define HEIGHT 20 typedef struct Point { int x; int y; } Point; typedef struct Node { Point data; struct Node *next; } Node; typedef enum Direction { UP, DOWN, LEFT, RIGHT } Direction; Node *createSnake(Point head) { Node *node = (Node *) malloc(sizeof(Node)); node->data = head; node->next = NULL; return node; } void printSnake(Node *snake) { while (snake != NULL) { printf("(%d,%d) ", snake->data.x, snake->data.y); snake = snake->next; } printf("\n"); } void drawMap(Node *snake, Point food) { system("cls"); for (int i = 0; i < HEIGHT; i++) { for (int j = 0; j < WIDTH; j++) { if (i == 0 || i == HEIGHT - 1 || j == 0 || j == WIDTH - 1) { printf("#"); } else if (i == food.y && j == food.x) { printf("$"); } else { int found = 0; Node *p = snake; while (p != NULL) { if (p->data.x == j && p->data.y == i) { printf("*"); found = 1; break; } p = p->next; } if (!found) { printf(" "); } } } printf("\n"); } } int isCollided(Node *snake) { Node *p = snake->next; while (p != NULL) { if (p->data.x == snake->data.x && p->data.y == snake->data.y) { return 1; } p = p->next; } return 0; } int isInMap(Point point) { return point.x > 0 && point.x < WIDTH - 1 && point.y > 0 && point.y < HEIGHT - 1; } Point generateFood(Node *snake) { Point food; do { food.x = rand() % (WIDTH - 2) + 1; food.y = rand() % (HEIGHT - 2) + 1; } while (!isInMap(food) || isCollided(snake)); return food; } void moveSnake(Node **snake, Direction direction, Point food, int *score) { Point head = (*snake)->data; switch (direction) { case UP: head.y--; break; case DOWN: head.y++; break; case LEFT: head.x--; break; case RIGHT: head.x++; break; } Node *node = (Node *) malloc(sizeof(Node)); node->data = head; node->next = *snake; *snake = node; if (head.x == food.x && head.y == food.y) { food = generateFood(*snake); *score += 10; } else { Node *tail = *snake; while (tail->next->next != NULL) { tail = tail->next; } free(tail->next); tail->next = NULL; } drawMap(*snake, food); } int main() { srand(time(NULL)); Point head = {WIDTH / 2, HEIGHT / 2}; Node *snake = createSnake(head); Direction direction = RIGHT; Point food = generateFood(snake); int score = 0; while (1) { if (_kbhit()) { char ch = _getch(); if (ch == 'w' && direction != DOWN) { direction = UP; } else if (ch == 's' && direction != UP) { direction = DOWN; } else if (ch == 'a' && direction != RIGHT) { direction = LEFT; } else if (ch == 'd' && direction != LEFT) { direction = RIGHT; } } moveSnake(&snake, direction, food, &score); if (!isInMap(snake->data) || isCollided(snake)) { printf("Game Over! Score: %d\n", score); break; } Sleep(100); } return 0; } ``` 代码中使用了Windows系统的控制台函数和键盘输入函数,需要在Windows系统下运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闪耀大叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值