【C语言】百行代码实现—推箱子小游戏

前言

第一次推箱子写还是在2019年的9月份不知不觉过去了1年多了,最近感觉没啥事就想着把这个小游戏重新写一下,感觉拿来练习练习代码风格还是不错的,由于在代码中写了很多的注释,所以在外部就不会写太多的说明了,大家可以多看看代码中的注释,希望这篇文章能够帮助到你🤭


项目需求

实现一款推箱子游戏,效果如下图所示,具体规则:

  1. 箱子只能推动而不能拉动;
  2. 如果箱子前一格是地板或箱子目的地,则可以推动一个箱子往前走一格,如果箱子已经在 箱子目的地则不能再推动;
  3. 推箱子的小人不能从箱子目的地上直接穿过;
  4. 注意不要把箱子推到死角上,不然就无法再推动它了;
  5. 所有箱子都成功推到箱子目的地,游戏结束,过关成功,或者步数超过100步游戏结束过关失败!

在这里插入图片描述

项目开始前需要准备的知识点

  • 搭台唱戏:
    1、首先大家要准备好自己用来表示各种类型的图片(背景,墙,人物,地板,箱子,以及成功过关和过关失败的图片)
    2、再利用easyx进行图形界面的设计

    如果大家没有资源的话我这有😀:
    美工资源
    提取码:2syq(感谢老师提供的美工资源)不过过关成功和过关失败的图大家得自己添加了,可以直接上网找个自己喜好的就好了,也没啥事
    easyx的下载网站:www.easyx.cn

	//搭台唱戏
	IMAGE bg_img;//文件背景句柄
	IMAGE success_img;//文件句柄:拿来存游戏完成的图片
	IMAGE failure_img;//文件句柄:拿来存游戏失败的图片
	initgraph(SCREEN_WIDTH,SCREEN_HIGHT);  //初始化幕布
	//加载道具图标
	loadimage(&bg_img,_T("blackground.bmp"),SCREEN_WIDTH,SCREEN_HIGHT,true);
	loadimage(&failure_img,_T("failure.jpg"),SCREEN_WIDTH,SCREEN_HIGHT,true);
	loadimage(&success_img,_T("success.jpg"),SCREEN_WIDTH,SCREEN_HIGHT,true);
	putimage(0,0,&bg_img);	//把背景刷上去,在(0,0)处刷背景
	
	//加载道具图标
	loadimage(&images[WALL],_T("wall_right.bmp"),RATIO,RATIO);
	loadimage(&images[FLOOR],_T("floor.bmp"),RATIO,RATIO);
	loadimage(&images[BOX_DES],_T("des.bmp"),RATIO,RATIO);
	loadimage(&images[MAN],_T("man.bmp"),RATIO,RATIO);
	loadimage(&images[BOX],_T("box.bmp"),RATIO,RATIO);
	loadimage(&images[HIT],_T("box.bmp"),RATIO,RATIO);
	for(int i=0;i<LINE;i++){
		for(int j=0;j<COLUMN;j++){
			putimage(START_X+j*RATIO,START_Y+i*RATIO,&images[map[i][j]]);
		}
	}

  • 热键控制

    头文件

  #include<conio.h>	//获取热键

获取按键

  if(_kbhit()){  //玩家有敲击键盘的动作
	    	char ch=_getch();  //获取刚刚按的键
		  ......
  }

项目的模块划分

在这里插入图片描述


项目实现

地图初始化

这里有些宏定义可以先不用看,主要还是看他的一些语法和整个框架,后面可执行源码我都会贴出来的

