【C++项目实现】推箱子(含数据库实现)

✍ 个人博客:https://blog.csdn.net/Newin2020?spm=1011.2415.3001.5343
📚 专栏目标:C++项目集合
📣 专栏定位:为刚学完C++或还在学习C++过程中的小伙伴提供练手的项目,帮助大家快速熟悉C++语法。
❤️ 如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
📝 参考视频:B 站 c++ 推箱子项目教程
🎏 唠叨唠叨:在这个专栏里,我将会整理一些有趣的C++项目来给大家练手~

项目实现 - 推箱子

一、项目需求

规则如下:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rEVMzEuD-1669547631552)(项目实现 - 推箱子.assets/image-20221125201322574.png)]

二、项目实现

地图初始化

这里的 x y 坐标和数学中的定义不太一样,这里的 x 坐标往下是增大,y 坐标往右是增大。

另外,下面代码中为了方便理解,暂时没有引入数据库,所以地图是用二维数组在程序中保存起来,后续用到数据库后会从数据库中读取地图信息(代码和数据库那里可能会比较混,但是在最后全部代码中只会用到数据库版本的代码)。

#include<graphics.h>
#include<iostream>
#include<stdlib.h>
#include<string>

using namespace std

#define RATIO 61	//地图中每个小方块的大小

#define SCREEN_WIDTH 960	//背景宽度
#define SCREEN_HEIGHT 768	//背景高度

#define LINE 9			//地图行数
#define COLUMN 12		//地图列数
    
#define START_X 100		//地图开始的横坐标
#define START_Y 150		//地图开始的纵坐标

enum _PROPS {
	WALL,	//墙:0
	FLOOR,	//地板:1
	BOX_DES,//箱子目的地:2
	MAN,	//小人:3
	BOX,	//箱子:4
	HIT,	//箱子命中目标:5
	ALL		//道具数量:6
};

struct _POS {
	int x;	//小人所在的二维数组的行
	int y;	//小人所在的二维数组的列
};

IMAGE images[ALL];	//存储道具图标

struct _POS man;

/**************************************
* 游戏地图
* 墙: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,0,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},
};

int main()
{
	IMAGE bg_img;	//存储背景

	//初始化背景
	initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);
	loadimage(&bg_img, _T("blackground.bmp"), SCREEN_WIDTH, SCREEN_HEIGHT, true);
	putimage(0, 0, &bg_img);

	//加载道具图标
	loadimage(&images[WALL], _T("wall.bmp"), RATIO, RATIO, true);
	loadimage(&images[FLOOR], _T("floor.bmp"), RATIO, RATIO, true);
	loadimage(&images[BOX_DES], _T("des.bmp"), RATIO, RATIO, true);
	loadimage(&images[MAN], _T("man.bmp"), RATIO, RATIO, true);
	loadimage(&images[BOX], _T("box.bmp"), RATIO, RATIO, true);
	loadimage(&images[HIT], _T("box.bmp"), RATIO, RATIO, true);

	//打印道具图标
	for (int i = 0; i < LINE; i++) {
		for (int j = 0; j < COLUMN; j++) {
            //统计目标箱子数
			if (map[i][j] == BOX_DES)	nums_hit++;
			//获取小人初始位置
			if (map[i][j] == MAN) {
				man.x = i;
				man.y = j;
			}
			putimage(START_X + j * RATIO, START_Y + i * RATIO, &images[map[i][j]]);
		}
	}

	return 0;
}

热键控制

热键定义:左=>a 下=>s 上=>w 右=>d 退出=>q

#include<conio.h>

//控制键上、下、左、右控制方向,'q'退出
#define KEY_UP 'w'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define KEY_DOWN 's'
#define KEY_QUIT 'q'

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

//主函数
//...
	//游戏环节
	bool quit = false;
	do {
		if (_kbhit()) {//玩家按键
			char ch = _getch();

			if (ch == KEY_UP) {
				gameControl(UP);
			}
			else if (ch == KEY_DOWN) {
				gameControl(DOWN);
			}
			else if (ch == KEY_LEFT) {
				gameControl(LEFT);
			}
			else if (ch == KEY_RIGHT) {
				gameControl(RIGHT);
			}
			else if (ch == KEY_QUIT) {
				quit = true;
			}
            if (isGameOver()) {
				quit = true;
				gameOverScence(&bg_img);
			}
		}
		Sleep(100);
	} while (quit == false); //!quit
//...

推箱子控制

难点:

  1. 小人可以从箱子目的地上经过。
  2. 小人可以将箱子从目的地上推开。

实现上面两个需求我们可以设置额外的两个变量:

  • nums_hit —— 用于记录当前还剩多少箱子没推到目的地上
  • last —— 用于记录小人上次待在什么地方(FLOOR/BOX_DES),初始化为 FLOOR
/******************************************
* 改变并打印当前图标在地图中的位置
* 输入:pos - 当前下标
*		prop - 当前图标
* 输出:void
******************************************/
void changeMap(struct _POS& pos, enum _PROPS prop){
	map[pos.x][pos.y] = prop;
	putimage(START_X + pos.y * RATIO, START_Y + pos.x * RATIO, &images[prop]);
}

/******************************************
* 判断当前位置是否越界
* 输入:pos 当前下标
* 输出:bool
******************************************/
bool isValid(struct _POS pos){
	if (map[pos.x][pos.y] == WALL)	return false;
	if (pos.x < 0 && pos.x >= LINE && pos.y < 0 && pos.y >= COLUMN)
		return false;
	return true;
}

