C语言:【项目实现】贪吃蛇

轩源源 2024-10-03 15:05:13 阅读 64

目录

1.介绍:

       (1).控制台程序(Console)(也就是控制控制台窗口的长和宽)

       (2).控制光标位置

       (3).GetStdHandle函数:

       (4).GetConsoleCursorInfo函数:

       (5).SetConsoleCursorInfo函数:

       (6).SetConsoleCursorPosition函数:

       (7).GetAsyncKeyState:

2.宽字符打印与本地化设置:

       (1).前情提要:

       (2).本地化介绍:本地化:

       (3).类项:

       (4).setlocale函数:设置本地化状态

       (5).宽字符的打印:

3.贪吃蛇游戏的实现:

       (1).test.c文件:

       (2).snake.h

      (3).snake.c


Hello everyone,今天我来教大家实现一下贪吃蛇游戏的相关操作:

首先呢,我们大家在写贪吃蛇代码前要先了解一些知识点(Win32 API技术),在这里,我就带着大家一起来看一下吧:

1.介绍:

        Windows这个多作业系统除了协调应用程序的执行等操作,他同时也是一个很大的服务中心,这个服务中心它包含着很多种类的服务,其实这些服务就是有一系列的函数组成的,换句话说,就是我们现在在这个电脑上所完成的一系列步骤,其实在都是由函数来实现的,Windows规定这些函数我们都可以其直接使用,比如说打开视窗,描绘图形,使用周边设备等功能,也就是Windows的每一个函数他都能完成一个特定的功能,因为这些函数服务的对象是应用程序(Applicancation),因此简称API函数,Win32 API也就是Microsoft Windows 32位平台的应用程序编程接口。那么,接下来,我就来给大家介绍几种实现我们的贪吃蛇游戏需要用到的函数吧。

       (1).控制台程序(Console)(也就是控制控制台窗口的长和宽)

       但我们想要去改变控制台的长和宽的时候,mode命令去实现这个功能,例如:mode con coles=100 lines=30,当我们在Windows系统控制台上输入这个命令时,然后按Enter键那么这个时候控制台就会变成长100,宽30。

       我们也可以通过title命令去改变控制台窗口的名字,例如:title 贪吃蛇,当我们在Windows系统控制台上输入这个命令时,然后按Enter键那么这个时候控制台的题目就会变成贪吃蛇(只仅限于是在程序没结束时控制台的题目一直都会使贪吃蛇)。

       注意:mode命令和title命令不可以直接在C语言中使用,但是我们可以使用system()函数来执行这一命令,代码如下所示:

system("mode con cols=100 lines=30");

system("title 贪吃蛇");

aef94fe4eb18482289e864f8e07301d5.png

调试控制台屏幕如图所示:调试控制台屏幕变成100行30列,而且题目变成贪吃蛇。

       (2).控制光标位置

       在了解这个知识点前,我们首先要知道控制台屏幕其实就是一个坐标轴,而其中的每一个位置就是那个白点一样是一个坐标,

17d86746472048df8c30d28657e34c57.png

