推箱子的小游戏(连接数据库),可二次开发

一个简单的推箱子游戏 ,还有完善优化的空间,可打包带走

目录:

1. 整个游戏架构
** 2. 数据库设计 **
3. 核心的代码介绍

  • **( 一 ) 整个游戏的架构: **
    流出图
( 二 ) 数据库设计
  • 数据库设计
  • **user 表的设计(玩家的一些基本信息) **
	CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL,
  `password` varchar(32) NOT NULL,
  `level_id` int DEFAULT '1',
  PRIMARY KEY (`id`));

-levels 表的设计(关卡的信息,地图的信息)
levels表
插入的数据记录

CREATE TABLE `levels` (
  `id` int NOT NULL DEFAULT '1',
  `name` varchar(64) NOT NULL,
  `map_row` int NOT NULL,
  `map_column` int NOT NULL,
  `map_data` varchar(4096) NOT NULL,
  `next_level_id` int DEFAULT '0'
) ;
( 三) 核心代码的介绍
  • 这个游戏的难度不是很大,在整个的游戏开发里面,将数据库中的地图信息进行转换是最重要的 , 我会着重介绍
  • 知识储备
  • 1. string 类的一个方法 find(),这里呢将我们将从数据库中拿到的地图数据一行一行的读取出来
  • 在C++中,string类的find()函数用于查找一个子字符串在另一个字符串中出现的位置。该函数的语法如下:
size_t find(const string& str, size_t pos = 0) const noexcept;

其中,str参数是要查找的子字符串,pos参数是在哪个位置开始查找(默认为0,即从字符串的开头开始查找)。该函数返回一个size_t类型的值,表示子字符串在原字符串中第一次出现的位置,如果找不到则返回string::npos。

  • 2. strtok_s()是C语言中的一个字符串处理函数,用于将字符串分割成多个子字符串, strtok_s()函数的语法如下:
char *strtok_s(char *str, const char *delim, char **context);

其中,str参数是要分割的字符串,delim参数是分隔符,context参数是一个指向指针的指针,用于存储下一次调用strtok_s()函数时的位置。该函数返回分割后的第一个子字符串,如果没有找到任何子字符串,则返回NULL。

//地图的转换
bool transfo_map_db2arry(mapInfo& level_map, int map[LINE][COLUMN]) {
	if (level_map.row > LINE || level_map.column > COLUMN) {
		printf("地图太大,请重新设置!");
		return false;
	}
	int start = 0, end = 0;
	int row = 0, column = 0;

	string mapData = level_map.mapData;
	do {
		//读出一行
		end = mapData.find('|', start);  //找到'|' 的位置
		if (end==-1) {
			end = mapData.length();
		}
		if (start >= end) {
			break;
		}
		string line = mapData.substr(start, end-start);
		start = end + 1;
		printf("get line: %s\n", line.c_str());
		//将每一行解析出来,放入地图数组
			char* next_token = NULL;
			char* item = strtok_s((char*)line.c_str(),",",&next_token);   //没有找到',',返回NULL
			column = 0;     //列置0		
			while (item) {
				printf("%c ", *item);
				map[row][column] = atoi(item);
				column++;
				item = strtok_s(NULL, ",", & next_token);
			}
			if (column < level_map.column) {
				printf("地图解析错误,终止!");
				return false;
			}
			printf("\n");
			row++;     //行数+1
	} while (1);
	if (row < level_map.row) {
		printf("地图行数少于设定值 %d(need : %d)",row,level_map.row);
		return false;
	}
	return true;
}
完整的代码

** boxMan.h 的头文件 **

#ifndef  _BOXMAN_H_
#define  _BOXMAN_H_
#include"dataBase.h"
#define LOGIN_MAX 4   //允许重试次数
bool login(userInfor& user);
//隐藏密码
void hidePassword(userInfor& user);
#endif // ! _BOXMAN_H_

** dataBase.h 的头文件 **

#ifndef _DATABASE_H_
#define _DATABASE_H_

#include<string>
#include<mysql.h>

using namespace std;

#define LINE  48  //行
#define COLUMN 48  //列

typedef struct _userInfor {
	int id;
	string username;
	string password;
	int level_id;
}userInfor;

