【C语言版推箱子】已用双缓冲技术解决闪屏问题

课设做的推箱子,经典老游戏,网上代码也非常多。

老师检查的时候让我解决闪屏问题,虽然知道需要使用双缓冲技术解决,但网上没有找到能直接帮助解决闪屏的博客文章之类的。

最后通过阅读微软API文档并结合搜索,终于解决。

完成的过程中参照了不少他人文章或者其他资料,但因为时间有点久,而当时又没注意保存参考链接,所以此处没办法给出参考链接了,抱歉。

因为IDE是Dev-Cpp_5.11,且加入了播放背景音乐的功能,所以需要对dev文件进行编译才可运行程序。

地图文件和背景音乐文件略

下面直接贴代码:

/*推箱子-infocodez*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <Windows.h>
#include <mmsystem.h>

#define WIDTH 9
#define HEIGHT 9
#define MAX_LEVEL 5

/*
    0    表示空地"  " 
    1    表示墙壁"■"
    2    表示人物"♀"
    3    表示箱子"◆"
    4    表示目的地"●"
    5    表示到达目的地的箱子"★" 
    6   表示人物和目的地重合"♂"
*/

//用于存储地图 
int map[HEIGHT][WIDTH]={
    {0, 0, 1, 1, 1, 0, 0, 0, 0},
    {0, 0, 1, 4, 1, 0, 0, 0, 0},
    {0, 0, 1, 0, 1, 1, 1, 1, 0},
    {1, 1, 1, 3, 0, 3, 4, 1, 0},
    {1, 4, 0, 3, 2, 1, 1, 1, 0},
    {1, 1, 1, 1, 3, 1, 0, 0, 0},
    {0, 0, 0, 1, 4, 1, 0, 0, 0},
    {0, 0, 0, 1, 1, 1, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0, 0, 0} 
};

//临时存储map数组,用于重置地图 
int tmap[HEIGHT][WIDTH];

//x、y表示人物位置,level表示关卡数,steps为已走步数,boxes箱子的个数
int x, y,level=1,steps,boxes;

//定义句柄,默认显示缓冲区和后台显示缓冲区的句柄
HANDLE hOutput,hOutBuf;

//定义光标的坐标 
COORD coord={0,0};

//双缓冲处理显示
DWORD bytes=0;//DWORD相当于unsigned long类型。bytes为两个显示缓冲区之间传输的字符数 
char data[2700];//两个显示缓冲区之间传输的字符

/*函数声明*/ 

//游戏说明 
void instructions(); 

//菜单界面 
void menu();

//开始游戏
void startGame();

//初始化数据
void initData();

//绘制地图
void drawMap();

//选择关卡
void chooseLevel();

//选择地图
void chooseMap();

//过关判定
void isPass();

//通关界面 
void win(); 

//人物向上移动
void moveUp();

//人物向左移动
void moveLeft();

//人物向下移动
void moveDown();

//人物向右移动
void moveRight();

//主函数
int main(){    
    system("mode con cols=90 lines=30");//设置窗口大小 
    system("title 推箱子小游戏"); //设置窗口标题 
    system("color F0");//设置窗体颜色。背景亮白色、字体黑色     
    
    PlaySound(TEXT("music.wav"),NULL,SND_FILENAME|SND_ASYNC|SND_LOOP);//异步方式循环播放背景音乐 
    
    //获取默认标准显示缓冲区句柄
    hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
    
    //创建新的缓冲区,作为后台显示缓冲区 
    hOutBuf=CreateConsoleScreenBuffer(
        GENERIC_READ | GENERIC_WRITE, //控制台屏幕缓冲区的读写权限
        FILE_SHARE_READ | FILE_SHARE_WRITE, //共享缓冲区的读写权限 
        NULL, //安全属性,默认值 
        CONSOLE_TEXTMODE_BUFFER, //缓冲区类型,唯一可选
        NULL//保留的属性,默认值 
    );
        
    //隐藏两个缓冲区的光标
    CONSOLE_CURSOR_INFO cci;
    cci.bVisible=0;//光标不可见 
    cci.dwSize=1;//光标填充的字符单元格的百分比 
    SetConsoleCursorInfo(hOutput, &cci);//设置指定控制台屏幕缓冲区的光标的大小和可见性
    SetConsoleCursorInfo(hOutBuf, &cci);
    
    instructions();
    menu();
    startGame();    
    return 0;
}

