项目实战——推箱子小游戏(引入数据库)
创始人
2024-03-14 02:33:47
0

数据库表设计

用户表

表名

Users

字段名

类型

是否为空

默认值

主、外键

备注

id

int(11)

NOT

1,自增长

PK

用户id

username

varchar(64)

NOT

用户名:英文字符、数字和特殊符号的组合

password

varchar(32)

NOT

密码:英文字符、数字和特殊符号的组合,8-16位

level_id

int

1

当前关卡,关联Levels表中的id

关卡表

表名

Levels

字段名

类型

是否为空

默认值

主、外键

备注

id

int

NOT

1

PK

游戏关卡序号,从1开始

name

varchar(64)

NOT

地图名称

map_row

int

NOT

地图二位组的总行数

map_column

int

NOT

地图二维组的总列数

map_data

varchar(4096)

NOT

地图数据,二维数组对应的行列式,多行以|分开,列以逗号分隔,最大接近支持45x45的地图

next_level_id

int

0

下一关的关卡id,0代表通关

mysql>  create database box_man;   #创建数据库box_man                                                                                                                       

mysql>                                                                                    

mysql>  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                                                             

         );                                                                                  

mysql>  create table levels(         #创建关卡表                                                         

             id int NOT NULL PRIMARY KEY default 1,                                                 

             name varchar(64) NOT NULL UNIQUE,                                                   

             map_row int  NOT NULL,                                                              

             map_column   int NOT NULL,                                                           

             map_data     varchar(4096) NOT NULL,                                       

             next_level_id  int default 0                                                    

         );                                                                                  

mysql>   insert into users values(1000, 'martin', md5('123456qweQWE'), 1);                    

mysql>   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);                                                                                      

                                                                                                                 

代码优化

登录认证

database.cpp

#include "database.h"

#include

#include

#define DB_NAME  "box_man"

#define DB_HOST  "127.0.0.1"

#define DB_PORT  3306

#define DB_USER  "root"

#define DB_USER_PASSWD  "123456qweQWE"

static bool connect_db(MYSQL& mysql);

