【C语言】贪吃蛇游戏

环境:windows系统,VS2019编译器

项目分析

贪吃蛇游戏机制是通过控制蛇上下左右移动并吃到食物得分

障碍物随机生成,蛇头碰到墙壁、障碍物或者碰到蛇身就游戏结束

食物随机生成,蛇吃到食物之后蛇身变长,蛇速加快

代码运行后可自定义障碍物数量、蛇初始速度、蛇身初识长度

主函数界面有开始游戏、游戏设置、退出游戏三个选项,通过键盘上下键控制选择

读写文件记录并显示当前得分和历史最高分

项目效果

【C语言】贪吃蛇游戏_第1张图片
【C语言】贪吃蛇游戏_第2张图片
【C语言】贪吃蛇游戏_第3张图片
【C语言】贪吃蛇游戏_第4张图片

snake.h头文件

头文件包含、宏定义、外部变量声明、函数声明

#pragma once 
#pragma warning (disable:4996) //消除警告
#include 
#include 
#include 
#include 
#include 
#include 

#define ROW 22
#define COL 42

#define EMPTY 0
#define WALL 1
#define FOOD 2
#define HEAD 3
#define BODY 4

#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
#define SPACE 32
#define ESC 27
#define ENTER 13

#define RATE 3000    //速度

typedef struct Body {
    int x, y;
}Body;

typedef struct Snake {
    int len;
    int x, y;
    Body* body;
}Snake;

extern int g_map[ROW][COL];
extern int g_max, g_grade;    //历史最高分,当前得分
extern Snake snake;
extern int g_len;                //蛇初识长度
extern int g_wall, g_food, g_snake;        //颜色
extern int g_obstacle;            //障碍物数量
extern int g_rate;                //速度

void CursorJump(int x, int y);        //光标跳转
void Color(int x);        //颜色设置
void ReadGrade();        //从文件读取最高分
void WriteGrade();        //更新最高分到文件
void InitMap();            //初始化地图
void InitSnake();        //初始化蛇
void RandFood();        //随机生成食物
void RandObstacle();    //随机生成障碍物
void DrawSnake(int flag);    //打印蛇
void MoveSnack(int x, int y);        //移动蛇
void JudgeGame(int x, int y);        //判断得分与结束
void Run(int x, int y);                //执行按键
void Game();            //游戏流程
void MainGame();        //主函数游戏流程
void Main();            //主函数直接调用,代替主函数执行代码,方便回调

snake.c源文件

文件读写

读档和存档游戏的最高分记录

#include "snake.h"

void CursorJump(int x, int y) {
    COORD pos;    //定义光标位置的结构体变量
    pos.X = x;
    pos.Y = y;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);        //设置光标位置
}

void Color(int x) {
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);    //设置颜色
    // 6——土黄色    7——白色    10——绿色    12——红色
}

void ReadGrade() {
    FILE* pf = fopen("snack.txt", "rb");
    if (pf == NULL) {
        pf = fopen("snack.txt", "wb");
        fwrite(&g_max, sizeof(int), 1, pf);
    }
    fseek(pf, 0, SEEK_SET);        //使文件指针pf指向文件开头
    fread(&g_max, sizeof(int), 1, pf);
    fclose(pf);
    pf = NULL;
}

void WriteGrade() {
    FILE* pf = fopen("snack.txt", "wb");
    if (pf == NULL) {
        perror("writeGrade::fopen");
        exit(1);
    }
    fwrite(&g_grade, sizeof(int), 1, pf);
    fclose(pf);
    pf = NULL;
}

初始化

初始化地图、蛇

void InitMap() {
    Color(g_wall);
    int i, j;
    for (i = 0; i < ROW; ++i) {
        for (j = 0; j < COL; ++j) {
            if (j == 0 || j == COL - 1) {
                g_map[i][j] = WALL;
                CursorJump(2 * j, i);
                printf("■");
            }
            else if (i == 0 || i == ROW - 1) {
                CursorJump(2 * j, i);
                g_map[i][j] = WALL;
                printf("■");
            }
            else {
                g_map[i][j] = EMPTY;
                CursorJump(2 * j, i);
                printf("  ");
            }
        }
    }
    Color(7);
    CursorJump(0, ROW);
    printf("当前得分: %d", g_grade);
    CursorJump(2 * COL - 18, ROW);
    printf("历史最高分: %d\0", g_max);
}