//游戏说明 
void instructions(){
    int s; 
    printf("\n\n\n\n\n\t\t\t\t     游戏说明\n\n");
    printf("\t\t本游戏共5关。将所有箱子全部堆到目的地则通过关卡。\n");
    printf("\t\t通过w、s、a、d键或方向键可上下左右移动人物。按r键可重置地图。\n");
    printf("\t\t♀代表人物、◆代表箱子、●代表目的地。\n");
    printf("\t\t阅读完毕请按任意键:\n\n");
    s=getch();
    return;
}

//菜单界面 
void menu(){
    int option,x=1;//option选项,x控制while循环并初始为1 
    system("cls");//清屏 
    printf("\n\n\n\n\n\t\t\t\t     菜 单\n\n");
    printf("\t\t\t\t1.直 接 开 始\n");
    printf("\t\t\t\t2.选 择 关 卡\n");
    printf("\n\n请输入你的选择:");
    while(x){
        scanf("%d",&option);
        fflush(stdin);//清空输入缓冲区
        switch(option){
            case 1:
                x=0;
                chooseMap();//将第1关的地图数据从文件中导入 
                startGame();
                break;
            case 2:
                x=0;
                chooseLevel();
                break;
            default:
                printf("输入错误,请重新输入:");
                break;
        }    
    }
}

//选择关卡
void chooseLevel(){
    int option1,x=1;//option1存储按键信息,x控制while循环并初始为1
    system("cls");//清屏 
    printf("\n\n\n\n\n\t\t\t\t     关卡说明\n\n");
    printf("\t\t     游戏共5关,第1关到第5关由易到难。\n");
    printf("\t\t     选择完关卡后,将直接开始游戏。\n\n\n\n");
    printf("请输入你选择的关卡:");
    while(x){
        scanf("%d",&option1);
        fflush(stdin);
        switch(option1){
            case 1:
                x=0;
                level=1;
                chooseMap();
                break;
            case 2:
                x=0;
                level=2;
                chooseMap();
                break;
            case 3:
                x=0;
                level=3;
                chooseMap();
                break;
            case 4:
                x=0;
                level=4;
                chooseMap();
                break;
            case 5:
                x=0;
                level=5;
                chooseMap();
                break;
            default:
                printf("输入错误,请重新输入:");
                break;                
        }
    }    
    startGame();
}

//过关判定
void isPass(){    
    if(!boxes){//当剩余箱子数为0,说明所有箱子均被推入目的地。则关卡数加1 
        int op,x=1;
        char str1[200];//用于合成字符串        
        level++;
        if(level>MAX_LEVEL){//当关卡数大于最大关卡数,说明已通关,进入通关界面 
            win();
            return;
        }
        
        //设置默认标准显示缓冲区光标的坐标        
        coord.X=0;
        coord.Y=18;
        SetConsoleCursorPosition(hOutput,coord);
        
        printf("\n输入1为继续闯关,输入0为结束游戏:");            
        while(1){            
            scanf("%d",&op);
            fflush(stdin);        
            if(op==1){//进入下一关游戏
                x=0;    
                chooseMap();
                startGame();
            }
            else if(op==0){
                x=0;
                printf("\n感谢您对本游戏的支持!\n");        
                exit(0);//退出游戏,结束程序 
            }
            else{
                printf("输入错误,请重新输入:");
            }             
        }         
    }
} 

//通关界面
void win(){
    system("cls");    
    printf(
    "\n\n\t\t\t  ★\'☆°★ *°∴°°☆☆★ *°☆☆\n"
    "\t\t\t  ★ °∴°°☆°★*°☆*°°∴*☆\n"
    "\t\t\t  ★*°∴°☆★∴°★*°°*°☆\n"
    "\t\t\t    ★°★☆°∴* ★*∴*°☆\n"
    "\t\t\t     ★  *★*°°∴°☆\n"
    "\t\t\t      ﹨  ☆*  ☆\n"
    "\t\t\t       ﹨   ☆\n"
    "\t\t\t        ﹨ /\n"
    "\n\n\n\n\n\n\n\t\t\t\t※恭喜你通关!太棒了!※\n\n\n\n");
    exit(0);//正常状态退出程序 
}

//选择地图
void chooseMap(){
    int i,j;
    FILE *fp;
    switch(level){
        case 1:
            fp=fopen("map1.txt","rt");//rt,打开已存在的文本文件并读取数据            
            break;
        case 2:
            fp=fopen("map2.txt","rt");
            break;
        case 3:
            fp=fopen("map3.txt","rt");
            break;
        case 4:
            fp=fopen("map4.txt","rt");
            break;
        case 5:
            fp=fopen("map5.txt","rt");
            break;
        default:
            printf("地图选择失败\n");
            exit(-1);//异常状态结束程序 
    }
    if(fp==NULL){
            printf("文件打开失败\n");
            exit(-1);//异常状态结束程序 
        }
    //将地图文件的数据复制到map数组 
    for(i=0;i<HEIGHT;i++){
        for(j=0;j<WIDTH;j++){
            fscanf(fp,"%d",&map[i][j]);
        }
    }
    
    fclose(fp);//关闭文件 
}

