C++贪食蛇

这是一个在windows控制台上运行的程序,游戏分为四个难度,其中简单难度和贪食蛇基本没有区别,只是可以穿越墙壁。另外三个难度加入了陷阱,吃到陷阱长度减3,下面直接贴上整份源代码,注释的比较详细了


#include <iostream>
#include <string>
#include <vector>
#include <deque>
#include <ctime>
#include <algorithm>
#include <windows.h>
#include <conio.h>
//#define _CRT_SECURE_NO_WARNINGS //visual studio无法使用sprinf,需要加入该行
using namespace std;
const int N = 30, M = 120;
const int ObjWidth = 2; //物品宽度,若部分电脑出线乱码,可以修改为1,并使用下面注释掉的图案
const char *const str[] = {"  ", "■", "●", "□", "★", "×"};
//const char *const str[] = {" ", "#", "O", "o", "F", "X"};
char ScreenData[N][M]; //储存需要被显示的内容
int ScreenColor[N][M]; //储存需要显示的字体颜色
int ConsoleColor[2][N][M]; //储存修改之前的颜色
HANDLE hOutbuf[2]; //输出缓冲区
int bufid; //当前激活的缓冲区,通过每次异或1来进行0/1之间的来回切换
CONSOLE_CURSOR_INFO cur;
void ProgramInit() { //程序初始化
    srand(time(nullptr));
    SetConsoleTitleA("贪食蛇■●□★×"); //设置控制台窗口,顺便检测是否会乱码
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < M; ++j) {
            ScreenColor[i][j] = 0x0f; //初始化为黑底白字
        }
    }
    bufid = 0;
    hOutbuf[0] = hOutbuf[1] = CreateConsoleScreenBuffer( //创建两个控制台缓冲区
        GENERIC_READ | GENERIC_WRITE, //控制台缓冲安全与访问权限
        FILE_SHARE_READ | FILE_SHARE_WRITE, //定义缓冲区可共享权限
        nullptr, //安全属性默认为NULL
        CONSOLE_TEXTMODE_BUFFER, //缓冲区类型,固定参数
        nullptr
    );
    cur.bVisible = 0; cur.dwSize = 1; //用于隐藏光标
    SetConsoleCursorInfo(hOutbuf[0], &cur);
    SetConsoleCursorInfo(hOutbuf[1], &cur);
    SetWindowLongPtrA( //禁用调整控制台大小,若可以手动扩大控制台大小,未知的原因会导致程序崩溃且进程不正常结束,需要任务管理器来杀死进程
        GetConsoleWindow(),
        GWL_STYLE,
        GetWindowLongPtrA(GetConsoleWindow(),GWL_STYLE)
        & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX
    );
}