/***************************************************

 *功能:通过用户名和密码从数据库获取用户信息

 *输入:

 *      user - 用户信息结构体

 *

 *返回值:

 *       获取成功返回true, 失败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]);

    printf("userid: %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;

}

database.h

#pragma once

#include

using namespace std;

//用户信息

typedef struct _userinfo{

    int id;            //用户id

    string username;   //用户名

    string passwd;     //密码

    int level_id;      //关卡id

}userinfo;

bool fetch_user_info(userinfo &user);

boxman.cpp

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;

}

获取关卡

//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

 *返回值:

 *       获取成功返回true, 失败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[5]);

    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);

   

    //释放结果集

    mysql_free_result(res);

    //关闭数据库

    mysql_close(&mysql);

    return  true;

}

地图适配

//database.h

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

//database.cpp

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("地图数据解析出错,终止!\n");

            return false;

        }

        printf("\n");

        row++;

        if (row >= level.map_row) {

            break;

        }

        start = end + 1;

    } while (1 == 1);

    if (row < level.map_row) {

        printf("地图行数少于设定, %d(need: %d),终止!\n", row, level.map_row);

        return false;

    }

    return true;

}

//boxman.cpp

//把数据库中的地图数据转换到map 中

  ret = transform_map_db2array(level, map);

下一关跳转

//database.h

bool update_user_level(userinfo& user, int next_level_id);

//database.cpp

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;

    }

    //关闭数据库

    mysql_close(&mysql);

    return true;

}

//boxman.cpp

//...............前面省略N行代码....................

void gameNextScene(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();

}

//...............中间省略N行代码....................

if (isGameOver()) {

    if (level.next_level < 1) {

        gameOverScene(&bg_img);

        quit = true;

        break;

    }

    gameNextScene(&bg_img);

    //更新用户下一关的关卡信息

    if (update_user_level(user, level.next_level)) {

        user.level_id = level.next_level;

    }

                   

    break;

    //quit = true;

}

以下是完整代码实现:

box_man.h

#pragma once
#include #define KEY_UP 'w'
#define KEY_DOWN 's'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define KEY_QUIT 'q'#define START_X 100		//x轴偏移
#define START_Y 150		//y轴偏移#define WEIGHT 960		//舞台长
#define HEIGHT 768		//舞台宽#define RATIO 61#define isValid(pos) pos.x>=0 && pos.x=0 && pos.y

box_man.cpp

#include 
#include 
#include 
#include 
#include "box_man.h"
#include "database.h"using namespace std;int map[MAP_X][MAP_Y] = { 0 };int hold = 0;IMAGE pic[ALL];		//资源图片bool fetch_user_info(userinfo& user);
bool fetch_level_info(levelinfo& level, int level_id);
bool transform_map_dbtoarry(levelinfo& level, int map[MAP_X][MAP_Y]);
bool update_user_level(userinfo& user, int next_level_id);void changeMap(struct _POS* pos, enum _RES prop)
{map[pos->x][pos->y] = prop;putimage(START_X + pos->y * RATIO, START_Y + pos->x * RATIO, &pic[prop]);
}struct _POS man;bool isGameOver()
{int i, j;if (hold == 1){return false;}for (i = 0; i < MAP_X; i++){for (j = 0; j < MAP_Y; j++){if (map[i][j] == BOX_DES){return false;}}}return true;
}void gameOverScene(IMAGE* bg)
{putimage(0, 0, bg);settextcolor(WHITE);RECT rec = { 0,0,WEIGHT,HEIGHT };settextstyle(20, 0, _T("宋体"));drawtext(_T("恭喜您~\n通关了!再见"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}void gameNextScene(IMAGE* bg)
{putimage(0, 0, bg);settextcolor(WHITE);RECT rec = { 0,0,WEIGHT,HEIGHT };settextstyle(20, 0, _T("宋体"));drawtext(_T("恭喜您~\n此关挑战成功,任意键跳转到下一关!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);::system("pause");cleardevice();
}void gameCotrol(enum _DIRECT 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)	//小人前是地板{if (hold == 0){changeMap(&next_pos, MAN);changeMap(&man, FLOOR);man = next_pos;}else{changeMap(&next_pos, MAN);changeMap(&man, BOX_DES);man = next_pos;hold = 0;}}else if (isValid(next_pos) && map[next_pos.x][next_pos.y] == BOX_DES)	//小人前是箱子目的地{changeMap(&next_pos, MAN);changeMap(&man, FLOOR);man = next_pos;hold = 1;}else if (isValid(next_pos) && map[next_pos.x][next_pos.y] == BOX)		//小人前是箱子{if (isValid(next_next_pos) && map[next_next_pos.x][next_next_pos.y] == BOX_DES){changeMap(&next_next_pos, HIT);changeMap(&next_pos, MAN);changeMap(&man, FLOOR);man = next_pos;}else if (isValid(next_next_pos) && map[next_next_pos.x][next_next_pos.y] == FLOOR)		//箱子前面是地板{if (hold == 1){changeMap(&next_next_pos, BOX);changeMap(&next_pos, MAN);changeMap(&man, BOX_DES);man = next_pos;hold = 0;}else{changeMap(&next_next_pos, BOX);changeMap(&next_pos, MAN);changeMap(&man, FLOOR);man = next_pos;}}}else if (isValid(next_next_pos) && map[next_pos.x][next_pos.y] == HIT)		//小人前方箱子和目的地重合{changeMap(&next_next_pos, BOX);changeMap(&next_pos, MAN);changeMap(&man, FLOOR);man = next_pos;hold = 1;}}bool login(userinfo& user)
{int times = 0; //登录次数int ret = false;do{cout << "请输入用户名:";cin >> user.username;cout << "请输入登录密码:";cin >> user.passwd;if (times > MAX_REIRY_TIMES){cout << "输入用户名或密码错误过多!" << endl;return false;}ret = fetch_user_info(user);} while (ret == false);return ret;
}void init_game_graph(IMAGE &bg_img)
{initgraph(WEIGHT, HEIGHT);loadimage(&bg_img, L"blackground.bmp", WEIGHT, HEIGHT, true);putimage(0, 0, &bg_img);loadimage(&pic[WALL], L"wall_right.bmp", RATIO, RATIO, true);loadimage(&pic[FLOOR], L"floor.bmp", RATIO, RATIO, true);loadimage(&pic[BOX_DES], L"des.bmp", RATIO, RATIO, true);loadimage(&pic[MAN], L"man.bmp", RATIO, RATIO, true);loadimage(&pic[BOX], L"box.bmp", RATIO, RATIO, true);loadimage(&pic[HIT], L"box.bmp", RATIO, RATIO, true);
}int main(void)
{userinfo user;levelinfo level;bool ret = false;IMAGE bg_img;if (!login(user)){cout << "登录失败" << endl;}else{cout << "登录成功!请开始你的表演" << endl;}//初始化游戏舞台init_game_graph(bg_img);::system("pause");bool quit = false;do{hold = 0; //每次到新的一关人物肯定不在目的地//根据用户所在的关卡id获取关卡数据ret = fetch_level_info(level, user.level_id);if (!ret){cout << "获取关卡数据失败,请重试!" << endl;::system("pause");exit(-1);}else{cout << "登录成功,用户id:" << user.id << ",您所在的关卡是:" << user.level_id << ",请开始您的表演!" << endl;}//把数据库中的地图数据转换到map中ret = transform_map_dbtoarry(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] == MAN){man.x = i;man.y = j;}putimage(START_X + j * RATIO, START_Y + i * RATIO, &pic[map[i][j]]);}}do{if (_kbhit()){char ch = _getch();if (ch == KEY_UP){gameCotrol(UP);}else if (ch == KEY_DOWN){gameCotrol(DOWN);}else if (ch == KEY_LEFT){gameCotrol(LEFT);}else if (ch == KEY_RIGHT){gameCotrol(RIGHT);}else if (ch == KEY_QUIT){quit = false;}if (isGameOver()){if (level.next_level < 1){gameOverScene(&bg_img);quit = true;break;}gameNextScene(&bg_img);if (update_user_level(user, level.next_level)){user.level_id = level.next_level;putimage(0, 0, &bg_img); //显示图片}break;}}Sleep(100);} while (!quit);} while (quit == false);::system("pause");closegraph();return 0;
}

database.cpp

#include 
#include 
#include "database.h"using namespace std;#define DB_NAME "box_man"
#define DB_HOST "127.0.0.1"
#define DB_POST 3306
#define DB_USER "root"
#define DB_USER_PASSWD "rfly"//static bool connect_db(MYSQL& mysql);bool connect_db(MYSQL& mysql)
{mysql_init(&mysql);mysql_options(&mysql,MYSQL_SET_CHARSET_NAME,"gbk");if (mysql_real_connect(&mysql, DB_HOST, DB_USER, DB_USER_PASSWD, DB_NAME, DB_POST, NULL, 0) == NULL){printf("数据库连接失败,错误原因:%s\n", mysql_error(&mysql));return false;}return true;
}/****************************
*功能:通过用户名和密码获取用户信息
*输入:
*		user - 用户信息结构体
* 
* 返回值:
*		获取成功返回true
*		获取失败返回false
*****************************/bool fetch_user_info(userinfo& user)
{MYSQL mysql;MYSQL_RES* res; //查询结果集MYSQL_ROW row;  //查询记录行char a[256];      //用来保存字符串查询语句int ret = false;//1.连接数据库if (connect_db(mysql) == false) {cout << "数据库连接失败" << endl;return false;}snprintf(a, 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, a); //等于0,表示查询成功if (ret){printf("数据库查询出错,%s 错误原因:%s\n", a, mysql_error(&mysql));mysql_close(&mysql);return false;}res = mysql_store_result(&mysql);row = mysql_fetch_row(res);if (row == NULL){cout <<"查询到空数据,失败!" << endl;mysql_free_result(res);mysql_close(&mysql);return false;}user.id = atoi(row[0]);user.level_id = atoi(row[1]);cout << "用户名id:" << user.id << "\t" << "用户关卡:" << user.level_id;mysql_free_result(res);mysql_close(&mysql);return true;
}bool fetch_level_info(levelinfo& level, int level_id)
{MYSQL mysql;MYSQL_RES* res; //查询结果集MYSQL_ROW row;  //查询记录行char sql[256];bool ret = false;if (connect_db(mysql) == false){cout << "数据库连接失败" << endl;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);if (ret){printf("数据库查询出错,%s 错误原因:%s\n", sql, mysql_error(&mysql));mysql_close(&mysql);return false;}res = mysql_store_result(&mysql);row = mysql_fetch_row(res);if (row == NULL){cout << "查询到空数据,失败!" << endl;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]);mysql_free_result(res);mysql_close(&mysql);return true;
}bool transform_map_dbtoarry(levelinfo& level, int map[MAP_X][MAP_Y])
{//1.合法性检查if (level.map_row > MAP_X || level.map_column > MAP_Y){cout << "给定的地图太大!" << endl;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);//find找到最后一行,没有'|'返回-1if (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("地图解析错误!\n");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),终止!\n", row, level.map_row);return false;}return true;
}/******************************
* 功能:更新用户的关卡id
* 输入:user	-将下一关的id赋值给user
* 
*		next_level_id 下一关id
*******************************/
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_idsnprintf(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;
}

database.h

#pragma once 
#include 
#include "box_man.h"
#include using namespace std;typedef struct _userinfo
{int id;string username;string passwd;int level_id;
}userinfo;typedef struct _levelinfo
{int id;	//关卡的idstring name; //关卡的名字int map_row; //地图的总行数int map_column; //地图的总列数string map_data; //二维地图的数据int next_level; //下一关卡的id
}levelinfo;

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...