一个简单的推箱子游戏 ,还有完善优化的空间,可打包带走
目录:
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 表的设计(关卡的信息,地图的信息)
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;
}
运行结果显示(我这里是加入了调试信息的)