C游戏开发——贪吃蛇

引言:

就是练手用的,这个真没什么好说的了。

代码:

#include <stdio.h>
#include <windows.h>
#include <time.h>

// 宏定义
#define MAP_WIDTH 60        // 地图宽度
#define MAP_HEIGHT 30        // 地图高度
#define BRICK "□"            // 四周的墙
#define GRID "■"            // 内部的网格
#define SNAKE_MAX_SIZE 100    // 蛇最大节数 
#define SNAKE_INIT_SIZE 3    // 蛇初始化节数
#define SNAKE_HEAD "★"        // 蛇头
#define SNAKE_BODY "⊙"        // 蛇身体
#define FOOD "◆"            // 食物

// 方向枚举
enum _direction
{
    DIR_UP,     //上
    DIR_DOWN,     //下
    DIR_LEFT,  //左
    DIR_RIGHT  //右
};

// 游戏状态枚举
enum _status
{
    RUNNING, 
    GAMEOVER,
    EXIT
};

// 蛇结构体(未用链表)
struct _snake
{
    int size;                        // 实际大小
    enum _direction direction;        // 运动方向
    enum _status status;            // 游戏状态
    POINT nodePos[SNAKE_MAX_SIZE];    // 蛇各节点坐标位置
} snake;

// 食物结构体
struct _food
{
    int x;
    int y;
} food;

// 公用变量
int gTopScore = 0;        // 最高分数
int gMyScore = 0;        // 我的分数
int gSpeed = 300;        // 原始速度
int gLevelSpeed = 300;    // 第一关速度(每吃10个增加一次速度)
int gCurSpeed = 300;    // 当前速度
int gMaxSpeed = 50;        // 最大速度

// 函数定义
void hideCursor();
void setCursorPosition(int x,int y);
void setColor(int color);
void wecome();
void drawScore();
void drawTip();
void init();
int getTopScore();
void setTopScore(int score);
void gameStart();
void drawMap();
void drawSnake();
void drawFood();
void keyPress();
void moveSnake();
int collide();
void pause();
void gameOver();
void eatFood();

int main()
{
    // 隐藏光标
    hideCursor();
    
    // 欢迎界面
    wecome();
    
    // 游戏开始
    gameStart();

    return 0;
}

// 隐藏光标显示
void hideCursor()
{
    // 得到标准的输入(STD_INPUT_HANDLE)、输出(STD_OUTPUT_HANDLE)或错误(STD_ERROR_HANDLE)的设备的句柄
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    
    // 光标信息变量(typedef struct _CONSOLE_CURSOR_INFO {DWORD dwSize;WINBOOL bVisible;} CONSOLE_CURSOR_INFO)
    CONSOLE_CURSOR_INFO cursorInfo;
    
    // 获取当前光标信息
    GetConsoleCursorInfo(hOut, &cursorInfo);
    
    // 设置当前光标不可见
    cursorInfo.bVisible = 0;
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursorInfo);
}

// 设置光标所在位置(x,y)
void setCursorPosition(int x,int y)
{
    // 文字在控制台的坐标结构变量
    COORD pos = {x,y};
    
    // 设置控制台光标所在位置
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}

// 设置字体颜色
void setColor(int color)
{
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),color);
}

// 显示欢迎界面
void wecome()
{
    // 设置窗口标题
    SetConsoleTitle("贪吃蛇");
    // 设置窗口大小(30*100)
    system("mode con cols=95 lines=30");
    // 设置操作说明
    setCursorPosition(37, 10);
    printf("欢迎来到贪吃蛇的世界");
    setCursorPosition(30, 14);
    printf("提示:↑ ↓ ← → 控制贪吃蛇方向");
    setCursorPosition(27, 16);
    printf("     空格暂停或继续游戏,ESC退出游戏");
    setCursorPosition(37, 20);
    system("pause");
    system("cls");
}

// 从文件中读取最高分
int getTopScore()
{
    int score = 0;
    
    // 打开文件
    FILE *pFile = fopen("./score.dat","rb");
    
    // 读取得分
    fread(&score,sizeof(int),1,pFile);
    
    // 关闭文件
    fclose(pFile);

    return score;
}

// 将最高记录保存到文件中
void setTopScore(int score)
{
    // 打开文件
    FILE *pFile = fopen("./score.dat","wb");
    
    // 写入得分
    fwrite(&score,sizeof(int),1,pFile);
    
    // 关闭文件
    fclose(pFile);
}

