C语言实战系列一:经典贪食蛇

C语言学习必须实战,并且学完语法后就必须立即用实战来巩固。一般需要10来个比较复杂的程序才能掌握C语言。今天就教大家第一个小程序,贪食蛇。

首先上代码

一、代码

#include <stdio.h>
#include <stdlib.h>
#include <curses.h>
#include <unistd.h>
#include <time.h>

#define WIDTH 20
#define HEIGHT 10

int score = 0;
int gameover;
int x, y, fruitX, fruitY, flag;

int tailX[100], tailY[100];
int nTail;

void setup() {
    gameover = 0;

    // 初始化蛇的位置
    x = WIDTH / 2;
    y = HEIGHT / 2;

    // 初始化食物的位置
    fruitX = rand() % WIDTH;
    fruitY = rand() % HEIGHT;
}

void draw() {
    clear(); // 清屏

    for (int i = 0; i < WIDTH + 2; i++)
        printw("#");

    printw("
");

    for (int i = 0; i < HEIGHT; i++) {
        for (int j = 0; j < WIDTH; j++) {
            if (j == 0)
                printw("#"); // 左墙
            if (i == y && j == x)
                printw("O"); // 蛇头
            else if (i == fruitY && j == fruitX)
                printw("F"); // 食物
            else {
                int isTail = 0;
                for (int k = 0; k < nTail; k++) {
                    if (tailX[k] == j && tailY[k] == i) {
                        printw("o"); // 蛇身
                        isTail = 1;
                    }
                }
                if (!isTail)
                    printw(" ");
            }

            if (j == WIDTH - 1)
                printw("#"); // 右墙
        }
        printw("
");
    }

    for (int i = 0; i < WIDTH + 2; i++)
        printw("#");

    printw("
");
    printw("Score:%d", score);
    refresh(); // 刷新屏幕
}

void input() {
    int ch = getch(); // 获取键盘输入
    switch (ch) {
        case 'a':
            flag = 1;
            break;
        case 'd':
            flag = 2;
            break;
        case 'w':
            flag = 3;
            break;
        case 's':
            flag = 4;
            break;
        case 'x':
            gameover = 1;
            break;
    }
}

void algorithm() {
    usleep(100000); // 休眠,控制蛇的速度
    int prevX = tailX[0];
    int prevY = tailY[0];
    int prev2X, prev2Y;
    tailX[0] = x;
    tailY[0] = y;

    for (int i = 1; i < nTail; i++) {
        prev2X = tailX[i];
        prev2Y = tailY[i];
        tailX[i] = prevX;
        tailY[i] = prevY;
        prevX = prev2X;
        prevY = prev2Y;
    }

    switch (flag) {
        case 1:
            x--;
            break;
        case 2:
            x++;
            break;
        case 3:
            y--;
            break;
        case 4:
            y++;
            break;
        default:
            break;
    }

    // 判断是否碰到墙
    if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT)
        gameover = 1;

    // 判断是否吃到食物
    if (x == fruitX && y == fruitY) {
        score += 10;
        fruitX = rand() % WIDTH;
        fruitY = rand() % HEIGHT;
        nTail++;
    }
}

int main() {
    srand(time(NULL)); // 设置随机数种子
    initscr(); // 初始化 curses
    keypad(stdscr, TRUE); // 启用键盘输入
    nodelay(stdscr, TRUE); // 非阻塞输入
    noecho(); // 关闭回显

    setup();

    while (!gameover) {
        draw();
        input();
        algorithm();
    }

    endwin(); // 关闭 curses
    return 0;
}

二、编译

代码的运行平台是ubuntu,代码用到了curses库,需要提前安装:

 sudo apt-get install libncurses5-dev

然后用下面命令编译

gcc snake_game.c -o snake_game -lncurses

执行程序:

./snake_game

注意!如果速度过快,就会结束,直接跳出程序到控制台,如何修改速度下面会讲到。

三、编程指导

  1. 初始化 (setup 函数):

    • gameover: 游戏结束标志。
    • xy: 蛇头的初始坐标。
    • fruitXfruitY: 食物的初始坐标。
    • flag: 用于存储用户输入的方向。
    • score: 记录游戏得分。
    • nTail: 蛇身的长度。
    • tailXtailY: 用于存储蛇身各部分的坐标。
  2. 游戏主循环:

    while (!gameover) {
        draw();
        input();
        algorithm();
    }
    

    游戏在一个无限循环中进行,直到 gameover 变为真。在每次循环中,先绘制游戏画面,然后处理用户输入,最后执行游戏逻辑。

  3. 输入处理 (input 函数):

    • 使用 getch 函数获取键盘输入。
    • 根据用户输入的键值设置 flag 的值,表示蛇移动的方向。
    • 'a': 左,'d': 右,'w': 上,'s': 下,'x': 退出游戏。
  4. 游戏逻辑 (algorithm 函数):

    • 使用 usleep 函数添加一点延迟,以控制蛇的速度。(如果速度过快,游戏会很快结束,需要添加延时)
    • 更新蛇身的位置。
    • 根据用户输入的方向移动蛇头。
    • 判断是否碰到墙,如果是则游戏结束。
    • 判断是否吃到食物,如果是则增加得分,重新生成食物,增加蛇身的长度。
  5. 绘制 (draw 函数):

    • 使用 clear 函数清空屏幕。
    • 使用 printw 函数输出字符。
    • 使用 refresh 函数刷新屏幕。
    • 输出游戏画面,包括墙、蛇头、食物和蛇身。
    • 输出得分信息。
  6. 初始化和关闭 curses (initscrendwin 函数):

    • initscr: 初始化 curses 库。
    • keypad: 启用键盘输入。
    • nodelay: 设置非阻塞输入。
    • noecho: 关闭回显。
    • endwin: 关闭 curses 库。
  7. 编译和运行:

    • 使用 gcc 编译器编译程序,并链接 ncurses 库。
    • 运行生成的可执行文件。

四、继续完善

这段贪食蛇游戏代码已经基本完成了一个简单的实现,但仍然有一些方面可以改进和完善,具体包括:

  1. 边界检查:

    当蛇头移动到边界时,游戏会结束。可以考虑在边界处实现蛇穿越的效果,即从一侧穿越到另一侧。

  2. 游戏难度递增:

    目前游戏的难度保持不变,可以考虑逐渐增加蛇的移动速度或者加快食物的生成速度,使得游戏更具挑战性。

  3. 碰撞检测:

    目前只有边界碰撞检测和食物碰撞检测,可以考虑添加对蛇头与蛇身碰撞的检测,以防止游戏结束。

  4. 游戏结束提示:

    当游戏结束时,可以输出更友好的提示信息,告诉玩家是因为碰到墙还是碰到自己身体而导致游戏结束。

  5. 代码结构和模块化:

    将代码进一步模块化,使得各部分功能更清晰、易读、易于维护。可以考虑将一些功能性代码抽象成函数。

  6. 用户界面交互:

    添加开始界面和游戏结束后的界面,以提供更良好的用户体验。

  7. 更多游戏元素:

    考虑添加更多的游戏元素,如障碍物、特殊道具等,以增加游戏的趣味性和多样性。

这些功能给大家作为练习去实现。