//开始游戏
void startGame(){
    int i,j;
    char direction;//存储按键动作    
    //记忆关卡初始时的地图,便于重置地图 
    for(i=0;i<HEIGHT;i++){
        for(j=0;j<WIDTH;j++){
            tmap[i][j]=map[i][j];
        }
    }
    
    initData();    
    while(1){        
        drawMap();                
        isPass();
        direction=getch();
        switch(direction){
            case 'w':
            case 'W':
            case 72://向上的方向键 
                moveUp();                
                break;
            case 'a':
            case 'A':
            case 75://向左的方向键 
                moveLeft(); 
                break;
            case 's':
            case 'S':
            case 80://向下的方向键 
                moveDown();
                break;
            case 'd':
            case 'D':
            case 77://向右的方向键 
                moveRight();
                break; 
            case 'R':
            case 'r'://重置地图 
                for(i=0;i<HEIGHT;i++)
                {
                    for(j=0;j<WIDTH;j++)
                    {
                        map[i][j]=tmap[i][j];
                    }
                }                
                startGame();
                break;
            default:
                break;    
        }
    } 
}

//初始化数据
void initData(){
    int i,j;
    boxes=0;//箱子数目初始化为零 
    steps=0;//步数初始化为零 
    
    //获取箱子数目和人物位置 
    for(i=0;i<HEIGHT;i++){
        for(j=0;j<WIDTH;j++){            
            //遍历到3时,箱子的数目增加 
            if(map[i][j]==3){
                boxes++;
            }
            //遍历到2或6时,记录人物位置 
            else if(map[i][j]==2||map[i][j]==6){
                x=j;
                y=i;
            } 
        }
    } 
}

//绘制地图
void drawMap(){    
    int i,j;
    char str[200];//用于合成字符串    
    
    //设置后台显示缓冲区光标的坐标    
    coord.X=29;
    coord.Y=4;
    SetConsoleCursorPosition(hOutBuf,coord); 
       
    sprintf(str,"第%d关\n",level);//合成字符串 
    WriteConsole(hOutBuf,str,strlen(str),NULL,NULL);//将字符串写入后台显示缓冲区 
    for(i=0;i<HEIGHT;i++){
        coord.X=24;
        coord.Y=i+6;
        SetConsoleCursorPosition(hOutBuf,coord);
        for(j=0;j<WIDTH;j++){
            switch(map[i][j]){
                case 0:
                    WriteConsole(hOutBuf, "  ", strlen("  "), NULL, NULL);;//空地 
                    break;
                case 1:
                    WriteConsole(hOutBuf, "■", strlen("■"), NULL, NULL);;//墙壁 
                    break;
                case 2:
                    WriteConsole(hOutBuf, "♀", strlen("♀"), NULL, NULL);//人物 
                    break;
                case 3:
                    WriteConsole(hOutBuf, "◆", strlen("◆"), NULL, NULL);//箱子 
                    break;
                case 4:
                    WriteConsole(hOutBuf, "●", strlen("●"), NULL, NULL);//目的地 
                    break;
                case 5:
                    WriteConsole(hOutBuf, "★", strlen("★"), NULL, NULL);//到达目的地的箱子
                    break; 
                case 6:
                    WriteConsole(hOutBuf, "♂", strlen("♂"), NULL, NULL);//人物和目的地重合
                    break;
                default:
                    WriteConsole(hOutBuf,"地图载入错误",strlen("地图载入错误"),NULL,NULL);
                    break;
            }        
        }
    }
    sprintf(str,"\n\n本关剩余目标数:%-4d\n本关已走步数:%-4d\n",boxes,steps); 
    WriteConsole(hOutBuf,str,strlen(str),NULL,NULL); 
    //设置两个缓冲区传输字符的起始坐标 
    coord.X=0;
    coord.Y=0;
    //将后台缓冲区的字符信息传输到默认标准显示缓冲区 
    ReadConsoleOutputCharacter(hOutBuf,data,2700,coord,&bytes);//读取后台缓冲区的字符信息 
    WriteConsoleOutputCharacter(hOutput,data,2700,coord,&bytes);//将字符信息写入默认标准显示缓冲区   
}

