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

简介: 本文介绍了一个基于Windows控制台的贪吃蛇游戏的实现方法。首先,需调整控制台界面以便更好地显示游戏。接着,文章详细描述了如何使用Win32 API函数如`COORD`、`GetStdHandle`、`GetConsoleCursorInfo`等来控制控制台的光标和窗口属性。此外,还介绍了如何利用`GetAsyncKeyState`函数实现键盘监听功能。文中还涉及了`<locale.h>`库的使用,以支持本地化字符显示。

准备工作

首先我们需要更改一下运行之后调用的控制台界面

如果运行之后出现的是上面的界面,就需要更改一下,鼠标右键点击控制台顶端,再点击设置


再启动就可以了,之后也可以自己自定义控制台的样式,例如颜色,字体,还是右键点击控制台顶端,再点击属性,就可以找到设置了。
之后我们可以通过一些命令来对控制台进行设置

system("mode con cols=100 lines=30");//设置窗口大小
system("title 贪吃蛇");//设置标题

以下是我们需要达到的最终效果

用到的win32API

COORD控制台坐标

COORD是一个结构体类型,在使用时需要包含的头文件是windows.h,定义的参数分别为x,y轴坐标

GetStdHandle

它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值)。这个句柄可以用于后续对设备进行操作或修改其属性。可以理解为就是一只手,通过这个手我们才能后续对设备进行操作

GetConsoleCursorInfo

用于检索有关指定的控制台屏幕缓冲区的光标的可见性和大小信息

示例:

int main() {
    HANDLE houtput = NULL;//获取句柄
    houtput = GetStdHandle(STD_OUTPUT_HANDLE);
    //定义光标信息结构体
    CONSOLE_CURSOR_INFO cursor_info = { 0 };
    //获取和houtput句柄相关的控制台上的光标信息,并存放在cursor_info中
    GetConsoleCursorInfo(houtput, &cursor_info);
    printf("%d\n", cursor_info.dwSize);
    
    return 0;
}

当我们把光标信息打印出来时,显示为25,也就是当前光标大小占整个字符的25%

//修改光标占比
cursor_info.dwSize = 50;
//设置和houtput句柄相关的控制台上光标信息
SetConsoleCursorInfo(houtput, &cursor_info);

可以加入上面的代码对光标进行修改,显示的就是占比50%的光标

还可以根据以下代码设置光标的位置

//定位光标位置
COORD pos = { 10,20 };
SetConsoleCursorPosition(houtput, pos);

GetAsyncKeyState

用于检测指定的键是否被按下或释放,接受一个虚拟键码作为参数,并返回一个short类型的值,如果指定的键被按下,则返回一个负数,表示该键此前被按下并一直保持按下状态;如果指定的键未被按下,则返回零。

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)  //把GetAsyncKeyState定义为宏,类似与函数
int main(){
   while (1) {
    //分别传入0,1,2的虚拟键码
    if (KEY_PRESS(0x30)) {
        printf("0\n");
    }
    else if (KEY_PRESS(0x31)) {
        printf("1\n");
    }
    else if (KEY_PRESS(0x32)) {
        printf("2\n");
    }
    }
return 0;
}

这样就能实现键盘监听的效果

<locale.h>本地化

用于改变程序的行为以适应不同的文化和语言环境,例如,中文的一个文字是宽字符,需要占用两个单字符

#include <stdio.h>
#include <locale.h>
int main() {
    char* ret = setlocale(LC_ALL, NULL);//c语言默认模式
    printf("%s\n", ret);
    ret = setlocale(LC_ALL, "");//简体中文模式
    printf("%s\n", ret);
    char a = 'a', b = 'b';
    printf("%c%c\n", a, b);
    //宽字符打印
    wchar_t w1 = L'你';
    wchar_t w2 = L'好';
    wprintf(L"%lc\n", w1);
    wprintf(L"%lc\n", w2);
    return 0;
}

设置本地化之后,就可以打印一些本地化的符号了,例如中文宽字符的打印,也可以明显的看出,宽字符要占用两个普通字符的宽度。

在后续的操作中需要注意,一个坐标能放一个普通字符,两个坐标才能放一个宽字符

主要部分

初始化成员