/******************************************
* 实现游戏四个方向的控制(上、下、左、右)
* 输入:direct - 人前进的方向
* 输出:void
******************************************/
void gameControl(enum _DIRECTION direct)
{
	struct _POS next_pos = man;
	struct _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;
	}

	if (isValid(next_pos) && map[next_pos.x][next_pos.y] == FLOOR){	//如果小人前面是地板
		changeMap(next_pos, MAN);
		changeMap(man, last);
		last = FLOOR;
		man = next_pos;
	}	
	else if (isValid(next_pos) && map[next_pos.x][next_pos.y] == BOX_DES) {	//如果小人前面是箱子目的地
		changeMap(man, last);
		//小人可以经过HIT,但不能改变其在地图上的值
		putimage(START_X + next_pos.y * RATIO, START_Y + next_pos.x * RATIO, &images[MAN]);
		man = next_pos;
		last = BOX_DES;
	}
	else if (isValid(next_next_pos) && (map[next_pos.x][next_pos.y] == BOX || map[next_pos.x][next_pos.y] == HIT)) { //如果小人前面是箱子或者已经在目的地上的箱子
		//如果要将箱子从目的地上推开,要用更新last变量,并且nums_hit要加1
		bool is_hit = false;
		//如果箱子推不动,就直接退出,以免更改last变量导致下次移动出现bug
		if (!(map[next_next_pos.x][next_next_pos.y] == FLOOR || map[next_next_pos.x][next_next_pos.y] == BOX_DES))
			return;
        //last需要在最后更新,防止覆盖之前的数据,因为接下来还要更新箱子和小人的位置
		if (map[next_pos.x][next_pos.y] == HIT) {
			is_hit = true;
			nums_hit++;
		}

		if (map[next_next_pos.x][next_next_pos.y] == FLOOR) {
			changeMap(next_next_pos, BOX);
			changeMap(next_pos, MAN);
			changeMap(man, last);
			man = next_pos;
		}
		else if (map[next_next_pos.x][next_next_pos.y] == BOX_DES) {
			changeMap(next_next_pos, HIT);
			changeMap(next_pos, MAN);
			changeMap(man, last);
			man = next_pos;
			nums_hit--;
		}

		//更新小人上次访问的位置
		if (is_hit)	last = BOX_DES;
		else last = FLOOR;
	}
}

游戏结束

将判断游戏是否结束以及游戏结束的通关场景封装成函数。

/*****************************************************
* 判断游戏是否结束
* 输入:无
* 输出:bool - true表示游戏结束,false表示游戏未结束
******************************************************/
bool isGameOver(){
	if (nums_hit != 0)	return false;
	return true;
}