void ShowConsole() { //在控制台中显示新的画面
    DWORD bytes = 0; COORD coord;
    bufid ^= 1; //再0和1之间切换,切换到背后的缓冲区
    for (short i = 0; i < N; ++i) {
        coord = {0, i};
        WriteConsoleOutputCharacterA(hOutbuf[bufid], ScreenData[i], M, coord, &bytes); //更新一行
        for (short j = 0; j < M; ++j) {
            coord.X = j;
            if (ConsoleColor[bufid][i][j] != ScreenColor[i][j]){ //若和之前颜色不同则需要更改颜色
                FillConsoleOutputAttribute(hOutbuf[bufid], ScreenColor[i][j], 1, coord, &bytes); //设置颜色
                ConsoleColor[bufid][i][j] = ScreenColor[i][j];
            }
        }
    }
    SetConsoleActiveScreenBuffer(hOutbuf[bufid]);
}
enum Obj { //表示地图上的物品
    Empty = 0,
    Wall = 1,
    Head = 2,
    Body = 3,
    Bean = 4,
    Trap = 5,
};
const int n = 20, m = 20;
const int sx = 1, sy = 1;
struct P { //点类
    int x, y;
    P (int a = 0, int b = 0) : x(a), y(b) {}
    P operator + (const P &b) const { return P(x + b.x, y + b.y); }
    bool operator < (const P &b) const { return x < b.x || x == b.x && y < b.y; }
};
P HeadPos; //蛇头位置
P BeanPos; //豆豆位置,用于快捷删除豆子
enum Direction { //表示方向的下标
    Up = 0,
    Right = 1,
    Down = 2,
    Left = 3
} dir, lastdir; //当前方向,上一步的方向
P dx[] = {P(-1, 0), P(0, 1), P(1, 0), P(0, -1)}; //表示四个方向的向量
Obj Map[25][25]; //储存游戏地图,下标范围是[1~n][1~m],[i][j]表示第i行第j列,最外圈为墙壁
deque<P>dq; //储存蛇身的队列,dq.size()表示长度,维护的位置队列实际上没有作用,但是可能会用于扩展功能
enum Difficulty { //表示游戏困难度
    Easy = 0,   //简单,适合手残党或者想获得很长长度的玩家
    Medium = 1, //中等,适合大多数玩家,加入了3个陷阱
    Hard = 2,   //困难,适合大多数玩家,加入了7个陷阱,游戏节奏较快
    Demon = 3,  //恶魔,适合高玩,15个陷阱和超快的速度可能会导致你难以获得超过7的分数
} difficulty;
int MaxTrapCnt[] = {0, 3, 7, 15}; //最大陷阱数量
int TrapCnt; //当前陷阱数量,用于判断是否需要刷新陷阱
// 速度用int表示,速度实际上指的是时钟周期,即每过多久会走一步
int BaseSpeed[] = {450, 380, 310, 250}; //基础速度
int Acceleration[] = {10, 20, 25, 50}; //加速度
int MaxSpeed[] = {220, 180, 135, 100}; //最大速度
int MoveTime; //实际移动一次的时间间隔
int MaxScore; //储存最高分数,因为吃到陷阱会减分,最终得分应该为最高得分
int EmptyMoveCnt; //储存空移动次数,当空移动80次后刷新豆子位置
enum GAMESTATE {
    End = 0,
    Run = 1,
    Pause = 2,
} GameState;
void UpdateMap() { //把地图内容更新到ScreenData数组,并根据内容赋值对应颜色
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            strcpy(ScreenData[sx + i] + sy + j * ObjWidth, str[Map[i][j]]);
            for (int k = 0; k < ObjWidth; ++k) {
                ScreenColor[sx + i][sy + j * ObjWidth + k] = 0x60;
                switch (Map[i][j]) {
                    case Empty: {
                        break;
                    }
                    case Wall: {
                        break;
                    }
                    case Head: {
                        ScreenColor[sx + i][sy + j * ObjWidth + k] |= 0x01;
                        break;
                    }
                    case Body: {
                        ScreenColor[sx + i][sy + j * ObjWidth + k] |= 0x09;
                        break;
                    }
                    case Bean: {
                        ScreenColor[sx + i][sy + j * ObjWidth + k] |= 0x04;
                        break;
                    }
                    case Trap: {
                        break;
                    }
                }
            }
        }
    }
}

void UpdateUI() { //更新UI
    vector<string>v_ui;
    static char s[30];
    switch (GameState) {
        case End: {
            sprintf(s, "游戏状态 : 结束");
            break;
        }
        case Run: {
            sprintf(s, "游戏状态 : 运行");
            break;
        }
        case Pause: {
            sprintf(s, "游戏状态 : 暂停");
            break;
        }
    }
    v_ui.emplace_back(s);
    sprintf(s, "当前智商 = %3d", int(dq.size()) - 1);
    v_ui.emplace_back(s);
    sprintf(s, "最高智商 = %3d", MaxScore);
    v_ui.emplace_back(s);
    const int sx1 = sx + n + 2, sy1 = sy + 10;
    for (unsigned i = 0; i < v_ui.size(); ++i) {
        for (unsigned j = 0; j < v_ui[i].length(); ++j) {
            ScreenColor[sx1 + i][sy1 + j] = 0x07;
            ScreenData[sx1 + i][sy1 + j]  = v_ui[i][j];
        }
    }
}

void UpdateScreen() { //更新屏幕显示
    UpdateMap();
    UpdateUI();
    ShowConsole();
}

void GenerateBean() { //随机生成一个豆豆
    vector<P>v;
    for (int i = 2; i < n; ++i) {
        for (int j = 2; j < m; ++j) {
            if (Map[i][j] == Empty) {
                v.emplace_back(i, j);
            }
        }
    }
    int i = rand() % v.size();
    Map[v[i].x][v[i].y] = Bean;
    BeanPos = P(v[i].x, v[i].y);
}

void GenerateTrap(int cnt = MaxTrapCnt[difficulty]) { //随机生成cnt个陷阱
    vector<P>v;
    for (int i = 2; i < n; ++i) {
        for (int j = 2; j < m; ++j) {
            if (Map[i][j] == Empty) {
                v.emplace_back(i, j);
            }
            else if (Map[i][j] == Trap){
                Map[i][j] = Empty;
            }
        }
    }
    random_shuffle(v.begin(), v.end());
    for (int i = 0; i < cnt && i < v.size(); ++i) {
        Map[v[i].x][v[i].y] = Trap;
    }
}

