【C/C++实战项目】贪吃蛇游戏

环境:windows系统,VS2019编译器

项目分析

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

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

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

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

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

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

项目效果

snake.h头文件

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

#pragma once 
#pragma warning (disable:4996) //消除警告
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <string.h>
#include <time.h>
#include <conio.h>

#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;
}

  • 17
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
好的,以下是一个简单的 C++ 贪吃蛇游戏示例代码: ```c++ #include <iostream> #include <conio.h> #include <windows.h> using namespace std; bool gameOver; const int width = 20; const int height = 20; int x, y, fruitX, fruitY, score; int tailX[100], tailY[100], nTail; enum eDirection { STOP = 0, LEFT, RIGHT, UP, DOWN }; eDirection dir; void Setup() { gameOver = false; dir = STOP; x = width / 2; y = height / 2; fruitX = rand() % width; fruitY = rand() % height; score = 0; } void Draw() { system("cls"); // 清屏 for (int i = 0; i < width + 2; i++) cout << "#"; cout << endl; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { if (j == 0) cout << "#"; if (i == y && j == x) cout << "O"; else if (i == fruitY && j == fruitX) cout << "F"; else { bool print = false; for (int k = 0; k < nTail; k++) { if (tailX[k] == j && tailY[k] == i) { cout << "o"; print = true; } } if (!print) cout << " "; } if (j == width - 1) cout << "#"; } cout << endl; } for (int i = 0; i < width + 2; i++) cout << "#"; cout << endl; cout << "Score: " << score << endl; } void Input() { if (_kbhit()) { switch (_getch()) { case 'a': dir = LEFT; break; case 'd': dir = RIGHT; break; case 'w': dir = UP; break; case 's': dir = DOWN; break; case 'x': gameOver = true; break; } } } void Logic() { int prevX = tailX[0]; int prevY = tailY[0]; int prev2X, prev2Y; tailX[0] = x; tailY[0] = y; for (int i = 1; i < nTail; i++) { prev2X = tailX[i]; prev2Y = tailY[i]; tailX[i] = prevX; tailY[i] = prevY; prevX = prev2X; prevY = prev2Y; } switch (dir) { case LEFT: x--; break; case RIGHT: x++; break; case UP: y--; break; case DOWN: y++; break; default: break; } if (x > width || x < 0 || y > height || y < 0) gameOver = true; for (int i = 0; i < nTail; i++) if (tailX[i] == x && tailY[i] == y) gameOver = true; if (x == fruitX && y == fruitY) { score += 10; fruitX = rand() % width; fruitY = rand() % height; nTail++; } } int main() { Setup(); while (!gameOver) { Draw(); Input(); Logic(); Sleep(100); // 控制游戏速度 } return 0; } ``` 这个游戏使用了 Windows.h 库来实现 `Sleep()` 函数,控制游戏速度。如果你在 Mac 或 Linux 上运行代码,可以使用 `<unistd.h>` 库来替换 `Sleep()` 函数。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AllinTome

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值