课设做的推箱子,经典老游戏,网上代码也非常多。
老师检查的时候让我解决闪屏问题,虽然知道需要使用双缓冲技术解决,但网上没有找到能直接帮助解决闪屏的博客文章之类的。
最后通过阅读微软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; }