C语言 贪吃蛇小游戏的实现

引言

贪吃蛇是一款经典的小游戏,简单有趣,适合初学者用来练习编程技能。本文将详细解析一个用 C 语言实现的贪吃蛇游戏代码,代码主要包含三个文件:test.cpp、snake.h 和 snake.cpp。

代码结构概述

  • test.cpp :主要负责游戏的测试逻辑,包含 test 函数和 main 函数。
  • snake.h:头文件,包含必要的头文件引用、宏定义、类型声明和函数声明。
  • snake.cpp :实现了 snake.h 中声明的函数,包含游戏的初始化、运行、结束等功能。

代码详细解析

snake.h 文件

cpp 复制代码
#pragma once

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

#define POS_X 24
#define POS_Y 5
  • #pragma once 确保头文件只被包含一次,防止重复定义。

  • 引入了必要的头文件,用于输入输出、系统操作、随机数生成等。

  • 定义了宏 POS_XPOS_Y,用于指定蛇的初始位置。

    cpp 复制代码
    // 蛇的方向
    enum DIRECTION
    {
        UP,    // 上
        DOWN,  // 下
        LEFT,  // 左
        RIGHT  // 右
    };
    
    // 蛇的状态
    enum GAME_STATUS
    {
        OK,             // 正常
        KILL_BY_WALL,   // 撞墙
        KILL_BY_SELF,   // 撞到自己
        END_NORMAL      // 正常退出
    };
  • 定义了两个枚举类型 DIRECTIONGAME_STATUS,分别表示蛇的移动方向和游戏状态。

    cpp 复制代码
    // 蛇身的节点声明
    typedef struct SnakeNode
    {
        // 坐标
        int x;
        int y;
        // 指针
        struct SnakeNode* next;
    } SnakeNode, *pSnakeNode;
    
    // 贪吃蛇
    typedef struct Snake
    {
        pSnakeNode _pSnake;    // 指向蛇头的指针
        pSnakeNode _pFood;     // 指向食物的指针
        enum DIRECTION dir;    // 蛇的方向
        enum GAME_STATUS status; // 游戏的状态
        int food_weight;       // 一个食物的分数
        int score;             // 总成绩
        int sleep_time;        // 休息时间,时间越短,蛇的速度越快
    } Snake, *pSnake;
  • 定义了蛇身节点结构体 SnakeNode 和贪吃蛇结构体 Snake,使用 typedef 简化类型名。

    cpp 复制代码
    //函数的声明
    
    //游戏的初始化
    void GameStart(pSnake ps);
    
    //获得坐标
    void SetPos(short x, short y);
    
    //欢迎界面
    void WelcomeToGame();
    
    //绘制地图
    void CreateMap();
    
    //初始化蛇
    void InitSnake(pSnake ps);
    
    //创造食物
    void CreateFood(pSnake ps);
    
    //运行游戏
    void GameRun(pSnake ps);
    
    //蛇的移动
    void SnakeMove(pSnake ps);
    
    //判断下一个节点处是否为食物
    int NextIsFood(pSnakeNode pn,pSnake ps);
    
    //下一个节点处是食物,吃掉食物
    void EatFood(pSnakeNode pn,pSnake ps);
    
    //下一个节点处不是食物
    void NoFood(pSnakeNode pn,pSnake ps);
    
    //检测蛇是否撞墙
    void KillByWall(pSnake ps);
    
    //检测蛇是否撞到自己
    void KillBySelf(pSnake ps);
    
    //游戏善后的工作
    void GameEnd(pSnake ps);
  • 声明了游戏中使用的所有函数,方便在 snake.cpp 中实现。

test.cpp 文件

cpp 复制代码
#include "snake.h"

// 完成的是游戏的测试逻辑
void test()
{
    int ch = 0;
    do
    {
        system("cls");
        // 创建贪吃蛇
        Snake snake = { 0 };
        // 初始化游戏
        // 1. 设置窗口大小,标题以及光标隐藏
        // 2. 打印欢迎界面,功能介绍
        // 3. 绘制地图
        // 4. 创建蛇
        // 5. 创建食物
        GameStart(&snake);
        // 运行游戏
        GameRun(&snake);
        // 结束游戏 - 善后工作
        GameEnd(&snake);
        SetPos(20, 20);
        printf("再来一局吗(Y/N):");
        ch = getchar();
        while (getchar() != '\n');
    } while (ch == 'Y' || ch == 'y');
    SetPos(0, 27);
}