/******************************************
* 游戏结束通关场景
* 函数里参数:
* DT_CENTER - 水平居中
* DT_VCENTER - 垂直居中
* DT_SINGLELINE - 文字显示在一行
* 输入:bg - 图片变量指针
* 输出:无
******************************************/
void gameOverScence(IMAGE* bg) {
	putimage(0, 0, bg);
	settextcolor(WHITE);
	RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };	//定义一个矩形
	settextstyle(20, 0, _T("宋体"));		//设置字的大小与风格
	drawtext(_T("恭喜您~\n游戏成功通关!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}

三、数据库实现

数据库表设计

用户表
字段名类型是否为空默认值主、外键备注
idint(11)NOT1,自增长PK用户 id
usernamevarchar(64)NOT用户名:英文字符、数字和特殊符号的组合
passwordvarchar(32)NOT密码:英文字符、数字和特殊符号的组合, 8-16 位
level_idint1当前关卡,关联 Levels 表中的 id
-- 用户表
create table users(
    id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
    username varchar(64) NOT NULL UNIQUE,
    password varchar(32) NOT NULL,
    level_id int default 1
);
关卡表
字段名类型是否为空默认值主、外键备注
idintNOT1PK游戏关卡序号,从 1 开始
namevarchar(64)NOT地图名称
map_rowintNOT地图二维组的总行数
map_columnintNOT地图二维组的总列数
map_datavarchar(4096)NOT地图数据,二维数组对应的行列式,多行以|分开,列以逗号分隔,最大接近 45×45 的地图
next_level_idint0下一关的关卡 id ,0 代表通关
初始化信息
-- 创建一个用户
insert into users values(1000, 'gdx', md5('123456gdx'), 1);
insert into users values(2, 'rock', md5('rock1234'), 2);

-- 创建一个关卡
insert into levels values(1, '牛刀小试', 9, 12, 
'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,0,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',0);
insert into levels values(2, '再接再厉', 12, 13, '0,0,0,0,0,0,0,0,0,0,0,0,0|0,1,1,1,1,1,1,1,0,0,0,0,0|0,1,1,4,1,1,1,4,0,0,0,0,0|0,0,0,0,1,1,1,1,0,0,0,0,0|0,0,0,0,1,4,1,1,0,0,0,0,0|0,0,0,0,1,1,1,4,1,0,0,0,0|0,2,2,1,4,1,0,0,1,0,0,0,0|0,2,2,1,1,4,0,0,1,1,1,1,0|0,2,2,1,4,3,1,1,1,1,1,1,0|0,2,0,0,0,4,0,0,0,1,0,1,0|0,2,0,0,0,1,1,1,1,1,0,0,0|0,0,0,0,0,0,0,0,0,0,0,0,0|', 0);

登录验证

database.h

定义用户信息的结构体,并定义从数据库获取用户信息的接口。

#pragma once
#include<string>

using namespace std;

typedef struct _userinfo {
	int id;				//用户id
	string username;	//用户名
	string passwd;		//密码
	int level_id;		//关卡id
}userinfo;

bool fetch_user_info(userinfo& user);
database.cpp

实现获取用户信息的接口,并将数据库连接的功能也封装成一个函数调用。

另外,下面 DB_USERDB_USER_PASSWD 要换成自己本地数据库的用户和密码,不然无法连接到本地数据库。

#include "database.h"
#include <mysql.h>
#include <stdio.h>

#define DB_NAME "box_man"
#define DB_HOST "127.0.0.1"
#define DB_PORT 3306
#define DB_USER "root"
#define DB_USER_PASSWD "123456"

static int debug = 1;

static bool connect_db(MYSQL& mysql);

/******************************************
* 功能:通过用户名和密码获取用户信息
* 输入:
*	user - 用户信息结构体
* 返回值:
*	获取成功返回ture,失败返回false
******************************************/
bool fetch_user_info(userinfo& user) {
	MYSQL mysql;
	MYSQL_RES* res;	//查询结果集
	MYSQL_ROW row;	//记录结构体
	char sql[256];
	bool ret = false;

	//1.连接到数据库
	if (connect_db(mysql) == false) {
		return false;
	}

	//2.根据用户名和密码获取用户信息(id, level_id)
	snprintf(sql, 256, "select id, level_id from users where username='%s' and password=md5('%s')", user.username.c_str(), user.passwd.c_str());
	ret = mysql_query(&mysql, sql);	//成功返回0

	if (ret) {
		printf("数据库查询出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));
		mysql_close(&mysql);
		return false;
	}

	//3.获取结果
	res = mysql_store_result(&mysql);
	row = mysql_fetch_row(res);

	if (row == NULL) {
		mysql_free_result(res);
		mysql_close(&mysql);
		return false;
	}

	user.id = atoi(row[0]);
	user.level_id = atoi(row[1]);
	if(debug) printf("userif: %d  level_id: %d\n", user.id, user.level_id);	//打印id

	//4.返回结果

	//释放结果集
	mysql_free_result(res);

	//关闭数据库
	mysql_close(&mysql);

	return true;
}

bool connect_db(MYSQL& mysql) {
	//1.初始化数据库句柄
	mysql_init(&mysql);

	//2.设置字符编码
	mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");

	//3.连接数据库
	if (mysql_real_connect(&mysql, DB_HOST, DB_USER, DB_USER_PASSWD, DB_NAME, DB_PORT, NULL, 0) == NULL) {
		printf("数据库连接出错,错误原因:%s\n", mysql_error(&mysql));
		return false;
	}

	return true;
}
box_man.cpp

将数据库登陆验证封装成一个函数进行调用,并在主函数中调用相关函数。

#include "database.h"

//数据库登录验证功能
bool login(userinfo& user) {
	int times = 0;
	bool ret = false;

	do {
		cout << "输入用户名:";
		cin >> user.username;

		cout << "请输入密码:";
		cin >> user.passwd;

		//返回bool,成功返回true,失败返回false
		ret = fetch_user_info(user);
		times++;
		if (times >= MAX_RETRY_TIMES) {
			break;
		}
		if (ret == false) {
			cout << "登录失败,请重新输入!" << endl;
		}
	} while (!ret);

	return ret;
}

int main(){
    //用户身份验证
	userinfo user;

	if (!login(user)) {
		cout << "登陆失败,请重新登录!" << endl;
		::system("pause");
		exit(-1);
	}
	else {
		cout << "用户" << user.id << ",您当前所在的关卡是:level-" << user.level_id << endl;
		cout << "您已登陆成功,请开始您的表演!" << endl;
		::system("pause");
	}
    
    //...
    
}

获取关卡

database.h

定义关卡信息结构体,并定义从数据库获取关卡信息的接口。

typedef struct _levelinfo {
	int id;			 //关卡的id
	string name;	 //关卡的名字
	int map_row;	 //地图总行数
	int map_column;	 //地图总列数
	string map_data; //二维地图数据
	int next_level;	 //下一关卡的id
}levelinfo;

bool fetch_level_info(levelinfo& level, int level_id);
database.cpp

实现从数据库获取关卡信息的接口。

/*********************************************************
* 功能:根据关卡id获取完整的关卡信息(如:地图,下一关等)
* 输入:
*	level - 保存关卡信息的结构体变量
*	level_id - 要获取详细关卡信息的关卡id
* 返回值:
*	获取成功返回ture,失败返回false
*********************************************************/
bool fetch_level_info(levelinfo& level, int level_id){
	MYSQL mysql;
	MYSQL_RES* res;	//查询结果集
	MYSQL_ROW row;	//记录结构体
	char sql[256];
	bool ret = false;

	//1.连接到数据库
	if (connect_db(mysql) == false) {
		return false;
	}

	//2.根据关卡id查询数据库获取关卡地图信息
	snprintf(sql, 256, "select name, map_row, map_column, map_data, next_level_id from levels where id=%d;", level_id);
	ret = mysql_query(&mysql, sql);	//成功返回0

	if (ret) {
		printf("数据库查询出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));
		mysql_close(&mysql);
		return false;
	}

	//3.获取结果
	res = mysql_store_result(&mysql);
	row = mysql_fetch_row(res);

	if (row == NULL) {
		mysql_free_result(res);
		mysql_close(&mysql);
		return false;
	}

	level.id = level_id;
	level.name = row[0];
	level.map_row = atoi(row[1]);
	level.map_column = atoi(row[2]);
	level.map_data = row[3];
	level.next_level = atoi(row[4]);
	if(debug) printf("level id: %d  name: %s map row: %d  map column: %d map data: %s next level: %d\n", level.id, level.name.c_str(), level.map_row, level.map_column, level.map_data.c_str(), level.next_level);
	
	//4.返回结果

	//释放结果集
	mysql_free_result(res);

	//关闭数据库
	mysql_close(&mysql);

	return true;
}
box_man.cpp

调用从数据库获取关卡信息的接口。

int main(){
    levelinfo level;
	bool ret = false;
    
    //...
    
    //根据用户所在的关卡id获取关卡数据
	ret = fetch_level_info(level, user.level_id);

	if (!ret) {
		cout << "获取关卡数据失败,请重试!" << endl;
		::system("pause");
		exit(-1);
	}

	::system("pause");
    
    //...
    
}

地图适配

前面提到地图暂时使用二维数组保存的,现在有了数据库所以可以从数据库中读取。但是地图的大小可变,故我们可以先将地图的行数和列数定义到一个最大值 48 ,这个 48×48 的二维数组足矣存下我们的所有地图。

每次读取地图后都用一个借口将得到的数据转换到二维数组 map 中,然后通过得到的行数和列数对这个二维数组进性读取使用,也就是说这个二维数组中的空间不一定完全用得到,可根据读入的地图信息用相应的一部分。

database.h

我们可以将行数和列数最大值以及地图转换接口定义到该头文件中。

#define LINE 48			//地图行数
#define COLUMN 48		//地图列数

bool transform_map_db2array(levelinfo& level, int map[LINE][COLUMN]);
database.cpp

实现地图转换功能。

/*********************************************************
* 功能:将从数据库读到的地图数据转换到二维数组中
* 输入:
*	level - 保存关卡信息的结构体变量
*	map	- 存储地图数据的二维数组
* 返回值:
*	获取成功返回ture,失败返回false
*********************************************************/
bool transform_map_db2array(levelinfo& level, int map[LINE][COLUMN]) {
	if (level.map_row > LINE || level.map_column > COLUMN) {
		printf("地图过大,请重新设置!\n");
		return false;
	}

	if (level.map_data.length() < 1) {
		printf("地图数据有误,请重新设置!\n");
		return false;
	}

	int start = 0, end = 0;
	int row = 0, column = 0;

	do {
		//找到一行数据的末尾
		end = level.map_data.find('|', start);

		//判断是否已经读到字符串结尾
		if (end < 0)	end = level.map_data.length();

		//如果起始位置大于等于终止位置,直接退出
		if (start >= end)	break;

		//截取一行数据
		string line = level.map_data.substr(start, end - start);
		printf("get line: %s\n", line.c_str());

		//对行数据进行解析
		char* next_token = NULL;
		char* item = strtok_s((char*)line.c_str(), ",", &next_token);
		column = 0;
		while (item && column < level.map_column) {
			printf("%s ", item);
			map[row][column] = atoi(item);
			column++;
			item = strtok_s(NULL, ",", &next_token);
		}

		//检查列数是否设置正确
		if (column < level.map_column) {
			printf("地图列数少于设定,%d(need:%d),终止!\n", column, level.map_column);
			return false;
		}

		printf("\n");
		row++;

		//如果行数过多,则直接截取
		if (row >= level.map_row) {
			break;
		}

		//读取下一行
		start = end + 1;

	} while (1);

	if (row < level.map_row) {
		printf("地图行数少于设定,%d(need:%d),终止!", row, level.map_row);
		return false;
	}

	return true;
}
box_man.cpp

调用地图转换接口(另外,记得将该文件中其它用到 LINECOLUMN 的地方改成从数据库获取到的行数 level.map_rowlevel.map_column ,最终版会呈现在最后的全部代码板块)。

int main(){
    
    //...
    
    	//把数据库中的地图转换到map中
	ret = transform_map_db2array(level, map);

	if (!ret) {
		cout << "地图数据转换失败,请重试!" << endl;
		::system("pause");
		exit(-1);
	}
    
    //...
    
}

下一关跳转

database.h

定义用户下一关关卡 id 更新接口。

bool update_user_level(userinfo& user, int next_level_id);
database.cpp

实现用户下一关关卡 id 更新功能。

/******************************************
* 功能:更新用户下一关信息
* 输入:
*	user - 用户信息结构体
*	next_level_id - 下一关的id
* 返回值:
*	更新成功返回ture,失败返回false
******************************************/
bool update_user_level(userinfo& user, int next_level_id)
{
	MYSQL mysql;
	MYSQL_RES* res;	//查询结果集
	MYSQL_ROW row;	//记录结构体
	char sql[256];
	bool ret = false;

	//1.连接到数据库
	if (connect_db(mysql) == false) {
		return false;
	}

	//2.根据用户id更新下一关的level_id
	snprintf(sql, 256, "update users set level_id=%d where id=%d;", next_level_id, user.id);

	ret = mysql_query(&mysql, sql);

	if (ret) {
		printf("数据库更新出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));
		mysql_close(&mysql);
		return false;
	}

	return true;
}
box_man.cpp

更改游戏结束通关场景函数接口,这里将其划分成两个函数,一个是用于除最后一关通关后的通关画面打印,一个是用于通关所有关卡后的画面打印。

另外,还需要在主函数里加上 do…while 循环使用户完成当前关卡后能够跳到下一关,下面代码显示主要部分,其它代码细节会放到最后全部代码板块。

/******************************************
* 游戏结束通关场景
* 函数里参数:
* DT_CENTER - 水平居中
* DT_VCENTER - 垂直居中
* DT_SINGLELINE - 文字显示在一行
* 输入:bg - 图片变量指针
* 输出:无
******************************************/
//通往下一关场景
void gameNextScence(IMAGE* bg) {
	putimage(0, 0, bg);
	settextcolor(WHITE);
	RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };	//定义一个矩形
	settextstyle(20, 0, _T("宋体"));		//设置字的大小与风格
	drawtext(_T("恭喜您~\n此关挑战成功,任意键跳转到下一关!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	::system("pause");
	cleardevice();	
}
//通关场景
void gameOverScence(IMAGE* bg) {
	putimage(0, 0, bg);
	settextcolor(WHITE);
	RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };	//定义一个矩形
	settextstyle(20, 0, _T("宋体"));		//设置字的大小与风格
	drawtext(_T("恭喜您~\n通关成功!有缘再会!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}

int main(){
    
    //...
    
    if (isGameOver()) {

        if (level.next_level < 1) {
            gameOverScence(&bg_img);
            quit = true;
            break;
        }

        gameNextScence(&bg_img);

        //更新用户下一关的管求爱信息
        if (update_user_level(user, level.next_level)) {
            user.level_id = level.next_level;
        }

        break;
    }
    
    //...
    
}

四、全部代码

不包含数据库代码

box_man.h
#pragma once
#include<graphics.h>	
#include<iostream>
#include<stdlib.h>
#include<string>
#include<conio.h>

using namespace std;

#define RATIO 61	//地图中每个小方块的大小

#define SCREEN_WIDTH 960	//背景宽度
#define SCREEN_HEIGHT 768	//背景高度

#define LINE 9			//地图行数
#define COLUMN 12		//地图列数
#define START_X 100		//地图开始的横坐标
#define START_Y 150		//地图开始的纵坐标

//控制键上、下、左、右控制方向,'q'退出
#define KEY_UP 'w'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define KEY_DOWN 's'
#define KEY_QUIT 'q'

enum _PROPS {
	WALL,	//墙:0
	FLOOR,	//地板:1
	BOX_DES,//箱子目的地:2
	MAN,	//小人:3
	BOX,	//箱子:4
	HIT,	//箱子命中目标:5
	ALL		//道具数量:6
};

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

struct _POS {
	int x;	//小人所在的二维数组的行
	int y;	//小人所在的二维数组的列
};

IMAGE images[ALL];	//存储道具图标

struct _POS man;	//小人在数组中的位置
int nums_hit;	//表示需要放的箱子数量
enum _PROPS last = FLOOR;	//记录小人上次待的位置是什么
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,0,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},
};

/******************************************
* 改变并打印当前图标在地图中的位置
* 输入:pos - 当前下标
*		prop - 当前图标
* 输出:void
******************************************/
void changeMap(struct _POS& pos, enum _PROPS prop){
	map[pos.x][pos.y] = prop;
	putimage(START_X + pos.y * RATIO, START_Y + pos.x * RATIO, &images[prop]);
}

/******************************************
* 判断当前位置是否越界
* 输入:pos 当前下标
* 输出:bool
******************************************/
bool isValid(struct _POS pos){
	if (map[pos.x][pos.y] == WALL)	return false;
	if (pos.x < 0 && pos.x >= LINE && pos.y < 0 && pos.y >= COLUMN)
		return false;
	return true;
}

/******************************************
* 实现游戏四个方向的控制(上、下、左、右)
* 输入:direct - 人前进的方向
* 输出:void
******************************************/
void gameControl(enum _DIRECTION direct)
{
	struct _POS next_pos = man;
	struct _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;
	}

	if (isValid(next_pos) && map[next_pos.x][next_pos.y] == FLOOR){
		changeMap(next_pos, MAN);
		changeMap(man, last);
		last = FLOOR;
		man = next_pos;
	}
	else if (isValid(next_pos) && map[next_pos.x][next_pos.y] == BOX_DES) {
		changeMap(man, last);
		//小人可以经过HIT,但不能改变其在地图上的值
		putimage(START_X + next_pos.y * RATIO, START_Y + next_pos.x * RATIO, &images[MAN]);
		man = next_pos;
		last = BOX_DES;
	}
	else if (isValid(next_next_pos) && (map[next_pos.x][next_pos.y] == BOX || map[next_pos.x][next_pos.y] == HIT)) {
		//如果要将箱子从目的地上推开,要用更新last变量,并且nums_hit要加1
		bool is_hit = false;
		//如果箱子推不动,就直接退出,以免更改last变量导致下次移动出现bug
		if (!(map[next_next_pos.x][next_next_pos.y] == FLOOR || map[next_next_pos.x][next_next_pos.y] == BOX_DES))
			return;
		if (map[next_pos.x][next_pos.y] == HIT) {
			is_hit = true;
			nums_hit++;
		}

		if (map[next_next_pos.x][next_next_pos.y] == FLOOR) {
			changeMap(next_next_pos, BOX);
			changeMap(next_pos, MAN);
			changeMap(man, last);
			man = next_pos;
		}
		else if (map[next_next_pos.x][next_next_pos.y] == BOX_DES) {
			changeMap(next_next_pos, HIT);
			changeMap(next_pos, MAN);
			changeMap(man, last);
			man = next_pos;
			nums_hit--;
		}

		//更新小人上次访问的位置
		if (is_hit)	last = BOX_DES;
		else last = FLOOR;
	}
}

/*****************************************************
* 判断游戏是否结束
* 输入:无
* 输出:bool - true表示游戏结束,false表示游戏未结束
******************************************************/
bool isGameOver(){
	if (nums_hit != 0)	return false;
	return true;
}

/******************************************
* 游戏结束通关场景
* 函数里参数:
* DT_CENTER - 水平居中
* DT_VCENTER - 垂直居中
* DT_SINGLELINE - 文字显示在一行
* 输入:bg - 图片变量指针
* 输出:无
******************************************/
void gameOverScence(IMAGE* bg) {
	putimage(0, 0, bg);
	settextcolor(WHITE);
	RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };	//定义一个矩形
	settextstyle(20, 0, _T("宋体"));		//设置字的大小与风格
	drawtext(_T("恭喜您~\n游戏成功通关!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}

int main()
{
	IMAGE bg_img;	//存储背景
	nums_hit = 0;	//初始化目标箱子数量

	//初始化背景
	initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);
	loadimage(&bg_img, _T("blackground.bmp"), SCREEN_WIDTH, SCREEN_HEIGHT, true);
	putimage(0, 0, &bg_img);

	//加载道具图标
	loadimage(&images[WALL], _T("wall.bmp"), RATIO, RATIO, true);
	loadimage(&images[FLOOR], _T("floor.bmp"), RATIO, RATIO, true);
	loadimage(&images[BOX_DES], _T("des.bmp"), RATIO, RATIO, true);
	loadimage(&images[MAN], _T("man.bmp"), RATIO, RATIO, true);
	loadimage(&images[BOX], _T("box.bmp"), RATIO, RATIO, true);
	loadimage(&images[HIT], _T("box.bmp"), RATIO, RATIO, true);

	//打印道具图标
	for (int i = 0; i < LINE; i++) {
		for (int j = 0; j < COLUMN; j++) {
			//统计目标箱子数
			if (map[i][j] == BOX_DES)	nums_hit++;
			//获取小人初始位置
			if (map[i][j] == MAN) {
				man.x = i;
				man.y = j;
			}
			putimage(START_X + j * RATIO, START_Y + i * RATIO, &images[map[i][j]]);
		}
	}

	//游戏环节
	bool quit = false;
	do {
		if (_kbhit()) {//玩家按键
			char ch = _getch();

			if (ch == KEY_UP) {
				gameControl(UP);
			}
			else if (ch == KEY_DOWN) {
				gameControl(DOWN);
			}
			else if (ch == KEY_LEFT) {
				gameControl(LEFT);
			}
			else if (ch == KEY_RIGHT) {
				gameControl(RIGHT);
			}
			else if (ch == KEY_QUIT) {
				quit = true;
			}
			if (isGameOver()) {
				quit = true;
				gameOverScence(&bg_img);
			}
		}
		Sleep(100);
	} while (quit == false); //!quit

	system("pause");

	closegraph();	//释放资源

	return 0;
}

包含数据库代码

database.h
#pragma once
#include<string>

using namespace std;

#define LINE 48			//地图行数
#define COLUMN 48		//地图列数

typedef struct _userinfo {
	int id;				//用户id
	string username;	//用户名
	string passwd;		//密码
	int level_id;		//关卡id
}userinfo;

typedef struct _levelinfo {
	int id;			 //关卡的id
	string name;	 //关卡的名字
	int map_row;	 //地图总行数
	int map_column;	 //地图总列数
	string map_data; //二维地图数据
	int next_level;	 //下一关卡的id
}levelinfo;

bool fetch_user_info(userinfo& user);
bool update_user_level(userinfo& user, int next_level_id);
bool fetch_level_info(levelinfo& level, int level_id);
bool transform_map_db2array(levelinfo& level, int map[LINE][COLUMN]);
database.cpp
#include "database.h"
#include <mysql.h>
#include <stdio.h>

#define DB_NAME "box_man"
#define DB_HOST "127.0.0.1"
#define DB_PORT 3306
#define DB_USER "root"
#define DB_USER_PASSWD "123456"

static int debug = 1;

static bool connect_db(MYSQL& mysql);

/******************************************
* 功能:通过用户名和密码获取用户信息
* 输入:
*	user - 用户信息结构体
* 返回值:
*	获取成功返回ture,失败返回false
******************************************/
bool fetch_user_info(userinfo& user) {
	MYSQL mysql;
	MYSQL_RES* res;	//查询结果集
	MYSQL_ROW row;	//记录结构体
	char sql[256];
	bool ret = false;

	//1.连接到数据库
	if (connect_db(mysql) == false) {
		return false;
	}

	//2.根据用户名和密码获取用户信息(id, level_id)
	snprintf(sql, 256, "select id, level_id from users where username='%s' and password=md5('%s')", user.username.c_str(), user.passwd.c_str());
	ret = mysql_query(&mysql, sql);	//成功返回0

	if (ret) {
		printf("数据库查询出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));
		mysql_close(&mysql);
		return false;
	}

	//3.获取结果
	res = mysql_store_result(&mysql);
	row = mysql_fetch_row(res);

	if (row == NULL) {
		mysql_free_result(res);
		mysql_close(&mysql);
		return false;
	}

	user.id = atoi(row[0]);
	user.level_id = atoi(row[1]);
	if(debug) printf("userif: %d  level_id: %d\n", user.id, user.level_id);	//打印id

	//4.返回结果

	//释放结果集
	mysql_free_result(res);

	//关闭数据库
	mysql_close(&mysql);

	return true;
}

/******************************************
* 功能:更新用户下一关信息
* 输入:
*	user - 用户信息结构体
*	next_level_id - 下一关的id
* 返回值:
*	更新成功返回ture,失败返回false
******************************************/
bool update_user_level(userinfo& user, int next_level_id)
{
	MYSQL mysql;
	MYSQL_RES* res;	//查询结果集
	MYSQL_ROW row;	//记录结构体
	char sql[256];
	bool ret = false;

	//1.连接到数据库
	if (connect_db(mysql) == false) {
		return false;
	}

	//2.根据用户id更新下一关的level_id
	snprintf(sql, 256, "update users set level_id=%d where id=%d;", next_level_id, user.id);

	ret = mysql_query(&mysql, sql);

	if (ret) {
		printf("数据库更新出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));
		mysql_close(&mysql);
		return false;
	}

	return true;
}

/*********************************************************
* 功能:根据关卡id获取完整的关卡信息(如:地图,下一关等)
* 输入:
*	level - 保存关卡信息的结构体变量
*	level_id - 要获取详细关卡信息的关卡id
* 返回值:
*	获取成功返回ture,失败返回false
*********************************************************/
bool fetch_level_info(levelinfo& level, int level_id){
	MYSQL mysql;
	MYSQL_RES* res;	//查询结果集
	MYSQL_ROW row;	//记录结构体
	char sql[256];
	bool ret = false;

	//1.连接到数据库
	if (connect_db(mysql) == false) {
		return false;
	}

	//2.根据关卡id查询数据库获取关卡地图信息
	snprintf(sql, 256, "select name, map_row, map_column, map_data, next_level_id from levels where id=%d;", level_id);
	ret = mysql_query(&mysql, sql);	//成功返回0

	if (ret) {
		printf("数据库查询出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));
		mysql_close(&mysql);
		return false;
	}

	//3.获取结果
	res = mysql_store_result(&mysql);
	row = mysql_fetch_row(res);

	if (row == NULL) {
		mysql_free_result(res);
		mysql_close(&mysql);
		return false;
	}

	level.id = level_id;
	level.name = row[0];
	level.map_row = atoi(row[1]);
	level.map_column = atoi(row[2]);
	level.map_data = row[3];
	level.next_level = atoi(row[4]);
	if(debug) printf("level id: %d  name: %s map row: %d  map column: %d map data: %s next level: %d\n", level.id, level.name.c_str(), level.map_row, level.map_column, level.map_data.c_str(), level.next_level);
	
	//4.返回结果

	//释放结果集
	mysql_free_result(res);

	//关闭数据库
	mysql_close(&mysql);

	return true;
}

//连接数据库
bool connect_db(MYSQL& mysql) {
	//1.初始化数据库句柄
	mysql_init(&mysql);

	//2.设置字符编码
	mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");

	//3.连接数据库
	if (mysql_real_connect(&mysql, DB_HOST, DB_USER, DB_USER_PASSWD, DB_NAME, DB_PORT, NULL, 0) == NULL) {
		printf("数据库连接出错,错误原因:%s\n", mysql_error(&mysql));
		return false;
	}

	return true;
}

/*********************************************************
* 功能:将从数据库读到的地图数据转换到二维数组中
* 输入:
*	level - 保存关卡信息的结构体变量
*	map	- 存储地图数据的二维数组
* 返回值:
*	获取成功返回ture,失败返回false
*********************************************************/
bool transform_map_db2array(levelinfo& level, int map[LINE][COLUMN]) {
	if (level.map_row > LINE || level.map_column > COLUMN) {
		printf("地图过大,请重新设置!\n");
		return false;
	}

	if (level.map_data.length() < 1) {
		printf("地图数据有误,请重新设置!\n");
		return false;
	}

	int start = 0, end = 0;
	int row = 0, column = 0;

	do {
		//找到一行数据的末尾
		end = level.map_data.find('|', start);

		//判断是否已经读到字符串结尾
		if (end < 0)	end = level.map_data.length();

		//如果起始位置大于等于终止位置,直接退出
		if (start >= end)	break;

		//截取一行数据
		string line = level.map_data.substr(start, end - start);
		printf("get line: %s\n", line.c_str());

		//对行数据进行解析
		char* next_token = NULL;
		char* item = strtok_s((char*)line.c_str(), ",", &next_token);
		column = 0;
		while (item && column < level.map_column) {
			printf("%s ", item);
			map[row][column] = atoi(item);
			column++;
			item = strtok_s(NULL, ",", &next_token);
		}

		//检查列数是否设置正确
		if (column < level.map_column) {
			printf("地图列数少于设定,%d(need:%d),终止!\n", column, level.map_column);
			return false;
		}

		printf("\n");
		row++;

		//如果行数过多,则直接截取
		if (row >= level.map_row) {
			break;
		}

		//读取下一行
		start = end + 1;

	} while (1);

	if (row < level.map_row) {
		printf("地图行数少于设定,%d(need:%d),终止!", row, level.map_row);
		return false;
	}

	return true;
}
box_man.h
#pragma once
#include<graphics.h>	
#include<iostream>
#include<stdlib.h>
#include<string>
#include<conio.h>

using namespace std;

#define RATIO 61	//地图中每个小方块的大小

#define SCREEN_WIDTH 960	//背景宽度
#define SCREEN_HEIGHT 768	//背景高度

//#define LINE 9			//地图行数
//#define COLUMN 12		//地图列数

#define START_X 80		//地图开始的横坐标
#define START_Y 30		//地图开始的纵坐标

//控制键上、下、左、右控制方向,'q'退出
#define KEY_UP 'w'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define KEY_DOWN 's'
#define KEY_QUIT 'q'

#define MAX_RETRY_TIMES 4

enum _PROPS {
	WALL,	//墙:0
	FLOOR,	//地板:1
	BOX_DES,//箱子目的地:2
	MAN,	//小人:3
	BOX,	//箱子:4
	HIT,	//箱子命中目标:5
	ALL		//道具数量:6
};

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

struct _POS {
	int x;	//小人所在的二维数组的行
	int y;	//小人所在的二维数组的列
};

IMAGE images[ALL];	//存储道具图标

struct _POS man;	//小人在数组中的位置
int nums_hit;	//表示需要放的箱子数量
enum _PROPS last = FLOOR;	//记录小人上次待的位置是什么
box_man.cpp
#include "box_man.h"
#include "database.h"

/**************************************
* 游戏地图
* 墙:0	  地板:1	 箱子目的地:2
* 小人:3	  箱子:4	 箱子命中目标:5
**************************************/
int map[LINE][COLUMN] = { 0 };
//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,0,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},
//};

/******************************************
* 改变并打印当前图标在地图中的位置
* 输入:pos - 当前下标
*		prop - 当前图标
* 输出:void
******************************************/
void changeMap(struct _POS& pos, enum _PROPS prop) {
	map[pos.x][pos.y] = prop;
	putimage(START_X + pos.y * RATIO, START_Y + pos.x * RATIO, &images[prop]);
}

/******************************************
* 判断当前位置是否越界
* 输入:pos 当前下标
* 输出:bool
******************************************/
bool isValid(struct _POS pos, levelinfo level) {
	if (map[pos.x][pos.y] == WALL)	return false;
	if (pos.x < 0 && pos.x >= level.map_row && pos.y < 0 && pos.y >= level.map_column)
		return false;
	return true;
}

/******************************************
* 实现游戏四个方向的控制(上、下、左、右)
* 输入:direct - 人前进的方向
* 输出:void
******************************************/
void gameControl(enum _DIRECTION direct, levelinfo level)
{
	struct _POS next_pos = man;
	struct _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;
	}

	if (isValid(next_pos, level) && map[next_pos.x][next_pos.y] == FLOOR) {
		changeMap(next_pos, MAN);
		changeMap(man, last);
		last = FLOOR;
		man = next_pos;
	}
	else if (isValid(next_pos, level) && map[next_pos.x][next_pos.y] == BOX_DES) {
		changeMap(man, last);
		//小人可以经过HIT,但不能改变其在地图上的值
		putimage(START_X + next_pos.y * RATIO, START_Y + next_pos.x * RATIO, &images[MAN]);
		man = next_pos;
		last = BOX_DES;
	}
	else if (isValid(next_next_pos, level) && (map[next_pos.x][next_pos.y] == BOX || map[next_pos.x][next_pos.y] == HIT)) {
		//如果要将箱子从目的地上推开,要用更新last变量,并且nums_hit要加1
		bool is_hit = false;
		//如果箱子推不动,就直接退出,以免更改last变量导致下次移动出现bug
		if (!(map[next_next_pos.x][next_next_pos.y] == FLOOR || map[next_next_pos.x][next_next_pos.y] == BOX_DES))
			return;
		if (map[next_pos.x][next_pos.y] == HIT) {
			is_hit = true;
			nums_hit++;
		}

		if (map[next_next_pos.x][next_next_pos.y] == FLOOR) {
			changeMap(next_next_pos, BOX);
			changeMap(next_pos, MAN);
			changeMap(man, last);
			man = next_pos;
		}
		else if (map[next_next_pos.x][next_next_pos.y] == BOX_DES) {
			changeMap(next_next_pos, HIT);
			changeMap(next_pos, MAN);
			changeMap(man, last);
			man = next_pos;
			nums_hit--;
		}

		//更新小人上次访问的位置
		if (is_hit)	last = BOX_DES;
		else last = FLOOR;
	}
}

/*****************************************************
* 判断游戏是否结束
* 输入:无
* 输出:bool - true表示游戏结束,false表示游戏未结束
******************************************************/
bool isGameOver() {
	if (nums_hit != 0)	return false;
	return true;
}

/******************************************
* 游戏结束通关场景
* 函数里参数:
* DT_CENTER - 水平居中
* DT_VCENTER - 垂直居中
* DT_SINGLELINE - 文字显示在一行
* 输入:bg - 图片变量指针
* 输出:无
******************************************/
//通往下一关场景
void gameNextScence(IMAGE* bg) {
	putimage(0, 0, bg);
	settextcolor(WHITE);
	RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };	//定义一个矩形
	settextstyle(20, 0, _T("宋体"));		//设置字的大小与风格
	drawtext(_T("恭喜您~\n此关挑战成功,任意键跳转到下一关!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	::system("pause");
	cleardevice();	
}
//通关场景
void gameOverScence(IMAGE* bg) {
	putimage(0, 0, bg);
	settextcolor(WHITE);
	RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };	//定义一个矩形
	settextstyle(20, 0, _T("宋体"));		//设置字的大小与风格
	drawtext(_T("恭喜您~\n通关成功!有缘再会!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}

//数据库登录验证功能
bool login(userinfo& user) {
	int times = 0;
	bool ret = false;

	do {
		cout << "输入用户名:";
		cin >> user.username;

		cout << "请输入密码:";
		cin >> user.passwd;

		//返回bool,成功返回true,失败返回false
		ret = fetch_user_info(user);
		times++;
		if (times >= MAX_RETRY_TIMES) {
			break;
		}
		if (ret == false) {
			cout << "登录失败,请重新输入!" << endl;
		}
	} while (!ret);

	return ret;
}

//初始化背景
void init_graph(IMAGE& bg_img) {
	nums_hit = 0;	//初始化目标箱子数量

	//初始化背景
	initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);
	loadimage(&bg_img, _T("blackground.bmp"), SCREEN_WIDTH, SCREEN_HEIGHT, true);
	putimage(0, 0, &bg_img);

	//加载道具图标
	loadimage(&images[WALL], _T("wall_right.bmp"), RATIO, RATIO, true);
	loadimage(&images[FLOOR], _T("floor.bmp"), RATIO, RATIO, true);
	loadimage(&images[BOX_DES], _T("des.bmp"), RATIO, RATIO, true);
	loadimage(&images[MAN], _T("man.bmp"), RATIO, RATIO, true);
	loadimage(&images[BOX], _T("box.bmp"), RATIO, RATIO, true);
	loadimage(&images[HIT], _T("box.bmp"), RATIO, RATIO, true);
}

int main()
{
	//用户身份验证
	userinfo user;
	levelinfo level;
	bool ret = false;

	if (!login(user)) {
		cout << "登陆失败,请重新登录!" << endl;
		::system("pause");
		exit(-1);
	}
	else {
		cout << "用户" << user.id << ",您当前所在的关卡是:level-" << user.level_id << endl;
		cout << "您已登陆成功,请开始您的表演!" << endl;
		::system("pause");
	}

	//初始化游戏背景
	IMAGE bg_img;	//存储背景
	init_graph(bg_img);

	bool quit = false;
	do {

		//根据用户所在的关卡id获取关卡数据
		ret = fetch_level_info(level, user.level_id);

		if (!ret) {
			cout << "获取关卡数据失败,请重试!" << endl;
			::system("pause");
			exit(-1);
		}

		//把数据库中的地图转换到map中
		ret = transform_map_db2array(level, map);

		if (!ret) {
			cout << "地图数据转换失败,请重试!" << endl;
			::system("pause");
			exit(-1);
		}

		//打印道具图标
		for (int i = 0; i < level.map_row; i++) {
			for (int j = 0; j < level.map_column; j++) {
				//统计目标箱子数
				if (map[i][j] == BOX_DES)	nums_hit++;
				//获取小人初始位置
				if (map[i][j] == MAN) {
					man.x = i;
					man.y = j;
				}
				putimage(START_X + j * RATIO, START_Y + i * RATIO, &images[map[i][j]]);
			}
		}

		//游戏环节
		do {
			if (_kbhit()) {//玩家按键
				char ch = _getch();

				if (ch == KEY_UP) {
					gameControl(UP, level);
				}
				else if (ch == KEY_DOWN) {
					gameControl(DOWN, level);
				}
				else if (ch == KEY_LEFT) {
					gameControl(LEFT, level);
				}
				else if (ch == KEY_RIGHT) {
					gameControl(RIGHT, level);
				}
				else if (ch == KEY_QUIT) {
					quit = true;
				}

				if (isGameOver()) {

					if (level.next_level < 1) {
						gameOverScence(&bg_img);
						quit = true;
						break;
					}

					gameNextScence(&bg_img);

					//更新用户下一关的管求爱信息
					if (update_user_level(user, level.next_level)) {
						user.level_id = level.next_level;
					}

					break;
				}
			}
			Sleep(100);
		} while (quit == false); //!quit

	} while (quit == false);

	::system("pause");

	closegraph();	//释放资源

	return 0;
}

参考资料:
https://www.bilibili.com/video/BV1SL4y1M7yL/?spm_id_from=333.337.search-card.all.click&vd_source=12c90255e5a0009cd588cada10859bd5

  • 7
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值