//人物向上移动
void moveUp(){    
    int ux, uy;//存放人物上方的坐标     
    
    //记录人物上方坐标
    ux=x;
    uy=y-1; 
    
    //人物上方没有元素或人物上方为墙壁
    if(y==0||map[uy][ux] == 1){
        return;
    }    

    //人物上方为目的地
    else if(map[uy][ux]==4){         
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;           
        }
        else if(map[y][x]==2){
            map[y][x]=0;
        }
        
        map[uy][ux]=6;    
    }
    
    //人物上方为已到达目的地的箱子 
    else if(map[uy][ux]==5){            
        //已到达目的地的箱子上方为墙壁、箱子、已到达目的地的箱子
        if(map[uy-1][ux]==1||map[uy-1][ux]==3||map[uy-1][ux]==5){
            return;
        }        
        //已到达目的地的箱子上方为目的地           
        else if(map[uy-1][ux]==4){  
            map[uy-1][ux]=5;            
        }        
        //已到达目的地的箱子上方为空地
        else if(map[uy-1][ux]==0){             
            map[uy-1][ux]=3;
            boxes++;//箱子的数目加1    
        }
        
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;
        }
        else if(map[y][x]==2){
            map[y][x]=0;
        }
        
        map[uy][ux]=6;
    } 
    
    //人物上方为箱子
    else if(map[uy][ux]==3){
        //箱子上方为墙壁、箱子、已到达目的地的箱子 
        if(map[uy-1][ux]==1||map[uy-1][ux]==3||map[uy-1][ux]==5){
            return;
        }        
        //箱子上方为目的地
        if(map[uy-1][ux]==4){
            map[uy-1][ux]=5;            
            boxes--;//箱子的数目减1     
            
        }
        //箱子上方为空地
        else if(map[uy-1][ux]==0){            
            map[uy-1][ux]=3;
        }
        
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;
            
        }
        else if(map[y][x]==2){
            map[y][x]=0;
        }
        
        map[uy][ux]=2;
    }
    
    //人物上方为空地
    else if(map[uy][ux]==0){        
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;                
        }
        else if(map[y][x]==2){
            map[y][x]=0;
        }
        
        map[uy][ux]=2;
    }
    
    y=uy;//更新人物坐标 
    steps++;//已走步数加1 
    return;
} 

//人物向左移动
void moveLeft(){
    int lx,ly;//存放人物左方的坐标  
    
    //记录人物左方坐标
    lx=x-1;
    ly=y; 

    //人物左方没有元素或人物左方为墙壁
    if(x==0||map[ly][lx]==1){
        return;
    }    
    
    //人物左方为目的地
    else if(map[ly][lx]==4){        
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;            
        }
        else if(map[y][x]==2){
            map[y][x]=0;
        }
        
        map[ly][lx]=6;
    }
    
    //人物左方为已到达目的地的箱子 
    else if(map[ly][lx]==5){
        //已到达目的地的箱子左方为墙壁、箱子、已到达目的地的箱子
        if(map[ly][lx-1]==1||map[ly][lx-1]==3||map[ly][lx-1]==5){
            return;
        }
        //已到达目的地的箱子左方为目的地,
        else if(map[ly][lx-1]==4){
            map[ly][lx-1]=5;
        }
        //已到达目的地的箱子左方为空地,
        else if(map[ly][lx-1]==0){
            map[ly][lx-1]=3;
            boxes++;//箱子的数目加1    
        }
        
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;
        }
        else if(map[y][x]==2){
            map[y][x]=0;
        }
        
        map[ly][lx]=6;
    } 
    
    //人物左方为箱子
    else if(map[ly][lx]==3){
        //箱子左方为墙壁、箱子、已到达目的地的箱子 
        if(map[ly][lx-1]==1||map[ly][lx-1]==3||map[ly][lx-1]==5){
            return;
        }        
        //箱子左方为目的地
        if(map[ly][lx-1]==4){
            map[ly][lx-1]=5;
            boxes--;//箱子的数目减1  
        }
        //箱子左方为空地
        else if(map[ly][lx-1]==0){            
            map[ly][lx-1]=3;
        }        
        
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;
        }
        else if(map[y][x]==2){
            map[y][x]=0;
        }
        
        map[ly][lx]=2;
    }
    //人物左方为空地
    else if(map[ly][lx]==0){
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;
        }
        else if(map[y][x]==2){
            map[y][x]=0;
        }
        
        map[ly][lx]=2;
    }
        
    x=lx;//更新人物坐标 
    steps++;//已走步数加1 
    return;
}