int main()
{
    srand((unsigned int)time(NULL));
    test();
    return 0;
}
  • test 函数是游戏的主循环,负责初始化游戏、运行游戏、结束游戏,并询问玩家是否再来一局。

  • main 函数初始化随机数种子,调用 test 函数开始游戏。

    snake.cpp 文件

    基本功能函数
    cpp 复制代码
    // 获得坐标
    void SetPos(short x, short y)
    {
        // 获得标准输出设备的句柄
        HANDLE houtput = NULL;
        houtput = GetStdHandle(STD_OUTPUT_HANDLE);
    
        // 定位光标的位置
        COORD pos = { x, y };
        SetConsoleCursorPosition(houtput, pos);
  • SetPos 函数用于设置控制台光标的位置,方便在指定位置输出字符。

    cpp 复制代码
    // 欢迎界面的打印
    void WelcomeToGame()
    {
        SetPos(40, 14);
        printf("欢迎来到贪吃蛇小游戏\n");
        SetPos(38, 20);
        system("pause");
        system("cls");
        SetPos(30, 14);
        printf("用 ↑, ↓, ←, → 来控制贪吃蛇的方向,按F3加速,F4减速\n");
        SetPos(30, 15);
        printf("加速能够得到更高的分数\n");
        SetPos(38, 20);
        system("pause");
        system("cls");
    }
  • WelcomeToGame 函数打印欢迎界面和游戏说明,等待用户按下任意键继续。

    cpp 复制代码
    // 绘制地图
    void CreateMap()
    {
        int i = 0;
        // 上
        for(i = 0; i < 29 ; i++)
        {
        printf("🤣");
        }
        // 下
        for(i = 0; i < 29; i++)
        {
        SetPos(i, 26);
        printf("🤣");
        }
        // 左
        for (i = 1; i <= 25; i++)
        {
            SetPos(0, i);
            printf("🤣");
        }
        // 右
        for (i = 1; i <= 25; i++)
        {
            SetPos(56, i);
            printf("🤣");
        }
    }
  • CreateMap 函数绘制游戏地图,使用字符 🤣 表示墙壁。

蛇的初始化和移动
cpp 复制代码
void InitSnake(pSnake ps)
{
    int i = 0;
    pSnakeNode cur = NULL;
    for (i = 0; i < 5; i++)
    {
        cur = (pSnakeNode)malloc(sizeof(SnakeNode));
        if (cur == NULL)
        {
            perror("malloc fail!");
            exit(1);
        }
        cur->next = NULL;
        cur->x = POS_X + 2 * i;
        cur->y = POS_Y;
        // 头插法插入链表
        if (ps->_pSnake == NULL) // 空
        {
            ps->_pSnake = cur;
        }
        else // 非空
        {
            cur->next = ps->_pSnake;
            ps->_pSnake = cur;
        }
    }
    cur = ps->_pSnake;
    while (cur)
    {
        SetPos(cur->x, cur->y);
        printf("😋");
        cur = cur->next;
    }

    // 设置贪吃蛇的属性
    ps->dir = RIGHT;
    ps->score = 0;
    ps->food_weight = 10;
    ps->sleep_time = 200; // 单位是毫秒
    ps->status = OK;
}
  • InitSnake 函数初始化蛇,使用头插法创建一个长度为 5 的蛇链表,并设置蛇的初始方向、分数、食物权重和速度。

    cpp 复制代码
    // 创造食物
    void CreateFood(pSnake ps)
    {
        int x = 0;
        int y = 0;
        // 生成的 x 是二的倍数
    again:
        do
        {
            x = rand() % 53 + 2;
            y = rand() % 25 + 1;
        } while (x % 2 != 0);
        // 蛇身不能与食物冲突
        pSnakeNode cur = ps->_pSnake;
        while (cur)
        {
            if (x == cur->x && y == cur->y)
            {
                goto again;
            }
            cur = cur->next;
        }
        // 创建食物节点
        pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
        if (pFood == NULL)
        {
            perror("malloc fail!");
            exit(1);
        }
        pFood->x = x;
        pFood->y = y;
        pFood->next = NULL;
        SetPos(x, y);
        printf("🍔");
        ps->_pFood = pFood;
    }
  • CreateFood 函数随机生成食物的位置,确保食物不会出现在蛇身上,并在指定位置打印食物。

    cpp 复制代码
    // 初始化游戏
    void GameStart(pSnake ps)
    {
        // 1. 设置窗口大小,标题以及光标隐藏
        system("chcp 65001");
        system("mode con cols=100 lines=30");
        system("title \xcc\xb0 \xb3\xd4 \xc9\xdf");
        HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
        // 隐藏光标操作
        _CONSOLE_CURSOR_INFO CursorInfo;
        GetConsoleCursorInfo(houtput, &CursorInfo); // 获取控制台光标信息
        CursorInfo.bVisible = false; // 隐藏控制台光标
        SetConsoleCursorInfo(houtput, &CursorInfo); // 设置控制台光标状态
    
        // 2. 打印欢迎界面,功能介绍
        WelcomeToGame();
        // 3. 绘制地图
        CreateMap();
        // 4. 创建蛇
        InitSnake(ps);
        // 5. 创建食物
        CreateFood(ps);
    }
  • GameStart 函数完成游戏的初始化工作,包括设置窗口大小、标题、隐藏光标,打印欢迎界面,绘制地图,创建蛇和食物。

游戏运行和结束
cpp 复制代码
// 运行游戏
void GameRun(pSnake ps)
{
    // 打印帮助信息
    PrintHelpInfo();
    do
    {
        // 打印总分数和食物的总分值
        SetPos(64, 8);
        printf("总分数:%d", ps->score);
        SetPos(64, 9);
        printf("当前食物分数:%2d", ps->food_weight);
        // 检测按键情况
        if (KEY_PRESS(VK_UP) && ps->dir != DOWN)
        {
            ps->dir = UP;
        }
        else if (KEY_PRESS(VK_DOWN) && ps->dir != UP)
        {
            ps->dir = DOWN;
        }
        else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT)
        {
            ps->dir = LEFT;
        }
        else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT)
        {
            ps->dir = RIGHT;
        }
        else if (KEY_PRESS(VK_SPACE))
        {
            // 暂停
            Pause();
        }
        else if (KEY_PRESS(VK_ESCAPE))
        {
            // 退出游戏
            ps->status = END_NORMAL;
        }
        else if (KEY_PRESS(VK_F3))
        {
            // 加速
            if (ps->sleep_time > 80)
            {
                ps->sleep_time -= 30;
                ps->food_weight += 2;
            }
        }
        else if (KEY_PRESS(VK_F4))
        {
            // 减速
            if (ps->food_weight > 2)
            {
                ps->sleep_time += 30;
                ps->food_weight -= 2;
            }
        }
        SnakeMove(ps); // 贪吃蛇走一步的过程
        Sleep(ps->sleep_time);
    } while (ps->status == OK);
}
  • GameRun 函数是游戏的主循环,负责打印帮助信息、分数信息,检测用户按键,控制蛇的移动,并根据蛇的状态判断游戏是否结束。

    cpp 复制代码
    // 游戏善后的工作
    void GameEnd(pSnake ps)
    {
        SetPos(24, 12);
        switch (ps->status)
        {
        case END_NORMAL:
            printf("您主动结束游戏\n");
            break;
        case KILL_BY_SELF:
            printf("您撞到自己,游戏结束\n");
            break;
        case KILL_BY_WALL:
            printf("您撞到墙上,游戏结束\n");
            break;
        }
    
        // 打印死蛇
        pSnakeNode prev = ps->_pSnake;
        while (prev)
        {
            SetPos(prev->x, prev->y);
            printf("😫");
            prev = prev->next;
        }
        // 释放蛇身的链表
        pSnakeNode cur = ps->_pSnake;
        while (cur)
        {
            pSnakeNode prev = cur;
            cur = cur->next;
            free(prev);
        }
    }
  • GameEnd 函数负责游戏结束后的善后工作,包括打印游戏结束信息,将蛇的表情改为 😫,并释放蛇身链表的内存。