void InitSnake() {
    snake.len = g_len;    //蛇身初识长度
    snake.x = COL / 2;
    snake.y = ROW / 2;    //蛇头u坐标
    g_map[snake.y][snake.x] = HEAD;
    snake.body = (Body*)malloc(sizeof(Body) * ROW * COL);
    if (snake.body == NULL) {
        perror("InitSnack::malloc");
        exit(1);
    }
    memset(snake.body, 0, sizeof(Body) * ROW * COL);
    int i = 0;
    while (i < g_len) {
        snake.body[i].x = snake.x - (i + 1);
        snake.body[i].y = snake.y;
        g_map[snake.body[i].y][snake.body[i].x] = BODY;
        ++i;
    }    //初始化蛇身
    g_rate = RATE;    //初始化速度
}

随机生成

随机生成食物和障碍物,障碍物只是游戏开始时随机生成一次

void RandFood() {
    int row, col;
    do {
        row = rand() % ROW;
        col = rand() % COL;
    } while (g_map[row][col] != EMPTY);
    g_map[row][col] = FOOD;
    Color(g_food);
    CursorJump(2 * col, row);
    printf("●");
}

void RandObstacle() {
    Color(8);    //灰色
    int obstacle = g_obstacle;
    int row, col;
    while (obstacle) {
        do {
            row = rand() % ROW;
            col = rand() % COL;
        } while (g_map[row][col] != EMPTY);
        g_map[row][col] = WALL;
        CursorJump(2 * col, row);
        printf("■");
        --obstacle;
    }
}

打印蛇与移动蛇

打印蛇分为覆盖和打印,覆盖是指将蛇尾用空格覆盖

蛇每移动一次都是要经过一次覆盖一次打印,先将蛇尾覆盖为空格,再将蛇前移之后打印出来

覆盖蛇的时候注意,蛇吃到食物之后身体变长,此时蛇尾不覆盖

void DrawSnake(int flag) {
    if (flag == 1) {
        //打印蛇
        Color(g_snake);
        CursorJump(2 * snake.x, snake.y);
        printf("■");    //打印蛇头
        int i = 0;
        while (i < snake.len) {
            CursorJump(2 * snake.body[i].x, snake.body[i].y);
            printf("□");
            ++i;
        }    //打印蛇身
    }
    else {
        //覆盖蛇
        if (snake.body[snake.len - 1].x != 0) {
            CursorJump(2 * snake.body[snake.len - 1].x, snake.body[snake.len - 1].y);
            printf("  ");    //将蛇尾覆盖为空格
        }
    }
}

void MoveSnack(int x, int y) {
    DrawSnake(0);    //覆盖蛇
    int tail = snake.len - 1;
    g_map[snake.body[tail].y][snake.body[tail].x] = EMPTY;    //将蛇尾标记为空
    g_map[snake.y][snake.x] = BODY;    //蛇移动后蛇头变为蛇身
    while (tail) {
        snake.body[tail].x = snake.body[tail - 1].x;
        snake.body[tail].y = snake.body[tail - 1].y;
        --tail;
    }
    snake.body[0].x = snake.x;
    snake.body[0].y = snake.y;
    snake.x += x;
    snake.y += y;    //蛇头位置更新
    DrawSnake(1);    //打印蛇
}

判断得分或游戏结束

若蛇头的下一步是食物,则得分,蛇身变长,蛇速变快

若蛇头的下一步是墙或者障碍物或者蛇身,则游戏结束,根据得分判断是否打破纪录

游戏结束之后询问玩家是否继续游戏