//地图信息
typedef struct _mapInfo {
	int level_id;
	string mapName;
	int row;     //行
	int column;  //列
	string mapData;
	int next_level;    //下一关的ID
}mapInfo;

//获取地图信息
bool fetch_map_info(int level_id, mapInfo & level_map);

//登录验证
bool chech_login(userInfor &user);

//连接数据库并验证账户信息
static bool connect_mysql(MYSQL& mysql, userInfor& user);

//地图转换
bool transfo_map_db2arry(mapInfo &level_map, int map[LINE][COLUMN]);

//进入下一关
bool update_next_level(userInfor &user,int level_map);

#endif // _USERINFOR_H_

** boxMan.cpp 文件 **

#include<iostream>
#include<string>
#include<conio.h>
#include"boxMan.h"

bool login(userInfor& user){

	int count = 0;     //记录登录的次数
	while (1) {
		hidePassword(user);
		//登录验证
		count++;
		if (chech_login(user)) {
			return true;
		}
		else if(count<LOGIN_MAX) {
			printf("\n账号或者密码输入错误,请重新输入\n");
			//消除错误标记
			cin.clear();
			//清空输入缓冲区
			cin.ignore(100, '\n');
			continue;
		}
		else {
			break;
		}
	}
	return false;
}

//密码隐藏
void hidePassword(userInfor& user) {
	printf("请输入账号:");
	std::cin >> user.username;
	printf("请输入密码:");
	//int i = 0;
	while (1) {
		char ch = _getch();
		if (ch=='\r') {     ///当读取到回车时,返回\r
			break;
		}
		user.password.push_back(ch);
		printf("*");
		//std::cin >> user.password;
	}
}

database.cpp 文件

#include<stdio.h>
#include"dataBase.h"

#define DEBUG 1

#define SQ_host "127.0.0.1"   //地址  
#define SQ_user "root"         //用户名
#define SQ_password  "ln123456789"   //密码
#define SQ_database   "box_man"      //数据库名
#define SQ_port    3306          //端口号


//连接数据库并验证账户信息
static bool connect_mysql(MYSQL& mysql, userInfor& user) {
	MYSQL_RES* res=NULL;     //查询结果集
	MYSQL_ROW row;           //保存查询信息的结构体

	bool ret = false;

	//初始化数据库
	mysql_init(&mysql);
	//设置字符集
	mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");
	//开始连接数据库
	if (mysql_real_connect(&mysql, SQ_host, SQ_user, SQ_password, SQ_database, SQ_port, NULL, 0)==NULL) {//成功返回0
		printf("数据库连接失败,失败原因:%s\n", mysql_error(&mysql));
		return false;
	}
	//根据用户名和密码查询用户信息
	char sql[256];   //保存SQ语句
	snprintf(sql, 256, "select id ,level_id from user where username=('%s') and password = md5('%s'); ", user.username.c_str(), user.password.c_str());
	 ret = mysql_query(&mysql, sql);     //验证账号密码,成功返回0
	if (ret) {
		printf("数据库查询失败,错误原因:%s\n", mysql_error(&mysql));
		mysql_close(&mysql);
		return false;
	}
	//获取查询的结果
	res = mysql_store_result(&mysql);   //拿到查询的信息
	row = mysql_fetch_row(res);    //将信息保存到 名为 row 的结构体里面

	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("user_id: %d, level_id: %d\n", user.id, user.level_id);

	//释放结果集
	mysql_free_result(res);
	//关闭数据库
	mysql_close(&mysql);
	return true;
}