总结

通过对这个贪吃蛇游戏代码的详细解析,我们可以看到如何使用 C 语言和 Windows API 来实现一个简单的控制台游戏。代码中使用了链表来表示蛇的身体,通过控制光标的位置来实现游戏界面的更新。同时,代码还处理了用户输入、游戏状态判断、内存管理等问题,是一个很好的学习示例。

相关推荐
陈天伟教授6 分钟前
Web前端开发 - 制作简单的焦点图效果
java·开发语言·前端·前端开发·visual studio
不吃肘击15 分钟前
MyBatisPlus使用教程
java·开发语言
阿方.91825 分钟前
《C 语言内存函数超详细讲解:从 memcpy 到 memcmp 的原理与实战》
c语言·开发语言·c++
zeijiershuai1 小时前
Mybatis-入门程序、 数据库连接池、XML映射配置文件、MybatisX
xml·java·开发语言·mybatis
BanyeBirth1 小时前
C++滑动门问题(附两种方法)
开发语言·c++
一伦明悦დ2 小时前
嵌入式系统C语言编程常用设计模式---参数表驱动设计
c语言·开发语言·单片机·设计模式
Want5952 小时前
Python炫酷烟花
开发语言·python·pygame
androidwork2 小时前
Android 内存溢出(OOM)的 Kotlin 排查与优化指南
android·开发语言·kotlin
androidwork2 小时前
Kotlin与Flutter:跨平台开发的互补之道与实战指南
开发语言·flutter·kotlin
明月看潮生2 小时前
青少年编程与数学 02-020 C#程序设计基础 02课题、开发环境
开发语言·青少年编程·c#·开发环境·编程与数学