d81cc1e39e274376810b47f85e236788.jpeg

        我们要想让光标定位(注意:现在只是定位,不是转变到这个位置)到某一个坐标位置,在Win32 API上面有一个结构体:COORD结构体:typedef struct _COORD{

                                                                         SHORT x;

                                                                         SHORT y;

                                                                   }COORD,*PCOORD;例如:COORD pos={10,30};//给这个结构体x赋值10,y赋值30,换句话说,pos定位到(10,30)这个坐标位置。

       (3).GetStdHandle函数:

       用于从一个特定的标准设备(标准输入(例如键盘),标准输出(屏幕),标准错误)中获得一个句柄(换句话说,就是你想要去操作一个设备,你就得去获得这个设备的一些数据,(比如说:你现在想要去使用锅,那么你就得抓住这口锅的手柄,这样你才能去操作这口锅)这里的句柄和锅的手柄是一个道理,当我们得到这个句柄之后,我们就可以去操作这个设备了)。

       HANDLE GetStdHandle (DWORD nStdHandle);//(HANDLE是句柄的类型,DWORD是GetStdHandle参数的类型,nStdHandle是参数的取值)

                                     {STD_INPUT_HANDLE    标准输入设备(输入缓冲区的控制台)

       nStdHandle的取值{STD_OUTPUT_HANDLE    标出输出设备(控制台屏幕缓冲区)

                                     {STD_ERROR_HANDLE    标准错误设备(控制台屏幕缓冲区)

例如:HANDLE output=GetStdHandle(STD_OUTPUT_HANDLE);//定义了一个句柄output里面有标准输出是设备的一些信息。

       (4).GetConsoleCursorInfo函数:

       检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息(获得光标信息)。

       BOOL WINAPT GetConsoleCursorInfo(HANDLE nStdHandle,PCONSOLE_CURSOR_INFO IpConsoleCursorInfo);//(nStdHandle句柄,PCONSOLE_CURSOR_INFO是指向CONSOLE_CURSOR_INFO结构体的指针,该结构接收有关主机游标(光标)的信息

                         typedef struct  _CONSOLE_CURSOR_INFO{

                                  DWORD dwSize;//光标的大小

                                  BOOL bVisible;//光标的显示度

                         };CONSOLE_CURSOR_INFO,*PCONSOLE_CURSOR_INFO;)。

例如:我想要获得控制台屏幕中光标的信息

                HANDLE   houtput=GetStdHandle(STD_OUTPUT_HANDLE);

                CONSOLE_CURSOR_IN   cursor_info={0};

                GetConsoleCursorInfo(houtput,&cursor_info);//将houtput句柄中有关光标的大小和显示度这两个信息放到cursor_info数组里面。(注意:这个函数仅仅只是获得信息,并不是改变)

       (5).SetConsoleCursorInfo函数:

       设置指定控制台屏幕缓的光标大小和可见度。

       BOOL WINAPT SetConsoleCursorInfo(HANDLE nStdHandle,const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo);//(nStdHandle句柄,CONSOLE_CURSOR_INFO *lpConsoleCursorInfo是指向CONSOLE_CURSOR_INFO结构体的指针)

例如:     HANDLE   houtput=GetStdHandle(STD_OUTPUT_HANDLE);

                CONSOLE_CURSOR_IN   cursor_info={0};

                GetConsoleCursorInfo(houtput,&cursor_info);

                cursor_info.bVisible=false;

                SetConsoleCursorInfo(houtput,&cursor_info);//将cursor_info中的光标信息在控制台屏幕中展现出来。

       (6).SetConsoleCursorPosition函数:

       设置光标位置。

       BOOL WINAPT SetConsoleCursorInfo(HANDLE nStdHandle,COORD dwCurPosition);//(nStdHandle句柄,dwCurPosition是COORD类型的结构体

                   typedef struct _COORD{

                            SHORT x;

                            SHORT y;

                   }COORD,*PCOORD;)

例如:        HANDLE   houtput=GetStdHandle(STD_OUTPUT_HANDLE);

                   COORD pos{10,20};

                   SetConsoleCursorPosition(houtput,pos);//将屏幕中光标的位置定位到pos中所指定的位置。

       (7).GetAsyncKeyState:

       获取键盘中各键的状态,它会返回一个16位的short类型的数据,如果返回值的最低位被置为1则说明,该键被按过,否则为0。

       SHORT GetAsyncKeyState(int vkey);//(每个键都有一个对应的虚拟键值,再此函数中就是指vkey)

例如:我想要判断一下键5是否被按过:

                    #define AK(vk)   ((GetAsyncKeyState(vk)&0x1)?1:0)//这里我们需要注意一下,GetAsyncKeyState函数它的返回值的16位的值是不确定的,但是我们只需要去判断它的最后是不是1或者是0即可。基于这种原因,我们让GetAsyncKeyState函数的返回值和0x1&,也就是如下列代码所示:比如说GetAsyncKeyState函数返回了值是

(函数返回的随机值)&   00101010   01010101       |          &             00101010   01010100

                                     00000000   00000001       |                         00000000   00000001

            最后的结果为   00000000   00000001       |                          00000000   00000000

2.宽字符打印与本地化设置:

       (1).前情提要:

acf459f79c094e38aad05e8ed213e250.jpeg

       (2).本地化介绍:<local.h>本地化:

       此头文件中所提供的函数用于控制C语言库中对于不同的地区会产生不一样行为的部分。换句话说,就是这个C语言在最开始的时候它是由美国人创造的,因此它仅限与使用英语地区的人使用,但是当你本地化之后,你就可以在屏幕上打印出汉字(我们的汉字占两个坐标位置(也就是可以打印宽字符))。

       (3).类项:

       通过上述解释,相信大家对于本地化已经有了一些了解,但是怎么修改呢?

比如:LC_ALL------针对所有类项进行修改

           LC_TIME----只影响时间的格式(类项不只有这两个,这里时间有限,就不给大家一一去介绍了,大家感兴趣的话可以去自行了解)

       (4).setlocale函数:设置本地化状态

       char*setlocale(int category,const char*locale);//(category这个参数指的就是上述类项其中的一种,针对第二个参数,标准只给了两种取值"C"(正常模式(也就是英语模式))和""(本地模式))。

例如:          setlocale(LC_ALL,"");//将所有的类项均转换为本地模式。

       (5).宽字符的打印:

       当我们将其转化为本地模式后,那么此时的编译器按逻辑来说是可以打印出汉字的,但还是要做一些处理,所用到的函数是什么,占位符是什么。

       我们在打印宽字符的时候只能使用wprintf函数,若打印的是宽字符,则所需占位符为%Lc,若为宽字符串,则所需占位符为%Ls。

例如:            setlocale(LC_ALL,"");

                       wprintf(L"%Lc",L'比');//比,占两个坐标位置

                       wprintf(L"%Ls",L'进击的巨人');//进击的巨人

3.贪吃蛇游戏的实现:

         OK好的,同志们,经过了上述知识点的解释,现在以我们的知识储备量就可以开始我们的游戏了。

       (1).test.c文件:

<code>#include "snake.h"

//完成的是游戏的测试逻辑

void test()

{

    int ch = 0;

    do

    {

        system("cls");

        Snake snake = { 0 };

        stratgame(&snake);

        rungame(&snake);

        //finishgame(&snake);

        setpos(20, 15);

        wprintf(L"再来一局吗?(Y/N):");

        ch = getchar();

        while (getchar() != '\n');

        //system("cls");

    } while (ch == 'y' || ch == 'Y');

    setpos(0, 27);

}

int main()

{

    //设置适配本地环境

       setlocale(LC_ALL, "");

    srand((unsigned int)time(NULL));

    test();

    return 0;

}

       (2).snake.h

#pragma once

#define _CRT_SECURE_NO_WARNINGS 1

#include <locale.h>

#include <windows.h>

#include <stdbool.h>

#include <stdlib.h>

#include <stdio.h>

#include <time.h>

#define POS_X 24

#define POS_Y 5

#define WALL L'□'

#define BODY L'●'

#define FOOD L'★'

enum DIRECTION

{

    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 DIRECTION _dir;

    enum GAME_STATUS _status;

    int _food_weight;

    int _score;      

    int _sleep_time; 

}Snake, * pSnake;

//定位光标位置

void setpos(short x, short y);

//游戏的初始化

void stratgame(pSnake ps);

//打印环境页面和功能介绍

void welcomesnake();

//创建地图

void createmap();

//创建蛇

void createsnake(pSnake ps);

//创建食物

void createfood(pSnake ps);

//运行游戏

void rungame(pSnake ps);

//打印帮助信息

void printhelpinfo();

//游戏暂停

void pause();

//贪吃蛇移动

void movesnake(pSnake ps);

//吃食物

void eatfood(pSnakeNode newnode, pSnake ps);

//不是食物

void nofood(pSnakeNode newnode, pSnake ps);

//碰到墙壁

void killbywall(pSnake ps);

//碰到自己

void killbyself(pSnake ps);

//结束游戏

void finishgame(pSnake ps);

      (3).snake.c

#include "snake.h"

//创建食物

void createfood(pSnake ps)

{

        int x = 0;

        int y = 0;

again:

        do

        {

            x = rand() % 53 + 2;

             y = rand() % 25 + 1;

        } while (x % 2 != 0);

        pSnakeNode cur = ps->;_pSnake;

        while (cur)

        {

            if (cur->x == x && cur->y == y)

            {

                goto again;

            }

            cur = cur->next;

        }

        pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));

        if (food == NULL)

        {

            perror("createfood()::malloc()");

            return 1;

        }

        food->x = x;

        food->y = y;

        food->next = NULL;

        setpos(x, y);

        wprintf(L"%lc", FOOD);

        ps->_pFood = food;

}