//获取地图信息
bool fetch_map_info(int level, mapInfo& level_map) {
	MYSQL mysql;    //数据库句柄
	MYSQL_RES* res = NULL;     //查询结果集
	MYSQL_ROW row;           //保存查询信息的结构体
	//初始化数据库
	mysql_init(&mysql);
	//设置字符集
	mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");
	//开始连接数据库
	if (mysql_real_connect(&mysql, SQ_host, SQ_user, SQ_password, SQ_database, SQ_port, NULL, 0) == NULL) {//成功返回0
		printf("数据库连接失败,失败原因:%s\n", mysql_error(&mysql));
		return false;
	}
	//获取数据
	char sql[256];
	snprintf(sql, 256, "select id,name,map_row,map_column,map_data,next_level_id from levels where id=%d;",level);
	//执行sql语句
	bool ret = mysql_query(&mysql, sql);        //验证账号密码,成功返回0
	if (ret) {
		printf("数据库查询失败%s,错误原因:%s\n",sql, mysql_error(&mysql));
		mysql_close(&mysql);
		return false;
	}
	//获取结果集
	res = mysql_store_result(&mysql);
	//将结果集的数据保存到row结构体中
	row = mysql_fetch_row(res);
	if (row==NULL) {
		printf("没有找到数据,错误原因%s\n", mysql_error(&mysql));
		mysql_free_result(res);
		mysql_close(&mysql);
		return false;
	}
	level_map.level_id = atoi(row[0]);
	level_map.mapName = row[1];
	level_map.row = atoi(row[2]);
	level_map.column = atoi(row[3]);
	level_map.mapData = row[4];
	level_map.next_level = atoi(row[5]);

	if (DEBUG) {
		printf("level_id: %d,mapName: %s,map_row: %d,map_column: %d,mapData: %s\n, next_level_id :%d",
			level_map.level_id, level_map.mapName.c_str(), level_map.row, 
			level_map.column, level_map.mapData.c_str(), level_map.next_level);
	}
	mysql_free_result(res);
	mysql_close(&mysql);
	return true;
}

//登录验证
bool chech_login(userInfor &user) {
	//连接数据库
	MYSQL mysql;      //数据库句柄
	if (connect_mysql(mysql,user)) {
		return true;
	}
	else {
		return false;
	}
}

//地图的转换
bool transfo_map_db2arry(mapInfo& level_map, int map[LINE][COLUMN]) {
	if (level_map.row > LINE || level_map.column > COLUMN) {
		printf("地图太大,请重新设置!");
		return false;
	}
	int start = 0, end = 0;
	int row = 0, column = 0;

	string mapData = level_map.mapData;
	do {
		//读出一行
		end = mapData.find('|', start);  //找到'|' 的位置
		if (end==-1) {
			end = mapData.length();
		}
		if (start >= end) {
			break;
		}
		string line = mapData.substr(start, end-start);
		start = end + 1;
		printf("get line: %s\n", line.c_str());
		//将每一行解析出来,放入地图数组
			char* next_token = NULL;
			char* item = strtok_s((char*)line.c_str(),",",&next_token);   //没有找到',',返回NULL
			column = 0;     //列置0		
			while (item) {
				printf("%c ", *item);
				map[row][column] = atoi(item);
				column++;
				item = strtok_s(NULL, ",", & next_token);
			}
			if (column < level_map.column) {
				printf("地图解析错误,终止!");
				return false;
			}
			printf("\n");
			row++;     //行数+1
	} while (1);
	if (row < level_map.row) {
		printf("地图行数少于设定值 %d(need : %d)",row,level_map.row);
		return false;
	}
	return true;
}
//0,0,1,0,1,0|0,1,2,0,3,4|1,2,0,2

//进入下一关,更新用户数据
bool update_next_level(userInfor& user, int next_level) {
	MYSQL mysql;    //数据库句柄
	//初始化数据库
	mysql_init(&mysql);
	//设置字符集
	mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");
	//开始连接数据库
	if (mysql_real_connect(&mysql, SQ_host, SQ_user, SQ_password, SQ_database, SQ_port, NULL, 0) == NULL) {//成功返回0
		printf("数据库连接失败,失败原因:%s\n", mysql_error(&mysql));
		return false;
	}
	//获取数据
	char sql[256];
	snprintf(sql, 256, "update user set level_id=%d  where id=%d ;", next_level,user.id);
	//执行sql语句
	bool ret = mysql_query(&mysql, sql);     //验证账号密码,成功返回0
	if (ret) {
		printf("数据库查询失败%s,错误原因:%s\n",sql ,mysql_error(&mysql));
		mysql_close(&mysql);
		return false;
	}
	mysql_close(&mysql);
	return true;
}

** main函数**

#include<iostream>
#include<graphics.h>
#include<conio.h>
#include<sstream>
#include"dataBase.h"
#include"boxMan.h"

#define SCREE_WIDTH 960   //界面的宽度
#define SCERR_HIGHT 800   //界面的高度

//判断所处的位置是否有效
#define	INLI(nextMan) nextMan.x>=0 && nextMan.x<9 && nextMan.y>=0&&nextMan.x<12

