经典复刻:C语言控制台贪吃蛇游戏代码解析与优化

开发背景

这个贪吃蛇游戏是使用C语言编写的,它采用了传统的控制台界面来展示游戏画面。游戏的目标是控制一条贪吃蛇在游戏区域内移动,吃掉随机生成的食物,并不断增长身体长度,直到撞到边界或自己的身体为止。代码中引入了区块链技术的概念,让贪吃蛇游戏变得更加智能、有趣和具有挑战性。

每个函数的功能介绍

  1. gotoxy(int x, int y)
    • 功能:该函数用于在控制台上设置光标位置。
    • 用途:在绘制游戏界面时,通过该函数来确定输出位置,以便画面的刷新和更新。
void gotoxy(int x, int y) {
    COORD coord;
    coord.X = x;
    coord.Y = y;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
  1. setTextColor(int color)
    • 功能:该函数用于设置控制台中输出文本的颜色。
    • 用途:通过设置文本颜色,使游戏画面更加丰富多彩,增加视觉体验。
void setTextColor(int color) {
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}
  1. clearInputBuffer():
    • 功能:该函数用于清空输入缓冲区。
    • 用途:避免由于输入过多导致缓冲区阻塞,确保程序能够正确获取用户输入。
void clearInputBuffer() {
    while (_kbhit()) {
        _getch();
    }
}
  1. init():
    • 功能:该函数用于初始化游戏的各种参数和数据结构。
    • 用途:在游戏开始前,对游戏进行初始化,包括贪吃蛇的起始位置、初始长度、食物位置、得分等。
void init() {
    gameover = false;
    direction = RIGHT;
    head = (struct Node*)malloc(sizeof(struct Node));
    // 省略部分初始化代码...
    score = 0;
    srand((unsigned int)time(NULL));
    foodX = rand() % WIDTH;
    foodY = rand() % HEIGHT;
    startNextRound = false;
}
  1. releaseSnake():
    • 功能:该函数用于释放贪吃蛇的内存空间。
    • 用途:游戏结束后,释放贪吃蛇所占用的内存,避免内存泄漏。
void releaseSnake() {
    while (head != NULL) {
        struct Node* temp = head;
        head = head->next;
        free(temp);
    }
}
  1. draw():
    • 功能:该函数用于绘制游戏画面,包括贪吃蛇、食物、得分等。
    • 用途:通过输出不同的字符和颜色,绘制贪吃蛇游戏的整体画面。
void draw() {
    // 设置控制台光标位置
    gotoxy(0, 0);

    // 画上边界
    // 省略部分绘制代码...

    // 显示得分和提示信息
    printf("得分: %d\n", score);
    printf("使用 W, A, S, D 控制移动\n");
    printf("按下空格键开始下一局\n");
}
  1. input():
    • 功能:该函数用于获取用户的输入,控制贪吃蛇的移动方向。
    • 用途:通过键盘输入,获取用户控制贪吃蛇的方向,并根据输入进行相应的操作。
void input() {
    if (_kbhit()) {
        int key = _getch();
        clearInputBuffer(); // 清空输入缓冲区
        switch (key) {
        case 'w':
            direction = UP;
            break;
        case 's':
            direction = DOWN;
            break;
        case 'a':
            direction = LEFT;
            break;
        case 'd':
            direction = RIGHT;
            break;
        case 'x':
            gameover = true;
            break;
        case ' ':
            if (gameover) {
                startNextRound = true;
            }
            break;
        default:
            break;
        }
    }
}
  1. move():

    • 功能:该函数用于处理贪吃蛇的移动逻辑,包括吃食物、增长长度和判断游戏结束等。
    • 用途:根据当前贪吃蛇的位置和用户的输入,更新贪吃蛇的位置和长度,并检查游戏是否结束。
void move() {
    // 移动蛇头
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->next = NULL;
    switch (direction) {
    case UP:
        newNode->x = head->x;
        newNode->y = (head->y - 1 + HEIGHT) % HEIGHT; // 从另一面出现
        break;
    case DOWN:
        newNode->x = head->x;
        newNode->y = (head->y + 1) % HEIGHT; // 从另一面出现
        break;
    case LEFT:
        newNode->x = (head->x - 1 + WIDTH) % WIDTH; // 从另一面出现
        newNode->y = head->y;
        break;
    case RIGHT:
        newNode->x = (head->x + 1) % WIDTH; // 从另一面出现
        newNode->y = head->y;
        break;
    default:
        break;
    }

    // 检查是否吃到食物
    if (newNode->x == foodX && newNode->y == foodY) {
        score++;
        foodX = rand() % WIDTH;
        foodY = rand() % HEIGHT;
    }
    else {
        // 删除蛇的尾部节点
        struct Node* temp = head;
        while (temp->next->next != NULL) {
            temp = temp->next;
        }
        free(temp->next);
        temp->next = NULL;
    }

    // 添加新节点到头部
    newNode->next = head;
    head = newNode;

    // 检查是否撞到自己的身体
    struct Node* current = head->next;
    while (current != NULL) {
        if (current->x == head->x && current->y == head->y) {
            gameover = true;
            break;
        }
        current = current->next;
    }
}

完整代码如下

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#include <conio.h>
#include <windows.h>

#define WIDTH 40
#define HEIGHT 20
#define INITIAL_LENGTH 3

enum Direction {
    UP,
    DOWN,
    LEFT,
    RIGHT
};

struct Node {
    int x;
    int y;
    struct Node* next;
};

enum Direction direction;
struct Node* head;
struct Node* tail;
int foodX, foodY;
int score;
bool gameover;
bool startNextRound; // 用于标记是否开始下一局游戏

void gotoxy(int x, int y) {
    COORD coord;
    coord.X = x;
    coord.Y = y;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

void setTextColor(int color) {
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}

void clearInputBuffer() {
    while (_kbhit()) {
        _getch();
    }
}

void init() {
    gameover = false;
    direction = RIGHT;
    head = (struct Node*)malloc(sizeof(struct Node));
    head->x = WIDTH / 2;
    head->y = HEIGHT / 2;
    head->next = NULL;
    tail = head;
    for (int i = 1; i < INITIAL_LENGTH; i++) {
        struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
        newNode->x = head->x - i;
        newNode->y = head->y;
        newNode->next = NULL;
        tail->next = newNode;
        tail = newNode;
    }
    score = 0;
    srand((unsigned int)time(NULL));
    foodX = rand() % WIDTH;
    foodY = rand() % HEIGHT;
    startNextRound = false;
}

void releaseSnake() {
    while (head != NULL) {
        struct Node* temp = head;
        head = head->next;
        free(temp);
    }
}

void draw() {
    // 设置控制台光标位置
    gotoxy(0, 0);

    // 画上边界
    for (int i = 0; i < WIDTH + 2; i++) {
        printf("=");
    }
    printf("\n");

    // 画中间部分和蛇身
    for (int i = 0; i < HEIGHT; i++) {
        printf("|");
        for (int j = 0; j < WIDTH; j++) {
            if (i == head->y && j == head->x) {
                setTextColor(12); // 颜色为12,红色
                printf("@"); // 蛇头
                setTextColor(15); // 颜色为15,白色
            } else if (i == foodY && j == foodX) {
                setTextColor(14); // 颜色为14,黄色
                printf("@"); // 食物
                setTextColor(15); // 颜色为15,白色
            } else {
                struct Node* current = head->next;
                bool printed = false;
                while (current != NULL) {
                    if (current->x == j && current->y == i) {
                        setTextColor(10); // 颜色为10,绿色
                        printf("#"); // 蛇身
                        setTextColor(15); // 颜色为15,白色
                        printed = true;
                        break;
                    }
                    current = current->next;
                }
                if (!printed) {
                    printf(" ");
                }
            }
        }
        printf("|\n");
    }

    // 画下边界
    for (int i = 0; i < WIDTH + 2; i++) {
        printf("=");
    }
    printf("\n");

    // 显示得分和提示信息
    printf("得分: %d\n", score);
    printf("使用 W, A, S, D 控制移动\n");
    printf("按下空格键开始下一局\n");
}

void input() {
    if (_kbhit()) {
        int key = _getch();
        clearInputBuffer(); // 清空输入缓冲区
        switch (key) {
        case 'w':
            direction = UP;
            break;
        case 's':
            direction = DOWN;
            break;
        case 'a':
            direction = LEFT;
            break;
        case 'd':
            direction = RIGHT;
            break;
        case 'x':
            gameover = true;
            break;
        case ' ':
            if (gameover) {
                startNextRound = true;
            }
            break;
        default:
            break;
        }
    }
}

void move() {
    // 移动蛇头
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->next = NULL;
    switch (direction) {
    case UP:
        newNode->x = head->x;
        newNode->y = (head->y - 1 + HEIGHT) % HEIGHT; // 从另一面出现
        break;
    case DOWN:
        newNode->x = head->x;
        newNode->y = (head->y + 1) % HEIGHT; // 从另一面出现
        break;
    case LEFT:
        newNode->x = (head->x - 1 + WIDTH) % WIDTH; // 从另一面出现
        newNode->y = head->y;
        break;
    case RIGHT:
        newNode->x = (head->x + 1) % WIDTH; // 从另一面出现
        newNode->y = head->y;
        break;
    default:
        break;
    }

    // 检查是否吃到食物
    if (newNode->x == foodX && newNode->y == foodY) {
        score++;
        foodX = rand() % WIDTH;
        foodY = rand() % HEIGHT;
    }
    else {
        // 删除蛇的尾部节点
        struct Node* temp = head;
        while (temp->next->next != NULL) {
            temp = temp->next;
        }
        free(temp->next);
        temp->next = NULL;
    }

    // 添加新节点到头部
    newNode->next = head;
    head = newNode;

    // 检查是否撞到自己的身体
    struct Node* current = head->next;
    while (current != NULL) {
        if (current->x == head->x && current->y == head->y) {
            gameover = true;
            break;
        }
        current = current->next;
    }
}

int main() {
    system("mode con cols=50 lines=30"); // 设置控制台窗口大小
    system("title 经典贪吃蛇游戏"); // 设置控制台窗口标题
    init();

    while (!gameover) {
        draw();
        input();
        if (gameover && !startNextRound) {
            setTextColor(12); // 颜色为12,红色
            gotoxy(WIDTH / 2 - 4, HEIGHT / 2);
            printf("游戏结束");
            setTextColor(15); // 颜色为15,白色
            gotoxy(WIDTH / 2 - 6, HEIGHT / 2 + 1);
            printf("最终得分:%d", score);
        }
        if (!gameover) {
            move();
            Sleep(100); // 控制游戏速度
        }
        if (startNextRound) {
            releaseSnake();
            init();
            gameover = false;
            startNextRound = false;
        }
    }

    setTextColor(15); // 颜色为15,白色
    gotoxy(WIDTH / 2 - 4, HEIGHT / 2 + 2);
    printf("按下任意键退出");
    _getch();

    releaseSnake();
    return 0;
}

二 技术总结

  • 在开发这个贪吃蛇游戏时,可能遇到的问题主要包括:
  • 实现游戏逻辑和画面的控制台显示:由于C语言的控制台操作相对简单,可能需要处理游戏画面的刷新和更新,以及用户输入的响应等问题。
  • 内存管理:贪吃蛇游戏涉及到动态内存分配(malloc()函数)和释放(free()函数),正确地进行内存管理是开发过程中需要注意的关键问题。
  • 处理用户输入:获取用户的输入并根据输入控制贪吃蛇的移动方向,需要考虑输入的合法性和及时响应用户操作的问题。
  • 游戏逻辑的实现:贪吃蛇游戏涉及到复杂的逻辑判断,如食物的生成、贪吃蛇的移动、长度的增长和游戏结束条件的判断等,需要仔细设计和测试逻辑。
  • 选择C语言进行开发主要有以下理由:
  • 性能要求:贪吃蛇游戏虽然相对简单,但需要快速响应用户的操作和刷新画面,要求较高的游戏开发性能。C语言是一种高性能的编程语言,适合处理游戏开发中的计算和数据操作。
    操作系统支持:C语言是一种通用的编程语言,在几乎所有主流操作系统上都有良好的支持。这使得开发的游戏能够在不同平台上运行。
  • 控制台编程:C语言相对于其他高级语言更容易实现控制台界面的操作,特别适合控制台游戏的开发。
  • 底层控制:C语言允许开发者直接操作内存和底层硬件,对于一些需要底层控制的游戏来说,是一种合适的选择。
  • 综上所述,C语言作为一种高性能、通用且可底层控制的编程语言,对于开发这个简单的贪吃蛇游戏来说是一个合理的选择。同时,引入区块链技术作为创新点,为传统游戏增添了新的玩法和可能性。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
用windows api 做的贪吃蛇 #include #include"resource.h" #include"Node.h" #include #include TCHAR szAppname[] = TEXT("Snack_eat"); #define SIDE (x_Client/80) #define x_Client 800 #define y_Client 800 #define X_MAX 800-20-SIDE //点x的范围 #define Y_MAX 800-60-SIDE //点y的范围 #define TIME_ID 1 #define SECOND 100 #define NUM_POINT 10 //点的总个数 #define ADD_SCORE 10 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwnd; //窗口句柄 MSG msg; //消息 WNDCLASS wndclass; //窗口类 HACCEL hAccel;//加速键句柄 wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口的水平和垂直尺寸被改变时,窗口被重绘 wndclass.lpfnWndProc = WndProc; //窗口过程为WndProc函数 wndclass.cbClsExtra = 0; //预留额外空间 wndclass.cbWndExtra = 0; //预留额外空间 wndclass.hInstance = hInstance; //应用程序的实例句柄,WinMain的第一个参数 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //设置图标 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //载入预定义的鼠标指针 wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //设置画刷 wndclass.lpszMenuName = szAppname; //设置菜单 wndclass.lpszClassName = szAppname; //设置窗口类的名字 if (!RegisterClass(&wndclass))//注册窗口类 { MessageBox(NULL, TEXT("这个程序需要windows NT!"), szAppname, MB_ICONERROR); return 0; } hwnd = CreateWindow(szAppname, TEXT("Snack_eat"),//CreateWindow函数调用时,WndProc将受到WM_CREATE WS_OVERLAPPEDWINDOW&~WS_THICKFRAME& ~WS_MAXIMIZEBOX,//普通的层叠窗口&禁止改变大小&禁止最大化 CW_USEDEFAULT, //初始x坐标(默认) CW_USEDEFAULT, //初始y坐标 x_Client, //初始x方向尺寸 770 y_Client, //初始y方向尺寸 750 NULL, //父窗口句柄 NULL, //窗口菜单句柄 hInstance, //程序实例句柄 WinMain函数中第二个参数 NULL); //创建参数 ShowWindow(hwnd, iCmdShow);//显示窗口,iCmdShow是WinMain的第四个参数,决定窗口在屏幕中的初始化显示形式,例:SW_SHOWNORMAL表示正常显示 UpdateWindow(hwnd);//使窗口客户区重绘,通过向WndProc发送一条WM_PAINT消息而完成的 hAccel = LoadAccelerators(hInstance, szAppname);//加载加速键 while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(hwnd, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }/* while (GetMessage(&msg, NULL, 0, 0))//GetMessage函数从消息队列中得到消息,填充msg。如果msg.message等于WM_QUIT,返回0,否则返回非0 { TranslateMessage(&msg);//将msg返回给windows已进行某些键盘消息的转换 DispatchMessage(&msg);//将msg再次返回给windows }*/ return msg.wParam;//msg.wParam是PostQuitMessage函数的参数值,通常是0 } ...

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不一样的老墨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值