//蛇前进的方向
enum DIRECTTON {
    UP = 1,
    DOWN, 
    LEFT,
    RIGHT
};
//游戏状态
enum GAME_STATUS {
    OK,//正常
    KILL_BY_WALL,//撞墙
    KILL_BY_SELF,//咬到自己
    END_NORMAL//正常结束
};
//定义蛇,坐标定位蛇的位置
typedef struct SnakeNode {
    int x;
    int y;
    struct SnakeNode* next;
}SnakeNode,*pSnakeNode;
//封装
typedef struct Snake {
    pSnakeNode psnake; //指向蛇头的指针
    pSnakeNode pFood;//指向食物的指针
    enum DIRECTTON dir;//蛇的方向
    enum GAME_STATUS status;//游戏的状态
    int food_weight;//一个食物的分数
    int score;//总分
    int sleep_time;
}Snake,*psnake;

这样我们就完成了所有成员变量的初始化

地图创建

void CreateMap()
{
    //上
    int i = 0;
    for (i = 0; i < 29; i++)
    {
        wprintf(L"%lc", WALL);
    }
    //下
    SetPos(0, 26);
    for (i = 0; i < 29; i++)
    {
        wprintf(L"%lc", WALL);
    }
    //左
    for (i = 1; i <= 25; i++)
    {
        SetPos(0, i);
        wprintf(L"%lc", WALL);
    }
    //右
    for (i = 1; i <= 25; i++)
    {
        SetPos(56, i);
        wprintf(L"%lc", WALL);
    }
    system("pause");
}

蛇的初始化

void InitSnake(psnake ps) {
    pSnakeNode cur = NULL;
    for (int i = 0; i < 5; i++) {
        cur = (pSnakeNode)malloc(sizeof(SnakeNode));
        if (cur == NULL) {
            perror("InitSnake");
            return;
        }
        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);
        wprintf(L"%lc", BODY);
        cur = cur->next;
    }
    //设置贪吃蛇的属性
    ps->dir = RIGHT;
    ps->score = 0;
    ps->food_weight = 10;
    ps->sleep_time = 200;
    ps->status = OK;
    getchar();
}

随机生成食物

void CreateFood(psnake ps)
{
    int x = 0;
    int y = 0;
    //生成x是2的倍数
    //x:2~54
    //y: 1~25
again:
    do
    {
        x = rand() % 53 + 2;
        y = rand() % 25 + 1;
    } while (x % 2 != 0);
    //x和y的坐标不能和蛇的身体坐标冲突
    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("CreateFood()::malloc()");
        return;
    }
    pFood->x = x;
    pFood->y = y;
    pFood->next = NULL;
    SetPos(x, y);//定位位置
    wprintf(L"%lc", FOOD);
    ps->pFood = pFood;
}

吃到食物后再生成食物,蛇身增加

void EatFood(pSnakeNode pn, psnake ps)
{
    //头插法
    ps->pFood->next = ps->psnake;
    ps->psnake = ps->pFood;
    //释放下一个位置的节点
    free(pn);
    pn = NULL;
    pSnakeNode cur = ps->psnake;
    //打印蛇
    while (cur)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%lc", BODY);
        cur = cur->next;
    }
    ps->score += ps->food_weight;
    //重新创建食物
    CreateFood(ps);
}

完整源码

snake.h

#pragma once
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <stdbool.h>
#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
enum DIRECTTON {
    UP = 1,
    DOWN,
    LEFT,
    RIGHT
};
enum GAME_STATUS {
    OK,//正常
    KILL_BY_WALL,//撞墙
    KILL_BY_SELF,//咬到自己
    END_NORMAL//正常结束
};
typedef struct SnakeNode {
    int x;
    int y;
    struct SnakeNode* next;
}SnakeNode,*pSnakeNode;
typedef struct Snake {
    pSnakeNode psnake; //指向蛇头的指针
    pSnakeNode pFood;//指向食物的指针
    enum DIRECTTON dir;//蛇的方向
    enum GAME_STATUS status;//游戏的状态
    int food_weight;//一个食物的分数
    int score;//总分
    int sleep_time;
}Snake,*psnake;
void GameStart(psnake p);
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.c