//墙: 0,地板: 1,箱子目的地: 2,   小人: 3, 箱子: 4, 箱子命中目标: 5
int map[LINE][COLUMN] = { 
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0 },
{ 0, 1, 4, 1, 0, 2, 1, 0, 2, 1, 0, 0 },
{ 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0 }, 
{ 0, 1, 0, 2, 0, 1, 1, 4, 1, 1, 1, 0 }, 
{ 0, 1, 1, 1, 1, 3, 1, 1, 1, 4, 1, 0 }, 
{ 0, 1, 2, 1, 1, 4, 1, 1, 1, 1, 1, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0 }, 
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};

	IMAGE bg_img;  //文件背景句柄
	IMAGE success_img;  //文件句柄:拿来存游戏完成的图片
	IMAGE failure_img;  //文件句柄:拿来存游戏失败的图片
	initgraph(SCREEN_WIDTH,SCREEN_HIGHT);  //初始化幕布
	loadimage(&bg_img,_T("blackground.bmp"),SCREEN_WIDTH,SCREEN_HIGHT,true);
	loadimage(&failure_img,_T("failure.jpg"),SCREEN_WIDTH,SCREEN_HIGHT,true);
	loadimage(&success_img,_T("success.jpg"),SCREEN_WIDTH,SCREEN_HIGHT,true);
	putimage(0,0,&bg_img);	//把背景刷上去
	
	//加载道具图标
	loadimage(&images[WALL],_T("wall_right.bmp"),RATIO,RATIO);
	loadimage(&images[FLOOR],_T("floor.bmp"),RATIO,RATIO);
	loadimage(&images[BOX_DES],_T("des.bmp"),RATIO,RATIO);
	loadimage(&images[MAN],_T("man.bmp"),RATIO,RATIO);
	loadimage(&images[BOX],_T("box.bmp"),RATIO,RATIO);
	loadimage(&images[HIT],_T("box.bmp"),RATIO,RATIO);
	
	//把每个位置对应的图片都刷上去
	for(int i=0;i<LINE;i++){
		for(int j=0;j<COLUMN;j++){
			putimage(START_X+j*RATIO,START_Y+i*RATIO,&images[map[i][j]]);
		}
	}

实现效果:
在这里插入图片描述

热键控制

	#include<conio.h>
	
	#define KEY_UP 'w'	//char 'a' 
	#define KEY_LEFT 'a'
	#define KEY_RIGHT 'd'
	#define KEY_DOWN 's'
	#define KEY_QUIT 'q'  //退出
	
	do{
		if(_kbhit()){  //玩家有敲击键盘的动作
			char ch=_getch();  //获取按的键
			if(ch==KEY_UP){
				gameControl(UP);
				step++;
			}else if(ch==KEY_DOWN){
				gameControl(DOWN);
				step++;
			}else if(ch==KEY_RIGHT){
				gameControl(RIGHT);
				step++;
			}else if(ch==KEY_LEFT){
				gameControl(LEFT);
				step++;
			}else if(ch==KEY_QUIT){
				quit=true;
			}
		
		}
	  }while(quit==false);

推箱子控制

同样的,这里也有一些结构体的定义和枚举类型的定义,先别太在意😂

/***************************
*改变游戏中的道具并且显示出来
*输入:line - 道具在地图数组的行下标
*	   column - 道具在地图数组的列下标
*	   prop-道具的类型
*返回值:无
****************************/
void changeMap(POS *pos,PROPS prop){
	map[pos->x][pos->y]=prop;
	putimage(START_X+pos->y*RATIO,START_Y+pos->x*RATIO,&images[prop]);
}


/**********************************************
*实现游戏四个方向(上、下、左、右)的控制 
* 输入:
* direct - 人前进方向 
* 返回值: 无 
**********************************************/
void gameControl(DIRECTION direct){
	POS next_pos=man;
	POS next_next_pos=man;
	switch(direct){
		case UP:
			next_pos.x--;
			next_next_pos.x-=2;
			break;
		case DOWN:
			next_pos.x++;
			next_next_pos.x+=2;
			break;
		case LEFT:
			next_pos.y--;
			next_next_pos.y-=2;
			break;
		case RIGHT:
			next_pos.y++;
			next_next_pos.y+=2;
			break;
		default:
			break;
	
	}


	//用带参数宏isValid来代替那一大串判断:
	//#define isValid(pos) pos.x>=0 && pos.x<LINE && pos.y>=0 && pos.y<COLUMN
	if(isValid(next_pos)&&map[next_pos.x][next_pos.y]==FLOOR){  //前方是地板
		changeMap(&next_pos,MAN);
		changeMap(&man,FLOOR);
		man=next_pos;
	}else if(isValid(next_next_pos)&&map[next_pos.x][next_pos.y]==BOX){  //人的前方是箱子时
		//两种情况,箱子前方是地板或者是目的地
		if(map[next_next_pos.x][next_next_pos.y]==FLOOR){
			changeMap(&next_next_pos,BOX);
			changeMap(&next_pos,MAN);
			changeMap(&man,FLOOR);
			man=next_pos;
		}else if(map[next_next_pos.x][next_next_pos.y]==BOX_DES){
			changeMap(&next_next_pos,HIT);//变成HIT以后虽然还是显示箱子,但是已经不能把这个位置上的箱子推向别的地方
			changeMap(&next_pos,MAN);
			changeMap(&man,FLOOR);
			man=next_pos;
		}
		
	}
	
	//由于代码重用太多了进行优化👆
	/*
	int x=man.x;
	int y=man.y;
	if(direct==UP){//先处理前进方向时地板的情况 x-1
		
	}else if(direct==DOWN){
			if((x+1)<LINE&&map[next_pos.x][next_pos.y]==FLOOR){
				changeMap(next_pos.x,next_pos.y,MAN);
				changeMap(man.x,man.y,FLOOR);
				man=next_pos;
			}
	}else if(direct==RIGHT){
			if((y+1)<COLUMN&&map[next_pos.x][next_pos.y]==FLOOR){
				changeMap(next_pos.x,next_pos.y,MAN);
				changeMap(man.x,man.y,FLOOR);
				man=next_pos;
			}	
	}else if(direct==LEFT){
			if((y-1)>=0&&map[next_pos.x][next_pos.y]==FLOOR){
				changeMap(next_pos.x,next_pos.y,MAN);
				changeMap(man.x,man.y,FLOOR);
				man=next_pos;
		}
	}
	*/
}