void JudgeGame(int x, int y) {
    if (g_map[snake.y + y][snake.x + x] == FOOD) {
        //蛇头下一步位置是食物
        //得分,蛇身变长,蛇速变快
        ++snake.len;
        g_grade += 10;
        if (g_rate > 1000) {
            g_rate -= 50;
        }
        Color(7);
        CursorJump(0, ROW);
        printf("当前得分: %d", g_grade);    //重新打印得分
        RandFood();    //重新生成食物
    }
    else if (g_map[snake.y + y][snake.x + x] == WALL
        || g_map[snake.y + y][snake.x + x] == BODY) {
        Sleep(1000);    //暂停1秒,给玩家反应时间
        system("cls");
        Color(7);
        CursorJump(2 * COL / 3, ROW / 2 - 3);
        if (g_grade > g_max) {
            printf("恭喜你创下新纪录: %d", g_grade);
            WriteGrade();
        }
        else {
            printf("请继续加油,本次得分为: %d", g_grade);
        }
        CursorJump(2 * COL / 3, ROW / 2);
        printf("GAME OVER");
        while (1) {
            //询问玩家是否再来一局
            char input;
            CursorJump(2 * COL / 3, ROW / 2 + 3);
            printf("                                         ");
            CursorJump(2 * COL / 3, ROW / 2 + 3);
            printf("再次挑战?(y/n) ");
            scanf("%c", &input);
            switch (input) {
            case 'y':
            case 'Y':
                system("cls");
                MainGame();
                break;
            case 'n':
            case 'N':
                Main();
                break;
            default:
                break;
            }
        }
    }
}

按键执行

按键执行调用 conio.h 里面的kbhit() 函数,通过循环控制,若无按键则蛇自动走下一步

kbhit() 函数可能会引发编译器的报错,用#pragma warning (disable:4996) //消除警告

键盘敲击,则执行键盘敲击的操作,若无键盘敲击则蛇按照方向往前走

void Run(int x, int y) {
    int t = 0;
    while (1) {
        if (t == 0) {
            t = g_rate;
        }
        while (--t) {
            //键盘被敲击,退出循环
            if (kbhit() != 0) {
                break;                
            }
        }
        if (t == 0) {
            //键盘未敲击,判断得分与移动蛇
            JudgeGame(x, y);
            MoveSnack(x, y);
        }
        else {
            break; //键盘被敲击,返回Game()函数
        }
    }
}

游戏操作

wsad、上下左右箭头:控制蛇移动的上下左右方向

space空格键:游戏暂停

r、R:重新开始

ESC:退出游戏

每一个键位的键值对在头文件进行宏定义

void Game() {
    int dir = RIGHT;    //默认初识方向向右
    int tmp = dir;    //记录蛇移动的方向
    while (1) {
        switch (dir) {
        case 'w':
        case 'W':
        case UP:
            Run(0, -1);    //向上移动,x不变,y-1
            tmp = UP;
            break;
        case 's':
        case 'S':
        case DOWN:
            Run(0, 1);
            tmp = DOWN;
            break;
        case 'a':
        case 'A':
        case LEFT:
            Run(-1, 0);
            tmp = LEFT;
            break;
        case 'd':
        case 'D':
        case RIGHT:
            Run(1, 0);
            tmp = RIGHT;
            break;
        case SPACE:
            system("pause>nul");    //暂停后按任意按键继续
            break;
        case ESC:
            system("cls");
            Color(7); //颜色设置为白色
            CursorJump(COL - 8, ROW / 2);
            printf("  游戏结束  ");
            CursorJump(COL - 8, ROW / 2 + 2);
            exit(0);
        case 'r':
        case 'R':
            system("cls");
            MainGame();
        }
        dir = getch();
        switch (dir) {
        case 'w':
        case 'W':
        case UP:
        case 's':
        case 'S':
        case DOWN:
            if (tmp == UP || tmp == DOWN) {
                dir = tmp;
            }
            break;
        case 'a':
        case 'A':
        case LEFT:
        case 'd':
        case 'D':
        case RIGHT:
            if (tmp == LEFT || tmp == RIGHT) {
                dir = tmp;
            }
            break;
        case SPACE:
        case ESC:
        case 'r':
        case 'R':
            break;    //这是个按键无需调整
        default:
            dir = tmp;
        }
    }
}

游戏流程

  • 读取历史最高分

  • 初始化地图、蛇

  • 随机生成食物、障碍物

  • 打印蛇

  • 游戏操作

void MainGame() {
    ReadGrade();        //读取历史最高分
    InitMap();
    InitSnake();
    RandFood();
    RandObstacle();
    DrawSnake(1);
    Game();
}

自定义游戏属性

自定义障碍物数量、蛇速、蛇身长度

尽量保证合理性

