推箱子(数据库篇)
Posted 独爱莫宝的三岁
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了推箱子(数据库篇)相关的知识,希望对你有一定的参考价值。
引言:
推箱子,经典小游戏,带我们回到小时候,来一波“爷童回”。可以在数据库表中设计不一样的关卡,自己设计地图,学习的同时又能体验游戏的乐趣~
实现:
采用C++语言,使用visual studio工具,与数据库进行相连,实现用户登录与获取关卡信息功能。
用户登录后可获取当前已到达哪一关卡,接着从那一关开始继续推箱子。同时加载地图也是通过读取关卡表,支持在表中添加多种不同地图,提升游戏体验。
效果:
源代码:
目录:
一、数据库表设计
二、连接数据库与用户登录
三、初始化游戏界面
四、获取并加载地图数据
五、操作游戏
六、跳转至下一关或结束游戏
一 、数据库表设计
1.1 用户表设计
用户表用来存用户的信息,包括用户id,用户名,密码,关卡id。
字段名 | 类型 | 是否为空 | 键 | 备注 |
---|---|---|---|---|
id | int | 否 | 主键 | 自动增长 |
username | varchar(64) | 否 | 唯一键 | 用户名(登录时使用) |
password | varchar(32) | 否 | NA | 密码(用md5加密) |
level_id | int | 否 | NA | 保存用户所在的关卡 |
用户表里中保存的关卡id(预设为1表示第一关),当用户下次登录时,从上次玩到的关卡开始继续游戏。
创建语句:
CREATE TABLE user (
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
username varchar(64) NOT NULL UNIQUE,
password varchar(32) NOT NULL,
level_id int DEFAULT '1');
1.2 关卡表设计
关卡表用来存放地图信息,包括地图id,地图名字,地图行数,地图列数,地图数据,下一关地图id。
字段名 | 类型 | 是否为空 | 键 | 备注 |
---|---|---|---|---|
id | int | 否 | 主键 | 地图的id |
name | varchar(64) | 否 | NA | 地图名字 |
map_row | int | 否 | NA | 地图行数 |
map_column | int | 否 | NA | 地图列数 |
map_data | varchar(32) | 否 | NA | 地图数据 |
next_level_id | int | 否 | NA | 下一关的地图id |
其中,通过读取表中的地图数据来加载地图,next_level_id存下一关的地图id,没有下一关存0。
创建语句:
CREATE TABLE levels(
id int NOT NULL PRIMARY KEY,
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');
二 、连接数据库与用户登录
2.1 连接数据库
创建了一个database文件,用来存放与数据库连接的方法,和需要与数据库交互的一些方法,database.h中为方法的声明,database.cpp中为方法的定义。
以下为连接数据库的方法,只写在database.cpp文件中:
#define DB_HOST "127.0.0.1"
#define DB_USER "root"
#define DB_PASSWD "123456!"
#define DB_NAME "push_box"
#define DB_PORT 3308
/**********************************************
*功能:连接数据库
*输入:
mysql - 句柄,数据库连接的一些需要使用的方法中需用到
*返回值:
连接成功返回true,连接失败返回false
***********************************************/
bool connectDB(MYSQL &mysql) {
mysql_init(&mysql); //初始化句柄
//设置字符编码
mysql_options(&mysql,MYSQL_SET_CHARSET_NAME,"gbk");
//连接数据库
if (mysql_real_connect(&mysql, DB_HOST, DB_USER, DB_PASSWD, DB_NAME, DB_PORT, NULL, 0) == NULL) {
printf("数据库连接失败,原因:%s",mysql_error(&mysql));
return false;
}
return true;
}
其中, DB_HOST, DB_USER, DB_PASSWD, DB_NAME, DB_PORT都使用了宏定义。
2.2 实现用户登录
在database文件中通过getUserInfo方法,用来获取用户信息并进行用户登录认证。
database.h:
#include "box.h"
bool getUserInfo(useInfo &user);
database.cpp:
#include <mysql.h>
#include <stdio.h>
#include "database.h"
//省略上面已写的宏定义与conactDB方法
/**********************************************
*功能:获取用户信息
*输入:
user - 保存用户信息的结构体变量
*返回值:
获取成功返回true,获取失败返回false
***********************************************/
bool getUserInfo(useInfo &user) {
MYSQL mysql;
MYSQL_RES *result; //定义结果集
MYSQL_ROW row; //记录结构体
char sql[256]; //将sql语句存入其中
int ret;
//连接到数据库
if (!connectDB(mysql)) {
return false;
}
snprintf(sql,256,"select id,level_id from user where username='%s' and password=md5('%s');", user.name.c_str(), user.pwd.c_str());
ret=mysql_query(&mysql, sql); //成功返回0
if (ret) {
printf("查询数据失败,%s,原因:%s",sql,mysql_error(&mysql));
mysql_close(&mysql); //关闭数据库
return false;
}
//获取结果
result = mysql_store_result(&mysql);
row = mysql_fetch_row(result); //结果读出
if (row == NULL) { //没有查询到数据
mysql_free_result(result); //释放结果集
mysql_close(&mysql); //关闭数据库
return false;
}
user.id = atoi(row[0]); //atoi将字符串转为int
user.levelID = atoi(row[1]);
mysql_free_result(result); //释放结果集
mysql_close(&mysql); //关闭数据库
return true;
}
其中userInfo为结构体,存放在box.h头文件中,如下所写:
typedef struct _useInfo{
int id; //用户id
string name; //用户名
string pwd; //密码
int levelID; //关卡id
}useInfo;
从数据库中获取的用户数据会保存到结构体对象中。
同时main方法中的写法如下:
#include <iostream>
#include <string>
#include <Windows.h>
#include <graphics.h>
#include <conio.h>
#include <easyx.h>
#include <mysql.h>
#include "box.h"
#include "database.h"
useInfo user; //创建user结构体对象
//用户登录方法,有四次输入密码的机会
bool login() {
// useInfo user;
int times = 0;
bool ret = false;
do {
times++;
cout << "请输入用户名:";
cin >> user.name;
cout << "请输入密码:";
cin >> user.pwd;
ret = getUserInfo(user);
if (times > 4) {
break;
}
if (ret == false) {
cout << "请重新输入账号/密码!" << endl;
}
} while (!ret);
return ret;
}
int main(){
if (login() == false) {
exit(-1);
}
return 0;
}
三 、初始化游戏界面
在main.cpp文件中,创建了initGame方法,用来初始化窗口大小,加载人、箱子等图片。
更新main.cpp:
#include <iostream>
#include <string>
#include <Windows.h>
#include <graphics.h>
#include <conio.h>
#include <easyx.h>
#include <mysql.h>
#include "box.h"
#include "database.h"
useInfo user; //创建user结构体对象
//創建數組來存放不同種類的圖片
IMAGE img[ALL];
IMAGE bg_img; //創建圖片對象
//用户登录方法,有四次输入密码的机会
//省略
//初始化游戏界面
void initGame() {
// cleardevice();
initgraph(BG_WIDTH, BG_HEIGTH); //初始化窗口大小
loadimage(&bg_img, _T("blackground.bmp"), BG_WIDTH, BG_HEIGTH, true); //加載背景圖片
putimage(0, 0, &bg_img); //把圖片放到窗口上
//加載墻,人等圖片
loadimage(&img[WALL], _T("wall_right.bmp"), IMG_SIZE, IMG_SIZE, true);
loadimage(&img[FLOOER], _T("floor.bmp"), IMG_SIZE, IMG_SIZE, true);
loadimage(&img[BOX_DEC], _T("des.bmp"), IMG_SIZE, IMG_SIZE, true);
loadimage(&img[MAN], _T("man.bmp"), IMG_SIZE, IMG_SIZE, true);
loadimage(&img[BOX], _T("box.bmp"), IMG_SIZE, IMG_SIZE, true);
loadimage(&img[HIT], _T("box.bmp"), IMG_SIZE, IMG_SIZE, true);
}
int main(){
if (login() == false) {
exit(-1);
}
initGame();
return 0;
}
initGame方法中的窗口大小为宏定义,图片数组中墙、地板等为枚举类型,都定义在box.h文件中:
#pragma once
#include <string>
using namespace std;
#define IMG_SIZE 61
#define BG_WIDTH 800
#define BG_HEIGTH 640
//枚舉道具
enum PROP {
WALL,
FLOOER,
BOX_DEC,
MAN,
BOX,
HIT,
ALL
};
四 、获取并加载地图数据
4.1 获取地图数据
在初始化界面后,需要从数据库中读取地图数据。同样也是在database文件中,通过getLevelInfo来获取关卡信息,如下所写:
//省略头文件,宏定义,conactDB方法和getUserInfo方法
/**********************************************
*功能:获取关卡信息
*输入:
level - 保存关卡信息的结构体变量
level_id - 传入的关卡id,根据id获取关卡信息
*返回值:
获取成功返回true,获取失败返回false
***********************************************/
bool getLevelInfo(levelInfo &level,int level_id) {
MYSQL mysql;
MYSQL_RES *result; //定义结果集
MYSQL_ROW row; //记录结构体
char sql[256]; //将sql语句存入其中
int ret;
//连接到数据库
if (!connectDB(mysql)) {
return false;
}
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", sql, mysql_error(&mysql));
mysql_close(&mysql); //关闭数据库
return false;
}
//获取结果
result = mysql_store_result(&mysql);
row = mysql_fetch_row(result); //结果读出
if (row == NULL) { //没有查询到数据
mysql_free_result(result); //释放结果集
mysql_close(&mysql); //关闭数据库
return false;
}
level.name = row[0]; //atoi将字符串转为int
level.map_row = atoi(row[1]);
level.map_column = atoi(row[2]);
level.map_data = row[3];
level.next_level = atoi(row[4]);
mysql_free_result(result); //释放结果集
mysql_close(&mysql); //关闭数据库
return true;
}
其中levelInfo为结构体,存放在box.h头文件中,如下所写:
typedef struct _levelInfo {
int id; //关卡id
string name; //关卡名字
int map_row; //地图行数
int map_column; //地图列数
string map_data; //地图数据
int next_level; //下一关id
}levelInfo;
从数据库中获取的地图数据会保存到结构体对象中。
更新main.cpp中代码:
#include <iostream>
#include <string>
#include <Windows.h>
#include <graphics.h>
#include <conio.h>
#include <easyx.h>
#include <mysql.h>
#include "box.h"
#include "database.h"
useInfo user; //创建user结构体对象
levelInfo level; //创建level结构体对象
//創建數組來存放不同種類的圖片
IMAGE img[ALL];
IMAGE bg_img; //創建圖片對象
//用户登录方法,有四次输入密码的机会
//省略
//初始化游戏界面
//省略
int main(){
if (login() == false) {
printf("登录失败\\n");
exit(-1);
}
initGame();
if (!getLevelInfo(level, user.levelID)) { // 获取地图数据
printf("获取地图数据失败\\n");
exit(-2);
}
return 0;
}
4.2 加载地图数据
读取出地图数据后,需要将数据解析出来,并保存到地图数组map中。在database中使用loadLevel方法:
/**********************************************
*功能:加载关卡信息
*输入:
level - 保存关卡信息的结构体变量
*返回值:
获取成功返回true,获取失败返回false
***********************************************/
bool loadLevel(levelInfo &level, int map[MAP_LINE][MAP_COLUMN]) {
if (level.map_row > MAP_LINE || level.map_column > MAP_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); //从start开始找(即从字符串第一个字符开始),直到找到|符号,返回|所在位置的下标,没找到返回-1
//当找到最后一串时,后面没有|符号了,end一定是返回-1,所以需要手动将end指向字符串结束符
if (end < 0) {
end = level.map_data.length();
}
string line = level.map_data.substr(start, end - start); //把字符串取出来
printf("line: %s\\n", line.c_str());
char *next_data = NULL;
char *item = strtok_s((char *)line.c_str(), ",", &next_data); //根据逗号解析字符串,返回
column = 0; //给列清零重新开始计数
while (item && column<level.map_column) { //一直解析到字符串结束
printf("*item: %s\\n", item);
map[row][column] = atoi(item);
column++;
item = strtok_s(NULL, ",", &next_data); //指向字符串中的下一个字符
}
printf("\\n");
if (column < level.map_column) {
printf("地图数据有误,请重新设计\\n");
return false;
}
row++;
if (row >= level.map_row) { //读完每一行后退出
break;
}
start = end + 1; //指向下一串字符
} while (1);
if (row < level.map_row) {
printf("地图数据有误,请重新设计\\n");
return false;
}
return true;
}
更新main.cpp文件:
#include <iostream>
#include <string>
#include <Windows.h>
#以上是关于推箱子(数据库篇)的主要内容,如果未能解决你的问题,请参考以下文章