游戏结束

游戏结束:

/*******************************************
*判断游戏是否结束,如果不存在一个箱子目的地则结束
*(其实还有很多种方案,比如可以在把箱子推到目的地时计数)
* 当step>100时游戏也结束
*输入:无
*结束了:输出true
*未结束:输出flase
*******************************************/
bool isGameOver(){
	//步数超过100步游戏结束且failure为true(失败)
	if(step>100){
		failure=true;
		return true;
	}
	for(int i=0;i<=LINE;i++){
		for(int j=0;j<COLUMN;j++){
			if(map[i][j]==BOX_DES)return false;
		}
	}
	return true;
}

结束场景

/*************************
*结束场景:玩家通关后显示
*输入:
*       bg-背景图片变量的指针
*返回值:无
**************************/
void gameOverSuccess(IMAGE* bg){
	putimage(0,0,bg);	
	settextcolor(WHITE);//设置文本颜色
	RECT rec={0,0,SCREEN_WIDTH,SCREEN_HIGHT};//画一个矩形
	settextstyle(20,0,_T("宋体"));
	/*
	 *文字:恭喜.....
	 *&rec:以rec为参考物
	 *DT_CENTER:水平居中
	 *DT_VCENTER:垂直居中
	 *DT_SINGLELINE:文字显示单行
	 */
	drawtext(_T("恭喜成为一名优秀的推箱子老司机!!!"),&rec,DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}


//游戏通关失败
void gameOverFailure(IMAGE* bg){
	putimage(0,0,bg);	
	settextcolor(WHITE);//设置文本颜色
	RECT rec={0,0,SCREEN_WIDTH,SCREEN_HIGHT};//画一个矩形
	settextstyle(20,0,_T("宋体"));
	/*
	 *文字:恭喜.....
	 *&rec:以rec为参考物
	 *DT_CENTER:水平居中
	 *DT_VCENTER:垂直居中
	 *DT_SINGLELINE:文字显示单行
	 */
	drawtext(_T("步数超标了哦!!!"),&rec,DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}

在main中

	if(isGameOver()){
			if(failure==true){  //如果failure为true则证明步数超标
				gameOverFailure(&failure_img);
			}else{
				gameOverSuccess(&success_img);
			}
			quit=true;
		}


可执行源码

.h文件

//box_man.h
#include<graphics.h>
#include<stdlib.h>
#include<cstdio>
#include<conio.h>	//获取热键

//利用宏定义好改值,可读性和可维护性
#define LINE 9
#define COLUMN 12
#define KEY_UP 'w'	//char 'a' 
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define KEY_DOWN 's'
#define KEY_QUIT 'q'  
#define RATIO 61  //比例	
#define SCREEN_WIDTH 960  
#define SCREEN_HIGHT 768

//起始位置
#define START_X 100
#define START_Y 150
#define isValid(pos) pos.x>=0 && pos.x<LINE && pos.y>=0 && pos.y<COLUMN
typedef enum _PROPS PROPS;
typedef enum _DIRECTION DIRECTION;
typedef struct _POS POS;

//设置枚举值
enum _PROPS{
	WALL,	//墙
	FLOOR,	//地板
	BOX_DES,//箱子目的地
	MAN,	//人
	BOX,	//箱子
	HIT,	//箱子命中目标
	ALL		//枚举元素有多少
};


//游戏控制方向
enum _DIRECTION{
		UP,
		DOWN,
		LEFT,
		RIGHT
};

//存小人的位置 
struct _POS{//很多结构体的定义都是以这种方式用“_”开头
	int x;//小人的行
	int y;//小人的列
};

int step;//记录步数
bool failure=false;
IMAGE images[ALL];
POS man;

bool isGameOver();
void gameOverSuccess(IMAGE* bg);
void gameOverFailure(IMAGE* bg);
void changeMap(POS *pos,PROPS prop);
void gameControl(DIRECTION direct);
void initMan();

.cpp文件

//box_man.cpp
#include"box_man.h"

//墙: 0,地板: 1,箱子目的地: 2,   小人: 3, 箱子: 4, 箱子命中目标: 5
int map[LINE][COLUMN] = { 
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0 },
{ 0, 1, 4, 1, 0, 2, 1, 0, 2, 1, 0, 0 },
{ 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0 }, 
{ 0, 1, 0, 2, 0, 1, 1, 4, 1, 1, 1, 0 }, 
{ 0, 1, 1, 1, 1, 3, 1, 1, 1, 4, 1, 0 }, 
{ 0, 1, 2, 1, 1, 4, 1, 1, 1, 1, 1, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0 }, 
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};


/*******************************************
*判断游戏是否结束,如果不存在一个箱子目的地则结束
*(其实还有很多种方案,比如可以在把箱子推到目的地时计数)
* 当step>100时游戏也结束
*输入:无
*结束了:输出true
*未结束:输出flase
*******************************************/
bool isGameOver(){
	//步数超过100步游戏结束且failure为true(失败)
	if(step>100){
		failure=true;
		return true;
	}
	for(int i=0;i<=LINE;i++){
		for(int j=0;j<COLUMN;j++){
			if(map[i][j]==BOX_DES)return false;
		}
	}
	return true;
}


/*************************
*结束场景:玩家通关后显示
*输入:
*       bg-背景图片变量的指针
*返回值:无
**************************/
void gameOverSuccess(IMAGE* bg){
	putimage(0,0,bg);	
	settextcolor(WHITE);//设置文本颜色
	RECT rec={0,0,SCREEN_WIDTH,SCREEN_HIGHT};//画一个矩形
	settextstyle(20,0,_T("宋体"));
	/*
	 *文字:恭喜.....
	 *&rec:以rec为参考物
	 *DT_CENTER:水平居中
	 *DT_VCENTER:垂直居中
	 *DT_SINGLELINE:文字显示单行
	 */
	drawtext(_T("恭喜成为一名优秀的推箱子老司机!!!"),&rec,DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}


//游戏失败
void gameOverFailure(IMAGE* bg){
	putimage(0,0,bg);	
	settextcolor(WHITE);//设置文本颜色
	RECT rec={0,0,SCREEN_WIDTH,SCREEN_HIGHT};//画一个矩形
	settextstyle(20,0,_T("宋体"));
	/*
	 *文字:恭喜.....
	 *&rec:以rec为参考物
	 *DT_CENTER:水平居中
	 *DT_VCENTER:垂直居中
	 *DT_SINGLELINE:文字显示单行
	 */
	drawtext(_T("步数超标了哦!!!"),&rec,DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}

/***************************
*改变游戏中的道具并且显示出来
*输入:line - 道具在地图数组的行下标
*	   column - 道具在地图数组的列下标
*	   prop-道具的类型
*返回值:无
****************************/
void changeMap(POS *pos,PROPS prop){
	map[pos->x][pos->y]=prop;
	putimage(START_X+pos->y*RATIO,START_Y+pos->x*RATIO,&images[prop]);
}


/**********************************************
*实现游戏四个方向(上、下、左、右)的控制 
* 输入:
* direct - 人前进方向 
* 返回值: 无 
**********************************************/
void gameControl(DIRECTION direct){
	POS next_pos=man;
	POS next_next_pos=man;
	switch(direct){
		case UP:
			next_pos.x--;
			next_next_pos.x-=2;
			break;
		case DOWN:
			next_pos.x++;
			next_next_pos.x+=2;
			break;
		case LEFT:
			next_pos.y--;
			next_next_pos.y-=2;
			break;
		case RIGHT:
			next_pos.y++;
			next_next_pos.y+=2;
			break;
		default:
			break;
	
	}


	//用带参数宏isValid来代替那一大串判断:
	//#define isValid(pos) pos.x>=0 && pos.x<LINE && pos.y>=0 && pos.y<COLUMN
	if(isValid(next_pos)&&map[next_pos.x][next_pos.y]==FLOOR){  //前方是地板
		changeMap(&next_pos,MAN);
		changeMap(&man,FLOOR);
		man=next_pos;
	}else if(isValid(next_next_pos)&&map[next_pos.x][next_pos.y]==BOX){  //人的前方是箱子时
		//两种情况,箱子前方是地板或者是目的地
		if(map[next_next_pos.x][next_next_pos.y]==FLOOR){
			changeMap(&next_next_pos,BOX);
			changeMap(&next_pos,MAN);
			changeMap(&man,FLOOR);
			man=next_pos;
		}else if(map[next_next_pos.x][next_next_pos.y]==BOX_DES){
			changeMap(&next_next_pos,HIT);//变成HIT以后虽然还是显示箱子,但是已经不能把这个位置上的箱子推向别的地方
			changeMap(&next_pos,MAN);
			changeMap(&man,FLOOR);
			man=next_pos;
		}
		
	}
	
	//由于代码重用太多了进行优化👆
	/*
	int x=man.x;
	int y=man.y;
	if(direct==UP){//先处理前进方向时地板的情况 x-1
		
	}else if(direct==DOWN){
			if((x+1)<LINE&&map[next_pos.x][next_pos.y]==FLOOR){
				changeMap(next_pos.x,next_pos.y,MAN);
				changeMap(man.x,man.y,FLOOR);
				man=next_pos;
			}
	}else if(direct==RIGHT){
			if((y+1)<COLUMN&&map[next_pos.x][next_pos.y]==FLOOR){
				changeMap(next_pos.x,next_pos.y,MAN);
				changeMap(man.x,man.y,FLOOR);
				man=next_pos;
			}	
	}else if(direct==LEFT){
			if((y-1)>=0&&map[next_pos.x][next_pos.y]==FLOOR){
				changeMap(next_pos.x,next_pos.y,MAN);
				changeMap(man.x,man.y,FLOOR);
				man=next_pos;
		}
	}
	*/
}

//找到man最开始的位置
void initMan(){
	for(int i=0;i<LINE;i++){
		for(int j=0;j<COLUMN;j++){
			if(map[i][j]==MAN)man.x=i,man.y=j;
		}
	}
}


int main() {
	//搭台唱戏
	IMAGE bg_img;//文件背景句柄
	IMAGE success_img;//文件句柄:拿来存游戏完成的图片
	IMAGE failure_img;//文件句柄:拿来存游戏失败的图片
	initgraph(SCREEN_WIDTH,SCREEN_HIGHT);  //初始化幕布
	loadimage(&bg_img,_T("blackground.bmp"),SCREEN_WIDTH,SCREEN_HIGHT,true);
	loadimage(&failure_img,_T("failure.jpg"),SCREEN_WIDTH,SCREEN_HIGHT,true);
	loadimage(&success_img,_T("success.jpg"),SCREEN_WIDTH,SCREEN_HIGHT,true);
	putimage(0,0,&bg_img);	//把背景刷上去
	
	//加载道具图标
	loadimage(&images[WALL],_T("wall_right.bmp"),RATIO,RATIO);
	loadimage(&images[FLOOR],_T("floor.bmp"),RATIO,RATIO);
	loadimage(&images[BOX_DES],_T("des.bmp"),RATIO,RATIO);
	loadimage(&images[MAN],_T("man.bmp"),RATIO,RATIO);
	loadimage(&images[BOX],_T("box.bmp"),RATIO,RATIO);
	loadimage(&images[HIT],_T("box.bmp"),RATIO,RATIO);
	for(int i=0;i<LINE;i++){
		for(int j=0;j<COLUMN;j++){
			putimage(START_X+j*RATIO,START_Y+i*RATIO,&images[map[i][j]]);
		}
	}
	
	//找到man的位置
	initMan();


	//循环:游戏环节
	bool quit=false;
	do{
		if(_kbhit()){  //玩家有敲击键盘的动作
			char ch=_getch();  //获取按的键
			if(ch==KEY_UP){
				gameControl(UP);
				step++;
			}else if(ch==KEY_DOWN){
				gameControl(DOWN);
				step++;
			}else if(ch==KEY_RIGHT){
				gameControl(RIGHT);
				step++;
			}else if(ch==KEY_LEFT){
				gameControl(LEFT);
				step++;
			}else if(ch==KEY_QUIT){
				quit=true;
			}
		
		}
		if(isGameOver()){
			if(failure==true){  //如果failure为true则证明步数超标
				gameOverFailure(&failure_img);
			}else{
				gameOverSuccess(&success_img);
			}
			quit=true;
		}
		Sleep(100);  //避免许多无效循环,所以我们用Sleep来暂停0.1秒
	}while(quit==false);
	system("pause");

	//释放资源
	closegraph();
	return 0;
}

运行结果

开始:
在这里插入图片描述
过关成功:在这里插入图片描述
过关失败:
在这里插入图片描述


感谢你看完了,如果文章中有错误或者说是在哪方面可以优化的地方希望你可以评论出来,同样也希望能和大家共同进步😁

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值