#define _CRT_SECURE_NO_WARNINGS 1
#define KEY_PRESS(vk)  ((GetAsyncKeyState(vk)&1)?1:0)
#include "snake.h"
void SetPos(short x, short y)
{
    //获得标准输出设备的句柄
    HANDLE houtput = NULL;
    houtput = GetStdHandle(STD_OUTPUT_HANDLE);
    //定位光标的位置
    COORD pos = { x, y };
    SetConsoleCursorPosition(houtput, pos);
}
void WelcomeToGame()
{
    SetPos(40, 14);
    wprintf(L"欢迎来到贪吃蛇小游戏\n");
    SetPos(42, 20);
    system("pause");
    system("cls");
    SetPos(25, 14);
    wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");
    SetPos(25, 15);
    wprintf(L"加速能够得到更高的分数\n");
    SetPos(42, 20);
    system("pause");
    system("cls");
}
void CreateMap()
{
    //上
    int i = 0;
    for (i = 0; i < 29; i++)
    {
        wprintf(L"%lc", WALL);
    }
    //下
    SetPos(0, 26);
    for (i = 0; i < 29; i++)
    {
        wprintf(L"%lc", WALL);
    }
    //左
    for (i = 1; i <= 25; i++)
    {
        SetPos(0, i);
        wprintf(L"%lc", WALL);
    }
    //右
    for (i = 1; i <= 25; i++)
    {
        SetPos(56, i);
        wprintf(L"%lc", WALL);
    }
    system("pause");
}
void InitSnake(psnake ps) {
    pSnakeNode cur = NULL;
    for (int i = 0; i < 5; i++) {
        cur = (pSnakeNode)malloc(sizeof(SnakeNode));
        if (cur == NULL) {
            perror("InitSnake");
            return;
        }
        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);
        wprintf(L"%lc", BODY);
        cur = cur->next;
    }
    //设置贪吃蛇的属性
    ps->dir = RIGHT;
    ps->score = 0;
    ps->food_weight = 10;
    ps->sleep_time = 200;
    ps->status = OK;
    getchar();
}
void GameStart(psnake ps)
{
    //设置窗口
    system("mode con cols=100 lines=30");
    system("title 贪吃蛇");
    HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
    //影藏光标操作
    CONSOLE_CURSOR_INFO CursorInfo;
    GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息
    CursorInfo.bVisible = false; //隐藏控制台光标
    SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态
    //开始界面
    WelcomeToGame();
    //绘制地图
    CreateMap();
    //创建蛇
    InitSnake(ps);
    //创建食物
    CreateFood(ps);
}
void CreateFood(psnake ps)
{
    int x = 0;
    int y = 0;
    //生成x是2的倍数
    //x:2~54
    //y: 1~25
again:
    do
    {
        x = rand() % 53 + 2;
        y = rand() % 25 + 1;
    } while (x % 2 != 0);
    //x和y的坐标不能和蛇的身体坐标冲突
    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("CreateFood()::malloc()");
        return;
    }
    pFood->x = x;
    pFood->y = y;
    pFood->next = NULL;
    SetPos(x, y);//定位位置
    wprintf(L"%lc", FOOD);
    ps->pFood = pFood;
}
void PrintHelpInfo()
{
    SetPos(64, 14);
    wprintf(L"%ls", L"不能穿墙,不能咬到自己");
    SetPos(64, 15);
    wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动");
    SetPos(64, 16);
    wprintf(L"%ls", L"按F3加速,F4减速");
    SetPos(64, 17);
    wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
    SetPos(64, 18);
    wprintf(L"%ls", L"制作");
}
void Pause()
{
    while (1)
    {
        Sleep(200);
        if (KEY_PRESS(VK_SPACE))
        {
            break;
        }
    }
}
int NextIsFood(pSnakeNode pn, psnake ps)
{
    return (ps->pFood->x == pn->x && ps->pFood->y == pn->y);
}
void EatFood(pSnakeNode pn, psnake ps)
{
    //头插法
    ps->pFood->next = ps->psnake;
    ps->psnake = ps->pFood;
    //释放下一个位置的节点
    free(pn);
    pn = NULL;
    pSnakeNode cur = ps->psnake;
    //打印蛇
    while (cur)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%lc", BODY);
        cur = cur->next;
    }
    ps->score += ps->food_weight;
    //重新创建食物
    CreateFood(ps);
}
void NoFood(pSnakeNode pn, psnake ps)
{
    //头插法
    pn->next = ps->psnake;
    ps->psnake = pn;
    pSnakeNode cur = ps->psnake;
    while (cur->next->next != NULL)
    {
        SetPos(cur->x, cur->y);
        wprintf(L"%lc", BODY);
        cur = cur->next;
    }
    //把最后一个结点打印成空格
    SetPos(cur->next->x, cur->next->y);
    printf("  ");
    //释放最后一个结点
    free(cur->next);
    //把倒数第二个节点的地址置为NULL
    cur->next = NULL;
}
void KillByWall(psnake ps)
{
    if (ps->psnake->x == 0 || ps->psnake->x == 56 ||
        ps->psnake->y == 0 || ps->psnake->y == 26)
    {
        ps->status = KILL_BY_WALL;
    }
}
void KillBySelf(psnake ps)
{
    pSnakeNode cur = ps->psnake->next;
    while (cur)
    {
        if (cur->x == ps->psnake->x && cur->y == ps->psnake->y)
        {
            ps->status = KILL_BY_SELF;
            break;
        }
        cur = cur->next;
    }
}
void SnakeMove(psnake ps)
{
    //创建一个结点,表示蛇即将到的下一个节点
    pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
    if (pNextNode == NULL)
    {
        perror("SnakeMove()::malloc()");
        return;
    }
    switch (ps->dir)
    {
    case UP:
        pNextNode->x = ps->psnake->x;
        pNextNode->y = ps->psnake->y - 1;
        break;
    case DOWN:
        pNextNode->x = ps->psnake->x;
        pNextNode->y = ps->psnake->y + 1;
        break;
    case LEFT:
        pNextNode->x = ps->psnake->x - 2;
        pNextNode->y = ps->psnake->y;
        break;
    case RIGHT:
        pNextNode->x = ps->psnake->x + 2;
        pNextNode->y = ps->psnake->y;
        break;
    }
    //检测下一个坐标处是否是食物
    if (NextIsFood(pNextNode, ps))
    {
        EatFood(pNextNode, ps);
    }
    else
    {
        NoFood(pNextNode, ps);
    }
    //检测蛇是否撞墙
    KillByWall(ps);
    //检测蛇是否撞到自己
    KillBySelf(ps);
}
void GameRun(psnake ps)
{
    //打印帮助信息
    PrintHelpInfo();
    do
    {
        //打印总分数和食物的分值
        SetPos(64, 10);
        printf("总分数:%d\n", ps->score);
        SetPos(64, 11);
        printf("当前食物的分数:%2d\n", 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);
}
void GameEnd(psnake ps)
{
    SetPos(24, 12);
    switch (ps->status)
    {
    case END_NORMAL:
        wprintf(L"结束游戏\n");
        break;
    case KILL_BY_WALL:
        wprintf(L"撞到墙上,游戏结束\n");
        break;
    case KILL_BY_SELF:
        wprintf(L"撞到了自己,游戏结束\n");
        break;
    }
    //释放蛇身的链表
    pSnakeNode cur = ps->psnake;
    while (cur)
    {
        pSnakeNode del = cur;
        cur = cur->next;
        free(del);
    }
}

text.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "snake.h"
void test()
{
    int ch = 0;
    do
    {
        system("cls");
        //创建贪吃蛇
        Snake snake = { 0 };
        GameStart(&snake);
        GameRun(&snake);
        //结束游戏
        GameEnd(&snake);
        SetPos(20, 15);
        printf("再来一局吗?(Y/N):");
        ch = getchar();
        while (getchar() != '\n');
    } while (ch == 'Y' || ch == 'y');
    SetPos(0, 27);
}
int main()
{
    setlocale(LC_ALL, "");
    srand((unsigned int)time(NULL));
    test();
    return 0;
}
相关文章
|
4月前
|
C语言 C++
【C语言】编写“猜数字“小游戏
【C语言】编写“猜数字“小游戏
121 1
|
4月前
|
存储 API C语言
【C语言】实践:贪吃蛇小游戏(附源码)(一)
【C语言】实践:贪吃蛇小游戏(附源码)
|
4月前
|
C语言 开发者
C语言实现猜数字小游戏(详细教程)
C语言实现猜数字小游戏(详细教程)
|
4月前
|
C语言 定位技术 API
【C语言】实践:贪吃蛇小游戏(附源码)(二)
【C语言】实践:贪吃蛇小游戏(附源码)
【C语言】实践:贪吃蛇小游戏(附源码)(二)
|
4月前
|
存储 定位技术 API
C语言项目实战:贪吃蛇
C语言项目实战:贪吃蛇
|
4月前
|
C语言
【C语言】实践:贪吃蛇小游戏(附源码)(三)
【C语言】实践:贪吃蛇小游戏(附源码)
|
4月前
|
C语言
C语言贪吃蛇小游戏来啦!
C语言贪吃蛇小游戏来啦!
49 0
|
30天前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
57 23
|
30天前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
57 15
|
30天前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
57 24

热门文章

最新文章

相关实验场景

更多