//创建蛇

void createsnake(pSnake ps)

{

    pSnakeNode cur = NULL;

    for (int i = 0; i < 5; i++)

    {

        cur = (pSnakeNode)malloc(sizeof(SnakeNode));

        if (cur == NULL)

        {

            perror("createsnake()::malloc()");

            return 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);

        wprintf(L"%lc", BODY);

        cur = cur->next;

    }

    //设置贪吃蛇的属性

    ps->_dir = RIGHT;

    ps->_food_weight = 10;

    ps->_score = 0;

    ps->_sleep_time = 200;

    ps->_status = OK;

}

//创建地图

void createmap()

{

    int i = 0;

    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);

    }

    setpos(0, 26);

    for (i = 0; i < 29; i++)

    {

        wprintf(L"%lc", WALL);

    }

}

//定位光标位置

void setpos(short x, short y)

{

    HANDLE houtput = NULL;

    houtput = GetStdHandle(STD_OUTPUT_HANDLE);

    COORD pos = { x,y };

    SetConsoleCursorPosition(houtput, pos);

}

//打印环境页面和功能介绍

void welcomesnake()

{

    setpos(40,14);

    wprintf(L"%ls",L"欢迎来到贪吃蛇小游戏\n");

    setpos(42, 14);

    system("pause");

    system("cls");

    setpos(25,14);

    wprintf(L"%ls", L"用↑.↓.←.→ 来控制蛇的移动,按F3加速,F4减速\n");

    setpos(25,15);

    wprintf(L"%ls", L"加速能得到更好的分数\n");

    setpos(42, 20);

    system("pause");

    system("cls");

}