void GameSetting() {
    system("cls");
    CursorJump(2 * COL / 3, ROW / 2 - 5);
    printf("设置障碍物数量 ( <=50 ): ");
    scanf("%d", &g_obstacle);
    while (g_obstacle > 50) {
        CursorJump(2 * COL / 3, ROW / 2 + 2);
        printf("                                 ");
        CursorJump(2 * COL / 3, ROW / 2 - 3);
        printf("障碍物过多,请重新设置: ");
        scanf("%d", &g_obstacle);
    }
    CursorJump(2 * COL / 3, ROW / 2);
    printf("设置速度( >=1000 ): ");
    scanf("%d", &g_rate);
    while (g_rate < 1000) {
        CursorJump(2 * COL / 3, ROW / 2 + 2);
        printf("                                 ");
        CursorJump(2 * COL / 3, ROW / 2 + 2);
        printf("速度过快,请重新设置: ");
        scanf("%d", &g_rate);
    }
    CursorJump(2 * COL / 3, ROW / 2 + 5);
    printf("设置蛇身初始长度( >= 1): ");
    scanf("%d", &g_len);
    while (g_len < 1) {
        CursorJump(2 * COL / 3, ROW / 2 + 2);
        printf("                                 ");
        CursorJump(2 * COL / 3, ROW / 2 + 7);
        printf("蛇身太短,请重新设置: ");
        scanf("%d", &g_rate);
    }
    Main();        //回到主程序函数
}

初始界面设计

void PrintFrame(int col, int row) {
    system("cls");
    CursorJump(col, ROW / 2 - 5);
    printf("开始游戏");
    CursorJump(col, ROW / 2);
    printf("游戏设置");
    CursorJump(col, ROW / 2 + 5);
    printf("退出游戏");
    CursorJump(col - 2,  row - 1);
    printf("*----------*");
    CursorJump(col - 2, row);
    printf("| ");
    CursorJump(col + 8, row);
    printf(" |");
    CursorJump(col - 2, row + 1);
    printf("*----------*");
    CursorJump(2 * COL - 14, ROW - 1);
    printf(" ↑↓ 选择");
    CursorJump(2 * COL - 14, ROW);
    printf("Enter 确认");
}

void Main() {
    srand((unsigned int)time(NULL));
    system("title 贪吃蛇");
    system("mode con cols=84 lines=23");    //设置终端窗口大小
    CONSOLE_CURSOR_INFO curInfo;    //光标信息结构体变量
    curInfo.dwSize = 1;
    curInfo.bVisible = FALSE;        //光标光标隐藏不可见
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &curInfo);                //设置光标信息
    Color(7);
    int col = 2 * COL / 3 + 5, row = ROW / 2 - 5;
    PrintFrame(col, row);
    char input;
    while (1) {
        while (1) {
            if (kbhit() != 0) {
                break;
            }
        }
        input = getch();
        switch (input) {
        case UP:
            if (row == ROW / 2 - 5) {
                row = ROW / 2 + 5;
            }
            else {
                row -= 5;
            }
            PrintFrame(col, row);
            break;
        case DOWN:
            if (row == ROW / 2 + 5) {
                row = ROW / 2 - 5;
            }
            else {
                row += 5;
            }
            PrintFrame(col, row);
            break;
        case ENTER:
            if (row == ROW / 2 - 5) {
                MainGame();
            }
            else if (row == ROW / 2) {
                GameSetting();
            }
            else {
                system("cls");
                CursorJump(2 * COL / 3 + 5, ROW / 2);
                printf("退出游戏");
                CursorJump(0, ROW);
                exit(0);
            }
            break;
        default:
            break;
        }
    }
}

main.c源文件

#include "snake.h"

int g_map[ROW][COL];
int g_max = 0, g_grade = 0;
Snake snake;
int g_len = 2;        //蛇初识长度默认为2
int g_wall = 6, g_food = 12, g_snake = 10;    //默认颜色设置
int g_obstacle = 5;    //默认障碍物数量
int g_rate = RATE;    //默认初始速度为3000,rate值越小速度越快,rate >= 2000

int main() {
    Main();
    return 0;
}

你可能感兴趣的:(主线篇,C++后端开发,游戏,c语言,c++,算法)