// 游戏初始化
void init()
{
    // 蛇初始化
    snake.size = SNAKE_INIT_SIZE;
    snake.direction = DIR_LEFT;
    snake.status = RUNNING;
    
    // 速度初始化
    gSpeed = 300;
    gLevelSpeed = 300;
    gCurSpeed = 300;
    gMaxSpeed = 50;
    
    // 得分初始化
    gTopScore = getTopScore();
    gMyScore = 0;
}

// 游戏开始
void gameStart()
{
    // 白底黑字
    setColor(15);
    
    // 初始化参数
    init();
    
    // 画地图
    drawMap();
    
    // 画蛇
    drawSnake();
    
    // 画随机生成的食物
    drawFood();
    
    // 游戏运行中
    while(snake.status == RUNNING)
    {
        // 键盘事件
        keyPress();
        // 蛇移动
        moveSnake();
        // 吃食物
        eatFood();
        // 更新分数
        drawScore();
        // 休眠
        Sleep(gCurSpeed);
    }
    
    // 游戏结束但还未确定是否退出
    while(snake.status != EXIT)
    {
        Sleep(300);
        gameOver();
    }
    
}

// 画地图(注意墙是长=2宽=1的长方形字符)
void drawMap()
{
    // 清屏
    system("cls");
    
    setColor(2);
    // 画内部网格
    for (int i = 0; i < MAP_WIDTH; i+=2)
    {
        for (int j = 0; j < MAP_HEIGHT; j++)
        {
            setCursorPosition(i, j);
            printf(GRID);
        }
    }
    
    setColor(6);
    // 画左右墙
    for (int i = 0; i < MAP_HEIGHT; i++)
    {
        setCursorPosition(0,i);
        printf(BRICK);
        setCursorPosition(MAP_WIDTH - 2,i);        // 因为画上下边墙时取不到MAP_WIDTH,从0开始嘛。因此这里得减二,长=2,宽=1的长方形
        printf(BRICK);
    }
    
    // 画上下墙
    for (int i = 0; i < MAP_WIDTH; i+=2)
    {
        setCursorPosition(i,0);
        printf(BRICK);
        setCursorPosition(i,MAP_HEIGHT - 1);    // 因为画左右边墙时取不到MAP_HEIGHT,从0开始嘛。因此这里得减一
        printf(BRICK);
    }
    
    // 画得分
    drawScore();
    
    // 画提示
    drawTip();
}

// 画得分
void drawScore()
{
    setColor(15);
    setCursorPosition(MAP_WIDTH + 2,2);
    printf("最高得分:%5d",gTopScore);
    setCursorPosition(MAP_WIDTH + 2,4);
    printf("我的得分:%5d",gMyScore);
    setCursorPosition(MAP_WIDTH + 2,6);
    printf("我的速度:%5d米/秒",(2 * gSpeed - gCurSpeed));
}

// 画游戏提示
void drawTip()
{
    setColor(9);
    setCursorPosition(MAP_WIDTH + 2,9);
    printf(" 游戏提示");
    setCursorPosition(MAP_WIDTH + 2,10);
    printf("┌─────────────┐");
    setCursorPosition(MAP_WIDTH + 2,11);
    printf("│                          │");
    setCursorPosition(MAP_WIDTH + 2,12);
    printf("│每个食物得分:1 分        │");
    setCursorPosition(MAP_WIDTH + 2,13);
    printf("│                          │");
    setCursorPosition(MAP_WIDTH + 2,14);
    printf("│不能穿墙也不能咬到自己哦  │");
    setCursorPosition(MAP_WIDTH + 2,15);
    printf("│                          │");
    setCursorPosition(MAP_WIDTH + 2,16);
    printf("│用↑ ↓ ← → 控制蛇移动  │");
    setCursorPosition(MAP_WIDTH + 2,17);
    printf("│                          │");
    setCursorPosition(MAP_WIDTH + 2,18);
    printf("│同一方向连续点击可以加速  │");
    setCursorPosition(MAP_WIDTH + 2,19);
    printf("│                          │");
    setCursorPosition(MAP_WIDTH + 2,20);
    printf("│按空格可以暂停或继续游戏  │");
    setCursorPosition(MAP_WIDTH + 2,21);
    printf("│                          │");
    setCursorPosition(MAP_WIDTH + 2,22);
    printf("│按ESC键可以退出游戏       │");
    setCursorPosition(MAP_WIDTH + 2,23);
    printf("│                          │");
    setCursorPosition(MAP_WIDTH + 2,24);
    printf("└─────────────┘");
    setColor(6);
    setCursorPosition(MAP_WIDTH + 2,26);
    printf(" 作者:我是小木鱼(Lag)");
    setCursorPosition(MAP_WIDTH + 2,28);
    printf("                   2023.02.15");
}

