项目实战——推箱子小游戏(引入数据库)
数据库表设计
用户表
表名 | 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 <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 "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 <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); |
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 <graphics.h>
#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<MAP_X && pos.y>=0 && pos.y<MAP_Y //判断小人在地图行走后是否有效
#define MAX_REIRY_TIMES 4
#define MAP_X 48
#define MAP_Y 12
enum _RES
{
WALL,
FLOOR,
BOX_DES,
MAN,
BOX,
HIT,
ALL
};
enum _DIRECT
{
UP,
DOWN,
LEFT,
RIGHT
};
struct _POS //小人坐标
{
int x;
int y;
};
box_man.cpp
#include <iostream>
#include <Windows.h>
#include <string>
#include <conio.h>
#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 <mysql.h>
#include <stdio.h>
#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找到最后一行,没有'|'返回-1
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);
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_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;
}
database.h
#pragma once
#include <string>
#include "box_man.h"
#include <iostream>
using namespace std;
typedef struct _userinfo
{
int id;
string username;
string passwd;
int level_id;
}userinfo;
typedef struct _levelinfo
{
int id; //关卡的id
string name; //关卡的名字
int map_row; //地图的总行数
int map_column; //地图的总列数
string map_data; //二维地图的数据
int next_level; //下一关卡的id
}levelinfo;
posted on 2022-12-03 00:15 会飞的鱼-blog 阅读(17) 评论(0) 编辑 收藏 举报 来源
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现