void ShowHelp() { //显示游戏帮助
    vector<string>v_help;
    static char s[40];
    v_help.emplace_back("游戏帮助:");
    v_help.emplace_back("按p暂停,按esc退出");
    v_help.emplace_back("按方向键或者wsad控制移动");
    sprintf(s, "%s表示蛇头,%s表示蛇身", str[Head], str[Body]);
    v_help.emplace_back(s);
    sprintf(s, "速度随着长度增长而加快,不同难度速度上限不同");
    v_help.emplace_back(s);
    sprintf(s, "%s表示豆子,吃到一个豆子蛇身长度+1", str[Bean]);
    v_help.emplace_back(s);
    sprintf(s, "%s表示陷阱,吃到一个陷阱蛇身长度-3", str[Trap]);
    v_help.emplace_back(s);
    v_help.emplace_back("可以穿墙,但会导致蛇身长度-1");
    v_help.emplace_back("80步没吃到豆豆,豆豆会自动刷新位置");
    v_help.emplace_back("吃到2个陷阱后,会重新生成陷阱");
    v_help.emplace_back("蛇头撞到蛇身,游戏结束");
    v_help.emplace_back("作者 : Markyyz");
    const int sx1 = sx + 5, sy1 = sy + m * ObjWidth + 5;
    for (int i = 0; i < v_help.size(); ++i) {
        sprintf(ScreenData[sx1 + i] + sy1, "%s", v_help[i].c_str());
        for (int j = 0; j < v_help[i].length(); ++j) {
            ScreenColor[sx1 + i][sy1 + j] = 0x06;
        }
    }
}

void Initial() { //游戏初始化
    memset(ScreenData, ' ', sizeof ScreenData);
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < M; ++j) {
            ScreenColor[i][j] = 0x0f;
        }
    }
    ShowHelp();
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            Map[i][j] = Empty;
        }
    }
    for (int i = 1; i <= n; ++i) {
        Map[i][1] = Map[i][m] = Wall;
    }
    for (int i = 1; i <= m; ++i) {
        Map[1][i] = Map[n][i] = Wall;
    }
    MaxScore = 0;
    EmptyMoveCnt = 0;
    HeadPos = P(n / 2, 4); //固定起点
    Map[HeadPos.x][HeadPos.y] = Head;
    dq.clear();
    dq.push_back(HeadPos);
    dir = Right;
    GenerateBean();
    GenerateTrap();
    TrapCnt = MaxTrapCnt[difficulty];
    GameState = Run;
    UpdateScreen();
}

void ChangeDir(int c) { //更新运动方向
    Direction tdir = dir;
    switch (c) {
        case 'w':
        case 72: { //up
            tdir=Up;
            break;
        }
        case 's':
        case 80: { //down
            tdir=Down;
            break;
        }
        case 'a':
        case 75: { //left
            tdir=Left;
            break;
        }
        case 'd':
        case 77: { //right
            tdir=Right;
            break;
        }
    }
    //只有三个方向是合法的,使用lastdir可以避免在一个时钟周期内连续多次输入从而完成原地掉头的现象
    if ((tdir - lastdir + 4) % 4 != 2) {
        dir = tdir;
    }
}

int Move() { //移动函数,每过一个时钟周期会调用一次
    P tp = HeadPos + dx[dir]; //根据方向计算下一步的位置
    if (tp.x <= 1 || tp.x >= n || tp.y <= 1 || tp.y >= m) { //穿越边界,从另一侧出现
        tp.x = 2 + (tp.x - 2 + (n - 2)) % (n - 2);
        tp.y = 2 + (tp.y - 2 + (m - 2)) % (m - 2);
        if (dq.size() > 1) { //穿越边界会减少一节身体,不会删除头部
            P p = dq.front();
            Map[p.x][p.y] = Empty;
            dq.pop_front();
        }
    }
    Obj ret = Map[tp.x][tp.y];
    switch (Map[tp.x][tp.y]) {
        case Bean: { //吃掉豆豆,只要移动头,尾巴不减少
            Map[HeadPos.x][HeadPos.y] = Body;
            EmptyMoveCnt = 0; //清除空移动次数
            GenerateBean(); //再生成一个豆豆
            break;
        }
        case Trap: { //撞到陷阱
            for (int i = 0; i < 3; ++i) { //去掉3个尾巴,不足的话至少保留一个头
                if (dq.size() <= 1) break;
                P p = dq.front();
                Map[p.x][p.y] = Empty;
                dq.pop_front();
            }
            Map[HeadPos.x][HeadPos.y] = Body;
            P p = dq.front();
            Map[p.x][p.y] = Empty;
            dq.pop_front();
            --TrapCnt;
            if (TrapCnt + 1 < MaxTrapCnt[difficulty]) { //若踩了两个陷阱,则需要重新生成所有陷阱
                TrapCnt = MaxTrapCnt[difficulty];
                GenerateTrap();
            }
            break;
        }
        case Empty: { //空移动,需要去除尾巴
            Map[HeadPos.x][HeadPos.y] = Body;
            P p = dq.front();
            Map[p.x][p.y] = Empty;
            dq.pop_front();
            ++EmptyMoveCnt;
            if (EmptyMoveCnt >= 80) { //若空移动次数多余80,则需要变换豆豆的位置
                EmptyMoveCnt = 0; //清除空移动次数
                Map[BeanPos.x][BeanPos.y] = Empty; //原先豆豆的位置变为空气
                GenerateBean();
            }
            break;
        }
        case Body: { //撞到蛇身,游戏结束
            return -1;
        }
        default:{
            break;
        }
    }
    HeadPos = tp; //最后更新头部,因为所有情况头的变化相同
    dq.push_back(HeadPos);
    Map[HeadPos.x][HeadPos.y] = Head;
    MaxScore = max(MaxScore, int(dq.size()) - 1); //移动完后更新显示内容
    return int(ret);
}