//控制键   q退出
#define KEY_UP  'w'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define KEY_DOWN  's'
#define KEY_QUIT 'q'

#define STAGE 61  //道具的大小   61*61

using namespace std;

/* 道具表示
	墙:0  地板:1  箱子目的地:2   小人:3  箱子:4   命中目标:5  
*/
enum _PROPS{
	WALL,  //墙
	FLOOR,  //地板
	BOX_DES, //箱子目的地
	MAN,   //小人
	BOX,  //箱子
	HIT,  ///命中目标
	ALL
};
//方向
enum _DIRECTE {
	UP,   //向上
	LEFT,  //向左
	RIGHT,  //向右
	DOWN,  //向下
};
struct _POS {
	int x;   //小人所在的行数
	int y;	//小人所在的列数
};
struct _POS man;
//下一个小人的位置
struct _POS nextMan;
// 箱子的下一个位置
struct _POS boxNext;

IMAGE image_I[ALL];   //道具图片

IMAGE image_B;   //背景图片

//地图
int map[LINE][COLUMN] = { 0 };


//int map[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,1,1,4,1,0,
//	0,1,1,1,0,3,1,1,1,4,1,0,
//	0,1,2,1,4,1,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,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,1,1,4,1,0|0,1,1,1,0,3,1,1,1,4,1,0|
//0,1,2,1,4,1,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

//初始化
void init(mapInfo &map_level) {
	initgraph(960, 768, EX_SHOWCONSOLE);
	loadimage(&image_B, _T("blackground.bmp"), SCREE_WIDTH, SCERR_HIGHT, true);   //加载图片
	putimage(0, 0, &image_B);     //输出图片

	loadimage(&image_I[WALL], "wall_right.bmp", STAGE, STAGE, true);
	loadimage(&image_I[FLOOR], "floor.bmp", STAGE, STAGE, true);
	loadimage(&image_I[BOX_DES], "des.bmp", STAGE, STAGE, true);
	loadimage(&image_I[MAN], "man.bmp", STAGE, STAGE, true);
	loadimage(&image_I[BOX], "box.bmp", STAGE, STAGE, true);
	loadimage(&image_I[HIT], "box.bmp", STAGE, STAGE, true);
	settextcolor(WHITE);
	settextstyle(50, 0, "宋体");
	//
	RECT rec = { 0, 0, SCREE_WIDTH, SCERR_HIGHT };
	settextcolor(WHITE);
	settextstyle(50, 0, "宋体");
	char txt[64];
	//snprintf(txt, 64, "第%d关: %s", map_level.level_id, map_level.mapName.c_str());
	//drawtext(txt, &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	stringstream ret;
	ret << "第" << map_level.level_id << "关: " << map_level.mapName;
	outtextxy(260, 30, ret.str().c_str());
}

