C语言实现贪吃蛇游戏

目录

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文件:

#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);
    }
}
  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值