C 语言实现经典贪吃蛇游戏

Posted Spring-_-Bear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C 语言实现经典贪吃蛇游戏相关的知识,希望对你有一定的参考价值。

原文链接: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(&timestamp);
    struct tm *ti = localtime(&timestamp);
    // 为当前游戏数据分配空间
    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以上是关于C 语言实现经典贪吃蛇游戏的主要内容,如果未能解决你的问题,请参考以下文章

贪吃蛇(C语言实现)

贪吃蛇二代 —— 穿墙版(C语言实现)

贪吃蛇小游戏程序(C语言)

c语言 贪吃蛇 程序

C语言课程设计,贪吃蛇应该怎么做?

C语言实现《贪吃蛇》小游戏,代码分享+思路注释