//改变小人所在的地图
void changeMap(int x,int y,enum _PROPS pro){
	map[x][y] = pro;
	putimage(100 + y * 61, 120 +x * 61, &image_I[pro]);
}
//控制小人方向
void gameControl(enum _DIRECTE dir) {
	nextMan = man;
	boxNext = man;
	switch (dir)
	{
	case UP: nextMan.x--;  boxNext.x = man.x - 2; 
		break; 
	case LEFT:  nextMan.y--;  boxNext.y = man.y - 2;
		break;
	case RIGHT:  nextMan.y++;  boxNext.y = man.y + 2;
		break;
	case DOWN:  nextMan.x++;  boxNext.x = man.x + 2;  
		break;
	}		
}
//更新地图
void updataMap() {
	//定义一个宏: inli(start)  nextMan.x>=0 && nextMan.x<9 && nextMan.y>=0&&nextMan.x<12

   if (INLI(nextMan)&& map[nextMan.x][nextMan.y] == FLOOR){
		changeMap(nextMan.x, nextMan.y, MAN);   //将下一步填充为man
		changeMap(man.x, man.y, FLOOR);         //将原来的位置变成floor(地板)
		man = nextMan;                          //更新小人的位置
	}
	else if (INLI(nextMan) && map[nextMan.x][nextMan.y] == BOX) {      //当下一步是箱子时
		if (INLI(boxNext) && map[boxNext.x][boxNext.y] == BOX_DES) {   //判断箱子的下一步是不是箱子目的地
			changeMap(boxNext.x, boxNext.y, HIT);                      //将目的地更新为箱子
			changeMap(nextMan.x, nextMan.y, MAN);                      //将当前位置的下一个位置更新成man
			changeMap(man.x, man.y, FLOOR);                            //将当前位置更新成地板
			man = nextMan;                                             //跟新小人的位置
			Sleep(500);  //500毫秒
		}
		else if (INLI(boxNext) && map[boxNext.x][boxNext.y] == FLOOR) {   //箱子的下一步是地板
			changeMap(boxNext.x, boxNext.y, BOX);                        //将地板更新成箱子
			changeMap(nextMan.x, nextMan.y, MAN);						 //将当前面位置的下一步更新成man
			changeMap(man.x, man.y, FLOOR);								 //将当前位置更新成地板
			man = nextMan;                                               //跟新小人的位置
		}
	}
}
//判断箱子是否全部到达目的地
bool isGameOver() {
	for (int i = 0; i < 9; i++) {
		for (int j = 0; j < 12; j++) {
			if (map[i][j] == BOX_DES) {
				return false;
			}
		}
	}
	return true;
}
//游戏结束
void over(IMAGE *image) {
	putimage(0, 0, image);
	RECT rec = { 0, 0, SCREE_WIDTH, SCERR_HIGHT };
	settextcolor(WHITE);
	settextstyle(50, 0, "宋体");
	drawtext("恭喜闯关成功,请按任意键开始下一关!", &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	::system("pause");
	cleardevice();    //清屏
}
void level_over(IMAGE* image) {
	putimage(0, 0, image);
	RECT rec = { 0, 0, SCREE_WIDTH, SCERR_HIGHT };
	settextcolor(WHITE);
	settextstyle(50, 0, "宋体");
	drawtext("已经没有下一关了,请按任意键结束游戏!", &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}

int main(void) {
	//登录验证
	userInfor user;    //用户信息
	mapInfo  level_map;    //地图信息
	if (login(user)) {
		printf("登录成功\n");
	}
	else {
		printf("登录失败!");
		exit(-1);
	}
	bool quit = false;     //循环控制变量
	do {
	//获取地图信息
	if (fetch_map_info(user.level_id, level_map)) {
		printf("获取地图成功\n");
	}
	else {
		cout << "获取地图失败!" << endl;
		exit(-1);
	}
	//地图转换
	::system("pause");
	//int map[LINE][COLUMN] = { 0 };
	if (transfo_map_db2arry(level_map, map)) {
		printf("地图转换成功\n");
	}	else
	{
		printf("地图转换失败\n");
		exit(-1);
	}
	::system("pause");
	init(level_map);   //初始化
	for (int i = 0; i < level_map.row ; i++) {
		for (int j = 0; j < level_map.column; j++) {
			if (map[i][j] == MAN) {
				man.x = i;
				man.y = j;
			}
			putimage(100+j * 61,120+ i * 61, &image_I[map[i][j]]);
		}
	}
		do {
			if (_kbhit()) {      //检测是否敲击键盘,有就返回true
				char ch = _getch();   //不回显
				if (ch == KEY_UP) {
					gameControl(UP);
					updataMap();
				}
				else if (ch == KEY_DOWN) {
					gameControl(DOWN);
					updataMap();
				}
				else if (ch == KEY_LEFT) {
					gameControl(LEFT);
					updataMap();
				}
				else if (ch == KEY_RIGHT) {
					gameControl(RIGHT);
					updataMap();
				}
				else if (ch == KEY_QUIT) {
					quit = true;
				}
				if (isGameOver()) {
					if (level_map.next_level < 1) {
						level_over(&image_B);
						::system("pause");
						break;
						quit = false;
					}
					//如果还有下一关
					over(&image_B);
					//跳转下一个关
					if (update_next_level(user, level_map.next_level)) {
						user.level_id = level_map.next_level;
						break;      //跳出内循环,加入外循环
					}
					else {
						printf("关卡跳转失败\n");
						::system("pause");
						exit(-1);
					}
				}
			}
		} while (1);
	} while (quit == false);
	
	closegraph();
	return 0;
}
运行结果显示(我这里是加入了调试信息的)

登录界面

详细介绍
关卡

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

零二年的冬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值