使用控制台实现贪吃蛇需要的技能加点:

控制台设置(包含于stdlib.h):

定义命令行窗口高/宽:
system("mode con cols=100 lines=30");

        system() 函数是一个C标准库函数,它允许程序执行操作系统命令。
        mode 是命令的关键字,代表“模式”,用于改变系统设备的配置。
        con 是“console”的缩写,指代命令行界面或控制台窗口。
        cols=100 设置了窗口的列数(宽度)为100个字符。
        lines=30 设置了窗口的行数(高度)为30行。
        长宽(二倍关系);
        合起来为改变控制台(命令行)窗口的配置:高30 宽100.

设置标题:
system("title 贪吃蛇");

        在程序运行结束前设定标题为贪吃蛇;
        在程序结束后标题恢复为默认状态。

控制台窗口坐标:

c语言利用控制台实现贪吃蛇-LMLPHP

                                                  坐标图(x轴俩个单位长度等于y轴一个单位)

使用windows.h头文件可以使用该结构体,该结构体有俩个成员:

typedef struct _COORD {
            SHORT X;
            SHORT Y;
    } COORD, *PCOORD;

    通过X,Y可以确定一个控制台上的一个位置,后面会用它来调整光标位置。

c语言利用控制台实现贪吃蛇-LMLPHP
    光标

获取/使用句柄(控制台句柄):

        获取句柄

        句柄可以理解为一个中介,通过中介可以操纵使用中介手里的资源。例如你需要找个兼职,但是不知道怎么做才能兼职,于是你找了一个地区的兼职中介,你将需求告诉了中介,最后中介帮你搞定了找兼职的事,这个中介只管他所在地区的资源。
        在这里你需要隐藏掉控制台的光标,毕竟我们不需要在贪吃蛇显示光标。

//获取当前控制台界面的句柄
HANDLE houtput = NULL;
//定义结构体用来接收存放句柄;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);

        GetStdHandle()是Windows API 提供的函数,参数为标准设备(标准输入、标准输出、标准错误),返回值为对应句柄
        STD_OUTPUT_HANDLE 表示标准输出设备(当前控制台)。

        使用句柄

        我们可以通过这个句柄对当前的控制台进行一些操作,这里我们先来一个获取光标信息的操作:

//首先定义一个光标信息的结构体
CONSOLE_CURSOR_INFO cursor_info = {0};

        该结构体是Windows API中用于描述控制台光标属性的数据结构,它包含以下两个成员:

typedef struct {
    DWORD dwSize;
    BOOL  bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

关键词解释:

        dwSize:这是一个DWORD类型的变量,表示光标的大小。这里的“大小”并不是指光标的物理尺寸,而是指光标所覆盖的字符单元的百分比。例如,如果设置为50,则光标将占据半个字符单元的高度。此值必须在1到100之间;
        DWORD类型是Windows API中定义的一种数据类型,全称为Double Word,表示一个32位的无符号整数。

        bVisible:这是一个BOOL类型的变量,指示光标是否可见。如果bVisible为TRUE,则光标可见;如果为FALSE,则光标不可见。


下面演示一下获取句柄->通过该句柄得到光标数据->打印出光标数据

        //获得标准输出设备的句柄
        HANDLE houtput = NULL;
        houtput = GetStdHandle(STD_OUTPUT_HANDLE);
        //定义一个光标信息的结构体
        CONSOLE_CURSOR_INFO cursor_info = {0};
        //获取和houtput句柄相关的控制台上的光标信息,存放在cursor_info中
        GetConsoleCursorInfo(houtput, &cursor_info);
        //打印当前控制台的光标大小,光标隐藏情况
        printf("%u, %d", cursor_info.dwSize, cursor_info.bVisible);

        除了得到句柄,我们也可以定义一个光标属性的结构体,设定好里面的参数,然后将他交给句柄来改变光标属性:
        

        //定义一个光标信息的结构体
        CONSOLE_CURSOR_INFO cursor_info = {0};
        //设定光标显示:否
        cursor_info.bVisible = false;
        //像前面一样,获得当前标准输出设备的句柄
        HANDLE houtput = NULL;
        houtput = GetStdHandle(STD_OUTPUT_HANDLE);
        //设置控制台上的光标信息(通过刚刚获得的句柄,SetConsoleCursorInfo通过句柄可以知道更改的是哪个窗口
        SetConsoleCursorInfo(houtput, &cursor_info);
        //暂停程序以便观察
        system("pause");
        观察发现光标不见了(不显示)

    改变光标位置(实现在控制台不同位置打印文本,而不是默认的从左到右、从上到下):

        获得当前标准输出设备的句柄
        HANDLE houtput = NULL;
        houtput = GetStdHandle(STD_OUTPUT_HANDLE);
        定义并初始化一个窗口坐标结构体,参数为横纵坐标:
        COORD pos = { 10, 20 };
        使用光标位置调整函数(参数为句柄和窗口坐标结构体)
        SetConsoleCursorPosition(houtput, pos);
        测试光标位置
        printf("10,20位置的光标在这里");

检测按键:

        在游戏中,我们需要通过上下左右控制蛇的方向,所以我们还需要一个检测按键的方法:

        以下举例为检测数字5是否被按过:

short ret = GetAsyncKeyState(0x35);
if ((ret & 1) == 1)//这里使用了位与运算符&来检查ret的最低位(第0位)是否为1
    printf("5被按过\n");
    else
    printf("没有被按过\n");

        这里用到了GetAsyncKeyState函数:这是Windows API中的一个函数,用于检查指定虚拟键的当前状态。它接收一个虚拟键码作为参数,返回一个短整型(short)值,该值反映了按键的"状态"包括"是否被按下"。虚拟键码0x35对应于键盘上的数字键"5"。        

        这段代码调用了GetAsyncKeyState函数,传入数字键"5"的虚拟键码,获取其状态并存储在变量ret中

        由于我们此次项目只检测按键是否被按过这一状态,不在乎按键的其他状态(如是否为持续按住),所以我们只会用到ret的最后一位数字,该数字为1表示按过,为0表示没按过。     
        我们可以用做一个宏用来检测某键是否被按过

 #define KEY_PRESS(vk)  ((GetAsyncKeyState(vk)&1)?1:0)

        参数为虚拟键码vk。
        虚拟键码表可以自行网上搜索

本地化:

        有一些特殊的字符需要俩个字节的编码来表示,而使用这些字符需要设置一个本地化:由于世界不同地区的文字差异,时间格式差异,符号差异,货币单位差异...,不同地区有不同的双字节编码方式(同样的编码可能表示不同的字符)。

        以下为查询当前程序的本地化设置。

#include <locale.h>//我们要用的setlocale函数在这个头文件        
    char* ret = setlocale(LC_ALL, NULL);
    printf("%s\n", ret);//打印出当前的本地化设置名称。

        调用setlocale函数,参数LC_ALL是一个宏,代表所有类别,用它的意思是对所有的地区性的东西动手。第二个参数为NULL时,NULL表示不修改地区性的东西。setlocale会返回修改后的本地化设置名称(返回的设置名称被存储在ret指向的字符数组中,随后通过printf打印出来。)

        以下为改变当前程序的本地化设置

    ret = setlocale(LC_ALL, "");

    printf("%s\n", ret);//再次打印出本地化设置名称。

        调用setlocale函数,这次第二个参数是空字符串""。当使用空字符串作为参数时,setlocale会尝试根据环境变量(如LC_ALL, LANG, LC_CTYPE等)来设置本地化信息,通常会选择用户或系统的默认本地化设置。

        再次打印出本地化设置名称,这次是在尝试应用用户默认设置后得到的结果。如果用户的默认设置与之前的查询结果不同,这里应该能看到变化。

        我们先尝试一下本地化后的汉字打印,以及本地的特殊符号打印:

//设置本地化
    setlocale(LC_ALL, "");
    //定义一个字符变量 ,由于该变量占俩个字节,所以叫做宽字符,宽字符的变量类型为wchar_t,赋值的变量前面加上L来说明该字符为宽字符,防止程序将其误会为普通的char类型字符
    wchar_t a = L'本';
    wchar_t b = L'地';
    wprintf(L"%lc\n", wc1);
    wprintf(L"%lc\n", wc2);
    wprintf(L"%lc\n", L'●');
    wprintf(L"%lc\n", L'★');

        打印可以看到宽字符的宽度是普通字符的俩倍。

单链表和结构体知识:

        蛇的身体我们通过单链表来维护。

贪吃蛇游戏实现:

适配本地环境,设定随机数"起点"  

//设置适配本地环境
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));

        我们生成食物需要用到随机数。

初始化游戏:

    1. 打印开始界面(欢迎界面,下一步后记得清屏)

        通过控制控制台坐标来调整光标位置,再打印文字来实现欢迎界面打印

c语言利用控制台实现贪吃蛇-LMLPHP

system("cls")//下一步进入操作介绍前记得清屏


    2. 功能介绍

            介绍游戏的操作方式,如何控制移动方向,设置速度,实现方式和上一步同理
    3. 绘制地图

            打印一个墙体,墙用方格子表示,方格子是宽字符,需设置本地化
    4. 创建蛇

            本质是创建一个链表,然后打印出来
    5. 创建食物

            在随机的坐标生成食物,本质为创建一个不和蛇相交的结点并打印

            注意食物的坐标横坐标应该为偶数
    6. 设置游戏的相关信息(开局蛇的方向,分数,食物分数,蛇速,蛇状态为存活)

            创建一个结构体来维护这些信息

运行游戏:

    1.显示打印当前分数和食物的分值
    2.用if else检测刚刚的按键情况并执行相应任务,改变方向、退出、加速、减速、暂停
    3.结算蛇走一步
        根据结算决定是否进行下一个循环:
        啥事没有->继续:蛇身链表头增,尾删实现蛇移动一格
        撞自己->结束 :蛇头遍历蛇身体存在相同坐标,此时代表撞了自己,设置蛇状态为撞自己并结束游戏。
        撞墙->结束:蛇头遍历墙体坐标存在相同坐标,此时代表撞了墙,设置蛇状态为撞墙并结束游戏。
        吃了食物->继续:蛇头坐标与食物坐标相同,蛇身链表不尾删,只头增,同时创建一个新食物,同初始化游戏第五条。
    4.程序休息一点点时间(模拟蛇的速度)

Sleep(time);

结束游戏:

    1.打印结束原因(你撞墙了,你撞自己了...)
    2.释放蛇链表内存
    3.询问是否再来一盘

04-21 14:17