int Input() { //检测键盘输入
    if (kbhit()) { //非阻塞函数,检测有键盘按下
        int key = getch(); //获取键盘信息
        switch (key) {
            case 'w':
            case 72: //up
            case 's':
            case 80: //down
            case 'a':
            case 75: //left
            case 'd':
            case 77: { //right
                ChangeDir(key); //更新方向
                break;
            }
            case 'p': { //pause
                GameState = Pause;
                break;
            }
            case 27: { //esc
                GameState = End;
                return -1;
            }
        }
    }
    return 0;
}

void Game() { //游戏主函数
    while (true) {
        clock_t temp = clock();
        lastdir = dir;
        while (clock() - temp < MoveTime) {
            if (Input() == -1) {
                GameState = End;
                return;
            }
        }
        //一个周期结束后要更新游戏状态
        bool flag = false; //暂停过
        if (GameState == Pause) {
            UpdateScreen();
            while (GameState == Pause) {
                if (kbhit()) {
                    switch (getch()) {
                        case 'p': {
                            GameState = Run;
                            UpdateScreen();
                            flag = true;
                            break;
                        }
                        case 27: {
                            GameState = End;
                            return;
                        }
                    }
                }
            }
        }
        if (flag) continue;
        if (Move() == -1) { //游戏结束
            GameState = End;
            return;
        }
        UpdateScreen();
        MoveTime = max(BaseSpeed[difficulty] - Acceleration[difficulty] * int(dq.size()), MaxSpeed[difficulty]);
    }
}

void Start() {
    Initial();
    Game();
    UpdateScreen();
}

void ShowMenu() { //显示游戏菜单
    vector<string>v_menu;
    v_menu.emplace_back("欢迎来到贪食蛇游戏");
    v_menu.emplace_back("1 : easy");
    v_menu.emplace_back("2 : medium");
    v_menu.emplace_back("3 : hard");
    v_menu.emplace_back("4 : demon");
    for (int i = 0; i < v_menu.size(); ++i) {
        for (int j = 0; j < v_menu[i].length(); ++j) {
            ScreenColor[i][j] = 0x06;
            ScreenData[i][j] = v_menu[i][j];
        }
    }
    ShowConsole();
}

void CleanScreen() { //清除屏幕
    memset(ScreenData, ' ', sizeof ScreenData);
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < M; ++j) {
            ScreenColor[i][j] = 0x0f;
        }
    }
}

bool Menu() { //菜单函数
    CleanScreen();
    ShowMenu();
    FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE));
    bool flag = true;
    while (flag) {
        flag = false;
        switch (getch()) {
            case '1': {
                difficulty = Easy;
                break;
            }
            case '2': {
                difficulty = Medium;
                break;
            }
            case '3': {
                difficulty = Hard;
                break;
            }
            case '4': {
                difficulty = Demon;
                break;
            }
            case 27: { //esc
                return false;
            }
            default: {
                flag = true;
                break;
            }
        }
    }
    Start();
    sprintf(ScreenData[0], "你太菜了!你的智商只有 : %d", MaxScore);
    UpdateScreen();
    Sleep(800); //等待一些时间,避免游戏结束时不小心多按
    sprintf(ScreenData[1], "按r重玩,按esc退出");
    UpdateScreen();
    FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE)); //清除输入缓冲,避免之前多按了一下,在这里被输入,导致直接结束
    while (true) {
        switch (getch()) {
            case 27: { //esc
                return false;
            }
            case 'r':{
                return true;
            }
            default:
                break;
        }
    }
}

int main() {
    ProgramInit();
    while (Menu()) {}
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值