// 画蛇
void drawSnake()
{
    // 画蛇头
    setColor(14);
    snake.nodePos[0].x = (MAP_WIDTH / 2);
    if (snake.nodePos[0].x % 2 != 0){snake.nodePos[0].x--;}        // x坐标必须是偶数
    snake.nodePos[0].y = MAP_HEIGHT / 2;
    setCursorPosition(snake.nodePos[0].x,snake.nodePos[0].y);
    printf(SNAKE_HEAD);
    // 画蛇身
    for (int i = 1; i < SNAKE_INIT_SIZE; i++)
    {
        snake.nodePos[i].x = snake.nodePos[i - 1].x + 2;
        snake.nodePos[i].y = snake.nodePos[i - 1].y;
        setCursorPosition(snake.nodePos[i].x,snake.nodePos[i].y);
        printf(SNAKE_BODY);
    }
}

// 画随机生成的食物
void drawFood()
{
    // rand() % 10 + i => [0-10) + i
    srand(time(NULL));    // 随机种子
    food.x = (rand() % ((MAP_WIDTH - 4)/ 2) + 1) * 2;    // 必须为偶数,否则蛇吃不到
    food.y = rand() % (MAP_HEIGHT - 2) + 1;
    // 判断是否与蛇重合
    for (int i = 0; i < snake.size; i++)
    {
        if (food.x == snake.nodePos[i].x && food.y == snake.nodePos[i].y)
        {
            drawFood();
            break;
        }
    }
    setCursorPosition(food.x,food.y);
    setColor(12);
    printf(FOOD);
}

// 键盘事件
void keyPress()
{
    // 判断键盘
    if (GetAsyncKeyState(VK_LEFT))    // 左键
    {
        if (snake.direction != DIR_RIGHT)    // 禁止调头
        {
            if (snake.direction == DIR_LEFT)
            {
                if (gCurSpeed > gMaxSpeed){gCurSpeed -= 10;}    // 方向相同,临时加速
            }
            else
            {
                snake.direction = DIR_LEFT;
                if (gCurSpeed != gLevelSpeed){gCurSpeed = gLevelSpeed;}        // 方向不同,恢复原来速度
            }
        }
    }
    else if(GetAsyncKeyState(VK_UP))    // 上键
    {
        if (snake.direction != DIR_DOWN)
        {
            if (snake.direction == DIR_UP)
            {
                if (gCurSpeed > gMaxSpeed){gCurSpeed -= 10;}    // 方向相同,临时加速
            }
            else
            {
                snake.direction = DIR_UP;
                if (gCurSpeed != gLevelSpeed){gCurSpeed = gLevelSpeed;}        // 方向不同,恢复原来速度
            }
        }
    }
    else if(GetAsyncKeyState(VK_RIGHT))        // 右键
    {
        if (snake.direction != DIR_LEFT)
        {
            if (snake.direction == DIR_RIGHT)
            {
                if (gCurSpeed > gMaxSpeed){gCurSpeed -= 10;}    // 方向相同,临时加速
            }
            else
            {
                snake.direction = DIR_RIGHT;
                if (gCurSpeed != gLevelSpeed){gCurSpeed = gLevelSpeed;}        // 方向不同,恢复原来速度
            }
        }
    }
    else if(GetAsyncKeyState(VK_DOWN))        // 下键
    {
        if (snake.direction != DIR_UP)
        {
            if (snake.direction == DIR_DOWN)
            {
                if (gCurSpeed > gMaxSpeed){gCurSpeed -= 10;}    // 方向相同,临时加速
            }
            else
            {
                snake.direction = DIR_DOWN;
                if (gCurSpeed != gLevelSpeed){gCurSpeed = gLevelSpeed;}        // 方向不同,恢复原来速度
            }
        }
    }
    else if(GetAsyncKeyState(VK_SPACE))        // 空格暂停
    {
        pause();
    }
    else if (GetAsyncKeyState(VK_ESCAPE))    // 退出
    {
        snake.status = GAMEOVER;
    }
    
}

