『游戏』贪吃蛇

准备一夏

如果你不想下 \(VS\) 可以去这里,但我不确保你一定找的到你要的

首先,下载俩东西:\(Visual Studio\) (大家都知道是啥,不知道的请戳(雾我在 \(VS2019\) 里测试的,我这没问题。。),图形库

还有四个图片,放在你的 \(exe\) 文件目录下,就可以了:第一个,第二个,第三个,第四个;

等等,它们还要分别命名为:main.jpg , main_exit.jpg , main_start.jpg , play.png;

好了,上面搞完了就可以复制代码了(别嫌麻烦,我一个 xxs 弄的,其实挺好看的)

顺带个游戏说明

开始游戏后,空格暂停,再按一次开始;按箭头控制,长按加速;蛇身颜色渐变,只是别弄太长了,\(len \leq 70\) 应该就行,不然尾巴就粉色,一直粉色。。。;ESC键退出游戏(按了就直接退出,别怪我没保存游戏进度,也没让你确认。。)。

"请谨慎食用!"

这就是丑不垃圾的代码

#include <graphics.h>
#include <conio.h>
#include <cstdio>
#include <iostream>
#include <thread>
using namespace std;

#pragma warning(disable : 4996)
#define BLOCK_SIZE 20 // 每个小格子的长宽大小
#define HEIGHT 30 // 高度上一共30个小格子
#define WIDTH 40  //  宽度上一共40个小格子
#define UP 72 // 上箭头ACLL码 72
#define DOWN 80 // 下箭头ACLL码 80
#define LEFT 75 // 左箭头ACLL码 75
#define RIGHT 77 // 右箭头ACLL码 77
#define ESC 27 // ESC键ACLL码
#define SPACE 32 // 空格ACLL码
#define IN_START m.x>=m_start.left && m.x<=m_start.right && m.y>=m_start.top && m.y<=m_start.bottom
#define IN_EXIT m.x>=m_exit.left && m.x<=m_exit.right && m.y>=m_exit.top && m.y<=m_exit.bottom

struct Locat{
    int x,y;
};

// 全局变量定义
int Blocks[HEIGHT][WIDTH];       // 二维数组,用于记录所有的游戏数据
int waitIndex;                   // 等待移动
char dir;                        // 小蛇移动方向
Locat food;                      // 食物的位置
IMAGE img;                       // 保存图像
IMAGE play;                      // 暂停图像
MOUSEMSG m;                      // 鼠标
bool isFailure;                  // 是否游戏失败
bool loop;                       // 是否游戏退出
bool skip;                       // 是否暂停
bool isstart;
bool isskipstart;
RECT m_start;
RECT m_exit;                       // 按钮坐标
Locat oldTail,oldHead,newHead;

void text(int x, int y, int height,LPCTSTR str,COLORREF color){
    settextstyle(height,0,_T("Consolas"));
    settextcolor(color);
    outtextxy(x,y,str);
    FlushBatchDraw();
}

void uninit(){
    EndBatchDraw();
}

void m_loadimage(IMAGE *img, LPCTSTR str){
    loadimage(img, str, BLOCK_SIZE*WIDTH, BLOCK_SIZE*HEIGHT, true);
}

void input(){
    void init();
    void move();
    while(!loop){
        if(kbhit()){
            char ch = getch();
            switch(ch){
                case UP: if(dir==DOWN){break;} dir=ch; move(); break;
                case DOWN: if(dir==UP){break;} dir=ch; move(); break;
                case LEFT: if(dir==RIGHT){break;} dir=ch; move(); break;
                case RIGHT: if(dir==LEFT) {break;} dir=ch; move(); break;
                case ESC: loop=true; isstart=true; break;
                case SPACE: 
                    if(isFailure){
                        uninit();
                        init();
                        skip = false;
                        cleardevice();
                    }else if(!isskipstart){
                        skip=!skip;
                    }else{
                        isskipstart = true;
                    }
                    isstart = true;
                    break;
                default: break;
            }
        }
    }
}

void init(){
    srand(time(NULL));
    for(int i=0; i<HEIGHT; i++){
        for(int j=0; j<WIDTH; j++){
            Blocks[i][j] = 0;
        }
    }
    thread in(input);
    in.detach();
    waitIndex = 1;
    isFailure = false;
    loop = false;
    skip = false;
    isstart = false;
    isskipstart = false;
    m_start.left = 320;
    m_start.top = 440;
    m_start.right = 490;
    m_start.bottom = 480;
    m_exit.left = 320;
    m_exit.top = 500;
    m_exit.right = 490;
    m_exit.bottom = 545;
    food.x = rand()%(HEIGHT-5)+2;
    food.y = rand()%(WIDTH-5)+2;
    Blocks[HEIGHT/2][WIDTH/2] = 1;
    for(int i=1; i<=4; i++){
        Blocks[HEIGHT/2][WIDTH/2-i] = i+1;
    }
    dir = RIGHT;
    initgraph(WIDTH*BLOCK_SIZE, HEIGHT*BLOCK_SIZE);
    BeginBatchDraw();
    setbkmode(TRANSPARENT);
    setlinecolor(RGB(36,36,36));
    m_loadimage(&img,_T("main.jpg"));
    loadimage(&play, _T("play.png"), 120, 120, true);
    putimage(0,0,&img);
    FlushBatchDraw();
    while(!isstart){
        if(isskipstart){
            isstart = true;
            skip = false;
            cleardevice();
            FlushBatchDraw();
            break;
        }
        m = GetMouseMsg();
        switch(m.uMsg){
            case WM_MOUSEMOVE:
                if(IN_START){
                    m_loadimage(&img,_T("main_start.jpg"));
                }else if(IN_EXIT){
                    m_loadimage(&img,_T("main_exit.jpg"));
                }else{
                    m_loadimage(&img,_T("main.jpg"));
                }
                putimage(0,0,&img);
                FlushBatchDraw();
                break;
            case WM_LBUTTONDOWN:
                if(IN_START || IN_EXIT){
                    isstart = true;
                    if(IN_EXIT){
                        loop = true;
                    }
                    cleardevice();
                    FlushBatchDraw();
                }
                break;
        }
    }
}

void move(){
    int max = 0;
    for(int x=0; x<HEIGHT; x++){
        for(int y=0; y<WIDTH; y++){
            if(Blocks[x][y]>0){
                Blocks[x][y]++;
            }
        }
    }
    for(int x=0; x<HEIGHT; x++){
        for(int y=0; y<WIDTH; y++){
            if(max<Blocks[x][y]){
                max = Blocks[x][y];
                oldTail.x = x;
                oldTail.y = y;
            }
            if(Blocks[x][y]==2){
                oldHead.x = x;
                oldHead.y = y;
            }
        }
    }
    newHead = oldHead;
    switch(dir){
        case UP: newHead.x=oldHead.x-1; break; 
        case DOWN: newHead.x=oldHead.x+1; break;
        case LEFT: newHead.y=oldHead.y-1; break;
        case RIGHT: newHead.y=oldHead.y+1; break;
        default: break;
    }
    if(newHead.x>=HEIGHT || newHead.x<0 || newHead.y>=WIDTH || newHead.y<0 || Blocks[newHead.x][newHead.y]>0){
        isFailure = true;
        return;
    }
    Blocks[newHead.x][newHead.y] = 1;
    if(newHead.x==food.x && newHead.y==food.y){
        do{
            food.x = rand()%(HEIGHT-5)+2;
            food.y = rand()%(WIDTH-5)+2;
        }while(Blocks[food.x][food.y]>1);
    }else{
        Blocks[oldTail.x][oldTail.y] = 0;
    }
}

void show(){
    cleardevice();
    for(int x=0; x<HEIGHT; x++){
        for(int y=0; y<WIDTH; y++){
            if(Blocks[x][y]>0){
                setfillcolor(HSVtoRGB(Blocks[x][y],0.9,1)); // HSVtoRGB(Blocks[x][y]*10,0.9,1)
            }else{
                setfillcolor(RGB(28,28,28));
            }
            fillrectangle(y*BLOCK_SIZE,x*BLOCK_SIZE,(y+1)*BLOCK_SIZE,(x+1)*BLOCK_SIZE);
        }
    }
    setfillcolor(RGB(0,255,0));
    fillrectangle(food.y*BLOCK_SIZE,food.x*BLOCK_SIZE,(food.y+1)*BLOCK_SIZE,(food.x+1)*BLOCK_SIZE);
    if(isFailure){
        skip = true;
    }
    FlushBatchDraw();
}

int main(){
    init();
    while(!loop){
        if(!skip && !isskipstart){
            show();
            waitIndex++;
            if(waitIndex==15){
                move();
                waitIndex = 1;
            }
        }else{
            if(isskipstart){
                continue;
            }else if(isFailure){
                text(250, 240, 80, _T("游戏失败"), RGB(255,0,0));
                FlushBatchDraw();
                isstart = false;
                skip = true;
            }else{
                if(isstart){
                    putimage(340, 240, &play, SRCPAINT);
                    FlushBatchDraw();
                }
            }
        }
    }
    uninit();
    return 0;
}

咋样,还行吧。。。


\[\Huge update\ 2021.10 \]

突然感觉以前代码空格好多(

改了下码风,稍微修改,并增加了亿些注释(累555)。

// 作者:LYqwq
// 博客:https://www.luogu.com.cn/blog/ericiscool/tanchishe
// 洛谷uid:399116
#include <graphics.h> // 简单但啥也不是(划掉)的头文件
#include <conio.h>
#include <cstdio>
#include <iostream>
#include <thread>
#pragma warning(disable : 4996) // VS会整一些莫名奇妙的警告导致运行不了,用这个禁用qwq
using namespace std;

// 定义宏养成习惯:加括号
// 这些可以自己瞎改,嘿嘿
// 注意这个窗口大小改了,按钮位置还在原来的,这样你就算按了你看到的“按钮”,也无效qwq
#define BLOCK_SIZE (20) // 每个小格子的长宽大小
#define HEIGHT (30) // 高度上小格子数量
#define WIDTH (40) //  宽度上小格子数量
#define SPEED (15) // 速度
#define INIT_LENGTH (5) // 初始时蛇身长度
#define COLOR_SPEED (1) // 颜色渐变速度
#define BLOCK_COLOR (RGB(28,28,28)) // 小格子颜色
#define FOOD_COLOR (RGB(0,255,0)) // 食物颜色
// 这里别改
#define UP (72) // 上箭头ACLL码,可直接表示方向
#define DOWN (80) // 下箭头ACLL码,同上
#define LEFT (75) // 左箭头ACLL码,同上
#define RIGHT (77) // 右箭头ACLL码,同上
#define ESC (27) // ESC键ACLL码
#define SPACE (32) // 空格ACLL码
#define IN_START (m.x>=m_start.left && m.x<=m_start.right && m.y>=m_start.top && m.y<=m_start.bottom) // 判断鼠标是否在开始界面的 开始游戏 按钮内
#define IN_EXIT (m.x>=m_exit.left && m.x<=m_exit.right && m.y>=m_exit.top && m.y<=m_exit.bottom) // 同上,判断按钮为 退出

struct Locat{ // 为了偷懒(划掉)定义一个表示坐标的结构体
    int x,y;
};

// 全局变量定义
int Blocks[HEIGHT][WIDTH];       // 二维数组,用于记录游戏中的地图
int waitIndex;                   // 等待移动,每次循环加一,如果大于等于SPEED就移动,并设为0,可以理解为记录移动时间的变量
char dir;                        // 小蛇移动方向
Locat food;                      // 食物的坐标
IMAGE img;                       // 保存图像
IMAGE play;                      // 暂停图像
MOUSEMSG m;                      // 鼠标
bool isFailure;                  // 是否游戏失败
bool loop;                       // 是否游戏退出
bool skip;                       // 是否暂停
bool isstart;                    // 游戏是否已开始
bool isskipstart;                // 是否按下开始按钮,游戏开始前按下空格也算
RECT m_start;                    // 按钮坐标
RECT m_exit;                     // 同上
Locat oldTail,oldHead,newHead;   // 蛇头蛇尾坐标和新的蛇头坐标,newHead用于每次移动
int dx[100];                     // 用于UP,DOWN,LEFT,RIGHT移动转换坐标
int dy[100];                     // 同上

// 函数声明
int main();
void text(int x,int y,int height,LPCTSTR str,COLORREF color);
void m_loadimage(IMAGE *img,LPCTSTR str);
void input();
void init();
void uninit();
void begin();
void move();
void show();

// 为了偷懒(划掉)定义一个在屏幕上显示文字的函数
// x,y:文字坐标
// height:文字高度
// str:显示的字符串
// color:颜色嘛,没什么好解释的
void text(int x,int y,int height,LPCTSTR str,COLORREF color=RGB(0,0,0)){
    settextstyle(height,0,_T("Consolas")); // 设置高度,宽度,字体
    settextcolor(color); // 颜色
    outtextxy(x,y,str); // 坐标和字符串
    FlushBatchDraw(); // 刷新屏幕
}

// 也是为了偷懒(划掉)定义一个用于加载图片的函数
// *imp:要加载的图片
void m_loadimage(IMAGE *img,LPCTSTR str){
    loadimage(img,str,BLOCK_SIZE*WIDTH,BLOCK_SIZE*HEIGHT,true);
}

// 用于读入,另建一个线程读入,可以省不少时间,而且操作很灵
void input(){
    while(!loop){ // 游戏没有退出才执行
        if(kbhit()){ // 如果有输入
            char ch=getch();
            switch(ch){
                // 读入的字符是上、下、左、右箭头,如果原方向相反则break。否则改变方向,并移动
                // 这里只要读入就移动顺便实现了长按加速功能
                case UP: if(dir==DOWN){break;} dir=ch; move(); break; // 别告诉我if不用加大括号,这样更容易分清楚if和外面的语句
                case DOWN: if(dir==UP){break;} dir=ch; move(); break;
                case LEFT: if(dir==RIGHT){break;} dir=ch; move(); break;
                case RIGHT: if(dir==LEFT) {break;} dir=ch; move(); break;
                // 如果检测到ESC就退出程序,并准备下一次游戏开始
                case ESC: loop=true; isstart=true; break;
                // 空格
                case SPACE:
                    if(isFailure){ // 如果死亡,就重新初始化一遍,暂停为否,清空屏幕
                        // 重新初始化
                        uninit();
                        init();
                        // 取消暂停状态
                        skip=false;
                        cleardevice(); // 清空屏幕
                    }else if(!isskipstart){ // 如果没按开始键
                        skip=!skip; // 暂停状态改变
                    }else{
                        isskipstart=true; // 按开始按钮
                    }
                    isstart=true; // 只要按了空格就将开始设为true
                    break;
                default: break; // 其他情况:break!我什么都不管,就这么break!
            }
        }
    }
}

// 初始化函数
void init(){
    // 前置初始化
    srand((unsigned)time(NULL)); // 初始化随机种子
    thread in(input); // 分离线程,读入,见上input()
    in.detach(); // 让这个线程自己执行,我们依旧干自己的事(
    // 记录游戏变量及移动坐标便宜
    waitIndex=0; // 初始设置移动计数变量为0
    isFailure=false; // 是否死亡,咋可能还没开始就没了(
    loop=false; // 是否退出游戏,刚开始呢(
    skip=false; // 是否暂停,并没有
    isstart=false; // 是否点击了开始按钮
    isskipstart=false; // 没按开始按钮
    dx[UP]=-1,dx[DOWN]=+1; // 移动时的坐标偏移设置,x。如果是LEFT或RIGHT的话x坐标没有变化,不管他,自动初始化为0了
    dy[LEFT]=-1,dy[RIGHT]=+1; // 同上,只不过是y,同上,将LEFT和RIGHT改为UP和DOWN
    // 按钮坐标
    m_start.left=320; // 设置开始按钮坐标,m_start是一个存储矩阵的结构体类型,此处是左上脚x坐标
    m_start.top=440; // 左上角y坐标
    m_start.right=490; // 右下角x坐标
    m_start.bottom=480; // 右下角y坐标,这里设置开始按钮坐标结束
    m_exit.left=320; // 同上,左上脚x坐标,只是设置的按钮坐标是结束游戏按钮坐标
    m_exit.top=500; // 左上角y坐标
    m_exit.right=490; // 右下角x坐标
    m_exit.bottom=545; // 右下角y坐标,结束
    // 食物
    food.x=rand()%(HEIGHT-5)+2;
    food.y=rand()%(WIDTH-5)+2; // 生成食物坐标,使用随机数,范围在2,2 ~ HEIGHT-2,WIDTH-2
    // 处理地图
    Blocks[HEIGHT/2][WIDTH/2]=1; // 设置蛇头坐标
    /*
    * 蛇身颜色渐变,所以采用这种方法:
    * 蛇头设为1,后面依次递增,便于设置颜色。
    */
    for(int i=1; i<INIT_LENGTH; i++){
        Blocks[HEIGHT/2][WIDTH/2-i]=i+1; // 因为1是蛇头,所以值为i+1,WIDTH/2-i恰好等于上一个身子的左边一个格子
    }
    dir=RIGHT; // 初始方向是右边
    initgraph(WIDTH*BLOCK_SIZE,HEIGHT*BLOCK_SIZE); // 初始化窗口,传入长度和宽度,这里用格子的边长*格子数量传入
    BeginBatchDraw(); // 开始批量绘画,这样每次绘制后需要调用FlushBatchDraw()函数才会在屏幕上显示。不用的话...你会发现屏幕一闪一闪的...
    setbkmode(TRANSPARENT); // 设置绘制文字时可以透明,不然黑乎乎一片...
    setlinecolor(RGB(36,36,36)); // 设置绘制线时的颜色,为灰色
    m_loadimage(&img,_T("main.jpg")); // 加载图片
    loadimage(&play,_T("play.png"),120,120,true); // 加载暂停图像,设置拉伸长度并设为自动填充
    putimage(0,0,&img); // 将开始界面输出到屏幕上
    FlushBatchDraw(); // 刷新画面
    begin(); // 准备开始
}

// 取消初始化,好像没啥用?
// 其实你没了之后重新开始的时候会用到(小声)
void uninit(){
    EndBatchDraw(); // 结束批量绘画
    for(int i=0; i<HEIGHT; i++){ // 设置地图全部为0,也就是空格
        for(int j=0; j<WIDTH; j++){
            Blocks[i][j]=0;
        }
    }
}

// 准备开始游戏吧
void begin(){
    while(!isstart){ // 还没开始
        if(isskipstart){ // 如果按下开始按钮
            isstart=true; // 整个游戏开始
            skip=false; // 暂停状态设为取消
            cleardevice(); // 清空屏幕
            FlushBatchDraw(); // 刷新屏幕
            break; // 直接走人
        }
        m=GetMouseMsg(); // 获取鼠标信息
        switch(m.uMsg){ // 一起来switch吧
            case WM_MOUSEMOVE: // 鼠标移动
                if(IN_START){ // 如果鼠标在开始按钮内
                    m_loadimage(&img,_T("main_start.jpg")); // 加载鼠标在开始按钮内的图片
                }else if(IN_EXIT){ // 如果鼠标在退出按钮内
                    m_loadimage(&img,_T("main_exit.jpg")); // 加载鼠标在退出按钮内的图片
                }else{
                    m_loadimage(&img,_T("main.jpg")); // 加载正常图片
                }
                putimage(0,0,&img); // 将图片输出在屏幕上
                FlushBatchDraw(); // 刷新屏幕
                break;
            case WM_LBUTTONDOWN: // 如果鼠标点击按下,注意是按下时,只要按下就判断这个,不是鼠标起来
                if(IN_START || IN_EXIT){ // 如果在按钮内按下鼠标
                    isstart=true; // 游戏开始了!
                    if(IN_EXIT){ // 要是是退出的话...游戏已退出,嘀!
                        loop=true; // 设置游戏已退出
                    }
                    cleardevice(); // 清空画面
                    FlushBatchDraw(); // 刷新屏幕
                }
                break;
            default: break; // 走人
        }
    }
}

// 移动
void move(){
    int max=0; // 原来尾巴的位置
    // 移动蛇身
    // 每次移动时可以直接遍历整个矩阵,只要值不等于0就说明是蛇身,
    // 加一,就等于变成了后面那个蛇身,方法是不是很巧妙?
    for(int x=0; x<HEIGHT; x++){
        for(int y=0; y<WIDTH; y++){
            if(Blocks[x][y]>0){
                Blocks[x][y]++;
            }
        }
    }
    // 处理蛇头蛇尾的删除及增加操作
    for(int x=0; x<HEIGHT; x++){
        for(int y=0; y<WIDTH; y++){
            if(Blocks[x][y]>max){ // 如果当前值比max还要大,就说明当前这个值更可能是蛇尾
                max=Blocks[x][y]; // 更新max
                oldTail.x=x; // 设置旧蛇尾左边
                oldTail.y=y;
            }
            if(Blocks[x][y]==2){ // 如果是上一个蛇头所在地,更新旧蛇头坐标
                oldHead.x=x;
                oldHead.y=y;
            }
        }
    }
    // 将蛇头移动到应有的位置
    // 直接使用坐标偏移,简单多了
    newHead.x=oldHead.x+dx[dir],newHead.y=oldHead.y+dy[dir];
    // 判断死亡条件
    if(newHead.x>=HEIGHT || newHead.x<0 || newHead.y>=WIDTH || newHead.y<0 || Blocks[newHead.x][newHead.y]>0){
        isFailure=true; // 个屁了qwq
        return; // 再见
    }
    Blocks[newHead.x][newHead.y]=1; // 转移蛇头位置
    if(newHead.x==food.x && newHead.y==food.y){ // 如果吃到了食物
        do{
            food.x=rand()%(HEIGHT-5)+2;
            food.y=rand()%(WIDTH-5)+2; // 随机出一个,范围不能太靠边
        }while(Blocks[food.x][food.y]>0); // 如果随机到的位置在蛇身上,继续随机,直到不在为止
        // 不用将旧尾巴设为0了,吃到了长身子嘛
    }else{
        Blocks[oldTail.x][oldTail.y]=0; // 没吃到,就把原来的尾巴设为0,等于移动了1下
    }
}

// 绘画
void show(){
    cleardevice(); // 清空屏幕,屏幕上看不到,因为没刷新屏幕
    for(int x=0; x<HEIGHT; x++){ // 遍历!我要遍历!
        for(int y=0; y<WIDTH; y++){
            if(Blocks[x][y]>0){ // 如果是蛇身
                setfillcolor(HSVtoRGB(Blocks[x][y]*COLOR_SPEED,0.9,1)); // 设置填充颜色为渐变颜色
            }else{
                setfillcolor(BLOCK_COLOR); // 否则设置颜色为普通小格子的颜色
            }
            fillrectangle(y*BLOCK_SIZE,x*BLOCK_SIZE,(y+1)*BLOCK_SIZE,(x+1)*BLOCK_SIZE); // 画在屏幕上
        }
    }
    setfillcolor(FOOD_COLOR); // 将填充颜色设为食物颜色
    fillrectangle(food.y*BLOCK_SIZE,food.x*BLOCK_SIZE,(food.y+1)*BLOCK_SIZE,(food.x+1)*BLOCK_SIZE); // 画食物
    if(isFailure){ // 如果没了
        skip=true; // 暂停
    }
    FlushBatchDraw(); // 刷新屏幕,酱紫就能看见我们刚画的东西了
}

// 一切从此开始
int main(){
    init(); // 首先初始化
    while(!loop){ // 只要游戏没退出
        if(!skip && !isskipstart){ // 游戏未暂停,也未按暂停键
            show(); // 绘制画面
            waitIndex++; // 移动时间++
            if(waitIndex>=SPEED){ // 如果到了移动的时间
                move(); // 那么移动
                waitIndex=0; // 并将时间计数器设为0
            }
        }else{
            if(isskipstart){ // 如果按了开始键
                continue; // 什么都不干,就是玩儿
            }else if(isFailure){ // 个屁了
                text(250,240,80,_T("游戏失败"),RGB(255,0,0)); // 在屏幕上绘制"游戏失败",颜色为红色
                FlushBatchDraw(); // 刷新屏幕
                isstart=false; // 死亡后开始状态设为false
                skip=true; // 死亡后暂停
            }else{
                if(isstart){ // 如果刚打开游戏
                    putimage(340,240,&play,SRCPAINT); // 绘制开始时的图片
                    FlushBatchDraw(); // 刷新画面
                }
            }
        }
    }
    uninit(); // 最后uninit()
    return 0;
}
posted @ 2022-01-21 21:35  仙山有茗  阅读(175)  评论(1编辑  收藏  举报