//人物向下移动
void moveDown(){    
    int dx,dy;//存放人物下方的坐标 
    
    //记录人物下方坐标
    dx=x;
    dy=y+1; 
    
    //人物下方没有元素或人物下方为墙壁    
    if(y==HEIGHT-1||map[dy][dx]==1){
        return;
    }    
    
    //人物下方为目的地
    else if(map[dy][dx]==4){
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;
        }
        else if(map[y][x]==2){
            map[y][x]=0;
        }
        
        map[dy][dx]=6;    
    }
    
    //人物下方为已到达目的地的箱子 
    else if(map[dy][dx]==5){
        //已到达目的地的箱子下方为墙壁、箱子、已到达目的地的箱子
        if(map[dy+1][dx]==1||map[dy+1][dx]==3||map[dy+1][dx]==5){
            return;
        }
        //已到达目的地的箱子下方为目的地       
        else if(map[dy+1][dx]==4){            
            map[dy+1][dx]=5;
        }
        //已到达目的地的箱子下方为空地
        else if(map[dy+1][dx]==0){
            map[dy+1][dx]=3;
            boxes++;//箱子的数目加1    
        }
        
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;                
        }
        else if(map[y][x]==2){
            map[y][x]=0;
        }
        
        map[dy][dx]=6;
    } 
    
    //人物下方为箱子
    else if(map[dy][dx]==3){
        //箱子下方为墙壁、箱子、已到达目的地的箱子
        if(map[dy+1][dx]==1||map[dy+1][dx]==3||map[dy+1][dx]==5){
            return;
        }        
        //箱子下方为目的地
        if(map[dy+1][dx]==4){
            map[dy+1][dx]=5;
            boxes--;//箱子的数目减1  
        }
        //箱子下方为空地
        else if(map[dy+1][dx]==0){
            map[dy+1][dx]=3; 
        }
        
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;                
        }
        else if(map[y][x]==2){
            map[y][x]=0;
        }
        
        map[dy][dx]=2;
    }
    
    //人物下方为空地
    else if(map[dy][dx]==0){        
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;                
        }
        else if(map[y][x]==2){
            map[y][x]=0;
        }
        
        map[dy][dx]=2;
    }
    
    y=dy;//更新人物坐标 
    steps++;//已走步数加1 
    return;    
}

//人物向右移动
void moveRight(){    
    int rx, ry;//存放人物右方的坐标 
    
    //记录人物右方坐标
    rx = x + 1;
    ry = y; 
    
    //人物右方没有元素或人物右方为墙壁
    if(x==WIDTH-1||map[ry][rx]==1){
        return;
    }
    
    //人物右方为目的地
    else if(map[ry][rx]==4){        
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;
        }
        else if(map[y][x]==2){            
            map[y][x]=0;
        }
        
        map[ry][rx]=6;
    }

    //人物右方为已到达目的地的箱子 
    else if(map[ry][rx]==5){
        //已到达目的地的箱子右方为墙壁、箱子、已到达目的地的箱子
        if(map[ry][rx+1]==1||map[ry][rx+1]==3||map[ry][rx+1]==5){
            return;
        }
        //已到达目的地的箱子右方为目的地       
        else if(map[ry][rx+1]==4){
            map[ry][rx+1]=5;            
        }
        //已到达目的地的箱子右方为空地,
        else if(map[ry][rx+1]==0){
            map[ry][rx+1]=3;
            boxes++;//箱子的数目加1    
        }
        
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;
        }
        else if(map[y][x]==2){            
            map[y][x]=0;
        }
        
        map[ry][rx]=6;
    } 
    
    //人物右方为箱子
    else if(map[ry][rx]==3){
        //箱子右方为墙壁、箱子、已到达目的地的箱子  
        if(map[ry][rx+1]==1||map[ry][rx+1]==3||map[ry][rx+1]==5){
            return;
        }        
        //箱子右方为目的地
        if(map[ry][rx+1]==4){
            map[ry][rx + 1] = 5;
            boxes--;//箱子的数目减1  
        }
        //箱子右方为空地
        else if(map[ry][rx+1]==0){
            map[ry][rx+1]=3; 
        }
        
         if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;
        }
        else if(map[y][x]==2){            
            map[y][x]=0;
        }
            
        map[ry][rx]=2;
    }
    
    //人物右方为空地
    else if(map[ry][rx]==0){
        if(map[y][x]==6){//人物原位置与目的地重合
            map[y][x]=4;
        }
        else if(map[y][x]==2){            
            map[y][x]=0;
        }
        
        map[ry][rx]=2;
    }
    
    x=rx;//更新人物坐标 
    steps++;//已走步数加1 
    return;    
}

 

 
上一篇:PS产品修图技巧实例讲解


下一篇:oracle 异步io