// 移动蛇
void moveSnake()
{

    // 判断该方向是否可以移动(要发生碰撞)
    if (collide() == 0){return;}
    
    // 擦除最后一节
    setCursorPosition(snake.nodePos[snake.size - 1].x,snake.nodePos[snake.size - 1].y);
    setColor(2);
    printf(GRID);

    // 循环替换除蛇头外的其他节
    for (int i = snake.size - 1; i > 0; i--)
    {
        snake.nodePos[i].x = snake.nodePos[i - 1].x;
        snake.nodePos[i].y = snake.nodePos[i - 1].y;
        setCursorPosition(snake.nodePos[i].x,snake.nodePos[i].y);
        setColor(14);
        printf(SNAKE_BODY);
    }
    
    // 处理蛇头
    switch (snake.direction)
    {
        case DIR_UP:
            snake.nodePos[0].y--;
            break;
        case DIR_DOWN:
            snake.nodePos[0].y++;
            break;
        case DIR_LEFT:
            snake.nodePos[0].x-=2;
            break;
        case DIR_RIGHT:
            snake.nodePos[0].x+=2;
            break;
    }
    setCursorPosition(snake.nodePos[0].x,snake.nodePos[0].y);
    setColor(14);
    printf(SNAKE_HEAD);
}

// 判断蛇移动的下一个位置能否发生碰撞
int collide()
{
    int newX = snake.nodePos[0].x;
    int newY = snake.nodePos[0].y;
    
    switch (snake.direction)
    {
        case DIR_UP:
            newY = snake.nodePos[0].y - 1;
            break;
        case DIR_DOWN:
            newY = snake.nodePos[0].y + 1;
            break;
        case DIR_LEFT:
            newX = snake.nodePos[0].x - 2;
            break;
        case DIR_RIGHT:
            newX = snake.nodePos[0].x + 2;
            break;
    }
    
    // 判断是否撞墙
    if (newX < 2 || newX > MAP_WIDTH - 4 || newY < 1 || newY > MAP_HEIGHT - 2)
    {
        // 游戏失败
        snake.status = GAMEOVER;
        return 0;
    }
    
    // 判断是否撞自身
    for (int i = 1; i < snake.size; i++)
    {
        if (newX == snake.nodePos[i].x && newY == snake.nodePos[i].y)
        {
            // 游戏失败
            snake.status = GAMEOVER;
            return 0;
        }
    }
    
    return 1;
}

// 吃食物
void eatFood()
{
    if (snake.nodePos[0].x == food.x && snake.nodePos[0].y == food.y)
    {
        // 蛇长一节
        if (snake.size < SNAKE_MAX_SIZE)
        {
            snake.nodePos[snake.size].x = snake.nodePos[snake.size - 1].x;
            snake.nodePos[snake.size].y = snake.nodePos[snake.size - 1].y;
            setCursorPosition(snake.nodePos[snake.size].x,snake.nodePos[snake.size].y);
            setColor(14);
            printf(SNAKE_BODY);
            snake.size++;
        }
        
        // 记录得分
        gMyScore++;
        
        // 判断速度是否加快
        if (gMyScore % 10 == 0)        // 10个为一关
        {
            if (gLevelSpeed > gMaxSpeed)
            {
                gLevelSpeed    -= 50;
                gCurSpeed = gLevelSpeed;
            }
        }
        
        // 重新生成食物
        drawFood();
    }
}

// 游戏暂停
void pause()
{
    while (1)
    {
        Sleep(300);
        if (GetAsyncKeyState(VK_SPACE)){break;}        // 再按一次空格继续游戏
    }
}

// 游戏结束
void gameOver()
{
    if (snake.status == RUNNING){return;}
    
    // 记录得分
    if (gMyScore > gTopScore){setTopScore(gMyScore);}
    
    // 胜败乃兵家常事,大虾请重新来过吧!
    setColor(136);
    for (int i = 10; i <= MAP_WIDTH - 12; i+=2)
    {
        for (int j = 9; j <= MAP_HEIGHT - 11; j++)
        {
            setCursorPosition(i, j);
            printf(GRID);
        }
    }
    setColor(119);
    for (int i = 12; i <= MAP_WIDTH - 14; i+=2)
    {
        for (int j = 10; j <= MAP_HEIGHT - 12; j++)
        {
            setCursorPosition(i, j);
            printf(GRID);
        }
    }
    setColor(116);
    setCursorPosition(14, 13);
    printf("胜败乃兵家常事,大虾请重新来过吧!");
    setCursorPosition(14, 15);
    printf("    是否重新开始游戏(Y/N)?");
    char key;
    scanf("%c",&key);
    if (key == 'y' || key == 'Y')
    {
        getchar();    // 吸收回车键
        gameStart();
        return;
    }
    else
    {
        getchar();
        snake.status = EXIT;
        setColor(0);
        system("cls");
        return;
    }
    
}

下载:

这个就不需要了吧,代码就这么几行而已。

猜你喜欢

转载自blog.csdn.net/lag_csdn/article/details/129045318