原文链接:C语言 贪吃蛇游戏
文章目录
- 一、说明
- 二、效果
- 2.1 欢迎界面
- 2.2 游戏规则
- 2.3 得分排行
- 2.4 退出游戏
- 2.5 游戏界面
- 2.6 游戏结束
 
- 三、源码
- 3.1 cmd.h
- 3.2 cmd.c
- 3.3 io.h
- 3.4 io.c
- 3.5 model.h
- 3.6 service.h
- 3.7 service.c
- 3.8 ui.h
- 3.9 ui.c
- 3.10 utils.h
- 3.11 utils.c
- 3.12 main.c
 
一、说明
笔者使用 C 语言实现经典贪吃蛇游戏,其中开发环境为 Windows 平台下的 VisualStudio2019
本文在 原文 的基础上将原文源码进行模块化拆分,以面向过程的自顶向下模块化思想进行编程,代码可读性高、系统健壮性强、游戏界面友好美观,功能做出适当调整并修复了一系列已知问题
二、效果
2.1 欢迎界面

2.2 游戏规则

2.3 得分排行

2.4 退出游戏

2.5 游戏界面

2.6 游戏结束

三、源码
3.1 cmd.h
#pragma once
// 设置光标
void setPox(int x, int y);
// 设置文本颜色
void setTextColor(unsigned short color);
3.2 cmd.c
#include<windows.h>
// 设置光标
void setPox(int x, int y) {
    COORD pox = {x, y};
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(hOut, pox);
}
// 设置文本颜色
void setTextColor(unsigned short color) {
    HANDLE hCon = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hCon, color);
}
3.3 io.h
#pragma once
// 游戏排行榜,读取游戏数据
void rankingList();
// 保存成绩,游戏存档
void saveGrade(int score);
3.4 io.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<time.h>
#include "model.h"
#include "utils.h"
// 游戏排行榜,读取游戏数据
void rankingList() {
    system("cls");
    // 打开游戏排行榜文件
    FILE *fp = fopen("rank.txt", "rb");
    if (fp == NULL) {
        setPox(56, 12);
        printf("游戏排行榜文件不存在");
        setPox(0, 0);
        return;
    }
    rewind(fp);
    // 读取文件中的游戏数据,最多读取 1000 条游戏记录
    record gameRecord[1000];
    int i = 0;
    // feof 检查文件是否结束,遇到结束符,返回非零
    while (!feof(fp)) {
        fread(&gameRecord[i], sizeof(struct record), 1, fp);
        i++;
    }
    // 按游戏得分排序
    qsort(gameRecord, i - 1, sizeof(record), compare);
    // 输出得分最高的 10 次游戏记录
    i = i > 10 ? 10 : i;
    // 输出游戏排行榜信息
    setPox(55, 3);
    setTextColor(12);
    printf("排行榜");
    setPox(42, 5);
    setTextColor(14);
    printf("得分\t\t\t时间\n");
    setTextColor(15);
    int j = 0;
    for (; j < i - 1; j++) {
        setPox(43, 7 + j * 2);
        printf("%d\t\t", gameRecord[j].grade);
        printf("%d/%02d/%02d ", gameRecord[j].year + 1900, gameRecord[j].mon + 1, gameRecord[j].day);
        printf("%02d:%02d:%02d\n", gameRecord[j].hour, gameRecord[j].min, gameRecord[j].sec);
    }
    setPox(43, 7 + j * 2);
    setTextColor(1);
    printf("注:按任意键继续···");
    fclose(fp);
}
// 保存成绩,游戏存档
void saveGrade(int score) {
    // 获取系统时间
    time_t timestamp;
    time(×tamp);
    struct tm *ti = localtime(×tamp);
    // 为当前游戏数据分配空间
    record *gameRecord = (record *) malloc(sizeof(record));
    // 保存年月日时分秒以及分数
    gameRecord->year = ti->tm_year;
    gameRecord->mon = ti->tm_mon;
    gameRecord->day = ti->tm_mday;
    gameRecord->hour = ti->tm_hour;
    gameRecord->min = ti->tm_min;
    gameRecord->sec = ti->tm_sec;
    gameRecord->grade = score;
    // 打开文件并追加写入本次游戏数据
    FILE *fp = fopen("rank.txt", "ab");
    if (fp == NULL)
        fp = fopen("rank.txt", "wb");
    fwrite(gameRecord, sizeof(record), 1, fp);
    fclose(fp);
    free(gameRecord);
}
3.5 model.h
#pragma once
// 蛇
typedef struct snake {
    int x;
    int y;
    struct snake *next;
} snake;
// 游戏记录
typedef struct record {
    int grade;
    int year;
    int mon;
    int day;
    int hour;
    int min;
    int sec;
} record;
// 游戏数据
typedef struct data {
    // 分数
    int score;
    // 速度,值越小蛇的速度越快
    int speed;
    // 速度等级,值越大小蛇速度越快
    int speedLevel;
    // 每个食物分数,加速一次 foodFraction 翻倍,减速一次 foodFraction 减半
    int foodFraction;
    // 速度变化前吃到的食物数,用于判断是否开启自动加速
    int eatenFoods;
} data;
3.6 service.h
#pragma once
#include "model.h"
// 初始化蛇
snake *initSnake();
// 随机食物
snake *randomFood(snake *q);
// 打印蛇身
void snakeBody(snake *p, int speed);
// 边界碰撞判定
int collision(snake *q);
// 释放蛇身
void destroy(snake *p);
// 游戏暂停
void suspendGame(snake *q);
// 蛇身传递  
void snakeInherit(snake *p);
// 吃到食物
snake *foodInMouth(snake *snakeHead, snake *food, data *curGameData);
// 自动加速
void autoAccelerate(data *curGameData, int isAutoAccelerate);
// 开始游戏
void startGame();
3.7 service.c
#define _CRT_SECURE_NO_WARNINGS
#include<windows.h>
#include "model.h"
// 初始化蛇
snake *initSnake() {
    snake *q = (snake *) malloc(sizeof(snake));
    q->next = NULL;
    for (int i = 6; i < 19; i = i + 2) {
        snake *tmp = (snake *) malloc(sizeof(snake));
        tmp->x = i;
        tmp->y = 5;
        tmp->next = q->next;
        q->next = tmp;
    }
    return q;
}
// 随机食物
snake *randomFood(snake *q) {
    snake *p, *k;
    k = (snake *) malloc(sizeof(snake));
    k->next = NULL;
    gotoHere:
    p = q->next;
    srand((unsigned) time(NULL));
    // 确保食物显示在游戏地图范围内
    while ((k->x = rand() % 57 + 4) % 2 != 0) { ;
    }
    k->y = rand() % 24 + 3;
    while (p != NULL) {
        // 如果新食物与蛇身重合,则重新生成
        if ((k->x == p->x && k->y == p->y))
            goto gotoHere;
        p = p->next;
    }
    setTextColor(12);
    setPox(k->x, k->y);
    printf("●");
    return k;
}
// 打印蛇身
void snakeBody(snake *p, int speed) {
    snake *r, *k = p->next;
    setTextColor(14);
    while (p->next != NULL) {
        r = p->next;
        p = r;
        setPox(r->x, r->y);
        printf("★");
    }
    if (k->x != p->x || k->y != p->y) {
        // 覆盖尾迹
        setPox(p->x, p->y);
        setTextColor(3);
        printf("■");
    }
    setPox(0, 0);
    Sleep(speed);
}
// 边界碰撞判定
int collision(snake *q) {
    snake *p = q->next, *r = p->next;
    // 撞墙
    if (p->x == 2 || p->x == 62 || p->y == 1 || p->y == 27) {
        return 1;
    }
    while (r->next != NULL) {
        // 咬到自己
        if (p->x == r->x && p->y == r->y)
            return 1;
        r = r->next;
    }
    return 0;
}
// 释放蛇身
void destroy(snake *p) {
    snake *q = p, *r;
    while (q->next != NULL) {
        r = q;
        q = q->next;
        free(r);
    }
    free(q);
}
// 游戏暂停
void suspendGame(snake *q) {
    setPox(0, 0);
    while (1) {
        // kbhit函数,非阻塞地响应键盘输入事件
        if (kbhit() && getch() == ' ')
            return;
    }
}
// 蛇身传递  
void snakeInherit(snake *p) {
    // p 为第一个结点,即蛇首
    snake *r = p->next;
    if (r->next != NULL)
        snakeInherit(r);
    // 把前一个结点的坐标传递给后一个结点(跟随)
    r->x = p->x;
    r->y = p->y;
}
// 吃到食物
snake *foodInMouth(snake *snakeHead, snake *food, data *curGameData) {
    // 蛇身长度加 1
    food->next = snakeHead->next;
    snakeHead->next = food;
    // 新的随机食物
    food = randomFood(snakeHead);
    // 更新分数
    curGameData->score += curGameData->foodFraction;
    scoreHint(curGameData->score);
    // 吃到的食物数加 1
    curGameData->eatenFoods++;
    return food;
}
// 自动加速
void autoAccelerate(data *curGameData, int isAutoAccelerate) {
    if (curGameData->eatenFoods % 3 == 0 && isAutoAccelerate == 1) {
        // 速度减半、速度等级增一、每个食物分数翻倍
        curGameData->speed /= 2;
        curGameData->speedLevel++;
        curGameData->foodFraction *= 2;
        curGameData->eatenFoods = 0;
        speedHint(curGameData->speedLevel);
    }
}
// 开始游戏
void startGame() {
    // 初始化本次游戏数据
    data *curGameData = (data *) malloc(sizeof(data));
    curGameData->score = 0;
    curGameData->speed = 300;
    curGameData->speedLevel = 1;
    curGameData->foodFraction = 2;
    curGameData->eatenFoods = 0;
    // 是否开启自动加速:[0]不开启     [1]开启
    int isAutoAccelerate = 1;
    system("cls");
    // 游戏地图
    gameMap();
    // 初始速度展示
    speedHint(curGameData->speedLevel);
    // 初始分数展示
    scoreHint(curGameData->score);
    // 初始化蛇,初始长度为 6 个节点
    snake *q = initSnake();
    // 初始化随机食物
    snake *food = randomFood(q);
    // 当前敲下的按键,默认为 d 即蛇往右移动
    char hitKey = 'd';
    // 上一次敲下的按键
    char preKey = 'd';
    while (1) {
        // 打印蛇身
        snakeBody(q, curGameData->speed);
        // 撞墙或咬到自己,游戏结束
        if (collision(q)) {
            // 游戏存档
            saveGrade(curGameData->score);
            // 销毁蛇身结点,释放存储空间
            destroy(q);
            // 结束游戏
            gameOver(curGameData->score);
            break;
        }
        // 键盘监听,kbhit函数,非阻塞地响应键盘输入事件
        if (kbhit()) {
            hitKey = getch();
        }
        // 敲击空格,暂停游戏
        if (hitKey == ' ') {
            suspendGame(q);
            // 恢复上一次的操作,继续游戏后按原方向爬行
            hitKey = preKey;
            continue;
        }
        // 兼容大写字母
        hitKey = hitKey < 91 ? hitKey + 32 : hitKey;
        // 蛇首撞击蛇身,游戏结束(上至下、下至上、左至右、右至左)
        if ((hitKey == 'd' && preKey == 'a') || (hitKey == 's' && preKey == 'w') || (hitKey == 'a' && preKey == 'd') ||
            (hitKey == 'w' && preKey == 's')) {
            saveGrade(curGameData->score);
            destroy(q);
            gameOver(curGameData->score);
            break;
        }
        // 按 q 加速
        if (hitKey == 'q') {
            // 速度减半、速度等级增一、每个食物分数翻倍、不再自动加速
            curGameData->speed /= 2;
            curGameData->speedLevel++;
            curGameData->foodFraction *= 2;
            curGameData->eatenFoods = 0;
            isAutoAccelerate = 0;
            speedHint(curGameData->speedLevel);
            // 恢复上一次的操作,加速后按原方向爬行
            hitKey = preKey;
            continue;
        }
        // 按 e 减速
        if (hitKey == 'e') {
            // 速度翻倍、速度等级减一、每个食物分数减半、不再自动加速
            curGameData->speed *= 2;
            curGameData->speedLevel--;
            curGameData->foodFraction /= 2;
            curGameData->eatenFoods = 0;
            isAutoAccelerate = 0;
            speedHint(curGameData->speedLevel);
            // 恢复上一次的操作,减速后按原方向爬行
            hitKey = preKey;
            continue;
        }
        // 上
        if (hitKey == 'w') {
            // 吃到食物,蛇身长度加 1,创建新食物,更新分数
            if (q->next->x == food->x && q->next->y - 1 == food->y) {
                food = foodInMouth(q, food, curGameData);
                autoAccelerate(curGameData, isAutoAccelerate);
            } else {
                // 未吃到食物,传递上一次的舍身
                snakeInherit(q->next);
                q->next->y -= 1;
            }
        }
        // 下
        if (hitKey == 's') {
            if (q->next->x == food->x && q->next->y + 1 == food->y) {
                food = foodInMouth(q, food, curGameData);
                autoAccelerate(curGameData, isAutoAccelerate);
            } else {
                snakeInherit(q->next);
                q->next->y += 1;
            }
        }
        // 左
        if (hitKey == 'a') {
            if (q->next->x - 2 == food->x && q->next->y == food->y) {
                food = foodInMouth(q, food, curGameData);
                autoAccelerate(curGameData, isAutoAccelerate);
            } else {
                snakeInherit(q->next);
                q->next->x -= 2;
            }
        }
        // 右
        if (hitKey == 'd') {
            if (q->next->x + 2 == food->x && q->next->y == food->y) {
                food = foodInMouth(q, food, curGameData);
                autoAccelerate(curGameData, isAutoAccelerate);
            } else {
                snakeInherit(q->next);
                q->next->x += 2;
            }
        }
        // 记录上一次的操作
        preKey = hitKey;
    }
}
3.8 ui.h
#pragma once
// 界面边框
void frame(int type);
// 欢迎页
void welcomePage();
// 游戏规则
void gameRules();
// 游戏结束
void gameOver(int score);
// 游戏地图
void gameMap();
// 速度提示
void speedHint(int speedLevel);
// 输出分数
void scoreHint(int score);
3.9 ui.c
// 界面边框
void frame(int type) {
    // 上边框
    setPox(17, 5);
    setTextColor(11);
    printf("⊙--------------------------");
    setTextColor(14);
    printf("oOOo");
    setTextColor(11);
    printf("----------");
    setTextColor(14);
    printf("(_)");
    setTextColor(11);
    printf("----------");
    setTextColor(14);
    printf("oOOo");
    setTextColor(11);
    printf("--------------------------⊙");
    // 左右竖边框
    for (int i = 6; i <= 19; i++) {
        setPox(17, i);
        printf("§");
        setPox(102, i);
        printf("§");
    }
    // 下边框
    setPox(17, 20);
    printf("⊙---------------------------------------");
    setTextColor(14);
    printf("☆☆☆");
    setTextColor(11);
    printf("--------------------------------------⊙");
    setPox(53, 23);
    printf("∵ˇˇˇˇˇˇˇ∵");
    setPox(53, 26);
    printf("∴^^^^^^^∴");
    // 启动页面字符图案
    if (type == 0) {
        setPox(57, 2);
        setTextColor(6);
        printf("\\\\\\|///");
        setPox(54, 3);
        printf("\\\\");
        setPox(58, 3);
        setTextColor(15);
        printf(".-.-");
        setPox(65, 3);
        setTextColor(6);
        printf("//");
        setPox(55, 4);
        setTextColor(14);
        printf("(");
        setPox(58, 4);
        setTextColor(15);
        printf(".@.@");
        setPox(65, 4);
        setTextColor(14);
        printf(")");
    } else {
        // 游戏结束字符图案
        setPox(57, 1);
        setTextColor(6);
        printf("∧    ∧");
        setPox(55, 2);
        printf(" /  \\  /  \\");
        setPox(54, 3);
        printf("( ︹ ˇ ︹ )");
        setPox(54, 4);
        printf("く ");
        setTextColor(15);
        printf("⊙    ⊙");
        setTextColor(14);
        printf(" / ");
        setPox(55, 5);
        printf("く   い   /");
        setPox(57, 6);
        printf("く 々 √");
        setPox(60, 7);
        printf("ˇ");
    }
}
// 欢迎页
void welcomePage() {
    system("cls");
    setPox(53, 8);
    setTextColor(14);
    printf("贪 吃 蛇 大 作 战");
    setPox(26, 14);
    printf("1.开始游戏");
    setPox(46, 14);
    printf("2.游戏规则");
    setPox(66, 14);
    printf("3.得分排行");
    setPox(86, 14);
    printf("4.退出游戏");
    // 绘制界面边框
    frame(0);
    setPox(56, 24);
    setTextColor(14);
    printf("前往:");
}
// 游戏规则
void gameRules() {
    system("cls");
    setPox(55, 5);
    printf("游戏规则");
    setTextColor(12);
    setPox(34, 8);
    printf("1. 'W''S''A''D' 控制上、下、左、右方向,空格键控制游戏暂停与继续");
    setPox(34, 10);
    printf("2. 按 Q 键可加速,按 E 键可减速,速度越快,单个食物分数越高");
    setPox(34, 12);
    printf("3. 在没有人为加速或减速的情况下,每吃到 3 个食物将会进行一次自动加速");
    setPox(34, 14);
    printf("4. 初始化每个食物 2 分,每加速一次单个食物分数翻倍,减速一次分数减半");
    setPox(34, 16);
    printf("5. 初始化小蛇长度为 6,每吃到一个食物长度就会加 1,分数相应地增加");
    setPox(34, 18);
    printf("6. 小蛇的初始速度为 300MS/格,速度等级为 1,等级值越高速度越快");
    setPox(34, 20);
    printf("7. 当蛇首撞墙或咬到蛇身时游戏结束");
    setPox(34, 22);
    printf("8. 以上按键皆不区分大小写");
    setPox(34, 24);
    setTextColor(1);
    printf("注:按任意键继续···");
}
// 游戏结束
void gameOver(int score) {
    int choice = 1;
    gotoHere:
    system("cls");
    setTextColor(12);
    setPox(45, 8);
    printf("游戏结束,蛇首撞墙或咬到蛇身\n");
    setPox(58, 12);
    setTextColor(14);
    printf("得分:%d", score);
    setTextColor(14);
    setPox(40, 16);
    printf("1.重新开始");
    setPox(56, 16);
    printf("2.返回主页");
    setPox(70, 16);
    printf("3.退出游戏");
    // 绘制界面边框
    frame(1);
    setPox(56, 24);
    printf("前往:");
    scanf("%d", &choice);
    switch (choice) {
        case 1:
            system("cls");
            startGame();
            break;
        case 2:
            return;
        case 3:
            system("cls");
            setPox(50, 15);
            printf("游戏结束,欢迎再次登录");
            setPox(0, 0);
            exit(0);
        default:
            setPox(0, 0);
            printf("您的输入有误,请重新输入!按任意键继续···");
            getch();
            goto gotoHere;
    }
}
// 游戏地图
void gameMap() {
    setTextColor(11);
    // 游戏界面边框:菱形边界,方块背景
    for (int i = 2; i < 27; i++) {
        // 左边框
        setPox(2, i);
        printf("◆");
        // 方块背景
        setTextColor(3);
        for (int j = 0; j < 29; j++)
            printf("■");
        // 右边框
        setTextColor(11);
        printf("◆");
    }
    // 上边框
    setPox(2, 1);
    for (int i = 0; i < 31; i++) {
        printf("◆");
    }
    // 下边框
    setPox(2, 27);
    for (int i = 0; i < 31; i++) {
        printf("◆");
    }
    // 游戏界面与游戏提示间隔框
    setTextColor(10);
    for (int i = 0; i < 30; i++) {
        setPox(70, i);
        printf("§");
    }
    // 得分提示边框
    setTextColor(6);
    setPox(82, 4);
    printf("∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞");
    setPox(82, 6);
    printf("∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞");
    setPox(82, 5);
    printf("φ");
    setPox(110, 5);
    printf("φ");
    // 操作提示
    setTextColor(12);
    setPox(94, 9);
    printf("操作提示");
    setPox(95, 11);
    printf("上:W");
    setPox(95, 12);
    printf("下:S");
    setPox(95, 13);
    printf("左:A");
    setPox(95, 14);
    printf("右:D");
    setPox(90, 16);
    printf("暂停/继续: 空格");
    // 速度提示框
    setTextColor(14);
    setPox(82, 19);
    printf("∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞");
    setPox(82, 21);
    printf("∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞");
    setPox(81, 20);
    printf("φ");
    setPox(111, 20);
    printf("φ");
    setPox(81, 23);
    printf("注:按 Q 键可加速,按 E 键可减速");
}
// 速度提示
void speedHint(int speedLevel) {
    setTextColor(11);
    setPox(97, 20);
    printf("%d", speedLevel);
}
// 输出分数
void scoreHint(int score) {
    setPox(97, 5);
    setTextColor(13);
    printf("%d", score);
}
3.10 utils.h
#pragma once
// 比较两个元素
int compare(const void *a, const void *b);
3.11 utils.c
// 比较两个元素
int compare(const void *a, const void *b) {
    return (*(int *) b - *(int *) a);
}
3.12 main.c
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include "model.h"
#include "cmd.h"
#include "utils.h"
#include "ui.h"
#include "io.h"
#include "service.h"
/*
 * 程序入口
 */
int main() {
    int choice;
    gotoHere:
    // 启动页面
    welcomePage();
    scanf("%d", &choice);
    switch (choice) {
        case 1:
            system("cls");
            startGame();
            goto gotoHere;
        case 2:
            gameRules();
            getch();
            goto gotoHere;
        case 3:
            rankingList();
            getch();
            goto gotoHere;
        case 4:
            system("cls");
            setPox(50, 15);
            printf("游戏结束,欢迎再次登录");
            setPox(0, 0);
            exit(0);
        default:
            setPox(0, 0);
            printf("您的输入有误,请重新输入!按任意键继续···");
            getch();
            goto gotoHere;
    }
    return 0;
}


