//游戏开始 

void stratgame(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);

    //打印环境页面和功能介绍

    welcomesnake();

    //创建地图

    createmap();

    //创建蛇

    createsnake(ps);

    //创建食物

    createfood(ps);

}

//判断按键是否被按

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

//游戏暂停

void pause()

{

    while (1)

    {

        Sleep(200);

        if (KEY_PRESS(VK_SPACE))

        {

            break;

        }

    }

}

//碰到自己

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 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 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 nofood(pSnakeNode newnode, pSnake ps)

{

    newnode->next = ps->_pSnake;

    ps->_pSnake = newnode;

    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);

    cur->next = NULL;

}

//吃食物

void eatfood(pSnakeNode newnode, pSnake ps)

{

    ps->_pFood->next = ps->_pSnake;

    ps->_pSnake = ps->_pFood;

    free(newnode);

    newnode=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 movesnake(pSnake ps)

{

    pSnakeNode newnode = (pSnakeNode)malloc(sizeof(SnakeNode));

    if (newnode == NULL)

    {

        perror("movesnake()::malloc()");

        return 1;

    }

    switch (ps->_dir)

    {

    case RIGHT:

        newnode->x = ps->_pSnake->x + 2;

        newnode->y = ps->_pSnake->y;

        break;

    case LEFT:

        newnode->x = ps->_pSnake->x - 2;

        newnode->y = ps->_pSnake->y;

        break;

    case DOWN:

        newnode->x = ps->_pSnake->x;

        newnode->y = ps->_pSnake->y + 1;

        break;

    case UP:

        newnode->x = ps->_pSnake->x;

        newnode->y = ps->_pSnake->y - 1;

        break;

    }

    if (newnode->x == ps->_pFood->x && newnode->y == ps->_pFood->y)

    {

        eatfood(newnode,ps);

    }

    else

    {

        nofood(newnode, ps);

    }

    killbywall(ps);

    killbyself(ps);

}

//运行游戏

void rungame(pSnake ps)

{

    printhelpinfo();

    do

    {

        setpos(64, 10);

        printf("总分数:%d\n", ps->_score);

        setpos(64, 11);

        printf("当前食物的分数:%2d\n", ps->_food_weight);

        if (ps->_dir != RIGHT && KEY_PRESS(VK_LEFT))

        {

            ps->_dir = LEFT;

        }

        else if (ps->_dir != LEFT && KEY_PRESS(VK_RIGHT))

        {

            ps->_dir = RIGHT;

        }

        else if (ps->_dir != UP && KEY_PRESS(VK_DOWN))

        {

            ps->_dir = DOWN;

        }

        else if (ps->_dir != DOWN && KEY_PRESS(VK_UP))

        {

            ps->_dir = UP;

        }

        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;

            }

        }

        movesnake(ps);

        Sleep(ps->_sleep_time);

    } while (ps->_status == OK);

}

//结束游戏

void finishgame(pSnake ps)

{

    setpos(24, 12);

    switch (ps->_status)

    {

    case KILL_BY_WALL:

        wprintf(L"%ls", L"您撞到墙上,结束游戏\n");

        break;

    case KILL_BY_SELF:

        wprintf(L"%ls", L"您撞到自己,结束游戏\n");

        break;

    case END_NORMAL:

        wprintf(L"%ls", L"您主动结束游戏\n");

        break;

    }

    pSnakeNode cur = ps->_pSnake;

    while (cur)

    {

        pSnakeNode del = cur;

        cur = cur->next;

        free(del);

    }

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。